@@ -20,23 +20,66 @@ import {
20
20
21
21
export const SLOW_TEST_THRESHOLD = 75 ;
22
22
23
+ /**
24
+ * This is a node:test reporter that tries to mimic Mocha's default reporter, as
25
+ * close as possible.
26
+ *
27
+ * It is designed to output information about the test runs as soon as possible
28
+ * and in test defintion order.
29
+ *
30
+ * Once the test run ends, it will output global information about it, based on
31
+ * the diagnostics emitted by node:test, and any custom or unrecognized
32
+ * diagnostics message.
33
+ *
34
+ * Finally, it will output the failure reasons for all the failed tests.
35
+ *
36
+ * @param source
37
+ */
23
38
export default async function * customReporter (
24
39
source : TestEventSource ,
25
40
) : TestReporterResult {
26
- // This reporter prints the tests in definition order, imitating Mocha's
27
- // default reporter as close as possible.
28
- //
29
- // We use this stack to keep track of the current test hierarchy.
30
- //
31
- // The output of each test (its context - the describe they belong to -
32
- // and if they passed/failed) is printed as soon as the test finishes.
41
+ /**
42
+ * The test reporter works by keeping a stack of the currently executing[1]
43
+ * tests and suites. We use it to keep track of the context of a test, so that
44
+ * when it passes or fails, we can print that context (if necessary), before
45
+ * its results.
46
+ *
47
+ * Printing the context of a test will normally imply printing all the
48
+ * describes where it is nested.
49
+ *
50
+ * As the context may be shared by more than one test, and we don't want to
51
+ * repeate it, we keep track of the last printed context element. We do this
52
+ * by keeping track of its index in the stack.
53
+ *
54
+ * We also keep track of any diagnostic message that its reported by node:test
55
+ * and at the end we try to parse the global diagnostics to gather information
56
+ * about the test run. If during this parsing we don't recognize or can't
57
+ * properly parse one of this diagnostics, we will print it at the end.
58
+ *
59
+ * Whenever a test fails, we pre-format its failure reason, so that don't need
60
+ * to keep the failure event in memory, and we can still print the failure
61
+ * reason at the end.
62
+ *
63
+ * This code is structed in the following way:
64
+ * - We use an async generator to process the events as they come, printing
65
+ * the information as soon as possible.
66
+ * - Instead of printing, we yield string.
67
+ * - Any formatting that needs to be done is done in the formatting module.
68
+ * - The formatting module exports functions that generate strings for the
69
+ * different parts of the test run's output. They do not print anything, and
70
+ * they never end in a newline. Any newline between different parts of the
71
+ * output is added by the generator.
72
+ * - The generaor drives the high-level format of the output, and only uses
73
+ * the formatting functions to generate repetitive parts of it.
74
+ *
75
+ * [1] As reporter by node:test, in defintion order, which may differ from
76
+ * actual execution order.
77
+ */
78
+
33
79
const stack : Array < TestEventData [ "test:start" ] > = [ ] ;
34
80
35
- // We use this number to keep track of which elements from the stack have
36
- // already been printed (e.g. a describe).
37
81
let lastPrintedIndex : number | undefined ;
38
82
39
- // Diagnostics are processed at the end, so we collect them all here
40
83
const diagnostics : Array < TestEventData [ "test:diagnostic" ] > = [ ] ;
41
84
42
85
const preFormattedFailureReasons : string [ ] = [ ] ;
@@ -55,7 +98,7 @@ export default async function* customReporter(
55
98
case "test:fail" : {
56
99
if ( event . data . details . type === "suite" ) {
57
100
// If a suite failed for a reason other than a subtest failing, we
58
- // want to print its failure, so we push it to the failures array .
101
+ // want to print its failure.
59
102
if ( event . type === "test:fail" ) {
60
103
if ( ! isSubtestFailedError ( event . data . details . error ) ) {
61
104
preFormattedFailureReasons . push (
@@ -68,14 +111,11 @@ export default async function* customReporter(
68
111
}
69
112
}
70
113
71
- stack . pop ( ) ;
72
-
73
114
// If a suite/describe was already printed, we need to descrease
74
115
// the lastPrintedIndex, as we are removing it from the stack.
75
116
//
76
117
// If its nesting was 0, we print an empty line to separate top-level
77
118
// describes.
78
-
79
119
if ( event . data . nesting === 0 ) {
80
120
lastPrintedIndex = undefined ;
81
121
yield "\n" ;
@@ -89,11 +129,13 @@ export default async function* customReporter(
89
129
}
90
130
}
91
131
132
+ // Remove the current test from the stack, as it was just processed
133
+ stack . pop ( ) ;
92
134
continue ;
93
135
}
94
136
95
137
// If we have printed everything except the current element in the stack
96
- // all of it's context/hierarchy has been printed (e.g. the describes).\
138
+ // all of it's context/hierarchy has been printed (e.g. its describes).
97
139
//
98
140
// Otherwise, we print all the unprinted elements in the stack, except
99
141
// for the last one, which is the current test.
@@ -117,24 +159,27 @@ export default async function* customReporter(
117
159
contextStack : stack ,
118
160
} ;
119
161
162
+ // We format the failure reason and store it in an array, so that we
163
+ // can output it at the end.
120
164
preFormattedFailureReasons . push ( formatFailureReason ( failure ) ) ;
121
165
122
166
yield formatTestFailure ( failure ) ;
123
167
}
124
168
169
+ // If the test was slow, we print a message about it
125
170
if ( event . data . details . duration_ms > SLOW_TEST_THRESHOLD ) {
126
171
yield formatSlowTestInfo ( event . data . details . duration_ms ) ;
127
172
}
128
173
129
174
yield "\n" ;
130
175
131
- stack . pop ( ) ;
132
-
133
176
// Top-level tests are separated by an empty line
134
177
if ( event . data . nesting === 0 ) {
135
178
yield "\n" ;
136
179
}
137
180
181
+ // Remove the current test from the stack, as it was just processed it
182
+ stack . pop ( ) ;
138
183
break ;
139
184
}
140
185
case "test:stderr" : {
0 commit comments