Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
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