Skip to content

Commit

Permalink
feat: parse yaml
Browse files Browse the repository at this point in the history
PR-URL: nodejs/node#45815
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
(cherry picked from commit 232efb06fe8787e9573e298ce7ac293ad23b7684)
  • Loading branch information
MoLow committed Feb 2, 2023
1 parent 4e778bc commit d1343a7
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 22 deletions.
4 changes: 4 additions & 0 deletions lib/internal/per_context/primordials.js
Expand Up @@ -20,6 +20,7 @@ exports.ArrayPrototypeSome = (arr, fn) => arr.some(fn)
exports.ArrayPrototypeSort = (arr, fn) => arr.sort(fn)
exports.ArrayPrototypeSplice = (arr, offset, len, ...el) => arr.splice(offset, len, ...el)
exports.ArrayPrototypeUnshift = (arr, ...el) => arr.unshift(...el)
exports.Boolean = Boolean
exports.Error = Error
exports.ErrorCaptureStackTrace = (...args) => Error.captureStackTrace(...args)
exports.FunctionPrototype = Function.prototype
Expand All @@ -28,6 +29,7 @@ exports.FunctionPrototypeCall = (fn, obj, ...args) => fn.call(obj, ...args)
exports.MathMax = (...args) => Math.max(...args)
exports.Number = Number
exports.NumberIsInteger = Number.isInteger
exports.NumberIsNaN = Number.isNaN
exports.NumberParseInt = (str, radix) => Number.parseInt(str, radix)
exports.NumberMIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER
exports.NumberMAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER
Expand Down Expand Up @@ -56,6 +58,7 @@ exports.SafePromiseRace = (array, mapFn) => Promise.race(mapFn ? array.map(mapFn
exports.SafeSet = Set
exports.SafeWeakMap = WeakMap
exports.SafeWeakSet = WeakSet
exports.String = String
exports.StringPrototypeEndsWith = (haystack, needle, index) => haystack.endsWith(needle, index)
exports.StringPrototypeIncludes = (str, needle) => str.includes(needle)
exports.StringPrototypeMatch = (str, reg) => str.match(reg)
Expand All @@ -66,6 +69,7 @@ exports.StringPrototypeReplaceAll = replaceAll
exports.StringPrototypeStartsWith = (haystack, needle, index) => haystack.startsWith(needle, index)
exports.StringPrototypeSlice = (str, ...args) => str.slice(...args)
exports.StringPrototypeSplit = (str, search, limit) => str.split(search, limit)
exports.StringPrototypeSubstring = (str, ...args) => str.substring(...args)
exports.StringPrototypeToUpperCase = str => str.toUpperCase()
exports.StringPrototypeTrim = str => str.trim()
exports.Symbol = Symbol
Expand Down
20 changes: 4 additions & 16 deletions lib/internal/test_runner/runner.js
@@ -1,11 +1,10 @@
// https://github.com/nodejs/node/blob/fec0fbc333c58e6ebbd2322f5120830fda880eb0/lib/internal/test_runner/runner.js
// https://github.com/nodejs/node/blob/232efb06fe8787e9573e298ce7ac293ad23b7684/lib/internal/test_runner/runner.js
'use strict'
const {
ArrayFrom,
ArrayPrototypeFilter,
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ArrayPrototypeJoin,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSort,
Expand All @@ -32,6 +31,7 @@ const { kEmptyObject } = require('#internal/util')
const { createTestTree } = require('#internal/test_runner/harness')
const { kDefaultIndent, kSubtestsFailed, Test } = require('#internal/test_runner/test')
const { TapParser } = require('#internal/test_runner/tap_parser')
const { YAMLToJs } = require('#internal/test_runner/yaml_parser')
const { TokenKind } = require('#internal/test_runner/tap_lexer')
const {
isSupportedFileType,
Expand Down Expand Up @@ -123,18 +123,6 @@ class FileTest extends Test {
#handleReportItem ({ kind, node, nesting = 0 }) {
const indent = StringPrototypeRepeat(kDefaultIndent, nesting + 1)

const details = (diagnostic) => {
return (
diagnostic && {
__proto__: null,
yaml:
`${indent} ` +
ArrayPrototypeJoin(diagnostic, `\n${indent} `) +
'\n'
}
)
}

switch (kind) {
case TokenKind.TAP_VERSION:
// TODO(manekinekko): handle TAP version coming from the parser.
Expand Down Expand Up @@ -168,15 +156,15 @@ class FileTest extends Test {
indent,
node.id,
node.description,
details(node.diagnostics),
YAMLToJs(node.diagnostics),
directive
)
} else {
this.reporter.fail(
indent,
node.id,
node.description,
details(node.diagnostics),
YAMLToJs(node.diagnostics),
directive
)
}
Expand Down
7 changes: 3 additions & 4 deletions lib/internal/test_runner/tap_stream.js
@@ -1,4 +1,4 @@
// https://github.com/nodejs/node/blob/22dc987fde29734c5bcbb7c33da20d184ff61627/lib/internal/test_runner/tap_stream.js
// https://github.com/nodejs/node/blob/232efb06fe8787e9573e298ce7ac293ad23b7684/lib/internal/test_runner/tap_stream.js

'use strict'

Expand Down Expand Up @@ -86,11 +86,10 @@ class TapStream extends Readable {
}

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

details += `${yaml || ''}`
details += jsToYaml(indent, 'duration_ms', duration)
details += jsToYaml(indent, 'duration_ms', duration_ms)
details += jsToYaml(indent, null, error)
details += `${indent} ...\n`
this.#tryPush(details)
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/test_runner/test.js
@@ -1,4 +1,4 @@
// https://github.com/nodejs/node/blob/215c5317d4837287fddb2e3b97872babd53183ac/lib/internal/test_runner/test.js
// https://github.com/nodejs/node/blob/232efb06fe8787e9573e298ce7ac293ad23b7684/lib/internal/test_runner/test.js

'use strict'

Expand Down Expand Up @@ -668,7 +668,7 @@ class Test extends AsyncResource {
this.reportSubtest()
}
let directive
const details = { __proto__: null, duration: this.#duration() }
const details = { __proto__: null, duration_ms: this.#duration() }

if (this.skipped) {
directive = this.reporter.getSkip(this.message)
Expand Down
121 changes: 121 additions & 0 deletions lib/internal/test_runner/yaml_parser.js
@@ -0,0 +1,121 @@
// https://github.com/nodejs/node/blob/232efb06fe8787e9573e298ce7ac293ad23b7684/lib/internal/test_runner/yaml_parser.js
'use strict'
const {
codes: {
ERR_TEST_FAILURE
}
} = require('#internal/errors')
const AssertionError = require('assert').AssertionError
const {
ArrayPrototypeJoin,
ArrayPrototypePush,
Error,
Number,
NumberIsNaN,
RegExpPrototypeExec,
StringPrototypeEndsWith,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeStartsWith,
StringPrototypeSubstring
} = require('#internal/per_context/primordials')

const kYamlKeyRegex = /^(\s+)?(\w+):(\s)+([>|][-+])?(.*)$/
const kStackDelimiter = ' at '

function reConstructError (parsedYaml) {
if (!('error' in parsedYaml)) {
return parsedYaml
}
const isAssertionError = parsedYaml.code === 'ERR_ASSERTION' ||
'actual' in parsedYaml || 'expected' in parsedYaml || 'operator' in parsedYaml
const isTestFailure = parsedYaml.code === 'ERR_TEST_FAILURE' || 'failureType' in parsedYaml
const stack = parsedYaml.stack ? kStackDelimiter + ArrayPrototypeJoin(parsedYaml.stack, `\n${kStackDelimiter}`) : ''
let error, cause

if (isAssertionError) {
cause = new AssertionError({
message: parsedYaml.error,
actual: parsedYaml.actual,
expected: parsedYaml.expected,
operator: parsedYaml.operator
})
} else {
// eslint-disable-next-line no-restricted-syntax
cause = new Error(parsedYaml.error)
cause.code = parsedYaml.code
}
cause.stack = stack

if (isTestFailure) {
error = new ERR_TEST_FAILURE(cause, parsedYaml.failureType)
error.stack = stack
}

parsedYaml.error = error ?? cause
delete parsedYaml.stack
delete parsedYaml.code
delete parsedYaml.failureType
delete parsedYaml.actual
delete parsedYaml.expected
delete parsedYaml.operator

return parsedYaml
}

function getYamlValue (value) {
if (StringPrototypeStartsWith(value, "'") && StringPrototypeEndsWith(value, "'")) {
return StringPrototypeSlice(value, 1, -1)
}
if (value === 'true') {
return true
}
if (value === 'false') {
return false
}
if (value !== '') {
const valueAsNumber = Number(value)
return NumberIsNaN(valueAsNumber) ? value : valueAsNumber
}
return value
}

// This parses the YAML generated by the built-in TAP reporter,
// which is a subset of the full YAML spec. There are some
// YAML features that won't be parsed here. This function should not be exposed publicly.
function YAMLToJs (lines) {
if (lines == null) {
return undefined
}
const result = { __proto__: null }
let isInYamlBlock = false
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (isInYamlBlock && !StringPrototypeStartsWith(line, StringPrototypeRepeat(' ', isInYamlBlock.indent))) {
result[isInYamlBlock.key] = isInYamlBlock.key === 'stack'
? result[isInYamlBlock.key]
: ArrayPrototypeJoin(result[isInYamlBlock.key], '\n')
isInYamlBlock = false
}
if (isInYamlBlock) {
const blockLine = StringPrototypeSubstring(line, isInYamlBlock.indent)
ArrayPrototypePush(result[isInYamlBlock.key], blockLine)
continue
}
const match = RegExpPrototypeExec(kYamlKeyRegex, line)
if (match !== null) {
const { 1: leadingSpaces, 2: key, 4: block, 5: value } = match
if (block) {
isInYamlBlock = { key, indent: (leadingSpaces?.length ?? 0) + 2 }
result[key] = []
} else {
result[key] = getYamlValue(value)
}
}
}
return reConstructError(result)
}

module.exports = {
YAMLToJs
}

0 comments on commit d1343a7

Please sign in to comment.