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
141 changes: 141 additions & 0 deletions framework/runners/csv-import-lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { measureAsync } from "../metrics";
import { PerfRunDiagnosticError } from "../types";
import type {
CsvImportCaseConfig,
PerfCase,
PerfRunContext,
PerfRunResult,
} from "../types";

export type CsvImportMeasurement<T> = {
name: string;
durationMs: number;
result: T;
};

type CsvImportBaseArgs = {
perfCase: PerfCase;
context: PerfRunContext;
config: CsvImportCaseConfig;
baseId: string;
};

type CsvImportPrepareArgs = CsvImportBaseArgs & {
tableName: string;
};

type CsvImportFixtureArgs<TFixture> = CsvImportBaseArgs & {
fixture: TFixture;
};

type CsvImportBuildResultArgs<TFixture, TPrimary, TSeedReady> = {
config: CsvImportCaseConfig;
prepareMeasurement?: CsvImportMeasurement<TFixture>;
seedReadyMeasurement?: CsvImportMeasurement<TSeedReady>;
primaryMeasurement?: CsvImportMeasurement<TPrimary>;
error?: unknown;
};

export type CsvImportLifecycleSpec<TFixture, TPrimary, TSeedReady> = {
hasReusableSeed: (config: CsvImportCaseConfig) => boolean;
seedlessResult: (perfCase: PerfCase) => PerfRunResult;
prepareExecute: (args: CsvImportPrepareArgs) => Promise<TFixture>;
prepareSeed: (args: CsvImportPrepareArgs) => Promise<TFixture>;
execute: (args: CsvImportFixtureArgs<TFixture>) => Promise<TPrimary>;
seedReady: (args: CsvImportFixtureArgs<TFixture>) => Promise<TSeedReady>;
buildResult: (
args: CsvImportBuildResultArgs<TFixture, TPrimary, TSeedReady>,
) => PerfRunResult;
cleanup: (
args: CsvImportBaseArgs & {
prepareMeasurement?: CsvImportMeasurement<TFixture>;
},
) => Promise<void>;
};

export const runCsvImportLifecycle = async <TFixture, TPrimary, TSeedReady>(
perfCase: PerfCase,
context: PerfRunContext,
spec: CsvImportLifecycleSpec<TFixture, TPrimary, TSeedReady>,
): Promise<PerfRunResult> => {
const config = perfCase.config as CsvImportCaseConfig;
const baseId = globalThis.testConfig.baseId;
const tableName = `${config.tableNamePrefix}-${Date.now()}`;
let prepareMeasurement: CsvImportMeasurement<TFixture> | undefined;

try {
prepareMeasurement = await measureAsync("prepare", () =>
spec.prepareExecute({ perfCase, context, config, baseId, tableName }),
);
let primaryMeasurement: CsvImportMeasurement<TPrimary> | undefined;

try {
primaryMeasurement = await measureAsync(config.threshold.metric, () =>
spec.execute({
perfCase,
context,
config,
baseId,
fixture: prepareMeasurement.result,
}),
);
} catch (error) {
throw new PerfRunDiagnosticError(
error instanceof Error ? error.message : String(error),
spec.buildResult({
config,
prepareMeasurement,
primaryMeasurement,
error,
}),
);
}

return spec.buildResult({
config,
prepareMeasurement,
primaryMeasurement,
});
} finally {
await spec.cleanup({
perfCase,
context,
config,
baseId,
prepareMeasurement,
});
}
};

export const seedCsvImportLifecycle = async <TFixture, TPrimary, TSeedReady>(
perfCase: PerfCase,
context: PerfRunContext,
spec: CsvImportLifecycleSpec<TFixture, TPrimary, TSeedReady>,
): Promise<PerfRunResult> => {
const config = perfCase.config as CsvImportCaseConfig;
const baseId = globalThis.testConfig.baseId;

if (!spec.hasReusableSeed(config)) {
return spec.seedlessResult(perfCase);
}

const tableName = `${config.tableNamePrefix}-seed-${Date.now()}`;
const prepareMeasurement = await measureAsync("prepare", () =>
spec.prepareSeed({ perfCase, context, config, baseId, tableName }),
);
const seedReadyMeasurement = await measureAsync("seedReady", () =>
spec.seedReady({
perfCase,
context,
config,
baseId,
fixture: prepareMeasurement.result,
}),
);

return spec.buildResult({
config,
prepareMeasurement,
seedReadyMeasurement,
});
};
141 changes: 52 additions & 89 deletions framework/runners/csv-import.runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ import type {
PerfRunContext,
PerfRunResult,
} from "../types";
import { PerfRunDiagnosticError } from "../types";
import {
runCsvImportLifecycle,
seedCsvImportLifecycle,
type CsvImportLifecycleSpec,
type CsvImportMeasurement,
} from "./csv-import-lifecycle";

type Measurement<T> = {
name: string;
durationMs: number;
result: T;
};
type Measurement<T> = CsvImportMeasurement<T>;

type CsvField = CsvImportCaseConfig["fields"][number] & {
id: string;
Expand Down Expand Up @@ -1256,55 +1257,42 @@ const buildCsvImportCaseResult = ({
};
};

export const runCsvImportCase = async (
perfCase: PerfCase,
context: PerfRunContext,
): Promise<PerfRunResult> => {
const config = perfCase.config as CsvImportCaseConfig;
const baseId = globalThis.testConfig.baseId;
const tableName = `${config.tableNamePrefix}-${Date.now()}`;
let prepareMeasurement: Measurement<CsvFixture> | undefined;
const targetMode = getCsvImportTargetMode(config);

try {
prepareMeasurement = await measureAsync("prepare", () =>
targetMode === "create-table"
? prepareCsvCreateTableFixture(tableName, config)
: prepareCsvImportFixture(baseId, tableName, config, perfCase),
);
let primaryMeasurement: Measurement<CsvImportPrimaryResult> | undefined;

try {
primaryMeasurement = await measureAsync(config.threshold.metric, () =>
importAndVerifyCsv(
baseId,
prepareMeasurement.result,
config,
context,
perfCase,
config.threshold.metric,
),
);
} catch (error) {
const diagnosticResult = buildCsvImportCaseResult({
config,
prepareMeasurement,
primaryMeasurement,
error,
});

throw new PerfRunDiagnosticError(
error instanceof Error ? error.message : String(error),
diagnosticResult,
);
}

return buildCsvImportCaseResult({
const csvImportLifecycleSpec: CsvImportLifecycleSpec<
CsvFixture,
CsvImportPrimaryResult,
Awaited<ReturnType<typeof assertCsvImportTargetEmpty>>
> = {
hasReusableSeed: (config) =>
getCsvImportTargetMode(config) !== "create-table",
seedlessResult: (perfCase) => ({
result: "skipped",
metrics: {},
thresholds: [],
details: {
skipped: true,
reason: "create-table has no reusable seed",
runner: perfCase.runner,
},
}),
prepareExecute: ({ perfCase, config, baseId, tableName }) =>
getCsvImportTargetMode(config) === "create-table"
? prepareCsvCreateTableFixture(tableName, config)
: prepareCsvImportFixture(baseId, tableName, config, perfCase),
prepareSeed: ({ perfCase, config, baseId, tableName }) =>
prepareCsvImportFixture(baseId, tableName, config, perfCase),
execute: ({ perfCase, context, config, baseId, fixture }) =>
importAndVerifyCsv(
baseId,
fixture,
config,
prepareMeasurement,
primaryMeasurement,
});
} finally {
context,
perfCase,
config.threshold.metric,
),
seedReady: ({ baseId, fixture }) =>
assertCsvImportTargetEmpty(baseId, fixture),
buildResult: buildCsvImportCaseResult,
cleanup: async ({ baseId, prepareMeasurement }) => {
// CI execute jobs run on a disposable restored DB copy, so the imported
// (mutated) table is discarded with the database. Locally the table is
// deleted: inplace import mutates the cached seed and create-table mode
Expand All @@ -1319,42 +1307,17 @@ export const runCsvImportCase = async (
);
}
}
}
},
};

export const seedCsvImportCase = async (
export const runCsvImportCase = (
perfCase: PerfCase,
_context: PerfRunContext,
): Promise<PerfRunResult> => {
const config = perfCase.config as CsvImportCaseConfig;
const baseId = globalThis.testConfig.baseId;
const targetMode = getCsvImportTargetMode(config);

if (targetMode === "create-table") {
return {
result: "skipped",
metrics: {},
thresholds: [],
details: {
skipped: true,
reason: "create-table has no reusable seed",
runner: perfCase.runner,
},
};
}

const tableName = `${config.tableNamePrefix}-seed-${Date.now()}`;
const prepareMeasurement = await measureAsync("prepare", () =>
prepareCsvImportFixture(baseId, tableName, config, perfCase),
);
const fixture = prepareMeasurement.result;
const seedReadyMeasurement = await measureAsync("seedReady", () =>
assertCsvImportTargetEmpty(baseId, fixture),
);
context: PerfRunContext,
): Promise<PerfRunResult> =>
runCsvImportLifecycle(perfCase, context, csvImportLifecycleSpec);

return buildCsvImportCaseResult({
config,
prepareMeasurement,
seedReadyMeasurement,
});
};
export const seedCsvImportCase = (
perfCase: PerfCase,
context: PerfRunContext,
): Promise<PerfRunResult> =>
seedCsvImportLifecycle(perfCase, context, csvImportLifecycleSpec);
22 changes: 20 additions & 2 deletions scripts/diff-artifacts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ const shouldMaskKey = (path, key) => {
return true;
}

if (key === "traceparent") {
return true;
}

if (pathEquals(path, ["thresholds"]) && isArrayIndex(key)) {
return false;
}
Expand All @@ -76,7 +80,21 @@ const shouldMaskKey = (path, key) => {

if (
pathEquals(path, ["details"]) &&
["windowId", "tableId", "tableName", "viewId"].includes(key)
["windowId", "tableId", "tableName", "dbTableName", "viewId"].includes(key)
) {
return true;
}

if (
pathEquals(path, ["details", "import"]) &&
["createdTableId", "requestMs"].includes(key)
) {
return true;
}

if (
pathEquals(path, ["details", "import", "completion"]) &&
["pollCount", "tableId"].includes(key)
) {
return true;
}
Expand All @@ -101,7 +119,7 @@ const shouldMaskKey = (path, key) => {
}

if (
pathEquals(path, ["details", "seed", "cache"]) &&
path.at(-1) === "cache" &&
["seedHash", "seedHashShort", "seedTableName"].includes(key)
) {
return true;
Expand Down
4 changes: 2 additions & 2 deletions tasks/runner-migration-tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ that uses it, because migrating a runner means re-verifying all of its cases.

Status as of 2026-06-18 on `main`.

**Migrated: 3 / 35 runner kinds · 3 / 55 cases.**
**Migrated: 4 / 35 runner kinds · 6 / 55 cases.**

## Migrated (✅ on the driver)

| Runner kind | Driver / where | Cases | Verified |
| ------------- | ------------------------------------------------ | ----------------------- | --------------------- |
| csv-import | `csv-import-lifecycle.ts` | 3 csv-import cases | ✅ v1+v2 pass (local) |
| record-delete | `record-replay-lifecycle.ts` (no setup) | record-delete/delete-1k | ✅ v1+v2 pass (local) |
| record-undo | `record-replay-lifecycle.ts` (delete setup) | record-undo/delete-1k | ✅ v1+v2 pass (local) |
| record-redo | `record-replay-lifecycle.ts` (delete+undo setup) | record-redo/delete-1k | ✅ v1+v2 pass (local) |
Expand All @@ -21,7 +22,6 @@ Status as of 2026-06-18 on `main`.
| Runner kind | # | Cases |
| ------------------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| conditional-lookup | 1 | lookup/conditional-10k |
| csv-import | 3 | csv-import/mixed-1k-20fields-create-table-import, csv-import/mixed-10k-20fields-create-table-import, csv-import/mixed-10k-20fields-inplace-import |
| duplicate-base | 3 | duplicate-base/10k-3tables-link-2workflow, duplicate-base/10k-3tables-link-2workflow-stream, export-base/10k-3tables-link-2workflow-stream |
| duplicate-table | 2 | duplicate-table/10k-20f, duplicate-table/10k-25f-5formula |
| field-convert | 2 | field-convert/10k-multi-select-to-text, field-convert/10k-text-to-formula |
Expand Down