blob: 73b39f90021cfcd635dbef9dc7dc3b5d706811ba [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 {ComponentFixture} from '@angular/core/testing';
import {assertDefined} from 'common/assert_utils';
import {Timestamp} from 'common/time';
import {TimestampConverter} from 'common/timestamp_converter';
import {UrlUtils} from 'common/url_utils';
import {ParserFactory as LegacyParserFactory} from 'parsers/legacy/parser_factory';
import {TracesParserFactory} from 'parsers/legacy/traces_parser_factory';
import {ParserFactory as PerfettoParserFactory} from 'parsers/perfetto/parser_factory';
import {Parser} from 'trace/parser';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceFile} from 'trace/trace_file';
import {TraceEntryTypeMap, TraceType} from 'trace/trace_type';
import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
import {TimestampConverterUtils} from './timestamp_converter_utils';
import {TraceBuilder} from './trace_builder';
class UnitTestUtils {
static async getFixtureFile(
srcFilename: string,
dstFilename: string = srcFilename,
): Promise<File> {
const url = UrlUtils.getRootUrl() + 'base/src/test/fixtures/' + srcFilename;
const response = await fetch(url);
expect(response.ok).toBeTrue();
const blob = await response.blob();
const file = new File([blob], dstFilename);
return file;
}
static async getTrace<T extends TraceType>(
type: T,
filename: string,
): Promise<Trace<T>> {
const converter = UnitTestUtils.getTimestampConverter(false);
const legacyParsers = await UnitTestUtils.getParsers(filename, converter);
expect(legacyParsers.length).toBeLessThanOrEqual(1);
if (legacyParsers.length === 1) {
expect(legacyParsers[0].getTraceType()).toEqual(type);
return new TraceBuilder<T>()
.setType(type)
.setParser(legacyParsers[0] as unknown as Parser<T>)
.build();
}
const perfettoParsers = await UnitTestUtils.getPerfettoParsers(filename);
expect(perfettoParsers.length).toEqual(1);
expect(perfettoParsers[0].getTraceType()).toEqual(type);
return new TraceBuilder<T>()
.setType(type)
.setParser(perfettoParsers[0] as unknown as Parser<T>)
.build();
}
static async getParser(
filename: string,
converter = UnitTestUtils.getTimestampConverter(),
initializeRealToElapsedTimeOffsetNs = true,
): Promise<Parser<object>> {
const parsers = await UnitTestUtils.getParsers(
filename,
converter,
initializeRealToElapsedTimeOffsetNs,
);
return parsers[0];
}
static async getParsers(
filename: string,
converter = UnitTestUtils.getTimestampConverter(),
initializeRealToElapsedTimeOffsetNs = true,
): Promise<Array<Parser<object>>> {
const file = new TraceFile(
await UnitTestUtils.getFixtureFile(filename),
undefined,
);
const fileAndParsers = await new LegacyParserFactory().createParsers(
[file],
converter,
undefined,
undefined,
);
if (initializeRealToElapsedTimeOffsetNs) {
const monotonicOffset = fileAndParsers
.find(
(fileAndParser) =>
fileAndParser.parser.getRealToMonotonicTimeOffsetNs() !== undefined,
)
?.parser.getRealToMonotonicTimeOffsetNs();
if (monotonicOffset !== undefined) {
converter.setRealToMonotonicTimeOffsetNs(monotonicOffset);
}
const bootTimeOffset = fileAndParsers
.find(
(fileAndParser) =>
fileAndParser.parser.getRealToBootTimeOffsetNs() !== undefined,
)
?.parser.getRealToBootTimeOffsetNs();
if (bootTimeOffset !== undefined) {
converter.setRealToBootTimeOffsetNs(bootTimeOffset);
}
}
const parsers = fileAndParsers.map((fileAndParser) => {
fileAndParser.parser.createTimestamps();
return fileAndParser.parser;
});
expect(parsers.length)
.withContext(`Should have been able to create a parser for ${filename}`)
.toBeGreaterThanOrEqual(1);
return parsers;
}
static async getPerfettoParser<T extends TraceType>(
traceType: T,
fixturePath: string,
withUTCOffset = false,
): Promise<Parser<TraceEntryTypeMap[T]>> {
const parsers = await UnitTestUtils.getPerfettoParsers(
fixturePath,
withUTCOffset,
);
const parser = assertDefined(
parsers.find((parser) => parser.getTraceType() === traceType),
);
return parser as Parser<TraceEntryTypeMap[T]>;
}
static async getPerfettoParsers(
fixturePath: string,
withUTCOffset = false,
): Promise<Array<Parser<object>>> {
const file = await UnitTestUtils.getFixtureFile(fixturePath);
const traceFile = new TraceFile(file);
const converter = UnitTestUtils.getTimestampConverter(withUTCOffset);
const parsers = await new PerfettoParserFactory().createParsers(
traceFile,
converter,
undefined,
);
parsers.forEach((parser) => {
converter.setRealToBootTimeOffsetNs(
assertDefined(parser.getRealToBootTimeOffsetNs()),
);
parser.createTimestamps();
});
return parsers;
}
static async getTracesParser(
filenames: string[],
withUTCOffset = false,
): Promise<Parser<object>> {
const converter = UnitTestUtils.getTimestampConverter(withUTCOffset);
const parsersArray = await Promise.all(
filenames.map(async (filename) =>
UnitTestUtils.getParser(filename, converter, true),
),
);
const offset = parsersArray
.filter((parser) => parser.getRealToBootTimeOffsetNs() !== undefined)
.sort((a, b) =>
Number(
(a.getRealToBootTimeOffsetNs() ?? 0n) -
(b.getRealToBootTimeOffsetNs() ?? 0n),
),
)
.at(-1)
?.getRealToBootTimeOffsetNs();
if (offset !== undefined) {
converter.setRealToBootTimeOffsetNs(offset);
}
const traces = new Traces();
parsersArray.forEach((parser) => {
const trace = Trace.fromParser(parser);
traces.addTrace(trace);
});
const tracesParsers = await new TracesParserFactory().createParsers(
traces,
converter,
);
expect(tracesParsers.length)
.withContext(
`Should have been able to create a traces parser for [${filenames.join()}]`,
)
.toEqual(1);
return tracesParsers[0];
}
static getTimestampConverter(withUTCOffset = false): TimestampConverter {
return withUTCOffset
? new TimestampConverter(TimestampConverterUtils.ASIA_TIMEZONE_INFO)
: new TimestampConverter(TimestampConverterUtils.UTC_TIMEZONE_INFO);
}
static async getWindowManagerState(index = 0): Promise<HierarchyTreeNode> {
return UnitTestUtils.getTraceEntry(
'traces/elapsed_and_real_timestamp/WindowManager.pb',
index,
);
}
static async getLayerTraceEntry(index = 0): Promise<HierarchyTreeNode> {
return await UnitTestUtils.getTraceEntry<HierarchyTreeNode>(
'traces/elapsed_timestamp/SurfaceFlinger.pb',
index,
);
}
static async getViewCaptureEntry(): Promise<HierarchyTreeNode> {
return await UnitTestUtils.getTraceEntry<HierarchyTreeNode>(
'traces/elapsed_and_real_timestamp/com.google.android.apps.nexuslauncher_0.vc',
);
}
static async getMultiDisplayLayerTraceEntry(): Promise<HierarchyTreeNode> {
return await UnitTestUtils.getTraceEntry<HierarchyTreeNode>(
'traces/elapsed_and_real_timestamp/SurfaceFlinger_multidisplay.pb',
);
}
static async getImeTraceEntries(): Promise<
[Map<TraceType, HierarchyTreeNode>, Map<TraceType, HierarchyTreeNode>]
> {
let surfaceFlingerEntry: HierarchyTreeNode | undefined;
{
const parser = (await UnitTestUtils.getParser(
'traces/ime/SurfaceFlinger_with_IME.pb',
)) as Parser<HierarchyTreeNode>;
surfaceFlingerEntry = await parser.getEntry(5);
}
let windowManagerEntry: HierarchyTreeNode | undefined;
{
const parser = (await UnitTestUtils.getParser(
'traces/ime/WindowManager_with_IME.pb',
)) as Parser<HierarchyTreeNode>;
windowManagerEntry = await parser.getEntry(2);
}
const entries = new Map<TraceType, HierarchyTreeNode>();
entries.set(
TraceType.INPUT_METHOD_CLIENTS,
await UnitTestUtils.getTraceEntry('traces/ime/InputMethodClients.pb'),
);
entries.set(
TraceType.INPUT_METHOD_MANAGER_SERVICE,
await UnitTestUtils.getTraceEntry(
'traces/ime/InputMethodManagerService.pb',
),
);
entries.set(
TraceType.INPUT_METHOD_SERVICE,
await UnitTestUtils.getTraceEntry('traces/ime/InputMethodService.pb'),
);
entries.set(TraceType.SURFACE_FLINGER, surfaceFlingerEntry);
entries.set(TraceType.WINDOW_MANAGER, windowManagerEntry);
const secondEntries = new Map<TraceType, HierarchyTreeNode>();
secondEntries.set(
TraceType.INPUT_METHOD_CLIENTS,
await UnitTestUtils.getTraceEntry('traces/ime/InputMethodClients.pb', 1),
);
secondEntries.set(TraceType.SURFACE_FLINGER, surfaceFlingerEntry);
secondEntries.set(TraceType.WINDOW_MANAGER, windowManagerEntry);
return [entries, secondEntries];
}
static timestampEqualityTester(first: any, second: any): boolean | undefined {
if (first instanceof Timestamp && second instanceof Timestamp) {
return UnitTestUtils.testTimestamps(first, second);
}
return undefined;
}
static checkSectionCollapseAndExpand<T>(
htmlElement: HTMLElement,
fixture: ComponentFixture<T>,
selector: string,
sectionTitle: string,
) {
const section = assertDefined(htmlElement.querySelector(selector));
const collapseButton = assertDefined(
section.querySelector('collapsible-section-title button'),
) as HTMLElement;
collapseButton.click();
fixture.detectChanges();
expect(section.classList).toContain('collapsed');
const collapsedSections = assertDefined(
htmlElement.querySelector('collapsed-sections'),
);
const collapsedSection = assertDefined(
collapsedSections.querySelector('.collapsed-section'),
) as HTMLElement;
expect(collapsedSection.textContent).toContain(sectionTitle);
collapsedSection.click();
fixture.detectChanges();
UnitTestUtils.checkNoCollapsedSectionButtons(htmlElement);
}
static checkNoCollapsedSectionButtons(htmlElement: HTMLElement) {
const collapsedSections = assertDefined(
htmlElement.querySelector('collapsed-sections'),
);
expect(
collapsedSections.querySelectorAll('.collapsed-section').length,
).toEqual(0);
}
private static testTimestamps(
timestamp: Timestamp,
expectedTimestamp: Timestamp,
): boolean {
if (timestamp.format() !== expectedTimestamp.format()) return false;
if (timestamp.getValueNs() !== expectedTimestamp.getValueNs()) {
return false;
}
return true;
}
private static async getTraceEntry<T>(filename: string, index = 0) {
const parser = (await UnitTestUtils.getParser(filename)) as Parser<T>;
return parser.getEntry(index);
}
}
export {UnitTestUtils};