as-test 1.1.10 → 1.3.0
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 +48 -0
- package/as-test.config.schema.json +15 -0
- package/assembly/coverage.ts +22 -26
- package/assembly/index.ts +2 -0
- package/assembly/src/expectation.ts +152 -44
- package/assembly/src/mode.ts +55 -0
- package/bin/commands/build-core.js +190 -65
- package/bin/commands/build.js +3 -1
- package/bin/commands/fuzz-core.js +30 -56
- package/bin/commands/init-core.js +253 -5
- package/bin/commands/run-core.js +38 -119
- package/bin/commands/test.js +1 -1
- package/bin/commands/web-session.js +4 -0
- package/bin/crash-store.js +1 -1
- package/bin/index.js +94 -152
- package/bin/types.js +7 -0
- package/bin/util.js +117 -0
- package/package.json +14 -9
- package/transform/lib/index.js +26 -0
package/bin/util.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
CoverageIgnoreOptions,
|
|
7
7
|
FuzzConfig,
|
|
8
8
|
ModeConfig,
|
|
9
|
+
normalizeFeatureName,
|
|
9
10
|
ReporterConfig,
|
|
10
11
|
RunOptions,
|
|
11
12
|
Runtime,
|
|
@@ -186,6 +187,7 @@ function parseConfigRaw(raw, configPath) {
|
|
|
186
187
|
typeof config.fuzz.crashDir == "string" && config.fuzz.crashDir.length
|
|
187
188
|
? config.fuzz.crashDir
|
|
188
189
|
: "./.as-test/crashes";
|
|
190
|
+
config.features = parseFeaturesField(raw.features);
|
|
189
191
|
config.modes = parseModes(raw.modes, configDir);
|
|
190
192
|
CONFIG_META.set(config, {
|
|
191
193
|
sourcePath: configPath,
|
|
@@ -193,6 +195,19 @@ function parseConfigRaw(raw, configPath) {
|
|
|
193
195
|
});
|
|
194
196
|
return config;
|
|
195
197
|
}
|
|
198
|
+
function parseFeaturesField(raw) {
|
|
199
|
+
if (!Array.isArray(raw)) return [];
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
const out = [];
|
|
202
|
+
for (const value of raw) {
|
|
203
|
+
if (typeof value != "string") continue;
|
|
204
|
+
const name = normalizeFeatureName(value);
|
|
205
|
+
if (!name.length || seen.has(name)) continue;
|
|
206
|
+
seen.add(name);
|
|
207
|
+
out.push(name);
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
196
211
|
const TOP_LEVEL_KEYS = new Set([
|
|
197
212
|
"$schema",
|
|
198
213
|
"input",
|
|
@@ -203,6 +218,7 @@ const TOP_LEVEL_KEYS = new Set([
|
|
|
203
218
|
"snapshotDir",
|
|
204
219
|
"config",
|
|
205
220
|
"coverage",
|
|
221
|
+
"features",
|
|
206
222
|
"env",
|
|
207
223
|
"buildOptions",
|
|
208
224
|
"fuzz",
|
|
@@ -238,6 +254,7 @@ function validateConfig(raw, configPath) {
|
|
|
238
254
|
validateStringField(raw, "snapshotDir", "$", issues);
|
|
239
255
|
validateStringField(raw, "config", "$", issues);
|
|
240
256
|
validateCoverageField(raw, "coverage", "$", issues);
|
|
257
|
+
validateFeaturesField(raw, "features", "$", issues);
|
|
241
258
|
validateEnvField(raw, "env", "$", issues);
|
|
242
259
|
validateBuildOptionsField(raw, "buildOptions", "$", issues);
|
|
243
260
|
validateFuzzField(raw, "fuzz", "$", issues);
|
|
@@ -742,12 +759,34 @@ function validateModesField(raw, key, pathPrefix, issues) {
|
|
|
742
759
|
validateStringField(modeObj, "snapshotDir", modePath, issues);
|
|
743
760
|
validateStringField(modeObj, "config", modePath, issues);
|
|
744
761
|
validateCoverageField(modeObj, "coverage", modePath, issues);
|
|
762
|
+
validateFeaturesField(modeObj, "features", modePath, issues);
|
|
745
763
|
validateFuzzField(modeObj, "fuzz", modePath, issues);
|
|
746
764
|
validateEnvField(modeObj, "env", modePath, issues);
|
|
747
765
|
validateBuildOptionsField(modeObj, "buildOptions", modePath, issues);
|
|
748
766
|
validateRunOptionsField(modeObj, "runOptions", modePath, issues);
|
|
749
767
|
}
|
|
750
768
|
}
|
|
769
|
+
function validateFeaturesField(raw, key, pathPrefix, issues) {
|
|
770
|
+
if (!(key in raw) || raw[key] == undefined) return;
|
|
771
|
+
const value = raw[key];
|
|
772
|
+
if (!Array.isArray(value)) {
|
|
773
|
+
issues.push({
|
|
774
|
+
path: `${pathPrefix}.${key}`,
|
|
775
|
+
message: "must be an array of feature name strings",
|
|
776
|
+
fix: 'example: "features": ["try-as", "simd"]',
|
|
777
|
+
});
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
for (let i = 0; i < value.length; i++) {
|
|
781
|
+
if (typeof value[i] != "string" || !value[i].trim().length) {
|
|
782
|
+
issues.push({
|
|
783
|
+
path: `${pathPrefix}.${key}[${i}]`,
|
|
784
|
+
message: "must be a non-empty string",
|
|
785
|
+
fix: 'feature names look like "try-as" or "simd"',
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
751
790
|
function isStringArray(value) {
|
|
752
791
|
return Array.isArray(value) && value.every((item) => typeof item == "string");
|
|
753
792
|
}
|
|
@@ -1051,6 +1090,7 @@ function cloneConfig(config) {
|
|
|
1051
1090
|
cloned.runOptions = cloneRunOptions(config.runOptions);
|
|
1052
1091
|
cloned.fuzz = cloneFuzzConfig(config.fuzz);
|
|
1053
1092
|
cloned.coverage = cloneCoverageOptions(config.coverage);
|
|
1093
|
+
cloned.features = [...config.features];
|
|
1054
1094
|
cloned.modes = Object.fromEntries(
|
|
1055
1095
|
Object.entries(config.modes).map(([name, mode]) => [
|
|
1056
1096
|
name,
|
|
@@ -1211,6 +1251,9 @@ function mergeRootConfig(base, override) {
|
|
|
1211
1251
|
raw.coverage,
|
|
1212
1252
|
);
|
|
1213
1253
|
}
|
|
1254
|
+
if (Array.isArray(raw.features)) {
|
|
1255
|
+
merged.features = [...override.features];
|
|
1256
|
+
}
|
|
1214
1257
|
if ("env" in raw) {
|
|
1215
1258
|
merged.env = { ...override.env };
|
|
1216
1259
|
}
|
|
@@ -1449,3 +1492,77 @@ export function resolveProjectModule(specifier) {
|
|
|
1449
1492
|
}
|
|
1450
1493
|
return null;
|
|
1451
1494
|
}
|
|
1495
|
+
// picomatch-compatible glob metacharacters; first occurrence marks the start
|
|
1496
|
+
// of the dynamic part of a pattern and everything before it is the static base.
|
|
1497
|
+
const GLOB_META_RE = /[*?[\](){}!|+@]/;
|
|
1498
|
+
// Longest non-glob prefix of a single pattern, returned with native separators.
|
|
1499
|
+
// Examples:
|
|
1500
|
+
// "assembly/__tests__/**/*.spec.ts" -> "assembly/__tests__"
|
|
1501
|
+
// "**/*.spec.ts" -> ""
|
|
1502
|
+
// "assembly/foo.spec.ts" -> "assembly" (no glob → use dirname)
|
|
1503
|
+
// "/abs/path/**/*.ts" -> "/abs/path"
|
|
1504
|
+
export function resolveGlobBase(pattern) {
|
|
1505
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
1506
|
+
const metaIdx = normalized.search(GLOB_META_RE);
|
|
1507
|
+
let base;
|
|
1508
|
+
if (metaIdx < 0) {
|
|
1509
|
+
const dir = dirname(normalized);
|
|
1510
|
+
base = dir == "." ? "" : dir;
|
|
1511
|
+
} else {
|
|
1512
|
+
const slice = normalized.slice(0, metaIdx);
|
|
1513
|
+
const lastSlash = slice.lastIndexOf("/");
|
|
1514
|
+
base = lastSlash < 0 ? "" : slice.slice(0, lastSlash);
|
|
1515
|
+
}
|
|
1516
|
+
if (!base.length) return "";
|
|
1517
|
+
return base.split("/").join(sep);
|
|
1518
|
+
}
|
|
1519
|
+
// Strip the most-specific matching configured input base off `file`, returning
|
|
1520
|
+
// the path relative to that base (with native separators). If no base matches,
|
|
1521
|
+
// returns the basename of the file. Comparison is component-wise — so
|
|
1522
|
+
// "assembly/__tests" is not a prefix of "assembly/__tests__/foo.spec.ts".
|
|
1523
|
+
export function resolveSpecRelativePath(file, inputPatterns) {
|
|
1524
|
+
const patterns = Array.isArray(inputPatterns)
|
|
1525
|
+
? inputPatterns
|
|
1526
|
+
: [inputPatterns];
|
|
1527
|
+
const absFile = resolve(process.cwd(), file);
|
|
1528
|
+
const fileComponents = toComponents(absFile);
|
|
1529
|
+
let bestBaseAbs = null;
|
|
1530
|
+
let bestLength = -1;
|
|
1531
|
+
for (const pattern of patterns) {
|
|
1532
|
+
const base = resolveGlobBase(pattern);
|
|
1533
|
+
const absBase = base.length
|
|
1534
|
+
? resolve(process.cwd(), base)
|
|
1535
|
+
: resolve(process.cwd());
|
|
1536
|
+
const baseComponents = toComponents(absBase);
|
|
1537
|
+
if (!isComponentPrefix(baseComponents, fileComponents)) continue;
|
|
1538
|
+
if (baseComponents.length > bestLength) {
|
|
1539
|
+
bestBaseAbs = absBase;
|
|
1540
|
+
bestLength = baseComponents.length;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
if (bestBaseAbs == null) return basename(file);
|
|
1544
|
+
const rel = relative(bestBaseAbs, absFile);
|
|
1545
|
+
return rel.length ? rel : basename(file);
|
|
1546
|
+
}
|
|
1547
|
+
// Compute the artifact path (relative to outDir) for a given spec/fuzz source
|
|
1548
|
+
// file. Strips ".ts" only, keeping ".spec" / ".fuzz" suffixes so spec and
|
|
1549
|
+
// fuzz artifacts can coexist in the same outDir.
|
|
1550
|
+
// assembly/__tests__/array.spec.ts -> "array.spec.wasm"
|
|
1551
|
+
// assembly/__tests__/nested/array.spec.ts -> "nested/array.spec.wasm"
|
|
1552
|
+
// assembly/__fuzz__/nested/array.fuzz.ts -> "nested/array.fuzz.wasm"
|
|
1553
|
+
export function resolveArtifactPath(file, inputPatterns) {
|
|
1554
|
+
const rel = resolveSpecRelativePath(file, inputPatterns);
|
|
1555
|
+
return rel.replace(/\.ts$/i, ".wasm");
|
|
1556
|
+
}
|
|
1557
|
+
function toComponents(absPath) {
|
|
1558
|
+
return absPath
|
|
1559
|
+
.split(/[\\/]+/)
|
|
1560
|
+
.filter((segment, idx) => segment.length || idx == 0);
|
|
1561
|
+
}
|
|
1562
|
+
function isComponentPrefix(prefix, full) {
|
|
1563
|
+
if (prefix.length > full.length) return false;
|
|
1564
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
1565
|
+
if (prefix[i] !== full[i]) return false;
|
|
1566
|
+
}
|
|
1567
|
+
return true;
|
|
1568
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "as-test",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"author": "Jairus Tanaka",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,16 +18,18 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@assemblyscript/wasi-shim": "^0.1.0",
|
|
20
20
|
"@eslint/js": "^10.0.1",
|
|
21
|
-
"@types/node": "^25.
|
|
21
|
+
"@types/node": "^25.9.1",
|
|
22
22
|
"as-sleep": "^0.0.2",
|
|
23
23
|
"as-test": "./",
|
|
24
24
|
"assemblyscript": "^0.28.17",
|
|
25
25
|
"assemblyscript-prettier": "^3.0.4",
|
|
26
|
-
"
|
|
26
|
+
"husky": "^9.1.7",
|
|
27
|
+
"json-as": "^1.3.7",
|
|
28
|
+
"playwright": "^1.60.0",
|
|
27
29
|
"prettier": "3.8.3",
|
|
28
|
-
"try-as": "^1.0
|
|
30
|
+
"try-as": "^1.1.0",
|
|
29
31
|
"typescript": "^6.0.3",
|
|
30
|
-
"typescript-eslint": "^8.59.
|
|
32
|
+
"typescript-eslint": "^8.59.4",
|
|
31
33
|
"vitepress": "^1.6.4"
|
|
32
34
|
},
|
|
33
35
|
"bin": {
|
|
@@ -76,9 +78,9 @@
|
|
|
76
78
|
},
|
|
77
79
|
"scripts": {
|
|
78
80
|
"test": "npm run test:as && npm run test:integration",
|
|
79
|
-
"test:as": "node ./bin/index.js test --parallel",
|
|
81
|
+
"test:as": "node ./bin/index.js test --parallel --enable try-as",
|
|
80
82
|
"test:integration": "npm run build:cli && npm run build:lib && node --test tests/*.test.mjs",
|
|
81
|
-
"test:ci": "node ./bin/index.js test --parallel --tap --config ./as-test.ci.config.json",
|
|
83
|
+
"test:ci": "node ./bin/index.js test --parallel --tap --enable try-as --config ./as-test.ci.config.json",
|
|
82
84
|
"fuzz": "node ./bin/index.js fuzz",
|
|
83
85
|
"bench:seed": "node ./bin/index.js fuzz --config ./as-test.bench.config.json --clean",
|
|
84
86
|
"bench:seed:compare": "bash ./tools/bench-seed-compare.sh 7",
|
|
@@ -90,7 +92,7 @@
|
|
|
90
92
|
"typecheck": "tsc -p cli --noEmit && tsc -p tsconfig.lib.json --noEmit && tsc -p transform --noEmit",
|
|
91
93
|
"lint": "eslint transform/src/**/*.ts tools/**/*.js eslint.config.js",
|
|
92
94
|
"build:lib": "tsc -p ./tsconfig.lib.json",
|
|
93
|
-
"build:transform": "tsc -p ./transform",
|
|
95
|
+
"build:transform": "tsc -p ./transform && prettier -w ./transform/",
|
|
94
96
|
"build:cli": "tsc -p cli",
|
|
95
97
|
"build:run": "npm run build:cli",
|
|
96
98
|
"docs:dev": "vitepress dev docs",
|
|
@@ -98,7 +100,10 @@
|
|
|
98
100
|
"docs:preview": "vitepress preview docs",
|
|
99
101
|
"format": "prettier -w .",
|
|
100
102
|
"release:check": "npm run build:cli && npm run build:lib && npm run build:transform && npm run test && npm run test:examples && npm pack --dry-run --cache /tmp/as-test-npm-cache",
|
|
101
|
-
"prepublishOnly": "npm run build:cli && npm run build:lib && npm run build:transform && npm run test && npm run format"
|
|
103
|
+
"prepublishOnly": "npm run build:cli && npm run build:lib && npm run build:transform && npm run test && npm run format",
|
|
104
|
+
"commitmsg:verify": "bash ./scripts/commit-msg.sh",
|
|
105
|
+
"precommit:verify": "bash ./scripts/pre-commit.sh",
|
|
106
|
+
"prepare": "husky"
|
|
102
107
|
},
|
|
103
108
|
"type": "module"
|
|
104
109
|
}
|
package/transform/lib/index.js
CHANGED
|
@@ -5,8 +5,10 @@ import { MockTransform } from "./mock.js";
|
|
|
5
5
|
import { LocationTransform } from "./location.js";
|
|
6
6
|
import { LogTransform } from "./log.js";
|
|
7
7
|
import { isStdlib } from "./util.js";
|
|
8
|
+
import { NodeKind } from "./types.js";
|
|
8
9
|
export default class Transformer extends Transform {
|
|
9
10
|
afterParse(parser) {
|
|
11
|
+
patchModeName(parser, process.env.AS_TEST_MODE_NAME ?? "default");
|
|
10
12
|
const mock = new MockTransform();
|
|
11
13
|
const location = new LocationTransform();
|
|
12
14
|
const log = new LogTransform(parser);
|
|
@@ -81,6 +83,30 @@ export default class Transformer extends Transform {
|
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
}
|
|
86
|
+
function patchModeName(parser, modeName) {
|
|
87
|
+
for (const source of parser.sources) {
|
|
88
|
+
if (!source.normalizedPath.endsWith("assembly/src/mode.ts"))
|
|
89
|
+
continue;
|
|
90
|
+
for (const stmt of source.statements) {
|
|
91
|
+
if (stmt.kind !== NodeKind.Variable)
|
|
92
|
+
continue;
|
|
93
|
+
const decls = stmt.declarations;
|
|
94
|
+
for (const decl of decls) {
|
|
95
|
+
if (decl.name.text !== "AS_TEST_MODE_NAME")
|
|
96
|
+
continue;
|
|
97
|
+
if (!decl.initializer)
|
|
98
|
+
continue;
|
|
99
|
+
if (decl.initializer.kind !== NodeKind.Literal)
|
|
100
|
+
continue;
|
|
101
|
+
const literal = decl.initializer;
|
|
102
|
+
if (literal.literalKind !== 2)
|
|
103
|
+
continue;
|
|
104
|
+
literal.value = modeName;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
84
110
|
function collectMockImportTargets(sources) {
|
|
85
111
|
const out = new Set();
|
|
86
112
|
const pattern = /\bmockImport\s*\(\s*["']([^"']+)["']/g;
|