• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 com.android.server.wm.flicker
18 
19 import androidx.annotation.VisibleForTesting
20 import com.android.compatibility.common.util.ZipUtil
21 import com.android.server.wm.flicker.assertions.AssertionData
22 import com.android.server.wm.flicker.assertions.FlickerAssertionError
23 import com.android.server.wm.flicker.assertions.FlickerAssertionErrorBuilder
24 import com.android.server.wm.flicker.assertions.FlickerSubject
25 import com.android.server.wm.flicker.dsl.AssertionTag
26 import com.android.server.wm.flicker.traces.FlickerTraceSubject
27 import com.android.server.wm.flicker.traces.eventlog.EventLogSubject
28 import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
29 import com.android.server.wm.flicker.traces.eventlog.FocusEvent
30 import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
31 import com.android.server.wm.flicker.traces.windowmanager.WindowManagerTraceSubject
32 import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
33 import com.android.server.wm.traces.common.layers.LayersTrace
34 import com.android.server.wm.traces.common.windowmanager.WindowManagerState
35 import java.io.File
36 import java.nio.file.Path
37 import kotlinx.coroutines.async
38 import kotlinx.coroutines.runBlocking
39 import kotlinx.coroutines.CoroutineScope
40 import kotlinx.coroutines.Deferred
41 import kotlinx.coroutines.Dispatchers
42 import kotlinx.coroutines.SupervisorJob
43 
44 /**
45  * Defines the result of a flicker run
46  */
47 class FlickerRunResult private constructor(
48     /**
49      * The trace files associated with the result (incl. screen recording)
50      */
51     _traceFile: Path?,
52     /**
53      * Determines which assertions to run (e.g., start, end, all, or a custom tag)
54      */
55     @JvmField var assertionTag: String,
56     /**
57      * Truth subject that corresponds to a [WindowManagerTrace] or [WindowManagerState]
58      */
59     internal val wmSubject: FlickerSubject?,
60     /**
61      * Truth subject that corresponds to a [LayersTrace] or [BaseLayerTraceEntry]
62      */
63     internal val layersSubject: FlickerSubject?,
64     /**
65      * Truth subject that corresponds to a list of [FocusEvent]
66      */
67     @VisibleForTesting
68     val eventLogSubject: EventLogSubject?
69 ) {
70     /**
71      * The object responsible for managing the trace file associated with this result.
72      *
73      * By default the file manager is the RunResult itself but in the case the RunResult is
74      * derived or extracted from another RunResult then that other RunResult should be the trace
75      * file manager.
76      */
77     internal var mTraceFile: TraceFile? =
78             if (_traceFile != null) TraceFile(_traceFile) else null
79 
80     internal val traceName = mTraceFile?.traceFile?.fileName ?: "UNNAMED_TRACE"
81 
82     var status: RunStatus = RunStatus.UNDEFINED
83         internal set(value) {
84             if (field != value) {
<lambda>null85                 require(value != RunStatus.UNDEFINED) {
86                     "Can't set status to UNDEFINED after being defined"
87                 }
<lambda>null88                 require(!field.isFailure) {
89                     "Status of run already set to a failed status $field " +
90                             "and can't be changed to $value."
91                 }
92                 field = value
93             }
94 
95             mTraceFile?.status = status
96         }
97 
setRunFailednull98     fun setRunFailed() {
99         status = RunStatus.RUN_FAILED
100     }
101 
102     val isSuccessfulRun: Boolean get() = !isFailedRun
103     val isFailedRun: Boolean get() {
<lambda>null104         require(status != RunStatus.UNDEFINED) {
105             "RunStatus cannot be UNDEFINED for $traceName ($assertionTag)"
106         }
107         // Other types of failures can only happen if the run has succeeded
108         return status == RunStatus.RUN_FAILED
109     }
110 
getSubjectsnull111     fun getSubjects(): List<FlickerSubject> {
112         val result = mutableListOf<FlickerSubject>()
113 
114         wmSubject?.run { result.add(this) }
115         layersSubject?.run { result.add(this) }
116         eventLogSubject?.run { result.add(this) }
117 
118         return result
119     }
120 
checkAssertionnull121     fun checkAssertion(assertion: AssertionData): FlickerAssertionError? {
122         require(status != RunStatus.UNDEFINED) { "A valid RunStatus has not been provided" }
123         return try {
124             assertion.checkAssertion(this)
125             null
126         } catch (error: Throwable) {
127             status = RunStatus.ASSERTION_FAILED
128             FlickerAssertionErrorBuilder()
129                     .fromError(error)
130                     .atTag(assertion.tag)
131                     .withTrace(this.mTraceFile)
132                     .build()
133         }
134     }
135 
136     /**
137      * Parse a [trace] into a [SubjectType] asynchronously
138      *
139      * The parsed subject is available in [promise]
140      */
141     class AsyncSubjectParser<SubjectType : FlickerTraceSubject<*>>(
142         val trace: Path,
143         parser: ((Path) -> SubjectType?)?
144     ) {
<lambda>null145         val promise: Deferred<SubjectType?>? = parser?.run { SCOPE.async { parser(trace) } }
146     }
147 
148     class Builder {
149         private var wmTraceData: AsyncSubjectParser<WindowManagerTraceSubject>? = null
150         private var layersTraceData: AsyncSubjectParser<LayersTraceSubject>? = null
151         var screenRecording: Path? = null
152 
153         /**
154          * List of focus events, if collected
155          */
156         var eventLog: List<FocusEvent>? = null
157 
158         /**
159          * Parses a [WindowManagerTraceSubject]
160          *
161          * @param traceFile of the trace file to parse
162          * @param parser lambda to parse the trace into a [WindowManagerTraceSubject]
163          */
setWmTracenull164         fun setWmTrace(traceFile: Path, parser: (Path) -> WindowManagerTraceSubject?) {
165             wmTraceData = AsyncSubjectParser(traceFile, parser)
166         }
167 
168         /**
169          * Parses a [LayersTraceSubject]
170          *
171          * @param traceFile of the trace file to parse
172          * @param parser lambda to parse the trace into a [LayersTraceSubject]
173          */
setLayersTracenull174         fun setLayersTrace(traceFile: Path, parser: (Path) -> LayersTraceSubject?) {
175             layersTraceData = AsyncSubjectParser(traceFile, parser)
176         }
177 
buildResultnull178         private fun buildResult(
179             assertionTag: String,
180             wmSubject: FlickerSubject?,
181             layersSubject: FlickerSubject?,
182             status: RunStatus,
183             traceFile: Path? = null,
184             eventLogSubject: EventLogSubject? = null
185         ): FlickerRunResult {
186             val result = FlickerRunResult(
187                 traceFile,
188                 assertionTag,
189                 wmSubject,
190                 layersSubject,
191                 eventLogSubject
192             )
193             result.status = status
194             return result
195         }
196 
197         /**
198          * Builds a new [FlickerRunResult] for a trace
199          *
200          * @param assertionTag Tag to associate with the result
201          * @param wmTrace WindowManager trace
202          * @param layersTrace Layers trace
203          */
buildStateResultnull204         fun buildStateResult(
205             assertionTag: String,
206             wmTrace: WindowManagerTrace?,
207             layersTrace: LayersTrace?,
208             wmTraceFile: Path?,
209             layersTraceFile: Path?,
210             testName: String,
211             iteration: Int,
212             status: RunStatus
213         ): FlickerRunResult {
214             val wmSubject = wmTrace?.let { WindowManagerTraceSubject.assertThat(it).first() }
215             val layersSubject = layersTrace?.let { LayersTraceSubject.assertThat(it).first() }
216 
217             val traceFiles = mutableListOf<File>()
218             wmTraceFile?.let { traceFiles.add(it.toFile()) }
219             layersTraceFile?.let { traceFiles.add(it.toFile()) }
220             val traceFile = compress(traceFiles, "${assertionTag}_${testName}_$iteration.zip")
221 
222             return buildResult(assertionTag, wmSubject, layersSubject, status,
223                     traceFile = traceFile)
224         }
225 
226         @VisibleForTesting
buildEventLogResultnull227         fun buildEventLogResult(status: RunStatus): FlickerRunResult {
228             val events = eventLog ?: emptyList()
229             return buildResult(
230                 AssertionTag.ALL,
231                 wmSubject = null,
232                 layersSubject = null,
233                 eventLogSubject = EventLogSubject.assertThat(events),
234                 status = status
235             )
236         }
237 
238         @VisibleForTesting
buildTraceResultsnull239         fun buildTraceResults(
240             testName: String,
241             iteration: Int,
242             status: RunStatus
243         ): List<FlickerRunResult> = runBlocking {
244             val wmSubject = wmTraceData?.promise?.await()
245             val layersSubject = layersTraceData?.promise?.await()
246 
247             val traceFile = compress(testName, iteration)
248             val traceResult = buildResult(
249                 AssertionTag.ALL, wmSubject, layersSubject, traceFile = traceFile, status = status)
250 
251             val initialStateResult = buildResult(
252                 AssertionTag.START, wmSubject?.first(), layersSubject?.first(), status = status)
253             initialStateResult.mTraceFile = traceResult.mTraceFile
254 
255             val finalStateResult = buildResult(
256                 AssertionTag.END, wmSubject?.last(), layersSubject?.last(), status = status)
257             finalStateResult.mTraceFile = traceResult.mTraceFile
258 
259             listOf(initialStateResult, finalStateResult, traceResult)
260         }
261 
compressnull262         private fun compress(testName: String, iteration: Int): Path? {
263             val traceFiles = mutableListOf<File>()
264             wmTraceData?.trace?.let { traceFiles.add(it.toFile()) }
265             layersTraceData?.trace?.let { traceFiles.add(it.toFile()) }
266             screenRecording?.let { traceFiles.add(it.toFile()) }
267 
268             return compress(traceFiles, "${testName}_$iteration.zip")
269         }
270 
compressnull271         private fun compress(traceFiles: List<File>, archiveName: String): Path? {
272             val files = traceFiles.filter { it.exists() }
273             if (files.isEmpty()) {
274                 return null
275             }
276 
277             val firstFile = files.first()
278             val compressedFile = firstFile.resolveSibling(archiveName)
279             ZipUtil.createZip(traceFiles, compressedFile)
280             traceFiles.forEach {
281                 it.delete()
282             }
283 
284             return compressedFile.toPath()
285         }
286 
buildAllnull287         fun buildAll(testName: String, iteration: Int, status: RunStatus): List<FlickerRunResult> {
288             val results = buildTraceResults(testName, iteration, status).toMutableList()
289             if (eventLog != null) {
290                 results.add(buildEventLogResult(status = status))
291             }
292 
293             return results
294         }
295 
setResultFromnull296         fun setResultFrom(resultSetter: IResultSetter) {
297             resultSetter.setResult(this)
298         }
299     }
300 
301     interface IResultSetter {
setResultnull302         fun setResult(builder: Builder)
303     }
304 
305     companion object {
306         private val SCOPE = CoroutineScope(Dispatchers.IO + SupervisorJob())
307 
308         enum class RunStatus(val prefix: String = "", val isFailure: Boolean) {
309             UNDEFINED("???", false),
310 
311             RUN_SUCCESS("UNCHECKED", false),
312             ASSERTION_SUCCESS("PASS", false),
313 
314             RUN_FAILED("FAILED_RUN", true),
315             PARSING_FAILURE("FAILED_PARSING", true),
316             ASSERTION_FAILED("FAIL", true);
317 
318             companion object {
319                 fun merge(runStatuses: List<RunStatus>): RunStatus {
320                     val precedence = listOf(ASSERTION_FAILED, RUN_FAILED, ASSERTION_SUCCESS)
321                     for (status in precedence) {
322                         if (runStatuses.any { it == status }) {
323                             return status
324                         }
325                     }
326 
327                     return UNDEFINED
328                 }
329             }
330         }
331     }
332 }
333