2022-09-09 09:12:18 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2023-02-15 23:09:31 +00:00
|
|
|
import fs from 'fs';
|
|
|
|
import path from 'path';
|
|
|
|
|
2022-09-08 10:32:39 +00:00
|
|
|
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'));
|
|
|
|
}
|
|
|
|
|
2023-03-24 14:31:47 +00:00
|
|
|
export function writeJSON(path: string, json: unknown): unknown {
|
|
|
|
return fs.writeFileSync(path, JSON.stringify(json, null, 2));
|
|
|
|
}
|
|
|
|
|
2022-09-08 10:32:39 +00:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2023-02-28 11:55:20 +00:00
|
|
|
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;
|
|
|
|
})
|
|
|
|
);
|
2023-03-23 09:22:17 +00:00
|
|
|
console.log(
|
|
|
|
'The recommendations are based on the following applied expectaions:'
|
|
|
|
);
|
|
|
|
prettyPrintJSON(
|
|
|
|
toPrint.map(item => {
|
|
|
|
return item.basedOn;
|
|
|
|
})
|
|
|
|
);
|
2023-02-28 11:55:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-08 10:32:39 +00:00
|
|
|
export function filterByParameters(
|
2023-01-13 15:14:37 +00:00
|
|
|
expectations: TestExpectation[],
|
2022-09-08 10:32:39 +00:00
|
|
|
parameters: string[]
|
|
|
|
): TestExpectation[] {
|
|
|
|
const querySet = new Set(parameters);
|
2023-01-13 15:14:37 +00:00
|
|
|
return expectations.filter(ex => {
|
2022-09-08 10:32:39 +00:00
|
|
|
return ex.parameters.every(param => {
|
|
|
|
return querySet.has(param);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-01-13 15:14:37 +00:00
|
|
|
* 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.
|
2022-09-08 10:32:39 +00:00
|
|
|
*/
|
2023-01-13 15:14:37 +00:00
|
|
|
export function findEffectiveExpectationForTest(
|
2022-09-08 10:32:39 +00:00
|
|
|
expectations: TestExpectation[],
|
|
|
|
result: MochaTestResult
|
|
|
|
): TestExpectation | undefined {
|
2023-03-20 09:59:50 +00:00
|
|
|
return expectations.find(expectation => {
|
|
|
|
return testIdMatchesExpectationPattern(result, expectation.testIdPattern);
|
|
|
|
});
|
2022-09-08 10:32:39 +00:00
|
|
|
}
|
|
|
|
|
2023-03-20 09:59:50 +00:00
|
|
|
export type RecommendedExpectation = {
|
2022-09-08 10:32:39 +00:00
|
|
|
expectation: TestExpectation;
|
|
|
|
action: 'remove' | 'add' | 'update';
|
2023-03-23 09:22:17 +00:00
|
|
|
basedOn?: TestExpectation;
|
2022-09-08 10:32:39 +00:00
|
|
|
};
|
|
|
|
|
2023-02-22 08:06:24 +00:00
|
|
|
export function isWildCardPattern(testIdPattern: string): boolean {
|
2023-03-20 09:59:50 +00:00
|
|
|
return testIdPattern.includes('*');
|
2023-02-22 08:06:24 +00:00
|
|
|
}
|
|
|
|
|
2022-09-08 10:32:39 +00:00
|
|
|
export function getExpectationUpdates(
|
|
|
|
results: MochaResults,
|
2023-02-28 11:55:20 +00:00
|
|
|
expectations: TestExpectation[],
|
2022-09-08 10:32:39 +00:00
|
|
|
context: {
|
|
|
|
platforms: NodeJS.Platform[];
|
|
|
|
parameters: string[];
|
|
|
|
}
|
2023-02-28 11:55:20 +00:00
|
|
|
): RecommendedExpectation[] {
|
|
|
|
const output: Map<string, RecommendedExpectation> = new Map();
|
2022-09-08 10:32:39 +00:00
|
|
|
|
|
|
|
for (const pass of results.passes) {
|
2023-02-28 11:55:20 +00:00
|
|
|
// 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
|
|
|
|
);
|
2023-02-22 08:06:24 +00:00
|
|
|
if (expectationEntry && !expectationEntry.expectations.includes('PASS')) {
|
2023-02-28 11:55:20 +00:00
|
|
|
addEntry({
|
2023-02-22 08:06:24 +00:00
|
|
|
expectation: expectationEntry,
|
2022-09-08 10:32:39 +00:00
|
|
|
action: 'remove',
|
2023-03-23 09:22:17 +00:00
|
|
|
basedOn: expectationEntry,
|
2022-09-08 10:32:39 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const failure of results.failures) {
|
2023-02-28 11:55:20 +00:00
|
|
|
// If an error occurs during a hook
|
|
|
|
// the error not have a file associated with it
|
|
|
|
if (!failure.file) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-02-22 08:06:24 +00:00
|
|
|
const expectationEntry = findEffectiveExpectationForTest(
|
2023-02-28 11:55:20 +00:00
|
|
|
expectations,
|
2023-02-22 08:06:24 +00:00
|
|
|
failure
|
|
|
|
);
|
2023-03-23 09:22:17 +00:00
|
|
|
if (expectationEntry) {
|
2022-09-08 10:32:39 +00:00
|
|
|
if (
|
2023-02-22 08:06:24 +00:00
|
|
|
!expectationEntry.expectations.includes(
|
|
|
|
getTestResultForFailure(failure)
|
|
|
|
)
|
2022-09-08 10:32:39 +00:00
|
|
|
) {
|
2023-03-23 09:22:17 +00:00
|
|
|
// 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,
|
|
|
|
});
|
|
|
|
}
|
2022-09-08 10:32:39 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-02-28 11:55:20 +00:00
|
|
|
addEntry({
|
2022-09-08 10:32:39 +00:00
|
|
|
expectation: {
|
|
|
|
testIdPattern: getTestId(failure.file, failure.fullTitle),
|
|
|
|
platforms: context.platforms,
|
|
|
|
parameters: context.parameters,
|
|
|
|
expectations: [getTestResultForFailure(failure)],
|
|
|
|
},
|
|
|
|
action: 'add',
|
2023-03-23 09:22:17 +00:00
|
|
|
basedOn: expectationEntry,
|
2022-09-08 10:32:39 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2023-02-28 11:55:20 +00:00
|
|
|
|
|
|
|
function addEntry(value: RecommendedExpectation) {
|
|
|
|
const key = JSON.stringify(value);
|
|
|
|
if (!output.has(key)) {
|
|
|
|
output.set(key, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [...output.values()];
|
2022-09-08 10:32:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getTestResultForFailure(
|
|
|
|
test: Pick<MochaTestResult, 'err'>
|
|
|
|
): TestResult {
|
|
|
|
return test.err?.code === 'ERR_MOCHA_TIMEOUT' ? 'TIMEOUT' : 'FAIL';
|
|
|
|
}
|
|
|
|
|
2023-01-13 15:14:37 +00:00
|
|
|
export function getTestId(file: string, fullTitle?: string): string {
|
|
|
|
return fullTitle
|
|
|
|
? `[${getFilename(file)}] ${fullTitle}`
|
|
|
|
: `[${getFilename(file)}]`;
|
2022-09-08 10:32:39 +00:00
|
|
|
}
|
2023-03-20 09:59:50 +00:00
|
|
|
|
|
|
|
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));
|
|
|
|
}
|