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