blob: b597ddc50a09ac88303c936029d7f1ac65960644 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {AbtChromeExtensionProtocolStub} from 'abt_chrome_extension/abt_chrome_extension_protocol_stub';
import {CrossToolProtocolStub} from 'cross_tool/cross_tool_protocol_stub';
import {MockStorage} from 'test/unit/mock_storage';
import {UnitTestUtils} from 'test/unit/utils';
import {RealTimestamp} from 'trace/timestamp';
import {TraceFile} from 'trace/trace_file';
import {TracePosition} from 'trace/trace_position';
import {ViewerFactory} from 'viewers/viewer_factory';
import {ViewerStub} from 'viewers/viewer_stub';
import {AppComponentStub} from './components/app_component_stub';
import {TimelineComponentStub} from './components/timeline/timeline_component_stub';
import {UploadTracesComponentStub} from './components/upload_traces_component_stub';
import {Mediator} from './mediator';
import {TimelineData} from './timeline_data';
import {TracePipeline} from './trace_pipeline';
describe('Mediator', () => {
const viewerStub = new ViewerStub('Title');
let tracePipeline: TracePipeline;
let timelineData: TimelineData;
let abtChromeExtensionProtocol: AbtChromeExtensionProtocolStub;
let crossToolProtocol: CrossToolProtocolStub;
let appComponent: AppComponentStub;
let timelineComponent: TimelineComponentStub;
let uploadTracesComponent: UploadTracesComponentStub;
let mediator: Mediator;
const TIMESTAMP_10 = new RealTimestamp(10n);
const TIMESTAMP_11 = new RealTimestamp(11n);
const POSITION_10 = TracePosition.fromTimestamp(TIMESTAMP_10);
const POSITION_11 = TracePosition.fromTimestamp(TIMESTAMP_11);
beforeEach(async () => {
timelineComponent = new TimelineComponentStub();
tracePipeline = new TracePipeline();
timelineData = new TimelineData();
abtChromeExtensionProtocol = new AbtChromeExtensionProtocolStub();
crossToolProtocol = new CrossToolProtocolStub();
appComponent = new AppComponentStub();
timelineComponent = new TimelineComponentStub();
uploadTracesComponent = new UploadTracesComponentStub();
mediator = new Mediator(
tracePipeline,
timelineData,
abtChromeExtensionProtocol,
crossToolProtocol,
appComponent,
new MockStorage()
);
mediator.setTimelineComponent(timelineComponent);
mediator.setUploadTracesComponent(uploadTracesComponent);
spyOn(ViewerFactory.prototype, 'createViewers').and.returnValue([viewerStub]);
});
it('handles data load event from Winscope', async () => {
spyOn(timelineData, 'initialize').and.callThrough();
spyOn(appComponent, 'onTraceDataLoaded');
spyOn(viewerStub, 'onTracePositionUpdate');
await loadTraces();
expect(timelineData.initialize).toHaveBeenCalledTimes(0);
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledTimes(0);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(0);
mediator.onWinscopeTraceDataLoaded();
expect(timelineData.initialize).toHaveBeenCalledTimes(1);
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
// notifies viewer about current timestamp on creation
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
});
//TODO: test "bugreport data from cross-tool protocol" when FileUtils is fully compatible with
// Node.js (b/262269229). FileUtils#unzipFile() currently can't execute on Node.js.
//TODO: test "data from ABT chrome extension" when FileUtils is fully compatible with Node.js
// (b/262269229).
it('handles start download event from ABT chrome extension', () => {
spyOn(uploadTracesComponent, 'onFilesDownloadStart');
expect(uploadTracesComponent.onFilesDownloadStart).toHaveBeenCalledTimes(0);
abtChromeExtensionProtocol.onBuganizerAttachmentsDownloadStart();
expect(uploadTracesComponent.onFilesDownloadStart).toHaveBeenCalledTimes(1);
});
it('handles empty downloaded files from ABT chrome extension', async () => {
spyOn(uploadTracesComponent, 'onFilesDownloaded');
expect(uploadTracesComponent.onFilesDownloaded).toHaveBeenCalledTimes(0);
// Pass files even if empty so that the upload component will update the progress bar
// and display error messages
await abtChromeExtensionProtocol.onBuganizerAttachmentsDownloaded([]);
expect(uploadTracesComponent.onFilesDownloaded).toHaveBeenCalledTimes(1);
});
it('propagates trace position update from timeline data', async () => {
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
spyOn(viewerStub, 'onTracePositionUpdate');
spyOn(timelineComponent, 'onTracePositionUpdate');
spyOn(crossToolProtocol, 'sendTimestamp');
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(0);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
// notify timestamp
timelineData.setPosition(POSITION_10);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
// notify same timestamp again (ignored, no timestamp change)
timelineData.setPosition(POSITION_10);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
// notify another timestamp
timelineData.setPosition(POSITION_11);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(2);
});
describe('timestamp received from remote tool', () => {
it('propagates trace position update', async () => {
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
spyOn(viewerStub, 'onTracePositionUpdate');
spyOn(timelineComponent, 'onTracePositionUpdate');
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(0);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
// receive timestamp
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
// receive same timestamp again (ignored, no timestamp change)
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
// receive another
await crossToolProtocol.onTimestampReceived(TIMESTAMP_11);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);
});
it("doesn't propagate timestamp back to remote tool", async () => {
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
spyOn(viewerStub, 'onTracePositionUpdate');
spyOn(crossToolProtocol, 'sendTimestamp');
// receive timestamp
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
});
it('defers propagation till traces are loaded and visualized', async () => {
spyOn(timelineComponent, 'onTracePositionUpdate');
// keep timestamp for later
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
// keep timestamp for later (replace previous one)
await crossToolProtocol.onTimestampReceived(TIMESTAMP_11);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
// apply timestamp
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledWith(POSITION_11);
});
});
const loadTraces = async () => {
const files = [
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb')
),
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/WindowManager.pb')
),
new TraceFile(
await UnitTestUtils.getFixtureFile(
'traces/elapsed_and_real_timestamp/screen_recording_metadata_v2.mp4'
)
),
];
const errors = await tracePipeline.loadTraceFiles(files);
expect(errors).toEqual([]);
};
});