Skip to content

Commit 725a24c

Browse files
XMadridHerrCai0907
andauthored
feat: support --onlyFailures (#53)
Co-authored-by: Congcong Cai <[email protected]>
1 parent d8e505e commit 725a24c

File tree

12 files changed

+82
-49
lines changed

12 files changed

+82
-49
lines changed

bin/as-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ if (lt(version, "12.16.0")) {
1414
exit(-1);
1515
}
1616
const argv = [];
17-
argv.push("--experimental-wasi-unstable-preview1");
17+
argv.push("--no-warnings");
1818
if (lt(version, "15.0.0")) {
1919
argv.push("--experimental-wasm-bigint");
2020
}

bin/cli.js

Lines changed: 9 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,21 @@ 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) ||
48+
config.collectCoverage ||
49+
(!options.testcase && !options.testNamePattern && !onlyFailures);
4550

4651
const testOption = {
4752
includes,
4853
excludes,
4954
testcases: options.testcase,
5055
testNamePattern: options.testNamePattern,
5156
collectCoverage,
57+
onlyFailures,
5258

5359
flags: config.flags || "",
5460
imports: config.imports || undefined,

docs/api-documents/options.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ There are command line options which can override the configuration in `as-test.
2727
```
2828
--testcase <testcases...> only run specified test cases
2929
--testNamePattern <test name pattern> run only tests with a name that matches the regex pattern
30+
--onlyFailures Run tests that failed in the previous
3031
```
3132

3233
There are several ways to run partial test cases:
3334

34-
#### Partial Test Files
35+
#### Run specified test files
3536

3637
Providing file path to `--testcase`, it can specify a certain group of files for testing.
3738

@@ -55,7 +56,7 @@ run `as-test --testcase a.test.ts b.test.ts` will match all tests in `a.test.ts`
5556

5657
:::
5758

58-
#### Partial Tests
59+
#### Run partial tests using a regex name pattern
5960

6061
Providing regex which can match targeted test name to `--testNamePattern`, it can specify a certain group of tests for testing.
6162

@@ -94,6 +95,10 @@ The framework join `DescriptionName` and `TestName` with `" "` by default, e.g.
9495

9596
:::
9697

98+
#### Run only failures
99+
100+
Provides `--onlyFailures` command line option to run the test cases that failed in the previous test only.
101+
97102
### Whether collect coverage information
98103

99104
```

package-lock.json

Lines changed: 0 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
},
3030
"dependencies": {
3131
"@assemblyscript/loader": ">=0.25.1",
32-
"@assemblyscript/wasi-shim": "^0.1.0",
3332
"chalk": "^5.2.0",
3433
"commander": "^8.3.0",
3534
"cross-spawn": "^7.0.3",

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: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
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+
12+
const { readFileSync, emptydirSync } = pkg;
1013

1114
export function validatArgument(includes: unknown, excludes: unknown) {
1215
if (!Array.isArray(includes)) {
@@ -31,13 +34,28 @@ export function validatArgument(includes: unknown, excludes: unknown) {
3134
* main function of unit-test, will throw Exception in most condition except job carsh
3235
*/
3336
export async function start_unit_test(options: TestOption): Promise<boolean> {
37+
const failurePath = join(options.outputFolder, "failures.json");
38+
let failedTestCases: string[] = [];
39+
if (options.onlyFailures) {
40+
failedTestCases = JSON.parse(readFileSync(failurePath, "utf8")) as string[];
41+
if (failedTestCases.length === 0) {
42+
options.collectCoverage = true;
43+
console.log(
44+
chalk.yellowBright(
45+
'Warning: no failed test cases found while enabled "onlyFailures", execute all test cases by default'
46+
)
47+
);
48+
}
49+
}
50+
3451
emptydirSync(options.outputFolder);
3552
emptydirSync(options.tempFolder);
3653
const unittestPackage = await precompile(
3754
options.includes,
3855
options.excludes,
3956
options.testcases,
4057
options.testNamePattern,
58+
failedTestCases,
4159
options.collectCoverage,
4260
options.flags
4361
);
@@ -58,6 +76,7 @@ export async function start_unit_test(options: TestOption): Promise<boolean> {
5876
);
5977
console.log(chalk.blueBright("execute testcases: ") + chalk.bold.greenBright("OK"));
6078

79+
await executedResult.writeFailures(failurePath);
6180
executedResult.print(console.log);
6281
if (options.collectCoverage) {
6382
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)