Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/sdk/tests-qvac/tests/desktop/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { ModelLoadingExecutor } from "../shared/executors/model-loading-executor
import { CompletionExecutor } from "../shared/executors/completion-executor.js";
import { ToolsExecutor } from "../shared/executors/tools-executor.js";
import { TranslationExecutor } from "../shared/executors/translation-executor.js";
import { TranslationBergamotCacheExecutor } from "../shared/executors/translation-bergamot-cache-executor.js";
import { ShardedModelExecutor } from "../shared/executors/sharded-model-executor.js";
import { HttpEmbeddingExecutor } from "../shared/executors/http-embedding-executor.js";
import { KvCacheExecutor } from "../shared/executors/kv-cache-executor.js";
Expand Down Expand Up @@ -390,6 +391,8 @@ export const executor = createExecutor({
new ErrorExecutor(resources),
new ToolsExecutor(resources),

// Must precede TranslationExecutor — patterns overlap, dispatch is first-match-wins.
new TranslationBergamotCacheExecutor(),
new TranslationExecutor(resources),
new ShardedModelExecutor(resources),
new OcrExecutor(resources),
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/tests-qvac/tests/mobile/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,10 @@ export const executor = createExecutor({
new SkipExecutor(/^multi-gpu-/, "Multi-GPU tests disabled on mobile (not supported on single-GPU devices)"),
new SkipExecutor(/^tools-(?!simple-function$|no-function-match$)/, "Tools test disabled on mobile"),
new SkipExecutor(/^(diffusion-|addon-logging-diffusion$)/, "SD v2.1 1B Q8_0 cold-load is too heavy for Device Farm devices (iOS variable 5–15min, Android blocks JS thread >300s and trips heartbeat)"),
new SkipExecutor(
/^translation-bergamot-.+-cache-reload$/,
"Server-side Bare code path, identical across platforms — desktop coverage is source of truth",
),
// suspend() hangs the test runner on mobile (the lifecycle coordinator
// pauses MQTT/network ops and never resumes within the test timeout).
// Only resume-idempotent is safe -- it does not call suspend().
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
loadModel,
unloadModel,
BERGAMOT_FR_EN,
BERGAMOT_EN_FR,
} from "@qvac/sdk";
import {
BaseExecutor,
type TestResult,
} from "@tetherto/qvac-test-suite";
import { translationBergamotCacheTests } from "../../translation-bergamot-cache-tests.js";

interface BergamotCacheParams {
pair: string;
}

interface ProgressEvent {
downloaded: number;
total: number;
percentage: number;
downloadKey: string;
}

const PAIRS = {
"fr-en": { descriptor: BERGAMOT_FR_EN, from: "fr", to: "en" },
"en-fr": { descriptor: BERGAMOT_EN_FR, from: "en", to: "fr" },
} as const;

type Pair = (typeof PAIRS)[keyof typeof PAIRS];

async function loadAndUnload(
pair: Pair,
onProgress?: (p: ProgressEvent) => void,
): Promise<void> {
const id = await loadModel({
modelSrc: pair.descriptor as never,
modelType: "nmt",
modelConfig: { engine: "Bergamot", from: pair.from, to: pair.to },
...(onProgress && { onProgress }),
});
await unloadModel({ modelId: id });
}

function summarizePartials(events: ProgressEvent[]) {
const partials = events.filter((e) => e.total > 0 && e.downloaded < e.total);
return { partials, touchedKeys: new Set(partials.map((e) => e.downloadKey)) };
}

export class TranslationBergamotCacheExecutor extends BaseExecutor<
typeof translationBergamotCacheTests
> {
pattern = /^translation-bergamot-.+-cache-reload$/;

protected handlers = Object.fromEntries(
translationBergamotCacheTests.map((t) => [t.testId, this.run.bind(this)]),
) as never;

// Round 1 warms the cache; Round 2 must be a pure cache hit. We detect
// re-downloads cross-platform via onProgress: a true cache hit emits at
// most one final 100% event per file, while a real download emits many
// partial-percentage events.
async run(params: BergamotCacheParams): Promise<TestResult> {
const pair = PAIRS[params.pair as keyof typeof PAIRS];
if (!pair) {
return { passed: false, output: `Unknown pair "${params.pair}"` };
}

try {
await loadAndUnload(pair);
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
return { passed: false, output: `Round 1 (warm cache) failed: ${msg}` };
}

const round2: ProgressEvent[] = [];
try {
await loadAndUnload(pair, (p) => round2.push(p));
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
return { passed: false, output: `Round 2 (cache hit) failed: ${msg}` };
}

const { partials, touchedKeys } = summarizePartials(round2);
if (touchedKeys.size > 0) {
const sample = partials
.slice(0, 3)
.map((e) => `${e.downloadKey}@${e.percentage.toFixed(0)}%`)
.join(", ");
return {
passed: false,
output: `Cache invalidation regression: Round 2 re-downloaded ${touchedKeys.size} file(s), ${partials.length} partial events. First: ${sample}`,
};
}

return {
passed: true,
output: `Cache hit on Round 2 — ${round2.length} cache-hit notification(s), no partial downloads`,
};
}
}
4 changes: 4 additions & 0 deletions packages/sdk/tests-qvac/tests/test-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { embeddingTests } from "./embedding-tests.js";
import { ragTests } from "./rag-tests.js";
import { translationIndicTransTests } from "./translation-indictrans-tests.js";
import { translationBergamotTests } from "./translation-bergamot-tests.js";
import { translationBergamotCacheTests } from "./translation-bergamot-cache-tests.js";
import { translationLlmTests } from "./translation-llm-tests.js";
import { translationSalamandraTests } from "./translation-salamandra-tests.js";
import { translationAfriquegemmaTests } from "./translation-afriquegemma-tests.js";
Expand Down Expand Up @@ -210,6 +211,9 @@ export const tests = [
// Translation: Bergamot (EN→FR, EN→ES)
...translationBergamotTests,

// Translation: Bergamot cache reload regression
...translationBergamotCacheTests,

// Translation: LLM (open-vocabulary via from/to)
...translationLlmTests,

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { TestDefinition } from "@tetherto/qvac-test-suite";

interface BergamotCacheParams {
pair: string;
}

const cacheReloadTest = (pair: string): TestDefinition => ({
testId: `translation-bergamot-${pair}-cache-reload`,
params: { pair } satisfies BergamotCacheParams,
expectation: { validation: "function", fn: () => true },
metadata: {
category: "translation-bergamot-cache",
dependency: "none",
estimatedDurationMs: 180000,
},
});

export const translationBergamotCacheTests: TestDefinition[] = [
cacheReloadTest("fr-en"),
cacheReloadTest("en-fr"),
];
Loading