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

View file

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

View file

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

View file

@ -640,7 +640,7 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
skipped: 0, skipped: 0,
}; };
let config: FullConfig; let config: FullConfig;
receiver = new TeleReporterReceiver(pathSeparator, { receiver = new TeleReporterReceiver({
version: () => 'v2', version: () => 'v2',
onConfigure: (c: FullConfig) => { 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. // 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 // 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. // suite for a single test run, and hence will have correct total.
lastRunReceiver = new TeleReporterReceiver(pathSeparator, { lastRunReceiver = new TeleReporterReceiver({
onBegin: (suite: Suite) => { onBegin: (suite: Suite) => {
lastRunTestCount = suite.allTests().length; lastRunTestCount = suite.allTests().length;
lastRunReceiver = undefined; lastRunReceiver = undefined;
} }
}, true, false); }, {
pathSeparator,
mergeProjects: true,
mergeTestCases: false
});
}, },
onBegin: (suite: Suite) => { onBegin: (suite: Suite) => {
@ -700,7 +704,11 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
onExit: () => {}, onExit: () => {},
onStepBegin: () => {}, onStepBegin: () => {},
onStepEnd: () => {}, onStepEnd: () => {},
}, true, true); }, {
pathSeparator,
mergeProjects: true,
mergeTestCases: true,
});
receiver._setClearPreviousResultsWhenTestBegins(); receiver._setClearPreviousResultsWhenTestBegins();
return sendMessage('list', {}); return sendMessage('list', {});
}; };