diff --git a/dev/attest/src/assertions/assertions.ts b/dev/attest/src/assertions/assertions.ts index df1a13a335..41e7db64b4 100644 --- a/dev/attest/src/assertions/assertions.ts +++ b/dev/attest/src/assertions/assertions.ts @@ -6,12 +6,9 @@ import { assertEquals } from "../assertions.js" import type { AssertionContext } from "../attest.js" import { caller } from "../main.js" import type { SnapshotArgs } from "../snapshot.js" -import { getSnapshotByName } from "../snapshot.js" +import { getSnapshotByName, queueSnapshotUpdate } from "../snapshot.js" import { getTypeDataAtPos } from "../type/getAssertionAtPos.js" -import { - updateExternalSnapshot, - writeInlineSnapshotUpdateToCacheDir -} from "../writeSnapshot.js" +import { updateExternalSnapshot } from "../writeSnapshot.js" import type { ExternalSnapshotArgs, RootAssertions } from "./types.js" import { assertEqualOrMatching, @@ -87,7 +84,7 @@ export class Assertions implements AssertionRecord { position: caller(), serializedValue: this.serializedActual } - writeInlineSnapshotUpdateToCacheDir(snapshotArgs) + queueSnapshotUpdate(snapshotArgs) } } else { // compare as strings, but if match fails, compare again as objects diff --git a/dev/attest/src/bench/baseline.ts b/dev/attest/src/bench/baseline.ts index 2d9805b6c8..a8f31ff47e 100644 --- a/dev/attest/src/bench/baseline.ts +++ b/dev/attest/src/bench/baseline.ts @@ -1,8 +1,6 @@ import process from "node:process" import { snapshot } from "arktype/internal/utils/serialize.js" -import type { QueuedUpdate } from "../snapshot.js" -import { createQueuedSnapshotUpdate } from "../snapshot.js" -import { writeUpdates } from "../writeSnapshot.js" +import { queueSnapshotUpdate } from "../snapshot.js" import type { BenchAssertionContext, BenchContext } from "./bench.js" import type { MarkMeasure, @@ -11,8 +9,6 @@ import type { } from "./measure/measure.js" import { stringifyMeasure } from "./measure/measure.js" -const queuedUpdates: QueuedUpdate[] = [] - export const queueBaselineUpdateIfNeeded = ( updated: Measure | MarkMeasure, baseline: Measure | MarkMeasure | undefined, @@ -28,19 +24,12 @@ export const queueBaselineUpdateIfNeeded = ( `Unable to update baseline for ${ctx.qualifiedName} ('lastSnapCallPosition' was unset).` ) } - if (queuedUpdates.length === 0) { - process.addListener("exit", () => { - writeUpdates(queuedUpdates) - }) - } - queuedUpdates.push( - createQueuedSnapshotUpdate({ - position: ctx.lastSnapCallPosition, - serializedValue, - snapFunctionName: ctx.kind, - baselinePath: ctx.qualifiedPath - }) - ) + queueSnapshotUpdate({ + position: ctx.lastSnapCallPosition, + serializedValue, + snapFunctionName: ctx.kind, + baselinePath: ctx.qualifiedPath + }) } /** Pretty print comparison and set the process.exitCode to 1 if delta threshold is exceeded */ diff --git a/dev/attest/src/snapshot.ts b/dev/attest/src/snapshot.ts index 3dac119c14..818ef673bd 100644 --- a/dev/attest/src/snapshot.ts +++ b/dev/attest/src/snapshot.ts @@ -1,11 +1,13 @@ +import { randomUUID } from "node:crypto" import { basename, dirname, isAbsolute, join } from "node:path" import type { CallExpression, Project, ts } from "ts-morph" import { SyntaxKind } from "ts-morph" -import { readJson } from "./main.js" -import { getTsMorphProject } from "./type/cacheAssertions.js" +import { getConfig } from "./config.js" +import { readJson, writeJson } from "./main.js" import { getTsNodeAtPosition } from "./type/getTsNodeAtPos.js" import type { SourcePosition } from "./utils.js" import { positionToString } from "./utils.js" +import { writeCachedInlineSnapshotUpdates } from "./writeSnapshot.js" export type SnapshotArgs = { position: SourcePosition @@ -28,7 +30,7 @@ export const findCallExpressionAncestor = ( const name = // If the call is made directly, e.g. snap(...), the expression will be an identifier, so can use its whole text expression.asKind(SyntaxKind.Identifier)?.getText() ?? - // If the call is made from a prop, e.g. snap in assert(...).snap(), check the name of the prop accessed + // If the call is made from a prop, e.g. snap in attest(...).snap(), check the name of the prop accessed expression .asKind(SyntaxKind.PropertyAccessExpression) ?.getName() @@ -64,24 +66,22 @@ export const getSnapshotByName = ( return readJson(snapshotPath)?.[basename(file)]?.[name] } -export const createQueuedSnapshotUpdate = ({ - position, - serializedValue, - snapFunctionName = "snap", - baselinePath -}: SnapshotArgs): QueuedUpdate => { - const snapCall = findCallExpressionAncestor( - getTsMorphProject(), - position, - snapFunctionName - ) - const newArgText = JSON.stringify(serializedValue) - return { - position, - snapCall, - snapFunctionName, - newArgText, - baselinePath +let writeCachedUpdatesOnExit = false +process.addListener("exit", () => { + if (writeCachedUpdatesOnExit) { + writeCachedInlineSnapshotUpdates() + } +}) + +/** + * Writes the update and position to cacheDir, which will eventually be read and copied to the source + * file by a cleanup process after all tests have completed. + */ +export const queueSnapshotUpdate = (args: SnapshotArgs) => { + const config = getConfig() + writeJson(join(config.snapCacheDir, `snap-${randomUUID()}.json`), args) + if (args.baselinePath || config.skipTypes) { + writeCachedUpdatesOnExit = true } } diff --git a/dev/attest/src/writeSnapshot.ts b/dev/attest/src/writeSnapshot.ts index 03116413a4..e9e9eb6f41 100644 --- a/dev/attest/src/writeSnapshot.ts +++ b/dev/attest/src/writeSnapshot.ts @@ -1,11 +1,11 @@ -import { randomUUID } from "node:crypto" import { existsSync, readdirSync } from "node:fs" import { basename, join } from "node:path" import type { Node, ts } from "ts-morph" import { getConfig } from "./config.js" import { readJson, shell, writeJson } from "./main.js" import type { QueuedUpdate, SnapshotArgs } from "./snapshot.js" -import { createQueuedSnapshotUpdate, resolveSnapshotPath } from "./snapshot.js" +import { findCallExpressionAncestor, resolveSnapshotPath } from "./snapshot.js" +import { getTsMorphProject } from "./type/cacheAssertions.js" import { getFileKey } from "./utils.js" export type ExternalSnapshotArgs = SnapshotArgs & { @@ -50,7 +50,7 @@ export const writeCachedInlineSnapshotUpdates = () => { } if (snapshotData) { try { - queuedUpdates.push(createQueuedSnapshotUpdate(snapshotData)) + queuedUpdates.push(snapshotArgsToQueuedUpdate(snapshotData)) } catch (error) { // If writeInlineSnapshotToFile throws an error, log it and move on to the next update console.error(String(error)) @@ -61,12 +61,25 @@ export const writeCachedInlineSnapshotUpdates = () => { writeUpdates(queuedUpdates) } -/** - * Writes the update and position to cacheDir, which will eventually be read and copied to the source - * file by a cleanup process after all tests have completed. - */ -export const writeInlineSnapshotUpdateToCacheDir = (args: SnapshotArgs) => { - writeJson(join(getConfig().snapCacheDir, `snap-${randomUUID()}.json`), args) +const snapshotArgsToQueuedUpdate = ({ + position, + serializedValue, + snapFunctionName = "snap", + baselinePath +}: SnapshotArgs): QueuedUpdate => { + const snapCall = findCallExpressionAncestor( + getTsMorphProject(), + position, + snapFunctionName + ) + const newArgText = JSON.stringify(serializedValue) + return { + position, + snapCall, + snapFunctionName, + newArgText, + baselinePath + } } // Waiting until process exit to write snapshots avoids invalidating existing source positions diff --git a/dev/attest/test/snapshots.test.ts b/dev/attest/test/snapshots.test.ts index 581e5bb643..7dab0435b4 100644 --- a/dev/attest/test/snapshots.test.ts +++ b/dev/attest/test/snapshots.test.ts @@ -14,5 +14,5 @@ describe("bench", () => { it("populates file", async () => { const actual = await runThenGetContents(benchActual, benchTemplate) equal(actual, expectedOutput) - }).timeout(30000) + }).timeout(60000) })