• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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