chore: intern test ids and abs paths (#24618)

Created named classes for TeleTestStep and TeleTestResult which makes it
easier to analyze in heap snapshot. Also changing `titlePath` from a
closure to a method on TeleTestStep saved ~100Mb out of 2300Mb.

Intern test ids and absolute paths which showed up a lot of duplicate
strings in heap snapshot.

Clear stepMap after processing onTestEnd.
This commit is contained in:
Yury Semikhatsky 2023-08-04 14:10:06 -07:00 committed by GitHub
parent 6ebee33857
commit d62493f925
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 34 deletions

View file

@ -0,0 +1,28 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
export class StringInternPool {
private _stringCache = new Map<string, string>();
public internString(s: string): string {
let result = this._stringCache.get(s);
if (!result) {
this._stringCache.set(s, s);
result = s;
}
return result;
}
}

View file

@ -20,6 +20,7 @@ import type { FullProject, Metadata } from '../../types/test';
import type * as reporterTypes from '../../types/testReporter';
import type { SuitePrivate } from '../../types/reporterPrivate';
import type { ReporterV2 } from '../reporters/reporterV2';
import { StringInternPool } from './stringInternPool';
export type JsonLocation = Location;
export type JsonError = string;
@ -130,6 +131,7 @@ export class TeleReporterReceiver {
private _reuseTestCases: boolean;
private _reportConfig: MergeReporterConfig | undefined;
private _config!: FullConfig;
private _stringPool = new StringInternPool();
constructor(pathSeparator: string, reporter: ReporterV2, reuseTestCases: boolean, reportConfig?: MergeReporterConfig) {
this._rootSuite = new TeleSuite('', 'root');
@ -248,6 +250,8 @@ export class TeleReporterReceiver {
result.error = result.errors?.[0];
result.attachments = this._parseAttachments(payload.attachments);
this._reporter.onTestEnd?.(test, result);
// Free up the memory as won't see these step ids.
result.stepMap = new Map();
}
private _onStepBegin(testId: string, resultId: string, payload: JsonTestStepStart) {
@ -255,19 +259,8 @@ export class TeleReporterReceiver {
const result = test.resultsMap.get(resultId)!;
const parentStep = payload.parentStepId ? result.stepMap.get(payload.parentStepId) : undefined;
const step: TestStep = {
titlePath: () => {
const parentPath = parentStep?.titlePath() || [];
return [...parentPath, payload.title];
},
title: payload.title,
category: payload.category,
location: this._absoluteLocation(payload.location),
parent: parentStep,
startTime: new Date(payload.startTime),
duration: -1,
steps: [],
};
const location = this._absoluteLocation(payload.location);
const step = new TeleTestStep(payload, parentStep, location);
if (parentStep)
parentStep.steps.push(step);
else
@ -307,6 +300,8 @@ export class TeleReporterReceiver {
}
private _onExit(): Promise<void> | void {
// Free up the memory from the string pool.
this._stringPool = new StringInternPool();
return this._reporter.onExit?.();
}
@ -403,7 +398,7 @@ export class TeleReporterReceiver {
private _absolutePath(relativePath?: string): string | undefined {
if (!relativePath)
return relativePath;
return this._rootDir + this._pathSeparator + relativePath;
return this._stringPool.internString(this._rootDir + this._pathSeparator + relativePath);
}
}
@ -503,31 +498,57 @@ export class TeleTestCase implements reporterTypes.TestCase {
}
_createTestResult(id: string): TeleTestResult {
const result: TeleTestResult = {
retry: this.results.length,
parallelIndex: -1,
workerIndex: -1,
duration: -1,
startTime: new Date(),
stdout: [],
stderr: [],
attachments: [],
status: 'skipped',
statusEx: 'scheduled',
steps: [],
errors: [],
stepMap: new Map(),
};
const result = new TeleTestResult(this.results.length);
this.results.push(result);
this.resultsMap.set(id, result);
return result;
}
}
export type TeleTestResult = reporterTypes.TestResult & {
stepMap: Map<string, reporterTypes.TestStep>;
statusEx: reporterTypes.TestResult['status'] | 'scheduled' | 'running';
};
class TeleTestStep implements TestStep {
title: string;
category: string;
location: Location | undefined;
parent: TestStep | undefined;
startTime: Date;
duration: number = -1;
steps: TestStep[] = [];
constructor(payload: JsonTestStepStart, parentStep: TestStep | undefined, location: Location | undefined) {
this.title = payload.title;
this.category = payload.category;
this.location = location;
this.parent = parentStep;
this.startTime = new Date(payload.startTime);
}
titlePath() {
const parentPath = this.parent?.titlePath() || [];
return [...parentPath, this.title];
}
}
class TeleTestResult implements reporterTypes.TestResult {
retry: reporterTypes.TestResult['retry'];
parallelIndex: reporterTypes.TestResult['parallelIndex'] = -1;
workerIndex: reporterTypes.TestResult['workerIndex'] = -1;
duration: reporterTypes.TestResult['duration'] = -1;
startTime: reporterTypes.TestResult['startTime'] = new Date();
stdout: reporterTypes.TestResult['stdout'] = [];
stderr: reporterTypes.TestResult['stderr'] = [];
attachments: reporterTypes.TestResult['attachments'] = [];
status: TestStatus = 'skipped';
steps: TeleTestStep[] = [];
errors: reporterTypes.TestResult['errors'] = [];
error: reporterTypes.TestResult['error'];
stepMap: Map<string, reporterTypes.TestStep> = new Map();
statusEx: reporterTypes.TestResult['status'] | 'scheduled' | 'running' = 'scheduled';
constructor(retry: number) {
this.retry = retry;
}
}
export type TeleFullProject = FullProject & { id: string };

View file

@ -21,6 +21,7 @@ import type { FullResult } from '../../types/testReporter';
import type { FullConfigInternal } from '../common/config';
import type { JsonConfig, JsonEvent, JsonProject, JsonSuite, JsonTestResultEnd } from '../isomorphic/teleReceiver';
import { TeleReporterReceiver } from '../isomorphic/teleReceiver';
import { StringInternPool } from '../isomorphic/stringInternPool';
import { createReporters } from '../runner/reporters';
import { Multiplexer } from './multiplexer';
import { ZipFile } from 'playwright-core/lib/utils';
@ -241,6 +242,7 @@ function printStatusToStdout(message: string) {
class ProjectNamePatcher {
private _testIds = new Set<string>();
private _stringPool = new StringInternPool();
constructor(private _allTestIds: Set<string>, private _projectNameSuffix: string) {
}
@ -305,6 +307,6 @@ class ProjectNamePatcher {
// make them unique and produce a separate test from each blob.
while (this._allTestIds.has(testId))
testId = testId + '1';
return testId;
return this._stringPool.internString(testId);
}
}