| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2006,6 +2006,32 @@ argument, then `error` is assumed to be omitted and the string will be used for | |||
| 2006 | 2006 | example in [`assert.throws()`][] carefully if using a string as the second | |
| 2007 | 2007 | argument gets considered. | |
| 2008 | 2008 | ||
| 2009 | + ## `assert.snapshot(value, name)` | ||
| 2010 | + | ||
| 2011 | + <!-- YAML | ||
| 2012 | + added: REPLACEME | ||
| 2013 | + --> | ||
| 2014 | + | ||
| 2015 | + > Stability: 1 - Experimental | ||
| 2016 | + | ||
| 2017 | + * `value` {any} the value to snapshot | ||
| 2018 | + * `name` {string} the name of snapshot. | ||
| 2019 | + * Returns: {Promise} | ||
| 2020 | + | ||
| 2021 | + reads a snapshot from a file, and compares `value` to the snapshot. | ||
| 2022 | + `value` is serialized with [`util.inspect()`][] | ||
| 2023 | + If the value is not strictly equal to the snapshot, | ||
| 2024 | + `assert.snapshot()` will return a rejected `Promise` | ||
| 2025 | + with an [`AssertionError`][]. | ||
| 2026 | + | ||
| 2027 | + If the snapshot file does not exist, the snapshot is written. | ||
| 2028 | + | ||
| 2029 | + In case it is needed to force a snapshot update, | ||
| 2030 | + use [`--update-assert-snapshot`][]; | ||
| 2031 | + | ||
| 2032 | + By default, a snapshot is read and written to a file, | ||
| 2033 | + using the same name as the main entrypoint with `.snapshot` as the extension. | ||
| 2034 | + | ||
| 2009 | 2035 | ## `assert.strictEqual(actual, expected[, message])` | |
| 2010 | 2036 | ||
| 2011 | 2037 | <!-- YAML | |
@@ -2442,6 +2468,7 @@ argument. | |||
| 2442 | 2468 | [Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript | |
| 2443 | 2469 | [Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring | |
| 2444 | 2470 | [`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality | |
| 2471 | + [`--update-assert-snapshot`]: cli.md#--update-assert-snapshot | ||
| 2445 | 2472 | [`===` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality | |
| 2446 | 2473 | [`==` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality | |
| 2447 | 2474 | [`AssertionError`]: #class-assertassertionerror | |
@@ -2473,5 +2500,6 @@ argument. | |||
| 2473 | 2500 | [`process.on('exit')`]: process.md#event-exit | |
| 2474 | 2501 | [`tracker.calls()`]: #trackercallsfn-exact | |
| 2475 | 2502 | [`tracker.verify()`]: #trackerverify | |
| 2503 | + [`util.inspect()`]: util.md#utilinspectobject-options | ||
| 2476 | 2504 | [enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties | |
| 2477 | 2505 | [prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1461,6 +1461,14 @@ occurs. One of the following modes can be chosen: | |||
| 1461 | 1461 | If a rejection happens during the command line entry point's ES module static | |
| 1462 | 1462 | loading phase, it will always raise it as an uncaught exception. | |
| 1463 | 1463 | ||
| 1464 | + ### `--update-assert-snapshot` | ||
| 1465 | + | ||
| 1466 | + <!-- YAML | ||
| 1467 | + added: REPLACEME | ||
| 1468 | + --> | ||
| 1469 | + | ||
| 1470 | + Force updating snapshot files for [`assert.snapshot()`][] | ||
| 1471 | + | ||
| 1464 | 1472 | ### `--use-bundled-ca`, `--use-openssl-ca` | |
| 1465 | 1473 | ||
| 1466 | 1474 | <!-- YAML | |
@@ -1819,6 +1827,7 @@ Node.js options that are allowed are: | |||
| 1819 | 1827 | * `--trace-warnings` | |
| 1820 | 1828 | * `--track-heap-objects` | |
| 1821 | 1829 | * `--unhandled-rejections` | |
| 1830 | + * `--update-assert-snapshot` | ||
| 1822 | 1831 | * `--use-bundled-ca` | |
| 1823 | 1832 | * `--use-largepages` | |
| 1824 | 1833 | * `--use-openssl-ca` | |
@@ -2189,6 +2198,7 @@ done | |||
| 2189 | 2198 | [`NO_COLOR`]: https://no-color.org | |
| 2190 | 2199 | [`SlowBuffer`]: buffer.md#class-slowbuffer | |
| 2191 | 2200 | [`YoungGenerationSizeFromSemiSpaceSize`]: https://chromium.googlesource.com/v8/v8.git/+/refs/tags/10.3.129/src/heap/heap.cc#328 | |
| 2201 | + [`assert.snapshot()`]: assert.md#assertsnapshotvalue-name | ||
| 2192 | 2202 | [`dns.lookup()`]: dns.md#dnslookuphostname-options-callback | |
| 2193 | 2203 | [`dns.setDefaultResultOrder()`]: dns.md#dnssetdefaultresultorderorder | |
| 2194 | 2204 | [`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -705,6 +705,13 @@ A special type of error that can be triggered whenever Node.js detects an | |||
| 705 | 705 | exceptional logic violation that should never occur. These are raised typically | |
| 706 | 706 | by the `node:assert` module. | |
| 707 | 707 | ||
| 708 | + <a id="ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED"></a> | ||
| 709 | + | ||
| 710 | + ### `ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED` | ||
| 711 | + | ||
| 712 | + An attempt was made to use `assert.snapshot()` in an environment that | ||
| 713 | + does not support snapshots, such as the REPL, or when using `node --eval`. | ||
| 714 | + | ||
| 708 | 715 | <a id="ERR_ASYNC_CALLBACK"></a> | |
| 709 | 716 | ||
| 710 | 717 | ### `ERR_ASYNC_CALLBACK` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1052,6 +1052,9 @@ assert.doesNotMatch = function doesNotMatch(string, regexp, message) { | |||
| 1052 | 1052 | ||
| 1053 | 1053 | assert.CallTracker = CallTracker; | |
| 1054 | 1054 | ||
| 1055 | + const snapshot = require('internal/assert/snapshot'); | ||
| 1056 | + assert.snapshot = snapshot; | ||
| 1057 | + | ||
| 1055 | 1058 | /** | |
| 1056 | 1059 | * Expose a strict only variant of assert. | |
| 1057 | 1060 | * @param {...any} args | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,129 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const { | ||
| 4 | + ArrayPrototypeJoin, | ||
| 5 | + ArrayPrototypeMap, | ||
| 6 | + ArrayPrototypeSlice, | ||
| 7 | + RegExp, | ||
| 8 | + SafeMap, | ||
| 9 | + SafeSet, | ||
| 10 | + StringPrototypeSplit, | ||
| 11 | + StringPrototypeReplace, | ||
| 12 | + Symbol, | ||
| 13 | + } = primordials; | ||
| 14 | + | ||
| 15 | + const { codes: { ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED } } = require('internal/errors'); | ||
| 16 | + const AssertionError = require('internal/assert/assertion_error'); | ||
| 17 | + const { inspect } = require('internal/util/inspect'); | ||
| 18 | + const { getOptionValue } = require('internal/options'); | ||
| 19 | + const { validateString } = require('internal/validators'); | ||
| 20 | + const { once } = require('events'); | ||
| 21 | + const { createReadStream, createWriteStream } = require('fs'); | ||
| 22 | + const path = require('path'); | ||
| 23 | + const assert = require('assert'); | ||
| 24 | + | ||
| 25 | + const kUpdateSnapshot = getOptionValue('--update-assert-snapshot'); | ||
| 26 | + const kInitialSnapshot = Symbol('kInitialSnapshot'); | ||
| 27 | + const kDefaultDelimiter = '\n#*#*#*#*#*#*#*#*#*#*#*#\n'; | ||
| 28 | + const kDefaultDelimiterRegex = new RegExp(kDefaultDelimiter.replaceAll('*', '\\*').replaceAll('\n', '\r?\n'), 'g'); | ||
| 29 | + const kKeyDelimiter = /:\r?\n/g; | ||
| 30 | + | ||
| 31 | + function getSnapshotPath() { | ||
| 32 | + if (process.mainModule) { | ||
| 33 | + const { dir, name } = path.parse(process.mainModule.filename); | ||
| 34 | + return path.join(dir, `${name}.snapshot`); | ||
| 35 | + } | ||
| 36 | + if (!process.argv[1]) { | ||
| 37 | + throw new ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED(); | ||
| 38 | + } | ||
| 39 | + const { dir, name } = path.parse(process.argv[1]); | ||
| 40 | + return path.join(dir, `${name}.snapshot`); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + function getSource() { | ||
| 44 | + return createReadStream(getSnapshotPath(), { encoding: 'utf8' }); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + let _target; | ||
| 48 | + function getTarget() { | ||
| 49 | + _target ??= createWriteStream(getSnapshotPath(), { encoding: 'utf8' }); | ||
| 50 | + return _target; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + function serializeName(name) { | ||
| 54 | + validateString(name, 'name'); | ||
| 55 | + return StringPrototypeReplace(`${name}`, kKeyDelimiter, '_'); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + let writtenNames; | ||
| 59 | + let snapshotValue; | ||
| 60 | + let counter = 0; | ||
| 61 | + | ||
| 62 | + async function writeSnapshot({ name, value }) { | ||
| 63 | + const target = getTarget(); | ||
| 64 | + if (counter > 1) { | ||
| 65 | + target.write(kDefaultDelimiter); | ||
| 66 | + } | ||
| 67 | + writtenNames = writtenNames || new SafeSet(); | ||
| 68 | + if (writtenNames.has(name)) { | ||
| 69 | + throw new AssertionError({ message: `Snapshot "${name}" already used` }); | ||
| 70 | + } | ||
| 71 | + writtenNames.add(name); | ||
| 72 | + const drained = target.write(`${name}:\n${value}`); | ||
| 73 | + await drained || once(target, 'drain'); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + async function getSnapshot() { | ||
| 77 | + if (snapshotValue !== undefined) { | ||
| 78 | + return snapshotValue; | ||
| 79 | + } | ||
| 80 | + if (kUpdateSnapshot) { | ||
| 81 | + snapshotValue = kInitialSnapshot; | ||
| 82 | + return kInitialSnapshot; | ||
| 83 | + } | ||
| 84 | + try { | ||
| 85 | + const source = getSource(); | ||
| 86 | + let data = ''; | ||
| 87 | + for await (const line of source) { | ||
| 88 | + data += line; | ||
| 89 | + } | ||
| 90 | + snapshotValue = new SafeMap( | ||
| 91 | + ArrayPrototypeMap( | ||
| 92 | + StringPrototypeSplit(data, kDefaultDelimiterRegex), | ||
| 93 | + (item) => { | ||
| 94 | + const arr = StringPrototypeSplit(item, kKeyDelimiter); | ||
| 95 | + return [ | ||
| 96 | + arr[0], | ||
| 97 | + ArrayPrototypeJoin(ArrayPrototypeSlice(arr, 1), ':\n'), | ||
| 98 | + ]; | ||
| 99 | + } | ||
| 100 | + )); | ||
| 101 | + } catch (e) { | ||
| 102 | + if (e.code === 'ENOENT') { | ||
| 103 | + snapshotValue = kInitialSnapshot; | ||
| 104 | + } else { | ||
| 105 | + throw e; | ||
| 106 | + } | ||
| 107 | + } | ||
| 108 | + return snapshotValue; | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + | ||
| 112 | + async function snapshot(input, name) { | ||
| 113 | + const snapshot = await getSnapshot(); | ||
| 114 | + counter = counter + 1; | ||
| 115 | + name = serializeName(name); | ||
| 116 | + | ||
| 117 | + const value = inspect(input); | ||
| 118 | + if (snapshot === kInitialSnapshot) { | ||
| 119 | + await writeSnapshot({ name, value }); | ||
| 120 | + } else if (snapshot.has(name)) { | ||
| 121 | + const expected = snapshot.get(name); | ||
| 122 | + // eslint-disable-next-line no-restricted-syntax | ||
| 123 | + assert.strictEqual(value, expected); | ||
| 124 | + } else { | ||
| 125 | + throw new AssertionError({ message: `Snapshot "${name}" does not exist`, actual: inspect(snapshot) }); | ||
| 126 | + } | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + module.exports = snapshot; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -936,6 +936,8 @@ module.exports = { | |||
| 936 | 936 | E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError); | |
| 937 | 937 | E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError); | |
| 938 | 938 | E('ERR_ASSERTION', '%s', Error); | |
| 939 | + E('ERR_ASSERT_SNAPSHOT_NOT_SUPPORTED', | ||
| 940 | + 'Snapshot is not supported in this context ', TypeError); | ||
| 939 | 941 | E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError); | |
| 940 | 942 | E('ERR_ASYNC_TYPE', 'Invalid name for async "type": %s', TypeError); | |
| 941 | 943 | E('ERR_BROTLI_INVALID_PARAM', '%s is not a valid Brotli parameter', RangeError); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -618,6 +618,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | |||
| 618 | 618 | &EnvironmentOptions::force_repl); | |
| 619 | 619 | AddAlias("-i", "--interactive"); | |
| 620 | 620 | ||
| 621 | + AddOption("--update-assert-snapshot", | ||
| 622 | + "update assert snapshot files", | ||
| 623 | + &EnvironmentOptions::update_assert_snapshot, | ||
| 624 | + kAllowedInEnvironment); | ||
| 625 | + | ||
| 621 | 626 | AddOption("--napi-modules", "", NoOp{}, kAllowedInEnvironment); | |
| 622 | 627 | ||
| 623 | 628 | AddOption("--tls-keylog", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -136,6 +136,7 @@ class EnvironmentOptions : public Options { | |||
| 136 | 136 | bool preserve_symlinks = false; | |
| 137 | 137 | bool preserve_symlinks_main = false; | |
| 138 | 138 | bool prof_process = false; | |
| 139 | + bool update_assert_snapshot = false; | ||
| 139 | 140 | #if HAVE_INSPECTOR | |
| 140 | 141 | std::string cpu_prof_dir; | |
| 141 | 142 | static const uint64_t kDefaultCpuProfInterval = 1000; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,3 @@ | |||
| 1 | + import assert from 'node:assert'; | ||
| 2 | + | ||
| 3 | + await assert.snapshot("test", "name"); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,4 @@ | |||
| 1 | + import assert from 'node:assert'; | ||
| 2 | + | ||
| 3 | + await assert.snapshot("test", "name"); | ||
| 4 | + await assert.snapshot("test", "another name"); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments