| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent bb24a88 commit d31c168
12 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3468,6 +3468,10 @@ ordered events, emitted immediately as the tests execute. | |||
| 3468 | 3468 | | [`'test:fail'`][] | [`'test:complete'`][] (`details.passed` is `false`) | | |
| 3469 | 3469 | | [`'test:plan'`][] | | | |
| 3470 | 3470 | | [`'test:diagnostic'`][] | | | |
| 3471 | + | | [`'test:log'`][] | | ||
| 3472 | + | ||
| 3473 | + [`'test:log'`][] is deliberately execution ordered only: it is the live | ||
| 3474 | + counterpart of [`'test:diagnostic'`][]'s buffered reporting. | ||
| 3471 | 3475 | ||
| 3472 | 3476 | File scoped and global events are always emitted immediately, in execution | |
| 3473 | 3477 | order. | |
@@ -3735,6 +3739,38 @@ When using process isolation (the default), the test name will be the file path | |||
| 3735 | 3739 | since the parent runner only knows about file-level tests. When using | |
| 3736 | 3740 | `--test-isolation=none`, the actual test name is shown. | |
| 3737 | 3741 | ||
| 3742 | + ### Event: `'test:log'` | ||
| 3743 | + | ||
| 3744 | + <!-- YAML | ||
| 3745 | + added: REPLACEME | ||
| 3746 | + --> | ||
| 3747 | + | ||
| 3748 | + * `data` {Object} | ||
| 3749 | + * `column` {number|undefined} The column number where the test is defined, or | ||
| 3750 | + `undefined` if the test was run through the REPL. | ||
| 3751 | + * `data` {any} The structured payload passed to [`context.log`][], or | ||
| 3752 | + `undefined` if none was provided. The test runner does not interpret this | ||
| 3753 | + value. | ||
| 3754 | + * `entryFile` {string|undefined} The path of the test file that was | ||
| 3755 | + executed as the entry point of the child process that emitted this event. | ||
| 3756 | + Only present when tests run with process isolation. May differ from | ||
| 3757 | + `file` when the test is defined in a module imported by the entry file. | ||
| 3758 | + * `file` {string|undefined} The path of the test file, | ||
| 3759 | + `undefined` if test was run through the REPL. | ||
| 3760 | + * `line` {number|undefined} The line number where the test is defined, or | ||
| 3761 | + `undefined` if the test was run through the REPL. | ||
| 3762 | + * `message` {string} The log message. | ||
| 3763 | + * `name` {string} The test name. | ||
| 3764 | + * `nesting` {number} The nesting level of the test. | ||
| 3765 | + * `parentId` {number|undefined} The `testId` of the enclosing test, or | ||
| 3766 | + `undefined` for top-level tests. | ||
| 3767 | + * `testId` {number} A numeric identifier for the test instance that emitted | ||
| 3768 | + the log message. | ||
| 3769 | + | ||
| 3770 | + Emitted when [`context.log`][] is called. Unlike [`'test:diagnostic'`][], | ||
| 3771 | + this event is emitted immediately, in the order that the tests execute, | ||
| 3772 | + making it suitable for reporters that render test output unbuffered. | ||
| 3773 | + | ||
| 3738 | 3774 | ### Event: `'test:pass'` | |
| 3739 | 3775 | ||
| 3740 | 3776 | * `data` {Object} | |
@@ -4258,6 +4294,29 @@ test('top level test', (t) => { | |||
| 4258 | 4294 | }); | |
| 4259 | 4295 | ``` | |
| 4260 | 4296 | ||
| 4297 | + ### `context.log(message[, data])` | ||
| 4298 | + | ||
| 4299 | + <!-- YAML | ||
| 4300 | + added: REPLACEME | ||
| 4301 | + --> | ||
| 4302 | + | ||
| 4303 | + * `message` {string} Message to be reported. | ||
| 4304 | + * `data` {any} Optional structured payload attached to the message. The test | ||
| 4305 | + runner passes it through untouched. When tests run with process isolation, | ||
| 4306 | + this value must be compatible with the [HTML structured clone algorithm][]. | ||
| 4307 | + | ||
| 4308 | + This function is used to write a log message to the output. Unlike | ||
| 4309 | + [`context.diagnostic`][], the resulting [`'test:log'`][] event is emitted | ||
| 4310 | + immediately, in the order that the tests execute, rather than being buffered | ||
| 4311 | + until the test reports its results. This function does not return a value. | ||
| 4312 | + | ||
| 4313 | + ```js | ||
| 4314 | + test('top level test', (t) => { | ||
| 4315 | + t.log('fetched user', { userId: 42 }); | ||
| 4316 | + t.log('retrying flaky endpoint', { attempt: 3 }); | ||
| 4317 | + }); | ||
| 4318 | + ``` | ||
| 4319 | + | ||
| 4261 | 4320 | ### `context.filePath` | |
| 4262 | 4321 | ||
| 4263 | 4322 | <!-- YAML | |
@@ -4739,6 +4798,26 @@ test.describe('my suite', (suite) => { | |||
| 4739 | 4798 | }); | |
| 4740 | 4799 | ``` | |
| 4741 | 4800 | ||
| 4801 | + ### `context.log(message[, data])` | ||
| 4802 | + | ||
| 4803 | + <!-- YAML | ||
| 4804 | + added: REPLACEME | ||
| 4805 | + --> | ||
| 4806 | + | ||
| 4807 | + * `message` {string} Message to be reported. | ||
| 4808 | + * `data` {any} Optional structured payload attached to the message. The test | ||
| 4809 | + runner passes it through untouched. | ||
| 4810 | + | ||
| 4811 | + Write a log message to the output. The resulting [`'test:log'`][] event is | ||
| 4812 | + emitted immediately, in the order that the tests execute. | ||
| 4813 | + | ||
| 4814 | + ```js | ||
| 4815 | + test.describe('my suite', (suite) => { | ||
| 4816 | + suite.log('Suite log message'); | ||
| 4817 | + }); | ||
| 4818 | + ``` | ||
| 4819 | + | ||
| 4820 | + [HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm | ||
| 4742 | 4821 | [TAP]: https://testanything.org/ | |
| 4743 | 4822 | [Test tags]: #test-tags | |
| 4744 | 4823 | [`'test:complete'`]: #event-testcomplete | |
@@ -4748,6 +4827,7 @@ test.describe('my suite', (suite) => { | |||
| 4748 | 4827 | [`'test:enqueue'`]: #event-testenqueue | |
| 4749 | 4828 | [`'test:fail'`]: #event-testfail | |
| 4750 | 4829 | [`'test:interrupted'`]: #event-testinterrupted | |
| 4830 | + [`'test:log'`]: #event-testlog | ||
| 4751 | 4831 | [`'test:pass'`]: #event-testpass | |
| 4752 | 4832 | [`'test:plan'`]: #event-testplan | |
| 4753 | 4833 | [`'test:start'`]: #event-teststart | |
@@ -4783,6 +4863,7 @@ test.describe('my suite', (suite) => { | |||
| 4783 | 4863 | [`TracingChannel`]: diagnostics_channel.md#class-tracingchannel | |
| 4784 | 4864 | [`assert.throws`]: assert.md#assertthrowsfn-error-message | |
| 4785 | 4865 | [`context.diagnostic`]: #contextdiagnosticmessage | |
| 4866 | + [`context.log`]: #contextlogmessage-data | ||
| 4786 | 4867 | [`context.skip`]: #contextskipmessage | |
| 4787 | 4868 | [`context.tags`]: #contexttags | |
| 4788 | 4869 | [`context.todo`]: #contexttodomessage | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -154,7 +154,8 @@ module.exports = async function* junitReporter(source) { | |||
| 154 | 154 | } | |
| 155 | 155 | break; | |
| 156 | 156 | } | |
| 157 | - case 'test:diagnostic': { | ||
| 157 | + case 'test:diagnostic': | ||
| 158 | + case 'test:log': { | ||
| 158 | 159 | const parent = currentSuite?.children ?? roots; | |
| 159 | 160 | ArrayPrototypePush(parent, { | |
| 160 | 161 | __proto__: null, nesting: event.data.nesting, comment: event.data.message, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -91,8 +91,9 @@ class SpecReporter extends Transform { | |||
| 91 | 91 | case 'test:stderr': | |
| 92 | 92 | case 'test:stdout': | |
| 93 | 93 | return data.message; | |
| 94 | - case 'test:diagnostic':{ | ||
| 95 | - const diagnosticColor = reporterColorMap[data.level] || reporterColorMap['test:diagnostic']; | ||
| 94 | + case 'test:diagnostic': | ||
| 95 | + case 'test:log': { | ||
| 96 | + const diagnosticColor = reporterColorMap[data.level] || reporterColorMap[type]; | ||
| 96 | 97 | return `${diagnosticColor}${indent(data.nesting)}${reporterUnicodeSymbolMap[type]}${data.message}${colors.white}\n`; | |
| 97 | 98 | } | |
| 98 | 99 | case 'test:coverage': | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -56,6 +56,7 @@ async function * tapReporter(source) { | |||
| 56 | 56 | } | |
| 57 | 57 | break; | |
| 58 | 58 | } case 'test:diagnostic': | |
| 59 | + case 'test:log': | ||
| 59 | 60 | yield `${indent(data.nesting)}# ${tapEscape(data.message)}\n`; | |
| 60 | 61 | break; | |
| 61 | 62 | case 'test:coverage': | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,6 +21,7 @@ const reporterUnicodeSymbolMap = { | |||
| 21 | 21 | 'test:fail': '\u2716 ', | |
| 22 | 22 | 'test:pass': '\u2714 ', | |
| 23 | 23 | 'test:diagnostic': '\u2139 ', | |
| 24 | + 'test:log': '\u2139 ', | ||
| 24 | 25 | 'test:coverage': '\u2139 ', | |
| 25 | 26 | 'arrow:right': '\u25B6 ', | |
| 26 | 27 | 'hyphen:minus': '\uFE63 ', | |
@@ -38,6 +39,9 @@ const reporterColorMap = { | |||
| 38 | 39 | get 'test:diagnostic'() { | |
| 39 | 40 | return colors.blue; | |
| 40 | 41 | }, | |
| 42 | + get 'test:log'() { | ||
| 43 | + return colors.blue; | ||
| 44 | + }, | ||
| 41 | 45 | get 'info'() { | |
| 42 | 46 | return colors.blue; | |
| 43 | 47 | }, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -130,7 +130,7 @@ const kCanceledTests = new SafeSet() | |||
| 130 | 130 | // Execution-ordered events are forwarded immediately, bypassing the | |
| 131 | 131 | // per-file declaration-order buffer. | |
| 132 | 132 | const kExecutionOrderedEvents = new SafeSet() | |
| 133 | - .add('test:enqueue').add('test:dequeue').add('test:complete'); | ||
| 133 | + .add('test:enqueue').add('test:dequeue').add('test:complete').add('test:log'); | ||
| 134 | 134 | ||
| 135 | 135 | let kResistStopPropagation; | |
| 136 | 136 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -80,6 +80,7 @@ const { | |||
| 80 | 80 | validateNumber, | |
| 81 | 81 | validateObject, | |
| 82 | 82 | validateOneOf, | |
| 83 | + validateString, | ||
| 83 | 84 | validateUint32, | |
| 84 | 85 | } = require('internal/validators'); | |
| 85 | 86 | const { | |
@@ -315,6 +316,10 @@ class TestContext { | |||
| 315 | 316 | this.#test.diagnostic(message); | |
| 316 | 317 | } | |
| 317 | 318 | ||
| 319 | + log(message, data) { | ||
| 320 | + this.#test.log(message, data); | ||
| 321 | + } | ||
| 322 | + | ||
| 318 | 323 | plan(count, options = kEmptyObject) { | |
| 319 | 324 | if (this.#test.plan !== null) { | |
| 320 | 325 | throw new ERR_TEST_FAILURE( | |
@@ -523,6 +528,10 @@ class SuiteContext { | |||
| 523 | 528 | diagnostic(message) { | |
| 524 | 529 | this.#suite.diagnostic(message); | |
| 525 | 530 | } | |
| 531 | + | ||
| 532 | + log(message, data) { | ||
| 533 | + this.#suite.log(message, data); | ||
| 534 | + } | ||
| 526 | 535 | } | |
| 527 | 536 | ||
| 528 | 537 | function parseExpectFailure(expectFailure) { | |
@@ -1198,6 +1207,12 @@ class Test extends AsyncResource { | |||
| 1198 | 1207 | ArrayPrototypePush(this.diagnostics, message); | |
| 1199 | 1208 | } | |
| 1200 | 1209 | ||
| 1210 | + log(message, data) { | ||
| 1211 | + validateString(message, 'message'); | ||
| 1212 | + this.reporter.log(this.nesting, this.loc, message, data, | ||
| 1213 | + this.name, this.testId, this.parent?.testId); | ||
| 1214 | + } | ||
| 1215 | + | ||
| 1201 | 1216 | start() { | |
| 1202 | 1217 | this.applyFilters(); | |
| 1203 | 1218 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -139,6 +139,19 @@ class TestsStream extends Readable { | |||
| 139 | 139 | }); | |
| 140 | 140 | } | |
| 141 | 141 | ||
| 142 | + log(nesting, loc, message, data, name, testId, parentId) { | ||
| 143 | + this[kEmitMessage]('test:log', { | ||
| 144 | + __proto__: null, | ||
| 145 | + name, | ||
| 146 | + nesting, | ||
| 147 | + testId, | ||
| 148 | + parentId, | ||
| 149 | + message, | ||
| 150 | + data, | ||
| 151 | + ...loc, | ||
| 152 | + }); | ||
| 153 | + } | ||
| 154 | + | ||
| 142 | 155 | diagnostic(nesting, loc, message, level = 'info') { | |
| 143 | 156 | this[kEmitMessage]('test:diagnostic', { | |
| 144 | 157 | __proto__: null, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,7 @@ | |||
| 1 | 1 | import { test } from 'node:test'; | |
| 2 | 2 | import assert from 'node:assert'; | |
| 3 | 3 | ||
| 4 | - test('fast-fail', () => { | ||
| 4 | + test('fast-fail', (t) => { | ||
| 5 | + t.log('live'); | ||
| 5 | 6 | assert.fail('fast'); | |
| 6 | 7 | }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,19 @@ | |||
| 1 | + import { suite, test } from 'node:test'; | ||
| 2 | + import { setTimeout } from 'node:timers/promises'; | ||
| 3 | + | ||
| 4 | + suite('my suite', (s) => { | ||
| 5 | + s.log('suite message'); | ||
| 6 | + test('in suite', () => {}); | ||
| 7 | + }); | ||
| 8 | + | ||
| 9 | + test('parent', { concurrency: 2 }, async (t) => { | ||
| 10 | + await Promise.all([ | ||
| 11 | + t.test('slow', async () => { | ||
| 12 | + await setTimeout(200); | ||
| 13 | + }), | ||
| 14 | + t.test('logger', (t) => { | ||
| 15 | + t.log('hello', { foo: 1 }); | ||
| 16 | + t.log('warned', { level: 'warn', attempt: 2 }); | ||
| 17 | + }), | ||
| 18 | + ]); | ||
| 19 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments