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 {TimeRange} from 'common/time';
19import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils';
20import {TracesBuilder} from 'test/unit/traces_builder';
21import {TraceBuilder} from 'test/unit/trace_builder';
22import {TracePosition} from 'trace/trace_position';
23import {TraceType} from 'trace/trace_type';
24import {TimelineData} from './timeline_data';
25
26describe('TimelineData', () => {
27  let timelineData: TimelineData;
28
29  const timestamp0 = TimestampConverterUtils.makeRealTimestamp(0n);
30  const timestamp5 = TimestampConverterUtils.makeRealTimestamp(5n);
31  const timestamp9 = TimestampConverterUtils.makeRealTimestamp(9n);
32  const timestamp10 = TimestampConverterUtils.makeRealTimestamp(10n);
33  const timestamp11 = TimestampConverterUtils.makeRealTimestamp(11n);
34
35  const traces = new TracesBuilder()
36    .setTimestamps(TraceType.PROTO_LOG, [timestamp9])
37    .setTimestamps(TraceType.EVENT_LOG, [timestamp9])
38    .setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10])
39    .setTimestamps(TraceType.WINDOW_MANAGER, [timestamp11])
40    .build();
41
42  const traceSf = assertDefined(traces.getTrace(TraceType.SURFACE_FLINGER));
43  const traceWm = assertDefined(traces.getTrace(TraceType.WINDOW_MANAGER));
44
45  const position10 = TracePosition.fromTraceEntry(
46    assertDefined(traces.getTrace(TraceType.SURFACE_FLINGER)).getEntry(0),
47  );
48  const position11 = TracePosition.fromTraceEntry(
49    assertDefined(traces.getTrace(TraceType.WINDOW_MANAGER)).getEntry(0),
50  );
51  const position1000 = TracePosition.fromTimestamp(
52    TimestampConverterUtils.makeRealTimestamp(1000n),
53  );
54
55  beforeEach(() => {
56    timelineData = new TimelineData();
57  });
58
59  it('can be initialized', () => {
60    expect(timelineData.getCurrentPosition()).toBeUndefined();
61
62    timelineData.initialize(
63      traces,
64      undefined,
65      TimestampConverterUtils.TIMESTAMP_CONVERTER,
66    );
67    expect(timelineData.getCurrentPosition()).toBeDefined();
68  });
69
70  describe('dumps', () => {
71    const traces = new TracesBuilder()
72      .setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10, timestamp11])
73      .setTimestamps(TraceType.WINDOW_MANAGER, [timestamp0])
74      .build();
75
76    const dumpWm = assertDefined(traces.getTrace(TraceType.WINDOW_MANAGER));
77
78    it('drops trace if it is a dump (will not display in timeline UI)', () => {
79      timelineData.initialize(
80        traces,
81        undefined,
82        TimestampConverterUtils.TIMESTAMP_CONVERTER,
83      );
84      expect(
85        timelineData.getTraces().getTrace(TraceType.WINDOW_MANAGER),
86      ).toBeUndefined();
87      expect(timelineData.getFullTimeRange().from).toBe(timestamp10);
88      expect(timelineData.getFullTimeRange().to).toBe(timestamp11);
89    });
90
91    it('is robust to prev/next entry request of a dump', () => {
92      timelineData.initialize(
93        traces,
94        undefined,
95        TimestampConverterUtils.TIMESTAMP_CONVERTER,
96      );
97      expect(timelineData.getPreviousEntryFor(dumpWm)).toBeUndefined();
98      expect(timelineData.getNextEntryFor(dumpWm)).toBeUndefined();
99    });
100  });
101
102  it('uses first entry of first active trace by default', () => {
103    timelineData.initialize(
104      traces,
105      undefined,
106      TimestampConverterUtils.TIMESTAMP_CONVERTER,
107    );
108    expect(timelineData.getCurrentPosition()).toEqual(position10);
109  });
110
111  it('uses explicit position if set', () => {
112    timelineData.initialize(
113      traces,
114      undefined,
115      TimestampConverterUtils.TIMESTAMP_CONVERTER,
116    );
117    expect(timelineData.getCurrentPosition()).toEqual(position10);
118
119    timelineData.setPosition(position1000);
120    expect(timelineData.getCurrentPosition()).toEqual(position1000);
121
122    timelineData.trySetActiveTrace(traceSf);
123    expect(timelineData.getCurrentPosition()).toEqual(position1000);
124
125    timelineData.trySetActiveTrace(traceWm);
126    expect(timelineData.getCurrentPosition()).toEqual(position1000);
127  });
128
129  it('sets active trace and update current position accordingly', () => {
130    timelineData.initialize(
131      traces,
132      undefined,
133      TimestampConverterUtils.TIMESTAMP_CONVERTER,
134    );
135
136    expect(timelineData.getCurrentPosition()).toEqual(position10);
137
138    timelineData.trySetActiveTrace(traceWm);
139    expect(timelineData.getCurrentPosition()).toEqual(position11);
140
141    timelineData.trySetActiveTrace(traceSf);
142    expect(timelineData.getCurrentPosition()).toEqual(position10);
143  });
144
145  it('does not set active trace if not present in timeline, or already set', () => {
146    timelineData.initialize(
147      traces,
148      undefined,
149      TimestampConverterUtils.TIMESTAMP_CONVERTER,
150    );
151
152    expect(timelineData.getCurrentPosition()).toEqual(position10);
153
154    let success = timelineData.trySetActiveTrace(traceWm);
155    expect(timelineData.getActiveTrace()).toEqual(traceWm);
156    expect(success).toBeTrue();
157
158    success = timelineData.trySetActiveTrace(traceWm);
159    expect(timelineData.getActiveTrace()).toEqual(traceWm);
160    expect(success).toBeFalse();
161
162    success = timelineData.trySetActiveTrace(
163      new TraceBuilder<{}>()
164        .setType(TraceType.SURFACE_FLINGER)
165        .setEntries([])
166        .build(),
167    );
168    expect(timelineData.getActiveTrace()).toEqual(traceWm);
169    expect(success).toBeFalse();
170  });
171
172  it('hasTimestamps()', () => {
173    expect(timelineData.hasTimestamps()).toBeFalse();
174
175    // no trace
176    {
177      const traces = new TracesBuilder().build();
178      timelineData.initialize(
179        traces,
180        undefined,
181        TimestampConverterUtils.TIMESTAMP_CONVERTER,
182      );
183      expect(timelineData.hasTimestamps()).toBeFalse();
184    }
185    // trace without timestamps
186    {
187      const traces = new TracesBuilder()
188        .setTimestamps(TraceType.SURFACE_FLINGER, [])
189        .build();
190      timelineData.initialize(
191        traces,
192        undefined,
193        TimestampConverterUtils.TIMESTAMP_CONVERTER,
194      );
195      expect(timelineData.hasTimestamps()).toBeFalse();
196    }
197    // trace with timestamps
198    {
199      const traces = new TracesBuilder()
200        .setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10])
201        .build();
202      timelineData.initialize(
203        traces,
204        undefined,
205        TimestampConverterUtils.TIMESTAMP_CONVERTER,
206      );
207      expect(timelineData.hasTimestamps()).toBeTrue();
208    }
209  });
210
211  it('hasMoreThanOneDistinctTimestamp()', () => {
212    expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
213
214    // no trace
215    {
216      const traces = new TracesBuilder().build();
217      timelineData.initialize(
218        traces,
219        undefined,
220        TimestampConverterUtils.TIMESTAMP_CONVERTER,
221      );
222      expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
223    }
224    // no distinct timestamps
225    {
226      const traces = new TracesBuilder()
227        .setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10])
228        .setTimestamps(TraceType.WINDOW_MANAGER, [timestamp10])
229        .build();
230      timelineData.initialize(
231        traces,
232        undefined,
233        TimestampConverterUtils.TIMESTAMP_CONVERTER,
234      );
235      expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
236    }
237    // distinct timestamps
238    {
239      const traces = new TracesBuilder()
240        .setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10])
241        .setTimestamps(TraceType.WINDOW_MANAGER, [timestamp11])
242        .build();
243      timelineData.initialize(
244        traces,
245        undefined,
246        TimestampConverterUtils.TIMESTAMP_CONVERTER,
247      );
248      expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeTrue();
249    }
250  });
251
252  it('getCurrentPosition() returns same object if no change to range', () => {
253    timelineData.initialize(
254      traces,
255      undefined,
256      TimestampConverterUtils.TIMESTAMP_CONVERTER,
257    );
258
259    expect(timelineData.getCurrentPosition()).toBe(
260      timelineData.getCurrentPosition(),
261    );
262
263    timelineData.setPosition(position11);
264
265    expect(timelineData.getCurrentPosition()).toBe(
266      timelineData.getCurrentPosition(),
267    );
268  });
269
270  it('makePositionFromActiveTrace()', () => {
271    timelineData.initialize(
272      traces,
273      undefined,
274      TimestampConverterUtils.TIMESTAMP_CONVERTER,
275    );
276    const time100 = TimestampConverterUtils.makeRealTimestamp(100n);
277
278    {
279      timelineData.trySetActiveTrace(traceSf);
280      const position = timelineData.makePositionFromActiveTrace(time100);
281      expect(position.timestamp).toEqual(time100);
282      expect(position.entry).toEqual(traceSf.getEntry(0));
283    }
284
285    {
286      timelineData.trySetActiveTrace(traceWm);
287      const position = timelineData.makePositionFromActiveTrace(time100);
288      expect(position.timestamp).toEqual(time100);
289      expect(position.entry).toEqual(traceWm.getEntry(0));
290    }
291  });
292
293  it('getFullTimeRange() returns same object if no change to range', () => {
294    timelineData.initialize(
295      traces,
296      undefined,
297      TimestampConverterUtils.TIMESTAMP_CONVERTER,
298    );
299
300    expect(timelineData.getFullTimeRange()).toBe(
301      timelineData.getFullTimeRange(),
302    );
303  });
304
305  it('getSelectionTimeRange() returns same object if no change to range', () => {
306    timelineData.initialize(
307      traces,
308      undefined,
309      TimestampConverterUtils.TIMESTAMP_CONVERTER,
310    );
311
312    expect(timelineData.getSelectionTimeRange()).toBe(
313      timelineData.getSelectionTimeRange(),
314    );
315
316    timelineData.setSelectionTimeRange(new TimeRange(timestamp0, timestamp5));
317
318    expect(timelineData.getSelectionTimeRange()).toBe(
319      timelineData.getSelectionTimeRange(),
320    );
321  });
322
323  it('getZoomRange() returns same object if no change to range', () => {
324    timelineData.initialize(
325      traces,
326      undefined,
327      TimestampConverterUtils.TIMESTAMP_CONVERTER,
328    );
329
330    expect(timelineData.getZoomRange()).toBe(timelineData.getZoomRange());
331
332    timelineData.setZoom(new TimeRange(timestamp0, timestamp5));
333
334    expect(timelineData.getZoomRange()).toBe(timelineData.getZoomRange());
335  });
336
337  it("getCurrentPosition() prioritizes active trace's first entry", () => {
338    timelineData.initialize(
339      traces,
340      undefined,
341      TimestampConverterUtils.TIMESTAMP_CONVERTER,
342    );
343    timelineData.trySetActiveTrace(traceWm);
344
345    expect(timelineData.getCurrentPosition()?.timestamp).toBe(timestamp11);
346  });
347});
348