1 /*
2  * Copyright (C) 2024 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.flicker.legacy
18 
19 import android.app.Instrumentation
20 import android.tools.flicker.Utils.ALL_MONITORS
21 import android.tools.io.TraceType
22 import android.tools.traces.getDefaultFlickerOutputDir
23 import android.tools.traces.monitors.ITransitionMonitor
24 import android.tools.traces.monitors.NoTraceMonitor
25 import android.tools.traces.monitors.ScreenRecorder
26 import android.tools.traces.parsers.WindowManagerStateHelper
27 import androidx.test.uiautomator.UiDevice
28 import java.io.File
29 
30 /** Build Flicker tests using Flicker DSL */
31 @FlickerDslMarker
32 class FlickerBuilder(
33     private val instrumentation: Instrumentation,
34     private val outputDir: File = getDefaultFlickerOutputDir(),
35     private val wmHelper: WindowManagerStateHelper =
36         WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false),
37     private val setupCommands: MutableList<FlickerTestData.() -> Any> = mutableListOf(),
38     private val transitionCommands: MutableList<FlickerTestData.() -> Any> = mutableListOf(),
39     private val teardownCommands: MutableList<FlickerTestData.() -> Any> = mutableListOf(),
40     val device: UiDevice = UiDevice.getInstance(instrumentation),
41     private val traceMonitors: MutableList<ITransitionMonitor> = ALL_MONITORS.toMutableList()
42 ) {
43     private var usingExistingTraces = false
44 
45     /**
46      * Configure a [ScreenRecorder].
47      *
48      * By default, the tracing is always active. To disable tracing return null
49      */
<lambda>null50     fun withScreenRecorder(screenRecorder: () -> ScreenRecorder?): FlickerBuilder = apply {
51         traceMonitors.removeIf { it is ScreenRecorder }
52         addMonitor(screenRecorder())
53     }
54 
<lambda>null55     fun withoutScreenRecorder(): FlickerBuilder = apply {
56         traceMonitors.removeIf { it is ScreenRecorder }
57     }
58 
59     /** Defines the setup commands executed before the [transitions] to test */
<lambda>null60     fun setup(commands: FlickerTestData.() -> Unit): FlickerBuilder = apply {
61         setupCommands.add(commands)
62     }
63 
64     /** Defines the teardown commands executed after the [transitions] to test */
<lambda>null65     fun teardown(commands: FlickerTestData.() -> Unit): FlickerBuilder = apply {
66         teardownCommands.add(commands)
67     }
68 
69     /** Defines the commands that trigger the behavior to test */
<lambda>null70     fun transitions(command: FlickerTestData.() -> Unit): FlickerBuilder = apply {
71         require(!usingExistingTraces) {
72             "Can't update transition after calling usingExistingTraces"
73         }
74         transitionCommands.add(command)
75     }
76 
77     data class TraceFiles(
78         val wmTrace: File,
79         val perfetto: File,
80         val wmTransitions: File,
81         val shellTransitions: File,
82         val eventLog: File
83     )
84 
85     /** Use pre-executed results instead of running transitions to get the traces */
<lambda>null86     fun usingExistingTraces(_traceFiles: () -> TraceFiles): FlickerBuilder = apply {
87         val traceFiles = _traceFiles()
88         // Remove all trace monitor and use only monitor that read from existing trace file
89         this.traceMonitors.clear()
90         addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.WM, traceFiles.wmTrace) })
91         addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.SF, traceFiles.perfetto) })
92         addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.TRANSACTION, traceFiles.perfetto) })
93         addMonitor(
94             NoTraceMonitor {
95                 it.addTraceResult(TraceType.LEGACY_WM_TRANSITION, traceFiles.wmTransitions)
96             }
97         )
98         addMonitor(
99             NoTraceMonitor {
100                 it.addTraceResult(TraceType.LEGACY_SHELL_TRANSITION, traceFiles.shellTransitions)
101             }
102         )
103         addMonitor(NoTraceMonitor { it.addTraceResult(TraceType.EVENT_LOG, traceFiles.eventLog) })
104 
105         // Remove all transitions execution
106         this.transitionCommands.clear()
107         this.usingExistingTraces = true
108     }
109 
110     /** Creates a new Flicker runner based on the current builder configuration */
buildnull111     fun build(): FlickerTestData {
112         return FlickerTestDataImpl(
113             instrumentation,
114             device,
115             outputDir,
116             traceMonitors,
117             setupCommands,
118             transitionCommands,
119             teardownCommands,
120             wmHelper
121         )
122     }
123 
124     /** Returns a copy of the current builder with the changes of [block] applied */
copynull125     fun copy(block: FlickerBuilder.() -> Unit) =
126         FlickerBuilder(
127                 instrumentation,
128                 outputDir.absoluteFile,
129                 wmHelper,
130                 setupCommands.toMutableList(),
131                 transitionCommands.toMutableList(),
132                 teardownCommands.toMutableList(),
133                 device,
134                 traceMonitors.toMutableList(),
135             )
136             .apply(block)
137 
138     private fun addMonitor(newMonitor: ITransitionMonitor?) {
139         require(!usingExistingTraces) { "Can't add monitors after calling usingExistingTraces" }
140 
141         if (newMonitor != null) {
142             traceMonitors.add(newMonitor)
143         }
144     }
145 }
146