├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .gitattributes
├── .github
└── workflows
│ ├── ci.yml
│ └── dirty-laundry.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .vscode
└── launch.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── __tests__
├── __outputs__
│ ├── dart-json.md
│ ├── dotnet-trx-only-failed.md
│ ├── dotnet-trx.md
│ ├── fluent-validation-test-results.md
│ ├── jest-junit-eslint.md
│ ├── jest-junit.md
│ ├── jest-test-errors-results.md
│ ├── jest-test-results.md
│ ├── mocha-json.md
│ ├── mocha-test-results.md
│ ├── mochawesome-json.md
│ ├── playwright-test-errors-results.md
│ ├── provider-test-results.md
│ ├── pulsar-test-results-no-merge.md
│ ├── pulsar-test-results.md
│ └── silent-notes-test-results.md
├── __snapshots__
│ ├── dart-json.test.ts.snap
│ ├── dotnet-trx.test.ts.snap
│ ├── java-junit.test.ts.snap
│ ├── jest-junit.test.ts.snap
│ ├── mocha-json.test.ts.snap
│ └── mochawesome-json.test.ts.snap
├── dart-json.test.ts
├── dotnet-trx.test.ts
├── fixtures
│ ├── assets
│ │ ├── MaterialIcons-Regular.woff
│ │ ├── MaterialIcons-Regular.woff2
│ │ ├── app.css
│ │ ├── app.css.map
│ │ ├── app.js
│ │ ├── app.js.LICENSE.txt
│ │ ├── app.js.map
│ │ ├── roboto-light-webfont.woff
│ │ ├── roboto-light-webfont.woff2
│ │ ├── roboto-medium-webfont.woff
│ │ ├── roboto-medium-webfont.woff2
│ │ ├── roboto-regular-webfont.woff
│ │ └── roboto-regular-webfont.woff2
│ ├── dart-json.json
│ ├── dotnet-trx.trx
│ ├── empty
│ │ ├── dart-json.json
│ │ ├── dotnet-trx.trx
│ │ ├── java-junit.xml
│ │ ├── jest-junit.xml
│ │ ├── mocha-json.json
│ │ └── mochawesome-json.json
│ ├── external
│ │ ├── FluentValidation.Tests.trx
│ │ ├── SilentNotes.trx
│ │ ├── flutter
│ │ │ ├── files.txt
│ │ │ └── provider-test-results.json
│ │ ├── java
│ │ │ ├── TEST-org.apache.pulsar.AddMissingPatchVersionTest.xml
│ │ │ ├── files.txt
│ │ │ └── pulsar-test-report.xml
│ │ ├── jest
│ │ │ ├── files.txt
│ │ │ └── jest-test-results.xml
│ │ └── mocha
│ │ │ ├── files.txt
│ │ │ └── mocha-test-results.json
│ ├── jest-junit-eslint.xml
│ ├── jest-junit.xml
│ ├── mocha-json.json
│ ├── mochawesome-json.html
│ ├── mochawesome-json.json
│ ├── mochawesome1-json.json
│ └── test-errors
│ │ ├── jest
│ │ ├── files.txt
│ │ └── jest-test-results.xml
│ │ └── playwright
│ │ ├── files.txt
│ │ └── test-results.xml
├── java-junit.test.ts
├── jest-junit.test.ts
├── mocha-json.test.ts
├── mochawesome-json.test.ts
└── utils
│ └── parse-utils.test.ts
├── action.yml
├── assets
├── fluent-validation-report.png
├── mocha-groups.png
├── provider-error-details.png
└── provider-error-summary.png
├── dist
├── index.js
├── index.js.map
├── licenses.txt
└── sourcemap-register.js
├── jest.config.js
├── package-lock.json
├── package.json
├── reports
├── dart
│ ├── .gitignore
│ ├── analysis_options.yaml
│ ├── lib
│ │ └── main.dart
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── test
│ │ ├── main_test.dart
│ │ └── second_test.dart
├── dotnet
│ ├── .gitignore
│ ├── DotnetTests.Unit
│ │ ├── Calculator.cs
│ │ └── DotnetTests.Unit.csproj
│ ├── DotnetTests.XUnitTests
│ │ ├── CalculatorTests.cs
│ │ └── DotnetTests.XUnitTests.csproj
│ └── DotnetTests.sln
├── jest
│ ├── __tests__
│ │ ├── main.test.js
│ │ └── second.test.js
│ ├── lib
│ │ └── main.js
│ ├── package-lock.json
│ └── package.json
├── mocha
│ ├── lib
│ │ └── main.js
│ ├── package-lock.json
│ ├── package.json
│ └── test
│ │ ├── main.test.js
│ │ └── second.test.js
└── mochawesome
│ ├── lib
│ └── main.js
│ ├── package-lock.json
│ ├── package.json
│ └── test
│ ├── main.test.js
│ └── second.test.js
├── src
├── input-providers
│ ├── artifact-provider.ts
│ ├── input-provider.ts
│ └── local-file-provider.ts
├── main.ts
├── parsers
│ ├── dart-json
│ │ ├── dart-json-parser.ts
│ │ └── dart-json-types.ts
│ ├── dotnet-trx
│ │ ├── dotnet-trx-parser.ts
│ │ └── dotnet-trx-types.ts
│ ├── java-junit
│ │ ├── java-junit-parser.ts
│ │ └── java-junit-types.ts
│ ├── jest-junit
│ │ ├── jest-junit-parser.ts
│ │ └── jest-junit-types.ts
│ ├── mocha-json
│ │ ├── mocha-json-parser.ts
│ │ └── mocha-json-types.ts
│ └── mochawesome-json
│ │ ├── mochawesome-json-parser.ts
│ │ └── mochawesome-json-types.ts
├── report
│ ├── get-annotations.ts
│ └── get-report.ts
├── test-parser.ts
├── test-results.ts
└── utils
│ ├── constants.ts
│ ├── exec.ts
│ ├── git.ts
│ ├── github-utils.ts
│ ├── markdown-utils.ts
│ ├── node-utils.ts
│ ├── parse-utils.ts
│ ├── path-utils.ts
│ └── slugger.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | insert_final_newline = true
8 |
9 | [*.cs]
10 | indent_size = 4
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | lib/
3 | node_modules/
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["jest", "@typescript-eslint"],
3 | "extends": ["plugin:github/recommended"],
4 | "parser": "@typescript-eslint/parser",
5 | "parserOptions": {
6 | "ecmaVersion": 9,
7 | "sourceType": "module",
8 | "project": "./tsconfig.json"
9 | },
10 | "rules": {
11 | "camelcase": "off",
12 | "eslint-comments/no-use": "off",
13 | "i18n-text/no-en": "off",
14 | "import/no-namespace": "off",
15 | "no-shadow": "off",
16 | "no-unused-vars": "off",
17 | "prefer-template": "off",
18 | "semi": [ "error", "never"],
19 | "@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
20 | "@typescript-eslint/array-type": "error",
21 | "@typescript-eslint/await-thenable": "error",
22 | "@typescript-eslint/ban-ts-comment": "error",
23 | "@typescript-eslint/consistent-type-assertions": "error",
24 | "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
25 | "@typescript-eslint/func-call-spacing": ["error", "never"],
26 | "@typescript-eslint/no-array-constructor": "error",
27 | "@typescript-eslint/no-empty-interface": "error",
28 | "@typescript-eslint/no-explicit-any": "off",
29 | "@typescript-eslint/no-extraneous-class": "error",
30 | "@typescript-eslint/no-for-in-array": "error",
31 | "@typescript-eslint/no-inferrable-types": "error",
32 | "@typescript-eslint/no-misused-new": "error",
33 | "@typescript-eslint/no-namespace": "error",
34 | "@typescript-eslint/no-require-imports": "error",
35 | "@typescript-eslint/no-shadow": "error",
36 | "@typescript-eslint/no-non-null-assertion": "warn",
37 | "@typescript-eslint/no-unnecessary-qualifier": "error",
38 | "@typescript-eslint/no-unnecessary-type-assertion": "error",
39 | "@typescript-eslint/no-unused-vars": ["error", {"varsIgnorePattern": "^_"}],
40 | "@typescript-eslint/no-useless-constructor": "error",
41 | "@typescript-eslint/no-var-requires": "error",
42 | "@typescript-eslint/prefer-for-of": "warn",
43 | "@typescript-eslint/prefer-function-type": "warn",
44 | "@typescript-eslint/prefer-includes": "error",
45 | "@typescript-eslint/prefer-string-starts-ends-with": "error",
46 | "@typescript-eslint/promise-function-async": "error",
47 | "@typescript-eslint/require-array-sort-compare": "error",
48 | "@typescript-eslint/restrict-plus-operands": "error",
49 | "@typescript-eslint/semi": ["error", "never"],
50 | "@typescript-eslint/type-annotation-spacing": "error",
51 | "@typescript-eslint/unbound-method": "error"
52 | },
53 | "env": {
54 | "node": true,
55 | "es6": true,
56 | "jest/globals": true
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 | dist/** -diff linguist-generated=true
3 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: 'CI'
2 | on:
3 | pull_request:
4 | paths-ignore: ['**.md']
5 | push:
6 | paths-ignore: ['**.md']
7 | branches:
8 | - main
9 | workflow_dispatch:
10 |
11 | permissions:
12 | actions: read
13 | checks: write
14 |
15 | jobs:
16 | build-test:
17 | name: Build & Test
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v4
21 | - run: npm ci
22 | - run: npm run build
23 | - run: npm run format-check
24 | - run: npm run lint
25 | - run: npm test
26 |
27 | - name: Upload test results
28 | if: success() || failure()
29 | uses: actions/upload-artifact@v4
30 | with:
31 | name: test-results
32 | path: __tests__/__results__/*.xml
33 |
34 | - name: Create test report
35 | uses: ./
36 | with:
37 | artifact: test-results
38 | name: Workflow Report
39 | path: '*.xml'
40 | reporter: jest-junit
41 |
42 | - name: Create mochawesome report
43 | uses: ./
44 | if: success() || failure()
45 | with:
46 | name: Mochawesome Tests
47 | path: __tests__/fixtures/mochawesome-json.json
48 | reporter: mochawesome-json
49 | fail-on-error: false
50 |
--------------------------------------------------------------------------------
/.github/workflows/dirty-laundry.yml:
--------------------------------------------------------------------------------
1 | name: 'Dirty Laundry Test'
2 | on:
3 | pull_request:
4 | paths-ignore: ['**.md']
5 | push:
6 | paths-ignore: ['**.md']
7 | workflow_dispatch:
8 |
9 | jobs:
10 | build-test:
11 | name: Build & Test
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - run: npm ci
16 | - run: npm run build
17 | - run: npm run format-check
18 | - run: npm run lint
19 |
20 | - name: Create mochawesome report
21 | uses: ./
22 | id: test-report
23 | if: success() || failure()
24 | with:
25 | name: Mochawesome Tests
26 | path: __tests__/fixtures/mochawesome1-json.json
27 | reporter: mochawesome-json
28 | fail-on-error: false
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependency directory
2 | node_modules
3 |
4 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 |
13 | # Diagnostic reports (https://nodejs.org/api/report.html)
14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
15 |
16 | # Runtime data
17 | pids
18 | *.pid
19 | *.seed
20 | *.pid.lock
21 |
22 | # Directory for instrumented libs generated by jscoverage/JSCover
23 | lib-cov
24 |
25 | # Coverage directory used by tools like istanbul
26 | coverage
27 | *.lcov
28 |
29 | # nyc test coverage
30 | .nyc_output
31 |
32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
33 | .grunt
34 |
35 | # Bower dependency directory (https://bower.io/)
36 | bower_components
37 |
38 | # node-waf configuration
39 | .lock-wscript
40 |
41 | # Compiled binary addons (https://nodejs.org/api/addons.html)
42 | build/Release
43 |
44 | # Dependency directories
45 | jspm_packages/
46 |
47 | # TypeScript v1 declaration files
48 | typings/
49 |
50 | # TypeScript cache
51 | *.tsbuildinfo
52 |
53 | # Optional npm cache directory
54 | .npm
55 |
56 | # Optional eslint cache
57 | .eslintcache
58 |
59 | # Optional REPL history
60 | .node_repl_history
61 |
62 | # Output of 'npm pack'
63 | *.tgz
64 |
65 | # Yarn Integrity file
66 | .yarn-integrity
67 |
68 | # dotenv environment variables file
69 | .env
70 | .env.test
71 |
72 | # parcel-bundler cache (https://parceljs.org/)
73 | .cache
74 |
75 | # next.js build output
76 | .next
77 |
78 | # nuxt.js build output
79 | .nuxt
80 |
81 | # vuepress build output
82 | .vuepress/dist
83 |
84 | # Serverless directories
85 | .serverless/
86 |
87 | # FuseBox cache
88 | .fusebox/
89 |
90 | # DynamoDB Local files
91 | .dynamodb/
92 |
93 | # OS metadata
94 | .DS_Store
95 | Thumbs.db
96 |
97 | # Ignore built ts files
98 | __tests__/runner/*
99 | lib/**/*
100 |
101 | # Project specific
102 | __tests__/__results__
103 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | lib/
3 | node_modules/
4 | __tests__/__outputs__
5 | __tests__/__snapshots__
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": false,
6 | "singleQuote": true,
7 | "trailingComma": "none",
8 | "bracketSpacing": false,
9 | "arrowParens": "avoid"
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "type": "node",
6 | "request": "launch",
7 | "name": "Jest All",
8 | "program": "${workspaceFolder}/node_modules/.bin/jest",
9 | "args": ["--runInBand"],
10 | "console": "integratedTerminal",
11 | "internalConsoleOptions": "neverOpen",
12 | "disableOptimisticBPs": true,
13 | "windows": {
14 | "program": "${workspaceFolder}/node_modules/jest/bin/jest",
15 | }
16 | },
17 | {
18 | "type": "node",
19 | "request": "launch",
20 | "name": "Jest Current File",
21 | "program": "${workspaceFolder}/node_modules/.bin/jest",
22 | "args": [
23 | "${fileBasenameNoExtension}",
24 | "--config",
25 | "jest.config.js"
26 | ],
27 | "console": "integratedTerminal",
28 | "internalConsoleOptions": "neverOpen",
29 | "disableOptimisticBPs": true,
30 | "windows": {
31 | "program": "${workspaceFolder}/node_modules/jest/bin/jest",
32 | }
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v1.5.0
4 | - [Add option to convert backslashes in path pattern to forward slashes](https://github.com/phoenix-actions/test-reporting/pull/128)
5 | - [Add option to generate only the summary from processed test results files](https://github.com/phoenix-actions/test-reporting/pull/123)
6 |
7 | ## v1.4.3
8 | - [Patch java-junit to handle missing time field](https://github.com/phoenix-actions/test-reporting/pull/115)
9 | - [Fix dart-json parsing broken by print message](https://github.com/phoenix-actions/test-reporting/pull/114)
10 |
11 | ## v1.4.2
12 | - [Fix dotnet-trx parsing of passed tests with non-empty error info](https://github.com/phoenix-actions/test-reporting/commit/43d89d5ee509bcef7bd0287aacc0c4a4fb9c1657)
13 |
14 | ## v1.4.1
15 | - [Fix dotnet-trx parsing of tests with custom display names](https://github.com/phoenix-actions/test-reporting/pull/105)
16 |
17 | ## v1.4.0
18 | - [Add support for mocha-json](https://github.com/phoenix-actions/test-reporting/pull/90)
19 | - [Use full URL to fix navigation from summary to suite details](https://github.com/phoenix-actions/test-reporting/pull/89)
20 | - [New report rendering with code blocks instead of tables](https://github.com/phoenix-actions/test-reporting/pull/88)
21 | - [Improve test error messages from flutter](https://github.com/phoenix-actions/test-reporting/pull/87)
22 |
23 | ## v1.3.1
24 | - [Fix: parsing of .NET duration string without milliseconds](https://github.com/phoenix-actions/test-reporting/pull/84)
25 | - [Fix: dart-json - remove group name from test case names](https://github.com/phoenix-actions/test-reporting/pull/85)
26 | - [Fix: net-trx parser crashing on missing duration attribute](https://github.com/phoenix-actions/test-reporting/pull/86)
27 |
28 | ## v1.3.0
29 | - [Add support for java-junit](https://github.com/phoenix-actions/test-reporting/pull/80)
30 | - [Fix: Handle test reports with no test cases](https://github.com/phoenix-actions/test-reporting/pull/70)
31 | - [Fix: Reduce number of API calls to get list of files tracked by GitHub](https://github.com/phoenix-actions/test-reporting/pull/69)
32 |
33 | ## v1.2.0
34 | - [Set `listTests` and `listSuites` to lower detail if report is too big](https://github.com/phoenix-actions/test-reporting/pull/60)
35 |
36 | ## v1.1.0
37 | - [Support public repo PR workflow](https://github.com/phoenix-actions/test-reporting/pull/56)
38 |
39 | ## v1.0.0
40 | Supported languages / frameworks:
41 | - .NET / xUnit / NUnit / MSTest
42 | - Dart / test
43 | - Flutter / test
44 | - JavaScript / JEST
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2021 Michal Dorner and contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/__tests__/__outputs__/dart-json.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ❌ fixtures/dart-json.json
3 | **6** tests were completed in **4s** with **1** passed, **4** failed and **1** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[test/main_test.dart](#r0s0)|1✔️|3❌||74ms|
7 | |[test/second_test.dart](#r0s1)||1❌|1✖️|51ms|
8 | ### ❌ test/main_test.dart
9 | ```
10 | Test 1
11 | ✔️ Passing test
12 | Test 1 Test 1.1
13 | ❌ Failing test
14 | Expected: <2>
15 | Actual: <1>
16 |
17 | ❌ Exception in target unit
18 | Exception: Some error
19 | Test 2
20 | ❌ Exception in test
21 | Exception: Some error
22 | ```
23 | ### ❌ test/second_test.dart
24 | ```
25 | ❌ Timeout test
26 | TimeoutException after 0:00:00.000001: Test timed out after 0 seconds.
27 | ✖️ Skipped test
28 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/dotnet-trx-only-failed.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ❌ fixtures/dotnet-trx.trx
3 | **11** tests were completed in **1s** with **5** passed, **5** failed and **1** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[DotnetTests.XUnitTests.CalculatorTests](#r0s0)|5✔️|5❌|1✖️|118ms|
7 | ### ❌ DotnetTests.XUnitTests.CalculatorTests
8 | ```
9 | ❌ Exception_In_TargetTest
10 | System.DivideByZeroException : Attempted to divide by zero.
11 | ❌ Exception_In_Test
12 | System.Exception : Test
13 | ❌ Failing_Test
14 | Assert.Equal() Failure
15 | Expected: 3
16 | Actual: 2
17 | ❌ Is_Even_Number(i: 3)
18 | Assert.True() Failure
19 | Expected: True
20 | Actual: False
21 | ❌ Should be even number(i: 3)
22 | Assert.True() Failure
23 | Expected: True
24 | Actual: False
25 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/dotnet-trx.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ❌ fixtures/dotnet-trx.trx
3 | **11** tests were completed in **1s** with **5** passed, **5** failed and **1** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[DotnetTests.XUnitTests.CalculatorTests](#r0s0)|5✔️|5❌|1✖️|118ms|
7 | ### ❌ DotnetTests.XUnitTests.CalculatorTests
8 | ```
9 | ✔️ Custom Name
10 | ❌ Exception_In_TargetTest
11 | System.DivideByZeroException : Attempted to divide by zero.
12 | ❌ Exception_In_Test
13 | System.Exception : Test
14 | ❌ Failing_Test
15 | Assert.Equal() Failure
16 | Expected: 3
17 | Actual: 2
18 | ✔️ Is_Even_Number(i: 2)
19 | ❌ Is_Even_Number(i: 3)
20 | Assert.True() Failure
21 | Expected: True
22 | Actual: False
23 | ✔️ Passing_Test
24 | ✔️ Should be even number(i: 2)
25 | ❌ Should be even number(i: 3)
26 | Assert.True() Failure
27 | Expected: True
28 | Actual: False
29 | ✖️ Skipped_Test
30 | ✔️ Timeout_Test
31 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/jest-junit-eslint.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ✔️ fixtures/jest-junit-eslint.xml
3 | **1** tests were completed in **0ms** with **1** passed, **0** failed and **0** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[test.jsx](#r0s0)|1✔️|||0ms|
7 | ### ✔️ test.jsx
8 | ```
9 | test
10 | ✔️ test.jsx
11 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/jest-junit.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ❌ fixtures/jest-junit.xml
3 | **6** tests were completed in **1s** with **1** passed, **4** failed and **1** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[__tests__\main.test.js](#r0s0)|1✔️|3❌||486ms|
7 | |[__tests__\second.test.js](#r0s1)||1❌|1✖️|82ms|
8 | ### ❌ __tests__\main.test.js
9 | ```
10 | Test 1
11 | ✔️ Passing test
12 | Test 1 › Test 1.1
13 | ❌ Failing test
14 | Error: expect(received).toBeTruthy()
15 | ❌ Exception in target unit
16 | Error: Some error
17 | Test 2
18 | ❌ Exception in test
19 | Error: Some error
20 | ```
21 | ### ❌ __tests__\second.test.js
22 | ```
23 | ❌ Timeout test
24 | : 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:
25 | ✖️ Skipped test
26 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/jest-test-errors-results.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ❌ fixtures/test-errors/jest/jest-test-results.xml
3 | **2** tests were completed in **646ms** with **0** passed, **2** failed and **0** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[libs/bar.spec.ts](#r0s0)||1❌||0ms|
7 | |[libs/foo.spec.ts](#r0s1)||1❌||0ms|
8 | ### ❌ libs/bar.spec.ts
9 | ```
10 | Test suite failed to run
11 | ❌ libs/bar.spec.ts
12 | ● Test suite failed to run
13 | ```
14 | ### ❌ libs/foo.spec.ts
15 | ```
16 | Test suite failed to run
17 | ❌ libs/foo.spec.ts
18 | ● Test suite failed to run
19 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/mocha-json.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ❌ fixtures/mocha-json.json
3 | **6** tests were completed in **11ms** with **1** passed, **4** failed and **1** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[test/main.test.js](#r0s0)|1✔️|3❌||3ms|
7 | |[test/second.test.js](#r0s1)||1❌|1✖️|2ms|
8 | ### ❌ test/main.test.js
9 | ```
10 | Test 1
11 | ✔️ Passing test
12 | Test 1 Test 1.1
13 | ❌ Exception in target unit
14 | Some error
15 | ❌ Failing test
16 | Expected values to be strictly equal:
17 |
18 | false !== true
19 |
20 | Test 2
21 | ❌ Exception in test
22 | Some error
23 | ```
24 | ### ❌ test/second.test.js
25 | ```
26 | ✖️ Skipped test
27 | ❌ Timeout test
28 | Timeout of 1ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js)
29 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/mocha-test-results.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ✔️ fixtures/external/mocha/mocha-test-results.json
3 | **839** tests were completed in **6s** with **833** passed, **0** failed and **6** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |test/node-unit/buffered-worker-pool.spec.js|14✔️|||8ms|
7 | |test/node-unit/cli/config.spec.js|10✔️|||8ms|
8 | |test/node-unit/cli/node-flags.spec.js|105✔️|||9ms|
9 | |test/node-unit/cli/options.spec.js|36✔️|||250ms|
10 | |test/node-unit/cli/run-helpers.spec.js|9✔️|||8ms|
11 | |test/node-unit/cli/run.spec.js|40✔️|||4ms|
12 | |test/node-unit/mocha.spec.js|24✔️|||33ms|
13 | |test/node-unit/parallel-buffered-runner.spec.js|19✔️|||23ms|
14 | |test/node-unit/reporters/parallel-buffered.spec.js|6✔️|||16ms|
15 | |test/node-unit/serializer.spec.js|40✔️|||31ms|
16 | |test/node-unit/stack-trace-filter.spec.js|2✔️||4✖️|1ms|
17 | |test/node-unit/utils.spec.js|5✔️|||1ms|
18 | |test/node-unit/worker.spec.js|15✔️|||92ms|
19 | |test/unit/context.spec.js|8✔️|||5ms|
20 | |test/unit/duration.spec.js|3✔️|||166ms|
21 | |test/unit/errors.spec.js|13✔️|||5ms|
22 | |test/unit/globals.spec.js|4✔️|||0ms|
23 | |test/unit/grep.spec.js|8✔️|||2ms|
24 | |test/unit/hook-async.spec.js|3✔️|||1ms|
25 | |test/unit/hook-sync-nested.spec.js|4✔️|||1ms|
26 | |test/unit/hook-sync.spec.js|3✔️|||0ms|
27 | |test/unit/hook-timeout.spec.js|1✔️|||0ms|
28 | |test/unit/hook.spec.js|4✔️|||0ms|
29 | |test/unit/mocha.spec.js|115✔️||1✖️|128ms|
30 | |test/unit/overspecified-async.spec.js|1✔️|||3ms|
31 | |test/unit/parse-query.spec.js|2✔️|||1ms|
32 | |test/unit/plugin-loader.spec.js|41✔️||1✖️|16ms|
33 | |test/unit/required-tokens.spec.js|1✔️|||0ms|
34 | |test/unit/root.spec.js|1✔️|||0ms|
35 | |test/unit/runnable.spec.js|55✔️|||122ms|
36 | |test/unit/runner.spec.js|77✔️|||43ms|
37 | |test/unit/suite.spec.js|57✔️|||14ms|
38 | |test/unit/test.spec.js|15✔️|||0ms|
39 | |test/unit/throw.spec.js|9✔️|||9ms|
40 | |test/unit/timeout.spec.js|8✔️|||109ms|
41 | |test/unit/utils.spec.js|75✔️|||24ms|
--------------------------------------------------------------------------------
/__tests__/__outputs__/mochawesome-json.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ❌ fixtures/mochawesome-json.json
3 | **5** tests were completed in **14ms** with **1** passed, **4** failed and **0** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[nofile](#r0s0)|1✔️|4❌||5ms|
7 | ### ❌ nofile
8 | ```
9 | ❌ Timeout test
10 | Error: Timeout of 1ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mochawesome/test/second.test.js)
11 | Test 1
12 | ✔️ Passing test
13 | Test 1 Test 1.1
14 | ❌ Exception in target unit
15 | Error: Some error
16 | ❌ Failing test
17 | AssertionError: Expected values to be strictly equal:
18 |
19 | false !== true
20 |
21 | Test 2
22 | ❌ Exception in test
23 | Error: Some error
24 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/playwright-test-errors-results.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ❌ fixtures/test-errors/playwright/test-results.xml
3 | **1** tests were completed in **849ms** with **0** passed, **1** failed and **0** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[libs/foo.spec.ts](#r0s0)||1❌||144s|
7 | ### ❌ libs/foo.spec.ts
8 | ```
9 | libs/foo.spec.ts
10 | ❌ Test suite failure
11 | [LRS:Chromium] › libs/foo.spec.ts:38:5 › Test suite failure
12 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/pulsar-test-results-no-merge.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ❌ fixtures/external/java/TEST-org.apache.pulsar.AddMissingPatchVersionTest.xml
3 | **2** tests were completed in **116ms** with **0** passed, **1** failed and **1** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[org.apache.pulsar.AddMissingPatchVersionTest](#r0s0)||1❌|1✖️|116ms|
7 | ### ❌ org.apache.pulsar.AddMissingPatchVersionTest
8 | ```
9 | ✖️ testVersionStrings
10 | ❌ testVersionStrings
11 | java.lang.AssertionError: expected [1.2.1] but found [1.2.0]
12 | ```
--------------------------------------------------------------------------------
/__tests__/__outputs__/silent-notes-test-results.md:
--------------------------------------------------------------------------------
1 | 
2 | ## ✔️ fixtures/external/SilentNotes.trx
3 | **79** tests were completed in **1s** with **67** passed, **0** failed and **12** skipped.
4 | |Test suite|Passed|Failed|Skipped|Time|
5 | |:---|---:|---:|---:|---:|
6 | |[VanillaCloudStorageClientTest.CloudStorageCredentialsTest](#r0s0)|6✔️|||30ms|
7 | |[VanillaCloudStorageClientTest.CloudStorageProviders.DropboxCloudStorageClientTest](#r0s1)|2✔️||3✖️|101ms|
8 | |[VanillaCloudStorageClientTest.CloudStorageProviders.FtpCloudStorageClientTest](#r0s2)|4✔️||3✖️|166ms|
9 | |[VanillaCloudStorageClientTest.CloudStorageProviders.GmxCloudStorageClientTest](#r0s3)|2✔️|||7ms|
10 | |[VanillaCloudStorageClientTest.CloudStorageProviders.GoogleCloudStorageClientTest](#r0s4)|1✔️||3✖️|40ms|
11 | |[VanillaCloudStorageClientTest.CloudStorageProviders.OnedriveCloudStorageClientTest](#r0s5)|1✔️||3✖️|15ms|
12 | |[VanillaCloudStorageClientTest.CloudStorageProviders.WebdavCloudStorageClientTest](#r0s6)|5✔️|||16ms|
13 | |[VanillaCloudStorageClientTest.CloudStorageTokenTest](#r0s7)|9✔️|||0ms|
14 | |[VanillaCloudStorageClientTest.OAuth2.AuthorizationResponseErrorTest](#r0s8)|3✔️|||3ms|
15 | |[VanillaCloudStorageClientTest.OAuth2.OAuth2UtilsTest](#r0s9)|9✔️|||12ms|
16 | |[VanillaCloudStorageClientTest.OAuth2CloudStorageClientTest](#r0s10)|5✔️|||13ms|
17 | |[VanillaCloudStorageClientTest.SecureStringExtensionsTest](#r0s11)|7✔️|||0ms|
18 | |[VanillaCloudStorageClientTest.SerializeableCloudStorageCredentialsTest](#r0s12)|13✔️|||43ms|
19 | ### ✔️ VanillaCloudStorageClientTest.CloudStorageCredentialsTest
20 | ```
21 | ✔️ AreEqualWorksWithDifferentPassword
22 | ✔️ AreEqualWorksWithSameContent
23 | ✔️ CorrectlyConvertsSecureStringToString
24 | ✔️ CorrectlyConvertsStringToSecureString
25 | ✔️ ValidateAcceptsValidCredentials
26 | ✔️ ValidateRejectsInvalidCredentials
27 | ```
28 | ### ✔️ VanillaCloudStorageClientTest.CloudStorageProviders.DropboxCloudStorageClientTest
29 | ```
30 | ✔️ FileLifecycleWorks
31 | ✖️ ReallyDoFetchToken
32 | ✖️ ReallyDoOpenAuthorizationPageInBrowser
33 | ✖️ ReallyDoRefreshToken
34 | ✔️ ThrowsAccessDeniedExceptionWithInvalidToken
35 | ```
36 | ### ✔️ VanillaCloudStorageClientTest.CloudStorageProviders.FtpCloudStorageClientTest
37 | ```
38 | ✔️ FileLifecycleWorks
39 | ✔️ SanitizeCredentials_ChangesInvalidPrefix
40 | ✔️ SecureSslConnectionWorks
41 | ✔️ ThrowsWithHttpInsteadOfFtp
42 | ✖️ ThrowsWithInvalidPassword
43 | ✖️ ThrowsWithInvalidUrl
44 | ✖️ ThrowsWithInvalidUsername
45 | ```
46 | ### ✔️ VanillaCloudStorageClientTest.CloudStorageProviders.GmxCloudStorageClientTest
47 | ```
48 | ✔️ ChoosesCorrectUrlForGmxComEmail
49 | ✔️ ChoosesCorrectUrlForGmxNetEmail
50 | ```
51 | ### ✔️ VanillaCloudStorageClientTest.CloudStorageProviders.GoogleCloudStorageClientTest
52 | ```
53 | ✔️ FileLifecycleWorks
54 | ✖️ ReallyDoFetchToken
55 | ✖️ ReallyDoOpenAuthorizationPageInBrowser
56 | ✖️ ReallyDoRefreshToken
57 | ```
58 | ### ✔️ VanillaCloudStorageClientTest.CloudStorageProviders.OnedriveCloudStorageClientTest
59 | ```
60 | ✔️ FileLifecycleWorks
61 | ✖️ ReallyDoFetchToken
62 | ✖️ ReallyDoOpenAuthorizationPageInBrowser
63 | ✖️ ReallyDoRefreshToken
64 | ```
65 | ### ✔️ VanillaCloudStorageClientTest.CloudStorageProviders.WebdavCloudStorageClientTest
66 | ```
67 | ✔️ FileLifecycleWorks
68 | ✔️ ParseGmxWebdavResponseCorrectly
69 | ✔️ ParseStratoWebdavResponseCorrectly
70 | ✔️ ThrowsWithInvalidPath
71 | ✔️ ThrowsWithInvalidUsername
72 | ```
73 | ### ✔️ VanillaCloudStorageClientTest.CloudStorageTokenTest
74 | ```
75 | ✔️ AreEqualWorksWithNullDate
76 | ✔️ AreEqualWorksWithSameContent
77 | ✔️ NeedsRefreshReturnsFalseForTokenFlow
78 | ✔️ NeedsRefreshReturnsFalseIfNotExpired
79 | ✔️ NeedsRefreshReturnsTrueIfExpired
80 | ✔️ NeedsRefreshReturnsTrueIfNoExpirationDate
81 | ✔️ SetExpiryDateBySecondsWorks
82 | ✔️ SetExpiryDateBySecondsWorksWithNull
83 | ✔️ SetExpiryDateBySecondsWorksWithVeryShortPeriod
84 | ```
85 | ### ✔️ VanillaCloudStorageClientTest.OAuth2.AuthorizationResponseErrorTest
86 | ```
87 | ✔️ ParsesAllErrorCodesCorrectly
88 | ✔️ ParsesNullErrorCodeCorrectly
89 | ✔️ ParsesUnknownErrorCodeCorrectly
90 | ```
91 | ### ✔️ VanillaCloudStorageClientTest.OAuth2.OAuth2UtilsTest
92 | ```
93 | ✔️ BuildAuthorizationRequestUrlEscapesParameters
94 | ✔️ BuildAuthorizationRequestUrlLeavesOutOptionalParameters
95 | ✔️ BuildAuthorizationRequestUrlThrowsWithMissingRedirectUrlForTokenFlow
96 | ✔️ BuildAuthorizationRequestUrlUsesAllParameters
97 | ✔️ BuildAuthorizationRequestUrlUsesCodeVerifier
98 | ✔️ ParseRealWorldDropboxRejectResponse
99 | ✔️ ParseRealWorldDropboxSuccessResponse
100 | ✔️ ParseRealWorldGoogleRejectResponse
101 | ✔️ ParseRealWorldGoogleSuccessResponse
102 | ```
103 | ### ✔️ VanillaCloudStorageClientTest.OAuth2CloudStorageClientTest
104 | ```
105 | ✔️ BuildOAuth2AuthorizationRequestUrlWorks
106 | ✔️ FetchTokenCanInterpretGoogleResponse
107 | ✔️ FetchTokenReturnsNullForDeniedAccess
108 | ✔️ FetchTokenThrowsWithWrongState
109 | ✔️ RefreshTokenCanInterpretGoogleResponse
110 | ```
111 | ### ✔️ VanillaCloudStorageClientTest.SecureStringExtensionsTest
112 | ```
113 | ✔️ AreEqualsWorksCorrectly
114 | ✔️ CorrectlyConvertsSecureStringToString
115 | ✔️ CorrectlyConvertsSecureStringToUnicodeBytes
116 | ✔️ CorrectlyConvertsSecureStringToUtf8Bytes
117 | ✔️ CorrectlyConvertsStringToSecureString
118 | ✔️ CorrectlyConvertsUnicodeBytesToSecureString
119 | ✔️ CorrectlyConvertsUtf8BytesToSecureString
120 | ```
121 | ### ✔️ VanillaCloudStorageClientTest.SerializeableCloudStorageCredentialsTest
122 | ```
123 | ✔️ DecryptAfterDesrializationCanReadAllPropertiesBack
124 | ✔️ DecryptAfterDesrializationRespectsNullProperties
125 | ✔️ EncryptBeforeSerializationProtectsAllNecessaryProperties
126 | ✔️ EncryptBeforeSerializationRespectsNullProperties
127 | ✔️ SerializedDatacontractCanBeReadBack
128 | ✔️ SerializedDatacontractDoesNotContainNullProperties
129 | ✔️ SerializedDatacontractDoesNotContainPlaintextData
130 | ✔️ SerializedJsonCanBeReadBack
131 | ✔️ SerializedJsonDoesNotContainNullProperties
132 | ✔️ SerializedJsonDoesNotContainPlaintextData
133 | ✔️ SerializedXmlCanBeReadBack
134 | ✔️ SerializedXmlDoesNotContainNullProperties
135 | ✔️ SerializedXmlDoesNotContainPlaintextData
136 | ```
--------------------------------------------------------------------------------
/__tests__/__snapshots__/mochawesome-json.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`mochawesome-json tests report from ./reports/mochawesome-json test results matches snapshot 1`] = `
4 | TestRunResult {
5 | "path": "fixtures/mochawesome-json.json",
6 | "suites": [
7 | TestSuiteResult {
8 | "groups": [
9 | TestGroupResult {
10 | "name": null,
11 | "tests": [
12 | TestCaseResult {
13 | "error": {
14 | "details": "Error: Timeout of 1ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mochawesome/test/second.test.js)
15 | at listOnTimeout (node:internal/timers:557:17)
16 | at processTimers (node:internal/timers:500:7)",
17 | "line": undefined,
18 | "message": "Error: Timeout of 1ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mochawesome/test/second.test.js)",
19 | "path": undefined,
20 | },
21 | "name": "Timeout test",
22 | "result": "failed",
23 | "time": 3,
24 | },
25 | ],
26 | },
27 | TestGroupResult {
28 | "name": "Test 1",
29 | "tests": [
30 | TestCaseResult {
31 | "error": undefined,
32 | "name": "Passing test",
33 | "result": "success",
34 | "time": 0,
35 | },
36 | ],
37 | },
38 | TestGroupResult {
39 | "name": "Test 1 Test 1.1",
40 | "tests": [
41 | TestCaseResult {
42 | "error": {
43 | "details": "Error: Some error
44 | at Object.throwError (lib/main.js:2:9)
45 | at Context. (test/main.test.js:15:11)
46 | at processImmediate (node:internal/timers:464:21)",
47 | "line": 2,
48 | "message": "Error: Some error",
49 | "path": "lib/main.js",
50 | },
51 | "name": "Exception in target unit",
52 | "result": "failed",
53 | "time": 0,
54 | },
55 | TestCaseResult {
56 | "error": {
57 | "details": "AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
58 |
59 | false !== true
60 |
61 | at Context. (test/main.test.js:11:14)
62 | at processImmediate (node:internal/timers:464:21)",
63 | "line": 11,
64 | "message": "AssertionError: Expected values to be strictly equal:
65 |
66 | false !== true
67 | ",
68 | "path": "test/main.test.js",
69 | },
70 | "name": "Failing test",
71 | "result": "failed",
72 | "time": 2,
73 | },
74 | ],
75 | },
76 | TestGroupResult {
77 | "name": "Test 2",
78 | "tests": [
79 | TestCaseResult {
80 | "error": {
81 | "details": "Error: Some error
82 | at Context. (test/main.test.js:22:11)
83 | at processImmediate (node:internal/timers:464:21)",
84 | "line": 22,
85 | "message": "Error: Some error",
86 | "path": "test/main.test.js",
87 | },
88 | "name": "Exception in test",
89 | "result": "failed",
90 | "time": 0,
91 | },
92 | ],
93 | },
94 | ],
95 | "name": "nofile",
96 | "totalTime": undefined,
97 | },
98 | ],
99 | "totalTime": 14,
100 | }
101 | `;
102 |
--------------------------------------------------------------------------------
/__tests__/dart-json.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import * as path from 'path'
3 |
4 | import {DartJsonParser} from '../src/parsers/dart-json/dart-json-parser'
5 | import {ParseOptions} from '../src/test-parser'
6 | import {getReport} from '../src/report/get-report'
7 | import {normalizeFilePath} from '../src/utils/path-utils'
8 |
9 | describe('dart-json tests', () => {
10 | it('produces empty test run result when there are no test cases', async () => {
11 | const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'dart-json.json')
12 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
13 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
14 |
15 | const opts: ParseOptions = {
16 | parseErrors: true,
17 | trackedFiles: []
18 | }
19 |
20 | const parser = new DartJsonParser(opts, 'dart')
21 | const result = await parser.parse(filePath, fileContent)
22 | expect(result.tests).toBe(0)
23 | expect(result.result).toBe('success')
24 | })
25 |
26 | it('matches report snapshot', async () => {
27 | const opts: ParseOptions = {
28 | parseErrors: true,
29 | trackedFiles: ['lib/main.dart', 'test/main_test.dart', 'test/second_test.dart']
30 | //workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/dart/'
31 | }
32 |
33 | const fixturePath = path.join(__dirname, 'fixtures', 'dart-json.json')
34 | const outputPath = path.join(__dirname, '__outputs__', 'dart-json.md')
35 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
36 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
37 |
38 | const parser = new DartJsonParser(opts, 'dart')
39 | const result = await parser.parse(filePath, fileContent)
40 | expect(result).toMatchSnapshot()
41 |
42 | const report = getReport([result])
43 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
44 | fs.writeFileSync(outputPath, report)
45 | })
46 |
47 | it('report from rrousselGit/provider test results matches snapshot', async () => {
48 | const fixturePath = path.join(__dirname, 'fixtures', 'external', 'flutter', 'provider-test-results.json')
49 | const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'flutter', 'files.txt')
50 | const outputPath = path.join(__dirname, '__outputs__', 'provider-test-results.md')
51 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
52 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
53 |
54 | const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
55 | const opts: ParseOptions = {
56 | trackedFiles,
57 | parseErrors: true
58 | //workDir: '/__w/provider/provider/'
59 | }
60 |
61 | const parser = new DartJsonParser(opts, 'flutter')
62 | const result = await parser.parse(filePath, fileContent)
63 | expect(result).toMatchSnapshot()
64 |
65 | const report = getReport([result])
66 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
67 | fs.writeFileSync(outputPath, report)
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/__tests__/dotnet-trx.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import * as path from 'path'
3 |
4 | import {DotnetTrxParser} from '../src/parsers/dotnet-trx/dotnet-trx-parser'
5 | import {ParseOptions} from '../src/test-parser'
6 | import {getReport, ReportOptions} from '../src/report/get-report'
7 | import {normalizeFilePath} from '../src/utils/path-utils'
8 |
9 | describe('dotnet-trx tests', () => {
10 | it('produces empty test run result when there are no test cases', async () => {
11 | const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'dotnet-trx.trx')
12 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
13 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
14 |
15 | const opts: ParseOptions = {
16 | parseErrors: true,
17 | trackedFiles: []
18 | }
19 |
20 | const parser = new DotnetTrxParser(opts)
21 | const result = await parser.parse(filePath, fileContent)
22 | expect(result.tests).toBe(0)
23 | expect(result.result).toBe('success')
24 | })
25 |
26 | it('matches report snapshot', async () => {
27 | const fixturePath = path.join(__dirname, 'fixtures', 'dotnet-trx.trx')
28 | const outputPath = path.join(__dirname, '__outputs__', 'dotnet-trx.md')
29 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
30 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
31 |
32 | const opts: ParseOptions = {
33 | parseErrors: true,
34 | trackedFiles: ['DotnetTests.Unit/Calculator.cs', 'DotnetTests.XUnitTests/CalculatorTests.cs']
35 | //workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/dotnet/'
36 | }
37 |
38 | const parser = new DotnetTrxParser(opts)
39 | const result = await parser.parse(filePath, fileContent)
40 | expect(result).toMatchSnapshot()
41 |
42 | const report = getReport([result])
43 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
44 | fs.writeFileSync(outputPath, report)
45 | })
46 |
47 | it('matches report snapshot (only failed tests)', async () => {
48 | const fixturePath = path.join(__dirname, 'fixtures', 'dotnet-trx.trx')
49 | const outputPath = path.join(__dirname, '__outputs__', 'dotnet-trx-only-failed.md')
50 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
51 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
52 |
53 | const opts: ParseOptions = {
54 | parseErrors: true,
55 | trackedFiles: ['DotnetTests.Unit/Calculator.cs', 'DotnetTests.XUnitTests/CalculatorTests.cs']
56 | //workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/dotnet/'
57 | }
58 |
59 | const parser = new DotnetTrxParser(opts)
60 | const result = await parser.parse(filePath, fileContent)
61 | expect(result).toMatchSnapshot()
62 |
63 | const reportOptions: ReportOptions = {
64 | listSuites: 'all',
65 | listTests: 'failed',
66 | onlySummary: false,
67 | slugPrefix: '',
68 | baseUrl: ''
69 | }
70 | const report = getReport([result], reportOptions)
71 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
72 | fs.writeFileSync(outputPath, report)
73 | })
74 |
75 | it('report from FluentValidation test results matches snapshot', async () => {
76 | const fixturePath = path.join(__dirname, 'fixtures', 'external', 'FluentValidation.Tests.trx')
77 | const outputPath = path.join(__dirname, '__outputs__', 'fluent-validation-test-results.md')
78 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
79 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
80 |
81 | const opts: ParseOptions = {
82 | trackedFiles: [],
83 | parseErrors: true
84 | }
85 |
86 | const parser = new DotnetTrxParser(opts)
87 | const result = await parser.parse(filePath, fileContent)
88 | expect(result).toMatchSnapshot()
89 |
90 | const report = getReport([result])
91 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
92 | fs.writeFileSync(outputPath, report)
93 | })
94 |
95 | it('report from SilentNotes test results matches snapshot', async () => {
96 | const fixturePath = path.join(__dirname, 'fixtures', 'external', 'SilentNotes.trx')
97 | const outputPath = path.join(__dirname, '__outputs__', 'silent-notes-test-results.md')
98 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
99 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
100 |
101 | const opts: ParseOptions = {
102 | trackedFiles: [],
103 | parseErrors: true
104 | }
105 |
106 | const parser = new DotnetTrxParser(opts)
107 | const result = await parser.parse(filePath, fileContent)
108 | expect(result).toMatchSnapshot()
109 |
110 | const report = getReport([result])
111 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
112 | fs.writeFileSync(outputPath, report)
113 | })
114 | })
115 |
--------------------------------------------------------------------------------
/__tests__/fixtures/assets/MaterialIcons-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/__tests__/fixtures/assets/MaterialIcons-Regular.woff
--------------------------------------------------------------------------------
/__tests__/fixtures/assets/MaterialIcons-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/__tests__/fixtures/assets/MaterialIcons-Regular.woff2
--------------------------------------------------------------------------------
/__tests__/fixtures/assets/app.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | object-assign
3 | (c) Sindre Sorhus
4 | @license MIT
5 | */
6 |
7 | /*!
8 | Copyright (c) 2017 Jed Watson.
9 | Licensed under the MIT License (MIT), see
10 | http://jedwatson.github.io/classnames
11 | */
12 |
13 | /*! *****************************************************************************
14 | Copyright (c) Microsoft Corporation. All rights reserved.
15 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
16 | this file except in compliance with the License. You may obtain a copy of the
17 | License at http://www.apache.org/licenses/LICENSE-2.0
18 |
19 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
21 | WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
22 | MERCHANTABLITY OR NON-INFRINGEMENT.
23 |
24 | See the Apache Version 2.0 License for specific language governing permissions
25 | and limitations under the License.
26 | ***************************************************************************** */
27 |
28 | /*! mochawesome-report-generator 6.0.1 | https://github.com/adamgruber/mochawesome-report-generator */
29 |
30 | /** @license React v0.19.1
31 | * scheduler.production.min.js
32 | *
33 | * Copyright (c) Facebook, Inc. and its affiliates.
34 | *
35 | * This source code is licensed under the MIT license found in the
36 | * LICENSE file in the root directory of this source tree.
37 | */
38 |
39 | /** @license React v16.13.1
40 | * react-dom.production.min.js
41 | *
42 | * Copyright (c) Facebook, Inc. and its affiliates.
43 | *
44 | * This source code is licensed under the MIT license found in the
45 | * LICENSE file in the root directory of this source tree.
46 | */
47 |
48 | /** @license React v16.13.1
49 | * react.production.min.js
50 | *
51 | * Copyright (c) Facebook, Inc. and its affiliates.
52 | *
53 | * This source code is licensed under the MIT license found in the
54 | * LICENSE file in the root directory of this source tree.
55 | */
56 |
--------------------------------------------------------------------------------
/__tests__/fixtures/assets/roboto-light-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/__tests__/fixtures/assets/roboto-light-webfont.woff
--------------------------------------------------------------------------------
/__tests__/fixtures/assets/roboto-light-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/__tests__/fixtures/assets/roboto-light-webfont.woff2
--------------------------------------------------------------------------------
/__tests__/fixtures/assets/roboto-medium-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/__tests__/fixtures/assets/roboto-medium-webfont.woff
--------------------------------------------------------------------------------
/__tests__/fixtures/assets/roboto-medium-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/__tests__/fixtures/assets/roboto-medium-webfont.woff2
--------------------------------------------------------------------------------
/__tests__/fixtures/assets/roboto-regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/__tests__/fixtures/assets/roboto-regular-webfont.woff
--------------------------------------------------------------------------------
/__tests__/fixtures/assets/roboto-regular-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/__tests__/fixtures/assets/roboto-regular-webfont.woff2
--------------------------------------------------------------------------------
/__tests__/fixtures/dart-json.json:
--------------------------------------------------------------------------------
1 | {"protocolVersion":"0.1.1","runnerVersion":"1.15.4","pid":7504,"type":"start","time":0}
2 | {"suite":{"id":0,"platform":"vm","path":"test\\main_test.dart"},"type":"suite","time":0}
3 | {"test":{"id":1,"name":"loading test\\main_test.dart","suiteID":0,"groupIDs":[],"metadata":{"skip":false,"skipReason":null},"line":null,"column":null,"url":null},"type":"testStart","time":1}
4 | {"suite":{"id":2,"platform":"vm","path":"test\\second_test.dart"},"type":"suite","time":11}
5 | {"test":{"id":3,"name":"loading test\\second_test.dart","suiteID":2,"groupIDs":[],"metadata":{"skip":false,"skipReason":null},"line":null,"column":null,"url":null},"type":"testStart","time":11}
6 | {"count":2,"type":"allSuites","time":11}
7 | {"testID":1,"messageType":"print","message":"Hello from the test","type":"print","time":3828}
8 | {"testID":3,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":3649}
9 | {"group":{"id":4,"suiteID":2,"parentID":null,"name":null,"metadata":{"skip":false,"skipReason":null},"testCount":2,"line":null,"column":null,"url":null},"type":"group","time":3654}
10 | {"test":{"id":5,"name":"Timeout test","suiteID":2,"groupIDs":[4],"metadata":{"skip":false,"skipReason":null},"line":5,"column":3,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/second_test.dart"},"type":"testStart","time":3655}
11 | {"testID":1,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":3672}
12 | {"group":{"id":6,"suiteID":0,"parentID":null,"name":null,"metadata":{"skip":false,"skipReason":null},"testCount":4,"line":null,"column":null,"url":null},"type":"group","time":3672}
13 | {"group":{"id":7,"suiteID":0,"parentID":6,"name":"Test 1","metadata":{"skip":false,"skipReason":null},"testCount":3,"line":6,"column":3,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/main_test.dart"},"type":"group","time":3672}
14 | {"test":{"id":8,"name":"Test 1 Passing test","suiteID":0,"groupIDs":[6,7],"metadata":{"skip":false,"skipReason":null},"line":7,"column":5,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/main_test.dart"},"type":"testStart","time":3672}
15 | {"testID":5,"error":"TimeoutException after 0:00:00.000001: Test timed out after 0 seconds.","stackTrace":"dart:isolate _RawReceivePortImpl._handleMessage\n","isFailure":false,"type":"error","time":3692}
16 | {"testID":5,"result":"error","skipped":false,"hidden":false,"type":"testDone","time":3692}
17 | {"test":{"id":9,"name":"Skipped test","suiteID":2,"groupIDs":[4],"metadata":{"skip":true,"skipReason":"skipped test"},"line":9,"column":3,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/second_test.dart"},"type":"testStart","time":3693}
18 | {"testID":9,"messageType":"skip","message":"Skip: skipped test","type":"print","time":3706}
19 | {"testID":9,"result":"success","skipped":true,"hidden":false,"type":"testDone","time":3707}
20 | {"testID":8,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":3708}
21 | {"group":{"id":10,"suiteID":0,"parentID":7,"name":"Test 1 Test 1.1","metadata":{"skip":false,"skipReason":null},"testCount":2,"line":11,"column":5,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/main_test.dart"},"type":"group","time":3715}
22 | {"test":{"id":11,"name":"Test 1 Test 1.1 Failing test","suiteID":0,"groupIDs":[6,7,10],"metadata":{"skip":false,"skipReason":null},"line":12,"column":7,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/main_test.dart"},"type":"testStart","time":3716}
23 | {"testID":11,"error":"Expected: <2>\n Actual: <1>\n","stackTrace":"package:test_api expect\ntest\\main_test.dart 13:9 main...\n","isFailure":true,"type":"error","time":3736}
24 | {"testID":11,"result":"failure","skipped":false,"hidden":false,"type":"testDone","time":3736}
25 | {"test":{"id":12,"name":"Test 1 Test 1.1 Exception in target unit","suiteID":0,"groupIDs":[6,7,10],"metadata":{"skip":false,"skipReason":null},"line":16,"column":7,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/main_test.dart"},"type":"testStart","time":3737}
26 | {"testID":12,"error":"Exception: Some error","stackTrace":"package:darttest/main.dart 2:3 throwError\ntest\\main_test.dart 17:9 main...\n","isFailure":false,"type":"error","time":3743}
27 | {"testID":12,"result":"error","skipped":false,"hidden":false,"type":"testDone","time":3743}
28 | {"group":{"id":13,"suiteID":0,"parentID":6,"name":"Test 2","metadata":{"skip":false,"skipReason":null},"testCount":1,"line":22,"column":3,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/main_test.dart"},"type":"group","time":3744}
29 | {"test":{"id":14,"name":"Test 2 Exception in test","suiteID":0,"groupIDs":[6,13],"metadata":{"skip":false,"skipReason":null},"line":23,"column":5,"url":"file:///C:/Users/Michal/Workspace/dorny/test-check/reports/dart/test/main_test.dart"},"type":"testStart","time":3744}
30 | {"testID":14,"error":"Exception: Some error","stackTrace":"test\\main_test.dart 24:7 main..\n","isFailure":false,"type":"error","time":3756}
31 | {"testID":14,"result":"error","skipped":false,"hidden":false,"type":"testDone","time":3756}
32 | {"success":false,"type":"done","time":3760}
33 |
--------------------------------------------------------------------------------
/__tests__/fixtures/empty/dart-json.json:
--------------------------------------------------------------------------------
1 | {"protocolVersion":"0.1.1","runnerVersion":"1.25.3","pid":7103,"type":"start","time":0}
2 | {"suite":{"id":0,"platform":"vm","path":"test/second_test.dart"},"type":"suite","time":0}
3 | {"test":{"id":1,"name":"loading test/second_test.dart","suiteID":0,"groupIDs":[],"metadata":{"skip":false,"skipReason":null},"line":null,"column":null,"url":null},"type":"testStart","time":0}
4 | {"suite":{"id":2,"platform":"vm","path":"test/main_test.dart"},"type":"suite","time":4}
5 | {"test":{"id":3,"name":"loading test/main_test.dart","suiteID":2,"groupIDs":[],"metadata":{"skip":false,"skipReason":null},"line":null,"column":null,"url":null},"type":"testStart","time":4}
6 | {"count":2,"time":5,"type":"allSuites"}
7 | {"testID":1,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":294}
8 | {"testID":3,"messageType":"print","message":"Hello from the test","type":"print","time":297}
9 | {"testID":3,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":321}
10 | {"group":{"id":4,"suiteID":2,"parentID":null,"name":"","metadata":{"skip":false,"skipReason":null},"testCount":0,"line":null,"column":null,"url":null},"type":"group","time":322}
11 | {"test":{"id":5,"name":"(setUpAll)","suiteID":2,"groupIDs":[4],"metadata":{"skip":false,"skipReason":null},"line":6,"column":3,"url":"file:///Users/user/test-reporter/reports/dart/test/main_test.dart"},"type":"testStart","time":322}
12 | {"testID":5,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":330}
13 | {"test":{"id":6,"name":"(tearDownAll)","suiteID":2,"groupIDs":[4],"metadata":{"skip":false,"skipReason":null},"line":7,"column":3,"url":"file:///Users/user/test-reporter/reports/dart/test/main_test.dart"},"type":"testStart","time":330}
14 | {"testID":6,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":331}
15 | {"success":true,"type":"done","time":333}
16 |
--------------------------------------------------------------------------------
/__tests__/fixtures/empty/dotnet-trx.trx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | No test is available in (...). Make sure that test discoverer & executors are registered and platform & framework version settings are appropriate and try again.
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/__tests__/fixtures/empty/java-junit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/__tests__/fixtures/empty/jest-junit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/__tests__/fixtures/empty/mocha-json.json:
--------------------------------------------------------------------------------
1 | {
2 | "stats": {
3 | "suites": 0,
4 | "tests": 0,
5 | "passes": 0,
6 | "pending": 0,
7 | "failures": 0,
8 | "start": "2021-03-08T20:01:44.391Z",
9 | "end": "2021-03-08T20:01:44.391Z",
10 | "duration": 0
11 | },
12 | "tests": [],
13 | "pending": [],
14 | "failures": [],
15 | "passes": []
16 | }
--------------------------------------------------------------------------------
/__tests__/fixtures/empty/mochawesome-json.json:
--------------------------------------------------------------------------------
1 | {
2 | "stats": {
3 | "suites": 0,
4 | "tests": 0,
5 | "passes": 0,
6 | "pending": 0,
7 | "failures": 0,
8 | "start": "2022-02-17T18:05:44.524Z",
9 | "end": "2022-02-17T18:05:54.535Z",
10 | "duration": 0,
11 | "testsRegistered": 0,
12 | "passPercent": 0,
13 | "pendingPercent": 0,
14 | "other": 0,
15 | "hasOther": false,
16 | "skipped": 0,
17 | "hasSkipped": true
18 | },
19 | "results": [],
20 | "meta": {}
21 | }
22 |
--------------------------------------------------------------------------------
/__tests__/fixtures/external/flutter/files.txt:
--------------------------------------------------------------------------------
1 | .github/ISSUE_TEMPLATE/bug_report.md
2 | .github/ISSUE_TEMPLATE/config.yml
3 | .github/ISSUE_TEMPLATE/feature_request.md
4 | .github/workflows/build.yml
5 | .gitignore
6 | CHANGELOG.md
7 | LICENSE
8 | README.md
9 | all_lint_rules.yaml
10 | analysis_options.yaml
11 | dart_test.yaml
12 | example/.gitignore
13 | example/.metadata
14 | example/lib/main.dart
15 | example/pubspec.yaml
16 | example/test/widget_test.dart
17 | example/test_driver/app.dart
18 | example/test_driver/app_test.dart
19 | lib/provider.dart
20 | lib/single_child_widget.dart
21 | lib/src/async_provider.dart
22 | lib/src/change_notifier_provider.dart
23 | lib/src/consumer.dart
24 | lib/src/deferred_inherited_provider.dart
25 | lib/src/inherited_provider.dart
26 | lib/src/listenable_provider.dart
27 | lib/src/provider.dart
28 | lib/src/proxy_provider.dart
29 | lib/src/reassemble_handler.dart
30 | lib/src/selector.dart
31 | lib/src/value_listenable_provider.dart
32 | pubspec.yaml
33 | resources/devtools_providers.jpg
34 | resources/expanded_devtools.jpg
35 | resources/flutter_favorite.png
36 | resources/translations/es_MX/README.md
37 | resources/translations/pt_br/README.md
38 | resources/translations/zh-CN/README.md
39 | scripts/flutter_test.sh
40 | test/builder_test.dart
41 | test/change_notifier_provider_test.dart
42 | test/common.dart
43 | test/consumer_test.dart
44 | test/context_test.dart
45 | test/dart_test.yaml
46 | test/future_provider_test.dart
47 | test/inherited_provider_test.dart
48 | test/listenable_provider_test.dart
49 | test/listenable_proxy_provider_test.dart
50 | test/multi_provider_test.dart
51 | test/provider_test.dart
52 | test/proxy_provider_test.dart
53 | test/reassemble_test.dart
54 | test/selector_test.dart
55 | test/stateful_provider_test.dart
56 | test/stream_provider_test.dart
57 | test/value_listenable_provider_test.dart
58 |
--------------------------------------------------------------------------------
/__tests__/fixtures/external/java/TEST-org.apache.pulsar.AddMissingPatchVersionTest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/__tests__/fixtures/jest-junit-eslint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/__tests__/fixtures/jest-junit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Error: expect(received).toBeTruthy()
8 |
9 | Received: false
10 | at Object.<anonymous> (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\__tests__\main.test.js:10:21)
11 | at Object.asyncJestTest (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:106:37)
12 | at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:45:12
13 | at new Promise (<anonymous>)
14 | at mapper (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:28:19)
15 | at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:75:41
16 | at processTicksAndRejections (internal/process/task_queues.js:97:5)
17 |
18 |
19 | Error: Some error
20 | at Object.throwError (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\lib\main.js:2:9)
21 | at Object.<anonymous> (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\__tests__\main.test.js:14:11)
22 | at Object.asyncJestTest (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:106:37)
23 | at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:45:12
24 | at new Promise (<anonymous>)
25 | at mapper (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:28:19)
26 | at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:75:41
27 | at processTicksAndRejections (internal/process/task_queues.js:97:5)
28 |
29 |
30 | Error: Some error
31 | at Object.<anonymous> (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\__tests__\main.test.js:21:11)
32 | at Object.asyncJestTest (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:106:37)
33 | at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:45:12
34 | at new Promise (<anonymous>)
35 | at mapper (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:28:19)
36 | at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:75:41
37 | at processTicksAndRejections (internal/process/task_queues.js:97:5)
38 |
39 |
40 |
41 |
42 | : 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:
43 | at new Spec (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmine\Spec.js:116:22)
44 | at new Spec (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\setup_jest_globals.js:78:9)
45 | at specFactory (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmine\Env.js:523:24)
46 | at Env.it (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmine\Env.js:592:24)
47 | at Env.it (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:134:23)
48 | at it (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmine\jasmineLight.js:100:21)
49 | at Object.<anonymous> (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\__tests__\second.test.js:1:34)
50 | at Runtime._execModule (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-runtime\build\index.js:1245:24)
51 | at Runtime._loadModule (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-runtime\build\index.js:844:12)
52 | at Runtime.requireModule (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-runtime\build\index.js:694:10)
53 | at jasmine2 (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\index.js:230:13)
54 | at runTestInternal (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-runner\build\runTest.js:380:22)
55 | at runTest (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-runner\build\runTest.js:472:34)
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/__tests__/fixtures/mocha-json.json:
--------------------------------------------------------------------------------
1 | {
2 | "stats": {
3 | "suites": 3,
4 | "tests": 6,
5 | "passes": 1,
6 | "pending": 1,
7 | "failures": 4,
8 | "start": "2022-02-18T14:30:07.077Z",
9 | "end": "2022-02-18T14:30:07.088Z",
10 | "duration": 11
11 | },
12 | "tests": [
13 | {
14 | "title": "Timeout test",
15 | "fullTitle": "Timeout test",
16 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js",
17 | "duration": 2,
18 | "currentRetry": 0,
19 | "err": {
20 | "stack": "Error: Timeout of 1ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js)\n at listOnTimeout (node:internal/timers:557:17)\n at processTimers (node:internal/timers:500:7)",
21 | "message": "Timeout of 1ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js)",
22 | "code": "ERR_MOCHA_TIMEOUT",
23 | "timeout": 1,
24 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js"
25 | }
26 | },
27 | {
28 | "title": "Skipped test",
29 | "fullTitle": "Skipped test",
30 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js",
31 | "currentRetry": 0,
32 | "err": {}
33 | },
34 | {
35 | "title": "Passing test",
36 | "fullTitle": "Test 1 Passing test",
37 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/main.test.js",
38 | "duration": 0,
39 | "currentRetry": 0,
40 | "speed": "fast",
41 | "err": {}
42 | },
43 | {
44 | "title": "Failing test",
45 | "fullTitle": "Test 1 Test 1.1 Failing test",
46 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/main.test.js",
47 | "duration": 3,
48 | "currentRetry": 0,
49 | "err": {
50 | "stack": "AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:\n\nfalse !== true\n\n at Context. (test/main.test.js:11:14)\n at processImmediate (node:internal/timers:464:21)",
51 | "message": "Expected values to be strictly equal:\n\nfalse !== true\n",
52 | "generatedMessage": true,
53 | "name": "AssertionError",
54 | "code": "ERR_ASSERTION",
55 | "actual": "false",
56 | "expected": "true",
57 | "operator": "strictEqual"
58 | }
59 | },
60 | {
61 | "title": "Exception in target unit",
62 | "fullTitle": "Test 1 Test 1.1 Exception in target unit",
63 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/main.test.js",
64 | "duration": 0,
65 | "currentRetry": 0,
66 | "err": {
67 | "stack": "Error: Some error\n at Object.throwError (lib/main.js:2:9)\n at Context. (test/main.test.js:15:11)\n at processImmediate (node:internal/timers:464:21)",
68 | "message": "Some error"
69 | }
70 | },
71 | {
72 | "title": "Exception in test",
73 | "fullTitle": "Test 2 Exception in test",
74 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/main.test.js",
75 | "duration": 0,
76 | "currentRetry": 0,
77 | "err": {
78 | "stack": "Error: Some error\n at Context. (test/main.test.js:22:11)\n at processImmediate (node:internal/timers:464:21)",
79 | "message": "Some error"
80 | }
81 | }
82 | ],
83 | "pending": [
84 | {
85 | "title": "Skipped test",
86 | "fullTitle": "Skipped test",
87 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js",
88 | "currentRetry": 0,
89 | "err": {}
90 | }
91 | ],
92 | "failures": [
93 | {
94 | "title": "Timeout test",
95 | "fullTitle": "Timeout test",
96 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js",
97 | "duration": 2,
98 | "currentRetry": 0,
99 | "err": {
100 | "stack": "Error: Timeout of 1ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js)\n at listOnTimeout (node:internal/timers:557:17)\n at processTimers (node:internal/timers:500:7)",
101 | "message": "Timeout of 1ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js)",
102 | "code": "ERR_MOCHA_TIMEOUT",
103 | "timeout": 1,
104 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/second.test.js"
105 | }
106 | },
107 | {
108 | "title": "Failing test",
109 | "fullTitle": "Test 1 Test 1.1 Failing test",
110 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/main.test.js",
111 | "duration": 3,
112 | "currentRetry": 0,
113 | "err": {
114 | "stack": "AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:\n\nfalse !== true\n\n at Context. (test/main.test.js:11:14)\n at processImmediate (node:internal/timers:464:21)",
115 | "message": "Expected values to be strictly equal:\n\nfalse !== true\n",
116 | "generatedMessage": true,
117 | "name": "AssertionError",
118 | "code": "ERR_ASSERTION",
119 | "actual": "false",
120 | "expected": "true",
121 | "operator": "strictEqual"
122 | }
123 | },
124 | {
125 | "title": "Exception in target unit",
126 | "fullTitle": "Test 1 Test 1.1 Exception in target unit",
127 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/main.test.js",
128 | "duration": 0,
129 | "currentRetry": 0,
130 | "err": {
131 | "stack": "Error: Some error\n at Object.throwError (lib/main.js:2:9)\n at Context. (test/main.test.js:15:11)\n at processImmediate (node:internal/timers:464:21)",
132 | "message": "Some error"
133 | }
134 | },
135 | {
136 | "title": "Exception in test",
137 | "fullTitle": "Test 2 Exception in test",
138 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/main.test.js",
139 | "duration": 0,
140 | "currentRetry": 0,
141 | "err": {
142 | "stack": "Error: Some error\n at Context. (test/main.test.js:22:11)\n at processImmediate (node:internal/timers:464:21)",
143 | "message": "Some error"
144 | }
145 | }
146 | ],
147 | "passes": [
148 | {
149 | "title": "Passing test",
150 | "fullTitle": "Test 1 Passing test",
151 | "file": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mocha/test/main.test.js",
152 | "duration": 0,
153 | "currentRetry": 0,
154 | "speed": "fast",
155 | "err": {}
156 | }
157 | ]
158 | }
159 |
--------------------------------------------------------------------------------
/__tests__/fixtures/mochawesome-json.html:
--------------------------------------------------------------------------------
1 |
2 | Mochawesome Report
--------------------------------------------------------------------------------
/__tests__/fixtures/mochawesome-json.json:
--------------------------------------------------------------------------------
1 | {
2 | "stats": {
3 | "suites": 3,
4 | "tests": 6,
5 | "passes": 1,
6 | "pending": 1,
7 | "failures": 4,
8 | "start": "2022-02-18T14:32:42.133Z",
9 | "end": "2022-02-18T14:32:42.147Z",
10 | "duration": 14,
11 | "testsRegistered": 6,
12 | "passPercent": 20,
13 | "pendingPercent": 16.666666666666664,
14 | "other": 0,
15 | "hasOther": false,
16 | "skipped": 0,
17 | "hasSkipped": false
18 | },
19 | "results": [
20 | {
21 | "uuid": "e74145e8-4f10-432c-8fe1-79fab8a0fe12",
22 | "title": "In-line test with no file",
23 | "fullFile": "nofile",
24 | "file": "nofile",
25 | "beforeHooks": [],
26 | "afterHooks": [],
27 | "tests": [
28 | {
29 | "title": "Timeout test",
30 | "fullTitle": "Timeout test",
31 | "timedOut": true,
32 | "duration": 3,
33 | "state": "failed",
34 | "speed": null,
35 | "pass": false,
36 | "fail": true,
37 | "pending": false,
38 | "context": null,
39 | "code": "this.timeout(1);\nsetTimeout(done, 1000);",
40 | "err": {
41 | "message": "Error: Timeout of 1ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mochawesome/test/second.test.js)",
42 | "estack": "Error: Timeout of 1ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mochawesome/test/second.test.js)\n at listOnTimeout (node:internal/timers:557:17)\n at processTimers (node:internal/timers:500:7)",
43 | "diff": null
44 | },
45 | "uuid": "3ad24933-108e-426f-9cc3-0d71ab9f35c2",
46 | "parentUUID": "e74145e8-4f10-432c-8fe1-79fab8a0fe12",
47 | "isHook": false,
48 | "skipped": false
49 | },
50 | {
51 | "title": "Skipped test",
52 | "fullTitle": "Skipped test",
53 | "timedOut": false,
54 | "duration": 0,
55 | "state": "pending",
56 | "speed": null,
57 | "pass": false,
58 | "fail": false,
59 | "pending": true,
60 | "context": null,
61 | "code": "",
62 | "err": {},
63 | "uuid": "f1b2a440-4080-44da-9346-bb5df85581e0",
64 | "parentUUID": "e74145e8-4f10-432c-8fe1-79fab8a0fe12",
65 | "isHook": false,
66 | "skipped": false
67 | }
68 | ],
69 | "suites": [
70 | {
71 | "uuid": "36bafd85-ff6a-4a77-920a-090deb0631c3",
72 | "title": "Test 1",
73 | "fullFile": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mochawesome/test/main.test.js",
74 | "file": "/test/main.test.js",
75 | "beforeHooks": [],
76 | "afterHooks": [],
77 | "tests": [
78 | {
79 | "title": "Passing test",
80 | "fullTitle": "Test 1 Passing test",
81 | "timedOut": false,
82 | "duration": 0,
83 | "state": "passed",
84 | "speed": "fast",
85 | "pass": true,
86 | "fail": false,
87 | "pending": false,
88 | "context": null,
89 | "code": "assert.equal(true, true)",
90 | "err": {},
91 | "uuid": "3ebb7382-adde-438c-9d31-694714656ac3",
92 | "parentUUID": "36bafd85-ff6a-4a77-920a-090deb0631c3",
93 | "isHook": false,
94 | "skipped": false
95 | }
96 | ],
97 | "suites": [
98 | {
99 | "uuid": "bd89b790-970b-41f8-965d-e67eae577576",
100 | "title": "Test 1.1",
101 | "fullFile": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mochawesome/test/main.test.js",
102 | "file": "/test/main.test.js",
103 | "beforeHooks": [],
104 | "afterHooks": [],
105 | "tests": [
106 | {
107 | "title": "Failing test",
108 | "fullTitle": "Test 1 Test 1.1 Failing test",
109 | "timedOut": false,
110 | "duration": 2,
111 | "state": "failed",
112 | "speed": null,
113 | "pass": false,
114 | "fail": true,
115 | "pending": false,
116 | "context": null,
117 | "code": "assert.equal(false, true)",
118 | "err": {
119 | "message": "AssertionError: Expected values to be strictly equal:\n\nfalse !== true\n",
120 | "estack": "AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:\n\nfalse !== true\n\n at Context. (test/main.test.js:11:14)\n at processImmediate (node:internal/timers:464:21)",
121 | "diff": "- false\n+ true\n"
122 | },
123 | "uuid": "e154e320-a501-49bd-b0dc-fcdf8fb6460a",
124 | "parentUUID": "bd89b790-970b-41f8-965d-e67eae577576",
125 | "isHook": false,
126 | "skipped": false
127 | },
128 | {
129 | "title": "Exception in target unit",
130 | "fullTitle": "Test 1 Test 1.1 Exception in target unit",
131 | "timedOut": false,
132 | "duration": 0,
133 | "state": "failed",
134 | "speed": null,
135 | "pass": false,
136 | "fail": true,
137 | "pending": false,
138 | "context": null,
139 | "code": "lib.throwError();",
140 | "err": {
141 | "message": "Error: Some error",
142 | "estack": "Error: Some error\n at Object.throwError (lib/main.js:2:9)\n at Context. (test/main.test.js:15:11)\n at processImmediate (node:internal/timers:464:21)",
143 | "diff": null
144 | },
145 | "uuid": "bee0d977-da09-43a9-9683-6e2676952f60",
146 | "parentUUID": "bd89b790-970b-41f8-965d-e67eae577576",
147 | "isHook": false,
148 | "skipped": false
149 | }
150 | ],
151 | "suites": [],
152 | "passes": [],
153 | "failures": ["e154e320-a501-49bd-b0dc-fcdf8fb6460a", "bee0d977-da09-43a9-9683-6e2676952f60"],
154 | "pending": [],
155 | "skipped": [],
156 | "duration": 2,
157 | "root": false,
158 | "rootEmpty": false,
159 | "_timeout": 2000
160 | }
161 | ],
162 | "passes": ["3ebb7382-adde-438c-9d31-694714656ac3"],
163 | "failures": [],
164 | "pending": [],
165 | "skipped": [],
166 | "duration": 0,
167 | "root": false,
168 | "rootEmpty": false,
169 | "_timeout": 2000
170 | },
171 | {
172 | "uuid": "930f5258-1a08-44d1-8343-5782cb1e0f39",
173 | "title": "Test 2",
174 | "fullFile": "/Users/work/Source/Repos/thirdparty/phoenix-test-reporter/reports/mochawesome/test/main.test.js",
175 | "file": "/test/main.test.js",
176 | "beforeHooks": [],
177 | "afterHooks": [],
178 | "tests": [
179 | {
180 | "title": "Exception in test",
181 | "fullTitle": "Test 2 Exception in test",
182 | "timedOut": false,
183 | "duration": 0,
184 | "state": "failed",
185 | "speed": null,
186 | "pass": false,
187 | "fail": true,
188 | "pending": false,
189 | "context": null,
190 | "code": "throw new Error('Some error');",
191 | "err": {
192 | "message": "Error: Some error",
193 | "estack": "Error: Some error\n at Context. (test/main.test.js:22:11)\n at processImmediate (node:internal/timers:464:21)",
194 | "diff": null
195 | },
196 | "uuid": "add99db1-a040-4ab8-86cf-91051665fb47",
197 | "parentUUID": "930f5258-1a08-44d1-8343-5782cb1e0f39",
198 | "isHook": false,
199 | "skipped": false
200 | }
201 | ],
202 | "suites": [],
203 | "passes": [],
204 | "failures": ["add99db1-a040-4ab8-86cf-91051665fb47"],
205 | "pending": [],
206 | "skipped": [],
207 | "duration": 0,
208 | "root": false,
209 | "rootEmpty": false,
210 | "_timeout": 2000
211 | }
212 | ],
213 | "passes": [],
214 | "failures": ["3ad24933-108e-426f-9cc3-0d71ab9f35c2"],
215 | "pending": ["f1b2a440-4080-44da-9346-bb5df85581e0"],
216 | "skipped": [],
217 | "duration": 3,
218 | "root": true,
219 | "rootEmpty": false,
220 | "_timeout": 2000
221 | }
222 | ],
223 | "meta": {
224 | "mocha": {
225 | "version": "9.2.0"
226 | },
227 | "mochawesome": {
228 | "options": {
229 | "quiet": false,
230 | "reportFilename": "mochawesome-json.json",
231 | "saveHtml": true,
232 | "saveJson": true,
233 | "consoleReporter": "spec",
234 | "useInlineDiffs": false,
235 | "code": true
236 | },
237 | "version": "7.0.1"
238 | },
239 | "marge": {
240 | "options": {
241 | "reportDir": "../../__tests__/fixtures/",
242 | "reportFilename": "mochawesome-json.json"
243 | },
244 | "version": "6.0.1"
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/__tests__/fixtures/test-errors/jest/files.txt:
--------------------------------------------------------------------------------
1 | libs/bar.spec.ts
2 | libs/foo.spec.ts
3 | tsconfig.json
4 |
--------------------------------------------------------------------------------
/__tests__/fixtures/test-errors/jest/jest-test-results.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ● Test suite failed to run
6 |
7 | tsconfig.json:13:3 - error TS6258: 'typeRoots' should be set inside the 'compilerOptions' object of the config json file
8 |
9 | 13 "typeRoots": ["./src/lib/types", "./node_modules/@types"],
10 | ~~~~~~~~~~~
11 |
12 |
13 |
14 |
15 |
16 | ● Test suite failed to run
17 |
18 | tsconfig.json:13:3 - error TS6258: 'typeRoots' should be set inside the 'compilerOptions' object of the config json file
19 |
20 | 13 "typeRoots": ["./src/lib/types", "./node_modules/@types"],
21 | ~~~~~~~~~~~
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/__tests__/fixtures/test-errors/playwright/files.txt:
--------------------------------------------------------------------------------
1 | libs/foo.spec.ts
2 | tsconfig.json
3 |
--------------------------------------------------------------------------------
/__tests__/fixtures/test-errors/playwright/test-results.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Email aka getByTestId('first')
9 | 2) aka getByTestId('second')
10 | ]]>
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/__tests__/java-junit.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import * as path from 'path'
3 |
4 | import {JavaJunitParser} from '../src/parsers/java-junit/java-junit-parser'
5 | import {ParseOptions} from '../src/test-parser'
6 | import {getReport} from '../src/report/get-report'
7 | import {normalizeFilePath} from '../src/utils/path-utils'
8 |
9 | describe('java-junit tests', () => {
10 | it('produces empty test run result when there are no test cases', async () => {
11 | const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'java-junit.xml')
12 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
13 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
14 |
15 | const opts: ParseOptions = {
16 | parseErrors: true,
17 | trackedFiles: []
18 | }
19 |
20 | const parser = new JavaJunitParser(opts)
21 | const result = await parser.parse(filePath, fileContent)
22 | expect(result.tests).toBe(0)
23 | expect(result.result).toBe('success')
24 | })
25 |
26 | it('report from apache/pulsar single suite test results matches snapshot', async () => {
27 | const fixturePath = path.join(
28 | __dirname,
29 | 'fixtures',
30 | 'external',
31 | 'java',
32 | 'TEST-org.apache.pulsar.AddMissingPatchVersionTest.xml'
33 | )
34 | const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'java', 'files.txt')
35 | const outputPath = path.join(__dirname, '__outputs__', 'pulsar-test-results-no-merge.md')
36 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
37 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
38 |
39 | const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
40 | const opts: ParseOptions = {
41 | parseErrors: true,
42 | trackedFiles
43 | }
44 |
45 | const parser = new JavaJunitParser(opts)
46 | const result = await parser.parse(filePath, fileContent)
47 | expect(result).toMatchSnapshot()
48 |
49 | const report = getReport([result])
50 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
51 | fs.writeFileSync(outputPath, report)
52 | })
53 |
54 | it('report from apache/pulsar test results matches snapshot', async () => {
55 | const fixturePath = path.join(__dirname, 'fixtures', 'external', 'java', 'pulsar-test-report.xml')
56 | const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'java', 'files.txt')
57 | const outputPath = path.join(__dirname, '__outputs__', 'pulsar-test-results.md')
58 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
59 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
60 |
61 | const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
62 | const opts: ParseOptions = {
63 | parseErrors: true,
64 | trackedFiles
65 | }
66 |
67 | const parser = new JavaJunitParser(opts)
68 | const result = await parser.parse(filePath, fileContent)
69 | expect(result).toMatchSnapshot()
70 |
71 | const report = getReport([result])
72 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
73 | fs.writeFileSync(outputPath, report)
74 | })
75 | })
76 |
--------------------------------------------------------------------------------
/__tests__/jest-junit.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import * as path from 'path'
3 |
4 | import {JestJunitParser} from '../src/parsers/jest-junit/jest-junit-parser'
5 | import {ParseOptions} from '../src/test-parser'
6 | import {getReport} from '../src/report/get-report'
7 | import {normalizeFilePath} from '../src/utils/path-utils'
8 |
9 | describe('jest-junit tests', () => {
10 | it('produces empty test run result when there are no test cases', async () => {
11 | const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'jest-junit.xml')
12 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
13 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
14 |
15 | const opts: ParseOptions = {
16 | parseErrors: true,
17 | trackedFiles: []
18 | }
19 |
20 | const parser = new JestJunitParser(opts)
21 | const result = await parser.parse(filePath, fileContent)
22 | expect(result.tests).toBe(0)
23 | expect(result.result).toBe('success')
24 | })
25 |
26 | it('report from ./reports/jest test results matches snapshot', async () => {
27 | const fixturePath = path.join(__dirname, 'fixtures', 'jest-junit.xml')
28 | const outputPath = path.join(__dirname, '__outputs__', 'jest-junit.md')
29 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
30 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
31 |
32 | const opts: ParseOptions = {
33 | parseErrors: true,
34 | trackedFiles: ['__tests__/main.test.js', '__tests__/second.test.js', 'lib/main.js']
35 | //workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/jest/'
36 | }
37 |
38 | const parser = new JestJunitParser(opts)
39 | const result = await parser.parse(filePath, fileContent)
40 | expect(result).toMatchSnapshot()
41 |
42 | const report = getReport([result])
43 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
44 | fs.writeFileSync(outputPath, report)
45 | })
46 |
47 | it('report from facebook/jest test results matches snapshot', async () => {
48 | const fixturePath = path.join(__dirname, 'fixtures', 'external', 'jest', 'jest-test-results.xml')
49 | const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'jest', 'files.txt')
50 | const outputPath = path.join(__dirname, '__outputs__', 'jest-test-results.md')
51 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
52 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
53 |
54 | const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
55 | const opts: ParseOptions = {
56 | parseErrors: true,
57 | trackedFiles
58 | //workDir: '/home/dorny/dorny/jest/'
59 | }
60 |
61 | const parser = new JestJunitParser(opts)
62 | const result = await parser.parse(filePath, fileContent)
63 | expect(result).toMatchSnapshot()
64 |
65 | const report = getReport([result])
66 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
67 | fs.writeFileSync(outputPath, report)
68 | })
69 |
70 | it('parsing ESLint report without timing information works', async () => {
71 | const fixturePath = path.join(__dirname, 'fixtures', 'jest-junit-eslint.xml')
72 | const outputPath = path.join(__dirname, '__outputs__', 'jest-junit-eslint.md')
73 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
74 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
75 |
76 | const opts: ParseOptions = {
77 | parseErrors: true,
78 | trackedFiles: ['test.js']
79 | }
80 |
81 | const parser = new JestJunitParser(opts)
82 | const result = await parser.parse(filePath, fileContent)
83 | expect(result).toMatchSnapshot()
84 |
85 | const report = getReport([result])
86 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
87 | fs.writeFileSync(outputPath, report)
88 | })
89 |
90 | it('jest testsuite errors example test results matches snapshot', async () => {
91 | const fixturePath = path.join(__dirname, 'fixtures', 'test-errors', 'jest', 'jest-test-results.xml')
92 | const trackedFilesPath = path.join(__dirname, 'fixtures', 'test-errors', 'jest', 'files.txt')
93 | const outputPath = path.join(__dirname, '__outputs__', 'jest-test-errors-results.md')
94 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
95 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
96 |
97 | const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
98 | const opts: ParseOptions = {
99 | parseErrors: true,
100 | trackedFiles
101 | //workDir: '/home/dorny/dorny/jest/'
102 | }
103 |
104 | const parser = new JestJunitParser(opts)
105 | const result = await parser.parse(filePath, fileContent)
106 | expect(result).toMatchSnapshot()
107 |
108 | const report = getReport([result])
109 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
110 | fs.writeFileSync(outputPath, report)
111 | })
112 |
113 | it('playwright testsuite errors example test results matches snapshot', async () => {
114 | const fixturePath = path.join(__dirname, 'fixtures', 'test-errors', 'playwright', 'test-results.xml')
115 | const trackedFilesPath = path.join(__dirname, 'fixtures', 'test-errors', 'playwright', 'files.txt')
116 | const outputPath = path.join(__dirname, '__outputs__', 'playwright-test-errors-results.md')
117 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
118 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
119 |
120 | const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
121 | const opts: ParseOptions = {
122 | parseErrors: true,
123 | trackedFiles
124 | }
125 |
126 | const parser = new JestJunitParser(opts)
127 | const result = await parser.parse(filePath, fileContent)
128 | expect(result).toMatchSnapshot()
129 |
130 | const report = getReport([result])
131 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
132 | fs.writeFileSync(outputPath, report)
133 | })
134 | })
135 |
--------------------------------------------------------------------------------
/__tests__/mocha-json.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import * as path from 'path'
3 |
4 | import {MochaJsonParser} from '../src/parsers/mocha-json/mocha-json-parser'
5 | import {ParseOptions} from '../src/test-parser'
6 | import {getReport} from '../src/report/get-report'
7 | import {normalizeFilePath} from '../src/utils/path-utils'
8 |
9 | describe('mocha-json tests', () => {
10 | it('produces empty test run result when there are no test cases', async () => {
11 | const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'mocha-json.json')
12 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
13 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
14 |
15 | const opts: ParseOptions = {
16 | parseErrors: true,
17 | trackedFiles: []
18 | }
19 |
20 | const parser = new MochaJsonParser(opts)
21 | const result = await parser.parse(filePath, fileContent)
22 | expect(result.tests).toBe(0)
23 | expect(result.result).toBe('success')
24 | })
25 |
26 | it('report from ./reports/mocha-json test results matches snapshot', async () => {
27 | const fixturePath = path.join(__dirname, 'fixtures', 'mocha-json.json')
28 | const outputPath = path.join(__dirname, '__outputs__', 'mocha-json.md')
29 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
30 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
31 |
32 | const opts: ParseOptions = {
33 | parseErrors: true,
34 | trackedFiles: ['test/main.test.js', 'test/second.test.js', 'lib/main.js']
35 | }
36 |
37 | const parser = new MochaJsonParser(opts)
38 | const result = await parser.parse(filePath, fileContent)
39 | expect(result).toMatchSnapshot()
40 |
41 | const report = getReport([result])
42 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
43 | fs.writeFileSync(outputPath, report)
44 | })
45 |
46 | it('report from mochajs/mocha test results matches snapshot', async () => {
47 | const fixturePath = path.join(__dirname, 'fixtures', 'external', 'mocha', 'mocha-test-results.json')
48 | const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'mocha', 'files.txt')
49 | const outputPath = path.join(__dirname, '__outputs__', 'mocha-test-results.md')
50 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
51 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
52 |
53 | const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
54 | const opts: ParseOptions = {
55 | parseErrors: true,
56 | trackedFiles
57 | }
58 |
59 | const parser = new MochaJsonParser(opts)
60 | const result = await parser.parse(filePath, fileContent)
61 | expect(result).toMatchSnapshot()
62 |
63 | const report = getReport([result])
64 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
65 | fs.writeFileSync(outputPath, report)
66 | })
67 | })
68 |
--------------------------------------------------------------------------------
/__tests__/mochawesome-json.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import * as path from 'path'
3 |
4 | import {MochawesomeJsonParser} from '../src/parsers/mochawesome-json/mochawesome-json-parser'
5 | import {ParseOptions} from '../src/test-parser'
6 | import {getReport} from '../src/report/get-report'
7 | import {normalizeFilePath} from '../src/utils/path-utils'
8 |
9 | describe('mochawesome-json tests', () => {
10 | it('produces empty test run result when there are no test cases', async () => {
11 | const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'mochawesome-json.json')
12 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
13 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
14 |
15 | const opts: ParseOptions = {
16 | parseErrors: true,
17 | trackedFiles: []
18 | }
19 |
20 | const parser = new MochawesomeJsonParser(opts)
21 | const result = await parser.parse(filePath, fileContent)
22 | expect(result.tests).toBe(0)
23 | expect(result.result).toBe('success')
24 | })
25 |
26 | it('report from ./reports/mochawesome-json test results matches snapshot', async () => {
27 | const fixturePath = path.join(__dirname, 'fixtures', 'mochawesome-json.json')
28 | const outputPath = path.join(__dirname, '__outputs__', 'mochawesome-json.md')
29 | const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
30 | const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
31 |
32 | const opts: ParseOptions = {
33 | parseErrors: true,
34 | trackedFiles: ['test/main.test.js', 'test/second.test.js', 'lib/main.js']
35 | }
36 |
37 | const parser = new MochawesomeJsonParser(opts)
38 | const result = await parser.parse(filePath, fileContent)
39 | expect(result).toMatchSnapshot()
40 |
41 | const report = getReport([result])
42 | fs.mkdirSync(path.dirname(outputPath), {recursive: true})
43 | fs.writeFileSync(outputPath, report)
44 | })
45 |
46 | // TODO - External
47 | // it('report from mochawesomejs/mochawesome test results matches snapshot', async () => {
48 | // const fixturePath = path.join(__dirname, 'fixtures', 'external', 'mochawesome', 'mochawesome-test-results.json')
49 | // const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'mochawesome', 'files.txt')
50 | // const outputPath = path.join(__dirname, '__outputs__', 'mochawesome-test-results.md')
51 | // const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
52 | // const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
53 |
54 | // const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
55 | // const opts: ParseOptions = {
56 | // parseErrors: true,
57 | // trackedFiles
58 | // }
59 |
60 | // const parser = new MochawesomeJsonParser(opts)
61 | // const result = await parser.parse(filePath, fileContent)
62 | // expect(result).toMatchSnapshot()
63 |
64 | // const report = getReport([result])
65 | // fs.mkdirSync(path.dirname(outputPath), {recursive: true})
66 | // fs.writeFileSync(outputPath, report)
67 | // })
68 | })
69 |
--------------------------------------------------------------------------------
/__tests__/utils/parse-utils.test.ts:
--------------------------------------------------------------------------------
1 | import {parseNetDuration} from '../../src/utils/parse-utils'
2 |
3 | describe('parseNetDuration', () => {
4 | it('returns 0 for 00:00:00', () => {
5 | const ms = parseNetDuration('00:00:00')
6 | expect(ms).toBe(0)
7 | })
8 |
9 | it('returns 0 for 00:00:00.0000000', () => {
10 | const ms = parseNetDuration('00:00:00.0000000')
11 | expect(ms).toBe(0)
12 | })
13 |
14 | it('returns 123 for 00:00:00.123', () => {
15 | const ms = parseNetDuration('00:00:00.123')
16 | expect(ms).toBe(123)
17 | })
18 |
19 | it('returns 12 * 1000 for 00:00:12', () => {
20 | const ms = parseNetDuration('00:00:12')
21 | expect(ms).toBe(12 * 1000)
22 | })
23 |
24 | it('returns 12 * 60 * 1000 for 00:12:00', () => {
25 | const ms = parseNetDuration('00:12:00')
26 | expect(ms).toBe(12 * 60 * 1000)
27 | })
28 |
29 | it('returns 12 * 60 * 60 * 1000 for 12:00:00', () => {
30 | const ms = parseNetDuration('12:00:00')
31 | expect(ms).toBe(12 * 60 * 60 * 1000)
32 | })
33 |
34 | it('throws when string has invalid format', () => {
35 | expect(() => parseNetDuration('12:34:56 not a duration')).toThrowError(/^Invalid format/)
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: Test Reporting
2 | description: |
3 | Shows test results in GitHub UI: .NET (xUnit, NUnit, MSTest), Dart, Flutter, Java (JUnit), JavaScript (JEST, Mocha)
4 | author: Ian Moroney
5 | inputs:
6 | artifact:
7 | description: Name or regex of artifact containing test results
8 | required: false
9 | name:
10 | description: Name of the check run
11 | required: true
12 | path:
13 | description: |
14 | Coma separated list of paths to test results
15 | Supports wildcards via [fast-glob](https://github.com/mrmlnc/fast-glob)
16 | All matched result files must be of same format
17 | required: true
18 | path-replace-backslashes:
19 | description: |
20 | The fast-glob library that is internally used interprets backslashes as escape characters.
21 | If enabled, all backslashes in provided path will be replaced by forward slashes and act as directory separators.
22 | It might be useful when path input variable is composed dynamically from existing directory paths on Windows.
23 | default: 'false'
24 | required: false
25 | reporter:
26 | description: |
27 | Format of test results. Supported options:
28 | - dart-json
29 | - dotnet-trx
30 | - flutter-json
31 | - java-junit
32 | - jest-junit
33 | - mocha-json
34 | - mochawesome-json
35 | required: true
36 | list-suites:
37 | description: |
38 | Limits which test suites are listed. Supported options:
39 | - all
40 | - failed
41 | required: true
42 | default: 'all'
43 | list-tests:
44 | description: |
45 | Limits which test cases are listed. Supported options:
46 | - all
47 | - failed
48 | - none
49 | required: true
50 | default: 'all'
51 | max-annotations:
52 | description: |
53 | Limits number of created annotations with error message and stack trace captured during test execution.
54 | Must be less or equal to 50.
55 | required: true
56 | default: '10'
57 | fail-on-error:
58 | description: Set this action as failed if test report contain any failed test
59 | required: true
60 | default: 'true'
61 | working-directory:
62 | description: Relative path under $GITHUB_WORKSPACE where the repository was checked out
63 | required: false
64 | only-summary:
65 | description: |
66 | Allows you to generate only the summary.
67 | If enabled, the report will contain a table listing each test results file and the number of passed, failed, and skipped tests.
68 | Detailed listing of test suites and test cases will be skipped.
69 | default: 'false'
70 | required: false
71 | output-to:
72 | description: |
73 | The location to write the report to. Supported options:
74 | - checks
75 | - step-summary
76 | default: 'checks'
77 | required: false
78 | token:
79 | description: GitHub Access Token
80 | required: false
81 | default: ${{ github.token }}
82 | outputs:
83 | conclusion:
84 | description: |
85 | Final conclusion of the created check run:
86 | - 'success' if no failed tests was found
87 | - 'failure' if any failed test was found
88 | passed:
89 | description: Count of passed tests
90 | failed:
91 | description: Count of failed tests
92 | skipped:
93 | description: Count of skipped tests
94 | time:
95 | description: Test execution time [ms]
96 | runHtmlUrl:
97 | description: Exposes the URL for the HTML version of the run report
98 | runs:
99 | using: 'node20'
100 | main: 'dist/index.js'
101 | branding:
102 | color: blue
103 | icon: file-text
104 |
--------------------------------------------------------------------------------
/assets/fluent-validation-report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/assets/fluent-validation-report.png
--------------------------------------------------------------------------------
/assets/mocha-groups.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/assets/mocha-groups.png
--------------------------------------------------------------------------------
/assets/provider-error-details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/assets/provider-error-details.png
--------------------------------------------------------------------------------
/assets/provider-error-summary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phoenix-actions/test-reporting/f957cd93fc2d848d556fa0d03c57bc79127b6b5e/assets/provider-error-summary.png
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | clearMocks: true,
3 | moduleFileExtensions: ['js', 'ts'],
4 | testEnvironment: 'node',
5 | testMatch: ['**/*.test.ts'],
6 | testRunner: 'jest-circus/runner',
7 | transform: {
8 | '^.+\\.ts$': 'ts-jest'
9 | },
10 | verbose: true
11 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-check",
3 | "version": "0.0.0",
4 | "private": true,
5 | "description": "Presents test results from popular testing frameworks as Github check run",
6 | "main": "lib/main.js",
7 | "scripts": {
8 | "build": "tsc",
9 | "format": "prettier --write **/*.ts",
10 | "format-check": "prettier --check **/*.ts",
11 | "lint": "eslint src/**/*.ts",
12 | "package": "ncc build --source-map --license licenses.txt",
13 | "test": "jest --ci --reporters=default --reporters=jest-junit",
14 | "all": "npm run build && npm run format && npm run lint && npm run package && npm test",
15 | "dart-fixture": "cd \"reports/dart\" && dart test --file-reporter=\"json:../../__tests__/fixtures/dart-json.json\"",
16 | "dotnet-fixture": "dotnet test reports/dotnet/DotnetTests.XUnitTests --logger \"trx;LogFileName=../../../../__tests__/fixtures/dotnet-trx.trx\"",
17 | "jest-fixture": "cd \"reports/jest\" && npm test",
18 | "mocha-fixture": "cd \"reports/mocha\" && npm test",
19 | "mochawesome-fixture": "cd \"reports/mochawesome\" && npm test"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/dorny/test-check.git"
24 | },
25 | "keywords": [
26 | "actions",
27 | "node",
28 | "test",
29 | "report"
30 | ],
31 | "author": "Michal Dorner ",
32 | "license": "MIT",
33 | "dependencies": {
34 | "@actions/core": "^1.10.1",
35 | "@actions/exec": "^1.1.1",
36 | "@actions/github": "^6.0.0",
37 | "adm-zip": "^0.5.3",
38 | "fast-glob": "^3.2.5",
39 | "got": "^11.8.2",
40 | "picomatch": "^2.2.2",
41 | "xml2js": "^0.4.23"
42 | },
43 | "devDependencies": {
44 | "@octokit/types": "^12.4.0",
45 | "@octokit/webhooks": "^12.0.11",
46 | "@octokit/webhooks-types": "^7.3.1",
47 | "@types/adm-zip": "^0.5.5",
48 | "@types/github-slugger": "^1.3.0",
49 | "@types/jest": "^29.5.12",
50 | "@types/node": "^18.19.28",
51 | "@types/picomatch": "^2.3.3",
52 | "@types/xml2js": "^0.4.14",
53 | "@typescript-eslint/eslint-plugin": "^7.8.0",
54 | "@typescript-eslint/parser": "^7.8.0",
55 | "@vercel/ncc": "^0.38.1",
56 | "eol-converter-cli": "^1.0.8",
57 | "eslint": "^8.56.0",
58 | "eslint-import-resolver-typescript": "^3.6.1",
59 | "eslint-plugin-github": "^4.10.2",
60 | "eslint-plugin-import": "^2.29.1",
61 | "eslint-plugin-jest": "^27.9.0",
62 | "eslint-plugin-prettier": "^5.1.3",
63 | "jest": "^29.7.0",
64 | "jest-circus": "^29.7.0",
65 | "jest-junit": "^16.0.0",
66 | "js-yaml": "^4.1.0",
67 | "prettier": "3.2.5",
68 | "ts-jest": "^29.1.2",
69 | "typescript": "^5.4.3"
70 | },
71 | "jest-junit": {
72 | "suiteName": "jest tests",
73 | "outputDirectory": "__tests__/__results__",
74 | "outputName": "jest-junit.xml",
75 | "ancestorSeparator": " › ",
76 | "uniqueOutputName": "false",
77 | "suiteNameTemplate": "{filepath}",
78 | "classNameTemplate": "{classname}",
79 | "titleTemplate": "{title}"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/reports/dart/.gitignore:
--------------------------------------------------------------------------------
1 | # Files and directories created by pub
2 | .dart_tool/
3 | .packages
4 |
5 | # Conventional directory for build outputs
6 | build/
7 |
8 | # Directory created by dartdoc
9 | doc/api/
10 |
--------------------------------------------------------------------------------
/reports/dart/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # Defines a default set of lint rules enforced for
2 | # projects at Google. For details and rationale,
3 | # see https://github.com/dart-lang/pedantic#enabled-lints.
4 | include: package:pedantic/analysis_options.yaml
5 |
6 | # For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
7 | # Uncomment to specify additional rules.
8 | # linter:
9 | # rules:
10 | # - camel_case_types
11 |
12 | analyzer:
13 | # exclude:
14 | # - path/to/excluded/files/**
15 |
--------------------------------------------------------------------------------
/reports/dart/lib/main.dart:
--------------------------------------------------------------------------------
1 | void throwError() {
2 | throw Exception('Some error');
3 | }
4 |
--------------------------------------------------------------------------------
/reports/dart/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: darttest
2 | description: A simple command-line application.
3 |
4 | environment:
5 | sdk: '>=2.12.0 <3.0.0'
6 |
7 | dev_dependencies:
8 | pedantic: ^1.9.0
9 | test: ^1.15.4
10 |
--------------------------------------------------------------------------------
/reports/dart/test/main_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:darttest/main.dart';
2 | import 'package:test/test.dart';
3 | import 'dart:io';
4 |
5 | void main() {
6 | group('Test 1', () {
7 | test('Passing test', () {
8 | expect(1, equals(1));
9 | });
10 |
11 | group('Test 1.1', () {
12 | test('Failing test', () {
13 | expect(1, equals(2));
14 | });
15 |
16 | test('Exception in target unit', () {
17 | throwError();
18 | });
19 | });
20 | });
21 |
22 | group('Test 2', () {
23 | test('Exception in test', () {
24 | throw Exception('Some error');
25 | });
26 | });
27 |
28 | print('Hello from the test');
29 | }
30 |
--------------------------------------------------------------------------------
/reports/dart/test/second_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:test/test.dart';
3 |
4 | void main() {
5 | test('Timeout test', () async {
6 | await Future.delayed(const Duration(seconds: 1));
7 | }, timeout: Timeout(Duration(microseconds: 1)));
8 |
9 | test('Skipped test', () {
10 | // do nothing
11 | }, skip: 'skipped test');
12 | }
13 |
--------------------------------------------------------------------------------
/reports/dotnet/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.vspscc
94 | *.vssscc
95 | .builds
96 | *.pidb
97 | *.svclog
98 | *.scc
99 |
100 | # Chutzpah Test files
101 | _Chutzpah*
102 |
103 | # Visual C++ cache files
104 | ipch/
105 | *.aps
106 | *.ncb
107 | *.opendb
108 | *.opensdf
109 | *.sdf
110 | *.cachefile
111 | *.VC.db
112 | *.VC.VC.opendb
113 |
114 | # Visual Studio profiler
115 | *.psess
116 | *.vsp
117 | *.vspx
118 | *.sap
119 |
120 | # Visual Studio Trace Files
121 | *.e2e
122 |
123 | # TFS 2012 Local Workspace
124 | $tf/
125 |
126 | # Guidance Automation Toolkit
127 | *.gpState
128 |
129 | # ReSharper is a .NET coding add-in
130 | _ReSharper*/
131 | *.[Rr]e[Ss]harper
132 | *.DotSettings.user
133 |
134 | # TeamCity is a build add-in
135 | _TeamCity*
136 |
137 | # DotCover is a Code Coverage Tool
138 | *.dotCover
139 |
140 | # AxoCover is a Code Coverage Tool
141 | .axoCover/*
142 | !.axoCover/settings.json
143 |
144 | # Coverlet is a free, cross platform Code Coverage Tool
145 | coverage*.json
146 | coverage*.xml
147 | coverage*.info
148 |
149 | # Visual Studio code coverage results
150 | *.coverage
151 | *.coveragexml
152 |
153 | # NCrunch
154 | _NCrunch_*
155 | .*crunch*.local.xml
156 | nCrunchTemp_*
157 |
158 | # MightyMoose
159 | *.mm.*
160 | AutoTest.Net/
161 |
162 | # Web workbench (sass)
163 | .sass-cache/
164 |
165 | # Installshield output folder
166 | [Ee]xpress/
167 |
168 | # DocProject is a documentation generator add-in
169 | DocProject/buildhelp/
170 | DocProject/Help/*.HxT
171 | DocProject/Help/*.HxC
172 | DocProject/Help/*.hhc
173 | DocProject/Help/*.hhk
174 | DocProject/Help/*.hhp
175 | DocProject/Help/Html2
176 | DocProject/Help/html
177 |
178 | # Click-Once directory
179 | publish/
180 |
181 | # Publish Web Output
182 | *.[Pp]ublish.xml
183 | *.azurePubxml
184 | # Note: Comment the next line if you want to checkin your web deploy settings,
185 | # but database connection strings (with potential passwords) will be unencrypted
186 | *.pubxml
187 | *.publishproj
188 |
189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
190 | # checkin your Azure Web App publish settings, but sensitive information contained
191 | # in these scripts will be unencrypted
192 | PublishScripts/
193 |
194 | # NuGet Packages
195 | *.nupkg
196 | # NuGet Symbol Packages
197 | *.snupkg
198 | # The packages folder can be ignored because of Package Restore
199 | **/[Pp]ackages/*
200 | # except build/, which is used as an MSBuild target.
201 | !**/[Pp]ackages/build/
202 | # Uncomment if necessary however generally it will be regenerated when needed
203 | #!**/[Pp]ackages/repositories.config
204 | # NuGet v3's project.json files produces more ignorable files
205 | *.nuget.props
206 | *.nuget.targets
207 |
208 | # Microsoft Azure Build Output
209 | csx/
210 | *.build.csdef
211 |
212 | # Microsoft Azure Emulator
213 | ecf/
214 | rcf/
215 |
216 | # Windows Store app package directories and files
217 | AppPackages/
218 | BundleArtifacts/
219 | Package.StoreAssociation.xml
220 | _pkginfo.txt
221 | *.appx
222 | *.appxbundle
223 | *.appxupload
224 |
225 | # Visual Studio cache files
226 | # files ending in .cache can be ignored
227 | *.[Cc]ache
228 | # but keep track of directories ending in .cache
229 | !?*.[Cc]ache/
230 |
231 | # Others
232 | ClientBin/
233 | ~$*
234 | *~
235 | *.dbmdl
236 | *.dbproj.schemaview
237 | *.jfm
238 | *.pfx
239 | *.publishsettings
240 | orleans.codegen.cs
241 |
242 | # Including strong name files can present a security risk
243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
244 | #*.snk
245 |
246 | # Since there are multiple workflows, uncomment next line to ignore bower_components
247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
248 | #bower_components/
249 |
250 | # RIA/Silverlight projects
251 | Generated_Code/
252 |
253 | # Backup & report files from converting an old project file
254 | # to a newer Visual Studio version. Backup files are not needed,
255 | # because we have git ;-)
256 | _UpgradeReport_Files/
257 | Backup*/
258 | UpgradeLog*.XML
259 | UpgradeLog*.htm
260 | ServiceFabricBackup/
261 | *.rptproj.bak
262 |
263 | # SQL Server files
264 | *.mdf
265 | *.ldf
266 | *.ndf
267 |
268 | # Business Intelligence projects
269 | *.rdl.data
270 | *.bim.layout
271 | *.bim_*.settings
272 | *.rptproj.rsuser
273 | *- [Bb]ackup.rdl
274 | *- [Bb]ackup ([0-9]).rdl
275 | *- [Bb]ackup ([0-9][0-9]).rdl
276 |
277 | # Microsoft Fakes
278 | FakesAssemblies/
279 |
280 | # GhostDoc plugin setting file
281 | *.GhostDoc.xml
282 |
283 | # Node.js Tools for Visual Studio
284 | .ntvs_analysis.dat
285 | node_modules/
286 |
287 | # Visual Studio 6 build log
288 | *.plg
289 |
290 | # Visual Studio 6 workspace options file
291 | *.opt
292 |
293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
294 | *.vbw
295 |
296 | # Visual Studio LightSwitch build output
297 | **/*.HTMLClient/GeneratedArtifacts
298 | **/*.DesktopClient/GeneratedArtifacts
299 | **/*.DesktopClient/ModelManifest.xml
300 | **/*.Server/GeneratedArtifacts
301 | **/*.Server/ModelManifest.xml
302 | _Pvt_Extensions
303 |
304 | # Paket dependency manager
305 | .paket/paket.exe
306 | paket-files/
307 |
308 | # FAKE - F# Make
309 | .fake/
310 |
311 | # CodeRush personal settings
312 | .cr/personal
313 |
314 | # Python Tools for Visual Studio (PTVS)
315 | __pycache__/
316 | *.pyc
317 |
318 | # Cake - Uncomment if you are using it
319 | # tools/**
320 | # !tools/packages.config
321 |
322 | # Tabs Studio
323 | *.tss
324 |
325 | # Telerik's JustMock configuration file
326 | *.jmconfig
327 |
328 | # BizTalk build output
329 | *.btp.cs
330 | *.btm.cs
331 | *.odx.cs
332 | *.xsd.cs
333 |
334 | # OpenCover UI analysis results
335 | OpenCover/
336 |
337 | # Azure Stream Analytics local run output
338 | ASALocalRun/
339 |
340 | # MSBuild Binary and Structured Log
341 | *.binlog
342 |
343 | # NVidia Nsight GPU debugger configuration file
344 | *.nvuser
345 |
346 | # MFractors (Xamarin productivity tool) working folder
347 | .mfractor/
348 |
349 | # Local History for Visual Studio
350 | .localhistory/
351 |
352 | # BeatPulse healthcheck temp database
353 | healthchecksdb
354 |
355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
356 | MigrationBackup/
357 |
358 | # Ionide (cross platform F# VS Code tools) working folder
359 | .ionide/
360 |
361 | # Fody - auto-generated XML schema
362 | FodyWeavers.xsd
363 |
--------------------------------------------------------------------------------
/reports/dotnet/DotnetTests.Unit/Calculator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DotnetTests.Unit
4 | {
5 | public class Calculator
6 | {
7 | public int Sum(int a, int b) => a + b;
8 |
9 | public int Div(int a, int b) => a / b;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/reports/dotnet/DotnetTests.Unit/DotnetTests.Unit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/reports/dotnet/DotnetTests.XUnitTests/CalculatorTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using DotnetTests.Unit;
4 | using Xunit;
5 |
6 | namespace DotnetTests.XUnitTests
7 | {
8 | public class CalculatorTests
9 | {
10 | private readonly Calculator _calculator = new Calculator();
11 |
12 | [Fact]
13 | public void Passing_Test()
14 | {
15 | Assert.Equal(2, _calculator.Sum(1,1));
16 | }
17 |
18 | [Fact(DisplayName = "Custom Name")]
19 | public void Passing_Test_With_Name()
20 | {
21 | Assert.Equal(2, _calculator.Sum(1, 1));
22 | }
23 |
24 | [Fact]
25 | public void Failing_Test()
26 | {
27 | Assert.Equal(3, _calculator.Sum(1, 1));
28 | }
29 |
30 | [Fact]
31 | public void Exception_In_TargetTest()
32 | {
33 | _calculator.Div(1, 0);
34 | }
35 |
36 | [Fact]
37 | public void Exception_In_Test()
38 | {
39 | throw new Exception("Test");
40 | }
41 |
42 | [Fact(Timeout = 1)]
43 | public void Timeout_Test()
44 | {
45 | Thread.Sleep(100);
46 | }
47 |
48 | [Fact(Skip = "Skipped test")]
49 | public void Skipped_Test()
50 | {
51 | throw new Exception("Test");
52 | }
53 |
54 | [Theory]
55 | [InlineData(2)]
56 | [InlineData(3)]
57 | public void Is_Even_Number(int i)
58 | {
59 | Assert.True(i % 2 == 0);
60 | }
61 |
62 | [Theory(DisplayName = "Should be even number")]
63 | [InlineData(2)]
64 | [InlineData(3)]
65 | public void Theory_With_Custom_Name(int i)
66 | {
67 | Assert.True(i % 2 == 0);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/reports/dotnet/DotnetTests.XUnitTests/DotnetTests.XUnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/reports/dotnet/DotnetTests.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30320.27
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetTests.Unit", "DotnetTests.Unit\DotnetTests.Unit.csproj", "{A47249E3-01A3-4E0A-9601-A3B2F7437C46}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BCAC3B31-ADB1-4221-9D5B-182EE868648C}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetTests.XUnitTests", "DotnetTests.XUnitTests\DotnetTests.XUnitTests.csproj", "{F8607EDB-D25D-47AA-8132-38ACA242E845}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {A47249E3-01A3-4E0A-9601-A3B2F7437C46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {A47249E3-01A3-4E0A-9601-A3B2F7437C46}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {A47249E3-01A3-4E0A-9601-A3B2F7437C46}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {A47249E3-01A3-4E0A-9601-A3B2F7437C46}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {F8607EDB-D25D-47AA-8132-38ACA242E845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {F8607EDB-D25D-47AA-8132-38ACA242E845}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {F8607EDB-D25D-47AA-8132-38ACA242E845}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {F8607EDB-D25D-47AA-8132-38ACA242E845}.Release|Any CPU.Build.0 = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(SolutionProperties) = preSolution
28 | HideSolutionNode = FALSE
29 | EndGlobalSection
30 | GlobalSection(NestedProjects) = preSolution
31 | {F8607EDB-D25D-47AA-8132-38ACA242E845} = {BCAC3B31-ADB1-4221-9D5B-182EE868648C}
32 | EndGlobalSection
33 | GlobalSection(ExtensibilityGlobals) = postSolution
34 | SolutionGuid = {6ED5543C-74AA-4B21-8050-943550F3F66E}
35 | EndGlobalSection
36 | EndGlobal
37 |
--------------------------------------------------------------------------------
/reports/jest/__tests__/main.test.js:
--------------------------------------------------------------------------------
1 | const lib = require('../lib/main')
2 |
3 | describe('Test 1', () => {
4 | test('Passing test', () => {
5 | expect(true).toBeTruthy()
6 | });
7 |
8 | describe('Test 1.1', () => {
9 | test('Failing test', () => {
10 | expect(false).toBeTruthy()
11 | });
12 |
13 | test('Exception in target unit', () => {
14 | lib.throwError();
15 | });
16 | });
17 | });
18 |
19 | describe('Test 2', () => {
20 | test('Exception in test', () => {
21 | throw new Error('Some error');
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/reports/jest/__tests__/second.test.js:
--------------------------------------------------------------------------------
1 | test('Timeout test', async () => {
2 | await new Promise(resolve => setTimeout(resolve, 1000));
3 | }, 1);
4 |
5 | test.skip('Skipped test', () => {
6 | // do nothing
7 | });
8 |
--------------------------------------------------------------------------------
/reports/jest/lib/main.js:
--------------------------------------------------------------------------------
1 | function throwError() {
2 | throw new Error('Some error')
3 | }
4 |
5 | exports.throwError = throwError
6 |
--------------------------------------------------------------------------------
/reports/jest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jest-fixture",
3 | "version": "0.0.0",
4 | "private": true,
5 | "description": "Generates test fixtures for test-check action",
6 | "scripts": {
7 | "test": "jest --ci --reporters=default --reporters=jest-junit"
8 | },
9 | "author": "Michal Dorner ",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "jest": "^26.5.3",
13 | "jest-junit": "^12.0.0"
14 | },
15 | "jest-junit": {
16 | "suiteName": "jest tests",
17 | "outputDirectory": "../../__tests__/fixtures",
18 | "outputName": "jest-junit.xml",
19 | "ancestorSeparator": " › ",
20 | "uniqueOutputName": "false",
21 | "suiteNameTemplate": "{filepath}",
22 | "classNameTemplate": "{classname}",
23 | "titleTemplate": "{title}"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/reports/mocha/lib/main.js:
--------------------------------------------------------------------------------
1 | function throwError() {
2 | throw new Error('Some error')
3 | }
4 |
5 | exports.throwError = throwError
6 |
--------------------------------------------------------------------------------
/reports/mocha/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mocha-fixture",
3 | "version": "0.0.0",
4 | "private": true,
5 | "description": "Generates test fixtures for test-reporting action",
6 | "scripts": {
7 | "test": "mocha --reporter json > ../../__tests__/fixtures/mocha-json.json"
8 | },
9 | "author": "Michal Dorner ",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "mocha": "^8.3.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/reports/mocha/test/main.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert').strict;
2 | const lib = require('../lib/main')
3 |
4 | describe('Test 1', () => {
5 | it('Passing test', () => {
6 | assert.equal(true, true)
7 | });
8 |
9 | describe('Test 1.1', () => {
10 | it('Failing test', () => {
11 | assert.equal(false, true)
12 | });
13 |
14 | it('Exception in target unit', () => {
15 | lib.throwError();
16 | });
17 | });
18 | });
19 |
20 | describe('Test 2', () => {
21 | it('Exception in test', () => {
22 | throw new Error('Some error');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/reports/mocha/test/second.test.js:
--------------------------------------------------------------------------------
1 | it('Timeout test', async function(done) {
2 | this.timeout(1);
3 | setTimeout(done, 1000);
4 | });
5 |
6 | it.skip('Skipped test', () => {
7 | // do nothing
8 | });
9 |
--------------------------------------------------------------------------------
/reports/mochawesome/lib/main.js:
--------------------------------------------------------------------------------
1 | function throwError() {
2 | throw new Error('Some error')
3 | }
4 |
5 | exports.throwError = throwError
6 |
--------------------------------------------------------------------------------
/reports/mochawesome/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mochawesome-fixture",
3 | "version": "0.0.0",
4 | "private": true,
5 | "description": "Generates test fixtures for test-reporting action",
6 | "scripts": {
7 | "test": "mocha --reporter mochawesome --reporter-options reportDir=../../__tests__/fixtures/,reportFilename=mochawesome-json.json"
8 | },
9 | "author": "Ian Moroney ",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "cypress": "^9.5.0",
13 | "mochawesome": "^7.0.1",
14 | "mocha": "^9.2.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/reports/mochawesome/test/main.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert').strict;
2 | const lib = require('../lib/main')
3 |
4 | describe('Test 1', () => {
5 | it('Passing test', () => {
6 | assert.equal(true, true)
7 | });
8 |
9 | describe('Test 1.1', () => {
10 | it('Failing test', () => {
11 | assert.equal(false, true)
12 | });
13 |
14 | it('Exception in target unit', () => {
15 | lib.throwError();
16 | });
17 | });
18 | });
19 |
20 | describe('Test 2', () => {
21 | it('Exception in test', () => {
22 | throw new Error('Some error');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/reports/mochawesome/test/second.test.js:
--------------------------------------------------------------------------------
1 | it('Timeout test', async function(done) {
2 | this.timeout(1);
3 | setTimeout(done, 1000);
4 | });
5 |
6 | it.skip('Skipped test', () => {
7 | // do nothing
8 | });
9 |
--------------------------------------------------------------------------------
/src/input-providers/artifact-provider.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core'
2 | import * as github from '@actions/github'
3 | import {GitHub} from '@actions/github/lib/utils'
4 |
5 | import Zip from 'adm-zip'
6 | import picomatch from 'picomatch'
7 |
8 | import {FileContent, InputProvider, ReportInput} from './input-provider'
9 | import {downloadArtifact, listFiles} from '../utils/github-utils'
10 |
11 | export class ArtifactProvider implements InputProvider {
12 | private readonly artifactNameMatch: (name: string) => boolean
13 | private readonly fileNameMatch: (name: string) => boolean
14 | private readonly getReportName: (name: string) => string
15 |
16 | constructor(
17 | readonly octokit: InstanceType,
18 | readonly artifact: string,
19 | readonly name: string,
20 | readonly pattern: string[],
21 | readonly sha: string,
22 | readonly runId: number,
23 | readonly token: string
24 | ) {
25 | if (this.artifact.startsWith('/')) {
26 | const endIndex = this.artifact.lastIndexOf('/')
27 | const rePattern = this.artifact.substring(1, endIndex)
28 | const reOpts = this.artifact.substring(endIndex + 1)
29 | const re = new RegExp(rePattern, reOpts)
30 | this.artifactNameMatch = (str: string) => re.test(str)
31 | this.getReportName = (str: string) => {
32 | const match = str.match(re)
33 | if (match === null) {
34 | throw new Error(`Artifact name '${str}' does not match regex ${this.artifact}`)
35 | }
36 | let reportName = this.name
37 | for (let i = 1; i < match.length; i++) {
38 | reportName = reportName.replace(new RegExp(`\\$${i}`, 'g'), match[i])
39 | }
40 | return reportName
41 | }
42 | } else {
43 | this.artifactNameMatch = (str: string) => str === this.artifact
44 | this.getReportName = () => this.name
45 | }
46 |
47 | this.fileNameMatch = picomatch(pattern)
48 | }
49 |
50 | async load(): Promise {
51 | const result: ReportInput = {}
52 |
53 | const resp = await this.octokit.rest.actions.listWorkflowRunArtifacts({
54 | ...github.context.repo,
55 | run_id: this.runId
56 | })
57 |
58 | if (resp.data.artifacts.length === 0) {
59 | core.warning(`No artifacts found in run ${this.runId}`)
60 | return {}
61 | }
62 |
63 | const artifacts = resp.data.artifacts.filter(a => this.artifactNameMatch(a.name))
64 | if (artifacts.length === 0) {
65 | core.warning(`No artifact matches ${this.artifact}`)
66 | return {}
67 | }
68 |
69 | for (const art of artifacts) {
70 | const fileName = `${art.name}.zip`
71 | await downloadArtifact(this.octokit, art.id, fileName, this.token)
72 | core.startGroup(`Reading archive ${fileName}`)
73 | try {
74 | const reportName = this.getReportName(art.name)
75 | core.info(`Report name: ${reportName}`)
76 | const files: FileContent[] = []
77 | const zip = new Zip(fileName)
78 | for (const entry of zip.getEntries()) {
79 | const file = entry.entryName
80 | if (entry.isDirectory) {
81 | core.info(`Skipping ${file}: entry is a directory`)
82 | continue
83 | }
84 | if (!this.fileNameMatch(file)) {
85 | core.info(`Skipping ${file}: filename does not match pattern`)
86 | continue
87 | }
88 | const content = zip.readAsText(entry)
89 | files.push({file, content})
90 | core.info(`Read ${file}: ${content.length} chars`)
91 | }
92 | if (result[reportName]) {
93 | result[reportName].push(...files)
94 | } else {
95 | result[reportName] = files
96 | }
97 | } finally {
98 | core.endGroup()
99 | }
100 | }
101 |
102 | return result
103 | }
104 |
105 | async listTrackedFiles(): Promise {
106 | return listFiles(this.octokit, this.sha)
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/input-providers/input-provider.ts:
--------------------------------------------------------------------------------
1 | export interface ReportInput {
2 | [reportName: string]: FileContent[]
3 | }
4 |
5 | export interface FileContent {
6 | file: string
7 | content: string
8 | }
9 |
10 | export interface InputProvider {
11 | load(): Promise
12 | listTrackedFiles(): Promise
13 | }
14 |
--------------------------------------------------------------------------------
/src/input-providers/local-file-provider.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import glob from 'fast-glob'
3 | import {FileContent, InputProvider, ReportInput} from './input-provider'
4 | import {listFiles} from '../utils/git'
5 |
6 | export class LocalFileProvider implements InputProvider {
7 | constructor(
8 | readonly name: string,
9 | readonly pattern: string[]
10 | ) {}
11 |
12 | async load(): Promise {
13 | const result: FileContent[] = []
14 | for (const pat of this.pattern) {
15 | const paths = await glob(pat, {dot: true})
16 | for (const file of paths) {
17 | const content = await fs.promises.readFile(file, {encoding: 'utf8'})
18 | result.push({file, content})
19 | }
20 | }
21 |
22 | return {[this.name]: result}
23 | }
24 |
25 | async listTrackedFiles(): Promise {
26 | return listFiles()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/parsers/dart-json/dart-json-parser.ts:
--------------------------------------------------------------------------------
1 | import {ParseOptions, TestParser} from '../../test-parser'
2 |
3 | import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
4 |
5 | import {
6 | ReportEvent,
7 | Suite,
8 | Group,
9 | TestStartEvent,
10 | TestDoneEvent,
11 | ErrorEvent,
12 | isSuiteEvent,
13 | isGroupEvent,
14 | isTestStartEvent,
15 | isTestDoneEvent,
16 | isErrorEvent,
17 | isDoneEvent,
18 | isMessageEvent,
19 | MessageEvent
20 | } from './dart-json-types'
21 |
22 | import {
23 | TestExecutionResult,
24 | TestRunResult,
25 | TestSuiteResult,
26 | TestGroupResult,
27 | TestCaseResult,
28 | TestCaseError
29 | } from '../../test-results'
30 |
31 | class TestRun {
32 | constructor(
33 | readonly path: string,
34 | readonly suites: TestSuite[],
35 | readonly success: boolean,
36 | readonly time: number
37 | ) {}
38 | }
39 |
40 | class TestSuite {
41 | constructor(readonly suite: Suite) {}
42 | readonly groups: {[id: number]: TestGroup} = {}
43 | }
44 |
45 | class TestGroup {
46 | constructor(readonly group: Group) {}
47 | readonly tests: TestCase[] = []
48 | }
49 |
50 | class TestCase {
51 | constructor(readonly testStart: TestStartEvent) {
52 | this.groupId = testStart.test.groupIDs[testStart.test.groupIDs.length - 1]
53 | }
54 | readonly groupId: number
55 | readonly print: MessageEvent[] = []
56 | testDone?: TestDoneEvent
57 | error?: ErrorEvent
58 |
59 | get result(): TestExecutionResult {
60 | if (this.testDone?.skipped) {
61 | return 'skipped'
62 | }
63 | if (this.testDone?.result === 'success') {
64 | return 'success'
65 | }
66 |
67 | if (this.testDone?.result === 'error' || this.testDone?.result === 'failure') {
68 | return 'failed'
69 | }
70 |
71 | return undefined
72 | }
73 |
74 | get time(): number {
75 | return this.testDone !== undefined ? this.testDone.time - this.testStart.time : 0
76 | }
77 | }
78 |
79 | export class DartJsonParser implements TestParser {
80 | assumedWorkDir: string | undefined
81 |
82 | constructor(
83 | readonly options: ParseOptions,
84 | readonly sdk: 'dart' | 'flutter'
85 | ) {}
86 |
87 | async parse(path: string, content: string): Promise {
88 | const tr = this.getTestRun(path, content)
89 | const result = this.getTestRunResult(tr)
90 | return Promise.resolve(result)
91 | }
92 |
93 | private getTestRun(path: string, content: string): TestRun {
94 | const lines = content.split(/\n\r?/g)
95 | const events = lines
96 | .map((str, i) => {
97 | if (str.trim() === '') {
98 | return null
99 | }
100 | try {
101 | return JSON.parse(str)
102 | } catch (e: any) {
103 | const col = e.columnNumber !== undefined ? `:${e.columnNumber}` : ''
104 | throw new Error(`Invalid JSON at ${path}:${i + 1}${col}\n\n${e}`)
105 | }
106 | })
107 | .filter(evt => evt != null) as ReportEvent[]
108 |
109 | let success = false
110 | let totalTime = 0
111 | const suites: {[id: number]: TestSuite} = {}
112 | const tests: {[id: number]: TestCase} = {}
113 |
114 | for (const evt of events) {
115 | if (isSuiteEvent(evt)) {
116 | suites[evt.suite.id] = new TestSuite(evt.suite)
117 | } else if (isGroupEvent(evt)) {
118 | suites[evt.group.suiteID].groups[evt.group.id] = new TestGroup(evt.group)
119 | } else if (isTestStartEvent(evt) && evt.test.url !== null) {
120 | const test: TestCase = new TestCase(evt)
121 | const suite = suites[evt.test.suiteID]
122 | const group = suite.groups[evt.test.groupIDs[evt.test.groupIDs.length - 1]]
123 | group.tests.push(test)
124 | tests[evt.test.id] = test
125 | } else if (isTestDoneEvent(evt) && tests[evt.testID]) {
126 | tests[evt.testID].testDone = evt
127 | } else if (isErrorEvent(evt) && tests[evt.testID]) {
128 | tests[evt.testID].error = evt
129 | } else if (isMessageEvent(evt) && tests[evt.testID]) {
130 | tests[evt.testID].print.push(evt)
131 | } else if (isDoneEvent(evt)) {
132 | success = evt.success
133 | totalTime = evt.time
134 | }
135 | }
136 |
137 | return new TestRun(path, Object.values(suites), success, totalTime)
138 | }
139 |
140 | private getTestRunResult(tr: TestRun): TestRunResult {
141 | const suites = tr.suites.map(s => {
142 | return new TestSuiteResult(this.getRelativePath(s.suite.path), this.getGroups(s))
143 | })
144 |
145 | return new TestRunResult(tr.path, suites, tr.time)
146 | }
147 |
148 | private getGroups(suite: TestSuite): TestGroupResult[] {
149 | const groups = Object.values(suite.groups).filter(grp => grp.tests.length > 0)
150 | groups.sort((a, b) => (a.group.line ?? 0) - (b.group.line ?? 0))
151 |
152 | return groups.map(group => {
153 | group.tests.sort((a, b) => (a.testStart.test.line ?? 0) - (b.testStart.test.line ?? 0))
154 | const tests = group.tests
155 | .filter(tc => !tc.testDone?.hidden)
156 | .map(tc => {
157 | const error = this.getError(suite, tc)
158 | const testName =
159 | group.group.name !== undefined && tc.testStart.test.name.startsWith(group.group.name)
160 | ? tc.testStart.test.name.slice(group.group.name.length).trim()
161 | : tc.testStart.test.name.trim()
162 | return new TestCaseResult(testName, tc.result, tc.time, error)
163 | })
164 | return new TestGroupResult(group.group.name, tests)
165 | })
166 | }
167 |
168 | private getError(testSuite: TestSuite, test: TestCase): TestCaseError | undefined {
169 | if (!this.options.parseErrors || !test.error) {
170 | return undefined
171 | }
172 |
173 | const {trackedFiles} = this.options
174 | const stackTrace = test.error?.stackTrace ?? ''
175 | const print = test.print
176 | .filter(p => p.messageType === 'print')
177 | .map(p => p.message)
178 | .join('\n')
179 | const details = [print, stackTrace].filter(str => str !== '').join('\n')
180 | const src = this.exceptionThrowSource(details, trackedFiles)
181 | const message = this.getErrorMessage(test.error?.error ?? '', print)
182 | let path
183 | let line
184 |
185 | if (src !== undefined) {
186 | path = src.path
187 | line = src.line
188 | } else {
189 | const testStartPath = this.getRelativePath(testSuite.suite.path)
190 | if (trackedFiles.includes(testStartPath)) {
191 | path = testStartPath
192 | line = test.testStart.test.root_line ?? test.testStart.test.line ?? undefined
193 | }
194 | }
195 |
196 | return {
197 | path,
198 | line,
199 | message,
200 | details
201 | }
202 | }
203 |
204 | private getErrorMessage(message: string, print: string): string {
205 | if (this.sdk === 'flutter') {
206 | const uselessMessageRe = /^Test failed\. See exception logs above\.\nThe test description was:/m
207 | const flutterPrintRe =
208 | /^══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞═+\s+(.*)\s+When the exception was thrown, this was the stack:/ms
209 | if (uselessMessageRe.test(message)) {
210 | const match = print.match(flutterPrintRe)
211 | if (match !== null) {
212 | return match[1]
213 | }
214 | }
215 | }
216 |
217 | return message || print
218 | }
219 |
220 | private exceptionThrowSource(ex: string, trackedFiles: string[]): {path: string; line: number} | undefined {
221 | const lines = ex.split(/\r?\n/g)
222 |
223 | // regexp to extract file path and line number from stack trace
224 | const dartRe = /^(?!package:)(.*)\s+(\d+):\d+\s+/
225 | const flutterRe = /^#\d+\s+.*\((?!package:)(.*):(\d+):\d+\)$/
226 | const re = this.sdk === 'dart' ? dartRe : flutterRe
227 |
228 | for (const str of lines) {
229 | const match = str.match(re)
230 | if (match !== null) {
231 | const [_, pathStr, lineStr] = match
232 | const path = normalizeFilePath(this.getRelativePath(pathStr))
233 | if (trackedFiles.includes(path)) {
234 | const line = parseInt(lineStr)
235 | return {path, line}
236 | }
237 | }
238 | }
239 | }
240 |
241 | private getRelativePath(path: string): string {
242 | const prefix = 'file://'
243 | if (path.startsWith(prefix)) {
244 | path = path.substr(prefix.length)
245 | }
246 |
247 | path = normalizeFilePath(path)
248 | const workDir = this.getWorkDir(path)
249 | if (workDir !== undefined && path.startsWith(workDir)) {
250 | path = path.substr(workDir.length)
251 | }
252 | return path
253 | }
254 |
255 | private getWorkDir(path: string): string | undefined {
256 | return (
257 | this.options.workDir ??
258 | this.assumedWorkDir ??
259 | (this.assumedWorkDir = getBasePath(path, this.options.trackedFiles))
260 | )
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/src/parsers/dart-json/dart-json-types.ts:
--------------------------------------------------------------------------------
1 | /// reflects documentation at https://github.com/dart-lang/test/blob/master/pkgs/test/doc/json_reporter.md
2 |
3 | export type ReportEvent =
4 | | StartEvent
5 | | AllSuitesEvent
6 | | SuiteEvent
7 | | DebugEvent
8 | | GroupEvent
9 | | TestStartEvent
10 | | TestDoneEvent
11 | | DoneEvent
12 | | MessageEvent
13 | | ErrorEvent
14 |
15 | export interface Event {
16 | type: 'start' | 'allSuites' | 'suite' | 'debug' | 'group' | 'testStart' | 'print' | 'error' | 'testDone' | 'done'
17 | time: number
18 | }
19 |
20 | export interface StartEvent extends Event {
21 | type: 'start'
22 | protocolVersion: string
23 | runnerVersion: string
24 | pid: number
25 | }
26 |
27 | export interface AllSuitesEvent extends Event {
28 | type: 'allSuites'
29 | count: number // The total number of suites that will be loaded.
30 | }
31 |
32 | export interface SuiteEvent extends Event {
33 | type: 'suite'
34 | suite: Suite
35 | }
36 |
37 | export interface GroupEvent extends Event {
38 | type: 'group'
39 | group: Group
40 | }
41 |
42 | export interface TestStartEvent extends Event {
43 | type: 'testStart'
44 | test: Test
45 | }
46 |
47 | export interface TestDoneEvent extends Event {
48 | type: 'testDone'
49 | testID: number
50 | result: 'success' | 'failure' | 'error'
51 | hidden: boolean
52 | skipped: boolean
53 | }
54 |
55 | export interface DoneEvent extends Event {
56 | type: 'done'
57 | success: boolean
58 | }
59 |
60 | export interface ErrorEvent extends Event {
61 | type: 'error'
62 | testID: number
63 | error: string
64 | stackTrace: string
65 | isFailure: boolean
66 | }
67 |
68 | export interface DebugEvent extends Event {
69 | type: 'debug'
70 | suiteID: number
71 | observatory: string
72 | remoteDebugger: string
73 | }
74 |
75 | export interface MessageEvent extends Event {
76 | type: 'print'
77 | testID: number
78 | messageType: 'print' | 'skip'
79 | message: string
80 | }
81 |
82 | export interface Suite {
83 | id: number
84 | platform?: string
85 | path: string
86 | }
87 |
88 | export interface Group {
89 | id: number
90 | name?: string
91 | suiteID: number
92 | parentID?: number
93 | testCount: number
94 | line: number | null // The (1-based) line on which the group was defined, or `null`.
95 | column: number | null // The (1-based) column on which the group was defined, or `null`.
96 | url: string | null
97 | }
98 |
99 | export interface Test {
100 | id: number
101 | name: string
102 | suiteID: number
103 | groupIDs: number[] // The IDs of groups containing this test, in order from outermost to innermost.
104 | line: number | null // The (1-based) line on which the test was defined, or `null`.
105 | column: number | null // The (1-based) column on which the test was defined, or `null`.
106 | url: string | null
107 | root_line?: number
108 | root_column?: number
109 | root_url: string | undefined
110 | }
111 |
112 | export function isSuiteEvent(event: Event): event is SuiteEvent {
113 | return event.type === 'suite'
114 | }
115 | export function isGroupEvent(event: Event): event is GroupEvent {
116 | return event.type === 'group'
117 | }
118 | export function isTestStartEvent(event: Event): event is TestStartEvent {
119 | return event.type === 'testStart'
120 | }
121 | export function isTestDoneEvent(event: Event): event is TestDoneEvent {
122 | return event.type === 'testDone'
123 | }
124 | export function isErrorEvent(event: Event): event is ErrorEvent {
125 | return event.type === 'error'
126 | }
127 | export function isDoneEvent(event: Event): event is DoneEvent {
128 | return event.type === 'done'
129 | }
130 | export function isMessageEvent(event: Event): event is MessageEvent {
131 | return event.type === 'print'
132 | }
133 |
--------------------------------------------------------------------------------
/src/parsers/dotnet-trx/dotnet-trx-parser.ts:
--------------------------------------------------------------------------------
1 | import {parseStringPromise} from 'xml2js'
2 |
3 | import {ErrorInfo, Outcome, TrxReport, UnitTest, UnitTestResult} from './dotnet-trx-types'
4 | import {ParseOptions, TestParser} from '../../test-parser'
5 |
6 | import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
7 | import {parseIsoDate, parseNetDuration} from '../../utils/parse-utils'
8 |
9 | import {
10 | TestExecutionResult,
11 | TestRunResult,
12 | TestSuiteResult,
13 | TestGroupResult,
14 | TestCaseResult,
15 | TestCaseError
16 | } from '../../test-results'
17 |
18 | class TestClass {
19 | constructor(readonly name: string) {}
20 | readonly tests: Test[] = []
21 | }
22 |
23 | class Test {
24 | constructor(
25 | readonly name: string,
26 | readonly outcome: Outcome,
27 | readonly duration: number,
28 | readonly error?: ErrorInfo
29 | ) {}
30 |
31 | get result(): TestExecutionResult | undefined {
32 | switch (this.outcome) {
33 | case 'Passed':
34 | return 'success'
35 | case 'NotExecuted':
36 | return 'skipped'
37 | case 'Failed':
38 | return 'failed'
39 | }
40 | }
41 | }
42 |
43 | export class DotnetTrxParser implements TestParser {
44 | assumedWorkDir: string | undefined
45 |
46 | constructor(readonly options: ParseOptions) {}
47 |
48 | async parse(path: string, content: string): Promise {
49 | const trx = await this.getTrxReport(path, content)
50 | const tc = this.getTestClasses(trx)
51 | const tr = this.getTestRunResult(path, trx, tc)
52 | tr.sort(true)
53 | return tr
54 | }
55 |
56 | private async getTrxReport(path: string, content: string): Promise {
57 | try {
58 | return (await parseStringPromise(content)) as TrxReport
59 | } catch (e) {
60 | throw new Error(`Invalid XML at ${path}\n\n${e}`)
61 | }
62 | }
63 |
64 | private getTestClasses(trx: TrxReport): TestClass[] {
65 | if (trx.TestRun.TestDefinitions === undefined || trx.TestRun.Results === undefined) {
66 | return []
67 | }
68 |
69 | const unitTests: {[id: string]: UnitTest} = {}
70 | for (const td of trx.TestRun.TestDefinitions) {
71 | for (const ut of td.UnitTest) {
72 | unitTests[ut.$.id] = ut
73 | }
74 | }
75 |
76 | const unitTestsResults = trx.TestRun.Results.flatMap(r => r.UnitTestResult).flatMap(result => ({
77 | result,
78 | test: unitTests[result.$.testId]
79 | }))
80 |
81 | const testClasses: {[name: string]: TestClass} = {}
82 | for (const r of unitTestsResults) {
83 | const className = r.test.TestMethod[0].$.className
84 | let tc = testClasses[className]
85 | if (tc === undefined) {
86 | tc = new TestClass(className)
87 | testClasses[tc.name] = tc
88 | }
89 | const error = this.getErrorInfo(r.result)
90 | const durationAttr = r.result.$.duration
91 | const duration = durationAttr ? parseNetDuration(durationAttr) : 0
92 |
93 | const resultTestName = r.result.$.testName
94 | const testName =
95 | resultTestName.startsWith(className) && resultTestName[className.length] === '.'
96 | ? resultTestName.substr(className.length + 1)
97 | : resultTestName
98 |
99 | const test = new Test(testName, r.result.$.outcome, duration, error)
100 | tc.tests.push(test)
101 | }
102 |
103 | const result = Object.values(testClasses)
104 | return result
105 | }
106 |
107 | private getTestRunResult(path: string, trx: TrxReport, testClasses: TestClass[]): TestRunResult {
108 | const times = trx.TestRun.Times[0].$
109 | const totalTime = parseIsoDate(times.finish).getTime() - parseIsoDate(times.start).getTime()
110 |
111 | const suites = testClasses.map(testClass => {
112 | const tests = testClass.tests.map(test => {
113 | const error = this.getError(test)
114 | return new TestCaseResult(test.name, test.result, test.duration, error)
115 | })
116 | const group = new TestGroupResult(null, tests)
117 | return new TestSuiteResult(testClass.name, [group])
118 | })
119 |
120 | return new TestRunResult(path, suites, totalTime)
121 | }
122 |
123 | private getErrorInfo(testResult: UnitTestResult): ErrorInfo | undefined {
124 | if (testResult.$.outcome !== 'Failed') {
125 | return undefined
126 | }
127 |
128 | const output = testResult.Output
129 | const error = output?.length > 0 && output[0].ErrorInfo?.length > 0 ? output[0].ErrorInfo[0] : undefined
130 | return error
131 | }
132 |
133 | private getError(test: Test): TestCaseError | undefined {
134 | if (!this.options.parseErrors || !test.error) {
135 | return undefined
136 | }
137 |
138 | const error = test.error
139 | if (
140 | !Array.isArray(error.Message) ||
141 | error.Message.length === 0 ||
142 | !Array.isArray(error.StackTrace) ||
143 | error.StackTrace.length === 0
144 | ) {
145 | return undefined
146 | }
147 |
148 | const message = test.error.Message[0]
149 | const stackTrace = test.error.StackTrace[0]
150 | let path
151 | let line
152 |
153 | const src = this.exceptionThrowSource(stackTrace)
154 | if (src) {
155 | path = src.path
156 | line = src.line
157 | }
158 |
159 | return {
160 | path,
161 | line,
162 | message,
163 | details: `${message}\n${stackTrace}`
164 | }
165 | }
166 |
167 | private exceptionThrowSource(stackTrace: string): {path: string; line: number} | undefined {
168 | const lines = stackTrace.split(/\r*\n/)
169 | const re = / in (.+):line (\d+)$/
170 | const {trackedFiles} = this.options
171 |
172 | for (const str of lines) {
173 | const match = str.match(re)
174 | if (match !== null) {
175 | const [_, fileStr, lineStr] = match
176 | const filePath = normalizeFilePath(fileStr)
177 | const workDir = this.getWorkDir(filePath)
178 | if (workDir) {
179 | const file = filePath.substr(workDir.length)
180 | if (trackedFiles.includes(file)) {
181 | const line = parseInt(lineStr)
182 | return {path: file, line}
183 | }
184 | }
185 | }
186 | }
187 | }
188 |
189 | private getWorkDir(path: string): string | undefined {
190 | return (
191 | this.options.workDir ??
192 | this.assumedWorkDir ??
193 | (this.assumedWorkDir = getBasePath(path, this.options.trackedFiles))
194 | )
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/parsers/dotnet-trx/dotnet-trx-types.ts:
--------------------------------------------------------------------------------
1 | export interface TrxReport {
2 | TestRun: TestRun
3 | }
4 |
5 | export interface TestRun {
6 | Times: Times[]
7 | Results?: Results[]
8 | TestDefinitions?: TestDefinitions[]
9 | }
10 |
11 | export interface Times {
12 | $: {
13 | creation: string
14 | queuing: string
15 | start: string
16 | finish: string
17 | }
18 | }
19 |
20 | export interface TestDefinitions {
21 | UnitTest: UnitTest[]
22 | }
23 |
24 | export interface UnitTest {
25 | $: {
26 | id: string
27 | }
28 | TestMethod: TestMethod[]
29 | }
30 |
31 | export interface TestMethod {
32 | $: {
33 | className: string
34 | name: string
35 | }
36 | }
37 |
38 | export interface Results {
39 | UnitTestResult: UnitTestResult[]
40 | }
41 |
42 | export interface UnitTestResult {
43 | $: {
44 | testId: string
45 | testName: string
46 | duration?: string
47 | outcome: Outcome
48 | }
49 | Output: Output[]
50 | }
51 |
52 | export interface Output {
53 | ErrorInfo: ErrorInfo[]
54 | }
55 | export interface ErrorInfo {
56 | Message: string[]
57 | StackTrace: string[]
58 | }
59 |
60 | export type Outcome = 'Passed' | 'NotExecuted' | 'Failed'
61 |
--------------------------------------------------------------------------------
/src/parsers/java-junit/java-junit-parser.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import {ParseOptions, TestParser} from '../../test-parser'
3 | import {parseStringPromise} from 'xml2js'
4 |
5 | import {JunitReport, SingleSuiteReport, TestCase, TestSuite} from './java-junit-types'
6 | import {normalizeFilePath} from '../../utils/path-utils'
7 |
8 | import {
9 | TestExecutionResult,
10 | TestRunResult,
11 | TestSuiteResult,
12 | TestGroupResult,
13 | TestCaseResult,
14 | TestCaseError
15 | } from '../../test-results'
16 |
17 | export class JavaJunitParser implements TestParser {
18 | readonly trackedFiles: {[fileName: string]: string[]}
19 |
20 | constructor(readonly options: ParseOptions) {
21 | // Map to efficient lookup of all paths with given file name
22 | this.trackedFiles = {}
23 | for (const filePath of options.trackedFiles) {
24 | const fileName = path.basename(filePath)
25 | const files = this.trackedFiles[fileName] ?? (this.trackedFiles[fileName] = [])
26 | files.push(normalizeFilePath(filePath))
27 | }
28 | }
29 |
30 | async parse(filePath: string, content: string): Promise {
31 | const reportOrSuite = await this.getJunitReport(filePath, content)
32 | const isReport = (reportOrSuite as JunitReport).testsuites !== undefined
33 |
34 | // XML might contain:
35 | // - multiple suites under root node
36 | // - single as root node
37 | let ju: JunitReport
38 | if (isReport) {
39 | ju = reportOrSuite as JunitReport
40 | } else {
41 | // Make it behave the same way as if suite was inside root node
42 | const suite = (reportOrSuite as SingleSuiteReport).testsuite
43 | ju = {
44 | testsuites: {
45 | $: {time: suite.$.time},
46 | testsuite: [suite]
47 | }
48 | }
49 | }
50 |
51 | return this.getTestRunResult(filePath, ju)
52 | }
53 |
54 | private async getJunitReport(filePath: string, content: string): Promise {
55 | try {
56 | return await parseStringPromise(content)
57 | } catch (e) {
58 | throw new Error(`Invalid XML at ${filePath}\n\n${e}`)
59 | }
60 | }
61 |
62 | private getTestRunResult(filePath: string, junit: JunitReport): TestRunResult {
63 | const suites =
64 | junit.testsuites.testsuite === undefined
65 | ? []
66 | : junit.testsuites.testsuite.map(ts => {
67 | const name = ts.$.name.trim()
68 | const time = parseFloat(ts.$.time) * 1000
69 | return new TestSuiteResult(name, this.getGroups(ts), time)
70 | })
71 |
72 | const seconds = parseFloat(junit.testsuites.$?.time)
73 | const time = isNaN(seconds) ? undefined : seconds * 1000
74 | return new TestRunResult(filePath, suites, time)
75 | }
76 |
77 | private getGroups(suite: TestSuite): TestGroupResult[] {
78 | if (suite.testcase === undefined) {
79 | return []
80 | }
81 |
82 | const groups: {name: string; tests: TestCase[]}[] = []
83 | for (const tc of suite.testcase) {
84 | // Normally classname is same as suite name - both refer to same Java class
85 | // Therefore it doesn't make sense to process it as a group
86 | // and tests will be added to default group with empty name
87 | const className = tc.$.classname === suite.$.name ? '' : tc.$.classname
88 | let grp = groups.find(g => g.name === className)
89 | if (grp === undefined) {
90 | grp = {name: className, tests: []}
91 | groups.push(grp)
92 | }
93 | grp.tests.push(tc)
94 | }
95 |
96 | return groups.map(grp => {
97 | const tests = grp.tests.map(tc => {
98 | const name = tc.$.name.trim()
99 | const result = this.getTestCaseResult(tc)
100 | const time = parseFloat(tc.$.time) * 1000
101 | const error = this.getTestCaseError(tc)
102 | return new TestCaseResult(name, result, time, error)
103 | })
104 | return new TestGroupResult(grp.name, tests)
105 | })
106 | }
107 |
108 | private getTestCaseResult(test: TestCase): TestExecutionResult {
109 | if (test.failure || test.error) return 'failed'
110 | if (test.skipped) return 'skipped'
111 | return 'success'
112 | }
113 |
114 | private getTestCaseError(tc: TestCase): TestCaseError | undefined {
115 | if (!this.options.parseErrors) {
116 | return undefined
117 | }
118 |
119 | // We process and the same way
120 | const failures = tc.failure ?? tc.error
121 | if (!failures) {
122 | return undefined
123 | }
124 |
125 | const failure = failures[0]
126 | const details = typeof failure === 'object' ? failure._ ?? '' : failure
127 | let filePath
128 | let line
129 |
130 | const src = this.exceptionThrowSource(details)
131 | if (src) {
132 | filePath = src.filePath
133 | line = src.line
134 | }
135 |
136 | let message
137 | if (typeof failure === 'object') {
138 | message = failure.$.message
139 | if (failure.$?.type) {
140 | message = failure.$.type + ': ' + message
141 | }
142 | }
143 | return {
144 | path: filePath,
145 | line,
146 | details,
147 | message
148 | }
149 | }
150 |
151 | private exceptionThrowSource(stackTrace = ''): {filePath: string; line: number} | undefined {
152 | const lines = stackTrace.split(/\r?\n/)
153 | const re = /^at (.*)\((.*):(\d+)\)$/
154 |
155 | for (const str of lines) {
156 | const match = str.match(re)
157 | if (match !== null) {
158 | const [_, tracePath, fileName, lineStr] = match
159 | const filePath = this.getFilePath(tracePath, fileName)
160 | if (filePath !== undefined) {
161 | const line = parseInt(lineStr)
162 | return {filePath, line}
163 | }
164 | }
165 | }
166 | }
167 |
168 | // Stacktrace in Java doesn't contain full paths to source file.
169 | // There are only package, file name and line.
170 | // Assuming folder structure matches package name (as it should in Java),
171 | // we can try to match tracked file.
172 | private getFilePath(tracePath: string, fileName: string): string | undefined {
173 | // Check if there is any tracked file with given name
174 | const files = this.trackedFiles[fileName]
175 | if (files === undefined) {
176 | return undefined
177 | }
178 |
179 | // Remove class name and method name from trace.
180 | // Take parts until first item with capital letter - package names are lowercase while class name is CamelCase.
181 | const packageParts = tracePath.split(/\./g)
182 | const packageIndex = packageParts.findIndex(part => part[0] <= 'Z')
183 | if (packageIndex !== -1) {
184 | packageParts.splice(packageIndex, packageParts.length - packageIndex)
185 | }
186 |
187 | if (packageParts.length === 0) {
188 | return undefined
189 | }
190 |
191 | // Get right file
192 | // - file name matches
193 | // - parent folders structure must reflect the package name
194 | for (const filePath of files) {
195 | const dirs = path.dirname(filePath).split(/\//g)
196 | if (packageParts.length > dirs.length) {
197 | continue
198 | }
199 | // get only N parent folders, where N = length of package name parts
200 | if (dirs.length > packageParts.length) {
201 | dirs.splice(0, dirs.length - packageParts.length)
202 | }
203 | // check if parent folder structure matches package name
204 | const isMatch = packageParts.every((part, i) => part === dirs[i])
205 | if (isMatch) {
206 | return filePath
207 | }
208 | }
209 |
210 | return undefined
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/parsers/java-junit/java-junit-types.ts:
--------------------------------------------------------------------------------
1 | export interface JunitReport {
2 | testsuites: TestSuites
3 | }
4 |
5 | export interface SingleSuiteReport {
6 | testsuite: TestSuite
7 | }
8 |
9 | export interface TestSuites {
10 | $: {
11 | time: string
12 | }
13 | testsuite?: TestSuite[]
14 | }
15 |
16 | export interface TestSuite {
17 | $: {
18 | name: string
19 | tests: string
20 | errors: string
21 | failures: string
22 | skipped: string
23 | time: string
24 | timestamp?: Date
25 | }
26 | testcase: TestCase[]
27 | }
28 |
29 | export interface TestCase {
30 | $: {
31 | classname: string
32 | file?: string
33 | name: string
34 | time: string
35 | }
36 | failure?: string | Failure[]
37 | error?: string | Failure[]
38 | skipped?: string[]
39 | }
40 |
41 | export interface Failure {
42 | _?: string
43 | $: {
44 | type?: string
45 | message: string
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/parsers/jest-junit/jest-junit-parser.ts:
--------------------------------------------------------------------------------
1 | import {ParseOptions, TestParser} from '../../test-parser'
2 | import {parseStringPromise} from 'xml2js'
3 |
4 | import {JunitReport, TestCase, TestSuite} from './jest-junit-types'
5 | import {getExceptionSource} from '../../utils/node-utils'
6 | import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
7 |
8 | import {
9 | TestExecutionResult,
10 | TestRunResult,
11 | TestSuiteResult,
12 | TestGroupResult,
13 | TestCaseResult,
14 | TestCaseError
15 | } from '../../test-results'
16 |
17 | export class JestJunitParser implements TestParser {
18 | assumedWorkDir: string | undefined
19 |
20 | constructor(readonly options: ParseOptions) {}
21 |
22 | async parse(path: string, content: string): Promise {
23 | const ju = await this.getJunitReport(path, content)
24 | return this.getTestRunResult(path, ju)
25 | }
26 |
27 | private async getJunitReport(path: string, content: string): Promise {
28 | try {
29 | return (await parseStringPromise(content)) as JunitReport
30 | } catch (e) {
31 | throw new Error(`Invalid XML at ${path}\n\n${e}`)
32 | }
33 | }
34 |
35 | private getTestRunResult(path: string, junit: JunitReport): TestRunResult {
36 | const suites =
37 | junit.testsuites.testsuite === undefined
38 | ? []
39 | : junit.testsuites.testsuite.map(ts => {
40 | const name = ts.$.name.trim()
41 | const time = parseFloat(ts.$.time) * 1000
42 | const sr = new TestSuiteResult(name, this.getGroups(ts), time)
43 | return sr
44 | })
45 |
46 | const time = junit.testsuites.$ && parseFloat(junit.testsuites.$.time) * 1000
47 | return new TestRunResult(path, suites, time)
48 | }
49 |
50 | private getGroups(suite: TestSuite): TestGroupResult[] {
51 | const groups: {describe: string; tests: TestCase[]}[] = []
52 | for (const tc of suite.testcase) {
53 | let grp = groups.find(g => g.describe === tc.$.classname)
54 | if (grp === undefined) {
55 | grp = {describe: tc.$.classname, tests: []}
56 | groups.push(grp)
57 | }
58 | grp.tests.push(tc)
59 | }
60 |
61 | return groups.map(grp => {
62 | const tests = grp.tests.map(tc => {
63 | const name = tc.$.name.trim()
64 | const result = this.getTestCaseResult(tc)
65 | const time = parseFloat(tc.$.time) * 1000
66 | const error = this.getTestCaseError(tc)
67 | return new TestCaseResult(name, result, time, error)
68 | })
69 | return new TestGroupResult(grp.describe, tests)
70 | })
71 | }
72 |
73 | private getTestCaseResult(test: TestCase): TestExecutionResult {
74 | if (test.failure || test.error) return 'failed'
75 | if (test.skipped) return 'skipped'
76 | return 'success'
77 | }
78 |
79 | private getTestCaseError(tc: TestCase): TestCaseError | undefined {
80 | if (!this.options.parseErrors || !(tc.failure || tc.error)) {
81 | return undefined
82 | }
83 |
84 | const details = tc.failure
85 | ? typeof tc.failure[0] === 'string'
86 | ? tc.failure[0]
87 | : tc.failure[0]._
88 | : tc.error
89 | ? tc.error[0]
90 | : 'unknown failure'
91 | let path
92 | let line
93 |
94 | const src = getExceptionSource(details, this.options.trackedFiles, file => this.getRelativePath(file))
95 | if (src) {
96 | path = src.path
97 | line = src.line
98 | }
99 |
100 | return {
101 | path,
102 | line,
103 | details
104 | }
105 | }
106 |
107 | private getRelativePath(path: string): string {
108 | path = normalizeFilePath(path)
109 | const workDir = this.getWorkDir(path)
110 | if (workDir !== undefined && path.startsWith(workDir)) {
111 | path = path.substr(workDir.length)
112 | }
113 | return path
114 | }
115 |
116 | private getWorkDir(path: string): string | undefined {
117 | return (
118 | this.options.workDir ??
119 | this.assumedWorkDir ??
120 | (this.assumedWorkDir = getBasePath(path, this.options.trackedFiles))
121 | )
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/parsers/jest-junit/jest-junit-types.ts:
--------------------------------------------------------------------------------
1 | export interface JunitReport {
2 | testsuites: TestSuites
3 | }
4 |
5 | export interface TestSuites {
6 | $: {
7 | time: string
8 | }
9 | testsuite?: TestSuite[]
10 | }
11 |
12 | export interface TestSuite {
13 | $: {
14 | name: string
15 | tests: string
16 | errors: string
17 | failures: string
18 | skipped: string
19 | time: string
20 | timestamp?: Date
21 | }
22 | testcase: TestCase[]
23 | }
24 |
25 | export interface TestCase {
26 | $: {
27 | classname: string
28 | file?: string
29 | name: string
30 | time: string
31 | }
32 | failure?: TestFailure[]
33 | skipped?: string[]
34 | error?: string[]
35 | }
36 |
37 | export type TestFailure =
38 | | string
39 | | {
40 | $: {
41 | type: string
42 | message: string
43 | }
44 | _: string
45 | }
46 |
--------------------------------------------------------------------------------
/src/parsers/mocha-json/mocha-json-parser.ts:
--------------------------------------------------------------------------------
1 | import {ParseOptions, TestParser} from '../../test-parser'
2 | import {
3 | TestCaseError,
4 | TestCaseResult,
5 | TestExecutionResult,
6 | TestGroupResult,
7 | TestRunResult,
8 | TestSuiteResult
9 | } from '../../test-results'
10 | import {getExceptionSource} from '../../utils/node-utils'
11 | import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
12 | import {MochaJson, MochaJsonTest} from './mocha-json-types'
13 |
14 | export class MochaJsonParser implements TestParser {
15 | assumedWorkDir: string | undefined
16 |
17 | constructor(readonly options: ParseOptions) {}
18 |
19 | async parse(path: string, content: string): Promise {
20 | const mocha = this.getMochaJson(path, content)
21 | const result = this.getTestRunResult(path, mocha)
22 | result.sort(true)
23 | return Promise.resolve(result)
24 | }
25 |
26 | private getMochaJson(path: string, content: string): MochaJson {
27 | try {
28 | return JSON.parse(content)
29 | } catch (e) {
30 | throw new Error(`Invalid JSON at ${path}\n\n${e}`)
31 | }
32 | }
33 |
34 | private getTestRunResult(resultsPath: string, mocha: MochaJson): TestRunResult {
35 | const suitesMap: {[path: string]: TestSuiteResult} = {}
36 |
37 | const getSuite = (test: MochaJsonTest): TestSuiteResult => {
38 | const path = this.getRelativePath(test.file)
39 | return suitesMap[path] ?? (suitesMap[path] = new TestSuiteResult(path, []))
40 | }
41 |
42 | for (const test of mocha.passes) {
43 | const suite = getSuite(test)
44 | this.processTest(suite, test, 'success')
45 | }
46 |
47 | for (const test of mocha.failures) {
48 | const suite = getSuite(test)
49 | this.processTest(suite, test, 'failed')
50 | }
51 |
52 | for (const test of mocha.pending) {
53 | const suite = getSuite(test)
54 | this.processTest(suite, test, 'skipped')
55 | }
56 |
57 | const suites = Object.values(suitesMap)
58 | return new TestRunResult(resultsPath, suites, mocha.stats.duration)
59 | }
60 |
61 | private processTest(suite: TestSuiteResult, test: MochaJsonTest, result: TestExecutionResult): void {
62 | const groupName =
63 | test.fullTitle !== test.title
64 | ? test.fullTitle.substr(0, test.fullTitle.length - test.title.length).trimEnd()
65 | : null
66 |
67 | let group = suite.groups.find(grp => grp.name === groupName)
68 | if (group === undefined) {
69 | group = new TestGroupResult(groupName, [])
70 | suite.groups.push(group)
71 | }
72 |
73 | const error = this.getTestCaseError(test)
74 | const testCase = new TestCaseResult(test.title, result, test.duration ?? 0, error)
75 | group.tests.push(testCase)
76 | }
77 |
78 | private getTestCaseError(test: MochaJsonTest): TestCaseError | undefined {
79 | const details = test.err.stack
80 | const message = test.err.message
81 | if (details === undefined) {
82 | return undefined
83 | }
84 |
85 | let path
86 | let line
87 |
88 | const src = getExceptionSource(details, this.options.trackedFiles, file => this.getRelativePath(file))
89 | if (src) {
90 | path = src.path
91 | line = src.line
92 | }
93 |
94 | return {
95 | path,
96 | line,
97 | message,
98 | details
99 | }
100 | }
101 |
102 | private getRelativePath(path: string): string {
103 | path = normalizeFilePath(path)
104 | const workDir = this.getWorkDir(path)
105 | if (workDir !== undefined && path.startsWith(workDir)) {
106 | path = path.substr(workDir.length)
107 | }
108 | return path
109 | }
110 |
111 | private getWorkDir(path: string): string | undefined {
112 | return (
113 | this.options.workDir ??
114 | this.assumedWorkDir ??
115 | (this.assumedWorkDir = getBasePath(path, this.options.trackedFiles))
116 | )
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/parsers/mocha-json/mocha-json-types.ts:
--------------------------------------------------------------------------------
1 | export interface MochaJson {
2 | stats: MochaJsonStats
3 | passes: MochaJsonTest[]
4 | pending: MochaJsonTest[]
5 | failures: MochaJsonTest[]
6 | }
7 |
8 | export interface MochaJsonStats {
9 | duration: number
10 | }
11 |
12 | export interface MochaJsonTest {
13 | title: string
14 | fullTitle: string
15 | file: string
16 | duration?: number
17 | err: MochaJsonTestError
18 | }
19 |
20 | export interface MochaJsonTestError {
21 | stack?: string
22 | message?: string
23 | }
24 |
--------------------------------------------------------------------------------
/src/parsers/mochawesome-json/mochawesome-json-parser.ts:
--------------------------------------------------------------------------------
1 | import {ParseOptions, TestParser} from '../../test-parser'
2 | import {
3 | TestCaseError,
4 | TestCaseResult,
5 | TestExecutionResult,
6 | TestGroupResult,
7 | TestRunResult,
8 | TestSuiteResult
9 | } from '../../test-results'
10 | import {getExceptionSource} from '../../utils/node-utils'
11 | import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
12 | import {MochawesomeJson, MochawesomeJsonSuite, MochawesomeJsonTest} from './mochawesome-json-types'
13 |
14 | export class MochawesomeJsonParser implements TestParser {
15 | assumedWorkDir: string | undefined
16 |
17 | constructor(readonly options: ParseOptions) {}
18 |
19 | async parse(path: string, content: string): Promise {
20 | const mochawesome = this.getMochawesomeJson(path, content)
21 | const result = this.getTestRunResult(path, mochawesome)
22 | result.sort(true)
23 | return Promise.resolve(result)
24 | }
25 |
26 | private getMochawesomeJson(path: string, content: string): MochawesomeJson {
27 | try {
28 | return JSON.parse(content)
29 | } catch (e) {
30 | throw new Error(`Invalid JSON at ${path}\n\n${e}`)
31 | }
32 | }
33 |
34 | private getTestRunResult(resultsPath: string, mochawesome: MochawesomeJson): TestRunResult {
35 | const suitesMap: {[path: string]: TestSuiteResult} = {}
36 |
37 | const results = mochawesome.results
38 |
39 | const getSuite = (fullFile: string): TestSuiteResult => {
40 | const path = this.getRelativePath(fullFile)
41 | return suitesMap[path] ?? (suitesMap[path] = new TestSuiteResult(path, []))
42 | }
43 |
44 | const processPassingTests = (tests: MochawesomeJsonTest[], suiteName: string): void => {
45 | const passingTests = tests?.filter(test => test.pass)
46 |
47 | if (passingTests) {
48 | for (const passingTest of passingTests) {
49 | const suite = getSuite(suiteName)
50 | this.processTest(suite, passingTest, 'success')
51 | }
52 | }
53 | }
54 |
55 | const processFailingTests = (tests: MochawesomeJsonTest[], suiteName: string): void => {
56 | const failingTests = tests?.filter(test => test.fail)
57 |
58 | if (failingTests) {
59 | for (const failingTest of failingTests) {
60 | const suite = getSuite(suiteName)
61 | this.processTest(suite, failingTest, 'failed')
62 | }
63 | }
64 | }
65 |
66 | const processPendingTests = (tests: MochawesomeJsonTest[], suiteName: string): void => {
67 | const pendingTests = tests?.filter(test => test.skipped)
68 |
69 | if (pendingTests) {
70 | for (const pendingTest of pendingTests) {
71 | const suite = getSuite(suiteName)
72 | this.processTest(suite, pendingTest, 'skipped')
73 | }
74 | }
75 | }
76 |
77 | const processAllTests = (tests: MochawesomeJsonTest[], suiteName: string): void => {
78 | processPassingTests(tests, suiteName)
79 | processFailingTests(tests, suiteName)
80 | processPendingTests(tests, suiteName)
81 | }
82 |
83 | // Handle nested suites
84 | const processNestedSuites = (suite: MochawesomeJsonSuite, nestedSuiteIndex: number, suiteName: string): void => {
85 | // Process suite tests
86 | processAllTests(suite.tests, suiteName)
87 |
88 | for (const innerSuite of suite.suites) {
89 | // Process inner suite tests
90 | processAllTests(innerSuite.tests, suiteName)
91 |
92 | if (innerSuite?.suites[nestedSuiteIndex]?.suites.length > 0) {
93 | processNestedSuites(innerSuite, 0, suiteName)
94 | } else {
95 | processAllTests(innerSuite?.suites[nestedSuiteIndex]?.tests, suiteName)
96 | nestedSuiteIndex++
97 |
98 | // TODO - Figure out how to get 1.1.1.1.2 - suites with more than one object in them
99 | }
100 | }
101 | }
102 |
103 | // Process Mochawesome Data
104 | for (const result of results) {
105 | const suites = result?.suites
106 | const filePath = result?.fullFile
107 | const suitelessTests = result?.tests
108 |
109 | // Process tests that aren't in a suite
110 | if (suitelessTests?.length > 0) {
111 | processAllTests(suitelessTests, filePath)
112 | }
113 |
114 | // Process tests that are in a suite
115 | if (suites?.length > 0) {
116 | for (const suite of suites) {
117 | processNestedSuites(suite, 0, filePath ? filePath : suite.title)
118 | }
119 | }
120 | }
121 |
122 | const mappedSuites = Object.values(suitesMap)
123 |
124 | return new TestRunResult(resultsPath, mappedSuites, mochawesome.stats.duration)
125 | }
126 |
127 | private processTest(suite: TestSuiteResult, test: MochawesomeJsonTest, result: TestExecutionResult): void {
128 | const groupName =
129 | test.fullTitle !== test.title
130 | ? test.fullTitle.substr(0, test.fullTitle.length - test.title.length).trimEnd()
131 | : null
132 |
133 | let group = suite.groups.find(grp => grp.name === groupName)
134 | if (group === undefined) {
135 | group = new TestGroupResult(groupName, [])
136 | suite.groups.push(group)
137 | }
138 |
139 | const error = this.getTestCaseError(test)
140 | const testCase = new TestCaseResult(test.title, result, test.duration ?? 0, error)
141 | group.tests.push(testCase)
142 | }
143 |
144 | private getTestCaseError(test: MochawesomeJsonTest): TestCaseError | undefined {
145 | const details = test.err.estack
146 | const message = test.err.message
147 | if (details === undefined) {
148 | return undefined
149 | }
150 |
151 | let path
152 | let line
153 |
154 | const src = getExceptionSource(details, this.options.trackedFiles, file => this.getRelativePath(file))
155 | if (src) {
156 | path = src.path
157 | line = src.line
158 | }
159 |
160 | return {
161 | path,
162 | line,
163 | message,
164 | details
165 | }
166 | }
167 |
168 | private getRelativePath(path: string): string {
169 | path = normalizeFilePath(path)
170 | const workDir = this.getWorkDir(path)
171 | if (workDir !== undefined && path.startsWith(workDir)) {
172 | path = path.substr(workDir.length)
173 | }
174 | return path
175 | }
176 |
177 | private getWorkDir(path: string): string | undefined {
178 | return (
179 | this.options.workDir ??
180 | this.assumedWorkDir ??
181 | (this.assumedWorkDir = getBasePath(path, this.options.trackedFiles))
182 | )
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/parsers/mochawesome-json/mochawesome-json-types.ts:
--------------------------------------------------------------------------------
1 | export interface MochawesomeJson {
2 | stats: MochawesomeJsonStat
3 | results: MochawesomeJsonSuite[]
4 | }
5 |
6 | export interface MochawesomeJsonStat {
7 | duration: number
8 | }
9 |
10 | export interface MochawesomeJsonSuite {
11 | tests: MochawesomeJsonTest[]
12 | suites: MochawesomeJsonSuite[]
13 | fullFile: string
14 | title: string
15 | }
16 |
17 | export interface MochawesomeJsonTest {
18 | title: string
19 | fullTitle: string
20 | duration: number
21 | pass: boolean
22 | fail: boolean
23 | skipped: boolean
24 | err: MochawesomeJsonTestError
25 | }
26 |
27 | export interface MochawesomeJsonTestError {
28 | message: string
29 | estack: string
30 | diff: string | null // TODO - Not sure if string
31 | }
32 |
--------------------------------------------------------------------------------
/src/report/get-annotations.ts:
--------------------------------------------------------------------------------
1 | import {ellipsis, fixEol} from '../utils/markdown-utils'
2 | import {TestRunResult} from '../test-results'
3 | import {getFirstNonEmptyLine} from '../utils/parse-utils'
4 |
5 | type Annotation = {
6 | path: string
7 | start_line: number
8 | end_line: number
9 | start_column?: number
10 | end_column?: number
11 | annotation_level: 'notice' | 'warning' | 'failure'
12 | message: string
13 | title?: string
14 | raw_details?: string
15 | }
16 |
17 | interface TestError {
18 | testRunPaths: string[]
19 | suiteName: string
20 | testName: string
21 | path: string
22 | line: number
23 | message: string
24 | details: string
25 | }
26 |
27 | export function getAnnotations(results: TestRunResult[], maxCount: number): Annotation[] {
28 | if (maxCount === 0) {
29 | return []
30 | }
31 |
32 | // Collect errors from TestRunResults
33 | // Merge duplicates if there are more test results files processed
34 | const errors: TestError[] = []
35 | const mergeDup = results.length > 1
36 | for (const tr of results) {
37 | for (const ts of tr.suites) {
38 | for (const tg of ts.groups) {
39 | for (const tc of tg.tests) {
40 | const err = tc.error
41 | if (err === undefined) {
42 | continue
43 | }
44 | const path = err.path ?? tr.path
45 | const line = err.line ?? 0
46 | if (mergeDup) {
47 | const dup = errors.find(e => path === e.path && line === e.line && err.details === e.details)
48 | if (dup !== undefined) {
49 | dup.testRunPaths.push(tr.path)
50 | continue
51 | }
52 | }
53 |
54 | errors.push({
55 | testRunPaths: [tr.path],
56 | suiteName: ts.name,
57 | testName: tg.name ? `${tg.name} ► ${tc.name}` : tc.name,
58 | details: err.details,
59 | message: err.message ?? getFirstNonEmptyLine(err.details) ?? 'Test failed',
60 | path,
61 | line
62 | })
63 | }
64 | }
65 | }
66 | }
67 |
68 | // Limit number of created annotations
69 | errors.splice(maxCount + 1)
70 |
71 | const annotations = errors.map(e => {
72 | const message = [
73 | 'Failed test found in:',
74 | e.testRunPaths.map(p => ` ${p}`).join('\n'),
75 | 'Error:',
76 | ident(fixEol(e.message), ' ')
77 | ].join('\n')
78 |
79 | return enforceCheckRunLimits({
80 | path: e.path,
81 | start_line: e.line,
82 | end_line: e.line,
83 | annotation_level: 'failure',
84 | title: `${e.suiteName} ► ${e.testName}`,
85 | raw_details: fixEol(e.details),
86 | message
87 | })
88 | })
89 |
90 | return annotations
91 | }
92 |
93 | function enforceCheckRunLimits(err: Annotation): Annotation {
94 | err.title = ellipsis(err.title || '', 255)
95 | err.message = ellipsis(err.message, 65535)
96 | if (err.raw_details) {
97 | err.raw_details = ellipsis(err.raw_details, 65535)
98 | }
99 | return err
100 | }
101 |
102 | function ident(text: string, prefix: string): string {
103 | return text
104 | .split(/\n/g)
105 | .map(line => prefix + line)
106 | .join('\n')
107 | }
108 |
--------------------------------------------------------------------------------
/src/test-parser.ts:
--------------------------------------------------------------------------------
1 | import {TestRunResult} from './test-results'
2 |
3 | export interface ParseOptions {
4 | parseErrors: boolean
5 | workDir?: string
6 | trackedFiles: string[]
7 | }
8 |
9 | export interface TestParser {
10 | parse(path: string, content: string): Promise
11 | }
12 |
--------------------------------------------------------------------------------
/src/test-results.ts:
--------------------------------------------------------------------------------
1 | export class TestRunResult {
2 | constructor(
3 | readonly path: string,
4 | readonly suites: TestSuiteResult[],
5 | private totalTime?: number
6 | ) {}
7 |
8 | get tests(): number {
9 | return this.suites.reduce((sum, g) => sum + g.tests, 0)
10 | }
11 |
12 | get passed(): number {
13 | return this.suites.reduce((sum, g) => sum + g.passed, 0)
14 | }
15 | get failed(): number {
16 | return this.suites.reduce((sum, g) => sum + g.failed, 0)
17 | }
18 | get skipped(): number {
19 | return this.suites.reduce((sum, g) => sum + g.skipped, 0)
20 | }
21 |
22 | get time(): number {
23 | return this.totalTime ?? this.suites.reduce((sum, g) => sum + g.time, 0)
24 | }
25 |
26 | get result(): TestExecutionResult {
27 | return this.suites.some(t => t.result === 'failed') ? 'failed' : 'success'
28 | }
29 |
30 | get failedSuites(): TestSuiteResult[] {
31 | return this.suites.filter(s => s.result === 'failed')
32 | }
33 |
34 | sort(deep: boolean): void {
35 | this.suites.sort((a, b) => a.name.localeCompare(b.name))
36 | if (deep) {
37 | for (const suite of this.suites) {
38 | suite.sort(deep)
39 | }
40 | }
41 | }
42 | }
43 |
44 | export class TestSuiteResult {
45 | constructor(
46 | readonly name: string,
47 | readonly groups: TestGroupResult[],
48 | private totalTime?: number
49 | ) {}
50 |
51 | get tests(): number {
52 | return this.groups.reduce((sum, g) => sum + g.tests.length, 0)
53 | }
54 |
55 | get passed(): number {
56 | return this.groups.reduce((sum, g) => sum + g.passed, 0)
57 | }
58 | get failed(): number {
59 | return this.groups.reduce((sum, g) => sum + g.failed, 0)
60 | }
61 | get skipped(): number {
62 | return this.groups.reduce((sum, g) => sum + g.skipped, 0)
63 | }
64 | get time(): number {
65 | return this.totalTime ?? this.groups.reduce((sum, g) => sum + g.time, 0)
66 | }
67 |
68 | get result(): TestExecutionResult {
69 | return this.groups.some(t => t.result === 'failed') ? 'failed' : 'success'
70 | }
71 |
72 | get failedGroups(): TestGroupResult[] {
73 | return this.groups.filter(grp => grp.result === 'failed')
74 | }
75 |
76 | sort(deep: boolean): void {
77 | this.groups.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''))
78 | if (deep) {
79 | for (const grp of this.groups) {
80 | grp.sort()
81 | }
82 | }
83 | }
84 | }
85 |
86 | export class TestGroupResult {
87 | constructor(
88 | readonly name: string | undefined | null,
89 | readonly tests: TestCaseResult[]
90 | ) {}
91 |
92 | get passed(): number {
93 | return this.tests.reduce((sum, t) => (t.result === 'success' ? sum + 1 : sum), 0)
94 | }
95 | get failed(): number {
96 | return this.tests.reduce((sum, t) => (t.result === 'failed' ? sum + 1 : sum), 0)
97 | }
98 | get skipped(): number {
99 | return this.tests.reduce((sum, t) => (t.result === 'skipped' ? sum + 1 : sum), 0)
100 | }
101 | get time(): number {
102 | return this.tests.reduce((sum, t) => sum + t.time, 0)
103 | }
104 |
105 | get result(): TestExecutionResult {
106 | return this.tests.some(t => t.result === 'failed') ? 'failed' : 'success'
107 | }
108 |
109 | get failedTests(): TestCaseResult[] {
110 | return this.tests.filter(tc => tc.result === 'failed')
111 | }
112 |
113 | sort(): void {
114 | this.tests.sort((a, b) => a.name.localeCompare(b.name))
115 | }
116 | }
117 |
118 | export class TestCaseResult {
119 | constructor(
120 | readonly name: string,
121 | readonly result: TestExecutionResult,
122 | readonly time: number,
123 | readonly error?: TestCaseError
124 | ) {}
125 | }
126 |
127 | export type TestExecutionResult = 'success' | 'skipped' | 'failed' | undefined
128 |
129 | export interface TestCaseError {
130 | path?: string
131 | line?: number
132 | message?: string
133 | details: string
134 | }
135 |
--------------------------------------------------------------------------------
/src/utils/constants.ts:
--------------------------------------------------------------------------------
1 | export enum Outputs {
2 | runHtmlUrl = 'runHtmlUrl'
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/exec.ts:
--------------------------------------------------------------------------------
1 | import {exec as execImpl, ExecOptions} from '@actions/exec'
2 |
3 | // Wraps original exec() function
4 | // Returns exit code and whole stdout/stderr
5 | export default async function exec(commandLine: string, args?: string[], options?: ExecOptions): Promise {
6 | options = options || {}
7 | let stdout = ''
8 | let stderr = ''
9 | options.listeners = {
10 | stdout: (data: Buffer) => (stdout += data.toString()),
11 | stderr: (data: Buffer) => (stderr += data.toString())
12 | }
13 | const code = await execImpl(commandLine, args, options)
14 | return {code, stdout, stderr}
15 | }
16 |
17 | export interface ExecResult {
18 | code: number
19 | stdout: string
20 | stderr: string
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/git.ts:
--------------------------------------------------------------------------------
1 | import * as core from '@actions/core'
2 | import exec from './exec'
3 |
4 | export async function listFiles(): Promise {
5 | core.startGroup('Listing all files tracked by git')
6 | let output = ''
7 | try {
8 | output = (await exec('git', ['ls-files', '-z'])).stdout
9 | } finally {
10 | fixStdOutNullTermination()
11 | core.endGroup()
12 | }
13 |
14 | return output.split('\u0000').filter(s => s.length > 0)
15 | }
16 |
17 | function fixStdOutNullTermination(): void {
18 | // Previous command uses NULL as delimiters and output is printed to stdout.
19 | // We have to make sure next thing written to stdout will start on new line.
20 | // Otherwise things like ::set-output wouldn't work.
21 | core.info('')
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils/github-utils.ts:
--------------------------------------------------------------------------------
1 | import {createWriteStream} from 'fs'
2 | import * as core from '@actions/core'
3 | import * as github from '@actions/github'
4 | import {GitHub} from '@actions/github/lib/utils'
5 | import type {PullRequest} from '@octokit/webhooks-types'
6 | import * as stream from 'stream'
7 | import {promisify} from 'util'
8 | import got from 'got'
9 | const asyncStream = promisify(stream.pipeline)
10 |
11 | export function getCheckRunContext(): {sha: string; runId: number} {
12 | if (github.context.eventName === 'workflow_run') {
13 | core.info('Action was triggered by workflow_run: using SHA and RUN_ID from triggering workflow')
14 | const event = github.context.payload
15 | if (!event.workflow_run) {
16 | throw new Error("Event of type 'workflow_run' is missing 'workflow_run' field")
17 | }
18 | return {
19 | sha: event.workflow_run.head_commit.id,
20 | runId: event.workflow_run.id
21 | }
22 | }
23 |
24 | const runId = github.context.runId
25 | if (github.context.payload.pull_request) {
26 | core.info(`Action was triggered by ${github.context.eventName}: using SHA from head of source branch`)
27 | const pr = github.context.payload.pull_request as PullRequest
28 | return {sha: pr.head.sha, runId}
29 | }
30 |
31 | return {sha: github.context.sha, runId}
32 | }
33 |
34 | export async function downloadArtifact(
35 | octokit: InstanceType,
36 | artifactId: number,
37 | fileName: string,
38 | token: string
39 | ): Promise {
40 | core.startGroup(`Downloading artifact ${fileName}`)
41 | try {
42 | core.info(`Artifact ID: ${artifactId}`)
43 |
44 | const req = octokit.rest.actions.downloadArtifact.endpoint({
45 | ...github.context.repo,
46 | artifact_id: artifactId,
47 | archive_format: 'zip'
48 | })
49 |
50 | const headers = {
51 | Authorization: `Bearer ${token}`
52 | }
53 |
54 | core.info(`Request URL: ${req.url}`)
55 | const resp = await got(req.url, {
56 | headers,
57 | followRedirect: false
58 | })
59 |
60 | core.info(`Fetch artifact URL: ${resp.statusCode} ${resp.statusMessage}`)
61 | if (resp.statusCode !== 302) {
62 | throw new Error('Fetch artifact URL failed: received unexpected status code')
63 | }
64 |
65 | const url = resp.headers.location
66 | if (url === undefined) {
67 | const receivedHeaders = Object.keys(resp.headers)
68 | core.info(`Received headers: ${receivedHeaders.join(', ')}`)
69 | throw new Error('Location header was not found in API response')
70 | }
71 | if (typeof url !== 'string') {
72 | throw new Error(`Location header has unexpected value: ${url}`)
73 | }
74 |
75 | const downloadStream = got.stream(url)
76 | const fileWriterStream = createWriteStream(fileName)
77 |
78 | core.info(`Downloading ${url}`)
79 | downloadStream.on('downloadProgress', ({transferred}) => {
80 | core.info(`Progress: ${transferred} B`)
81 | })
82 | await asyncStream(downloadStream, fileWriterStream)
83 | } finally {
84 | core.endGroup()
85 | }
86 | }
87 |
88 | export async function listFiles(octokit: InstanceType, sha: string): Promise {
89 | core.startGroup('Fetching list of tracked files from GitHub')
90 | try {
91 | const commit = await octokit.rest.git.getCommit({
92 | commit_sha: sha,
93 | ...github.context.repo
94 | })
95 | const files = await listGitTree(octokit, commit.data.tree.sha, '')
96 | return files
97 | } finally {
98 | core.endGroup()
99 | }
100 | }
101 |
102 | async function listGitTree(octokit: InstanceType, sha: string, path: string): Promise {
103 | const pathLog = path ? ` at ${path}` : ''
104 | core.info(`Fetching tree ${sha}${pathLog}`)
105 | let truncated = false
106 | let tree = await octokit.rest.git.getTree({
107 | recursive: 'true',
108 | tree_sha: sha,
109 | ...github.context.repo
110 | })
111 |
112 | if (tree.data.truncated) {
113 | truncated = true
114 | tree = await octokit.rest.git.getTree({
115 | tree_sha: sha,
116 | ...github.context.repo
117 | })
118 | }
119 |
120 | const result: string[] = []
121 | for (const tr of tree.data.tree) {
122 | const file = `${path}${tr.path}`
123 | if (tr.type === 'blob') {
124 | result.push(file)
125 | } else if (tr.type === 'tree' && truncated) {
126 | const files = await listGitTree(octokit, tr.sha as string, `${file}/`)
127 | result.push(...files)
128 | }
129 | }
130 |
131 | return result
132 | }
133 |
--------------------------------------------------------------------------------
/src/utils/markdown-utils.ts:
--------------------------------------------------------------------------------
1 | export enum Align {
2 | Left = ':---',
3 | Center = ':---:',
4 | Right = '---:',
5 | None = '---'
6 | }
7 |
8 | export const Icon = {
9 | skip: '✖️', // ':heavy_multiplication_x:'
10 | success: '✔️', // ':heavy_check_mark:'
11 | fail: '❌' // ':x:'
12 | }
13 |
14 | export function link(title: string, address: string): string {
15 | return `[${title}](${address})`
16 | }
17 |
18 | type ToString = string | number | boolean | Date
19 | export function table(headers: ToString[], align: ToString[], ...rows: ToString[][]): string {
20 | const headerRow = `|${headers.map(tableEscape).join('|')}|`
21 | const alignRow = `|${align.join('|')}|`
22 | const contentRows = rows.map(row => `|${row.map(tableEscape).join('|')}|`).join('\n')
23 | return [headerRow, alignRow, contentRows].join('\n')
24 | }
25 |
26 | export function tableEscape(content: ToString): string {
27 | return content.toString().replace('|', '\\|')
28 | }
29 |
30 | export function fixEol(text?: string): string {
31 | return text?.replace(/\r/g, '') ?? ''
32 | }
33 |
34 | export function ellipsis(text: string, maxLength: number): string {
35 | if (text.length <= maxLength) {
36 | return text
37 | }
38 |
39 | return text.substr(0, maxLength - 3) + '...'
40 | }
41 |
42 | export function formatTime(ms: number): string {
43 | if (ms > 1000) {
44 | return `${Math.round(ms / 1000)}s`
45 | }
46 |
47 | return `${Math.round(ms)}ms`
48 | }
49 |
--------------------------------------------------------------------------------
/src/utils/node-utils.ts:
--------------------------------------------------------------------------------
1 | import {normalizeFilePath} from './path-utils'
2 |
3 | export function getExceptionSource(
4 | stackTrace: string,
5 | trackedFiles: string[],
6 | getRelativePath: (str: string) => string
7 | ): {path: string; line: number} | undefined {
8 | const lines = stackTrace.split(/\r?\n/)
9 | const re = /(?:\(| › )(.*):(\d+):\d+(?:\)$| › )/
10 |
11 | for (const str of lines) {
12 | const match = str.match(re)
13 | if (match !== null) {
14 | const [_, fileStr, lineStr] = match
15 | const filePath = normalizeFilePath(fileStr)
16 | if (filePath.startsWith('internal/') || filePath.includes('/node_modules/')) {
17 | continue
18 | }
19 | const path = getRelativePath(filePath)
20 | if (!path) {
21 | continue
22 | }
23 | if (trackedFiles.includes(path)) {
24 | const line = parseInt(lineStr)
25 |
26 | return {path, line}
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/parse-utils.ts:
--------------------------------------------------------------------------------
1 | export function parseNetDuration(str: string): number {
2 | const durationRe = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)$/
3 | const durationMatch = str.match(durationRe)
4 | if (durationMatch === null) {
5 | throw new Error(`Invalid format: "${str}" is not NET duration`)
6 | }
7 |
8 | const [_, hourStr, minStr, secStr] = durationMatch
9 | return (parseInt(hourStr) * 3600 + parseInt(minStr) * 60 + parseFloat(secStr)) * 1000
10 | }
11 |
12 | export function parseIsoDate(str: string): Date {
13 | const isoDateRe = /^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)$/
14 | if (str === undefined || !isoDateRe.test(str)) {
15 | throw new Error(`Invalid format: "${str}" is not ISO date`)
16 | }
17 |
18 | return new Date(str)
19 | }
20 |
21 | export function getFirstNonEmptyLine(stackTrace: string): string | undefined {
22 | const lines = stackTrace.split(/\r?\n/g)
23 | return lines.find(str => !/^\s*$/.test(str))
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/path-utils.ts:
--------------------------------------------------------------------------------
1 | export function normalizeDirPath(path: string, addTrailingSlash: boolean): string {
2 | if (!path) {
3 | return path
4 | }
5 |
6 | path = normalizeFilePath(path)
7 | if (addTrailingSlash && !path.endsWith('/')) {
8 | path += '/'
9 | }
10 | return path
11 | }
12 |
13 | export function normalizeFilePath(path: string): string {
14 | if (!path) {
15 | return path
16 | }
17 |
18 | return path.trim().replace(/\\/g, '/')
19 | }
20 |
21 | export function getBasePath(path: string, trackedFiles: string[]): string | undefined {
22 | if (trackedFiles.includes(path)) {
23 | return ''
24 | }
25 |
26 | let max = ''
27 | for (const file of trackedFiles) {
28 | if (path.endsWith(file) && file.length > max.length) {
29 | max = file
30 | }
31 | }
32 |
33 | if (max === '') {
34 | return undefined
35 | }
36 |
37 | const base = path.substr(0, path.length - max.length)
38 | return base
39 | }
40 |
--------------------------------------------------------------------------------
/src/utils/slugger.ts:
--------------------------------------------------------------------------------
1 | // Returns HTML element id and href link usable as manual anchor links
2 | // This is needed because Github in check run summary doesn't automatically
3 | // create links out of headings as it normally does for other markdown content
4 | export function slug(name: string): {id: string; link: string} {
5 | const slugId = name
6 | .trim()
7 | .replace(/_/g, '')
8 | .replace(/[./\\]/g, '-')
9 | .replace(/[^\w-]/g, '')
10 |
11 | const id = `user-content-${slugId}`
12 | const link = `#${slugId}`
13 | return {id, link}
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
4 | "lib": ["ES2019"],
5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
6 | "outDir": "./lib", /* Redirect output structure to the directory. */
7 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
8 | "strict": true, /* Enable all strict type-checking options. */
9 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
10 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
11 | },
12 | "exclude": ["node_modules", "**/*.test.ts"]
13 | }
14 |
--------------------------------------------------------------------------------