Skip to content

Commit 5d671a6

Browse files
authored
refactor: use ExecutionRecorder to collect assert result (#36)
1 parent f6765cd commit 5d671a6

File tree

10 files changed

+138
-202
lines changed

10 files changed

+138
-202
lines changed

assembly/assertCollector.ts

Lines changed: 0 additions & 55 deletions
This file was deleted.

assembly/env.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export namespace assertResult {
2+
3+
@external("__unittest_framework_env","addDescription")
4+
export declare function addDescription(description: string): void;
5+
6+
7+
@external("__unittest_framework_env","removeDescription")
8+
export declare function removeDescription(): void;
9+
10+
11+
@external("__unittest_framework_env","collectCheckResult")
12+
export declare function collectCheckResult(
13+
result: bool,
14+
codeInfoIndex: number,
15+
actualValue: string,
16+
expectValue: string,
17+
): void;
18+
}

assembly/expect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { equal, isNull } from "./comparison";
2-
import { assertResult } from "./assertCollector";
2+
import { assertResult } from "./env";
33
import { toJson } from "./formatPrint";
44

55

assembly/implement.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { assertResult } from "./assertCollector";
1+
import { assertResult } from "./env";
22
import { MockFn, mockFunctionStatus } from "./mockInstrument";
33

44
export function describeImpl(

assembly/output.ts

Lines changed: 0 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,5 @@
1-
import { assertResult } from "./assertCollector";
21
import { outputTrace } from "./covInstrument";
32

4-
import {
5-
args_sizes_get,
6-
args_get,
7-
errno,
8-
errnoToString,
9-
fd,
10-
path_open,
11-
lookupflags,
12-
rights,
13-
oflags,
14-
fdflags,
15-
iovec,
16-
fd_write,
17-
fd_close,
18-
} from "@assemblyscript/wasi-shim/assembly/bindings/wasi_snapshot_preview1";
19-
203
export function output(): void {
21-
const kvPair: string[] = [];
22-
kvPair.push(assertResult.totalString());
23-
kvPair.push(assertResult.failString());
24-
kvPair.push(assertResult.failInfoString());
25-
assertResult.clear();
26-
274
outputTrace();
28-
29-
const outputString = "{" + kvPair.join(",") + "}";
30-
31-
const outputFileName = getArgs()[1].slice(0, -5) + ".assert.log";
32-
writeFile(outputFileName, outputString);
33-
}
34-
35-
let ret: errno;
36-
37-
function perror(msg: string): void {
38-
if (ret == errno.SUCCESS) {
39-
return;
40-
}
41-
assert(false, errnoToString(ret) + " " + msg);
42-
}
43-
44-
function checkMemory(offset: usize): void {
45-
assert(offset < usize(i32.MAX_VALUE), "OOM");
46-
}
47-
48-
function fromCString(cstring: usize): string {
49-
let size = 0;
50-
while (load<u8>(cstring + size) !== 0) {
51-
size++;
52-
}
53-
return String.UTF8.decodeUnsafe(cstring, size);
54-
}
55-
56-
function getArgs(): string[] {
57-
const args: string[] = [];
58-
59-
const count_and_size = memory.data(sizeof<usize>() * 2);
60-
checkMemory(count_and_size + sizeof<usize>() * 2);
61-
ret = args_sizes_get(count_and_size, count_and_size + 4);
62-
perror("args_sizes_get");
63-
const argc = load<usize>(count_and_size, 0);
64-
const argv_total_size = load<usize>(count_and_size, sizeof<usize>());
65-
66-
const argv_ptr_array = new ArrayBuffer(i32((argc + 1) * sizeof<usize>()));
67-
const argv_total_string = new ArrayBuffer(i32(argv_total_size));
68-
checkMemory(changetype<usize>(argv_ptr_array) + (argc + 1) * sizeof<usize>());
69-
checkMemory(changetype<usize>(argv_total_string) + argv_total_size);
70-
ret = args_get(
71-
changetype<usize>(argv_ptr_array),
72-
changetype<usize>(argv_total_string),
73-
);
74-
perror("args_get");
75-
for (let i: usize = 0; i < argc; i++) {
76-
const argv_ptr = load<usize>(
77-
changetype<usize>(argv_ptr_array) + i * sizeof<usize>(),
78-
);
79-
const arg = fromCString(argv_ptr);
80-
args.push(arg);
81-
}
82-
83-
return args;
84-
}
85-
86-
function writeFile(path: string, data: string): void {
87-
if (data.length == 0) return;
88-
// open
89-
const utf8path = String.UTF8.encode(path);
90-
const fdptr = memory.data(sizeof<fd>());
91-
checkMemory(changetype<usize>(utf8path) + utf8path.byteLength);
92-
ret = path_open(
93-
3,
94-
lookupflags.SYMLINK_FOLLOW,
95-
changetype<usize>(utf8path),
96-
utf8path.byteLength,
97-
oflags.CREAT | oflags.TRUNC,
98-
rights.FD_WRITE |
99-
rights.FD_SEEK |
100-
rights.FD_TELL |
101-
rights.FD_FILESTAT_GET |
102-
rights.PATH_CREATE_FILE,
103-
rights.FD_WRITE |
104-
rights.FD_SEEK |
105-
rights.FD_TELL |
106-
rights.FD_FILESTAT_GET |
107-
rights.PATH_CREATE_FILE,
108-
fdflags.SYNC,
109-
fdptr,
110-
);
111-
perror("path_open");
112-
const fileDescriptor: fd = load<fd>(fdptr);
113-
114-
// write
115-
const buf = String.UTF8.encode(data);
116-
const iov = changetype<iovec>(memory.data(sizeof<iovec>()));
117-
iov.buf = changetype<usize>(buf);
118-
iov.buf_len = buf.byteLength;
119-
const written_ptr = memory.data(sizeof<usize>());
120-
checkMemory(changetype<usize>(buf) + buf.byteLength);
121-
do {
122-
ret = fd_write(fileDescriptor, changetype<usize>(iov), 1, written_ptr);
123-
perror("fd_write");
124-
iov.buf_len -= load<usize>(written_ptr);
125-
iov.buf += load<usize>(written_ptr);
126-
} while (iov.buf_len > 0);
127-
fd_close(fileDescriptor);
1285
}

src/assertResult.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { promises } from "node:fs";
22
import { json2map } from "./utils/index.js";
3-
import { AssertErrorMessages, AssertMessages, ErrorMessages, ExpectInfo, IAssertResult } from "./interface.js";
3+
import { AssertErrorMessages, AssertMessage, ErrorMessages, ExpectInfo, IAssertResult } from "./interface.js";
44

55
const readFile = promises.readFile;
66

@@ -17,7 +17,7 @@ export class AssertResult {
1717
try {
1818
const expectContent = await readFile(expectInfoFilePath, { encoding: "utf8" });
1919
expectInfo = json2map(JSON.parse(expectContent) as ExpectInfo);
20-
for (const [key, value] of json2map<AssertMessages>(result.failed_info)) {
20+
for (const [key, value] of json2map<AssertMessage[]>(result.failed_info)) {
2121
const errorMsgs: ErrorMessages = [];
2222
for (const msg of value) {
2323
const [index, actualValue, expectValue] = msg;

src/core/execute.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { WASI } from "node:wasi";
22
import { promises } from "node:fs";
33
import { ensureDirSync } from "fs-extra";
4-
import { basename, join } from "node:path";
4+
import { basename } from "node:path";
55
import { instantiate, Imports as ASImports } from "@assemblyscript/loader";
66
import { AssertResult } from "../assertResult.js";
77
import { Imports, ImportsArgument } from "../index.js";
8-
import { IAssertResult, InstrumentResult } from "../interface.js";
8+
import { InstrumentResult } from "../interface.js";
99
import { mockInstruFunc, covInstruFunc } from "../utils/import.js";
1010
import { supplyDefaultFunction } from "../utils/index.js";
1111
import { parseImportFunctionInfo } from "../utils/wasmparser.js";
12+
import { ExecutionRecorder } from "./executionRecorder.js";
13+
1214
const readFile = promises.readFile;
1315

14-
async function nodeExecutor(wasm: string, outFolder: string, imports: Imports) {
16+
async function nodeExecutor(wasm: string, outFolder: string, imports: Imports): Promise<ExecutionRecorder> {
1517
const wasi = new WASI({
1618
args: ["node", basename(wasm)],
1719
env: process.env,
@@ -21,10 +23,13 @@ async function nodeExecutor(wasm: string, outFolder: string, imports: Imports) {
2123
version: "preview1",
2224
});
2325

26+
const recorder = new ExecutionRecorder();
27+
2428
const importsArg = new ImportsArgument();
2529
const userDefinedImportsObject = imports === null ? {} : imports(importsArg);
2630
const importObject: ASImports = {
2731
wasi_snapshot_preview1: wasi.wasiImport,
32+
...recorder.getCollectionFuncSet(importsArg),
2833
mockInstrument: mockInstruFunc,
2934
...covInstruFunc(wasm),
3035
...userDefinedImportsObject,
@@ -45,6 +50,7 @@ async function nodeExecutor(wasm: string, outFolder: string, imports: Imports) {
4550
}
4651
throw new Error("node executor abort.");
4752
}
53+
return recorder;
4854
}
4955

5056
export async function execWasmBinarys(
@@ -56,22 +62,9 @@ export async function execWasmBinarys(
5662
ensureDirSync(outFolder);
5763
await Promise.all<void>(
5864
instrumentResult.map(async (res): Promise<void> => {
59-
await nodeExecutor(res.instrumentedWasm, outFolder, imports);
6065
const { instrumentedWasm, expectInfo } = res;
61-
const assertLogFilePath = join(outFolder, basename(instrumentedWasm).slice(0, -4).concat("assert.log"));
62-
63-
let content;
64-
try {
65-
content = await readFile(assertLogFilePath, { encoding: "utf8" });
66-
} catch (error) {
67-
if (error instanceof Error) {
68-
console.error(error.stack);
69-
}
70-
throw new Error(`maybe forget call "endTest()" at the end of "*.test.ts" or Job abort before output`);
71-
}
72-
const assertResult = JSON.parse(content) as IAssertResult;
73-
74-
await assertRes.merge(assertResult, expectInfo);
66+
const recorder: ExecutionRecorder = await nodeExecutor(instrumentedWasm, outFolder, imports);
67+
await assertRes.merge(recorder, expectInfo);
7568
})
7669
);
7770
return assertRes;

src/core/executionRecorder.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { ImportsArgument } from "../index.js";
2+
import { AssertFailMessage, AssertMessage, IAssertResult } from "../interface.js";
3+
4+
export class ExecutionRecorder implements IAssertResult {
5+
total: number = 0;
6+
fail: number = 0;
7+
failed_info: AssertFailMessage = {};
8+
_currentTestDescriptions: string[] = [];
9+
10+
_addDescription(description: string): void {
11+
this._currentTestDescriptions.push(description);
12+
}
13+
_removeDescription(): void {
14+
this._currentTestDescriptions.pop();
15+
}
16+
collectCheckResult(result: boolean, codeInfoIndex: number, actualValue: string, expectValue: string): void {
17+
this.total++;
18+
if (!result) {
19+
this.fail++;
20+
const testCaseFullName = this._currentTestDescriptions.join(" - ");
21+
const assertMessage: AssertMessage = [codeInfoIndex.toString(), actualValue, expectValue];
22+
this.failed_info[testCaseFullName] = this.failed_info[testCaseFullName] || [];
23+
this.failed_info[testCaseFullName].push(assertMessage);
24+
}
25+
}
26+
27+
getCollectionFuncSet(arg: ImportsArgument): Record<string, Record<string, unknown>> {
28+
return {
29+
__unittest_framework_env: {
30+
addDescription: (description: number): void => {
31+
this._addDescription(arg.exports!.__getString(description));
32+
},
33+
removeDescription: (): void => {
34+
this._removeDescription();
35+
},
36+
collectCheckResult: (result: number, codeInfoIndex: number, actualValue: number, expectValue: number): void => {
37+
this.collectCheckResult(
38+
result !== 0,
39+
codeInfoIndex,
40+
arg.exports!.__getString(actualValue),
41+
arg.exports!.__getString(expectValue)
42+
);
43+
},
44+
},
45+
};
46+
}
47+
}

src/interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ export type TestCaseName = string;
5151
export type ExpectInfoIndex = string;
5252
export type AssertExpectValue = string;
5353
export type AssertActualValue = string;
54-
export type AssertMessages = [ExpectInfoIndex, AssertActualValue, AssertExpectValue][];
55-
export type AssertFailMessage = Record<TestCaseName, AssertMessages>;
54+
export type AssertMessage = [ExpectInfoIndex, AssertActualValue, AssertExpectValue];
55+
export type AssertFailMessage = Record<TestCaseName, AssertMessage[]>;
5656

5757
export type ErrorMessages = string[];
5858
export type AssertErrorMessages = Map<TestCaseName, ErrorMessages>;

0 commit comments

Comments
 (0)