Skip to content

Commit 0592aa4

Browse files
committed
support --onlyFailures
1 parent 5a6b779 commit 0592aa4

File tree

6 files changed

+64
-30
lines changed

6 files changed

+64
-30
lines changed

bin/cli.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ program
1818
.option("--coverageLimit [error warning...]", "set warn(yellow) and error(red) upper limit in coverage report")
1919
.option("--testcase <testcases...>", "run only specified test cases")
2020
.option("--testNamePattern <test name pattern>", "run only tests with a name that matches the regex pattern")
21-
.option("--collectCoverage <boolean>", "whether to collect coverage information and report");
21+
.option("--collectCoverage <boolean>", "whether to collect coverage information and report")
22+
.option("--onlyFailures", "Run tests that failed in the previous");
2223

2324
program.parse(process.argv);
2425
const options = program.opts();
@@ -39,16 +40,19 @@ if (includes === undefined) {
3940
const excludes = config.exclude || [];
4041
validatArgument(includes, excludes);
4142

42-
// if enabled testcase or testNamePattern, disable collectCoverage by default
43+
const onlyFailures = options.onlyFailures || false;
44+
45+
// if enabled testcase or testNamePattern or onlyFailures, disable collectCoverage by default
4346
const collectCoverage =
44-
Boolean(options.collectCoverage) || config.collectCoverage || (!options.testcase && !options.testNamePattern);
47+
Boolean(options.collectCoverage) || config.collectCoverage || (!options.testcase && !options.testNamePattern && !onlyFailures);
4548

4649
const testOption = {
4750
includes,
4851
excludes,
4952
testcases: options.testcase,
5053
testNamePattern: options.testNamePattern,
5154
collectCoverage,
55+
onlyFailures,
5256

5357
flags: config.flags || "",
5458
imports: config.imports || undefined,

src/core/execute.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const readFile = promises.readFile;
1616
async function nodeExecutor(
1717
instrumentResult: InstrumentResult,
1818
outFolder: string,
19-
matchedTestNames?: string[],
19+
matchedTestNames: string[],
2020
imports?: Imports
2121
): Promise<SingleExecutionResult> {
2222
const wasi = new WASI({
@@ -52,7 +52,7 @@ async function nodeExecutor(
5252
wasi.start(ins);
5353
const execTestFunction = ins.exports["executeTestFunction"];
5454
assert(typeof execTestFunction === "function");
55-
if (matchedTestNames === undefined) {
55+
if (matchedTestNames.length === 0) {
5656
// By default, all testcases are executed
5757
for (const functionInfo of executionRecorder.registerFunctions) {
5858
const [testCaseName, functionIndex] = functionInfo;
@@ -85,7 +85,7 @@ async function nodeExecutor(
8585
export async function execWasmBinaries(
8686
outFolder: string,
8787
instrumentResults: InstrumentResult[],
88-
matchedTestNames?: string[],
88+
matchedTestNames: string[],
8989
imports?: Imports
9090
): Promise<ExecutionResult> {
9191
const assertRes = new ExecutionResult();

src/core/precompile.ts

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,59 @@ import { SourceFunctionInfo, UnittestPackage } from "../interface.js";
1010
import { projectRoot } from "../utils/projectRoot.js";
1111
import assert from "node:assert";
1212

13+
// eslint-disable-next-line sonarjs/cognitive-complexity
1314
export async function precompile(
1415
includes: string[],
1516
excludes: string[],
16-
testcases: string[] | undefined,
17+
testcases: string[] | undefined, // this field specifed test file names
1718
testNamePattern: string | undefined,
19+
failedTestNames: string[],
1820
collectCoverage: boolean,
1921
flags: string
2022
): Promise<UnittestPackage> {
2123
// if specify testcases, use testcases for unittest
2224
// otherwise, get testcases(*.test.ts) in includes directory
2325
const testCodePaths = testcases ?? getRelatedFiles(includes, excludes, (path: string) => path.endsWith(".test.ts"));
26+
const matchedTestFiles = new Set<string>();
27+
let matchedTestNames: string[] = [];
2428

25-
if (testNamePattern) {
26-
const matchedTestNames: string[] = [];
27-
const matchedTestFiles = new Set<string>();
29+
if (testNamePattern || failedTestNames.length > 0) {
30+
// if enabled testNamePattern or enabled onlyFailures, need listTestName transform
2831
const testNameInfos = new Map<string, string[]>();
2932
const testNameTransformFunction = join(projectRoot, "transform", "listTestNames.mjs");
3033
for (const testCodePath of testCodePaths) {
3134
await transform(testNameTransformFunction, testCodePath, flags, () => {
3235
testNameInfos.set(testCodePath, testNames);
3336
});
3437
}
35-
const regexPattern = new RegExp(testNamePattern);
36-
for (const [fileName, testNames] of testNameInfos) {
37-
for (const testName of testNames) {
38-
if (regexPattern.test(testName)) {
39-
matchedTestNames.push(testName);
40-
matchedTestFiles.add(fileName);
38+
if (testNamePattern) {
39+
const regexPattern = new RegExp(testNamePattern);
40+
for (const [fileName, testNames] of testNameInfos) {
41+
for (const testName of testNames) {
42+
if (regexPattern.test(testName)) {
43+
matchedTestNames.push(testName);
44+
matchedTestFiles.add(fileName);
45+
}
4146
}
4247
}
4348
}
4449

45-
assert(matchedTestFiles.size > 0, `No matched testname using ${testNamePattern}`);
46-
return {
47-
testCodePaths: Array.from(matchedTestFiles),
48-
matchedTestNames: matchedTestNames,
49-
};
50+
if (failedTestNames.length > 0) {
51+
matchedTestNames = failedTestNames;
52+
for (const [fileName, testNames] of testNameInfos) {
53+
for (const testName of testNames) {
54+
if (matchedTestNames.includes(testName)) {
55+
matchedTestFiles.add(fileName);
56+
}
57+
}
58+
}
59+
}
60+
61+
assert(matchedTestFiles.size > 0, "No matched testname");
5062
}
5163

64+
const sourceFunctions = new Map<string, SourceFunctionInfo[]>();
5265
if (collectCoverage) {
53-
const sourceFunctions = new Map<string, SourceFunctionInfo[]>();
5466
const sourceCodePaths = getRelatedFiles(includes, excludes, (path: string) => !path.endsWith(".test.ts"));
5567
const sourceTransformFunction = join(projectRoot, "transform", "listFunctions.mjs");
5668
// The batchSize = 2 is empirical data after benchmarking
@@ -64,13 +76,13 @@ export async function precompile(
6476
)
6577
);
6678
}
67-
return {
68-
testCodePaths,
69-
sourceFunctions,
70-
};
7179
}
7280

73-
return { testCodePaths };
81+
return {
82+
testCodePaths: matchedTestFiles.size > 0 ? Array.from(matchedTestFiles) : testCodePaths,
83+
matchedTestNames,
84+
sourceFunctions,
85+
};
7486
}
7587

7688
async function transform(transformFunction: string, codePath: string, flags: string, collectCallback: () => void) {

src/executionResult.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { json2map } from "./utils/index.js";
33
import { FailedInfoMap, AssertMessage, ExpectInfo, IAssertResult } from "./interface.js";
44
import chalk from "chalk";
55

6-
const readFile = promises.readFile;
6+
const { readFile, writeFile } = promises;
77

88
export class ExecutionResult {
99
fail = 0;
@@ -43,6 +43,10 @@ export class ExecutionResult {
4343
}
4444
}
4545

46+
async writeFailures(failuresPath: string) {
47+
await writeFile(failuresPath, JSON.stringify(Array.from(this.failedInfos.keys())));
48+
}
49+
4650
print(log: (msg: string) => void): void {
4751
const rate =
4852
(this.fail === 0 ? chalk.greenBright(this.total) : chalk.redBright(this.total - this.fail)) +

src/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import chalk from "chalk";
2-
import { emptydirSync } from "fs-extra";
2+
import pkg from "fs-extra";
33
import { Parser } from "./parser/index.js";
44
import { compile } from "./core/compile.js";
55
import { precompile } from "./core/precompile.js";
66
import { instrument } from "./core/instrument.js";
77
import { execWasmBinaries } from "./core/execute.js";
88
import { generateReport, reportConfig } from "./generator/index.js";
99
import { TestOption } from "./interface.js";
10+
import { join } from "node:path";
11+
import assert from "node:assert";
12+
13+
const { readFileSync, emptydirSync } = pkg;
1014

1115
export function validatArgument(includes: unknown, excludes: unknown) {
1216
if (!Array.isArray(includes)) {
@@ -31,13 +35,21 @@ export function validatArgument(includes: unknown, excludes: unknown) {
3135
* main function of unit-test, will throw Exception in most condition except job carsh
3236
*/
3337
export async function start_unit_test(options: TestOption): Promise<boolean> {
38+
const failurePath = join(options.outputFolder, "failures.json");
39+
let failedTestCases: string[] = [];
40+
if (options.onlyFailures) {
41+
failedTestCases = JSON.parse(readFileSync(failurePath, "utf8")) as string[];
42+
assert(failedTestCases.length > 0, "No failed test cases found");
43+
}
44+
3445
emptydirSync(options.outputFolder);
3546
emptydirSync(options.tempFolder);
3647
const unittestPackage = await precompile(
3748
options.includes,
3849
options.excludes,
3950
options.testcases,
4051
options.testNamePattern,
52+
failedTestCases,
4153
options.collectCoverage,
4254
options.flags
4355
);
@@ -58,6 +70,7 @@ export async function start_unit_test(options: TestOption): Promise<boolean> {
5870
);
5971
console.log(chalk.blueBright("execute testcases: ") + chalk.bold.greenBright("OK"));
6072

73+
await executedResult.writeFailures(failurePath);
6174
executedResult.print(console.log);
6275
if (options.collectCoverage) {
6376
const parser = new Parser();

src/interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export class CodeCoverage {
168168

169169
export interface UnittestPackage {
170170
readonly testCodePaths: string[];
171-
readonly matchedTestNames?: string[];
171+
readonly matchedTestNames: string[];
172172
readonly sourceFunctions?: Map<string, SourceFunctionInfo[]>;
173173
}
174174

@@ -197,6 +197,7 @@ export interface TestOption {
197197
testcases?: string[];
198198
testNamePattern?: string;
199199
collectCoverage: boolean;
200+
onlyFailures: boolean;
200201

201202
flags: string;
202203
imports?: Imports;

0 commit comments

Comments
 (0)