puppeteer/tools/mochaRunner/src/utils.ts

219 lines
5.5 KiB
TypeScript
Raw Normal View History

/**
* 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 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;
})
);
}
}
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
.filter(expectation => {
return (
'' === expectation.testIdPattern ||
getTestId(result.file) === expectation.testIdPattern ||
getTestId(result.file, result.fullTitle) === expectation.testIdPattern
);
})
.pop();
}
type RecommendedExpectation = {
expectation: TestExpectation;
action: 'remove' | 'add' | 'update';
};
export function isWildCardPattern(testIdPattern: string): boolean {
testIdPattern = testIdPattern.trim();
return (
testIdPattern === '' ||
Boolean(testIdPattern.match(/^\[[a-zA-Z]+\.spec\]$/))
);
}
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',
});
}
}
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 the effective explanation is a wildcard, we recommend adding a new
// expectation instead of updating the wildcard that might affect multiple
// tests.
if (
expectationEntry &&
!isWildCardPattern(expectationEntry.testIdPattern)
) {
if (
!expectationEntry.expectations.includes(
getTestResultForFailure(failure)
)
) {
addEntry({
expectation: {
...expectationEntry,
expectations: [
...expectationEntry.expectations,
getTestResultForFailure(failure),
],
},
action: 'update',
});
}
} else {
addEntry({
expectation: {
testIdPattern: getTestId(failure.file, failure.fullTitle),
platforms: context.platforms,
parameters: context.parameters,
expectations: [getTestResultForFailure(failure)],
},
action: 'add',
});
}
}
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)}]`;
}