• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2023 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {assertExists} from '../base/logging';
16import {Duration} from '../base/time';
17import {TimeScale} from '../base/time_scale';
18import {Track, TrackRenderContext} from '../public/track';
19import {HighPrecisionTime} from '../base/high_precision_time';
20import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
21import {TrackManagerImpl} from '../core/track_manager';
22
23function makeMockTrack() {
24  return {
25    onCreate: jest.fn(),
26    onUpdate: jest.fn(),
27    onDestroy: jest.fn(),
28
29    render: jest.fn(),
30    onFullRedraw: jest.fn(),
31    getSliceVerticalBounds: jest.fn(),
32    getHeight: jest.fn(),
33    getTrackShellButtons: jest.fn(),
34    onMouseMove: jest.fn(),
35    onMouseClick: jest.fn(),
36    onMouseOut: jest.fn(),
37  };
38}
39
40async function settle() {
41  await new Promise((r) => setTimeout(r, 0));
42}
43
44let mockTrack: ReturnType<typeof makeMockTrack>;
45let td: Track;
46let trackManager: TrackManagerImpl;
47const visibleWindow = new HighPrecisionTimeSpan(HighPrecisionTime.ZERO, 0);
48const dummyCtx: TrackRenderContext = {
49  trackUri: 'foo',
50  ctx: new CanvasRenderingContext2D(),
51  size: {width: 123, height: 123},
52  visibleWindow,
53  resolution: Duration.ZERO,
54  timescale: new TimeScale(visibleWindow, {left: 0, right: 0}),
55};
56
57beforeEach(() => {
58  mockTrack = makeMockTrack();
59  td = {
60    uri: 'test',
61    title: 'foo',
62    track: mockTrack,
63  };
64  trackManager = new TrackManagerImpl();
65  trackManager.registerTrack(td);
66});
67
68describe('TrackManager', () => {
69  it('calls track lifecycle hooks', async () => {
70    const entry = assertExists(trackManager.getTrackFSM(td.uri));
71
72    entry.render(dummyCtx);
73    await settle();
74    expect(mockTrack.onCreate).toHaveBeenCalledTimes(1);
75    expect(mockTrack.onUpdate).toHaveBeenCalledTimes(1);
76
77    // Double flush should destroy all tracks
78    trackManager.flushOldTracks();
79    trackManager.flushOldTracks();
80    await settle();
81    expect(mockTrack.onDestroy).toHaveBeenCalledTimes(1);
82  });
83
84  it('calls onCrate lazily', async () => {
85    // Check we wait until the first call to render before calling onCreate
86    const entry = assertExists(trackManager.getTrackFSM(td.uri));
87    await settle();
88    expect(mockTrack.onCreate).not.toHaveBeenCalled();
89
90    entry.render(dummyCtx);
91    await settle();
92    expect(mockTrack.onCreate).toHaveBeenCalledTimes(1);
93  });
94
95  it('reuses tracks', async () => {
96    const first = assertExists(trackManager.getTrackFSM(td.uri));
97    trackManager.flushOldTracks();
98    first.render(dummyCtx);
99    await settle();
100
101    const second = assertExists(trackManager.getTrackFSM(td.uri));
102    trackManager.flushOldTracks();
103    second.render(dummyCtx);
104    await settle();
105
106    expect(first).toBe(second);
107    // Ensure onCreate called only once
108    expect(mockTrack.onCreate).toHaveBeenCalledTimes(1);
109  });
110
111  it('destroys tracks when they are not resolved for one cycle', async () => {
112    const entry = assertExists(trackManager.getTrackFSM(td.uri));
113    entry.render(dummyCtx);
114
115    // Double flush should destroy all tracks
116    trackManager.flushOldTracks();
117    trackManager.flushOldTracks();
118
119    await settle();
120
121    expect(mockTrack.onDestroy).toHaveBeenCalledTimes(1);
122  });
123
124  it('contains crash inside onCreate()', async () => {
125    const entry = assertExists(trackManager.getTrackFSM(td.uri));
126    const e = new Error();
127
128    // Mock crash inside onCreate
129    mockTrack.onCreate.mockImplementationOnce(() => {
130      throw e;
131    });
132
133    entry.render(dummyCtx);
134    await settle();
135
136    expect(mockTrack.onCreate).toHaveBeenCalledTimes(1);
137    expect(mockTrack.onUpdate).not.toHaveBeenCalled();
138    expect(entry.getError()).toBe(e);
139  });
140
141  it('contains crash inside onUpdate()', async () => {
142    const entry = assertExists(trackManager.getTrackFSM(td.uri));
143    const e = new Error();
144
145    // Mock crash inside onUpdate
146    mockTrack.onUpdate.mockImplementationOnce(() => {
147      throw e;
148    });
149
150    entry.render(dummyCtx);
151    await settle();
152
153    expect(mockTrack.onCreate).toHaveBeenCalledTimes(1);
154    expect(mockTrack.onUpdate).toHaveBeenCalledTimes(1);
155    expect(entry.getError()).toBe(e);
156  });
157
158  it('handles dispose after crash', async () => {
159    const entry = assertExists(trackManager.getTrackFSM(td.uri));
160    const e = new Error();
161
162    // Mock crash inside onUpdate
163    mockTrack.onUpdate.mockImplementationOnce(() => {
164      throw e;
165    });
166
167    entry.render(dummyCtx);
168    await settle();
169
170    // Ensure we don't crash during the next render cycle
171    entry.render(dummyCtx);
172    await settle();
173  });
174});
175