• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 ANYf 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 {InMemoryStorage} from 'common/in_memory_storage';
19import {TracePositionUpdate} from 'messaging/winscope_event';
20import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils';
21import {TraceBuilder} from 'test/unit/trace_builder';
22import {TreeNodeUtils} from 'test/unit/tree_node_utils';
23import {UnitTestUtils} from 'test/unit/utils';
24import {Parser} from 'trace/parser';
25import {Trace} from 'trace/trace';
26import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
27import {Presenter} from './presenter';
28import {UiData, UiDataEntryType} from './ui_data';
29
30describe('PresenterTransactions', () => {
31  let parser: Parser<PropertyTreeNode>;
32  let trace: Trace<PropertyTreeNode>;
33  let presenter: Presenter;
34  let outputUiData: undefined | UiData;
35  const TOTAL_OUTPUT_ENTRIES = 1647;
36
37  beforeAll(async () => {
38    jasmine.addCustomEqualityTester(TreeNodeUtils.treeNodeEqualityTester);
39    parser = (await UnitTestUtils.getParser(
40      'traces/elapsed_and_real_timestamp/Transactions.pb',
41    )) as Parser<PropertyTreeNode>;
42  });
43
44  beforeEach(async () => {
45    outputUiData = undefined;
46    await setUpTestEnvironment();
47  });
48
49  it('is robust to empty trace', async () => {
50    const trace = new TraceBuilder<PropertyTreeNode>().setEntries([]).build();
51    presenter = new Presenter(trace, new InMemoryStorage(), (data: UiData) => {
52      outputUiData = data;
53    });
54
55    expect(outputUiData).toEqual(UiData.EMPTY);
56
57    const expectedUiData = UiData.EMPTY;
58    expectedUiData.propertiesUserOptions = {
59      showDefaults: {
60        name: 'Show defaults',
61        enabled: false,
62        tooltip: `
63                If checked, shows the value of all properties.
64                Otherwise, hides all properties whose value is
65                the default for its data type.
66              `,
67      },
68    };
69    await presenter.onAppEvent(
70      TracePositionUpdate.fromTimestamp(
71        TimestampConverterUtils.makeRealTimestamp(10n),
72      ),
73    );
74    expect(outputUiData).toEqual(UiData.EMPTY);
75  });
76
77  it('processes trace position update and computes output UI data', async () => {
78    await presenter.onAppEvent(createTracePositionUpdate(0));
79    checkInitialTracePositionUpdate();
80
81    expect(assertDefined(outputUiData).allPids).toEqual([
82      'N/A',
83      '0',
84      '515',
85      '1593',
86      '2022',
87      '2322',
88      '2463',
89      '3300',
90    ]);
91    expect(assertDefined(outputUiData).allUids).toEqual([
92      'N/A',
93      '1000',
94      '1003',
95      '10169',
96      '10235',
97      '10239',
98    ]);
99    expect(assertDefined(outputUiData).allTypes).toEqual([
100      'DISPLAY_CHANGED',
101      'LAYER_ADDED',
102      'LAYER_CHANGED',
103      'LAYER_DESTROYED',
104      'LAYER_HANDLE_DESTROYED',
105      'NO_OP',
106    ]);
107
108    expect(assertDefined(outputUiData).allTransactionIds.length).toEqual(1295);
109    expect(assertDefined(outputUiData).allLayerAndDisplayIds.length).toEqual(
110      117,
111    );
112    expect(assertDefined(outputUiData).entries.length).toEqual(
113      TOTAL_OUTPUT_ENTRIES,
114    );
115  });
116
117  it('processes trace position update and updates current entry and scroll position', async () => {
118    await presenter.onAppEvent(createTracePositionUpdate(0));
119    checkInitialTracePositionUpdate();
120
121    await presenter.onAppEvent(createTracePositionUpdate(10));
122    expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
123    expect(assertDefined(outputUiData).scrollToIndex).toEqual(13);
124  });
125
126  it('filters entries according to transaction ID filter', () => {
127    presenter.onTransactionIdFilterChanged([]);
128    expect(assertDefined(outputUiData).entries.length).toEqual(
129      TOTAL_OUTPUT_ENTRIES,
130    );
131
132    presenter.onTransactionIdFilterChanged(['2211908157465']);
133    expect(
134      new Set(
135        assertDefined(outputUiData).entries.map((entry) => entry.transactionId),
136      ),
137    ).toEqual(new Set(['2211908157465']));
138  });
139
140  it('filters entries according to VSYNC ID filter', () => {
141    presenter.onVSyncIdFilterChanged([]);
142    expect(assertDefined(outputUiData).entries.length).toEqual(
143      TOTAL_OUTPUT_ENTRIES,
144    );
145
146    presenter.onVSyncIdFilterChanged(['1']);
147    expect(
148      new Set(
149        assertDefined(outputUiData).entries.map((entry) => entry.vsyncId),
150      ),
151    ).toEqual(new Set([1]));
152
153    presenter.onVSyncIdFilterChanged(['1', '3', '10']);
154    expect(
155      new Set(
156        assertDefined(outputUiData).entries.map((entry) => entry.vsyncId),
157      ),
158    ).toEqual(new Set([1, 3, 10]));
159  });
160
161  it('filters entries according to PID filter', () => {
162    presenter.onPidFilterChanged([]);
163    expect(
164      new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid)),
165    ).toEqual(
166      new Set(['N/A', '0', '515', '1593', '2022', '2322', '2463', '3300']),
167    );
168
169    presenter.onPidFilterChanged(['0']);
170    expect(
171      new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid)),
172    ).toEqual(new Set(['0']));
173
174    presenter.onPidFilterChanged(['0', '515']);
175    expect(
176      new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid)),
177    ).toEqual(new Set(['0', '515']));
178  });
179
180  it('filters entries according to UID filter', () => {
181    presenter.onUidFilterChanged([]);
182    expect(
183      new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid)),
184    ).toEqual(new Set(['N/A', '1000', '1003', '10169', '10235', '10239']));
185
186    presenter.onUidFilterChanged(['1000']);
187    expect(
188      new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid)),
189    ).toEqual(new Set(['1000']));
190
191    presenter.onUidFilterChanged(['1000', '1003']);
192    expect(
193      new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid)),
194    ).toEqual(new Set(['1000', '1003']));
195  });
196
197  it('filters entries according to type filter', () => {
198    presenter.onTypeFilterChanged([]);
199    expect(
200      new Set(assertDefined(outputUiData).entries.map((entry) => entry.type)),
201    ).toEqual(
202      new Set([
203        UiDataEntryType.DISPLAY_CHANGED,
204        UiDataEntryType.LAYER_ADDED,
205        UiDataEntryType.LAYER_CHANGED,
206        UiDataEntryType.LAYER_DESTROYED,
207        UiDataEntryType.LAYER_HANDLE_DESTROYED,
208        UiDataEntryType.NO_OP,
209      ]),
210    );
211
212    presenter.onTypeFilterChanged([UiDataEntryType.LAYER_ADDED]);
213    expect(
214      new Set(assertDefined(outputUiData).entries.map((entry) => entry.type)),
215    ).toEqual(new Set([UiDataEntryType.LAYER_ADDED]));
216
217    presenter.onTypeFilterChanged([
218      UiDataEntryType.LAYER_ADDED,
219      UiDataEntryType.LAYER_DESTROYED,
220    ]);
221    expect(
222      new Set(assertDefined(outputUiData).entries.map((entry) => entry.type)),
223    ).toEqual(
224      new Set([UiDataEntryType.LAYER_ADDED, UiDataEntryType.LAYER_DESTROYED]),
225    );
226  });
227
228  it('filters entries according to layer or display ID filter', () => {
229    presenter.onLayerIdFilterChanged([]);
230    expect(
231      new Set(
232        assertDefined(outputUiData).entries.map(
233          (entry) => entry.layerOrDisplayId,
234        ),
235      ).size,
236    ).toBeGreaterThan(20);
237
238    presenter.onLayerIdFilterChanged(['1']);
239    expect(
240      new Set(
241        assertDefined(outputUiData).entries.map(
242          (entry) => entry.layerOrDisplayId,
243        ),
244      ),
245    ).toEqual(new Set(['1']));
246
247    presenter.onLayerIdFilterChanged(['1', '3']);
248    expect(
249      new Set(
250        assertDefined(outputUiData).entries.map(
251          (entry) => entry.layerOrDisplayId,
252        ),
253      ),
254    ).toEqual(new Set(['1', '3']));
255  });
256
257  it('includes no op transitions', () => {
258    presenter.onTypeFilterChanged([UiDataEntryType.NO_OP]);
259    expect(
260      new Set(assertDefined(outputUiData).entries.map((entry) => entry.type)),
261    ).toEqual(new Set([UiDataEntryType.NO_OP]));
262
263    for (const entry of assertDefined(outputUiData).entries) {
264      expect(entry.layerOrDisplayId).toEqual('');
265      expect(entry.what).toEqual('');
266      expect(entry.propertiesTree).toEqual(undefined);
267    }
268  });
269
270  it('filters entries according to "what" search string', () => {
271    expect(assertDefined(outputUiData).entries.length).toEqual(
272      TOTAL_OUTPUT_ENTRIES,
273    );
274
275    presenter.onWhatFilterChanged([]);
276    expect(assertDefined(outputUiData).entries.length).toEqual(
277      TOTAL_OUTPUT_ENTRIES,
278    );
279
280    presenter.onWhatFilterChanged(['Crop']);
281    expect(assertDefined(outputUiData).entries.length).toBeLessThan(
282      TOTAL_OUTPUT_ENTRIES,
283    );
284
285    presenter.onWhatFilterChanged(['STRING_WITH_NO_MATCHES']);
286    expect(assertDefined(outputUiData).entries.length).toEqual(0);
287  });
288
289  it('updates selected entry ui data when entry clicked', async () => {
290    await presenter.onAppEvent(createTracePositionUpdate(0));
291    checkInitialTracePositionUpdate();
292
293    const newIndex = 10;
294    presenter.onEntryClicked(newIndex);
295    checkSelectedEntryUiData(newIndex);
296    expect(assertDefined(outputUiData).scrollToIndex).toEqual(undefined); // no scrolling
297
298    // does not remove selection when entry clicked again
299    presenter.onEntryClicked(newIndex);
300    checkSelectedEntryUiData(newIndex);
301    expect(assertDefined(outputUiData).scrollToIndex).toEqual(undefined);
302  });
303
304  it('updates selected entry ui data when entry changed by key press', async () => {
305    await presenter.onAppEvent(createTracePositionUpdate(0));
306    checkInitialTracePositionUpdate();
307
308    const newIndex = 10;
309    presenter.onEntryChangedByKeyPress(newIndex);
310    checkSelectedEntryUiData(newIndex);
311    expect(assertDefined(outputUiData).scrollToIndex).toEqual(newIndex);
312  });
313
314  it('computes current entry index', async () => {
315    await presenter.onAppEvent(createTracePositionUpdate(0));
316    expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
317
318    await presenter.onAppEvent(createTracePositionUpdate(10));
319    expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
320  });
321
322  it('updates current entry index when filters change', async () => {
323    await presenter.onAppEvent(createTracePositionUpdate(10));
324
325    presenter.onPidFilterChanged([]);
326    expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
327
328    presenter.onPidFilterChanged(['0']);
329    expect(assertDefined(outputUiData).currentEntryIndex).toEqual(10);
330
331    presenter.onPidFilterChanged(['0', '515']);
332    expect(assertDefined(outputUiData).currentEntryIndex).toEqual(11);
333
334    presenter.onPidFilterChanged(['0', '515', 'N/A']);
335    expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
336  });
337
338  it('formats entry time', async () => {
339    await setUpTestEnvironment();
340    expect(
341      assertDefined(outputUiData).entries[0].time.formattedValue(),
342    ).toEqual('2022-08-03, 06:19:01.051');
343  });
344
345  async function setUpTestEnvironment() {
346    trace = new TraceBuilder<PropertyTreeNode>().setParser(parser).build();
347    presenter = new Presenter(trace, new InMemoryStorage(), (data: UiData) => {
348      outputUiData = data;
349    });
350    await presenter.onAppEvent(createTracePositionUpdate(0)); // trigger initialization
351  }
352
353  function createTracePositionUpdate(entryIndex: number): TracePositionUpdate {
354    const entry = trace.getEntry(entryIndex);
355    return TracePositionUpdate.fromTraceEntry(entry);
356  }
357
358  function checkInitialTracePositionUpdate() {
359    const uiData = assertDefined(outputUiData);
360    expect(uiData.currentEntryIndex).toEqual(0);
361    expect(uiData.selectedEntryIndex).toBeUndefined();
362    expect(uiData.scrollToIndex).toEqual(0);
363    expect(assertDefined(uiData.currentPropertiesTree).id).toEqual(
364      assertDefined(uiData.entries[0].propertiesTree).id,
365    );
366  }
367
368  function checkSelectedEntryUiData(index: number) {
369    const uiData = assertDefined(outputUiData);
370    expect(uiData.currentEntryIndex).toEqual(0);
371    expect(uiData.selectedEntryIndex).toEqual(index);
372    expect(assertDefined(uiData.currentPropertiesTree).id).toEqual(
373      assertDefined(uiData.entries[index].propertiesTree).id,
374    );
375  }
376});
377