From 19f993e06184e25c6e8816194836c1d0d0fb1762 Mon Sep 17 00:00:00 2001 From: takahirom Date: Fri, 6 Dec 2024 22:22:36 +0900 Subject: [PATCH] Add roborazzi.compare.output.dir gradle.properties --- .../takahirom/roborazzi/RoborazziOptions.kt | 6 +- .../roborazzi/RoborazziProperties.kt | 5 ++ .../roborazzi/RoborazziGradleProject.kt | 21 +++++ .../roborazzi/RoborazziGradleProjectTest.kt | 82 ++++++++++++++++++- .../takahirom/roborazzi/RoborazziPlugin.kt | 65 +++++++++++++-- 5 files changed, 166 insertions(+), 13 deletions(-) diff --git a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt index 282bc0fbc..91d05a5a5 100644 --- a/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt +++ b/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt @@ -116,7 +116,7 @@ data class RoborazziOptions( } data class CompareOptions( - val outputDirectoryPath: String = roborazziSystemPropertyOutputDirectory(), + val outputDirectoryPath: String = roborazziSystemPropertyCompareOutputDirectory(), val imageComparator: ImageComparator = DefaultImageComparator, val comparisonStyle: ComparisonStyle = ComparisonStyle.Grid(), val aiAssertionOptions: AiAssertionOptions? = null, @@ -127,7 +127,7 @@ data class RoborazziOptions( imageComparator: ImageComparator = DefaultImageComparator, resultValidator: (result: ImageComparator.ComparisonResult) -> Boolean = DefaultResultValidator, ): this( - outputDirectoryPath = roborazziSystemPropertyOutputDirectory(), + outputDirectoryPath = roborazziSystemPropertyCompareOutputDirectory(), imageComparator = imageComparator, resultValidator = resultValidator, ) @@ -145,7 +145,7 @@ data class RoborazziOptions( } constructor( - outputDirectoryPath: String = roborazziSystemPropertyOutputDirectory(), + outputDirectoryPath: String = roborazziSystemPropertyCompareOutputDirectory(), /** * This value determines the threshold of pixel change at which the diff image is output or not. * The value should be between 0 and 1 diff --git a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoborazziProperties.kt b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoborazziProperties.kt index b3874fb36..a4116ba1a 100644 --- a/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoborazziProperties.kt +++ b/include-build/roborazzi-core/src/commonMain/kotlin/com/github/takahirom/roborazzi/RoborazziProperties.kt @@ -8,6 +8,11 @@ fun roborazziSystemPropertyOutputDirectory(): String { return getSystemProperty("roborazzi.output.dir", DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH) } +@ExperimentalRoborazziApi +fun roborazziSystemPropertyCompareOutputDirectory(): String { + return getSystemProperty("roborazzi.compare.output.dir", DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH) +} + @ExperimentalRoborazziApi fun roborazziSystemPropertyImageExtension(): String { return getSystemProperty("roborazzi.record.image.extension", "png") diff --git a/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProject.kt b/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProject.kt index 7a107a9f4..b68a15ab4 100644 --- a/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProject.kt +++ b/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProject.kt @@ -206,6 +206,7 @@ class AppModule(val rootProject: RoborazziGradleRootProject, val testProjectDir: private val PATH = "app/build.gradle.kts" var removeOutputDirBeforeTestTypeTask = false var customOutputDirPath: String? = null + var customCompareOutputDirPath: String? = null init { addIncludeBuild() @@ -312,6 +313,19 @@ dependencies { roborazzi { outputDir.set(file("$customOutputDirPath")) } + + """.trimIndent() + ) + } + if (customCompareOutputDirPath != null) { + buildFile.appendText( + """ + roborazzi { + compare { + outputDir.set(file("$customCompareOutputDirPath")) + } + } + """.trimIndent() ) } @@ -468,6 +482,13 @@ class MainActivity : ComponentActivity() { file.appendText("\nroborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDirectory") } + fun removeCompareOutputDir() { + if(!testProjectDir.root.resolve("app/build/custom_compare_outputDirectoryPath") + .deleteRecursively()){ + throw IllegalStateException("Failed to delete custom_compare_outputDirectoryPath") + } + } + fun addRuleTest() { val file = testProjectDir.root.resolve("app/src/test/java/com/github/takahirom/integration_test_project/RoborazziTest.kt") diff --git a/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProjectTest.kt b/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProjectTest.kt index fa1b27bcb..be5346b76 100644 --- a/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProjectTest.kt +++ b/include-build/roborazzi-gradle-plugin/src/integrationTest/java/io/github/takahirom/roborazzi/RoborazziGradleProjectTest.kt @@ -4,6 +4,7 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.rules.TestWatcher +import org.junit.runner.Description /** * Run this test with `cd include-build` and `./gradlew roborazzi-gradle-plugin:check` @@ -20,6 +21,11 @@ class RoborazziGradleProjectTest { override fun starting(description: org.junit.runner.Description?) { println("RoborazziGradleProjectTest.${description?.methodName} started") } + + override fun finished(description: Description?) { + super.finished(description) + println("RoborazziGradleProjectTest.${description?.methodName} finished") + } } private val className = "com.github.takahirom.integration_test_project.RoborazziTest" @@ -146,6 +152,7 @@ class RoborazziGradleProjectTest { RoborazziGradleRootProject(testProjectDir).appModule.apply { val customDirFromGradle = "src/screenshots/roborazzi_customdir_from_gradle" buildGradle.customOutputDirPath = customDirFromGradle + removeRoborazziOutputDir() val output1 = record().output assertNotSkipped(output1) val output2 = record().output @@ -153,8 +160,79 @@ class RoborazziGradleProjectTest { checkResultsSummaryFileExists() checkRecordedFileExists("app/$customDirFromGradle/$className.testCapture.png") - checkRecordedFileNotExists("$$screenshotAndName.testCapture_compare.png") - checkRecordedFileNotExists("$$screenshotAndName.testCapture_actual.png") + checkRecordedFileNotExists("$screenshotAndName.testCapture.png") + checkRecordedFileNotExists("$screenshotAndName.testCapture_compare.png") + checkRecordedFileNotExists("$screenshotAndName.testCapture_actual.png") + checkRecordedFileNotExists("app/$customDirFromGradle/$className.testCapture_compare.png") + + // We should be able to use the cache + changeScreen() + removeRoborazziOutputDir() + compare() + checkRecordedFileExists("$screenshotAndName.testCapture_compare.png") + removeRoborazziOutputDir() + compare() + + checkResultsSummaryFileExists() + checkRecordedFileExists("app/$customDirFromGradle/$className.testCapture.png") + checkRecordedFileExists("$screenshotAndName.testCapture_compare.png") + checkRecordedFileExists("$screenshotAndName.testCapture_actual.png") + checkRecordedFileNotExists("app/$customDirFromGradle/$className.testCapture_compare.png") + } + } + + @Test + fun compareWithCompareOutputDirPathItShouldSavedInTheDirectory() { + RoborazziGradleRootProject(testProjectDir).appModule.apply { + buildGradle.customCompareOutputDirPath = "build/custom_compare_outputDirectoryPath" + + record() + changeScreen() + compare() + + checkResultsSummaryFileExists() + checkRecordedFileExists("$screenshotAndName.testCapture.png") + checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture_compare.png") + checkRecordedFileNotExists("$screenshotAndName.testCapture_compare.png") + + // We should be able to use the cache + removeRoborazziOutputDir() + compare() + removeRoborazziOutputDir() + val buildResult = compare() + assertSkipped(buildResult.output) + + checkResultsSummaryFileExists() + checkRecordedFileExists("$screenshotAndName.testCapture.png") + checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture_compare.png") + checkRecordedFileNotExists("$screenshotAndName.testCapture_compare.png") + } + } + + @Test + fun compareWithBothOutputDirPathItShouldSavedInTheDirectory() { + RoborazziGradleRootProject(testProjectDir).appModule.apply { + buildGradle.customCompareOutputDirPath = "build/custom_compare_outputDirectoryPath" + val customDirFromGradle = "build/custom_compare_outputDirectoryPath" + buildGradle.customOutputDirPath = customDirFromGradle + + record() + changeScreen() + compare() + + checkResultsSummaryFileExists() + checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture.png") + checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture_compare.png") + checkRecordedFileNotExists("app/build/outputs/roborazzi/$className.testCapture_compare.png") + + // We should be able to use the cache + removeCompareOutputDir() + compare() + + checkResultsSummaryFileExists() + checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture.png") + checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture_compare.png") + checkRecordedFileNotExists("app/build/outputs/roborazzi/$className.testCapture_compare.png") } } diff --git a/include-build/roborazzi-gradle-plugin/src/main/java/io/github/takahirom/roborazzi/RoborazziPlugin.kt b/include-build/roborazzi-gradle-plugin/src/main/java/io/github/takahirom/roborazzi/RoborazziPlugin.kt index b8ece69eb..cb75f7951 100644 --- a/include-build/roborazzi-gradle-plugin/src/main/java/io/github/takahirom/roborazzi/RoborazziPlugin.kt +++ b/include-build/roborazzi-gradle-plugin/src/main/java/io/github/takahirom/roborazzi/RoborazziPlugin.kt @@ -14,7 +14,9 @@ import org.gradle.api.DefaultTask import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection import org.gradle.api.file.RegularFile import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property @@ -49,6 +51,15 @@ private const val DEFAULT_TEMP_DIR = "intermediates/roborazzi" open class RoborazziExtension @Inject constructor(objects: ObjectFactory) { val outputDir: DirectoryProperty = objects.directoryProperty() + @ExperimentalRoborazziApi + val compare: RoborazziCompareExtension = + objects.newInstance(RoborazziCompareExtension::class.java) + + @ExperimentalRoborazziApi + fun compare(action: Action) { + action.execute(compare) + } + @ExperimentalRoborazziApi val generateComposePreviewRobolectricTests: GenerateComposePreviewRobolectricTestsExtension = objects.newInstance(GenerateComposePreviewRobolectricTestsExtension::class.java) @@ -59,6 +70,10 @@ open class RoborazziExtension @Inject constructor(objects: ObjectFactory) { } } +open class RoborazziCompareExtension @Inject constructor(objects: ObjectFactory) { + val outputDir: DirectoryProperty = objects.directoryProperty() +} + val KnownImageFileExtensions: Set = setOf("png", "gif", "jpg", "jpeg", "webp") @Suppress("unused") @@ -93,6 +108,8 @@ abstract class RoborazziPlugin : Plugin { // For fixing unexpected skip test val outputDir = extension.outputDir.convention(project.layout.buildDirectory.dir(DEFAULT_OUTPUT_DIR)) + val compareOutputDirProvider = + extension.compare.outputDir.convention(project.layout.buildDirectory.dir(DEFAULT_OUTPUT_DIR)) val testTaskOutputDir: DirectoryProperty = project.objects.directoryProperty() val intermediateDir = testTaskOutputDir.convention(project.layout.buildDirectory.dir(DEFAULT_TEMP_DIR)) @@ -243,11 +260,13 @@ abstract class RoborazziPlugin : Plugin { roborazziProperties["roborazzi.test.verify"] == "true" || roborazziProperties["roborazzi.test.compare"] == "true" } + val isCompareOutputIsNeededProvider = isImageInputUsedProvider val projectAbsolutePathProvider = project.providers.provider { project.projectDir.absolutePath } val outputDirRelativePathFromProjectProvider = outputDir.map { project.relativePath(it) } + val resultDirFileProperty = project.layout.buildDirectory.dir(RoborazziReportConst.resultDirPathFromBuildDir) val resultDirFileTree = @@ -270,11 +289,6 @@ abstract class RoborazziPlugin : Plugin { finalizeTestTask.infoln("Roborazzi: roborazziTestFinalizer.onlyIf doesRoborazziRun $doesRoborazziRun") doesRoborazziRun } - val taskPath = if (project.path == ":") { - ":" - } else { - project.path + ":" - } finalizeTestTask.doLast { val startCopy = System.currentTimeMillis() intermediateDir.get().asFile.mkdirs() @@ -289,12 +303,12 @@ abstract class RoborazziPlugin : Plugin { testTaskProvider .configureEach { test: AbstractTestTask -> val resultsDir = resultDirFileProperty.get().asFile - test.inputs.files( - isImageInputUsedProvider.map { isImageInputUsed -> + val imageInputProvider: Provider = + isImageInputUsedProvider.flatMap { isImageInputUsed -> if (!isImageInputUsed) { // Note: this is not files in outputDir, // but empty input when running in record mode. - outputDir.get().files() + outputDir.map { it.files(/* this means empty files*/) } } else if (restoreOutputDirRoborazziTaskProvider.isPresent) { // Previous outputs are an input when running in compare or verify mode. // However, during record runs the output dir might not exist yet, so we use @@ -317,7 +331,38 @@ abstract class RoborazziPlugin : Plugin { } } } + test.inputs.files( + imageInputProvider ) + test.outputs.dirs( + compareOutputDirProvider.flatMap { compareOutputDir: Directory -> + isCompareOutputIsNeededProvider.flatMap { isImageInputUsed -> + imageInputProvider + .map { imageInput: FileCollection -> + if (isImageInputUsed) { + // If it is not compare or verify, we don't need to output anything for comparison + outputDir.files(/* empty files */) + } else if (imageInput.files.any { + // Check if the compare output directory is the same as the input directory + if (it.isDirectory) { + it.absolutePath == compareOutputDir.asFile.absolutePath + } else { + it.parentFile.absolutePath == compareOutputDir.asFile.absolutePath + } + }) { + outputDir.files(/* empty files */) + } else { + if (!compareOutputDir.asFile.exists()) { + compareOutputDir.asFile.mkdirs() + } + compareOutputDir + } + } + } + }.map { + test.infoln("Roborazzi: Set output dir ${it} to test task") + it + }) test.outputs.dir(intermediateDirForEachVariant.map { test.infoln("Roborazzi: Set output dir $it to test task") it @@ -374,6 +419,10 @@ abstract class RoborazziPlugin : Plugin { // Other properties test.systemProperties["roborazzi.output.dir"] = outputDirRelativePathFromProjectProvider.get() + if (compareOutputDirProvider.isPresent) { + test.systemProperties["roborazzi.compare.output.dir"] = + compareOutputDirProvider.get() + } test.systemProperties["roborazzi.result.dir"] = resultDirRelativePathFromProjectProvider.get() test.systemProperties["roborazzi.project.path"] =