Skip to content

Commit 4cea285

Browse files
QVAC-18790 infra: scope e2e bootstrap to deps required by filtered tests (#1991)
* infra[notask]: scope e2e bootstrap to deps required by filtered tests Wire up the new producer-driven bootstrap contract from @tetherto/qvac-test-suite 0.6.3 so the SDK e2e harness stops pre-loading every registered model when a --filter / --suite is in play. - ResourceManager.downloadAllOnce now accepts an optional `allowedDeps: ReadonlySet<string>` to scope downloads + pre-loads to a subset of registered models; without it the manager keeps the legacy "warm everything" behavior. - New `collect-test-deps` helper extracts the metadata.dependency / metadata.dependencies keys from a TestDefinition[] and dedupes them. - desktop/mobile bootstrap callbacks now take the `filteredTests?: TestDefinition[]` argument the framework supplies via register-ack, fed through collectTestDeps into downloadAllOnce. Locally cuts iOS bootstrap from ~25min to ~20s and desktop from ~10min (warm cache) to ~10s for `--filter transcription,parakeet`. Bumps @tetherto/qvac-test-suite to a temporary preview build of qvac-test-suite#70 (0.6.3-tmp.pr-70.runid-...); will be moved to ^0.6.3 once that PR lands and a real release is cut. Co-authored-by: Cursor <cursoragent@cursor.com> * test[skiplog]: update test-framework-version to latest * test[skiplog]: update test-framework-version to latest --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent faa1261 commit 4cea285

5 files changed

Lines changed: 72 additions & 12 deletions

File tree

packages/sdk/tests-qvac/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
},
1717
"dependencies": {
1818
"@qvac/sdk": "file:..",
19-
"@tetherto/qvac-test-suite": "^0.6.2",
19+
"@tetherto/qvac-test-suite": "^0.6.3",
2020
"mqtt": "^5.14.1",
2121
"react-native": "0.81.5",
2222
"react-native-bare-kit": "0.12.3"

packages/sdk/tests-qvac/tests/desktop/consumer.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createExecutor } from "@tetherto/qvac-test-suite";
1+
import { createExecutor, type TestDefinition } from "@tetherto/qvac-test-suite";
22
import {
33
profiler,
44
LLAMA_3_2_1B_INST_Q4_0,
@@ -45,6 +45,7 @@ import {
4545
} from "@qvac/sdk";
4646
import * as path from "node:path";
4747
import { ResourceManager } from "../shared/resource-manager.js";
48+
import { collectTestDeps } from "../shared/collect-test-deps.js";
4849
import { ModelLoadingExecutor } from "../shared/executors/model-loading-executor.js";
4950
import { CompletionExecutor } from "../shared/executors/completion-executor.js";
5051
import { ToolsExecutor } from "../shared/executors/tools-executor.js";
@@ -359,7 +360,7 @@ resources.define("upscaler", {
359360
},
360361
});
361362

362-
export async function bootstrap() {
363+
export async function bootstrap(filteredTests?: TestDefinition[]) {
363364
// Point the SDK at the committed e2e fixture unless the developer
364365
// already provided their own qvac.config.json / QVAC_CONFIG_PATH.
365366
// This exercises the registryDownloadMaxRetries + registryStreamTimeoutMs
@@ -370,7 +371,10 @@ export async function bootstrap() {
370371
"fixtures/qvac.config.e2e.json",
371372
);
372373
}
373-
await resources.downloadAllOnce(console.log);
374+
// `filteredTests` (when present) is the producer's post-filter test list
375+
// delivered via register-ack; absence keeps the legacy "warm everything" path.
376+
const allowedDeps = filteredTests ? collectTestDeps(filteredTests) : undefined;
377+
await resources.downloadAllOnce(console.log, { allowedDeps });
374378
};
375379

376380
export const executor = createExecutor({

packages/sdk/tests-qvac/tests/mobile/consumer.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Platform } from "react-native";
22
import { createExecutor, SkipExecutor } from "@tetherto/qvac-test-suite/mobile";
3+
import type { TestDefinition } from "@tetherto/qvac-test-suite";
34
import {
45
profiler,
56
LLAMA_3_2_1B_INST_Q4_0,
@@ -41,6 +42,7 @@ import {
4142
SD_V2_1_1B_Q8_0,
4243
} from "@qvac/sdk";
4344
import { ResourceManager } from "../shared/resource-manager.js";
45+
import { collectTestDeps } from "../shared/collect-test-deps.js";
4446
import { resolveBundledAssetUri } from "./asset-uri.js";
4547
import { ModelLoadingExecutor } from "../shared/executors/model-loading-executor.js";
4648
import { CompletionExecutor } from "../shared/executors/completion-executor.js";
@@ -341,8 +343,11 @@ function skipTests(testIds: string[], reason: string) {
341343
return new SkipExecutor(new RegExp(`^(${testIds.join("|")})$`), reason);
342344
}
343345

344-
export async function bootstrap() {
345-
await resources.downloadAllOnce(console.log);
346+
export async function bootstrap(filteredTests?: TestDefinition[]) {
347+
// `filteredTests` (when present) is the producer's post-filter test list
348+
// delivered via register-ack; absence keeps the legacy "warm everything" path.
349+
const allowedDeps = filteredTests ? collectTestDeps(filteredTests) : undefined;
350+
await resources.downloadAllOnce(console.log, { allowedDeps });
346351
}
347352

348353
export const executor = createExecutor({
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { TestDefinition } from "@tetherto/qvac-test-suite";
2+
3+
/**
4+
* Collect ResourceManager dep keys declared in test `metadata`:
5+
* - `metadata.dependency: "llm"` — single
6+
* - `metadata.dependencies: ["llm", "ocr"]` — multi
7+
* The sentinel `"none"` (used by tests that intentionally pre-load nothing)
8+
* is dropped. Result is feed-ready for `downloadAllOnce({ allowedDeps })`.
9+
*/
10+
export function collectTestDeps(tests: readonly TestDefinition[]): Set<string> {
11+
const deps = new Set<string>();
12+
for (const test of tests) {
13+
const meta = test.metadata as Record<string, unknown> | undefined;
14+
if (!meta) continue;
15+
16+
const single = meta["dependency"];
17+
if (typeof single === "string" && single.length > 0 && single !== "none") {
18+
deps.add(single);
19+
}
20+
21+
const multi = meta["dependencies"];
22+
if (Array.isArray(multi)) {
23+
for (const dep of multi) {
24+
if (typeof dep === "string" && dep.length > 0 && dep !== "none") {
25+
deps.add(dep);
26+
}
27+
}
28+
}
29+
}
30+
return deps;
31+
}

packages/sdk/tests-qvac/tests/shared/resource-manager.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,35 @@ export class ResourceManager {
6464
this.definitions.set(dep, definition);
6565
}
6666

67-
async downloadAllOnce(log?: (msg: string) => void): Promise<void> {
67+
/**
68+
* Pre-download (and pre-load+unload for `preLoadUnload` entries) every
69+
* registered model. `options.allowedDeps`, if given, narrows the work
70+
* to that subset; omit it to keep the legacy "warm everything" path.
71+
* Idempotent on `downloaded` — pass the full filter on the first call;
72+
* later calls with a different filter are a no-op.
73+
*/
74+
async downloadAllOnce(
75+
log?: (msg: string) => void,
76+
options: { allowedDeps?: ReadonlySet<string> } = {},
77+
): Promise<void> {
6878
if (this.downloaded) return;
6979
this.downloaded = true;
7080

71-
const entries = Array.from(this.definitions.entries()).filter(
72-
([, def]) => !def.skipPreDownload,
73-
);
74-
const preLoadUnload = Array.from(this.definitions.entries()).filter(([, def]) => def.preLoadUnload);
75-
const skipped = this.definitions.size - entries.length;
81+
const allowed = options.allowedDeps;
82+
const isAllowed = (dep: string) => allowed === undefined || allowed.has(dep);
83+
84+
const allDefinitions = Array.from(this.definitions.entries());
85+
const entries = allDefinitions.filter(([dep, def]) => !def.skipPreDownload && isAllowed(dep));
86+
const preLoadUnload = allDefinitions.filter(([dep, def]) => def.preLoadUnload && isAllowed(dep));
87+
88+
if (allowed !== undefined) {
89+
const filteredOut = allDefinitions.filter(([dep]) => !isAllowed(dep)).length;
90+
log?.(
91+
`🎯 Bootstrap dep-filter active: keeping ${allowed.size} dep(s); ${filteredOut} of ${allDefinitions.length} defined excluded`,
92+
);
93+
}
94+
95+
const skipped = allDefinitions.filter(([dep, def]) => def.skipPreDownload && isAllowed(dep)).length;
7696
if (skipped > 0) log?.(`⏭️ Skipping ${skipped} models marked skipPreDownload`);
7797

7898
log?.(`📥 Downloading ${entries.length} models in parallel...`);

0 commit comments

Comments
 (0)