Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
dorny
/
test-reporter
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Pull requests
0
Actions
Security
Insights
Additional navigation options
Code
Pull requests
Actions
Security
Insights
Files
3963c53
.github
.vscode
__tests__
assets
dist
reports
src
input-providers
parsers
report
utils
main.ts
test-parser.ts
test-results.ts
.editorconfig
.eslintignore
.eslintrc.json
.gitattributes
.gitignore
.nvmrc
.prettierignore
.prettierrc.json
CHANGELOG.md
LICENSE
README.md
action.yml
jest.config.js
package-lock.json
package.json
tsconfig.json
Breadcrumbs
test-reporter
/
src
/
main.ts
Blame
Blame
Latest commit
History
History
225 lines (193 loc) · 8.01 KB
Breadcrumbs
test-reporter
/
src
/
main.ts
Top
File metadata and controls
Code
Blame
225 lines (193 loc) · 8.01 KB
Raw
import * as core from '@actions/core' import * as github from '@actions/github' import {GitHub} from '@actions/github/lib/utils' import {ArtifactProvider} from './input-providers/artifact-provider' import {LocalFileProvider} from './input-providers/local-file-provider' import {FileContent} from './input-providers/input-provider' import {ParseOptions, TestParser} from './test-parser' import {TestRunResult} from './test-results' import {getAnnotations} from './report/get-annotations' import {getReport} from './report/get-report' import {DartJsonParser} from './parsers/dart-json/dart-json-parser' 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 {normalizeDirPath, normalizeFilePath} from './utils/path-utils' import {getCheckRunContext} from './utils/github-utils' import {Icon} from './utils/markdown-utils' async function main(): Promise<void> { try { const testReporter = new TestReporter() await testReporter.run() } catch (error) { if (error instanceof Error) core.setFailed(error) else core.setFailed(JSON.stringify(error)) } } class TestReporter { readonly artifact = core.getInput('artifact', {required: false}) readonly name = core.getInput('name', {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 listSuites = core.getInput('list-suites', {required: true}) as 'all' | 'failed' readonly listTests = core.getInput('list-tests', {required: true}) as 'all' | 'failed' | 'none' readonly maxAnnotations = parseInt(core.getInput('max-annotations', {required: true})) readonly failOnError = core.getInput('fail-on-error', {required: true}) === 'true' 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 octokit: InstanceType<typeof GitHub> readonly context = getCheckRunContext() constructor() { this.octokit = github.getOctokit(this.token) if (this.listSuites !== 'all' && this.listSuites !== 'failed') { core.setFailed(`Input parameter 'list-suites' has invalid value`) return } if (this.listTests !== 'all' && this.listTests !== 'failed' && this.listTests !== 'none') { core.setFailed(`Input parameter 'list-tests' has invalid value`) return } if (isNaN(this.maxAnnotations) || this.maxAnnotations < 0 || this.maxAnnotations > 50) { core.setFailed(`Input parameter 'max-annotations' has invalid value`) return } } async run(): Promise<void> { if (this.workDirInput) { core.info(`Changing directory to '${this.workDirInput}'`) process.chdir(this.workDirInput) } core.info(`Check runs will be created with SHA=${this.context.sha}`) // 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 ? new ArtifactProvider( this.octokit, this.artifact, this.name, pattern, this.context.sha, this.context.runId, this.token ) : new LocalFileProvider(this.name, pattern) const parseErrors = this.maxAnnotations > 0 const trackedFiles = parseErrors ? await inputProvider.listTrackedFiles() : [] const workDir = this.artifact ? undefined : normalizeDirPath(process.cwd(), true) if (parseErrors) core.info(`Found ${trackedFiles.length} files tracked by GitHub`) const options: ParseOptions = { workDir, trackedFiles, parseErrors } core.info(`Using test report parser '${this.reporter}'`) const parser = this.getParser(this.reporter, options) const results: TestRunResult[] = [] const input = await inputProvider.load() for (const [reportName, files] of Object.entries(input)) { try { core.startGroup(`Creating test report ${reportName}`) const tr = await this.createReport(parser, reportName, files) results.push(...tr) } finally { core.endGroup() } } const isFailed = results.some(tr => tr.result === 'failed') const conclusion = isFailed ? 'failure' : 'success' const passed = results.reduce((sum, tr) => sum + tr.passed, 0) const failed = results.reduce((sum, tr) => sum + tr.failed, 0) const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0) const time = results.reduce((sum, tr) => sum + tr.time, 0) core.setOutput('conclusion', conclusion) core.setOutput('passed', passed) core.setOutput('failed', failed) core.setOutput('skipped', skipped) core.setOutput('time', time) if (this.failOnError && isFailed) { core.setFailed(`Failed test were found and 'fail-on-error' option is set to ${this.failOnError}`) return } if (results.length === 0) { core.setFailed(`No test report files were found`) return } } async createReport(parser: TestParser, name: string, files: FileContent[]): Promise<TestRunResult[]> { if (files.length === 0) { core.warning(`No file matches path ${this.path}`) return [] } core.info(`Processing test results for check run ${name}`) const results: TestRunResult[] = [] for (const {file, content} of files) { try { const tr = await parser.parse(file, content) results.push(tr) } catch (error) { core.error(`Processing test results from ${file} failed`) throw error } } core.info(`Creating check run ${name}`) const createResp = await this.octokit.rest.checks.create({ head_sha: this.context.sha, name, status: 'in_progress', output: { title: name, summary: '' }, ...github.context.repo }) core.info('Creating report summary') const {listSuites, listTests, onlySummary} = this const baseUrl = createResp.data.html_url as string const summary = getReport(results, {listSuites, listTests, baseUrl, onlySummary}) core.info('Creating annotations') const annotations = getAnnotations(results, this.maxAnnotations) const isFailed = this.failOnError && results.some(tr => tr.result === 'failed') const conclusion = isFailed ? 'failure' : 'success' const icon = isFailed ? Icon.fail : Icon.success core.info(`Updating check run conclusion (${conclusion}) and output`) const resp = await this.octokit.rest.checks.update({ check_run_id: createResp.data.id, conclusion, status: 'completed', output: { title: `${name} ${icon}`, summary, annotations }, ...github.context.repo }) core.info(`Check run create response: ${resp.status}`) core.info(`Check run URL: ${resp.data.url}`) core.info(`Check run HTML: ${resp.data.html_url}`) return results } getParser(reporter: string, options: ParseOptions): TestParser { switch (reporter) { case 'dart-json': return new DartJsonParser(options, 'dart') case 'dotnet-trx': return new DotnetTrxParser(options) case 'flutter-json': return new DartJsonParser(options, 'flutter') case 'java-junit': return new JavaJunitParser(options) case 'jest-junit': return new JestJunitParser(options) case 'mocha-json': return new MochaJsonParser(options) default: throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`) } } } main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
You can’t perform that action at this time.