mirror of
https://github.com/dorny/test-reporter.git
synced 2025-12-16 22:37:09 +01:00
Merge pull request #19 from dorny/jest-annotations
Improve JEST tests reports with check-run annotations
This commit is contained in:
commit
27b542dbb4
16 changed files with 1652 additions and 136 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,113 @@
|
||||||
// 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 {
|
||||||
**6** tests were completed in **1.360s** with **1** passed, **1** skipped and **4** failed.
|
"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_line": 10,
|
||||||
|
"title": "[__tests__\\\\main.test.js] Failing test",
|
||||||
|
},
|
||||||
|
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_line": 2,
|
||||||
|
"title": "[__tests__\\\\main.test.js] Exception in target unit",
|
||||||
|
},
|
||||||
|
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_line": 21,
|
||||||
|
"title": "[__tests__\\\\main.test.js] Exception in test",
|
||||||
|
},
|
||||||
|
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_line": 1,
|
||||||
|
"title": "[__tests__\\\\second.test.js] Timeout test",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"summary": "**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 ✖️ |
|
||||||
| :---: | :--- | ---: | ---: | ---: | ---: | ---: |
|
| :---: | :--- | ---: | ---: | ---: | ---: | ---: |
|
||||||
| ❌ | [__tests__\\\\main.test.js](#ts-0-tests-main-test-js) | 4 | 0.486s | 1 | 3 | 0 |
|
| ❌ | [__tests__\\\\main.test.js](#ts-0-tests-main-test-js) | 4 | 0.486s | 1 | 3 | 0 |
|
||||||
| ❌ | [__tests__\\\\second.test.js](#ts-1-tests-second-test-js) | 2 | 0.082s | 0 | 1 | 1 |
|
| ❌ | [__tests__\\\\second.test.js](#ts-1-tests-second-test-js) | 2 | 0.082s | 0 | 1 | 1 |
|
||||||
## Test Suites
|
# Test Suites
|
||||||
|
|
||||||
### <a id=\\"user-content-ts-0-tests-main-test-js\\" href=\\"#ts-0-tests-main-test-js\\">__tests__\\\\main.test.js</a> ❌
|
## <a id=\\"user-content-ts-0-tests-main-test-js\\" href=\\"#ts-0-tests-main-test-js\\">__tests__\\\\main.test.js</a> ❌
|
||||||
|
|
||||||
#### Test 1
|
### Test 1
|
||||||
|
|
||||||
| Result | Test | Time | Details |
|
| Result | Test | Time |
|
||||||
| :---: | :--- | ---: | --- |
|
| :---: | :--- | ---: |
|
||||||
| ✔️ | Passing test | 1ms | |
|
| ✔️ | Passing test | 1ms |
|
||||||
|
|
||||||
#### Test 1 › Test 1.1
|
### Test 1 › Test 1.1
|
||||||
|
|
||||||
| Result | Test | Time | Details |
|
| Result | Test | Time |
|
||||||
| :---: | :--- | ---: | --- |
|
| :---: | :--- | ---: |
|
||||||
| ❌ | Failing test | 2ms | <details><summary>Error: expect(received).toBeTruthy()</summary><pre>Received: false<br> at Object.<anonymous> (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\__tests__\\\\main.test.js:10:21)<br> at Object.asyncJestTest (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmineAsyncInstall.js:106:37)<br> at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:45:12<br> at new Promise (<anonymous>)<br> at mapper (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:28:19)<br> at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:75:41<br> at processTicksAndRejections (internal/process/task_queues.js:97:5)</pre></details> |
|
| ❌ | Failing test | 2ms |
|
||||||
| ❌ | Exception in target unit | 0ms | <details><summary>Error: Some error</summary><pre> at Object.throwError (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\lib\\\\main.js:2:9)<br> at Object.<anonymous> (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\__tests__\\\\main.test.js:14:11)<br> at Object.asyncJestTest (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmineAsyncInstall.js:106:37)<br> at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:45:12<br> at new Promise (<anonymous>)<br> at mapper (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:28:19)<br> at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:75:41<br> at processTicksAndRejections (internal/process/task_queues.js:97:5)</pre></details> |
|
| ❌ | Exception in target unit | 0ms |
|
||||||
|
|
||||||
#### Test 2
|
### Test 2
|
||||||
|
|
||||||
| Result | Test | Time | Details |
|
| Result | Test | Time |
|
||||||
| :---: | :--- | ---: | --- |
|
| :---: | :--- | ---: |
|
||||||
| ❌ | Exception in test | 0ms | <details><summary>Error: Some error</summary><pre> at Object.<anonymous> (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\__tests__\\\\main.test.js:21:11)<br> at Object.asyncJestTest (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\jasmineAsyncInstall.js:106:37)<br> at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:45:12<br> at new Promise (<anonymous>)<br> at mapper (C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:28:19)<br> at C:\\\\Users\\\\Michal\\\\Workspace\\\\dorny\\\\test-check\\\\reports\\\\jest\\\\node_modules\\\\jest-jasmine2\\\\build\\\\queueRunner.js:75:41<br> at processTicksAndRejections (internal/process/task_queues.js:97:5)</pre></details> |
|
| ❌ | Exception in test | 0ms |
|
||||||
|
|
||||||
### <a id=\\"user-content-ts-1-tests-second-test-js\\" href=\\"#ts-1-tests-second-test-js\\">__tests__\\\\second.test.js</a> ❌
|
## <a id=\\"user-content-ts-1-tests-second-test-js\\" href=\\"#ts-1-tests-second-test-js\\">__tests__\\\\second.test.js</a> ❌
|
||||||
|
|
||||||
| Result | Test | Time | Details |
|
| Result | Test | Time |
|
||||||
| :---: | :--- | ---: | --- |
|
| :---: | :--- | ---: |
|
||||||
| ❌ | 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 |
|
||||||
| ✖️ | Skipped test | 0ms | Skipped |
|
| ✖️ | Skipped test | 0ms |
|
||||||
"
|
",
|
||||||
|
"title": "jest tests ❌",
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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: |
|
||||||
|
|
|
||||||
1391
dist/index.js
generated
vendored
1391
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.
|
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
13
package-lock.json
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
23
src/main.ts
23
src/main.ts
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,26 @@
|
||||||
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} 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
|
||||||
const testsuites = junit.testsuites
|
const testsuites = junit.testsuites
|
||||||
const success = !(testsuites.$?.failures > 0 || testsuites.$?.errors > 0)
|
const success = !(testsuites.$?.failures > 0 || testsuites.$?.errors > 0)
|
||||||
|
const icon = success ? Icon.success : Icon.fail
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success,
|
success,
|
||||||
output: {
|
output: {
|
||||||
title: junit.testsuites.$.name,
|
title: `${junit.testsuites.$.name.trim()} ${icon}`,
|
||||||
summary: getSummary(success, junit)
|
summary: getSummary(success, junit),
|
||||||
|
annotations: options.annotations ? getAnnotations(junit, options.workDir, options.trackedFiles) : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,14 +28,12 @@ export async function parseJestJunit(content: string): Promise<TestResult> {
|
||||||
function getSummary(success: boolean, junit: JunitReport): string {
|
function getSummary(success: boolean, junit: JunitReport): string {
|
||||||
const stats = junit.testsuites.$
|
const stats = junit.testsuites.$
|
||||||
|
|
||||||
const icon = success ? Icon.success : Icon.fail
|
|
||||||
const time = `${stats.time.toFixed(3)}s`
|
const time = `${stats.time.toFixed(3)}s`
|
||||||
|
|
||||||
const skipped = getSkippedCount(junit.testsuites)
|
const skipped = getSkippedCount(junit.testsuites)
|
||||||
const failed = stats.errors + stats.failures
|
const failed = stats.errors + stats.failures
|
||||||
const passed = stats.tests - failed - skipped
|
const passed = stats.tests - failed - skipped
|
||||||
|
|
||||||
const heading = `# ${stats.name} ${icon}`
|
|
||||||
const headingLine = `**${stats.tests}** tests were completed in **${time}** with **${passed}** passed, **${skipped}** skipped and **${failed}** failed.`
|
const headingLine = `**${stats.tests}** tests were completed in **${time}** with **${passed}** passed, **${skipped}** skipped and **${failed}** failed.`
|
||||||
|
|
||||||
const suitesSummary = junit.testsuites.testsuite.map((ts, i) => {
|
const suitesSummary = junit.testsuites.testsuite.map((ts, i) => {
|
||||||
|
|
@ -41,7 +42,7 @@ function getSummary(success: boolean, junit: JunitReport): string {
|
||||||
const pass = ts.$.tests - fail - skip
|
const pass = ts.$.tests - fail - skip
|
||||||
const tm = `${ts.$.time.toFixed(3)}s`
|
const tm = `${ts.$.time.toFixed(3)}s`
|
||||||
const result = success ? Icon.success : Icon.fail
|
const result = success ? Icon.success : Icon.fail
|
||||||
const tsName = ts.$.name
|
const tsName = ts.$.name.trim()
|
||||||
const tsAddr = makeSuiteSlug(i, tsName).link
|
const tsAddr = makeSuiteSlug(i, tsName).link
|
||||||
const tsNameLink = link(tsName, tsAddr)
|
const tsNameLink = link(tsName, tsAddr)
|
||||||
return [result, tsNameLink, ts.$.tests, tm, pass, fail, skip]
|
return [result, tsNameLink, ts.$.tests, tm, pass, fail, skip]
|
||||||
|
|
@ -54,9 +55,9 @@ function getSummary(success: boolean, junit: JunitReport): string {
|
||||||
)
|
)
|
||||||
|
|
||||||
const suites = junit.testsuites?.testsuite?.map((ts, i) => getSuiteSummary(ts, i)).join('\n')
|
const suites = junit.testsuites?.testsuite?.map((ts, i) => getSuiteSummary(ts, i)).join('\n')
|
||||||
const suitesSection = `## Test Suites\n\n${suites}`
|
const suitesSection = `# Test Suites\n\n${suites}`
|
||||||
|
|
||||||
return `${heading}\n${headingLine}\n${summary}\n${suitesSection}`
|
return `${headingLine}\n${summary}\n${suitesSection}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSkippedCount(suites: TestSuites): number {
|
function getSkippedCount(suites: TestSuites): number {
|
||||||
|
|
@ -79,16 +80,15 @@ function getSuiteSummary(suite: TestSuite, index: number): string {
|
||||||
|
|
||||||
const content = groups
|
const content = groups
|
||||||
.map(grp => {
|
.map(grp => {
|
||||||
const header = grp.describe !== '' ? `#### ${grp.describe}\n\n` : ''
|
const header = grp.describe !== '' ? `### ${grp.describe.trim()}\n\n` : ''
|
||||||
const tests = table(
|
const tests = table(
|
||||||
['Result', 'Test', 'Time', 'Details'],
|
['Result', 'Test', 'Time'],
|
||||||
[Align.Center, Align.Left, Align.Right, Align.None],
|
[Align.Center, Align.Left, Align.Right],
|
||||||
...grp.tests.map(tc => {
|
...grp.tests.map(tc => {
|
||||||
const name = tc.$.name
|
const name = tc.$.name.trim()
|
||||||
const time = `${Math.round(tc.$.time * 1000)}ms`
|
const time = `${Math.round(tc.$.time * 1000)}ms`
|
||||||
const result = getTestCaseIcon(tc)
|
const result = getTestCaseIcon(tc)
|
||||||
const ex = getTestCaseDetails(tc)
|
return [result, name, time]
|
||||||
return [result, name, time, ex]
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -96,10 +96,10 @@ function getSuiteSummary(suite: TestSuite, index: number): string {
|
||||||
})
|
})
|
||||||
.join('\n')
|
.join('\n')
|
||||||
|
|
||||||
const tsName = suite.$.name
|
const tsName = suite.$.name.trim()
|
||||||
const tsSlug = makeSuiteSlug(index, tsName)
|
const tsSlug = makeSuiteSlug(index, tsName)
|
||||||
const tsNameLink = `<a id="${tsSlug.id}" href="${tsSlug.link}">${tsName}</a>`
|
const tsNameLink = `<a id="${tsSlug.id}" href="${tsSlug.link}">${tsName}</a>`
|
||||||
return `### ${tsNameLink} ${icon}\n\n${content}`
|
return `## ${tsNameLink} ${icon}\n\n${content}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTestCaseIcon(test: TestCase): string {
|
function getTestCaseIcon(test: TestCase): string {
|
||||||
|
|
@ -108,20 +108,58 @@ function getTestCaseIcon(test: TestCase): string {
|
||||||
return Icon.success
|
return Icon.success
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTestCaseDetails(test: TestCase): string {
|
|
||||||
if (test.skipped !== undefined) {
|
|
||||||
return 'Skipped'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (test.failure !== undefined) {
|
|
||||||
const failure = test.failure.join('\n')
|
|
||||||
return exceptionCell(failure)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeSuiteSlug(index: number, name: string): {id: string; link: string} {
|
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,
|
||||||
|
path: src.file,
|
||||||
|
message: ex,
|
||||||
|
title: `[${suite.$.name}] ${tc.$.name.trim()}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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'
|
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
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'})
|
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('')
|
||||||
|
}
|
||||||
|
|
@ -21,32 +21,12 @@ export function link(title: string, address: string): string {
|
||||||
|
|
||||||
type ToString = string | number | boolean | Date
|
type ToString = string | number | boolean | Date
|
||||||
export function table(headers: ToString[], align: ToString[], ...rows: ToString[][]): string {
|
export function table(headers: ToString[], align: ToString[], ...rows: ToString[][]): string {
|
||||||
const headerRow = `| ${headers.join(' | ')} |`
|
const headerRow = `| ${headers.map(tableEscape).join(' | ')} |`
|
||||||
const alignRow = `| ${align.join(' | ')} |`
|
const alignRow = `| ${align.join(' | ')} |`
|
||||||
const contentRows = rows.map(row => `| ${row.join(' | ')} |`).join('\n')
|
const contentRows = rows.map(row => `| ${row.map(tableEscape).join(' | ')} |`).join('\n')
|
||||||
return [headerRow, alignRow, contentRows].join('\n')
|
return [headerRow, alignRow, contentRows].join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function exceptionCell(ex: string): string {
|
export function tableEscape(content: ToString): string {
|
||||||
const lines = ex.split(/\r?\n/)
|
return content.toString().replace('|', '\\|')
|
||||||
if (lines.length === 0) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const summary = tableEscape(lines.shift()?.trim() || '')
|
|
||||||
const emptyLine = /^\s*$/
|
|
||||||
const firstNonEmptyLine = lines.findIndex(l => !emptyLine.test(l))
|
|
||||||
|
|
||||||
if (firstNonEmptyLine === -1) {
|
|
||||||
return summary
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentLines = firstNonEmptyLine > 0 ? lines.slice(firstNonEmptyLine) : lines
|
|
||||||
|
|
||||||
const content = '<pre>' + tableEscape(contentLines.join('<br>')) + '</pre>'
|
|
||||||
return details(summary, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tableEscape(content: string): string {
|
|
||||||
return content.replace('|', '\\|')
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue