vscode-tmgrammar-test
Version:
Test runner for VSCode textmate grammars
351 lines • 14.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsoleFullReporter = exports.ConsoleCompactReporter = exports.XunitGitlabReporter = exports.XunitGenericReporter = exports.CompositeReporter = void 0;
const chalk_1 = __importDefault(require("chalk"));
const fs = __importStar(require("fs"));
const p = __importStar(require("path"));
const tty = __importStar(require("tty"));
const os_1 = require("os");
const path_1 = require("path");
class CompositeReporter {
constructor(...reporters) {
this.reporters = reporters;
}
reportTestResult(filename, testCase, failures) {
this.reporters.forEach(r => r.reportTestResult(filename, testCase, failures));
}
reportGrammarTestError(filename, testCase, reason) {
this.reporters.forEach(r => r.reportGrammarTestError(filename, testCase, reason));
}
reportParseError(filename, error) {
this.reporters.forEach(r => r.reportParseError(filename, error));
}
reportSuiteResult() {
this.reporters.forEach(r => r.reportSuiteResult());
}
}
exports.CompositeReporter = CompositeReporter;
class XunitReportPerTestReporter {
constructor(reportPath) {
this.reportPath = reportPath;
// follows this schema https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd
// produces report in a way which looks nice when viewed in GitLab CI/CD web GUI, but is not neccesarily semantically correct
this.suites = [];
}
reportParseError(filename, error) {
const suite = this.getSuite(filename);
suite.cases.push({
name: "Parse test file",
classname: this.caseClassname(filename),
failures: [{
type: 'error',
message: "Failed to parse test file",
body: `${error}`
}]
});
}
reportGrammarTestError(filename, parsedFile, reason) {
const suite = this.getSuite(filename, parsedFile);
suite.cases.push({
name: "Run grammar tests",
classname: this.caseClassname(filename),
failures: [{
type: 'error',
message: "Error when running grammar tests",
body: `${reason}`
}]
});
}
red(text) {
return text;
}
gray(text) {
return text;
}
whiteBright(text) {
return text;
}
getSuite(filename, parsedFile) {
const suite = {
file: `TEST-${filename.replaceAll(path_1.sep, '.')}.xml`,
name: parsedFile?.metadata.description || filename,
cases: []
};
this.suites.push(suite);
return suite;
}
getCase(suite, filename, assertion) {
const name = `${filename}:${assertion.testCaseLineNumber + 1}`;
for (const c of suite.cases) {
if (c.name === name) {
return c;
}
}
const c = {
name,
classname: this.caseClassname(filename),
failures: []
};
suite.cases.push(c);
return c;
}
reportSuiteResult() {
fs.mkdirSync(this.reportPath, { recursive: true });
for (const suite of this.suites.values()) {
fs.writeFileSync(p.resolve(this.reportPath, suite.file), this.renderSuite(suite));
}
}
renderSuite(s) {
return `
<testsuite
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd"
name="${s.name}"
tests="${s.cases.length}"
failures="${this.suiteFailuresCount(s)}"
errors="${this.suiteErrorsCount(s)}"
skipped="0"
>${s.cases.reduce((a, c) => a + "\n" + this.renderCase(c), "")}
</testsuite>
`;
}
renderCase(c) {
return ` <testcase name="${c.name}" ${this.classnameAttr(c)}time="0">${c.failures.reduce((a, f) => a + "\n" + this.renderFailure(f), "")}${this.newlineIfHasItems(c.failures)}</testcase>`;
}
classnameAttr(c) {
return c.classname ? `classname="${c.classname}" ` : "";
}
renderFailure(f) {
return ` <${f.type} message="${f.message}" type="${f.type === 'failure' ? 'TestFailure' : 'GrammarTestError'}">${this.escapedXml(f.body)}</${f.type}>`;
}
newlineIfHasItems(arr) {
return arr.length === 0 ? "" : "\n";
}
escapedXml(raw) {
return raw
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
}
class XunitGenericReporter extends XunitReportPerTestReporter {
// follows this schema https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd and produces one report file per test file
// if some CI requires single report file may also implement reporter for this format https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd
constructor(reportPath) {
super(reportPath);
}
reportTestResult(filename, parsedFile, failures) {
const suite = this.getSuite(filename, parsedFile);
// source line in the test file is treated as testcase
// and every failed assertion associated with this source line is failure in that testcase
for (const assertion of parsedFile.assertions) {
const c = this.getCase(suite, filename, assertion);
for (const failure of failures) {
if (failure.line !== assertion.testCaseLineNumber) {
continue;
}
const { l, s, e } = getCorrectedOffsets(failure);
const bodyLines = [];
printSourceLine(parsedFile, failure, '', 200, m => bodyLines.push(m), this);
printReason(parsedFile, failure, '', m => bodyLines.push(m), this);
c.failures.push({
type: 'failure',
message: `Assertion failed at ${l}:${s}:${e}`,
body: bodyLines.join("\n")
});
}
}
}
caseClassname(filename) {
return undefined;
}
suiteFailuresCount(s) {
return s.cases.reduce((accSuite, c) => accSuite + c.failures.reduce((accCase, f) => accCase + (f.type === 'failure' ? 1 : 0), 0), 0);
}
suiteErrorsCount(s) {
return s.cases.reduce((accSuite, c) => accSuite + c.failures.reduce((accCase, f) => accCase + (f.type === 'error' ? 1 : 0), 0), 0);
}
}
exports.XunitGenericReporter = XunitGenericReporter;
class XunitGitlabReporter extends XunitReportPerTestReporter {
// follows this schema https://maven.apache.org/surefire/maven-surefire-plugin/xsd/surefire-test-report.xsd
// produces report in a way which looks nice when viewed in GitLab CI/CD web GUI, but is not neccesarily semantically correct
constructor(reportPath) {
super(reportPath);
}
reportTestResult(filename, parsedFile, failures) {
const suite = this.getSuite(filename, parsedFile);
for (const assertion of parsedFile.assertions) {
const c = this.getCase(suite, filename, assertion);
const bodyLines = [];
for (const failure of failures) {
if (failure.line !== assertion.testCaseLineNumber) {
continue;
}
printAssertionLocation(filename, failure, '', m => bodyLines.push(m), this);
printSourceLine(parsedFile, failure, '', 200, m => bodyLines.push(m), this);
printReason(parsedFile, failure, '', m => bodyLines.push(m), this);
bodyLines.push("");
}
if (bodyLines.length > 0) {
c.failures.push({
type: 'failure',
message: `Failed at soure line ${assertion.testCaseLineNumber + 1}`,
body: bodyLines.join("\n")
});
}
}
}
caseClassname(filename) {
return filename;
}
suiteFailuresCount(s) {
return s.cases.reduce((accSuite, c) => accSuite + (c.failures.some(f => f.type === 'failure') ? 1 : 0), 0);
}
suiteErrorsCount(s) {
return s.cases.reduce((accSuite, c) => accSuite + (c.failures.some(f => f.type === 'error') ? 1 : 0), 0);
}
}
exports.XunitGitlabReporter = XunitGitlabReporter;
const symbols = {
ok: '✓',
err: '✖',
dot: '․',
comma: ',',
bang: '!'
};
if (process.platform === 'win32') {
symbols.ok = '\u221A';
symbols.err = '\u00D7';
symbols.dot = '.';
}
const Padding = ' ';
let isatty = tty.isatty(1) && tty.isatty(2);
let terminalWidth = 75;
if (isatty) {
terminalWidth = process.stdout.getWindowSize()[0];
}
function handleGrammarTestError(filename, testCase, reason) {
console.log(chalk_1.default.red(symbols.err) + ' testcase ' + chalk_1.default.gray(filename) + ' aborted due to an error');
console.log(reason);
}
function handleParseError(filename, error) {
console.log(chalk_1.default.red('ERROR') + " can't parse testcase: " + chalk_1.default.whiteBright(filename) + '');
console.log(error);
}
class ConsoleCompactReporter {
constructor() {
this.reportParseError = handleParseError;
this.reportGrammarTestError = handleGrammarTestError;
}
reportTestResult(filename, testCase, failures) {
if (failures.length === 0) {
console.log(chalk_1.default.green(symbols.ok) + ' ' + chalk_1.default.whiteBright(filename) + ` run successfuly.`);
}
else {
failures.forEach((failure) => {
console.log(`ERROR ${filename}:${failure.line + 1}:${failure.start + 1}:${failure.end + 1} ${this.renderCompactErrorMsg(testCase, failure)}`);
});
}
}
renderCompactErrorMsg(testCase, failure) {
let res = '';
if (failure.missing && failure.missing.length > 0) {
res += `Missing required scopes: [ ${failure.missing.join(' ')} ] `;
}
if (failure.unexpected && failure.unexpected.length > 0) {
res += `Prohibited scopes: [ ${failure.unexpected.join(' ')} ] `;
}
if (failure.actual !== undefined) {
res += `actual scopes: [${failure.actual.join(' ')}]`;
}
return res;
}
reportSuiteResult() { }
}
exports.ConsoleCompactReporter = ConsoleCompactReporter;
class ConsoleFullReporter {
constructor() {
this.reportParseError = handleParseError;
this.reportGrammarTestError = handleGrammarTestError;
}
reportTestResult(filename, testCase, failures) {
if (failures.length === 0) {
console.log(chalk_1.default.green(symbols.ok) + ' ' + chalk_1.default.whiteBright(filename) + ` run successfuly.`);
}
else {
console.log(chalk_1.default.red(symbols.err + ' ' + filename + ' failed'));
failures.forEach((failure) => {
printAssertionLocation(filename, failure, Padding, console.log, chalk_1.default);
printSourceLine(testCase, failure, Padding, terminalWidth, console.log, chalk_1.default);
printReason(testCase, failure, Padding, console.log, chalk_1.default);
console.log(os_1.EOL);
});
console.log('');
}
}
reportSuiteResult() { }
}
exports.ConsoleFullReporter = ConsoleFullReporter;
function printAssertionLocation(filename, failure, padding, sink, colorizer) {
const { l, s, e } = getCorrectedOffsets(failure);
sink(padding + 'at [' + colorizer.whiteBright(`${filename}:${l}:${s}:${e}`) + ']:');
}
function getCorrectedOffsets(failure) {
return {
l: failure.line + 1,
s: failure.start + 1,
e: failure.end + 1
};
}
function printSourceLine(testCase, failure, padding, terminalWidth, sink, colorizer) {
const line = testCase.source[failure.srcLine];
const pos = failure.line + 1 + ': ';
const accents = ' '.repeat(failure.start) + '^'.repeat(failure.end - failure.start);
const termWidth = terminalWidth - pos.length - Padding.length - 5;
const trimLeft = failure.end > termWidth ? Math.max(0, failure.start - 8) : 0;
const line1 = line.substr(trimLeft);
const accents1 = accents.substr(trimLeft);
sink(padding + colorizer.gray(pos) + line1.substr(0, termWidth));
sink(padding + ' '.repeat(pos.length) + accents1.substr(0, termWidth));
}
function printReason(testCase, failure, padding, sink, colorizer) {
if (failure.missing && failure.missing.length > 0) {
sink(colorizer.red(padding + 'missing required scopes: ') + colorizer.gray(failure.missing.join(' ')));
}
if (failure.unexpected && failure.unexpected.length > 0) {
sink(colorizer.red(padding + 'prohibited scopes: ') + colorizer.gray(failure.unexpected.join(' ')));
}
if (failure.actual !== undefined) {
sink(colorizer.red(padding + 'actual: ') + colorizer.gray(failure.actual.join(' ')));
}
}
//# sourceMappingURL=reporter.js.map