pipework 0.242.0 → 0.245.0
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/CHANGELOG.md +14 -0
- package/dist/core/config/namespace.d.ts +1 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/schema.d.ts +2 -0
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +1 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/data/migrate/apply.d.ts +14 -0
- package/dist/data/migrate/apply.d.ts.map +1 -1
- package/dist/data/migrate/apply.js +10 -2
- package/dist/data/migrate/apply.js.map +1 -1
- package/dist/data/migrate/provision-framework.d.ts.map +1 -1
- package/dist/data/migrate/provision-framework.js +6 -2
- package/dist/data/migrate/provision-framework.js.map +1 -1
- package/dist/infra/alerts/handle.d.ts.map +1 -1
- package/dist/infra/alerts/handle.js +11 -10
- package/dist/infra/alerts/handle.js.map +1 -1
- package/dist/test/auto-setup.js +2 -2
- package/dist/test/auto-setup.js.map +1 -1
- package/dist/test/index.d.ts +1 -0
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +1 -0
- package/dist/test/index.js.map +1 -1
- package/dist/test/plugin.d.ts +6 -1
- package/dist/test/plugin.d.ts.map +1 -1
- package/dist/test/plugin.js +12 -1
- package/dist/test/plugin.js.map +1 -1
- package/dist/test/reset.d.ts +35 -0
- package/dist/test/reset.d.ts.map +1 -0
- package/dist/test/reset.js +179 -0
- package/dist/test/reset.js.map +1 -0
- package/dist/test/setup.d.ts.map +1 -1
- package/dist/test/setup.js +89 -7
- package/dist/test/setup.js.map +1 -1
- package/dist/test/vitest.js +2 -2
- package/dist/test/vitest.js.map +1 -1
- package/dist/test/watch.d.ts +21 -0
- package/dist/test/watch.d.ts.map +1 -0
- package/dist/test/watch.js +81 -0
- package/dist/test/watch.js.map +1 -0
- package/package.json +1 -1
package/dist/test/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { setupTestDatabases, teardownTestDatabases, getTestDatabase, getTestDatabases, type TestDatabase, type SetupOptions } from './setup.js';
|
|
2
2
|
export { flushPendingDrops, TEST_DROP_LOG_PREFIX, TEST_REAP_LOG_PREFIX } from './setup.js';
|
|
3
3
|
export { SetupStalled, logSlowPhases, SETUP_PROGRESS_LOG_PREFIX, type SetupPhase, type SetupProgress, type SetupProgressListener, } from './progress.js';
|
|
4
|
+
export { armTestWatch, TEST_WATCH_LOG_PREFIX, type TestWatchTarget } from './watch.js';
|
|
4
5
|
export { beginTestIsolation, endTestIsolation, type TestContext, type TestDatabaseConnections } from './plugin.js';
|
|
5
6
|
export { setupPipeworkTests, useTestDb, useSetupDb, useTestInstance, useTestClient, type UseTestClientOptions, closeSetupConnections, SetupConnectionCloseError, teardownAfterAll, withFlow, type WithFlowOptions } from './vitest.js';
|
|
6
7
|
export { mockAuth, asTenant } from './auth.js';
|
package/dist/test/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AAC/I,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAC1F,OAAO,EACL,YAAY,EACZ,aAAa,EACb,yBAAyB,EACzB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,qBAAqB,GAC3B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AACtO,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AACzF,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,GAAG,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACrF,OAAO,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AAC/I,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAC1F,OAAO,EACL,YAAY,EACZ,aAAa,EACb,yBAAyB,EACzB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,qBAAqB,GAC3B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AACtF,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AACtO,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AACzF,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,GAAG,EAAE,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACrF,OAAO,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/test/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { setupTestDatabases, teardownTestDatabases, getTestDatabase, getTestDatabases } from './setup.js';
|
|
2
2
|
export { flushPendingDrops, TEST_DROP_LOG_PREFIX, TEST_REAP_LOG_PREFIX } from './setup.js';
|
|
3
3
|
export { SetupStalled, logSlowPhases, SETUP_PROGRESS_LOG_PREFIX, } from './progress.js';
|
|
4
|
+
export { armTestWatch, TEST_WATCH_LOG_PREFIX } from './watch.js';
|
|
4
5
|
export { beginTestIsolation, endTestIsolation } from './plugin.js';
|
|
5
6
|
export { setupPipeworkTests, useTestDb, useSetupDb, useTestInstance, useTestClient, closeSetupConnections, SetupConnectionCloseError, teardownAfterAll, withFlow } from './vitest.js';
|
|
6
7
|
export { mockAuth, asTenant } from './auth.js';
|
package/dist/test/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,EAAE,gBAAgB,EAAwC,MAAM,YAAY,CAAA;AAC/I,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAC1F,OAAO,EACL,YAAY,EACZ,aAAa,EACb,yBAAyB,GAI1B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAkD,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAA6B,qBAAqB,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,QAAQ,EAAwB,MAAM,aAAa,CAAA;AACtO,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,cAAc,EAA2B,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,QAAQ,IAAI,YAAY,EAA8B,MAAM,oBAAoB,CAAA;AACzF,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,GAAG,EAA+C,MAAM,oBAAoB,CAAA;AACrF,OAAO,EAAE,gBAAgB,EAAqG,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,EAAE,gBAAgB,EAAwC,MAAM,YAAY,CAAA;AAC/I,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAC1F,OAAO,EACL,YAAY,EACZ,aAAa,EACb,yBAAyB,GAI1B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAwB,MAAM,YAAY,CAAA;AACtF,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAkD,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAA6B,qBAAqB,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,QAAQ,EAAwB,MAAM,aAAa,CAAA;AACtO,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,cAAc,EAA2B,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,QAAQ,IAAI,YAAY,EAA8B,MAAM,oBAAoB,CAAA;AACzF,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,GAAG,EAA+C,MAAM,oBAAoB,CAAA;AACrF,OAAO,EAAE,gBAAgB,EAAqG,MAAM,oBAAoB,CAAA"}
|
package/dist/test/plugin.d.ts
CHANGED
|
@@ -11,7 +11,12 @@ export interface TestContext {
|
|
|
11
11
|
id: string;
|
|
12
12
|
db: TestDatabaseConnections;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
/** The slice of vitest's `task` the watch needs: `fullName` and the resolved `timeout`. */
|
|
15
|
+
export interface WatchedTask {
|
|
16
|
+
fullName: string;
|
|
17
|
+
timeout: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function beginTestIsolation(instance: Manifold, task?: WatchedTask): Promise<TestContext>;
|
|
15
20
|
export declare function endTestIsolation(instance: Manifold, ctx: TestContext): Promise<void>;
|
|
16
21
|
export declare function createTestContext(instance: Manifold, testConnections: TestContext, auth?: unknown, tenant?: string | null): Flow;
|
|
17
22
|
export declare function propagateTestSessionVars(instance: Manifold, testConnections: TestContext, tenant: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/test/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAErF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAIvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/test/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAErF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAIvD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAOnE,MAAM,WAAW,uBAAuB;IACtC,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAAA;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,uBAAuB,CAAA;CAC5B;AAcD,2FAA2F;AAC3F,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CA0CrG;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B1F;AAiCD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAMhI;AAED,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,QAAQ,EAClB,eAAe,EAAE,WAAW,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EACpB,aAAa,CAAC,EAAE,qBAAqB,GACpC;IAAE,OAAO,EAAE,kBAAkB,CAAC;IAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAM7D;AAED,OAAO,EAAE,YAAY,EAAE,CAAA"}
|
package/dist/test/plugin.js
CHANGED
|
@@ -11,8 +11,10 @@ import { collapseTransactionsToSavepoints } from './savepoints.js';
|
|
|
11
11
|
import { splitLargeInserts } from '../data/db/insert-split.js';
|
|
12
12
|
import { settleTraceWritesBounded } from '../trace/runtime/pending-writes.js';
|
|
13
13
|
import { createSystemHandle } from './system-channel.js';
|
|
14
|
+
import { armTestWatch } from './watch.js';
|
|
14
15
|
const contextConnections = new Map();
|
|
15
|
-
|
|
16
|
+
const contextWatches = new Map();
|
|
17
|
+
export async function beginTestIsolation(instance, task) {
|
|
16
18
|
const dbConnections = {};
|
|
17
19
|
const contextId = randomUUID();
|
|
18
20
|
const connections = new Map();
|
|
@@ -37,10 +39,19 @@ export async function beginTestIsolation(instance) {
|
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
contextConnections.set(contextId, connections);
|
|
42
|
+
if (task !== undefined) {
|
|
43
|
+
const databases = [...connections.keys()].flatMap(n => {
|
|
44
|
+
const d = getTestDatabase(n);
|
|
45
|
+
return d ? [{ database: d.database, url: d.url }] : [];
|
|
46
|
+
});
|
|
47
|
+
contextWatches.set(contextId, armTestWatch({ name: task.fullName, timeoutMs: task.timeout, databases }));
|
|
48
|
+
}
|
|
40
49
|
instance.isolation.markBegin();
|
|
41
50
|
return { id: contextId, db: dbConnections };
|
|
42
51
|
}
|
|
43
52
|
export async function endTestIsolation(instance, ctx) {
|
|
53
|
+
contextWatches.get(ctx.id)?.();
|
|
54
|
+
contextWatches.delete(ctx.id);
|
|
44
55
|
await settleTraceWritesBounded();
|
|
45
56
|
instance.pool.clearOverrides();
|
|
46
57
|
const connections = contextConnections.get(ctx.id);
|
package/dist/test/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/test/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAE,OAAO,EAA2B,MAAM,oCAAoC,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAA;AAE5C,OAAO,EAAE,eAAe,EAAqB,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAE,iBAAiB,IAAI,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,oDAAoD,CAAA;AAE3F,OAAO,EAAE,gCAAgC,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAA;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/test/plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const TEST_RESET_LOG_PREFIX = "pipework:test-reset";
|
|
2
|
+
export type FreshCloneReason = 'digest' | 'prepared-xact' | 'matview' | 'error';
|
|
3
|
+
export interface Baseline {
|
|
4
|
+
digest: string;
|
|
5
|
+
/** Main-fork bytes of every user table, partition and matview. */
|
|
6
|
+
sizes: Map<string, number>;
|
|
7
|
+
/** relfilenode of every matview: a refresh rewrites it. */
|
|
8
|
+
matviews: Map<string, string>;
|
|
9
|
+
/** Tables that already had storage at baseline: always cleared and reloaded. */
|
|
10
|
+
stored: Set<string>;
|
|
11
|
+
/** COPY text of every table that had rows. */
|
|
12
|
+
rows: Map<string, Buffer>;
|
|
13
|
+
/** last_value (null = not called) and start_value of every sequence. */
|
|
14
|
+
sequences: Map<string, {
|
|
15
|
+
last: string | null;
|
|
16
|
+
start: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
export type ResetOutcome = {
|
|
20
|
+
ok: true;
|
|
21
|
+
cleared: number;
|
|
22
|
+
reloaded: number;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
reason: FreshCloneReason;
|
|
26
|
+
};
|
|
27
|
+
/** Read the state a reset returns to. `url` is a superuser connection to the database. */
|
|
28
|
+
export declare function captureBaseline(url: string): Promise<Baseline>;
|
|
29
|
+
/**
|
|
30
|
+
* Return the database to its baseline. Call after the file's backends are gone.
|
|
31
|
+
* Refuses (ok: false) rather than reset a database whose state a reset cannot
|
|
32
|
+
* undo; nothing is changed in that case.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resetDatabase(url: string, baseline: Baseline, superuser?: boolean): Promise<ResetOutcome>;
|
|
35
|
+
//# sourceMappingURL=reset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/test/reset.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,qBAAqB,wBAAwB,CAAA;AAE1D,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,eAAe,GAAG,SAAS,GAAG,OAAO,CAAA;AAE/E,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,kEAAkE;IAClE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1B,2DAA2D;IAC3D,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,gFAAgF;IAChF,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACnB,8CAA8C;IAC9C,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzB,wEAAwE;IACxE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/D;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAA;AAyE3C,0FAA0F;AAC1F,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0BpE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,EAClB,SAAS,UAAO,GACf,OAAO,CAAC,YAAY,CAAC,CA6DvB"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import postgres from 'postgres';
|
|
2
|
+
// A worker keeps each per-file database across files and returns it to the
|
|
3
|
+
// state it had right after the clone. The baseline is captured once, before
|
|
4
|
+
// any file runs on it; a reset either restores that state exactly or reports
|
|
5
|
+
// that it cannot, and the caller then takes a fresh clone instead.
|
|
6
|
+
export const TEST_RESET_LOG_PREFIX = 'pipework:test-reset';
|
|
7
|
+
const USER_NAMESPACE = `n.nspname !~ '^pg_(toast_)?temp_' AND n.nspname <> 'information_schema' AND n.nspname <> 'pg_catalog'`;
|
|
8
|
+
// One md5 over the user-relevant catalogs. Storage fields (relfilenode,
|
|
9
|
+
// relpages, reltuples, relfrozenxid, ...) are left out: they move without any
|
|
10
|
+
// schema change. Objects below oid 16384 are the cluster's own.
|
|
11
|
+
const DIGEST_QUERY = `
|
|
12
|
+
SELECT md5(concat_ws('|',
|
|
13
|
+
(SELECT md5(coalesce(string_agg(row(n.oid, n.nspname, n.nspowner, n.nspacl)::text, E'\\n' ORDER BY n.oid), ''))
|
|
14
|
+
FROM pg_namespace n WHERE (n.oid >= 16384 OR n.nspname = 'public') AND ${USER_NAMESPACE}),
|
|
15
|
+
(SELECT md5(coalesce(string_agg(row(c.oid, c.relname, c.relnamespace, c.relkind, c.relpersistence, c.relowner, c.relacl, c.relrowsecurity, c.relforcerowsecurity, c.reloptions, c.relispartition, c.relpartbound::text)::text, E'\\n' ORDER BY c.oid), ''))
|
|
16
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.oid >= 16384 AND ${USER_NAMESPACE}),
|
|
17
|
+
(SELECT md5(coalesce(string_agg(row(a.attrelid, a.attnum, a.attname, a.atttypid, a.attnotnull, a.atthasdef, a.attisdropped, a.attidentity, a.attgenerated, a.attacl, a.atttypmod, a.attcollation)::text, E'\\n' ORDER BY a.attrelid, a.attnum), ''))
|
|
18
|
+
FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
19
|
+
WHERE a.attnum > 0 AND c.oid >= 16384 AND ${USER_NAMESPACE}),
|
|
20
|
+
(SELECT md5(coalesce(string_agg(row(d.oid, d.adrelid, d.adnum, pg_get_expr(d.adbin, d.adrelid))::text, E'\\n' ORDER BY d.oid), '')) FROM pg_attrdef d WHERE d.adrelid >= 16384),
|
|
21
|
+
(SELECT md5(coalesce(string_agg(row(k.oid, k.conname, k.contype, k.conrelid, k.convalidated, k.conkey, k.confrelid, k.confkey, k.conbin, k.condeferrable, k.condeferred, k.connoinherit)::text, E'\\n' ORDER BY k.oid), '')) FROM pg_constraint k WHERE k.oid >= 16384),
|
|
22
|
+
(SELECT md5(coalesce(string_agg(row(i.indexrelid, i.indrelid, i.indkey, i.indisunique, i.indisvalid, i.indisready, i.indpred, i.indexprs)::text, E'\\n' ORDER BY i.indexrelid), '')) FROM pg_index i WHERE i.indexrelid >= 16384),
|
|
23
|
+
(SELECT md5(coalesce(string_agg(row(p.oid, p.proname, p.pronamespace, p.proowner, p.proacl, p.prokind, md5(p.prosrc), p.proargtypes, p.prorettype, p.provolatile, p.prosecdef, p.proconfig, p.proisstrict)::text, E'\\n' ORDER BY p.oid), '')) FROM pg_proc p WHERE p.oid >= 16384),
|
|
24
|
+
(SELECT md5(coalesce(string_agg(row(t.oid, t.tgname, t.tgrelid, t.tgenabled, t.tgfoid, t.tgtype, t.tgattr, t.tgqual::text)::text, E'\\n' ORDER BY t.oid), '')) FROM pg_trigger t WHERE t.tgrelid >= 16384),
|
|
25
|
+
(SELECT md5(coalesce(string_agg(row(o.oid, o.polname, o.polrelid, o.polcmd, o.polroles, o.polqual, o.polwithcheck)::text, E'\\n' ORDER BY o.oid), '')) FROM pg_policy o),
|
|
26
|
+
(SELECT md5(coalesce(string_agg(row(r.oid, r.rulename, r.ev_class, r.ev_action)::text, E'\\n' ORDER BY r.oid), '')) FROM pg_rewrite r WHERE r.oid >= 16384),
|
|
27
|
+
(SELECT md5(coalesce(string_agg(row(y.oid, y.typname, y.typnamespace, y.typtype)::text, E'\\n' ORDER BY y.oid), ''))
|
|
28
|
+
FROM pg_type y JOIN pg_namespace n ON n.oid = y.typnamespace WHERE y.oid >= 16384 AND ${USER_NAMESPACE}),
|
|
29
|
+
(SELECT md5(coalesce(string_agg(row(s.seqrelid, s.seqtypid, s.seqstart, s.seqincrement, s.seqmax, s.seqmin, s.seqcache, s.seqcycle)::text, E'\\n' ORDER BY s.seqrelid), '')) FROM pg_sequence s WHERE s.seqrelid >= 16384),
|
|
30
|
+
(SELECT md5(coalesce(string_agg(row(e.oid, e.extname, e.extversion, e.extnamespace)::text, E'\\n' ORDER BY e.oid), '')) FROM pg_extension e),
|
|
31
|
+
(SELECT md5(coalesce(string_agg(row(u.oid, u.pubname)::text, E'\\n' ORDER BY u.oid), '')) FROM pg_publication u),
|
|
32
|
+
(SELECT md5(coalesce(string_agg(row(v.oid, v.evtname, v.evtevent, v.evtfoid, v.evtenabled)::text, E'\\n' ORDER BY v.oid), '')) FROM pg_event_trigger v),
|
|
33
|
+
(SELECT md5(coalesce(string_agg(row(h.inhrelid, h.inhparent, h.inhseqno)::text, E'\\n' ORDER BY h.inhrelid, h.inhparent), '')) FROM pg_inherits h),
|
|
34
|
+
(SELECT md5(coalesce(string_agg(row(m.oid, m.enumtypid, m.enumlabel, m.enumsortorder)::text, E'\\n' ORDER BY m.oid), '')) FROM pg_enum m),
|
|
35
|
+
(SELECT md5(coalesce(string_agg(row(q.oid, q.oprname, q.oprnamespace, q.oprleft, q.oprright, q.oprresult, q.oprcode)::text, E'\\n' ORDER BY q.oid), '')) FROM pg_operator q WHERE q.oid >= 16384),
|
|
36
|
+
(SELECT md5(coalesce(string_agg(row(w.oid, w.castsource, w.casttarget, w.castfunc, w.castcontext, w.castmethod)::text, E'\\n' ORDER BY w.oid), '')) FROM pg_cast w WHERE w.oid >= 16384),
|
|
37
|
+
(SELECT md5(coalesce(string_agg(row(l.oid, l.collname, l.collnamespace, l.collprovider, l.collisdeterministic, l.collcollate, l.collctype)::text, E'\\n' ORDER BY l.oid), '')) FROM pg_collation l WHERE l.oid >= 16384),
|
|
38
|
+
(SELECT md5(coalesce(string_agg(row(z.oid, z.stxrelid, z.stxname, z.stxkeys, z.stxkind)::text, E'\\n' ORDER BY z.oid), '')) FROM pg_statistic_ext z WHERE z.oid >= 16384),
|
|
39
|
+
(SELECT count(*)::text FROM pg_largeobject_metadata),
|
|
40
|
+
(SELECT md5(coalesce(string_agg(row(x.objoid, x.classoid, x.objsubid, x.description)::text, E'\\n' ORDER BY x.objoid, x.classoid, x.objsubid), '')) FROM pg_description x WHERE x.objoid >= 16384),
|
|
41
|
+
(SELECT md5(coalesce(string_agg(row(f.oid, f.defaclrole, f.defaclnamespace, f.defaclobjtype, f.defaclacl)::text, E'\\n' ORDER BY f.oid), '')) FROM pg_default_acl f),
|
|
42
|
+
(SELECT md5(coalesce(string_agg(row(b.setrole, b.setconfig)::text, E'\\n' ORDER BY b.setrole), ''))
|
|
43
|
+
FROM pg_db_role_setting b WHERE b.setdatabase = (SELECT oid FROM pg_database WHERE datname = current_database())),
|
|
44
|
+
(SELECT datacl::text FROM pg_database WHERE datname = current_database())
|
|
45
|
+
)) AS digest`;
|
|
46
|
+
const STORAGE_QUERY = `
|
|
47
|
+
SELECT format('%I.%I', n.nspname, c.relname) AS name, c.relkind::text AS kind,
|
|
48
|
+
c.relfilenode::text AS filenode, pg_relation_size(c.oid, 'main')::text AS size
|
|
49
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
50
|
+
WHERE c.relkind IN ('r', 'm') AND c.oid >= 16384 AND ${USER_NAMESPACE}`;
|
|
51
|
+
const SEQUENCE_QUERY = `
|
|
52
|
+
SELECT format('%I.%I', schemaname, sequencename) AS name, last_value::text AS last, start_value::text AS start
|
|
53
|
+
FROM pg_sequences WHERE schemaname !~ '^pg_(toast_)?temp_' AND schemaname NOT IN ('information_schema', 'pg_catalog')`;
|
|
54
|
+
function connect(url) {
|
|
55
|
+
return postgres(url, { max: 1, onnotice: () => { }, idle_timeout: 0, max_lifetime: 0 });
|
|
56
|
+
}
|
|
57
|
+
async function readCopy(sql, table) {
|
|
58
|
+
const stream = await sql.unsafe(`COPY ${table} TO STDOUT`).readable();
|
|
59
|
+
const chunks = [];
|
|
60
|
+
for await (const chunk of stream)
|
|
61
|
+
chunks.push(Buffer.from(chunk));
|
|
62
|
+
return Buffer.concat(chunks);
|
|
63
|
+
}
|
|
64
|
+
async function writeCopy(sql, table, data) {
|
|
65
|
+
const sink = await sql.unsafe(`COPY ${table} FROM STDIN`).writable();
|
|
66
|
+
await new Promise((resolve, reject) => {
|
|
67
|
+
sink.once('error', reject);
|
|
68
|
+
sink.once('finish', resolve);
|
|
69
|
+
sink.end(data);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Read the state a reset returns to. `url` is a superuser connection to the database. */
|
|
73
|
+
export async function captureBaseline(url) {
|
|
74
|
+
const sql = connect(url);
|
|
75
|
+
try {
|
|
76
|
+
const [digestRow] = await sql.unsafe(DIGEST_QUERY);
|
|
77
|
+
const storage = await sql.unsafe(STORAGE_QUERY);
|
|
78
|
+
const sizes = new Map();
|
|
79
|
+
const matviews = new Map();
|
|
80
|
+
const stored = new Set();
|
|
81
|
+
for (const row of storage) {
|
|
82
|
+
sizes.set(row.name, Number(row.size));
|
|
83
|
+
if (row.kind === 'm')
|
|
84
|
+
matviews.set(row.name, row.filenode);
|
|
85
|
+
else if (Number(row.size) > 0)
|
|
86
|
+
stored.add(row.name);
|
|
87
|
+
}
|
|
88
|
+
const rows = new Map();
|
|
89
|
+
for (const table of stored) {
|
|
90
|
+
const [has] = await sql.unsafe(`SELECT EXISTS (SELECT 1 FROM ${table}) AS has`);
|
|
91
|
+
if (has.has)
|
|
92
|
+
rows.set(table, await readCopy(sql, table));
|
|
93
|
+
}
|
|
94
|
+
const sequences = new Map();
|
|
95
|
+
for (const row of await sql.unsafe(SEQUENCE_QUERY)) {
|
|
96
|
+
sequences.set(row.name, { last: row.last, start: row.start });
|
|
97
|
+
}
|
|
98
|
+
return { digest: digestRow.digest, sizes, matviews, stored, rows, sequences };
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await sql.end();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Return the database to its baseline. Call after the file's backends are gone.
|
|
106
|
+
* Refuses (ok: false) rather than reset a database whose state a reset cannot
|
|
107
|
+
* undo; nothing is changed in that case.
|
|
108
|
+
*/
|
|
109
|
+
export async function resetDatabase(url, baseline, superuser = true) {
|
|
110
|
+
let sql = null;
|
|
111
|
+
try {
|
|
112
|
+
sql = connect(url);
|
|
113
|
+
const [prepared] = await sql.unsafe(`SELECT count(*)::int AS n FROM pg_prepared_xacts WHERE database = current_database()`);
|
|
114
|
+
if (prepared.n > 0)
|
|
115
|
+
return { ok: false, reason: 'prepared-xact' };
|
|
116
|
+
const [digestRow] = await sql.unsafe(DIGEST_QUERY);
|
|
117
|
+
if (digestRow.digest !== baseline.digest)
|
|
118
|
+
return { ok: false, reason: 'digest' };
|
|
119
|
+
const storage = await sql.unsafe(STORAGE_QUERY);
|
|
120
|
+
const dirty = [];
|
|
121
|
+
for (const row of storage) {
|
|
122
|
+
const base = baseline.sizes.get(row.name);
|
|
123
|
+
if (base === undefined)
|
|
124
|
+
return { ok: false, reason: 'digest' };
|
|
125
|
+
if (row.kind === 'm') {
|
|
126
|
+
if (Number(row.size) !== base || row.filenode !== baseline.matviews.get(row.name)) {
|
|
127
|
+
return { ok: false, reason: 'matview' };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (Number(row.size) > base || baseline.stored.has(row.name)) {
|
|
131
|
+
dirty.push(row.name);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (dirty.length > 0) {
|
|
135
|
+
await sql.begin(async (tx) => {
|
|
136
|
+
// Superuser: reload without firing triggers or checking FKs. Otherwise
|
|
137
|
+
// reload plainly, then refuse if a trigger wrote into a table that
|
|
138
|
+
// should be empty.
|
|
139
|
+
if (superuser)
|
|
140
|
+
await tx.unsafe(`SET LOCAL session_replication_role = replica`);
|
|
141
|
+
await tx.unsafe(`TRUNCATE ${dirty.join(', ')} CASCADE`);
|
|
142
|
+
for (const [table, data] of baseline.rows)
|
|
143
|
+
await writeCopy(tx, table, data);
|
|
144
|
+
if (!superuser && baseline.rows.size > 0) {
|
|
145
|
+
for (const row of await tx.unsafe(STORAGE_QUERY)) {
|
|
146
|
+
if (baseline.stored.has(row.name) || baseline.matviews.has(row.name))
|
|
147
|
+
continue;
|
|
148
|
+
if (Number(row.size) > (baseline.sizes.get(row.name) ?? 0))
|
|
149
|
+
throw new Error(`reload wrote to ${row.name}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const current = await sql.unsafe(SEQUENCE_QUERY);
|
|
155
|
+
const fixes = [];
|
|
156
|
+
for (const row of current) {
|
|
157
|
+
const base = baseline.sequences.get(row.name);
|
|
158
|
+
if (base === undefined)
|
|
159
|
+
return { ok: false, reason: 'digest' };
|
|
160
|
+
if (row.last === base.last)
|
|
161
|
+
continue;
|
|
162
|
+
const literal = `'${row.name.replace(/'/g, "''")}'::regclass`;
|
|
163
|
+
fixes.push(base.last === null
|
|
164
|
+
? `setval(${literal}, ${base.start}, false)`
|
|
165
|
+
: `setval(${literal}, ${base.last}, true)`);
|
|
166
|
+
}
|
|
167
|
+
if (fixes.length > 0)
|
|
168
|
+
await sql.unsafe(`SELECT ${fixes.join(', ')}`);
|
|
169
|
+
return { ok: true, cleared: dirty.length, reloaded: baseline.rows.size };
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return { ok: false, reason: 'error' };
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
if (sql !== null)
|
|
176
|
+
await sql.end().catch(() => { });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=reset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/test/reset.ts"],"names":[],"mappings":"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"}
|
package/dist/test/setup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/test/setup.ts"],"names":[],"mappings":"AACA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAUnD,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/test/setup.ts"],"names":[],"mappings":"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"}
|
package/dist/test/setup.js
CHANGED
|
@@ -12,11 +12,20 @@ import { testDbPrefix, appRolePrefix, templateDbPrefix } from './namespace.js';
|
|
|
12
12
|
import { templateFingerprint, templateInputs } from './fingerprint.js';
|
|
13
13
|
import { PhaseTracker, withStallWatchdog, DEFAULT_SETUP_STALL_MS, } from './progress.js';
|
|
14
14
|
import { describeError } from '../core/logging/error-chain.js';
|
|
15
|
+
import { captureBaseline, resetDatabase, TEST_RESET_LOG_PREFIX } from './reset.js';
|
|
15
16
|
// Marker table written inside a template as the last act of a successful build.
|
|
16
17
|
// A template database that exists without this row is the wreckage of a builder
|
|
17
18
|
// that died mid-migration: it must be rebuilt, never cloned.
|
|
18
19
|
const TEMPLATE_MARKER_TABLE = 'pipework_test_template';
|
|
19
20
|
const testDatabases = new Map();
|
|
21
|
+
const retained = new Map();
|
|
22
|
+
let databasePerFile = false;
|
|
23
|
+
let runAdminUrl = '';
|
|
24
|
+
let adminIsSuperuser = null;
|
|
25
|
+
async function readSuperuser() {
|
|
26
|
+
const rows = await adminClient `SELECT rolsuper FROM pg_roles WHERE rolname = current_user`;
|
|
27
|
+
return rows[0]?.['rolsuper'] === true;
|
|
28
|
+
}
|
|
20
29
|
const templates = new Map();
|
|
21
30
|
let adminClient = null;
|
|
22
31
|
// A random per-process fingerprint, embedded in every database and role name
|
|
@@ -31,9 +40,8 @@ function templateDbName(baseName, fingerprint) {
|
|
|
31
40
|
}
|
|
32
41
|
/** Log prefix for what the orphan reaper left alone (#391). */
|
|
33
42
|
export const TEST_REAP_LOG_PREFIX = 'pipework:test-reap';
|
|
34
|
-
// One name per CLONE, not per process. A
|
|
35
|
-
//
|
|
36
|
-
// the same worker must not ask for the name still queued for drop.
|
|
43
|
+
// One name per CLONE, not per process. A finished database may still be queued
|
|
44
|
+
// for drop (see `pendingDrops`), so the next clone must not ask for its name.
|
|
37
45
|
let cloneSeq = 0;
|
|
38
46
|
function testDbName(baseName) {
|
|
39
47
|
return `${testDbPrefix()}${baseName}_${cloneSeq++}_${process.pid}_${runNonce}`;
|
|
@@ -91,6 +99,7 @@ async function runSetup(instance, tracker) {
|
|
|
91
99
|
};
|
|
92
100
|
});
|
|
93
101
|
dropBatchSize = instance.config.raw.test?.dropBatchSize ?? DEFAULT_DROP_BATCH_SIZE;
|
|
102
|
+
databasePerFile = instance.config.raw.test?.databasePerFile ?? false;
|
|
94
103
|
const cloneGuards = resolveCloneGuards(instance.config.raw);
|
|
95
104
|
const firstDb = databases[0];
|
|
96
105
|
if (firstDb !== undefined) {
|
|
@@ -99,9 +108,19 @@ async function runSetup(instance, tracker) {
|
|
|
99
108
|
}
|
|
100
109
|
for (const plan of plans) {
|
|
101
110
|
const template = await ensureTemplate(instance, plan, tracker);
|
|
111
|
+
const key = JSON.stringify([plan.name, plan.dbConfig.url, template.templateDb, cloneGuards]);
|
|
112
|
+
const kept = databasePerFile ? undefined : await takeKept(key);
|
|
113
|
+
if (kept !== undefined) {
|
|
114
|
+
testDatabases.set(plan.name, kept.testDb);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
102
117
|
tracker.reached('clone-start', plan.name);
|
|
103
118
|
const testDb = await cloneTestDatabase(plan.name, plan.dbConfig, template, cloneGuards, n => tracker.reached('clone-start', plan.name, `retry ${n}`));
|
|
104
119
|
tracker.reached('clone-done', plan.name);
|
|
120
|
+
if (!databasePerFile) {
|
|
121
|
+
const baseline = await captureBaseline(withDatabase(runAdminUrl, testDb.database));
|
|
122
|
+
retained.set(testDb.database, { testDb, baseline, key, inUse: true, needsReset: false });
|
|
123
|
+
}
|
|
105
124
|
testDatabases.set(plan.name, testDb);
|
|
106
125
|
}
|
|
107
126
|
tracker.reached('ready');
|
|
@@ -115,6 +134,7 @@ async function runSetup(instance, tracker) {
|
|
|
115
134
|
// this process dies, and is never unlocked per file: the template outlives
|
|
116
135
|
// every file's database, so the lock that protects it must outlive them too.
|
|
117
136
|
async function prepareRun(adminUrl, liveTemplates, tracker) {
|
|
137
|
+
runAdminUrl = adminUrl;
|
|
118
138
|
if (adminClient === null) {
|
|
119
139
|
adminClient = postgres(adminUrl, { max: 1, onnotice: () => { } });
|
|
120
140
|
}
|
|
@@ -450,8 +470,10 @@ async function buildTemplate(instance, plan, tracker) {
|
|
|
450
470
|
const admin = drizzle(adminClient);
|
|
451
471
|
// A template database that exists at this point failed its readiness check —
|
|
452
472
|
// the leftovers of a builder that died. Drop and start over.
|
|
473
|
+
tracker.reached('template-build', plan.name, 'drop');
|
|
453
474
|
await closeTemplateClients(admin, templateDb);
|
|
454
|
-
await withTemplateRetry(() => admin.execute(sql.raw(`DROP DATABASE IF EXISTS ${templateIdent}`)), { prefix: TEMPLATE_DROP_LOG_PREFIX });
|
|
475
|
+
await withTemplateRetry(() => admin.execute(sql.raw(`DROP DATABASE IF EXISTS ${templateIdent}`)), { prefix: TEMPLATE_DROP_LOG_PREFIX, onRetry: n => tracker.reached('template-build', plan.name, `drop retry ${n}`) });
|
|
476
|
+
tracker.reached('template-build', plan.name, 'create');
|
|
455
477
|
await admin.execute(sql.raw(`CREATE DATABASE ${templateIdent}`));
|
|
456
478
|
// #325: a test database is dropped or rolled back — durable commits are pure
|
|
457
479
|
// overhead. `synchronous_commit` is a per-database setting and does not copy
|
|
@@ -471,6 +493,7 @@ async function buildTemplate(instance, plan, tracker) {
|
|
|
471
493
|
? withDatabase(dbConfig.testOwnerUrl, templateDb)
|
|
472
494
|
: undefined;
|
|
473
495
|
if (migrateUrlStr !== undefined) {
|
|
496
|
+
tracker.reached('template-build', plan.name, 'owner');
|
|
474
497
|
const ownerIdent = quoteIdentifier(new URL(migrateUrlStr).username, 'role');
|
|
475
498
|
await admin.execute(sql.raw(`GRANT CONNECT ON DATABASE ${templateIdent} TO ${ownerIdent}`));
|
|
476
499
|
// PG15+: `public` belongs to the database owner and grants CREATE to
|
|
@@ -480,6 +503,7 @@ async function buildTemplate(instance, plan, tracker) {
|
|
|
480
503
|
// Door policies in the migrations say `TO <platform role>`, so the role must
|
|
481
504
|
// exist before they run.
|
|
482
505
|
if (dbConfig.rls?.platformRole !== undefined) {
|
|
506
|
+
tracker.reached('template-build', plan.name, 'platform role');
|
|
483
507
|
await provisionPlatformRole(adminClient, dbConfig.rls.platformRole, new URL(templateUrlStr).username);
|
|
484
508
|
}
|
|
485
509
|
if (dbConfig.extensions.length > 0) {
|
|
@@ -489,6 +513,7 @@ async function buildTemplate(instance, plan, tracker) {
|
|
|
489
513
|
}
|
|
490
514
|
}
|
|
491
515
|
if (dbConfig.migrations !== undefined) {
|
|
516
|
+
tracker.reached('template-migrate', plan.name, 'start');
|
|
492
517
|
await applyMigrations({
|
|
493
518
|
migrationsFolder: dbConfig.migrations,
|
|
494
519
|
migrationsTable: dbConfig.migrationTable,
|
|
@@ -498,6 +523,10 @@ async function buildTemplate(instance, plan, tracker) {
|
|
|
498
523
|
// A migration stack is the one step long enough to look like a stall.
|
|
499
524
|
// Each file applied is an advance. Issue #380.
|
|
500
525
|
onMigration: (tag, index, total) => tracker.reached('template-migrate', plan.name, `${index}/${total} ${tag}`),
|
|
526
|
+
// Statements inside a long file, and the steps before the first file,
|
|
527
|
+
// are advances too. Issue #871.
|
|
528
|
+
onStatement: (tag, statement, statements, index, total) => tracker.reached('template-migrate', plan.name, `${index}/${total} ${tag} stmt ${statement}/${statements}`),
|
|
529
|
+
onPrepare: detail => tracker.reached('template-migrate', plan.name, `prepare ${detail}`),
|
|
501
530
|
});
|
|
502
531
|
}
|
|
503
532
|
// Framework-owned partitioned tables (trace family) are kept out of app
|
|
@@ -516,7 +545,7 @@ async function buildTemplate(instance, plan, tracker) {
|
|
|
516
545
|
// of CREATEs, each of which can wait on the disk. Issue #806.
|
|
517
546
|
onProgress: (step, detail) => step === 'partition'
|
|
518
547
|
? tracker.reached('template-partitions', plan.name, detail)
|
|
519
|
-
: tracker.reached('template-framework', plan.name, step),
|
|
548
|
+
: tracker.reached('template-framework', plan.name, detail === undefined ? step : `${step} ${detail}`),
|
|
520
549
|
});
|
|
521
550
|
const grantBeat = (detail) => tracker.reached('template-grants', plan.name, detail);
|
|
522
551
|
grantBeat('role');
|
|
@@ -863,6 +892,7 @@ async function closeRunConnections() {
|
|
|
863
892
|
if (adminClient !== null) {
|
|
864
893
|
await adminClient.end();
|
|
865
894
|
adminClient = null;
|
|
895
|
+
adminIsSuperuser = null;
|
|
866
896
|
}
|
|
867
897
|
if (lockClient !== null) {
|
|
868
898
|
await lockClient.end();
|
|
@@ -907,10 +937,22 @@ export async function teardownTestDatabases() {
|
|
|
907
937
|
return;
|
|
908
938
|
}
|
|
909
939
|
const admin = drizzle(adminClient);
|
|
940
|
+
// Kept databases are reset by the next setup that takes them, not here: a
|
|
941
|
+
// process that ends after one file never pays for it. Without an IPC channel
|
|
942
|
+
// nothing follows this teardown (see below): drop instead.
|
|
943
|
+
const canReuse = typeof process.send === 'function';
|
|
910
944
|
for (const [, testDb] of testDatabases) {
|
|
911
945
|
try {
|
|
912
946
|
await admin.execute(sql `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ${testDb.database} AND pid != pg_backend_pid()`);
|
|
913
|
-
|
|
947
|
+
const keep = retained.get(testDb.database);
|
|
948
|
+
if (keep !== undefined && canReuse) {
|
|
949
|
+
keep.inUse = false;
|
|
950
|
+
keep.needsReset = true;
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
retained.delete(testDb.database);
|
|
954
|
+
pendingDrops.push(testDb.database);
|
|
955
|
+
}
|
|
914
956
|
}
|
|
915
957
|
catch {
|
|
916
958
|
// Best effort cleanup — a leftover clone is reaped by the next run.
|
|
@@ -927,7 +969,43 @@ export async function teardownTestDatabases() {
|
|
|
927
969
|
await closeRunConnections();
|
|
928
970
|
}
|
|
929
971
|
}
|
|
930
|
-
|
|
972
|
+
/**
|
|
973
|
+
* An idle kept database for this key, reset and marked in use. One whose reset
|
|
974
|
+
* fails is queued for drop and the next candidate tried; none left means the
|
|
975
|
+
* caller clones.
|
|
976
|
+
*/
|
|
977
|
+
async function takeKept(key) {
|
|
978
|
+
for (const keep of retained.values()) {
|
|
979
|
+
if (keep.key !== key || keep.inUse)
|
|
980
|
+
continue;
|
|
981
|
+
if (keep.needsReset && !await resetKept(keep)) {
|
|
982
|
+
retained.delete(keep.testDb.database);
|
|
983
|
+
pendingDrops.push(keep.testDb.database);
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
keep.needsReset = false;
|
|
987
|
+
keep.inUse = true;
|
|
988
|
+
return keep;
|
|
989
|
+
}
|
|
990
|
+
return undefined;
|
|
991
|
+
}
|
|
992
|
+
/** Reset a kept database to its baseline and say so. False: it needs a fresh clone. */
|
|
993
|
+
async function resetKept(keep) {
|
|
994
|
+
const start = Date.now();
|
|
995
|
+
adminIsSuperuser ??= await readSuperuser();
|
|
996
|
+
const outcome = await resetDatabase(withDatabase(runAdminUrl, keep.testDb.database), keep.baseline, adminIsSuperuser);
|
|
997
|
+
const db = keep.testDb.database;
|
|
998
|
+
console.warn(outcome.ok
|
|
999
|
+
? `${TEST_RESET_LOG_PREFIX} db=${db} cleared=${outcome.cleared} reloaded=${outcome.reloaded} elapsed_ms=${Date.now() - start}`
|
|
1000
|
+
: `${TEST_RESET_LOG_PREFIX} db=${db} fresh-clone reason=${outcome.reason}`);
|
|
1001
|
+
return outcome.ok;
|
|
1002
|
+
}
|
|
1003
|
+
// Per-file databases that are done with, waiting to be dropped together.
|
|
1004
|
+
//
|
|
1005
|
+
// A worker keeps its databases across files (`test.databasePerFile` off), so
|
|
1006
|
+
// what reaches this queue is a database whose reset fell back to a fresh clone,
|
|
1007
|
+
// every kept database at worker shutdown, and every database with
|
|
1008
|
+
// `databasePerFile`.
|
|
931
1009
|
//
|
|
932
1010
|
// Every `DROP DATABASE` forces an IMMEDIATE cluster-wide checkpoint and waits
|
|
933
1011
|
// for it — `RequestCheckpoint(CHECKPOINT_IMMEDIATE | FORCE | WAIT)` in
|
|
@@ -953,6 +1031,10 @@ export const TEST_DROP_LOG_PREFIX = 'pipework:test-drop';
|
|
|
953
1031
|
* rather than failing the flush for the rest.
|
|
954
1032
|
*/
|
|
955
1033
|
export async function flushPendingDrops(reason, log = message => console.warn(message), now = () => Date.now()) {
|
|
1034
|
+
if (reason === 'shutdown') {
|
|
1035
|
+
pendingDrops.push(...retained.keys());
|
|
1036
|
+
retained.clear();
|
|
1037
|
+
}
|
|
956
1038
|
if (adminClient === null || pendingDrops.length === 0)
|
|
957
1039
|
return 0;
|
|
958
1040
|
const admin = drizzle(adminClient);
|