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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
/dist

/build*
/transform/*.mjs
/transform/tsconfig.tsbuildinfo

/coverage
/coverage-ts
Expand Down
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
/coverage-ts
/dist
/node_modules
/transform/*.mjs
/tests/ts/fixture
/third_party
/tests/cpp/lit/expectInstrument**/*.json
Expand Down
21 changes: 3 additions & 18 deletions as-test.config.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
/**
* @type {import("./config.d.ts").Config}
*/
export default {
/** file include in test */
include: ["assembly", "tests/as"],

/** optional: file exclude */
exclude: [],

/** optional: assemblyscript compile flag, default is --exportStart _start -O0 */
flags: "",

/**
* optional: import functions
* @param {ImportsArgument} runtime
* @returns
*/
imports(runtime) {
return {};
},

/** optional: template file path, default "coverage" */
temp: "coverage",

/** optional: report file path, default "coverage" */
output: "coverage",

/** optional: test result output format, default "table" */
mode: ["html", "json", "table"],

isolated: false,
};
4 changes: 4 additions & 0 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,17 @@ const getBoolean = (optionValue, configValue) => {
const isolatedInConfig = getBoolean(options.isolated, config.isolated);
const isolated = isolatedInConfig ?? false;

const entryFiles = config.entryFiles ?? null;

/**
* @type {import("../dist/interface.d.ts").TestOption}
*/
const testOption = {
includes,
excludes,
testFiles,
entryFiles,

testNamePattern: testNamePattern,
collectCoverage,
onlyFailures,
Expand Down
6 changes: 4 additions & 2 deletions config.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Imports } from "./dist/interface.d.ts";
import type { Imports } from "./dist/interface.d.ts";

export type OutputMode = "html" | "json" | "table";

Expand All @@ -7,14 +7,16 @@ export declare class Config {
include: string[];
/** Files to exclude from testing and coverage statistics, has higher priority than include */
exclude?: string[];
/** entry files for the whole projects, used to collect all source code information. default value is `${include}/index.ts` */
entryFiles?: string[];

/** whether to collect coverage information, default is true */
collectCoverage?: boolean;

/** create an wasm instance for each test files. default is false (will be true in next major version) */
isolated?: boolean;

/** assemblyscript compile flag, default is --exportStart _start --sourceMap --debug -O0 */
/** assemblyscript compile flag, default is --exportStart __unit_test_start --sourceMap --debug -O0 */
flags?: string;
imports?: Imports;

Expand Down
4 changes: 4 additions & 0 deletions docs/release-note.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Changed the default value of `isolated` from `true` to `false`.

🛠️ Improvements

- Introduce new configuration `entryFiles` to reach all source code. ([#88](https://github.com/wasm-ecosystem/assemblyscript-unittest-framework/pull/88))

## 1.4.1

🚀 Highlight Features
Expand Down
20 changes: 1 addition & 19 deletions docs/technical-details/code-debug-info.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,4 @@
}
```

```ts
interface CodeDebugInfo {
debugFiles: string[];
debugInfos: Record<string, FunctionDebugInfo>;
}

interface FunctionDebugInfo {
index: number;
branchInfo: Branch[];
lineInfo: LineInfos;
}

type LineRange = [FileIndex, LineIndex, ColumnIndex][][];
type LineInfos = LineRange[][];
type FileIndex = number;
type LineIndex = number;
type ColumnIndex = number;
type Branch = [number, number];
```
The schema corresponding to json can be found in interface `DebugInfo` in `src/interface.ts`.
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default [
{
rules: {
"unicorn/no-array-for-each": "off",
"sonarjs/fixme-tag": "off",
},
},
];
8 changes: 0 additions & 8 deletions instrumentation/BasicBlockAnalysis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ namespace wasmInstrumentation {
///
class BasicBlockAnalysis final {
public:
///
/// @brief Add include file to debug info analysis
///
/// @param include
inline void addInclude(const std::string &&include) noexcept {
includes.emplace_back(include);
}

///
/// @brief Add exclude file to debug info analysis
///
Expand Down
14 changes: 2 additions & 12 deletions instrumentation/CoverageInstru.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ void CoverageInstru::innerAnalysis(BasicBlockAnalysis &basicBlockAnalysis) const
Json::Reader jsonReader;
Json::Value includesJsonValue;
Json::Value excludesJsonValue;
if (!config->includes.empty()) {
jsonReader.parse(std::string(config->includes), includesJsonValue);
if (includesJsonValue.isArray()) {
const uint32_t includesJsonSize = includesJsonValue.size();
for (uint32_t i = 0U; i < includesJsonSize; ++i) {
basicBlockAnalysis.addInclude(includesJsonValue[i].asString());
}
}
}
if (!config->excludes.empty()) {
jsonReader.parse(std::string(config->excludes), excludesJsonValue);
if (excludesJsonValue.isArray()) {
Expand Down Expand Up @@ -181,8 +172,8 @@ extern "C" EMSCRIPTEN_KEEPALIVE wasmInstrumentation::InstrumentationResponse
wasm_instrument(char const *const fileName, char const *const targetName,
char const *const reportFunction, char const *const sourceMap,
char const *const expectInfoOutputFilePath,
char const *const debugInfoOutputFilePath, char const *const includes,
char const *const excludes, bool skipLib, bool collectCoverage) noexcept {
char const *const debugInfoOutputFilePath, char const *const excludes, bool skipLib,
bool collectCoverage) noexcept {

wasmInstrumentation::InstrumentationConfig config;
config.fileName = fileName;
Expand All @@ -191,7 +182,6 @@ wasm_instrument(char const *const fileName, char const *const targetName,
config.sourceMap = sourceMap;
config.expectInfoOutputFilePath = expectInfoOutputFilePath;
config.debugInfoOutputFilePath = debugInfoOutputFilePath;
config.includes = includes;
config.excludes = excludes;
config.skipLib = skipLib;
config.collectCoverage = collectCoverage;
Expand Down
7 changes: 3 additions & 4 deletions instrumentation/CoverageInstru.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ class InstrumentationConfig final {
std::string_view reportFunction; ///< trace report function name
std::string_view sourceMap; ///< input source map file name
std::string_view debugInfoOutputFilePath; ///< debug info output file name
std::string_view includes; ///< function include filter
std::string_view excludes; ///< function exclude filter
std::string_view expectInfoOutputFilePath; ///< exception info output file name
bool skipLib = true; ///< if skip lib functions
Expand All @@ -56,7 +55,7 @@ class InstrumentationConfig final {
const InstrumentationConfig &instance) noexcept {
out << "filename: " << instance.fileName << ", targetName: " << instance.targetName
<< ", sourceMap: " << instance.sourceMap << ", reportFunction:" << instance.reportFunction
<< ", includes: " << instance.includes << ", excludes: " << instance.excludes
<< ", excludes: " << instance.excludes
<< ", expectInfoOutputFilePath: " << instance.expectInfoOutputFilePath
<< ", skipLib: " << std::boolalpha << instance.skipLib
<< ", collectCoverage: " << std::boolalpha << instance.collectCoverage << std::endl;
Expand Down Expand Up @@ -120,7 +119,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE wasmInstrumentation::InstrumentationResponse
wasm_instrument(char const *const fileName, char const *const targetName,
char const *const reportFunction, char const *const sourceMap,
char const *const expectInfoOutputFilePath,
char const *const debugInfoOutputFilePath, char const *const includes = NULL,
char const *const excludes = NULL, bool skipLib = true, bool collectCoverage = true) noexcept;
char const *const debugInfoOutputFilePath, char const *const excludes, bool skipLib,
bool collectCoverage) noexcept;
#endif
#endif
1 change: 0 additions & 1 deletion instrumentation/wasm-instrumentation.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ interface Instrumenter {
sourceMap: number,
expectInfoOutputFilePath: number,
debugInfoOutputFilePath: number,
includes: number,
excludes: number,
skipLib: boolean,
collectCoverage: boolean
Expand Down
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
},
"prettier": "@schleifner/prettier-config",
"scripts": {
"watch:ts": "concurrently --kill-others \"tsc --build ./src/tsconfig.json --watch\" \"tsc --build ./transform/tsconfig.json --watch\"",
"build": "node scripts/build_instrumentation.js -j 2 && tsc --build ./transform/tsconfig.json && tsc --build ./src/tsconfig.json",
"watch:ts": "tsc --build ./src/tsconfig.json --watch",
"build": "node scripts/build_instrumentation.js -j 2 && tsc --build ./src/tsconfig.json",
"test:as": "node bin/as-test.js",
"test:ts": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"test:cpp": "cmake -B build -S . && cmake --build build --parallel 2 --target wasm-instrumentation-test wasm-opt && build/bin/wasm-instrumentation-test",
"test:e2e": " node tests/e2e/run.js",
"test": "npm run test:as && npm run test:ts && npm run test:cpp && npm run test:e2e",
"lint:ts": "eslint src transform tests/ts/test --max-warnings=0",
"lint:ts": "eslint src tests/ts/test --max-warnings=0",
"lint:as": "npx eslint --config ./assembly/eslint.config.mjs assembly --max-warnings=0",
"lint": "npm run lint:ts && prettier -c .",
"lint:fix:ts": "eslint src transform tests/ts/test --fix && prettier --write .",
"lint:fix:ts": "eslint src tests/ts/test --fix && prettier --write .",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs"
},
Expand Down Expand Up @@ -83,7 +83,6 @@
"dist/**/*",
"docs/**/*",
"resource/**/*",
"transform/**/*",
"LICENSE",
"README.md"
]
Expand Down
83 changes: 83 additions & 0 deletions src/core/analyze.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Will transform all source file to get all relative functions
*/

import ignore from "ignore";
import { join, relative, resolve } from "node:path";
import { getIncludeFiles } from "../utils/pathResolver.js";
import { TestOption } from "../interface.js";
import assert from "node:assert";

type AnalyzeOption = Pick<TestOption, "includes" | "excludes" | "testFiles" | "testNamePattern" | "entryFiles">;

interface UnittestPackage {
readonly testCodePaths: string[];
readonly sourceCodePaths: string[];
readonly entryFiles: string[];
readonly filterByName: (fullTestName: string) => boolean;
}

export function analyze(
{ includes, excludes, testNamePattern, testFiles, entryFiles }: AnalyzeOption,
failedTestNames: string[]
): UnittestPackage {
const testCodePaths = testFiles ?? getRelatedFiles(includes, excludes, (path: string) => path.endsWith(".test.ts"));
const sourceCodePaths = getRelatedFiles(
includes,
excludes,
(path: string) => path.endsWith(".ts") && !path.endsWith(".test.ts")
);
return {
// if specify testFiles, use testFiles for unittest
// otherwise, get testFiles(*.test.ts) in includes directory
testCodePaths: testCodePaths,
// get all source files in includes directory
sourceCodePaths: sourceCodePaths,
entryFiles: entryFiles ?? getEntryFiles(includes, sourceCodePaths),
filterByName: getFilterByName(testNamePattern, failedTestNames),
};
}

function getEntryFiles(includes: string[], sourceCodePaths: string[]): string[] {
// entry files must be in source code paths
return includes
.map((include) => (include.endsWith(".ts") ? include : join(include, "index.ts")))
.filter((include) => include.endsWith(".ts") && sourceCodePaths.includes(include));
}

function getFilterByName(testNamePattern: string | null, failedTestNames: string[]): UnittestPackage["filterByName"] {
assert(
!(testNamePattern !== null && failedTestNames.length > 0),
"Cannot use testNamePattern and failedTestNames together"
);
if (testNamePattern !== null) {
const regexPattern = new RegExp(testNamePattern);
return (fullTestName: string): boolean => regexPattern.test(fullTestName);
}
if (failedTestNames.length > 0) {
return (fullTestName: string): boolean => failedTestNames.includes(fullTestName);
}
return (): boolean => true;
}

// a. include in config
// b. exclude in config
function getRelatedFiles(includes: string[], excludes: string[], filter: (path: string) => boolean) {
const result: string[] = [];
const includeFiles = getIncludeFiles(includes, (path) => path.endsWith(".ts")); // a
const exc = ignore().add(excludes);

for (const path of includeFiles) {
const relativePath = relative(".", path);
if (relativePath.startsWith("..")) {
throw new Error(`file ${path} out of scope (${resolve(".")})`);
}
if (exc.ignores(relativePath)) {
continue; // ab
}
if (filter(path)) {
result.push(path.replaceAll(/\\/g, "/"));
}
}
return result;
}
21 changes: 14 additions & 7 deletions src/core/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { TestOption } from "../interface.js";

export type CompileOption = Pick<TestOption, "isolated" | "outputFolder" | "flags">;

export async function compile(testCodePaths: string[], option: CompileOption): Promise<string[]> {
export async function compile(testCodePaths: string[], entryFiles: string[], option: CompileOption): Promise<string[]> {
const { isolated } = option;
return isolated ? await separatedCompile(testCodePaths, option) : [await unifiedCompile(testCodePaths, option)];
return isolated
? await separatedCompile(testCodePaths, entryFiles, option)
: [await unifiedCompile(testCodePaths, entryFiles, option)];
}

function getNewPath(newFolder: string, oldFolder: string, srcPath: string): string {
Expand All @@ -22,7 +24,8 @@ function getAscArgs(sources: string[], outputWasm: string, outputWat: string, fl
"--textFile",
outputWat,
"--exportStart",
"_start",
// avoid name conflict with user-defined start functions
"__unit_test_start",
"--sourceMap",
"--debug",
"-O0",
Expand All @@ -34,24 +37,28 @@ function getAscArgs(sources: string[], outputWasm: string, outputWat: string, fl
return ascArgv;
}

async function unifiedCompile(testCodePaths: string[], option: CompileOption): Promise<string> {
async function unifiedCompile(testCodePaths: string[], entryFiles: string[], option: CompileOption): Promise<string> {
const { outputFolder, flags } = option;
const outputWasm = join(outputFolder, "test.wasm").replaceAll(/\\/g, "/");
const outputWat = join(outputFolder, "test.wat").replaceAll(/\\/g, "/");
const ascArgv = getAscArgs(testCodePaths, outputWasm, outputWat, flags);
const ascArgv = getAscArgs(testCodePaths.concat(entryFiles), outputWasm, outputWat, flags);
await ascMain(ascArgv, false);
return outputWasm;
}

async function separatedCompile(testCodePaths: string[], option: CompileOption): Promise<string[]> {
async function separatedCompile(
testCodePaths: string[],
entryFiles: string[],
option: CompileOption
): Promise<string[]> {
const { outputFolder, flags } = option;
const wasm: string[] = [];
const root = findRoot(testCodePaths);
const compileOneFile = async (testCodePath: string) => {
const outputWasm = getNewPath(outputFolder, root, testCodePath).slice(0, -2).concat("wasm");
wasm.push(outputWasm);
const outputWat = getNewPath(outputFolder, root, testCodePath).slice(0, -2).concat("wat");
const ascArgv = getAscArgs([testCodePath], outputWasm, outputWat, flags);
const ascArgv = getAscArgs([testCodePath, ...entryFiles], outputWasm, outputWat, flags);
await ascMain(ascArgv, false);
};

Expand Down
Loading