• 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 {TracePositionUpdate} from 'messaging/winscope_event';
19import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
20import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils';
21import {TracesBuilder} from 'test/unit/traces_builder';
22import {TraceBuilder} from 'test/unit/trace_builder';
23import {Trace} from 'trace/trace';
24import {TraceType} from 'trace/trace_type';
25import {
26  DEFAULT_PROPERTY_FORMATTER,
27  TIMESTAMP_NODE_FORMATTER,
28} from 'trace/tree_node/formatters';
29import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
30import {Presenter} from './presenter';
31import {UiData, UiDataMessage} from './ui_data';
32
33describe('ViewerProtoLogPresenter', () => {
34  let presenter: Presenter;
35  let inputMessages: UiDataMessage[];
36  let trace: Trace<PropertyTreeNode>;
37  let positionUpdate10: TracePositionUpdate;
38  let positionUpdate11: TracePositionUpdate;
39  let positionUpdate12: TracePositionUpdate;
40  let outputUiData: undefined | UiData;
41
42  beforeEach(async () => {
43    const time10 = TimestampConverterUtils.makeRealTimestamp(10n);
44    const time11 = TimestampConverterUtils.makeRealTimestamp(11n);
45    const time12 = TimestampConverterUtils.makeRealTimestamp(12n);
46    const elapsedTime10 = TimestampConverterUtils.makeElapsedTimestamp(10n);
47    const elapsedTime20 = TimestampConverterUtils.makeElapsedTimestamp(20n);
48    const elapsedTime30 = TimestampConverterUtils.makeElapsedTimestamp(30n);
49
50    const entries = [
51      new PropertyTreeBuilder()
52        .setRootId('ProtologTrace')
53        .setName('message')
54        .setChildren([
55          {name: 'text', value: 'text0', formatter: DEFAULT_PROPERTY_FORMATTER},
56          {
57            name: 'timestamp',
58            value: elapsedTime10,
59            formatter: TIMESTAMP_NODE_FORMATTER,
60          },
61          {name: 'tag', value: 'tag0', formatter: DEFAULT_PROPERTY_FORMATTER},
62          {
63            name: 'level',
64            value: 'level0',
65            formatter: DEFAULT_PROPERTY_FORMATTER,
66          },
67          {
68            name: 'at',
69            value: 'sourcefile0',
70            formatter: DEFAULT_PROPERTY_FORMATTER,
71          },
72        ])
73        .build(),
74
75      new PropertyTreeBuilder()
76        .setRootId('ProtologTrace')
77        .setName('message')
78        .setChildren([
79          {name: 'text', value: 'text1', formatter: DEFAULT_PROPERTY_FORMATTER},
80          {
81            name: 'timestamp',
82            value: elapsedTime20,
83            formatter: TIMESTAMP_NODE_FORMATTER,
84          },
85          {name: 'tag', value: 'tag1', formatter: DEFAULT_PROPERTY_FORMATTER},
86          {
87            name: 'level',
88            value: 'level1',
89            formatter: DEFAULT_PROPERTY_FORMATTER,
90          },
91          {
92            name: 'at',
93            value: 'sourcefile1',
94            formatter: DEFAULT_PROPERTY_FORMATTER,
95          },
96        ])
97        .build(),
98
99      new PropertyTreeBuilder()
100        .setRootId('ProtologTrace')
101        .setName('message')
102        .setChildren([
103          {name: 'text', value: 'text2', formatter: DEFAULT_PROPERTY_FORMATTER},
104          {
105            name: 'timestamp',
106            value: elapsedTime30,
107            formatter: TIMESTAMP_NODE_FORMATTER,
108          },
109          {name: 'tag', value: 'tag2', formatter: DEFAULT_PROPERTY_FORMATTER},
110          {
111            name: 'level',
112            value: 'level2',
113            formatter: DEFAULT_PROPERTY_FORMATTER,
114          },
115          {
116            name: 'at',
117            value: 'sourcefile2',
118            formatter: DEFAULT_PROPERTY_FORMATTER,
119          },
120        ])
121        .build(),
122    ];
123
124    inputMessages = [
125      {
126        traceIndex: 0,
127        text: 'text0',
128        time: assertDefined(entries[0].getChildByName('timestamp')),
129        tag: 'tag0',
130        level: 'level0',
131        at: 'sourcefile0',
132      },
133      {
134        traceIndex: 1,
135        text: 'text1',
136        time: assertDefined(entries[1].getChildByName('timestamp')),
137        tag: 'tag1',
138        level: 'level1',
139        at: 'sourcefile1',
140      },
141      {
142        traceIndex: 2,
143        text: 'text2',
144        time: assertDefined(entries[2].getChildByName('timestamp')),
145        tag: 'tag2',
146        level: 'level2',
147        at: 'sourcefile2',
148      },
149    ];
150
151    trace = new TraceBuilder<PropertyTreeNode>()
152      .setEntries(entries)
153      .setTimestamps([time10, time11, time12])
154      .build();
155
156    positionUpdate10 = TracePositionUpdate.fromTimestamp(time10);
157    positionUpdate11 = TracePositionUpdate.fromTimestamp(time11);
158    positionUpdate12 = TracePositionUpdate.fromTimestamp(time12);
159
160    outputUiData = undefined;
161
162    presenter = new Presenter(trace, (data: UiData) => {
163      outputUiData = data;
164    });
165    await presenter.onAppEvent(positionUpdate10); // trigger initialization
166  });
167
168  it('is robust to empty trace', async () => {
169    const traces = new TracesBuilder()
170      .setEntries(TraceType.PROTO_LOG, [])
171      .build();
172    const trace = new TraceBuilder<PropertyTreeNode>().setEntries([]).build();
173    presenter = new Presenter(trace, (data: UiData) => {
174      outputUiData = data;
175    });
176
177    const uiData = assertDefined(outputUiData);
178    expect(uiData.messages).toEqual([]);
179    expect(uiData.currentMessageIndex).toBeUndefined();
180
181    await presenter.onAppEvent(positionUpdate10);
182
183    const newUiData = assertDefined(outputUiData);
184    expect(newUiData.messages).toEqual([]);
185    expect(newUiData.currentMessageIndex).toBeUndefined();
186  });
187
188  it('processes trace position updates', async () => {
189    await presenter.onAppEvent(positionUpdate10);
190
191    const uiData = assertDefined(outputUiData);
192    expect(uiData.allLogLevels).toEqual(['level0', 'level1', 'level2']);
193    expect(uiData.allTags).toEqual(['tag0', 'tag1', 'tag2']);
194    expect(uiData.allSourceFiles).toEqual([
195      'sourcefile0',
196      'sourcefile1',
197      'sourcefile2',
198    ]);
199    expect(uiData.messages).toEqual(inputMessages);
200    expect(uiData.currentMessageIndex).toEqual(0);
201  });
202
203  it('updates displayed messages according to log levels filter', () => {
204    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
205
206    presenter.onLogLevelsFilterChanged([]);
207    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
208
209    presenter.onLogLevelsFilterChanged(['level1']);
210    expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
211
212    presenter.onLogLevelsFilterChanged(['level0', 'level1', 'level2']);
213    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
214  });
215
216  it('updates displayed messages according to tags filter', () => {
217    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
218
219    presenter.onTagsFilterChanged([]);
220    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
221
222    presenter.onTagsFilterChanged(['tag1']);
223    expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
224
225    presenter.onTagsFilterChanged(['tag0', 'tag1', 'tag2']);
226    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
227  });
228
229  it('updates displayed messages according to source files filter', () => {
230    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
231
232    presenter.onSourceFilesFilterChanged([]);
233    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
234
235    presenter.onSourceFilesFilterChanged(['sourcefile1']);
236    expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
237
238    presenter.onSourceFilesFilterChanged([
239      'sourcefile0',
240      'sourcefile1',
241      'sourcefile2',
242    ]);
243    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
244  });
245
246  it('updates displayed messages according to search string filter', () => {
247    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
248
249    presenter.onSearchStringFilterChanged('');
250    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
251
252    presenter.onSearchStringFilterChanged('text');
253    expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
254
255    presenter.onSearchStringFilterChanged('text0');
256    expect(assertDefined(outputUiData).messages).toEqual([inputMessages[0]]);
257
258    presenter.onSearchStringFilterChanged('text1');
259    expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
260  });
261
262  it('computes current message index', async () => {
263    // Position -> entry #0
264    await presenter.onAppEvent(positionUpdate10);
265    presenter.onLogLevelsFilterChanged([]);
266    expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
267
268    presenter.onLogLevelsFilterChanged(['level0']);
269    expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
270
271    presenter.onLogLevelsFilterChanged([]);
272    expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
273
274    // Position -> entry #1
275    await presenter.onAppEvent(positionUpdate11);
276    presenter.onLogLevelsFilterChanged([]);
277    expect(assertDefined(outputUiData).currentMessageIndex).toEqual(1);
278
279    presenter.onLogLevelsFilterChanged(['level0']);
280    expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
281
282    presenter.onLogLevelsFilterChanged(['level1']);
283    expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
284
285    presenter.onLogLevelsFilterChanged(['level0', 'level1']);
286    expect(assertDefined(outputUiData).currentMessageIndex).toEqual(1);
287
288    // Position -> entry #2
289    await presenter.onAppEvent(positionUpdate12);
290    presenter.onLogLevelsFilterChanged([]);
291    expect(assertDefined(outputUiData).currentMessageIndex).toEqual(2);
292  });
293
294  it('updates selected message index', () => {
295    expect(assertDefined(outputUiData).selectedMessageIndex).toBeUndefined();
296    presenter.onMessageClicked(3);
297    expect(assertDefined(outputUiData).selectedMessageIndex).toEqual(3);
298  });
299});
300