puppeteer/tools/mochaRunner/src/utils.ts
2023-03-24 15:31:47 +01:00

254 lines
6.9 KiB
TypeScript

/**
* Copyright 2022 Google Inc. All rights reserved.
*
* 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
*
* https://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 fs from 'fs';
import path from 'path';
import {
MochaTestResult,
TestExpectation,
MochaResults,
TestResult,
} from './types.js';
export function extendProcessEnv(envs: object[]): NodeJS.ProcessEnv {
return envs.reduce(
(acc: object, item: object) => {
Object.assign(acc, item);
return acc;
},
{
...process.env,
}
) as NodeJS.ProcessEnv;
}
export function getFilename(file: string): string {
return path.basename(file).replace(path.extname(file), '');
}
export function readJSON(path: string): unknown {
return JSON.parse(fs.readFileSync(path, 'utf-8'));
}
export function writeJSON(path: string, json: unknown): unknown {
return fs.writeFileSync(path, JSON.stringify(json, null, 2));
}
export function filterByPlatform<T extends {platforms: NodeJS.Platform[]}>(
items: T[],
platform: NodeJS.Platform
): T[] {
return items.filter(item => {
return item.platforms.includes(platform);
});
}
export function prettyPrintJSON(json: unknown): void {
console.log(JSON.stringify(json, null, 2));
}
export function printSuggestions(
recommendations: RecommendedExpectation[],
action: RecommendedExpectation['action'],
message: string
): void {
const toPrint = recommendations.filter(item => {
return item.action === action;
});
if (toPrint.length) {
console.log(message);
prettyPrintJSON(
toPrint.map(item => {
return item.expectation;
})
);
console.log(
'The recommendations are based on the following applied expectaions:'
);
prettyPrintJSON(
toPrint.map(item => {
return item.basedOn;
})
);
}
}
export function filterByParameters(
expectations: TestExpectation[],
parameters: string[]
): TestExpectation[] {
const querySet = new Set(parameters);
return expectations.filter(ex => {
return ex.parameters.every(param => {
return querySet.has(param);
});
});
}
/**
* The last expectation that matches an empty string as all tests pattern
* or the name of the file or the whole name of the test the filter wins.
*/
export function findEffectiveExpectationForTest(
expectations: TestExpectation[],
result: MochaTestResult
): TestExpectation | undefined {
return expectations.find(expectation => {
return testIdMatchesExpectationPattern(result, expectation.testIdPattern);
});
}
export type RecommendedExpectation = {
expectation: TestExpectation;
action: 'remove' | 'add' | 'update';
basedOn?: TestExpectation;
};
export function isWildCardPattern(testIdPattern: string): boolean {
return testIdPattern.includes('*');
}
export function getExpectationUpdates(
results: MochaResults,
expectations: TestExpectation[],
context: {
platforms: NodeJS.Platform[];
parameters: string[];
}
): RecommendedExpectation[] {
const output: Map<string, RecommendedExpectation> = new Map();
for (const pass of results.passes) {
// If an error occurs during a hook
// the error not have a file associated with it
if (!pass.file) {
continue;
}
const expectationEntry = findEffectiveExpectationForTest(
expectations,
pass
);
if (expectationEntry && !expectationEntry.expectations.includes('PASS')) {
addEntry({
expectation: expectationEntry,
action: 'remove',
basedOn: expectationEntry,
});
}
}
for (const failure of results.failures) {
// If an error occurs during a hook
// the error not have a file associated with it
if (!failure.file) {
continue;
}
const expectationEntry = findEffectiveExpectationForTest(
expectations,
failure
);
if (expectationEntry) {
if (
!expectationEntry.expectations.includes(
getTestResultForFailure(failure)
)
) {
// If the effective explanation is a wildcard, we recommend adding a new
// expectation instead of updating the wildcard that might affect multiple
// tests.
if (isWildCardPattern(expectationEntry.testIdPattern)) {
addEntry({
expectation: {
testIdPattern: getTestId(failure.file, failure.fullTitle),
platforms: context.platforms,
parameters: context.parameters,
expectations: [getTestResultForFailure(failure)],
},
action: 'add',
basedOn: expectationEntry,
});
} else {
addEntry({
expectation: {
...expectationEntry,
expectations: [
...expectationEntry.expectations,
getTestResultForFailure(failure),
],
},
action: 'update',
basedOn: expectationEntry,
});
}
}
} else {
addEntry({
expectation: {
testIdPattern: getTestId(failure.file, failure.fullTitle),
platforms: context.platforms,
parameters: context.parameters,
expectations: [getTestResultForFailure(failure)],
},
action: 'add',
basedOn: expectationEntry,
});
}
}
function addEntry(value: RecommendedExpectation) {
const key = JSON.stringify(value);
if (!output.has(key)) {
output.set(key, value);
}
}
return [...output.values()];
}
export function getTestResultForFailure(
test: Pick<MochaTestResult, 'err'>
): TestResult {
return test.err?.code === 'ERR_MOCHA_TIMEOUT' ? 'TIMEOUT' : 'FAIL';
}
export function getTestId(file: string, fullTitle?: string): string {
return fullTitle
? `[${getFilename(file)}] ${fullTitle}`
: `[${getFilename(file)}]`;
}
export function testIdMatchesExpectationPattern(
test: MochaTestResult | Mocha.Test,
pattern: string
): boolean {
const patternRegExString = pattern
// Replace `*` with non special character
.replace(/\*/g, '--STAR--')
// Escape special characters https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
// Replace placeholder with greedy match
.replace(/--STAR--/g, '(.*)?');
// Match beginning and end explicitly
const patternRegEx = new RegExp(`^${patternRegExString}$`);
const fullTitle =
typeof test.fullTitle === 'string' ? test.fullTitle : test.fullTitle();
return patternRegEx.test(getTestId(test.file ?? '', fullTitle));
}