mirror of
https://github.com/dorny/test-reporter.git
synced 2025-12-16 06:17:10 +01:00
Merge pull request #47 from dorny/improvements-and-fixes
Improvements and fixes for v1
This commit is contained in:
commit
10ba3946cb
32 changed files with 56323 additions and 1413 deletions
22
README.md
22
README.md
|
|
@ -49,9 +49,9 @@ jobs:
|
||||||
# Name of the Check Run which will be created
|
# Name of the Check Run which will be created
|
||||||
name: ''
|
name: ''
|
||||||
|
|
||||||
# Path to test report
|
# Coma separated list of paths to test reports
|
||||||
# Supports wildcards via [fast-glob](https://github.com/mrmlnc/fast-glob)
|
# Supports wildcards via [fast-glob](https://github.com/mrmlnc/fast-glob)
|
||||||
# Path may match multiple result files of same format
|
# All matched result files must be of same format
|
||||||
path: ''
|
path: ''
|
||||||
|
|
||||||
# Format of test report. Supported options:
|
# Format of test report. Supported options:
|
||||||
|
|
@ -61,8 +61,20 @@ jobs:
|
||||||
# jest-junit
|
# jest-junit
|
||||||
reporter: ''
|
reporter: ''
|
||||||
|
|
||||||
# Enables code annotations with error message and stack trace captured during test execution
|
# Limits which test suites are listed:
|
||||||
annotations: 'true'
|
# all
|
||||||
|
# failed
|
||||||
|
list-suites: 'all'
|
||||||
|
|
||||||
|
# Limits which test cases are listed:
|
||||||
|
# all
|
||||||
|
# failed
|
||||||
|
# none
|
||||||
|
list-tests: 'all'
|
||||||
|
|
||||||
|
# Limits number of created annotations with error message and stack trace captured during test execution.
|
||||||
|
# Must be less or equal to 50.
|
||||||
|
max-annotations: '10'
|
||||||
|
|
||||||
# Set action as failed if test report contain any failed test
|
# Set action as failed if test report contain any failed test
|
||||||
fail-on-error: 'true'
|
fail-on-error: 'true'
|
||||||
|
|
@ -109,7 +121,7 @@ Or you can configure TRX test output in `*.csproj` or `Directory.Build.props`:
|
||||||
```xml
|
```xml
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<VSTestLogger>trx%3bLogFileName=$(MSBuildProjectName).trx</VSTestLogger>
|
<VSTestLogger>trx%3bLogFileName=$(MSBuildProjectName).trx</VSTestLogger>
|
||||||
<VSTestResultsDirectory>$(MSBuildThisFileDirectory)/reports</VSTestResultsDirectory>
|
<VSTestResultsDirectory>$(MSBuildThisFileDirectory)/TestResults/$(TargetFramework)</VSTestResultsDirectory>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,32 @@
|
||||||

|

|
||||||
|
## <a id="user-content-r0" href="#r0">fixtures/dart-json.json</a> ❌
|
||||||
|
**6** tests were completed in **3.760s** with **1** passed, **4** failed and **1** skipped.
|
||||||
|
|Test suite|Passed|Failed|Skipped|Time|
|
||||||
|
|:---|---:|---:|---:|---:|
|
||||||
|
|[test\main_test.dart](#r0s0)|1✔️|3❌||74ms|
|
||||||
|
|[test\second_test.dart](#r0s1)||1❌|1✖️|51ms|
|
||||||
|
### <a id="user-content-r0s0" href="#r0s0">test\main_test.dart</a> ❌
|
||||||
|
**4** tests were completed in **74ms** with **1** passed, **3** failed and **0** skipped.
|
||||||
|
|
||||||
### fixtures/dart-json.json
|
**Test 1**
|
||||||
|
|Result|Test|Time|
|
||||||
|
|:---:|:---|---:|
|
||||||
|
|✔️|Test 1 Passing test|36ms|
|
||||||
|
|
||||||
**6** tests were completed in **3.760s** with **1** passed, **1** skipped and **4** failed.
|
**Test 1 Test 1.1**
|
||||||
|
|Result|Test|Time|
|
||||||
|
|:---:|:---|---:|
|
||||||
|
|❌|Test 1 Test 1.1 Failing test|20ms|
|
||||||
|
|❌|Test 1 Test 1.1 Exception in target unit|6ms|
|
||||||
|
|
||||||
| Result | Suite | Tests | Time | Passed ✔️ | Skipped ✖️ | Failed ❌ |
|
**Test 2**
|
||||||
| :---: | :--- | ---: | ---: | ---: | ---: | ---: |
|
|Result|Test|Time|
|
||||||
| ❌ | [test\main_test.dart](#ts-0-test-maintest-dart) | 4 | 74ms | 1 | 0 | 3 |
|
|:---:|:---|---:|
|
||||||
| ❌ | [test\second_test.dart](#ts-1-test-secondtest-dart) | 2 | 51ms | 0 | 1 | 1 |
|
|❌|Test 2 Exception in test|12ms|
|
||||||
|
### <a id="user-content-r0s1" href="#r0s1">test\second_test.dart</a> ❌
|
||||||
|
**2** tests were completed in **51ms** with **0** passed, **1** failed and **1** skipped.
|
||||||
|
|
||||||
# Test Suites
|
|Result|Test|Time|
|
||||||
|
|:---:|:---|---:|
|
||||||
## <a id="user-content-ts-0-test-maintest-dart" href="#ts-0-test-maintest-dart">test\main_test.dart</a> ❌
|
|❌|Timeout test|37ms|
|
||||||
|
|✖️|Skipped test|14ms|
|
||||||
### Test 1
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ✔️ | Test 1 Passing test | 36ms |
|
|
||||||
|
|
||||||
### Test 1 Test 1.1
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Test 1 Test 1.1 Failing test | 20ms |
|
|
||||||
| ❌ | Test 1 Test 1.1 Exception in target unit | 6ms |
|
|
||||||
|
|
||||||
### Test 2
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Test 2 Exception in test | 12ms |
|
|
||||||
|
|
||||||
## <a id="user-content-ts-1-test-secondtest-dart" href="#ts-1-test-secondtest-dart">test\second_test.dart</a> ❌
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Timeout test | 37ms |
|
|
||||||
| ✖️ | Skipped test | 14ms |
|
|
||||||
|
|
@ -1,23 +1,18 @@
|
||||||

|

|
||||||
|
## <a id="user-content-r0" href="#r0">fixtures/dotnet-trx.trx</a> ❌
|
||||||
|
**7** tests were completed in **1.061s** with **3** passed, **3** failed and **1** skipped.
|
||||||
|
|Test suite|Passed|Failed|Skipped|Time|
|
||||||
|
|:---|---:|---:|---:|---:|
|
||||||
|
|[DotnetTests.XUnitTests.CalculatorTests](#r0s0)|3✔️|3❌|1✖️|110ms|
|
||||||
|
### <a id="user-content-r0s0" href="#r0s0">DotnetTests.XUnitTests.CalculatorTests</a> ❌
|
||||||
|
**7** tests were completed in **110ms** with **3** passed, **3** failed and **1** skipped.
|
||||||
|
|
||||||
### fixtures/dotnet-trx.trx
|
|Result|Test|Time|
|
||||||
|
|:---:|:---|---:|
|
||||||
**7** tests were completed in **1.061s** with **3** passed, **1** skipped and **3** failed.
|
|❌|Exception_In_TargetTest|0ms|
|
||||||
|
|❌|Exception_In_Test|2ms|
|
||||||
| Result | Suite | Tests | Time | Passed ✔️ | Skipped ✖️ | Failed ❌ |
|
|❌|Failing_Test|3ms|
|
||||||
| :---: | :--- | ---: | ---: | ---: | ---: | ---: |
|
|✔️|Passing_Test|0ms|
|
||||||
| ❌ | [DotnetTests.XUnitTests.CalculatorTests](#ts-0-DotnetTests-XUnitTests-CalculatorTests) | 7 | 109.5761ms | 3 | 1 | 3 |
|
|✔️|Passing_Test_With_Name|0ms|
|
||||||
|
|✖️|Skipped_Test|1ms|
|
||||||
# Test Suites
|
|✔️|Timeout_Test|102ms|
|
||||||
|
|
||||||
## <a id="user-content-ts-0-DotnetTests-XUnitTests-CalculatorTests" href="#ts-0-DotnetTests-XUnitTests-CalculatorTests">DotnetTests.XUnitTests.CalculatorTests</a> ❌
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Exception_In_TargetTest | 0.4975ms |
|
|
||||||
| ❌ | Exception_In_Test | 2.2728ms |
|
|
||||||
| ❌ | Failing_Test | 3.2953ms |
|
|
||||||
| ✔️ | Passing_Test | 0.1254ms |
|
|
||||||
| ✔️ | Passing_Test_With_Name | 0.103ms |
|
|
||||||
| ✖️ | Skipped_Test | 1ms |
|
|
||||||
| ✔️ | Timeout_Test | 102.2821ms |
|
|
||||||
67
__tests__/__outputs__/fluent-validation-test-results.md
Normal file
67
__tests__/__outputs__/fluent-validation-test-results.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|

|
||||||
|
## <a id="user-content-r0" href="#r0">fixtures/external/FluentValidation.Tests.trx</a> ✔️
|
||||||
|
**804** tests were completed in **4.480s** with **803** passed, **0** failed and **1** skipped.
|
||||||
|
|Test suite|Passed|Failed|Skipped|Time|
|
||||||
|
|:---|---:|---:|---:|---:|
|
||||||
|
|FluentValidation.Tests.AbstractValidatorTester|35✔️|||12ms|
|
||||||
|
|FluentValidation.Tests.AccessorCacheTests|4✔️||1✖️|4ms|
|
||||||
|
|FluentValidation.Tests.AssemblyScannerTester|2✔️|||2ms|
|
||||||
|
|FluentValidation.Tests.CascadingFailuresTester|38✔️|||23ms|
|
||||||
|
|FluentValidation.Tests.ChainedValidationTester|13✔️|||6ms|
|
||||||
|
|FluentValidation.Tests.ChainingValidatorsTester|3✔️|||1ms|
|
||||||
|
|FluentValidation.Tests.ChildRulesTests|2✔️|||7ms|
|
||||||
|
|FluentValidation.Tests.CollectionValidatorWithParentTests|16✔️|||13ms|
|
||||||
|
|FluentValidation.Tests.ComplexValidationTester|17✔️|||26ms|
|
||||||
|
|FluentValidation.Tests.ConditionTests|18✔️|||9ms|
|
||||||
|
|FluentValidation.Tests.CreditCardValidatorTests|2✔️|||2ms|
|
||||||
|
|FluentValidation.Tests.CustomFailureActionTester|3✔️|||1ms|
|
||||||
|
|FluentValidation.Tests.CustomMessageFormatTester|6✔️|||3ms|
|
||||||
|
|FluentValidation.Tests.CustomValidatorTester|10✔️|||6ms|
|
||||||
|
|FluentValidation.Tests.DefaultValidatorExtensionTester|30✔️|||38ms|
|
||||||
|
|FluentValidation.Tests.EmailValidatorTests|36✔️|||18ms|
|
||||||
|
|FluentValidation.Tests.EmptyTester|9✔️|||5ms|
|
||||||
|
|FluentValidation.Tests.EnumValidatorTests|12✔️|||24ms|
|
||||||
|
|FluentValidation.Tests.EqualValidatorTests|10✔️|||3ms|
|
||||||
|
|FluentValidation.Tests.ExactLengthValidatorTester|6✔️|||2ms|
|
||||||
|
|FluentValidation.Tests.ExclusiveBetweenValidatorTests|19✔️|||6ms|
|
||||||
|
|FluentValidation.Tests.ExtensionTester|4✔️|||1ms|
|
||||||
|
|FluentValidation.Tests.ForEachRuleTests|34✔️|||47ms|
|
||||||
|
|FluentValidation.Tests.GreaterThanOrEqualToValidatorTester|14✔️|||5ms|
|
||||||
|
|FluentValidation.Tests.GreaterThanValidatorTester|13✔️|||4ms|
|
||||||
|
|FluentValidation.Tests.InclusiveBetweenValidatorTests|18✔️|||4ms|
|
||||||
|
|FluentValidation.Tests.InheritanceValidatorTest|11✔️|||18ms|
|
||||||
|
|FluentValidation.Tests.InlineValidatorTester|1✔️|||2ms|
|
||||||
|
|FluentValidation.Tests.LanguageManagerTests|21✔️|||28ms|
|
||||||
|
|FluentValidation.Tests.LengthValidatorTests|16✔️|||17ms|
|
||||||
|
|FluentValidation.Tests.LessThanOrEqualToValidatorTester|13✔️|||4ms|
|
||||||
|
|FluentValidation.Tests.LessThanValidatorTester|16✔️|||6ms|
|
||||||
|
|FluentValidation.Tests.LocalisedMessagesTester|6✔️|||3ms|
|
||||||
|
|FluentValidation.Tests.LocalisedNameTester|2✔️|||1ms|
|
||||||
|
|FluentValidation.Tests.MemberAccessorTests|9✔️|||5ms|
|
||||||
|
|FluentValidation.Tests.MessageFormatterTests|10✔️|||2ms|
|
||||||
|
|FluentValidation.Tests.ModelLevelValidatorTests|2✔️|||1ms|
|
||||||
|
|FluentValidation.Tests.NameResolutionPluggabilityTester|3✔️|||2ms|
|
||||||
|
|FluentValidation.Tests.NotEmptyTester|10✔️|||7ms|
|
||||||
|
|FluentValidation.Tests.NotEqualValidatorTests|11✔️|||7ms|
|
||||||
|
|FluentValidation.Tests.NotNullTester|5✔️|||1ms|
|
||||||
|
|FluentValidation.Tests.NullTester|5✔️|||2ms|
|
||||||
|
|FluentValidation.Tests.OnFailureTests|10✔️|||8ms|
|
||||||
|
|FluentValidation.Tests.PredicateValidatorTester|5✔️|||2ms|
|
||||||
|
|FluentValidation.Tests.PropertyChainTests|7✔️|||1ms|
|
||||||
|
|FluentValidation.Tests.RegularExpressionValidatorTests|15✔️|||6ms|
|
||||||
|
|FluentValidation.Tests.RuleBuilderTests|29✔️|||96ms|
|
||||||
|
|FluentValidation.Tests.RuleDependencyTests|14✔️|||2.511s|
|
||||||
|
|FluentValidation.Tests.RulesetTests|21✔️|||14ms|
|
||||||
|
|FluentValidation.Tests.ScalePrecisionValidatorTests|6✔️|||4ms|
|
||||||
|
|FluentValidation.Tests.SharedConditionTests|42✔️|||42ms|
|
||||||
|
|FluentValidation.Tests.StandalonePropertyValidationTester|1✔️|||0ms|
|
||||||
|
|FluentValidation.Tests.StringEnumValidatorTests|10✔️|||5ms|
|
||||||
|
|FluentValidation.Tests.TrackingCollectionTests|3✔️|||2ms|
|
||||||
|
|FluentValidation.Tests.TransformTests|4✔️|||3ms|
|
||||||
|
|FluentValidation.Tests.UserSeverityTester|7✔️|||3ms|
|
||||||
|
|FluentValidation.Tests.UserStateTester|4✔️|||3ms|
|
||||||
|
|FluentValidation.Tests.ValidateAndThrowTester|14✔️|||25ms|
|
||||||
|
|FluentValidation.Tests.ValidationResultTests|8✔️|||8ms|
|
||||||
|
|FluentValidation.Tests.ValidatorDescriptorTester|5✔️|||1ms|
|
||||||
|
|FluentValidation.Tests.ValidatorSelectorTests|10✔️|||9ms|
|
||||||
|
|FluentValidation.Tests.ValidatorTesterTester|73✔️|||74ms|
|
||||||
|
|
@ -1,40 +1,32 @@
|
||||||

|

|
||||||
|
## <a id="user-content-r0" href="#r0">fixtures/jest-junit.xml</a> ❌
|
||||||
|
**6** tests were completed in **1.360s** with **1** passed, **4** failed and **1** skipped.
|
||||||
|
|Test suite|Passed|Failed|Skipped|Time|
|
||||||
|
|:---|---:|---:|---:|---:|
|
||||||
|
|[__tests__\main.test.js](#r0s0)|1✔️|3❌||486ms|
|
||||||
|
|[__tests__\second.test.js](#r0s1)||1❌|1✖️|82ms|
|
||||||
|
### <a id="user-content-r0s0" href="#r0s0">__tests__\main.test.js</a> ❌
|
||||||
|
**4** tests were completed in **486ms** with **1** passed, **3** failed and **0** skipped.
|
||||||
|
|
||||||
### fixtures/jest-junit.xml
|
**Test 1**
|
||||||
|
|Result|Test|Time|
|
||||||
|
|:---:|:---|---:|
|
||||||
|
|✔️|Passing test|1ms|
|
||||||
|
|
||||||
**6** tests were completed in **1.360s** with **1** passed, **1** skipped and **4** failed.
|
**Test 1 › Test 1.1**
|
||||||
|
|Result|Test|Time|
|
||||||
|
|:---:|:---|---:|
|
||||||
|
|❌|Failing test|2ms|
|
||||||
|
|❌|Exception in target unit|0ms|
|
||||||
|
|
||||||
| Result | Suite | Tests | Time | Passed ✔️ | Skipped ✖️ | Failed ❌ |
|
**Test 2**
|
||||||
| :---: | :--- | ---: | ---: | ---: | ---: | ---: |
|
|Result|Test|Time|
|
||||||
| ❌ | [__tests__\main.test.js](#ts-0-tests-main-test-js) | 4 | 486ms | 1 | 0 | 3 |
|
|:---:|:---|---:|
|
||||||
| ❌ | [__tests__\second.test.js](#ts-1-tests-second-test-js) | 2 | 82ms | 0 | 1 | 1 |
|
|❌|Exception in test|0ms|
|
||||||
|
### <a id="user-content-r0s1" href="#r0s1">__tests__\second.test.js</a> ❌
|
||||||
|
**2** tests were completed in **82ms** with **0** passed, **1** failed and **1** skipped.
|
||||||
|
|
||||||
# Test Suites
|
|Result|Test|Time|
|
||||||
|
|:---:|:---|---:|
|
||||||
## <a id="user-content-ts-0-tests-main-test-js" href="#ts-0-tests-main-test-js">__tests__\main.test.js</a> ❌
|
|❌|Timeout test|4ms|
|
||||||
|
|✖️|Skipped test|0ms|
|
||||||
### Test 1
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ✔️ | Passing test | 1ms |
|
|
||||||
|
|
||||||
### Test 1 › Test 1.1
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Failing test | 2ms |
|
|
||||||
| ❌ | Exception in target unit | 0ms |
|
|
||||||
|
|
||||||
### Test 2
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Exception in test | 0ms |
|
|
||||||
|
|
||||||
## <a id="user-content-ts-1-tests-second-test-js" href="#ts-1-tests-second-test-js">__tests__\second.test.js</a> ❌
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Timeout test | 4ms |
|
|
||||||
| ✖️ | Skipped test | 0ms |
|
|
||||||
434
__tests__/__outputs__/jest-test-results.md
Normal file
434
__tests__/__outputs__/jest-test-results.md
Normal file
|
|
@ -0,0 +1,434 @@
|
||||||
|

|
||||||
|
## <a id="user-content-r0" href="#r0">fixtures/external/jest/jest-test-results.xml</a> ❌
|
||||||
|
**4239** tests were completed in **165.872s** with **4207** passed, **2** failed and **30** skipped.
|
||||||
|
|Test suite|Passed|Failed|Skipped|Time|
|
||||||
|
|:---|---:|---:|---:|---:|
|
||||||
|
|e2e/__tests__/asyncAndCallback.test.ts|1✔️|||746ms|
|
||||||
|
|e2e/__tests__/asyncRegenerator.test.ts|1✔️|||4.127s|
|
||||||
|
|e2e/__tests__/autoClearMocks.test.ts|2✔️|||1.681s|
|
||||||
|
|e2e/__tests__/autoResetMocks.test.ts|2✔️|||1.666s|
|
||||||
|
|e2e/__tests__/autoRestoreMocks.test.ts|2✔️|||1.797s|
|
||||||
|
|e2e/__tests__/babelPluginJestHoist.test.ts|1✔️|||6.249s|
|
||||||
|
|e2e/__tests__/badSourceMap.test.ts|1✔️|||858ms|
|
||||||
|
|e2e/__tests__/beforeAllFiltered.ts|1✔️|||958ms|
|
||||||
|
|e2e/__tests__/beforeEachQueue.ts|1✔️||1✖️|55ms|
|
||||||
|
|e2e/__tests__/callDoneTwice.test.ts|1✔️|||882ms|
|
||||||
|
|e2e/__tests__/chaiAssertionLibrary.ts|1✔️|||1.902s|
|
||||||
|
|e2e/__tests__/circularInequality.test.ts|1✔️|||1.451s|
|
||||||
|
|e2e/__tests__/circusConcurrentEach.test.ts|2✔️|||1.591s|
|
||||||
|
|e2e/__tests__/circusDeclarationErrors.test.ts|1✔️|||869ms|
|
||||||
|
|e2e/__tests__/clearCache.test.ts|2✔️|||1.004s|
|
||||||
|
|e2e/__tests__/cliHandlesExactFilenames.test.ts|2✔️|||1.230s|
|
||||||
|
|e2e/__tests__/compareDomNodes.test.ts|1✔️|||1.407s|
|
||||||
|
|e2e/__tests__/config.test.ts|6✔️|||3.945s|
|
||||||
|
|e2e/__tests__/console.test.ts|7✔️|||8.072s|
|
||||||
|
|e2e/__tests__/consoleAfterTeardown.test.ts|1✔️|||1.341s|
|
||||||
|
|e2e/__tests__/consoleLogOutputWhenRunInBand.test.ts|1✔️|||793ms|
|
||||||
|
|e2e/__tests__/coverageHandlebars.test.ts|1✔️|||1.873s|
|
||||||
|
|e2e/__tests__/coverageRemapping.test.ts|1✔️|||12.701s|
|
||||||
|
|e2e/__tests__/coverageReport.test.ts|12✔️|||22.264s|
|
||||||
|
|e2e/__tests__/coverageThreshold.test.ts|5✔️|||4.868s|
|
||||||
|
|e2e/__tests__/coverageTransformInstrumented.test.ts|1✔️|||5.029s|
|
||||||
|
|e2e/__tests__/coverageWithoutTransform.test.ts|1✔️|||1.075s|
|
||||||
|
|e2e/__tests__/createProcessObject.test.ts|1✔️|||908ms|
|
||||||
|
|e2e/__tests__/customInlineSnapshotMatchers.test.ts|1✔️|||2.206s|
|
||||||
|
|e2e/__tests__/customMatcherStackTrace.test.ts|2✔️|||1.539s|
|
||||||
|
|e2e/__tests__/customReporters.test.ts|9✔️|||6.553s|
|
||||||
|
|e2e/__tests__/customResolver.test.ts|1✔️|||826ms|
|
||||||
|
|e2e/__tests__/customTestSequencers.test.ts|3✔️|||2.757s|
|
||||||
|
|e2e/__tests__/debug.test.ts|1✔️|||899ms|
|
||||||
|
|e2e/__tests__/declarationErrors.test.ts|3✔️|||2.389s|
|
||||||
|
|e2e/__tests__/dependencyClash.test.ts|1✔️|||833ms|
|
||||||
|
|e2e/__tests__/detectOpenHandles.ts|8✔️|||7.528s|
|
||||||
|
|e2e/__tests__/domDiffing.test.ts|1✔️|||1.361s|
|
||||||
|
|e2e/__tests__/doneInHooks.test.ts|1✔️|||855ms|
|
||||||
|
|e2e/__tests__/dynamicRequireDependencies.ts|1✔️|||847ms|
|
||||||
|
|e2e/__tests__/each.test.ts|7✔️|||4.721s|
|
||||||
|
|e2e/__tests__/emptyDescribeWithHooks.test.ts|4✔️|||2.886s|
|
||||||
|
|e2e/__tests__/emptySuiteError.test.ts|1✔️|||885ms|
|
||||||
|
|e2e/__tests__/env.test.ts|6✔️|||5.221s|
|
||||||
|
|e2e/__tests__/environmentAfterTeardown.test.ts|1✔️|||892ms|
|
||||||
|
|e2e/__tests__/errorOnDeprecated.test.ts|1✔️||24✖️|56ms|
|
||||||
|
|e2e/__tests__/esmConfigFile.test.ts|3✔️|||526ms|
|
||||||
|
|e2e/__tests__/executeTestsOnceInMpr.ts|1✔️|||976ms|
|
||||||
|
|e2e/__tests__/existentRoots.test.ts|4✔️|||627ms|
|
||||||
|
|e2e/__tests__/expectAsyncMatcher.test.ts|2✔️|||2.732s|
|
||||||
|
|e2e/__tests__/expectInVm.test.ts|1✔️|||1.527s|
|
||||||
|
|e2e/__tests__/extraGlobals.test.ts|1✔️|||1.011s|
|
||||||
|
|e2e/__tests__/failureDetailsProperty.test.ts|1✔️|||907ms|
|
||||||
|
|e2e/__tests__/failures.test.ts|7✔️|||10.353s|
|
||||||
|
|e2e/__tests__/fakePromises.test.ts|2✔️|||1.716s|
|
||||||
|
|e2e/__tests__/fatalWorkerError.test.ts|1✔️|||3.167s|
|
||||||
|
|e2e/__tests__/filter.test.ts|7✔️|||5.422s|
|
||||||
|
|e2e/__tests__/findRelatedFiles.test.ts|5✔️|||6.230s|
|
||||||
|
|e2e/__tests__/focusedTests.test.ts|1✔️|||888ms|
|
||||||
|
|e2e/__tests__/forceExit.test.ts|1✔️|||2.208s|
|
||||||
|
|e2e/__tests__/generatorMock.test.ts|1✔️|||1.027s|
|
||||||
|
|e2e/__tests__/global-mutation.test.ts|1✔️|||40ms|
|
||||||
|
|e2e/__tests__/global.test.ts|1✔️|||31ms|
|
||||||
|
|e2e/__tests__/globals.test.ts|10✔️|||7.505s|
|
||||||
|
|e2e/__tests__/globalSetup.test.ts|10✔️|||13.926s|
|
||||||
|
|e2e/__tests__/globalTeardown.test.ts|7✔️|||11.886s|
|
||||||
|
|e2e/__tests__/hasteMapMockChanged.test.ts|1✔️|||379ms|
|
||||||
|
|e2e/__tests__/hasteMapSha1.test.ts|1✔️|||298ms|
|
||||||
|
|e2e/__tests__/hasteMapSize.test.ts|2✔️|||397ms|
|
||||||
|
|e2e/__tests__/importedGlobals.test.ts|1✔️|||1.043s|
|
||||||
|
|e2e/__tests__/injectGlobals.test.ts|2✔️|||1.860s|
|
||||||
|
|e2e/__tests__/jasmineAsync.test.ts|15✔️|||28.291s|
|
||||||
|
|e2e/__tests__/jasmineAsyncWithPendingDuringTest.ts|1✔️||1✖️|72ms|
|
||||||
|
|e2e/__tests__/jest.config.js.test.ts|3✔️|||2.134s|
|
||||||
|
|e2e/__tests__/jest.config.ts.test.ts|5✔️|||14.322s|
|
||||||
|
|[e2e/__tests__/jestChangedFiles.test.ts](#r0s75)|9✔️|1❌||9.045s|
|
||||||
|
|e2e/__tests__/jestEnvironmentJsdom.test.ts|1✔️|||1.744s|
|
||||||
|
|e2e/__tests__/jestRequireActual.test.ts|1✔️|||1.665s|
|
||||||
|
|e2e/__tests__/jestRequireMock.test.ts|1✔️|||2.119s|
|
||||||
|
|e2e/__tests__/json.test.ts|2✔️|||29ms|
|
||||||
|
|e2e/__tests__/jsonReporter.test.ts|2✔️|||1.514s|
|
||||||
|
|e2e/__tests__/lifecycles.ts|1✔️|||861ms|
|
||||||
|
|e2e/__tests__/listTests.test.ts|2✔️|||945ms|
|
||||||
|
|e2e/__tests__/locationInResults.test.ts|2✔️|||1.764s|
|
||||||
|
|e2e/__tests__/logHeapUsage.test.ts|1✔️|||884ms|
|
||||||
|
|e2e/__tests__/mockNames.test.ts|8✔️|||6.771s|
|
||||||
|
|e2e/__tests__/modernFakeTimers.test.ts|2✔️|||1.680s|
|
||||||
|
|e2e/__tests__/moduleNameMapper.test.ts|5✔️|||5.395s|
|
||||||
|
|e2e/__tests__/moduleParentNullInTest.ts|1✔️|||886ms|
|
||||||
|
|e2e/__tests__/multiProjectRunner.test.ts|14✔️|||16.360s|
|
||||||
|
|e2e/__tests__/nativeAsyncMock.test.ts|1✔️|||55ms|
|
||||||
|
|e2e/__tests__/nativeEsm.test.ts|2✔️||1✖️|905ms|
|
||||||
|
|e2e/__tests__/nativeEsmTypescript.test.ts|1✔️|||956ms|
|
||||||
|
|e2e/__tests__/nestedEventLoop.test.ts|1✔️|||1.422s|
|
||||||
|
|e2e/__tests__/nestedTestDefinitions.test.ts|4✔️|||4.641s|
|
||||||
|
|e2e/__tests__/nodePath.test.ts|1✔️|||866ms|
|
||||||
|
|e2e/__tests__/noTestFound.test.ts|2✔️|||1.063s|
|
||||||
|
|e2e/__tests__/noTestsFound.test.ts|5✔️|||2.739s|
|
||||||
|
|[e2e/__tests__/onlyChanged.test.ts](#r0s98)|8✔️|1❌||22.281s|
|
||||||
|
|e2e/__tests__/onlyFailuresNonWatch.test.ts|1✔️|||2.893s|
|
||||||
|
|e2e/__tests__/overrideGlobals.test.ts|2✔️|||2.046s|
|
||||||
|
|e2e/__tests__/pnp.test.ts|1✔️|||2.715s|
|
||||||
|
|e2e/__tests__/presets.test.ts|2✔️|||1.966s|
|
||||||
|
|e2e/__tests__/processExit.test.ts|1✔️|||1.070s|
|
||||||
|
|e2e/__tests__/promiseReject.test.ts|1✔️|||967ms|
|
||||||
|
|e2e/__tests__/regexCharInPath.test.ts|1✔️|||962ms|
|
||||||
|
|e2e/__tests__/requireAfterTeardown.test.ts|1✔️|||921ms|
|
||||||
|
|e2e/__tests__/requireMain.test.ts|1✔️|||1.137s|
|
||||||
|
|e2e/__tests__/requireMainAfterCreateRequire.test.ts|1✔️|||966ms|
|
||||||
|
|e2e/__tests__/requireMainIsolateModules.test.ts|1✔️|||976ms|
|
||||||
|
|e2e/__tests__/requireMainResetModules.test.ts|2✔️|||1.961s|
|
||||||
|
|e2e/__tests__/requireV8Module.test.ts|1✔️|||30ms|
|
||||||
|
|e2e/__tests__/resetModules.test.ts|1✔️|||926ms|
|
||||||
|
|e2e/__tests__/resolve.test.ts|1✔️|||1.863s|
|
||||||
|
|e2e/__tests__/resolveGetPaths.test.ts|1✔️|||1.155s|
|
||||||
|
|e2e/__tests__/resolveNodeModule.test.ts|1✔️|||943ms|
|
||||||
|
|e2e/__tests__/resolveNoFileExtensions.test.ts|2✔️|||1.263s|
|
||||||
|
|e2e/__tests__/resolveWithPaths.test.ts|1✔️|||1.170s|
|
||||||
|
|e2e/__tests__/runProgrammatically.test.ts|2✔️|||575ms|
|
||||||
|
|e2e/__tests__/runTestsByPath.test.ts|1✔️|||1.999s|
|
||||||
|
|e2e/__tests__/runtimeInternalModuleRegistry.test.ts|1✔️|||1.202s|
|
||||||
|
|e2e/__tests__/selectProjects.test.ts|18✔️|||5.236s|
|
||||||
|
|e2e/__tests__/setImmediate.test.ts|1✔️|||904ms|
|
||||||
|
|e2e/__tests__/setupFilesAfterEnvConfig.test.ts|2✔️|||1.967s|
|
||||||
|
|e2e/__tests__/showConfig.test.ts|1✔️|||195ms|
|
||||||
|
|e2e/__tests__/skipBeforeAfterAll.test.ts|1✔️|||1.061s|
|
||||||
|
|e2e/__tests__/snapshot-unknown.test.ts|1✔️|||838ms|
|
||||||
|
|e2e/__tests__/snapshot.test.ts|9✔️|||13.899s|
|
||||||
|
|e2e/__tests__/snapshotMockFs.test.ts|1✔️|||883ms|
|
||||||
|
|e2e/__tests__/snapshotResolver.test.ts|1✔️|||823ms|
|
||||||
|
|e2e/__tests__/snapshotSerializers.test.ts|2✔️|||2.065s|
|
||||||
|
|e2e/__tests__/stackTrace.test.ts|7✔️|||4.725s|
|
||||||
|
|e2e/__tests__/stackTraceNoCaptureStackTrace.test.ts|1✔️|||899ms|
|
||||||
|
|e2e/__tests__/stackTraceSourceMaps.test.ts|1✔️|||2.185s|
|
||||||
|
|e2e/__tests__/stackTraceSourceMapsWithCoverage.test.ts|1✔️|||2.444s|
|
||||||
|
|e2e/__tests__/supportsDashedArgs.ts|2✔️|||968ms|
|
||||||
|
|e2e/__tests__/symbol.test.ts|1✔️|||49ms|
|
||||||
|
|e2e/__tests__/testEnvironment.test.ts|1✔️|||1.628s|
|
||||||
|
|e2e/__tests__/testEnvironmentAsync.test.ts|1✔️|||1.493s|
|
||||||
|
|e2e/__tests__/testEnvironmentCircus.test.ts|1✔️|||1.501s|
|
||||||
|
|e2e/__tests__/testEnvironmentCircusAsync.test.ts|1✔️|||1.507s|
|
||||||
|
|e2e/__tests__/testFailureExitCode.test.ts|2✔️|||4.476s|
|
||||||
|
|e2e/__tests__/testInRoot.test.ts|1✔️|||1.009s|
|
||||||
|
|e2e/__tests__/testNamePattern.test.ts|1✔️|||859ms|
|
||||||
|
|e2e/__tests__/testNamePatternSkipped.test.ts|1✔️|||991ms|
|
||||||
|
|e2e/__tests__/testPathPatternReporterMessage.test.ts|1✔️|||3.076s|
|
||||||
|
|e2e/__tests__/testResultsProcessor.test.ts|1✔️|||910ms|
|
||||||
|
|e2e/__tests__/testRetries.test.ts|4✔️|||3.277s|
|
||||||
|
|e2e/__tests__/testTodo.test.ts|5✔️|||3.573s|
|
||||||
|
|e2e/__tests__/timeouts.test.ts|4✔️|||4.029s|
|
||||||
|
|e2e/__tests__/timeoutsLegacy.test.ts|1✔️||3✖️|71ms|
|
||||||
|
|e2e/__tests__/timerResetMocks.test.ts|2✔️|||1.878s|
|
||||||
|
|e2e/__tests__/timerUseRealTimers.test.ts|1✔️|||1.018s|
|
||||||
|
|e2e/__tests__/toMatchInlineSnapshot.test.ts|12✔️|||23.917s|
|
||||||
|
|e2e/__tests__/toMatchInlineSnapshotWithRetries.test.ts|3✔️|||4.670s|
|
||||||
|
|e2e/__tests__/toMatchSnapshot.test.ts|9✔️|||17.025s|
|
||||||
|
|e2e/__tests__/toMatchSnapshotWithRetries.test.ts|2✔️|||4.435s|
|
||||||
|
|e2e/__tests__/toMatchSnapshotWithStringSerializer.test.ts|3✔️|||3.544s|
|
||||||
|
|e2e/__tests__/toThrowErrorMatchingInlineSnapshot.test.ts|4✔️|||3.562s|
|
||||||
|
|e2e/__tests__/toThrowErrorMatchingSnapshot.test.ts|5✔️|||3.524s|
|
||||||
|
|e2e/__tests__/transform.test.ts|16✔️|||26.740s|
|
||||||
|
|e2e/__tests__/transformLinkedModules.test.ts|1✔️|||783ms|
|
||||||
|
|e2e/__tests__/typescriptCoverage.test.ts|1✔️|||2.893s|
|
||||||
|
|e2e/__tests__/unexpectedToken.test.ts|3✔️|||3.411s|
|
||||||
|
|e2e/__tests__/useStderr.test.ts|1✔️|||1.352s|
|
||||||
|
|e2e/__tests__/v8Coverage.test.ts|2✔️|||2.412s|
|
||||||
|
|e2e/__tests__/verbose.test.ts|1✔️|||683ms|
|
||||||
|
|e2e/__tests__/version.test.ts|1✔️|||138ms|
|
||||||
|
|e2e/__tests__/watchModeNoAccess.test.ts|1✔️|||4.370s|
|
||||||
|
|e2e/__tests__/watchModeOnlyFailed.test.ts|1✔️|||1.394s|
|
||||||
|
|e2e/__tests__/watchModePatterns.test.ts|2✔️|||3.503s|
|
||||||
|
|e2e/__tests__/watchModeUpdateSnapshot.test.ts|1✔️|||1.075s|
|
||||||
|
|e2e/__tests__/workerForceExit.test.ts|2✔️|||4.751s|
|
||||||
|
|e2e/__tests__/wrongEnv.test.ts|5✔️|||3.877s|
|
||||||
|
|e2e/custom-test-sequencer/a.test.js|1✔️|||29ms|
|
||||||
|
|e2e/custom-test-sequencer/b.test.js|1✔️|||21ms|
|
||||||
|
|e2e/custom-test-sequencer/c.test.js|1✔️|||42ms|
|
||||||
|
|e2e/custom-test-sequencer/d.test.js|1✔️|||21ms|
|
||||||
|
|e2e/custom-test-sequencer/e.test.js|1✔️|||27ms|
|
||||||
|
|e2e/test-in-root/spec.js|1✔️|||19ms|
|
||||||
|
|e2e/test-in-root/test.js|1✔️|||37ms|
|
||||||
|
|e2e/timer-reset-mocks/after-reset-all-mocks/timerAndMock.test.js|2✔️|||30ms|
|
||||||
|
|e2e/timer-reset-mocks/with-reset-mocks/timerWithMock.test.js|1✔️|||34ms|
|
||||||
|
|e2e/v8-coverage/empty-sourcemap/test.ts|1✔️|||31ms|
|
||||||
|
|examples/angular/app.component.spec.ts|3✔️|||654ms|
|
||||||
|
|examples/angular/shared/data.service.spec.ts|2✔️|||431ms|
|
||||||
|
|examples/angular/shared/sub.service.spec.ts|1✔️|||109ms|
|
||||||
|
|examples/async/__tests__/user.test.js|8✔️|||96ms|
|
||||||
|
|examples/automatic-mocks/__tests__/automock.test.js|2✔️|||74ms|
|
||||||
|
|examples/automatic-mocks/__tests__/createMockFromModule.test.js|2✔️|||115ms|
|
||||||
|
|examples/automatic-mocks/__tests__/disableAutomocking.test.js|1✔️|||24ms|
|
||||||
|
|examples/enzyme/__tests__/CheckboxWithLabel-test.js|1✔️|||434ms|
|
||||||
|
|examples/getting-started/sum.test.js|1✔️|||78ms|
|
||||||
|
|examples/jquery/__tests__/display_user.test.js|1✔️|||196ms|
|
||||||
|
|examples/jquery/__tests__/fetch_current_user.test.js|2✔️|||196ms|
|
||||||
|
|examples/manual-mocks/__tests__/file_summarizer.test.js|1✔️|||87ms|
|
||||||
|
|examples/manual-mocks/__tests__/lodashMocking.test.js|1✔️|||109ms|
|
||||||
|
|examples/manual-mocks/__tests__/user.test.js|1✔️|||41ms|
|
||||||
|
|examples/manual-mocks/__tests__/userMocked.test.js|1✔️|||105ms|
|
||||||
|
|examples/module-mock/__tests__/full_mock.js|1✔️|||60ms|
|
||||||
|
|examples/module-mock/__tests__/mock_per_test.js|2✔️|||116ms|
|
||||||
|
|examples/module-mock/__tests__/partial_mock.js|1✔️|||215ms|
|
||||||
|
|examples/mongodb/__test__/db.test.js|1✔️|||236ms|
|
||||||
|
|examples/react-native/__tests__/intro.test.js|4✔️|||8.559s|
|
||||||
|
|examples/react-testing-library/__tests__/CheckboxWithLabel-test.js|1✔️|||469ms|
|
||||||
|
|examples/react/__tests__/CheckboxWithLabel-test.js|1✔️|||256ms|
|
||||||
|
|examples/snapshot/__tests__/clock.react.test.js|1✔️|||62ms|
|
||||||
|
|examples/snapshot/__tests__/link.react.test.js|4✔️|||181ms|
|
||||||
|
|examples/timer/__tests__/infinite_timer_game.test.js|1✔️|||94ms|
|
||||||
|
|examples/timer/__tests__/timer_game.test.js|3✔️|||74ms|
|
||||||
|
|examples/typescript/__tests__/calc.test.ts|6✔️|||276ms|
|
||||||
|
|examples/typescript/__tests__/CheckboxWithLabel-test.tsx|1✔️|||227ms|
|
||||||
|
|examples/typescript/__tests__/sub-test.ts|1✔️|||43ms|
|
||||||
|
|examples/typescript/__tests__/sum-test.ts|2✔️|||69ms|
|
||||||
|
|examples/typescript/__tests__/sum.test.js|2✔️|||100ms|
|
||||||
|
|packages/babel-jest/src/__tests__/index.ts|6✔️|||371ms|
|
||||||
|
|packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts|4✔️|||347ms|
|
||||||
|
|packages/diff-sequences/src/__tests__/index.property.test.ts|7✔️|||357ms|
|
||||||
|
|packages/diff-sequences/src/__tests__/index.test.ts|48✔️|||195ms|
|
||||||
|
|packages/expect/src/__tests__/assertionCounts.test.ts|6✔️|||60ms|
|
||||||
|
|packages/expect/src/__tests__/asymmetricMatchers.test.ts|38✔️|||207ms|
|
||||||
|
|packages/expect/src/__tests__/extend.test.ts|10✔️|||99ms|
|
||||||
|
|packages/expect/src/__tests__/isError.test.ts|4✔️|||43ms|
|
||||||
|
|packages/expect/src/__tests__/matchers-toContain.property.test.ts|2✔️|||236ms|
|
||||||
|
|packages/expect/src/__tests__/matchers-toContainEqual.property.test.ts|2✔️|||287ms|
|
||||||
|
|packages/expect/src/__tests__/matchers-toEqual.property.test.ts|2✔️|||1.062s|
|
||||||
|
|packages/expect/src/__tests__/matchers-toStrictEqual.property.test.ts|3✔️|||394ms|
|
||||||
|
|packages/expect/src/__tests__/matchers.test.js|592✔️|||862ms|
|
||||||
|
|packages/expect/src/__tests__/spyMatchers.test.ts|248✔️|||395ms|
|
||||||
|
|packages/expect/src/__tests__/stacktrace.test.ts|3✔️|||69ms|
|
||||||
|
|packages/expect/src/__tests__/symbolInObjects.test.ts|3✔️|||33ms|
|
||||||
|
|packages/expect/src/__tests__/toEqual-dom.test.ts|12✔️|||99ms|
|
||||||
|
|packages/expect/src/__tests__/toThrowMatchers.test.ts|98✔️|||257ms|
|
||||||
|
|packages/expect/src/__tests__/utils.test.ts|41✔️|||147ms|
|
||||||
|
|packages/jest-circus/src/__tests__/afterAll.test.ts|6✔️|||5.755s|
|
||||||
|
|packages/jest-circus/src/__tests__/baseTest.test.ts|2✔️|||2.902s|
|
||||||
|
|packages/jest-circus/src/__tests__/circusItTestError.test.ts|8✔️|||300ms|
|
||||||
|
|packages/jest-circus/src/__tests__/circusItTodoTestError.test.ts|3✔️|||81ms|
|
||||||
|
|packages/jest-circus/src/__tests__/hooks.test.ts|3✔️|||3.762s|
|
||||||
|
|packages/jest-circus/src/__tests__/hooksError.test.ts|32✔️|||127ms|
|
||||||
|
|packages/jest-cli/src/__tests__/cli/args.test.ts|17✔️|||345ms|
|
||||||
|
|packages/jest-cli/src/init/__tests__/init.test.js|24✔️|||119ms|
|
||||||
|
|packages/jest-cli/src/init/__tests__/modifyPackageJson.test.ts|4✔️|||30ms|
|
||||||
|
|packages/jest-config/src/__tests__/Defaults.test.ts|1✔️|||672ms|
|
||||||
|
|packages/jest-config/src/__tests__/getMaxWorkers.test.ts|7✔️|||67ms|
|
||||||
|
|packages/jest-config/src/__tests__/normalize.test.js|118✔️|||798ms|
|
||||||
|
|packages/jest-config/src/__tests__/readConfig.test.ts|1✔️|||76ms|
|
||||||
|
|packages/jest-config/src/__tests__/readConfigs.test.ts|3✔️|||135ms|
|
||||||
|
|packages/jest-config/src/__tests__/resolveConfigPath.test.ts|10✔️|||183ms|
|
||||||
|
|packages/jest-config/src/__tests__/setFromArgv.test.ts|4✔️|||53ms|
|
||||||
|
|packages/jest-config/src/__tests__/validatePattern.test.ts|4✔️|||52ms|
|
||||||
|
|packages/jest-console/src/__tests__/bufferedConsole.test.ts|20✔️|||171ms|
|
||||||
|
|packages/jest-console/src/__tests__/CustomConsole.test.ts|23✔️|||115ms|
|
||||||
|
|packages/jest-console/src/__tests__/getConsoleOutput.test.ts|12✔️|||56ms|
|
||||||
|
|packages/jest-core/src/__tests__/FailedTestsCache.test.js|1✔️|||25ms|
|
||||||
|
|packages/jest-core/src/__tests__/getNoTestsFoundMessage.test.js|5✔️|||61ms|
|
||||||
|
|packages/jest-core/src/__tests__/globals.test.ts|1✔️|||22ms|
|
||||||
|
|packages/jest-core/src/__tests__/runJest.test.js|2✔️|||261ms|
|
||||||
|
|packages/jest-core/src/__tests__/SearchSource.test.ts|27✔️|||2.596s|
|
||||||
|
|packages/jest-core/src/__tests__/SnapshotInteractiveMode.test.js|13✔️|||89ms|
|
||||||
|
|packages/jest-core/src/__tests__/TestScheduler.test.js|8✔️|||520ms|
|
||||||
|
|packages/jest-core/src/__tests__/testSchedulerHelper.test.js|12✔️|||48ms|
|
||||||
|
|packages/jest-core/src/__tests__/watch.test.js|80✔️|||6.755s|
|
||||||
|
|packages/jest-core/src/__tests__/watchFileChanges.test.ts|1✔️|||1.514s|
|
||||||
|
|packages/jest-core/src/__tests__/watchFilenamePatternMode.test.js|2✔️|||165ms|
|
||||||
|
|packages/jest-core/src/__tests__/watchTestNamePatternMode.test.js|1✔️|||246ms|
|
||||||
|
|packages/jest-core/src/lib/__tests__/isValidPath.test.ts|3✔️|||166ms|
|
||||||
|
|packages/jest-core/src/lib/__tests__/logDebugMessages.test.ts|3✔️|||48ms|
|
||||||
|
|packages/jest-create-cache-key-function/src/__tests__/index.test.ts|1✔️|||75ms|
|
||||||
|
|packages/jest-diff/src/__tests__/diff.test.ts|107✔️|||625ms|
|
||||||
|
|packages/jest-diff/src/__tests__/diffStringsRaw.test.ts|2✔️|||55ms|
|
||||||
|
|packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts|24✔️|||72ms|
|
||||||
|
|packages/jest-diff/src/__tests__/joinAlignedDiffs.test.ts|6✔️|||44ms|
|
||||||
|
|packages/jest-docblock/src/__tests__/index.test.ts|36✔️|||177ms|
|
||||||
|
|packages/jest-each/src/__tests__/array.test.ts|159✔️|||192ms|
|
||||||
|
|packages/jest-each/src/__tests__/index.test.ts|10✔️|||44ms|
|
||||||
|
|packages/jest-each/src/__tests__/template.test.ts|242✔️|||483ms|
|
||||||
|
|packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts|2✔️|||783ms|
|
||||||
|
|packages/jest-environment-node/src/__tests__/node_environment.test.ts|6✔️|||184ms|
|
||||||
|
|packages/jest-fake-timers/src/__tests__/legacyFakeTimers.test.ts|50✔️|||302ms|
|
||||||
|
|packages/jest-fake-timers/src/__tests__/modernFakeTimers.test.ts|40✔️|||317ms|
|
||||||
|
|packages/jest-get-type/src/__tests__/getType.test.ts|14✔️|||45ms|
|
||||||
|
|packages/jest-get-type/src/__tests__/isPrimitive.test.ts|18✔️|||36ms|
|
||||||
|
|packages/jest-globals/src/__tests__/index.ts|1✔️|||533ms|
|
||||||
|
|packages/jest-haste-map/src/__tests__/get_mock_name.test.js|1✔️|||22ms|
|
||||||
|
|packages/jest-haste-map/src/__tests__/includes_dotfiles.test.ts|1✔️|||337ms|
|
||||||
|
|packages/jest-haste-map/src/__tests__/index.test.js|44✔️|||1.145s|
|
||||||
|
|packages/jest-haste-map/src/__tests__/worker.test.js|7✔️|||100ms|
|
||||||
|
|packages/jest-haste-map/src/crawlers/__tests__/node.test.js|10✔️|||170ms|
|
||||||
|
|packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js|8✔️|||153ms|
|
||||||
|
|packages/jest-haste-map/src/lib/__tests__/dependencyExtractor.test.js|15✔️|||56ms|
|
||||||
|
|packages/jest-haste-map/src/lib/__tests__/fast_path.test.js|5✔️|||29ms|
|
||||||
|
|packages/jest-haste-map/src/lib/__tests__/getPlatformExtension.test.js|1✔️|||35ms|
|
||||||
|
|packages/jest-haste-map/src/lib/__tests__/isRegExpSupported.test.js|2✔️|||31ms|
|
||||||
|
|packages/jest-haste-map/src/lib/__tests__/normalizePathSep.test.js|2✔️|||35ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/concurrent.test.ts|3✔️|||24ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/expectationResultFactory.test.ts|7✔️|||70ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/hooksError.test.ts|32✔️|||51ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/iterators.test.ts|4✔️|||43ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/itTestError.test.ts|6✔️|||32ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/itToTestAlias.test.ts|1✔️|||23ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/pTimeout.test.ts|3✔️|||44ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/queueRunner.test.ts|6✔️|||93ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/reporter.test.ts|1✔️|||107ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/Suite.test.ts|1✔️|||84ms|
|
||||||
|
|packages/jest-jasmine2/src/__tests__/todoError.test.ts|3✔️|||27ms|
|
||||||
|
|packages/jest-leak-detector/src/__tests__/index.test.ts|6✔️|||986ms|
|
||||||
|
|packages/jest-matcher-utils/src/__tests__/deepCyclicCopyReplaceable.test.ts|11✔️|||49ms|
|
||||||
|
|packages/jest-matcher-utils/src/__tests__/deepCyclicCopyReplaceableDom.test.ts|2✔️|||48ms|
|
||||||
|
|packages/jest-matcher-utils/src/__tests__/index.test.ts|48✔️|||391ms|
|
||||||
|
|packages/jest-matcher-utils/src/__tests__/printDiffOrStringify.test.ts|21✔️|||114ms|
|
||||||
|
|packages/jest-matcher-utils/src/__tests__/Replaceable.test.ts|17✔️|||111ms|
|
||||||
|
|packages/jest-message-util/src/__tests__/messages.test.ts|11✔️|||205ms|
|
||||||
|
|packages/jest-mock/src/__tests__/index.test.ts|84✔️|||509ms|
|
||||||
|
|packages/jest-regex-util/src/__tests__/index.test.ts|8✔️|||56ms|
|
||||||
|
|packages/jest-repl/src/__tests__/jest_repl.test.js|1✔️|||1.172s|
|
||||||
|
|packages/jest-repl/src/__tests__/runtime_cli.test.js|4✔️|||4.094s|
|
||||||
|
|packages/jest-reporters/src/__tests__/CoverageReporter.test.js|12✔️|||397ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/CoverageWorker.test.js|2✔️|||199ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/DefaultReporter.test.js|2✔️|||148ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/generateEmptyCoverage.test.js|3✔️|||1.129s|
|
||||||
|
|packages/jest-reporters/src/__tests__/getResultHeader.test.js|4✔️|||30ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/getSnapshotStatus.test.js|3✔️|||28ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/getSnapshotSummary.test.js|4✔️|||49ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/getWatermarks.test.ts|2✔️|||37ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/NotifyReporter.test.ts|18✔️|||166ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/SummaryReporter.test.js|4✔️|||366ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/utils.test.ts|10✔️|||85ms|
|
||||||
|
|packages/jest-reporters/src/__tests__/VerboseReporter.test.js|11✔️|||425ms|
|
||||||
|
|packages/jest-resolve-dependencies/src/__tests__/dependency_resolver.test.ts|11✔️|||666ms|
|
||||||
|
|packages/jest-resolve/src/__tests__/isBuiltinModule.test.ts|4✔️|||36ms|
|
||||||
|
|packages/jest-resolve/src/__tests__/resolve.test.ts|16✔️|||1.308s|
|
||||||
|
|packages/jest-runner/src/__tests__/testRunner.test.ts|2✔️|||905ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/instrumentation.test.ts|1✔️|||275ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_create_mock_from_module.test.js|3✔️|||606ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_environment.test.js|2✔️|||497ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_internal_module.test.js|4✔️|||727ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_jest_fn.js|4✔️|||479ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js|2✔️|||521ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_mock.test.js|4✔️|||743ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_module_directories.test.js|4✔️|||525ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_node_path.test.js|4✔️|||1.088s|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_require_actual.test.js|2✔️|||478ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_require_cache.test.js|2✔️|||454ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_require_mock.test.js|13✔️|||962ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_require_module_no_ext.test.js|1✔️|||261ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_require_module_or_mock_transitive_deps.test.js|6✔️|||2.366s|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_require_module_or_mock.test.js|17✔️|||1.223s|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_require_module.test.js|27✔️|||2.439s|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_require_resolve.test.ts|5✔️|||707ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/runtime_wrap.js|2✔️|||263ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/Runtime-sourceMaps.test.js|1✔️|||584ms|
|
||||||
|
|packages/jest-runtime/src/__tests__/Runtime-statics.test.js|2✔️|||162ms|
|
||||||
|
|packages/jest-serializer/src/__tests__/index.test.ts|17✔️|||158ms|
|
||||||
|
|packages/jest-snapshot/src/__tests__/dedentLines.test.ts|17✔️|||94ms|
|
||||||
|
|packages/jest-snapshot/src/__tests__/InlineSnapshots.test.ts|22✔️|||1.149s|
|
||||||
|
|packages/jest-snapshot/src/__tests__/matcher.test.ts|1✔️|||131ms|
|
||||||
|
|packages/jest-snapshot/src/__tests__/mockSerializer.test.ts|10✔️|||45ms|
|
||||||
|
|packages/jest-snapshot/src/__tests__/printSnapshot.test.ts|71✔️|||1.188s|
|
||||||
|
|packages/jest-snapshot/src/__tests__/SnapshotResolver.test.ts|10✔️|||98ms|
|
||||||
|
|packages/jest-snapshot/src/__tests__/throwMatcher.test.ts|3✔️|||481ms|
|
||||||
|
|packages/jest-snapshot/src/__tests__/utils.test.ts|26✔️|||214ms|
|
||||||
|
|packages/jest-source-map/src/__tests__/getCallsite.test.ts|3✔️|||86ms|
|
||||||
|
|packages/jest-test-result/src/__tests__/formatTestResults.test.ts|1✔️|||53ms|
|
||||||
|
|packages/jest-test-sequencer/src/__tests__/test_sequencer.test.js|8✔️|||251ms|
|
||||||
|
|packages/jest-transform/src/__tests__/ScriptTransformer.test.ts|22✔️|||1.660s|
|
||||||
|
|packages/jest-transform/src/__tests__/shouldInstrument.test.ts|25✔️|||155ms|
|
||||||
|
|packages/jest-util/src/__tests__/createProcessObject.test.ts|4✔️|||81ms|
|
||||||
|
|packages/jest-util/src/__tests__/deepCyclicCopy.test.ts|12✔️|||86ms|
|
||||||
|
|packages/jest-util/src/__tests__/errorWithStack.test.ts|1✔️|||41ms|
|
||||||
|
|packages/jest-util/src/__tests__/formatTime.test.ts|11✔️|||82ms|
|
||||||
|
|packages/jest-util/src/__tests__/globsToMatcher.test.ts|4✔️|||56ms|
|
||||||
|
|packages/jest-util/src/__tests__/installCommonGlobals.test.ts|2✔️|||68ms|
|
||||||
|
|packages/jest-util/src/__tests__/isInteractive.test.ts|2✔️|||35ms|
|
||||||
|
|packages/jest-util/src/__tests__/isPromise.test.ts|10✔️|||30ms|
|
||||||
|
|packages/jest-validate/src/__tests__/validate.test.ts|23✔️|||283ms|
|
||||||
|
|packages/jest-validate/src/__tests__/validateCLIOptions.test.js|6✔️|||83ms|
|
||||||
|
|packages/jest-watcher/src/lib/__tests__/formatTestNameByPattern.test.ts|11✔️|||129ms|
|
||||||
|
|packages/jest-watcher/src/lib/__tests__/prompt.test.ts|3✔️|||91ms|
|
||||||
|
|packages/jest-watcher/src/lib/__tests__/scroll.test.ts|5✔️|||57ms|
|
||||||
|
|packages/jest-worker/src/__tests__/Farm.test.js|10✔️|||158ms|
|
||||||
|
|packages/jest-worker/src/__tests__/FifoQueue.test.js|3✔️|||48ms|
|
||||||
|
|packages/jest-worker/src/__tests__/index.test.js|8✔️|||230ms|
|
||||||
|
|packages/jest-worker/src/__tests__/PriorityQueue.test.js|5✔️|||63ms|
|
||||||
|
|packages/jest-worker/src/__tests__/process-integration.test.js|5✔️|||62ms|
|
||||||
|
|packages/jest-worker/src/__tests__/thread-integration.test.js|6✔️|||114ms|
|
||||||
|
|packages/jest-worker/src/__tests__/WorkerPool.test.js|3✔️|||51ms|
|
||||||
|
|packages/jest-worker/src/base/__tests__/BaseWorkerPool.test.js|11✔️|||653ms|
|
||||||
|
|packages/jest-worker/src/workers/__tests__/ChildProcessWorker.test.js|17✔️|||184ms|
|
||||||
|
|packages/jest-worker/src/workers/__tests__/NodeThreadsWorker.test.js|15✔️|||258ms|
|
||||||
|
|packages/jest-worker/src/workers/__tests__/processChild.test.js|10✔️|||135ms|
|
||||||
|
|packages/jest-worker/src/workers/__tests__/threadChild.test.js|10✔️|||120ms|
|
||||||
|
|packages/pretty-format/src/__tests__/AsymmetricMatcher.test.ts|38✔️|||137ms|
|
||||||
|
|packages/pretty-format/src/__tests__/ConvertAnsi.test.ts|6✔️|||43ms|
|
||||||
|
|packages/pretty-format/src/__tests__/DOMCollection.test.ts|10✔️|||64ms|
|
||||||
|
|packages/pretty-format/src/__tests__/DOMElement.test.ts|28✔️|||148ms|
|
||||||
|
|packages/pretty-format/src/__tests__/Immutable.test.ts|111✔️|||443ms|
|
||||||
|
|packages/pretty-format/src/__tests__/prettyFormat.test.ts|86✔️|||219ms|
|
||||||
|
|packages/pretty-format/src/__tests__/react.test.tsx|55✔️|||325ms|
|
||||||
|
|packages/pretty-format/src/__tests__/ReactElement.test.ts|3✔️|||64ms|
|
||||||
|
### <a id="user-content-r0s75" href="#r0s75">e2e/__tests__/jestChangedFiles.test.ts</a> ❌
|
||||||
|
**10** tests were completed in **9.045s** with **9** passed, **1** failed and **0** skipped.
|
||||||
|
|
||||||
|
|Result|Test|Time|
|
||||||
|
|:---:|:---|---:|
|
||||||
|
|✔️|gets hg SCM roots and dedupes them|559ms|
|
||||||
|
|✔️|gets git SCM roots and dedupes them|416ms|
|
||||||
|
|✔️|gets mixed git and hg SCM roots and dedupes them|467ms|
|
||||||
|
|✔️|gets changed files for git|2.298s|
|
||||||
|
|✔️|monitors only root paths for git|151ms|
|
||||||
|
|✔️|does not find changes in files with no diff, for git|628ms|
|
||||||
|
|✔️|handles a bad revision for "changedSince", for git|878ms|
|
||||||
|
|❌|gets changed files for hg|2.219s|
|
||||||
|
|✔️|monitors only root paths for hg|281ms|
|
||||||
|
|✔️|handles a bad revision for "changedSince", for hg|949ms|
|
||||||
|
### <a id="user-content-r0s98" href="#r0s98">e2e/__tests__/onlyChanged.test.ts</a> ❌
|
||||||
|
**9** tests were completed in **22.281s** with **8** passed, **1** failed and **0** skipped.
|
||||||
|
|
||||||
|
|Result|Test|Time|
|
||||||
|
|:---:|:---|---:|
|
||||||
|
|✔️|run for "onlyChanged" and "changedSince"|1.464s|
|
||||||
|
|✔️|run only changed files|5.196s|
|
||||||
|
|✔️|report test coverage for only changed files|1.889s|
|
||||||
|
|✔️|report test coverage of source on test file change under only changed files|822ms|
|
||||||
|
|✔️|do not pickup non-tested files when reporting coverage on only changed files|861ms|
|
||||||
|
|✔️|collect test coverage when using onlyChanged|1.058s|
|
||||||
|
|✔️|onlyChanged in config is overwritten by --all or testPathPattern|7.023s|
|
||||||
|
|❌|gets changed files for hg|3.765s|
|
||||||
|
|✔️|path on Windows is case-insensitive|0ms|
|
||||||
|
|
@ -1,98 +1,106 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`dart-json tests matches report snapshot 1`] = `
|
exports[`dart-json tests matches report snapshot 1`] = `
|
||||||
Object {
|
TestRunResult {
|
||||||
"annotations": Array [
|
"path": "fixtures/dart-json.json",
|
||||||
Object {
|
"suites": Array [
|
||||||
"annotation_level": "failure",
|
TestSuiteResult {
|
||||||
"end_line": 13,
|
"groups": Array [
|
||||||
"message": "Expected: <2>
|
TestGroupResult {
|
||||||
|
"name": "Test 1",
|
||||||
|
"tests": Array [
|
||||||
|
TestCaseResult {
|
||||||
|
"error": undefined,
|
||||||
|
"name": "Test 1 Passing test",
|
||||||
|
"result": "success",
|
||||||
|
"time": 36,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
TestGroupResult {
|
||||||
|
"name": "Test 1 Test 1.1",
|
||||||
|
"tests": Array [
|
||||||
|
TestCaseResult {
|
||||||
|
"error": Object {
|
||||||
|
"line": 13,
|
||||||
|
"message": "Expected: <2>
|
||||||
Actual: <1>
|
Actual: <1>
|
||||||
|
",
|
||||||
|
"path": "test/main_test.dart",
|
||||||
package:test_api expect
|
"stackTrace": "package:test_api expect
|
||||||
test\\\\main_test.dart 13:9 main.<fn>.<fn>.<fn>
|
test\\\\main_test.dart 13:9 main.<fn>.<fn>.<fn>
|
||||||
",
|
",
|
||||||
"path": "test/main_test.dart",
|
},
|
||||||
"start_line": 13,
|
"name": "Test 1 Test 1.1 Failing test",
|
||||||
"title": "[test\\\\main_test.dart] Test 1 Test 1.1 Failing test",
|
"result": "failed",
|
||||||
},
|
"time": 20,
|
||||||
Object {
|
},
|
||||||
"annotation_level": "failure",
|
TestCaseResult {
|
||||||
"end_line": 2,
|
"error": Object {
|
||||||
"message": "Exception: Some error
|
"line": 2,
|
||||||
|
"message": "Exception: Some error",
|
||||||
package:darttest/main.dart 2:3 throwError
|
"path": "lib/main.dart",
|
||||||
|
"stackTrace": "package:darttest/main.dart 2:3 throwError
|
||||||
test\\\\main_test.dart 17:9 main.<fn>.<fn>.<fn>
|
test\\\\main_test.dart 17:9 main.<fn>.<fn>.<fn>
|
||||||
",
|
",
|
||||||
"path": "lib/main.dart",
|
},
|
||||||
"start_line": 2,
|
"name": "Test 1 Test 1.1 Exception in target unit",
|
||||||
"title": "[test\\\\main_test.dart] Test 1 Test 1.1 Exception in target unit",
|
"result": "failed",
|
||||||
},
|
"time": 6,
|
||||||
Object {
|
},
|
||||||
"annotation_level": "failure",
|
],
|
||||||
"end_line": 24,
|
},
|
||||||
"message": "Exception: Some error
|
TestGroupResult {
|
||||||
|
"name": "Test 2",
|
||||||
test\\\\main_test.dart 24:7 main.<fn>.<fn>
|
"tests": Array [
|
||||||
|
TestCaseResult {
|
||||||
|
"error": Object {
|
||||||
|
"line": 24,
|
||||||
|
"message": "Exception: Some error",
|
||||||
|
"path": "test/main_test.dart",
|
||||||
|
"stackTrace": "test\\\\main_test.dart 24:7 main.<fn>.<fn>
|
||||||
",
|
",
|
||||||
"path": "test/main_test.dart",
|
},
|
||||||
"start_line": 24,
|
"name": "Test 2 Exception in test",
|
||||||
"title": "[test\\\\main_test.dart] Test 2 Exception in test",
|
"result": "failed",
|
||||||
|
"time": 12,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"name": "test\\\\main_test.dart",
|
||||||
|
"totalTime": undefined,
|
||||||
},
|
},
|
||||||
Object {
|
TestSuiteResult {
|
||||||
"annotation_level": "failure",
|
"groups": Array [
|
||||||
"end_line": 5,
|
TestGroupResult {
|
||||||
"message": "TimeoutException after 0:00:00.000001: Test timed out after 0 seconds.
|
"name": null,
|
||||||
|
"tests": Array [
|
||||||
dart:isolate _RawReceivePortImpl._handleMessage
|
TestCaseResult {
|
||||||
|
"error": Object {
|
||||||
|
"line": 5,
|
||||||
|
"message": "TimeoutException after 0:00:00.000001: Test timed out after 0 seconds.",
|
||||||
|
"path": "test/second_test.dart",
|
||||||
|
"stackTrace": "dart:isolate _RawReceivePortImpl._handleMessage
|
||||||
",
|
",
|
||||||
"path": "test/second_test.dart",
|
},
|
||||||
"start_line": 5,
|
"name": "Timeout test",
|
||||||
"title": "[test\\\\second_test.dart] Timeout test",
|
"result": "failed",
|
||||||
|
"time": 37,
|
||||||
|
},
|
||||||
|
TestCaseResult {
|
||||||
|
"error": undefined,
|
||||||
|
"name": "Skipped test",
|
||||||
|
"result": "skipped",
|
||||||
|
"time": 14,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"name": "test\\\\second_test.dart",
|
||||||
|
"totalTime": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"summary": "
|
"totalTime": 3760,
|
||||||
|
|
||||||
### fixtures/dart-json.json
|
|
||||||
|
|
||||||
**6** tests were completed in **3.760s** with **1** passed, **1** skipped and **4** failed.
|
|
||||||
|
|
||||||
| Result | Suite | Tests | Time | Passed ✔️ | Skipped ✖️ | Failed ❌ |
|
|
||||||
| :---: | :--- | ---: | ---: | ---: | ---: | ---: |
|
|
||||||
| ❌ | [test\\\\main_test.dart](#ts-0-test-maintest-dart) | 4 | 74ms | 1 | 0 | 3 |
|
|
||||||
| ❌ | [test\\\\second_test.dart](#ts-1-test-secondtest-dart) | 2 | 51ms | 0 | 1 | 1 |
|
|
||||||
|
|
||||||
# Test Suites
|
|
||||||
|
|
||||||
## <a id=\\"user-content-ts-0-test-maintest-dart\\" href=\\"#ts-0-test-maintest-dart\\">test\\\\main_test.dart</a> ❌
|
|
||||||
|
|
||||||
### Test 1
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ✔️ | Test 1 Passing test | 36ms |
|
|
||||||
|
|
||||||
### Test 1 Test 1.1
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Test 1 Test 1.1 Failing test | 20ms |
|
|
||||||
| ❌ | Test 1 Test 1.1 Exception in target unit | 6ms |
|
|
||||||
|
|
||||||
### Test 2
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Test 2 Exception in test | 12ms |
|
|
||||||
|
|
||||||
## <a id=\\"user-content-ts-1-test-secondtest-dart\\" href=\\"#ts-1-test-secondtest-dart\\">test\\\\second_test.dart</a> ❌
|
|
||||||
|
|
||||||
| Result | Test | Time |
|
|
||||||
| :---: | :--- | ---: |
|
|
||||||
| ❌ | Timeout test | 37ms |
|
|
||||||
| ✖️ | Skipped test | 14ms |
|
|
||||||
",
|
|
||||||
"title": "Dart tests ❌",
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,31 +1,30 @@
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
|
||||||
import {parseDartJson} from '../src/parsers/dart-json/dart-json-parser'
|
import {DartJsonParser} from '../src/parsers/dart-json/dart-json-parser'
|
||||||
import {ParseOptions} from '../src/parsers/parser-types'
|
import {ParseOptions} from '../src/test-parser'
|
||||||
|
import {getReport} from '../src/report/get-report'
|
||||||
import {normalizeFilePath} from '../src/utils/file-utils'
|
import {normalizeFilePath} from '../src/utils/file-utils'
|
||||||
|
|
||||||
const fixturePath = path.join(__dirname, 'fixtures', 'dart-json.json')
|
|
||||||
const outputPath = path.join(__dirname, '__outputs__', 'dart-json.md')
|
|
||||||
const xmlFixture = {
|
|
||||||
path: normalizeFilePath(path.relative(__dirname, fixturePath)),
|
|
||||||
content: fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('dart-json tests', () => {
|
describe('dart-json tests', () => {
|
||||||
it('matches report snapshot', async () => {
|
it('matches report snapshot', async () => {
|
||||||
const opts: ParseOptions = {
|
const opts: ParseOptions = {
|
||||||
name: 'Dart tests',
|
parseErrors: true,
|
||||||
annotations: true,
|
|
||||||
trackedFiles: ['lib/main.dart', 'test/main_test.dart', 'test/second_test.dart'],
|
trackedFiles: ['lib/main.dart', 'test/main_test.dart', 'test/second_test.dart'],
|
||||||
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/dart/'
|
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/dart/'
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await parseDartJson([xmlFixture], opts)
|
const fixturePath = path.join(__dirname, 'fixtures', 'dart-json.json')
|
||||||
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
const outputPath = path.join(__dirname, '__outputs__', 'dart-json.md')
|
||||||
fs.writeFileSync(outputPath, result?.output?.summary ?? '')
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||||
|
|
||||||
expect(result.success).toBeFalsy()
|
const parser = new DartJsonParser(opts)
|
||||||
expect(result?.output).toMatchSnapshot()
|
const result = await parser.parse(filePath, fileContent)
|
||||||
|
expect(result).toMatchSnapshot()
|
||||||
|
|
||||||
|
const report = getReport([result])
|
||||||
|
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||||
|
fs.writeFileSync(outputPath, report)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,51 @@
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
|
||||||
import {parseDotnetTrx} from '../src/parsers/dotnet-trx/dotnet-trx-parser'
|
import {DotnetTrxParser} from '../src/parsers/dotnet-trx/dotnet-trx-parser'
|
||||||
import {ParseOptions} from '../src/parsers/parser-types'
|
import {ParseOptions} from '../src/test-parser'
|
||||||
|
import {getReport} from '../src/report/get-report'
|
||||||
import {normalizeFilePath} from '../src/utils/file-utils'
|
import {normalizeFilePath} from '../src/utils/file-utils'
|
||||||
|
|
||||||
const fixturePath = path.join(__dirname, 'fixtures', 'dotnet-trx.trx')
|
|
||||||
const outputPath = path.join(__dirname, '__outputs__', 'dotnet-trx.md')
|
|
||||||
const xmlFixture = {
|
|
||||||
path: normalizeFilePath(path.relative(__dirname, fixturePath)),
|
|
||||||
content: fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('dotnet-trx tests', () => {
|
describe('dotnet-trx tests', () => {
|
||||||
it('matches report snapshot', async () => {
|
it('matches report snapshot', async () => {
|
||||||
|
const fixturePath = path.join(__dirname, 'fixtures', 'dotnet-trx.trx')
|
||||||
|
const outputPath = path.join(__dirname, '__outputs__', 'dotnet-trx.md')
|
||||||
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||||
|
|
||||||
const opts: ParseOptions = {
|
const opts: ParseOptions = {
|
||||||
name: 'Dotnet TRX tests',
|
parseErrors: true,
|
||||||
annotations: true,
|
|
||||||
trackedFiles: ['DotnetTests.Unit/Calculator.cs', 'DotnetTests.XUnitTests/CalculatorTests.cs'],
|
trackedFiles: ['DotnetTests.Unit/Calculator.cs', 'DotnetTests.XUnitTests/CalculatorTests.cs'],
|
||||||
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/dotnet/'
|
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/dotnet/'
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await parseDotnetTrx([xmlFixture], opts)
|
const parser = new DotnetTrxParser(opts)
|
||||||
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
const result = await parser.parse(filePath, fileContent)
|
||||||
fs.writeFileSync(outputPath, result?.output?.summary ?? '')
|
expect(result).toMatchSnapshot()
|
||||||
|
|
||||||
expect(result.success).toBeFalsy()
|
const report = getReport([result])
|
||||||
expect(result?.output).toMatchSnapshot()
|
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||||
|
fs.writeFileSync(outputPath, report)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('report from FluentValidation test results matches snapshot', async () => {
|
||||||
|
const fixturePath = path.join(__dirname, 'fixtures', 'external', 'FluentValidation.Tests.trx')
|
||||||
|
const outputPath = path.join(__dirname, '__outputs__', 'fluent-validation-test-results.md')
|
||||||
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||||
|
|
||||||
|
const opts: ParseOptions = {
|
||||||
|
trackedFiles: [],
|
||||||
|
parseErrors: true,
|
||||||
|
workDir: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = new DotnetTrxParser(opts)
|
||||||
|
const result = await parser.parse(filePath, fileContent)
|
||||||
|
expect(result).toMatchSnapshot()
|
||||||
|
|
||||||
|
const report = getReport([result], {listTests: 'failed'})
|
||||||
|
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||||
|
fs.writeFileSync(outputPath, report)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
4662
__tests__/fixtures/external/FluentValidation.Tests.trx
vendored
Normal file
4662
__tests__/fixtures/external/FluentValidation.Tests.trx
vendored
Normal file
File diff suppressed because it is too large
Load diff
2317
__tests__/fixtures/external/jest/files.txt
vendored
Normal file
2317
__tests__/fixtures/external/jest/files.txt
vendored
Normal file
File diff suppressed because it is too large
Load diff
9357
__tests__/fixtures/external/jest/jest-test-results.xml
vendored
Normal file
9357
__tests__/fixtures/external/jest/jest-test-results.xml
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,31 +1,53 @@
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
|
||||||
import {parseJestJunit} from '../src/parsers/jest-junit/jest-junit-parser'
|
import {JestJunitParser} from '../src/parsers/jest-junit/jest-junit-parser'
|
||||||
import {ParseOptions} from '../src/parsers/parser-types'
|
import {ParseOptions} from '../src/test-parser'
|
||||||
|
import {getReport} from '../src/report/get-report'
|
||||||
import {normalizeFilePath} from '../src/utils/file-utils'
|
import {normalizeFilePath} from '../src/utils/file-utils'
|
||||||
|
|
||||||
const fixturePath = path.join(__dirname, 'fixtures', 'jest-junit.xml')
|
|
||||||
const outputPath = path.join(__dirname, '__outputs__', 'jest-junit.md')
|
|
||||||
const xmlFixture = {
|
|
||||||
path: normalizeFilePath(path.relative(__dirname, fixturePath)),
|
|
||||||
content: fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('jest-junit tests', () => {
|
describe('jest-junit tests', () => {
|
||||||
it('matches report snapshot', async () => {
|
it('report from ./reports/jest test results matches snapshot', async () => {
|
||||||
|
const fixturePath = path.join(__dirname, 'fixtures', 'jest-junit.xml')
|
||||||
|
const outputPath = path.join(__dirname, '__outputs__', 'jest-junit.md')
|
||||||
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||||
|
|
||||||
const opts: ParseOptions = {
|
const opts: ParseOptions = {
|
||||||
name: 'jest tests',
|
parseErrors: true,
|
||||||
annotations: true,
|
|
||||||
trackedFiles: ['__tests__/main.test.js', '__tests__/second.test.js', 'lib/main.js'],
|
trackedFiles: ['__tests__/main.test.js', '__tests__/second.test.js', 'lib/main.js'],
|
||||||
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/jest/'
|
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/jest/'
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await parseJestJunit([xmlFixture], opts)
|
const parser = new JestJunitParser(opts)
|
||||||
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
const result = await parser.parse(filePath, fileContent)
|
||||||
fs.writeFileSync(outputPath, result?.output?.summary ?? '')
|
expect(result).toMatchSnapshot()
|
||||||
|
|
||||||
expect(result.success).toBeFalsy()
|
const report = getReport([result])
|
||||||
expect(result?.output).toMatchSnapshot()
|
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||||
|
fs.writeFileSync(outputPath, report)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('report from facebook/jest test results matches snapshot', async () => {
|
||||||
|
const fixturePath = path.join(__dirname, 'fixtures', 'external', 'jest', 'jest-test-results.xml')
|
||||||
|
const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'jest', 'files.txt')
|
||||||
|
const outputPath = path.join(__dirname, '__outputs__', 'jest-test-results.md')
|
||||||
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||||
|
|
||||||
|
const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
|
||||||
|
const opts: ParseOptions = {
|
||||||
|
parseErrors: true,
|
||||||
|
trackedFiles,
|
||||||
|
workDir: '/home/dorny/dorny/jest/'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = new JestJunitParser(opts)
|
||||||
|
const result = await parser.parse(filePath, fileContent)
|
||||||
|
expect(result).toMatchSnapshot()
|
||||||
|
|
||||||
|
const report = getReport([result], {listTests: 'failed'})
|
||||||
|
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||||
|
fs.writeFileSync(outputPath, report)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
44
action.yml
44
action.yml
|
|
@ -4,19 +4,14 @@ description: |
|
||||||
Supports .NET (xUnit, NUnit, MSTest), Dart, Flutter and JavaScript (JEST).
|
Supports .NET (xUnit, NUnit, MSTest), Dart, Flutter and JavaScript (JEST).
|
||||||
author: Michal Dorner <dorner.michal@gmail.com>
|
author: Michal Dorner <dorner.michal@gmail.com>
|
||||||
inputs:
|
inputs:
|
||||||
annotations:
|
|
||||||
description: Enables code annotations with error message and stack trace captured during test execution
|
|
||||||
required: true
|
|
||||||
default: 'true'
|
|
||||||
fail-on-error:
|
|
||||||
description: Set this action as failed if test report contain any failed test
|
|
||||||
required: true
|
|
||||||
default: 'true'
|
|
||||||
name:
|
name:
|
||||||
description: Name of the check run
|
description: Name of the check run
|
||||||
required: true
|
required: true
|
||||||
path:
|
path:
|
||||||
description: Path to test report
|
description: |
|
||||||
|
Coma separated list of paths to test reports
|
||||||
|
Supports wildcards via [fast-glob](https://github.com/mrmlnc/fast-glob)
|
||||||
|
All matched result files must be of same format
|
||||||
required: true
|
required: true
|
||||||
reporter:
|
reporter:
|
||||||
description: |
|
description: |
|
||||||
|
|
@ -26,13 +21,38 @@ inputs:
|
||||||
- flutter-machine
|
- flutter-machine
|
||||||
- jest-junit
|
- jest-junit
|
||||||
required: true
|
required: true
|
||||||
|
list-suites:
|
||||||
|
description: |
|
||||||
|
Limits which test suites are listed. Supported options:
|
||||||
|
- all
|
||||||
|
- only-failed
|
||||||
|
required: true
|
||||||
|
default: 'all'
|
||||||
|
list-tests:
|
||||||
|
description: |
|
||||||
|
Limits which test cases are listed. Supported options:
|
||||||
|
- all
|
||||||
|
- only-failed
|
||||||
|
- none
|
||||||
|
required: true
|
||||||
|
default: 'all'
|
||||||
|
max-annotations:
|
||||||
|
description: |
|
||||||
|
Limits number of created annotations with error message and stack trace captured during test execution.
|
||||||
|
Must be less or equal to 50.
|
||||||
|
required: true
|
||||||
|
default: '10'
|
||||||
|
fail-on-error:
|
||||||
|
description: Set this action as failed if test report contain any failed test
|
||||||
|
required: true
|
||||||
|
default: 'true'
|
||||||
|
working-directory:
|
||||||
|
description: Relative path under $GITHUB_WORKSPACE where the repository was checked out
|
||||||
|
required: false
|
||||||
token:
|
token:
|
||||||
description: GitHub Access Token
|
description: GitHub Access Token
|
||||||
required: false
|
required: false
|
||||||
default: ${{ github.token }}
|
default: ${{ github.token }}
|
||||||
working-directory:
|
|
||||||
description: Relative path under $GITHUB_WORKSPACE where the repository was checked out
|
|
||||||
required: false
|
|
||||||
outputs:
|
outputs:
|
||||||
conclusion:
|
conclusion:
|
||||||
description: |
|
description: |
|
||||||
|
|
|
||||||
1119
dist/index.js
generated
vendored
1119
dist/index.js
generated
vendored
File diff suppressed because it is too large
Load diff
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
114
src/main.ts
114
src/main.ts
|
|
@ -2,13 +2,20 @@ import * as core from '@actions/core'
|
||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import glob from 'fast-glob'
|
import glob from 'fast-glob'
|
||||||
import {parseDartJson} from './parsers/dart-json/dart-json-parser'
|
|
||||||
import {parseDotnetTrx} from './parsers/dotnet-trx/dotnet-trx-parser'
|
import {ParseOptions, TestParser} from './test-parser'
|
||||||
import {parseJestJunit} from './parsers/jest-junit/jest-junit-parser'
|
import {TestRunResult} from './test-results'
|
||||||
import {FileContent, ParseOptions, ParseTestResult} from './parsers/parser-types'
|
import {getAnnotations} from './report/get-annotations'
|
||||||
|
import {getReport} from './report/get-report'
|
||||||
|
|
||||||
|
import {DartJsonParser} from './parsers/dart-json/dart-json-parser'
|
||||||
|
import {DotnetTrxParser} from './parsers/dotnet-trx/dotnet-trx-parser'
|
||||||
|
import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser'
|
||||||
|
|
||||||
import {normalizeDirPath} from './utils/file-utils'
|
import {normalizeDirPath} from './utils/file-utils'
|
||||||
import {listFiles} from './utils/git'
|
import {listFiles} from './utils/git'
|
||||||
import {getCheckRunSha} from './utils/github-utils'
|
import {getCheckRunSha} from './utils/github-utils'
|
||||||
|
import {Icon} from './utils/markdown-utils'
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -19,13 +26,30 @@ async function run(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const annotations = core.getInput('annotations', {required: true}) === 'true'
|
|
||||||
const failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
|
|
||||||
const name = core.getInput('name', {required: true})
|
const name = core.getInput('name', {required: true})
|
||||||
const path = core.getInput('path', {required: true})
|
const path = core.getInput('path', {required: true})
|
||||||
const reporter = core.getInput('reporter', {required: true})
|
const reporter = core.getInput('reporter', {required: true})
|
||||||
const token = core.getInput('token', {required: true})
|
const listSuites = core.getInput('list-suites', {required: true})
|
||||||
|
const listTests = core.getInput('list-tests', {required: true})
|
||||||
|
const maxAnnotations = parseInt(core.getInput('max-annotations', {required: true}))
|
||||||
|
const failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
|
||||||
const workDirInput = core.getInput('working-directory', {required: false})
|
const workDirInput = core.getInput('working-directory', {required: false})
|
||||||
|
const token = core.getInput('token', {required: true})
|
||||||
|
|
||||||
|
if (listSuites !== 'all' && listSuites !== 'failed') {
|
||||||
|
core.setFailed(`Input parameter 'list-suites' has invalid value`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listTests !== 'all' && listTests !== 'failed' && listTests !== 'none') {
|
||||||
|
core.setFailed(`Input parameter 'list-tests' has invalid value`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(maxAnnotations) || maxAnnotations < 0 || maxAnnotations > 50) {
|
||||||
|
core.setFailed(`Input parameter 'max-annotations' has invalid value`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (workDirInput) {
|
if (workDirInput) {
|
||||||
core.info(`Changing directory to ${workDirInput}`)
|
core.info(`Changing directory to ${workDirInput}`)
|
||||||
|
|
@ -37,26 +61,41 @@ async function main(): Promise<void> {
|
||||||
const sha = getCheckRunSha()
|
const sha = getCheckRunSha()
|
||||||
|
|
||||||
// We won't need tracked files if we are not going to create annotations
|
// We won't need tracked files if we are not going to create annotations
|
||||||
const trackedFiles = annotations ? await listFiles() : []
|
const parseErrors = maxAnnotations > 0
|
||||||
|
const trackedFiles = parseErrors ? await listFiles() : []
|
||||||
|
|
||||||
const opts: ParseOptions = {
|
const options: ParseOptions = {
|
||||||
name,
|
|
||||||
annotations,
|
|
||||||
trackedFiles,
|
trackedFiles,
|
||||||
workDir
|
workDir,
|
||||||
|
parseErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
const parser = getParser(reporter)
|
core.info(`Using test report parser '${reporter}'`)
|
||||||
const files = await getFiles(path)
|
const parser = getParser(reporter, options)
|
||||||
|
|
||||||
|
const files = await getFiles(path)
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
core.setFailed(`No file matches path '${path}'`)
|
core.setFailed(`No file matches path '${path}'`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
core.info(`Using test report parser '${reporter}'`)
|
const results: TestRunResult[] = []
|
||||||
const result = await parser(files, opts)
|
for (const file of files) {
|
||||||
const conclusion = result.success ? 'success' : 'failure'
|
core.info(`Processing test report ${file}`)
|
||||||
|
const content = await fs.promises.readFile(file, {encoding: 'utf8'})
|
||||||
|
const tr = await parser.parse(file, content)
|
||||||
|
results.push(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info('Creating report summary')
|
||||||
|
const summary = getReport(results, {listSuites, listTests})
|
||||||
|
|
||||||
|
core.info('Creating annotations')
|
||||||
|
const annotations = getAnnotations(results, maxAnnotations)
|
||||||
|
|
||||||
|
const isFailed = results.some(tr => tr.result === 'failed')
|
||||||
|
const conclusion = isFailed ? 'failure' : 'success'
|
||||||
|
const icon = isFailed ? Icon.fail : Icon.success
|
||||||
|
|
||||||
core.info(`Creating check run '${name}' with conclusion '${conclusion}'`)
|
core.info(`Creating check run '${name}' with conclusion '${conclusion}'`)
|
||||||
await octokit.checks.create({
|
await octokit.checks.create({
|
||||||
|
|
@ -64,40 +103,49 @@ async function main(): Promise<void> {
|
||||||
name,
|
name,
|
||||||
conclusion,
|
conclusion,
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
output: result.output,
|
output: {
|
||||||
|
title: `${name} ${icon}`,
|
||||||
|
summary,
|
||||||
|
annotations
|
||||||
|
},
|
||||||
...github.context.repo
|
...github.context.repo
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const passed = results.reduce((sum, tr) => sum + tr.passed, 0)
|
||||||
|
const failed = results.reduce((sum, tr) => sum + tr.failed, 0)
|
||||||
|
const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0)
|
||||||
|
const time = results.reduce((sum, tr) => sum + tr.time, 0)
|
||||||
|
|
||||||
core.setOutput('conclusion', conclusion)
|
core.setOutput('conclusion', conclusion)
|
||||||
if (failOnError && !result.success) {
|
core.setOutput('passed', passed)
|
||||||
|
core.setOutput('failed', failed)
|
||||||
|
core.setOutput('skipped', skipped)
|
||||||
|
core.setOutput('time', time)
|
||||||
|
|
||||||
|
if (failOnError && isFailed) {
|
||||||
core.setFailed(`Failed test has been found and 'fail-on-error' option is set to ${failOnError}`)
|
core.setFailed(`Failed test has been found and 'fail-on-error' option is set to ${failOnError}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getParser(reporter: string): ParseTestResult {
|
function getParser(reporter: string, options: ParseOptions): TestParser {
|
||||||
switch (reporter) {
|
switch (reporter) {
|
||||||
case 'dart-json':
|
case 'dart-json':
|
||||||
return parseDartJson
|
return new DartJsonParser(options)
|
||||||
case 'dotnet-trx':
|
case 'dotnet-trx':
|
||||||
return parseDotnetTrx
|
return new DotnetTrxParser(options)
|
||||||
case 'flutter-machine':
|
case 'flutter-machine':
|
||||||
return parseDartJson
|
return new DartJsonParser(options)
|
||||||
case 'jest-junit':
|
case 'jest-junit':
|
||||||
return parseJestJunit
|
return new JestJunitParser(options)
|
||||||
default:
|
default:
|
||||||
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`)
|
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFiles(pattern: string): Promise<FileContent[]> {
|
export async function getFiles(pattern: string): Promise<string[]> {
|
||||||
const paths = await glob(pattern, {dot: true})
|
const tasks = pattern.split(',').map(async pat => glob(pat, {dot: true}))
|
||||||
return Promise.all(
|
const paths = await Promise.all(tasks)
|
||||||
paths.map(async path => {
|
return paths.flat()
|
||||||
core.info(`Reading test report '${path}'`)
|
|
||||||
const content = await fs.promises.readFile(path, {encoding: 'utf8'})
|
|
||||||
return {path, content}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types'
|
import {ParseOptions, TestParser} from '../../test-parser'
|
||||||
|
|
||||||
import getReport from '../../report/get-report'
|
|
||||||
import {normalizeFilePath} from '../../utils/file-utils'
|
import {normalizeFilePath} from '../../utils/file-utils'
|
||||||
import {Icon, fixEol} from '../../utils/markdown-utils'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ReportEvent,
|
ReportEvent,
|
||||||
|
|
@ -25,8 +23,9 @@ import {
|
||||||
TestRunResult,
|
TestRunResult,
|
||||||
TestSuiteResult,
|
TestSuiteResult,
|
||||||
TestGroupResult,
|
TestGroupResult,
|
||||||
TestCaseResult
|
TestCaseResult,
|
||||||
} from '../../report/test-results'
|
TestCaseError
|
||||||
|
} from '../../test-results'
|
||||||
|
|
||||||
class TestRun {
|
class TestRun {
|
||||||
constructor(readonly path: string, readonly suites: TestSuite[], readonly success: boolean, readonly time: number) {}
|
constructor(readonly path: string, readonly suites: TestSuite[], readonly success: boolean, readonly time: number) {}
|
||||||
|
|
@ -69,165 +68,143 @@ class TestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseDartJson(files: FileContent[], options: ParseOptions): Promise<TestResult> {
|
export class DartJsonParser implements TestParser {
|
||||||
const testRuns = files.map(f => getTestRun(f.path, f.content))
|
constructor(readonly options: ParseOptions) {}
|
||||||
const testRunsResults = testRuns.map(getTestRunResult)
|
|
||||||
const success = testRuns.every(tr => tr.success)
|
|
||||||
const icon = success ? Icon.success : Icon.fail
|
|
||||||
|
|
||||||
return {
|
async parse(path: string, content: string): Promise<TestRunResult> {
|
||||||
success,
|
const tr = this.getTestRun(path, content)
|
||||||
output: {
|
const result = this.getTestRunResult(tr)
|
||||||
title: `${options.name.trim()} ${icon}`,
|
return Promise.resolve(result)
|
||||||
summary: getReport(testRunsResults),
|
|
||||||
annotations: options.annotations ? getAnnotations(testRuns, options.workDir, options.trackedFiles) : undefined
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function getTestRun(path: string, content: string): TestRun {
|
private getTestRun(path: string, content: string): TestRun {
|
||||||
core.info(`Parsing content of '${path}'`)
|
core.info(`Parsing content of '${path}'`)
|
||||||
const lines = content.split(/\n\r?/g)
|
const lines = content.split(/\n\r?/g)
|
||||||
const events = lines
|
const events = lines
|
||||||
.map((str, i) => {
|
.map((str, i) => {
|
||||||
if (str.trim() === '') {
|
if (str.trim() === '') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return JSON.parse(str)
|
return JSON.parse(str)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const col = e.columnNumber !== undefined ? `:${e.columnNumber}` : ''
|
const col = e.columnNumber !== undefined ? `:${e.columnNumber}` : ''
|
||||||
new Error(`Invalid JSON at ${path}:${i + 1}${col}\n\n${e}`)
|
new Error(`Invalid JSON at ${path}:${i + 1}${col}\n\n${e}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(evt => evt != null) as ReportEvent[]
|
||||||
|
|
||||||
|
let success = false
|
||||||
|
let totalTime = 0
|
||||||
|
const suites: {[id: number]: TestSuite} = {}
|
||||||
|
const tests: {[id: number]: TestCase} = {}
|
||||||
|
|
||||||
|
for (const evt of events) {
|
||||||
|
if (isSuiteEvent(evt)) {
|
||||||
|
suites[evt.suite.id] = new TestSuite(evt.suite)
|
||||||
|
} else if (isGroupEvent(evt)) {
|
||||||
|
suites[evt.group.suiteID].groups[evt.group.id] = new TestGroup(evt.group)
|
||||||
|
} else if (isTestStartEvent(evt) && evt.test.url !== null) {
|
||||||
|
const test: TestCase = new TestCase(evt)
|
||||||
|
const suite = suites[evt.test.suiteID]
|
||||||
|
const group = suite.groups[evt.test.groupIDs[evt.test.groupIDs.length - 1]]
|
||||||
|
group.tests.push(test)
|
||||||
|
tests[evt.test.id] = test
|
||||||
|
} else if (isTestDoneEvent(evt) && !evt.hidden) {
|
||||||
|
tests[evt.testID].testDone = evt
|
||||||
|
} else if (isErrorEvent(evt)) {
|
||||||
|
tests[evt.testID].error = evt
|
||||||
|
} else if (isDoneEvent(evt)) {
|
||||||
|
success = evt.success
|
||||||
|
totalTime = evt.time
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TestRun(path, Object.values(suites), success, totalTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTestRunResult(tr: TestRun): TestRunResult {
|
||||||
|
const suites = tr.suites.map(s => {
|
||||||
|
return new TestSuiteResult(s.suite.path, this.getGroups(s))
|
||||||
})
|
})
|
||||||
.filter(evt => evt != null) as ReportEvent[]
|
|
||||||
|
|
||||||
let success = false
|
return new TestRunResult(tr.path, suites, tr.time)
|
||||||
let totalTime = 0
|
}
|
||||||
const suites: {[id: number]: TestSuite} = {}
|
|
||||||
const tests: {[id: number]: TestCase} = {}
|
|
||||||
|
|
||||||
for (const evt of events) {
|
private getGroups(suite: TestSuite): TestGroupResult[] {
|
||||||
if (isSuiteEvent(evt)) {
|
const groups = Object.values(suite.groups).filter(grp => grp.tests.length > 0)
|
||||||
suites[evt.suite.id] = new TestSuite(evt.suite)
|
groups.sort((a, b) => (a.group.line ?? 0) - (b.group.line ?? 0))
|
||||||
} else if (isGroupEvent(evt)) {
|
|
||||||
suites[evt.group.suiteID].groups[evt.group.id] = new TestGroup(evt.group)
|
return groups.map(group => {
|
||||||
} else if (isTestStartEvent(evt) && evt.test.url !== null) {
|
group.tests.sort((a, b) => (a.testStart.test.line ?? 0) - (b.testStart.test.line ?? 0))
|
||||||
const test: TestCase = new TestCase(evt)
|
const tests = group.tests.map(t => this.getTest(t))
|
||||||
const suite = suites[evt.test.suiteID]
|
return new TestGroupResult(group.group.name, tests)
|
||||||
const group = suite.groups[evt.test.groupIDs[evt.test.groupIDs.length - 1]]
|
})
|
||||||
group.tests.push(test)
|
}
|
||||||
tests[evt.test.id] = test
|
|
||||||
} else if (isTestDoneEvent(evt) && !evt.hidden) {
|
private getTest(tc: TestCase): TestCaseResult {
|
||||||
tests[evt.testID].testDone = evt
|
const error = this.getError(tc)
|
||||||
} else if (isErrorEvent(evt)) {
|
return new TestCaseResult(tc.testStart.test.name, tc.result, tc.time, error)
|
||||||
tests[evt.testID].error = evt
|
}
|
||||||
} else if (isDoneEvent(evt)) {
|
|
||||||
success = evt.success
|
private getError(test: TestCase): TestCaseError | undefined {
|
||||||
totalTime = evt.time
|
if (!this.options.parseErrors || !test.error) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const {workDir, trackedFiles} = this.options
|
||||||
|
const message = test.error?.error ?? ''
|
||||||
|
const stackTrace = test.error?.stackTrace ?? ''
|
||||||
|
const src = this.exceptionThrowSource(stackTrace, trackedFiles)
|
||||||
|
let path
|
||||||
|
let line
|
||||||
|
|
||||||
|
if (src !== undefined) {
|
||||||
|
;(path = src.path), (line = src.line)
|
||||||
|
} else {
|
||||||
|
const testStartPath = this.getRelativePathFromUrl(test.testStart.test.url ?? '', workDir)
|
||||||
|
if (trackedFiles.includes(testStartPath)) {
|
||||||
|
path = testStartPath
|
||||||
|
}
|
||||||
|
line = test.testStart.test.line ?? undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
line,
|
||||||
|
message,
|
||||||
|
stackTrace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TestRun(path, Object.values(suites), success, totalTime)
|
private exceptionThrowSource(ex: string, trackedFiles: string[]): {path: string; line: number} | undefined {
|
||||||
}
|
// imports from package which is tested are listed in stack traces as 'package:xyz/' which maps to relative path 'lib/'
|
||||||
|
const packageRe = /^package:[a-zA-z0-9_$]+\//
|
||||||
|
const lines = ex.split(/\r?\n/).map(str => str.replace(packageRe, 'lib/'))
|
||||||
|
|
||||||
function getTestRunResult(tr: TestRun): TestRunResult {
|
// regexp to extract file path and line number from stack trace
|
||||||
const suites = tr.suites.map(s => {
|
const re = /^(.*)\s+(\d+):\d+\s+/
|
||||||
return new TestSuiteResult(s.suite.path, getGroups(s))
|
for (const str of lines) {
|
||||||
})
|
const match = str.match(re)
|
||||||
|
if (match !== null) {
|
||||||
return new TestRunResult(tr.path, suites, tr.time)
|
const [_, pathStr, lineStr] = match
|
||||||
}
|
const path = normalizeFilePath(pathStr)
|
||||||
|
if (trackedFiles.includes(path)) {
|
||||||
function getGroups(suite: TestSuite): TestGroupResult[] {
|
const line = parseInt(lineStr)
|
||||||
const groups = Object.values(suite.groups).filter(grp => grp.tests.length > 0)
|
return {path, line}
|
||||||
groups.sort((a, b) => (a.group.line ?? 0) - (b.group.line ?? 0))
|
|
||||||
|
|
||||||
return groups.map(group => {
|
|
||||||
group.tests.sort((a, b) => (a.testStart.test.line ?? 0) - (b.testStart.test.line ?? 0))
|
|
||||||
const tests = group.tests.map(t => new TestCaseResult(t.testStart.test.name, t.result, t.time))
|
|
||||||
return new TestGroupResult(group.group.name, tests)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAnnotations(testRuns: TestRun[], workDir: string, trackedFiles: string[]): Annotation[] {
|
|
||||||
const annotations: Annotation[] = []
|
|
||||||
for (const tr of testRuns) {
|
|
||||||
for (const suite of tr.suites) {
|
|
||||||
for (const group of Object.values(suite.groups)) {
|
|
||||||
for (const test of group.tests) {
|
|
||||||
if (test.error) {
|
|
||||||
const err = getAnnotation(test, suite, workDir, trackedFiles)
|
|
||||||
if (err !== null) {
|
|
||||||
annotations.push(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return annotations
|
private getRelativePathFromUrl(file: string, workDir: string): string {
|
||||||
}
|
const prefix = 'file:///'
|
||||||
|
if (file.startsWith(prefix)) {
|
||||||
function getAnnotation(
|
file = file.substr(prefix.length)
|
||||||
test: TestCase,
|
|
||||||
testSuite: TestSuite,
|
|
||||||
workDir: string,
|
|
||||||
trackedFiles: string[]
|
|
||||||
): Annotation | null {
|
|
||||||
const stack = test.error?.stackTrace ?? ''
|
|
||||||
let src = exceptionThrowSource(stack, trackedFiles)
|
|
||||||
if (src === null) {
|
|
||||||
const file = getRelativePathFromUrl(test.testStart.test.url ?? '', workDir)
|
|
||||||
if (!trackedFiles.includes(file)) {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
src = {
|
if (file.startsWith(workDir)) {
|
||||||
file,
|
file = file.substr(workDir.length)
|
||||||
line: test.testStart.test.line ?? 0
|
|
||||||
}
|
}
|
||||||
}
|
return file
|
||||||
|
|
||||||
return {
|
|
||||||
annotation_level: 'failure',
|
|
||||||
start_line: src.line,
|
|
||||||
end_line: src.line,
|
|
||||||
path: src.file,
|
|
||||||
message: `${fixEol(test.error?.error)}\n\n${fixEol(test.error?.stackTrace)}`,
|
|
||||||
title: `[${testSuite.suite.path}] ${test.testStart.test.name}`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exceptionThrowSource(ex: string, trackedFiles: string[]): {file: string; line: number} | null {
|
|
||||||
// imports from package which is tested are listed in stack traces as 'package:xyz/' which maps to relative path 'lib/'
|
|
||||||
const packageRe = /^package:[a-zA-z0-9_$]+\//
|
|
||||||
const lines = ex.split(/\r?\n/).map(str => str.replace(packageRe, 'lib/'))
|
|
||||||
|
|
||||||
// regexp to extract file path and line number from stack trace
|
|
||||||
const re = /^(.*)\s+(\d+):\d+\s+/
|
|
||||||
for (const str of lines) {
|
|
||||||
const match = str.match(re)
|
|
||||||
if (match !== null) {
|
|
||||||
const [_, fileStr, lineStr] = match
|
|
||||||
const file = normalizeFilePath(fileStr)
|
|
||||||
if (trackedFiles.includes(file)) {
|
|
||||||
const line = parseInt(lineStr)
|
|
||||||
return {file, line}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRelativePathFromUrl(file: string, workdir: string): string {
|
|
||||||
const prefix = 'file:///'
|
|
||||||
if (file.startsWith(prefix)) {
|
|
||||||
file = file.substr(prefix.length)
|
|
||||||
}
|
|
||||||
if (file.startsWith(workdir)) {
|
|
||||||
file = file.substr(workdir.length)
|
|
||||||
}
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {ErrorInfo, Outcome, TestMethod, TrxReport} from './dotnet-trx-types'
|
|
||||||
|
|
||||||
import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types'
|
|
||||||
import {parseStringPromise} from 'xml2js'
|
import {parseStringPromise} from 'xml2js'
|
||||||
|
|
||||||
|
import {ErrorInfo, Outcome, TestMethod, TrxReport} from './dotnet-trx-types'
|
||||||
|
import {ParseOptions, TestParser} from '../../test-parser'
|
||||||
|
|
||||||
import {normalizeFilePath} from '../../utils/file-utils'
|
import {normalizeFilePath} from '../../utils/file-utils'
|
||||||
import {parseAttribute} from '../../utils/xml-utils'
|
import {parseIsoDate, parseNetDuration} from '../../utils/parse-utils'
|
||||||
import {Icon, fixEol} from '../../utils/markdown-utils'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TestExecutionResult,
|
TestExecutionResult,
|
||||||
TestRunResult,
|
TestRunResult,
|
||||||
TestSuiteResult,
|
TestSuiteResult,
|
||||||
TestGroupResult,
|
TestGroupResult,
|
||||||
TestCaseResult
|
TestCaseResult,
|
||||||
} from '../../report/test-results'
|
TestCaseError
|
||||||
import getReport from '../../report/get-report'
|
} from '../../test-results'
|
||||||
|
|
||||||
class TestClass {
|
class TestClass {
|
||||||
constructor(readonly name: string) {}
|
constructor(readonly name: string) {}
|
||||||
|
|
@ -42,133 +41,117 @@ class Test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function parseDotnetTrx(files: FileContent[], options: ParseOptions): Promise<TestResult> {
|
export class DotnetTrxParser implements TestParser {
|
||||||
const testRuns: TestRunResult[] = []
|
constructor(readonly options: ParseOptions) {}
|
||||||
const testClasses: TestClass[] = []
|
|
||||||
|
|
||||||
for (const file of files) {
|
async parse(path: string, content: string): Promise<TestRunResult> {
|
||||||
const trx = await getTrxReport(file)
|
const trx = await this.getTrxReport(path, content)
|
||||||
const tc = getTestClasses(trx)
|
const tc = this.getTestClasses(trx)
|
||||||
const tr = getTestRunResult(file.path, trx, tc)
|
const tr = this.getTestRunResult(path, trx, tc)
|
||||||
testRuns.push(tr)
|
return tr
|
||||||
testClasses.push(...tc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = testRuns.every(tr => tr.result === 'success')
|
private async getTrxReport(path: string, content: string): Promise<TrxReport> {
|
||||||
const icon = success ? Icon.success : Icon.fail
|
core.info(`Parsing content of '${path}'`)
|
||||||
|
try {
|
||||||
return {
|
return (await parseStringPromise(content)) as TrxReport
|
||||||
success,
|
} catch (e) {
|
||||||
output: {
|
throw new Error(`Invalid XML at ${path}\n\n${e}`)
|
||||||
title: `${options.name.trim()} ${icon}`,
|
|
||||||
summary: getReport(testRuns),
|
|
||||||
annotations: options.annotations ? getAnnotations(testClasses, options.workDir, options.trackedFiles) : undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getTrxReport(file: FileContent): Promise<TrxReport> {
|
|
||||||
core.info(`Parsing content of '${file.path}'`)
|
|
||||||
try {
|
|
||||||
return (await parseStringPromise(file.content, {
|
|
||||||
attrValueProcessors: [parseAttribute]
|
|
||||||
})) as TrxReport
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Invalid XML at ${file.path}\n\n${e}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTestRunResult(path: string, trx: TrxReport, testClasses: TestClass[]): TestRunResult {
|
|
||||||
const times = trx.TestRun.Times[0].$
|
|
||||||
const totalTime = times.finish.getTime() - times.start.getTime()
|
|
||||||
|
|
||||||
const suites = testClasses.map(tc => {
|
|
||||||
const tests = tc.tests.map(t => new TestCaseResult(t.name, t.result, t.duration))
|
|
||||||
const group = new TestGroupResult(null, tests)
|
|
||||||
return new TestSuiteResult(tc.name, [group])
|
|
||||||
})
|
|
||||||
|
|
||||||
return new TestRunResult(path, suites, totalTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTestClasses(trx: TrxReport): TestClass[] {
|
|
||||||
const unitTests: {[id: string]: TestMethod} = {}
|
|
||||||
for (const td of trx.TestRun.TestDefinitions) {
|
|
||||||
for (const ut of td.UnitTest) {
|
|
||||||
unitTests[ut.$.id] = ut.TestMethod[0]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unitTestsResults = trx.TestRun.Results.flatMap(r => r.UnitTestResult).flatMap(unitTestResult => ({
|
private getTestClasses(trx: TrxReport): TestClass[] {
|
||||||
unitTestResult,
|
const unitTests: {[id: string]: TestMethod} = {}
|
||||||
testMethod: unitTests[unitTestResult.$.testId]
|
for (const td of trx.TestRun.TestDefinitions) {
|
||||||
}))
|
for (const ut of td.UnitTest) {
|
||||||
|
unitTests[ut.$.id] = ut.TestMethod[0]
|
||||||
const testClasses: {[name: string]: TestClass} = {}
|
}
|
||||||
for (const r of unitTestsResults) {
|
|
||||||
let tc = testClasses[r.testMethod.$.className]
|
|
||||||
if (tc === undefined) {
|
|
||||||
tc = new TestClass(r.testMethod.$.className)
|
|
||||||
testClasses[tc.name] = tc
|
|
||||||
}
|
}
|
||||||
const output = r.unitTestResult.Output
|
|
||||||
const error = output?.length > 0 && output[0].ErrorInfo?.length > 0 ? output[0].ErrorInfo[0] : undefined
|
const unitTestsResults = trx.TestRun.Results.flatMap(r => r.UnitTestResult).flatMap(unitTestResult => ({
|
||||||
const test = new Test(r.testMethod.$.name, r.unitTestResult.$.outcome, r.unitTestResult.$.duration, error)
|
unitTestResult,
|
||||||
tc.tests.push(test)
|
testMethod: unitTests[unitTestResult.$.testId]
|
||||||
|
}))
|
||||||
|
|
||||||
|
const testClasses: {[name: string]: TestClass} = {}
|
||||||
|
for (const r of unitTestsResults) {
|
||||||
|
let tc = testClasses[r.testMethod.$.className]
|
||||||
|
if (tc === undefined) {
|
||||||
|
tc = new TestClass(r.testMethod.$.className)
|
||||||
|
testClasses[tc.name] = tc
|
||||||
|
}
|
||||||
|
const output = r.unitTestResult.Output
|
||||||
|
const error = output?.length > 0 && output[0].ErrorInfo?.length > 0 ? output[0].ErrorInfo[0] : undefined
|
||||||
|
const duration = parseNetDuration(r.unitTestResult.$.duration)
|
||||||
|
const test = new Test(r.testMethod.$.name, r.unitTestResult.$.outcome, duration, error)
|
||||||
|
tc.tests.push(test)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = Object.values(testClasses)
|
||||||
|
result.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
for (const tc of result) {
|
||||||
|
tc.tests.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = Object.values(testClasses)
|
private getTestRunResult(path: string, trx: TrxReport, testClasses: TestClass[]): TestRunResult {
|
||||||
result.sort((a, b) => a.name.localeCompare(b.name))
|
const times = trx.TestRun.Times[0].$
|
||||||
for (const tc of result) {
|
const totalTime = parseIsoDate(times.finish).getTime() - parseIsoDate(times.start).getTime()
|
||||||
tc.tests.sort((a, b) => a.name.localeCompare(b.name))
|
|
||||||
|
const suites = testClasses.map(testClass => {
|
||||||
|
const tests = testClass.tests.map(test => {
|
||||||
|
const error = this.getError(test)
|
||||||
|
return new TestCaseResult(test.name, test.result, test.duration, error)
|
||||||
|
})
|
||||||
|
const group = new TestGroupResult(null, tests)
|
||||||
|
return new TestSuiteResult(testClass.name, [group])
|
||||||
|
})
|
||||||
|
|
||||||
|
return new TestRunResult(path, suites, totalTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
private getError(test: Test): TestCaseError | undefined {
|
||||||
}
|
if (!this.options.parseErrors || !test.error) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
function getAnnotations(testClasses: TestClass[], workDir: string, trackedFiles: string[]): Annotation[] {
|
const message = test.error.Message[0]
|
||||||
const annotations: Annotation[] = []
|
const stackTrace = test.error.StackTrace[0]
|
||||||
for (const tc of testClasses) {
|
let path
|
||||||
for (const t of tc.tests) {
|
let line
|
||||||
if (t.error) {
|
|
||||||
const src = exceptionThrowSource(t.error.StackTrace[0], workDir, trackedFiles)
|
const src = this.exceptionThrowSource(stackTrace)
|
||||||
if (src === null) {
|
if (src) {
|
||||||
continue
|
path = src.path
|
||||||
|
line = src.line
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
line,
|
||||||
|
message,
|
||||||
|
stackTrace: `${message}\n${stackTrace}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private exceptionThrowSource(stackTrace: string): {path: string; line: number} | undefined {
|
||||||
|
const lines = stackTrace.split(/\r*\n/)
|
||||||
|
const re = / in (.+):line (\d+)$/
|
||||||
|
const {workDir, trackedFiles} = this.options
|
||||||
|
|
||||||
|
for (const str of lines) {
|
||||||
|
const match = str.match(re)
|
||||||
|
if (match !== null) {
|
||||||
|
const [_, fileStr, lineStr] = match
|
||||||
|
const filePath = normalizeFilePath(fileStr)
|
||||||
|
const file = filePath.startsWith(workDir) ? filePath.substr(workDir.length) : filePath
|
||||||
|
if (trackedFiles.includes(file)) {
|
||||||
|
const line = parseInt(lineStr)
|
||||||
|
return {path: file, line}
|
||||||
}
|
}
|
||||||
annotations.push({
|
|
||||||
annotation_level: 'failure',
|
|
||||||
start_line: src.line,
|
|
||||||
end_line: src.line,
|
|
||||||
path: src.file,
|
|
||||||
message: fixEol(t.error.Message[0]),
|
|
||||||
title: `[${tc.name}] ${t.name}`
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return annotations
|
|
||||||
}
|
|
||||||
|
|
||||||
export function exceptionThrowSource(
|
|
||||||
ex: string,
|
|
||||||
workDir: string,
|
|
||||||
trackedFiles: string[]
|
|
||||||
): {file: string; line: number} | null {
|
|
||||||
const lines = ex.split(/\r*\n/)
|
|
||||||
const re = / in (.+):line (\d+)$/
|
|
||||||
|
|
||||||
for (const str of lines) {
|
|
||||||
const match = str.match(re)
|
|
||||||
if (match !== null) {
|
|
||||||
const [_, fileStr, lineStr] = match
|
|
||||||
const filePath = normalizeFilePath(fileStr)
|
|
||||||
const file = filePath.startsWith(workDir) ? filePath.substr(workDir.length) : filePath
|
|
||||||
if (trackedFiles.includes(file)) {
|
|
||||||
const line = parseInt(lineStr)
|
|
||||||
return {file, line}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ export interface TestRun {
|
||||||
|
|
||||||
export interface Times {
|
export interface Times {
|
||||||
$: {
|
$: {
|
||||||
creation: Date
|
creation: string
|
||||||
queuing: Date
|
queuing: string
|
||||||
start: Date
|
start: string
|
||||||
finish: Date
|
finish: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ export interface UnitTestResult {
|
||||||
$: {
|
$: {
|
||||||
testId: string
|
testId: string
|
||||||
testName: string
|
testName: string
|
||||||
duration: number
|
duration: string
|
||||||
outcome: Outcome
|
outcome: Outcome
|
||||||
}
|
}
|
||||||
Output: Output[]
|
Output: Output[]
|
||||||
|
|
|
||||||
|
|
@ -1,145 +1,116 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types'
|
import {ParseOptions, TestParser} from '../../test-parser'
|
||||||
import {parseStringPromise} from 'xml2js'
|
import {parseStringPromise} from 'xml2js'
|
||||||
|
|
||||||
import {JunitReport, TestCase, TestSuite} from './jest-junit-types'
|
import {JunitReport, TestCase, TestSuite} from './jest-junit-types'
|
||||||
import {fixEol, Icon} from '../../utils/markdown-utils'
|
|
||||||
import {normalizeFilePath} from '../../utils/file-utils'
|
import {normalizeFilePath} from '../../utils/file-utils'
|
||||||
import {parseAttribute} from '../../utils/xml-utils'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TestExecutionResult,
|
TestExecutionResult,
|
||||||
TestRunResult,
|
TestRunResult,
|
||||||
TestSuiteResult,
|
TestSuiteResult,
|
||||||
TestGroupResult,
|
TestGroupResult,
|
||||||
TestCaseResult
|
TestCaseResult,
|
||||||
} from '../../report/test-results'
|
TestCaseError
|
||||||
import getReport from '../../report/get-report'
|
} from '../../test-results'
|
||||||
|
|
||||||
export async function parseJestJunit(files: FileContent[], options: ParseOptions): Promise<TestResult> {
|
export class JestJunitParser implements TestParser {
|
||||||
const junit: JunitReport[] = []
|
constructor(readonly options: ParseOptions) {}
|
||||||
const testRuns: TestRunResult[] = []
|
|
||||||
|
|
||||||
for (const file of files) {
|
async parse(path: string, content: string): Promise<TestRunResult> {
|
||||||
const ju = await getJunitReport(file)
|
const ju = await this.getJunitReport(path, content)
|
||||||
const tr = getTestRunResult(file.path, ju)
|
return this.getTestRunResult(path, ju)
|
||||||
junit.push(ju)
|
|
||||||
testRuns.push(tr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = testRuns.every(tr => tr.result === 'success')
|
private async getJunitReport(path: string, content: string): Promise<JunitReport> {
|
||||||
const icon = success ? Icon.success : Icon.fail
|
core.info(`Parsing content of '${path}'`)
|
||||||
|
try {
|
||||||
return {
|
return (await parseStringPromise(content)) as JunitReport
|
||||||
success,
|
} catch (e) {
|
||||||
output: {
|
throw new Error(`Invalid XML at ${path}\n\n${e}`)
|
||||||
title: `${options.name.trim()} ${icon}`,
|
|
||||||
summary: getReport(testRuns),
|
|
||||||
annotations: options.annotations ? getAnnotations(junit, options.workDir, options.trackedFiles) : undefined
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async function getJunitReport(file: FileContent): Promise<JunitReport> {
|
private getTestRunResult(path: string, junit: JunitReport): TestRunResult {
|
||||||
core.info(`Parsing content of '${file.path}'`)
|
const suites = junit.testsuites.testsuite.map(ts => {
|
||||||
try {
|
const name = ts.$.name.trim()
|
||||||
return (await parseStringPromise(file.content, {
|
const time = parseFloat(ts.$.time) * 1000
|
||||||
attrValueProcessors: [parseAttribute]
|
const sr = new TestSuiteResult(name, this.getGroups(ts), time)
|
||||||
})) as JunitReport
|
return sr
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Invalid XML at ${file.path}\n\n${e}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTestRunResult(path: string, junit: JunitReport): TestRunResult {
|
|
||||||
const suites = junit.testsuites.testsuite.map(ts => {
|
|
||||||
const name = ts.$.name.trim()
|
|
||||||
const time = ts.$.time * 1000
|
|
||||||
const sr = new TestSuiteResult(name, getGroups(ts), time)
|
|
||||||
return sr
|
|
||||||
})
|
|
||||||
|
|
||||||
const time = junit.testsuites.$.time * 1000
|
|
||||||
return new TestRunResult(path, suites, time)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getGroups(suite: TestSuite): TestGroupResult[] {
|
|
||||||
const groups: {describe: string; tests: TestCase[]}[] = []
|
|
||||||
for (const tc of suite.testcase) {
|
|
||||||
let grp = groups.find(g => g.describe === tc.$.classname)
|
|
||||||
if (grp === undefined) {
|
|
||||||
grp = {describe: tc.$.classname, tests: []}
|
|
||||||
groups.push(grp)
|
|
||||||
}
|
|
||||||
grp.tests.push(tc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return groups.map(grp => {
|
|
||||||
const tests = grp.tests.map(tc => {
|
|
||||||
const name = tc.$.name.trim()
|
|
||||||
const result = getTestCaseResult(tc)
|
|
||||||
const time = tc.$.time * 1000
|
|
||||||
return new TestCaseResult(name, result, time)
|
|
||||||
})
|
})
|
||||||
return new TestGroupResult(grp.describe, tests)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTestCaseResult(test: TestCase): TestExecutionResult {
|
const time = parseFloat(junit.testsuites.$.time) * 1000
|
||||||
if (test.failure) return 'failed'
|
return new TestRunResult(path, suites, time)
|
||||||
if (test.skipped) return 'skipped'
|
}
|
||||||
return 'success'
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAnnotations(junitReports: JunitReport[], workDir: string, trackedFiles: string[]): Annotation[] {
|
private getGroups(suite: TestSuite): TestGroupResult[] {
|
||||||
const annotations: Annotation[] = []
|
const groups: {describe: string; tests: TestCase[]}[] = []
|
||||||
for (const junit of junitReports) {
|
for (const tc of suite.testcase) {
|
||||||
for (const suite of junit.testsuites.testsuite) {
|
let grp = groups.find(g => g.describe === tc.$.classname)
|
||||||
for (const tc of suite.testcase) {
|
if (grp === undefined) {
|
||||||
if (!tc.failure) {
|
grp = {describe: tc.$.classname, tests: []}
|
||||||
continue
|
groups.push(grp)
|
||||||
}
|
}
|
||||||
for (const ex of tc.failure) {
|
grp.tests.push(tc)
|
||||||
const src = exceptionThrowSource(ex, workDir, trackedFiles)
|
}
|
||||||
if (src === null) {
|
|
||||||
continue
|
return groups.map(grp => {
|
||||||
}
|
const tests = grp.tests.map(tc => {
|
||||||
annotations.push({
|
const name = tc.$.name.trim()
|
||||||
annotation_level: 'failure',
|
const result = this.getTestCaseResult(tc)
|
||||||
start_line: src.line,
|
const time = parseFloat(tc.$.time) * 1000
|
||||||
end_line: src.line,
|
const error = this.getTestCaseError(tc)
|
||||||
path: src.file,
|
return new TestCaseResult(name, result, time, error)
|
||||||
message: fixEol(ex),
|
})
|
||||||
title: `[${suite.$.name}] ${tc.$.name.trim()}`
|
return new TestGroupResult(grp.describe, tests)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTestCaseResult(test: TestCase): TestExecutionResult {
|
||||||
|
if (test.failure) return 'failed'
|
||||||
|
if (test.skipped) return 'skipped'
|
||||||
|
return 'success'
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTestCaseError(tc: TestCase): TestCaseError | undefined {
|
||||||
|
if (!this.options.parseErrors || !tc.failure) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const stackTrace = tc.failure[0]
|
||||||
|
let path
|
||||||
|
let line
|
||||||
|
|
||||||
|
const src = this.exceptionThrowSource(stackTrace)
|
||||||
|
if (src) {
|
||||||
|
path = src.path
|
||||||
|
line = src.line
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path,
|
||||||
|
line,
|
||||||
|
stackTrace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private exceptionThrowSource(stackTrace: string): {path: string; line: number} | undefined {
|
||||||
|
const lines = stackTrace.split(/\r?\n/)
|
||||||
|
const re = /\((.*):(\d+):\d+\)$/
|
||||||
|
|
||||||
|
const {workDir, trackedFiles} = this.options
|
||||||
|
for (const str of lines) {
|
||||||
|
const match = str.match(re)
|
||||||
|
if (match !== null) {
|
||||||
|
const [_, fileStr, lineStr] = match
|
||||||
|
const filePath = normalizeFilePath(fileStr)
|
||||||
|
const path = filePath.startsWith(workDir) ? filePath.substr(workDir.length) : filePath
|
||||||
|
if (trackedFiles.includes(path)) {
|
||||||
|
const line = parseInt(lineStr)
|
||||||
|
|
||||||
|
return {path, line}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return annotations
|
|
||||||
}
|
|
||||||
|
|
||||||
export function exceptionThrowSource(
|
|
||||||
ex: string,
|
|
||||||
workDir: string,
|
|
||||||
trackedFiles: string[]
|
|
||||||
): {file: string; line: number; column: number} | null {
|
|
||||||
const lines = ex.split(/\r?\n/)
|
|
||||||
const re = /\((.*):(\d+):(\d+)\)$/
|
|
||||||
|
|
||||||
for (const str of lines) {
|
|
||||||
const match = str.match(re)
|
|
||||||
if (match !== null) {
|
|
||||||
const [_, fileStr, lineStr, colStr] = match
|
|
||||||
const filePath = normalizeFilePath(fileStr)
|
|
||||||
const file = filePath.startsWith(workDir) ? filePath.substr(workDir.length) : filePath
|
|
||||||
if (trackedFiles.includes(file)) {
|
|
||||||
const line = parseInt(lineStr)
|
|
||||||
const column = parseInt(colStr)
|
|
||||||
return {file, line, column}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ export interface JunitReport {
|
||||||
export interface TestSuites {
|
export interface TestSuites {
|
||||||
$: {
|
$: {
|
||||||
name: string
|
name: string
|
||||||
tests: number
|
tests: string
|
||||||
failures: number // assertion failed
|
failures: string // assertion failed
|
||||||
errors: number // unhandled exception during test execution
|
errors: string // unhandled exception during test execution
|
||||||
time: number
|
time: string
|
||||||
}
|
}
|
||||||
testsuite: TestSuite[]
|
testsuite: TestSuite[]
|
||||||
}
|
}
|
||||||
|
|
@ -16,11 +16,11 @@ export interface TestSuites {
|
||||||
export interface TestSuite {
|
export interface TestSuite {
|
||||||
$: {
|
$: {
|
||||||
name: string
|
name: string
|
||||||
tests: number
|
tests: string
|
||||||
errors: number
|
errors: string
|
||||||
failures: number
|
failures: string
|
||||||
skipped: number
|
skipped: string
|
||||||
time: number
|
time: string
|
||||||
timestamp?: Date
|
timestamp?: Date
|
||||||
}
|
}
|
||||||
testcase: TestCase[]
|
testcase: TestCase[]
|
||||||
|
|
@ -31,7 +31,7 @@ export interface TestCase {
|
||||||
classname: string
|
classname: string
|
||||||
file?: string
|
file?: string
|
||||||
name: string
|
name: string
|
||||||
time: number
|
time: string
|
||||||
}
|
}
|
||||||
failure?: string[]
|
failure?: string[]
|
||||||
skipped?: string[]
|
skipped?: string[]
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import {Endpoints} from '@octokit/types'
|
|
||||||
|
|
||||||
export type OutputParameters = Endpoints['POST /repos/{owner}/{repo}/check-runs']['parameters']['output']
|
|
||||||
export type Annotation = {
|
|
||||||
path: string
|
|
||||||
start_line: number
|
|
||||||
end_line: number
|
|
||||||
start_column?: number
|
|
||||||
end_column?: number
|
|
||||||
annotation_level: 'notice' | 'warning' | 'failure'
|
|
||||||
message: string
|
|
||||||
title?: string
|
|
||||||
raw_details?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ParseTestResult = (files: FileContent[], options: ParseOptions) => Promise<TestResult>
|
|
||||||
|
|
||||||
export type FileContent = {path: string; content: string}
|
|
||||||
|
|
||||||
export interface ParseOptions {
|
|
||||||
name: string
|
|
||||||
annotations: boolean
|
|
||||||
workDir: string
|
|
||||||
trackedFiles: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TestResult {
|
|
||||||
success: boolean
|
|
||||||
output: OutputParameters
|
|
||||||
}
|
|
||||||
111
src/report/get-annotations.ts
Normal file
111
src/report/get-annotations.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import {ellipsis, fixEol} from '../utils/markdown-utils'
|
||||||
|
import {TestRunResult} from '../test-results'
|
||||||
|
|
||||||
|
type Annotation = {
|
||||||
|
path: string
|
||||||
|
start_line: number
|
||||||
|
end_line: number
|
||||||
|
start_column?: number
|
||||||
|
end_column?: number
|
||||||
|
annotation_level: 'notice' | 'warning' | 'failure'
|
||||||
|
message: string
|
||||||
|
title?: string
|
||||||
|
raw_details?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestError {
|
||||||
|
testRunPaths: string[]
|
||||||
|
suiteName: string
|
||||||
|
testName: string
|
||||||
|
path: string
|
||||||
|
line: number
|
||||||
|
stackTrace: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAnnotations(results: TestRunResult[], maxCount: number): Annotation[] {
|
||||||
|
if (maxCount === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect errors from TestRunResults
|
||||||
|
// Merge duplicates if there are more test results files processed
|
||||||
|
const errors: TestError[] = []
|
||||||
|
const mergeDup = results.length > 1
|
||||||
|
for (const tr of results) {
|
||||||
|
for (const ts of tr.suites) {
|
||||||
|
for (const tg of ts.groups) {
|
||||||
|
for (const tc of tg.tests) {
|
||||||
|
const err = tc.error
|
||||||
|
if (err === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const path = err.path ?? tr.path
|
||||||
|
const line = err.line ?? 0
|
||||||
|
if (mergeDup) {
|
||||||
|
const dup = errors.find(e => path === e.path && line === e.line && err.stackTrace === e.stackTrace)
|
||||||
|
if (dup !== undefined) {
|
||||||
|
dup.testRunPaths.push(tr.path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.push({
|
||||||
|
testRunPaths: [tr.path],
|
||||||
|
suiteName: ts.name,
|
||||||
|
testName: tc.name,
|
||||||
|
stackTrace: err.stackTrace,
|
||||||
|
message: err.message ?? getFirstNonEmptyLine(err.stackTrace) ?? 'Test failed',
|
||||||
|
path,
|
||||||
|
line
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit number of created annotations
|
||||||
|
errors.splice(maxCount + 1)
|
||||||
|
|
||||||
|
const annotations = errors.map(e => {
|
||||||
|
const message = [
|
||||||
|
'Failed test found in:',
|
||||||
|
e.testRunPaths.map(p => ` ${p}`).join('\n'),
|
||||||
|
'Error:',
|
||||||
|
ident(fixEol(e.message), ' ')
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
return enforceCheckRunLimits({
|
||||||
|
path: e.path,
|
||||||
|
start_line: e.line,
|
||||||
|
end_line: e.line,
|
||||||
|
annotation_level: 'failure',
|
||||||
|
title: `${e.suiteName} ► ${e.testName}`,
|
||||||
|
raw_details: fixEol(e.stackTrace),
|
||||||
|
message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return annotations
|
||||||
|
}
|
||||||
|
|
||||||
|
function enforceCheckRunLimits(err: Annotation): Annotation {
|
||||||
|
err.title = ellipsis(err.title || '', 255)
|
||||||
|
err.message = ellipsis(err.message, 65535)
|
||||||
|
if (err.raw_details) {
|
||||||
|
err.raw_details = ellipsis(err.raw_details, 65535)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFirstNonEmptyLine(stackTrace: string): string | undefined {
|
||||||
|
const lines = stackTrace.split(/\r?\n/g)
|
||||||
|
return lines.find(str => !/^\s*$/.test(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
function ident(text: string, prefix: string): string {
|
||||||
|
return text
|
||||||
|
.split(/\n/g)
|
||||||
|
.map(line => prefix + line)
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
|
@ -1,94 +1,201 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {TestExecutionResult, TestRunResult, TestSuiteResult} from './test-results'
|
import {TestExecutionResult, TestRunResult, TestSuiteResult} from '../test-results'
|
||||||
import {Align, Icon, link, table} from '../utils/markdown-utils'
|
import {Align, formatTime, Icon, link, table} from '../utils/markdown-utils'
|
||||||
import {slug} from '../utils/slugger'
|
import {slug} from '../utils/slugger'
|
||||||
|
|
||||||
export default function getReport(results: TestRunResult[]): string {
|
export interface ReportOptions {
|
||||||
const badge = getBadge(results)
|
listSuites?: 'all' | 'failed'
|
||||||
const runsSummary = results.map(getRunSummary).join('\n\n')
|
listTests?: 'all' | 'failed' | 'none'
|
||||||
const suites = results
|
|
||||||
.flatMap(tr => tr.suites)
|
|
||||||
.map((ts, i) => getSuiteSummary(ts, i))
|
|
||||||
.join('\n')
|
|
||||||
|
|
||||||
const suitesSection = `# Test Suites\n\n${suites}`
|
|
||||||
return [badge, runsSummary, suitesSection].join('\n\n')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBadge(results: TestRunResult[]): string {
|
export function getReport(results: TestRunResult[], options: ReportOptions = {}): string {
|
||||||
|
core.info('Generating check run summary')
|
||||||
|
|
||||||
|
const maxReportLength = 65535
|
||||||
|
const sections: string[] = []
|
||||||
|
|
||||||
|
applySort(results)
|
||||||
|
|
||||||
|
const badge = getReportBadge(results)
|
||||||
|
sections.push(badge)
|
||||||
|
|
||||||
|
const runs = getTestRunsReport(results, options)
|
||||||
|
sections.push(...runs)
|
||||||
|
|
||||||
|
const report = sections.join('\n')
|
||||||
|
if (report.length > maxReportLength) {
|
||||||
|
let msg = `**Check Run summary limit of ${maxReportLength} chars was exceed**`
|
||||||
|
if (options.listTests !== 'all') {
|
||||||
|
msg += '\n- Consider setting `list-tests` option to `only-failed` or `none`'
|
||||||
|
}
|
||||||
|
if (options.listSuites !== 'all') {
|
||||||
|
msg += '\n- Consider setting `list-suites` option to `only-failed`'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${badge}\n${msg}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return report
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySort(results: TestRunResult[]): void {
|
||||||
|
results.sort((a, b) => a.path.localeCompare(b.path))
|
||||||
|
for (const res of results) {
|
||||||
|
res.suites.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReportBadge(results: TestRunResult[]): string {
|
||||||
const passed = results.reduce((sum, tr) => sum + tr.passed, 0)
|
const passed = results.reduce((sum, tr) => sum + tr.passed, 0)
|
||||||
const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0)
|
const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0)
|
||||||
const failed = results.reduce((sum, tr) => sum + tr.failed, 0)
|
const failed = results.reduce((sum, tr) => sum + tr.failed, 0)
|
||||||
|
return getBadge(passed, failed, skipped)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBadge(passed: number, failed: number, skipped: number): string {
|
||||||
|
const text = []
|
||||||
|
if (passed > 0) {
|
||||||
|
text.push(`${passed} passed`)
|
||||||
|
}
|
||||||
|
if (failed > 0) {
|
||||||
|
text.push(`${failed} failed`)
|
||||||
|
}
|
||||||
|
if (skipped > 0) {
|
||||||
|
text.push(`${skipped} skipped`)
|
||||||
|
}
|
||||||
|
const message = text.length > 0 ? text.join(', ') : 'none'
|
||||||
|
|
||||||
const passedText = passed > 0 ? `${passed} passed` : null
|
|
||||||
const skippedText = skipped > 0 ? `${skipped} skipped` : null
|
|
||||||
const failedText = failed > 0 ? `${failed} failed` : null
|
|
||||||
const message = [passedText, skippedText, failedText].filter(s => s != null).join(', ') || 'none'
|
|
||||||
let color = 'success'
|
let color = 'success'
|
||||||
if (failed > 0) {
|
if (failed > 0) {
|
||||||
color = 'critical'
|
color = 'critical'
|
||||||
} else if (passed === 0 && failed === 0) {
|
} else if (passed === 0 && failed === 0) {
|
||||||
color = 'yellow'
|
color = 'yellow'
|
||||||
}
|
}
|
||||||
|
const hint = failed > 0 ? 'Tests failed' : 'Tests passed successfully'
|
||||||
const uri = encodeURIComponent(`tests-${message}-${color}`)
|
const uri = encodeURIComponent(`tests-${message}-${color}`)
|
||||||
const text = failed > 0 ? 'Tests failed' : 'Tests passed successfully'
|
return ``
|
||||||
return ``
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRunSummary(tr: TestRunResult): string {
|
function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): string[] {
|
||||||
core.info('Generating check run summary')
|
const sections: string[] = []
|
||||||
const time = `${(tr.time / 1000).toFixed(3)}s`
|
|
||||||
const headingLine1 = `### ${tr.path}`
|
|
||||||
const headingLine2 = `**${tr.tests}** tests were completed in **${time}** with **${tr.passed}** passed, **${tr.skipped}** skipped and **${tr.failed}** failed.`
|
|
||||||
|
|
||||||
const suitesSummary = tr.suites.map((s, i) => {
|
if (testRuns.length > 1) {
|
||||||
const icon = getResultIcon(s.result)
|
const tableData = testRuns.map((tr, runIndex) => {
|
||||||
const tsTime = `${s.time}ms`
|
const time = formatTime(tr.time)
|
||||||
const tsName = s.name
|
const name = tr.path
|
||||||
const tsAddr = makeSuiteSlug(i, tsName).link
|
const addr = makeRunSlug(runIndex).link
|
||||||
const tsNameLink = link(tsName, tsAddr)
|
const nameLink = link(name, addr)
|
||||||
return [icon, tsNameLink, s.tests, tsTime, s.passed, s.skipped, s.failed]
|
const passed = tr.passed > 0 ? `${tr.passed}${Icon.success}` : ''
|
||||||
})
|
const failed = tr.failed > 0 ? `${tr.failed}${Icon.fail}` : ''
|
||||||
|
const skipped = tr.skipped > 0 ? `${tr.skipped}${Icon.skip}` : ''
|
||||||
const summary = table(
|
return [nameLink, passed, failed, skipped, time]
|
||||||
['Result', 'Suite', 'Tests', 'Time', `Passed ${Icon.success}`, `Skipped ${Icon.skip}`, `Failed ${Icon.fail}`],
|
|
||||||
[Align.Center, Align.Left, Align.Right, Align.Right, Align.Right, Align.Right, Align.Right],
|
|
||||||
...suitesSummary
|
|
||||||
)
|
|
||||||
|
|
||||||
return [headingLine1, headingLine2, summary].join('\n\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSuiteSummary(ts: TestSuiteResult, index: number): string {
|
|
||||||
const icon = getResultIcon(ts.result)
|
|
||||||
const content = ts.groups
|
|
||||||
.map(grp => {
|
|
||||||
const header = grp.name ? `### ${grp.name}\n\n` : ''
|
|
||||||
const tests = table(
|
|
||||||
['Result', 'Test', 'Time'],
|
|
||||||
[Align.Center, Align.Left, Align.Right],
|
|
||||||
...grp.tests.map(tc => {
|
|
||||||
const name = tc.name
|
|
||||||
const time = `${tc.time}ms`
|
|
||||||
const result = getResultIcon(tc.result)
|
|
||||||
return [result, name, time]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return `${header}${tests}\n`
|
|
||||||
})
|
})
|
||||||
.join('\n')
|
|
||||||
|
const resultsTable = table(
|
||||||
|
['Report', 'Passed', 'Failed', 'Skipped', 'Time'],
|
||||||
|
[Align.Left, Align.Right, Align.Right, Align.Right, Align.Right],
|
||||||
|
...tableData
|
||||||
|
)
|
||||||
|
sections.push(resultsTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat()
|
||||||
|
sections.push(...suitesReports)
|
||||||
|
return sections
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSuitesReport(tr: TestRunResult, runIndex: number, options: ReportOptions): string[] {
|
||||||
|
const sections: string[] = []
|
||||||
|
|
||||||
|
const trSlug = makeRunSlug(runIndex)
|
||||||
|
const nameLink = `<a id="${trSlug.id}" href="${trSlug.link}">${tr.path}</a>`
|
||||||
|
const icon = getResultIcon(tr.result)
|
||||||
|
sections.push(`## ${nameLink} ${icon}`)
|
||||||
|
|
||||||
|
const time = formatTime(tr.time)
|
||||||
|
const headingLine2 = `**${tr.tests}** tests were completed in **${time}** with **${tr.passed}** passed, **${tr.failed}** failed and **${tr.skipped}** skipped.`
|
||||||
|
sections.push(headingLine2)
|
||||||
|
|
||||||
|
const suites = options.listSuites === 'failed' ? tr.failedSuites : tr.suites
|
||||||
|
if (suites.length > 0) {
|
||||||
|
const suitesTable = table(
|
||||||
|
['Test suite', 'Passed', 'Failed', 'Skipped', 'Time'],
|
||||||
|
[Align.Left, Align.Right, Align.Right, Align.Right, Align.Right],
|
||||||
|
...suites.map((s, suiteIndex) => {
|
||||||
|
const tsTime = formatTime(s.time)
|
||||||
|
const tsName = s.name
|
||||||
|
const skipLink = options.listTests === 'none' || (options.listTests === 'failed' && s.result !== 'failed')
|
||||||
|
const tsAddr = makeSuiteSlug(runIndex, suiteIndex).link
|
||||||
|
const tsNameLink = skipLink ? tsName : link(tsName, tsAddr)
|
||||||
|
const passed = s.passed > 0 ? `${s.passed}${Icon.success}` : ''
|
||||||
|
const failed = s.failed > 0 ? `${s.failed}${Icon.fail}` : ''
|
||||||
|
const skipped = s.skipped > 0 ? `${s.skipped}${Icon.skip}` : ''
|
||||||
|
return [tsNameLink, passed, failed, skipped, tsTime]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
sections.push(suitesTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.listTests !== 'none') {
|
||||||
|
const tests = suites.map((ts, suiteIndex) => getTestsReport(ts, runIndex, suiteIndex, options)).flat()
|
||||||
|
|
||||||
|
if (tests.length > 1) {
|
||||||
|
sections.push(...tests)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTestsReport(ts: TestSuiteResult, runIndex: number, suiteIndex: number, options: ReportOptions): string[] {
|
||||||
|
const groups = options.listTests === 'failed' ? ts.failedGroups : ts.groups
|
||||||
|
if (groups.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const sections: string[] = []
|
||||||
|
|
||||||
const tsName = ts.name
|
const tsName = ts.name
|
||||||
const tsSlug = makeSuiteSlug(index, tsName)
|
const tsSlug = makeSuiteSlug(runIndex, suiteIndex)
|
||||||
const tsNameLink = `<a id="${tsSlug.id}" href="${tsSlug.link}">${tsName}</a>`
|
const tsNameLink = `<a id="${tsSlug.id}" href="${tsSlug.link}">${tsName}</a>`
|
||||||
return `## ${tsNameLink} ${icon}\n\n${content}`
|
const icon = getResultIcon(ts.result)
|
||||||
|
sections.push(`### ${tsNameLink} ${icon}`)
|
||||||
|
|
||||||
|
const tsTime = formatTime(ts.time)
|
||||||
|
const headingLine2 = `**${ts.tests}** tests were completed in **${tsTime}** with **${ts.passed}** passed, **${ts.failed}** failed and **${ts.skipped}** skipped.`
|
||||||
|
sections.push(headingLine2)
|
||||||
|
|
||||||
|
for (const grp of groups) {
|
||||||
|
const tests = options.listTests === 'failed' ? grp.failedTests : grp.tests
|
||||||
|
if (tests.length === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const grpHeader = grp.name ? `\n**${grp.name}**` : ''
|
||||||
|
const testsTable = table(
|
||||||
|
['Result', 'Test', 'Time'],
|
||||||
|
[Align.Center, Align.Left, Align.Right],
|
||||||
|
...grp.tests.map(tc => {
|
||||||
|
const name = tc.name
|
||||||
|
const time = formatTime(tc.time)
|
||||||
|
const result = getResultIcon(tc.result)
|
||||||
|
return [result, name, time]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
sections.push(grpHeader, testsTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSuiteSlug(index: number, name: string): {id: string; link: string} {
|
function makeRunSlug(runIndex: number): {id: string; link: string} {
|
||||||
// use "ts-$index-" as prefix to avoid slug conflicts after escaping the paths
|
// use prefix to avoid slug conflicts after escaping the paths
|
||||||
return slug(`ts-${index}-${name}`)
|
return slug(`r${runIndex}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSuiteSlug(runIndex: number, suiteIndex: number): {id: string; link: string} {
|
||||||
|
// use prefix to avoid slug conflicts after escaping the paths
|
||||||
|
return slug(`r${runIndex}s${suiteIndex}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getResultIcon(result: TestExecutionResult): string {
|
function getResultIcon(result: TestExecutionResult): string {
|
||||||
|
|
|
||||||
11
src/test-parser.ts
Normal file
11
src/test-parser.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import {TestRunResult} from './test-results'
|
||||||
|
|
||||||
|
export interface ParseOptions {
|
||||||
|
parseErrors: boolean
|
||||||
|
workDir: string
|
||||||
|
trackedFiles: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestParser {
|
||||||
|
parse(path: string, content: string): Promise<TestRunResult>
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,10 @@ export class TestRunResult {
|
||||||
get result(): TestExecutionResult {
|
get result(): TestExecutionResult {
|
||||||
return this.suites.some(t => t.result === 'failed') ? 'failed' : 'success'
|
return this.suites.some(t => t.result === 'failed') ? 'failed' : 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get failedSuites(): TestSuiteResult[] {
|
||||||
|
return this.suites.filter(s => s.result === 'failed')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestSuiteResult {
|
export class TestSuiteResult {
|
||||||
|
|
@ -47,6 +51,10 @@ export class TestSuiteResult {
|
||||||
get result(): TestExecutionResult {
|
get result(): TestExecutionResult {
|
||||||
return this.groups.some(t => t.result === 'failed') ? 'failed' : 'success'
|
return this.groups.some(t => t.result === 'failed') ? 'failed' : 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get failedGroups(): TestGroupResult[] {
|
||||||
|
return this.groups.filter(grp => grp.result === 'failed')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestGroupResult {
|
export class TestGroupResult {
|
||||||
|
|
@ -68,10 +76,26 @@ export class TestGroupResult {
|
||||||
get result(): TestExecutionResult {
|
get result(): TestExecutionResult {
|
||||||
return this.tests.some(t => t.result === 'failed') ? 'failed' : 'success'
|
return this.tests.some(t => t.result === 'failed') ? 'failed' : 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get failedTests(): TestCaseResult[] {
|
||||||
|
return this.tests.filter(tc => tc.result === 'failed')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestCaseResult {
|
export class TestCaseResult {
|
||||||
constructor(readonly name: string, readonly result: TestExecutionResult, readonly time: number) {}
|
constructor(
|
||||||
|
readonly name: string,
|
||||||
|
readonly result: TestExecutionResult,
|
||||||
|
readonly time: number,
|
||||||
|
readonly error?: TestCaseError
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TestExecutionResult = 'success' | 'skipped' | 'failed' | undefined
|
export type TestExecutionResult = 'success' | 'skipped' | 'failed' | undefined
|
||||||
|
|
||||||
|
export interface TestCaseError {
|
||||||
|
path?: string
|
||||||
|
line?: number
|
||||||
|
message?: string
|
||||||
|
stackTrace: string
|
||||||
|
}
|
||||||
|
|
@ -11,19 +11,15 @@ export const Icon = {
|
||||||
fail: '❌' // ':x:'
|
fail: '❌' // ':x:'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function details(summary: string, content: string): string {
|
|
||||||
return `<details><summary>${summary}</summary>${content}</details>`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function link(title: string, address: string): string {
|
export function link(title: string, address: string): string {
|
||||||
return `[${title}](${address})`
|
return `[${title}](${address})`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToString = string | number | boolean | Date
|
type ToString = string | number | boolean | Date
|
||||||
export function table(headers: ToString[], align: ToString[], ...rows: ToString[][]): string {
|
export function table(headers: ToString[], align: ToString[], ...rows: ToString[][]): string {
|
||||||
const headerRow = `| ${headers.map(tableEscape).join(' | ')} |`
|
const headerRow = `|${headers.map(tableEscape).join('|')}|`
|
||||||
const alignRow = `| ${align.join(' | ')} |`
|
const alignRow = `|${align.join('|')}|`
|
||||||
const contentRows = rows.map(row => `| ${row.map(tableEscape).join(' | ')} |`).join('\n')
|
const contentRows = rows.map(row => `|${row.map(tableEscape).join('|')}|`).join('\n')
|
||||||
return [headerRow, alignRow, contentRows].join('\n')
|
return [headerRow, alignRow, contentRows].join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,3 +30,19 @@ export function tableEscape(content: ToString): string {
|
||||||
export function fixEol(text?: string): string {
|
export function fixEol(text?: string): string {
|
||||||
return text?.replace(/\r/g, '') ?? ''
|
return text?.replace(/\r/g, '') ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ellipsis(text: string, maxLength: number): string {
|
||||||
|
if (text.length <= maxLength) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.substr(0, maxLength - 3) + '...'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTime(ms: number): string {
|
||||||
|
if (ms > 1000) {
|
||||||
|
return `${(ms / 1000).toFixed(3)}s`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${Math.round(ms)}ms`
|
||||||
|
}
|
||||||
|
|
|
||||||
20
src/utils/parse-utils.ts
Normal file
20
src/utils/parse-utils.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
export function parseNetDuration(str: string): number {
|
||||||
|
// matches dotnet duration: 00:00:00.0010000
|
||||||
|
const durationRe = /^(\d\d):(\d\d):(\d\d\.\d+)$/
|
||||||
|
const durationMatch = str.match(durationRe)
|
||||||
|
if (durationMatch === null) {
|
||||||
|
throw new Error(`Invalid format: "${str}" is not NET duration`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [_, hourStr, minStr, secStr] = durationMatch
|
||||||
|
return (parseInt(hourStr) * 3600 + parseInt(minStr) * 60 + parseFloat(secStr)) * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseIsoDate(str: string): Date {
|
||||||
|
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)$/
|
||||||
|
if (str === undefined || !isoDateRe.test(str)) {
|
||||||
|
throw new Error(`Invalid format: "${str}" is not ISO date`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(str)
|
||||||
|
}
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
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)$/
|
|
||||||
|
|
||||||
// matches dotnet duration: 00:00:00.0010000
|
|
||||||
const durationRe = /^(\d\d):(\d\d):(\d\d\.\d+)$/
|
|
||||||
|
|
||||||
export function parseAttribute(str: string | undefined): string | Date | number | undefined {
|
|
||||||
if (str === '' || str === undefined) {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isoDateRe.test(str)) {
|
|
||||||
return new Date(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
const durationMatch = str.match(durationRe)
|
|
||||||
if (durationMatch !== null) {
|
|
||||||
const [_, hourStr, minStr, secStr] = durationMatch
|
|
||||||
return (parseInt(hourStr) * 3600 + parseInt(minStr) * 60 + parseFloat(secStr)) * 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
const num = parseFloat(str)
|
|
||||||
if (isNaN(num)) {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue