@matter/testing 0.11.0-alpha.0-20241027-de3c9d280
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/LICENSE +201 -0
- package/README.md +40 -0
- package/bin/test.js +7 -0
- package/chip/Dockerfile +23 -0
- package/dist/cjs/chip/pics-file.d.ts +20 -0
- package/dist/cjs/chip/pics-file.d.ts.map +1 -0
- package/dist/cjs/chip/pics-file.js +79 -0
- package/dist/cjs/chip/pics-file.js.map +6 -0
- package/dist/cjs/chip.d.ts +60 -0
- package/dist/cjs/chip.d.ts.map +1 -0
- package/dist/cjs/chip.js +306 -0
- package/dist/cjs/chip.js.map +6 -0
- package/dist/cjs/cli.d.ts +9 -0
- package/dist/cjs/cli.d.ts.map +1 -0
- package/dist/cjs/cli.js +115 -0
- package/dist/cjs/cli.js.map +6 -0
- package/dist/cjs/failure-detail.d.ts +19 -0
- package/dist/cjs/failure-detail.d.ts.map +1 -0
- package/dist/cjs/failure-detail.js +143 -0
- package/dist/cjs/failure-detail.js.map +6 -0
- package/dist/cjs/global-declarations.d.ts +38 -0
- package/dist/cjs/global-declarations.d.ts.map +1 -0
- package/dist/cjs/global-declarations.js +8 -0
- package/dist/cjs/global-declarations.js.map +6 -0
- package/dist/cjs/global-definitions.d.ts +7 -0
- package/dist/cjs/global-definitions.d.ts.map +1 -0
- package/dist/cjs/global-definitions.js +50 -0
- package/dist/cjs/global-definitions.js.map +6 -0
- package/dist/cjs/index.d.ts +10 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +27 -0
- package/dist/cjs/index.js.map +6 -0
- package/dist/cjs/mocha.d.ts +36 -0
- package/dist/cjs/mocha.d.ts.map +1 -0
- package/dist/cjs/mocha.js +155 -0
- package/dist/cjs/mocha.js.map +6 -0
- package/dist/cjs/mocharc.cjs +68 -0
- package/dist/cjs/mocharc.d.cts +7 -0
- package/dist/cjs/mocks/crypto.d.ts +7 -0
- package/dist/cjs/mocks/crypto.d.ts.map +1 -0
- package/dist/cjs/mocks/crypto.js +79 -0
- package/dist/cjs/mocks/crypto.js.map +6 -0
- package/dist/cjs/mocks/environment.d.ts +7 -0
- package/dist/cjs/mocks/environment.d.ts.map +1 -0
- package/dist/cjs/mocks/environment.js +7 -0
- package/dist/cjs/mocks/environment.js.map +6 -0
- package/dist/cjs/mocks/index.d.ts +10 -0
- package/dist/cjs/mocks/index.d.ts.map +1 -0
- package/dist/cjs/mocks/index.js +27 -0
- package/dist/cjs/mocks/index.js.map +6 -0
- package/dist/cjs/mocks/logging.d.ts +28 -0
- package/dist/cjs/mocks/logging.d.ts.map +1 -0
- package/dist/cjs/mocks/logging.js +93 -0
- package/dist/cjs/mocks/logging.js.map +6 -0
- package/dist/cjs/mocks/time.d.ts +88 -0
- package/dist/cjs/mocks/time.d.ts.map +1 -0
- package/dist/cjs/mocks/time.js +251 -0
- package/dist/cjs/mocks/time.js.map +6 -0
- package/dist/cjs/node.d.ts +9 -0
- package/dist/cjs/node.d.ts.map +1 -0
- package/dist/cjs/node.js +112 -0
- package/dist/cjs/node.js.map +6 -0
- package/dist/cjs/options.d.ts +19 -0
- package/dist/cjs/options.d.ts.map +1 -0
- package/dist/cjs/options.js +62 -0
- package/dist/cjs/options.js.map +6 -0
- package/dist/cjs/package.json +6 -0
- package/dist/cjs/reporter.d.ts +50 -0
- package/dist/cjs/reporter.d.ts.map +1 -0
- package/dist/cjs/reporter.js +137 -0
- package/dist/cjs/reporter.js.map +6 -0
- package/dist/cjs/runner.d.ts +21 -0
- package/dist/cjs/runner.d.ts.map +1 -0
- package/dist/cjs/runner.js +113 -0
- package/dist/cjs/runner.js.map +6 -0
- package/dist/cjs/util/docker.d.ts +27 -0
- package/dist/cjs/util/docker.d.ts.map +1 -0
- package/dist/cjs/util/docker.js +163 -0
- package/dist/cjs/util/docker.js.map +6 -0
- package/dist/cjs/util/files.d.ts +7 -0
- package/dist/cjs/util/files.d.ts.map +1 -0
- package/dist/cjs/util/files.js +42 -0
- package/dist/cjs/util/files.js.map +6 -0
- package/dist/cjs/util/node-shims.d.ts +7 -0
- package/dist/cjs/util/node-shims.d.ts.map +1 -0
- package/dist/cjs/util/node-shims.js +12 -0
- package/dist/cjs/util/node-shims.js.map +6 -0
- package/dist/cjs/util/trace-unhandled.d.ts +9 -0
- package/dist/cjs/util/trace-unhandled.d.ts.map +1 -0
- package/dist/cjs/util/trace-unhandled.js +48 -0
- package/dist/cjs/util/trace-unhandled.js.map +6 -0
- package/dist/cjs/util/wtf.d.ts +10 -0
- package/dist/cjs/util/wtf.d.ts.map +1 -0
- package/dist/cjs/util/wtf.js +53 -0
- package/dist/cjs/util/wtf.js.map +6 -0
- package/dist/cjs/web.d.ts +8 -0
- package/dist/cjs/web.d.ts.map +1 -0
- package/dist/cjs/web.js +165 -0
- package/dist/cjs/web.js.map +6 -0
- package/dist/esm/chip/pics-file.d.ts +20 -0
- package/dist/esm/chip/pics-file.d.ts.map +1 -0
- package/dist/esm/chip/pics-file.js +59 -0
- package/dist/esm/chip/pics-file.js.map +6 -0
- package/dist/esm/chip.d.ts +60 -0
- package/dist/esm/chip.d.ts.map +1 -0
- package/dist/esm/chip.js +286 -0
- package/dist/esm/chip.js.map +6 -0
- package/dist/esm/cli.d.ts +9 -0
- package/dist/esm/cli.d.ts.map +1 -0
- package/dist/esm/cli.js +85 -0
- package/dist/esm/cli.js.map +6 -0
- package/dist/esm/failure-detail.d.ts +19 -0
- package/dist/esm/failure-detail.d.ts.map +1 -0
- package/dist/esm/failure-detail.js +113 -0
- package/dist/esm/failure-detail.js.map +6 -0
- package/dist/esm/global-declarations.d.ts +38 -0
- package/dist/esm/global-declarations.d.ts.map +1 -0
- package/dist/esm/global-declarations.js +7 -0
- package/dist/esm/global-declarations.js.map +6 -0
- package/dist/esm/global-definitions.d.ts +7 -0
- package/dist/esm/global-definitions.d.ts.map +1 -0
- package/dist/esm/global-definitions.js +27 -0
- package/dist/esm/global-definitions.js.map +6 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +6 -0
- package/dist/esm/mocha.d.ts +36 -0
- package/dist/esm/mocha.d.ts.map +1 -0
- package/dist/esm/mocha.js +135 -0
- package/dist/esm/mocha.js.map +6 -0
- package/dist/esm/mocharc.cjs +68 -0
- package/dist/esm/mocharc.d.cts +7 -0
- package/dist/esm/mocks/crypto.d.ts +7 -0
- package/dist/esm/mocks/crypto.d.ts.map +1 -0
- package/dist/esm/mocks/crypto.js +59 -0
- package/dist/esm/mocks/crypto.js.map +6 -0
- package/dist/esm/mocks/environment.d.ts +7 -0
- package/dist/esm/mocks/environment.d.ts.map +1 -0
- package/dist/esm/mocks/environment.js +6 -0
- package/dist/esm/mocks/environment.js.map +6 -0
- package/dist/esm/mocks/index.d.ts +10 -0
- package/dist/esm/mocks/index.d.ts.map +1 -0
- package/dist/esm/mocks/index.js +10 -0
- package/dist/esm/mocks/index.js.map +6 -0
- package/dist/esm/mocks/logging.d.ts +28 -0
- package/dist/esm/mocks/logging.d.ts.map +1 -0
- package/dist/esm/mocks/logging.js +63 -0
- package/dist/esm/mocks/logging.js.map +6 -0
- package/dist/esm/mocks/time.d.ts +88 -0
- package/dist/esm/mocks/time.d.ts.map +1 -0
- package/dist/esm/mocks/time.js +231 -0
- package/dist/esm/mocks/time.js.map +6 -0
- package/dist/esm/node.d.ts +9 -0
- package/dist/esm/node.d.ts.map +1 -0
- package/dist/esm/node.js +82 -0
- package/dist/esm/node.js.map +6 -0
- package/dist/esm/options.d.ts +19 -0
- package/dist/esm/options.d.ts.map +1 -0
- package/dist/esm/options.js +42 -0
- package/dist/esm/options.js.map +6 -0
- package/dist/esm/package.json +6 -0
- package/dist/esm/reporter.d.ts +50 -0
- package/dist/esm/reporter.d.ts.map +1 -0
- package/dist/esm/reporter.js +107 -0
- package/dist/esm/reporter.js.map +6 -0
- package/dist/esm/runner.d.ts +21 -0
- package/dist/esm/runner.d.ts.map +1 -0
- package/dist/esm/runner.js +83 -0
- package/dist/esm/runner.js.map +6 -0
- package/dist/esm/util/docker.d.ts +27 -0
- package/dist/esm/util/docker.d.ts.map +1 -0
- package/dist/esm/util/docker.js +133 -0
- package/dist/esm/util/docker.js.map +6 -0
- package/dist/esm/util/files.d.ts +7 -0
- package/dist/esm/util/files.d.ts.map +1 -0
- package/dist/esm/util/files.js +22 -0
- package/dist/esm/util/files.js.map +6 -0
- package/dist/esm/util/node-shims.d.ts +7 -0
- package/dist/esm/util/node-shims.d.ts.map +1 -0
- package/dist/esm/util/node-shims.js +11 -0
- package/dist/esm/util/node-shims.js.map +6 -0
- package/dist/esm/util/trace-unhandled.d.ts +9 -0
- package/dist/esm/util/trace-unhandled.d.ts.map +1 -0
- package/dist/esm/util/trace-unhandled.js +18 -0
- package/dist/esm/util/trace-unhandled.js.map +6 -0
- package/dist/esm/util/wtf.d.ts +10 -0
- package/dist/esm/util/wtf.d.ts.map +1 -0
- package/dist/esm/util/wtf.js +23 -0
- package/dist/esm/util/wtf.js.map +6 -0
- package/dist/esm/web.d.ts +8 -0
- package/dist/esm/web.d.ts.map +1 -0
- package/dist/esm/web.js +135 -0
- package/dist/esm/web.js.map +6 -0
- package/package.json +72 -0
- package/src/chip/pics-file.ts +71 -0
- package/src/chip/pics.properties +52 -0
- package/src/chip.ts +420 -0
- package/src/cli.ts +122 -0
- package/src/failure-detail.ts +126 -0
- package/src/global-declarations.ts +50 -0
- package/src/global-definitions.ts +35 -0
- package/src/index.html +16 -0
- package/src/index.ts +10 -0
- package/src/mocha.ts +162 -0
- package/src/mocharc.cjs +68 -0
- package/src/mocharc.d.cts +7 -0
- package/src/mocks/crypto.ts +70 -0
- package/src/mocks/environment.ts +7 -0
- package/src/mocks/index.ts +10 -0
- package/src/mocks/logging.ts +99 -0
- package/src/mocks/time.ts +295 -0
- package/src/node.ts +106 -0
- package/src/options.ts +58 -0
- package/src/reporter.ts +144 -0
- package/src/runner.ts +92 -0
- package/src/tsconfig.json +12 -0
- package/src/util/docker.ts +176 -0
- package/src/util/files.ts +25 -0
- package/src/util/node-shims.ts +13 -0
- package/src/util/trace-unhandled.ts +17 -0
- package/src/util/wtf.ts +25 -0
- package/src/web.ts +174 -0
- package/test/mocks/MockTimeTest.ts +113 -0
- package/test/tsconfig.json +18 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readdir } from "fs/promises";
|
|
8
|
+
import { Writable } from "stream";
|
|
9
|
+
|
|
10
|
+
import Dockerode from "dockerode";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A high-level docker control API specialized for our purposes.
|
|
14
|
+
*/
|
|
15
|
+
export class Docker {
|
|
16
|
+
#intf = new Dockerode();
|
|
17
|
+
|
|
18
|
+
async *run(imageName: string, options?: Docker.RunOptions) {
|
|
19
|
+
const { args, createOptions } = configureRun(options);
|
|
20
|
+
|
|
21
|
+
let resolve: undefined | ((text?: string) => void);
|
|
22
|
+
let reject: undefined | ((error: Error) => void);
|
|
23
|
+
let signal: undefined | Promise<string | undefined>;
|
|
24
|
+
|
|
25
|
+
function newSignal() {
|
|
26
|
+
signal = new Promise((newResolve, newReject) => {
|
|
27
|
+
resolve = text => {
|
|
28
|
+
newResolve(text);
|
|
29
|
+
newSignal();
|
|
30
|
+
};
|
|
31
|
+
reject = newReject;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
newSignal();
|
|
36
|
+
|
|
37
|
+
const output = new Writable();
|
|
38
|
+
output._write = (chunk, _encoding, done) => {
|
|
39
|
+
resolve!(chunk.toString("utf-8"));
|
|
40
|
+
newSignal();
|
|
41
|
+
done();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.#intf.run(imageName, args, output, createOptions).then(
|
|
45
|
+
result => {
|
|
46
|
+
const statusCode = result?.[0]?.StatusCode;
|
|
47
|
+
if (typeof statusCode !== "number") {
|
|
48
|
+
throw new Error(`Process exit status "${statusCode}" is non-numeric`);
|
|
49
|
+
}
|
|
50
|
+
if (statusCode === 0) {
|
|
51
|
+
resolve!(undefined);
|
|
52
|
+
} else {
|
|
53
|
+
reject!(new Error(`Process exited with error status "${statusCode}"`));
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
error => reject!(error),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
while (true) {
|
|
60
|
+
const value = await signal;
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
yield value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async readFileFromImage(imageName: string, pathInImage: string) {
|
|
69
|
+
const output = Array<string>();
|
|
70
|
+
|
|
71
|
+
for await (const chunk of this.run(imageName, {
|
|
72
|
+
entrypoint: "/usr/bin/cat",
|
|
73
|
+
args: [pathInImage],
|
|
74
|
+
})) {
|
|
75
|
+
output.push(chunk);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return output.join("").replace(/\r\n/g, "\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async resolveGlobFromImage(imageName: string, glob: string) {
|
|
82
|
+
const output = Array<string>();
|
|
83
|
+
|
|
84
|
+
for await (const chunk of this.run(imageName, {
|
|
85
|
+
entrypoint: ["/bin/bash", "-c"],
|
|
86
|
+
args: `ls ${glob}`,
|
|
87
|
+
})) {
|
|
88
|
+
output.push(chunk);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return output
|
|
92
|
+
.join("")
|
|
93
|
+
.split(/\r?\n/)
|
|
94
|
+
.filter(line => line !== "");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async buildImage(name: string, path: string) {
|
|
98
|
+
const files = await readdir(path);
|
|
99
|
+
|
|
100
|
+
const stream = await this.#intf.buildImage(
|
|
101
|
+
{
|
|
102
|
+
context: path,
|
|
103
|
+
src: files,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
t: name,
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
await new Promise<void>((resolve, reject) => {
|
|
111
|
+
this.#intf.modem.followProgress(stream, (error, result) => {
|
|
112
|
+
if (error) {
|
|
113
|
+
reject(error);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const finalMessage = result[result.length - 1];
|
|
117
|
+
const errorMessage = finalMessage?.error ?? finalMessage?.errorDetail?.message;
|
|
118
|
+
if (errorMessage) {
|
|
119
|
+
reject(new Error(errorMessage));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
resolve();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
namespace Docker {
|
|
129
|
+
export interface RunOptions {
|
|
130
|
+
entrypoint?: string | string[];
|
|
131
|
+
args?: string | string[];
|
|
132
|
+
env?: Record<string, string>;
|
|
133
|
+
privileged?: boolean;
|
|
134
|
+
binds?: Record<string, string>;
|
|
135
|
+
network?: "host";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function configureRun(options?: Docker.RunOptions) {
|
|
140
|
+
if (options === undefined) {
|
|
141
|
+
options = {};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let { args } = options;
|
|
145
|
+
if (args === undefined) {
|
|
146
|
+
args = [];
|
|
147
|
+
} else if (typeof args === "string") {
|
|
148
|
+
args = [args];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const createOptions = {
|
|
152
|
+
HostConfig: {
|
|
153
|
+
AutoRemove: true,
|
|
154
|
+
},
|
|
155
|
+
} as Dockerode.ContainerCreateOptions;
|
|
156
|
+
|
|
157
|
+
const { entrypoint, env, binds, network } = options ?? {};
|
|
158
|
+
|
|
159
|
+
if (entrypoint !== undefined) {
|
|
160
|
+
createOptions.Entrypoint = entrypoint;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (env) {
|
|
164
|
+
createOptions.Env = Object.entries(env).map(([k, v]) => `${k}=${v}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (binds) {
|
|
168
|
+
createOptions.HostConfig!.Binds = Object.entries(binds).map(([k, v]) => `${k}:${v}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (network) {
|
|
172
|
+
createOptions.HostConfig!.NetworkMode = network;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { args, createOptions };
|
|
176
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Package } from "#tools";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
|
|
10
|
+
export function listSupportFiles(format = "cjs") {
|
|
11
|
+
const files = Array<string>();
|
|
12
|
+
|
|
13
|
+
// Always load tooling code in ESM format as tooling globals load as ESM
|
|
14
|
+
const testing = Package.tools.findPackage("@matter/testing");
|
|
15
|
+
files.push(testing.resolve("dist/esm/global-definitions.js"));
|
|
16
|
+
files.push(testing.resolve("dist/esm/mocks/index.js"));
|
|
17
|
+
|
|
18
|
+
// Package code should load in the format being tested
|
|
19
|
+
const config = new Package().resolve(`build/${format}/test/test.config.js`);
|
|
20
|
+
if (existsSync(config)) {
|
|
21
|
+
files.push(config);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return files;
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import "mocha";
|
|
8
|
+
import { webcrypto } from "node:crypto";
|
|
9
|
+
|
|
10
|
+
// Required for Node < 19
|
|
11
|
+
if (globalThis.crypto === undefined) {
|
|
12
|
+
Object.assign(globalThis, { crypto: webcrypto });
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const traceUnhandled = {
|
|
8
|
+
async initialize() {
|
|
9
|
+
// We process args and environment manually so we don't need dependencies and can install as early as possible
|
|
10
|
+
if (!process.argv.includes("--trace-unhandled") && !process.env.MATTER_TRACE_UNHANDLED) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { register } = await import("trace-unhandled");
|
|
15
|
+
register();
|
|
16
|
+
},
|
|
17
|
+
};
|
package/src/util/wtf.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const wtf = {
|
|
8
|
+
async initialize() {
|
|
9
|
+
// This will only work under actual Node.js
|
|
10
|
+
if (typeof process !== "object" || process?.release?.name !== "node") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// We process args and environment manually so we don't need dependencies and can install wtfnode as early as
|
|
15
|
+
// possible
|
|
16
|
+
if (!process.argv.includes("--wtf") && !process.env.MATTER_WTF) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { dump } = await import("wtfnode");
|
|
21
|
+
this.dump = () => dump();
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
dump() {},
|
|
25
|
+
};
|
package/src/web.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Package } from "#tools";
|
|
8
|
+
import { build } from "esbuild";
|
|
9
|
+
import express from "express";
|
|
10
|
+
import { writeFile } from "fs/promises";
|
|
11
|
+
import http from "http";
|
|
12
|
+
import { AddressInfo } from "net";
|
|
13
|
+
import { relative } from "path";
|
|
14
|
+
import { Browser, chromium, ConsoleMessage, Page } from "playwright";
|
|
15
|
+
import { TestOptions } from "./options.js";
|
|
16
|
+
import { ConsoleProxyReporter, Reporter } from "./reporter.js";
|
|
17
|
+
import type { TestRunner } from "./runner.js";
|
|
18
|
+
|
|
19
|
+
export async function testWeb(runner: TestRunner, manual: boolean) {
|
|
20
|
+
const files = await runner.loadFiles("esm");
|
|
21
|
+
const bundlePath = await bundle(files, runner.pkg);
|
|
22
|
+
|
|
23
|
+
const server = await new Promise<http.Server>((resolve, reject) => {
|
|
24
|
+
try {
|
|
25
|
+
const server = express()
|
|
26
|
+
.use(express.static(Package.workspace.resolve("node_modules")))
|
|
27
|
+
.use(express.static(Package.workspace.path))
|
|
28
|
+
.get("/", (_, res) => res.send(buildIndex(bundlePath)))
|
|
29
|
+
.listen(0, "localhost", () => resolve(server));
|
|
30
|
+
} catch (e) {
|
|
31
|
+
reject(e);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const addr = server.address() as AddressInfo;
|
|
36
|
+
let ip = addr.address;
|
|
37
|
+
if (ip.indexOf(":") !== -1) {
|
|
38
|
+
ip = `[${ip}]`;
|
|
39
|
+
}
|
|
40
|
+
const url = `http://${ip}:${addr.port}/`;
|
|
41
|
+
|
|
42
|
+
await new Promise<void>((resolve, reject) => {
|
|
43
|
+
server.on("error", reject);
|
|
44
|
+
server.on("close", resolve);
|
|
45
|
+
|
|
46
|
+
if (manual) {
|
|
47
|
+
console.log(`Run tests manually at ${url}`);
|
|
48
|
+
} else {
|
|
49
|
+
testInBrowser(url, runner.reporter, runner.options)
|
|
50
|
+
.then(() => {
|
|
51
|
+
server.close(() => {
|
|
52
|
+
// Hmm the close event above doesn't fire
|
|
53
|
+
resolve();
|
|
54
|
+
});
|
|
55
|
+
})
|
|
56
|
+
.catch(error => {
|
|
57
|
+
reject(error);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function testInBrowser(url: string, reporter: Reporter, options: TestOptions) {
|
|
64
|
+
async function setup() {
|
|
65
|
+
const browser = await chromium.launch();
|
|
66
|
+
const page = await browser.newPage();
|
|
67
|
+
return { browser, page };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function run({ browser, page }: { browser: Browser; page: Page }) {
|
|
71
|
+
await page.goto(url);
|
|
72
|
+
await page.evaluate(options => (globalThis as any).MatterTest.auto(options), options);
|
|
73
|
+
await browser.close();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await new Promise<void>((resolve, reject) => {
|
|
77
|
+
setup()
|
|
78
|
+
.then(cx => {
|
|
79
|
+
cx.page.on("console", consoleHandler(reporter));
|
|
80
|
+
cx.page.on("pageerror", error => reject(error));
|
|
81
|
+
return cx;
|
|
82
|
+
})
|
|
83
|
+
.then(run)
|
|
84
|
+
.then(resolve)
|
|
85
|
+
.catch(reject);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function consoleHandler(reporter: Reporter) {
|
|
90
|
+
return (message: ConsoleMessage) => {
|
|
91
|
+
const type = message.type();
|
|
92
|
+
switch (type) {
|
|
93
|
+
case "log":
|
|
94
|
+
case "debug":
|
|
95
|
+
case "info":
|
|
96
|
+
case "error":
|
|
97
|
+
case "warn":
|
|
98
|
+
case "trace":
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
default:
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const text = message.text();
|
|
106
|
+
|
|
107
|
+
// If it's not an encoded reporter update, write to normal console
|
|
108
|
+
if (type !== "log" || !text.startsWith(ConsoleProxyReporter.FLAG)) {
|
|
109
|
+
console[type](text);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Invoke reporter
|
|
114
|
+
const args: string[] = JSON.parse(text.slice(ConsoleProxyReporter.FLAG.length));
|
|
115
|
+
const method = (reporter as any)[args[0]];
|
|
116
|
+
if (typeof method !== "function") {
|
|
117
|
+
throw new Error(`Invalid encoded reporter update method ${args[0]}`);
|
|
118
|
+
}
|
|
119
|
+
method.call(reporter, ...args.slice(1));
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function buildIndex(bundlePath: string) {
|
|
124
|
+
return `<!DOCTYPE html>
|
|
125
|
+
<html>
|
|
126
|
+
<head>
|
|
127
|
+
<title>Matter.js Web Testing</title>
|
|
128
|
+
<link rel="stylesheet" href="mocha/mocha.css">
|
|
129
|
+
</head>
|
|
130
|
+
<body>
|
|
131
|
+
<div id="mocha"><h1><a href="javascript:MatterTest.start()">run tests</a></h1></div>
|
|
132
|
+
<script src="mocha/mocha.js"></script>
|
|
133
|
+
<script src="${bundlePath}" type="module"></script>
|
|
134
|
+
</body>
|
|
135
|
+
</html>`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function bundle(files: string[], pkg: Package) {
|
|
139
|
+
const entrypointPath = pkg.resolve("build/esm/test-entrypoint.js");
|
|
140
|
+
const bundlePath = pkg.resolve("build/cjs/test-bundle.js");
|
|
141
|
+
|
|
142
|
+
const entrypoint = files
|
|
143
|
+
.map(path => {
|
|
144
|
+
path = relative(pkg.resolve("build/esm"), path).replace(/\\/g, "/");
|
|
145
|
+
if (!path.startsWith(".")) {
|
|
146
|
+
path = `./${path}`;
|
|
147
|
+
}
|
|
148
|
+
return `import ${JSON.stringify(path)}`;
|
|
149
|
+
})
|
|
150
|
+
.join("\n");
|
|
151
|
+
|
|
152
|
+
// I was unable to get esbuild to resolve the entrypoint using the "stdin" and "absWorkingDir" options. So we just
|
|
153
|
+
// write to disk
|
|
154
|
+
await writeFile(pkg.resolve("build/esm/test-entrypoint.js"), entrypoint);
|
|
155
|
+
|
|
156
|
+
await build({
|
|
157
|
+
entryPoints: [entrypointPath],
|
|
158
|
+
bundle: true,
|
|
159
|
+
format: "cjs",
|
|
160
|
+
outfile: bundlePath,
|
|
161
|
+
external: ["wtfnode"],
|
|
162
|
+
keepNames: true,
|
|
163
|
+
|
|
164
|
+
// This doesn't work...
|
|
165
|
+
// logOverride: {
|
|
166
|
+
// "direct-eval": "silent",
|
|
167
|
+
// },
|
|
168
|
+
|
|
169
|
+
// ...so we do this:
|
|
170
|
+
logLevel: "error",
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return pkg.workspace.relative(bundlePath);
|
|
174
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const FAKE_TIME = 36000000;
|
|
8
|
+
|
|
9
|
+
describe("MockTime", () => {
|
|
10
|
+
beforeEach(() => MockTime.reset(FAKE_TIME));
|
|
11
|
+
|
|
12
|
+
describe("now", () => {
|
|
13
|
+
it("returns the fake date", () => {
|
|
14
|
+
const result = MockTime.now();
|
|
15
|
+
|
|
16
|
+
expect(result.getTime()).equal(FAKE_TIME);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("nowMs", () => {
|
|
21
|
+
it("returns the fake time", () => {
|
|
22
|
+
const result = MockTime.nowMs();
|
|
23
|
+
|
|
24
|
+
expect(result).equal(FAKE_TIME);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("advanceTime", () => {
|
|
29
|
+
it("advances the time by the duration specified", async () => {
|
|
30
|
+
await MockTime.advance(45);
|
|
31
|
+
|
|
32
|
+
expect(MockTime.nowMs()).equal(FAKE_TIME + 45);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("getPeriodicTimer", () => {
|
|
37
|
+
it("returns a periodic timer that will call a callback periodically", async () => {
|
|
38
|
+
let firedTime;
|
|
39
|
+
|
|
40
|
+
const result = MockTime.getPeriodicTimer("Test periodic", 30, () => (firedTime = MockTime.nowMs()));
|
|
41
|
+
expect(result.isRunning).equal(false);
|
|
42
|
+
|
|
43
|
+
result.start();
|
|
44
|
+
|
|
45
|
+
expect(result.isRunning).equal(true);
|
|
46
|
+
expect(firedTime).equal(undefined);
|
|
47
|
+
|
|
48
|
+
await MockTime.advance(45);
|
|
49
|
+
|
|
50
|
+
expect(firedTime).equal(FAKE_TIME + 30);
|
|
51
|
+
|
|
52
|
+
await MockTime.advance(20);
|
|
53
|
+
|
|
54
|
+
expect(firedTime).equal(FAKE_TIME + 60);
|
|
55
|
+
|
|
56
|
+
expect(result.isRunning).equal(true);
|
|
57
|
+
|
|
58
|
+
result.stop();
|
|
59
|
+
expect(result.isRunning).equal(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("returns a periodic timer that can be stopped", async () => {
|
|
63
|
+
let firedTime;
|
|
64
|
+
|
|
65
|
+
const result = MockTime.getPeriodicTimer("Test periodic", 30, () => (firedTime = MockTime.nowMs()));
|
|
66
|
+
result.start();
|
|
67
|
+
result.stop();
|
|
68
|
+
|
|
69
|
+
expect(firedTime).equal(undefined);
|
|
70
|
+
|
|
71
|
+
await MockTime.advance(45);
|
|
72
|
+
|
|
73
|
+
expect(firedTime).equal(undefined);
|
|
74
|
+
expect(result.isRunning).equal(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("getTimer", () => {
|
|
79
|
+
it("returns a timer that will call a callback in the future", async () => {
|
|
80
|
+
let firedTime;
|
|
81
|
+
|
|
82
|
+
const result = MockTime.getTimer("Test", 30, () => (firedTime = MockTime.nowMs()));
|
|
83
|
+
expect(result.isRunning).equal(false);
|
|
84
|
+
result.start();
|
|
85
|
+
expect(result.isRunning).equal(true);
|
|
86
|
+
|
|
87
|
+
expect(firedTime).equal(undefined);
|
|
88
|
+
|
|
89
|
+
await MockTime.advance(45);
|
|
90
|
+
|
|
91
|
+
expect(firedTime).equal(FAKE_TIME + 30);
|
|
92
|
+
expect(result.isRunning).equal(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("returns a timer that can be stopped", async () => {
|
|
96
|
+
let firedTime;
|
|
97
|
+
|
|
98
|
+
const result = MockTime.getTimer("Test", 30, () => (firedTime = MockTime.nowMs()));
|
|
99
|
+
expect(result.isRunning).equal(false);
|
|
100
|
+
result.start();
|
|
101
|
+
expect(result.isRunning).equal(true);
|
|
102
|
+
result.stop();
|
|
103
|
+
expect(result.isRunning).equal(false);
|
|
104
|
+
|
|
105
|
+
expect(firedTime).equal(undefined);
|
|
106
|
+
|
|
107
|
+
await MockTime.advance(45);
|
|
108
|
+
|
|
109
|
+
expect(firedTime).equal(undefined);
|
|
110
|
+
expect(result.isRunning).equal(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|