Skip to content

Commit

Permalink
make record task cacheable
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielittner committed Apr 3, 2024
1 parent a2b9bf4 commit 579059d
Show file tree
Hide file tree
Showing 15 changed files with 626 additions and 42 deletions.
5 changes: 5 additions & 0 deletions paparazzi-gradle-plugin/api/paparazzi-gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ public abstract class app/cash/paparazzi/gradle/PrepareResourcesTask : org/gradl
public final fun writeResourcesFile ()V
}

public abstract class app/cash/paparazzi/gradle/RecordPaparazziTask : app/cash/paparazzi/gradle/PaparazziPlugin$PaparazziTask {
public fun <init> ()V
public final fun copy ()V
}

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import org.gradle.api.artifacts.type.ArtifactTypeDefinition
import org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE
import org.gradle.api.file.FileCollection
import org.gradle.api.internal.artifacts.transform.UnzipTransform
import org.gradle.api.logging.LogLevel.LIFECYCLE
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.reporting.ReportingExtension
import org.gradle.api.tasks.Delete
Expand Down Expand Up @@ -111,6 +110,7 @@ public class PaparazziPlugin : Plugin<Project> {

val projectDirectory = project.layout.projectDirectory
val buildDirectory = project.layout.buildDirectory
val failureDir = buildDirectory.dir("paparazzi/failures")
val gradleUserHomeDir = project.gradle.gradleUserHomeDir
val reportOutputDir = project.extensions.getByType(ReportingExtension::class.java).baseDirectory.dir("paparazzi/${variant.name}")

Expand Down Expand Up @@ -194,9 +194,11 @@ public class PaparazziPlugin : Plugin<Project> {
.configure { it.dependsOn(writeResourcesTask) }
}

val recordTaskProvider = project.tasks.register("recordPaparazzi$variantSlug", PaparazziTask::class.java) {
val recordTaskProvider = project.tasks.register("recordPaparazzi$variantSlug", RecordPaparazziTask::class.java) {
it.group = VERIFICATION_GROUP
it.description = "Record golden images for variant '${variant.name}'"
it.reportsDirectory.set(reportOutputDir)
it.goldenSnapshotDirectory.set(snapshotOutputDir)
it.mustRunAfter(deleteSnapshots)
}
recordVariants.configure { it.dependsOn(recordTaskProvider) }
Expand All @@ -212,11 +214,9 @@ public class PaparazziPlugin : Plugin<Project> {
}
verifyVariants.configure { it.dependsOn(verifyTaskProvider) }

val isRecordRun = project.objects.property(Boolean::class.java)
val isVerifyRun = project.objects.property(Boolean::class.java)

project.gradle.taskGraph.whenReady { graph ->
isRecordRun.set(recordTaskProvider.map { graph.hasTask(it) })
isVerifyRun.set(verifyTaskProvider.map { graph.hasTask(it) })
}

Expand All @@ -225,12 +225,12 @@ public class PaparazziPlugin : Plugin<Project> {
writeResourcesTask.flatMap { it.paparazziResources.asFile }.get().path
test.systemProperties["paparazzi.project.dir"] = projectDirectory.toString()
test.systemProperties["paparazzi.build.dir"] = buildDirectory.get().toString()
test.systemProperties["paparazzi.failure.dir"] = failureDir.get().toString()
test.systemProperties["paparazzi.report.dir"] = reportOutputDir.get().toString()
test.systemProperties["paparazzi.snapshot.dir"] = snapshotOutputDir.toString()
test.systemProperties["paparazzi.artifacts.cache.dir"] = gradleUserHomeDir.path
test.systemProperties.putAll(project.properties.filterKeys { it.startsWith("app.cash.paparazzi") })

test.inputs.property("paparazzi.test.record", isRecordRun)
test.inputs.property("paparazzi.test.verify", isVerifyRun)

test.inputs.files(localResourceDirs)
Expand All @@ -248,23 +248,19 @@ public class PaparazziPlugin : Plugin<Project> {
test.inputs.files(nativePlatformFileCollection)
.withPropertyName("paparazzi.nativePlatform")
.withPathSensitivity(PathSensitivity.NONE)
test.inputs.files(project.files(snapshotOutputDir))
.withPathSensitivity(PathSensitivity.NAME_ONLY)

test.outputs.dir(reportOutputDir)
test.outputs.dir(snapshotOutputDir)
test.outputs.dir(failureDir)

test.doFirst {
// Note: these are lazy properties that are not resolvable in the Gradle configuration phase.
// They need special handling, so they're added as inputs.property above, and systemProperty here.
test.systemProperties["paparazzi.platform.data.root"] =
nativePlatformFileCollection.singleFile.absolutePath
test.systemProperties["paparazzi.test.record"] = isRecordRun.get()
test.systemProperties["paparazzi.test.verify"] = isVerifyRun.get()
}

test.doLast {
val uri = reportOutputDir.get().asFile.toPath().resolve("index.html").toUri()
test.logger.log(LIFECYCLE, "See the Paparazzi report at: $uri")
}
}

recordTaskProvider.configure { it.dependsOn(testTaskProvider) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package app.cash.paparazzi.gradle

import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction

public abstract class RecordPaparazziTask : PaparazziPlugin.PaparazziTask() {

@get:Internal
internal abstract val reportsDirectory: DirectoryProperty

@InputDirectory
internal fun getImageSnapshots(): Provider<Directory> {
return reportsDirectory.dir("goldenImages")
}

// Don't declare as output so that Gradle doesn't require
// a depnendency from test task to this task.
// Since this is just copying files it's ok to run it every time.
@get:Internal
internal abstract val goldenSnapshotDirectory: DirectoryProperty

@TaskAction
public fun copy() {
val goldenImagesDirectory = goldenSnapshotDirectory.dir("images").get()
getImageSnapshots().get().asFile.listFiles()!!.forEach { snapshotFile ->
val target = goldenImagesDirectory.file(snapshotFile.name)
snapshotFile.copyTo(target.asFile, overwrite = true)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import okio.buffer
import okio.source
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE
import org.gradle.testkit.runner.TaskOutcome.SUCCESS
import org.junit.After
Expand Down Expand Up @@ -508,13 +509,12 @@ class PaparazziPluginTest {
}

@Test
fun rerunOnReportDeletion() {
val fixtureRoot = File("src/test/projects/rerun-report")
val reportDir = File(fixtureRoot, "build/reports/paparazzi/debug").registerForDeletionOnExit()
val reportHtml = File(reportDir, "index.html")
assertThat(reportHtml.exists()).isFalse()
fun rerunOnSnapshotDeletion() {
val fixtureRoot = File("src/test/projects/rerun-snapshots")

File(fixtureRoot, "src/test/snapshots").registerForDeletionOnExit()
val snapshotsDir = File(fixtureRoot, "src/test/snapshots").registerForDeletionOnExit()
val snapshot = File(snapshotsDir, "images/app.cash.paparazzi.plugin.test_RecordTest_record.png")
assertThat(snapshot.exists()).isFalse()

// Take 1
val firstRunResult = gradleRunner
Expand All @@ -526,30 +526,37 @@ class PaparazziPluginTest {
assertThat(this).isNotNull()
assertThat(this!!.outcome).isEqualTo(SUCCESS)
}
assertThat(reportHtml.exists()).isTrue()
assertThat(snapshot.exists()).isTrue()

// Remove report
reportDir.deleteRecursively()
// Remove snapshot
snapshotsDir.deleteRecursively()

// Take 2
val secondRunResult = gradleRunner
.withArguments("recordPaparazziDebug", "--stacktrace")
.runFixture(fixtureRoot) { build() }

// test task does not run again
with(secondRunResult.task(":testDebugUnitTest")) {
assertThat(this).isNotNull()
assertThat(this!!.outcome).isEqualTo(SUCCESS) // not UP-TO-DATE
assertThat(this!!.outcome).isEqualTo(TaskOutcome.UP_TO_DATE)
}
// copy runs again
with(secondRunResult.task(":recordPaparazziDebug")) {
assertThat(this).isNotNull()
assertThat(this!!.outcome).isEqualTo(SUCCESS)
}
assertThat(reportHtml.exists()).isTrue()

assertThat(snapshot.exists()).isTrue()
}

@Test
fun rerunOnSnapshotDeletion() {
val fixtureRoot = File("src/test/projects/rerun-snapshots")
fun rerunOnExistingSnapshotDeletion() {
val fixtureRoot = File("src/test/projects/rerun-snapshots-existing")

val snapshotsDir = File(fixtureRoot, "src/test/snapshots").registerForDeletionOnExit()
val snapshotsDir = File(fixtureRoot, "src/test/snapshots")
val snapshot = File(snapshotsDir, "images/app.cash.paparazzi.plugin.test_RecordTest_record.png")
assertThat(snapshot.exists()).isFalse()
assertThat(snapshot.exists()).isTrue()

// Take 1
val firstRunResult = gradleRunner
Expand All @@ -571,10 +578,12 @@ class PaparazziPluginTest {
.withArguments("recordPaparazziDebug", "--stacktrace")
.runFixture(fixtureRoot) { build() }

// test task does not run again
with(secondRunResult.task(":testDebugUnitTest")) {
assertThat(this).isNotNull()
assertThat(this!!.outcome).isEqualTo(SUCCESS) // not UP-TO-DATE
}

assertThat(snapshot.exists()).isTrue()
}

Expand All @@ -595,22 +604,11 @@ class PaparazziPluginTest {

// Take 2
val secondRunResult = gradleRunner
.withArguments("recordPaparazziDebug", "--stacktrace")
.forwardOutput()
.runFixture(fixtureRoot) { build() }

with(secondRunResult.task(":testDebugUnitTest")) {
assertThat(this).isNotNull()
assertThat(this!!.outcome).isEqualTo(SUCCESS) // not UP-TO-DATE
}

// Take 3
val thirdRunResult = gradleRunner
.withArguments("verifyPaparazziDebug", "--stacktrace")
.forwardOutput()
.runFixture(fixtureRoot) { build() }

with(thirdRunResult.task(":testDebugUnitTest")) {
with(secondRunResult.task(":testDebugUnitTest")) {
assertThat(this).isNotNull()
assertThat(this!!.outcome).isEqualTo(SUCCESS) // not UP-TO-DATE
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'app.cash.paparazzi'
}

android {
namespace 'app.cash.paparazzi.plugin.test'
compileSdk libs.versions.compileSdk.get() as int
defaultConfig {
minSdk libs.versions.minSdk.get() as int
}
compileOptions {
sourceCompatibility = libs.versions.javaTarget.get()
targetCompatibility = libs.versions.javaTarget.get()
}
kotlinOptions {
jvmTarget = libs.versions.javaTarget.get()
}
}

apply from: '../guava-fix.gradle'
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package app.cash.paparazzi.plugin.test

import android.view.View
import app.cash.paparazzi.Paparazzi
import org.junit.Rule
import org.junit.Test

class RecordTest {
@get:Rule
val paparazzi = Paparazzi()

@Test
fun record() {
paparazzi.snapshot(View(paparazzi.context))
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions paparazzi/api/paparazzi.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
public final class app/cash/paparazzi/DefaultSnapshotWriter : app/cash/paparazzi/SnapshotHandler {
public static final field $stable I
public fun <init> ()V
public fun <init> (Ljava/io/File;)V
public synthetic fun <init> (Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
public fun newFrameHandler (Lapp/cash/paparazzi/Snapshot;II)Lapp/cash/paparazzi/SnapshotHandler$FrameHandler;
}

public final class app/cash/paparazzi/DeviceConfig {
public static final field $stable I
public static final field Companion Lapp/cash/paparazzi/DeviceConfig$Companion;
Expand Down Expand Up @@ -216,6 +225,15 @@ public final class app/cash/paparazzi/SnapshotVerifier : app/cash/paparazzi/Snap
public fun newFrameHandler (Lapp/cash/paparazzi/Snapshot;II)Lapp/cash/paparazzi/SnapshotHandler$FrameHandler;
}

public final class app/cash/paparazzi/SnapshotWriter : app/cash/paparazzi/SnapshotHandler {
public static final field $stable I
public fun <init> ()V
public fun <init> (Ljava/io/File;)V
public synthetic fun <init> (Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
public fun newFrameHandler (Lapp/cash/paparazzi/Snapshot;II)Lapp/cash/paparazzi/SnapshotHandler$FrameHandler;
}

public final class app/cash/paparazzi/TestName {
public static final field $stable I
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
Expand Down
4 changes: 4 additions & 0 deletions paparazzi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ tasks.withType(Test).configureEach {
"paparazzi.build.dir",
project.layout.buildDirectory.get().toString()
)
systemProperty(
"paparazzi.failure.dir",
project.layout.buildDirectory.dir("failures").get().toString()
)
systemProperty(
"paparazzi.report.dir",
project.extensions.getByType(ReportingExtension).baseDirectory.dir("paparazzi").get().toString()
Expand Down
Loading

0 comments on commit 579059d

Please sign in to comment.