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
18
19 import android.app.Instrumentation
20 import android.content.Context
21 import android.tools.common.Scenario
22 import android.tools.common.ScenarioBuilder
23 import android.tools.common.ScenarioImpl
24 import android.tools.common.Timestamp
25 import android.tools.common.Timestamps
26 import android.tools.common.io.Reader
27 import android.tools.common.io.ResultArtifactDescriptor
28 import android.tools.common.io.RunStatus
29 import android.tools.common.io.WINSCOPE_EXT
30 import android.tools.common.parsers.events.EventLogParser
31 import android.tools.device.flicker.datastore.CachedResultWriter
32 import android.tools.device.flicker.legacy.AbstractFlickerTestData
33 import android.tools.device.flicker.legacy.FlickerBuilder
34 import android.tools.device.flicker.legacy.FlickerTestData
35 import android.tools.device.traces.TRACE_CONFIG_REQUIRE_CHANGES
36 import android.tools.device.traces.io.ArtifactBuilder
37 import android.tools.device.traces.io.InMemoryArtifact
38 import android.tools.device.traces.io.ParsedTracesReader
39 import android.tools.device.traces.io.ResultReader
40 import android.tools.device.traces.io.ResultWriter
41 import android.tools.device.traces.monitors.ITransitionMonitor
42 import android.tools.device.traces.monitors.ScreenRecorder
43 import android.tools.device.traces.monitors.events.EventLogMonitor
44 import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor
45 import android.tools.device.traces.monitors.surfaceflinger.TransactionsTraceMonitor
46 import android.tools.device.traces.monitors.wm.ShellTransitionTraceMonitor
47 import android.tools.device.traces.monitors.wm.WindowManagerTraceMonitor
48 import android.tools.device.traces.monitors.wm.WmTransitionTraceMonitor
49 import android.tools.device.traces.parsers.WindowManagerStateHelper
50 import android.tools.device.traces.parsers.surfaceflinger.LayersTraceParser
51 import android.tools.device.traces.parsers.surfaceflinger.TransactionsTraceParser
52 import android.tools.device.traces.parsers.wm.TransitionTraceParser
53 import android.tools.device.traces.parsers.wm.WindowManagerDumpParser
54 import android.tools.device.traces.parsers.wm.WindowManagerTraceParser
55 import androidx.test.platform.app.InstrumentationRegistry
56 import androidx.test.uiautomator.UiDevice
57 import com.google.common.io.ByteStreams
58 import com.google.common.truth.Truth
59 import java.io.File
60 import java.io.FileInputStream
61 import java.io.IOException
62 import java.util.zip.ZipInputStream
63 import kotlin.io.path.createTempDirectory
64 import kotlin.io.path.createTempFile
65 import kotlin.io.path.name
66 import org.mockito.Mockito
67
68 internal val TEST_SCENARIO = ScenarioBuilder().forClass("test").build() as ScenarioImpl
69
70 internal fun outputFileName(status: RunStatus) =
71 File("/sdcard/flicker/${status.prefix}__test_ROTATION_0_GESTURAL_NAV.zip")
72
73 internal fun newTestResultWriter(
74 scenario: Scenario = ScenarioBuilder().forClass(createTempFile().name).build()
75 ) =
76 ResultWriter()
77 .forScenario(scenario)
78 .withOutputDir(createTempDirectory().toFile())
79 .setRunComplete()
80
81 internal fun newTestCachedResultWriter(scenario: Scenario = TEST_SCENARIO) =
82 CachedResultWriter()
83 .forScenario(scenario)
84 .withOutputDir(createTempDirectory().toFile())
85 .setRunComplete()
86
87 internal fun getWmTraceReaderFromAsset(
88 relativePath: String,
89 from: Long = Long.MIN_VALUE,
90 to: Long = Long.MAX_VALUE,
91 addInitialEntry: Boolean = true,
92 legacyTrace: Boolean = false,
93 ): Reader {
94 return ParsedTracesReader(
95 artifact = InMemoryArtifact(relativePath),
96 wmTrace =
97 WindowManagerTraceParser(legacyTrace)
98 .parse(
99 readAsset(relativePath),
100 Timestamps.from(elapsedNanos = from),
101 Timestamps.from(elapsedNanos = to),
102 addInitialEntry,
103 clearCache = false
104 )
105 )
106 }
107
getWmDumpReaderFromAssetnull108 internal fun getWmDumpReaderFromAsset(relativePath: String): Reader {
109 return ParsedTracesReader(
110 artifact = InMemoryArtifact(relativePath),
111 wmTrace = WindowManagerDumpParser().parse(readAsset(relativePath), clearCache = false)
112 )
113 }
114
getLayerTraceReaderFromAssetnull115 internal fun getLayerTraceReaderFromAsset(
116 relativePath: String,
117 ignoreOrphanLayers: Boolean = true,
118 legacyTrace: Boolean = false,
119 from: Timestamp = Timestamps.min(),
120 to: Timestamp = Timestamps.max()
121 ): Reader {
122 return ParsedTracesReader(
123 artifact = InMemoryArtifact(relativePath),
124 layersTrace =
125 LayersTraceParser(
126 ignoreLayersStackMatchNoDisplay = false,
127 ignoreLayersInVirtualDisplay = false,
128 legacyTrace = legacyTrace,
129 ) {
130 ignoreOrphanLayers
131 }
132 .parse(readAsset(relativePath), from, to)
133 )
134 }
135
getTraceReaderFromScenarionull136 internal fun getTraceReaderFromScenario(scenario: String): Reader {
137 val scenarioTraces = getScenarioTraces("AppLaunch")
138
139 return ParsedTracesReader(
140 artifact = InMemoryArtifact(scenario),
141 wmTrace = WindowManagerTraceParser().parse(scenarioTraces.wmTrace.readBytes()),
142 layersTrace = LayersTraceParser().parse(scenarioTraces.layersTrace.readBytes()),
143 transitionsTrace =
144 TransitionTraceParser()
145 .parse(
146 scenarioTraces.wmTransitions.readBytes(),
147 scenarioTraces.shellTransitions.readBytes()
148 ),
149 transactionsTrace =
150 TransactionsTraceParser().parse(scenarioTraces.transactions.readBytes()),
151 eventLog = EventLogParser().parse(scenarioTraces.eventLog.readBytes()),
152 )
153 }
154
155 @Throws(Exception::class)
readAssetnull156 internal fun readAsset(relativePath: String): ByteArray {
157 val context: Context = InstrumentationRegistry.getInstrumentation().context
158 val inputStream = context.resources.assets.open("testdata/$relativePath")
159 return ByteStreams.toByteArray(inputStream)
160 }
161
162 @Throws(IOException::class)
readAssetAsFilenull163 fun readAssetAsFile(relativePath: String): File {
164 val context: Context = InstrumentationRegistry.getInstrumentation().context
165 return File(context.cacheDir, relativePath).also {
166 if (!it.exists()) {
167 it.outputStream().use { cache ->
168 context.assets.open("testdata/$relativePath").use { inputStream ->
169 inputStream.copyTo(cache)
170 }
171 }
172 }
173 }
174 }
175
176 /**
177 * Runs `r` and asserts that an exception with type `expectedThrowable` is thrown.
178 *
179 * @param r the [Runnable] which is run and expected to throw.
180 * @throws AssertionError if `r` does not throw, or throws a runnable that is not an instance of
181 * `expectedThrowable`.
182 */
assertThrowsnull183 inline fun <reified ExceptionType> assertThrows(r: () -> Unit): ExceptionType {
184 try {
185 r()
186 } catch (t: Throwable) {
187 when {
188 ExceptionType::class.java.isInstance(t) -> return t as ExceptionType
189 t is Exception ->
190 throw AssertionError(
191 "Expected ${ExceptionType::class.java}, but got '${t.javaClass}'",
192 t
193 )
194 // Re-throw Errors and other non-Exception throwables.
195 else -> throw t
196 }
197 }
198 error("Expected exception ${ExceptionType::class.java}, but nothing was thrown")
199 }
200
assertFailnull201 inline fun assertFail(expectedMessage: String, predicate: () -> Any) {
202 val error = assertThrows<AssertionError> { predicate() }
203 Truth.assertThat(error).hasMessageThat().contains(expectedMessage)
204 }
205
assertThatErrorContainsDebugInfonull206 fun assertThatErrorContainsDebugInfo(error: Throwable) {
207 Truth.assertThat(error).hasMessageThat().contains("What?")
208 Truth.assertThat(error).hasMessageThat().contains("Where?")
209 }
210
assertArchiveContainsFilesnull211 fun assertArchiveContainsFiles(archivePath: File, expectedFiles: List<String>) {
212 Truth.assertWithMessage("Expected trace archive `$archivePath` to exist")
213 .that(archivePath.exists())
214 .isTrue()
215
216 val archiveStream = ZipInputStream(FileInputStream(archivePath))
217
218 val actualFiles = generateSequence { archiveStream.nextEntry }.map { it.name }.toList()
219
220 Truth.assertWithMessage("Trace archive doesn't contain all expected traces")
221 .that(actualFiles)
222 .containsExactlyElementsIn(expectedFiles)
223 }
224
getScenarioTracesnull225 fun getScenarioTraces(scenario: String): FlickerBuilder.TraceFiles {
226 lateinit var wmTrace: File
227 lateinit var layersTrace: File
228 lateinit var transactionsTrace: File
229 lateinit var wmTransitionTrace: File
230 lateinit var shellTransitionTrace: File
231 lateinit var eventLog: File
232 val traces =
233 mapOf<String, (File) -> Unit>(
234 "wm_trace" to { wmTrace = it },
235 "layers_trace" to { layersTrace = it },
236 "transactions_trace" to { transactionsTrace = it },
237 "wm_transition_trace" to { wmTransitionTrace = it },
238 "shell_transition_trace" to { shellTransitionTrace = it },
239 "eventlog" to { eventLog = it }
240 )
241 for ((traceName, resultSetter) in traces.entries) {
242 val traceBytes = readAsset("scenarios/$scenario/$traceName$WINSCOPE_EXT")
243 val traceFile = File.createTempFile(traceName, WINSCOPE_EXT)
244 traceFile.writeBytes(traceBytes)
245 resultSetter.invoke(traceFile)
246 }
247
248 return FlickerBuilder.TraceFiles(
249 wmTrace,
250 layersTrace,
251 transactionsTrace,
252 wmTransitionTrace,
253 shellTransitionTrace,
254 eventLog,
255 )
256 }
257
assertExceptionMessagenull258 fun assertExceptionMessage(error: Throwable?, expectedValue: String) {
259 Truth.assertWithMessage("Expected exception")
260 .that(error)
261 .hasMessageThat()
262 .contains(expectedValue)
263 }
264
createMockedFlickernull265 fun createMockedFlicker(
266 setup: List<FlickerTestData.() -> Unit> = emptyList(),
267 teardown: List<FlickerTestData.() -> Unit> = emptyList(),
268 transitions: List<FlickerTestData.() -> Unit> = emptyList(),
269 extraMonitor: ITransitionMonitor? = null
270 ): FlickerTestData {
271 val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
272 val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
273 val mockedFlicker = Mockito.mock(AbstractFlickerTestData::class.java)
274 val monitors: MutableList<ITransitionMonitor> =
275 mutableListOf(WindowManagerTraceMonitor(), LayersTraceMonitor())
276 extraMonitor?.let { monitors.add(it) }
277 Mockito.`when`(mockedFlicker.wmHelper).thenReturn(WindowManagerStateHelper())
278 Mockito.`when`(mockedFlicker.device).thenReturn(uiDevice)
279 Mockito.`when`(mockedFlicker.outputDir).thenReturn(createTempDirectory().toFile())
280 Mockito.`when`(mockedFlicker.traceMonitors).thenReturn(monitors)
281 Mockito.`when`(mockedFlicker.transitionSetup).thenReturn(setup)
282 Mockito.`when`(mockedFlicker.transitionTeardown).thenReturn(teardown)
283 Mockito.`when`(mockedFlicker.transitions).thenReturn(transitions)
284 return mockedFlicker
285 }
286
captureTracenull287 fun captureTrace(scenario: Scenario, actions: () -> Unit): ResultReader {
288 if (scenario.isEmpty) {
289 ScenarioBuilder().forClass("UNNAMED_CAPTURE").build()
290 }
291 val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
292 val writer =
293 ResultWriter()
294 .forScenario(scenario)
295 .withOutputDir(createTempDirectory().toFile())
296 .setRunComplete()
297 val monitors =
298 listOf(
299 ScreenRecorder(instrumentation.targetContext),
300 EventLogMonitor(),
301 TransactionsTraceMonitor(),
302 WmTransitionTraceMonitor(),
303 ShellTransitionTraceMonitor(),
304 WindowManagerTraceMonitor(),
305 LayersTraceMonitor()
306 )
307 try {
308 monitors.forEach { it.start() }
309 actions.invoke()
310 } finally {
311 monitors.forEach { it.stop(writer) }
312 }
313 val result = writer.write()
314
315 return ResultReader(result, TRACE_CONFIG_REQUIRE_CHANGES)
316 }
317
createDefaultArtifactBuildernull318 fun createDefaultArtifactBuilder(
319 status: RunStatus,
320 outputDir: File = createTempDirectory().toFile(),
321 files: Map<ResultArtifactDescriptor, File> = emptyMap()
322 ) =
323 ArtifactBuilder()
324 .withScenario(TEST_SCENARIO)
325 .withOutputDir(outputDir)
326 .withStatus(status)
327 .withFiles(files)
328