mirror of
https://github.com/dorny/test-reporter.git
synced 2026-02-02 03:15:22 -08:00
Compare commits
17 Commits
feature/ci
...
v1.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c40d89d5e9 | ||
|
|
9a5ccba454 | ||
|
|
82b258b19e | ||
|
|
80874c1df3 | ||
|
|
99e65e60b1 | ||
|
|
0863296b14 | ||
|
|
1212842c04 | ||
|
|
4cd9c62896 | ||
|
|
1a3cfe6b48 | ||
|
|
7e5f292040 | ||
|
|
ccc63b813f | ||
|
|
eaa763f6ff | ||
|
|
214929bdc3 | ||
|
|
5c9213582c | ||
|
|
5edc9e96e2 | ||
|
|
afe6793191 | ||
|
|
5c714d27be |
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## 1.8.0
|
||||
* Add `SwiftXunitParser` class based on `JavaJunitParser` for `swift-xunit` reporter https://github.com/dorny/test-reporter/pull/317
|
||||
* Use NodeJS 18 LTS as default runtime https://github.com/dorny/test-reporter/pull/332
|
||||
* Escape `<>` characters in suite name https://github.com/dorny/test-reporter/pull/236
|
||||
* Update actions runtime to Node20 https://github.com/dorny/test-reporter/pull/315
|
||||
* Update check title and remove icon https://github.com/dorny/test-reporter/pull/144
|
||||
|
||||
## 1.7.0
|
||||
* Fix #199: Use ✅ instead of ✔️ for better cross platform look by @petrdvorak in https://github.com/dorny/test-reporter/pull/200
|
||||
* Verify content of dist/ folder matches build output by @dorny in https://github.com/dorny/test-reporter/pull/207
|
||||
|
||||
@@ -142,6 +142,7 @@ jobs:
|
||||
# java-junit
|
||||
# jest-junit
|
||||
# mocha-json
|
||||
# rspec-json
|
||||
reporter: ''
|
||||
|
||||
# Allows you to generate only the summary.
|
||||
|
||||
16
__tests__/__outputs__/rspec-json.md
Normal file
16
__tests__/__outputs__/rspec-json.md
Normal file
@@ -0,0 +1,16 @@
|
||||

|
||||
## ❌ <a id="user-content-r0" href="#r0">fixtures/rspec-json.json</a>
|
||||
**3** tests were completed in **0ms** with **1** passed, **1** failed and **1** skipped.
|
||||
|Test suite|Passed|Failed|Skipped|Time|
|
||||
|:---|---:|---:|---:|---:|
|
||||
|[./spec/config/check_env_vars_spec.rb](#r0s0)|1✅|1❌|1⚪|0ms|
|
||||
### ❌ <a id="user-content-r0s0" href="#r0s0">./spec/config/check_env_vars_spec.rb</a>
|
||||
```
|
||||
CheckEnvVars#call when all env vars are defined behaves like success load
|
||||
❌ CheckEnvVars#call when all env vars are defined behaves like success load fails in assertion
|
||||
(#ActiveSupport::BroadcastLogger:0x00007f1007fedf58).debug("All config env vars exist")
|
||||
expected: 0 times with arguments: ("All config env vars exist")
|
||||
received: 1 time with arguments: ("All config env vars exist")
|
||||
✅ CheckEnvVars#call when all env vars are defined behaves like success load logs success message
|
||||
⚪ CheckEnvVars#call when all env vars are defined behaves like success load skips the test
|
||||
```
|
||||
49
__tests__/__snapshots__/rspec-json.test.ts.snap
Normal file
49
__tests__/__snapshots__/rspec-json.test.ts.snap
Normal file
@@ -0,0 +1,49 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rspec-json tests report from ./reports/rspec-json test results matches snapshot 1`] = `
|
||||
TestRunResult {
|
||||
"path": "fixtures/rspec-json.json",
|
||||
"suites": [
|
||||
TestSuiteResult {
|
||||
"groups": [
|
||||
TestGroupResult {
|
||||
"name": "CheckEnvVars#call when all env vars are defined behaves like success load",
|
||||
"tests": [
|
||||
TestCaseResult {
|
||||
"error": {
|
||||
"details": "/usr/local/bundle/ruby/3.3.0/gems/net-http-0.4.1/lib/net/http.rb:1603:in \`initialize'
|
||||
./config/check_env_vars.rb:11:in \`call'
|
||||
./spec/config/check_env_vars_spec.rb:7:in \`block (3 levels) in <top (required)>'
|
||||
./spec/config/check_env_vars_spec.rb:19:in \`block (4 levels) in <top (required)>'",
|
||||
"line": 11,
|
||||
"message": "(#ActiveSupport::BroadcastLogger:0x00007f1007fedf58).debug("All config env vars exist")
|
||||
expected: 0 times with arguments: ("All config env vars exist")
|
||||
received: 1 time with arguments: ("All config env vars exist")",
|
||||
"path": "./config/check_env_vars.rb",
|
||||
},
|
||||
"name": "CheckEnvVars#call when all env vars are defined behaves like success load fails in assertion",
|
||||
"result": "failed",
|
||||
"time": 0.004411051,
|
||||
},
|
||||
TestCaseResult {
|
||||
"error": undefined,
|
||||
"name": "CheckEnvVars#call when all env vars are defined behaves like success load logs success message",
|
||||
"result": "success",
|
||||
"time": 0.079159625,
|
||||
},
|
||||
TestCaseResult {
|
||||
"error": undefined,
|
||||
"name": "CheckEnvVars#call when all env vars are defined behaves like success load skips the test",
|
||||
"result": "skipped",
|
||||
"time": 0.000023007,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
"name": "./spec/config/check_env_vars_spec.rb",
|
||||
"totalTime": undefined,
|
||||
},
|
||||
],
|
||||
"totalTime": 0.19118387,
|
||||
}
|
||||
`;
|
||||
17
__tests__/fixtures/empty/rspec-json.json
Normal file
17
__tests__/fixtures/empty/rspec-json.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": "3.13.0",
|
||||
"messages": [
|
||||
"No examples found."
|
||||
],
|
||||
"examples": [
|
||||
|
||||
],
|
||||
"summary": {
|
||||
"duration": 0.002514266,
|
||||
"example_count": 0,
|
||||
"failure_count": 0,
|
||||
"pending_count": 0,
|
||||
"errors_outside_of_examples_count": 0
|
||||
},
|
||||
"summary_line": "0 examples, 0 failures"
|
||||
}
|
||||
53
__tests__/fixtures/rspec-json.json
Normal file
53
__tests__/fixtures/rspec-json.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"version": "3.13.0",
|
||||
"examples": [
|
||||
{
|
||||
"id": "./spec/config/check_env_vars_spec.rb[1:1:1:1:1]",
|
||||
"description": "logs success message",
|
||||
"full_description": "CheckEnvVars#call when all env vars are defined behaves like success load logs success message",
|
||||
"status": "passed",
|
||||
"file_path": "./spec/config/check_env_vars_spec.rb",
|
||||
"line_number": 12,
|
||||
"run_time": 0.079159625,
|
||||
"pending_message": null
|
||||
},
|
||||
{
|
||||
"id": "./spec/config/check_env_vars_spec.rb[1:1:1:1:2]",
|
||||
"description": "fails in assertion",
|
||||
"full_description": "CheckEnvVars#call when all env vars are defined behaves like success load fails in assertion",
|
||||
"status": "failed",
|
||||
"file_path": "./spec/config/check_env_vars_spec.rb",
|
||||
"line_number": 17,
|
||||
"run_time": 0.004411051,
|
||||
"pending_message": null,
|
||||
"exception": {
|
||||
"class": "RSpec::Mocks::MockExpectationError",
|
||||
"message": "(#ActiveSupport::BroadcastLogger:0x00007f1007fedf58).debug(\"All config env vars exist\")\n expected: 0 times with arguments: (\"All config env vars exist\")\n received: 1 time with arguments: (\"All config env vars exist\")",
|
||||
"backtrace": [
|
||||
"/usr/local/bundle/ruby/3.3.0/gems/net-http-0.4.1/lib/net/http.rb:1603:in `initialize'",
|
||||
"./config/check_env_vars.rb:11:in `call'",
|
||||
"./spec/config/check_env_vars_spec.rb:7:in `block (3 levels) in \u003ctop (required)\u003e'",
|
||||
"./spec/config/check_env_vars_spec.rb:19:in `block (4 levels) in \u003ctop (required)\u003e'"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "./spec/config/check_env_vars_spec.rb[1:1:1:1:4]",
|
||||
"description": "skips the test",
|
||||
"full_description": "CheckEnvVars#call when all env vars are defined behaves like success load skips the test",
|
||||
"status": "pending",
|
||||
"file_path": "./spec/config/check_env_vars_spec.rb",
|
||||
"line_number": 27,
|
||||
"run_time": 2.3007e-05,
|
||||
"pending_message": "Temporarily skipped with xit"
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"duration": 0.19118387,
|
||||
"example_count": 3,
|
||||
"failure_count": 1,
|
||||
"pending_count": 1,
|
||||
"errors_outside_of_examples_count": 0
|
||||
},
|
||||
"summary_line": "3 examples, 1 failures, 1 pending"
|
||||
}
|
||||
45
__tests__/rspec-json.test.ts
Normal file
45
__tests__/rspec-json.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
import {RspecJsonParser} from '../src/parsers/rspec-json/rspec-json-parser'
|
||||
import {ParseOptions} from '../src/test-parser'
|
||||
import {getReport} from '../src/report/get-report'
|
||||
import {normalizeFilePath} from '../src/utils/path-utils'
|
||||
|
||||
describe('rspec-json tests', () => {
|
||||
it('produces empty test run result when there are no test cases', async () => {
|
||||
const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'rspec-json.json')
|
||||
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||
|
||||
const opts: ParseOptions = {
|
||||
parseErrors: true,
|
||||
trackedFiles: []
|
||||
}
|
||||
|
||||
const parser = new RspecJsonParser(opts)
|
||||
const result = await parser.parse(filePath, fileContent)
|
||||
expect(result.tests).toBe(0)
|
||||
expect(result.result).toBe('success')
|
||||
})
|
||||
|
||||
it('report from ./reports/rspec-json test results matches snapshot', async () => {
|
||||
const fixturePath = path.join(__dirname, 'fixtures', 'rspec-json.json')
|
||||
const outputPath = path.join(__dirname, '__outputs__', 'rspec-json.md')
|
||||
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||
|
||||
const opts: ParseOptions = {
|
||||
parseErrors: true,
|
||||
trackedFiles: ['test/main.test.js', 'test/second.test.js', 'lib/main.js']
|
||||
}
|
||||
|
||||
const parser = new RspecJsonParser(opts)
|
||||
const result = await parser.parse(filePath, fileContent)
|
||||
expect(result).toMatchSnapshot()
|
||||
|
||||
const report = getReport([result])
|
||||
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||
fs.writeFileSync(outputPath, report)
|
||||
})
|
||||
})
|
||||
@@ -31,6 +31,7 @@ inputs:
|
||||
- java-junit
|
||||
- jest-junit
|
||||
- mocha-json
|
||||
- rspec-json
|
||||
- swift-xunit
|
||||
required: true
|
||||
list-suites:
|
||||
|
||||
188
dist/index.js
generated
vendored
188
dist/index.js
generated
vendored
@@ -265,6 +265,7 @@ const dotnet_trx_parser_1 = __nccwpck_require__(2664);
|
||||
const java_junit_parser_1 = __nccwpck_require__(676);
|
||||
const jest_junit_parser_1 = __nccwpck_require__(1113);
|
||||
const mocha_json_parser_1 = __nccwpck_require__(6043);
|
||||
const rspec_json_parser_1 = __nccwpck_require__(406);
|
||||
const swift_xunit_parser_1 = __nccwpck_require__(5366);
|
||||
const path_utils_1 = __nccwpck_require__(4070);
|
||||
const github_utils_1 = __nccwpck_require__(3522);
|
||||
@@ -434,6 +435,8 @@ class TestReporter {
|
||||
return new jest_junit_parser_1.JestJunitParser(options);
|
||||
case 'mocha-json':
|
||||
return new mocha_json_parser_1.MochaJsonParser(options);
|
||||
case 'rspec-json':
|
||||
return new rspec_json_parser_1.RspecJsonParser(options);
|
||||
case 'swift-xunit':
|
||||
return new swift_xunit_parser_1.SwiftXunitParser(options);
|
||||
default:
|
||||
@@ -1403,6 +1406,121 @@ class MochaJsonParser {
|
||||
exports.MochaJsonParser = MochaJsonParser;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 406:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.RspecJsonParser = void 0;
|
||||
const test_results_1 = __nccwpck_require__(2768);
|
||||
class RspecJsonParser {
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
}
|
||||
parse(path, content) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const mocha = this.getRspecJson(path, content);
|
||||
const result = this.getTestRunResult(path, mocha);
|
||||
result.sort(true);
|
||||
return Promise.resolve(result);
|
||||
});
|
||||
}
|
||||
getRspecJson(path, content) {
|
||||
try {
|
||||
return JSON.parse(content);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Invalid JSON at ${path}\n\n${e}`);
|
||||
}
|
||||
}
|
||||
getTestRunResult(resultsPath, rspec) {
|
||||
const suitesMap = {};
|
||||
const getSuite = (test) => {
|
||||
var _a;
|
||||
const path = test.file_path;
|
||||
return (_a = suitesMap[path]) !== null && _a !== void 0 ? _a : (suitesMap[path] = new test_results_1.TestSuiteResult(path, []));
|
||||
};
|
||||
for (const test of rspec.examples) {
|
||||
const suite = getSuite(test);
|
||||
if (test.status === 'failed') {
|
||||
this.processTest(suite, test, 'failed');
|
||||
}
|
||||
else if (test.status === 'passed') {
|
||||
this.processTest(suite, test, 'success');
|
||||
}
|
||||
else if (test.status === 'pending') {
|
||||
this.processTest(suite, test, 'skipped');
|
||||
}
|
||||
}
|
||||
const suites = Object.values(suitesMap);
|
||||
return new test_results_1.TestRunResult(resultsPath, suites, rspec.summary.duration);
|
||||
}
|
||||
processTest(suite, test, result) {
|
||||
var _a;
|
||||
const groupName = test.full_description !== test.description
|
||||
? test.full_description.substr(0, test.full_description.length - test.description.length).trimEnd()
|
||||
: null;
|
||||
let group = suite.groups.find(grp => grp.name === groupName);
|
||||
if (group === undefined) {
|
||||
group = new test_results_1.TestGroupResult(groupName, []);
|
||||
suite.groups.push(group);
|
||||
}
|
||||
const error = this.getTestCaseError(test);
|
||||
const testCase = new test_results_1.TestCaseResult(test.full_description, result, (_a = test.run_time) !== null && _a !== void 0 ? _a : 0, error);
|
||||
group.tests.push(testCase);
|
||||
}
|
||||
getTestCaseError(test) {
|
||||
var _a, _b;
|
||||
const backtrace = (_a = test.exception) === null || _a === void 0 ? void 0 : _a.backtrace;
|
||||
const message = (_b = test.exception) === null || _b === void 0 ? void 0 : _b.message;
|
||||
if (backtrace === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
let path;
|
||||
let line;
|
||||
const details = backtrace.join('\n');
|
||||
const src = this.getExceptionSource(backtrace);
|
||||
if (src) {
|
||||
path = src.path;
|
||||
line = src.line;
|
||||
}
|
||||
return {
|
||||
path,
|
||||
line,
|
||||
message,
|
||||
details
|
||||
};
|
||||
}
|
||||
getExceptionSource(backtrace) {
|
||||
const re = /^(.*?):(\d+):/;
|
||||
for (const str of backtrace) {
|
||||
const match = str.match(re);
|
||||
if (match !== null) {
|
||||
const [_, path, lineStr] = match;
|
||||
if (path.startsWith('./')) {
|
||||
const line = parseInt(lineStr);
|
||||
return { path, line };
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
exports.RspecJsonParser = RspecJsonParser;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 5366:
|
||||
@@ -11219,7 +11337,8 @@ module.exports = function (/**String*/ input, /** object */ options) {
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
getEntries: function () {
|
||||
getEntries: function (/**String*/ password) {
|
||||
_zip.password=password;
|
||||
return _zip ? _zip.entries : [];
|
||||
},
|
||||
|
||||
@@ -11296,7 +11415,7 @@ module.exports = function (/**String*/ input, /** object */ options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var content = item.getData();
|
||||
var content = item.getData(_zip.password);
|
||||
if (!content) throw new Error(Utils.Errors.CANT_EXTRACT_FILE);
|
||||
|
||||
if (filetools.fs.existsSync(target) && !overwrite) {
|
||||
@@ -11458,9 +11577,9 @@ module.exports = function (/**String*/ input, /** object */ options) {
|
||||
callback(getError("Unable to set times", filePath));
|
||||
return;
|
||||
}
|
||||
fileEntries.delete(entry);
|
||||
// call the callback if it was last entry
|
||||
done();
|
||||
fileEntries.delete(entry);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -11624,7 +11743,9 @@ module.exports = function () {
|
||||
set time(val) {
|
||||
setTime(val);
|
||||
},
|
||||
|
||||
get timeHighByte() {
|
||||
return (_time >>> 8) & 0xff;
|
||||
},
|
||||
get crc() {
|
||||
return _crc;
|
||||
},
|
||||
@@ -12238,8 +12359,12 @@ function decrypt(/*Buffer*/ data, /*Object*/ header, /*String, Buffer*/ pwd) {
|
||||
// 2. decrypt salt what is always 12 bytes and is a part of file content
|
||||
const salt = decrypter(data.slice(0, 12));
|
||||
|
||||
// 3. does password meet expectations
|
||||
if (salt[11] !== header.crc >>> 24) {
|
||||
// if bit 3 (0x08) of the general-purpose flags field is set, check salt[11] with the high byte of the header time
|
||||
// 2 byte data block (as per Info-Zip spec), otherwise check with the high byte of the header entry
|
||||
const verifyByte = ((header.flags & 0x8) === 0x8) ? header.timeHighByte : header.crc >>> 24;
|
||||
|
||||
//3. does password meet expectations
|
||||
if (salt[11] !== verifyByte) {
|
||||
throw "ADM-ZIP: Wrong Password";
|
||||
}
|
||||
|
||||
@@ -13205,6 +13330,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) {
|
||||
_comment = Buffer.alloc(0),
|
||||
mainHeader = new Headers.MainHeader(),
|
||||
loadedEntries = false;
|
||||
var password = null;
|
||||
|
||||
// assign options
|
||||
const opts = Object.assign(Object.create(null), options);
|
||||
@@ -13456,7 +13582,7 @@ module.exports = function (/*Buffer|null*/ inBuffer, /** object */ options) {
|
||||
// 1.2. postheader - data after data header
|
||||
const postHeader = Buffer.alloc(entryNameLen + entry.extra.length);
|
||||
entry.rawEntryName.copy(postHeader, 0);
|
||||
postHeader.copy(entry.extra, entryNameLen);
|
||||
entry.extra.copy(postHeader, entryNameLen);
|
||||
|
||||
// 2. offsets
|
||||
const dataLength = dataHeader.length + postHeader.length + compressedData.length;
|
||||
@@ -26637,31 +26763,21 @@ module.exports.CancelError = CancelError;
|
||||
"use strict";
|
||||
|
||||
|
||||
const os = __nccwpck_require__(2037);
|
||||
const pico = __nccwpck_require__(3322);
|
||||
|
||||
const isWindows = os.platform() === 'win32';
|
||||
const utils = __nccwpck_require__(479);
|
||||
|
||||
function picomatch(glob, options, returnState = false) {
|
||||
// default to os.platform()
|
||||
if (options && (options.windows === null || options.windows === undefined)) {
|
||||
// don't mutate the original options object
|
||||
options = { ...options, windows: isWindows };
|
||||
options = { ...options, windows: utils.isWindows() };
|
||||
}
|
||||
|
||||
return pico(glob, options, returnState);
|
||||
}
|
||||
|
||||
Object.assign(picomatch, pico);
|
||||
module.exports = picomatch;
|
||||
// public api
|
||||
module.exports.test = pico.test;
|
||||
module.exports.matchBase = pico.matchBase;
|
||||
module.exports.isMatch = pico.isMatch;
|
||||
module.exports.parse = pico.parse;
|
||||
module.exports.scan = pico.scan;
|
||||
module.exports.compileRe = pico.compileRe;
|
||||
module.exports.toRegex = pico.toRegex;
|
||||
// for tests
|
||||
module.exports.makeRe = pico.makeRe;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -27068,8 +27184,8 @@ const parse = (input, options) => {
|
||||
|
||||
if (tok.value || tok.output) append(tok);
|
||||
if (prev && prev.type === 'text' && tok.type === 'text') {
|
||||
prev.output = (prev.output || prev.value) + tok.value;
|
||||
prev.value += tok.value;
|
||||
prev.output = (prev.output || '') + tok.value;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -27557,10 +27673,6 @@ const parse = (input, options) => {
|
||||
const next = peek();
|
||||
let output = value;
|
||||
|
||||
if (next === '<' && !utils.supportsLookbehinds()) {
|
||||
throw new Error('Node.js v10 or higher is required for regex lookbehinds');
|
||||
}
|
||||
|
||||
if ((prev.value === '(' && !/[!=<:]/.test(next)) || (next === '<' && !/<([!=]|\w+>)/.test(remaining()))) {
|
||||
output = `\\${value}`;
|
||||
}
|
||||
@@ -28702,6 +28814,7 @@ module.exports = scan;
|
||||
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
||||
|
||||
"use strict";
|
||||
/*global navigator*/
|
||||
|
||||
|
||||
const {
|
||||
@@ -28717,22 +28830,25 @@ exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str);
|
||||
exports.escapeRegex = str => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, '\\$1');
|
||||
exports.toPosixSlashes = str => str.replace(REGEX_BACKSLASH, '/');
|
||||
|
||||
exports.isWindows = () => {
|
||||
if (typeof navigator !== 'undefined' && navigator.platform) {
|
||||
const platform = navigator.platform.toLowerCase();
|
||||
return platform === 'win32' || platform === 'windows';
|
||||
}
|
||||
|
||||
if (typeof process !== 'undefined' && process.platform) {
|
||||
return process.platform === 'win32';
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.removeBackslashes = str => {
|
||||
return str.replace(REGEX_REMOVE_BACKSLASH, match => {
|
||||
return match === '\\' ? '' : match;
|
||||
});
|
||||
};
|
||||
|
||||
exports.supportsLookbehinds = () => {
|
||||
if (typeof process !== 'undefined') {
|
||||
const segs = process.version.slice(1).split('.').map(Number);
|
||||
if (segs.length === 3 && segs[0] >= 9 || (segs[0] === 8 && segs[1] >= 10)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.escapeLast = (input, char, lastIdx) => {
|
||||
const idx = input.lastIndexOf(char, lastIdx);
|
||||
if (idx === -1) return input;
|
||||
|
||||
960
package-lock.json
generated
960
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "test-check",
|
||||
"version": "1.7.0",
|
||||
"name": "test-reporter",
|
||||
"version": "1.9.0",
|
||||
"private": true,
|
||||
"description": "Presents test results from popular testing frameworks as Github check run",
|
||||
"main": "lib/main.js",
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/dorny/test-check.git"
|
||||
"url": "git+https://github.com/dorny/test-reporter.git"
|
||||
},
|
||||
"keywords": [
|
||||
"actions",
|
||||
@@ -35,39 +35,39 @@
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"adm-zip": "^0.5.10",
|
||||
"adm-zip": "^0.5.12",
|
||||
"fast-glob": "^3.3.2",
|
||||
"got": "^11.8.2",
|
||||
"picomatch": "^3.0.1",
|
||||
"got": "^11.8.6",
|
||||
"picomatch": "^4.0.2",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/types": "^12.4.0",
|
||||
"@octokit/webhooks": "^12.0.10",
|
||||
"@octokit/webhooks": "^12.0.11",
|
||||
"@octokit/webhooks-types": "^7.3.1",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/github-slugger": "^1.3.0",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^18.19.28",
|
||||
"@types/picomatch": "^2.3.3",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eol-converter-cli": "^1.0.8",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-github": "^4.10.1",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-jest": "^27.6.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^3.1.1",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.4.3"
|
||||
},
|
||||
"jest-junit": {
|
||||
"suiteName": "jest tests",
|
||||
|
||||
@@ -15,6 +15,7 @@ import {DotnetTrxParser} from './parsers/dotnet-trx/dotnet-trx-parser'
|
||||
import {JavaJunitParser} from './parsers/java-junit/java-junit-parser'
|
||||
import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser'
|
||||
import {MochaJsonParser} from './parsers/mocha-json/mocha-json-parser'
|
||||
import {RspecJsonParser} from './parsers/rspec-json/rspec-json-parser'
|
||||
import {SwiftXunitParser} from './parsers/swift-xunit/swift-xunit-parser'
|
||||
|
||||
import {normalizeDirPath, normalizeFilePath} from './utils/path-utils'
|
||||
@@ -223,6 +224,8 @@ class TestReporter {
|
||||
return new JestJunitParser(options)
|
||||
case 'mocha-json':
|
||||
return new MochaJsonParser(options)
|
||||
case 'rspec-json':
|
||||
return new RspecJsonParser(options)
|
||||
case 'swift-xunit':
|
||||
return new SwiftXunitParser(options)
|
||||
default:
|
||||
|
||||
113
src/parsers/rspec-json/rspec-json-parser.ts
Normal file
113
src/parsers/rspec-json/rspec-json-parser.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Console } from 'console'
|
||||
import {ParseOptions, TestParser} from '../../test-parser'
|
||||
import {
|
||||
TestCaseError,
|
||||
TestCaseResult,
|
||||
TestExecutionResult,
|
||||
TestGroupResult,
|
||||
TestRunResult,
|
||||
TestSuiteResult
|
||||
} from '../../test-results'
|
||||
import {RspecJson, RspecExample} from './rspec-json-types'
|
||||
|
||||
export class RspecJsonParser implements TestParser {
|
||||
assumedWorkDir: string | undefined
|
||||
|
||||
constructor(readonly options: ParseOptions) {}
|
||||
|
||||
async parse(path: string, content: string): Promise<TestRunResult> {
|
||||
const mocha = this.getRspecJson(path, content)
|
||||
const result = this.getTestRunResult(path, mocha)
|
||||
result.sort(true)
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
|
||||
private getRspecJson(path: string, content: string): RspecJson {
|
||||
try {
|
||||
return JSON.parse(content)
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid JSON at ${path}\n\n${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
private getTestRunResult(resultsPath: string, rspec: RspecJson): TestRunResult {
|
||||
const suitesMap: {[path: string]: TestSuiteResult} = {}
|
||||
|
||||
const getSuite = (test: RspecExample): TestSuiteResult => {
|
||||
const path = test.file_path
|
||||
return suitesMap[path] ?? (suitesMap[path] = new TestSuiteResult(path, []))
|
||||
}
|
||||
|
||||
for (const test of rspec.examples) {
|
||||
const suite = getSuite(test)
|
||||
if (test.status === 'failed') {
|
||||
this.processTest(suite, test, 'failed')
|
||||
} else if (test.status === 'passed') {
|
||||
this.processTest(suite, test, 'success')
|
||||
} else if (test.status === 'pending') {
|
||||
this.processTest(suite, test, 'skipped')
|
||||
}
|
||||
}
|
||||
|
||||
const suites = Object.values(suitesMap)
|
||||
return new TestRunResult(resultsPath, suites, rspec.summary.duration)
|
||||
}
|
||||
|
||||
private processTest(suite: TestSuiteResult, test: RspecExample, result: TestExecutionResult): void {
|
||||
const groupName =
|
||||
test.full_description !== test.description
|
||||
? test.full_description.substr(0, test.full_description.length - test.description.length).trimEnd()
|
||||
: null
|
||||
|
||||
let group = suite.groups.find(grp => grp.name === groupName)
|
||||
if (group === undefined) {
|
||||
group = new TestGroupResult(groupName, [])
|
||||
suite.groups.push(group)
|
||||
}
|
||||
|
||||
const error = this.getTestCaseError(test)
|
||||
const testCase = new TestCaseResult(test.full_description, result, test.run_time ?? 0, error)
|
||||
group.tests.push(testCase)
|
||||
}
|
||||
|
||||
private getTestCaseError(test: RspecExample): TestCaseError | undefined {
|
||||
const backtrace = test.exception?.backtrace
|
||||
const message = test.exception?.message
|
||||
if (backtrace === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let path
|
||||
let line
|
||||
const details = backtrace.join('\n')
|
||||
|
||||
const src = this.getExceptionSource(backtrace)
|
||||
if (src) {
|
||||
path = src.path
|
||||
line = src.line
|
||||
}
|
||||
|
||||
return {
|
||||
path,
|
||||
line,
|
||||
message,
|
||||
details
|
||||
}
|
||||
}
|
||||
|
||||
private getExceptionSource(backtrace: string[]): {path: string; line: number} | undefined {
|
||||
const re = /^(.*?):(\d+):/
|
||||
|
||||
for (const str of backtrace) {
|
||||
const match = str.match(re)
|
||||
if (match !== null) {
|
||||
const [_, path, lineStr] = match
|
||||
if (path.startsWith('./')) {
|
||||
const line = parseInt(lineStr)
|
||||
return {path, line}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
34
src/parsers/rspec-json/rspec-json-types.ts
Normal file
34
src/parsers/rspec-json/rspec-json-types.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export interface RspecJson {
|
||||
version: number
|
||||
examples: RspecExample[]
|
||||
summary: RspecSummary
|
||||
summary_line: string
|
||||
}
|
||||
|
||||
export interface RspecExample {
|
||||
id: string
|
||||
description: string
|
||||
full_description: string
|
||||
status: TestStatus
|
||||
file_path: string
|
||||
line_number: number
|
||||
run_time: number
|
||||
pending_message: string | null
|
||||
exception?: RspecException
|
||||
}
|
||||
|
||||
type TestStatus = 'passed' | 'failed' | 'pending';
|
||||
|
||||
export interface RspecException {
|
||||
class: string
|
||||
message: string
|
||||
backtrace: string[]
|
||||
}
|
||||
|
||||
export interface RspecSummary {
|
||||
duration: number
|
||||
example_count: number
|
||||
failure_count: number
|
||||
pending_count: number
|
||||
errors_outside_of_examples_count: number
|
||||
}
|
||||
Reference in New Issue
Block a user