assert: remove assert.snapshot

PR-URL: https://github.com/nodejs/node/pull/46112
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
Moshe Atlow authored and GitHub committed 2023-01-07 20:24:24 +00:00
1 parent 4aa537c005
commit 11e7858553
22 files changed
+6 -405

No files matched your search

-44
View File
@@ -2093,48 +2093,6 @@ argument, then `error` is assumed to be omitted and the string will be used for
example in [`assert.throws()`][] carefully if using a string as the second
argument gets considered.
## `assert.snapshot(value, name)`
<!-- YAML
added: v18.8.0
-->
> Stability: 1 - Experimental
* `value` {any} the value to snapshot.
* `name` {string} the name of the snapshot.
* Returns: {Promise}
Reads the `name` snapshot from a file and compares `value` to the snapshot.
`value` is serialized with [`util.inspect()`][]. If the value is not strictly
equal to the snapshot, `assert.snapshot()` returns a rejected `Promise` with an
[`AssertionError`][].
The snapshot filename uses the same basename as the application's main
entrypoint with a `.snapshot` extension. If the snapshot file does not exist,
it is created. The [`--update-assert-snapshot`][] command line flag can be used
to force the update of an existing snapshot.
```mjs
import assert from 'node:assert/strict';
// Assuming that the application's main entrypoint is app.mjs, this reads the
// 'snapshotName' snapshot from app.snapshot and strictly compares its value
// to `util.inspect('value')`.
await assert.snapshot('value', 'snapshotName');
```
```cjs
const assert = require('node:assert/strict');
(async () => {
// Assuming that the application's main entrypoint is app.js, this reads the
// 'snapshotName' snapshot from app.snapshot and strictly compares its value
// to `util.inspect('value')`.
await assert.snapshot('value', 'snapshotName');
})();
```
## `assert.strictEqual(actual, expected[, message])`
<!-- YAML
@@ -2571,7 +2529,6 @@ argument.
[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
[`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality
[`--update-assert-snapshot`]: cli.md#--update-assert-snapshot
[`===` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality
[`==` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality
[`AssertionError`]: #class-assertassertionerror
@@ -2603,6 +2560,5 @@ argument.
[`process.on('exit')`]: process.md#event-exit
[`tracker.calls()`]: #trackercallsfn-exact
[`tracker.verify()`]: #trackerverify
[`util.inspect()`]: util.md#utilinspectobject-options
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
-10
View File
@@ -1536,14 +1536,6 @@ occurs. One of the following modes can be chosen:
If a rejection happens during the command line entry point's ES module static
loading phase, it will always raise it as an uncaught exception.
### `--update-assert-snapshot`
<!-- YAML
added: v18.8.0
-->
Updates snapshot files used by [`assert.snapshot()`][].
### `--use-bundled-ca`, `--use-openssl-ca`
<!-- YAML
@@ -1972,7 +1964,6 @@ Node.js options that are allowed are:
* `--trace-warnings`
* `--track-heap-objects`
* `--unhandled-rejections`
* `--update-assert-snapshot`
* `--use-bundled-ca`
* `--use-largepages`
* `--use-openssl-ca`
@@ -2353,7 +2344,6 @@ done
[`NO_COLOR`]: https://no-color.org
[`SlowBuffer`]: buffer.md#class-slowbuffer
[`YoungGenerationSizeFromSemiSpaceSize`]: https://chromium.googlesource.com/v8/v8.git/+/refs/tags/10.3.129/src/heap/heap.cc#328
[`assert.snapshot()`]: assert.md#assertsnapshotvalue-name
[`dns.lookup()`]: dns.md#dnslookuphostname-options-callback
[`dns.setDefaultResultOrder()`]: dns.md#dnssetdefaultresultorderorder
[`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options
-7
View File
@@ -705,13 +705,6 @@ A special type of error that can be triggered whenever Node.js detects an
exceptional logic violation that should never occur. These are raised typically
by the `node:assert` module.
<a id="ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED"></a>
### `ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED`
An attempt was made to use `assert.snapshot()` in an environment that
does not support snapshots, such as the REPL, or when using `node --eval`.
<a id="ERR_ASYNC_CALLBACK"></a>
### `ERR_ASYNC_CALLBACK`
-3
View File
@@ -491,9 +491,6 @@ Track heap object allocations for heap snapshots.
.It Fl -unhandled-rejections=mode
Define the behavior for unhandled rejections. Can be one of `strict` (raise an error), `warn` (enforce warnings) or `none` (silence warnings).
.
.It Fl -update-assert-snapshot
Updates snapshot files used by `assert.snapshot()`.
.
.It Fl -use-bundled-ca , Fl -use-openssl-ca
Use bundled Mozilla CA store as supplied by current Node.js version or use OpenSSL's default CA store.
The default store is selectable at build-time.
-3
View File
@@ -1052,9 +1052,6 @@ assert.doesNotMatch = function doesNotMatch(string, regexp, message) {
assert.CallTracker = CallTracker;
const snapshot = require('internal/assert/snapshot');
assert.snapshot = snapshot;
/**
* Expose a strict only variant of assert.
* @param {...any} args
-129
View File
@@ -1,129 +0,0 @@
'use strict';
const {
ArrayPrototypeJoin,
ArrayPrototypeMap,
ArrayPrototypeSlice,
RegExp,
SafeMap,
SafeSet,
StringPrototypeSplit,
StringPrototypeReplace,
Symbol,
} = primordials;
const { codes: { ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED } } = require('internal/errors');
const AssertionError = require('internal/assert/assertion_error');
const { inspect } = require('internal/util/inspect');
const { getOptionValue } = require('internal/options');
const { validateString } = require('internal/validators');
const { once } = require('events');
const { createReadStream, createWriteStream } = require('fs');
const path = require('path');
const assert = require('assert');
const kUpdateSnapshot = getOptionValue('--update-assert-snapshot');
const kInitialSnapshot = Symbol('kInitialSnapshot');
const kDefaultDelimiter = '\n#*#*#*#*#*#*#*#*#*#*#*#\n';
const kDefaultDelimiterRegex = new RegExp(kDefaultDelimiter.replaceAll('*', '\\*').replaceAll('\n', '\r?\n'), 'g');
const kKeyDelimiter = /:\r?\n/g;
function getSnapshotPath() {
if (process.mainModule) {
const { dir, name } = path.parse(process.mainModule.filename);
return path.join(dir, `${name}.snapshot`);
}
if (!process.argv[1]) {
throw new ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED();
}
const { dir, name } = path.parse(process.argv[1]);
return path.join(dir, `${name}.snapshot`);
}
function getSource() {
return createReadStream(getSnapshotPath(), { encoding: 'utf8' });
}
let _target;
function getTarget() {
_target ??= createWriteStream(getSnapshotPath(), { encoding: 'utf8' });
return _target;
}
function serializeName(name) {
validateString(name, 'name');
return StringPrototypeReplace(`${name}`, kKeyDelimiter, '_');
}
let writtenNames;
let snapshotValue;
let counter = 0;
async function writeSnapshot({ name, value }) {
const target = getTarget();
if (counter > 1) {
target.write(kDefaultDelimiter);
}
writtenNames = writtenNames || new SafeSet();
if (writtenNames.has(name)) {
throw new AssertionError({ message: `Snapshot "${name}" already used` });
}
writtenNames.add(name);
const drained = target.write(`${name}:\n${value}`);
await drained || once(target, 'drain');
}
async function getSnapshot() {
if (snapshotValue !== undefined) {
return snapshotValue;
}
if (kUpdateSnapshot) {
snapshotValue = kInitialSnapshot;
return kInitialSnapshot;
}
try {
const source = getSource();
let data = '';
for await (const line of source) {
data += line;
}
snapshotValue = new SafeMap(
ArrayPrototypeMap(
StringPrototypeSplit(data, kDefaultDelimiterRegex),
(item) => {
const arr = StringPrototypeSplit(item, kKeyDelimiter);
return [
arr[0],
ArrayPrototypeJoin(ArrayPrototypeSlice(arr, 1), ':\n'),
];
}
));
} catch (e) {
if (e.code === 'ENOENT') {
snapshotValue = kInitialSnapshot;
} else {
throw e;
}
}
return snapshotValue;
}
async function snapshot(input, name) {
const snapshot = await getSnapshot();
counter = counter + 1;
name = serializeName(name);
const value = inspect(input);
if (snapshot === kInitialSnapshot) {
await writeSnapshot({ name, value });
} else if (snapshot.has(name)) {
const expected = snapshot.get(name);
// eslint-disable-next-line no-restricted-syntax
assert.strictEqual(value, expected);
} else {
throw new AssertionError({ message: `Snapshot "${name}" does not exist`, actual: inspect(snapshot) });
}
}
module.exports = snapshot;
-2
View File
@@ -962,8 +962,6 @@ module.exports = {
E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError);
E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
E('ERR_ASSERTION', '%s', Error);
E('ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED',
'Snapshot is not supported in this context ', TypeError);
E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError);
E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s', TypeError);
E('ERR_BROTLI_INVALID_PARAM', '%s is not a valid Brotli parameter', RangeError);
-5
View File
@@ -663,11 +663,6 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
&EnvironmentOptions::force_repl);
AddAlias("-i", "--interactive");
AddOption("--update-assert-snapshot",
"update assert snapshot files",
&EnvironmentOptions::update_assert_snapshot,
kAllowedInEnvvar);
AddOption("--napi-modules", "", NoOp{}, kAllowedInEnvvar);
AddOption("--tls-keylog",
-1
View File
@@ -139,7 +139,6 @@ class EnvironmentOptions : public Options {
bool preserve_symlinks = false;
bool preserve_symlinks_main = false;
bool prof_process = false;
bool update_assert_snapshot = false;
#if HAVE_INSPECTOR
std::string cpu_prof_dir;
static const uint64_t kDefaultCpuProfInterval = 1000;
-3
View File
@@ -1,3 +0,0 @@
import assert from 'node:assert';
await assert.snapshot('test', 'name');
-4
View File
@@ -1,4 +0,0 @@
import assert from 'node:assert';
await assert.snapshot('test', 'name');
await assert.snapshot('test', 'another name');
-4
View File
@@ -1,4 +0,0 @@
import assert from 'node:assert';
await assert.snapshot('test', 'another name');
await assert.snapshot('test', 'non existing');
@@ -1,5 +0,0 @@
another name:
'test'
#*#*#*#*#*#*#*#*#*#*#*#
name:
'test'
-11
View File
@@ -1,11 +0,0 @@
import assert from 'node:assert';
function random() {
return `Random Value: ${Math.random()}`;
}
function transform(value) {
return value.replaceAll(/Random Value: \d+\.+\d+/g, 'Random Value: *');
}
await assert.snapshot(transform(random()), 'random1');
await assert.snapshot(transform(random()), 'random2');
-5
View File
@@ -1,5 +0,0 @@
random1:
'Random Value: *'
#*#*#*#*#*#*#*#*#*#*#*#
random2:
'Random Value: *'
-11
View File
@@ -1,11 +0,0 @@
import assert from 'node:assert';
function fn() {
this.should.be.a.fn();
return false;
}
await assert.snapshot(fn, 'function');
await assert.snapshot({ foo: "bar" }, 'object');
await assert.snapshot(new Set([1, 2, 3]), 'set');
await assert.snapshot(new Map([['one', 1], ['two', 2]]), 'map');
-11
View File
@@ -1,11 +0,0 @@
function:
[Function: fn]
#*#*#*#*#*#*#*#*#*#*#*#
object:
{ foo: 'bar' }
#*#*#*#*#*#*#*#*#*#*#*#
set:
Set(3) { 1, 2, 3 }
#*#*#*#*#*#*#*#*#*#*#*#
map:
Map(2) { 'one' => 1, 'two' => 2 }
-3
View File
@@ -1,3 +0,0 @@
import assert from 'node:assert';
await assert.snapshot('test', 'snapshot');
-3
View File
@@ -1,3 +0,0 @@
import assert from 'node:assert';
await assert.snapshot('changed', 'snapshot');
-2
View File
@@ -1,2 +0,0 @@
snapshot:
'original'
-133
View File
@@ -1,133 +0,0 @@
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import tmpdir from '../common/tmpdir.js';
import assert from 'node:assert';
import path from 'node:path';
import { execPath } from 'node:process';
import { writeFile, readFile, unlink } from 'node:fs/promises';
import { describe, it } from 'node:test';
tmpdir.refresh();
function getSnapshotPath(filename) {
const { name, dir } = path.parse(filename);
return path.join(tmpdir.path, dir, `${name}.snapshot`);
}
async function spawnTmpfile(content = '', filename, extraFlags = []) {
const header = filename.endsWith('.mjs') ?
'import assert from \'node:assert\';' :
'const assert = require(\'node:assert\');';
await writeFile(path.join(tmpdir.path, filename), `${header}\n${content}`);
const { stdout, stderr, code } = await spawnPromisified(
execPath,
['--no-warnings', ...extraFlags, filename],
{ cwd: tmpdir.path });
const snapshotPath = getSnapshotPath(filename);
const snapshot = await readFile(snapshotPath, 'utf8').catch((err) => err);
return { stdout, stderr, code, snapshot, snapshotPath, filename };
}
async function spawnFixture(filename) {
const { dir, base, name } = path.parse(filename);
const { stdout, stderr, code } = await spawnPromisified(execPath, ['--no-warnings', base], { cwd: dir });
const snapshotPath = path.join(dir, `${name}.snapshot`);
const snapshot = await readFile(snapshotPath, 'utf8').catch((err) => err);
return { stdout, stderr, code, snapshot, snapshotPath };
}
describe('assert.snapshot', { concurrency: true }, () => {
it('should write snapshot', async () => {
const { stderr, code, snapshot, snapshotPath } = await spawnFixture(fixtures.path('assert-snapshot/basic.mjs'));
await unlink(snapshotPath);
assert.strictEqual(stderr, '');
assert.strictEqual(code, 0);
assert.match(snapshot, /^name:\r?\n'test'$/);
});
it('should write multiple snapshots', async () => {
const { stderr, code, snapshot, snapshotPath } = await spawnFixture(fixtures.path('assert-snapshot/multiple.mjs'));
await unlink(snapshotPath);
assert.strictEqual(stderr, '');
assert.strictEqual(code, 0);
assert.match(snapshot, /^name:\n'test'\r?\n#\*#\*#\*#\*#\*#\*#\*#\*#\*#\*#\*#\r?\nanother name:\r?\n'test'$/);
});
it('should succeed running multiple times', async () => {
let result = await spawnFixture(fixtures.path('assert-snapshot/single.mjs'), false);
assert.strictEqual(result.stderr, '');
assert.strictEqual(result.code, 0);
assert.match(result.snapshot, /^snapshot:\r?\n'test'$/);
result = await spawnFixture(fixtures.path('assert-snapshot/single.mjs'));
await unlink(result.snapshotPath);
assert.strictEqual(result.stderr, '');
assert.strictEqual(result.code, 0);
assert.match(result.snapshot, /^snapshot:\r?\n'test'$/);
});
it('should fail when name is not provided', async () => {
for (const name of [1, undefined, null, {}, function() {}]) {
await assert.rejects(() => assert.snapshot('', name), {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: /^The "name" argument must be of type string/
});
}
});
it('should fail when value does not match snapshot', async () => {
const { code, stderr, snapshot } = await spawnFixture(fixtures.path('assert-snapshot/value-changed.mjs'));
assert.match(stderr, /AssertionError \[ERR_ASSERTION\]/);
assert.strictEqual(code, 1);
assert.match(snapshot, /^snapshot:\r?\n'original'$/);
});
it('should fail when snapshot does not contain a named snapshot', async () => {
const { code, stderr, snapshot } = await spawnFixture(fixtures.path('assert-snapshot/non-existing-name.mjs'));
assert.match(stderr, /AssertionError \[ERR_ASSERTION\]/);
assert.match(stderr, /Snapshot "non existing" does not exist/);
assert.strictEqual(code, 1);
assert.match(snapshot, /^another name:\r?\n'test'\r?\n#\*#\*#\*#\*#\*#\*#\*#\*#\*#\*#\*#\r?\nname:\r?\n'test'$/);
});
it('should snapshot a random replaced value', async () => {
const originalSnapshot = await readFile(fixtures.path('assert-snapshot/random.snapshot'), 'utf8');
const { stderr, code, snapshot } = await spawnFixture(fixtures.path('assert-snapshot/random.mjs'));
assert.strictEqual(stderr, '');
assert.strictEqual(code, 0);
assert.strictEqual(snapshot, originalSnapshot);
});
it('should serialize values', async () => {
const originalSnapshot = await readFile(fixtures.path('assert-snapshot/serialize.snapshot'), 'utf8');
const { stderr, code, snapshot } = await spawnFixture(fixtures.path('assert-snapshot/serialize.mjs'));
assert.strictEqual(stderr, '');
assert.strictEqual(code, 0);
assert.strictEqual(snapshot, originalSnapshot);
});
it('should override snapshot when passing --update-assert-snapshot', async () => {
const filename = 'updated.mjs';
await writeFile(getSnapshotPath(filename), 'snapshot:\n\'test\'');
const { stderr, code, snapshot } = await spawnTmpfile('await assert.snapshot(\'changed\', \'snapshot\');',
filename, ['--update-assert-snapshot']);
assert.strictEqual(stderr, '');
assert.strictEqual(code, 0);
assert.match(snapshot, /^snapshot:\r?\n'changed'$/);
});
it('snapshot file should have the name of the module - esm', async () => {
const filename = 'name.mjs';
const { snapshotPath } = await spawnTmpfile('await assert.snapshot("test");', filename);
assert.match(snapshotPath, /name\.snapshot$/);
});
it('snapshot file should have the name of the module - common js', async () => {
const filename = 'name.js';
const { snapshotPath } = await spawnTmpfile('assert.snapshot("test").then(() => process.exit());', filename);
assert.match(snapshotPath, /name\.snapshot$/);
});
});
+6 -6
View File
@@ -13,7 +13,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
stream.on('test:fail', common.mustNotCall());
stream.on('test:pass', common.mustNotCall());
// eslint-disable-next-line no-unused-vars
for await (const _ of stream); // TODO(MoLow): assert.snapshot
for await (const _ of stream);
});
it('should fail with non existing file', async () => {
@@ -21,7 +21,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
stream.on('test:fail', common.mustCall(1));
stream.on('test:pass', common.mustNotCall());
// eslint-disable-next-line no-unused-vars
for await (const _ of stream); // TODO(MoLow): assert.snapshot
for await (const _ of stream);
});
it('should succeed with a file', async () => {
@@ -29,7 +29,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
stream.on('test:fail', common.mustNotCall());
stream.on('test:pass', common.mustCall(2));
// eslint-disable-next-line no-unused-vars
for await (const _ of stream); // TODO(MoLow): assert.snapshot
for await (const _ of stream);
});
it('should run same file twice', async () => {
@@ -37,7 +37,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
stream.on('test:fail', common.mustNotCall());
stream.on('test:pass', common.mustCall(4));
// eslint-disable-next-line no-unused-vars
for await (const _ of stream); // TODO(MoLow): assert.snapshot
for await (const _ of stream);
});
it('should run a failed test', async () => {
@@ -45,7 +45,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
stream.on('test:fail', common.mustCall(1));
stream.on('test:pass', common.mustNotCall());
// eslint-disable-next-line no-unused-vars
for await (const _ of stream); // TODO(MoLow): assert.snapshot
for await (const _ of stream);
});
it('should support timeout', async () => {
@@ -56,7 +56,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
stream.on('test:fail', common.mustCall(2));
stream.on('test:pass', common.mustNotCall());
// eslint-disable-next-line no-unused-vars
for await (const _ of stream); // TODO(MoLow): assert.snapshot
for await (const _ of stream);
});
it('should validate files', async () => {