@zukhruf/testing 0.3.23 → 0.3.24
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/README.md +21 -7
- package/dist/docker/docker.d.ts +2 -2
- package/dist/docker/docker.d.ts.map +1 -1
- package/dist/docker/docker.js +5 -4
- package/dist/docker/docker.js.map +1 -1
- package/dist/docker/host.d.ts +19 -6
- package/dist/docker/host.d.ts.map +1 -1
- package/dist/docker/host.js +73 -31
- package/dist/docker/host.js.map +1 -1
- package/dist/docker/test-run.d.ts +16 -8
- package/dist/docker/test-run.d.ts.map +1 -1
- package/dist/docker/test-run.js +21 -9
- package/dist/docker/test-run.js.map +1 -1
- package/dist/scripts/docker-global-setup.d.ts +4 -0
- package/dist/scripts/docker-global-setup.d.ts.map +1 -0
- package/dist/scripts/docker-global-setup.js +232 -0
- package/dist/scripts/docker-global-setup.js.map +1 -0
- package/package.json +8 -5
- package/dist/scripts/run-docker-tests.d.ts +0 -3
- package/dist/scripts/run-docker-tests.d.ts.map +0 -1
- package/dist/scripts/run-docker-tests.js +0 -227
- package/dist/scripts/run-docker-tests.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @zukhruf/testing
|
|
2
2
|
|
|
3
|
-
Disposable fixtures for integration tests on Node.js: Docker containers and database servers, SQLite and DuckDB databases, BigQuery datasets, HTTP servers and controlled streams. Each acquisition returns a handle that `await using` cleans up, also when the test fails. A background server has no handle: it lives until the process exits.
|
|
3
|
+
Disposable fixtures for integration tests on Node.js: Docker containers and database servers, SQLite and DuckDB databases, BigQuery datasets, HTTP servers and controlled streams. Each acquisition returns a handle that `await using` cleans up, also when the test fails. A background server has no handle: it lives until the process exits. Node's global setup and global teardown remove what a killed test run left behind.
|
|
4
4
|
|
|
5
5
|
The words in these documents have one meaning each. See the glossary in [CONTEXT.md](./CONTEXT.md).
|
|
6
6
|
|
|
@@ -118,19 +118,33 @@ docker stop <container-id>
|
|
|
118
118
|
|
|
119
119
|
They run with `--rm`, so stopping also removes them. The image tag is part of the configuration: stop the server to pick up a newer image behind the same tag.
|
|
120
120
|
|
|
121
|
-
##
|
|
121
|
+
## A killed run
|
|
122
122
|
|
|
123
|
-
Disposal cleans up after a test that ends. A test process that is killed (a timeout
|
|
123
|
+
Disposal cleans up after a test that ends. A test process that is killed (a timeout with `--test-force-exit`, Ctrl-C, an out-of-memory kill) does not dispose its handles. A running container does not stop by itself. Thus Node's global setup and global teardown make the test processes of one `node --test` invocation into one run. Re-export the two functions from one file in your project:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
// src/testing/docker-global-setup.ts
|
|
127
|
+
export {
|
|
128
|
+
globalSetup,
|
|
129
|
+
globalTeardown,
|
|
130
|
+
} from '@zukhruf/testing/docker-global-setup';
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Then give the file to `node --test`. The value is a path relative to the working directory. Node does not accept a package name there.
|
|
124
134
|
|
|
125
135
|
```sh
|
|
126
|
-
|
|
136
|
+
node --test --test-force-exit --test-global-setup=src/testing/docker-global-setup.ts "src/**/*.test.ts"
|
|
127
137
|
```
|
|
128
138
|
|
|
129
|
-
|
|
139
|
+
The global setup sets `ZUKHRUF_TESTING_RUN_ID` and `ZUKHRUF_TESTING_RUN_DIR` for the test processes. A `Docker` built with `TestRun.fromEnvironment(process.env)` labels what it creates with the run. It records SSH forwards and host directories in the run's directory. It also writes a process record for its test process, and for each Docker or SSH command that it starts, while that command runs.
|
|
140
|
+
|
|
141
|
+
The global teardown removes the labeled containers with their anonymous volumes, the labeled volumes and the recorded paths. It never removes shared servers. The teardown is synchronous, because Node does not wait for an asynchronous teardown under `--test-force-exit`. When every recorded process has ended, the teardown deletes the run's records. A recorded process that still runs can still create a container, for example a `docker run` that a killed test process started. Then the teardown keeps the records, and a later run removes what is left after that process ends.
|
|
142
|
+
|
|
143
|
+
If cleanup fails, for example because the engine is unreachable, the global teardown prints the error, keeps the run's records, and sets exit code 1. The next run on the same engine removes what the run left, from any project of this user. This is recovery. Recovery does not touch a run while its runner or one of its recorded processes lives. The records are in `$XDG_STATE_HOME/zukhruf-testing/docker-runs` (`~/.local/state/zukhruf-testing/docker-runs` by default). Do not delete a record to hide a cleanup failure.
|
|
130
144
|
|
|
131
|
-
|
|
145
|
+
The global setup reads the engine with `docker context inspect`, so the Docker CLI must be installed. It calls the engine only when a dead run on that engine has records of processes to sweep.
|
|
132
146
|
|
|
133
|
-
On an SSH engine
|
|
147
|
+
On an SSH engine, run one test file at a time with `--test-concurrency=1` if the engine cannot take more. An SSH forward runs `cat` in a remote shell, so the SSH user needs a shell on the engine's host. The forward ends when the process that opened it ends, also when that process is killed.
|
|
134
148
|
|
|
135
149
|
To pass the run to another container API, spread `docker.defaults`: it holds the default `resources` and the run's `labels`.
|
|
136
150
|
|
package/dist/docker/docker.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface ServiceOptions extends ContainerOptions {
|
|
|
34
34
|
healthy?: (container: ServiceContainer) => unknown;
|
|
35
35
|
}
|
|
36
36
|
export interface DockerOptions {
|
|
37
|
-
/** The
|
|
37
|
+
/** The run that owns what this process creates: `TestRun.fromEnvironment(process.env)`. */
|
|
38
38
|
testRun?: TestRun | undefined;
|
|
39
39
|
}
|
|
40
40
|
/** The engine the Docker CLI selects. Docker owns cross-process coordination; this instance holds no server cache. */
|
|
@@ -54,7 +54,7 @@ export declare class Docker {
|
|
|
54
54
|
endpoint: string;
|
|
55
55
|
}>;
|
|
56
56
|
directory(): Promise<DockerDirectory>;
|
|
57
|
-
/** Creates a named volume.
|
|
57
|
+
/** Creates a named volume. Its run's cleanup removes it if this process dies first. */
|
|
58
58
|
volume(): Promise<DockerVolume>;
|
|
59
59
|
readonly command: DockerCommand;
|
|
60
60
|
isAvailable(): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/docker/docker.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,SAAS,EACT,KAAK,aAAa,EAClB,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,+BAA+B,CAAC;AAGhE,yEAAyE;AACzE,MAAM,WAAW,KAAK;IACpB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,wGAAwG;IACxG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC5C,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACtD,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,gBAAgB,KAAK,OAAO,CAAC;CACpD;AAED,MAAM,WAAW,aAAa;IAC5B,
|
|
1
|
+
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/docker/docker.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,SAAS,EACT,KAAK,aAAa,EAClB,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,+BAA+B,CAAC;AAGhE,yEAAyE;AACzE,MAAM,WAAW,KAAK;IACpB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,wGAAwG;IACxG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC5C,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACtD,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,gBAAgB,KAAK,OAAO,CAAC;CACpD;AAED,MAAM,WAAW,aAAa;IAC5B,2FAA2F;IAC3F,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AA2BD,sHAAsH;AACtH,qBAAa,MAAM;;gBAcL,EAAE,OAAO,EAAE,GAAE,aAAkB;IAI3C,6EAA6E;IAC7E,IAAI,QAAQ,IAAI;QACd,SAAS,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,CAKA;IAEK,IAAI,IAAI,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAU3D,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC;IAI3C,uFAAuF;IACjF,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAYrC,QAAQ,CAAC,OAAO,EAAE,aAAa,CACY;IAErC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAarC,gGAAgG;IAC1F,KAAK,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC;IAK1D,gGAAgG;IAC1F,KAAK,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAe/D;;;;OAIG;IACG,KAAK,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAqB/D,2GAA2G;IACrG,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;CA6KtD"}
|
package/dist/docker/docker.js
CHANGED
|
@@ -27,8 +27,9 @@ const byKey = ([a], [b]) => {
|
|
|
27
27
|
/** The engine the Docker CLI selects. Docker owns cross-process coordination; this instance holds no server cache. */
|
|
28
28
|
export class Docker {
|
|
29
29
|
#testRun;
|
|
30
|
-
// The first resolution pins the engine: handles keep the engine they were
|
|
31
|
-
|
|
30
|
+
// The first resolution pins the engine: handles keep the engine they were
|
|
31
|
+
// made on. It records this process in the run before any command runs.
|
|
32
|
+
#connection = pMemoize(() => DockerHost.resolve(this.#testRun));
|
|
32
33
|
#probe = pMemoize(async () => {
|
|
33
34
|
try {
|
|
34
35
|
await this.command(['info']);
|
|
@@ -60,7 +61,7 @@ export class Docker {
|
|
|
60
61
|
async directory() {
|
|
61
62
|
return DockerDirectory.create(await this.#connection(), this.#testRun);
|
|
62
63
|
}
|
|
63
|
-
/** Creates a named volume.
|
|
64
|
+
/** Creates a named volume. Its run's cleanup removes it if this process dies first. */
|
|
64
65
|
async volume() {
|
|
65
66
|
const host = await this.#require();
|
|
66
67
|
const name = `zukhruf-testing-${randomUUID()}`;
|
|
@@ -242,7 +243,7 @@ export class Docker {
|
|
|
242
243
|
const port = /:(\d+)$/.exec(stdout.trim())?.[1];
|
|
243
244
|
if (!port)
|
|
244
245
|
throw new Error(`Failed to get mapped port for container ${id}: ${stdout}`);
|
|
245
|
-
const connection = await host.forward(Number.parseInt(port, 10)
|
|
246
|
+
const connection = await host.forward(Number.parseInt(port, 10));
|
|
246
247
|
const container = new ServiceContainer(id, connection.port, host.command, connection.close);
|
|
247
248
|
try {
|
|
248
249
|
await healthy?.(container);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../src/docker/docker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,QAAQ,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EACL,SAAS,EAET,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,sEAAsE;AACtE,MAAM,CAAC,MAAM,mBAAmB,GAAG,4BAA4B,CAAC;AAChE,MAAM,YAAY,GAAG,4BAA4B,CAAC;AA+ClD,MAAM,SAAS,GAAG,CAAC,MAA8B,EAAY,EAAE,CAC7D,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;IAC/C,SAAS;IACT,GAAG,GAAG,IAAI,KAAK,EAAE;CAClB,CAAC,CAAC;AAEL,MAAM,OAAO,GAAG,CAAC,YAAoB,EAAY,EAAE,CAAC;IAClD,IAAI;IACJ,cAAc,YAAY,EAAE;CAC7B,CAAC;AAEF,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAmB,EAAE,CAAC,CAAC,CAAmB,EAAU,EAAE;IACrE,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACpB,OAAO,CAAC,CAAC;AACX,CAAC,CAAC;AAEF,sHAAsH;AACtH,MAAM,OAAO,MAAM;IACR,QAAQ,CAAsB;IACvC,
|
|
1
|
+
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../src/docker/docker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,QAAQ,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EACL,SAAS,EAET,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,sEAAsE;AACtE,MAAM,CAAC,MAAM,mBAAmB,GAAG,4BAA4B,CAAC;AAChE,MAAM,YAAY,GAAG,4BAA4B,CAAC;AA+ClD,MAAM,SAAS,GAAG,CAAC,MAA8B,EAAY,EAAE,CAC7D,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;IAC/C,SAAS;IACT,GAAG,GAAG,IAAI,KAAK,EAAE;CAClB,CAAC,CAAC;AAEL,MAAM,OAAO,GAAG,CAAC,YAAoB,EAAY,EAAE,CAAC;IAClD,IAAI;IACJ,cAAc,YAAY,EAAE;CAC7B,CAAC;AAEF,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAmB,EAAE,CAAC,CAAC,CAAmB,EAAU,EAAE;IACrE,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACpB,OAAO,CAAC,CAAC;AACX,CAAC,CAAC;AAEF,sHAAsH;AACtH,MAAM,OAAO,MAAM;IACR,QAAQ,CAAsB;IACvC,0EAA0E;IAC1E,uEAAuE;IAC9D,WAAW,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChE,MAAM,GAAG,QAAQ,CAAC,KAAK,IAAoB,EAAE;QACpD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YAC7B,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACrC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,YAAY,EAAE,OAAO,KAAoB,EAAE;QACzC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,6EAA6E;IAC7E,IAAI,QAAQ;QAIV,OAAO;YACL,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE;YACpC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE;SACpC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QACtC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;YACpC,MAAM;YACN,UAAU;YACV,mBAAmB;SACpB,CAAC,CAAC;QACH,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,SAAS;QACb,OAAO,eAAe,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzE,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,mBAAmB,UAAU,EAAE,EAAE,CAAC;QAC/C,MAAM,IAAI,CAAC,OAAO,CAAC;YACjB,QAAQ;YACR,QAAQ;YACR,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAClC,IAAI;SACL,CAAC,CAAC;QACH,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IAEQ,OAAO,GAAkB,KAAK,EAAE,IAAI,EAAE,EAAE,CAC/C,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3C,KAAK,CAAC,WAAW;QACf,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK,CAAC,SAAS;YAClB,MAAM,IAAI,KAAK,CAAC,+CAA+C,EAAE;gBAC/D,KAAK,EAAE,KAAK,CAAC,KAAK;aACnB,CAAC,CAAC;QACL,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;IAED,gGAAgG;IAChG,KAAK,CAAC,KAAK,CAAC,OAAyB;QACnC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5E,CAAC;IAED,gGAAgG;IAChG,KAAK,CAAC,KAAK,CAAC,OAAuB;QACjC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAC3B,IAAI,EACJ,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,EAC1C,OAAO,CACR,CAAC;QACF,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACjD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,OAAuB;QACjC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,mBAAmB,WAAW,EAAE,CAAC;QAC9D,MAAM,SAAS,GACb,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACjC,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACzD,IACE,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,mBAAmB,CAAC,KAAK,GAAG;YACtD,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,WAAW,EACrD,CAAC;YACD,MAAM,IAAI,KAAK,CACb,aAAa,IAAI,+CAA+C,CACjE,CAAC;QACJ,CAAC;QACD,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACzC,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,2GAA2G;IAC3G,KAAK,CAAC,GAAG,CAAC,OAAyB;QACjC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,OAAO,CACX,IAAgB,EAChB,IAAY,EACZ,WAAmB,EACnB,OAAuB;QAEvB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC;gBACjB,QAAQ;gBACR,MAAM;gBACN,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC;gBAChC,GAAG,IAAI,CAAC,KAAK,CAAC;oBACZ,GAAG,OAAO;oBACV,IAAI;oBACJ,MAAM,EAAE;wBACN,GAAG,OAAO,CAAC,MAAM;wBACjB,CAAC,mBAAmB,CAAC,EAAE,GAAG;wBAC1B,CAAC,YAAY,CAAC,EAAE,WAAW;qBAC5B;iBACF,CAAC;gBACF,OAAO,CAAC,KAAK;gBACb,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;aAC3B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,CAAC,CAAC,KAAK,YAAY,eAAe,CAAC;gBACnC,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,gCAAgC,CAAC;gBAExD,MAAM,KAAK,CAAC;QAChB,CAAC;QACD,sEAAsE;QACtE,wEAAwE;QACxE,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,aAAa,IAAI,qBAAqB,CAAC,CAAC;QAClE,OAAO,OAAO,CACZ,KAAK,IAAI,EAAE;YACT,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAClD,IAAI,CAAC,SAAS;gBAAE,MAAM,OAAO,CAAC;YAC9B,OAAO,SAAS,CAAC;QACnB,CAAC,EACD,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,KAAK,OAAO,EAAE,CAClD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CACX,IAAgB,EAChB,KAAe,EACf,OAAyB;QAEzB,MAAM,EAAE,GAAG,CACT,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC,EAAE,OAAO,CAAC,CACjE,CAAC,IAAI,EAAE,CAAC;QACT,IAAI,CAAC,EAAE;YACL,MAAM,IAAI,KAAK,CAAC,oCAAoC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACvE,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,OAAO,CACX,IAAgB,EAChB,IAAc,EACd,OAAyB;QAEzB,MAAM,IAAI,GACR,OAAO,CAAC,IAAI;YACZ,QAAQ,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,IAAI,UAAU,EAAE,EAAE,CAAC;QAC3E,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9D,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;YACpC,GAAG,IAAI;YACP,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YAC3C,OAAO,CAAC,KAAK;YACb,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;SAC3B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,YAAY,CAAC,OAAuB;QAClC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QACpC,OAAO,UAAU,CAAC,QAAQ,CAAC;aACxB,MAAM,CACL,IAAI,CAAC,SAAS,CAAC;YACb,gBAAgB,EAAE,CAAC;YACnB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,MAAM;YAC1C,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI;YACpC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;YAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;YAClD,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;YACxD,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;YAC5B,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE;YAC1B,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;YACjC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;SAC3C,CAAC,CACH;aACA,MAAM,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,OAA4C;QAChD,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QACpC,OAAO;YACL,QAAQ;YACR,OAAO,CAAC,IAAI;YACZ,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;YAClC,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;gBAC7D,IAAI;gBACJ,GAAG,GAAG,IAAI,KAAK,EAAE;aAClB,CAAC;YACF,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;gBAClE,UAAU;gBACV,GAAG,MAAM,IAAI,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;aAC9C,CAAC;YACF,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC7D,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1C,GAAG,CAAC,OAAO,CAAC,gBAAgB,KAAK,SAAS;gBACxC,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,CAAC,uBAAuB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACxD,UAAU;YACV,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,MAAM;YAClC,QAAQ;YACR,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC;SACvC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,QAAQ,CACZ,IAAgB,EAChB,IAAY;QAEZ,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;YACtE,MAAM,CAAC,SAAS,CAAC,GAAyB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7D,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,KAAK,YAAY,eAAe;gBAChC,8BAA8B,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;gBAEjD,OAAO,SAAS,CAAC;YACnB,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CACV,IAAgB,EAChB,EAAU,EACV,EAAE,YAAY,EAAE,OAAO,EAAkB;QAEzC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC1E,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI;YACP,MAAM,IAAI,KAAK,CACb,2CAA2C,EAAE,KAAK,MAAM,EAAE,CAC3D,CAAC;QACJ,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QACjE,MAAM,SAAS,GAAG,IAAI,gBAAgB,CACpC,EAAE,EACF,UAAU,CAAC,IAAI,EACf,IAAI,CAAC,OAAO,EACZ,UAAU,CAAC,KAAK,CACjB,CAAC;QACF,IAAI,CAAC;YACH,MAAM,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;YAC3B,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,SAAS,CAAC,UAAU,EAAE,CAAC;YAC7B,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF"}
|
package/dist/docker/host.d.ts
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
+
import { type Result } from 'nano-spawn';
|
|
1
2
|
import { type TestRun } from './test-run.ts';
|
|
2
3
|
export declare const quote: (value: string) => string;
|
|
3
|
-
/**
|
|
4
|
+
/** How long one Docker CLI call may take before this process stops it. */
|
|
5
|
+
export declare const DOCKER_COMMAND_TIMEOUT = 240000;
|
|
6
|
+
/** How long one command in the SSH engine's remote shell may take. */
|
|
7
|
+
export declare const SHELL_TIMEOUT = 30000;
|
|
8
|
+
/** The environment that points the Docker CLI at `endpoint`, whatever context is selected. */
|
|
9
|
+
export declare const dockerEnvironment: (endpoint: string) => Record<string, string | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* An engine endpoint. Resolve it with the Docker CLI so its native
|
|
12
|
+
* context/environment precedence wins. With a run, each command it starts
|
|
13
|
+
* has a process record while it runs.
|
|
14
|
+
*/
|
|
4
15
|
export declare class DockerHost {
|
|
16
|
+
#private;
|
|
5
17
|
readonly endpoint: string;
|
|
6
|
-
|
|
7
|
-
|
|
18
|
+
constructor(endpoint: string, run: TestRun | undefined);
|
|
19
|
+
/** The engine the Docker CLI selects. A run gets this process's process record. */
|
|
20
|
+
static resolve(run: TestRun | undefined): Promise<DockerHost>;
|
|
8
21
|
get remote(): boolean;
|
|
9
|
-
readonly command: (args: string[]) =>
|
|
22
|
+
readonly command: (args: string[]) => Promise<Result>;
|
|
10
23
|
sshArgs(options?: string[]): string[];
|
|
11
|
-
shell(script: string, input?: string):
|
|
12
|
-
forward(port: number
|
|
24
|
+
shell(script: string, input?: string): Promise<Result>;
|
|
25
|
+
forward(port: number): Promise<{
|
|
13
26
|
port: number;
|
|
14
27
|
close: () => Promise<void>;
|
|
15
28
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/docker/host.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/docker/host.ts"],"names":[],"mappings":"AAKA,OAAgB,EACd,KAAK,MAAM,EAGZ,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,eAAe,CAAC;AAE5D,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,KAAG,MACC,CAAC;AAExC,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,SAAU,CAAC;AAC9C,sEAAsE;AACtE,eAAO,MAAM,aAAa,QAAS,CAAC;AAEpC,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,GAC5B,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAGlC,CAAC;AAKH;;;;GAIG;AACH,qBAAa,UAAU;;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAGd,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,SAAS;IAUtD,mFAAmF;WACtE,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IAYnE,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,QAAQ,CAAC,OAAO,GAAI,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,MAAM,CAAC,CAMhD;IAeJ,OAAO,CAAC,OAAO,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE;IAqBzC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAShD,OAAO,CACX,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;CAkJzD"}
|
package/dist/docker/host.js
CHANGED
|
@@ -2,38 +2,69 @@ import { once } from 'node:events';
|
|
|
2
2
|
import { mkdtempDisposable, stat } from 'node:fs/promises';
|
|
3
3
|
import { connect, createServer } from 'node:net';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import command, { SubprocessError } from 'nano-spawn';
|
|
5
|
+
import command, { SubprocessError, } from 'nano-spawn';
|
|
6
6
|
import { timebox } from "../async/timebox.js";
|
|
7
7
|
import { forwardPrefix } from "./test-run.js";
|
|
8
8
|
export const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
9
|
+
/** How long one Docker CLI call may take before this process stops it. */
|
|
10
|
+
export const DOCKER_COMMAND_TIMEOUT = 240_000;
|
|
11
|
+
/** How long one command in the SSH engine's remote shell may take. */
|
|
12
|
+
export const SHELL_TIMEOUT = 30_000;
|
|
13
|
+
/** The environment that points the Docker CLI at `endpoint`, whatever context is selected. */
|
|
14
|
+
export const dockerEnvironment = (endpoint) => ({
|
|
15
|
+
DOCKER_CONTEXT: undefined,
|
|
16
|
+
DOCKER_HOST: endpoint,
|
|
17
|
+
});
|
|
9
18
|
/** Local Unix sockets, Docker Desktop's named pipe on Windows, and SSH engines. A `tcp://` engine may be on another machine. */
|
|
10
19
|
const supportedEndpoints = ['unix://', 'npipe://', 'ssh://'];
|
|
11
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* An engine endpoint. Resolve it with the Docker CLI so its native
|
|
22
|
+
* context/environment precedence wins. With a run, each command it starts
|
|
23
|
+
* has a process record while it runs.
|
|
24
|
+
*/
|
|
12
25
|
export class DockerHost {
|
|
13
26
|
endpoint;
|
|
14
|
-
|
|
27
|
+
#run;
|
|
28
|
+
constructor(endpoint, run) {
|
|
29
|
+
if (!supportedEndpoints.some((scheme) => endpoint.startsWith(scheme))) {
|
|
30
|
+
throw new Error(`Unsupported Docker endpoint ${endpoint}; tests support ${supportedEndpoints.join(', ')}`);
|
|
31
|
+
}
|
|
15
32
|
this.endpoint = endpoint;
|
|
33
|
+
this.#run = run;
|
|
16
34
|
}
|
|
17
|
-
|
|
35
|
+
/** The engine the Docker CLI selects. A run gets this process's process record. */
|
|
36
|
+
static async resolve(run) {
|
|
18
37
|
const { stdout } = await command('docker', [
|
|
19
38
|
'context',
|
|
20
39
|
'inspect',
|
|
21
40
|
'--format',
|
|
22
41
|
'{{.Endpoints.docker.Host}}',
|
|
23
42
|
]);
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
return new DockerHost(endpoint);
|
|
43
|
+
const host = new DockerHost(stdout.trim(), run);
|
|
44
|
+
await run?.recordProcess(process.pid);
|
|
45
|
+
return host;
|
|
29
46
|
}
|
|
30
47
|
get remote() {
|
|
31
48
|
return this.endpoint.startsWith('ssh://');
|
|
32
49
|
}
|
|
33
|
-
command = (args) => command('docker', args, {
|
|
34
|
-
env:
|
|
35
|
-
timeout:
|
|
36
|
-
});
|
|
50
|
+
command = (args) => this.#recorded(command('docker', args, {
|
|
51
|
+
env: dockerEnvironment(this.endpoint),
|
|
52
|
+
timeout: DOCKER_COMMAND_TIMEOUT,
|
|
53
|
+
}));
|
|
54
|
+
/** A killed process leaves its commands running; their records keep its run until they end. */
|
|
55
|
+
async #recorded(subprocess) {
|
|
56
|
+
if (!this.#run)
|
|
57
|
+
return subprocess;
|
|
58
|
+
// Awaited below; a command that fails while its record is written must
|
|
59
|
+
// not count as an unhandled rejection.
|
|
60
|
+
subprocess.catch(() => { });
|
|
61
|
+
const { pid } = await subprocess.nodeChildProcess;
|
|
62
|
+
// Node leaves pid undefined when the spawn failed: no process to record.
|
|
63
|
+
if (pid === undefined)
|
|
64
|
+
return subprocess;
|
|
65
|
+
await using _record = await this.#run.recordProcess(pid);
|
|
66
|
+
return await subprocess;
|
|
67
|
+
}
|
|
37
68
|
sshArgs(options = []) {
|
|
38
69
|
const url = new URL(this.endpoint);
|
|
39
70
|
// Pass the original hostname to OpenSSH: ~/.ssh/config, jump hosts, keys,
|
|
@@ -55,12 +86,13 @@ export class DockerHost {
|
|
|
55
86
|
];
|
|
56
87
|
}
|
|
57
88
|
shell(script, input) {
|
|
58
|
-
return command('ssh', [...this.sshArgs(), script], {
|
|
89
|
+
return this.#recorded(command('ssh', [...this.sshArgs(), script], {
|
|
59
90
|
stdin: input === undefined ? 'ignore' : { string: input },
|
|
60
|
-
timeout:
|
|
61
|
-
});
|
|
91
|
+
timeout: SHELL_TIMEOUT,
|
|
92
|
+
}));
|
|
62
93
|
}
|
|
63
|
-
async forward(port
|
|
94
|
+
async forward(port) {
|
|
95
|
+
const run = this.#run;
|
|
64
96
|
if (!this.remote)
|
|
65
97
|
return { port, close: async () => { } };
|
|
66
98
|
const resources = new AsyncDisposableStack();
|
|
@@ -92,19 +124,26 @@ export class DockerHost {
|
|
|
92
124
|
// OpenSSH multiplexes client streams over one authenticated connection.
|
|
93
125
|
// Node owns the ephemeral TCP listener, so no port-reservation race or
|
|
94
126
|
// fresh SSH login is added to each database client's connect timeout.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
127
|
+
// A remote `cat` on a stdin pipe this process holds, not -N: when this
|
|
128
|
+
// process dies, even by SIGKILL, the OS closes the pipe, `cat` reads
|
|
129
|
+
// EOF and ssh exits instead of staying connected under PID 1. The
|
|
130
|
+
// command needs a remote shell, which the Docker CLI over ssh already
|
|
131
|
+
// uses to run `docker system dial-stdio`.
|
|
132
|
+
const ssh = command('ssh', [
|
|
133
|
+
...this.sshArgs([
|
|
134
|
+
'-S',
|
|
135
|
+
'none',
|
|
136
|
+
'-o',
|
|
137
|
+
'ControlMaster=no',
|
|
138
|
+
'-o',
|
|
139
|
+
'ControlPersist=no',
|
|
140
|
+
'-o',
|
|
141
|
+
'ExitOnForwardFailure=yes',
|
|
142
|
+
'-L',
|
|
143
|
+
`${path}:127.0.0.1:${port}`,
|
|
144
|
+
]),
|
|
145
|
+
'cat >/dev/null',
|
|
146
|
+
], { stdin: 'pipe', stdout: 'ignore' });
|
|
108
147
|
// How ssh ended, with what it printed: a Result for exit 0, or the
|
|
109
148
|
// SubprocessError for a failure or a signal.
|
|
110
149
|
const exit = ssh.then((result) => result, (error) => {
|
|
@@ -119,8 +158,11 @@ export class DockerHost {
|
|
|
119
158
|
server.close();
|
|
120
159
|
});
|
|
121
160
|
const child = await ssh.nodeChildProcess;
|
|
161
|
+
// Disposed after ssh has stopped.
|
|
162
|
+
if (run && child.pid !== undefined)
|
|
163
|
+
resources.use(await run.recordProcess(child.pid));
|
|
122
164
|
resources.defer(async () => {
|
|
123
|
-
child.
|
|
165
|
+
child.stdin.end();
|
|
124
166
|
const deadline = setTimeout(() => child.kill('SIGKILL'), 1_000);
|
|
125
167
|
try {
|
|
126
168
|
await exit;
|
package/dist/docker/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/docker/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAe,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/docker/host.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,26 +1,34 @@
|
|
|
1
|
-
/** Set by the
|
|
1
|
+
/** Set by the global setup for the run's test processes: the run's id. */
|
|
2
2
|
export declare const TEST_RUN_ID = "ZUKHRUF_TESTING_RUN_ID";
|
|
3
|
-
/** Set by the
|
|
3
|
+
/** Set by the global setup for the run's test processes: the run's records. */
|
|
4
4
|
export declare const TEST_RUN_DIRECTORY = "ZUKHRUF_TESTING_RUN_DIR";
|
|
5
5
|
/** The label that ties a container or volume to the run that created it. */
|
|
6
6
|
export declare const TEST_RUN_LABEL = "dev.zukhruf.testing.run";
|
|
7
7
|
/**
|
|
8
|
-
* One
|
|
9
|
-
* carries the run's label or a record in the run's
|
|
10
|
-
*
|
|
8
|
+
* One `node --test` invocation with the Docker global setup. What its test
|
|
9
|
+
* processes create carries the run's label or a record in the run's
|
|
10
|
+
* directory, so the global teardown, or the recovery of a later run, removes
|
|
11
|
+
* it after a process that was killed before it could.
|
|
11
12
|
*/
|
|
12
13
|
export declare class TestRun {
|
|
13
14
|
readonly id: string;
|
|
14
15
|
readonly directory: string;
|
|
15
16
|
constructor(id: string, directory: string);
|
|
16
17
|
/**
|
|
17
|
-
* The run the
|
|
18
|
-
* the host passes (`process.env`). Undefined outside a
|
|
18
|
+
* The run the global setup started this process in, read from the
|
|
19
|
+
* environment the host passes (`process.env`). Undefined outside a run.
|
|
19
20
|
*/
|
|
20
21
|
static fromEnvironment(environment: Readonly<Record<string, string | undefined>>): TestRun | undefined;
|
|
21
22
|
get labels(): Record<string, string>;
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
+
* Writes a process record for `pid`, a test process or a command it
|
|
25
|
+
* started. While a recorded process lives, the run's records stay.
|
|
26
|
+
* Disposing the record forgets a command that ended. The random id keeps
|
|
27
|
+
* a reused pid from deleting the record of the next command that has it.
|
|
28
|
+
*/
|
|
29
|
+
recordProcess(pid: number): Promise<AsyncDisposable>;
|
|
30
|
+
/**
|
|
31
|
+
* Records a path that the global teardown removes if this process dies first.
|
|
24
32
|
* Disposing the record forgets the path once the process removed it itself.
|
|
25
33
|
*/
|
|
26
34
|
record(kind: 'forward' | 'directory', path: string): Promise<AsyncDisposable>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-run.d.ts","sourceRoot":"","sources":["../../src/docker/test-run.ts"],"names":[],"mappings":"AAIA,
|
|
1
|
+
{"version":3,"file":"test-run.d.ts","sourceRoot":"","sources":["../../src/docker/test-run.ts"],"names":[],"mappings":"AAIA,0EAA0E;AAC1E,eAAO,MAAM,WAAW,2BAA2B,CAAC;AACpD,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,4BAA4B,CAAC;AAC5D,4EAA4E;AAC5E,eAAO,MAAM,cAAc,4BAA4B,CAAC;AAExD;;;;;GAKG;AACH,qBAAa,OAAO;IAClB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKzC;;;OAGG;IACH,MAAM,CAAC,eAAe,CACpB,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GACxD,OAAO,GAAG,SAAS;IAWtB,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEnC;IAED;;;;;OAKG;IACG,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAM1D;;;OAGG;IACG,MAAM,CACV,IAAI,EAAE,SAAS,GAAG,WAAW,EAC7B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC;CAK5B;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,GAAI,KAAK,OAAO,GAAG,SAAS,KAAG,MACZ,CAAC;AAEhD,2EAA2E;AAC3E,eAAO,MAAM,aAAa,GAAI,KAAK,OAAO,GAAG,SAAS,KAAG,MACG,CAAC"}
|
package/dist/docker/test-run.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
/** Set by the
|
|
4
|
+
/** Set by the global setup for the run's test processes: the run's id. */
|
|
5
5
|
export const TEST_RUN_ID = 'ZUKHRUF_TESTING_RUN_ID';
|
|
6
|
-
/** Set by the
|
|
6
|
+
/** Set by the global setup for the run's test processes: the run's records. */
|
|
7
7
|
export const TEST_RUN_DIRECTORY = 'ZUKHRUF_TESTING_RUN_DIR';
|
|
8
8
|
/** The label that ties a container or volume to the run that created it. */
|
|
9
9
|
export const TEST_RUN_LABEL = 'dev.zukhruf.testing.run';
|
|
10
10
|
/**
|
|
11
|
-
* One
|
|
12
|
-
* carries the run's label or a record in the run's
|
|
13
|
-
*
|
|
11
|
+
* One `node --test` invocation with the Docker global setup. What its test
|
|
12
|
+
* processes create carries the run's label or a record in the run's
|
|
13
|
+
* directory, so the global teardown, or the recovery of a later run, removes
|
|
14
|
+
* it after a process that was killed before it could.
|
|
14
15
|
*/
|
|
15
16
|
export class TestRun {
|
|
16
17
|
id;
|
|
@@ -20,8 +21,8 @@ export class TestRun {
|
|
|
20
21
|
this.directory = directory;
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
|
-
* The run the
|
|
24
|
-
* the host passes (`process.env`). Undefined outside a
|
|
24
|
+
* The run the global setup started this process in, read from the
|
|
25
|
+
* environment the host passes (`process.env`). Undefined outside a run.
|
|
25
26
|
*/
|
|
26
27
|
static fromEnvironment(environment) {
|
|
27
28
|
const id = environment[TEST_RUN_ID];
|
|
@@ -29,14 +30,25 @@ export class TestRun {
|
|
|
29
30
|
if (id === undefined && directory === undefined)
|
|
30
31
|
return undefined;
|
|
31
32
|
if (!id || !directory)
|
|
32
|
-
throw new Error(`The
|
|
33
|
+
throw new Error(`The global setup sets ${TEST_RUN_ID} and ${TEST_RUN_DIRECTORY} together, but only one of them is set`);
|
|
33
34
|
return new TestRun(id, directory);
|
|
34
35
|
}
|
|
35
36
|
get labels() {
|
|
36
37
|
return { [TEST_RUN_LABEL]: this.id };
|
|
37
38
|
}
|
|
38
39
|
/**
|
|
39
|
-
*
|
|
40
|
+
* Writes a process record for `pid`, a test process or a command it
|
|
41
|
+
* started. While a recorded process lives, the run's records stay.
|
|
42
|
+
* Disposing the record forgets a command that ended. The random id keeps
|
|
43
|
+
* a reused pid from deleting the record of the next command that has it.
|
|
44
|
+
*/
|
|
45
|
+
async recordProcess(pid) {
|
|
46
|
+
const file = join(this.directory, `process-${pid}-${randomUUID()}.json`);
|
|
47
|
+
await writeFile(file, JSON.stringify({ pid }));
|
|
48
|
+
return { [Symbol.asyncDispose]: () => rm(file, { force: true }) };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Records a path that the global teardown removes if this process dies first.
|
|
40
52
|
* Disposing the record forgets the path once the process removed it itself.
|
|
41
53
|
*/
|
|
42
54
|
async record(kind, path) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-run.js","sourceRoot":"","sources":["../../src/docker/test-run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,
|
|
1
|
+
{"version":3,"file":"test-run.js","sourceRoot":"","sources":["../../src/docker/test-run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,WAAW,GAAG,wBAAwB,CAAC;AACpD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AAC5D,4EAA4E;AAC5E,MAAM,CAAC,MAAM,cAAc,GAAG,yBAAyB,CAAC;AAExD;;;;;GAKG;AACH,MAAM,OAAO,OAAO;IACT,EAAE,CAAS;IACX,SAAS,CAAS;IAE3B,YAAY,EAAU,EAAE,SAAiB;QACvC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,eAAe,CACpB,WAAyD;QAEzD,MAAM,EAAE,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,SAAS,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAAC;QAClD,IAAI,EAAE,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAClE,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS;YACnB,MAAM,IAAI,KAAK,CACb,yBAAyB,WAAW,QAAQ,kBAAkB,wCAAwC,CACvG,CAAC;QACJ,OAAO,IAAI,OAAO,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,MAAM;QACR,OAAO,EAAE,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;IACvC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,aAAa,CAAC,GAAW;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,UAAU,EAAE,OAAO,CAAC,CAAC;QACzE,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAC/C,OAAO,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACpE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CACV,IAA6B,EAC7B,IAAY;QAEZ,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,IAAI,UAAU,EAAE,OAAO,CAAC,CAAC;QAClE,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAChD,OAAO,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACpE,CAAC;CACF;AAED,6EAA6E;AAC7E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAwB,EAAU,EAAE,CAClE,mBAAmB,GAAG,EAAE,EAAE,IAAI,YAAY,GAAG,CAAC;AAEhD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAwB,EAAU,EAAE,CAChE,gCAAgC,GAAG,EAAE,EAAE,IAAI,YAAY,GAAG,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-global-setup.d.ts","sourceRoot":"","sources":["../../src/scripts/docker-global-setup.ts"],"names":[],"mappings":"AAkNA,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CA2BjD;AAED,gFAAgF;AAChF,wBAAgB,cAAc,IAAI,IAAI,CA0BrC"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Docker global setup and teardown for `node --test
|
|
3
|
+
* --test-global-setup=<file>`, where the file re-exports both functions. The
|
|
4
|
+
* setup starts one run: its test processes tie what they create to the run
|
|
5
|
+
* (see TestRun.fromEnvironment), and the teardown removes it after they exit.
|
|
6
|
+
* A run that was killed before it could is removed by a later run on the same
|
|
7
|
+
* engine, from any project of this user.
|
|
8
|
+
*/
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
12
|
+
import { mkdir, readFile, readdir, rename, writeFile } from 'node:fs/promises';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { SHARED_SERVER_LABEL } from "../docker/docker.js";
|
|
16
|
+
import { DOCKER_COMMAND_TIMEOUT, DockerHost, SHELL_TIMEOUT, dockerEnvironment, quote, } from "../docker/host.js";
|
|
17
|
+
import { TEST_RUN_DIRECTORY, TEST_RUN_ID, TEST_RUN_LABEL, TestRun, directoryPrefix, forwardPrefix, } from "../docker/test-run.js";
|
|
18
|
+
const codeOf = (error) => error instanceof Error && 'code' in error ? error.code : undefined;
|
|
19
|
+
/** What a failed execFileSync call printed: its error carries the output it captured. */
|
|
20
|
+
const stderrOf = (error) => error instanceof Error && 'stderr' in error ? String(error.stderr) : '';
|
|
21
|
+
function alive(pid) {
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, 0);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
return codeOf(error) !== 'ESRCH';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Waits without returning to the event loop, which force-exit would end. */
|
|
31
|
+
const block = (milliseconds) => {
|
|
32
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
33
|
+
};
|
|
34
|
+
/** Ignores Docker's answer that `missing` names something already gone. */
|
|
35
|
+
const unlessMissing = (missing, error) => {
|
|
36
|
+
if (!missing.test(stderrOf(error)))
|
|
37
|
+
throw error;
|
|
38
|
+
};
|
|
39
|
+
function readRecordedPath(file) {
|
|
40
|
+
const { path } = JSON.parse(readFileSync(file, 'utf8'));
|
|
41
|
+
return path;
|
|
42
|
+
}
|
|
43
|
+
/** The pids in the run's process records. */
|
|
44
|
+
function recordedProcesses(directory) {
|
|
45
|
+
return readdirSync(directory).flatMap((file) => {
|
|
46
|
+
const pid = /^process-(\d+)-[\da-f-]{36}\.json$/.exec(file)?.[1];
|
|
47
|
+
return pid === undefined ? [] : [Number(pid)];
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Removes what carries the run's label and what its records name. It is
|
|
52
|
+
* synchronous: under `--test-force-exit`, Node does not await the global
|
|
53
|
+
* teardown before it exits (internal/test_runner/test, `harness.teardown()`
|
|
54
|
+
* and then `process.exit()`).
|
|
55
|
+
*/
|
|
56
|
+
function cleanup(host, run, directory) {
|
|
57
|
+
const docker = (args) => execFileSync('docker', args, {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
env: { ...process.env, ...dockerEnvironment(host.endpoint) },
|
|
60
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
61
|
+
timeout: DOCKER_COMMAND_TIMEOUT,
|
|
62
|
+
}).trim();
|
|
63
|
+
const owner = new TestRun(run.id, directory);
|
|
64
|
+
// Labels are attached atomically at creation. Never prune a daemon, remove
|
|
65
|
+
// shared servers, or trust names alone as proof of ownership.
|
|
66
|
+
const filter = ['--filter', `label=${TEST_RUN_LABEL}=${run.id}`];
|
|
67
|
+
for (const id of docker(['ps', '-aq', ...filter])
|
|
68
|
+
.split('\n')
|
|
69
|
+
.filter(Boolean)) {
|
|
70
|
+
try {
|
|
71
|
+
const shared = docker([
|
|
72
|
+
'inspect',
|
|
73
|
+
'--format',
|
|
74
|
+
`{{index .Config.Labels "${SHARED_SERVER_LABEL}"}}`,
|
|
75
|
+
id,
|
|
76
|
+
]);
|
|
77
|
+
if (shared !== '1')
|
|
78
|
+
remove(docker, id);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
unlessMissing(/No such (object|container)/i, error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const name of docker(['volume', 'ls', '-q', ...filter])
|
|
85
|
+
.split('\n')
|
|
86
|
+
.filter(Boolean)) {
|
|
87
|
+
try {
|
|
88
|
+
docker(['volume', 'rm', name]);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
unlessMissing(/no such volume|volume \S+ not found/i, error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const file of readdirSync(directory)) {
|
|
95
|
+
if (file.startsWith('forward-')) {
|
|
96
|
+
const path = readRecordedPath(join(directory, file));
|
|
97
|
+
if (!path.startsWith(forwardPrefix(owner)) || path.slice(5).includes('/'))
|
|
98
|
+
throw new Error(`Invalid SSH forwarding cleanup record: ${path}`);
|
|
99
|
+
rmSync(path, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
else if (file.startsWith('directory-')) {
|
|
102
|
+
const path = readRecordedPath(join(directory, file));
|
|
103
|
+
if (!path.includes(`/${directoryPrefix(owner)}`) || path.includes('/../'))
|
|
104
|
+
throw new Error(`Invalid fixture cleanup record: ${path}`);
|
|
105
|
+
if (host.remote)
|
|
106
|
+
execFileSync('ssh', [...host.sshArgs(), `rm -rf -- ${quote(path)}`], {
|
|
107
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
108
|
+
timeout: SHELL_TIMEOUT,
|
|
109
|
+
});
|
|
110
|
+
else
|
|
111
|
+
rmSync(path, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* A test process that stops its --rm server as it exits makes Docker refuse
|
|
117
|
+
* this removal until its own one ends; retry until the container is gone.
|
|
118
|
+
*/
|
|
119
|
+
function remove(docker, id) {
|
|
120
|
+
const deadline = Date.now() + DOCKER_COMMAND_TIMEOUT;
|
|
121
|
+
for (;;) {
|
|
122
|
+
try {
|
|
123
|
+
docker(['rm', '--force', '--volumes', id]);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (Date.now() > deadline ||
|
|
128
|
+
!/removal of container \S+ is already in progress/.test(stderrOf(error)))
|
|
129
|
+
throw error;
|
|
130
|
+
block(250);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function readRun(directory) {
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(await readFile(join(directory, 'run.json'), 'utf8'));
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Its global setup may still be writing it, or another one just claimed it.
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Removes what dead runs on this engine left behind. */
|
|
144
|
+
async function recover(root, host) {
|
|
145
|
+
for (const name of await readdir(root)) {
|
|
146
|
+
// A claim names its runner: while it lives, the run is being recovered.
|
|
147
|
+
const claimant = /^recovering-(\d+)-/.exec(name)?.[1];
|
|
148
|
+
if (claimant !== undefined && alive(Number(claimant)))
|
|
149
|
+
continue;
|
|
150
|
+
const directory = join(root, name);
|
|
151
|
+
const previous = await readRun(directory);
|
|
152
|
+
if (!previous || previous.endpoint !== host.endpoint || alive(previous.pid))
|
|
153
|
+
continue;
|
|
154
|
+
// A killed runner leaves its test processes, and a killed test process
|
|
155
|
+
// its Docker commands, running. Each one can still create something.
|
|
156
|
+
let processes;
|
|
157
|
+
try {
|
|
158
|
+
processes = recordedProcesses(directory);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
// Another runner claimed the run between the listing and this read.
|
|
162
|
+
if (codeOf(error) === 'ENOENT')
|
|
163
|
+
continue;
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
if (processes.some(alive))
|
|
167
|
+
continue;
|
|
168
|
+
// Runners start in parallel; the rename lets exactly one recover a run.
|
|
169
|
+
const claimed = join(root, `recovering-${process.pid}-${randomUUID()}`);
|
|
170
|
+
try {
|
|
171
|
+
await rename(directory, claimed);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (codeOf(error) === 'ENOENT')
|
|
175
|
+
continue;
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
if (processes.length > 0)
|
|
180
|
+
cleanup(host, previous, claimed);
|
|
181
|
+
rmSync(claimed, { recursive: true, force: true });
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
throw new Error(`Could not recover Docker test resources. Retained ownership record: ${claimed}`, { cause: error });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export async function globalSetup() {
|
|
189
|
+
// The XDG Base Directory specification: an unset or empty XDG_STATE_HOME
|
|
190
|
+
// means ~/.local/state.
|
|
191
|
+
const root = join(process.env.XDG_STATE_HOME || join(homedir(), '.local', 'state'), 'zukhruf-testing', 'docker-runs');
|
|
192
|
+
await mkdir(root, { recursive: true });
|
|
193
|
+
const host = await DockerHost.resolve(undefined);
|
|
194
|
+
await recover(root, host);
|
|
195
|
+
const run = {
|
|
196
|
+
id: randomUUID(),
|
|
197
|
+
endpoint: host.endpoint,
|
|
198
|
+
pid: process.pid,
|
|
199
|
+
};
|
|
200
|
+
const directory = join(root, run.id);
|
|
201
|
+
await mkdir(directory);
|
|
202
|
+
const pending = join(directory, 'run.pending.json');
|
|
203
|
+
await writeFile(pending, JSON.stringify(run));
|
|
204
|
+
await rename(pending, join(directory, 'run.json'));
|
|
205
|
+
process.env[TEST_RUN_ID] = run.id;
|
|
206
|
+
process.env[TEST_RUN_DIRECTORY] = directory;
|
|
207
|
+
process.env.DOCKER_HOST = host.endpoint;
|
|
208
|
+
delete process.env.DOCKER_CONTEXT;
|
|
209
|
+
}
|
|
210
|
+
/** Synchronous, like cleanup, so that `--test-force-exit` cannot cut it off. */
|
|
211
|
+
export function globalTeardown() {
|
|
212
|
+
const owner = TestRun.fromEnvironment(process.env);
|
|
213
|
+
if (!owner)
|
|
214
|
+
throw new Error(`The Docker global teardown runs after its global setup, which sets ${TEST_RUN_ID} and ${TEST_RUN_DIRECTORY}`);
|
|
215
|
+
try {
|
|
216
|
+
const run = JSON.parse(readFileSync(join(owner.directory, 'run.json'), 'utf8'));
|
|
217
|
+
const processes = recordedProcesses(owner.directory);
|
|
218
|
+
// No test process recorded itself, so the run created nothing.
|
|
219
|
+
if (processes.length > 0)
|
|
220
|
+
cleanup(new DockerHost(run.endpoint, undefined), run, owner.directory);
|
|
221
|
+
// A process that still lives can still create something: the next run's
|
|
222
|
+
// recovery sweeps again once it has ended.
|
|
223
|
+
if (!processes.some(alive))
|
|
224
|
+
rmSync(owner.directory, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
// Node sets exit code 1 for a teardown that throws, but prints nothing.
|
|
228
|
+
console.error(`Docker test cleanup failed; retained ownership record for retry: ${owner.directory}`, error);
|
|
229
|
+
process.exitCode = 1;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=docker-global-setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker-global-setup.js","sourceRoot":"","sources":["../../src/scripts/docker-global-setup.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zukhruf/testing",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Disposable fixtures for integration tests: Docker containers and database servers, SQLite and DuckDB databases, HTTP servers and streams.
|
|
3
|
+
"version": "0.3.24",
|
|
4
|
+
"description": "Disposable fixtures for integration tests: Docker containers and database servers, SQLite and DuckDB databases, HTTP servers and streams. Node's global setup and global teardown remove what a killed test run left behind.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -24,9 +24,6 @@
|
|
|
24
24
|
"disposable"
|
|
25
25
|
],
|
|
26
26
|
"type": "module",
|
|
27
|
-
"bin": {
|
|
28
|
-
"zukhruf-docker-tests": "./dist/scripts/run-docker-tests.js"
|
|
29
|
-
},
|
|
30
27
|
"exports": {
|
|
31
28
|
"./package.json": "./package.json",
|
|
32
29
|
"./node": {
|
|
@@ -106,6 +103,12 @@
|
|
|
106
103
|
"types": "./dist/streams/stream-harness.d.ts",
|
|
107
104
|
"import": "./dist/streams/stream-harness.js",
|
|
108
105
|
"default": "./dist/streams/stream-harness.js"
|
|
106
|
+
},
|
|
107
|
+
"./docker-global-setup": {
|
|
108
|
+
"@zukhruf/source": "./src/scripts/docker-global-setup.ts",
|
|
109
|
+
"types": "./dist/scripts/docker-global-setup.d.ts",
|
|
110
|
+
"import": "./dist/scripts/docker-global-setup.js",
|
|
111
|
+
"default": "./dist/scripts/docker-global-setup.js"
|
|
109
112
|
}
|
|
110
113
|
},
|
|
111
114
|
"files": [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-docker-tests.d.ts","sourceRoot":"","sources":["../../src/scripts/run-docker-tests.ts"],"names":[],"mappings":""}
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* zukhruf-docker-tests: runs `node --test` with this command's arguments as one
|
|
4
|
-
* supervised run. Its test processes tie what they create to the run (see
|
|
5
|
-
* TestRun.fromEnvironment), and this process removes it after they exit. A
|
|
6
|
-
* run that was killed before it could is removed by the next run on the same
|
|
7
|
-
* engine, from any project of this user.
|
|
8
|
-
*/
|
|
9
|
-
import { spawn } from 'node:child_process';
|
|
10
|
-
import { randomUUID } from 'node:crypto';
|
|
11
|
-
import { mkdir, readFile, readdir, rename, rm, writeFile, } from 'node:fs/promises';
|
|
12
|
-
import { homedir } from 'node:os';
|
|
13
|
-
import { join } from 'node:path';
|
|
14
|
-
import { setTimeout as delay } from 'node:timers/promises';
|
|
15
|
-
import { SubprocessError } from 'nano-spawn';
|
|
16
|
-
import { SHARED_SERVER_LABEL } from "../docker/docker.js";
|
|
17
|
-
import { DockerHost, quote } from "../docker/host.js";
|
|
18
|
-
import { TEST_RUN_DIRECTORY, TEST_RUN_ID, TEST_RUN_LABEL, TestRun, directoryPrefix, forwardPrefix, } from "../docker/test-run.js";
|
|
19
|
-
if (process.platform === 'win32')
|
|
20
|
-
throw new Error('zukhruf-docker-tests stops a run as one process group, which Windows does not have');
|
|
21
|
-
const root = join(process.env.XDG_STATE_HOME || join(homedir(), '.local', 'state'), 'zukhruf-testing', 'docker-runs');
|
|
22
|
-
const codeOf = (error) => error instanceof Error && 'code' in error ? error.code : undefined;
|
|
23
|
-
function alive(pid) {
|
|
24
|
-
if (!pid)
|
|
25
|
-
return false;
|
|
26
|
-
try {
|
|
27
|
-
process.kill(pid, 0);
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
return codeOf(error) !== 'ESRCH';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/** Ignores Docker's answer that `missing` names something already gone. */
|
|
35
|
-
const unlessMissing = (missing) => (error) => {
|
|
36
|
-
if (!(error instanceof SubprocessError) || !missing.test(error.stderr))
|
|
37
|
-
throw error;
|
|
38
|
-
};
|
|
39
|
-
async function readRecordedPath(file) {
|
|
40
|
-
const { path } = JSON.parse(await readFile(file, 'utf8'));
|
|
41
|
-
return path;
|
|
42
|
-
}
|
|
43
|
-
async function cleanup(host, run, directory, release) {
|
|
44
|
-
const owner = new TestRun(run.id, directory);
|
|
45
|
-
// Labels are attached atomically at creation. Never prune a daemon, remove
|
|
46
|
-
// shared servers, or trust names alone as proof of ownership.
|
|
47
|
-
const filter = ['--filter', `label=${TEST_RUN_LABEL}=${run.id}`];
|
|
48
|
-
const { stdout: containers } = await host.command(['ps', '-aq', ...filter]);
|
|
49
|
-
for (const id of containers.split('\n').filter(Boolean)) {
|
|
50
|
-
await host
|
|
51
|
-
.command([
|
|
52
|
-
'inspect',
|
|
53
|
-
'--format',
|
|
54
|
-
`{{index .Config.Labels "${SHARED_SERVER_LABEL}"}}`,
|
|
55
|
-
id,
|
|
56
|
-
])
|
|
57
|
-
.then(async ({ stdout: shared }) => {
|
|
58
|
-
if (shared !== '1')
|
|
59
|
-
await host.command(['rm', '--force', '--volumes', id]);
|
|
60
|
-
})
|
|
61
|
-
.catch(unlessMissing(/No such (object|container)/i));
|
|
62
|
-
}
|
|
63
|
-
const { stdout: volumes } = await host.command([
|
|
64
|
-
'volume',
|
|
65
|
-
'ls',
|
|
66
|
-
'-q',
|
|
67
|
-
...filter,
|
|
68
|
-
]);
|
|
69
|
-
for (const name of volumes.split('\n').filter(Boolean)) {
|
|
70
|
-
await host
|
|
71
|
-
.command(['volume', 'rm', name])
|
|
72
|
-
.catch(unlessMissing(/no such volume|volume \S+ not found/i));
|
|
73
|
-
}
|
|
74
|
-
for (const file of await readdir(directory)) {
|
|
75
|
-
if (file.startsWith('forward-')) {
|
|
76
|
-
const path = await readRecordedPath(join(directory, file));
|
|
77
|
-
if (!path.startsWith(forwardPrefix(owner)) || path.slice(5).includes('/'))
|
|
78
|
-
throw new Error(`Invalid SSH forwarding cleanup record: ${path}`);
|
|
79
|
-
await rm(path, { recursive: true, force: true });
|
|
80
|
-
}
|
|
81
|
-
else if (file.startsWith('directory-')) {
|
|
82
|
-
const path = await readRecordedPath(join(directory, file));
|
|
83
|
-
if (!path.includes(`/${directoryPrefix(owner)}`) || path.includes('/../'))
|
|
84
|
-
throw new Error(`Invalid fixture cleanup record: ${path}`);
|
|
85
|
-
if (host.remote)
|
|
86
|
-
await host.shell(`rm -rf -- ${quote(path)}`);
|
|
87
|
-
else
|
|
88
|
-
await rm(path, { recursive: true, force: true });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (release)
|
|
92
|
-
await rm(directory, { recursive: true, force: true });
|
|
93
|
-
}
|
|
94
|
-
async function readRun(directory) {
|
|
95
|
-
try {
|
|
96
|
-
return JSON.parse(await readFile(join(directory, 'run.json'), 'utf8'));
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// Its supervisor may still be writing it, or another one just claimed it.
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
/** Removes what dead runs on this engine left behind. */
|
|
104
|
-
async function recover(host) {
|
|
105
|
-
for (const name of await readdir(root)) {
|
|
106
|
-
// A claim names its supervisor: while it lives, the run is being recovered.
|
|
107
|
-
const claimant = /^recovering-(\d+)-/.exec(name)?.[1];
|
|
108
|
-
if (claimant !== undefined && alive(Number(claimant)))
|
|
109
|
-
continue;
|
|
110
|
-
const directory = join(root, name);
|
|
111
|
-
const previous = await readRun(directory);
|
|
112
|
-
if (!previous ||
|
|
113
|
-
previous.endpoint !== host.endpoint ||
|
|
114
|
-
alive(previous.pid) ||
|
|
115
|
-
alive(previous.childPid) ||
|
|
116
|
-
(previous.childPid !== undefined && alive(-previous.childPid)))
|
|
117
|
-
continue;
|
|
118
|
-
// Supervisors start in parallel; the rename lets exactly one recover a run.
|
|
119
|
-
const claimed = join(root, `recovering-${process.pid}-${randomUUID()}`);
|
|
120
|
-
try {
|
|
121
|
-
await rename(directory, claimed);
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
if (codeOf(error) === 'ENOENT')
|
|
125
|
-
continue;
|
|
126
|
-
throw error;
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
await cleanup(host, previous, claimed, true);
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
throw new Error(`Could not recover Docker test resources. Retained ownership record: ${claimed}`, { cause: error });
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
await mkdir(root, { recursive: true });
|
|
137
|
-
const host = await DockerHost.resolve();
|
|
138
|
-
await recover(host);
|
|
139
|
-
const run = {
|
|
140
|
-
id: randomUUID(),
|
|
141
|
-
endpoint: host.endpoint,
|
|
142
|
-
pid: process.pid,
|
|
143
|
-
};
|
|
144
|
-
const directory = join(root, run.id);
|
|
145
|
-
await mkdir(directory);
|
|
146
|
-
const record = async (current) => {
|
|
147
|
-
const pending = join(directory, 'run.pending.json');
|
|
148
|
-
await writeFile(pending, JSON.stringify(current));
|
|
149
|
-
await rename(pending, join(directory, 'run.json'));
|
|
150
|
-
};
|
|
151
|
-
await record(run);
|
|
152
|
-
const args = process.argv.slice(2);
|
|
153
|
-
const concurrency = host.remote && !args.some((arg) => arg.startsWith('--test-concurrency'))
|
|
154
|
-
? ['--test-concurrency=1']
|
|
155
|
-
: [];
|
|
156
|
-
const child = spawn(process.execPath, ['--test', ...concurrency, ...args], {
|
|
157
|
-
stdio: 'inherit',
|
|
158
|
-
detached: true,
|
|
159
|
-
env: {
|
|
160
|
-
...process.env,
|
|
161
|
-
DOCKER_CONTEXT: undefined,
|
|
162
|
-
DOCKER_HOST: host.endpoint,
|
|
163
|
-
[TEST_RUN_ID]: run.id,
|
|
164
|
-
[TEST_RUN_DIRECTORY]: directory,
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
const exited = new Promise((resolve, reject) => {
|
|
168
|
-
child.once('error', reject);
|
|
169
|
-
child.once('exit', (code) => resolve(code ?? 1));
|
|
170
|
-
});
|
|
171
|
-
/** Signals the run's process group; false when the group could not be stopped. */
|
|
172
|
-
async function kill(signal, attempt = 0) {
|
|
173
|
-
if (!child.pid)
|
|
174
|
-
return true;
|
|
175
|
-
try {
|
|
176
|
-
process.kill(-child.pid, signal);
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
if (codeOf(error) === 'ESRCH')
|
|
181
|
-
return true;
|
|
182
|
-
// macOS can briefly report EPERM while an empty group is being reaped.
|
|
183
|
-
// Retry that transition; a persistent denial must retain ownership.
|
|
184
|
-
if (codeOf(error) === 'EPERM' && attempt < 3) {
|
|
185
|
-
await delay(25);
|
|
186
|
-
return kill(signal, attempt + 1);
|
|
187
|
-
}
|
|
188
|
-
console.error(`Could not signal Docker test process group ${child.pid}:`, error);
|
|
189
|
-
child.kill(signal);
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
const finished = new AbortController();
|
|
194
|
-
const interruption = new AbortController();
|
|
195
|
-
const interrupt = () => {
|
|
196
|
-
if (interruption.signal.aborted)
|
|
197
|
-
return;
|
|
198
|
-
interruption.abort();
|
|
199
|
-
void kill('SIGTERM');
|
|
200
|
-
void delay(5_000, undefined, { signal: finished.signal }).then(() => kill('SIGKILL'), () => { });
|
|
201
|
-
};
|
|
202
|
-
process.on('SIGINT', interrupt);
|
|
203
|
-
process.on('SIGTERM', interrupt);
|
|
204
|
-
try {
|
|
205
|
-
await record({ ...run, childPid: child.pid });
|
|
206
|
-
const code = await exited;
|
|
207
|
-
process.exitCode = interruption.signal.aborted ? 130 : code;
|
|
208
|
-
}
|
|
209
|
-
finally {
|
|
210
|
-
finished.abort();
|
|
211
|
-
// Includes SSH channels in workers killed by node --test or --test-force-exit.
|
|
212
|
-
const stopped = await kill('SIGKILL');
|
|
213
|
-
try {
|
|
214
|
-
await cleanup(host, run, directory, stopped);
|
|
215
|
-
if (!stopped) {
|
|
216
|
-
process.exitCode = 1;
|
|
217
|
-
console.error(`Docker test termination failed; retained ownership record for retry: ${directory}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
catch (error) {
|
|
221
|
-
process.exitCode = 1;
|
|
222
|
-
console.error(`Docker test cleanup failed; retained ownership record for retry: ${directory}`, error);
|
|
223
|
-
}
|
|
224
|
-
process.off('SIGINT', interrupt);
|
|
225
|
-
process.off('SIGTERM', interrupt);
|
|
226
|
-
}
|
|
227
|
-
//# sourceMappingURL=run-docker-tests.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-docker-tests.js","sourceRoot":"","sources":["../../src/scripts/run-docker-tests.ts"],"names":[],"mappings":"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"}
|