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.
@@ -1,13 +1,18 @@
1
1
  import { quote } from "../util/json";
2
2
 
3
+ import { sendLog } from "../util/wipc";
4
+
3
5
  export class Log {
4
6
  public order: i32 = 0;
5
7
  public depth: i32 = 0;
8
+ public file: string = "unknown";
6
9
  public text: string;
7
10
  constructor(text: string) {
8
11
  this.text = text;
9
12
  }
10
- display(): void {}
13
+ display(): void {
14
+ sendLog(this.file, this.depth, this.text);
15
+ }
11
16
 
12
17
  serialize(): string {
13
18
  return (
@@ -12,6 +12,7 @@ export class Suite {
12
12
  public time: Time = new Time();
13
13
  public description: string;
14
14
  public depth: i32 = 0;
15
+ public snapshotCount: i32 = 0;
15
16
  public suites: Suite[] = [];
16
17
  public tests: Tests[] = [];
17
18
  public logs: Log[] = [];
@@ -43,6 +44,7 @@ export class Suite {
43
44
  log.order = this.order++;
44
45
  this.logs.push(log);
45
46
  log.depth = this.depth + 1;
47
+ log.file = this.file;
46
48
  log.display();
47
49
  }
48
50
 
@@ -54,12 +56,19 @@ export class Suite {
54
56
  this.time.start = performance.now();
55
57
  sendSuiteStart(this.file, this.depth, this.kind, this.description);
56
58
  const isSkippedCase =
57
- this.kind == "xdescribe" || this.kind == "xtest" || this.kind == "xit";
59
+ this.kind == "xdescribe" ||
60
+ this.kind == "xtest" ||
61
+ this.kind == "xit" ||
62
+ this.kind == "xonly" ||
63
+ this.kind == "todo";
58
64
  const isTestCase =
59
65
  this.kind == "test" ||
60
66
  this.kind == "it" ||
67
+ this.kind == "only" ||
61
68
  this.kind == "xtest" ||
62
- this.kind == "xit";
69
+ this.kind == "xit" ||
70
+ this.kind == "xonly" ||
71
+ this.kind == "todo";
63
72
 
64
73
  if (isSkippedCase) {
65
74
  this.time.end = performance.now();
@@ -85,12 +94,18 @@ export class Suite {
85
94
  // @ts-ignore
86
95
  depth--;
87
96
 
97
+ const hasOnlyChildren = this.hasOnlyChildren();
98
+
88
99
  let hasFail = false;
89
100
  let hasOk = false;
90
101
  let hasSkip = false;
91
102
  for (let i = 0; i < this.suites.length; i++) {
92
103
  const suite = unchecked(this.suites[i]);
93
- suite.run();
104
+ if (hasOnlyChildren && suite.kind != "only") {
105
+ suite.skip();
106
+ } else {
107
+ suite.run();
108
+ }
94
109
  if (suite.verdict == "fail") {
95
110
  hasFail = true;
96
111
  } else if (suite.verdict == "ok") {
@@ -128,6 +143,33 @@ export class Suite {
128
143
  );
129
144
  }
130
145
 
146
+ skip(): void {
147
+ // @ts-ignore
148
+ current_suite = this;
149
+ // @ts-ignore
150
+ depth++;
151
+ this.time.start = performance.now();
152
+ this.time.end = this.time.start;
153
+ this.verdict = "skip";
154
+ sendSuiteStart(this.file, this.depth, this.kind, this.description);
155
+ // @ts-ignore
156
+ depth--;
157
+ sendSuiteEnd(
158
+ this.file,
159
+ this.depth,
160
+ this.kind,
161
+ this.description,
162
+ this.verdict,
163
+ );
164
+ }
165
+
166
+ private hasOnlyChildren(): bool {
167
+ for (let i = 0; i < this.suites.length; i++) {
168
+ if (unchecked(this.suites[i]).kind == "only") return true;
169
+ }
170
+ return false;
171
+ }
172
+
131
173
  serialize(): string {
132
174
  let out = "{";
133
175
  if (this.depth <= 0) {
@@ -31,6 +31,11 @@ export function stringifyValue<T>(value: T): string {
31
31
  return stringifyArray<valueof<T>>(value as valueof<T>[]);
32
32
  }
33
33
 
34
+ if (isManaged<T>()) {
35
+ // @ts-expect-error: method exists
36
+ return value.__as_test_json();
37
+ }
38
+
34
39
  const formatted = stringify<T>(value);
35
40
  if (formatted != "none") {
36
41
  return quote(formatted);
@@ -39,6 +44,10 @@ export function stringifyValue<T>(value: T): string {
39
44
  return quote(nameof<T>());
40
45
  }
41
46
 
47
+ export function __as_test_json_value<T>(value: T): string {
48
+ return stringifyValue<T>(value);
49
+ }
50
+
42
51
  function stringifyArray<T>(values: T[]): string {
43
52
  if (!values.length) return "[]";
44
53
 
@@ -59,6 +68,10 @@ function escape(value: string): string {
59
68
  out += '\\"';
60
69
  } else if (ch == 92) {
61
70
  out += "\\\\";
71
+ } else if (ch == 8) {
72
+ out += "\\b";
73
+ } else if (ch == 12) {
74
+ out += "\\f";
62
75
  } else if (ch == 10) {
63
76
  out += "\\n";
64
77
  } else if (ch == 13) {
@@ -66,13 +79,34 @@ function escape(value: string): string {
66
79
  } else if (ch == 9) {
67
80
  out += "\\t";
68
81
  } else if (ch < 32) {
69
- out += "\\u00";
70
- const hex = ch.toString(16);
71
- if (hex.length < 2) out += "0";
72
- out += hex;
82
+ out += unicodeEscape(ch);
83
+ } else if (ch >= 0xd800 && ch <= 0xdfff) {
84
+ if (ch <= 0xdbff && i + 1 < value.length) {
85
+ const next = value.charCodeAt(i + 1);
86
+ if (next >= 0xdc00 && next <= 0xdfff) {
87
+ out += value.charAt(i);
88
+ out += value.charAt(i + 1);
89
+ i++;
90
+ continue;
91
+ }
92
+ }
93
+ out += unicodeEscape(ch);
73
94
  } else {
74
95
  out += value.charAt(i);
75
96
  }
76
97
  }
77
98
  return out;
78
99
  }
100
+
101
+ function unicodeEscape(code: i32): string {
102
+ let out = "\\u";
103
+ out += hexNibble((code >> 12) & 0xf);
104
+ out += hexNibble((code >> 8) & 0xf);
105
+ out += hexNibble((code >> 4) & 0xf);
106
+ out += hexNibble(code & 0xf);
107
+ return out;
108
+ }
109
+
110
+ function hexNibble(value: i32): string {
111
+ return String.fromCharCode(value < 10 ? 48 + value : 87 + value);
112
+ }
@@ -1,3 +1,5 @@
1
+ import { quote as q } from "./json";
2
+
1
3
  // @ts-ignore
2
4
  @external("env", "process.stdout.write")
3
5
  declare function process_stdout_write(data: ArrayBuffer): void;
@@ -47,6 +49,11 @@ export class SnapshotReply {
47
49
  public expected: string = "";
48
50
  }
49
51
 
52
+ export class FuzzConfigReply {
53
+ public runs: i32 = 1000;
54
+ public seed: u64 = 1337;
55
+ }
56
+
50
57
  export function sendAssertionFailure(
51
58
  key: string,
52
59
  instr: string,
@@ -96,6 +103,13 @@ export function sendSuiteEnd(
96
103
  );
97
104
  }
98
105
 
106
+ export function sendLog(file: string, depth: i32, text: string): void {
107
+ sendJson(
108
+ MessageType.CALL,
109
+ `{"kind":"event:log","file":${q(file)},"depth":${depth.toString()},"text":${q(text)}}`,
110
+ );
111
+ }
112
+
99
113
  export function snapshotAssert(key: string, actual: string): SnapshotReply {
100
114
  sendJson(
101
115
  MessageType.CALL,
@@ -117,12 +131,32 @@ export function snapshotAssert(key: string, actual: string): SnapshotReply {
117
131
  return reply;
118
132
  }
119
133
 
134
+ export function requestFuzzConfig(): FuzzConfigReply {
135
+ sendJson(MessageType.CALL, `{"kind":"fuzz:config"}`);
136
+ const response = readFrame();
137
+ if (response == null || response.type != MessageType.CALL) {
138
+ return new FuzzConfigReply();
139
+ }
140
+ const body = String.UTF8.decode(response.payload);
141
+ if (!body.length) {
142
+ return new FuzzConfigReply();
143
+ }
144
+ const sep = body.indexOf("\n");
145
+ if (sep < 0) return new FuzzConfigReply();
146
+ const reply = new FuzzConfigReply();
147
+ const runs = body.slice(0, sep);
148
+ const seed = body.slice(sep + 1);
149
+ if (runs.length) reply.runs = I32.parseInt(runs);
150
+ if (seed.length) reply.seed = U64.parseInt(seed);
151
+ return reply;
152
+ }
153
+
120
154
  export function sendReport(report: string): void {
121
155
  sendFrame(MessageType.DATA, String.UTF8.encode(report));
122
156
  }
123
157
 
124
158
  export function sendWarning(message: string): void {
125
- writeStdout(String.UTF8.encode("[WARN] " + message + "\n"));
159
+ sendJson(MessageType.CALL, `{"kind":"event:warn","message":${q(message)}}`);
126
160
  }
127
161
 
128
162
  function sendJson(type: MessageType, body: string): void {
@@ -259,28 +293,3 @@ function wasiRead(max: i32): ArrayBuffer {
259
293
  memory.copy(changetype<usize>(partial), changetype<usize>(out), size);
260
294
  return partial;
261
295
  }
262
-
263
- function q(value: string): string {
264
- return '"' + escape(value) + '"';
265
- }
266
-
267
- function escape(value: string): string {
268
- let out = "";
269
- for (let i = 0; i < value.length; i++) {
270
- const ch = value.charCodeAt(i);
271
- if (ch == 34) {
272
- out += '\\"';
273
- } else if (ch == 92) {
274
- out += "\\\\";
275
- } else if (ch == 10) {
276
- out += "\\n";
277
- } else if (ch == 13) {
278
- out += "\\r";
279
- } else if (ch == 9) {
280
- out += "\\t";
281
- } else {
282
- out += String.fromCharCode(ch);
283
- }
284
- }
285
- return out;
286
- }
@@ -0,0 +1,149 @@
1
+ import { spawn } from "child_process";
2
+ import { fileURLToPath } from "url";
3
+ export class BuildWorkerPool {
4
+ constructor(size) {
5
+ this.pools = new Map();
6
+ this.nextId = 1;
7
+ this.closed = false;
8
+ this.size = Math.max(1, size);
9
+ }
10
+ buildFileMode(args) {
11
+ if (this.closed) {
12
+ return Promise.reject(new Error("build worker pool is closed"));
13
+ }
14
+ const featureToggles = args.featureToggles ?? {};
15
+ const overrides = args.overrides ?? {};
16
+ const signature = buildSignature(args.modeName, featureToggles, overrides);
17
+ const pool = this.getPool(signature);
18
+ return new Promise((resolve, reject) => {
19
+ pool.queue.push({
20
+ id: this.nextId++,
21
+ configPath: args.configPath,
22
+ file: args.file,
23
+ modeName: args.modeName,
24
+ featureToggles,
25
+ overrides,
26
+ resolve,
27
+ reject,
28
+ });
29
+ this.pump(pool);
30
+ });
31
+ }
32
+ async close() {
33
+ this.closed = true;
34
+ const waits = [];
35
+ for (const pool of this.pools.values()) {
36
+ while (pool.queue.length) {
37
+ const task = pool.queue.shift();
38
+ task.reject(new Error("build worker pool closed"));
39
+ }
40
+ for (const worker of pool.workers) {
41
+ waits.push(new Promise((resolve) => {
42
+ if (worker.child.exitCode != null || worker.child.killed) {
43
+ resolve();
44
+ return;
45
+ }
46
+ worker.child.once("exit", () => resolve());
47
+ worker.child.kill();
48
+ }));
49
+ }
50
+ }
51
+ await Promise.all(waits);
52
+ }
53
+ getPool(signature) {
54
+ let pool = this.pools.get(signature);
55
+ if (pool)
56
+ return pool;
57
+ pool = {
58
+ workers: Array.from({ length: this.size }, () => this.spawnWorker(signature)),
59
+ queue: [],
60
+ };
61
+ this.pools.set(signature, pool);
62
+ return pool;
63
+ }
64
+ spawnWorker(signature) {
65
+ const workerPath = fileURLToPath(new URL("./build-worker.js", import.meta.url));
66
+ const child = spawn(process.execPath, [workerPath], {
67
+ stdio: ["ignore", "ignore", "ignore", "ipc"],
68
+ });
69
+ const worker = {
70
+ child,
71
+ busy: false,
72
+ task: null,
73
+ };
74
+ child.on("message", (message) => {
75
+ this.onMessage(signature, worker, message);
76
+ });
77
+ child.on("exit", () => {
78
+ const pool = this.pools.get(signature);
79
+ const failedTask = worker.task;
80
+ worker.busy = false;
81
+ worker.task = null;
82
+ if (failedTask) {
83
+ failedTask.reject(new Error("build worker exited unexpectedly"));
84
+ }
85
+ if (!pool || this.closed)
86
+ return;
87
+ const index = pool.workers.indexOf(worker);
88
+ if (index >= 0) {
89
+ pool.workers[index] = this.spawnWorker(signature);
90
+ }
91
+ this.pump(pool);
92
+ });
93
+ return worker;
94
+ }
95
+ onMessage(signature, worker, message) {
96
+ const pool = this.pools.get(signature);
97
+ const task = worker.task;
98
+ if (!pool || !task || task.id !== message.id)
99
+ return;
100
+ worker.busy = false;
101
+ worker.task = null;
102
+ if (message.type == "done") {
103
+ task.resolve();
104
+ }
105
+ else {
106
+ task.reject(deserializeError(message.error));
107
+ }
108
+ this.pump(pool);
109
+ }
110
+ pump(pool) {
111
+ for (const worker of pool.workers) {
112
+ if (worker.busy)
113
+ continue;
114
+ const task = pool.queue.shift();
115
+ if (!task)
116
+ return;
117
+ worker.busy = true;
118
+ worker.task = task;
119
+ worker.child.send({
120
+ type: "build-file",
121
+ id: task.id,
122
+ configPath: task.configPath,
123
+ file: task.file,
124
+ modeName: task.modeName,
125
+ featureToggles: task.featureToggles,
126
+ overrides: task.overrides,
127
+ });
128
+ }
129
+ }
130
+ }
131
+ function buildSignature(modeName, featureToggles, overrides) {
132
+ return JSON.stringify({
133
+ modeName: modeName ?? "default",
134
+ featureToggles,
135
+ overrides,
136
+ });
137
+ }
138
+ function deserializeError(payload) {
139
+ const error = new Error(typeof payload.message == "string" ? payload.message : "unknown error");
140
+ error.name = typeof payload.name == "string" ? payload.name : "Error";
141
+ if (typeof payload.stack == "string")
142
+ error.stack = payload.stack;
143
+ for (const [key, value] of Object.entries(payload)) {
144
+ if (key == "name" || key == "message" || key == "stack")
145
+ continue;
146
+ error[key] = value;
147
+ }
148
+ return error;
149
+ }
@@ -0,0 +1,43 @@
1
+ import { build } from "./commands/build-core.js";
2
+ process.env.AS_TEST_BUILD_API = "1";
3
+ process.on("message", async (message) => {
4
+ if (!message || message.type != "build-file")
5
+ return;
6
+ try {
7
+ await build(message.configPath, [message.file], message.modeName, message.featureToggles, message.overrides);
8
+ send({
9
+ type: "done",
10
+ id: message.id,
11
+ });
12
+ }
13
+ catch (error) {
14
+ send({
15
+ type: "error",
16
+ id: message.id,
17
+ error: serializeError(error),
18
+ });
19
+ }
20
+ });
21
+ function send(message) {
22
+ if (!process.send)
23
+ return;
24
+ process.send(message);
25
+ }
26
+ function serializeError(error) {
27
+ if (!(error instanceof Error)) {
28
+ return {
29
+ name: "Error",
30
+ message: typeof error == "string" ? error : "unknown error",
31
+ };
32
+ }
33
+ const out = {
34
+ name: error.name,
35
+ message: error.message,
36
+ stack: error.stack,
37
+ };
38
+ const errorRecord = error;
39
+ for (const key of Object.keys(error)) {
40
+ out[key] = errorRecord[key];
41
+ }
42
+ return out;
43
+ }