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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/dist

/build*
/transform/listFunctions.mjs
/transform/*.mjs
/transform/tsconfig.tsbuildinfo

/coverage
Expand Down
4 changes: 4 additions & 0 deletions assembly/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export namespace assertResult {
export declare function removeDescription(): void;


@external("__unittest_framework_env","registerTestFunction")
export declare function registerTestFunction(index: u32): void;


@external("__unittest_framework_env","collectCheckResult")
export declare function collectCheckResult(
result: bool,
Expand Down
1 change: 1 addition & 0 deletions assembly/implement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function describeImpl(
}
export function testImpl(description: string, testFunction: () => void): void {
assertResult.addDescription(description);
assertResult.registerTestFunction(testFunction.index);
testFunction();
assertResult.removeDescription();
mockFunctionStatus.clear();
Expand Down
7 changes: 5 additions & 2 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ program
.option("--temp <path>", "test template file folder")
.option("--output <path>", "coverage report output folder")
.option("--mode <output mode>", "test result output format")
.option("--coverageLimit [error warning...]", "set warn(yellow) and error(red) upper limit in coverage report");
.option("--coverageLimit [error warning...]", "set warn(yellow) and error(red) upper limit in coverage report")
.option("--testNamePattern <test name pattern>", "run only tests with a name that matches the regex pattern");

program.parse(process.argv);
const options = program.opts();
Expand Down Expand Up @@ -53,9 +54,11 @@ let outputFolder = options.output || config.output || "coverage";
let errorLimit = options.coverageLimit?.at(0);
let warnLimit = options.coverageLimit?.at(1);

let testNamePattern = options.testNamePattern;

validatArgument(includes, excludes);
start_unit_test(
{ includes, excludes, testcases },
{ includes, excludes, testcases, testNamePattern },
{ flags, imports },
{ tempFolder, outputFolder, mode, warnLimit, errorLimit }
)
Expand Down
8 changes: 8 additions & 0 deletions src/core/executionRecorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export class ExecutionRecorder implements IAssertResult {
total: number = 0;
fail: number = 0;
failed_info: AssertFailMessage = {};
registerFunctions: [string, number][] = [];
_currentTestDescriptions: string[] = [];

_addDescription(description: string): void {
Expand All @@ -13,6 +14,10 @@ export class ExecutionRecorder implements IAssertResult {
_removeDescription(): void {
this._currentTestDescriptions.pop();
}
registerTestFunction(fncIndex: number): void {
const testCaseFullName = this._currentTestDescriptions.join(" - ");
this.registerFunctions.push([testCaseFullName, fncIndex]);
}
collectCheckResult(result: boolean, codeInfoIndex: number, actualValue: string, expectValue: string): void {
this.total++;
if (!result) {
Expand All @@ -33,6 +38,9 @@ export class ExecutionRecorder implements IAssertResult {
removeDescription: (): void => {
this._removeDescription();
},
registerTestFunction: (index: number): void => {
this.registerTestFunction(index);
},
collectCheckResult: (result: number, codeInfoIndex: number, actualValue: number, expectValue: number): void => {
this.collectCheckResult(
result !== 0,
Expand Down
64 changes: 44 additions & 20 deletions src/core/precompile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,73 @@ import { getIncludeFiles } from "../utils/pathResolver.js";
import { SourceFunctionInfo, UnittestPackage } from "../interface.js";
import { projectRoot } from "../utils/projectRoot.js";

const sourceFunctions = new Map<string, SourceFunctionInfo[]>();
export async function precompile(
includes: string[],
excludes: string[],
testcases: string[] | undefined,
flags: string,
transformFunction = join(projectRoot, "transform", "listFunctions.mjs")
testNamePattern: string | undefined,
flags: string
): Promise<UnittestPackage> {
// if specify testcases, use testcases for unittest
// otherwise, get testcases(*.test.ts) in includes directory
const testCodePaths = testcases ?? getRelatedFiles(includes, excludes, (path: string) => path.endsWith(".test.ts"));

const sourceCodePaths = getRelatedFiles(includes, excludes, (path: string) => !path.endsWith(".test.ts"));
const matchedTestNames: string[] = [];
if (testNamePattern) {
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);
});
}
const regexPattern = new RegExp(testNamePattern);
for (const testNames of testNameInfos.values()) {
for (const testName of testNames) {
if (regexPattern.test(testName)) {
matchedTestNames.push(testName);
}
}
}
}

const sourceCodePaths = getRelatedFiles(includes, excludes, (path: string) => !path.endsWith(".test.ts"));
const sourceFunctions = new Map<string, SourceFunctionInfo[]>();
const sourceTransformFunction = join(projectRoot, "transform", "listFunctions.mjs");
// The batchSize = 2 is empirical data after benchmarking
const batchSize = 2;
for (let i = 0; i < sourceCodePaths.length; i += batchSize) {
await Promise.all(
sourceCodePaths.slice(i, i + batchSize).map((sourcePath) => transform(sourcePath, transformFunction, flags))
sourceCodePaths.slice(i, i + batchSize).map((sourcePath) =>
transform(sourceTransformFunction, sourcePath, flags, () => {
sourceFunctions.set(sourcePath, functionInfos);
})
)
);
}

return {
testCodePaths,
matchedTestNames,
sourceFunctions,
};
}

async function transform(transformFunction: string, codePath: string, flags: string, collectCallback: () => void) {
let ascArgv = [codePath, "--noEmit", "--disableWarning", "--transform", transformFunction, "-O0"];
if (flags) {
const argv = flags.split(" ");
ascArgv = ascArgv.concat(argv);
}
const { error, stderr } = await main(ascArgv);
if (error) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
console.error(stderr.toString());
throw error;
}
collectCallback();
}

// a. include in config
// b. exclude in config
export function getRelatedFiles(includes: string[], excludes: string[], filter: (path: string) => boolean) {
Expand All @@ -58,18 +97,3 @@ export function getRelatedFiles(includes: string[], excludes: string[], filter:
}
return result;
}

async function transform(sourceCodePath: string, transformFunction: string, flags: string) {
let ascArgv = [sourceCodePath, "--noEmit", "--disableWarning", "--transform", transformFunction, "-O0"];
if (flags) {
const argv = flags.split(" ");
ascArgv = ascArgv.concat(argv);
}
const { error, stderr } = await main(ascArgv);
if (error) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
console.error(stderr.toString());
throw error;
}
sourceFunctions.set(sourceCodePath, functionInfos);
}
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface FileOption {
includes: string[];
excludes: string[];
testcases: string[] | undefined;
testNamePattern: string | undefined;
}
export interface TestOption {
flags: string;
Expand All @@ -75,7 +76,7 @@ export type OutputMode = "html" | "json" | "table";
export async function start_unit_test(fo: FileOption, to: TestOption, oo: OutputOption): Promise<boolean> {
emptydirSync(oo.outputFolder);
emptydirSync(oo.tempFolder);
const unittestPackage = await precompile(fo.includes, fo.excludes, fo.testcases, to.flags);
const unittestPackage = await precompile(fo.includes, fo.excludes, fo.testcases, fo.testNamePattern, to.flags);
console.log(chalk.blueBright("code analysis: ") + chalk.bold.greenBright("OK"));
const wasmPaths = await compile(unittestPackage.testCodePaths, oo.tempFolder, to.flags);
console.log(chalk.blueBright("compile testcases: ") + chalk.bold.greenBright("OK"));
Expand Down
6 changes: 6 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export class CodeCoverage {

export interface UnittestPackage {
readonly testCodePaths: string[];
readonly matchedTestNames: string[];
readonly sourceFunctions: Map<string, SourceFunctionInfo[]>;
}

Expand All @@ -158,5 +159,10 @@ export interface SourceFunctionInfo {
range: [number, number];
}

export interface TestNameInfo {
testName: string;
testFilePath: string;
}

export const OrganizationName = "wasm-ecosystem";
export const Repository = "https://github.com/wasm-ecosystem/assemblyscript-unittest-framework";
2 changes: 2 additions & 0 deletions src/type/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { SourceFunctionInfo } from "../interface.ts";
declare global {
// store listFunctions transform results in global
let functionInfos: SourceFunctionInfo[];
// store listTestNames transform results in global
let testNames: string[];
}

export {};
5 changes: 1 addition & 4 deletions tests/ts/test/core/precompile.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { join } from "node:path";
import { precompile } from "../../../../src/core/precompile.js";
import { projectRoot } from "../../../../src/utils/projectRoot.js";

test("listFunction transform", async () => {
const transformFunction = join(projectRoot, "transform", "listFunctions.mjs");
const unittestPackages = await precompile(["tests/ts/fixture/transformFunction.ts"], [], [], "", transformFunction);
const unittestPackages = await precompile(["tests/ts/fixture/transformFunction.ts"], [], undefined, undefined, "");
expect(unittestPackages.testCodePaths).toEqual([]);
expect(unittestPackages.sourceFunctions).toMatchSnapshot();
});
Loading