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