• 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 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 {AbtChromeExtensionProtocolStub} from 'abt_chrome_extension/abt_chrome_extension_protocol_stub';
18import {CrossToolProtocolStub} from 'cross_tool/cross_tool_protocol_stub';
19import {ProgressListenerStub} from 'interfaces/progress_listener_stub';
20import {MockStorage} from 'test/unit/mock_storage';
21import {UnitTestUtils} from 'test/unit/utils';
22import {RealTimestamp} from 'trace/timestamp';
23import {TraceFile} from 'trace/trace_file';
24import {TracePosition} from 'trace/trace_position';
25import {ViewerFactory} from 'viewers/viewer_factory';
26import {ViewerStub} from 'viewers/viewer_stub';
27import {AppComponentStub} from './components/app_component_stub';
28import {SnackBarOpenerStub} from './components/snack_bar_opener_stub';
29import {TimelineComponentStub} from './components/timeline/timeline_component_stub';
30import {Mediator} from './mediator';
31import {TimelineData} from './timeline_data';
32import {TracePipeline} from './trace_pipeline';
33
34describe('Mediator', () => {
35  const viewerStub = new ViewerStub('Title');
36  let inputFiles: File[];
37  let tracePipeline: TracePipeline;
38  let timelineData: TimelineData;
39  let abtChromeExtensionProtocol: AbtChromeExtensionProtocolStub;
40  let crossToolProtocol: CrossToolProtocolStub;
41  let appComponent: AppComponentStub;
42  let timelineComponent: TimelineComponentStub;
43  let uploadTracesComponent: ProgressListenerStub;
44  let collectTracesComponent: ProgressListenerStub;
45  let snackBarOpener: SnackBarOpenerStub;
46  let mediator: Mediator;
47
48  const TIMESTAMP_10 = new RealTimestamp(10n);
49  const TIMESTAMP_11 = new RealTimestamp(11n);
50  const POSITION_10 = TracePosition.fromTimestamp(TIMESTAMP_10);
51  const POSITION_11 = TracePosition.fromTimestamp(TIMESTAMP_11);
52
53  beforeAll(async () => {
54    inputFiles = [
55      await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb'),
56      await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/WindowManager.pb'),
57      await UnitTestUtils.getFixtureFile(
58        'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4'
59      ),
60    ];
61  });
62
63  beforeEach(async () => {
64    tracePipeline = new TracePipeline();
65    timelineData = new TimelineData();
66    abtChromeExtensionProtocol = new AbtChromeExtensionProtocolStub();
67    crossToolProtocol = new CrossToolProtocolStub();
68    appComponent = new AppComponentStub();
69    timelineComponent = new TimelineComponentStub();
70    uploadTracesComponent = new ProgressListenerStub();
71    collectTracesComponent = new ProgressListenerStub();
72    snackBarOpener = new SnackBarOpenerStub();
73    mediator = new Mediator(
74      tracePipeline,
75      timelineData,
76      abtChromeExtensionProtocol,
77      crossToolProtocol,
78      appComponent,
79      snackBarOpener,
80      new MockStorage()
81    );
82    mediator.setTimelineComponent(timelineComponent);
83    mediator.setUploadTracesComponent(uploadTracesComponent);
84    mediator.setCollectTracesComponent(collectTracesComponent);
85
86    spyOn(ViewerFactory.prototype, 'createViewers').and.returnValue([viewerStub]);
87  });
88
89  it('handles uploaded traces from Winscope', async () => {
90    const spies = [
91      spyOn(uploadTracesComponent, 'onProgressUpdate'),
92      spyOn(uploadTracesComponent, 'onOperationFinished'),
93      spyOn(timelineData, 'initialize').and.callThrough(),
94      spyOn(appComponent, 'onTraceDataLoaded'),
95      spyOn(viewerStub, 'onTracePositionUpdate'),
96      spyOn(timelineComponent, 'onTracePositionUpdate'),
97      spyOn(crossToolProtocol, 'sendTimestamp'),
98    ];
99
100    await mediator.onWinscopeFilesUploaded(inputFiles);
101
102    expect(uploadTracesComponent.onProgressUpdate).toHaveBeenCalled();
103    expect(uploadTracesComponent.onOperationFinished).toHaveBeenCalled();
104    expect(timelineData.initialize).not.toHaveBeenCalled();
105    expect(appComponent.onTraceDataLoaded).not.toHaveBeenCalled();
106    expect(viewerStub.onTracePositionUpdate).not.toHaveBeenCalled();
107
108    spies.forEach((spy) => {
109      spy.calls.reset();
110    });
111    await mediator.onWinscopeViewTracesRequest();
112
113    expect(uploadTracesComponent.onProgressUpdate).toHaveBeenCalled();
114    expect(uploadTracesComponent.onOperationFinished).toHaveBeenCalled();
115    expect(timelineData.initialize).toHaveBeenCalledTimes(1);
116    expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
117
118    // propagates trace position on viewers creation
119    expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
120    expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
121    expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
122  });
123
124  it('handles collected traces from Winscope', async () => {
125    const spies = [
126      spyOn(collectTracesComponent, 'onProgressUpdate'),
127      spyOn(collectTracesComponent, 'onOperationFinished'),
128      spyOn(timelineData, 'initialize').and.callThrough(),
129      spyOn(appComponent, 'onTraceDataLoaded'),
130      spyOn(viewerStub, 'onTracePositionUpdate'),
131      spyOn(timelineComponent, 'onTracePositionUpdate'),
132      spyOn(crossToolProtocol, 'sendTimestamp'),
133    ];
134
135    await mediator.onWinscopeFilesCollected(inputFiles);
136
137    expect(collectTracesComponent.onProgressUpdate).toHaveBeenCalled();
138    expect(collectTracesComponent.onOperationFinished).toHaveBeenCalled();
139    expect(timelineData.initialize).toHaveBeenCalledTimes(1);
140    expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
141
142    // propagates trace position on viewers creation
143    expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
144    expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
145    expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
146  });
147
148  //TODO: test "bugreport data from cross-tool protocol" when FileUtils is fully compatible with
149  //      Node.js (b/262269229). FileUtils#unzipFile() currently can't execute on Node.js.
150
151  //TODO: test "data from ABT chrome extension" when FileUtils is fully compatible with Node.js
152  //      (b/262269229).
153
154  it('handles start download event from ABT chrome extension', () => {
155    spyOn(uploadTracesComponent, 'onProgressUpdate');
156    expect(uploadTracesComponent.onProgressUpdate).toHaveBeenCalledTimes(0);
157
158    abtChromeExtensionProtocol.onBuganizerAttachmentsDownloadStart();
159    expect(uploadTracesComponent.onProgressUpdate).toHaveBeenCalledTimes(1);
160  });
161
162  it('handles empty downloaded files from ABT chrome extension', async () => {
163    spyOn(uploadTracesComponent, 'onOperationFinished');
164    expect(uploadTracesComponent.onOperationFinished).toHaveBeenCalledTimes(0);
165
166    // Pass files even if empty so that the upload component will update the progress bar
167    // and display error messages
168    await abtChromeExtensionProtocol.onBuganizerAttachmentsDownloaded([]);
169    expect(uploadTracesComponent.onOperationFinished).toHaveBeenCalledTimes(1);
170  });
171
172  it('propagates trace position update from timeline component', async () => {
173    await loadTraceFiles();
174    await mediator.onWinscopeViewTracesRequest();
175
176    spyOn(viewerStub, 'onTracePositionUpdate');
177    spyOn(timelineComponent, 'onTracePositionUpdate');
178    spyOn(crossToolProtocol, 'sendTimestamp');
179    expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(0);
180    expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
181    expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
182
183    // notify position
184    await mediator.onTimelineTracePositionUpdate(POSITION_10);
185    expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
186    expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
187    expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
188
189    // notify position
190    await mediator.onTimelineTracePositionUpdate(POSITION_11);
191    expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
192    expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);
193    expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(2);
194  });
195
196  describe('timestamp received from remote tool', () => {
197    it('propagates trace position update', async () => {
198      await loadTraceFiles();
199      await mediator.onWinscopeViewTracesRequest();
200
201      spyOn(viewerStub, 'onTracePositionUpdate');
202      spyOn(timelineComponent, 'onTracePositionUpdate');
203      expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(0);
204      expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
205
206      // receive timestamp
207      await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
208      expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
209      expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
210
211      // receive timestamp
212      await crossToolProtocol.onTimestampReceived(TIMESTAMP_11);
213      expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
214      expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);
215    });
216
217    it("doesn't propagate timestamp back to remote tool", async () => {
218      await loadTraceFiles();
219      await mediator.onWinscopeViewTracesRequest();
220
221      spyOn(viewerStub, 'onTracePositionUpdate');
222      spyOn(crossToolProtocol, 'sendTimestamp');
223
224      // receive timestamp
225      await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
226      expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
227      expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
228    });
229
230    it('defers propagation till traces are loaded and visualized', async () => {
231      spyOn(timelineComponent, 'onTracePositionUpdate');
232
233      // keep timestamp for later
234      await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
235      expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
236
237      // keep timestamp for later (replace previous one)
238      await crossToolProtocol.onTimestampReceived(TIMESTAMP_11);
239      expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
240
241      // apply timestamp
242      await loadTraceFiles();
243      await mediator.onWinscopeViewTracesRequest();
244      expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledWith(POSITION_11);
245    });
246  });
247
248  const loadTraceFiles = async () => {
249    const traceFiles = inputFiles.map((file) => new TraceFile(file));
250    const errors = await tracePipeline.loadTraceFiles(traceFiles);
251    expect(errors).toEqual([]);
252  };
253});
254