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
1 change: 0 additions & 1 deletion assembly/covInstrument.ts

This file was deleted.

8 changes: 4 additions & 4 deletions assembly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
testImpl,
unmockImpl,
} from "./implement";
import { output } from "./output";
import { MockFn } from "./mockInstrument";
export { MockFn } from "./mockInstrument";

Expand Down Expand Up @@ -57,6 +56,7 @@ export function expect<T>(value: T): Value<T> {
return new Value<T>(value);
}

export function endTest(): void {
output();
}
/**
* @deprecated no need to use endTest now
*/
export function endTest(): void {}
5 changes: 0 additions & 5 deletions assembly/output.ts

This file was deleted.

33 changes: 33 additions & 0 deletions src/core/covRecorder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import assert from "node:assert";
import { writeFileSync } from "node:fs";

export class CoverageRecorder {
private _runtimeTrace: Array<[number, number]> = [];

getCollectionFuncSet(): Record<string, unknown> {
return {
covInstrument: {
traceExpression: (functionIndex: number, basicBlockIndex: number, type: number): void => {
switch (type) {
case 1: // call in
case 2: {
// call out
// do not need for now
break;
}
case 0: {
this._runtimeTrace.push([functionIndex, basicBlockIndex]);
break;
}
}
},
},
};
}

outputTrace(wasm: string) {
assert(wasm.endsWith("instrumented.wasm"));
const traceOutputFile = wasm.slice(0, -17).concat("trace");
writeFileSync(traceOutputFile, JSON.stringify(this._runtimeTrace));
}
}
15 changes: 9 additions & 6 deletions src/core/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { instantiate, Imports as ASImports } from "@assemblyscript/loader";
import { AssertResult } from "../assertResult.js";
import { Imports, ImportsArgument } from "../index.js";
import { InstrumentResult } from "../interface.js";
import { mockInstruFunc, covInstruFunc } from "../utils/import.js";
import { mockInstrumentFunc } from "../utils/import.js";
import { supplyDefaultFunction } from "../utils/index.js";
import { parseImportFunctionInfo } from "../utils/wasmparser.js";
import { ExecutionRecorder } from "./executionRecorder.js";
import { CoverageRecorder } from "./covRecorder.js";

const readFile = promises.readFile;

Expand All @@ -23,15 +24,16 @@ async function nodeExecutor(wasm: string, outFolder: string, imports: Imports):
version: "preview1",
});

const recorder = new ExecutionRecorder();
const executionRecorder = new ExecutionRecorder();
const coverageRecorder = new CoverageRecorder();

const importsArg = new ImportsArgument();
const userDefinedImportsObject = imports === null ? {} : imports(importsArg);
const importObject: ASImports = {
wasi_snapshot_preview1: wasi.wasiImport,
...recorder.getCollectionFuncSet(importsArg),
mockInstrument: mockInstruFunc,
...covInstruFunc(wasm),
...executionRecorder.getCollectionFuncSet(importsArg),
mockInstrument: mockInstrumentFunc,
...coverageRecorder.getCollectionFuncSet(),
...userDefinedImportsObject,
} as ASImports;
const binaryBuffer = await readFile(wasm);
Expand All @@ -50,7 +52,8 @@ async function nodeExecutor(wasm: string, outFolder: string, imports: Imports):
}
throw new Error("node executor abort.");
}
return recorder;
coverageRecorder.outputTrace(wasm);
return executionRecorder;
}

export async function execWasmBinarys(
Expand Down
49 changes: 11 additions & 38 deletions src/utils/import.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,62 @@
import assert from "node:assert";
import { writeFileSync } from "node:fs";

interface MockValue {
calls: number;
ignore: boolean;
newIndex: number;
}

export const mockInstruFunc = {
export const mockInstrumentFunc = {
// isCall = true, return -1 if not mocked;
// isCall = false, return oldIndex if not mocked.
checkMock(index: number, isCall: boolean): number {
if (mockInstruFunc["mockFunctionStatus.has"](index)) {
return mockInstruFunc["mockFunctionStatus.get"](index);
if (mockInstrumentFunc["mockFunctionStatus.has"](index)) {
return mockInstrumentFunc["mockFunctionStatus.get"](index);
}
return isCall ? -1 : index;
},
"mockFunctionStatus.last": 0,
"mockFunctionStatus.state": new Map<number, MockValue>(),
"mockFunctionStatus.clear": function () {
mockInstruFunc["mockFunctionStatus.state"].clear();
mockInstrumentFunc["mockFunctionStatus.state"].clear();
},
"mockFunctionStatus.set": function (k: number, v: number) {
const value: MockValue = {
calls: 0,
ignore: false,
newIndex: v,
};
mockInstruFunc["mockFunctionStatus.state"].set(k, value);
mockInstrumentFunc["mockFunctionStatus.state"].set(k, value);
},
"mockFunctionStatus.get": function (k: number): number {
const fn = mockInstruFunc["mockFunctionStatus.state"].get(k);
const fn = mockInstrumentFunc["mockFunctionStatus.state"].get(k);
assert(fn);
fn.calls++;
mockInstruFunc["mockFunctionStatus.last"] = k;
mockInstrumentFunc["mockFunctionStatus.last"] = k;
return fn.newIndex;
},
"mockFunctionStatus.lastGet": function (): number {
return mockInstruFunc["mockFunctionStatus.last"];
return mockInstrumentFunc["mockFunctionStatus.last"];
},
"mockFunctionStatus.has": function (k: number): boolean {
const fn = mockInstruFunc["mockFunctionStatus.state"].get(k);
const fn = mockInstrumentFunc["mockFunctionStatus.state"].get(k);
if (fn === undefined) {
return false;
}
return !fn.ignore;
},
"mockFunctionStatus.getCalls": function (oldIndex: number, newIndex: number): number {
const fn = mockInstruFunc["mockFunctionStatus.state"].get(oldIndex);
const fn = mockInstrumentFunc["mockFunctionStatus.state"].get(oldIndex);
if (fn === undefined || fn.newIndex !== newIndex) {
return 0;
}
return fn.calls;
},
"mockFunctionStatus.setIgnore": function (k: number, v: boolean) {
const fn = mockInstruFunc["mockFunctionStatus.state"].get(k);
const fn = mockInstrumentFunc["mockFunctionStatus.state"].get(k);
if (fn === undefined) {
return;
}
fn.ignore = v;
},
};

export function covInstruFunc(wasm: string) {
const covInstrument = {
runtimeTrace: new Array<[number, number]>(),
traceExpression(functionIndex: number, basicBlockIndex: number, type: number) {
switch (type) {
case 1: // call in
case 2: {
// call out
// do not need for now
break;
}
case 0: {
covInstrument.runtimeTrace.push([functionIndex, basicBlockIndex]);
break;
}
}
},
outputTrace() {
assert(wasm.endsWith("instrumented.wasm"));
const traceOutputFile = wasm.slice(0, -17).concat("trace");
writeFileSync(traceOutputFile, JSON.stringify(covInstrument.runtimeTrace));
},
};
return { covInstrument };
}
63 changes: 23 additions & 40 deletions tests/ts/test/utils/import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,35 @@ jest.unstable_mockModule("node:fs", () => ({
writeFileSync: mockWriteFile,
}));

const { mockInstruFunc, covInstruFunc } = await import("../../../../src/utils/import.js");
const fs = await import("node:fs");
const { mockInstrumentFunc } = await import("../../../../src/utils/import.js");

describe("imports", () => {
test("mockInstrument", () => {
// mock(oldFunctionIndex, newFunctionIndex);
mockInstruFunc["mockFunctionStatus.set"](1, 4);
expect(mockInstruFunc.checkMock(1, true)).toEqual(4);
expect(mockInstruFunc.checkMock(2, false)).toEqual(2);
expect(mockInstruFunc.checkMock(2, true)).toEqual(-1);
expect(mockInstruFunc["mockFunctionStatus.lastGet"]()).toEqual(1);
expect(mockInstruFunc["mockFunctionStatus.getCalls"](1, 4)).toEqual(1);
expect(mockInstruFunc["mockFunctionStatus.getCalls"](2, 4)).toEqual(0);
expect(mockInstruFunc["mockFunctionStatus.getCalls"](1, 3)).toEqual(0);
expect(() => mockInstruFunc["mockFunctionStatus.get"](2)).toThrow();
mockInstrumentFunc["mockFunctionStatus.set"](1, 4);
expect(mockInstrumentFunc.checkMock(1, true)).toEqual(4);
expect(mockInstrumentFunc.checkMock(2, false)).toEqual(2);
expect(mockInstrumentFunc.checkMock(2, true)).toEqual(-1);
expect(mockInstrumentFunc["mockFunctionStatus.lastGet"]()).toEqual(1);
expect(mockInstrumentFunc["mockFunctionStatus.getCalls"](1, 4)).toEqual(1);
expect(mockInstrumentFunc["mockFunctionStatus.getCalls"](2, 4)).toEqual(0);
expect(mockInstrumentFunc["mockFunctionStatus.getCalls"](1, 3)).toEqual(0);
expect(() => mockInstrumentFunc["mockFunctionStatus.get"](2)).toThrow();
// unmock(oldFunction)
mockInstruFunc["mockFunctionStatus.setIgnore"](1, true);
expect(mockInstruFunc.checkMock(1, false)).toEqual(1);
expect(mockInstruFunc.checkMock(1, true)).toEqual(-1);
mockInstruFunc["mockFunctionStatus.setIgnore"](2, true);
expect(mockInstruFunc.checkMock(2, false)).toEqual(2);
expect(mockInstruFunc.checkMock(2, true)).toEqual(-1);
mockInstrumentFunc["mockFunctionStatus.setIgnore"](1, true);
expect(mockInstrumentFunc.checkMock(1, false)).toEqual(1);
expect(mockInstrumentFunc.checkMock(1, true)).toEqual(-1);
mockInstrumentFunc["mockFunctionStatus.setIgnore"](2, true);
expect(mockInstrumentFunc.checkMock(2, false)).toEqual(2);
expect(mockInstrumentFunc.checkMock(2, true)).toEqual(-1);
// remock(oldFunction)
mockInstruFunc["mockFunctionStatus.setIgnore"](1, false);
expect(mockInstruFunc.checkMock(1, false)).toEqual(4);
mockInstruFunc["mockFunctionStatus.setIgnore"](2, false);
expect(mockInstruFunc.checkMock(2, false)).toEqual(2);
expect(mockInstruFunc.checkMock(2, true)).toEqual(-1);
mockInstrumentFunc["mockFunctionStatus.setIgnore"](1, false);
expect(mockInstrumentFunc.checkMock(1, false)).toEqual(4);
mockInstrumentFunc["mockFunctionStatus.setIgnore"](2, false);
expect(mockInstrumentFunc.checkMock(2, false)).toEqual(2);
expect(mockInstrumentFunc.checkMock(2, true)).toEqual(-1);
// clear
mockInstruFunc["mockFunctionStatus.clear"]();
expect(mockInstruFunc["mockFunctionStatus.state"].size).toEqual(0);
});

test("covInstrument", () => {
const { covInstrument } = covInstruFunc("test.instrumented.wasm");
expect(jest.isMockFunction(fs.writeFileSync)).toBeTruthy();
covInstrument.traceExpression(1, -1, 1);
covInstrument.traceExpression(1, 1, 0);
covInstrument.traceExpression(1, 3, 0);
covInstrument.traceExpression(1, -1, 2);
covInstrument.outputTrace();
expect(covInstrument.runtimeTrace).toEqual([
[1, 1],
[1, 3],
]);
expect(mockWriteFile).toHaveBeenCalledTimes(1);
expect(mockWriteFile).toHaveBeenCalledWith("test.trace", "[[1,1],[1,3]]");
mockInstrumentFunc["mockFunctionStatus.clear"]();
expect(mockInstrumentFunc["mockFunctionStatus.state"].size).toEqual(0);
});
});