Skip to content

Commit ccca0a3

Browse files
committed
support collectCoverage options
1 parent a6d5784 commit ccca0a3

File tree

11 files changed

+211
-162
lines changed

11 files changed

+211
-162
lines changed

bin/cli.js

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,17 @@ import { validatArgument, start_unit_test } from "../dist/index.js";
1212
const program = new Command();
1313
program
1414
.option("--config <config file>", "path of config file", "as-test.config.js")
15-
.option("--testcase <testcases...>", "only run specified test cases")
15+
.option("--testcase <testcases...>", "run only specified test cases")
1616
.option("--temp <path>", "test template file folder")
1717
.option("--output <path>", "coverage report output folder")
18-
.option("--mode <output mode>", "test result output format")
18+
.option("--mode <output mode>", "coverage report output format")
1919
.option("--coverageLimit [error warning...]", "set warn(yellow) and error(red) upper limit in coverage report")
20-
.option("--testNamePattern <test name pattern>", "run only tests with a name that matches the regex pattern");
20+
.option("--testNamePattern <test name pattern>", "run only tests with a name that matches the regex pattern")
21+
.option("--collectCoverage <boolean>", "whether to collect coverage information and report");
2122

2223
program.parse(process.argv);
2324
const options = program.opts();
2425

25-
if (options.config === undefined) {
26-
console.error(chalk.redBright("Miss config file") + "\n");
27-
console.error(program.helpInformation());
28-
exit(-1);
29-
}
3026
const configPath = resolve(".", options.config);
3127
if (!fs.pathExistsSync(configPath)) {
3228
console.error(chalk.redBright("Miss config file") + "\n");
@@ -35,33 +31,35 @@ if (!fs.pathExistsSync(configPath)) {
3531
}
3632
const config = (await import(pathToFileURL(configPath))).default;
3733

38-
let includes = config.include;
34+
const includes = config.include;
3935
if (includes === undefined) {
4036
console.error(chalk.redBright("Miss include in config file") + "\n");
4137
exit(-1);
4238
}
43-
let excludes = config.exclude || [];
44-
let testcases = options.testcase;
45-
46-
let flags = config.flags || "";
47-
let imports = config.imports || null;
39+
const excludes = config.exclude || [];
40+
validatArgument(includes, excludes);
4841

49-
let mode = options.mode || config.mode || "table";
42+
// if enabled testcase or testNamePattern, disable collectCoverage by default
43+
const collectCoverage = Boolean(options.collectCoverage) || config.collectCoverage || (!options.testcase && !options.testNamePattern);
5044

51-
let tempFolder = options.temp || config.temp || "coverage";
52-
let outputFolder = options.output || config.output || "coverage";
45+
const testOption = {
46+
includes,
47+
excludes,
48+
testcases: options.testcase,
49+
testNamePattern: options.testNamePattern,
50+
collectCoverage,
5351

54-
let errorLimit = options.coverageLimit?.at(0);
55-
let warnLimit = options.coverageLimit?.at(1);
52+
flags: config.flags || "",
53+
imports: config.imports || undefined,
5654

57-
let testNamePattern = options.testNamePattern;
55+
tempFolder: options.temp || config.temp || "coverage",
56+
outputFolder: options.output || config.output || "coverage",
57+
mode: options.mode || config.mode || "table",
58+
warnLimit: Number(options.coverageLimit?.at(1)),
59+
errorLimit: Number(options.coverageLimit?.at(0)),
60+
}
5861

59-
validatArgument(includes, excludes);
60-
start_unit_test(
61-
{ includes, excludes, testcases, testNamePattern },
62-
{ flags, imports },
63-
{ tempFolder, outputFolder, mode, warnLimit, errorLimit }
64-
)
62+
start_unit_test(testOption)
6563
.then((success) => {
6664
if (!success) {
6765
console.error(chalk.redBright("Test Failed") + "\n");

instrumentation/CoverageInstru.cpp

Lines changed: 72 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,15 @@ void CoverageInstru::innerAnalysis(BasicBlockAnalysis &basicBlockAnalysis) const
3030

3131
InstrumentationResponse CoverageInstru::instrument() const noexcept {
3232
if (config->fileName.empty() || config->reportFunction.empty() || config->sourceMap.empty() ||
33-
config->targetName.empty() || config->expectInfoOutputFilePath.empty() ||
34-
config->debugInfoOutputFilePath.empty()) {
33+
config->targetName.empty() || config->expectInfoOutputFilePath.empty()
34+
) {
3535
std::cout << *config << std::endl;
3636
return InstrumentationResponse::CONFIG_ERROR; // config error
3737
}
3838
std::filesystem::path filePath(config->fileName);
3939
std::filesystem::path targetFilePath(config->targetName);
40-
std::filesystem::path debugInfoPath(config->debugInfoOutputFilePath);
4140
std::filesystem::path sourceMapPath(config->sourceMap);
4241
if ((!std::filesystem::exists(filePath)) ||
43-
(!std::filesystem::exists(debugInfoPath.parent_path())) ||
4442
(!std::filesystem::exists(sourceMapPath)) ||
4543
(!std::filesystem::exists(targetFilePath.parent_path()))) {
4644
std::cout << *config << std::endl;
@@ -49,69 +47,82 @@ InstrumentationResponse CoverageInstru::instrument() const noexcept {
4947

5048
wasm::Module module;
5149
wasm::ModuleReader reader;
52-
50+
Json::StreamWriterBuilder jsonBuilder;
51+
jsonBuilder["indentation"] = "";
5352
reader.read(std::string(config->fileName), module, std::string(config->sourceMap));
5453
BasicBlockAnalysis basicBlockAnalysis = BasicBlockAnalysis();
5554
innerAnalysis(basicBlockAnalysis);
56-
BasicBlockWalker basicBlockWalker = BasicBlockWalker(&module, basicBlockAnalysis);
57-
basicBlockWalker.basicBlockWalk();
58-
const std::unordered_map<std::string_view, FunctionAnalysisResult> &results =
59-
basicBlockWalker.getResults();
60-
Json::Value json;
61-
Json::Value debugInfoJson;
62-
Json::Value debugFileJson;
63-
for (auto &[function, result] : results) {
64-
Json::Value innerJson;
65-
innerJson["index"] = result.functionIndex;
66-
Json::Value branchInfoArray(Json::ValueType::arrayValue);
67-
for (const auto &branchInfo : result.branchInfo) {
68-
Json::Value inner_array;
69-
inner_array.append(branchInfo.first);
70-
inner_array.append(branchInfo.second);
71-
branchInfoArray.append(std::move(inner_array));
55+
56+
if (config->collectCoverage) {
57+
if (config->debugInfoOutputFilePath.empty()) {
58+
std::cout << *config << std::endl;
59+
return InstrumentationResponse::CONFIG_ERROR; // config error
60+
}
61+
std::filesystem::path debugInfoPath(config->debugInfoOutputFilePath);
62+
if ((!std::filesystem::exists(debugInfoPath.parent_path()))) {
63+
std::cout << *config << std::endl;
64+
return InstrumentationResponse::CONFIG_FILEPATH_ERROR; // config file path error
7265
}
73-
innerJson["branchInfo"] = branchInfoArray;
74-
Json::Value debugLineJson;
75-
for (const auto &basicBlock : result.basicBlocks) {
76-
if (basicBlock.basicBlockIndex != static_cast<wasm::Index>(-1)) {
77-
Json::Value debugLineItemJsonArray(Json::ValueType::arrayValue);
78-
for (const auto &debugLine : basicBlock.debugLocations) {
79-
Json::Value debugInfo;
80-
debugInfo.append(debugLine.fileIndex);
81-
debugInfo.append(debugLine.lineNumber);
82-
debugInfo.append(debugLine.columnNumber);
83-
debugLineItemJsonArray.append(std::move(debugInfo));
66+
67+
BasicBlockWalker basicBlockWalker = BasicBlockWalker(&module, basicBlockAnalysis);
68+
basicBlockWalker.basicBlockWalk();
69+
const std::unordered_map<std::string_view, FunctionAnalysisResult> &results =
70+
basicBlockWalker.getResults();
71+
Json::Value json;
72+
Json::Value debugInfoJson;
73+
Json::Value debugFileJson;
74+
for (auto &[function, result] : results) {
75+
Json::Value innerJson;
76+
innerJson["index"] = result.functionIndex;
77+
Json::Value branchInfoArray(Json::ValueType::arrayValue);
78+
for (const auto &branchInfo : result.branchInfo) {
79+
Json::Value inner_array;
80+
inner_array.append(branchInfo.first);
81+
inner_array.append(branchInfo.second);
82+
branchInfoArray.append(std::move(inner_array));
83+
}
84+
innerJson["branchInfo"] = branchInfoArray;
85+
Json::Value debugLineJson;
86+
for (const auto &basicBlock : result.basicBlocks) {
87+
if (basicBlock.basicBlockIndex != static_cast<wasm::Index>(-1)) {
88+
Json::Value debugLineItemJsonArray(Json::ValueType::arrayValue);
89+
for (const auto &debugLine : basicBlock.debugLocations) {
90+
Json::Value debugInfo;
91+
debugInfo.append(debugLine.fileIndex);
92+
debugInfo.append(debugLine.lineNumber);
93+
debugInfo.append(debugLine.columnNumber);
94+
debugLineItemJsonArray.append(std::move(debugInfo));
95+
}
96+
debugLineJson[basicBlock.basicBlockIndex] = debugLineItemJsonArray;
8497
}
85-
debugLineJson[basicBlock.basicBlockIndex] = debugLineItemJsonArray;
8698
}
99+
innerJson["lineInfo"] = debugLineJson;
100+
debugInfoJson[function.data()] = innerJson;
87101
}
88-
innerJson["lineInfo"] = debugLineJson;
89-
debugInfoJson[function.data()] = innerJson;
90-
}
91-
for (const std::string &debugInfoFileName : module.debugInfoFileNames) {
92-
debugFileJson.append(debugInfoFileName);
93-
}
94-
json["debugInfos"] = debugInfoJson;
95-
json["debugFiles"] = debugFileJson;
96-
std::ofstream jsonWriteStream(config->debugInfoOutputFilePath.data(), std::ios::trunc);
97-
Json::StreamWriterBuilder jsonBuilder;
98-
jsonBuilder["indentation"] = "";
99-
std::unique_ptr<Json::StreamWriter> jsonWriter(jsonBuilder.newStreamWriter());
100-
if (jsonWriter->write(json, &jsonWriteStream) != 0) {
101-
// Hard to control IO error
102-
// LCOV_EXCL_START
103-
return InstrumentationResponse::DEBUG_INFO_GENERATION_ERROR; // debug info json write failed
104-
// LCOV_EXCL_STOP
105-
}
106-
jsonWriteStream.close();
107-
if (jsonWriteStream.fail() || jsonWriteStream.bad()) {
108-
// Hard to control IO error
109-
// LCOV_EXCL_START
110-
return InstrumentationResponse::DEBUG_INFO_GENERATION_ERROR; // debug info json write failed
111-
// LCOV_EXCL_STOP
102+
for (const std::string &debugInfoFileName : module.debugInfoFileNames) {
103+
debugFileJson.append(debugInfoFileName);
104+
}
105+
json["debugInfos"] = debugInfoJson;
106+
json["debugFiles"] = debugFileJson;
107+
std::ofstream jsonWriteStream(config->debugInfoOutputFilePath.data(), std::ios::trunc);
108+
109+
std::unique_ptr<Json::StreamWriter> jsonWriter(jsonBuilder.newStreamWriter());
110+
if (jsonWriter->write(json, &jsonWriteStream) != 0) {
111+
// Hard to control IO error
112+
// LCOV_EXCL_START
113+
return InstrumentationResponse::DEBUG_INFO_GENERATION_ERROR; // debug info json write failed
114+
// LCOV_EXCL_STOP
115+
}
116+
jsonWriteStream.close();
117+
if (jsonWriteStream.fail() || jsonWriteStream.bad()) {
118+
// Hard to control IO error
119+
// LCOV_EXCL_START
120+
return InstrumentationResponse::DEBUG_INFO_GENERATION_ERROR; // debug info json write failed
121+
// LCOV_EXCL_STOP
122+
}
123+
CovInstrumentationWalker covWalker(&module, config->reportFunction.data(), basicBlockWalker);
124+
covWalker.covWalk();
112125
}
113-
CovInstrumentationWalker covWalker(&module, config->reportFunction.data(), basicBlockWalker);
114-
covWalker.covWalk();
115126

116127
MockInstrumentationWalker mockWalker(&module);
117128
mockWalker.mockWalk();
@@ -167,7 +178,7 @@ wasm_instrument(char const *const fileName, char const *const targetName,
167178
char const *const reportFunction, char const *const sourceMap,
168179
char const *const expectInfoOutputFilePath,
169180
char const *const debugInfoOutputFilePath, char const *const includes,
170-
char const *const excludes, bool skipLib) noexcept {
181+
char const *const excludes, bool skipLib, bool collectCoverage) noexcept {
171182

172183
wasmInstrumentation::InstrumentationConfig config;
173184
config.fileName = fileName;
@@ -179,6 +190,7 @@ wasm_instrument(char const *const fileName, char const *const targetName,
179190
config.includes = includes;
180191
config.excludes = excludes;
181192
config.skipLib = skipLib;
193+
config.collectCoverage = collectCoverage;
182194
wasmInstrumentation::CoverageInstru instrumentor(&config);
183195
return instrumentor.instrument();
184196
}

instrumentation/CoverageInstru.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class InstrumentationConfig final {
4444
std::string_view excludes; ///< function exclude filter
4545
std::string_view expectInfoOutputFilePath; ///< exception info output file name
4646
bool skipLib = true; ///< if skip lib functions
47+
bool collectCoverage = true; ///< whether collect coverage information
4748

4849
///
4950
///@brief Print information of InstrumentationConfig to output stream
@@ -57,7 +58,8 @@ class InstrumentationConfig final {
5758
<< ", sourceMap: " << instance.sourceMap << ", reportFunction:" << instance.reportFunction
5859
<< ", includes: " << instance.includes << ", excludes: " << instance.excludes
5960
<< ", expectInfoOutputFilePath: " << instance.expectInfoOutputFilePath
60-
<< ", skipLib: " << std::boolalpha << instance.skipLib << std::endl;
61+
<< ", skipLib: " << std::boolalpha << instance.skipLib
62+
<< ", collectCoverage: " << std::boolalpha << instance.collectCoverage << std::endl;
6163
return out;
6264
}
6365
};
@@ -119,6 +121,6 @@ wasm_instrument(char const *const fileName, char const *const targetName,
119121
char const *const reportFunction, char const *const sourceMap,
120122
char const *const expectInfoOutputFilePath,
121123
char const *const debugInfoOutputFilePath, char const *const includes = NULL,
122-
char const *const excludes = NULL, bool skipLib = true) noexcept;
124+
char const *const excludes = NULL, bool skipLib = true, bool collectCoverage = true) noexcept;
123125
#endif
124126
#endif

instrumentation/wasm-instrumentation.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ interface Instrumenter {
1111
debugInfoOutputFilePath: number,
1212
includes: number,
1313
excludes: number,
14-
skipLib: boolean
14+
skipLib: boolean,
15+
collectCoverage: boolean
1516
): void;
1617
_free(ptr: number): void;
1718
}

src/core/execute.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ import { ensureDirSync } from "fs-extra";
44
import { basename } from "node:path";
55
import { instantiate, Imports as ASImports } from "@assemblyscript/loader";
66
import { AssertResult } from "../assertResult.js";
7-
import { Imports, ImportsArgument } from "../index.js";
8-
import { InstrumentResult } from "../interface.js";
7+
import { InstrumentResult, Imports, ImportsArgument } from "../interface.js";
98
import { mockInstruFunc, covInstruFunc } from "../utils/import.js";
109
import { supplyDefaultFunction } from "../utils/index.js";
1110
import { parseImportFunctionInfo } from "../utils/wasmparser.js";
1211
import { ExecutionRecorder } from "./executionRecorder.js";
1312

1413
const readFile = promises.readFile;
1514

16-
async function nodeExecutor(wasm: string, outFolder: string, imports: Imports): Promise<ExecutionRecorder> {
15+
async function nodeExecutor(wasm: string, outFolder: string, imports?: Imports): Promise<ExecutionRecorder> {
1716
const wasi = new WASI({
1817
args: ["node", basename(wasm)],
1918
env: process.env,
@@ -26,7 +25,7 @@ async function nodeExecutor(wasm: string, outFolder: string, imports: Imports):
2625
const recorder = new ExecutionRecorder();
2726

2827
const importsArg = new ImportsArgument();
29-
const userDefinedImportsObject = imports === null ? {} : imports(importsArg);
28+
const userDefinedImportsObject = imports === undefined ? {} : imports!(importsArg);
3029
const importObject: ASImports = {
3130
wasi_snapshot_preview1: wasi.wasiImport,
3231
...recorder.getCollectionFuncSet(importsArg),
@@ -56,7 +55,7 @@ async function nodeExecutor(wasm: string, outFolder: string, imports: Imports):
5655
export async function execWasmBinarys(
5756
outFolder: string,
5857
instrumentResult: InstrumentResult[],
59-
imports: Imports
58+
imports?: Imports
6059
): Promise<AssertResult> {
6160
const assertRes = new AssertResult();
6261
ensureDirSync(outFolder);

src/core/executionRecorder.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ImportsArgument } from "../index.js";
2-
import { AssertFailMessage, AssertMessage, IAssertResult } from "../interface.js";
1+
import { AssertFailMessage, AssertMessage, IAssertResult, ImportsArgument } from "../interface.js";
32

43
export class ExecutionRecorder implements IAssertResult {
54
total: number = 0;

src/core/instrument.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import initInstrumenter from "../../build_wasm/bin/wasm-instrumentation.js";
22
import { InstrumentResult } from "../interface.js";
33

4-
export async function instrument(sourceWasms: string[], sourceCodePaths: string[]): Promise<InstrumentResult[]> {
4+
export async function instrument(
5+
sourceWasms: string[],
6+
sourceCodePaths: string[],
7+
collectCoverage: boolean
8+
): Promise<InstrumentResult[]> {
59
const includeRegexs = sourceCodePaths.map((path) => {
610
return `(start:)?${path.slice(0, -3)}.*`;
711
});
@@ -24,7 +28,18 @@ export async function instrument(sourceWasms: string[], sourceCodePaths: string[
2428
const expectInfo = instrumenter.allocateUTF8(expectInfoFile);
2529
const include = instrumenter.allocateUTF8(includeFilter);
2630

27-
instrumenter._wasm_instrument(source, output, report, sourceMap, expectInfo, debugInfo, include, 0, true);
31+
instrumenter._wasm_instrument(
32+
source,
33+
output,
34+
report,
35+
sourceMap,
36+
expectInfo,
37+
debugInfo,
38+
include,
39+
0,
40+
true,
41+
collectCoverage
42+
);
2843
const result: InstrumentResult = {
2944
sourceWasm: sourceFile,
3045
instrumentedWasm: outputFile,

0 commit comments

Comments
 (0)