From 1c925a11210cabfbaae07392c7bd170eb649a84d Mon Sep 17 00:00:00 2001 From: HynLcc Date: Thu, 18 Jun 2026 21:01:37 +0800 Subject: [PATCH] Migrate csv import runner lifecycle --- framework/runners/csv-import-lifecycle.ts | 141 ++++++++++++++++++++++ framework/runners/csv-import.runner.ts | 141 ++++++++-------------- scripts/diff-artifacts.mjs | 22 +++- tasks/runner-migration-tracker.md | 4 +- 4 files changed, 215 insertions(+), 93 deletions(-) create mode 100644 framework/runners/csv-import-lifecycle.ts diff --git a/framework/runners/csv-import-lifecycle.ts b/framework/runners/csv-import-lifecycle.ts new file mode 100644 index 0000000..d2a302f --- /dev/null +++ b/framework/runners/csv-import-lifecycle.ts @@ -0,0 +1,141 @@ +import { measureAsync } from "../metrics"; +import { PerfRunDiagnosticError } from "../types"; +import type { + CsvImportCaseConfig, + PerfCase, + PerfRunContext, + PerfRunResult, +} from "../types"; + +export type CsvImportMeasurement = { + name: string; + durationMs: number; + result: T; +}; + +type CsvImportBaseArgs = { + perfCase: PerfCase; + context: PerfRunContext; + config: CsvImportCaseConfig; + baseId: string; +}; + +type CsvImportPrepareArgs = CsvImportBaseArgs & { + tableName: string; +}; + +type CsvImportFixtureArgs = CsvImportBaseArgs & { + fixture: TFixture; +}; + +type CsvImportBuildResultArgs = { + config: CsvImportCaseConfig; + prepareMeasurement?: CsvImportMeasurement; + seedReadyMeasurement?: CsvImportMeasurement; + primaryMeasurement?: CsvImportMeasurement; + error?: unknown; +}; + +export type CsvImportLifecycleSpec = { + hasReusableSeed: (config: CsvImportCaseConfig) => boolean; + seedlessResult: (perfCase: PerfCase) => PerfRunResult; + prepareExecute: (args: CsvImportPrepareArgs) => Promise; + prepareSeed: (args: CsvImportPrepareArgs) => Promise; + execute: (args: CsvImportFixtureArgs) => Promise; + seedReady: (args: CsvImportFixtureArgs) => Promise; + buildResult: ( + args: CsvImportBuildResultArgs, + ) => PerfRunResult; + cleanup: ( + args: CsvImportBaseArgs & { + prepareMeasurement?: CsvImportMeasurement; + }, + ) => Promise; +}; + +export const runCsvImportLifecycle = async ( + perfCase: PerfCase, + context: PerfRunContext, + spec: CsvImportLifecycleSpec, +): Promise => { + const config = perfCase.config as CsvImportCaseConfig; + const baseId = globalThis.testConfig.baseId; + const tableName = `${config.tableNamePrefix}-${Date.now()}`; + let prepareMeasurement: CsvImportMeasurement | undefined; + + try { + prepareMeasurement = await measureAsync("prepare", () => + spec.prepareExecute({ perfCase, context, config, baseId, tableName }), + ); + let primaryMeasurement: CsvImportMeasurement | 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 ( + perfCase: PerfCase, + context: PerfRunContext, + spec: CsvImportLifecycleSpec, +): Promise => { + 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, + }); +}; diff --git a/framework/runners/csv-import.runner.ts b/framework/runners/csv-import.runner.ts index 90e865e..fb6c025 100644 --- a/framework/runners/csv-import.runner.ts +++ b/framework/runners/csv-import.runner.ts @@ -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 = { - name: string; - durationMs: number; - result: T; -}; +type Measurement = CsvImportMeasurement; type CsvField = CsvImportCaseConfig["fields"][number] & { id: string; @@ -1256,55 +1257,42 @@ const buildCsvImportCaseResult = ({ }; }; -export const runCsvImportCase = async ( - perfCase: PerfCase, - context: PerfRunContext, -): Promise => { - const config = perfCase.config as CsvImportCaseConfig; - const baseId = globalThis.testConfig.baseId; - const tableName = `${config.tableNamePrefix}-${Date.now()}`; - let prepareMeasurement: Measurement | undefined; - const targetMode = getCsvImportTargetMode(config); - - try { - prepareMeasurement = await measureAsync("prepare", () => - targetMode === "create-table" - ? prepareCsvCreateTableFixture(tableName, config) - : prepareCsvImportFixture(baseId, tableName, config, perfCase), - ); - let primaryMeasurement: Measurement | 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> +> = { + 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 @@ -1319,42 +1307,17 @@ export const runCsvImportCase = async ( ); } } - } + }, }; -export const seedCsvImportCase = async ( +export const runCsvImportCase = ( perfCase: PerfCase, - _context: PerfRunContext, -): Promise => { - 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 => + runCsvImportLifecycle(perfCase, context, csvImportLifecycleSpec); - return buildCsvImportCaseResult({ - config, - prepareMeasurement, - seedReadyMeasurement, - }); -}; +export const seedCsvImportCase = ( + perfCase: PerfCase, + context: PerfRunContext, +): Promise => + seedCsvImportLifecycle(perfCase, context, csvImportLifecycleSpec); diff --git a/scripts/diff-artifacts.mjs b/scripts/diff-artifacts.mjs index b98556f..273d674 100644 --- a/scripts/diff-artifacts.mjs +++ b/scripts/diff-artifacts.mjs @@ -61,6 +61,10 @@ const shouldMaskKey = (path, key) => { return true; } + if (key === "traceparent") { + return true; + } + if (pathEquals(path, ["thresholds"]) && isArrayIndex(key)) { return false; } @@ -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; } @@ -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; diff --git a/tasks/runner-migration-tracker.md b/tasks/runner-migration-tracker.md index 9493b59..dab9b41 100644 --- a/tasks/runner-migration-tracker.md +++ b/tasks/runner-migration-tracker.md @@ -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) | @@ -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 |