chore: inject string pool into the tele receiver (#29781)

This commit is contained in:
Pavel Feldman 2024-03-04 08:46:32 -08:00 committed by GitHub
parent d5d4f591f3
commit 68284b0505
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 74 additions and 59 deletions

View file

@ -18,8 +18,8 @@ import type { Annotation } from '../common/config';
import type { FullProject, Metadata } from '../../types/test';
import type * as reporterTypes from '../../types/testReporter';
import type { ReporterV2 } from '../reporters/reporterV2';
import { StringInternPool } from './stringInternPool';
export type StringIntern = (s: string) => string;
export type JsonLocation = reporterTypes.Location;
export type JsonError = string;
export type JsonStackFrame = { file: string, line: number, column: number };
@ -28,8 +28,6 @@ export type JsonStdIOType = 'stdout' | 'stderr';
export type JsonConfig = Pick<reporterTypes.FullConfig, 'configFile' | 'globalTimeout' | 'maxFailures' | 'metadata' | 'rootDir' | 'version' | 'workers'>;
export type MergeReporterConfig = Pick<reporterTypes.FullConfig, 'configFile' | 'quiet' | 'reportSlowTests' | 'rootDir' | 'reporter' >;
export type JsonPattern = {
s?: string;
r?: { source: string, flags: string };
@ -123,27 +121,28 @@ export type JsonEvent = {
params: any
};
type TeleReporterReceiverOptions = {
pathSeparator: string;
mergeProjects: boolean;
mergeTestCases: boolean;
internString?: StringIntern;
configOverrides?: Pick<reporterTypes.FullConfig, 'configFile' | 'quiet' | 'reportSlowTests' | 'reporter'>;
};
export class TeleReporterReceiver {
private _rootSuite: TeleSuite;
private _pathSeparator: string;
private _options: TeleReporterReceiverOptions;
private _reporter: Partial<ReporterV2>;
private _tests = new Map<string, TeleTestCase>();
private _rootDir!: string;
private _listOnly = false;
private _clearPreviousResultsWhenTestBegins: boolean = false;
private _mergeTestCases: boolean;
private _mergeProjects: boolean;
private _reportConfig: MergeReporterConfig | undefined;
private _config!: reporterTypes.FullConfig;
private _stringPool = new StringInternPool();
constructor(pathSeparator: string, reporter: Partial<ReporterV2>, mergeProjects: boolean, mergeTestCases: boolean, reportConfig?: MergeReporterConfig) {
constructor(reporter: Partial<ReporterV2>, options: TeleReporterReceiverOptions) {
this._rootSuite = new TeleSuite('', 'root');
this._pathSeparator = pathSeparator;
this._options = options;
this._reporter = reporter;
this._mergeProjects = mergeProjects;
this._mergeTestCases = mergeTestCases;
this._reportConfig = reportConfig;
}
dispatch(mode: 'list' | 'test', message: JsonEvent): Promise<void> | void {
@ -202,7 +201,7 @@ export class TeleReporterReceiver {
}
private _onProject(project: JsonProject) {
let projectSuite = this._mergeProjects ? this._rootSuite.suites.find(suite => suite.project()!.name === project.name) : undefined;
let projectSuite = this._options.mergeProjects ? this._rootSuite.suites.find(suite => suite.project()!.name === project.name) : undefined;
if (!projectSuite) {
projectSuite = new TeleSuite(project.name, 'project');
this._rootSuite.suites.push(projectSuite);
@ -315,17 +314,16 @@ export class TeleReporterReceiver {
private _onExit(): Promise<void> | void {
// Free up the memory from the string pool.
this._stringPool = new StringInternPool();
return this._reporter.onExit?.();
}
private _parseConfig(config: JsonConfig): reporterTypes.FullConfig {
const result = { ...baseFullConfig, ...config };
if (this._reportConfig) {
result.configFile = this._reportConfig.configFile;
result.reportSlowTests = this._reportConfig.reportSlowTests;
result.quiet = this._reportConfig.quiet;
result.reporter = [...this._reportConfig.reporter];
if (this._options.configOverrides) {
result.configFile = this._options.configOverrides.configFile;
result.reportSlowTests = this._options.configOverrides.reportSlowTests;
result.quiet = this._options.configOverrides.quiet;
result.reporter = [...this._options.configOverrides.reporter];
}
return result;
}
@ -375,7 +373,7 @@ export class TeleReporterReceiver {
private _mergeTestsInto(jsonTests: JsonTestCase[], parent: TeleSuite) {
for (const jsonTest of jsonTests) {
let targetTest = this._mergeTestCases ? parent.tests.find(s => s.title === jsonTest.title && s.repeatEachIndex === jsonTest.repeatEachIndex) : undefined;
let targetTest = this._options.mergeTestCases ? parent.tests.find(s => s.title === jsonTest.title && s.repeatEachIndex === jsonTest.repeatEachIndex) : undefined;
if (!targetTest) {
targetTest = new TeleTestCase(jsonTest.testId, jsonTest.title, this._absoluteLocation(jsonTest.location), jsonTest.repeatEachIndex);
targetTest.parent = parent;
@ -410,9 +408,12 @@ export class TeleReporterReceiver {
private _absolutePath(relativePath?: string): string | undefined {
if (relativePath === undefined)
return;
return this._stringPool.internString(this._rootDir + this._pathSeparator + relativePath);
return this._internString(this._rootDir + this._options.pathSeparator + relativePath);
}
private _internString(s: string): string {
return this._options.internString ? this._options.internString(s) : s;
}
}
export class TeleSuite {

View file

@ -51,9 +51,15 @@ export async function createMergedReport(config: FullConfigInternal, dir: string
if (shardFiles.length === 0)
throw new Error(`No report files found in ${dir}`);
const eventData = await mergeEvents(dir, shardFiles, stringPool, printStatus, rootDirOverride);
// If expicit config is provided, use platform path separator, otherwise use the one from the report (if any).
const pathSep = rootDirOverride ? path.sep : (eventData.pathSeparatorFromMetadata ?? path.sep);
const receiver = new TeleReporterReceiver(pathSep, multiplexer, false, false, config.config);
// If explicit config is provided, use platform path separator, otherwise use the one from the report (if any).
const pathSeparator = rootDirOverride ? path.sep : (eventData.pathSeparatorFromMetadata ?? path.sep);
const receiver = new TeleReporterReceiver(multiplexer, {
pathSeparator,
mergeProjects: false,
mergeTestCases: false,
internString: s => stringPool.internString(s),
configOverrides: config.config,
});
printStatus(`processing test events`);
const dispatchEvents = async (events: JsonEvent[]) => {

View file

@ -16,17 +16,17 @@
import path from 'path';
import { createGuid } from 'playwright-core/lib/utils';
import type { FullConfig, FullResult, Location, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
import type { JsonAttachment, JsonConfig, JsonEvent, JsonFullResult, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
import type * as reporterTypes from '../../types/testReporter';
import type * as teleReceiver from '../isomorphic/teleReceiver';
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
import type { ReporterV2 } from './reporterV2';
export class TeleReporterEmitter implements ReporterV2 {
private _messageSink: (message: JsonEvent) => void;
private _messageSink: (message: teleReceiver.JsonEvent) => void;
private _rootDir!: string;
private _skipBuffers: boolean;
constructor(messageSink: (message: JsonEvent) => void, skipBuffers: boolean) {
constructor(messageSink: (message: teleReceiver.JsonEvent) => void, skipBuffers: boolean) {
this._messageSink = messageSink;
this._skipBuffers = skipBuffers;
}
@ -35,19 +35,19 @@ export class TeleReporterEmitter implements ReporterV2 {
return 'v2';
}
onConfigure(config: FullConfig) {
onConfigure(config: reporterTypes.FullConfig) {
this._rootDir = config.rootDir;
this._messageSink({ method: 'onConfigure', params: { config: this._serializeConfig(config) } });
}
onBegin(suite: Suite) {
onBegin(suite: reporterTypes.Suite) {
const projects = suite.suites.map(projectSuite => this._serializeProject(projectSuite));
for (const project of projects)
this._messageSink({ method: 'onProject', params: { project } });
this._messageSink({ method: 'onBegin', params: undefined });
}
onTestBegin(test: TestCase, result: TestResult): void {
onTestBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult): void {
(result as any)[idSymbol] = createGuid();
this._messageSink({
method: 'onTestBegin',
@ -58,8 +58,8 @@ export class TeleReporterEmitter implements ReporterV2 {
});
}
onTestEnd(test: TestCase, result: TestResult): void {
const testEnd: JsonTestEnd = {
onTestEnd(test: reporterTypes.TestCase, result: reporterTypes.TestResult): void {
const testEnd: teleReceiver.JsonTestEnd = {
testId: test.id,
expectedStatus: test.expectedStatus,
annotations: test.annotations,
@ -74,7 +74,7 @@ export class TeleReporterEmitter implements ReporterV2 {
});
}
onStepBegin(test: TestCase, result: TestResult, step: TestStep): void {
onStepBegin(test: reporterTypes.TestCase, result: reporterTypes.TestResult, step: reporterTypes.TestStep): void {
(step as any)[idSymbol] = createGuid();
this._messageSink({
method: 'onStepBegin',
@ -86,7 +86,7 @@ export class TeleReporterEmitter implements ReporterV2 {
});
}
onStepEnd(test: TestCase, result: TestResult, step: TestStep): void {
onStepEnd(test: reporterTypes.TestCase, result: reporterTypes.TestResult, step: reporterTypes.TestStep): void {
this._messageSink({
method: 'onStepEnd',
params: {
@ -97,22 +97,22 @@ export class TeleReporterEmitter implements ReporterV2 {
});
}
onError(error: TestError): void {
onError(error: reporterTypes.TestError): void {
this._messageSink({
method: 'onError',
params: { error }
});
}
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult): void {
onStdOut(chunk: string | Buffer, test?: reporterTypes.TestCase, result?: reporterTypes.TestResult): void {
this._onStdIO('stdout', chunk, test, result);
}
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult): void {
onStdErr(chunk: string | Buffer, test?: reporterTypes.TestCase, result?: reporterTypes.TestResult): void {
this._onStdIO('stderr', chunk, test, result);
}
private _onStdIO(type: JsonStdIOType, chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void {
private _onStdIO(type: teleReceiver.JsonStdIOType, chunk: string | Buffer, test: void | reporterTypes.TestCase, result: void | reporterTypes.TestResult): void {
const isBase64 = typeof chunk !== 'string';
const data = isBase64 ? chunk.toString('base64') : chunk;
this._messageSink({
@ -121,8 +121,8 @@ export class TeleReporterEmitter implements ReporterV2 {
});
}
async onEnd(result: FullResult) {
const resultPayload: JsonFullResult = {
async onEnd(result: reporterTypes.FullResult) {
const resultPayload: teleReceiver.JsonFullResult = {
status: result.status,
startTime: result.startTime.getTime(),
duration: result.duration,
@ -142,7 +142,7 @@ export class TeleReporterEmitter implements ReporterV2 {
return false;
}
private _serializeConfig(config: FullConfig): JsonConfig {
private _serializeConfig(config: reporterTypes.FullConfig): teleReceiver.JsonConfig {
return {
configFile: this._relativePath(config.configFile),
globalTimeout: config.globalTimeout,
@ -154,9 +154,9 @@ export class TeleReporterEmitter implements ReporterV2 {
};
}
private _serializeProject(suite: Suite): JsonProject {
private _serializeProject(suite: reporterTypes.Suite): teleReceiver.JsonProject {
const project = suite.project()!;
const report: JsonProject = {
const report: teleReceiver.JsonProject = {
metadata: project.metadata,
name: project.name,
outputDir: this._relativePath(project.outputDir),
@ -178,7 +178,7 @@ export class TeleReporterEmitter implements ReporterV2 {
return report;
}
private _serializeSuite(suite: Suite): JsonSuite {
private _serializeSuite(suite: reporterTypes.Suite): teleReceiver.JsonSuite {
const result = {
title: suite.title,
location: this._relativeLocation(suite.location),
@ -188,7 +188,7 @@ export class TeleReporterEmitter implements ReporterV2 {
return result;
}
private _serializeTest(test: TestCase): JsonTestCase {
private _serializeTest(test: reporterTypes.TestCase): teleReceiver.JsonTestCase {
return {
testId: test.id,
title: test.title,
@ -199,7 +199,7 @@ export class TeleReporterEmitter implements ReporterV2 {
};
}
private _serializeResultStart(result: TestResult): JsonTestResultStart {
private _serializeResultStart(result: reporterTypes.TestResult): teleReceiver.JsonTestResultStart {
return {
id: (result as any)[idSymbol],
retry: result.retry,
@ -209,7 +209,7 @@ export class TeleReporterEmitter implements ReporterV2 {
};
}
private _serializeResultEnd(result: TestResult): JsonTestResultEnd {
private _serializeResultEnd(result: reporterTypes.TestResult): teleReceiver.JsonTestResultEnd {
return {
id: (result as any)[idSymbol],
duration: result.duration,
@ -219,7 +219,7 @@ export class TeleReporterEmitter implements ReporterV2 {
};
}
_serializeAttachments(attachments: TestResult['attachments']): JsonAttachment[] {
_serializeAttachments(attachments: reporterTypes.TestResult['attachments']): teleReceiver.JsonAttachment[] {
return attachments.map(a => {
return {
...a,
@ -229,7 +229,7 @@ export class TeleReporterEmitter implements ReporterV2 {
});
}
private _serializeStepStart(step: TestStep): JsonTestStepStart {
private _serializeStepStart(step: reporterTypes.TestStep): teleReceiver.JsonTestStepStart {
return {
id: (step as any)[idSymbol],
parentStepId: (step.parent as any)?.[idSymbol],
@ -240,7 +240,7 @@ export class TeleReporterEmitter implements ReporterV2 {
};
}
private _serializeStepEnd(step: TestStep): JsonTestStepEnd {
private _serializeStepEnd(step: reporterTypes.TestStep): teleReceiver.JsonTestStepEnd {
return {
id: (step as any)[idSymbol],
duration: step.duration,
@ -248,9 +248,9 @@ export class TeleReporterEmitter implements ReporterV2 {
};
}
private _relativeLocation(location: Location): Location;
private _relativeLocation(location?: Location): Location | undefined;
private _relativeLocation(location: Location | undefined): Location | undefined {
private _relativeLocation(location: reporterTypes.Location): reporterTypes.Location;
private _relativeLocation(location?: reporterTypes.Location): reporterTypes.Location | undefined;
private _relativeLocation(location: reporterTypes.Location | undefined): reporterTypes.Location | undefined {
if (!location)
return location;
return {

View file

@ -640,7 +640,7 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
skipped: 0,
};
let config: FullConfig;
receiver = new TeleReporterReceiver(pathSeparator, {
receiver = new TeleReporterReceiver({
version: () => 'v2',
onConfigure: (c: FullConfig) => {
@ -649,12 +649,16 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
// run one test, we still get many tests via rootSuite.allTests().length.
// To work around that, have a dedicated per-run receiver that will only have
// suite for a single test run, and hence will have correct total.
lastRunReceiver = new TeleReporterReceiver(pathSeparator, {
lastRunReceiver = new TeleReporterReceiver({
onBegin: (suite: Suite) => {
lastRunTestCount = suite.allTests().length;
lastRunReceiver = undefined;
}
}, true, false);
}, {
pathSeparator,
mergeProjects: true,
mergeTestCases: false
});
},
onBegin: (suite: Suite) => {
@ -700,7 +704,11 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
onExit: () => {},
onStepBegin: () => {},
onStepEnd: () => {},
}, true, true);
}, {
pathSeparator,
mergeProjects: true,
mergeTestCases: true,
});
receiver._setClearPreviousResultsWhenTestBegins();
return sendMessage('list', {});
};