Create annotations where exceptions were thrown

This commit is contained in:
Michal Dorner 2020-11-28 21:24:57 +01:00
parent 6750c31e23
commit fc8cfe0f32
No known key found for this signature in database
GPG key ID: 9EEE04B48DA36786
15 changed files with 1606 additions and 19 deletions

View file

@ -12,6 +12,7 @@
"eslint-comments/no-use": "off", "eslint-comments/no-use": "off",
"import/no-namespace": "off", "import/no-namespace": "off",
"no-shadow": "off", "no-shadow": "off",
"no-unused-vars": "off",
"prefer-template": "off", "prefer-template": "off",
"semi": [ "error", "never"], "semi": [ "error", "never"],
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}], "@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
@ -34,7 +35,7 @@
"@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-unnecessary-qualifier": "error", "@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "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-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error", "@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "warn", "@typescript-eslint/prefer-for-of": "warn",

View file

@ -1,7 +1,83 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`jest-junit tests matches report snapshot 1`] = ` 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. **6** tests were completed in **1.360s** with **1** passed, **1** skipped and **4** failed.
| Result | Suite | Tests | Time | Passed ✔️ | Failed ❌ | Skipped ✖️ | | 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> | | ❌ | 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 | | ✖️ | Skipped test | 0ms | Skipped |
" ",
"title": "jest tests",
}
`; `;

View file

@ -2,17 +2,24 @@ import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import {parseJestJunit} from '../src/parsers/jest-junit/jest-junit-parser' 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 xmlFixture = fs.readFileSync(path.join(__dirname, 'fixtures', 'jest-junit.xml'), {encoding: 'utf8'})
const outputPath = __dirname + '/__outputs__/jest-junit.md' const outputPath = __dirname + '/__outputs__/jest-junit.md'
describe('jest-junit tests', () => { describe('jest-junit tests', () => {
it('matches report snapshot', async () => { 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.mkdirSync(path.dirname(outputPath), {recursive: true})
fs.writeFileSync(outputPath, result?.output?.summary ?? '') fs.writeFileSync(outputPath, result?.output?.summary ?? '')
expect(result.success).toBeFalsy() expect(result.success).toBeFalsy()
expect(result?.output?.summary).toMatchSnapshot() expect(result?.output).toMatchSnapshot()
}) })
}) })

View file

@ -8,6 +8,10 @@ description: |
author: 'Michal Dorner <dorner.michal@gmail.com>' author: 'Michal Dorner <dorner.michal@gmail.com>'
inputs: inputs:
annotations:
description: 'Annotate code where exceptions in tests were thrown'
required: true
default: 'true'
fail-on-error: fail-on-error:
description: 'Set this action as failed if test report contains any failed test' description: 'Set this action as failed if test report contains any failed test'
required: true required: true
@ -30,6 +34,9 @@ inputs:
description: 'GitHub Access Token' description: 'GitHub Access Token'
required: false required: false
default: ${{ github.token }} default: ${{ github.token }}
working-directory:
description: 'Relative path under $GITHUB_WORKSPACE where the repository was checked out.'
required: false
outputs: outputs:
conclusion: conclusion:
description: | description: |

1333
dist/index.js generated vendored

File diff suppressed because it is too large Load diff

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

6
dist/licenses.txt generated vendored
View file

@ -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. 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 @actions/github
MIT 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. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@actions/io
MIT
@octokit/auth-token @octokit/auth-token
MIT MIT
The MIT License The MIT License

13
package-lock.json generated
View file

@ -9,6 +9,14 @@
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" "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": { "@actions/github": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@actions/github/-/github-4.0.0.tgz",
@ -28,6 +36,11 @@
"tunnel": "0.0.6" "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": { "@babel/code-frame": {
"version": "7.5.5", "version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",

View file

@ -30,6 +30,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/exec": "^1.0.4",
"@actions/github": "^4.0.0", "@actions/github": "^4.0.0",
"xml2js": "^0.4.23" "xml2js": "^0.4.23"
}, },

View file

@ -1,8 +1,9 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as github from '@actions/github' import * as github from '@actions/github'
import {parseJestJunit} from './parsers/jest-junit/jest-junit-parser' import {parseJestJunit} from './parsers/jest-junit/jest-junit-parser'
import {ParseTestResult} from './parsers/test-parser' import {ParseOptions, ParseTestResult} from './parsers/test-parser'
import {getFileContent} from './utils/file-utils' import {getFileContent, normalizeDirPath} from './utils/file-utils'
import {listFiles} from './utils/git'
import {getCheckRunSha} from './utils/github-utils' import {getCheckRunSha} from './utils/github-utils'
async function run(): Promise<void> { async function run(): Promise<void> {
@ -14,18 +15,34 @@ async function run(): Promise<void> {
} }
async function main(): 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 failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
const name = core.getInput('name', {required: true}) const name = core.getInput('name', {required: true})
const path = core.getInput('path', {required: true}) const path = core.getInput('path', {required: true})
const reporter = core.getInput('reporter', {required: true}) const reporter = core.getInput('reporter', {required: true})
const token = core.getInput('token', {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 octokit = github.getOctokit(token)
const sha = getCheckRunSha() 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 parser = getParser(reporter)
const content = getFileContent(path) const content = getFileContent(path)
const result = await parser(content) const result = await parser(content, opts)
const conclusion = result.success ? 'success' : 'failure' const conclusion = result.success ? 'success' : 'failure'
await octokit.checks.create({ await octokit.checks.create({

View file

@ -1,12 +1,13 @@
import {TestResult} from '../test-parser' import {Annotation, ParseOptions, TestResult} from '../test-parser'
import {parseStringPromise} from 'xml2js' import {parseStringPromise} from 'xml2js'
import {JunitReport, TestCase, TestSuite, TestSuites} from './jest-junit-types' import {JunitReport, TestCase, TestSuite, TestSuites} from './jest-junit-types'
import {Align, Icon, link, table, exceptionCell} from '../../utils/markdown-utils' import {Align, Icon, link, table, exceptionCell} from '../../utils/markdown-utils'
import {normalizeFilePath} from '../../utils/file-utils'
import {slug} from '../../utils/slugger' import {slug} from '../../utils/slugger'
import {parseAttribute} from '../../utils/xml-utils' 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, { const junit = (await parseStringPromise(content, {
attrValueProcessors: [parseAttribute] attrValueProcessors: [parseAttribute]
})) as JunitReport })) as JunitReport
@ -17,7 +18,8 @@ export async function parseJestJunit(content: string): Promise<TestResult> {
success, success,
output: { output: {
title: junit.testsuites.$.name, 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 // use "ts-$index-" as prefix to avoid slug conflicts after escaping the paths
return slug(`ts-${index}-${name}`) 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
}

View file

@ -1,8 +1,25 @@
import {Endpoints} from '@octokit/types' 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 { export interface TestResult {
success: boolean success: boolean

21
src/utils/exec.ts Normal file
View 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
}

View file

@ -11,3 +11,23 @@ export function getFileContent(path: string): string {
return fs.readFileSync(path, {encoding: 'utf8'}) 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
View 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('')
}