Skip to content

Commit d1343a7

Browse files
committed
feat: parse yaml
PR-URL: nodejs/node#45815 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]> (cherry picked from commit 232efb06fe8787e9573e298ce7ac293ad23b7684)
1 parent 4e778bc commit d1343a7

File tree

5 files changed

+134
-22
lines changed

5 files changed

+134
-22
lines changed

lib/internal/per_context/primordials.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ exports.ArrayPrototypeSome = (arr, fn) => arr.some(fn)
2020
exports.ArrayPrototypeSort = (arr, fn) => arr.sort(fn)
2121
exports.ArrayPrototypeSplice = (arr, offset, len, ...el) => arr.splice(offset, len, ...el)
2222
exports.ArrayPrototypeUnshift = (arr, ...el) => arr.unshift(...el)
23+
exports.Boolean = Boolean
2324
exports.Error = Error
2425
exports.ErrorCaptureStackTrace = (...args) => Error.captureStackTrace(...args)
2526
exports.FunctionPrototype = Function.prototype
@@ -28,6 +29,7 @@ exports.FunctionPrototypeCall = (fn, obj, ...args) => fn.call(obj, ...args)
2829
exports.MathMax = (...args) => Math.max(...args)
2930
exports.Number = Number
3031
exports.NumberIsInteger = Number.isInteger
32+
exports.NumberIsNaN = Number.isNaN
3133
exports.NumberParseInt = (str, radix) => Number.parseInt(str, radix)
3234
exports.NumberMIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER
3335
exports.NumberMAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER
@@ -56,6 +58,7 @@ exports.SafePromiseRace = (array, mapFn) => Promise.race(mapFn ? array.map(mapFn
5658
exports.SafeSet = Set
5759
exports.SafeWeakMap = WeakMap
5860
exports.SafeWeakSet = WeakSet
61+
exports.String = String
5962
exports.StringPrototypeEndsWith = (haystack, needle, index) => haystack.endsWith(needle, index)
6063
exports.StringPrototypeIncludes = (str, needle) => str.includes(needle)
6164
exports.StringPrototypeMatch = (str, reg) => str.match(reg)
@@ -66,6 +69,7 @@ exports.StringPrototypeReplaceAll = replaceAll
6669
exports.StringPrototypeStartsWith = (haystack, needle, index) => haystack.startsWith(needle, index)
6770
exports.StringPrototypeSlice = (str, ...args) => str.slice(...args)
6871
exports.StringPrototypeSplit = (str, search, limit) => str.split(search, limit)
72+
exports.StringPrototypeSubstring = (str, ...args) => str.substring(...args)
6973
exports.StringPrototypeToUpperCase = str => str.toUpperCase()
7074
exports.StringPrototypeTrim = str => str.trim()
7175
exports.Symbol = Symbol

lib/internal/test_runner/runner.js

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
// https://github.com/nodejs/node/blob/fec0fbc333c58e6ebbd2322f5120830fda880eb0/lib/internal/test_runner/runner.js
1+
// https://github.com/nodejs/node/blob/232efb06fe8787e9573e298ce7ac293ad23b7684/lib/internal/test_runner/runner.js
22
'use strict'
33
const {
44
ArrayFrom,
55
ArrayPrototypeFilter,
66
ArrayPrototypeForEach,
77
ArrayPrototypeIncludes,
8-
ArrayPrototypeJoin,
98
ArrayPrototypePush,
109
ArrayPrototypeSlice,
1110
ArrayPrototypeSort,
@@ -32,6 +31,7 @@ const { kEmptyObject } = require('#internal/util')
3231
const { createTestTree } = require('#internal/test_runner/harness')
3332
const { kDefaultIndent, kSubtestsFailed, Test } = require('#internal/test_runner/test')
3433
const { TapParser } = require('#internal/test_runner/tap_parser')
34+
const { YAMLToJs } = require('#internal/test_runner/yaml_parser')
3535
const { TokenKind } = require('#internal/test_runner/tap_lexer')
3636
const {
3737
isSupportedFileType,
@@ -123,18 +123,6 @@ class FileTest extends Test {
123123
#handleReportItem ({ kind, node, nesting = 0 }) {
124124
const indent = StringPrototypeRepeat(kDefaultIndent, nesting + 1)
125125

126-
const details = (diagnostic) => {
127-
return (
128-
diagnostic && {
129-
__proto__: null,
130-
yaml:
131-
`${indent} ` +
132-
ArrayPrototypeJoin(diagnostic, `\n${indent} `) +
133-
'\n'
134-
}
135-
)
136-
}
137-
138126
switch (kind) {
139127
case TokenKind.TAP_VERSION:
140128
// TODO(manekinekko): handle TAP version coming from the parser.
@@ -168,15 +156,15 @@ class FileTest extends Test {
168156
indent,
169157
node.id,
170158
node.description,
171-
details(node.diagnostics),
159+
YAMLToJs(node.diagnostics),
172160
directive
173161
)
174162
} else {
175163
this.reporter.fail(
176164
indent,
177165
node.id,
178166
node.description,
179-
details(node.diagnostics),
167+
YAMLToJs(node.diagnostics),
180168
directive
181169
)
182170
}

lib/internal/test_runner/tap_stream.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// https://github.com/nodejs/node/blob/22dc987fde29734c5bcbb7c33da20d184ff61627/lib/internal/test_runner/tap_stream.js
1+
// https://github.com/nodejs/node/blob/232efb06fe8787e9573e298ce7ac293ad23b7684/lib/internal/test_runner/tap_stream.js
22

33
'use strict'
44

@@ -86,11 +86,10 @@ class TapStream extends Readable {
8686
}
8787

8888
#details (indent, data = kEmptyObject) {
89-
const { error, duration, yaml } = data
89+
const { error, duration_ms } = data // eslint-disable-line camelcase
9090
let details = `${indent} ---\n`
9191

92-
details += `${yaml || ''}`
93-
details += jsToYaml(indent, 'duration_ms', duration)
92+
details += jsToYaml(indent, 'duration_ms', duration_ms)
9493
details += jsToYaml(indent, null, error)
9594
details += `${indent} ...\n`
9695
this.#tryPush(details)

lib/internal/test_runner/test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// https://github.com/nodejs/node/blob/215c5317d4837287fddb2e3b97872babd53183ac/lib/internal/test_runner/test.js
1+
// https://github.com/nodejs/node/blob/232efb06fe8787e9573e298ce7ac293ad23b7684/lib/internal/test_runner/test.js
22

33
'use strict'
44

@@ -668,7 +668,7 @@ class Test extends AsyncResource {
668668
this.reportSubtest()
669669
}
670670
let directive
671-
const details = { __proto__: null, duration: this.#duration() }
671+
const details = { __proto__: null, duration_ms: this.#duration() }
672672

673673
if (this.skipped) {
674674
directive = this.reporter.getSkip(this.message)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// https://github.com/nodejs/node/blob/232efb06fe8787e9573e298ce7ac293ad23b7684/lib/internal/test_runner/yaml_parser.js
2+
'use strict'
3+
const {
4+
codes: {
5+
ERR_TEST_FAILURE
6+
}
7+
} = require('#internal/errors')
8+
const AssertionError = require('assert').AssertionError
9+
const {
10+
ArrayPrototypeJoin,
11+
ArrayPrototypePush,
12+
Error,
13+
Number,
14+
NumberIsNaN,
15+
RegExpPrototypeExec,
16+
StringPrototypeEndsWith,
17+
StringPrototypeRepeat,
18+
StringPrototypeSlice,
19+
StringPrototypeStartsWith,
20+
StringPrototypeSubstring
21+
} = require('#internal/per_context/primordials')
22+
23+
const kYamlKeyRegex = /^(\s+)?(\w+):(\s)+([>|][-+])?(.*)$/
24+
const kStackDelimiter = ' at '
25+
26+
function reConstructError (parsedYaml) {
27+
if (!('error' in parsedYaml)) {
28+
return parsedYaml
29+
}
30+
const isAssertionError = parsedYaml.code === 'ERR_ASSERTION' ||
31+
'actual' in parsedYaml || 'expected' in parsedYaml || 'operator' in parsedYaml
32+
const isTestFailure = parsedYaml.code === 'ERR_TEST_FAILURE' || 'failureType' in parsedYaml
33+
const stack = parsedYaml.stack ? kStackDelimiter + ArrayPrototypeJoin(parsedYaml.stack, `\n${kStackDelimiter}`) : ''
34+
let error, cause
35+
36+
if (isAssertionError) {
37+
cause = new AssertionError({
38+
message: parsedYaml.error,
39+
actual: parsedYaml.actual,
40+
expected: parsedYaml.expected,
41+
operator: parsedYaml.operator
42+
})
43+
} else {
44+
// eslint-disable-next-line no-restricted-syntax
45+
cause = new Error(parsedYaml.error)
46+
cause.code = parsedYaml.code
47+
}
48+
cause.stack = stack
49+
50+
if (isTestFailure) {
51+
error = new ERR_TEST_FAILURE(cause, parsedYaml.failureType)
52+
error.stack = stack
53+
}
54+
55+
parsedYaml.error = error ?? cause
56+
delete parsedYaml.stack
57+
delete parsedYaml.code
58+
delete parsedYaml.failureType
59+
delete parsedYaml.actual
60+
delete parsedYaml.expected
61+
delete parsedYaml.operator
62+
63+
return parsedYaml
64+
}
65+
66+
function getYamlValue (value) {
67+
if (StringPrototypeStartsWith(value, "'") && StringPrototypeEndsWith(value, "'")) {
68+
return StringPrototypeSlice(value, 1, -1)
69+
}
70+
if (value === 'true') {
71+
return true
72+
}
73+
if (value === 'false') {
74+
return false
75+
}
76+
if (value !== '') {
77+
const valueAsNumber = Number(value)
78+
return NumberIsNaN(valueAsNumber) ? value : valueAsNumber
79+
}
80+
return value
81+
}
82+
83+
// This parses the YAML generated by the built-in TAP reporter,
84+
// which is a subset of the full YAML spec. There are some
85+
// YAML features that won't be parsed here. This function should not be exposed publicly.
86+
function YAMLToJs (lines) {
87+
if (lines == null) {
88+
return undefined
89+
}
90+
const result = { __proto__: null }
91+
let isInYamlBlock = false
92+
for (let i = 0; i < lines.length; i++) {
93+
const line = lines[i]
94+
if (isInYamlBlock && !StringPrototypeStartsWith(line, StringPrototypeRepeat(' ', isInYamlBlock.indent))) {
95+
result[isInYamlBlock.key] = isInYamlBlock.key === 'stack'
96+
? result[isInYamlBlock.key]
97+
: ArrayPrototypeJoin(result[isInYamlBlock.key], '\n')
98+
isInYamlBlock = false
99+
}
100+
if (isInYamlBlock) {
101+
const blockLine = StringPrototypeSubstring(line, isInYamlBlock.indent)
102+
ArrayPrototypePush(result[isInYamlBlock.key], blockLine)
103+
continue
104+
}
105+
const match = RegExpPrototypeExec(kYamlKeyRegex, line)
106+
if (match !== null) {
107+
const { 1: leadingSpaces, 2: key, 4: block, 5: value } = match
108+
if (block) {
109+
isInYamlBlock = { key, indent: (leadingSpaces?.length ?? 0) + 2 }
110+
result[key] = []
111+
} else {
112+
result[key] = getYamlValue(value)
113+
}
114+
}
115+
}
116+
return reConstructError(result)
117+
}
118+
119+
module.exports = {
120+
YAMLToJs
121+
}

0 commit comments

Comments
 (0)