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:
parent
6ebee33857
commit
d62493f925
28
packages/playwright-test/src/isomorphic/stringInternPool.ts
Normal file
28
packages/playwright-test/src/isomorphic/stringInternPool.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ import type { FullProject, Metadata } from '../../types/test';
|
||||||
import type * as reporterTypes from '../../types/testReporter';
|
import type * as reporterTypes from '../../types/testReporter';
|
||||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||||
import type { ReporterV2 } from '../reporters/reporterV2';
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
|
import { StringInternPool } from './stringInternPool';
|
||||||
|
|
||||||
export type JsonLocation = Location;
|
export type JsonLocation = Location;
|
||||||
export type JsonError = string;
|
export type JsonError = string;
|
||||||
|
|
@ -130,6 +131,7 @@ export class TeleReporterReceiver {
|
||||||
private _reuseTestCases: boolean;
|
private _reuseTestCases: boolean;
|
||||||
private _reportConfig: MergeReporterConfig | undefined;
|
private _reportConfig: MergeReporterConfig | undefined;
|
||||||
private _config!: FullConfig;
|
private _config!: FullConfig;
|
||||||
|
private _stringPool = new StringInternPool();
|
||||||
|
|
||||||
constructor(pathSeparator: string, reporter: ReporterV2, reuseTestCases: boolean, reportConfig?: MergeReporterConfig) {
|
constructor(pathSeparator: string, reporter: ReporterV2, reuseTestCases: boolean, reportConfig?: MergeReporterConfig) {
|
||||||
this._rootSuite = new TeleSuite('', 'root');
|
this._rootSuite = new TeleSuite('', 'root');
|
||||||
|
|
@ -248,6 +250,8 @@ export class TeleReporterReceiver {
|
||||||
result.error = result.errors?.[0];
|
result.error = result.errors?.[0];
|
||||||
result.attachments = this._parseAttachments(payload.attachments);
|
result.attachments = this._parseAttachments(payload.attachments);
|
||||||
this._reporter.onTestEnd?.(test, result);
|
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) {
|
private _onStepBegin(testId: string, resultId: string, payload: JsonTestStepStart) {
|
||||||
|
|
@ -255,19 +259,8 @@ export class TeleReporterReceiver {
|
||||||
const result = test.resultsMap.get(resultId)!;
|
const result = test.resultsMap.get(resultId)!;
|
||||||
const parentStep = payload.parentStepId ? result.stepMap.get(payload.parentStepId) : undefined;
|
const parentStep = payload.parentStepId ? result.stepMap.get(payload.parentStepId) : undefined;
|
||||||
|
|
||||||
const step: TestStep = {
|
const location = this._absoluteLocation(payload.location);
|
||||||
titlePath: () => {
|
const step = new TeleTestStep(payload, parentStep, location);
|
||||||
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: [],
|
|
||||||
};
|
|
||||||
if (parentStep)
|
if (parentStep)
|
||||||
parentStep.steps.push(step);
|
parentStep.steps.push(step);
|
||||||
else
|
else
|
||||||
|
|
@ -307,6 +300,8 @@ export class TeleReporterReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onExit(): Promise<void> | void {
|
private _onExit(): Promise<void> | void {
|
||||||
|
// Free up the memory from the string pool.
|
||||||
|
this._stringPool = new StringInternPool();
|
||||||
return this._reporter.onExit?.();
|
return this._reporter.onExit?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -403,7 +398,7 @@ export class TeleReporterReceiver {
|
||||||
private _absolutePath(relativePath?: string): string | undefined {
|
private _absolutePath(relativePath?: string): string | undefined {
|
||||||
if (!relativePath)
|
if (!relativePath)
|
||||||
return 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 {
|
_createTestResult(id: string): TeleTestResult {
|
||||||
const result: TeleTestResult = {
|
const result = new TeleTestResult(this.results.length);
|
||||||
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(),
|
|
||||||
};
|
|
||||||
this.results.push(result);
|
this.results.push(result);
|
||||||
this.resultsMap.set(id, result);
|
this.resultsMap.set(id, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TeleTestResult = reporterTypes.TestResult & {
|
class TeleTestStep implements TestStep {
|
||||||
stepMap: Map<string, reporterTypes.TestStep>;
|
title: string;
|
||||||
statusEx: reporterTypes.TestResult['status'] | 'scheduled' | 'running';
|
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 };
|
export type TeleFullProject = FullProject & { id: string };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import type { FullResult } from '../../types/testReporter';
|
||||||
import type { FullConfigInternal } from '../common/config';
|
import type { FullConfigInternal } from '../common/config';
|
||||||
import type { JsonConfig, JsonEvent, JsonProject, JsonSuite, JsonTestResultEnd } from '../isomorphic/teleReceiver';
|
import type { JsonConfig, JsonEvent, JsonProject, JsonSuite, JsonTestResultEnd } from '../isomorphic/teleReceiver';
|
||||||
import { TeleReporterReceiver } from '../isomorphic/teleReceiver';
|
import { TeleReporterReceiver } from '../isomorphic/teleReceiver';
|
||||||
|
import { StringInternPool } from '../isomorphic/stringInternPool';
|
||||||
import { createReporters } from '../runner/reporters';
|
import { createReporters } from '../runner/reporters';
|
||||||
import { Multiplexer } from './multiplexer';
|
import { Multiplexer } from './multiplexer';
|
||||||
import { ZipFile } from 'playwright-core/lib/utils';
|
import { ZipFile } from 'playwright-core/lib/utils';
|
||||||
|
|
@ -241,6 +242,7 @@ function printStatusToStdout(message: string) {
|
||||||
|
|
||||||
class ProjectNamePatcher {
|
class ProjectNamePatcher {
|
||||||
private _testIds = new Set<string>();
|
private _testIds = new Set<string>();
|
||||||
|
private _stringPool = new StringInternPool();
|
||||||
|
|
||||||
constructor(private _allTestIds: Set<string>, private _projectNameSuffix: string) {
|
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.
|
// make them unique and produce a separate test from each blob.
|
||||||
while (this._allTestIds.has(testId))
|
while (this._allTestIds.has(testId))
|
||||||
testId = testId + '1';
|
testId = testId + '1';
|
||||||
return testId;
|
return this._stringPool.internString(testId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue