as-test 1.0.1 → 1.0.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 +112 -1
- package/README.md +138 -406
- package/as-test.config.schema.json +210 -17
- package/assembly/__fuzz__/array.fuzz.ts +10 -0
- package/assembly/__fuzz__/bytes.fuzz.ts +8 -0
- package/assembly/__fuzz__/math.fuzz.ts +9 -0
- package/assembly/__fuzz__/string.fuzz.ts +21 -0
- package/assembly/index.ts +141 -86
- package/assembly/src/expectation.ts +104 -19
- package/assembly/src/fuzz.ts +723 -0
- package/assembly/src/log.ts +6 -1
- package/assembly/src/suite.ts +45 -3
- package/assembly/util/json.ts +38 -4
- package/assembly/util/wipc.ts +35 -26
- package/bin/build-worker-pool.js +149 -0
- package/bin/build-worker.js +43 -0
- package/bin/commands/build-core.js +214 -28
- package/bin/commands/build.js +1 -0
- package/bin/commands/fuzz-core.js +306 -0
- package/bin/commands/fuzz.js +10 -0
- package/bin/commands/init-core.js +129 -24
- package/bin/commands/run-core.js +525 -123
- package/bin/commands/run.js +4 -1
- package/bin/commands/test.js +8 -3
- package/bin/commands/web-runner-source.js +634 -0
- package/bin/crash-store.js +64 -0
- package/bin/index.js +1484 -169
- package/bin/reporters/default.js +281 -49
- package/bin/reporters/tap.js +83 -2
- package/bin/types.js +19 -2
- package/bin/util.js +315 -33
- package/bin/wipc.js +79 -0
- package/package.json +19 -9
- package/transform/lib/coverage.js +1 -2
- package/transform/lib/index.js +3 -3
- package/transform/lib/log.js +1 -1
package/bin/util.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "fs";
|
|
2
|
-
import { BuildOptions, Config, CoverageOptions, ModeConfig, ReporterConfig, RunOptions, Runtime, } from "./types.js";
|
|
2
|
+
import { BuildOptions, Config, CoverageOptions, FuzzConfig, ModeConfig, ReporterConfig, RunOptions, Runtime, } from "./types.js";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import { delimiter, dirname, join } from "path";
|
|
5
|
+
import { delimiter, dirname, join, resolve } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
export function formatTime(ms) {
|
|
8
8
|
if (ms < 0) {
|
|
@@ -48,9 +48,10 @@ export function loadConfig(CONFIG_PATH, warn = false) {
|
|
|
48
48
|
}
|
|
49
49
|
const raw = parsed;
|
|
50
50
|
validateConfig(raw, CONFIG_PATH);
|
|
51
|
+
const configDir = dirname(CONFIG_PATH);
|
|
51
52
|
const config = Object.assign(new Config(), raw);
|
|
52
53
|
applyOutputConfig(raw.output, raw, config);
|
|
53
|
-
config.env =
|
|
54
|
+
config.env = parseEnvValue(raw.env, configDir, "$.env");
|
|
54
55
|
const runOptionsRaw = raw.runOptions ?? {};
|
|
55
56
|
config.buildOptions = Object.assign(new BuildOptions(), raw.buildOptions ?? {});
|
|
56
57
|
config.buildOptions.cmd =
|
|
@@ -58,6 +59,7 @@ export function loadConfig(CONFIG_PATH, warn = false) {
|
|
|
58
59
|
config.buildOptions.args = Array.isArray(config.buildOptions.args)
|
|
59
60
|
? config.buildOptions.args.filter((item) => typeof item == "string")
|
|
60
61
|
: [];
|
|
62
|
+
config.buildOptions.env = parseEnvValue(raw.buildOptions?.env, configDir, "$.buildOptions.env");
|
|
61
63
|
config.buildOptions.target =
|
|
62
64
|
typeof config.buildOptions.target == "string" &&
|
|
63
65
|
config.buildOptions.target.length
|
|
@@ -99,8 +101,35 @@ export function loadConfig(CONFIG_PATH, warn = false) {
|
|
|
99
101
|
? legacyRun
|
|
100
102
|
: runtime.cmd;
|
|
101
103
|
runtime.cmd = cmd;
|
|
104
|
+
runtime.browser =
|
|
105
|
+
runtimeRaw && typeof runtimeRaw.browser == "string"
|
|
106
|
+
? runtimeRaw.browser
|
|
107
|
+
: "";
|
|
102
108
|
config.runOptions.runtime = runtime;
|
|
103
|
-
config.
|
|
109
|
+
config.runOptions.env = parseEnvValue(runOptionsRaw.env, configDir, "$.runOptions.env");
|
|
110
|
+
const fuzzRaw = raw.fuzz ?? {};
|
|
111
|
+
config.fuzz = Object.assign(new FuzzConfig(), fuzzRaw);
|
|
112
|
+
config.fuzz.input = Array.isArray(config.fuzz.input)
|
|
113
|
+
? config.fuzz.input.filter((item) => typeof item == "string")
|
|
114
|
+
: typeof fuzzRaw.input == "string"
|
|
115
|
+
? [fuzzRaw.input]
|
|
116
|
+
: new FuzzConfig().input;
|
|
117
|
+
config.fuzz.runs = normalizePositiveNumber(config.fuzz.runs, 1000);
|
|
118
|
+
config.fuzz.seed = normalizeNonNegativeNumber(config.fuzz.seed, 1337);
|
|
119
|
+
config.fuzz.maxInputBytes = normalizePositiveNumber(config.fuzz.maxInputBytes, 4096);
|
|
120
|
+
config.fuzz.target =
|
|
121
|
+
typeof config.fuzz.target == "string" && config.fuzz.target.length
|
|
122
|
+
? config.fuzz.target
|
|
123
|
+
: "bindings";
|
|
124
|
+
config.fuzz.corpusDir =
|
|
125
|
+
typeof config.fuzz.corpusDir == "string" && config.fuzz.corpusDir.length
|
|
126
|
+
? config.fuzz.corpusDir
|
|
127
|
+
: "./.as-test/fuzz/corpus";
|
|
128
|
+
config.fuzz.crashDir =
|
|
129
|
+
typeof config.fuzz.crashDir == "string" && config.fuzz.crashDir.length
|
|
130
|
+
? config.fuzz.crashDir
|
|
131
|
+
: "./.as-test/crashes";
|
|
132
|
+
config.modes = parseModes(raw.modes, configDir);
|
|
104
133
|
return config;
|
|
105
134
|
}
|
|
106
135
|
}
|
|
@@ -116,14 +145,24 @@ const TOP_LEVEL_KEYS = new Set([
|
|
|
116
145
|
"coverage",
|
|
117
146
|
"env",
|
|
118
147
|
"buildOptions",
|
|
148
|
+
"fuzz",
|
|
119
149
|
"modes",
|
|
120
150
|
"runOptions",
|
|
121
151
|
]);
|
|
122
|
-
const BUILD_OPTION_KEYS = new Set(["cmd", "args", "target"]);
|
|
123
|
-
const RUN_OPTION_KEYS = new Set(["runtime", "reporter", "run"]); // includes legacy "run"
|
|
124
|
-
const RUNTIME_OPTION_KEYS = new Set(["cmd", "run"]); // includes legacy "run"
|
|
152
|
+
const BUILD_OPTION_KEYS = new Set(["cmd", "args", "target", "env"]);
|
|
153
|
+
const RUN_OPTION_KEYS = new Set(["runtime", "reporter", "run", "env"]); // includes legacy "run"
|
|
154
|
+
const RUNTIME_OPTION_KEYS = new Set(["cmd", "run", "browser"]); // includes legacy "run"
|
|
125
155
|
const REPORTER_OPTION_KEYS = new Set(["name", "options", "outDir", "outFile"]);
|
|
126
156
|
const OUTPUT_OPTION_KEYS = new Set(["build", "logs", "coverage", "snapshots"]);
|
|
157
|
+
const FUZZ_OPTION_KEYS = new Set([
|
|
158
|
+
"input",
|
|
159
|
+
"runs",
|
|
160
|
+
"seed",
|
|
161
|
+
"maxInputBytes",
|
|
162
|
+
"target",
|
|
163
|
+
"corpusDir",
|
|
164
|
+
"crashDir",
|
|
165
|
+
]);
|
|
127
166
|
const MODE_KEYS = new Set([
|
|
128
167
|
"outDir",
|
|
129
168
|
"logs",
|
|
@@ -149,6 +188,7 @@ function validateConfig(raw, configPath) {
|
|
|
149
188
|
validateCoverageField(raw, "coverage", "$", issues);
|
|
150
189
|
validateEnvField(raw, "env", "$", issues);
|
|
151
190
|
validateBuildOptionsField(raw, "buildOptions", "$", issues);
|
|
191
|
+
validateFuzzField(raw, "fuzz", "$", issues);
|
|
152
192
|
validateRunOptionsField(raw, "runOptions", "$", issues);
|
|
153
193
|
validateModesField(raw, "modes", "$", issues);
|
|
154
194
|
if (!issues.length)
|
|
@@ -279,7 +319,7 @@ function validateCoverageValue(value, path, issues) {
|
|
|
279
319
|
issues.push({
|
|
280
320
|
path,
|
|
281
321
|
message: "must be a boolean or object",
|
|
282
|
-
fix: 'use true/false or { "enabled": true, "includeSpecs": false }',
|
|
322
|
+
fix: 'use true/false or { "enabled": true, "includeSpecs": false, "include": ["assembly/**/*.ts"], "exclude": ["assembly/__tests__/**/*.spec.ts"] }',
|
|
283
323
|
});
|
|
284
324
|
return;
|
|
285
325
|
}
|
|
@@ -298,16 +338,72 @@ function validateCoverageValue(value, path, issues) {
|
|
|
298
338
|
fix: "set to true or false",
|
|
299
339
|
});
|
|
300
340
|
}
|
|
341
|
+
validateStringArrayField(obj, "include", path, issues);
|
|
342
|
+
validateStringArrayField(obj, "exclude", path, issues);
|
|
343
|
+
}
|
|
344
|
+
function validateStringArrayField(raw, key, pathPrefix, issues) {
|
|
345
|
+
if (!(key in raw) || raw[key] == undefined)
|
|
346
|
+
return;
|
|
347
|
+
const value = raw[key];
|
|
348
|
+
if (!Array.isArray(value)) {
|
|
349
|
+
issues.push({
|
|
350
|
+
path: `${pathPrefix}.${key}`,
|
|
351
|
+
message: "must be an array of strings",
|
|
352
|
+
fix: `set "${key}" to an array of glob patterns`,
|
|
353
|
+
});
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
for (let i = 0; i < value.length; i++) {
|
|
357
|
+
if (typeof value[i] == "string" && value[i].length)
|
|
358
|
+
continue;
|
|
359
|
+
issues.push({
|
|
360
|
+
path: `${pathPrefix}.${key}[${i}]`,
|
|
361
|
+
message: "must be a non-empty string",
|
|
362
|
+
fix: "remove invalid entries or replace them with valid glob strings",
|
|
363
|
+
});
|
|
364
|
+
}
|
|
301
365
|
}
|
|
302
366
|
function validateEnvField(raw, key, pathPrefix, issues) {
|
|
303
367
|
if (!(key in raw) || raw[key] == undefined)
|
|
304
368
|
return;
|
|
305
369
|
const value = raw[key];
|
|
306
|
-
if (
|
|
370
|
+
if (typeof value == "string") {
|
|
371
|
+
if (!value.length) {
|
|
372
|
+
issues.push({
|
|
373
|
+
path: `${pathPrefix}.${key}`,
|
|
374
|
+
message: "must not be an empty string",
|
|
375
|
+
fix: 'use a .env file path like "./secrets/.env"',
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (Array.isArray(value)) {
|
|
381
|
+
for (let i = 0; i < value.length; i++) {
|
|
382
|
+
const item = value[i];
|
|
383
|
+
if (typeof item != "string" || !item.length) {
|
|
384
|
+
issues.push({
|
|
385
|
+
path: `${pathPrefix}.${key}[${i}]`,
|
|
386
|
+
message: "must be a non-empty string",
|
|
387
|
+
fix: 'use entries like "FOO=1"',
|
|
388
|
+
});
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const separator = item.indexOf("=");
|
|
392
|
+
if (separator <= 0) {
|
|
393
|
+
issues.push({
|
|
394
|
+
path: `${pathPrefix}.${key}[${i}]`,
|
|
395
|
+
message: 'must use "KEY=value" format',
|
|
396
|
+
fix: 'example: "FOO=1"',
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (!value || typeof value != "object") {
|
|
307
403
|
issues.push({
|
|
308
404
|
path: `${pathPrefix}.${key}`,
|
|
309
|
-
message: "must be
|
|
310
|
-
fix: 'example: "env": { "MY_FLAG": "1" }',
|
|
405
|
+
message: "must be a .env file path, array of KEY=value strings, or object of string values",
|
|
406
|
+
fix: 'example: "env": "./secrets/.env" or ["MY_FLAG=1"] or { "MY_FLAG": "1" }',
|
|
311
407
|
});
|
|
312
408
|
return;
|
|
313
409
|
}
|
|
@@ -354,17 +450,20 @@ function validateBuildOptionsField(raw, key, pathPrefix, issues) {
|
|
|
354
450
|
issues.push({
|
|
355
451
|
path: `${pathPrefix}.${key}.target`,
|
|
356
452
|
message: "must be a string",
|
|
357
|
-
fix: 'set to "wasi" or "
|
|
453
|
+
fix: 'set to "wasi", "bindings", or "web"',
|
|
358
454
|
});
|
|
359
455
|
}
|
|
360
|
-
else if (obj.target != "wasi" &&
|
|
456
|
+
else if (obj.target != "wasi" &&
|
|
457
|
+
obj.target != "bindings" &&
|
|
458
|
+
obj.target != "web") {
|
|
361
459
|
issues.push({
|
|
362
460
|
path: `${pathPrefix}.${key}.target`,
|
|
363
|
-
message: `must be "wasi" or "
|
|
461
|
+
message: `must be "wasi", "bindings", or "web"`,
|
|
364
462
|
fix: `received "${obj.target}"`,
|
|
365
463
|
});
|
|
366
464
|
}
|
|
367
465
|
}
|
|
466
|
+
validateEnvField(obj, "env", `${pathPrefix}.${key}`, issues);
|
|
368
467
|
}
|
|
369
468
|
function validateRunOptionsField(raw, key, pathPrefix, issues) {
|
|
370
469
|
if (!(key in raw) || raw[key] == undefined)
|
|
@@ -413,6 +512,13 @@ function validateRunOptionsField(raw, key, pathPrefix, issues) {
|
|
|
413
512
|
fix: 'legacy "run" should be a command string',
|
|
414
513
|
});
|
|
415
514
|
}
|
|
515
|
+
if ("browser" in runtimeObj && typeof runtimeObj.browser != "string") {
|
|
516
|
+
issues.push({
|
|
517
|
+
path: `${pathPrefix}.${key}.runtime.browser`,
|
|
518
|
+
message: "must be a string",
|
|
519
|
+
fix: 'set to "chrome", "chromium", "firefox", "webkit", or an executable path',
|
|
520
|
+
});
|
|
521
|
+
}
|
|
416
522
|
}
|
|
417
523
|
}
|
|
418
524
|
if ("reporter" in obj && obj.reporter != undefined) {
|
|
@@ -463,6 +569,36 @@ function validateRunOptionsField(raw, key, pathPrefix, issues) {
|
|
|
463
569
|
});
|
|
464
570
|
}
|
|
465
571
|
}
|
|
572
|
+
validateEnvField(obj, "env", `${pathPrefix}.${key}`, issues);
|
|
573
|
+
}
|
|
574
|
+
function validateFuzzField(raw, key, pathPrefix, issues) {
|
|
575
|
+
if (!(key in raw) || raw[key] == undefined)
|
|
576
|
+
return;
|
|
577
|
+
const value = raw[key];
|
|
578
|
+
if (!value || typeof value != "object" || Array.isArray(value)) {
|
|
579
|
+
issues.push({
|
|
580
|
+
path: `${pathPrefix}.${key}`,
|
|
581
|
+
message: "must be an object",
|
|
582
|
+
fix: 'example: "fuzz": { "input": ["./assembly/__fuzz__/*.fuzz.ts"], "runs": 1000 }',
|
|
583
|
+
});
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const obj = value;
|
|
587
|
+
validateUnknownKeys(obj, FUZZ_OPTION_KEYS, `${pathPrefix}.${key}`, issues);
|
|
588
|
+
validateInputField(obj, "input", `${pathPrefix}.${key}`, issues);
|
|
589
|
+
validateStringField(obj, "target", `${pathPrefix}.${key}`, issues);
|
|
590
|
+
validateStringField(obj, "corpusDir", `${pathPrefix}.${key}`, issues);
|
|
591
|
+
validateStringField(obj, "crashDir", `${pathPrefix}.${key}`, issues);
|
|
592
|
+
validateNumberField(obj, "runs", `${pathPrefix}.${key}`, issues, true);
|
|
593
|
+
validateNumberField(obj, "seed", `${pathPrefix}.${key}`, issues, false);
|
|
594
|
+
validateNumberField(obj, "maxInputBytes", `${pathPrefix}.${key}`, issues, true);
|
|
595
|
+
if ("target" in obj && obj.target != "bindings") {
|
|
596
|
+
issues.push({
|
|
597
|
+
path: `${pathPrefix}.${key}.target`,
|
|
598
|
+
message: 'must be "bindings"',
|
|
599
|
+
fix: 'set to "bindings"',
|
|
600
|
+
});
|
|
601
|
+
}
|
|
466
602
|
}
|
|
467
603
|
function validateModesField(raw, key, pathPrefix, issues) {
|
|
468
604
|
if (!(key in raw) || raw[key] == undefined)
|
|
@@ -501,6 +637,33 @@ function validateModesField(raw, key, pathPrefix, issues) {
|
|
|
501
637
|
function isStringArray(value) {
|
|
502
638
|
return Array.isArray(value) && value.every((item) => typeof item == "string");
|
|
503
639
|
}
|
|
640
|
+
function validateNumberField(raw, key, pathPrefix, issues, positiveOnly) {
|
|
641
|
+
if (!(key in raw) || raw[key] == undefined)
|
|
642
|
+
return;
|
|
643
|
+
if (typeof raw[key] != "number" || !Number.isFinite(raw[key])) {
|
|
644
|
+
issues.push({
|
|
645
|
+
path: `${pathPrefix}.${key}`,
|
|
646
|
+
message: "must be a finite number",
|
|
647
|
+
fix: `set "${key}" to a numeric value`,
|
|
648
|
+
});
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (positiveOnly && Number(raw[key]) <= 0) {
|
|
652
|
+
issues.push({
|
|
653
|
+
path: `${pathPrefix}.${key}`,
|
|
654
|
+
message: "must be greater than zero",
|
|
655
|
+
fix: `set "${key}" to a positive integer`,
|
|
656
|
+
});
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (!positiveOnly && Number(raw[key]) < 0) {
|
|
660
|
+
issues.push({
|
|
661
|
+
path: `${pathPrefix}.${key}`,
|
|
662
|
+
message: "must be zero or greater",
|
|
663
|
+
fix: `set "${key}" to a non-negative integer`,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
504
667
|
function resolveClosestKey(value, keys) {
|
|
505
668
|
let best = null;
|
|
506
669
|
let bestDistance = Number.POSITIVE_INFINITY;
|
|
@@ -589,7 +752,7 @@ function applyOutputRoot(root, rawConfig, config) {
|
|
|
589
752
|
config.snapshotDir = join(root, "snapshots");
|
|
590
753
|
}
|
|
591
754
|
}
|
|
592
|
-
function parseModes(raw) {
|
|
755
|
+
function parseModes(raw, configDir) {
|
|
593
756
|
if (!raw || typeof raw != "object" || Array.isArray(raw))
|
|
594
757
|
return {};
|
|
595
758
|
const out = {};
|
|
@@ -629,6 +792,7 @@ function parseModes(raw) {
|
|
|
629
792
|
if (Array.isArray(buildRaw.args)) {
|
|
630
793
|
build.args = buildRaw.args.filter((item) => typeof item == "string");
|
|
631
794
|
}
|
|
795
|
+
build.env = parseEnvValue(buildRaw.env, configDir, `$.modes.${name}.buildOptions.env`);
|
|
632
796
|
if (typeof buildRaw.target == "string" && buildRaw.target.length) {
|
|
633
797
|
build.target = buildRaw.target;
|
|
634
798
|
}
|
|
@@ -649,6 +813,8 @@ function parseModes(raw) {
|
|
|
649
813
|
else {
|
|
650
814
|
runtime.cmd = "";
|
|
651
815
|
}
|
|
816
|
+
runtime.browser =
|
|
817
|
+
typeof runtimeRaw.browser == "string" ? runtimeRaw.browser : "";
|
|
652
818
|
run.runtime = runtime;
|
|
653
819
|
}
|
|
654
820
|
if (typeof runRaw.reporter == "string") {
|
|
@@ -666,16 +832,10 @@ function parseModes(raw) {
|
|
|
666
832
|
typeof reporter.outFile == "string" ? reporter.outFile : "";
|
|
667
833
|
run.reporter = reporter;
|
|
668
834
|
}
|
|
835
|
+
run.env = parseEnvValue(runRaw.env, configDir, `$.modes.${name}.runOptions.env`);
|
|
669
836
|
mode.runOptions = run;
|
|
670
837
|
}
|
|
671
|
-
|
|
672
|
-
const env = {};
|
|
673
|
-
for (const [key, val] of Object.entries(modeRaw.env)) {
|
|
674
|
-
if (typeof val == "string")
|
|
675
|
-
env[key] = val;
|
|
676
|
-
}
|
|
677
|
-
mode.env = env;
|
|
678
|
-
}
|
|
838
|
+
mode.env = parseEnvValue(modeRaw.env, configDir, `$.modes.${name}.env`);
|
|
679
839
|
out[name] = mode;
|
|
680
840
|
}
|
|
681
841
|
return out;
|
|
@@ -690,6 +850,87 @@ function parseEnvMap(raw) {
|
|
|
690
850
|
}
|
|
691
851
|
return env;
|
|
692
852
|
}
|
|
853
|
+
function parseEnvValue(raw, configDir, pathLabel) {
|
|
854
|
+
if (raw == undefined)
|
|
855
|
+
return {};
|
|
856
|
+
if (typeof raw == "string") {
|
|
857
|
+
return parseEnvFile(resolve(configDir, raw), pathLabel);
|
|
858
|
+
}
|
|
859
|
+
if (Array.isArray(raw)) {
|
|
860
|
+
return parseInlineEnvEntries(raw, pathLabel);
|
|
861
|
+
}
|
|
862
|
+
return parseEnvMap(raw);
|
|
863
|
+
}
|
|
864
|
+
function parseInlineEnvEntries(values, pathLabel) {
|
|
865
|
+
const env = {};
|
|
866
|
+
for (let i = 0; i < values.length; i++) {
|
|
867
|
+
const item = values[i];
|
|
868
|
+
if (typeof item != "string")
|
|
869
|
+
continue;
|
|
870
|
+
const separator = item.indexOf("=");
|
|
871
|
+
if (separator <= 0) {
|
|
872
|
+
throw new Error(`invalid config at ${pathLabel}\nenv entry at index ${i} must use KEY=value format`);
|
|
873
|
+
}
|
|
874
|
+
const key = item.slice(0, separator).trim();
|
|
875
|
+
const value = item.slice(separator + 1);
|
|
876
|
+
if (!key.length) {
|
|
877
|
+
throw new Error(`invalid config at ${pathLabel}\nenv entry at index ${i} must use a non-empty key`);
|
|
878
|
+
}
|
|
879
|
+
env[key] = value;
|
|
880
|
+
}
|
|
881
|
+
return env;
|
|
882
|
+
}
|
|
883
|
+
function parseEnvFile(envPath, pathLabel) {
|
|
884
|
+
if (!existsSync(envPath)) {
|
|
885
|
+
throw new Error(`invalid config at ${pathLabel}\nenv file not found: ${envPath}`);
|
|
886
|
+
}
|
|
887
|
+
const env = {};
|
|
888
|
+
const lines = readFileSync(envPath, "utf8").split(/\r?\n/);
|
|
889
|
+
for (let i = 0; i < lines.length; i++) {
|
|
890
|
+
const rawLine = lines[i];
|
|
891
|
+
const line = rawLine.trim();
|
|
892
|
+
if (!line.length || line.startsWith("#"))
|
|
893
|
+
continue;
|
|
894
|
+
const normalized = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
895
|
+
const separator = normalized.indexOf("=");
|
|
896
|
+
if (separator <= 0) {
|
|
897
|
+
throw new Error(`invalid config at ${pathLabel}\ninvalid env line ${i + 1} in ${envPath}: expected KEY=value`);
|
|
898
|
+
}
|
|
899
|
+
const key = normalized.slice(0, separator).trim();
|
|
900
|
+
const value = normalized.slice(separator + 1).trim();
|
|
901
|
+
env[key] = unquoteEnvValue(value);
|
|
902
|
+
}
|
|
903
|
+
return env;
|
|
904
|
+
}
|
|
905
|
+
function unquoteEnvValue(value) {
|
|
906
|
+
if (value.length < 2)
|
|
907
|
+
return value;
|
|
908
|
+
const quote = value[0];
|
|
909
|
+
if ((quote != '"' && quote != "'") || value[value.length - 1] != quote) {
|
|
910
|
+
return value;
|
|
911
|
+
}
|
|
912
|
+
const inner = value.slice(1, -1);
|
|
913
|
+
if (quote == "'")
|
|
914
|
+
return inner;
|
|
915
|
+
return inner
|
|
916
|
+
.replace(/\\n/g, "\n")
|
|
917
|
+
.replace(/\\r/g, "\r")
|
|
918
|
+
.replace(/\\t/g, "\t")
|
|
919
|
+
.replace(/\\"/g, '"')
|
|
920
|
+
.replace(/\\\\/g, "\\");
|
|
921
|
+
}
|
|
922
|
+
function normalizePositiveNumber(value, fallback) {
|
|
923
|
+
if (typeof value != "number" || !Number.isFinite(value) || value <= 0) {
|
|
924
|
+
return fallback;
|
|
925
|
+
}
|
|
926
|
+
return Math.floor(value);
|
|
927
|
+
}
|
|
928
|
+
function normalizeNonNegativeNumber(value, fallback) {
|
|
929
|
+
if (typeof value != "number" || !Number.isFinite(value) || value < 0) {
|
|
930
|
+
return fallback;
|
|
931
|
+
}
|
|
932
|
+
return Math.floor(value);
|
|
933
|
+
}
|
|
693
934
|
export function resolveModeNames(rawArgs) {
|
|
694
935
|
const names = [];
|
|
695
936
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
@@ -718,12 +959,32 @@ function appendModeTokens(out, value) {
|
|
|
718
959
|
}
|
|
719
960
|
export function applyMode(config, modeName) {
|
|
720
961
|
if (!modeName) {
|
|
962
|
+
const merged = Object.assign(new Config(), config);
|
|
963
|
+
merged.buildOptions = Object.assign(new BuildOptions(), config.buildOptions);
|
|
964
|
+
merged.runOptions = Object.assign(new RunOptions(), config.runOptions);
|
|
965
|
+
merged.runOptions.runtime = Object.assign(new Runtime(), config.runOptions.runtime);
|
|
966
|
+
merged.buildOptions.env = { ...config.buildOptions.env };
|
|
967
|
+
merged.runOptions.env = { ...config.runOptions.env };
|
|
968
|
+
merged.outDir = appendPathSegment(config.outDir, "default");
|
|
969
|
+
if (config.logs != "none") {
|
|
970
|
+
merged.logs = appendPathSegment(config.logs, "default");
|
|
971
|
+
}
|
|
972
|
+
if (config.coverageDir != "none") {
|
|
973
|
+
merged.coverageDir = appendPathSegment(config.coverageDir, "default");
|
|
974
|
+
}
|
|
975
|
+
merged.fuzz = Object.assign(new FuzzConfig(), config.fuzz);
|
|
976
|
+
merged.fuzz.crashDir = appendPathSegment(config.fuzz.crashDir, "default");
|
|
977
|
+
merged.fuzz.corpusDir = appendPathSegment(config.fuzz.corpusDir, "default");
|
|
978
|
+
const env = {
|
|
979
|
+
...process.env,
|
|
980
|
+
...config.env,
|
|
981
|
+
};
|
|
982
|
+
if (merged.runOptions.runtime.browser.length) {
|
|
983
|
+
env.BROWSER = merged.runOptions.runtime.browser;
|
|
984
|
+
}
|
|
721
985
|
return {
|
|
722
|
-
config,
|
|
723
|
-
env
|
|
724
|
-
...process.env,
|
|
725
|
-
...config.env,
|
|
726
|
-
},
|
|
986
|
+
config: merged,
|
|
987
|
+
env,
|
|
727
988
|
};
|
|
728
989
|
}
|
|
729
990
|
const mode = config.modes[modeName];
|
|
@@ -736,6 +997,8 @@ export function applyMode(config, modeName) {
|
|
|
736
997
|
merged.buildOptions = Object.assign(new BuildOptions(), config.buildOptions);
|
|
737
998
|
merged.runOptions = Object.assign(new RunOptions(), config.runOptions);
|
|
738
999
|
merged.runOptions.runtime = Object.assign(new Runtime(), config.runOptions.runtime);
|
|
1000
|
+
merged.buildOptions.env = { ...config.buildOptions.env };
|
|
1001
|
+
merged.runOptions.env = { ...config.runOptions.env };
|
|
739
1002
|
if (mode.outDir)
|
|
740
1003
|
merged.outDir = mode.outDir;
|
|
741
1004
|
else
|
|
@@ -764,19 +1027,38 @@ export function applyMode(config, modeName) {
|
|
|
764
1027
|
...mode.buildOptions.args,
|
|
765
1028
|
];
|
|
766
1029
|
}
|
|
1030
|
+
if (mode.buildOptions.env) {
|
|
1031
|
+
merged.buildOptions.env = {
|
|
1032
|
+
...merged.buildOptions.env,
|
|
1033
|
+
...mode.buildOptions.env,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
767
1036
|
if (mode.runOptions.runtime?.cmd) {
|
|
768
1037
|
merged.runOptions.runtime.cmd = mode.runOptions.runtime.cmd;
|
|
769
1038
|
}
|
|
1039
|
+
if (mode.runOptions.runtime?.browser != undefined) {
|
|
1040
|
+
merged.runOptions.runtime.browser = mode.runOptions.runtime.browser;
|
|
1041
|
+
}
|
|
770
1042
|
if (mode.runOptions.reporter != undefined) {
|
|
771
1043
|
merged.runOptions.reporter = mode.runOptions.reporter;
|
|
772
1044
|
}
|
|
1045
|
+
if (mode.runOptions.env) {
|
|
1046
|
+
merged.runOptions.env = {
|
|
1047
|
+
...merged.runOptions.env,
|
|
1048
|
+
...mode.runOptions.env,
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
const env = {
|
|
1052
|
+
...process.env,
|
|
1053
|
+
...config.env,
|
|
1054
|
+
...mode.env,
|
|
1055
|
+
};
|
|
1056
|
+
if (merged.runOptions.runtime.browser.length) {
|
|
1057
|
+
env.BROWSER = merged.runOptions.runtime.browser;
|
|
1058
|
+
}
|
|
773
1059
|
return {
|
|
774
1060
|
config: merged,
|
|
775
|
-
env
|
|
776
|
-
...process.env,
|
|
777
|
-
...config.env,
|
|
778
|
-
...mode.env,
|
|
779
|
-
},
|
|
1061
|
+
env,
|
|
780
1062
|
modeName,
|
|
781
1063
|
};
|
|
782
1064
|
}
|
package/bin/wipc.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export var MessageType;
|
|
2
|
+
(function (MessageType) {
|
|
3
|
+
MessageType[MessageType["OPEN"] = 0] = "OPEN";
|
|
4
|
+
MessageType[MessageType["CLOSE"] = 1] = "CLOSE";
|
|
5
|
+
MessageType[MessageType["CALL"] = 2] = "CALL";
|
|
6
|
+
MessageType[MessageType["DATA"] = 3] = "DATA";
|
|
7
|
+
})(MessageType || (MessageType = {}));
|
|
8
|
+
export class Channel {
|
|
9
|
+
constructor(input = process.stdin, output = process.stdout) {
|
|
10
|
+
this.input = input;
|
|
11
|
+
this.output = output;
|
|
12
|
+
this.buffer = Buffer.alloc(0);
|
|
13
|
+
this.input.on("data", (chunk) => this.onData(chunk));
|
|
14
|
+
}
|
|
15
|
+
send(type, payload) {
|
|
16
|
+
const body = payload ?? Buffer.alloc(0);
|
|
17
|
+
const header = Buffer.alloc(Channel.HEADER_SIZE);
|
|
18
|
+
Channel.MAGIC.copy(header, 0);
|
|
19
|
+
header.writeUInt8(type, 4);
|
|
20
|
+
header.writeUInt32LE(body.length, 5);
|
|
21
|
+
this.output.write(Buffer.concat([header, body]));
|
|
22
|
+
}
|
|
23
|
+
sendJSON(type, msg) {
|
|
24
|
+
const json = Buffer.from(JSON.stringify(msg), "utf8");
|
|
25
|
+
this.send(type, json);
|
|
26
|
+
}
|
|
27
|
+
onData(chunk) {
|
|
28
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
29
|
+
while (true) {
|
|
30
|
+
if (this.buffer.length === 0)
|
|
31
|
+
return;
|
|
32
|
+
const idx = this.buffer.indexOf(Channel.MAGIC);
|
|
33
|
+
if (idx === -1) {
|
|
34
|
+
this.onPassthrough(this.buffer);
|
|
35
|
+
this.buffer = Buffer.alloc(0);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (idx > 0) {
|
|
39
|
+
this.onPassthrough(this.buffer.subarray(0, idx));
|
|
40
|
+
this.buffer = this.buffer.subarray(idx);
|
|
41
|
+
}
|
|
42
|
+
if (this.buffer.length < Channel.HEADER_SIZE)
|
|
43
|
+
return;
|
|
44
|
+
const type = this.buffer.readUInt8(4);
|
|
45
|
+
const length = this.buffer.readUInt32LE(5);
|
|
46
|
+
const frameSize = Channel.HEADER_SIZE + length;
|
|
47
|
+
if (this.buffer.length < frameSize)
|
|
48
|
+
return;
|
|
49
|
+
const payload = this.buffer.subarray(Channel.HEADER_SIZE, frameSize);
|
|
50
|
+
this.buffer = this.buffer.subarray(frameSize);
|
|
51
|
+
this.handleFrame(type, payload);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
handleFrame(type, payload) {
|
|
55
|
+
switch (type) {
|
|
56
|
+
case MessageType.OPEN:
|
|
57
|
+
this.onOpen();
|
|
58
|
+
break;
|
|
59
|
+
case MessageType.CLOSE:
|
|
60
|
+
this.onClose();
|
|
61
|
+
break;
|
|
62
|
+
case MessageType.CALL:
|
|
63
|
+
this.onCall(JSON.parse(payload.toString("utf8")));
|
|
64
|
+
break;
|
|
65
|
+
case MessageType.DATA:
|
|
66
|
+
this.onDataMessage(payload);
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
throw new Error(`Unknown frame type: ${type}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
onPassthrough(_data) { }
|
|
73
|
+
onOpen() { }
|
|
74
|
+
onClose() { }
|
|
75
|
+
onCall(_msg) { }
|
|
76
|
+
onDataMessage(_data) { }
|
|
77
|
+
}
|
|
78
|
+
Channel.MAGIC = Buffer.from("WIPC");
|
|
79
|
+
Channel.HEADER_SIZE = 9;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "as-test",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"author": "Jairus Tanaka",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"as-console": "^7.0.0",
|
|
12
12
|
"chalk": "^5.6.2",
|
|
13
13
|
"glob": "^13.0.3",
|
|
14
|
-
"typer-diff": "^1.1.1"
|
|
14
|
+
"typer-diff": "^1.1.1",
|
|
15
|
+
"wipc-js": "^0.1.1"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@assemblyscript/wasi-shim": "^0.1.0",
|
|
@@ -20,11 +21,12 @@
|
|
|
20
21
|
"as-sleep": "^0.0.2",
|
|
21
22
|
"as-test": "./",
|
|
22
23
|
"assemblyscript": "^0.28.9",
|
|
23
|
-
"assemblyscript-prettier": "^3.0.
|
|
24
|
-
"prettier": "3.
|
|
24
|
+
"assemblyscript-prettier": "^3.0.4",
|
|
25
|
+
"prettier": "3.8.1",
|
|
25
26
|
"try-as": "^0.2.5",
|
|
26
27
|
"typescript": "^5.9.3",
|
|
27
|
-
"typescript-eslint": "^8.55.0"
|
|
28
|
+
"typescript-eslint": "^8.55.0",
|
|
29
|
+
"vitepress": "^1.6.4"
|
|
28
30
|
},
|
|
29
31
|
"bin": {
|
|
30
32
|
"as-test": "./bin/index.js",
|
|
@@ -49,7 +51,7 @@
|
|
|
49
51
|
"as-test.config.schema.json",
|
|
50
52
|
"index.ts"
|
|
51
53
|
],
|
|
52
|
-
"homepage": "https://
|
|
54
|
+
"homepage": "https://docs.jairus.dev/as-test",
|
|
53
55
|
"keywords": [
|
|
54
56
|
"assemblyscript",
|
|
55
57
|
"testing",
|
|
@@ -63,16 +65,24 @@
|
|
|
63
65
|
},
|
|
64
66
|
"scripts": {
|
|
65
67
|
"test": "node ./bin/index.js test",
|
|
68
|
+
"fuzz": "node ./bin/index.js fuzz",
|
|
69
|
+
"test:ci": "node ./bin/index.js test --tap --config ./as-test.ci.config.json",
|
|
66
70
|
"test:examples": "npm --prefix ./examples run test",
|
|
71
|
+
"ci:act": "bash ./tools/act.sh push",
|
|
72
|
+
"ci:act:pr": "bash ./tools/act.sh pull_request",
|
|
73
|
+
"ci:act:tests": "act push -W .github/workflows/as-test.yml",
|
|
74
|
+
"ci:act:examples": "act push -W .github/workflows/examples.yml",
|
|
67
75
|
"typecheck": "tsc -p cli --noEmit && tsc -p transform --noEmit",
|
|
68
76
|
"lint": "eslint transform/src/**/*.ts tools/**/*.js eslint.config.js",
|
|
69
77
|
"build:transform": "tsc -p ./transform",
|
|
70
78
|
"build:cli": "tsc -p cli",
|
|
71
79
|
"build:run": "npm run build:cli",
|
|
72
|
-
"
|
|
80
|
+
"docs:dev": "vitepress dev docs",
|
|
81
|
+
"docs:build": "vitepress build docs",
|
|
82
|
+
"docs:preview": "vitepress preview docs",
|
|
83
|
+
"format": "prettier -w .",
|
|
73
84
|
"release:check": "npm run build:cli && npm run build:transform && npm run test && npm run test:examples && npm pack --dry-run --cache /tmp/as-test-npm-cache",
|
|
74
85
|
"prepublishOnly": "npm run build:cli && npm run build:transform && npm run test"
|
|
75
86
|
},
|
|
76
|
-
"type": "module"
|
|
77
|
-
"types": "assembly/index.ts"
|
|
87
|
+
"type": "module"
|
|
78
88
|
}
|