xref: /aosp_15_r20/platform_testing/libraries/flicker/utils/test/src/android/tools/testutils/Utils.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.tools.testutils
18 
19 import android.content.Context
20 import android.tools.Scenario
21 import android.tools.ScenarioBuilder
22 import android.tools.ScenarioImpl
23 import android.tools.Timestamp
24 import android.tools.Timestamps
25 import android.tools.io.Reader
26 import android.tools.io.ResultArtifactDescriptor
27 import android.tools.io.RunStatus
28 import android.tools.rules.StopAllTracesRule
29 import android.tools.testrules.CacheCleanupRule
30 import android.tools.testrules.InitializeCrossPlatformRule
31 import android.tools.traces.io.ArtifactBuilder
32 import android.tools.traces.io.ResultWriter
33 import android.tools.traces.parsers.perfetto.LayersTraceParser
34 import android.tools.traces.parsers.perfetto.TraceProcessorSession
35 import android.tools.traces.parsers.perfetto.WindowManagerTraceParser
36 import android.tools.traces.parsers.wm.LegacyWindowManagerTraceParser
37 import android.tools.traces.parsers.wm.WindowManagerDumpParser
38 import android.tools.traces.wm.ConfigurationContainerImpl
39 import android.tools.traces.wm.RootWindowContainer
40 import android.tools.traces.wm.WindowContainerImpl
41 import android.tools.traces.wm.WindowManagerTrace
42 import androidx.test.platform.app.InstrumentationRegistry
43 import androidx.test.uiautomator.UiDevice
44 import com.google.common.io.ByteStreams
45 import com.google.common.truth.Truth
46 import java.io.File
47 import java.io.FileInputStream
48 import java.io.IOException
49 import java.util.zip.ZipInputStream
50 import kotlin.io.path.createTempDirectory
51 import kotlin.io.path.name
52 import org.junit.rules.RuleChain
53 
54 /** Factory function to create cleanup test rule */
55 fun CleanFlickerEnvironmentRule(): RuleChain =
56     RuleChain.outerRule(InitializeCrossPlatformRule())
57         .around(StopAllTracesRule())
58         .around(CacheCleanupRule())
59 
60 val TEST_SCENARIO = ScenarioBuilder().forClass("test").build() as ScenarioImpl
61 
62 const val SYSTEMUI_PACKAGE = "com.android.systemui"
63 
64 /**
65  * Runs `r` and asserts that an exception with type `expectedThrowable` is thrown.
66  *
67  * @param r the [Runnable] which is run and expected to throw.
68  * @throws AssertionError if `r` does not throw, or throws a runnable that is not an instance of
69  *   `expectedThrowable`.
70  */
71 inline fun <reified ExceptionType> assertThrows(r: () -> Unit): ExceptionType {
72     try {
73         r()
74     } catch (t: Throwable) {
75         when {
76             ExceptionType::class.java.isInstance(t) -> return t as ExceptionType
77             t is Exception ->
78                 throw AssertionError(
79                     "Expected ${ExceptionType::class.java}, but got '${t.javaClass}'",
80                     t,
81                 )
82             // Re-throw Errors and other non-Exception throwables.
83             else -> throw t
84         }
85     }
86     error("Expected exception ${ExceptionType::class.java}, but nothing was thrown")
87 }
88 
assertFailnull89 fun assertFail(expectedMessage: String, predicate: () -> Any) {
90     val error = assertThrows<AssertionError> { predicate() }
91     Truth.assertThat(error).hasMessageThat().contains(expectedMessage)
92 }
93 
assertThatErrorContainsDebugInfonull94 fun assertThatErrorContainsDebugInfo(error: Throwable) {
95     Truth.assertThat(error).hasMessageThat().contains("What?")
96     Truth.assertThat(error).hasMessageThat().contains("Where?")
97 }
98 
99 /**
100  * Method to check if the [archivePath] contains trace files from one of the expected files list as
101  * given in the [possibleExpectedFiles].
102  */
assertArchiveContainsFilesnull103 fun assertArchiveContainsFiles(archivePath: File, possibleExpectedFiles: List<List<String>>) {
104     Truth.assertWithMessage("Expected trace archive `$archivePath` to exist")
105         .that(archivePath.exists())
106         .isTrue()
107 
108     val actualFiles = getActualTraceFilesFromArchive(archivePath)
109     var isActualTraceAsExpected = false
110 
111     for (expectedFiles: List<String> in possibleExpectedFiles) {
112         if (actualFiles.equalsIgnoreOrder(expectedFiles)) {
113             isActualTraceAsExpected = true
114             break
115         }
116     }
117 
118     val messageActualFiles = "[${actualFiles.joinToString(", ")}]"
119     val messageExpectedFiles =
120         "${possibleExpectedFiles.map { "[${it.joinToString(", ")}]"}.joinToString(", ")}"
121     Truth.assertWithMessage(
122             "Trace archive doesn't contain expected traces." +
123                 "\n Actual: $messageActualFiles" +
124                 "\n Expected: $messageExpectedFiles"
125         )
126         .that(isActualTraceAsExpected)
127         .isTrue()
128 }
129 
getActualTraceFilesFromArchivenull130 fun getActualTraceFilesFromArchive(archivePath: File): List<String> {
131     val archiveStream = ZipInputStream(FileInputStream(archivePath))
132     return generateSequence { archiveStream.nextEntry }.map { it.name }.toList()
133 }
134 
equalsIgnoreOrdernull135 fun <T> List<T>.equalsIgnoreOrder(other: List<T>) = this.toSet() == other.toSet()
136 
137 fun getWmTraceReaderFromAsset(
138     relativePathWithoutExtension: String,
139     from: Long = Long.MIN_VALUE,
140     to: Long = Long.MAX_VALUE,
141     addInitialEntry: Boolean = true,
142     legacyTrace: Boolean = false,
143 ): Reader {
144     fun parseTrace(): WindowManagerTrace {
145         val traceData = readAsset("$relativePathWithoutExtension.perfetto-trace")
146         return TraceProcessorSession.loadPerfettoTrace(traceData) { session ->
147             WindowManagerTraceParser()
148                 .parse(
149                     session,
150                     Timestamps.from(elapsedNanos = from),
151                     Timestamps.from(elapsedNanos = to),
152                 )
153         }
154     }
155 
156     fun parseLegacyTrace(): WindowManagerTrace {
157         val traceData =
158             runCatching { readAsset("$relativePathWithoutExtension.pb") }.getOrNull()
159                 ?: runCatching { readAsset("$relativePathWithoutExtension.winscope") }.getOrNull()
160                 ?: error("Can't find legacy trace file $relativePathWithoutExtension")
161 
162         return LegacyWindowManagerTraceParser(legacyTrace)
163             .parse(
164                 traceData,
165                 Timestamps.from(elapsedNanos = from),
166                 Timestamps.from(elapsedNanos = to),
167                 addInitialEntry,
168                 clearCache = false,
169             )
170     }
171 
172     val trace =
173         if (android.tracing.Flags.perfettoWmTracing()) {
174             parseTrace()
175         } else {
176             parseLegacyTrace()
177         }
178 
179     return ParsedTracesReader(
180         artifact = TestArtifact(relativePathWithoutExtension),
181         wmTrace = trace,
182     )
183 }
184 
getWmDumpReaderFromAssetnull185 fun getWmDumpReaderFromAsset(relativePathWithoutExtension: String): Reader {
186     fun parseDump(): WindowManagerTrace {
187         val traceData = readAsset("$relativePathWithoutExtension.perfetto-trace")
188         return TraceProcessorSession.loadPerfettoTrace(traceData) { session ->
189             WindowManagerTraceParser().parse(session)
190         }
191     }
192 
193     fun parseLegacyDump(): WindowManagerTrace {
194         val traceData =
195             runCatching { readAsset("$relativePathWithoutExtension.pb") }.getOrNull()
196                 ?: runCatching { readAsset("$relativePathWithoutExtension.winscope") }.getOrNull()
197                 ?: error("Can't find legacy trace file $relativePathWithoutExtension")
198         return WindowManagerDumpParser().parse(traceData, clearCache = false)
199     }
200 
201     val wmTrace =
202         if (android.tracing.Flags.perfettoWmDump()) {
203             parseDump()
204         } else {
205             parseLegacyDump()
206         }
207     return ParsedTracesReader(
208         artifact = TestArtifact(relativePathWithoutExtension),
209         wmTrace = wmTrace,
210     )
211 }
212 
getLayerTraceReaderFromAssetnull213 fun getLayerTraceReaderFromAsset(
214     relativePath: String,
215     ignoreOrphanLayers: Boolean = true,
216     from: Timestamp = Timestamps.min(),
217     to: Timestamp = Timestamps.max(),
218 ): Reader {
219     val layersTrace =
220         TraceProcessorSession.loadPerfettoTrace(readAsset(relativePath)) { session ->
221             LayersTraceParser(
222                     ignoreLayersStackMatchNoDisplay = false,
223                     ignoreLayersInVirtualDisplay = false,
224                 ) {
225                     ignoreOrphanLayers
226                 }
227                 .parse(session, from, to)
228         }
229     return ParsedTracesReader(artifact = TestArtifact(relativePath), layersTrace = layersTrace)
230 }
231 
232 @Throws(Exception::class)
readAssetnull233 fun readAsset(relativePath: String): ByteArray {
234     val context: Context = InstrumentationRegistry.getInstrumentation().context
235     val inputStream = context.resources.assets.open("testdata/$relativePath")
236     return ByteStreams.toByteArray(inputStream)
237 }
238 
239 @Throws(IOException::class)
readAssetAsFilenull240 fun readAssetAsFile(relativePath: String): File {
241     val context: Context = InstrumentationRegistry.getInstrumentation().context
242     return File(context.cacheDir, relativePath).also {
243         if (!it.exists()) {
244             it.outputStream().use { cache ->
245                 context.assets.open("testdata/$relativePath").use { inputStream ->
246                     inputStream.copyTo(cache)
247                 }
248             }
249         }
250     }
251 }
252 
newTestResultWriternull253 fun newTestResultWriter(
254     scenario: Scenario = ScenarioBuilder().forClass(kotlin.io.path.createTempFile().name).build()
255 ) =
256     ResultWriter()
257         .forScenario(scenario)
258         .withOutputDir(createTempDirectory().toFile())
259         .setRunComplete()
260 
261 fun assertExceptionMessage(error: Throwable?, expectedValue: String) {
262     Truth.assertWithMessage("Expected exception")
263         .that(error)
264         .hasMessageThat()
265         .contains(expectedValue)
266 }
267 
outputFileNamenull268 fun outputFileName(status: RunStatus) =
269     File("/sdcard/flicker/${status.prefix}__test_ROTATION_0_GESTURAL_NAV.zip")
270 
271 fun createDefaultArtifactBuilder(
272     status: RunStatus,
273     outputDir: File = createTempDirectory().toFile(),
274     files: Map<ResultArtifactDescriptor, File> = emptyMap(),
275 ) =
276     ArtifactBuilder()
277         .withScenario(TEST_SCENARIO)
278         .withOutputDir(outputDir)
279         .withStatus(status)
280         .withFiles(files)
281 
282 fun getLauncherPackageName() =
283     UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).launcherPackageName
284 
285 fun getSystemUiUidName(): String {
286     val packageManager = InstrumentationRegistry.getInstrumentation().context.getPackageManager()
287     val uid = packageManager.getApplicationInfo(SYSTEMUI_PACKAGE, 0).uid
288     return requireNotNull(packageManager.getNameForUid(uid))
289 }
290 
newEmptyRootContainernull291 fun newEmptyRootContainer(orientation: Int = 0, layerId: Int = 0) =
292     RootWindowContainer(
293         WindowContainerImpl(
294             title = "root",
295             token = "",
296             orientation = orientation,
297             layerId = layerId,
298             _isVisible = true,
299             _children = emptyList(),
300             configurationContainer = ConfigurationContainerImpl.EMPTY,
301             computedZ = 0,
302         )
303     )
304