1/* 2 * Copyright (C) 2023 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 {TraceOverridden} from 'messaging/user_warnings'; 18import {getFixtureFile} from 'test/unit/fixture_utils'; 19import {UserNotifierChecker} from 'test/unit/user_notifier_checker'; 20import {TraceFile} from 'trace/trace_file'; 21import {TraceFileFilter} from './trace_file_filter'; 22 23describe('TraceFileFilter', () => { 24 const filter = new TraceFileFilter(); 25 26 // Could be any file, we just need an instance of File to be used as a fake bugreport archive 27 const bugreportArchive = new File( 28 [new ArrayBuffer(0)], 29 'test_bugreport.zip', 30 ) as unknown as File; 31 32 let userNotifierChecker: UserNotifierChecker; 33 34 beforeAll(() => { 35 userNotifierChecker = new UserNotifierChecker(); 36 }); 37 38 beforeEach(() => { 39 userNotifierChecker.reset(); 40 }); 41 42 describe('bugreport (detects it is a bugreport)', () => { 43 it('ignores non-trace dirs', async () => { 44 const pickedBugreportFiles = [ 45 makeTraceFile( 46 'FS/data/misc/wmtrace/surface_flinger.bp', 47 bugreportArchive, 48 ), 49 makeTraceFile('FS/data/misc/wmtrace/transactions.bp', bugreportArchive), 50 makeTraceFile('proto/window_CRITICAL.proto', bugreportArchive), 51 makeTraceFile('proto/input_method_CRITICAL.proto', bugreportArchive), 52 makeTraceFile('proto/SurfaceFlinger_CRITICAL.proto', bugreportArchive), 53 ]; 54 55 const ignoredBugreportFile = makeTraceFile( 56 'FS/data/misc/ignored-dir/wm_transition_trace.bp', 57 bugreportArchive, 58 ); 59 60 const bugreportFiles = [ 61 await makeBugreportMainEntryTraceFile(), 62 await makeBugreportCodenameTraceFile(), 63 ...pickedBugreportFiles, 64 ignoredBugreportFile, 65 ]; 66 67 // Corner case: 68 // A plain trace file is loaded along the bugreport 69 // -> trace file must not be ignored 70 // 71 // Note: 72 // The even weirder corner case where two bugreports are loaded at the same time is 73 // currently not properly handled. 74 const plainTraceFile = makeTraceFile( 75 'would-be-ignored-if-was-part-of-bugreport/input_method_clients.pb', 76 ); 77 78 const result = await filter.filter([...bugreportFiles, plainTraceFile]); 79 expect(result.perfetto).toBeUndefined(); 80 81 const expectedLegacy = new Set([...pickedBugreportFiles, plainTraceFile]); 82 const actualLegacy = new Set(result.legacy); 83 expect(actualLegacy).toEqual(expectedLegacy); 84 userNotifierChecker.expectNone(); 85 }); 86 87 it('picks perfetto systrace.pftrace', async () => { 88 const perfettoSystemTrace = makeTraceFile( 89 'FS/data/misc/perfetto-traces/bugreport/systrace.pftrace', 90 bugreportArchive, 91 ); 92 const bugreportFiles = [ 93 await makeBugreportMainEntryTraceFile(), 94 await makeBugreportCodenameTraceFile(), 95 perfettoSystemTrace, 96 makeTraceFile( 97 'FS/data/misc/perfetto-traces/other.perfetto-trace', 98 bugreportArchive, 99 ), 100 makeTraceFile( 101 'FS/data/misc/perfetto-traces/other.pftrace', 102 bugreportArchive, 103 ), 104 ]; 105 const result = await filter.filter(bugreportFiles); 106 expect(result.perfetto).toEqual(perfettoSystemTrace); 107 expect(result.legacy).toEqual([]); 108 userNotifierChecker.expectNone(); 109 }); 110 111 it('ignores perfetto traces other than systrace.pftrace', async () => { 112 const bugreportFiles = [ 113 await makeBugreportMainEntryTraceFile(), 114 await makeBugreportCodenameTraceFile(), 115 makeTraceFile( 116 'FS/data/misc/perfetto-traces/other.perfetto-trace', 117 bugreportArchive, 118 ), 119 makeTraceFile( 120 'FS/data/misc/perfetto-traces/other.pftrace', 121 bugreportArchive, 122 ), 123 ]; 124 const result = await filter.filter(bugreportFiles); 125 expect(result.perfetto).toBeUndefined(); 126 expect(result.legacy).toEqual([]); 127 userNotifierChecker.expectNone(); 128 }); 129 130 it('identifies timezone information from bugreport codename file', async () => { 131 const legacyFile = makeTraceFile( 132 'proto/window_CRITICAL.proto', 133 bugreportArchive, 134 ); 135 const bugreportFiles = [ 136 await makeBugreportMainEntryTraceFile(), 137 await makeBugreportCodenameTraceFile(), 138 legacyFile, 139 ]; 140 const result = await filter.filter(bugreportFiles); 141 expect(result.legacy).toEqual([legacyFile]); 142 expect(result.perfetto).toBeUndefined(); 143 expect(result.timezoneInfo).toEqual({ 144 timezone: 'Asia/Kolkata', 145 locale: 'en-US', 146 }); 147 userNotifierChecker.expectNone(); 148 }); 149 150 it('unzips trace files within bugreport zip', async () => { 151 const zippedTraceFile = await makeZippedTraceFile(); 152 153 const bugreportFiles = [ 154 await makeBugreportMainEntryTraceFile(), 155 await makeBugreportCodenameTraceFile(), 156 zippedTraceFile, 157 ]; 158 159 const result = await filter.filter(bugreportFiles); 160 expect(result.perfetto).toBeUndefined(); 161 expect(result.legacy.map((file) => file.file.name)).toEqual([ 162 'Surface Flinger/SurfaceFlinger.pb', 163 'Window Manager/WindowManager.pb', 164 ]); 165 userNotifierChecker.expectNone(); 166 }); 167 }); 168 169 describe('plain input (no bugreport)', () => { 170 it('picks perfetto trace with .perfetto-trace extension', async () => { 171 const perfettoTrace = makeTraceFile('file.perfetto-trace'); 172 await checkPerfettoFilePickedWithoutErrors(perfettoTrace); 173 }); 174 175 it('picks perfetto trace with .pftrace extension', async () => { 176 const pftrace = makeTraceFile('file.pftrace'); 177 await checkPerfettoFilePickedWithoutErrors(pftrace); 178 }); 179 180 it('picks perfetto trace with .perfetto extension', async () => { 181 const perfetto = makeTraceFile('file.perfetto'); 182 await checkPerfettoFilePickedWithoutErrors(perfetto); 183 }); 184 185 it('picks perfetto trace with .perfetto-trace.gz extension', async () => { 186 const perfettoTraceGz = makeTraceFile('file.perfetto-trace.gz'); 187 await checkPerfettoFilePickedWithoutErrors(perfettoTraceGz); 188 }); 189 190 it('picks perfetto trace with .pftrace.gz extension', async () => { 191 const pftraceGz = makeTraceFile('file.pftrace.gz'); 192 await checkPerfettoFilePickedWithoutErrors(pftraceGz); 193 }); 194 195 it('picks perfetto trace with .perfetto.gz extension', async () => { 196 const perfettoGz = makeTraceFile('file.perfetto.gz'); 197 await checkPerfettoFilePickedWithoutErrors(perfettoGz); 198 }); 199 200 it('picks largest perfetto trace', async () => { 201 const small = makeTraceFile('small.perfetto-trace', undefined, 10); 202 const medium = makeTraceFile('medium.perfetto-trace', undefined, 20); 203 const large = makeTraceFile('large.perfetto-trace', undefined, 30); 204 const result = await filter.filter([small, large, medium]); 205 expect(result.perfetto).toEqual(large); 206 expect(result.legacy).toEqual([]); 207 userNotifierChecker.expectAdded([ 208 new TraceOverridden(small.getDescriptor()), 209 new TraceOverridden(medium.getDescriptor()), 210 ]); 211 }); 212 213 it('extracts screen recording metadata', async () => { 214 const metadataJson = await makeMetadataJsonFile(); 215 const screenRecording = makeTraceFile('screen_recording.mp4'); 216 const result = await filter.filter([screenRecording, metadataJson]); 217 expect(result.legacy).toEqual([screenRecording]); 218 expect(result.metadata.screenRecordingOffsets).toEqual({ 219 elapsedRealTimeNanos: 0n, 220 realToElapsedTimeOffsetNanos: 1732721670187419904n, 221 }); 222 userNotifierChecker.expectNone(); 223 }); 224 225 async function checkPerfettoFilePickedWithoutErrors( 226 perfettoFile: TraceFile, 227 ) { 228 const result = await filter.filter([perfettoFile]); 229 expect(result.perfetto).toEqual(perfettoFile); 230 expect(result.legacy).toEqual([]); 231 userNotifierChecker.expectNone(); 232 } 233 }); 234 235 function makeTraceFile( 236 filename: string, 237 parentArchive?: File, 238 size?: number, 239 ): TraceFile { 240 size = size ?? 0; 241 const file = new File([new ArrayBuffer(size)], filename); 242 return new TraceFile(file as unknown as File, parentArchive); 243 } 244 245 async function makeBugreportMainEntryTraceFile(): Promise<TraceFile> { 246 const file = await getFixtureFile( 247 'bugreports/main_entry.txt', 248 'main_entry.txt', 249 ); 250 return new TraceFile(file, bugreportArchive); 251 } 252 253 async function makeBugreportCodenameTraceFile(): Promise<TraceFile> { 254 const file = await getFixtureFile( 255 'bugreports/bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt', 256 'bugreport-codename_beta-UPB2.230407.019-2023-05-30-14-33-48.txt', 257 ); 258 return new TraceFile(file, bugreportArchive); 259 } 260 261 async function makeZippedTraceFile(): Promise<TraceFile> { 262 const file = await getFixtureFile( 263 'traces/winscope.zip', 264 'FS/data/misc/wmtrace/winscope.zip', 265 ); 266 return new TraceFile(file, bugreportArchive); 267 } 268 269 async function makeMetadataJsonFile(): Promise<TraceFile> { 270 const file = await getFixtureFile( 271 'traces/elapsed_and_real_timestamp/screen_recording_metadata.json', 272 ); 273 return new TraceFile(file, bugreportArchive); 274 } 275}); 276