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