1'use strict'; 2const common = require('.'); 3const path = require('node:path'); 4const test = require('node:test'); 5const fs = require('node:fs/promises'); 6const assert = require('node:assert/strict'); 7 8const stackFramesRegexp = /(?<=\n)(\s+)((.+?)\s+\()?(?:\(?(.+?):(\d+)(?::(\d+))?)\)?(\s+\{)?(\[\d+m)?(\n|$)/g; 9const windowNewlineRegexp = /\r/g; 10 11function replaceNodeVersion(str) { 12 return str.replaceAll(process.version, '*'); 13} 14 15function replaceStackTrace(str, replacement = '$1*$7$8\n') { 16 return str.replace(stackFramesRegexp, replacement); 17} 18 19function replaceWindowsLineEndings(str) { 20 return str.replace(windowNewlineRegexp, ''); 21} 22 23function replaceWindowsPaths(str) { 24 return common.isWindows ? str.replaceAll(path.win32.sep, path.posix.sep) : str; 25} 26 27function replaceFullPaths(str) { 28 return str.replaceAll(process.cwd(), ''); 29} 30 31function transform(...args) { 32 return (str) => args.reduce((acc, fn) => fn(acc), str); 33} 34 35function getSnapshotPath(filename) { 36 const { name, dir } = path.parse(filename); 37 return path.resolve(dir, `${name}.snapshot`); 38} 39 40async function assertSnapshot(actual, filename = process.argv[1]) { 41 const snapshot = getSnapshotPath(filename); 42 if (process.env.NODE_REGENERATE_SNAPSHOTS) { 43 await fs.writeFile(snapshot, actual); 44 } else { 45 let expected; 46 try { 47 expected = await fs.readFile(snapshot, 'utf8'); 48 } catch (e) { 49 if (e.code === 'ENOENT') { 50 console.log( 51 'Snapshot file does not exist. You can create a new one by running the test with NODE_REGENERATE_SNAPSHOTS=1', 52 ); 53 } 54 throw e; 55 } 56 assert.strictEqual(actual, replaceWindowsLineEndings(expected)); 57 } 58} 59 60/** 61 * Spawn a process and assert its output against a snapshot. 62 * if you want to automatically update the snapshot, run tests with NODE_REGENERATE_SNAPSHOTS=1 63 * transform is a function that takes the output and returns a string that will be compared against the snapshot 64 * this is useful for normalizing output such as stack traces 65 * there are some predefined transforms in this file such as replaceStackTrace and replaceWindowsLineEndings 66 * both of which can be used as an example for writing your own 67 * compose multiple transforms by passing them as arguments to the transform function: 68 * assertSnapshot.transform(assertSnapshot.replaceStackTrace, assertSnapshot.replaceWindowsLineEndings) 69 * @param {string} filename 70 * @param {function(string): string} [transform] 71 * @param {object} [options] - control how the child process is spawned 72 * @param {boolean} [options.tty] - whether to spawn the process in a pseudo-tty 73 * @returns {Promise<void>} 74 */ 75async function spawnAndAssert(filename, transform = (x) => x, { tty = false, ...options } = {}) { 76 if (tty && common.isWindows) { 77 test({ skip: 'Skipping pseudo-tty tests, as pseudo terminals are not available on Windows.' }); 78 return; 79 } 80 const flags = common.parseTestFlags(filename); 81 const executable = tty ? 'tools/pseudo-tty.py' : process.execPath; 82 const args = tty ? [process.execPath, ...flags, filename] : [...flags, filename]; 83 const { stdout, stderr } = await common.spawnPromisified(executable, args, options); 84 await assertSnapshot(transform(`${stdout}${stderr}`), filename); 85} 86 87module.exports = { 88 assertSnapshot, 89 getSnapshotPath, 90 replaceFullPaths, 91 replaceNodeVersion, 92 replaceStackTrace, 93 replaceWindowsLineEndings, 94 replaceWindowsPaths, 95 spawnAndAssert, 96 transform, 97}; 98