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
4 changes: 4 additions & 0 deletions assembly/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export namespace assertResult {
export declare function registerTestFunction(index: u32): void;


@external("__unittest_framework_env","finishTestFunction")
export declare function finishTestFunction(): 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 @@ -13,6 +13,7 @@ export function testImpl(description: string, testFunction: () => void): void {
assertResult.addDescription(description);
assertResult.registerTestFunction(testFunction.index);
testFunction();
assertResult.finishTestFunction();
assertResult.removeDescription();
mockFunctionStatus.clear();
}
Expand Down
8 changes: 7 additions & 1 deletion example/as-test.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ module.exports = {
* @returns
*/
imports(runtime) {
return {};
return {
env: {
log: (msg) => {
runtime.framework.log(runtime.exports.__getString(msg));
},
},
};
},

/** template file path, default "coverage" */
Expand Down
8 changes: 7 additions & 1 deletion example/as-test.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ export default {
* @returns
*/
imports(runtime) {
return {};
return {
env: {
log: (msg) => {
runtime.framework.log(runtime.exports.__getString(msg));
},
},
};
},

/** template file path, default "coverage" */
Expand Down
1 change: 1 addition & 0 deletions example/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export declare function log(msg: string): void;
7 changes: 3 additions & 4 deletions example/source.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, test, expect, mock, endTest, unmock, remock } from "../assembly";

import { describe, test, expect, mock } from "../assembly";
import { log } from "./env";
import { add, Test } from "./source";

describe("example 1", () => {
Expand All @@ -16,6 +16,7 @@ describe("example 1", () => {
expect(add(2, 2)).equal(3);
});
test("out of mock range, this test should be failed", () => {
log("2 + 2 should be 4");
expect(add(2, 2)).equal(3);
});
});
Expand All @@ -36,5 +37,3 @@ describe("example 2", () => {
expect(fn.calls).equal(1);
});
});

endTest();
4 changes: 1 addition & 3 deletions example/source2.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, test, expect, mock, endTest } from "../assembly";
import { describe, test, expect } from "../assembly";
import { quick_sort } from "./source2";

describe("quick_sork", () => {
Expand All @@ -14,5 +14,3 @@ describe("quick_sork", () => {
expect(d).equal([1, 2, 3, 4, 5]);
});
});

endTest();
36 changes: 30 additions & 6 deletions src/assertResult.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { promises } from "node:fs";
import { json2map } from "./utils/index.js";
import { AssertErrorMessages, AssertMessage, ErrorMessages, ExpectInfo, IAssertResult } from "./interface.js";
import { FailedInfoMap, AssertMessage, ExpectInfo, IAssertResult } from "./interface.js";
import chalk from "chalk";

const readFile = promises.readFile;

export class AssertResult {
fail = 0;
total = 0;
failed_info: AssertErrorMessages = new Map();
failedInfos: FailedInfoMap = new Map();

async merge(result: IAssertResult, expectInfoFilePath: string) {
this.fail += result.fail;
Expand All @@ -17,8 +18,8 @@ export class AssertResult {
try {
const expectContent = await readFile(expectInfoFilePath, { encoding: "utf8" });
expectInfo = json2map(JSON.parse(expectContent) as ExpectInfo);
for (const [key, value] of json2map<AssertMessage[]>(result.failed_info)) {
const errorMsgs: ErrorMessages = [];
for (const [testcaseName, value] of json2map<AssertMessage[]>(result.failed_info)) {
const errorMsgs: string[] = [];
for (const msg of value) {
const [index, actualValue, expectValue] = msg;
const debugLocation = expectInfo.get(index);
Expand All @@ -28,13 +29,36 @@ export class AssertResult {
}
errorMsgs.push(errorMsg);
}
this.failed_info.set(key, errorMsgs);
this.failedInfos.set(testcaseName, {
assertMessages: errorMsgs,
logMessages: result.failedLogMessages[testcaseName],
});
}
} catch (error) {
if (error instanceof Error) {
console.error(error.stack);
}
throw new Error(`maybe forget call "endTest()" at the end of "*.test.ts" or Job abort before output`);
throw error;
}
}
}

print(log: (msg: string) => void): void {
const rate =
(this.fail === 0 ? chalk.greenBright(this.total) : chalk.redBright(this.total - this.fail)) +
"/" +
this.total.toString();
log(`\ntest case: ${rate} (success/total)\n`);
if (this.fail !== 0) {
log(chalk.red("Error Message: "));
for (const [testcaseName, { assertMessages, logMessages }] of this.failedInfos.entries()) {
log(`\t${testcaseName}: `);
for (const assertMessage of assertMessages) {
log("\t\t" + chalk.yellow(assertMessage));
}
for (const logMessage of logMessages ?? []) {
log(chalk.gray(logMessage));
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async function nodeExecutor(
const executionRecorder = new ExecutionRecorder();
const coverageRecorder = new CoverageRecorder();

const importsArg = new ImportsArgument();
const importsArg = new ImportsArgument(executionRecorder);
const userDefinedImportsObject = imports === null ? {} : imports(importsArg);
const importObject: ASImports = {
wasi_snapshot_preview1: wasi.wasiImport,
Expand Down
68 changes: 59 additions & 9 deletions src/core/executionRecorder.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,81 @@
import { ImportsArgument } from "../index.js";
import { AssertFailMessage, AssertMessage, IAssertResult } from "../interface.js";
import { ImportsArgument, UnitTestFramework } from "../index.js";
import { AssertFailMessage, AssertMessage, IAssertResult, FailedLogMessages } from "../interface.js";

export class ExecutionRecorder implements IAssertResult {
class LogRecorder {
#currentTestLogMessages: string[] = [];
#isTestFailed: boolean = false;

addLog(msg: string): void {
this.#currentTestLogMessages.push(msg);
}
markTestFailed(): void {
this.#isTestFailed = true;
}

onStartTest(): void {
this.#currentTestLogMessages = [];
this.#isTestFailed = false;
}
onFinishTest(): string[] | null {
if (this.#currentTestLogMessages.length === 0) {
return null;
}
if (this.#isTestFailed === false) {
return null;
}
return this.#currentTestLogMessages;
}
}

// to do: split execution environment and recorder
export class ExecutionRecorder implements IAssertResult, UnitTestFramework {
total: number = 0;
fail: number = 0;
failed_info: AssertFailMessage = {};
failedLogMessages: FailedLogMessages = {};

registerFunctions: [string, number][] = [];
_currentTestDescriptions: string[] = [];
#currentTestDescriptions: string[] = [];
#logRecorder = new LogRecorder();

get #currentTestDescription(): string {
return this.#currentTestDescriptions.join(" - ");
}

_addDescription(description: string): void {
this._currentTestDescriptions.push(description);
this.#currentTestDescriptions.push(description);
}
_removeDescription(): void {
this._currentTestDescriptions.pop();
this.#currentTestDescriptions.pop();
}
registerTestFunction(fncIndex: number): void {
const testCaseFullName = this._currentTestDescriptions.join(" - ");
this.registerFunctions.push([testCaseFullName, fncIndex]);
this.registerFunctions.push([this.#currentTestDescription, fncIndex]);
this.#logRecorder.onStartTest();
}
_finishTestFunction(): void {
const logMessages: string[] | null = this.#logRecorder.onFinishTest();
if (logMessages !== null) {
const testCaseFullName = this.#currentTestDescription;
this.failedLogMessages[testCaseFullName] = (this.failedLogMessages[testCaseFullName] || []).concat(logMessages);
}
}

collectCheckResult(result: boolean, codeInfoIndex: number, actualValue: string, expectValue: string): void {
this.total++;
if (!result) {
this.#logRecorder.markTestFailed();
this.fail++;
const testCaseFullName = this._currentTestDescriptions.join(" - ");
const testCaseFullName = this.#currentTestDescription;
const assertMessage: AssertMessage = [codeInfoIndex.toString(), actualValue, expectValue];
this.failed_info[testCaseFullName] = this.failed_info[testCaseFullName] || [];
this.failed_info[testCaseFullName].push(assertMessage);
}
}

log(msg: string): void {
this.#logRecorder.addLog(msg);
}

getCollectionFuncSet(arg: ImportsArgument): Record<string, Record<string, unknown>> {
return {
__unittest_framework_env: {
Expand All @@ -41,6 +88,9 @@ export class ExecutionRecorder implements IAssertResult {
registerTestFunction: (index: number): void => {
this.registerTestFunction(index);
},
finishTestFunction: () => {
this._finishTestFunction();
},
collectCheckResult: (result: number, codeInfoIndex: number, actualValue: number, expectValue: number): void => {
this.collectCheckResult(
result !== 0,
Expand Down
27 changes: 10 additions & 17 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,11 @@ import { emptydirSync } from "fs-extra";
import { ASUtil } from "@assemblyscript/loader";
import { Parser } from "./parser/index.js";
import { compile } from "./core/compile.js";
import { AssertResult } from "./assertResult.js";
import { precompile } from "./core/precompile.js";
import { instrument } from "./core/instrument.js";
import { execWasmBinaries } from "./core/execute.js";
import { generateReport, reportConfig } from "./generator/index.js";

function logAssertResult(trace: AssertResult): void {
const render = (failed: number, total: number) =>
(trace.fail === 0 ? chalk.greenBright(total) : chalk.redBright(total - failed)) + "/" + trace.total.toString();
console.log(`\ntest case: ${render(trace.fail, trace.total)} (success/total)\n`);
if (trace.fail !== 0) {
console.log(chalk.red("Error Message: "));
for (const [k, errMsgs] of trace.failed_info.entries()) {
console.log(`\t${k}: `);
for (const v of errMsgs) {
console.log("\t\t" + chalk.yellow(v));
}
}
}
}

export function validatArgument(includes: unknown, excludes: unknown) {
if (!Array.isArray(includes)) {
throw new TypeError("include section illegal");
Expand All @@ -43,10 +27,19 @@ export function validatArgument(includes: unknown, excludes: unknown) {
}
}

export abstract class UnitTestFramework {
/**
* function to redirect log message to unittest framework
* @param msg: message to log
*/
abstract log(msg: string): void;
}

export class ImportsArgument {
module: WebAssembly.Module | null = null;
instance: WebAssembly.Instance | null = null;
exports: (ASUtil & Record<string, unknown>) | null = null;
constructor(public framework: UnitTestFramework) {}
}

export type Imports = ((arg: ImportsArgument) => Record<string, unknown>) | null;
Expand Down Expand Up @@ -84,7 +77,7 @@ export async function start_unit_test(fo: FileOption, to: TestOption, oo: Output
console.log(chalk.blueBright("instrument: ") + chalk.bold.greenBright("OK"));
const executedResult = await execWasmBinaries(oo.tempFolder, instrumentResult, to.imports);
console.log(chalk.blueBright("execute testcases: ") + chalk.bold.greenBright("OK"));
logAssertResult(executedResult);
executedResult.print(console.log);
const parser = new Parser();
const fileCoverageInfo = await parser.parse(instrumentResult, unittestPackage.sourceFunctions);
reportConfig.warningLimit = oo.warnLimit ?? reportConfig.warningLimit;
Expand Down
6 changes: 4 additions & 2 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ export type AssertActualValue = string;
export type AssertMessage = [ExpectInfoIndex, AssertActualValue, AssertExpectValue];
export type AssertFailMessage = Record<TestCaseName, AssertMessage[]>;

export type ErrorMessages = string[];
export type AssertErrorMessages = Map<TestCaseName, ErrorMessages>;
export type FailedLogMessages = Record<TestCaseName, string[]>;

export type FailedInfoMap = Map<TestCaseName, { assertMessages: string[]; logMessages: string[] | undefined }>;

export type ExpectInfoDebugLocation = string;
export type ExpectInfo = Record<ExpectInfoIndex, ExpectInfoDebugLocation>;
Expand All @@ -79,6 +80,7 @@ export interface IAssertResult {
fail: number;
total: number;
failed_info: AssertFailMessage;
failedLogMessages: FailedLogMessages;
}

export interface ImportFunctionInfo {
Expand Down
25 changes: 25 additions & 0 deletions tests/ts/test/__snapshots__/assertResult.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`print 1`] = `
"
test case: 27/28 (success/total)

Error Message:
A:
tests/as/comparison.test.ts:10:20 value: 100 expect: = 200
log message 1
log message 2
log message 3"
`;

exports[`print 2`] = `
"
test case: 27/28 (success/total)

Error Message: 
A:
tests/as/comparison.test.ts:10:20 value: 100 expect: = 200
log message 1
log message 2
log message 3"
`;
Loading