Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Absolute paths in Test tasks's systemProperties cause remote build cache misses #1874

Open
remcomokveld opened this issue Mar 14, 2025 · 0 comments
Labels
bug Something isn't working

Comments

@remcomokveld
Copy link

Description

The Paparazzi plugin configures the test tasks with a bunch of paths and those paths are set up as absolute paths. system properties are part of the cache key so if CI writes remote build cache entries while having the repository checked out in a directory like /tmp/my-repository and local machines have it in ~/Projects/my-repository the cache key of test tasks is different resulting in a cache miss.

Steps to Reproduce

The problem can be reproduced by updating the unit test app.cash.paparazzi.gradle.PaparazziPluginTest.cacheable() to run the second run in a different directory. In that case assertions that the task came from cache fail

  @Test
  fun cacheable() {
    val fixtureRoot = File("src/test/projects/cacheable")
    // Also validate remote cache by running the test in two separate directories that use the same build cache
    val secondFixturesRoot = fixtureRoot.parentFile.resolve("cacheable-2").registerForDeletionOnExit()
    fixtureRoot.copyRecursively(secondFixturesRoot)
    fixtureRoot.resolve("build-cache").registerForDeletionOnExit()

    val firstRun = gradleRunner
      .withArguments("testDebug", "--build-cache", "--stacktrace")
      .runFixture(fixtureRoot) { build() }

    with(firstRun.task(":preparePaparazziDebugResources")) {
      assertThat(this).isNotNull()
      assertThat(this!!.outcome).isNotEqualTo(FROM_CACHE)
    }
    with(firstRun.task(":testDebugUnitTest")) {
      assertThat(this).isNotNull()
      assertThat(this!!.outcome).isNotEqualTo(FROM_CACHE)
    }

    val secondRun = gradleRunner
      .withArguments("testDebug", "--build-cache", "--stacktrace")
      .runFixture(secondFixturesRoot) { build() }

    with(secondRun.task(":preparePaparazziDebugResources")) {
      assertThat(this).isNotNull()
      assertThat(this!!.outcome).isEqualTo(FROM_CACHE)
    }
    with(secondRun.task(":testDebugUnitTest")) {
      assertThat(this).isNotNull()
      assertThat(this!!.outcome).isEqualTo(FROM_CACHE)
    }
  }

Expected behavior

A second run without input changes in a project that is in a different directory should come from cache. The recommended way of setting up remote build cache is to store cache entries on CI so that local dev machines can use them. If there are absolute paths in the cache key those local machines would never be able to use them

Workaround

I was able to work around this by adding the following gradle logic

androidComponents {
    onVariants { variant ->
        variant.hostTests.forEach { (_, hostTest) ->
            hostTest.configureTestTask { testTask ->
                testTask.systemProperties.toMap().forEach { (key, value) ->
                    if (key.toString().startsWith("paparazzi.") && value is String && value.startsWith(projectDir.absolutePath)) {
                        testTask.systemProperty(key, value.replace(projectDir.absolutePath, "."))
                    }
                }
                testTask.systemProperty("paparazzi.artifacts.cache.dir", "./gradle-user-home")
            }
        }
    }
}

and initialize paparazzi as follows

    private val gradleUserHome = File(
        System.getenv("GRADLE_USER_HOME")
            ?: System.getProperty("user.home")?.plus(File.separator)?.plus(".gradle")
            ?: error("Could not determine gradle user home")
    )
    private val paparazzi = Paparazzi(
        environment = detectEnvironment().run {
            copy(
                localResourceDirs = localResourceDirs.map { asCanonicalPath(it) },
                moduleResourceDirs = moduleResourceDirs.map { asCanonicalPath(it) },
                libraryResourceDirs = libraryResourceDirs.map { asCanonicalPath(it) },
                allModuleAssetDirs = allModuleAssetDirs.map { asCanonicalPath(it) },
                libraryAssetDirs = libraryAssetDirs.map { asCanonicalPath(it) },
                appTestDir = File(appTestDir).canonicalPath
            )
        },
    )

    private fun asCanonicalPath(path: String) = when {
        path.startsWith("./gradle-user-home/") -> gradleUserHome.resolve(path.removePrefix("./gradle-user-home/")).canonicalPath
        path.startsWith("./") -> File(path).canonicalPath
        else -> path
    }

Additional information:

  • Paparazzi Version: 1.3.5
  • OS: MacOS
  • Compile SDK: 35
  • Gradle Version: 8.10.2
  • Android Gradle Plugin Version: 8.8.0
@remcomokveld remcomokveld added the bug Something isn't working label Mar 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant