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


@external("__unittest_framework_env","registerBeforeEachFunction")
export declare function registerBeforeEachFunction(index: u32): boolean;


@external("__unittest_framework_env","registerAfterEachFunction")
export declare function registerAfterEachFunction(index: u32): boolean;


@external("__unittest_framework_env","collectCheckResult")
export declare function collectCheckResult(
result: bool,
Expand Down
10 changes: 8 additions & 2 deletions assembly/implement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ export function testImpl(name: string, testFunction: () => void): void {
assertResult.removeDescription();
}

export function beforeEachImpl(func: () => void): void {}
export function beforeEachImpl(func: () => void): void {
const result = assertResult.registerBeforeEachFunction(func.index);
assert(result, "register setup function failed");
}

export function afterEachImpl(func: () => void): void {}
export function afterEachImpl(func: () => void): void {
const result = assertResult.registerAfterEachFunction(func.index);
assert(result, "register teardown function failed");
}

export function mockImpl<T extends Function>(
originalFunction: T,
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default defineConfig({
{ text: "Configuration", link: "/api-documents/configuration" },
{ text: "Options", link: "/api-documents/options" },
{ text: "Matchers", link: "/api-documents/matchers" },
{ text: "Setup Teardown", link: "/api-documents/setup-teardown" },
{ text: "Mock Function", link: "/api-documents/mock-function" },
{ text: "Report", link: "/api-documents/coverage-report" },
{ text: "Return Code", link: "/api-documents/return-code.md" },
Expand Down
36 changes: 36 additions & 0 deletions docs/api-documents/setup-teardown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Setup And Teardown

Often while writing tests you have some setup work that needs to happen before tests run, and you have some finishing work that needs to happen after tests run. unittest framework provides helper functions to handle this.

If you have some work you need to do repeatedly for many tests, you can use `beforeEach` and `afterEach` hooks.

::: info
`beforeEach` and `afterEach` can only work inside describe which will limit its scope
:::

### How to Use

```ts
let setup = 0;
describe("setup", () => {
// effect for the whole describe including sub-describe
beforeEach(() => {
setup = 10;
});
test("1st", () => {
expect(setup).equal(10);
setup = 100;
});
test("2nd", () => {
expect(setup).equal(10);
setup = 100;
});
test("3nd", () => {
expect(setup).equal(10);
});
});
```

:::info
If multiple `beforeEach` or `afterEach` is registered, they will be call in order.
:::
1 change: 1 addition & 0 deletions docs/release-note.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Improved the as-test performances.
- Introduce new features `isolated: false` to significantly reduce test execution time in large projects. ([#73](https://github.com/wasm-ecosystem/assemblyscript-unittest-framework/pull/73))
- Introduce setup and teardown API. ([#77](https://github.com/wasm-ecosystem/assemblyscript-unittest-framework/pull/77))

## 1.3.1

Expand Down
38 changes: 22 additions & 16 deletions src/core/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,36 @@ async function nodeExecutor(
throw new Error("node executor abort");
};

try {
executionRecorder.startTestFunction(`${instrumentResult.baseName} - init`);
wasi.start(ins);
} catch (error) {
await exceptionHandler(error);
}
executionRecorder.finishTestFunction();
await executionRecorder.runTestFunction(
`${instrumentResult.baseName} - init`,
() => {
wasi.start(ins);
},
exceptionHandler
);

const execTestFunction = ins.exports["executeTestFunction"];
const execTestFunction = ins.exports["executeTestFunction"] as (a: number) => void;
assert(typeof execTestFunction === "function");

for (const testCase of executionRecorder.testCases) {
if (isCrashed) {
break;
}
const { fullName, functionIndex } = testCase;
const { fullName, functionIndex, setupFunctions, teardownFunctions } = testCase;
if (matchedTestNames.length === 0 || matchedTestNames.includes(fullName)) {
executionRecorder.startTestFunction(fullName);
try {
(execTestFunction as (a: number) => void)(functionIndex);
} catch (error) {
await exceptionHandler(error);
}
executionRecorder.finishTestFunction();
await executionRecorder.runTestFunction(
fullName,
() => {
for (const setupFuncIndex of setupFunctions) {
execTestFunction(setupFuncIndex);
}
execTestFunction(functionIndex);
for (const teardownFuncIndex of teardownFunctions) {
execTestFunction(teardownFuncIndex);
}
},
exceptionHandler
);
mockStatusRecorder.clear();
}
}
Expand Down
60 changes: 56 additions & 4 deletions src/core/executionRecorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,21 @@ export class ExecutionResult implements IExecutionResult {

class TestBlock {
constructor(public description: string) {}
setupFunctions: number[] = [];
teardownFunctions: number[] = [];
}

class TestCase {
export class TestCase {
fullName: string;
setupFunctions: number[];
teardownFunctions: number[];
constructor(
testBlockStack: TestBlock[],
public functionIndex: number
) {
this.fullName = testBlockStack.map((block) => block.description).join(" ");
this.setupFunctions = testBlockStack.flatMap((block) => block.setupFunctions);
this.teardownFunctions = testBlockStack.flatMap((block) => block.teardownFunctions);
}
}

Expand All @@ -73,22 +79,62 @@ export class ExecutionRecorder implements UnitTestFramework {
_removeDescription(): void {
this.testBlockStack.pop();
}

get lastTestBlock(): TestBlock | undefined {
return this.testBlockStack.at(-1);
}
// return false if error
_registerSetup(functionIndex: number): boolean {
const lastTestBlock = this.lastTestBlock;
if (lastTestBlock === undefined) {
return false;
} else {
lastTestBlock.setupFunctions.push(functionIndex);
return true;
}
}
// return false if error
_registerTeardown(functionIndex: number): boolean {
const lastTestBlock = this.lastTestBlock;
if (lastTestBlock === undefined) {
return false;
} else {
lastTestBlock.teardownFunctions.push(functionIndex);
return true;
}
}
_addTestCase(functionIndex: number): void {
this.testCases.push(new TestCase(this.testBlockStack, functionIndex));
}

startTestFunction(testCaseFullName: string): void {
this.currentExecutedTestCaseFullName = testCaseFullName;
_startTestFunction(fullName: string): void {
this.currentExecutedTestCaseFullName = fullName;
this.logRecorder.reset();
}
finishTestFunction(): void {
_finishTestFunction(): void {
const logMessages: string[] | null = this.logRecorder.onFinishTest();
if (logMessages !== null) {
this.result.failedLogMessages[this.currentExecutedTestCaseFullName] = (
this.result.failedLogMessages[this.currentExecutedTestCaseFullName] || []
).concat(logMessages);
}
}
async runTestFunction(
fullName: string,
runner: () => Promise<void> | void,
exceptionHandler: (error: unknown) => Promise<void>
) {
this._startTestFunction(fullName);
try {
const r = runner();
if (r instanceof Promise) {
await r;
}
} catch (error) {
await exceptionHandler(error);
}
this._finishTestFunction();
}

notifyTestCrash(error: ExecutionError): void {
this.logRecorder.addLog(`Reason: ${chalk.red(error.message)}`);
Expand Down Expand Up @@ -128,6 +174,12 @@ export class ExecutionRecorder implements UnitTestFramework {
registerTestFunction: (index: number): void => {
this._addTestCase(index);
},
registerBeforeEachFunction: (index: number): boolean => {
return this._registerSetup(index);
},
registerAfterEachFunction: (index: number): boolean => {
return this._registerTeardown(index);
},
collectCheckResult: (result: number, codeInfoIndex: number, actualValue: number, expectValue: number): void => {
this.collectCheckResult(
result !== 0,
Expand Down
10 changes: 7 additions & 3 deletions tests/e2e/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ runEndToEndTest("compilationFailed", "", (error, 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("printLogInFailedInfo", "", (error, stdout, stderr) => {
assert(error.code === 1);
});

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

runEndToEndTest(
"testFiles",
Expand Down
14 changes: 14 additions & 0 deletions tests/e2e/setup-teardown/as-test.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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],
temp: path.join(__dirname, "tmp"),
output: path.join(__dirname, "tmp"),
mode: [],
isolated: true,
};
28 changes: 28 additions & 0 deletions tests/e2e/setup-teardown/nest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { test, expect, describe, beforeEach } from "../../../assembly";

let setup0 = 0;
let setup1 = 0;
describe("setup", () => {
beforeEach(() => {
setup0 = 10;
setup1 = 20;
});
describe("nested", () => {
test("1st", () => {
expect(setup0).equal(10);
expect(setup1).equal(20);
setup0 = 100;
setup1 = 200;
});
test("2nd", () => {
expect(setup0).equal(10);
expect(setup1).equal(20);
setup0 = 100;
setup1 = 200;
});
});
test("3nd", () => {
expect(setup0).equal(10);
expect(setup1).equal(20);
});
});
26 changes: 26 additions & 0 deletions tests/e2e/setup-teardown/setup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { test, expect, describe, beforeEach } from "../../../assembly";

let setup0 = 0;
let setup1 = 0;
describe("setup", () => {
beforeEach(() => {
setup0 = 10;
setup1 = 20;
});
test("1st", () => {
expect(setup0).equal(10);
expect(setup1).equal(20);
setup0 = 100;
setup1 = 200;
});
test("2nd", () => {
expect(setup0).equal(10);
expect(setup1).equal(20);
setup0 = 100;
setup1 = 200;
});
test("3nd", () => {
expect(setup0).equal(10);
expect(setup1).equal(20);
});
});
3 changes: 3 additions & 0 deletions tests/e2e/setup-teardown/setup_out_of_block.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { beforeEach } from "../../../assembly";

beforeEach(() => {});
16 changes: 16 additions & 0 deletions tests/e2e/setup-teardown/stdout.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
code analysis: OK
compile test files: OK
instrument: OK
execute test files: OK

test case: 18/19 (success/total)

Error Message:
tests/e2e/setup-teardown/tmp/setup_out_of_block.test - init:
Test Crashed!
Reason: unreachable
at assembly/implement/beforeEachImpl (assembly/implement.ts:20:2)
at assembly/index/beforeEach (assembly/index.ts:47:2)
at start:tests/e2e/setup-teardown/setup_out_of_block.test (tests/e2e/setup-teardown/tmp/setup_out_of_block.test.instrumented.wasm:1:547)
at ~start (tests/e2e/setup-teardown/tmp/setup_out_of_block.test.instrumented.wasm:1:325)

26 changes: 26 additions & 0 deletions tests/e2e/setup-teardown/teardown.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { test, expect, describe, afterEach } from "../../../assembly";

let teardown0 = 0;
let teardown1 = 0;
describe("teardown", () => {
afterEach(() => {
teardown0 = 10;
teardown1 = 20;
});
test("1st", () => {
expect(teardown0).equal(0);
expect(teardown1).equal(0);
teardown0 = 100;
teardown1 = 200;
});
test("2nd", () => {
expect(teardown0).equal(10);
expect(teardown1).equal(20);
teardown0 = 100;
teardown1 = 200;
});
test("3nd", () => {
expect(teardown0).equal(10);
expect(teardown1).equal(20);
});
});
4 changes: 4 additions & 0 deletions tests/e2e/setup-teardown/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "assemblyscript/std/assembly.json",
"include": ["./**/*.ts"]
}
Loading