@zukhruf/testing 0.0.0-stage → 0.3.2

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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -2
  3. package/dist/async/index.d.ts +3 -0
  4. package/dist/async/index.d.ts.map +1 -0
  5. package/dist/async/index.js +3 -0
  6. package/dist/async/index.js.map +1 -0
  7. package/dist/async/settle-within.d.ts +2 -0
  8. package/dist/async/settle-within.d.ts.map +1 -0
  9. package/dist/async/settle-within.js +6 -0
  10. package/dist/async/settle-within.js.map +1 -0
  11. package/dist/async/timebox.d.ts +41 -0
  12. package/dist/async/timebox.d.ts.map +1 -0
  13. package/dist/async/timebox.js +42 -0
  14. package/dist/async/timebox.js.map +1 -0
  15. package/dist/databases/bigquery.d.ts +16 -0
  16. package/dist/databases/bigquery.d.ts.map +1 -0
  17. package/dist/databases/bigquery.js +21 -0
  18. package/dist/databases/bigquery.js.map +1 -0
  19. package/dist/databases/clickhouse.d.ts +12 -0
  20. package/dist/databases/clickhouse.d.ts.map +1 -0
  21. package/dist/databases/clickhouse.js +22 -0
  22. package/dist/databases/clickhouse.js.map +1 -0
  23. package/dist/databases/database.d.ts +24 -0
  24. package/dist/databases/database.d.ts.map +1 -0
  25. package/dist/databases/database.js +2 -0
  26. package/dist/databases/database.js.map +1 -0
  27. package/dist/databases/duckdb.d.ts +11 -0
  28. package/dist/databases/duckdb.d.ts.map +1 -0
  29. package/dist/databases/duckdb.js +13 -0
  30. package/dist/databases/duckdb.js.map +1 -0
  31. package/dist/databases/mysql.d.ts +17 -0
  32. package/dist/databases/mysql.d.ts.map +1 -0
  33. package/dist/databases/mysql.js +116 -0
  34. package/dist/databases/mysql.js.map +1 -0
  35. package/dist/databases/postgres.d.ts +15 -0
  36. package/dist/databases/postgres.d.ts.map +1 -0
  37. package/dist/databases/postgres.js +96 -0
  38. package/dist/databases/postgres.js.map +1 -0
  39. package/dist/databases/sqlite.d.ts +14 -0
  40. package/dist/databases/sqlite.d.ts.map +1 -0
  41. package/dist/databases/sqlite.js +66 -0
  42. package/dist/databases/sqlite.js.map +1 -0
  43. package/dist/databases/sqlserver.d.ts +14 -0
  44. package/dist/databases/sqlserver.d.ts.map +1 -0
  45. package/dist/databases/sqlserver.js +134 -0
  46. package/dist/databases/sqlserver.js.map +1 -0
  47. package/dist/docker/container.d.ts +29 -0
  48. package/dist/docker/container.d.ts.map +1 -0
  49. package/dist/docker/container.js +55 -0
  50. package/dist/docker/container.js.map +1 -0
  51. package/dist/docker/directory.d.ts +18 -0
  52. package/dist/docker/directory.d.ts.map +1 -0
  53. package/dist/docker/directory.js +77 -0
  54. package/dist/docker/directory.js.map +1 -0
  55. package/dist/docker/docker.d.ts +74 -0
  56. package/dist/docker/docker.d.ts.map +1 -0
  57. package/dist/docker/docker.js +257 -0
  58. package/dist/docker/docker.js.map +1 -0
  59. package/dist/docker/host.d.ts +17 -0
  60. package/dist/docker/host.d.ts.map +1 -0
  61. package/dist/docker/host.js +170 -0
  62. package/dist/docker/host.js.map +1 -0
  63. package/dist/docker/index.d.ts +7 -0
  64. package/dist/docker/index.d.ts.map +1 -0
  65. package/dist/docker/index.js +7 -0
  66. package/dist/docker/index.js.map +1 -0
  67. package/dist/docker/skip-without-docker.d.ts +12 -0
  68. package/dist/docker/skip-without-docker.d.ts.map +1 -0
  69. package/dist/docker/skip-without-docker.js +31 -0
  70. package/dist/docker/skip-without-docker.js.map +1 -0
  71. package/dist/docker/test-run.d.ts +32 -0
  72. package/dist/docker/test-run.d.ts.map +1 -0
  73. package/dist/docker/test-run.js +52 -0
  74. package/dist/docker/test-run.js.map +1 -0
  75. package/dist/docker/volume.d.ts +9 -0
  76. package/dist/docker/volume.d.ts.map +1 -0
  77. package/dist/docker/volume.js +13 -0
  78. package/dist/docker/volume.js.map +1 -0
  79. package/dist/http/http-server.d.ts +12 -0
  80. package/dist/http/http-server.d.ts.map +1 -0
  81. package/dist/http/http-server.js +29 -0
  82. package/dist/http/http-server.js.map +1 -0
  83. package/dist/scripts/run-docker-tests.d.ts +3 -0
  84. package/dist/scripts/run-docker-tests.d.ts.map +1 -0
  85. package/dist/scripts/run-docker-tests.js +227 -0
  86. package/dist/scripts/run-docker-tests.js.map +1 -0
  87. package/dist/streams/stream-harness.d.ts +29 -0
  88. package/dist/streams/stream-harness.d.ts.map +1 -0
  89. package/dist/streams/stream-harness.js +75 -0
  90. package/dist/streams/stream-harness.js.map +1 -0
  91. package/package.json +126 -4
@@ -0,0 +1,15 @@
1
+ import type { Database, DatabaseOptions } from './database.ts';
2
+ export type { Database, DatabaseOptions } from './database.ts';
3
+ export interface PostgresOptions extends DatabaseOptions {
4
+ user?: string;
5
+ }
6
+ /** PostgreSQL test configuration. Each acquisition returns an independent handle. */
7
+ export declare class Postgres {
8
+ #private;
9
+ constructor({ docker, image, password, database, user, labels, }: PostgresOptions);
10
+ /** Create an isolated database on the persistent shared server. */
11
+ database(): Promise<Database>;
12
+ /** Start a dedicated server; the returned handle owns its container. */
13
+ start(): Promise<Database>;
14
+ }
15
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/databases/postgres.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/D,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/D,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qFAAqF;AACrF,qBAAa,QAAQ;;gBAIP,EACV,MAAM,EACN,KAA4B,EAC5B,QAAyB,EACzB,QAAmB,EACnB,IAAiB,EACjB,MAAM,GACP,EAAE,eAAe;IAKlB,mEAAmE;IAC7D,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IAoBnC,wEAAwE;IAClE,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC;CA+EjC"}
@@ -0,0 +1,96 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { timebox } from "../async/timebox.js";
3
+ /** PostgreSQL test configuration. Each acquisition returns an independent handle. */
4
+ export class Postgres {
5
+ #docker;
6
+ #options;
7
+ constructor({ docker, image = 'postgres:18-alpine', password = 'testpassword', database = 'testdb', user = 'postgres', labels, }) {
8
+ this.#docker = docker;
9
+ this.#options = { image, password, database, user, labels };
10
+ }
11
+ /** Create an isolated database on the persistent shared server. */
12
+ async database() {
13
+ const container = await this.#acquire('postgres', 'reuse');
14
+ const database = `test_${randomUUID().replaceAll('-', '')}`;
15
+ try {
16
+ await this.#sql(container, `CREATE DATABASE ${database}`);
17
+ }
18
+ catch (error) {
19
+ await container.disconnect();
20
+ throw error;
21
+ }
22
+ return this.#handle(container, database, async () => {
23
+ await this.#sql(container, `DROP DATABASE IF EXISTS ${database} WITH (FORCE)`).catch(() => {
24
+ /* Best-effort cleanup after external removal. */
25
+ });
26
+ await container.disconnect();
27
+ });
28
+ }
29
+ /** Start a dedicated server; the returned handle owns its container. */
30
+ async start() {
31
+ const { database } = this.#options;
32
+ const container = await this.#acquire(database, 'serve');
33
+ return this.#handle(container, database, () => container.cleanup());
34
+ }
35
+ #sql(container, sql) {
36
+ return container.exec([
37
+ 'psql',
38
+ '-U',
39
+ this.#options.user,
40
+ '-d',
41
+ 'postgres',
42
+ '-c',
43
+ sql,
44
+ ]);
45
+ }
46
+ #acquire(database, mode) {
47
+ const { image, password, user, labels } = this.#options;
48
+ return this.#docker[mode]({
49
+ image,
50
+ labels,
51
+ internalPort: 5432,
52
+ env: {
53
+ POSTGRES_PASSWORD: password,
54
+ POSTGRES_DB: database,
55
+ POSTGRES_USER: user,
56
+ },
57
+ tmpfs: ['/var/lib/postgresql:rw,size=512m'],
58
+ memorySwappiness: 0,
59
+ healthy: (container) => this.#ready(container, database),
60
+ });
61
+ }
62
+ #ready(container, database) {
63
+ const { user } = this.#options;
64
+ // TCP excludes PostgreSQL's temporary, socket-only initialization server.
65
+ return timebox(async () => {
66
+ await container.exec(['pg_isready', '-h', '127.0.0.1', '-U', user]);
67
+ await container.exec([
68
+ 'psql',
69
+ '-h',
70
+ '127.0.0.1',
71
+ '-U',
72
+ user,
73
+ '-d',
74
+ database,
75
+ '-c',
76
+ 'SELECT 1',
77
+ ]);
78
+ }, { maxRetryTime: 60_000 });
79
+ }
80
+ #handle(container, database, cleanup) {
81
+ const { image, user, password } = this.#options;
82
+ return {
83
+ connectionString: `postgresql://${user}:${password}@${container.host}:${container.port}/${database}`,
84
+ image,
85
+ user,
86
+ password,
87
+ database,
88
+ containerId: container.containerId,
89
+ host: container.host,
90
+ port: container.port,
91
+ cleanup,
92
+ [Symbol.asyncDispose]: cleanup,
93
+ };
94
+ }
95
+ }
96
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/databases/postgres.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ export interface SqliteDatabase extends AsyncDisposable {
3
+ path: string;
4
+ connection: DatabaseSync;
5
+ /** Close the connection before removing its temporary directory. */
6
+ cleanup: () => Promise<void>;
7
+ }
8
+ /** Each acquisition owns an independent, temporary SQLite database. */
9
+ export declare class Sqlite {
10
+ /** Holds a write lock in another process, which releases even while the caller blocks. */
11
+ writeLock(path: string, durationMs: number): Promise<AsyncDisposable>;
12
+ database(): Promise<SqliteDatabase>;
13
+ }
14
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/databases/sqlite.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,YAAY,CAAC;IACzB,oEAAoE;IACpE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,uEAAuE;AACvE,qBAAa,MAAM;IACjB,0FAA0F;IACpF,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAoDrE,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;CAY1C"}
@@ -0,0 +1,66 @@
1
+ import { once } from 'node:events';
2
+ import { mkdtempDisposable } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { DatabaseSync } from 'node:sqlite';
6
+ import spawn, { SubprocessError } from 'nano-spawn';
7
+ /** Each acquisition owns an independent, temporary SQLite database. */
8
+ export class Sqlite {
9
+ /** Holds a write lock in another process, which releases even while the caller blocks. */
10
+ async writeLock(path, durationMs) {
11
+ const holder = spawn(process.execPath, [
12
+ '--input-type=module',
13
+ '--eval',
14
+ `
15
+ import { DatabaseSync } from 'node:sqlite';
16
+ import { setTimeout } from 'node:timers/promises';
17
+
18
+ process.once('disconnect', () => process.exit(0));
19
+ using database = new DatabaseSync(process.argv[1]);
20
+ database.exec('BEGIN IMMEDIATE');
21
+ process.send('locked');
22
+ await setTimeout(Number(process.argv[2]));
23
+ database.exec('ROLLBACK');
24
+ process.disconnect();
25
+ `,
26
+ path,
27
+ String(durationMs),
28
+ ], { stdio: ['ignore', 'ignore', 'pipe', 'ipc'] });
29
+ const exited = holder.then(() => {
30
+ throw new Error('SQLite lock holder exited before acquiring the lock.');
31
+ });
32
+ const child = await Promise.race([holder.nodeChildProcess, exited]);
33
+ try {
34
+ await Promise.race([once(child, 'message'), exited]);
35
+ }
36
+ catch (error) {
37
+ child.kill();
38
+ await Promise.allSettled([holder]);
39
+ throw error;
40
+ }
41
+ const resources = new AsyncDisposableStack();
42
+ resources.defer(async () => {
43
+ const killed = child.kill();
44
+ try {
45
+ await holder;
46
+ }
47
+ catch (error) {
48
+ if (!killed ||
49
+ !(error instanceof SubprocessError) ||
50
+ error.signalName !== 'SIGTERM')
51
+ throw error;
52
+ }
53
+ });
54
+ return resources;
55
+ }
56
+ async database() {
57
+ await using resources = new AsyncDisposableStack();
58
+ const directory = resources.use(await mkdtempDisposable(join(tmpdir(), 'zukhruf-testing-sqlite-')));
59
+ const path = join(directory.path, 'test.sqlite');
60
+ const connection = resources.use(new DatabaseSync(path));
61
+ const owned = resources.move();
62
+ const cleanup = () => owned.disposeAsync();
63
+ return { path, connection, cleanup, [Symbol.asyncDispose]: cleanup };
64
+ }
65
+ }
66
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/databases/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AASpD,uEAAuE;AACvE,MAAM,OAAO,MAAM;IACjB,0FAA0F;IAC1F,KAAK,CAAC,SAAS,CAAC,IAAY,EAAE,UAAkB;QAC9C,MAAM,MAAM,GAAG,KAAK,CAClB,OAAO,CAAC,QAAQ,EAChB;YACE,qBAAqB;YACrB,QAAQ;YACR;;;;;;;;;;;SAWC;YACD,IAAI;YACJ,MAAM,CAAC,UAAU,CAAC;SACnB,EACD,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,CAC/C,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YAC9B,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;QACpE,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,EAAE,CAAC;YACb,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YACnC,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,SAAS,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE;YACzB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC;YACf,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IACE,CAAC,MAAM;oBACP,CAAC,CAAC,KAAK,YAAY,eAAe,CAAC;oBACnC,KAAK,CAAC,UAAU,KAAK,SAAS;oBAE9B,MAAM,KAAK,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,YAAY,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QACnD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAC7B,MAAM,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,yBAAyB,CAAC,CAAC,CACnE,CAAC;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;QAE3C,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC;IACvE,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ import type { Database, DatabaseOptions } from './database.ts';
2
+ export type { Database, DatabaseOptions } from './database.ts';
3
+ export declare const SQL_SERVER_FULL_IMAGE = "mcr.microsoft.com/mssql/server:2022-latest";
4
+ export declare const SQL_SERVER_EDGE_IMAGE = "mcr.microsoft.com/azure-sql-edge:latest";
5
+ /** SQL Server test configuration. Without an image, the engine's architecture picks one. */
6
+ export declare class SqlServer {
7
+ #private;
8
+ constructor({ docker, image, password, database, labels, }: DatabaseOptions);
9
+ /** Create an isolated database on the persistent shared server. */
10
+ database(): Promise<Database>;
11
+ /** Start a dedicated server; the returned handle owns its container. */
12
+ start(): Promise<Database>;
13
+ }
14
+ //# sourceMappingURL=sqlserver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlserver.d.ts","sourceRoot":"","sources":["../../src/databases/sqlserver.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/D,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE/D,eAAO,MAAM,qBAAqB,+CACY,CAAC;AAC/C,eAAO,MAAM,qBAAqB,4CAA4C,CAAC;AAE/E,4FAA4F;AAC5F,qBAAa,SAAS;;gBAaR,EACV,MAAM,EACN,KAAK,EACL,QAA+B,EAC/B,QAAmB,EACnB,MAAM,GACP,EAAE,eAAe;IAKlB,mEAAmE;IAC7D,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IAmBnC,wEAAwE;IAClE,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC;CA+GjC"}
@@ -0,0 +1,134 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import sql from 'mssql';
3
+ import pMemoize from 'p-memoize';
4
+ import { timebox } from "../async/timebox.js";
5
+ export const SQL_SERVER_FULL_IMAGE = 'mcr.microsoft.com/mssql/server:2022-latest';
6
+ export const SQL_SERVER_EDGE_IMAGE = 'mcr.microsoft.com/azure-sql-edge:latest';
7
+ /** SQL Server test configuration. Without an image, the engine's architecture picks one. */
8
+ export class SqlServer {
9
+ #docker;
10
+ #options;
11
+ #image = pMemoize(async () => {
12
+ if (this.#options.image)
13
+ return this.#options.image;
14
+ if (!(await this.#docker.isAvailable()))
15
+ throw new Error('Docker is required for container-backed tests');
16
+ const { architecture } = await this.#docker.info();
17
+ return ['arm64', 'aarch64'].includes(architecture)
18
+ ? SQL_SERVER_EDGE_IMAGE
19
+ : SQL_SERVER_FULL_IMAGE;
20
+ });
21
+ constructor({ docker, image, password = 'StrongP@ssw0rd123!', database = 'testdb', labels, }) {
22
+ this.#docker = docker;
23
+ this.#options = { image, password, database, labels };
24
+ }
25
+ /** Create an isolated database on the persistent shared server. */
26
+ async database() {
27
+ const image = await this.#image();
28
+ const container = await this.#acquire(image, 'reuse');
29
+ const database = `test_${randomUUID().replaceAll('-', '')}`;
30
+ try {
31
+ await this.#createDatabase(container, database);
32
+ return this.#handle(container, image, database, async () => {
33
+ try {
34
+ await this.#dropDatabase(container, database);
35
+ }
36
+ finally {
37
+ await container.disconnect();
38
+ }
39
+ });
40
+ }
41
+ catch (error) {
42
+ await container.disconnect();
43
+ throw error;
44
+ }
45
+ }
46
+ /** Start a dedicated server; the returned handle owns its container. */
47
+ async start() {
48
+ const image = await this.#image();
49
+ const container = await this.#acquire(image, 'serve');
50
+ const { database } = this.#options;
51
+ try {
52
+ await this.#createDatabase(container, database);
53
+ return this.#handle(container, image, database, () => container.cleanup());
54
+ }
55
+ catch (error) {
56
+ await container.cleanup();
57
+ throw error;
58
+ }
59
+ }
60
+ #connectionString(container, database) {
61
+ return `Server=${container.host},${container.port};Database=${database};User Id=sa;Password=${this.#options.password};TrustServerCertificate=true;Encrypt=false;`;
62
+ }
63
+ async #ping(container) {
64
+ const pool = new sql.ConnectionPool(`${this.#connectionString(container, 'master')}connectionTimeout=1000;requestTimeout=1000;`);
65
+ try {
66
+ await pool.connect();
67
+ await pool.request().query('SELECT 1');
68
+ }
69
+ finally {
70
+ await pool.close().catch(() => { });
71
+ }
72
+ }
73
+ async #createDatabase(container, database) {
74
+ const pool = new sql.ConnectionPool(this.#connectionString(container, 'master'));
75
+ await pool.connect();
76
+ try {
77
+ await pool
78
+ .request()
79
+ .query(`IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = '${database}') CREATE DATABASE [${database}]`);
80
+ }
81
+ finally {
82
+ await pool.close();
83
+ }
84
+ }
85
+ async #dropDatabase(container, database) {
86
+ const pool = new sql.ConnectionPool(this.#connectionString(container, 'master'));
87
+ try {
88
+ await pool.connect();
89
+ await pool
90
+ .request()
91
+ .query(`IF DB_ID('${database}') IS NOT NULL BEGIN ALTER DATABASE [${database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [${database}]; END`);
92
+ }
93
+ catch {
94
+ // Best-effort cleanup; a leaked database lasts until container removal.
95
+ }
96
+ finally {
97
+ await pool.close().catch(() => { });
98
+ }
99
+ }
100
+ #acquire(image, mode) {
101
+ const { password, labels } = this.#options;
102
+ return this.#docker[mode]({
103
+ image,
104
+ labels,
105
+ env: {
106
+ ACCEPT_EULA: 'Y',
107
+ MSSQL_SA_PASSWORD: password,
108
+ MSSQL_MEMORY_LIMIT_MB: '2048',
109
+ ...(image.includes('azure-sql-edge') ? {} : { MSSQL_PID: 'Express' }),
110
+ },
111
+ internalPort: 1433,
112
+ tmpfs: ['/var/opt/mssql:rw,size=2g,mode=1777'],
113
+ memory: '3g',
114
+ memorySwappiness: 0,
115
+ healthy: (container) => timebox(() => this.#ping(container), { maxRetryTime: 180_000 }),
116
+ });
117
+ }
118
+ #handle(container, image, database, cleanup) {
119
+ const { password } = this.#options;
120
+ return {
121
+ connectionString: this.#connectionString(container, database),
122
+ image,
123
+ user: 'sa',
124
+ password,
125
+ database,
126
+ containerId: container.containerId,
127
+ host: container.host,
128
+ port: container.port,
129
+ cleanup,
130
+ [Symbol.asyncDispose]: cleanup,
131
+ };
132
+ }
133
+ }
134
+ //# sourceMappingURL=sqlserver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlserver.js","sourceRoot":"","sources":["../../src/databases/sqlserver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { type Result } from 'nano-spawn';
2
+ /** The Docker CLI, bound to the engine that a handle was created on. */
3
+ export type DockerCommand = (args: string[]) => Promise<Result>;
4
+ /** A container this process started. Disposal removes it with its anonymous volumes. */
5
+ export declare class Container implements AsyncDisposable {
6
+ readonly containerId: string;
7
+ readonly command: DockerCommand;
8
+ constructor(containerId: string, command: DockerCommand);
9
+ readonly exec: (command: string[]) => Promise<Result>;
10
+ /** What the container printed so far, standard output and error interleaved. */
11
+ logs(): Promise<string>;
12
+ /** Sends `signal` to the container's main process, as `docker kill` does. */
13
+ kill(signal?: string): Promise<void>;
14
+ cleanup(): Promise<void>;
15
+ [Symbol.asyncDispose](): Promise<void>;
16
+ }
17
+ /**
18
+ * A container whose port answers on 127.0.0.1 of this machine, through an SSH
19
+ * forward when the engine is remote. Disposal also closes the forward.
20
+ */
21
+ export declare class ServiceContainer extends Container {
22
+ readonly host = "127.0.0.1";
23
+ readonly port: number;
24
+ /** Closes the forward and leaves the container running for its other users. */
25
+ readonly disconnect: () => Promise<void>;
26
+ constructor(containerId: string, port: number, command: DockerCommand, disconnect: () => Promise<void>);
27
+ cleanup(): Promise<void>;
28
+ }
29
+ //# sourceMappingURL=container.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/docker/container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAmB,MAAM,YAAY,CAAC;AAE1D,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEhE,wFAAwF;AACxF,qBAAa,SAAU,YAAW,eAAe;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;gBAEpB,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa;IAMvD,QAAQ,CAAC,IAAI,GAAI,SAAS,MAAM,EAAE,KAAG,OAAO,CAAC,MAAM,CAAC,CACG;IAEvD,gFAAgF;IAC1E,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAK7B,6EAA6E;IACvE,IAAI,CAAC,MAAM,SAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAY9B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAGvC;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,SAAS;IAC7C,QAAQ,CAAC,IAAI,eAAe;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;gBAGvC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC;IAOlB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAOxC"}
@@ -0,0 +1,55 @@
1
+ import { SubprocessError } from 'nano-spawn';
2
+ /** A container this process started. Disposal removes it with its anonymous volumes. */
3
+ export class Container {
4
+ containerId;
5
+ command;
6
+ constructor(containerId, command) {
7
+ this.containerId = containerId;
8
+ this.command = command;
9
+ }
10
+ // Bound: readiness probes receive it destructured.
11
+ exec = (command) => this.command(['exec', this.containerId, ...command]);
12
+ /** What the container printed so far, standard output and error interleaved. */
13
+ async logs() {
14
+ const { output } = await this.command(['logs', this.containerId]);
15
+ return output;
16
+ }
17
+ /** Sends `signal` to the container's main process, as `docker kill` does. */
18
+ async kill(signal = 'SIGKILL') {
19
+ await this.command(['kill', '--signal', signal, this.containerId]);
20
+ }
21
+ async cleanup() {
22
+ await this.command(['rm', '--force', '--volumes', this.containerId]).catch((error) => {
23
+ if (!(error instanceof SubprocessError) ||
24
+ !/No such container/i.test(error.stderr))
25
+ throw error;
26
+ });
27
+ }
28
+ [Symbol.asyncDispose]() {
29
+ return this.cleanup();
30
+ }
31
+ }
32
+ /**
33
+ * A container whose port answers on 127.0.0.1 of this machine, through an SSH
34
+ * forward when the engine is remote. Disposal also closes the forward.
35
+ */
36
+ export class ServiceContainer extends Container {
37
+ host = '127.0.0.1';
38
+ port;
39
+ /** Closes the forward and leaves the container running for its other users. */
40
+ disconnect;
41
+ constructor(containerId, port, command, disconnect) {
42
+ super(containerId, command);
43
+ this.port = port;
44
+ this.disconnect = disconnect;
45
+ }
46
+ async cleanup() {
47
+ try {
48
+ await super.cleanup();
49
+ }
50
+ finally {
51
+ await this.disconnect();
52
+ }
53
+ }
54
+ }
55
+ //# sourceMappingURL=container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/docker/container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,eAAe,EAAE,MAAM,YAAY,CAAC;AAK1D,wFAAwF;AACxF,MAAM,OAAO,SAAS;IACX,WAAW,CAAS;IACpB,OAAO,CAAgB;IAEhC,YAAY,WAAmB,EAAE,OAAsB;QACrD,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,mDAAmD;IAC1C,IAAI,GAAG,CAAC,OAAiB,EAAmB,EAAE,CACrD,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;IAEvD,gFAAgF;IAChF,KAAK,CAAC,IAAI;QACR,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;QAClE,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,6EAA6E;IAC7E,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,SAAS;QAC3B,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CACxE,CAAC,KAAc,EAAE,EAAE;YACjB,IACE,CAAC,CAAC,KAAK,YAAY,eAAe,CAAC;gBACnC,CAAC,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;gBAExC,MAAM,KAAK,CAAC;QAChB,CAAC,CACF,CAAC;IACJ,CAAC;IAED,CAAC,MAAM,CAAC,YAAY,CAAC;QACnB,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,gBAAiB,SAAQ,SAAS;IACpC,IAAI,GAAG,WAAW,CAAC;IACnB,IAAI,CAAS;IACtB,+EAA+E;IACtE,UAAU,CAAsB;IAEzC,YACE,WAAmB,EACnB,IAAY,EACZ,OAAsB,EACtB,UAA+B;QAE/B,KAAK,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAEQ,KAAK,CAAC,OAAO;QACpB,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACxB,CAAC;gBAAS,CAAC;YACT,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ import { DockerHost } from './host.ts';
2
+ import { type TestRun } from './test-run.ts';
3
+ /** A disposable fixture directory on the selected daemon's host filesystem. */
4
+ export declare class DockerDirectory implements AsyncDisposable {
5
+ #private;
6
+ readonly path: string;
7
+ readonly host: DockerHost;
8
+ private constructor();
9
+ static create(host: DockerHost, run: TestRun | undefined): Promise<DockerDirectory>;
10
+ private resolve;
11
+ mkdir(name: string): Promise<void>;
12
+ writeFile(name: string, content: string, mode?: number): Promise<void>;
13
+ readFile(name: string): Promise<string>;
14
+ chmod(name: string, mode: number): Promise<void>;
15
+ symlink(target: string, name: string): Promise<void>;
16
+ [Symbol.asyncDispose](): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory.d.ts","sourceRoot":"","sources":["../../src/docker/directory.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,UAAU,EAAS,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAmB,MAAM,eAAe,CAAC;AAE9D,+EAA+E;AAC/E,qBAAa,eAAgB,YAAW,eAAe;;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAG1B,OAAO;WAUM,MAAM,CACjB,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,OAAO,GAAG,SAAS,GACvB,OAAO,CAAC,eAAe,CAAC;IAS3B,OAAO,CAAC,OAAO;IAMT,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,SAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAarE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAWvC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7C"}
@@ -0,0 +1,77 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { chmod, mkdir, readFile, rm, symlink, writeFile, } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join, posix } from 'node:path';
5
+ import { DockerHost, quote } from "./host.js";
6
+ import { directoryPrefix } from "./test-run.js";
7
+ /** A disposable fixture directory on the selected daemon's host filesystem. */
8
+ export class DockerDirectory {
9
+ path;
10
+ host;
11
+ #record;
12
+ constructor(path, host, record) {
13
+ this.path = path;
14
+ this.host = host;
15
+ this.#record = record;
16
+ }
17
+ static async create(host, run) {
18
+ const name = `${directoryPrefix(run)}${randomUUID()}`;
19
+ const path = host.remote ? `/tmp/${name}` : join(tmpdir(), name);
20
+ const record = await run?.record('directory', path);
21
+ if (host.remote)
22
+ await host.shell(`mkdir -m 700 -- ${quote(path)}`);
23
+ else
24
+ await mkdir(path, { mode: 0o700 });
25
+ return new DockerDirectory(path, host, record);
26
+ }
27
+ resolve(name) {
28
+ if (posix.isAbsolute(name) || name.split('/').includes('..'))
29
+ throw new Error('Fixture paths must stay inside their directory');
30
+ return posix.join(this.path, name);
31
+ }
32
+ async mkdir(name) {
33
+ const path = this.resolve(name);
34
+ if (this.host.remote)
35
+ await this.host.shell(`mkdir -p -- ${quote(path)}`);
36
+ else
37
+ await mkdir(path, { recursive: true });
38
+ }
39
+ async writeFile(name, content, mode = 0o644) {
40
+ const path = this.resolve(name);
41
+ if (this.host.remote)
42
+ await this.host.shell(`cat > ${quote(path)} && chmod ${mode.toString(8)} -- ${quote(path)}`, content);
43
+ else {
44
+ await writeFile(path, content);
45
+ await chmod(path, mode);
46
+ }
47
+ }
48
+ async readFile(name) {
49
+ const path = this.resolve(name);
50
+ // base64 prevents nano-spawn from stripping the fixture's trailing newline.
51
+ if (this.host.remote)
52
+ return Buffer.from((await this.host.shell(`base64 < ${quote(path)}`)).stdout, 'base64').toString('utf8');
53
+ return readFile(path, 'utf8');
54
+ }
55
+ async chmod(name, mode) {
56
+ const path = this.resolve(name);
57
+ if (this.host.remote)
58
+ await this.host.shell(`chmod ${mode.toString(8)} -- ${quote(path)}`);
59
+ else
60
+ await chmod(path, mode);
61
+ }
62
+ async symlink(target, name) {
63
+ const path = this.resolve(name);
64
+ if (this.host.remote)
65
+ await this.host.shell(`ln -s -- ${quote(target)} ${quote(path)}`);
66
+ else
67
+ await symlink(target, path);
68
+ }
69
+ async [Symbol.asyncDispose]() {
70
+ if (this.host.remote)
71
+ await this.host.shell(`rm -rf -- ${quote(this.path)}`);
72
+ else
73
+ await rm(this.path, { recursive: true, force: true });
74
+ await this.#record?.[Symbol.asyncDispose]();
75
+ }
76
+ }
77
+ //# sourceMappingURL=directory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory.js","sourceRoot":"","sources":["../../src/docker/directory.ts"],"names":[],"mappings":"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"}