mirror of
https://github.com/dorny/test-reporter.git
synced 2026-02-02 03:15:22 -08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d00bb14cb | ||
|
|
a585725c8b | ||
|
|
de0b4b9ece | ||
|
|
ad831af420 | ||
|
|
2ac8b4498f | ||
|
|
17e793242c | ||
|
|
e8f4fdfec7 | ||
|
|
d01ef000ba | ||
|
|
6969ae6af5 | ||
|
|
7c6c7df048 | ||
|
|
0ed324d155 | ||
|
|
72c193c336 | ||
|
|
e873f73dd6 | ||
|
|
f88270a385 | ||
|
|
dcaab46b46 |
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -1,11 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
# Enable version updates for npm
|
|
||||||
- package-ecosystem: 'npm'
|
|
||||||
# Look for `package.json` and `lock` files in the `root` directory
|
|
||||||
directory: '/'
|
|
||||||
# Check the npm registry for updates every day (weekdays)
|
|
||||||
schedule:
|
|
||||||
interval: 'monthly'
|
|
||||||
ignore:
|
|
||||||
- dependency-name: '@types/node'
|
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.5.0
|
||||||
|
- [Add option to convert backslashes in path pattern to forward slashes](https://github.com/dorny/test-reporter/pull/128)
|
||||||
|
- [Add option to generate only the summary from processed test results files](https://github.com/dorny/test-reporter/pull/123)
|
||||||
|
|
||||||
|
## v1.4.3
|
||||||
|
- [Patch java-junit to handle missing time field](https://github.com/dorny/test-reporter/pull/115)
|
||||||
|
- [Fix dart-json parsing broken by print message](https://github.com/dorny/test-reporter/pull/114)
|
||||||
|
|
||||||
## v1.4.2
|
## v1.4.2
|
||||||
- [Fix dotnet-trx parsing of passed tests with non-empty error info](https://github.com/dorny/test-reporter/commit/43d89d5ee509bcef7bd0287aacc0c4a4fb9c1657)
|
- [Fix dotnet-trx parsing of passed tests with non-empty error info](https://github.com/dorny/test-reporter/commit/43d89d5ee509bcef7bd0287aacc0c4a4fb9c1657)
|
||||||
|
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -119,6 +119,11 @@ jobs:
|
|||||||
# All matched result files must be of the same format
|
# All matched result files must be of the same format
|
||||||
path: ''
|
path: ''
|
||||||
|
|
||||||
|
# The fast-glob library that is internally used interprets backslashes as escape characters.
|
||||||
|
# If enabled, all backslashes in provided path will be replaced by forward slashes and act as directory separators.
|
||||||
|
# It might be useful when path input variable is composed dynamically from existing directory paths on Windows.
|
||||||
|
path-replace-backslashes: 'false'
|
||||||
|
|
||||||
# Format of test results. Supported options:
|
# Format of test results. Supported options:
|
||||||
# dart-json
|
# dart-json
|
||||||
# dotnet-trx
|
# dotnet-trx
|
||||||
@@ -128,6 +133,11 @@ jobs:
|
|||||||
# mocha-json
|
# mocha-json
|
||||||
reporter: ''
|
reporter: ''
|
||||||
|
|
||||||
|
# Allows you to generate only the summary.
|
||||||
|
# If enabled, the report will contain a table listing each test results file and the number of passed, failed, and skipped tests.
|
||||||
|
# Detailed listing of test suites and test cases will be skipped.
|
||||||
|
only-summary: 'false'
|
||||||
|
|
||||||
# Limits which test suites are listed:
|
# Limits which test suites are listed:
|
||||||
# all
|
# all
|
||||||
# failed
|
# failed
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
{"suite":{"id":2,"platform":"vm","path":"test\\second_test.dart"},"type":"suite","time":11}
|
{"suite":{"id":2,"platform":"vm","path":"test\\second_test.dart"},"type":"suite","time":11}
|
||||||
{"test":{"id":3,"name":"loading test\\second_test.dart","suiteID":2,"groupIDs":[],"metadata":{"skip":false,"skipReason":null},"line":null,"column":null,"url":null},"type":"testStart","time":11}
|
{"test":{"id":3,"name":"loading test\\second_test.dart","suiteID":2,"groupIDs":[],"metadata":{"skip":false,"skipReason":null},"line":null,"column":null,"url":null},"type":"testStart","time":11}
|
||||||
{"count":2,"type":"allSuites","time":11}
|
{"count":2,"type":"allSuites","time":11}
|
||||||
|
{"testID":1,"messageType":"print","message":"Hello from the test","type":"print","time":3828}
|
||||||
{"testID":3,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":3649}
|
{"testID":3,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":3649}
|
||||||
{"group":{"id":4,"suiteID":2,"parentID":null,"name":null,"metadata":{"skip":false,"skipReason":null},"testCount":2,"line":null,"column":null,"url":null},"type":"group","time":3654}
|
{"group":{"id":4,"suiteID":2,"parentID":null,"name":null,"metadata":{"skip":false,"skipReason":null},"testCount":2,"line":null,"column":null,"url":null},"type":"group","time":3654}
|
||||||
{"test":{"id":5,"name":"Timeout test","suiteID":2,"groupIDs":[4],"metadata":{"skip":false,"skipReason":null},"line":5,"column":3,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/second_test.dart"},"type":"testStart","time":3655}
|
{"test":{"id":5,"name":"Timeout test","suiteID":2,"groupIDs":[4],"metadata":{"skip":false,"skipReason":null},"line":5,"column":3,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/second_test.dart"},"type":"testStart","time":3655}
|
||||||
|
|||||||
14
action.yml
14
action.yml
@@ -15,6 +15,13 @@ inputs:
|
|||||||
Supports wildcards via [fast-glob](https://github.com/mrmlnc/fast-glob)
|
Supports wildcards via [fast-glob](https://github.com/mrmlnc/fast-glob)
|
||||||
All matched result files must be of same format
|
All matched result files must be of same format
|
||||||
required: true
|
required: true
|
||||||
|
path-replace-backslashes:
|
||||||
|
description: |
|
||||||
|
The fast-glob library that is internally used interprets backslashes as escape characters.
|
||||||
|
If enabled, all backslashes in provided path will be replaced by forward slashes and act as directory separators.
|
||||||
|
It might be useful when path input variable is composed dynamically from existing directory paths on Windows.
|
||||||
|
default: 'false'
|
||||||
|
required: false
|
||||||
reporter:
|
reporter:
|
||||||
description: |
|
description: |
|
||||||
Format of test results. Supported options:
|
Format of test results. Supported options:
|
||||||
@@ -53,6 +60,13 @@ inputs:
|
|||||||
working-directory:
|
working-directory:
|
||||||
description: Relative path under $GITHUB_WORKSPACE where the repository was checked out
|
description: Relative path under $GITHUB_WORKSPACE where the repository was checked out
|
||||||
required: false
|
required: false
|
||||||
|
only-summary:
|
||||||
|
description: |
|
||||||
|
Allows you to generate only the summary.
|
||||||
|
If enabled, the report will contain a table listing each test results file and the number of passed, failed, and skipped tests.
|
||||||
|
Detailed listing of test suites and test cases will be skipped.
|
||||||
|
default: 'false'
|
||||||
|
required: false
|
||||||
token:
|
token:
|
||||||
description: GitHub Access Token
|
description: GitHub Access Token
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
56
dist/index.js
generated
vendored
56
dist/index.js
generated
vendored
@@ -239,12 +239,14 @@ class TestReporter {
|
|||||||
this.artifact = core.getInput('artifact', { required: false });
|
this.artifact = core.getInput('artifact', { required: false });
|
||||||
this.name = core.getInput('name', { required: true });
|
this.name = core.getInput('name', { required: true });
|
||||||
this.path = core.getInput('path', { required: true });
|
this.path = core.getInput('path', { required: true });
|
||||||
|
this.pathReplaceBackslashes = core.getInput('path-replace-backslashes', { required: false }) === 'true';
|
||||||
this.reporter = core.getInput('reporter', { required: true });
|
this.reporter = core.getInput('reporter', { required: true });
|
||||||
this.listSuites = core.getInput('list-suites', { required: true });
|
this.listSuites = core.getInput('list-suites', { required: true });
|
||||||
this.listTests = core.getInput('list-tests', { required: true });
|
this.listTests = core.getInput('list-tests', { required: true });
|
||||||
this.maxAnnotations = parseInt(core.getInput('max-annotations', { required: true }));
|
this.maxAnnotations = parseInt(core.getInput('max-annotations', { required: true }));
|
||||||
this.failOnError = core.getInput('fail-on-error', { required: true }) === 'true';
|
this.failOnError = core.getInput('fail-on-error', { required: true }) === 'true';
|
||||||
this.workDirInput = core.getInput('working-directory', { required: false });
|
this.workDirInput = core.getInput('working-directory', { required: false });
|
||||||
|
this.onlySummary = core.getInput('only-summary', { required: false }) === 'true';
|
||||||
this.token = core.getInput('token', { required: true });
|
this.token = core.getInput('token', { required: true });
|
||||||
this.context = github_utils_1.getCheckRunContext();
|
this.context = github_utils_1.getCheckRunContext();
|
||||||
this.octokit = github.getOctokit(this.token);
|
this.octokit = github.getOctokit(this.token);
|
||||||
@@ -267,7 +269,10 @@ class TestReporter {
|
|||||||
process.chdir(this.workDirInput);
|
process.chdir(this.workDirInput);
|
||||||
}
|
}
|
||||||
core.info(`Check runs will be created with SHA=${this.context.sha}`);
|
core.info(`Check runs will be created with SHA=${this.context.sha}`);
|
||||||
const pattern = this.path.split(',');
|
// Split path pattern by ',' and optionally convert all backslashes to forward slashes
|
||||||
|
// fast-glob (micromatch) always interprets backslashes as escape characters instead of directory separators
|
||||||
|
const pathsList = this.path.split(',');
|
||||||
|
const pattern = this.pathReplaceBackslashes ? pathsList.map(path_utils_1.normalizeFilePath) : pathsList;
|
||||||
const inputProvider = this.artifact
|
const inputProvider = this.artifact
|
||||||
? new artifact_provider_1.ArtifactProvider(this.octokit, this.artifact, this.name, pattern, this.context.sha, this.context.runId, this.token)
|
? new artifact_provider_1.ArtifactProvider(this.octokit, this.artifact, this.name, pattern, this.context.sha, this.context.runId, this.token)
|
||||||
: new local_file_provider_1.LocalFileProvider(this.name, pattern);
|
: new local_file_provider_1.LocalFileProvider(this.name, pattern);
|
||||||
@@ -337,9 +342,9 @@ class TestReporter {
|
|||||||
...github.context.repo
|
...github.context.repo
|
||||||
});
|
});
|
||||||
core.info('Creating report summary');
|
core.info('Creating report summary');
|
||||||
const { listSuites, listTests } = this;
|
const { listSuites, listTests, onlySummary } = this;
|
||||||
const baseUrl = createResp.data.html_url;
|
const baseUrl = createResp.data.html_url;
|
||||||
const summary = get_report_1.getReport(results, { listSuites, listTests, baseUrl });
|
const summary = get_report_1.getReport(results, { listSuites, listTests, baseUrl, onlySummary });
|
||||||
core.info('Creating annotations');
|
core.info('Creating annotations');
|
||||||
const annotations = get_annotations_1.getAnnotations(results, this.maxAnnotations);
|
const annotations = get_annotations_1.getAnnotations(results, this.maxAnnotations);
|
||||||
const isFailed = results.some(tr => tr.result === 'failed');
|
const isFailed = results.some(tr => tr.result === 'failed');
|
||||||
@@ -483,13 +488,13 @@ class DartJsonParser {
|
|||||||
group.tests.push(test);
|
group.tests.push(test);
|
||||||
tests[evt.test.id] = test;
|
tests[evt.test.id] = test;
|
||||||
}
|
}
|
||||||
else if (dart_json_types_1.isTestDoneEvent(evt) && !evt.hidden) {
|
else if (dart_json_types_1.isTestDoneEvent(evt) && !evt.hidden && tests[evt.testID]) {
|
||||||
tests[evt.testID].testDone = evt;
|
tests[evt.testID].testDone = evt;
|
||||||
}
|
}
|
||||||
else if (dart_json_types_1.isErrorEvent(evt)) {
|
else if (dart_json_types_1.isErrorEvent(evt) && tests[evt.testID]) {
|
||||||
tests[evt.testID].error = evt;
|
tests[evt.testID].error = evt;
|
||||||
}
|
}
|
||||||
else if (dart_json_types_1.isMessageEvent(evt)) {
|
else if (dart_json_types_1.isMessageEvent(evt) && tests[evt.testID]) {
|
||||||
tests[evt.testID].print.push(evt);
|
tests[evt.testID].print.push(evt);
|
||||||
}
|
}
|
||||||
else if (dart_json_types_1.isDoneEvent(evt)) {
|
else if (dart_json_types_1.isDoneEvent(evt)) {
|
||||||
@@ -763,10 +768,10 @@ class DotnetTrxParser {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const error = test.error;
|
const error = test.error;
|
||||||
if (!Array.isArray(error.Message)
|
if (!Array.isArray(error.Message) ||
|
||||||
|| error.Message.length === 0
|
error.Message.length === 0 ||
|
||||||
|| !Array.isArray(error.StackTrace)
|
!Array.isArray(error.StackTrace) ||
|
||||||
|| error.StackTrace.length === 0) {
|
error.StackTrace.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const message = test.error.Message[0];
|
const message = test.error.Message[0];
|
||||||
@@ -888,6 +893,7 @@ class JavaJunitParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
getTestRunResult(filePath, junit) {
|
getTestRunResult(filePath, junit) {
|
||||||
|
var _a;
|
||||||
const suites = junit.testsuites.testsuite === undefined
|
const suites = junit.testsuites.testsuite === undefined
|
||||||
? []
|
? []
|
||||||
: junit.testsuites.testsuite.map(ts => {
|
: junit.testsuites.testsuite.map(ts => {
|
||||||
@@ -896,7 +902,8 @@ class JavaJunitParser {
|
|||||||
const sr = new test_results_1.TestSuiteResult(name, this.getGroups(ts), time);
|
const sr = new test_results_1.TestSuiteResult(name, this.getGroups(ts), time);
|
||||||
return sr;
|
return sr;
|
||||||
});
|
});
|
||||||
const time = parseFloat(junit.testsuites.$.time) * 1000;
|
const seconds = parseFloat((_a = junit.testsuites.$) === null || _a === void 0 ? void 0 : _a.time);
|
||||||
|
const time = isNaN(seconds) ? undefined : seconds * 1000;
|
||||||
return new test_results_1.TestRunResult(filePath, suites, time);
|
return new test_results_1.TestRunResult(filePath, suites, time);
|
||||||
}
|
}
|
||||||
getGroups(suite) {
|
getGroups(suite) {
|
||||||
@@ -928,18 +935,24 @@ class JavaJunitParser {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
getTestCaseResult(test) {
|
getTestCaseResult(test) {
|
||||||
if (test.failure)
|
if (test.failure || test.error)
|
||||||
return 'failed';
|
return 'failed';
|
||||||
if (test.skipped)
|
if (test.skipped)
|
||||||
return 'skipped';
|
return 'skipped';
|
||||||
return 'success';
|
return 'success';
|
||||||
}
|
}
|
||||||
getTestCaseError(tc) {
|
getTestCaseError(tc) {
|
||||||
if (!this.options.parseErrors || !tc.failure) {
|
var _a;
|
||||||
|
if (!this.options.parseErrors) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const failure = tc.failure[0];
|
// We process <error> and <failure> the same way
|
||||||
const details = failure._;
|
const failures = (_a = tc.failure) !== null && _a !== void 0 ? _a : tc.error;
|
||||||
|
if (!failures) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const failure = failures[0];
|
||||||
|
const details = typeof failure === 'object' ? failure._ : failure;
|
||||||
let filePath;
|
let filePath;
|
||||||
let line;
|
let line;
|
||||||
const src = this.exceptionThrowSource(details);
|
const src = this.exceptionThrowSource(details);
|
||||||
@@ -951,7 +964,7 @@ class JavaJunitParser {
|
|||||||
path: filePath,
|
path: filePath,
|
||||||
line,
|
line,
|
||||||
details,
|
details,
|
||||||
message: failure.message
|
message: typeof failure === 'object' ? failure.message : undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
exceptionThrowSource(stackTrace) {
|
exceptionThrowSource(stackTrace) {
|
||||||
@@ -1342,7 +1355,8 @@ const MAX_REPORT_LENGTH = 65535;
|
|||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
listSuites: 'all',
|
listSuites: 'all',
|
||||||
listTests: 'all',
|
listTests: 'all',
|
||||||
baseUrl: ''
|
baseUrl: '',
|
||||||
|
onlySummary: false
|
||||||
};
|
};
|
||||||
function getReport(results, options = defaultOptions) {
|
function getReport(results, options = defaultOptions) {
|
||||||
core.info('Generating check run summary');
|
core.info('Generating check run summary');
|
||||||
@@ -1440,7 +1454,7 @@ function getBadge(passed, failed, skipped) {
|
|||||||
}
|
}
|
||||||
function getTestRunsReport(testRuns, options) {
|
function getTestRunsReport(testRuns, options) {
|
||||||
const sections = [];
|
const sections = [];
|
||||||
if (testRuns.length > 1) {
|
if (testRuns.length > 1 || options.onlySummary) {
|
||||||
const tableData = testRuns.map((tr, runIndex) => {
|
const tableData = testRuns.map((tr, runIndex) => {
|
||||||
const time = markdown_utils_1.formatTime(tr.time);
|
const time = markdown_utils_1.formatTime(tr.time);
|
||||||
const name = tr.path;
|
const name = tr.path;
|
||||||
@@ -1454,8 +1468,10 @@ function getTestRunsReport(testRuns, options) {
|
|||||||
const resultsTable = markdown_utils_1.table(['Report', 'Passed', 'Failed', 'Skipped', 'Time'], [markdown_utils_1.Align.Left, markdown_utils_1.Align.Right, markdown_utils_1.Align.Right, markdown_utils_1.Align.Right, markdown_utils_1.Align.Right], ...tableData);
|
const resultsTable = markdown_utils_1.table(['Report', 'Passed', 'Failed', 'Skipped', 'Time'], [markdown_utils_1.Align.Left, markdown_utils_1.Align.Right, markdown_utils_1.Align.Right, markdown_utils_1.Align.Right, markdown_utils_1.Align.Right], ...tableData);
|
||||||
sections.push(resultsTable);
|
sections.push(resultsTable);
|
||||||
}
|
}
|
||||||
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat();
|
if (options.onlySummary === false) {
|
||||||
sections.push(...suitesReports);
|
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat();
|
||||||
|
sections.push(...suitesReports);
|
||||||
|
}
|
||||||
return sections;
|
return sections;
|
||||||
}
|
}
|
||||||
function getSuitesReport(tr, runIndex, options) {
|
function getSuitesReport(tr, runIndex, options) {
|
||||||
|
|||||||
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
36
dist/licenses.txt
generated
vendored
36
dist/licenses.txt
generated
vendored
@@ -341,27 +341,27 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
|
|
||||||
adm-zip
|
adm-zip
|
||||||
MIT
|
MIT
|
||||||
Copyright (c) 2012 Another-D-Mention Software and other contributors,
|
MIT License
|
||||||
http://www.another-d-mention.ro/
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Copyright (c) 2012 Another-D-Mention Software and other contributors
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
included in all copies or substantial portions of the Software.
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
The above copyright notice and this permission notice shall be included in all
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
copies or substantial portions of the Software.
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
before-after-hook
|
before-after-hook
|
||||||
|
|||||||
@@ -24,4 +24,6 @@ void main() {
|
|||||||
throw Exception('Some error');
|
throw Exception('Some error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
print('Hello from the test');
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/main.ts
14
src/main.ts
@@ -16,7 +16,7 @@ import {JavaJunitParser} from './parsers/java-junit/java-junit-parser'
|
|||||||
import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser'
|
import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser'
|
||||||
import {MochaJsonParser} from './parsers/mocha-json/mocha-json-parser'
|
import {MochaJsonParser} from './parsers/mocha-json/mocha-json-parser'
|
||||||
|
|
||||||
import {normalizeDirPath} from './utils/path-utils'
|
import {normalizeDirPath, normalizeFilePath} from './utils/path-utils'
|
||||||
import {getCheckRunContext} from './utils/github-utils'
|
import {getCheckRunContext} from './utils/github-utils'
|
||||||
import {Icon} from './utils/markdown-utils'
|
import {Icon} from './utils/markdown-utils'
|
||||||
|
|
||||||
@@ -33,12 +33,14 @@ class TestReporter {
|
|||||||
readonly artifact = core.getInput('artifact', {required: false})
|
readonly artifact = core.getInput('artifact', {required: false})
|
||||||
readonly name = core.getInput('name', {required: true})
|
readonly name = core.getInput('name', {required: true})
|
||||||
readonly path = core.getInput('path', {required: true})
|
readonly path = core.getInput('path', {required: true})
|
||||||
|
readonly pathReplaceBackslashes = core.getInput('path-replace-backslashes', {required: false}) === 'true'
|
||||||
readonly reporter = core.getInput('reporter', {required: true})
|
readonly reporter = core.getInput('reporter', {required: true})
|
||||||
readonly listSuites = core.getInput('list-suites', {required: true}) as 'all' | 'failed'
|
readonly listSuites = core.getInput('list-suites', {required: true}) as 'all' | 'failed'
|
||||||
readonly listTests = core.getInput('list-tests', {required: true}) as 'all' | 'failed' | 'none'
|
readonly listTests = core.getInput('list-tests', {required: true}) as 'all' | 'failed' | 'none'
|
||||||
readonly maxAnnotations = parseInt(core.getInput('max-annotations', {required: true}))
|
readonly maxAnnotations = parseInt(core.getInput('max-annotations', {required: true}))
|
||||||
readonly failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
|
readonly failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
|
||||||
readonly workDirInput = core.getInput('working-directory', {required: false})
|
readonly workDirInput = core.getInput('working-directory', {required: false})
|
||||||
|
readonly onlySummary = core.getInput('only-summary', {required: false}) === 'true'
|
||||||
readonly token = core.getInput('token', {required: true})
|
readonly token = core.getInput('token', {required: true})
|
||||||
readonly octokit: InstanceType<typeof GitHub>
|
readonly octokit: InstanceType<typeof GitHub>
|
||||||
readonly context = getCheckRunContext()
|
readonly context = getCheckRunContext()
|
||||||
@@ -70,7 +72,11 @@ class TestReporter {
|
|||||||
|
|
||||||
core.info(`Check runs will be created with SHA=${this.context.sha}`)
|
core.info(`Check runs will be created with SHA=${this.context.sha}`)
|
||||||
|
|
||||||
const pattern = this.path.split(',')
|
// Split path pattern by ',' and optionally convert all backslashes to forward slashes
|
||||||
|
// fast-glob (micromatch) always interprets backslashes as escape characters instead of directory separators
|
||||||
|
const pathsList = this.path.split(',')
|
||||||
|
const pattern = this.pathReplaceBackslashes ? pathsList.map(normalizeFilePath) : pathsList
|
||||||
|
|
||||||
const inputProvider = this.artifact
|
const inputProvider = this.artifact
|
||||||
? new ArtifactProvider(
|
? new ArtifactProvider(
|
||||||
this.octokit,
|
this.octokit,
|
||||||
@@ -160,9 +166,9 @@ class TestReporter {
|
|||||||
})
|
})
|
||||||
|
|
||||||
core.info('Creating report summary')
|
core.info('Creating report summary')
|
||||||
const {listSuites, listTests} = this
|
const {listSuites, listTests, onlySummary} = this
|
||||||
const baseUrl = createResp.data.html_url
|
const baseUrl = createResp.data.html_url
|
||||||
const summary = getReport(results, {listSuites, listTests, baseUrl})
|
const summary = getReport(results, {listSuites, listTests, baseUrl, onlySummary})
|
||||||
|
|
||||||
core.info('Creating annotations')
|
core.info('Creating annotations')
|
||||||
const annotations = getAnnotations(results, this.maxAnnotations)
|
const annotations = getAnnotations(results, this.maxAnnotations)
|
||||||
|
|||||||
@@ -114,11 +114,11 @@ export class DartJsonParser implements TestParser {
|
|||||||
const group = suite.groups[evt.test.groupIDs[evt.test.groupIDs.length - 1]]
|
const group = suite.groups[evt.test.groupIDs[evt.test.groupIDs.length - 1]]
|
||||||
group.tests.push(test)
|
group.tests.push(test)
|
||||||
tests[evt.test.id] = test
|
tests[evt.test.id] = test
|
||||||
} else if (isTestDoneEvent(evt) && !evt.hidden) {
|
} else if (isTestDoneEvent(evt) && !evt.hidden && tests[evt.testID]) {
|
||||||
tests[evt.testID].testDone = evt
|
tests[evt.testID].testDone = evt
|
||||||
} else if (isErrorEvent(evt)) {
|
} else if (isErrorEvent(evt) && tests[evt.testID]) {
|
||||||
tests[evt.testID].error = evt
|
tests[evt.testID].error = evt
|
||||||
} else if (isMessageEvent(evt)) {
|
} else if (isMessageEvent(evt) && tests[evt.testID]) {
|
||||||
tests[evt.testID].print.push(evt)
|
tests[evt.testID].print.push(evt)
|
||||||
} else if (isDoneEvent(evt)) {
|
} else if (isDoneEvent(evt)) {
|
||||||
success = evt.success
|
success = evt.success
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ export class JavaJunitParser implements TestParser {
|
|||||||
return sr
|
return sr
|
||||||
})
|
})
|
||||||
|
|
||||||
const time = parseFloat(junit.testsuites.$.time) * 1000
|
const seconds = parseFloat(junit.testsuites.$?.time)
|
||||||
|
const time = isNaN(seconds) ? undefined : seconds * 1000
|
||||||
return new TestRunResult(filePath, suites, time)
|
return new TestRunResult(filePath, suites, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,18 +107,24 @@ export class JavaJunitParser implements TestParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getTestCaseResult(test: TestCase): TestExecutionResult {
|
private getTestCaseResult(test: TestCase): TestExecutionResult {
|
||||||
if (test.failure) return 'failed'
|
if (test.failure || test.error) return 'failed'
|
||||||
if (test.skipped) return 'skipped'
|
if (test.skipped) return 'skipped'
|
||||||
return 'success'
|
return 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTestCaseError(tc: TestCase): TestCaseError | undefined {
|
private getTestCaseError(tc: TestCase): TestCaseError | undefined {
|
||||||
if (!this.options.parseErrors || !tc.failure) {
|
if (!this.options.parseErrors) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const failure = tc.failure[0]
|
// We process <error> and <failure> the same way
|
||||||
const details = failure._
|
const failures = tc.failure ?? tc.error
|
||||||
|
if (!failures) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const failure = failures[0]
|
||||||
|
const details = typeof failure === 'object' ? failure._ : failure
|
||||||
let filePath
|
let filePath
|
||||||
let line
|
let line
|
||||||
|
|
||||||
@@ -131,7 +138,7 @@ export class JavaJunitParser implements TestParser {
|
|||||||
path: filePath,
|
path: filePath,
|
||||||
line,
|
line,
|
||||||
details,
|
details,
|
||||||
message: failure.message
|
message: typeof failure === 'object' ? failure.message : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ export interface TestCase {
|
|||||||
name: string
|
name: string
|
||||||
time: string
|
time: string
|
||||||
}
|
}
|
||||||
failure?: Failure[]
|
failure?: string | Failure[]
|
||||||
|
error?: string | Failure[]
|
||||||
skipped?: string[]
|
skipped?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ export interface ReportOptions {
|
|||||||
listSuites: 'all' | 'failed'
|
listSuites: 'all' | 'failed'
|
||||||
listTests: 'all' | 'failed' | 'none'
|
listTests: 'all' | 'failed' | 'none'
|
||||||
baseUrl: string
|
baseUrl: string
|
||||||
|
onlySummary: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: ReportOptions = {
|
const defaultOptions: ReportOptions = {
|
||||||
listSuites: 'all',
|
listSuites: 'all',
|
||||||
listTests: 'all',
|
listTests: 'all',
|
||||||
baseUrl: ''
|
baseUrl: '',
|
||||||
|
onlySummary: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReport(results: TestRunResult[], options: ReportOptions = defaultOptions): string {
|
export function getReport(results: TestRunResult[], options: ReportOptions = defaultOptions): string {
|
||||||
@@ -132,7 +134,7 @@ function getBadge(passed: number, failed: number, skipped: number): string {
|
|||||||
function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): string[] {
|
function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): string[] {
|
||||||
const sections: string[] = []
|
const sections: string[] = []
|
||||||
|
|
||||||
if (testRuns.length > 1) {
|
if (testRuns.length > 1 || options.onlySummary) {
|
||||||
const tableData = testRuns.map((tr, runIndex) => {
|
const tableData = testRuns.map((tr, runIndex) => {
|
||||||
const time = formatTime(tr.time)
|
const time = formatTime(tr.time)
|
||||||
const name = tr.path
|
const name = tr.path
|
||||||
@@ -152,8 +154,10 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
|
|||||||
sections.push(resultsTable)
|
sections.push(resultsTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat()
|
if (options.onlySummary === false) {
|
||||||
sections.push(...suitesReports)
|
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat()
|
||||||
|
sections.push(...suitesReports)
|
||||||
|
}
|
||||||
return sections
|
return sections
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user