as-test 1.0.16 → 1.1.1

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.
@@ -0,0 +1,104 @@
1
+ export function formatValue<T>(value: T, deep: boolean = false): string {
2
+ if (isNullable<T>() && changetype<usize>(value) == <usize>0) {
3
+ return "null";
4
+ }
5
+
6
+ if (isString<T>()) {
7
+ const text = value as string;
8
+ return deep ? "'" + text + "'" : text;
9
+ }
10
+
11
+ if (isBoolean<T>() || isInteger<T>() || isFloat<T>()) {
12
+ // @ts-expect-error: primitive formatting
13
+ return value.toString();
14
+ }
15
+
16
+ if (isArray<T>()) {
17
+ // @ts-expect-error: array-like handling
18
+ const values = value as valueof<T>[];
19
+ if (!values.length) return "[]";
20
+ let out = "[";
21
+ for (let i = 0; i < values.length; i++) {
22
+ if (i) out += ", ";
23
+ out += formatValue<valueof<T>>(unchecked(values[i]), true);
24
+ }
25
+ out += "]";
26
+ return out;
27
+ }
28
+
29
+ if (value instanceof Map) {
30
+ // @ts-expect-error: generic runtime access
31
+ const keys = value.keys();
32
+ if (!keys.length) return "Map(0) {}";
33
+ // @ts-expect-error: generic runtime access
34
+ const values = value.values();
35
+ let out = "Map(" + keys.length.toString() + ") { ";
36
+ for (let i = 0; i < keys.length; i++) {
37
+ if (i) out += ", ";
38
+ out += formatValue(changetype<valueof<typeof keys>>(unchecked(keys[i])), true);
39
+ out += " => ";
40
+ out += formatValue(
41
+ changetype<valueof<typeof values>>(unchecked(values[i])),
42
+ true,
43
+ );
44
+ }
45
+ out += " }";
46
+ return out;
47
+ }
48
+
49
+ if (value instanceof Set) {
50
+ // @ts-expect-error: generic runtime access
51
+ const values = value.values();
52
+ if (!values.length) return "Set(0) {}";
53
+ let out = "Set(" + values.length.toString() + ") { ";
54
+ for (let i = 0; i < values.length; i++) {
55
+ if (i) out += ", ";
56
+ out += formatValue(
57
+ changetype<valueof<typeof values>>(unchecked(values[i])),
58
+ true,
59
+ );
60
+ }
61
+ out += " }";
62
+ return out;
63
+ }
64
+
65
+ if (isManaged<T>()) {
66
+ // @ts-expect-error: custom serializer when provided
67
+ if (isDefined(value.__as_test_json)) {
68
+ // @ts-expect-error: dynamic method dispatch
69
+ return value.__as_test_json();
70
+ }
71
+ }
72
+
73
+ return nameof<T>();
74
+ }
75
+
76
+ @inline
77
+ export function colorText(format: i32[], text: string): string {
78
+ return `\u001b[${format[0].toString()}m${text}\u001b[${format[1].toString()}m`;
79
+ }
80
+
81
+ @inline
82
+ export function red(text: string): string {
83
+ return colorText([31, 39], text);
84
+ }
85
+
86
+ @inline
87
+ export function green(text: string): string {
88
+ return colorText([32, 39], text);
89
+ }
90
+
91
+ @inline
92
+ export function bgRed(text: string): string {
93
+ return colorText([41, 49], text);
94
+ }
95
+
96
+ @inline
97
+ export function bgGreen(text: string): string {
98
+ return colorText([42, 49], text);
99
+ }
100
+
101
+ @inline
102
+ export function bold(text: string): string {
103
+ return colorText([1, 22], text);
104
+ }
@@ -1,4 +1,4 @@
1
- import { rainbow } from "as-rainbow";
1
+ import { bgGreen, bgRed } from "./format";
2
2
 
3
3
  export function visualize<T>(value: T): string {
4
4
  if (isNullable<T>() && changetype<usize>(value) == <usize>0) {
@@ -61,16 +61,16 @@ export function diff(left: string, right: string, not: boolean = false): Diff {
61
61
  const rChar = right.charAt(i);
62
62
  if (not) {
63
63
  if (lChar == rChar) {
64
- lDiff += rainbow.bgGreen(rChar);
65
- rDiff += rainbow.bgRed(lChar);
64
+ lDiff += bgGreen(rChar);
65
+ rDiff += bgRed(lChar);
66
66
  } else {
67
67
  lDiff += rChar;
68
68
  rDiff += lChar;
69
69
  }
70
70
  } else {
71
71
  if (lChar != rChar) {
72
- lDiff += rainbow.bgGreen(rChar);
73
- rDiff += rainbow.bgRed(lChar);
72
+ lDiff += bgGreen(rChar);
73
+ rDiff += bgRed(lChar);
74
74
  } else {
75
75
  lDiff += rChar;
76
76
  rDiff += lChar;
@@ -80,9 +80,9 @@ export function diff(left: string, right: string, not: boolean = false): Diff {
80
80
 
81
81
  if (!not) {
82
82
  for (; i < left.length; i++) {
83
- rDiff += rainbow.bgRed(left.charAt(i));
83
+ rDiff += bgRed(left.charAt(i));
84
84
  }
85
- for (; i < right.length; i++) lDiff += rainbow.bgRed(right.charAt(i));
85
+ for (; i < right.length; i++) lDiff += bgRed(right.charAt(i));
86
86
  }
87
87
 
88
88
  return {
@@ -90,9 +90,3 @@ export function diff(left: string, right: string, not: boolean = false): Diff {
90
90
  right: rDiff,
91
91
  };
92
92
  }
93
-
94
- // @ts-ignore
95
- @inline
96
- export function colorText(format: i32[], text: string): string {
97
- return `\u001b[${format[0].toString()}m${text}\u001b[${format[1].toString()}m`;
98
- }
@@ -1,4 +1,4 @@
1
- import { stringify } from "as-console/stringify";
1
+ import { formatValue } from "./format";
2
2
 
3
3
  export function quote(value: string): string {
4
4
  return '"' + escape(value) + '"';
@@ -36,7 +36,7 @@ export function stringifyValue<T>(value: T): string {
36
36
  return value.__as_test_json();
37
37
  }
38
38
 
39
- const formatted = stringify<T>(value);
39
+ const formatted = formatValue<T>(value);
40
40
  if (formatted != "none") {
41
41
  return quote(formatted);
42
42
  }
@@ -34,6 +34,8 @@ const HEADER_SIZE: i32 = 9;
34
34
  const IOV_SIZE: usize = sizeof<usize>() * 2;
35
35
  const U32_SIZE: usize = sizeof<u32>();
36
36
  const REPORT_CHUNK_BYTES: i32 = 65536;
37
+ const WASI_ERRNO_AGAIN: i32 = 6;
38
+ const WASI_ERRNO_INTR: i32 = 27;
37
39
 
38
40
  // @ts-ignore
39
41
  const IS_BINDINGS: bool = isDefined(AS_TEST_BINDINGS);
@@ -303,9 +305,12 @@ function wasiWriteAll(data: ArrayBuffer): void {
303
305
  store<usize>(iovPtr, <usize>left, sizeof<usize>());
304
306
  store<u32>(writtenPtr, 0, 0);
305
307
  const errno = wasi_fd_write(1, iovPtr, 1, writtenPtr);
308
+ if (errno == WASI_ERRNO_AGAIN || errno == WASI_ERRNO_INTR) {
309
+ continue;
310
+ }
306
311
  if (errno != 0) return;
307
312
  const written = <i32>load<u32>(writtenPtr, 0);
308
- if (written <= 0) return;
313
+ if (written <= 0) continue;
309
314
  offset += written;
310
315
  }
311
316
  }
@@ -319,10 +324,15 @@ function wasiRead(max: i32): ArrayBuffer {
319
324
 
320
325
  store<usize>(iovPtr, changetype<usize>(out), 0);
321
326
  store<usize>(iovPtr, <usize>max, sizeof<usize>());
322
- store<u32>(readPtr, 0, 0);
323
-
324
- const errno = wasi_fd_read(0, iovPtr, 1, readPtr);
325
- if (errno != 0) return new ArrayBuffer(0);
327
+ while (true) {
328
+ store<u32>(readPtr, 0, 0);
329
+ const errno = wasi_fd_read(0, iovPtr, 1, readPtr);
330
+ if (errno == WASI_ERRNO_AGAIN || errno == WASI_ERRNO_INTR) {
331
+ continue;
332
+ }
333
+ if (errno != 0) return new ArrayBuffer(0);
334
+ break;
335
+ }
326
336
 
327
337
  const size = <i32>load<u32>(readPtr, 0);
328
338
  if (size <= 0) return new ArrayBuffer(0);
@@ -0,0 +1,135 @@
1
+ import chalk from "chalk";
2
+ import { existsSync, rmSync } from "fs";
3
+ import * as path from "path";
4
+ import { applyMode, loadConfig } from "../util.js";
5
+ const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
6
+ export async function clean(configPath = DEFAULT_CONFIG_PATH, modes = [undefined], fullClean = false) {
7
+ const loadedConfig = loadConfig(configPath, true);
8
+ const targets = new Map();
9
+ const ownership = buildOwnershipMap(loadedConfig);
10
+ if (fullClean) {
11
+ collectRootTarget(targets, loadedConfig.outDir, "build");
12
+ collectRootTarget(targets, loadedConfig.fuzz.crashDir, "crashes");
13
+ collectRootTarget(targets, loadedConfig.coverageDir, "coverage");
14
+ collectRootTarget(targets, loadedConfig.logs, "logs");
15
+ for (const modeName of modes) {
16
+ const active = applyMode(loadedConfig, modeName).config;
17
+ collectTarget(targets, active.outDir, modeName, "build");
18
+ collectTarget(targets, active.fuzz.crashDir, modeName, "crashes");
19
+ collectTarget(targets, active.coverageDir, modeName, "coverage");
20
+ collectTarget(targets, active.logs, modeName, "logs");
21
+ }
22
+ pruneNestedTargets(targets);
23
+ }
24
+ else {
25
+ for (const modeName of modes) {
26
+ const active = applyMode(loadedConfig, modeName).config;
27
+ collectTarget(targets, active.outDir, modeName, "build");
28
+ collectTarget(targets, active.fuzz.crashDir, modeName, "crashes");
29
+ collectTarget(targets, active.coverageDir, modeName, "coverage");
30
+ collectTarget(targets, active.logs, modeName, "logs");
31
+ }
32
+ }
33
+ let removed = 0;
34
+ for (const [targetPath, owners] of [...targets.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
35
+ if (!fullClean) {
36
+ const allOwners = ownership.get(targetPath) ?? owners;
37
+ const unselectedOwners = allOwners.filter((owner) => !owners.includes(owner));
38
+ if (unselectedOwners.length) {
39
+ continue;
40
+ }
41
+ }
42
+ if (!existsSync(targetPath)) {
43
+ continue;
44
+ }
45
+ rmSync(targetPath, { recursive: true, force: true });
46
+ removed++;
47
+ process.stdout.write(`${chalk.bgGreenBright.black(" CLEAN ")} ${toRelativePath(targetPath)} ${chalk.dim(`(${owners.join(", ")})`)}\n`);
48
+ }
49
+ process.stdout.write(`${chalk.bold("Summary:")} removed ${removed} path(s)\n`);
50
+ }
51
+ function buildOwnershipMap(loadedConfig) {
52
+ const ownership = new Map();
53
+ const modeNames = [
54
+ undefined,
55
+ ...Object.keys(loadedConfig.modes),
56
+ ];
57
+ for (const modeName of modeNames) {
58
+ const active = applyMode(loadedConfig, modeName).config;
59
+ collectOwnership(ownership, active.outDir, modeName, "build");
60
+ collectOwnership(ownership, active.fuzz.crashDir, modeName, "crashes");
61
+ collectOwnership(ownership, active.coverageDir, modeName, "coverage");
62
+ collectOwnership(ownership, active.logs, modeName, "logs");
63
+ }
64
+ return ownership;
65
+ }
66
+ function collectRootTarget(targets, rawPath, kind) {
67
+ if (!rawPath || rawPath == "none")
68
+ return;
69
+ const resolved = path.resolve(process.cwd(), rawPath);
70
+ ensureSafeCleanPath(resolved, rawPath, kind);
71
+ const owner = `all:${kind}`;
72
+ const existing = targets.get(resolved);
73
+ if (existing) {
74
+ if (!existing.includes(owner))
75
+ existing.push(owner);
76
+ return;
77
+ }
78
+ targets.set(resolved, [owner]);
79
+ }
80
+ function collectOwnership(ownership, rawPath, modeName, kind) {
81
+ if (!rawPath || rawPath == "none")
82
+ return;
83
+ const resolved = path.resolve(process.cwd(), rawPath);
84
+ ensureSafeCleanPath(resolved, rawPath, kind);
85
+ const owner = `${modeName ?? "default"}:${kind}`;
86
+ const existing = ownership.get(resolved);
87
+ if (existing) {
88
+ if (!existing.includes(owner))
89
+ existing.push(owner);
90
+ return;
91
+ }
92
+ ownership.set(resolved, [owner]);
93
+ }
94
+ function collectTarget(targets, rawPath, modeName, kind) {
95
+ if (!rawPath || rawPath == "none")
96
+ return;
97
+ const resolved = path.resolve(process.cwd(), rawPath);
98
+ ensureSafeCleanPath(resolved, rawPath, kind);
99
+ const owner = `${modeName ?? "default"}:${kind}`;
100
+ const existing = targets.get(resolved);
101
+ if (existing) {
102
+ if (!existing.includes(owner))
103
+ existing.push(owner);
104
+ return;
105
+ }
106
+ targets.set(resolved, [owner]);
107
+ }
108
+ function pruneNestedTargets(targets) {
109
+ const paths = [...targets.keys()].sort((a, b) => a.length - b.length);
110
+ for (const targetPath of paths) {
111
+ for (const otherPath of paths) {
112
+ if (targetPath == otherPath)
113
+ continue;
114
+ const relative = path.relative(targetPath, otherPath);
115
+ if (!relative.length || relative == ".." || relative.startsWith(`..${path.sep}`)) {
116
+ continue;
117
+ }
118
+ targets.delete(otherPath);
119
+ }
120
+ }
121
+ }
122
+ function ensureSafeCleanPath(resolvedPath, rawPath, kind) {
123
+ const cwd = path.resolve(process.cwd());
124
+ const relative = path.relative(cwd, resolvedPath);
125
+ if (!relative.length ||
126
+ relative == ".." ||
127
+ relative.startsWith(`..${path.sep}`) ||
128
+ path.parse(resolvedPath).root == resolvedPath) {
129
+ throw new Error(`refusing to clean unsafe ${kind} path "${rawPath}" (${resolvedPath})`);
130
+ }
131
+ }
132
+ function toRelativePath(targetPath) {
133
+ const relative = path.relative(process.cwd(), targetPath);
134
+ return relative.length ? relative : ".";
135
+ }
@@ -0,0 +1,51 @@
1
+ import chalk from "chalk";
2
+ import { createInterface } from "readline";
3
+ import { clean } from "./clean-core.js";
4
+ import { loadConfig } from "../util.js";
5
+ export { clean } from "./clean-core.js";
6
+ export async function executeCleanCommand(rawArgs, configPath, selectedModes, resolveExecutionModes) {
7
+ const force = rawArgs.includes("-f") || rawArgs.includes("--force");
8
+ const modeTargets = selectedModes.length > 0
9
+ ? resolveExecutionModes(configPath, selectedModes)
10
+ : resolveAllCleanModes(configPath);
11
+ if (!force && selectedModes.length == 0) {
12
+ await confirmFullClean(configPath);
13
+ }
14
+ await clean(configPath, modeTargets, selectedModes.length == 0);
15
+ }
16
+ function resolveAllCleanModes(configPath) {
17
+ const resolvedConfigPath = configPath ?? "./as-test.config.json";
18
+ const config = loadConfig(resolvedConfigPath, true);
19
+ return [undefined, ...Object.keys(config.modes)];
20
+ }
21
+ async function confirmFullClean(configPath) {
22
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
23
+ throw new Error('clean without --mode requires confirmation. Re-run with "-f" or "--force" to skip the prompt.');
24
+ }
25
+ const target = configPath ? ` in ${configPath}` : "";
26
+ process.stdout.write(chalk.bold.blue("◇ Confirm Clean") +
27
+ "\n" +
28
+ `│ This will remove configured build outputs, crash reports, and logs for every mode${target}.\n` +
29
+ "│\n");
30
+ const answer = await promptLine("Continue? [Y/n] ");
31
+ const normalized = answer.trim().toLowerCase();
32
+ if (normalized == "" || normalized == "y" || normalized == "yes")
33
+ return;
34
+ if (normalized == "n" || normalized == "no") {
35
+ process.stdout.write(chalk.dim("clean cancelled\n"));
36
+ process.exit(0);
37
+ }
38
+ throw new Error(`invalid answer "${answer}". Expected yes or no.`);
39
+ }
40
+ function promptLine(question) {
41
+ return new Promise((resolve) => {
42
+ const rl = createInterface({
43
+ input: process.stdin,
44
+ output: process.stdout,
45
+ });
46
+ rl.question(question, (answer) => {
47
+ rl.close();
48
+ resolve(answer);
49
+ });
50
+ });
51
+ }