as-test 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +32 -3
- package/as-test.config.schema.json +18 -0
- package/assembly/coverage.ts +20 -0
- package/assembly/util/json.ts +2 -3
- package/bin/build.js +97 -17
- package/bin/index.js +476 -45
- package/bin/init.js +1 -0
- package/bin/reporters/default.js +26 -9
- package/bin/run.js +55 -2
- package/bin/types.js +2 -0
- package/bin/util.js +36 -3
- package/package.json +40 -42
- package/transform/lib/coverage.js +130 -124
- package/transform/lib/index.js +56 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 2026-02-25 - v0.5.3
|
|
4
|
+
|
|
5
|
+
### CLI, Modes & Matrix
|
|
6
|
+
|
|
7
|
+
- feat: support mode fan-out behavior consistently across `ast build`, `ast run`, and `ast test`.
|
|
8
|
+
- feat: when no `--mode` is provided and modes are configured, run using configured modes.
|
|
9
|
+
- feat: add matrix-style per-file output with mode-aware timing:
|
|
10
|
+
- non-verbose: average time
|
|
11
|
+
- verbose: per-mode times.
|
|
12
|
+
- feat: add real-time matrix line updates in the default reporter and normalize timing precision to one decimal.
|
|
13
|
+
- feat: support comma-separated bare selectors (for example `ast test box,custom,generics,string`) across build/run/test selectors.
|
|
14
|
+
|
|
15
|
+
### Config Merge & Env Behavior
|
|
16
|
+
|
|
17
|
+
- fix: apply mode config as field-level merge over base config instead of replacing entire sections.
|
|
18
|
+
- fix: merge `buildOptions.args` between base config and mode config.
|
|
19
|
+
- fix: pass config env variables to both build and run processes for mode execution.
|
|
20
|
+
|
|
21
|
+
### Build Pipeline & Feature Flags
|
|
22
|
+
|
|
23
|
+
- feat: include the exact build command in build failure output.
|
|
24
|
+
- feat: allow `buildOptions.cmd` to override default command generation while still appending user build args.
|
|
25
|
+
- feat: support CLI feature toggles:
|
|
26
|
+
- `--enable coverage` / `--disable coverage`
|
|
27
|
+
- `--enable try-as` / `--disable try-as`.
|
|
28
|
+
|
|
29
|
+
### Reporter & Summaries
|
|
30
|
+
|
|
31
|
+
- fix: move mode summary rendering into the default reporter (via run-complete event payload).
|
|
32
|
+
- feat: include `modeSummary` for single-mode runs.
|
|
33
|
+
- feat: include mode and snapshot totals in final summary output.
|
|
34
|
+
- fix: `--clean` output now behaves as non-TTY in default reporter:
|
|
35
|
+
- no in-place line editing,
|
|
36
|
+
- no suite expand/collapse logs,
|
|
37
|
+
- final per-file verdict lines only.
|
|
38
|
+
|
|
39
|
+
### Coverage & Transform
|
|
40
|
+
|
|
41
|
+
- fix: ignore AssemblyScript builtin/compiler helper calls during coverage instrumentation (including `isString`, `changetype<T>`, `idof<T>`, `sizeof<T>`).
|
|
42
|
+
- fix: mock transform now collects mocked import targets across sources so WASI mock imports are rewritten reliably (resolves import-shape runtime failures).
|
|
43
|
+
|
|
3
44
|
## 2026-02-24 - v0.5.2
|
|
4
45
|
|
|
5
46
|
### Runtime & Serialization
|
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ describe("math", () => {
|
|
|
74
74
|
});
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
###
|
|
77
|
+
### File selection (`ast run`, `ast build`, `ast test`)
|
|
78
78
|
|
|
79
79
|
No selectors:
|
|
80
80
|
|
|
@@ -105,6 +105,14 @@ Multiple selectors:
|
|
|
105
105
|
ast test sleep array ./assembly/__tests__/snapshot.spec.ts
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
Comma-separated bare suite names:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
ast test box,custom,generics,string
|
|
112
|
+
ast run box,custom,generics,string
|
|
113
|
+
ast build box,custom,generics,string
|
|
114
|
+
```
|
|
115
|
+
|
|
108
116
|
If nothing matches, `ast test` exits non-zero with:
|
|
109
117
|
|
|
110
118
|
```text
|
|
@@ -114,11 +122,21 @@ No test files matched: ...
|
|
|
114
122
|
### Useful flags
|
|
115
123
|
|
|
116
124
|
- `--config <path>`: use another config file
|
|
117
|
-
- `--mode <name[,name...]>`: run one or multiple named config modes
|
|
125
|
+
- `--mode <name[,name...]>`: run one or multiple named config modes (if omitted and `modes` is configured, as-test runs all configured modes)
|
|
118
126
|
- `--update-snapshots`: write snapshot updates
|
|
119
127
|
- `--no-snapshot`: disable snapshot assertions for the run
|
|
120
128
|
- `--show-coverage`: print uncovered coverage points
|
|
129
|
+
- `--enable <feature>`: enable as-test feature (`coverage`, `try-as`)
|
|
130
|
+
- `--disable <feature>`: disable as-test feature (`coverage`, `try-as`)
|
|
121
131
|
- `--verbose`: keep expanded suite/test lines and update running `....` statuses in place
|
|
132
|
+
- `--clean`: disable in-place TTY updates and print only final per-file verdict lines. Useful for CI/CD.
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
ast build --enable try-as
|
|
138
|
+
ast test --disable coverage
|
|
139
|
+
```
|
|
122
140
|
|
|
123
141
|
## Mocking
|
|
124
142
|
|
|
@@ -236,7 +254,9 @@ Example:
|
|
|
236
254
|
"snapshotDir": "./.as-test/snapshots",
|
|
237
255
|
"config": "none",
|
|
238
256
|
"coverage": true,
|
|
257
|
+
"env": {},
|
|
239
258
|
"buildOptions": {
|
|
259
|
+
"cmd": "",
|
|
240
260
|
"args": [],
|
|
241
261
|
"target": "wasi"
|
|
242
262
|
},
|
|
@@ -257,8 +277,10 @@ Key fields:
|
|
|
257
277
|
- `logs`: log output dir or `"none"`
|
|
258
278
|
- `coverageDir`: coverage output dir or `"none"`
|
|
259
279
|
- `snapshotDir`: snapshot storage dir
|
|
280
|
+
- `env`: environment variables injected into build and runtime processes
|
|
281
|
+
- `buildOptions.cmd`: optional custom build command template; when set it replaces default build command and flags. Supports `<file>`, `<name>`, `<outFile>`, `<target>`, `<mode>`
|
|
260
282
|
- `buildOptions.target`: `wasi` or `bindings`
|
|
261
|
-
- `modes`: named overrides for target/args/runtime/env/artifact directories (selected via `--mode`)
|
|
283
|
+
- `modes`: named overrides for command/target/args/runtime/env/artifact directories (selected via `--mode`); `mode.env` overrides top-level `env`
|
|
262
284
|
- `runOptions.runtime.cmd`: runtime command, supports `<file>` and `<name>`; if its script path is missing, as-test falls back to the default runner for the selected target
|
|
263
285
|
- `runOptions.reporter`: reporter selection as a string or object
|
|
264
286
|
|
|
@@ -309,6 +331,13 @@ Run all modes:
|
|
|
309
331
|
ast test --mode wasi-simd,wasi-nosimd,bindings-node-simd
|
|
310
332
|
```
|
|
311
333
|
|
|
334
|
+
Summary totals:
|
|
335
|
+
|
|
336
|
+
- `Modes` in the default reporter is config-scoped (`total` is all configured modes)
|
|
337
|
+
- when selecting fewer modes with `--mode`, unselected modes are counted as `skipped`
|
|
338
|
+
- `Files` in the default reporter is also config-scoped (`total` is all files from configured input patterns)
|
|
339
|
+
- when selecting fewer files, unselected files are counted as `skipped`
|
|
340
|
+
|
|
312
341
|
When using `--mode`, compiled artifacts are emitted as:
|
|
313
342
|
|
|
314
343
|
```text
|
|
@@ -80,10 +80,23 @@
|
|
|
80
80
|
],
|
|
81
81
|
"default": true
|
|
82
82
|
},
|
|
83
|
+
"env": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"description": "Environment variables injected when building/running.",
|
|
86
|
+
"additionalProperties": {
|
|
87
|
+
"type": "string"
|
|
88
|
+
},
|
|
89
|
+
"default": {}
|
|
90
|
+
},
|
|
83
91
|
"buildOptions": {
|
|
84
92
|
"type": "object",
|
|
85
93
|
"additionalProperties": false,
|
|
86
94
|
"properties": {
|
|
95
|
+
"cmd": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"description": "Custom build command template. When set, this replaces the default asc command/flags/output handling. Supports placeholders: <file>, <name>, <outFile>, <target>, <mode>.",
|
|
98
|
+
"default": ""
|
|
99
|
+
},
|
|
87
100
|
"args": {
|
|
88
101
|
"type": "array",
|
|
89
102
|
"items": {
|
|
@@ -98,6 +111,7 @@
|
|
|
98
111
|
}
|
|
99
112
|
},
|
|
100
113
|
"default": {
|
|
114
|
+
"cmd": "",
|
|
101
115
|
"args": [],
|
|
102
116
|
"target": "wasi"
|
|
103
117
|
}
|
|
@@ -153,6 +167,10 @@
|
|
|
153
167
|
"type": "object",
|
|
154
168
|
"additionalProperties": false,
|
|
155
169
|
"properties": {
|
|
170
|
+
"cmd": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"description": "Mode-specific custom build command template."
|
|
173
|
+
},
|
|
156
174
|
"args": {
|
|
157
175
|
"type": "array",
|
|
158
176
|
"items": {
|
package/assembly/coverage.ts
CHANGED
|
@@ -23,6 +23,26 @@ export function __REGISTER(point: CoverPoint): void {
|
|
|
23
23
|
Coverage.SN.hashes.set(point.hash, point);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export function __REGISTER_RAW(
|
|
27
|
+
file: string,
|
|
28
|
+
hash: string,
|
|
29
|
+
line: i32,
|
|
30
|
+
column: i32,
|
|
31
|
+
type: string,
|
|
32
|
+
): void {
|
|
33
|
+
if (Coverage.SN.allIndex.has(hash)) return;
|
|
34
|
+
const point = new CoverPoint();
|
|
35
|
+
point.file = file;
|
|
36
|
+
point.hash = hash;
|
|
37
|
+
point.line = line;
|
|
38
|
+
point.column = column;
|
|
39
|
+
point.type = type;
|
|
40
|
+
Coverage.SN.points++;
|
|
41
|
+
Coverage.SN.allIndex.set(hash, Coverage.SN.all.length);
|
|
42
|
+
Coverage.SN.all.push(point);
|
|
43
|
+
Coverage.SN.hashes.set(hash, point);
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
export function __COVER(hash: string): void {
|
|
27
47
|
if (Coverage.SN.allIndex.has(hash)) {
|
|
28
48
|
const index = Coverage.SN.allIndex.get(hash);
|
package/assembly/util/json.ts
CHANGED
|
@@ -18,17 +18,16 @@ export function stringifyValue<T>(value: T): string {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (isInteger<T>() || isFloat<T>()) {
|
|
21
|
-
// @ts-
|
|
21
|
+
// @ts-expect-error: type
|
|
22
22
|
return value.toString();
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (isString<T>()) {
|
|
26
|
-
// @ts-ignore
|
|
27
26
|
return quote(value as string);
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
if (isArray<T>()) {
|
|
31
|
-
// @ts-
|
|
30
|
+
// @ts-expect-error: type
|
|
32
31
|
return stringifyArray<valueof<T>>(value as valueof<T>[]);
|
|
33
32
|
}
|
|
34
33
|
|
package/bin/build.js
CHANGED
|
@@ -5,29 +5,67 @@ import { execSync } from "child_process";
|
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import { applyMode, getPkgRunner, loadConfig } from "./util.js";
|
|
7
7
|
const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
|
|
8
|
-
export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName) {
|
|
8
|
+
export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, featureToggles = {}) {
|
|
9
9
|
const loadedConfig = loadConfig(configPath, false);
|
|
10
10
|
const mode = applyMode(loadedConfig, modeName);
|
|
11
11
|
const config = mode.config;
|
|
12
|
-
|
|
12
|
+
if (!hasCustomBuildCommand(config)) {
|
|
13
|
+
ensureDeps(config);
|
|
14
|
+
}
|
|
13
15
|
const pkgRunner = getPkgRunner();
|
|
14
16
|
const inputPatterns = resolveInputPatterns(config.input, selectors);
|
|
15
17
|
const inputFiles = (await glob(inputPatterns)).sort((a, b) => a.localeCompare(b));
|
|
16
|
-
const
|
|
18
|
+
const coverageEnabled = resolveCoverageEnabled(config.coverage, featureToggles.coverage);
|
|
19
|
+
const buildEnv = {
|
|
20
|
+
...mode.env,
|
|
21
|
+
AS_TEST_COVERAGE_ENABLED: coverageEnabled ? "1" : "0",
|
|
22
|
+
};
|
|
17
23
|
for (const file of inputFiles) {
|
|
18
|
-
let cmd = `${pkgRunner} asc ${file}${buildArgs}`;
|
|
19
24
|
const outFile = `${config.outDir}/${resolveArtifactFileName(file, config.buildOptions.target, modeName)}`;
|
|
20
|
-
|
|
21
|
-
cmd += " -o " + outFile;
|
|
22
|
-
}
|
|
25
|
+
const cmd = getBuildCommand(config, pkgRunner, file, outFile, modeName, featureToggles);
|
|
23
26
|
try {
|
|
24
|
-
buildFile(cmd,
|
|
27
|
+
buildFile(cmd, buildEnv);
|
|
25
28
|
}
|
|
26
29
|
catch (error) {
|
|
27
|
-
|
|
30
|
+
const modeLabel = modeName ?? "default";
|
|
31
|
+
throw new Error(`Failed to build ${path.basename(file)} in mode ${modeLabel} with ${getBuildStderr(error)}\nBuild command: ${cmd}`);
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
}
|
|
35
|
+
function hasCustomBuildCommand(config) {
|
|
36
|
+
return !!config.buildOptions.cmd.trim().length;
|
|
37
|
+
}
|
|
38
|
+
function getBuildCommand(config, pkgRunner, file, outFile, modeName, featureToggles = {}) {
|
|
39
|
+
const userArgs = getUserBuildArgs(config);
|
|
40
|
+
if (hasCustomBuildCommand(config)) {
|
|
41
|
+
return `${expandBuildCommand(config.buildOptions.cmd, file, outFile, config.buildOptions.target, modeName)}${userArgs}`;
|
|
42
|
+
}
|
|
43
|
+
const defaultArgs = getDefaultBuildArgs(config, featureToggles);
|
|
44
|
+
let cmd = `${pkgRunner} asc ${file}${userArgs}${defaultArgs}`;
|
|
45
|
+
if (config.outDir) {
|
|
46
|
+
cmd += " -o " + outFile;
|
|
47
|
+
}
|
|
48
|
+
return cmd;
|
|
49
|
+
}
|
|
50
|
+
function getUserBuildArgs(config) {
|
|
51
|
+
const args = config.buildOptions.args.filter((value) => value.length > 0);
|
|
52
|
+
if (args.length) {
|
|
53
|
+
return " " + args.join(" ");
|
|
54
|
+
}
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
function expandBuildCommand(template, file, outFile, target, modeName) {
|
|
58
|
+
const name = path
|
|
59
|
+
.basename(file)
|
|
60
|
+
.replace(/\.spec\.ts$/, "")
|
|
61
|
+
.replace(/\.ts$/, "");
|
|
62
|
+
return template
|
|
63
|
+
.replace(/<file>/g, file)
|
|
64
|
+
.replace(/<name>/g, name)
|
|
65
|
+
.replace(/<outFile>/g, outFile)
|
|
66
|
+
.replace(/<target>/g, target)
|
|
67
|
+
.replace(/<mode>/g, modeName ?? "");
|
|
68
|
+
}
|
|
31
69
|
function resolveArtifactFileName(file, target, modeName) {
|
|
32
70
|
const base = path
|
|
33
71
|
.basename(file)
|
|
@@ -45,7 +83,7 @@ function resolveInputPatterns(configured, selectors) {
|
|
|
45
83
|
if (!selectors.length)
|
|
46
84
|
return configuredInputs;
|
|
47
85
|
const patterns = new Set();
|
|
48
|
-
for (const selector of selectors) {
|
|
86
|
+
for (const selector of expandSelectors(selectors)) {
|
|
49
87
|
if (!selector)
|
|
50
88
|
continue;
|
|
51
89
|
if (isBareSuiteSelector(selector)) {
|
|
@@ -59,6 +97,30 @@ function resolveInputPatterns(configured, selectors) {
|
|
|
59
97
|
}
|
|
60
98
|
return [...patterns];
|
|
61
99
|
}
|
|
100
|
+
function expandSelectors(selectors) {
|
|
101
|
+
const expanded = [];
|
|
102
|
+
for (const selector of selectors) {
|
|
103
|
+
if (!selector)
|
|
104
|
+
continue;
|
|
105
|
+
if (!shouldSplitSelector(selector)) {
|
|
106
|
+
expanded.push(selector);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
for (const token of selector.split(",")) {
|
|
110
|
+
const trimmed = token.trim();
|
|
111
|
+
if (!trimmed.length)
|
|
112
|
+
continue;
|
|
113
|
+
expanded.push(trimmed);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return expanded;
|
|
117
|
+
}
|
|
118
|
+
function shouldSplitSelector(selector) {
|
|
119
|
+
return (selector.includes(",") &&
|
|
120
|
+
!selector.includes("/") &&
|
|
121
|
+
!selector.includes("\\") &&
|
|
122
|
+
!/[*?[\]{}]/.test(selector));
|
|
123
|
+
}
|
|
62
124
|
function isBareSuiteSelector(selector) {
|
|
63
125
|
return (!selector.includes("/") &&
|
|
64
126
|
!selector.includes("\\") &&
|
|
@@ -98,16 +160,17 @@ function getBuildStderr(error) {
|
|
|
98
160
|
const message = typeof err?.message == "string" ? err.message.trim() : "";
|
|
99
161
|
return message || "unknown error";
|
|
100
162
|
}
|
|
101
|
-
function
|
|
163
|
+
function getDefaultBuildArgs(config, featureToggles) {
|
|
102
164
|
let buildArgs = "";
|
|
165
|
+
const tryAsEnabled = resolveTryAsEnabled(featureToggles.tryAs);
|
|
103
166
|
buildArgs += " --transform as-test/transform";
|
|
104
|
-
if (
|
|
167
|
+
if (tryAsEnabled) {
|
|
105
168
|
buildArgs += " --transform try-as/transform";
|
|
106
169
|
}
|
|
107
170
|
if (config.config && config.config !== "none") {
|
|
108
171
|
buildArgs += " --config " + config.config;
|
|
109
172
|
}
|
|
110
|
-
if (
|
|
173
|
+
if (tryAsEnabled) {
|
|
111
174
|
buildArgs += " --use AS_TEST_TRY_AS=1";
|
|
112
175
|
}
|
|
113
176
|
// Should also strip any bindings-enabling from asconfig
|
|
@@ -124,12 +187,29 @@ function getBuildArgs(config) {
|
|
|
124
187
|
console.log(`${chalk.bgRed(" ERROR ")}${chalk.dim(":")} could not determine target in config! Set target to 'bindings' or 'wasi'`);
|
|
125
188
|
process.exit(1);
|
|
126
189
|
}
|
|
127
|
-
if (config.buildOptions.args.length &&
|
|
128
|
-
config.buildOptions.args.find((v) => v.length > 0)) {
|
|
129
|
-
buildArgs += " " + config.buildOptions.args.join(" ");
|
|
130
|
-
}
|
|
131
190
|
return buildArgs;
|
|
132
191
|
}
|
|
192
|
+
function resolveTryAsEnabled(override) {
|
|
193
|
+
const installed = hasTryAsRuntime();
|
|
194
|
+
if (override === false)
|
|
195
|
+
return false;
|
|
196
|
+
if (override === true && !installed) {
|
|
197
|
+
throw new Error('try-as feature was enabled, but package "try-as" is not installed');
|
|
198
|
+
}
|
|
199
|
+
return installed;
|
|
200
|
+
}
|
|
201
|
+
function resolveCoverageEnabled(rawCoverage, override) {
|
|
202
|
+
if (override != undefined)
|
|
203
|
+
return override;
|
|
204
|
+
if (typeof rawCoverage == "boolean")
|
|
205
|
+
return rawCoverage;
|
|
206
|
+
if (rawCoverage && typeof rawCoverage == "object") {
|
|
207
|
+
const enabled = rawCoverage.enabled;
|
|
208
|
+
if (typeof enabled == "boolean")
|
|
209
|
+
return enabled;
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
133
213
|
function hasTryAsRuntime() {
|
|
134
214
|
return (existsSync(path.join(process.cwd(), "node_modules/try-as")) ||
|
|
135
215
|
existsSync(path.join(process.cwd(), "node_modules/try-as/package.json")));
|