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
7 changes: 2 additions & 5 deletions src/core/covRecorder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import assert from "node:assert";
import { writeFileSync } from "node:fs";

export class CoverageRecorder {
Expand All @@ -25,9 +24,7 @@ export class CoverageRecorder {
};
}

outputTrace(wasm: string) {
assert(wasm.endsWith("instrumented.wasm"));
const traceOutputFile = wasm.slice(0, -17).concat("trace");
writeFileSync(traceOutputFile, JSON.stringify(this._runtimeTrace));
outputTrace(traceFile: string) {
writeFileSync(traceFile, JSON.stringify(this._runtimeTrace));
}
}
24 changes: 13 additions & 11 deletions src/core/execute.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { WASI } from "node:wasi";
import { promises } from "node:fs";
import { ensureDirSync } from "fs-extra";
import { basename } from "node:path";
import { instantiate, Imports as ASImports } from "@assemblyscript/loader";
import { AssertResult } from "../assertResult.js";
import { Imports, ImportsArgument } from "../index.js";
Expand All @@ -14,9 +13,13 @@ import { CoverageRecorder } from "./covRecorder.js";

const readFile = promises.readFile;

async function nodeExecutor(wasm: string, outFolder: string, imports: Imports): Promise<ExecutionRecorder> {
async function nodeExecutor(
instrumentResult: InstrumentResult,
outFolder: string,
imports: Imports
): Promise<ExecutionRecorder> {
const wasi = new WASI({
args: ["node", basename(wasm)],
args: ["node", instrumentResult.baseName],
env: process.env,
preopens: {
"/": outFolder,
Expand All @@ -36,7 +39,7 @@ async function nodeExecutor(wasm: string, outFolder: string, imports: Imports):
...coverageRecorder.getCollectionFuncSet(),
...userDefinedImportsObject,
} as ASImports;
const binaryBuffer = await readFile(wasm);
const binaryBuffer = await readFile(instrumentResult.instrumentedWasm);
const binary = binaryBuffer.buffer.slice(binaryBuffer.byteOffset, binaryBuffer.byteOffset + binaryBuffer.byteLength);
const importFuncList = parseImportFunctionInfo(binary as ArrayBuffer);
supplyDefaultFunction(importFuncList, importObject);
Expand All @@ -52,22 +55,21 @@ async function nodeExecutor(wasm: string, outFolder: string, imports: Imports):
}
throw new Error("node executor abort.");
}
coverageRecorder.outputTrace(wasm);
coverageRecorder.outputTrace(instrumentResult.traceFile);
return executionRecorder;
}

export async function execWasmBinarys(
export async function execWasmBinaries(
outFolder: string,
instrumentResult: InstrumentResult[],
instrumentResults: InstrumentResult[],
imports: Imports
): Promise<AssertResult> {
const assertRes = new AssertResult();
ensureDirSync(outFolder);
await Promise.all<void>(
instrumentResult.map(async (res): Promise<void> => {
const { instrumentedWasm, expectInfo } = res;
const recorder: ExecutionRecorder = await nodeExecutor(instrumentedWasm, outFolder, imports);
await assertRes.merge(recorder, expectInfo);
instrumentResults.map(async (instrumentResult): Promise<void> => {
const recorder: ExecutionRecorder = await nodeExecutor(instrumentResult, outFolder, imports);
await assertRes.merge(recorder, instrumentResult.expectInfo);
})
);
return assertRes;
Expand Down
20 changes: 6 additions & 14 deletions src/core/instrument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,19 @@ export async function instrument(sourceWasms: string[], sourceCodePaths: string[
const instrumenter = await initInstrumenter();
for (const sourceFile of sourceWasms) {
const baseName = sourceFile.slice(0, -5);
const outputFile = baseName.concat(".instrumented.wasm");
const result = new InstrumentResult(baseName);

const reportFunction = "covInstrument/traceExpression";
const sourceMapFile = baseName.concat(".wasm.map");
const debugInfoFile = baseName.concat(".debugInfo.json");
const expectInfoFile = baseName.concat(".expectInfo.json");

const source = instrumenter.allocateUTF8(sourceFile);
const output = instrumenter.allocateUTF8(outputFile);
const output = instrumenter.allocateUTF8(result.instrumentedWasm);
const report = instrumenter.allocateUTF8(reportFunction);
const sourceMap = instrumenter.allocateUTF8(sourceMapFile);
const debugInfo = instrumenter.allocateUTF8(debugInfoFile);
const expectInfo = instrumenter.allocateUTF8(expectInfoFile);
const sourceMap = instrumenter.allocateUTF8(result.sourceMap);
const debugInfo = instrumenter.allocateUTF8(result.debugInfo);
const expectInfo = instrumenter.allocateUTF8(result.expectInfo);
const include = instrumenter.allocateUTF8(includeFilter);

instrumenter._wasm_instrument(source, output, report, sourceMap, expectInfo, debugInfo, include, 0, true);
const result: InstrumentResult = {
sourceWasm: sourceFile,
instrumentedWasm: outputFile,
debugInfo: debugInfoFile,
expectInfo: expectInfoFile,
};
for (const ptr of [source, output, report, sourceMap, debugInfo, expectInfo, include]) {
instrumenter._free(ptr);
}
Expand Down
7 changes: 3 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { compile } from "./core/compile.js";
import { AssertResult } from "./assertResult.js";
import { precompile } from "./core/precompile.js";
import { instrument } from "./core/instrument.js";
import { execWasmBinarys } from "./core/execute.js";
import { execWasmBinaries } from "./core/execute.js";
import { generateReport, reportConfig } from "./generator/index.js";

function logAssertResult(trace: AssertResult): void {
Expand Down Expand Up @@ -81,12 +81,11 @@ export async function start_unit_test(fo: FileOption, to: TestOption, oo: Output
console.log(chalk.blueBright("compile testcases: ") + chalk.bold.greenBright("OK"));
const instrumentResult = await instrument(wasmPaths, Array.from(unittestPackage.sourceFunctions.keys()));
console.log(chalk.blueBright("instrument: ") + chalk.bold.greenBright("OK"));
const executedResult = await execWasmBinarys(oo.tempFolder, instrumentResult, to.imports);
const executedResult = await execWasmBinaries(oo.tempFolder, instrumentResult, to.imports);
console.log(chalk.blueBright("execute testcases: ") + chalk.bold.greenBright("OK"));
logAssertResult(executedResult);
const debugInfoFiles = instrumentResult.map((res) => res.debugInfo);
const parser = new Parser();
const fileCoverageInfo = await parser.parse(debugInfoFiles, unittestPackage.sourceFunctions);
const fileCoverageInfo = await parser.parse(instrumentResult, unittestPackage.sourceFunctions);
reportConfig.warningLimit = oo.warnLimit ?? reportConfig.warningLimit;
reportConfig.errorLimit = oo.errorLimit ?? reportConfig.errorLimit;
generateReport(oo.mode, oo.outputFolder, fileCoverageInfo);
Expand Down
25 changes: 20 additions & 5 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,26 @@
import { Type } from "wasmparser";

// instrumented file information
export interface InstrumentResult {
sourceWasm: string;
instrumentedWasm: string;
debugInfo: string;
expectInfo: string;
export class InstrumentResult {
constructor(public baseName: string) {}
get sourceWasm() {
return this.baseName.concat(".wasm");
}
get instrumentedWasm(): string {
return this.baseName.concat(".instrumented.wasm");
}
get sourceMap(): string {
return this.baseName.concat(".wasm.map");
}
get debugInfo(): string {
return this.baseName.concat(".debugInfo.json");
}
get expectInfo(): string {
return this.baseName.concat(".expectInfo.json");
}
get traceFile(): string {
return this.baseName.concat(".trace");
}
}

export type CodeSnippetIndex = number;
Expand Down
23 changes: 11 additions & 12 deletions src/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
FunctionCoverageResult,
SourceFunctionInfo,
LineRange,
InstrumentResult,
} from "../interface.js";

export class Parser {
Expand All @@ -25,11 +26,11 @@ export class Parser {
functionCovInfoMap = new Map<string, CovInfo>();

async parse(
debugInfoFiles: string[],
instrumentResults: InstrumentResult[],
sourceFunctions: Map<string, SourceFunctionInfo[]>
): Promise<FileCoverageResult[]> {
for (const debugInfoFile of debugInfoFiles) {
await this.traceParse(debugInfoFile);
for (const instrumentResult of instrumentResults) {
await this.traceParse(instrumentResult);
}
this.generateFunctionCoverage();
await this.generateFileCoverage(sourceFunctions);
Expand All @@ -41,9 +42,9 @@ export class Parser {
const tempCovTraceMap = new Map<FunctionIndex, CodeSnippetIndex[]>();
for (const trace of traces) {
const [funIndex, codeSnippetIndex] = trace;
const codeIndexs = tempCovTraceMap.get(funIndex);
if (codeIndexs) {
codeIndexs.push(codeSnippetIndex);
const codeIndexes = tempCovTraceMap.get(funIndex);
if (codeIndexes) {
codeIndexes.push(codeSnippetIndex);
} else {
tempCovTraceMap.set(funIndex, [codeSnippetIndex]);
}
Expand All @@ -63,12 +64,10 @@ export class Parser {
* parse debugInfo and traceInfo to functionCovTraceMap and functionCovInfoMap
* @param debugInfoFile debugInfo file path generated by instrumenter
*/
async traceParse(debugInfoFile: string) {
const traceFile = debugInfoFile.slice(0, -14).concat("trace");

async traceParse(instrumentResult: InstrumentResult) {
const [tempCovTraceMap, { debugInfos, debugFiles }] = await Promise.all([
this.getTempCovTraceMap(traceFile),
this.getDebugInfos(debugInfoFile),
this.getTempCovTraceMap(instrumentResult.traceFile),
this.getDebugInfos(instrumentResult.debugInfo),
]);

for (const [name, info] of debugInfos) {
Expand All @@ -86,7 +85,7 @@ export class Parser {
.filter((range) => {
const filename = debugFiles[range[0]];
if (filename === undefined) {
throw new Error(`unknown erorr: not find fileIndex ${range[0]} in ${debugInfoFile}`);
throw new Error(`unknown error: not find fileIndex ${range[0]} in ${instrumentResult.debugInfo}`);
}
// if basicBlock is inline function from other files, ignore it
return checkFunctionName(filename, name);
Expand Down
17 changes: 9 additions & 8 deletions tests/ts/test/core/instrument.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ const outputDir = join(tmpdir(), "assemblyscript-unittest-framework");

test("Instrument", async () => {
await compile([fixturePath], outputDir, "--memoryBase 16 --exportTable");
const base = join(outputDir, "constructor");
const wasmPath = join(outputDir, "constructor.wasm");
const sourceCodePath = "tests/ts/fixture/constructor.ts";
const result = await instrument([wasmPath], [sourceCodePath]);
expect(result.length).toEqual(1);
const results = await instrument([wasmPath], [sourceCodePath]);
expect(results.length).toEqual(1);
const result = results[0]!;
const instrumentedWasm = join(outputDir, "constructor.instrumented.wasm");
const debugInfo = join(outputDir, "constructor.debugInfo.json");
const expectInfo = join(outputDir, "constructor.expectInfo.json");
expect(result[0]).toEqual({
sourceWasm: wasmPath,
instrumentedWasm,
debugInfo,
expectInfo,
});
expect(result.baseName).toEqual(base);
expect(result.sourceWasm).toEqual(wasmPath);
expect(result.instrumentedWasm).toEqual(instrumentedWasm);
expect(result.debugInfo).toEqual(debugInfo);
expect(result.expectInfo).toEqual(expectInfo);
expect(fs.existsSync(instrumentedWasm)).toEqual(true);
expect(fs.existsSync(debugInfo)).toEqual(true);
expect(fs.existsSync(expectInfo)).toEqual(true);
Expand Down
5 changes: 2 additions & 3 deletions tests/ts/test/parser/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { join } from "node:path";
// eslint-disable-next-line n/no-extraneous-import
import { jest } from "@jest/globals";
import { fileURLToPath, URL } from "node:url";
import { SourceFunctionInfo } from "../../../../src/interface.js";
import { InstrumentResult, SourceFunctionInfo } from "../../../../src/interface.js";

jest.unstable_mockModule("node:fs/promises", () => ({
readFile: jest.fn(() => "\n".repeat(50)),
Expand All @@ -16,8 +16,7 @@ describe("Parser", () => {
const parser = new Parser();

test("traceParse", async () => {
const debugFilePath = join(dirname, "..", "..", "fixture", "traceParse.debugInfo.json");
await parser.traceParse(debugFilePath);
await parser.traceParse(new InstrumentResult(join(dirname, "..", "..", "fixture", "traceParse")));
expect(parser.functionCovInfoMap).toMatchSnapshot();
expect(parser.functionCovTraceMap).toMatchSnapshot();
});
Expand Down