mirror of
https://github.com/dorny/test-reporter.git
synced 2025-12-15 13:57:09 +01:00
Create annotations where exceptions were thrown
This commit is contained in:
parent
6750c31e23
commit
fc8cfe0f32
15 changed files with 1606 additions and 19 deletions
|
|
@ -12,6 +12,7 @@
|
|||
"eslint-comments/no-use": "off",
|
||||
"import/no-namespace": "off",
|
||||
"no-shadow": "off",
|
||||
"no-unused-vars": "off",
|
||||
"prefer-template": "off",
|
||||
"semi": [ "error", "never"],
|
||||
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
|
||||
|
|
@ -34,7 +35,7 @@
|
|||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-unnecessary-qualifier": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["error", {"varsIgnorePattern": "^_"}],
|
||||
"@typescript-eslint/no-useless-constructor": "error",
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"@typescript-eslint/prefer-for-of": "warn",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,83 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`jest-junit tests matches report snapshot 1`] = `
|
||||
"# jest tests ❌
|
||||
Object {
|
||||
"annotations": Array [
|
||||
Object {
|
||||
"annotation_level": "failure",
|
||||
"end_line": 10,
|
||||
"message": "Error: expect(received).toBeTruthy()
|
||||
|
||||
Received: false
|
||||
at Object.<anonymous> (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\__tests__\\\\main.test.js:10:21)
|
||||
at Object.asyncJestTest (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmineAsyncInstall.js:106:37)
|
||||
at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:45:12
|
||||
at new Promise (<anonymous>)
|
||||
at mapper (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:28:19)
|
||||
at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:75:41
|
||||
at processTicksAndRejections (internal/process/task_queues.js:97:5)",
|
||||
"path": "__tests__/main.test.js",
|
||||
"start_column": 21,
|
||||
"start_line": 10,
|
||||
"title": "Exception was thrown here",
|
||||
},
|
||||
Object {
|
||||
"annotation_level": "failure",
|
||||
"end_line": 2,
|
||||
"message": "Error: Some error
|
||||
at Object.throwError (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\lib\\\\main.js:2:9)
|
||||
at Object.<anonymous> (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\__tests__\\\\main.test.js:14:11)
|
||||
at Object.asyncJestTest (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmineAsyncInstall.js:106:37)
|
||||
at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:45:12
|
||||
at new Promise (<anonymous>)
|
||||
at mapper (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:28:19)
|
||||
at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:75:41
|
||||
at processTicksAndRejections (internal/process/task_queues.js:97:5)",
|
||||
"path": "lib/main.js",
|
||||
"start_column": 9,
|
||||
"start_line": 2,
|
||||
"title": "Exception was thrown here",
|
||||
},
|
||||
Object {
|
||||
"annotation_level": "failure",
|
||||
"end_line": 21,
|
||||
"message": "Error: Some error
|
||||
at Object.<anonymous> (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\__tests__\\\\main.test.js:21:11)
|
||||
at Object.asyncJestTest (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmineAsyncInstall.js:106:37)
|
||||
at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:45:12
|
||||
at new Promise (<anonymous>)
|
||||
at mapper (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:28:19)
|
||||
at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:75:41
|
||||
at processTicksAndRejections (internal/process/task_queues.js:97:5)",
|
||||
"path": "__tests__/main.test.js",
|
||||
"start_column": 11,
|
||||
"start_line": 21,
|
||||
"title": "Exception was thrown here",
|
||||
},
|
||||
Object {
|
||||
"annotation_level": "failure",
|
||||
"end_line": 1,
|
||||
"message": ": Timeout - Async callback was not invoked within the 1 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 1 ms timeout specified by jest.setTimeout.Error:
|
||||
at new Spec (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmine\\\\Spec.js:116:22)
|
||||
at new Spec (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\setup_jest_globals.js:78:9)
|
||||
at specFactory (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmine\\\\Env.js:523:24)
|
||||
at Env.it (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmine\\\\Env.js:592:24)
|
||||
at Env.it (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmineAsyncInstall.js:134:23)
|
||||
at it (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmine\\\\jasmineLight.js:100:21)
|
||||
at Object.<anonymous> (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\__tests__\\\\second.test.js:1:34)
|
||||
at Runtime._execModule (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runtime\\\\build\\\\index.js:1245:24)
|
||||
at Runtime._loadModule (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runtime\\\\build\\\\index.js:844:12)
|
||||
at Runtime.requireModule (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runtime\\\\build\\\\index.js:694:10)
|
||||
at jasmine2 (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\index.js:230:13)
|
||||
at runTestInternal (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runner\\\\build\\\\runTest.js:380:22)
|
||||
at runTest (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runner\\\\build\\\\runTest.js:472:34)",
|
||||
"path": "__tests__/second.test.js",
|
||||
"start_column": 34,
|
||||
"start_line": 1,
|
||||
"title": "Exception was thrown here",
|
||||
},
|
||||
],
|
||||
"summary": "# jest tests ❌
|
||||
**6** tests were completed in **1.360s** with **1** passed, **1** skipped and **4** failed.
|
||||
| Result | Suite | Tests | Time | Passed ✔️ | Failed ❌ | Skipped ✖️ |
|
||||
| :---: | :--- | ---: | ---: | ---: | ---: | ---: |
|
||||
|
|
@ -36,5 +112,7 @@ exports[`jest-junit tests matches report snapshot 1`] = `
|
|||
| :---: | :--- | ---: | --- |
|
||||
| ❌ | Timeout test | 4ms | <details><summary>: Timeout - Async callback was not invoked within the 1 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 1 ms timeout specified by jest.setTimeout.Error:</summary><pre> at new Spec (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmine\\\\Spec.js:116:22)<br> at new Spec (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\setup_jest_globals.js:78:9)<br> at specFactory (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmine\\\\Env.js:523:24)<br> at Env.it (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmine\\\\Env.js:592:24)<br> at Env.it (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmineAsyncInstall.js:134:23)<br> at it (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmine\\\\jasmineLight.js:100:21)<br> at Object.<anonymous> (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\__tests__\\\\second.test.js:1:34)<br> at Runtime._execModule (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runtime\\\\build\\\\index.js:1245:24)<br> at Runtime._loadModule (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runtime\\\\build\\\\index.js:844:12)<br> at Runtime.requireModule (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runtime\\\\build\\\\index.js:694:10)<br> at jasmine2 (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\index.js:230:13)<br> at runTestInternal (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runner\\\\build\\\\runTest.js:380:22)<br> at runTest (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-runner\\\\build\\\\runTest.js:472:34)</pre></details> |
|
||||
| ✖️ | Skipped test | 0ms | Skipped |
|
||||
"
|
||||
",
|
||||
"title": "jest tests",
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -2,17 +2,24 @@ import * as fs from 'fs'
|
|||
import * as path from 'path'
|
||||
|
||||
import {parseJestJunit} from '../src/parsers/jest-junit/jest-junit-parser'
|
||||
import {ParseOptions} from '../src/parsers/test-parser'
|
||||
|
||||
const xmlFixture = fs.readFileSync(path.join(__dirname, 'fixtures', 'jest-junit.xml'), {encoding: 'utf8'})
|
||||
const outputPath = __dirname + '/__outputs__/jest-junit.md'
|
||||
|
||||
describe('jest-junit tests', () => {
|
||||
it('matches report snapshot', async () => {
|
||||
const result = await parseJestJunit(xmlFixture)
|
||||
const opts: ParseOptions = {
|
||||
annotations: true,
|
||||
trackedFiles: ['__tests__/main.test.js', '__tests__/second.test.js', 'lib/main.js'],
|
||||
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/jest/'
|
||||
}
|
||||
|
||||
const result = await parseJestJunit(xmlFixture, opts)
|
||||
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||
fs.writeFileSync(outputPath, result?.output?.summary ?? '')
|
||||
|
||||
expect(result.success).toBeFalsy()
|
||||
expect(result?.output?.summary).toMatchSnapshot()
|
||||
expect(result?.output).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ description: |
|
|||
|
||||
author: 'Michal Dorner <dorner.michal@gmail.com>'
|
||||
inputs:
|
||||
annotations:
|
||||
description: 'Annotate code where exceptions in tests were thrown'
|
||||
required: true
|
||||
default: 'true'
|
||||
fail-on-error:
|
||||
description: 'Set this action as failed if test report contains any failed test'
|
||||
required: true
|
||||
|
|
@ -30,6 +34,9 @@ inputs:
|
|||
description: 'GitHub Access Token'
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
working-directory:
|
||||
description: 'Relative path under $GITHUB_WORKSPACE where the repository was checked out.'
|
||||
required: false
|
||||
outputs:
|
||||
conclusion:
|
||||
description: |
|
||||
|
|
|
|||
1333
dist/index.js
generated
vendored
1333
dist/index.js
generated
vendored
File diff suppressed because it is too large
Load diff
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
6
dist/licenses.txt
generated
vendored
6
dist/licenses.txt
generated
vendored
|
|
@ -10,6 +10,9 @@ The above copyright notice and this permission notice shall be included in all c
|
|||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 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.
|
||||
|
||||
@actions/exec
|
||||
MIT
|
||||
|
||||
@actions/github
|
||||
MIT
|
||||
|
||||
|
|
@ -38,6 +41,9 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
@actions/io
|
||||
MIT
|
||||
|
||||
@octokit/auth-token
|
||||
MIT
|
||||
The MIT License
|
||||
|
|
|
|||
13
package-lock.json
generated
13
package-lock.json
generated
|
|
@ -9,6 +9,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
||||
},
|
||||
"@actions/exec": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz",
|
||||
"integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==",
|
||||
"requires": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@actions/github": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-4.0.0.tgz",
|
||||
|
|
@ -28,6 +36,11 @@
|
|||
"tunnel": "0.0.6"
|
||||
}
|
||||
},
|
||||
"@actions/io": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
|
||||
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/exec": "^1.0.4",
|
||||
"@actions/github": "^4.0.0",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
|
|
|
|||
23
src/main.ts
23
src/main.ts
|
|
@ -1,8 +1,9 @@
|
|||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import {parseJestJunit} from './parsers/jest-junit/jest-junit-parser'
|
||||
import {ParseTestResult} from './parsers/test-parser'
|
||||
import {getFileContent} from './utils/file-utils'
|
||||
import {ParseOptions, ParseTestResult} from './parsers/test-parser'
|
||||
import {getFileContent, normalizeDirPath} from './utils/file-utils'
|
||||
import {listFiles} from './utils/git'
|
||||
import {getCheckRunSha} from './utils/github-utils'
|
||||
|
||||
async function run(): Promise<void> {
|
||||
|
|
@ -14,18 +15,34 @@ async function run(): Promise<void> {
|
|||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const annotations = core.getInput('annotations', {required: true}) === 'true'
|
||||
const failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
|
||||
const name = core.getInput('name', {required: true})
|
||||
const path = core.getInput('path', {required: true})
|
||||
const reporter = core.getInput('reporter', {required: true})
|
||||
const token = core.getInput('token', {required: true})
|
||||
const workDirInput = core.getInput('working-directory', {required: false})
|
||||
|
||||
if (workDirInput) {
|
||||
process.chdir(workDirInput)
|
||||
}
|
||||
|
||||
const workDir = normalizeDirPath(workDirInput || process.cwd(), true)
|
||||
const octokit = github.getOctokit(token)
|
||||
const sha = getCheckRunSha()
|
||||
|
||||
// We won't need tracked files if we are not going to create annotations
|
||||
const trackedFiles = annotations ? await listFiles() : []
|
||||
|
||||
const opts: ParseOptions = {
|
||||
annotations,
|
||||
trackedFiles,
|
||||
workDir
|
||||
}
|
||||
|
||||
const parser = getParser(reporter)
|
||||
const content = getFileContent(path)
|
||||
const result = await parser(content)
|
||||
const result = await parser(content, opts)
|
||||
const conclusion = result.success ? 'success' : 'failure'
|
||||
|
||||
await octokit.checks.create({
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import {TestResult} from '../test-parser'
|
||||
import {Annotation, ParseOptions, TestResult} from '../test-parser'
|
||||
import {parseStringPromise} from 'xml2js'
|
||||
|
||||
import {JunitReport, TestCase, TestSuite, TestSuites} from './jest-junit-types'
|
||||
import {Align, Icon, link, table, exceptionCell} from '../../utils/markdown-utils'
|
||||
import {normalizeFilePath} from '../../utils/file-utils'
|
||||
import {slug} from '../../utils/slugger'
|
||||
import {parseAttribute} from '../../utils/xml-utils'
|
||||
|
||||
export async function parseJestJunit(content: string): Promise<TestResult> {
|
||||
export async function parseJestJunit(content: string, options: ParseOptions): Promise<TestResult> {
|
||||
const junit = (await parseStringPromise(content, {
|
||||
attrValueProcessors: [parseAttribute]
|
||||
})) as JunitReport
|
||||
|
|
@ -17,7 +18,8 @@ export async function parseJestJunit(content: string): Promise<TestResult> {
|
|||
success,
|
||||
output: {
|
||||
title: junit.testsuites.$.name,
|
||||
summary: getSummary(success, junit)
|
||||
summary: getSummary(success, junit),
|
||||
annotations: options.annotations ? getAnnotations(junit, options.workDir, options.trackedFiles) : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -125,3 +127,55 @@ function makeSuiteSlug(index: number, name: string): {id: string; link: string}
|
|||
// use "ts-$index-" as prefix to avoid slug conflicts after escaping the paths
|
||||
return slug(`ts-${index}-${name}`)
|
||||
}
|
||||
|
||||
function getAnnotations(junit: JunitReport, workDir: string, trackedFiles: string[]): Annotation[] {
|
||||
const annotations: Annotation[] = []
|
||||
for (const suite of junit.testsuites.testsuite) {
|
||||
for (const tc of suite.testcase) {
|
||||
if (!tc.failure) {
|
||||
continue
|
||||
}
|
||||
for (const ex of tc.failure) {
|
||||
const src = exceptionThrowSource(ex, workDir, trackedFiles)
|
||||
if (src === null) {
|
||||
continue
|
||||
}
|
||||
annotations.push({
|
||||
annotation_level: 'failure',
|
||||
start_line: src.line,
|
||||
end_line: src.line,
|
||||
start_column: src.column,
|
||||
path: src.file,
|
||||
message: ex,
|
||||
title: 'Exception was thrown here'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
export function exceptionThrowSource(
|
||||
ex: string,
|
||||
workDir: string,
|
||||
trackedFiles: string[]
|
||||
): {file: string; line: number; column: number} | null {
|
||||
const lines = ex.split(/\r?\n/)
|
||||
const re = /\((.*):(\d+):(\d+)\)$/
|
||||
|
||||
for (const str of lines) {
|
||||
const match = str.match(re)
|
||||
if (match !== null) {
|
||||
const [_, fileStr, lineStr, colStr] = match
|
||||
const filePath = normalizeFilePath(fileStr)
|
||||
const file = filePath.startsWith(workDir) ? filePath.substr(workDir.length) : filePath
|
||||
if (trackedFiles.includes(file)) {
|
||||
const line = parseInt(lineStr)
|
||||
const column = parseInt(colStr)
|
||||
return {file, line, column}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,25 @@
|
|||
import {Endpoints} from '@octokit/types'
|
||||
|
||||
type OutputParameters = Endpoints['POST /repos/:owner/:repo/check-runs']['parameters']['output']
|
||||
export type OutputParameters = Endpoints['POST /repos/:owner/:repo/check-runs']['parameters']['output']
|
||||
export type Annotation = {
|
||||
path: string
|
||||
start_line: number
|
||||
end_line: number
|
||||
start_column?: number
|
||||
end_column?: number
|
||||
annotation_level: 'notice' | 'warning' | 'failure'
|
||||
message: string
|
||||
title?: string
|
||||
raw_details?: string
|
||||
}
|
||||
|
||||
export type ParseTestResult = (content: string) => Promise<TestResult>
|
||||
export type ParseTestResult = (content: string, options: ParseOptions) => Promise<TestResult>
|
||||
|
||||
export interface ParseOptions {
|
||||
annotations: boolean
|
||||
workDir: string
|
||||
trackedFiles: string[]
|
||||
}
|
||||
|
||||
export interface TestResult {
|
||||
success: boolean
|
||||
|
|
|
|||
21
src/utils/exec.ts
Normal file
21
src/utils/exec.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import {exec as execImpl, ExecOptions} from '@actions/exec'
|
||||
|
||||
// Wraps original exec() function
|
||||
// Returns exit code and whole stdout/stderr
|
||||
export default async function exec(commandLine: string, args?: string[], options?: ExecOptions): Promise<ExecResult> {
|
||||
options = options || {}
|
||||
let stdout = ''
|
||||
let stderr = ''
|
||||
options.listeners = {
|
||||
stdout: (data: Buffer) => (stdout += data.toString()),
|
||||
stderr: (data: Buffer) => (stderr += data.toString())
|
||||
}
|
||||
const code = await execImpl(commandLine, args, options)
|
||||
return {code, stdout, stderr}
|
||||
}
|
||||
|
||||
export interface ExecResult {
|
||||
code: number
|
||||
stdout: string
|
||||
stderr: string
|
||||
}
|
||||
|
|
@ -11,3 +11,23 @@ export function getFileContent(path: string): string {
|
|||
|
||||
return fs.readFileSync(path, {encoding: 'utf8'})
|
||||
}
|
||||
|
||||
export function normalizeDirPath(path: string, trailingSeparator: boolean): string {
|
||||
if (!path) {
|
||||
return path
|
||||
}
|
||||
|
||||
path = normalizeFilePath(path)
|
||||
if (trailingSeparator && !path.endsWith('/')) {
|
||||
path += '/'
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
export function normalizeFilePath(path: string): string {
|
||||
if (!path) {
|
||||
return path
|
||||
}
|
||||
|
||||
return path.trim().replace(/\\/g, '/')
|
||||
}
|
||||
|
|
|
|||
22
src/utils/git.ts
Normal file
22
src/utils/git.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import * as core from '@actions/core'
|
||||
import exec from './exec'
|
||||
|
||||
export async function listFiles(): Promise<string[]> {
|
||||
core.startGroup('Listing all files tracked by git')
|
||||
let output = ''
|
||||
try {
|
||||
output = (await exec('git', ['ls-files', '-z'])).stdout
|
||||
} finally {
|
||||
fixStdOutNullTermination()
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
return output.split('\u0000').filter(s => s.length > 0)
|
||||
}
|
||||
|
||||
function fixStdOutNullTermination(): void {
|
||||
// Previous command uses NULL as delimiters and output is printed to stdout.
|
||||
// We have to make sure next thing written to stdout will start on new line.
|
||||
// Otherwise things like ::set-output wouldn't work.
|
||||
core.info('')
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue