Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ if (options.testcase !== undefined) {
const testFiles = options.testFiles || options.testcase;

const onlyFailures = options.onlyFailures || false;
const testNamePattern = options.testNamePattern ?? null;

if (onlyFailures && testNamePattern !== null) {
console.error(chalk.redBright("Cannot use --onlyFailures and --testNamePattern together") + "\n");
exit(3);
}

// if enabled testcase or testNamePattern or onlyFailures, disable collectCoverage by default
const collectCoverage =
Expand Down Expand Up @@ -89,7 +95,7 @@ const testOption = {
includes,
excludes,
testFiles,
testNamePattern: options.testNamePattern,
testNamePattern: testNamePattern,
collectCoverage,
onlyFailures,

Expand Down
8 changes: 4 additions & 4 deletions src/core/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const readFile = promises.readFile;
async function nodeExecutor(
instrumentResult: InstrumentResult,
outFolder: string,
matchedTestNames: string[],
filterByName: (fullTestName: string) => boolean,
imports?: Imports
): Promise<ExecutionResult> {
const wasi = new WASI({
Expand Down Expand Up @@ -85,7 +85,7 @@ async function nodeExecutor(
break;
}
const { fullName, functionIndex, setupFunctions, teardownFunctions } = testCase;
if (matchedTestNames.length === 0 || matchedTestNames.includes(fullName)) {
if (filterByName(fullName)) {
await executionRecorder.runTestFunction(
fullName,
() => {
Expand All @@ -110,14 +110,14 @@ async function nodeExecutor(
export async function execWasmBinaries(
outFolder: string,
instrumentResults: InstrumentResult[],
matchedTestNames: string[],
filterByName: (fullTestName: string) => boolean,
imports?: Imports
): Promise<ExecutionResultSummary> {
const assertRes = new ExecutionResultSummary();
ensureDirSync(outFolder);
await Promise.all<void>(
instrumentResults.map(async (instrumentResult): Promise<void> => {
const result: ExecutionResult = await nodeExecutor(instrumentResult, outFolder, matchedTestNames, imports);
const result: ExecutionResult = await nodeExecutor(instrumentResult, outFolder, filterByName, imports);
await assertRes.merge(result, instrumentResult.expectInfo);
})
);
Expand Down
67 changes: 21 additions & 46 deletions src/core/precompile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,60 +7,33 @@ import { join, relative, resolve } from "node:path";
import { getIncludeFiles } from "../utils/pathResolver.js";
import { SourceFunctionInfo, UnittestPackage } from "../interface.js";
import { projectRoot } from "../utils/projectRoot.js";
import assert from "node:assert";
import { ascMain } from "../utils/ascWrapper.js";
import assert from "node:assert";

function getFilterByName(testNamePattern: string | null, failedTestNames: string[]): UnittestPackage["filterByName"] {
assert(
!(testNamePattern !== null && failedTestNames.length > 0),
"Cannot use testNamePattern and failedTestNames together"
);
if (testNamePattern !== null) {
const regexPattern = new RegExp(testNamePattern);
return (fullTestName: string): boolean => regexPattern.test(fullTestName);
}
if (failedTestNames.length > 0) {
return (fullTestName: string): boolean => failedTestNames.includes(fullTestName);
}
return (): boolean => true;
}

// eslint-disable-next-line sonarjs/cognitive-complexity
export async function precompile(
includes: string[],
excludes: string[],
testFiles: string[] | undefined, // this field specifed test file names
testNamePattern: string | undefined,
testNamePattern: string | null,
failedTestNames: string[],
collectCoverage: boolean,
flags: string
): Promise<UnittestPackage> {
// if specify testFiles, use testFiles for unittest
// otherwise, get testFiles(*.test.ts) in includes directory
const testCodePaths = testFiles ?? getRelatedFiles(includes, excludes, (path: string) => path.endsWith(".test.ts"));
const matchedTestFiles = new Set<string>();
let matchedTestNames: string[] = [];

if (testNamePattern || failedTestNames.length > 0) {
// if enabled testNamePattern or enabled onlyFailures, need listTestName transform
const testNameInfos = new Map<string, string[]>();
const testNameTransformFunction = join(projectRoot, "transform", "listTestNames.mjs");
for (const testCodePath of testCodePaths) {
await transform(testNameTransformFunction, testCodePath, flags, () => {
testNameInfos.set(testCodePath, testNames);
});
}
if (testNamePattern) {
const regexPattern = new RegExp(testNamePattern);
for (const [fileName, testNames] of testNameInfos) {
for (const testName of testNames) {
if (regexPattern.test(testName)) {
matchedTestNames.push(testName);
matchedTestFiles.add(fileName);
}
}
}
}

if (failedTestNames.length > 0) {
matchedTestNames = failedTestNames;
for (const [fileName, testNames] of testNameInfos) {
for (const testName of testNames) {
if (matchedTestNames.includes(testName)) {
matchedTestFiles.add(fileName);
}
}
}
}

assert(matchedTestFiles.size > 0, "No matched testname");
}

let sourceFunctions: Map<string, SourceFunctionInfo[]> | undefined = undefined;
if (collectCoverage) {
const sourceCodePaths = getRelatedFiles(includes, excludes, (path: string) => !path.endsWith(".test.ts"));
Expand All @@ -69,8 +42,10 @@ export async function precompile(
sourceFunctions = await transform(sourceTransformFunction, sourceCodePaths, flags, () => __functionInfos);
}
return {
testCodePaths: matchedTestFiles.size > 0 ? Array.from(matchedTestFiles) : testCodePaths,
matchedTestNames,
// if specify testFiles, use testFiles for unittest
// otherwise, get testFiles(*.test.ts) in includes directory
testCodePaths: testFiles ?? getRelatedFiles(includes, excludes, (path: string) => path.endsWith(".test.ts")),
filterByName: getFilterByName(testNamePattern, failedTestNames),
sourceFunctions: sourceFunctions || new Map<string, SourceFunctionInfo[]>(),
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async function startUniTestImpl(options: TestOption): Promise<number> {
const executedResult = await execWasmBinaries(
options.tempFolder,
instrumentResult,
unittestPackage.matchedTestNames,
unittestPackage.filterByName,
options.imports
);
console.log(chalk.blueBright("execute test files: ") + chalk.bold.greenBright("OK"));
Expand Down
4 changes: 2 additions & 2 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export class CodeCoverage {

export interface UnittestPackage {
readonly testCodePaths: string[];
readonly matchedTestNames: string[];
readonly filterByName: (fullTestName: string) => boolean;
readonly sourceFunctions?: Map<string, SourceFunctionInfo[]>;
}

Expand All @@ -201,7 +201,7 @@ export interface TestOption {
includes: string[];
excludes: string[];
testFiles?: string[];
testNamePattern?: string;
testNamePattern: string | null;
collectCoverage: boolean;
onlyFailures: boolean;

Expand Down
22 changes: 22 additions & 0 deletions tests/e2e/on-failure-only/as-test.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import path from "node:path";

const __dirname = path.dirname(new URL(import.meta.url).pathname);

/**
* @type {import("../../../config.d.ts").Config}
*/
export default {
include: [__dirname],
imports(runtime) {
return {
env: {
log: (msg) => {
runtime.framework.log(runtime.exports.__getString(msg));
},
},
};
},
temp: path.join(__dirname, "tmp"),
output: path.join(__dirname, "tmp"),
mode: [],
};
9 changes: 9 additions & 0 deletions tests/e2e/on-failure-only/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { test, expect } from "../../../assembly";

test("failure on test", () => {
expect(1 + 1).equal(3);
});

test("success on test", () => {
expect(1 + 1).equal(2);
});
21 changes: 21 additions & 0 deletions tests/e2e/on-failure-only/stdout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
code analysis: OK
compile test files: OK
instrument: OK
execute test files: OK

test case: 1/2 (success/total)

Error Message:
failure on test:
tests/e2e/on-failure-only/index.test.ts:4:2 value: 2 expect: = 3

code analysis: OK
compile test files: OK
instrument: OK
execute test files: OK

test case: 0/1 (success/total)

Error Message:
failure on test:
tests/e2e/on-failure-only/index.test.ts:4:2 value: 2 expect: = 3
4 changes: 4 additions & 0 deletions tests/e2e/on-failure-only/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "assemblyscript/std/assembly.json",
"include": ["./**/*.ts"]
}
62 changes: 47 additions & 15 deletions tests/e2e/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,49 +28,81 @@ function isEnabled(name) {
return enabledTests.includes(name);
}

function runEndToEndTest(name, flags, handle) {
if (isEnabled(name)) {
async function runEndToEndTest(name, flags, handle) {
if (!isEnabled(name)) {
return;
}
return new Promise((resolve) => {
console.log(`Running e2e test: ${name}`);
exec(`node ./bin/as-test.js --config tests/e2e/${name}/as-test.config.js ${flags}`, (error, stdout, stderr) => {
// standard check
const expectStdOut = readFileSync(`tests/e2e/${name}/stdout.txt`, "utf-8");
if (expectStdOut !== stdout) {
console.log(`========= STDOUT ${name} =========`);
console.log(getDiff(expectStdOut, stdout));
console.log(`========= STDERR ${name} =========`);
console.log(stderr);
process.exit(1);
}
// customize check
handle(error, stdout, stderr);
resolve();
});
});
}

function checkOutput(name, stdout, stderr) {
const expectStdOut = readFileSync(`tests/e2e/${name}/stdout.txt`, "utf-8");
if (expectStdOut !== stdout) {
console.log(`========= STDOUT ${name} =========`);
console.log(getDiff(expectStdOut, stdout));
console.log(`========= STDERR ${name} =========`);
console.log(stderr);
process.exit(1);
}
}

runEndToEndTest("assertFailed", "", (error, stdout, stderr) => {
checkOutput("assertFailed", stdout, stderr);
assert(error.code === 1);
});

runEndToEndTest("compilationFailed", "", (error, stdout, stderr) => {
checkOutput("compilationFailed", stdout, stderr);
assert(error.code === 2);
});

runEndToEndTest("isolated-cli", "--isolated false", (error, stdout, stderr) => {});
runEndToEndTest("isolated-false", "", (error, stdout, stderr) => {});
runEndToEndTest("isolated-true", "", (error, stdout, stderr) => {});
runEndToEndTest("isolated-cli", "--isolated false", (error, stdout, stderr) => {
checkOutput("isolated-cli", stdout, stderr);
});
runEndToEndTest("isolated-false", "", (error, stdout, stderr) => {
checkOutput("isolated-false", stdout, stderr);
});
runEndToEndTest("isolated-true", "", (error, stdout, stderr) => {
checkOutput("isolated-true", stdout, stderr);
});

(async () => {
let mergedStdout = "";
let mergedStderr = "";
await runEndToEndTest("on-failure-only", "", (error, stdout, stderr) => {
mergedStdout += stdout;
mergedStderr += stderr;
});
mergedStdout += "\n";
mergedStderr += "\n";
await runEndToEndTest("on-failure-only", "--onlyFailures", (error, stdout, stderr) => {
mergedStdout += stdout;
mergedStderr += stderr;
});
checkOutput("on-failure-only", mergedStdout, mergedStderr);
})();

runEndToEndTest("printLogInFailedInfo", "", (error, stdout, stderr) => {
checkOutput("printLogInFailedInfo", stdout, stderr);
assert(error.code === 1);
});

runEndToEndTest("setup-teardown", "", (error, stdout, stderr) => {
checkOutput("setup-teardown", stdout, stderr);
assert(error.code === 1);
});

runEndToEndTest(
"testFiles",
"--testFiles tests/e2e/testFiles/succeed_0.test.ts tests/e2e/testFiles/succeed_1.test.ts",
(error, stdout, stderr) => {
checkOutput("testFiles", stdout, stderr);
assert(error === null);
}
);
Loading