Skip to content

Commit

Permalink
test_runner: refactor snapshots to get file from context
Browse files Browse the repository at this point in the history
This commit refactors the internals of snapshot tests to get the
name of the test file from the test context instead of passing
it to the SnapshotManager constructor. This is prep work for
supporting running test files in the test runner process.
  • Loading branch information
cjihrig committed Jul 15, 2024
1 parent 9a932c0 commit 8349967
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 143 deletions.
135 changes: 72 additions & 63 deletions lib/internal/test_runner/snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,50 +58,12 @@ function setDefaultSnapshotSerializers(serializers) {
serializerFns = ArrayPrototypeSlice(serializers);
}

class SnapshotManager {
constructor(entryFile, updateSnapshots) {
this.entryFile = entryFile;
this.snapshotFile = undefined;
class SnapshotFile {
constructor(snapshotFile) {
this.snapshotFile = snapshotFile;
this.snapshots = { __proto__: null };
this.nameCounts = new SafeMap();
// A manager instance will only read or write snapshot files based on the
// updateSnapshots argument.
this.loaded = updateSnapshots;
this.updateSnapshots = updateSnapshots;
}

resolveSnapshotFile() {
if (this.snapshotFile === undefined) {
const resolved = resolveSnapshotPathFn(this.entryFile);

if (typeof resolved !== 'string') {
const err = new ERR_INVALID_STATE('Invalid snapshot filename.');
err.filename = resolved;
throw err;
}

this.snapshotFile = resolved;
}
}

serialize(input, serializers = serializerFns) {
try {
let value = input;

for (let i = 0; i < serializers.length; ++i) {
const fn = serializers[i];
value = fn(value);
}

return `\n${templateEscape(value)}\n`;
} catch (err) {
const error = new ERR_INVALID_STATE(
'The provided serializers did not generate a string.',
);
error.input = input;
error.cause = err;
throw error;
}
this.loaded = false;
}

getSnapshot(id) {
Expand All @@ -122,12 +84,11 @@ class SnapshotManager {

nextId(name) {
const count = this.nameCounts.get(name) ?? 1;

this.nameCounts.set(name, count + 1);
return `${name} ${count}`;
}

readSnapshotFile() {
readFile() {
if (this.loaded) {
debug('skipping read of snapshot file');
return;
Expand Down Expand Up @@ -162,12 +123,7 @@ class SnapshotManager {
}
}

writeSnapshotFile() {
if (!this.updateSnapshots) {
debug('skipping write of snapshot file');
return;
}

writeFile() {
try {
const keys = ArrayPrototypeSort(ObjectKeys(this.snapshots));
const snapshotStrings = ArrayPrototypeMap(keys, (key) => {
Expand All @@ -184,34 +140,87 @@ class SnapshotManager {
throw error;
}
}
}

class SnapshotManager {
constructor(updateSnapshots) {
// A manager instance will only read or write snapshot files based on the
// updateSnapshots argument.
this.updateSnapshots = updateSnapshots;
this.cache = new SafeMap();
}

resolveSnapshotFile(entryFile) {
let snapshotFile = this.cache.get(entryFile);

if (snapshotFile === undefined) {
const resolved = resolveSnapshotPathFn(entryFile);

if (typeof resolved !== 'string') {
const err = new ERR_INVALID_STATE('Invalid snapshot filename.');
err.filename = resolved;
throw err;
}

snapshotFile = new SnapshotFile(resolved);
snapshotFile.loaded = this.updateSnapshots;
this.cache.set(entryFile, snapshotFile);
}

return snapshotFile;
}

serialize(input, serializers = serializerFns) {
try {
let value = input;

for (let i = 0; i < serializers.length; ++i) {
const fn = serializers[i];
value = fn(value);
}

return `\n${templateEscape(value)}\n`;
} catch (err) {
const error = new ERR_INVALID_STATE(
'The provided serializers did not generate a string.',
);
error.input = input;
error.cause = err;
throw error;
}
}

writeSnapshotFiles() {
if (!this.updateSnapshots) {
debug('skipping write of snapshot files');
return;
}

this.cache.forEach((snapshotFile) => {
snapshotFile.writeFile();
});
}

createAssert() {
const manager = this;

return function snapshotAssertion(actual, options = kEmptyObject) {
emitExperimentalWarning(kExperimentalWarning);
// Resolve the snapshot file here so that any resolution errors are
// surfaced as early as possible.
manager.resolveSnapshotFile();

const { fullName } = this;
const id = manager.nextId(fullName);

validateObject(options, 'options');

const {
serializers = serializerFns,
} = options;

validateFunctionArray(serializers, 'options.serializers');

const { filePath, fullName } = this;
const snapshotFile = manager.resolveSnapshotFile(filePath);
const value = manager.serialize(actual, serializers);
const id = snapshotFile.nextId(fullName);

if (manager.updateSnapshots) {
manager.setSnapshot(id, value);
snapshotFile.setSnapshot(id, value);
} else {
manager.readSnapshotFile();
strictEqual(value, manager.getSnapshot(id));
snapshotFile.readFile();
strictEqual(value, snapshotFile.getSnapshot(id));
}
};
}
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ function lazyAssertObject(harness) {
const { getOptionValue } = require('internal/options');
if (getOptionValue('--experimental-test-snapshots')) {
const { SnapshotManager } = require('internal/test_runner/snapshot');
harness.snapshotManager = new SnapshotManager(kFilename, updateSnapshots);
harness.snapshotManager = new SnapshotManager(updateSnapshots);
assertObj.set('snapshot', harness.snapshotManager.createAssert());
}
}
Expand Down Expand Up @@ -977,7 +977,7 @@ class Test extends AsyncResource {

// Call this harness.coverage() before collecting diagnostics, since failure to collect coverage is a diagnostic.
const coverage = harness.coverage();
harness.snapshotManager?.writeSnapshotFile();
harness.snapshotManager?.writeSnapshotFiles();
for (let i = 0; i < diagnostics.length; i++) {
reporter.diagnostic(nesting, loc, diagnostics[i]);
}
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/test-runner/snapshots/imported-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';
const { suite, test } = require('node:test');

suite('imported suite', () => {
test('imported test', (t) => {
t.assert.snapshot({ foo: 1, bar: 2 });
});
});
2 changes: 2 additions & 0 deletions test/fixtures/test-runner/snapshots/unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ test('`${foo}`', async (t) => {
const options = { serializers: [() => { return '***'; }]};
t.assert.snapshot('snapshotted string', options);
});

require('./imported-tests');
Loading

0 comments on commit 8349967

Please sign in to comment.