Skip to content

Commit

Permalink
test_runner: parse yaml
Browse files Browse the repository at this point in the history
PR-URL: #45815
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
  • Loading branch information
MoLow authored and juanarbol committed Jan 22, 2023
1 parent 874f6c3 commit 87a0e86
Show file tree
Hide file tree
Showing 6 changed files with 785 additions and 19 deletions.
18 changes: 3 additions & 15 deletions lib/internal/test_runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const {
ArrayPrototypeFilter,
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ArrayPrototypeJoin,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSort,
Expand Down Expand Up @@ -35,6 +34,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 {
Expand Down Expand Up @@ -129,18 +129,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 @@ -174,15 +162,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
5 changes: 2 additions & 3 deletions lib/internal/test_runner/tap_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,10 @@ class TapStream extends Readable {
}

#details(indent, data = kEmptyObject) {
const { error, duration, yaml } = data;
const { error, duration_ms } = data;
let details = `${indent} ---\n`;

details += `${yaml ? 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
2 changes: 1 addition & 1 deletion lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,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
119 changes: 119 additions & 0 deletions lib/internal/test_runner/yaml_parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
'use strict';
const {
codes: {
ERR_TEST_FAILURE,
}
} = require('internal/errors');
const AssertionError = require('internal/assert/assertion_error');
const {
ArrayPrototypeJoin,
ArrayPrototypePush,
Error,
Number,
NumberIsNaN,
RegExpPrototypeExec,
StringPrototypeEndsWith,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeStartsWith,
StringPrototypeSubstring,
} = 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,
};
6 changes: 6 additions & 0 deletions test/message/test_runner_output_cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Flags: --no-warnings
'use strict';
require('../common');
const spawn = require('node:child_process').spawn;
spawn(process.execPath,
['--no-warnings', '--test', 'test/message/test_runner_output.js'], { stdio: 'inherit' });
Loading

0 comments on commit 87a0e86

Please sign in to comment.