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.utils
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.CacheCleanupRule
29 import android.tools.rules.InitializeCrossPlatformRule
30 import android.tools.rules.StopAllTracesRule
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.wm.WindowManagerDumpParser
36 import android.tools.traces.parsers.wm.WindowManagerTraceParser
37 import android.tools.traces.wm.ConfigurationContainerImpl
38 import android.tools.traces.wm.RootWindowContainer
39 import android.tools.traces.wm.WindowContainerImpl
40 import androidx.test.platform.app.InstrumentationRegistry
41 import androidx.test.uiautomator.UiDevice
42 import com.google.common.io.ByteStreams
43 import com.google.common.truth.Truth
44 import java.io.File
45 import java.io.FileInputStream
46 import java.io.IOException
47 import java.util.zip.ZipInputStream
48 import kotlin.io.path.createTempDirectory
49 import kotlin.io.path.name
50 import org.junit.rules.RuleChain
51 
52 /** Factory function to create cleanup test rule */
53 fun CleanFlickerEnvironmentRule(): RuleChain =
54     RuleChain.outerRule(InitializeCrossPlatformRule())
55         .around(StopAllTracesRule())
56         .around(CacheCleanupRule())
57 
58 val TEST_SCENARIO = ScenarioBuilder().forClass("test").build() as ScenarioImpl
59 
60 const val SYSTEMUI_PACKAGE = "com.android.systemui"
61 
62 /**
63  * Runs `r` and asserts that an exception with type `expectedThrowable` is thrown.
64  *
65  * @param r the [Runnable] which is run and expected to throw.
66  * @throws AssertionError if `r` does not throw, or throws a runnable that is not an instance of
67  *   `expectedThrowable`.
68  */
69 inline fun <reified ExceptionType> assertThrows(r: () -> Unit): ExceptionType {
70     try {
71         r()
72     } catch (t: Throwable) {
73         when {
74             ExceptionType::class.java.isInstance(t) -> return t as ExceptionType
75             t is Exception ->
76                 throw AssertionError(
77                     "Expected ${ExceptionType::class.java}, but got '${t.javaClass}'",
78                     t
79                 )
80             // Re-throw Errors and other non-Exception throwables.
81             else -> throw t
82         }
83     }
84     error("Expected exception ${ExceptionType::class.java}, but nothing was thrown")
85 }
86 
assertFailnull87 fun assertFail(expectedMessage: String, predicate: () -> Any) {
88     val error = assertThrows<AssertionError> { predicate() }
89     Truth.assertThat(error).hasMessageThat().contains(expectedMessage)
90 }
91 
assertThatErrorContainsDebugInfonull92 fun assertThatErrorContainsDebugInfo(error: Throwable) {
93     Truth.assertThat(error).hasMessageThat().contains("What?")
94     Truth.assertThat(error).hasMessageThat().contains("Where?")
95 }
96 
97 /**
98  * Method to check if the [archivePath] contains trace files from one of the expected files list as
99  * given in the [possibleExpectedFiles].
100  */
assertArchiveContainsFilesnull101 fun assertArchiveContainsFiles(archivePath: File, possibleExpectedFiles: List<List<String>>) {
102     Truth.assertWithMessage("Expected trace archive `$archivePath` to exist")
103         .that(archivePath.exists())
104         .isTrue()
105 
106     val actualFiles = getActualTraceFilesFromArchive(archivePath)
107     var isActualTraceAsExpected = false
108 
109     for (expectedFiles: List<String> in possibleExpectedFiles) {
110         if (actualFiles.equalsIgnoreOrder(expectedFiles)) {
111             isActualTraceAsExpected = true
112             break
113         }
114     }
115 
116     Truth.assertWithMessage("Trace archive doesn't contain all expected traces")
117         .that(isActualTraceAsExpected)
118         .isTrue()
119 }
120 
getActualTraceFilesFromArchivenull121 fun getActualTraceFilesFromArchive(archivePath: File): List<String> {
122     val archiveStream = ZipInputStream(FileInputStream(archivePath))
123     return generateSequence { archiveStream.nextEntry }.map { it.name }.toList()
124 }
125 
equalsIgnoreOrdernull126 fun <T> List<T>.equalsIgnoreOrder(other: List<T>) = this.toSet() == other.toSet()
127 
128 fun getWmTraceReaderFromAsset(
129     relativePath: String,
130     from: Long = Long.MIN_VALUE,
131     to: Long = Long.MAX_VALUE,
132     addInitialEntry: Boolean = true,
133     legacyTrace: Boolean = false,
134 ): Reader {
135     return ParsedTracesReader(
136         artifact = TestArtifact(relativePath),
137         wmTrace =
138             WindowManagerTraceParser(legacyTrace)
139                 .parse(
140                     readAsset(relativePath),
141                     Timestamps.from(elapsedNanos = from),
142                     Timestamps.from(elapsedNanos = to),
143                     addInitialEntry,
144                     clearCache = false
145                 )
146     )
147 }
148 
getWmDumpReaderFromAssetnull149 fun getWmDumpReaderFromAsset(relativePath: String): Reader {
150     return ParsedTracesReader(
151         artifact = TestArtifact(relativePath),
152         wmTrace = WindowManagerDumpParser().parse(readAsset(relativePath), clearCache = false)
153     )
154 }
155 
getLayerTraceReaderFromAssetnull156 fun getLayerTraceReaderFromAsset(
157     relativePath: String,
158     ignoreOrphanLayers: Boolean = true,
159     from: Timestamp = Timestamps.min(),
160     to: Timestamp = Timestamps.max()
161 ): Reader {
162     val layersTrace =
163         TraceProcessorSession.loadPerfettoTrace(readAsset(relativePath)) { session ->
164             LayersTraceParser(
165                     ignoreLayersStackMatchNoDisplay = false,
166                     ignoreLayersInVirtualDisplay = false,
167                 ) {
168                     ignoreOrphanLayers
169                 }
170                 .parse(session, from, to)
171         }
172     return ParsedTracesReader(artifact = TestArtifact(relativePath), layersTrace = layersTrace)
173 }
174 
175 @Throws(Exception::class)
readAssetnull176 fun readAsset(relativePath: String): ByteArray {
177     val context: Context = InstrumentationRegistry.getInstrumentation().context
178     val inputStream = context.resources.assets.open("testdata/$relativePath")
179     return ByteStreams.toByteArray(inputStream)
180 }
181 
182 @Throws(IOException::class)
readAssetAsFilenull183 fun readAssetAsFile(relativePath: String): File {
184     val context: Context = InstrumentationRegistry.getInstrumentation().context
185     return File(context.cacheDir, relativePath).also {
186         if (!it.exists()) {
187             it.outputStream().use { cache ->
188                 context.assets.open("testdata/$relativePath").use { inputStream ->
189                     inputStream.copyTo(cache)
190                 }
191             }
192         }
193     }
194 }
195 
newTestResultWriternull196 fun newTestResultWriter(
197     scenario: Scenario = ScenarioBuilder().forClass(kotlin.io.path.createTempFile().name).build()
198 ) =
199     ResultWriter()
200         .forScenario(scenario)
201         .withOutputDir(createTempDirectory().toFile())
202         .setRunComplete()
203 
204 fun assertExceptionMessage(error: Throwable?, expectedValue: String) {
205     Truth.assertWithMessage("Expected exception")
206         .that(error)
207         .hasMessageThat()
208         .contains(expectedValue)
209 }
210 
outputFileNamenull211 fun outputFileName(status: RunStatus) =
212     File("/sdcard/flicker/${status.prefix}__test_ROTATION_0_GESTURAL_NAV.zip")
213 
214 fun createDefaultArtifactBuilder(
215     status: RunStatus,
216     outputDir: File = createTempDirectory().toFile(),
217     files: Map<ResultArtifactDescriptor, File> = emptyMap()
218 ) =
219     ArtifactBuilder()
220         .withScenario(TEST_SCENARIO)
221         .withOutputDir(outputDir)
222         .withStatus(status)
223         .withFiles(files)
224 
225 fun getLauncherPackageName() =
226     UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).launcherPackageName
227 
228 fun getSystemUiUidName(): String {
229     val packageManager = InstrumentationRegistry.getInstrumentation().context.getPackageManager()
230     val uid = packageManager.getApplicationInfo(SYSTEMUI_PACKAGE, 0).uid
231     return requireNotNull(packageManager.getNameForUid(uid))
232 }
233 
newEmptyRootContainernull234 fun newEmptyRootContainer(orientation: Int = 0, layerId: Int = 0) =
235     RootWindowContainer(
236         WindowContainerImpl(
237             title = "root",
238             token = "",
239             orientation = orientation,
240             layerId = layerId,
241             _isVisible = true,
242             _children = emptyList(),
243             configurationContainer = ConfigurationContainerImpl.EMPTY,
244             computedZ = 0
245         )
246     )
247