Vishnu Nair | 8248b7c | 2018-08-01 10:13:36 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 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 android.annotation.Nullable; |
| 20 | import android.support.annotation.VisibleForTesting; |
Vishnu Nair | 8248b7c | 2018-08-01 10:13:36 -0700 | [diff] [blame] | 21 | import android.util.Log; |
| 22 | |
Brett Chabot | 502ec7a | 2019-03-01 14:43:20 -0800 | [diff] [blame^] | 23 | import androidx.test.InstrumentationRegistry; |
| 24 | |
Vishnu Nair | 8248b7c | 2018-08-01 10:13:36 -0700 | [diff] [blame] | 25 | import com.android.server.wm.flicker.monitor.ITransitionMonitor; |
| 26 | import com.android.server.wm.flicker.monitor.LayersTraceMonitor; |
| 27 | import com.android.server.wm.flicker.monitor.ScreenRecorder; |
| 28 | import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor; |
| 29 | import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor; |
| 30 | |
| 31 | import com.google.common.io.Files; |
| 32 | |
| 33 | import java.io.IOException; |
| 34 | import java.nio.file.Path; |
| 35 | import java.util.ArrayList; |
| 36 | import java.util.LinkedList; |
| 37 | import java.util.List; |
| 38 | |
| 39 | /** |
| 40 | * Builds and runs UI transitions capturing test artifacts. |
| 41 | * |
| 42 | * User can compose a transition from simpler steps, specifying setup and teardown steps. During |
| 43 | * a transition, Layers trace, WindowManager trace, screen recordings and window animation frame |
| 44 | * stats can be captured. |
| 45 | * |
| 46 | * <pre> |
| 47 | * Transition builder options: |
| 48 | * {@link TransitionBuilder#run(Runnable)} run transition under test. Monitors will be started |
| 49 | * before the transition and stopped after the transition is completed. |
| 50 | * {@link TransitionBuilder#repeat(int)} repeat transitions under test multiple times recording |
| 51 | * result for each run. |
| 52 | * {@link TransitionBuilder#withTag(String)} specify a string identifier used to prefix logs and |
| 53 | * artifacts generated. |
| 54 | * {@link TransitionBuilder#runBeforeAll(Runnable)} run setup transitions once before all other |
| 55 | * transition are run to set up an initial state on device. |
| 56 | * {@link TransitionBuilder#runBefore(Runnable)} run setup transitions before each test transition |
| 57 | * run. |
| 58 | * {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions after each test |
| 59 | * transition. |
| 60 | * {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions once after all |
| 61 | * other transition are run. |
| 62 | * {@link TransitionBuilder#includeJankyRuns()} disables {@link WindowAnimationFrameStatsMonitor} |
| 63 | * to monitor janky frames. If janky frames are detected, then the test run is skipped. This |
| 64 | * monitor is enabled by default. |
| 65 | * {@link TransitionBuilder#skipLayersTrace()} disables {@link LayersTraceMonitor} used to |
| 66 | * capture Layers trace during a transition. This monitor is enabled by default. |
| 67 | * {@link TransitionBuilder#skipWindowManagerTrace()} disables {@link WindowManagerTraceMonitor} |
| 68 | * used to capture WindowManager trace during a transition. This monitor is enabled by |
| 69 | * default. |
| 70 | * {@link TransitionBuilder#recordAllRuns()} records the screen contents and saves it to a file. |
| 71 | * All the runs including setup and teardown transitions are included in the recording. This |
| 72 | * monitor is used for debugging purposes. |
| 73 | * {@link TransitionBuilder#recordEachRun()} records the screen contents during test transitions |
| 74 | * and saves it to a file for each run. This monitor is used for debugging purposes. |
| 75 | * |
| 76 | * Example transition to capture WindowManager and Layers trace when opening a test app: |
| 77 | * {@code |
| 78 | * TransitionRunner.newBuilder() |
| 79 | * .withTag("OpenTestAppFast") |
| 80 | * .runBeforeAll(UiAutomationLib::wakeUp) |
| 81 | * .runBeforeAll(UiAutomationLib::UnlockDevice) |
| 82 | * .runBeforeAll(UiAutomationLib::openTestApp) |
| 83 | * .runBefore(UiAutomationLib::closeTestApp) |
| 84 | * .run(UiAutomationLib::openTestApp) |
| 85 | * .runAfterAll(UiAutomationLib::closeTestApp) |
| 86 | * .repeat(5) |
| 87 | * .build() |
| 88 | * .run(); |
| 89 | * } |
| 90 | * </pre> |
| 91 | */ |
| 92 | class TransitionRunner { |
| 93 | private static final String TAG = "FLICKER"; |
| 94 | private final ScreenRecorder mScreenRecorder; |
| 95 | private final WindowManagerTraceMonitor mWmTraceMonitor; |
| 96 | private final LayersTraceMonitor mLayersTraceMonitor; |
| 97 | private final WindowAnimationFrameStatsMonitor mFrameStatsMonitor; |
| 98 | |
| 99 | private final List<ITransitionMonitor> mAllRunsMonitors; |
| 100 | private final List<ITransitionMonitor> mPerRunMonitors; |
| 101 | private final List<Runnable> mBeforeAlls; |
| 102 | private final List<Runnable> mBefores; |
| 103 | private final List<Runnable> mTransitions; |
| 104 | private final List<Runnable> mAfters; |
| 105 | private final List<Runnable> mAfterAlls; |
| 106 | |
| 107 | private final int mIterations; |
| 108 | private final String mTestTag; |
| 109 | |
| 110 | @Nullable |
| 111 | private List<TransitionResult> mResults = null; |
| 112 | |
| 113 | private TransitionRunner(TransitionBuilder builder) { |
| 114 | mScreenRecorder = builder.mScreenRecorder; |
| 115 | mWmTraceMonitor = builder.mWmTraceMonitor; |
| 116 | mLayersTraceMonitor = builder.mLayersTraceMonitor; |
| 117 | mFrameStatsMonitor = builder.mFrameStatsMonitor; |
| 118 | |
| 119 | mAllRunsMonitors = builder.mAllRunsMonitors; |
| 120 | mPerRunMonitors = builder.mPerRunMonitors; |
| 121 | mBeforeAlls = builder.mBeforeAlls; |
| 122 | mBefores = builder.mBefores; |
| 123 | mTransitions = builder.mTransitions; |
| 124 | mAfters = builder.mAfters; |
| 125 | mAfterAlls = builder.mAfterAlls; |
| 126 | |
| 127 | mIterations = builder.mIterations; |
| 128 | mTestTag = builder.mTestTag; |
| 129 | } |
| 130 | |
| 131 | static TransitionBuilder newBuilder() { |
| 132 | return new TransitionBuilder(); |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Runs the composed transition and calls monitors at the appropriate stages. If jank monitor |
| 137 | * is enabled, transitions with jank are skipped. |
| 138 | * |
| 139 | * @return itself |
| 140 | */ |
| 141 | TransitionRunner run() { |
| 142 | mResults = new ArrayList<>(); |
| 143 | mAllRunsMonitors.forEach(ITransitionMonitor::start); |
| 144 | mBeforeAlls.forEach(Runnable::run); |
| 145 | for (int iteration = 0; iteration < mIterations; iteration++) { |
| 146 | mBefores.forEach(Runnable::run); |
| 147 | mPerRunMonitors.forEach(ITransitionMonitor::start); |
| 148 | mTransitions.forEach(Runnable::run); |
| 149 | mPerRunMonitors.forEach(ITransitionMonitor::stop); |
| 150 | mAfters.forEach(Runnable::run); |
| 151 | if (runJankFree() && mFrameStatsMonitor.jankyFramesDetected()) { |
| 152 | String msg = String.format("Skipping iteration %d/%d for test %s due to jank. %s", |
| 153 | iteration, mIterations - 1, mTestTag, mFrameStatsMonitor.toString()); |
| 154 | Log.e(TAG, msg); |
| 155 | continue; |
| 156 | } |
| 157 | mResults.add(saveResult(iteration)); |
| 158 | } |
| 159 | mAfterAlls.forEach(Runnable::run); |
| 160 | mAllRunsMonitors.forEach(monitor -> { |
| 161 | monitor.stop(); |
| 162 | Path path = monitor.save(mTestTag); |
| 163 | Log.e(TAG, "Video saved to " + path.toString()); |
| 164 | }); |
| 165 | return this; |
| 166 | } |
| 167 | |
| 168 | /** |
| 169 | * Returns a list of transition results. |
| 170 | * |
| 171 | * @return list of transition results. |
| 172 | */ |
| 173 | List<TransitionResult> getResults() { |
| 174 | if (mResults == null) { |
| 175 | throw new IllegalStateException("Results do not exist!"); |
| 176 | } |
| 177 | return mResults; |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Deletes all transition results that are not marked for saving. |
| 182 | * |
| 183 | * @return list of transition results. |
| 184 | */ |
| 185 | void deleteResults() { |
| 186 | if (mResults == null) { |
| 187 | return; |
| 188 | } |
| 189 | mResults.stream() |
| 190 | .filter(TransitionResult::canDelete) |
| 191 | .forEach(TransitionResult::delete); |
| 192 | mResults = null; |
| 193 | } |
| 194 | |
| 195 | /** |
| 196 | * Saves monitor results to file. |
| 197 | * |
| 198 | * @return object containing paths to test artifacts |
| 199 | */ |
| 200 | private TransitionResult saveResult(int iteration) { |
| 201 | Path windowTrace = null; |
| 202 | Path layerTrace = null; |
| 203 | Path screenCaptureVideo = null; |
| 204 | |
| 205 | if (mPerRunMonitors.contains(mWmTraceMonitor)) { |
| 206 | windowTrace = mWmTraceMonitor.save(mTestTag, iteration); |
| 207 | } |
| 208 | if (mPerRunMonitors.contains(mLayersTraceMonitor)) { |
| 209 | layerTrace = mLayersTraceMonitor.save(mTestTag, iteration); |
| 210 | } |
| 211 | if (mPerRunMonitors.contains(mScreenRecorder)) { |
| 212 | screenCaptureVideo = mScreenRecorder.save(mTestTag, iteration); |
| 213 | } |
| 214 | return new TransitionResult(layerTrace, windowTrace, screenCaptureVideo); |
| 215 | } |
| 216 | |
| 217 | private boolean runJankFree() { |
| 218 | return mPerRunMonitors.contains(mFrameStatsMonitor); |
| 219 | } |
| 220 | |
| 221 | public String getTestTag() { |
| 222 | return mTestTag; |
| 223 | } |
| 224 | |
| 225 | /** |
| 226 | * Stores paths to all test artifacts. |
| 227 | */ |
| 228 | @VisibleForTesting |
| 229 | public static class TransitionResult { |
| 230 | @Nullable |
| 231 | final Path layersTrace; |
| 232 | @Nullable |
| 233 | final Path windowManagerTrace; |
| 234 | @Nullable |
| 235 | final Path screenCaptureVideo; |
| 236 | private boolean flaggedForSaving; |
| 237 | |
| 238 | TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace, |
| 239 | @Nullable Path screenCaptureVideo) { |
| 240 | this.layersTrace = layersTrace; |
| 241 | this.windowManagerTrace = windowManagerTrace; |
| 242 | this.screenCaptureVideo = screenCaptureVideo; |
| 243 | } |
| 244 | |
| 245 | void flagForSaving() { |
| 246 | flaggedForSaving = true; |
| 247 | } |
| 248 | |
| 249 | boolean canDelete() { |
| 250 | return !flaggedForSaving; |
| 251 | } |
| 252 | |
| 253 | boolean layersTraceExists() { |
| 254 | return layersTrace != null && layersTrace.toFile().exists(); |
| 255 | } |
| 256 | |
| 257 | byte[] getLayersTrace() { |
| 258 | try { |
| 259 | return Files.toByteArray(this.layersTrace.toFile()); |
| 260 | } catch (IOException e) { |
| 261 | throw new RuntimeException(e); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | Path getLayersTracePath() { |
| 266 | return layersTrace; |
| 267 | } |
| 268 | |
| 269 | boolean windowManagerTraceExists() { |
| 270 | return windowManagerTrace != null && windowManagerTrace.toFile().exists(); |
| 271 | } |
| 272 | |
| 273 | public byte[] getWindowManagerTrace() { |
| 274 | try { |
| 275 | return Files.toByteArray(this.windowManagerTrace.toFile()); |
| 276 | } catch (IOException e) { |
| 277 | throw new RuntimeException(e); |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | Path getWindowManagerTracePath() { |
| 282 | return windowManagerTrace; |
| 283 | } |
| 284 | |
| 285 | boolean screenCaptureVideoExists() { |
| 286 | return screenCaptureVideo != null && screenCaptureVideo.toFile().exists(); |
| 287 | } |
| 288 | |
| 289 | Path screenCaptureVideoPath() { |
| 290 | return screenCaptureVideo; |
| 291 | } |
| 292 | |
| 293 | void delete() { |
| 294 | if (layersTraceExists()) layersTrace.toFile().delete(); |
| 295 | if (windowManagerTraceExists()) windowManagerTrace.toFile().delete(); |
| 296 | if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete(); |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | /** |
| 301 | * Builds a {@link TransitionRunner} instance. |
| 302 | */ |
| 303 | static class TransitionBuilder { |
| 304 | private ScreenRecorder mScreenRecorder; |
| 305 | private WindowManagerTraceMonitor mWmTraceMonitor; |
| 306 | private LayersTraceMonitor mLayersTraceMonitor; |
| 307 | private WindowAnimationFrameStatsMonitor mFrameStatsMonitor; |
| 308 | |
| 309 | private List<ITransitionMonitor> mAllRunsMonitors = new LinkedList<>(); |
| 310 | private List<ITransitionMonitor> mPerRunMonitors = new LinkedList<>(); |
| 311 | private List<Runnable> mBeforeAlls = new LinkedList<>(); |
| 312 | private List<Runnable> mBefores = new LinkedList<>(); |
| 313 | private List<Runnable> mTransitions = new LinkedList<>(); |
| 314 | private List<Runnable> mAfters = new LinkedList<>(); |
| 315 | private List<Runnable> mAfterAlls = new LinkedList<>(); |
| 316 | |
| 317 | private boolean mRunJankFree = true; |
| 318 | private boolean mCaptureWindowManagerTrace = true; |
| 319 | private boolean mCaptureLayersTrace = true; |
| 320 | private boolean mRecordEachRun = false; |
| 321 | private int mIterations = 1; |
| 322 | private String mTestTag = ""; |
| 323 | |
| 324 | private boolean mRecordAllRuns = false; |
| 325 | |
| 326 | TransitionBuilder() { |
| 327 | mScreenRecorder = new ScreenRecorder(); |
| 328 | mWmTraceMonitor = new WindowManagerTraceMonitor(); |
| 329 | mLayersTraceMonitor = new LayersTraceMonitor(); |
| 330 | mFrameStatsMonitor = new |
| 331 | WindowAnimationFrameStatsMonitor(InstrumentationRegistry.getInstrumentation()); |
| 332 | } |
| 333 | |
| 334 | TransitionRunner build() { |
| 335 | if (mCaptureWindowManagerTrace) { |
| 336 | mPerRunMonitors.add(mWmTraceMonitor); |
| 337 | } |
| 338 | |
| 339 | if (mCaptureLayersTrace) { |
| 340 | mPerRunMonitors.add(mLayersTraceMonitor); |
| 341 | } |
| 342 | |
| 343 | if (mRunJankFree) { |
| 344 | mPerRunMonitors.add(mFrameStatsMonitor); |
| 345 | } |
| 346 | |
| 347 | if (mRecordAllRuns) { |
| 348 | mAllRunsMonitors.add(mScreenRecorder); |
| 349 | } |
| 350 | |
| 351 | if (mRecordEachRun) { |
| 352 | mPerRunMonitors.add(mScreenRecorder); |
| 353 | } |
| 354 | |
| 355 | return new TransitionRunner(this); |
| 356 | } |
| 357 | |
| 358 | TransitionBuilder runBeforeAll(Runnable runnable) { |
| 359 | mBeforeAlls.add(runnable); |
| 360 | return this; |
| 361 | } |
| 362 | |
| 363 | TransitionBuilder runBefore(Runnable runnable) { |
| 364 | mBefores.add(runnable); |
| 365 | return this; |
| 366 | } |
| 367 | |
| 368 | TransitionBuilder run(Runnable runnable) { |
| 369 | mTransitions.add(runnable); |
| 370 | return this; |
| 371 | } |
| 372 | |
| 373 | TransitionBuilder runAfter(Runnable runnable) { |
| 374 | mAfters.add(runnable); |
| 375 | return this; |
| 376 | } |
| 377 | |
| 378 | TransitionBuilder runAfterAll(Runnable runnable) { |
| 379 | mAfterAlls.add(runnable); |
| 380 | return this; |
| 381 | } |
| 382 | |
| 383 | TransitionBuilder repeat(int iterations) { |
| 384 | mIterations = iterations; |
| 385 | return this; |
| 386 | } |
| 387 | |
| 388 | TransitionBuilder skipWindowManagerTrace() { |
| 389 | mCaptureWindowManagerTrace = false; |
| 390 | return this; |
| 391 | } |
| 392 | |
| 393 | TransitionBuilder skipLayersTrace() { |
| 394 | mCaptureLayersTrace = false; |
| 395 | return this; |
| 396 | } |
| 397 | |
| 398 | TransitionBuilder includeJankyRuns() { |
| 399 | mRunJankFree = false; |
| 400 | return this; |
| 401 | } |
| 402 | |
| 403 | TransitionBuilder recordEachRun() { |
| 404 | if (mRecordAllRuns) { |
| 405 | throw new IllegalArgumentException("Invalid option with recordAllRuns"); |
| 406 | } |
| 407 | mRecordEachRun = true; |
| 408 | return this; |
| 409 | } |
| 410 | |
| 411 | TransitionBuilder recordAllRuns() { |
| 412 | if (mRecordEachRun) { |
| 413 | throw new IllegalArgumentException("Invalid option with recordEachRun"); |
| 414 | } |
| 415 | mRecordAllRuns = true; |
| 416 | return this; |
| 417 | } |
| 418 | |
| 419 | TransitionBuilder withTag(String testTag) { |
| 420 | mTestTag = testTag; |
| 421 | return this; |
| 422 | } |
| 423 | } |
| 424 | } |