| '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; |