1/* 2 * Copyright (C) 2022 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 17import {assertDefined} from 'common/assert_utils'; 18import {FileUtils} from 'common/file_utils'; 19import {ProgressListenerStub} from 'messaging/progress_listener_stub'; 20import {UserWarning} from 'messaging/user_warning'; 21import { 22 CorruptedArchive, 23 InvalidPerfettoTrace, 24 NoInputFiles, 25 TraceOverridden, 26 UnsupportedFileFormat, 27} from 'messaging/user_warnings'; 28import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils'; 29import {TracesUtils} from 'test/unit/traces_utils'; 30import {UnitTestUtils} from 'test/unit/utils'; 31import {TraceType} from 'trace/trace_type'; 32import {FilesSource} from './files_source'; 33import {TracePipeline} from './trace_pipeline'; 34 35describe('TracePipeline', () => { 36 let validSfFile: File; 37 let validWmFile: File; 38 let warnings: UserWarning[]; 39 let progressListener: ProgressListenerStub; 40 let tracePipeline: TracePipeline; 41 42 beforeEach(async () => { 43 jasmine.addCustomEqualityTester(UnitTestUtils.timestampEqualityTester); 44 validSfFile = await UnitTestUtils.getFixtureFile( 45 'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb', 46 ); 47 validWmFile = await UnitTestUtils.getFixtureFile( 48 'traces/elapsed_and_real_timestamp/WindowManager.pb', 49 ); 50 51 warnings = []; 52 53 progressListener = new ProgressListenerStub(); 54 spyOn(progressListener, 'onProgressUpdate'); 55 spyOn(progressListener, 'onOperationFinished'); 56 57 tracePipeline = new TracePipeline(); 58 }); 59 60 it('can load valid trace files', async () => { 61 expect(tracePipeline.getTraces().getSize()).toEqual(0); 62 63 await loadFiles([validSfFile, validWmFile], FilesSource.TEST); 64 await expectLoadResult(2, []); 65 66 expect(tracePipeline.getDownloadArchiveFilename()).toMatch( 67 new RegExp(`${FilesSource.TEST}_`), 68 ); 69 expect(tracePipeline.getTraces().getSize()).toEqual(2); 70 71 const traceEntries = await TracesUtils.extractEntries( 72 tracePipeline.getTraces(), 73 ); 74 expect(traceEntries.get(TraceType.WINDOW_MANAGER)?.length).toBeGreaterThan( 75 0, 76 ); 77 expect(traceEntries.get(TraceType.SURFACE_FLINGER)?.length).toBeGreaterThan( 78 0, 79 ); 80 }); 81 82 it('can load valid gzipped file', async () => { 83 expect(tracePipeline.getTraces().getSize()).toEqual(0); 84 85 const gzippedFile = await UnitTestUtils.getFixtureFile( 86 'traces/WindowManager.pb.gz', 87 ); 88 89 await loadFiles([gzippedFile], FilesSource.TEST); 90 await expectLoadResult(1, []); 91 92 expect(tracePipeline.getTraces().getSize()).toEqual(1); 93 94 const traceEntries = await TracesUtils.extractEntries( 95 tracePipeline.getTraces(), 96 ); 97 expect(traceEntries.get(TraceType.WINDOW_MANAGER)?.length).toBeGreaterThan( 98 0, 99 ); 100 }); 101 102 it('can set download archive filename based on files source', async () => { 103 await loadFiles([validSfFile]); 104 await expectLoadResult(1, []); 105 expect(tracePipeline.getDownloadArchiveFilename()).toMatch( 106 new RegExp('SurfaceFlinger_'), 107 ); 108 109 tracePipeline.clear(); 110 111 await loadFiles([validSfFile, validWmFile], FilesSource.COLLECTED); 112 await expectLoadResult(2, []); 113 expect(tracePipeline.getDownloadArchiveFilename()).toMatch( 114 new RegExp(`${FilesSource.COLLECTED}_`), 115 ); 116 }); 117 118 it('can convert illegal uploaded archive filename to legal name for download archive', async () => { 119 const fileWithIllegalName = await UnitTestUtils.getFixtureFile( 120 'traces/SFtrace(with_illegal_characters).pb', 121 ); 122 await loadFiles([fileWithIllegalName]); 123 await expectLoadResult(1, []); 124 const downloadFilename = tracePipeline.getDownloadArchiveFilename(); 125 expect(FileUtils.DOWNLOAD_FILENAME_REGEX.test(downloadFilename)).toBeTrue(); 126 }); 127 128 it('detects bugreports and filters out files based on their directory', async () => { 129 expect(tracePipeline.getTraces().getSize()).toEqual(0); 130 131 const bugreportFiles = [ 132 await UnitTestUtils.getFixtureFile( 133 'bugreports/main_entry.txt', 134 'main_entry.txt', 135 ), 136 await UnitTestUtils.getFixtureFile( 137 'bugreports/bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt', 138 'bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt', 139 ), 140 await UnitTestUtils.getFixtureFile( 141 'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb', 142 'FS/data/misc/wmtrace/surface_flinger.bp', 143 ), 144 await UnitTestUtils.getFixtureFile( 145 'traces/elapsed_and_real_timestamp/wm_transition_trace.pb', 146 'FS/data/misc/ignored-dir/window_manager.bp', 147 ), 148 ]; 149 150 const bugreportArchive = new File( 151 [await FileUtils.createZipArchive(bugreportFiles)], 152 'bugreport.zip', 153 ); 154 155 // Corner case: 156 // Another file is loaded along the bugreport -> the file must not be ignored 157 // 158 // Note: 159 // The even weirder corner case where two bugreports are loaded at the same time is 160 // currently not properly handled. 161 const otherFile = await UnitTestUtils.getFixtureFile( 162 'traces/elapsed_and_real_timestamp/InputMethodClients.pb', 163 'would-be-ignored-if-was-in-bugreport-archive/input_method_clients.pb', 164 ); 165 166 await loadFiles([bugreportArchive, otherFile]); 167 await expectLoadResult(2, []); 168 169 const traces = tracePipeline.getTraces(); 170 expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeDefined(); 171 expect(traces.getTrace(TraceType.WINDOW_MANAGER)).toBeUndefined(); // ignored 172 expect(traces.getTrace(TraceType.INPUT_METHOD_CLIENTS)).toBeDefined(); 173 }); 174 175 it('detects bugreports and extracts timezone info, then calculates utc offset', async () => { 176 const bugreportFiles = [ 177 await UnitTestUtils.getFixtureFile( 178 'bugreports/main_entry.txt', 179 'main_entry.txt', 180 ), 181 await UnitTestUtils.getFixtureFile( 182 'bugreports/bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt', 183 'bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt', 184 ), 185 await UnitTestUtils.getFixtureFile( 186 'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb', 187 'FS/data/misc/wmtrace/surface_flinger.bp', 188 ), 189 ]; 190 const bugreportArchive = new File( 191 [await FileUtils.createZipArchive(bugreportFiles)], 192 'bugreport.zip', 193 ); 194 195 await loadFiles([bugreportArchive]); 196 await expectLoadResult(1, []); 197 198 const timestampConverter = tracePipeline.getTimestampConverter(); 199 expect(timestampConverter); 200 expect(timestampConverter.getUTCOffset()).toEqual('UTC+05:30'); 201 202 const expectedTimestamp = 203 TimestampConverterUtils.makeRealTimestampWithUTCOffset( 204 1659107089102062832n, 205 ); 206 expect( 207 timestampConverter.makeTimestampFromMonotonicNs(14500282843n), 208 ).toEqual(expectedTimestamp); 209 }); 210 211 it('is robust to corrupted archive', async () => { 212 const corruptedArchive = await UnitTestUtils.getFixtureFile( 213 'corrupted_archive.zip', 214 ); 215 216 await loadFiles([corruptedArchive]); 217 218 await expectLoadResult(0, [ 219 new CorruptedArchive(corruptedArchive), 220 new NoInputFiles(), 221 ]); 222 }); 223 224 it('is robust to invalid trace files', async () => { 225 const invalidFiles = [ 226 await UnitTestUtils.getFixtureFile('winscope_homepage.jpg'), 227 ]; 228 229 await loadFiles(invalidFiles); 230 231 await expectLoadResult(0, [ 232 new UnsupportedFileFormat('winscope_homepage.jpg'), 233 ]); 234 }); 235 236 it('is robust to invalid perfetto trace files', async () => { 237 const invalidFiles = [ 238 await UnitTestUtils.getFixtureFile( 239 'traces/perfetto/invalid_protolog.perfetto-trace', 240 ), 241 ]; 242 243 await loadFiles(invalidFiles); 244 245 await expectLoadResult(0, [ 246 new InvalidPerfettoTrace('invalid_protolog.perfetto-trace', [ 247 'Perfetto trace has no IME Clients entries', 248 'Perfetto trace has no IME system_server entries', 249 'Perfetto trace has no IME Service entries', 250 'Perfetto trace has no ProtoLog entries', 251 'Perfetto trace has no Surface Flinger entries', 252 'Perfetto trace has no Transactions entries', 253 'Perfetto trace has no Transitions entries', 254 'Perfetto trace has no ViewCapture windows', 255 'Perfetto trace has no Motion Events entries', 256 'Perfetto trace has no Key Events entries', 257 ]), 258 ]); 259 }); 260 261 it('is robust to mixed valid and invalid trace files', async () => { 262 expect(tracePipeline.getTraces().getSize()).toEqual(0); 263 const files = [ 264 await UnitTestUtils.getFixtureFile('winscope_homepage.jpg'), 265 await UnitTestUtils.getFixtureFile('traces/dump_WindowManager.pb'), 266 ]; 267 268 await loadFiles(files); 269 270 await expectLoadResult(1, [ 271 new UnsupportedFileFormat('winscope_homepage.jpg'), 272 ]); 273 }); 274 275 it('can remove traces', async () => { 276 await loadFiles([validSfFile, validWmFile]); 277 await expectLoadResult(2, []); 278 279 const sfTrace = assertDefined( 280 tracePipeline.getTraces().getTrace(TraceType.SURFACE_FLINGER), 281 ); 282 const wmTrace = assertDefined( 283 tracePipeline.getTraces().getTrace(TraceType.WINDOW_MANAGER), 284 ); 285 286 tracePipeline.removeTrace(sfTrace); 287 await expectLoadResult(1, []); 288 289 tracePipeline.removeTrace(wmTrace); 290 await expectLoadResult(0, []); 291 }); 292 293 it('gets loaded traces', async () => { 294 await loadFiles([validSfFile, validWmFile]); 295 await expectLoadResult(2, []); 296 297 const traces = tracePipeline.getTraces(); 298 299 const actualTraceTypes = new Set(traces.mapTrace((trace) => trace.type)); 300 const expectedTraceTypes = new Set([ 301 TraceType.SURFACE_FLINGER, 302 TraceType.WINDOW_MANAGER, 303 ]); 304 expect(actualTraceTypes).toEqual(expectedTraceTypes); 305 306 const sfTrace = assertDefined(traces.getTrace(TraceType.SURFACE_FLINGER)); 307 expect(sfTrace.getDescriptors().length).toBeGreaterThan(0); 308 }); 309 310 it('gets screenrecording data', async () => { 311 const files = [ 312 await UnitTestUtils.getFixtureFile( 313 'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4', 314 ), 315 ]; 316 await loadFiles(files); 317 await expectLoadResult(1, []); 318 319 const video = await tracePipeline.getScreenRecordingVideo(); 320 expect(video).toBeDefined(); 321 expect(video?.size).toBeGreaterThan(0); 322 }); 323 324 it('gets screenshot data', async () => { 325 const files = [await UnitTestUtils.getFixtureFile('traces/screenshot.png')]; 326 await loadFiles(files); 327 await expectLoadResult(1, []); 328 329 const video = await tracePipeline.getScreenRecordingVideo(); 330 expect(video).toBeDefined(); 331 expect(video?.size).toBeGreaterThan(0); 332 }); 333 334 it('prioritises screenrecording over screenshot data', async () => { 335 const files = [ 336 await UnitTestUtils.getFixtureFile('traces/screenshot.png'), 337 await UnitTestUtils.getFixtureFile( 338 'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4', 339 ), 340 ]; 341 await loadFiles(files); 342 await expectLoadResult(1, [ 343 new TraceOverridden('screenshot.png', TraceType.SCREEN_RECORDING), 344 ]); 345 346 const video = await tracePipeline.getScreenRecordingVideo(); 347 expect(video).toBeDefined(); 348 expect(video?.size).toBeGreaterThan(0); 349 }); 350 351 it('creates traces with correct type', async () => { 352 await loadFiles([validSfFile, validWmFile]); 353 await expectLoadResult(2, []); 354 355 const traces = tracePipeline.getTraces(); 356 traces.forEachTrace((trace, type) => { 357 expect(trace.type).toEqual(type); 358 }); 359 }); 360 361 it('creates zip archive with loaded trace files', async () => { 362 const files = [ 363 await UnitTestUtils.getFixtureFile( 364 'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4', 365 ), 366 await UnitTestUtils.getFixtureFile( 367 'traces/perfetto/transactions_trace.perfetto-trace', 368 ), 369 ]; 370 await loadFiles(files); 371 await expectLoadResult(2, []); 372 373 const archiveBlob = 374 await tracePipeline.makeZipArchiveWithLoadedTraceFiles(); 375 const actualFiles = await FileUtils.unzipFile(archiveBlob); 376 const actualFilenames = actualFiles 377 .map((file) => { 378 return file.name; 379 }) 380 .sort(); 381 382 const expectedFilenames = [ 383 'screen_recording_metadata_v2.mp4', 384 'transactions_trace.perfetto-trace', 385 ]; 386 387 expect(actualFilenames).toEqual(expectedFilenames); 388 }); 389 390 it('can be cleared', async () => { 391 await loadFiles([validSfFile, validWmFile]); 392 await expectLoadResult(2, []); 393 394 tracePipeline.clear(); 395 expect(tracePipeline.getTraces().getSize()).toEqual(0); 396 }); 397 398 it('can filter traces without visualization', async () => { 399 const shellTransitionFile = await UnitTestUtils.getFixtureFile( 400 'traces/elapsed_and_real_timestamp/shell_transition_trace.pb', 401 ); 402 await loadFiles([validSfFile, shellTransitionFile]); 403 await expectLoadResult(2, []); 404 405 tracePipeline.filterTracesWithoutVisualization(); 406 expect(tracePipeline.getTraces().getSize()).toEqual(1); 407 expect( 408 tracePipeline.getTraces().getTrace(TraceType.SHELL_TRANSITION), 409 ).toBeUndefined(); 410 }); 411 412 async function loadFiles( 413 files: File[], 414 source: FilesSource = FilesSource.TEST, 415 ) { 416 const notificationListener = { 417 onNotifications(notifications: UserWarning[]) { 418 warnings.push(...notifications); 419 }, 420 }; 421 await tracePipeline.loadFiles( 422 files, 423 source, 424 notificationListener, 425 progressListener, 426 ); 427 expect(progressListener.onOperationFinished).toHaveBeenCalled(); 428 await tracePipeline.buildTraces(); 429 } 430 431 async function expectLoadResult( 432 numberOfTraces: number, 433 expectedWarnings: UserWarning[], 434 ) { 435 expect(warnings).toEqual(expectedWarnings); 436 expect(tracePipeline.getTraces().getSize()).toEqual(numberOfTraces); 437 } 438}); 439