Introduce file-type agnostic `GoldenPathManager.
This is in preparation for the `MotionTestRule`, where non-image golden data is written to disk as well
Test: Unit tests
Bug: 322324387
Change-Id: If983a0b462ce7c6d9fbe9f0fe90d1c931dd43b43
diff --git a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenPathManagerTest.kt
similarity index 71%
rename from libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt
rename to libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenPathManagerTest.kt
index 776cc0f..b3c24c5 100644
--- a/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenImagePathManagerTest.kt
+++ b/libraries/screenshot/src/androidTest/java/platform/test/screenshot/GoldenPathManagerTest.kt
@@ -26,19 +26,44 @@
@RunWith(AndroidJUnit4::class)
@MediumTest
-class GoldenImagePathManagerTest {
+class GoldenPathManagerTest {
@Test
- fun goldenWithContextTest() {
- val localGoldenRoot = "/localgoldenroot/"
- val context = InstrumentationRegistry.getInstrumentation().getContext()
- val gim = GoldenImagePathManager(context, localGoldenRoot)
+ fun goldenImageWithContextTest() {
+ val subject = GoldenImagePathManager(InstrumentationRegistry.getInstrumentation().context)
// Test for resolving device local paths.
- val localGoldenFullImagePath = gim.goldenIdentifierResolver(testName = "test1")
+ val localGoldenFullImagePath = subject.goldenIdentifierResolver(testName = "test1")
assertThat(localGoldenFullImagePath).endsWith("/test1.png")
assertThat(localGoldenFullImagePath.split("/").size).isEqualTo(2)
}
+ @Test
+ fun goldenPathManager_includesPathConfig() {
+ val subject =
+ GoldenPathManager(
+ InstrumentationRegistry.getInstrumentation().context,
+ pathConfig = PathConfig(PathElementNoContext("something", true) { "mydevice" })
+ )
+ val pathSegments = subject.goldenIdentifierResolver(testName = "test1").split("/")
+ assertThat(pathSegments).containsExactly("mydevice", "test1.png").inOrder()
+ }
+
+ @Test
+ fun goldenPathManager_appendsPngExtensionByDefault() {
+ val context = InstrumentationRegistry.getInstrumentation().context
+ val subject = GoldenPathManager(context)
+ val result = subject.goldenIdentifierResolver(testName = "test1")
+ assertThat(result).endsWith("/test1.png")
+ }
+
+ @Test
+ fun goldenPathManager_allowsOverrideFileExtension() {
+ val context = InstrumentationRegistry.getInstrumentation().context
+ val subject = GoldenPathManager(context)
+ val result = subject.goldenIdentifierResolver(testName = "test1", extension = "proto")
+ assertThat(result).endsWith("/test1.proto")
+ }
+
private fun pathContextExtractor(context: Context): String {
return when {
(context.resources.displayMetrics.densityDpi.toString().length > 0) -> "context"
diff --git a/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt b/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
index 7f054eb..f7bd7ad 100644
--- a/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
+++ b/libraries/screenshot/src/main/java/platform/test/screenshot/GoldenImagePathManager.kt
@@ -32,17 +32,13 @@
/**
* Class to manage Directory structure of golden images.
*
- * When you run a AR Diff test, different attributes/dimensions of the platform you are running
+ * When you run a AR Diff test, different attributes/dimensions of the platform you are running on,
+ * such as build, screen resolution, orientation etc. will/may render differently and therefore may
+ * require a different golden image to compare against. You can manage these multiple golden images
+ * related to your test using this utility class. It supports both device-less or device based
+ * configurations. Please see GoldenImagePathManagerTest for detailed examples.
*
- * on, such as build, screen resolution, orientation etc. will/may render differently and therefore
- * may require a different golden image to compare against. You can manage these multiple golden
- * images related to your test using this utility class. It supports both device-less or device
- * based configurations. Please see GoldenImagePathManagerTest for detailed examples.
- *
- * You can configure where to find the golden images repo and local cache using [locationConfig]
- *
- * All the goldens are stored under a directory structure, which is determined by [pathConfig]. See
- * getDefaultPathConfig for the current implementation.
+ * You can configure where to find the golden images repo and local cache using [locationConfig].
*
* There are two ways to modify how the golden images are stored and retrieved for your test: A.
* (Recommended) Create your own PathConfig object which takes a series of [PathElement]s Each path
@@ -55,6 +51,8 @@
*
* NOTE: This class does not determine what combinations of attributes / dimensions your test code
* will run for. That decision/configuration is part of your test configuration.
+ *
+ * @see GoldenPathManager use the file-type agnostic version instead.
*/
open class GoldenImagePathManager
@JvmOverloads
@@ -72,23 +70,51 @@
}
}
- public val imageExtension = "png"
+ val imageExtension = "png"
/*
* Uses [pathConfig] and [testName] to construct the full path to the golden image.
*/
- public open fun goldenIdentifierResolver(testName: String): String {
+ open fun goldenIdentifierResolver(testName: String): String {
val relativePath = pathConfig.resolveRelativePath(appContext)
return "$relativePath$testName.$imageExtension"
}
}
+/**
+ * Class to manage directory structure of golden files.
+ *
+ * Same as [GoldenImagePathManager], but without the builtin assumption about images.
+ *
+ * TODO(b/322324387): fold GoldenImagePathManager into this class.
+ */
+open class GoldenPathManager
+@JvmOverloads
+constructor(
+ appContext: Context,
+ assetsPathRelativeToBuildRoot: String = "assets",
+ deviceLocalPath: String = getDeviceOutputDirectory(appContext),
+ pathConfig: PathConfig = getSimplePathConfig()
+) : GoldenImagePathManager(appContext, assetsPathRelativeToBuildRoot, deviceLocalPath, pathConfig) {
+
+ final override fun goldenIdentifierResolver(testName: String) =
+ goldenIdentifierResolver(testName, imageExtension)
+
+ /*
+ * Uses [pathConfig] and [testName] to construct the full path to the golden image.
+ */
+ open fun goldenIdentifierResolver(testName: String, extension: String): String {
+ val relativePath = pathConfig.resolveRelativePath(appContext)
+ return "$relativePath$testName.$extension"
+ }
+}
+
/*
* Every dimension that impacts the golden image needs to be a part of the path/filename
* that is used to access the golden. There are two types of attributes / dimensions.
* One that depend on the device context and the once that are context agnostic.
*/
-abstract sealed class PathElementBase {
+sealed class PathElementBase {
abstract val attr: String
abstract val isDir: Boolean
}
@@ -128,7 +154,6 @@
when (it) {
is PathElementWithContext -> it.func(context)
is PathElementNoContext -> it.func()
- else -> ""
} + if (it.isDir) "/" else "_"
}
.joinToString("")
@@ -140,7 +165,7 @@
* An example directory structure using this config would be
* /google/pixel6/api32/600_400/
*/
-public fun getDefaultPathConfig(): PathConfig {
+fun getDefaultPathConfig(): PathConfig {
return PathConfig(
PathElementNoContext(BRAND_TAG, true, ::getDeviceBrand),
PathElementNoContext(MODEL_TAG, true, ::getDeviceModel),
@@ -150,7 +175,7 @@
)
}
-public fun getSimplePathConfig(): PathConfig {
+fun getSimplePathConfig(): PathConfig {
return PathConfig(PathElementNoContext(MODEL_TAG, true, ::getDeviceModel))
}
@@ -172,11 +197,11 @@
/*
* Default output directory where all images generated as part of the test are stored.
*/
-public fun getDeviceOutputDirectory(context: Context) =
+fun getDeviceOutputDirectory(context: Context) =
File(context.filesDir, "platform_screenshots").toString()
/* Standard implementations for the usual list of dimensions that affect a golden image. */
-public fun getDeviceModel(): String {
+fun getDeviceModel(): String {
var model = Build.MODEL.lowercase()
arrayOf("phone", "x86_64", "x86", "x64", "gms", "wear").forEach {
model = model.replace(it, "")
@@ -184,7 +209,7 @@
return model.trim().replace(" ", "_")
}
-public fun getDeviceBrand(): String {
+fun getDeviceBrand(): String {
var brand = Build.BRAND.lowercase()
arrayOf("phone", "x86_64", "x86", "x64", "gms", "wear").forEach {
brand = brand.replace(it, "")
@@ -192,15 +217,14 @@
return brand.trim().replace(" ", "_")
}
-public fun getAPIVersion() = "API" + Build.VERSION.SDK_INT.toString()
+fun getAPIVersion() = "API" + Build.VERSION.SDK_INT.toString()
-public fun getScreenResolution(context: Context) =
+fun getScreenResolution(context: Context) =
context.resources.displayMetrics.densityDpi.toString() + "dpi"
-public fun getScreenOrientation(context: Context) =
- context.resources.configuration.orientation.toString()
+fun getScreenOrientation(context: Context) = context.resources.configuration.orientation.toString()
-public fun getScreenSize(context: Context): String {
+fun getScreenSize(context: Context): String {
val heightdp = context.resources.configuration.screenHeightDp.toString()
val widthdp = context.resources.configuration.screenWidthDp.toString()
return "${heightdp}_$widthdp"