@specific.dev/spectest 0.92.0 → 0.93.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/README.md +7 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/k3s.d.ts +26 -4
- package/dist/components/k3s.js +105 -61
- package/dist/daemon.js +30 -97
- package/dist/harness/built-image.d.ts +16 -0
- package/dist/harness/built-image.js +57 -0
- package/dist/harness/certificate.d.ts +15 -0
- package/dist/harness/certificate.js +67 -0
- package/dist/harness/k3s-image-loader.js +2 -53
- package/dist/harness/registry-seed.d.ts +12 -0
- package/dist/harness/registry-seed.js +61 -0
- package/dist/harness/service-graph.d.ts +3 -0
- package/dist/harness/service-graph.js +19 -9
- package/dist/index.d.ts +6 -6
- package/package.json +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/k3s-registry.test.ts +50 -0
- package/src/components/k3s.ts +114 -52
- package/src/daemon.ts +30 -108
- package/src/harness/built-image.ts +53 -0
- package/src/harness/certificate.test.ts +31 -0
- package/src/harness/certificate.ts +73 -0
- package/src/harness/eval-typecheck.test.ts +28 -0
- package/src/harness/fetch.test.ts +28 -0
- package/src/harness/k3s-image-loader.ts +2 -47
- package/src/harness/registry-seed.test.ts +60 -0
- package/src/harness/registry-seed.ts +61 -0
- package/src/harness/service-graph.test.ts +31 -1
- package/src/harness/service-graph.ts +23 -0
- package/src/index.ts +12 -8
|
@@ -1,9 +1,39 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import { topologicalOrder, validateServiceGraph } from "./service-graph";
|
|
3
|
+
import { startServices, topologicalOrder, validateServiceGraph } from "./service-graph";
|
|
4
4
|
|
|
5
5
|
const svc = (name: string, dependsOn?: string[]) => ({ name, dependsOn });
|
|
6
6
|
|
|
7
|
+
test("bootstrap waits for sibling hooks after a failure and never starts failed dependents", async () => {
|
|
8
|
+
let release!: () => void;
|
|
9
|
+
const gate = new Promise<void>(resolve => { release = resolve; });
|
|
10
|
+
const started: string[] = [];
|
|
11
|
+
let completed = false;
|
|
12
|
+
let settled = false;
|
|
13
|
+
const failed = new Error("setup failed");
|
|
14
|
+
const result = startServices([svc("bad"), svc("slow"), svc("child", ["bad"])], async ({ name }) => {
|
|
15
|
+
started.push(name);
|
|
16
|
+
if (name === "bad") throw failed;
|
|
17
|
+
if (name === "slow") { await gate; completed = true; }
|
|
18
|
+
}).then(() => { settled = true; return undefined; }, error => { settled = true; return error; });
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
20
|
+
expect(started).toEqual(["bad", "slow"]);
|
|
21
|
+
expect(settled).toBe(false);
|
|
22
|
+
release();
|
|
23
|
+
const error = await result;
|
|
24
|
+
expect(completed).toBe(true);
|
|
25
|
+
expect(error instanceof AggregateError ? error.errors : [error]).toContain(failed);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("shared dependencies complete setup once before either dependent starts", async () => {
|
|
29
|
+
const started: string[] = [];
|
|
30
|
+
await startServices([svc("a", ["db"]), svc("b", ["db"]), svc("db")], async ({ name }) => {
|
|
31
|
+
await Promise.resolve();
|
|
32
|
+
started.push(name);
|
|
33
|
+
});
|
|
34
|
+
expect(started).toEqual(["db", "a", "b"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
7
37
|
describe("validateServiceGraph", () => {
|
|
8
38
|
test("returns a name→service map for a clean graph", () => {
|
|
9
39
|
const map = validateServiceGraph([svc("db"), svc("api", ["db"])]);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { waitForAll } from "./concurrency.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The `dependsOn` graph: validation, and the traversal order bring-up
|
|
3
5
|
* uses.
|
|
@@ -95,3 +97,24 @@ export function topologicalOrder<T extends GraphNode>(services: readonly T[]): T
|
|
|
95
97
|
for (const s of services) visit(s.name);
|
|
96
98
|
return out;
|
|
97
99
|
}
|
|
100
|
+
|
|
101
|
+
/** Start each service after its dependencies finish setup. Drain all branches
|
|
102
|
+
* before reporting failure so teardown cannot race containers or hooks. */
|
|
103
|
+
export async function startServices<T extends GraphNode>(
|
|
104
|
+
services: readonly T[],
|
|
105
|
+
startOne: (svc: T) => Promise<void>,
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
const byName = validateServiceGraph(services);
|
|
108
|
+
const started = new Map<string, Promise<void>>();
|
|
109
|
+
const start = (svc: T): Promise<void> => {
|
|
110
|
+
const existing = started.get(svc.name);
|
|
111
|
+
if (existing) return existing;
|
|
112
|
+
const pending = (async () => {
|
|
113
|
+
await Promise.all((svc.dependsOn ?? []).map(name => start(byName.get(name)!)));
|
|
114
|
+
await startOne(svc);
|
|
115
|
+
})();
|
|
116
|
+
started.set(svc.name, pending);
|
|
117
|
+
return pending;
|
|
118
|
+
};
|
|
119
|
+
await waitForAll(services.map(start));
|
|
120
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -796,13 +796,16 @@ export interface ServiceDefinition<
|
|
|
796
796
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
797
797
|
export type ServicesMap = Record<string, ServiceDefinition<any>>;
|
|
798
798
|
|
|
799
|
-
/** Awaited return type of a service's
|
|
800
|
-
*
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
helpers
|
|
804
|
-
|
|
805
|
-
|
|
799
|
+
/** Awaited return type of a service's helpers factory. Check key presence
|
|
800
|
+
* before removing optionality: annotations keep `helpers?`, while plain
|
|
801
|
+
* service literals with no factory must stay out of ctx.svc. */
|
|
802
|
+
type HelpersOf<D> = "helpers" extends keyof D
|
|
803
|
+
? [NonNullable<D["helpers"]>] extends [never]
|
|
804
|
+
? never
|
|
805
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
806
|
+
: NonNullable<D["helpers"]> extends (...args: any[]) => infer R
|
|
807
|
+
? Awaited<R>
|
|
808
|
+
: never
|
|
806
809
|
: never;
|
|
807
810
|
|
|
808
811
|
/**
|
|
@@ -1247,7 +1250,8 @@ export type BuildImage = (
|
|
|
1247
1250
|
) & {
|
|
1248
1251
|
/** Full image reference, e.g. "k8s.internal:5000/runtime:spectest".
|
|
1249
1252
|
* Optional for a service; required for a Kubernetes build. This names a
|
|
1250
|
-
* local image
|
|
1253
|
+
* local image. In k3s, builds targeting `registry.host` are also published
|
|
1254
|
+
* to that component's registry; other references are only preloaded. */
|
|
1251
1255
|
reference?: string;
|
|
1252
1256
|
};
|
|
1253
1257
|
|