1'use strict'; 2 3require('../common'); 4const fixtures = require('../common/fixtures'); 5const tmpdir = require('../common/tmpdir'); 6const { describe, it } = require('node:test'); 7const { spawnSync } = require('node:child_process'); 8const assert = require('node:assert'); 9const path = require('node:path'); 10const fs = require('node:fs'); 11 12const testFile = fixtures.path('test-runner/reporters.js'); 13tmpdir.refresh(); 14 15let tmpFiles = 0; 16describe('node:test reporters', { concurrency: true }, () => { 17 it('should default to outputing TAP to stdout', async () => { 18 const child = spawnSync(process.execPath, ['--test', testFile]); 19 assert.strictEqual(child.stderr.toString(), ''); 20 assert.match(child.stdout.toString(), /TAP version 13/); 21 assert.match(child.stdout.toString(), /ok 1 - ok/); 22 assert.match(child.stdout.toString(), /not ok 2 - failing/); 23 assert.match(child.stdout.toString(), /ok 2 - top level/); 24 }); 25 26 it('should default destination to stdout when passing a single reporter', async () => { 27 const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'dot', testFile]); 28 assert.strictEqual(child.stderr.toString(), ''); 29 assert.strictEqual(child.stdout.toString(), '.XX.\n'); 30 }); 31 32 it('should throw when passing reporters without a destination', async () => { 33 const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'dot', '--test-reporter', 'tap', testFile]); 34 assert.match(child.stderr.toString(), /The argument '--test-reporter' must match the number of specified '--test-reporter-destination'\. Received \[ 'dot', 'tap' \]/); 35 assert.strictEqual(child.stdout.toString(), ''); 36 }); 37 38 it('should throw when passing a destination without a reporter', async () => { 39 const child = spawnSync(process.execPath, ['--test', '--test-reporter-destination', 'tap', testFile]); 40 assert.match(child.stderr.toString(), /The argument '--test-reporter' must match the number of specified '--test-reporter-destination'\. Received \[\]/); 41 assert.strictEqual(child.stdout.toString(), ''); 42 }); 43 44 it('should support stdout as a destination', async () => { 45 const child = spawnSync(process.execPath, 46 ['--test', '--test-reporter', 'dot', '--test-reporter-destination', 'stdout', testFile]); 47 assert.strictEqual(child.stderr.toString(), ''); 48 assert.strictEqual(child.stdout.toString(), '.XX.\n'); 49 }); 50 51 it('should support stderr as a destination', async () => { 52 const child = spawnSync(process.execPath, 53 ['--test', '--test-reporter', 'dot', '--test-reporter-destination', 'stderr', testFile]); 54 assert.strictEqual(child.stderr.toString(), '.XX.\n'); 55 assert.strictEqual(child.stdout.toString(), ''); 56 }); 57 58 it('should support a file as a destination', async () => { 59 const file = path.join(tmpdir.path, `${tmpFiles++}.out`); 60 const child = spawnSync(process.execPath, 61 ['--test', '--test-reporter', 'dot', '--test-reporter-destination', file, testFile]); 62 assert.strictEqual(child.stderr.toString(), ''); 63 assert.strictEqual(child.stdout.toString(), ''); 64 assert.strictEqual(fs.readFileSync(file, 'utf8'), '.XX.\n'); 65 }); 66 67 it('should disallow using v8-serializer as reporter', async () => { 68 const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'v8-serializer', testFile]); 69 assert.strictEqual(child.stdout.toString(), ''); 70 assert(child.status > 0); 71 assert.match(child.stderr.toString(), /ERR_MODULE_NOT_FOUND/); 72 }); 73 74 it('should support multiple reporters', async () => { 75 const file = path.join(tmpdir.path, `${tmpFiles++}.out`); 76 const file2 = path.join(tmpdir.path, `${tmpFiles++}.out`); 77 const child = spawnSync(process.execPath, 78 ['--test', 79 '--test-reporter', 'dot', '--test-reporter-destination', file, 80 '--test-reporter', 'spec', '--test-reporter-destination', file2, 81 '--test-reporter', 'tap', '--test-reporter-destination', 'stdout', 82 testFile]); 83 assert.match(child.stdout.toString(), /TAP version 13/); 84 assert.match(child.stdout.toString(), /# duration_ms/); 85 assert.strictEqual(fs.readFileSync(file, 'utf8'), '.XX.\n'); 86 const file2Contents = fs.readFileSync(file2, 'utf8'); 87 assert.match(file2Contents, /▶ nested/); 88 assert.match(file2Contents, /✔ ok/); 89 assert.match(file2Contents, /✖ failing/); 90 }); 91 92 ['js', 'cjs', 'mjs'].forEach((ext) => { 93 it(`should support a '${ext}' file as a custom reporter`, async () => { 94 const filename = `custom.${ext}`; 95 const child = spawnSync(process.execPath, 96 ['--test', '--test-reporter', fixtures.fileURL('test-runner/custom_reporters/', filename), 97 testFile]); 98 assert.strictEqual(child.stderr.toString(), ''); 99 const stdout = child.stdout.toString(); 100 assert.match(stdout, /{"test:enqueue":5,"test:dequeue":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/); 101 assert.strictEqual(stdout.slice(0, filename.length + 2), `${filename} {`); 102 }); 103 }); 104 105 it('should support a custom reporter from node_modules', async () => { 106 const child = spawnSync(process.execPath, 107 ['--test', '--test-reporter', 'reporter-cjs', 'reporters.js'], 108 { cwd: fixtures.path('test-runner') }); 109 assert.strictEqual(child.stderr.toString(), ''); 110 assert.match( 111 child.stdout.toString(), 112 /^package: reporter-cjs{"test:enqueue":5,"test:dequeue":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/, 113 ); 114 }); 115 116 it('should support a custom ESM reporter from node_modules', async () => { 117 const child = spawnSync(process.execPath, 118 ['--test', '--test-reporter', 'reporter-esm', 'reporters.js'], 119 { cwd: fixtures.path('test-runner') }); 120 assert.strictEqual(child.stderr.toString(), ''); 121 assert.match( 122 child.stdout.toString(), 123 /^package: reporter-esm{"test:enqueue":5,"test:dequeue":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/, 124 ); 125 }); 126 127 it('should throw when reporter setup throws asynchronously', async () => { 128 const child = spawnSync( 129 process.execPath, 130 ['--test', '--test-reporter', fixtures.fileURL('empty.js'), 'reporters.js'], 131 { cwd: fixtures.path('test-runner') } 132 ); 133 assert.strictEqual(child.status, 7); 134 assert.strictEqual(child.signal, null); 135 assert.strictEqual(child.stdout.toString(), ''); 136 assert.match(child.stderr.toString(), /ERR_INVALID_ARG_TYPE/); 137 }); 138 139 it('should throw when reporter errors', async () => { 140 const child = spawnSync(process.execPath, 141 ['--test', '--test-reporter', fixtures.fileURL('test-runner/custom_reporters/throwing.js'), 142 fixtures.path('test-runner/default-behavior/index.test.js')]); 143 assert.strictEqual(child.status, 7); 144 assert.strictEqual(child.signal, null); 145 assert.strictEqual(child.stdout.toString(), 'Going to throw an error\n'); 146 assert.match(child.stderr.toString(), /Error: Reporting error\r?\n\s+at customReporter/); 147 }); 148 149 it('should throw when reporter errors asynchronously', async () => { 150 const child = spawnSync(process.execPath, 151 ['--test', '--test-reporter', 152 fixtures.fileURL('test-runner/custom_reporters/throwing-async.js'), 153 fixtures.path('test-runner/default-behavior/index.test.js')]); 154 assert.strictEqual(child.status, 7); 155 assert.strictEqual(child.signal, null); 156 assert.strictEqual(child.stdout.toString(), 'Going to throw an error\n'); 157 assert.match(child.stderr.toString(), /Emitted 'error' event on Duplex instance/); 158 }); 159}); 160