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: ''
|
||||
|
||||
# Path to test report
|
||||
# Coma separated list of paths to test reports
|
||||
# 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: ''
|
||||
|
||||
# Format of test report. Supported options:
|
||||
|
|
@ -61,8 +61,20 @@ jobs:
|
|||
# jest-junit
|
||||
reporter: ''
|
||||
|
||||
# Enables code annotations with error message and stack trace captured during test execution
|
||||
annotations: 'true'
|
||||
# Limits which test suites are listed:
|
||||
# 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
|
||||
fail-on-error: 'true'
|
||||
|
|
@ -109,7 +121,7 @@ Or you can configure TRX test output in `*.csproj` or `Directory.Build.props`:
|
|||
```xml
|
||||
<PropertyGroup>
|
||||
<VSTestLogger>trx%3bLogFileName=$(MSBuildProjectName).trx</VSTestLogger>
|
||||
<VSTestResultsDirectory>$(MSBuildThisFileDirectory)/reports</VSTestResultsDirectory>
|
||||
<VSTestResultsDirectory>$(MSBuildThisFileDirectory)/TestResults/$(TargetFramework)</VSTestResultsDirectory>
|
||||
</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\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**
|
||||
|Result|Test|Time|
|
||||
|:---:|:---|---:|
|
||||
|❌|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
|
||||
|
||||
## <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 |
|
||||
|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
|
||||
|
||||
**7** tests were completed in **1.061s** with **3** passed, **1** skipped and **3** failed.
|
||||
|
||||
| Result | Suite | Tests | Time | Passed ✔️ | Skipped ✖️ | Failed ❌ |
|
||||
| :---: | :--- | ---: | ---: | ---: | ---: | ---: |
|
||||
| ❌ | [DotnetTests.XUnitTests.CalculatorTests](#ts-0-DotnetTests-XUnitTests-CalculatorTests) | 7 | 109.5761ms | 3 | 1 | 3 |
|
||||
|
||||
# Test Suites
|
||||
|
||||
## <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 |
|
||||
|Result|Test|Time|
|
||||
|:---:|:---|---:|
|
||||
|❌|Exception_In_TargetTest|0ms|
|
||||
|❌|Exception_In_Test|2ms|
|
||||
|❌|Failing_Test|3ms|
|
||||
|✔️|Passing_Test|0ms|
|
||||
|✔️|Passing_Test_With_Name|0ms|
|
||||
|✖️|Skipped_Test|1ms|
|
||||
|✔️|Timeout_Test|102ms|
|
||||
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 ❌ |
|
||||
| :---: | :--- | ---: | ---: | ---: | ---: | ---: |
|
||||
| ❌ | [__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 |
|
||||
**Test 2**
|
||||
|Result|Test|Time|
|
||||
|:---:|:---|---:|
|
||||
|❌|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
|
||||
|
||||
## <a id="user-content-ts-0-tests-main-test-js" href="#ts-0-tests-main-test-js">__tests__\main.test.js</a> ❌
|
||||
|
||||
### 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 |
|
||||
|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
|
||||
|
||||
exports[`dart-json tests matches report snapshot 1`] = `
|
||||
Object {
|
||||
"annotations": Array [
|
||||
Object {
|
||||
"annotation_level": "failure",
|
||||
"end_line": 13,
|
||||
"message": "Expected: <2>
|
||||
TestRunResult {
|
||||
"path": "fixtures/dart-json.json",
|
||||
"suites": Array [
|
||||
TestSuiteResult {
|
||||
"groups": Array [
|
||||
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>
|
||||
|
||||
|
||||
package:test_api expect
|
||||
",
|
||||
"path": "test/main_test.dart",
|
||||
"stackTrace": "package:test_api expect
|
||||
test\\\\main_test.dart 13:9 main.<fn>.<fn>.<fn>
|
||||
",
|
||||
"path": "test/main_test.dart",
|
||||
"start_line": 13,
|
||||
"title": "[test\\\\main_test.dart] Test 1 Test 1.1 Failing test",
|
||||
},
|
||||
Object {
|
||||
"annotation_level": "failure",
|
||||
"end_line": 2,
|
||||
"message": "Exception: Some error
|
||||
|
||||
package:darttest/main.dart 2:3 throwError
|
||||
},
|
||||
"name": "Test 1 Test 1.1 Failing test",
|
||||
"result": "failed",
|
||||
"time": 20,
|
||||
},
|
||||
TestCaseResult {
|
||||
"error": Object {
|
||||
"line": 2,
|
||||
"message": "Exception: Some error",
|
||||
"path": "lib/main.dart",
|
||||
"stackTrace": "package:darttest/main.dart 2:3 throwError
|
||||
test\\\\main_test.dart 17:9 main.<fn>.<fn>.<fn>
|
||||
",
|
||||
"path": "lib/main.dart",
|
||||
"start_line": 2,
|
||||
"title": "[test\\\\main_test.dart] Test 1 Test 1.1 Exception in target unit",
|
||||
},
|
||||
Object {
|
||||
"annotation_level": "failure",
|
||||
"end_line": 24,
|
||||
"message": "Exception: Some error
|
||||
|
||||
test\\\\main_test.dart 24:7 main.<fn>.<fn>
|
||||
},
|
||||
"name": "Test 1 Test 1.1 Exception in target unit",
|
||||
"result": "failed",
|
||||
"time": 6,
|
||||
},
|
||||
],
|
||||
},
|
||||
TestGroupResult {
|
||||
"name": "Test 2",
|
||||
"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,
|
||||
"title": "[test\\\\main_test.dart] Test 2 Exception in test",
|
||||
},
|
||||
"name": "Test 2 Exception in test",
|
||||
"result": "failed",
|
||||
"time": 12,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
"name": "test\\\\main_test.dart",
|
||||
"totalTime": undefined,
|
||||
},
|
||||
Object {
|
||||
"annotation_level": "failure",
|
||||
"end_line": 5,
|
||||
"message": "TimeoutException after 0:00:00.000001: Test timed out after 0 seconds.
|
||||
|
||||
dart:isolate _RawReceivePortImpl._handleMessage
|
||||
TestSuiteResult {
|
||||
"groups": Array [
|
||||
TestGroupResult {
|
||||
"name": null,
|
||||
"tests": Array [
|
||||
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,
|
||||
"title": "[test\\\\second_test.dart] Timeout test",
|
||||
},
|
||||
"name": "Timeout test",
|
||||
"result": "failed",
|
||||
"time": 37,
|
||||
},
|
||||
TestCaseResult {
|
||||
"error": undefined,
|
||||
"name": "Skipped test",
|
||||
"result": "skipped",
|
||||
"time": 14,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
"name": "test\\\\second_test.dart",
|
||||
"totalTime": undefined,
|
||||
},
|
||||
],
|
||||
"summary": "
|
||||
|
||||
### 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 ❌",
|
||||
"totalTime": 3760,
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
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 path from 'path'
|
||||
|
||||
import {parseDartJson} from '../src/parsers/dart-json/dart-json-parser'
|
||||
import {ParseOptions} from '../src/parsers/parser-types'
|
||||
import {DartJsonParser} from '../src/parsers/dart-json/dart-json-parser'
|
||||
import {ParseOptions} from '../src/test-parser'
|
||||
import {getReport} from '../src/report/get-report'
|
||||
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', () => {
|
||||
it('matches report snapshot', async () => {
|
||||
const opts: ParseOptions = {
|
||||
name: 'Dart tests',
|
||||
annotations: true,
|
||||
parseErrors: true,
|
||||
trackedFiles: ['lib/main.dart', 'test/main_test.dart', 'test/second_test.dart'],
|
||||
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/dart/'
|
||||
}
|
||||
|
||||
const result = await parseDartJson([xmlFixture], opts)
|
||||
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||
fs.writeFileSync(outputPath, result?.output?.summary ?? '')
|
||||
const fixturePath = path.join(__dirname, 'fixtures', 'dart-json.json')
|
||||
const outputPath = path.join(__dirname, '__outputs__', 'dart-json.md')
|
||||
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||
|
||||
expect(result.success).toBeFalsy()
|
||||
expect(result?.output).toMatchSnapshot()
|
||||
const parser = new DartJsonParser(opts)
|
||||
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 path from 'path'
|
||||
|
||||
import {parseDotnetTrx} from '../src/parsers/dotnet-trx/dotnet-trx-parser'
|
||||
import {ParseOptions} from '../src/parsers/parser-types'
|
||||
import {DotnetTrxParser} from '../src/parsers/dotnet-trx/dotnet-trx-parser'
|
||||
import {ParseOptions} from '../src/test-parser'
|
||||
import {getReport} from '../src/report/get-report'
|
||||
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', () => {
|
||||
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 = {
|
||||
name: 'Dotnet TRX tests',
|
||||
annotations: true,
|
||||
parseErrors: true,
|
||||
trackedFiles: ['DotnetTests.Unit/Calculator.cs', 'DotnetTests.XUnitTests/CalculatorTests.cs'],
|
||||
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/dotnet/'
|
||||
}
|
||||
|
||||
const result = await parseDotnetTrx([xmlFixture], opts)
|
||||
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||
fs.writeFileSync(outputPath, result?.output?.summary ?? '')
|
||||
const parser = new DotnetTrxParser(opts)
|
||||
const result = await parser.parse(filePath, fileContent)
|
||||
expect(result).toMatchSnapshot()
|
||||
|
||||
expect(result.success).toBeFalsy()
|
||||
expect(result?.output).toMatchSnapshot()
|
||||
const report = getReport([result])
|
||||
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 path from 'path'
|
||||
|
||||
import {parseJestJunit} from '../src/parsers/jest-junit/jest-junit-parser'
|
||||
import {ParseOptions} from '../src/parsers/parser-types'
|
||||
import {JestJunitParser} from '../src/parsers/jest-junit/jest-junit-parser'
|
||||
import {ParseOptions} from '../src/test-parser'
|
||||
import {getReport} from '../src/report/get-report'
|
||||
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', () => {
|
||||
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 = {
|
||||
name: 'jest tests',
|
||||
annotations: true,
|
||||
parseErrors: true,
|
||||
trackedFiles: ['__tests__/main.test.js', '__tests__/second.test.js', 'lib/main.js'],
|
||||
workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/jest/'
|
||||
}
|
||||
|
||||
const result = await parseJestJunit([xmlFixture], opts)
|
||||
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
|
||||
fs.writeFileSync(outputPath, result?.output?.summary ?? '')
|
||||
const parser = new JestJunitParser(opts)
|
||||
const result = await parser.parse(filePath, fileContent)
|
||||
expect(result).toMatchSnapshot()
|
||||
|
||||
expect(result.success).toBeFalsy()
|
||||
expect(result?.output).toMatchSnapshot()
|
||||
const report = getReport([result])
|
||||
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).
|
||||
author: Michal Dorner <dorner.michal@gmail.com>
|
||||
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:
|
||||
description: Name of the check run
|
||||
required: true
|
||||
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
|
||||
reporter:
|
||||
description: |
|
||||
|
|
@ -26,13 +21,38 @@ inputs:
|
|||
- flutter-machine
|
||||
- jest-junit
|
||||
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:
|
||||
description: GitHub Access Token
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
working-directory:
|
||||
description: Relative path under $GITHUB_WORKSPACE where the repository was checked out
|
||||
required: false
|
||||
outputs:
|
||||
conclusion:
|
||||
description: |
|
||||
|
|
|
|||
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 fs from 'fs'
|
||||
import glob from 'fast-glob'
|
||||
import {parseDartJson} from './parsers/dart-json/dart-json-parser'
|
||||
import {parseDotnetTrx} from './parsers/dotnet-trx/dotnet-trx-parser'
|
||||
import {parseJestJunit} from './parsers/jest-junit/jest-junit-parser'
|
||||
import {FileContent, ParseOptions, ParseTestResult} from './parsers/parser-types'
|
||||
|
||||
import {ParseOptions, TestParser} from './test-parser'
|
||||
import {TestRunResult} from './test-results'
|
||||
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 {listFiles} from './utils/git'
|
||||
import {getCheckRunSha} from './utils/github-utils'
|
||||
import {Icon} from './utils/markdown-utils'
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
|
|
@ -19,13 +26,30 @@ async function run(): Promise<void> {
|
|||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const annotations = core.getInput('annotations', {required: true}) === 'true'
|
||||
const failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
|
||||
const name = core.getInput('name', {required: true})
|
||||
const path = core.getInput('path', {required: true})
|
||||
const reporter = core.getInput('reporter', {required: true})
|
||||
const token = core.getInput('token', {required: true})
|
||||
const 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 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) {
|
||||
core.info(`Changing directory to ${workDirInput}`)
|
||||
|
|
@ -37,26 +61,41 @@ async function main(): Promise<void> {
|
|||
const sha = getCheckRunSha()
|
||||
|
||||
// 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 = {
|
||||
name,
|
||||
annotations,
|
||||
const options: ParseOptions = {
|
||||
trackedFiles,
|
||||
workDir
|
||||
workDir,
|
||||
parseErrors
|
||||
}
|
||||
|
||||
const parser = getParser(reporter)
|
||||
const files = await getFiles(path)
|
||||
core.info(`Using test report parser '${reporter}'`)
|
||||
const parser = getParser(reporter, options)
|
||||
|
||||
const files = await getFiles(path)
|
||||
if (files.length === 0) {
|
||||
core.setFailed(`No file matches path '${path}'`)
|
||||
return
|
||||
}
|
||||
|
||||
core.info(`Using test report parser '${reporter}'`)
|
||||
const result = await parser(files, opts)
|
||||
const conclusion = result.success ? 'success' : 'failure'
|
||||
const results: TestRunResult[] = []
|
||||
for (const file of files) {
|
||||
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}'`)
|
||||
await octokit.checks.create({
|
||||
|
|
@ -64,40 +103,49 @@ async function main(): Promise<void> {
|
|||
name,
|
||||
conclusion,
|
||||
status: 'completed',
|
||||
output: result.output,
|
||||
output: {
|
||||
title: `${name} ${icon}`,
|
||||
summary,
|
||||
annotations
|
||||
},
|
||||
...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)
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
|
||||
function getParser(reporter: string): ParseTestResult {
|
||||
function getParser(reporter: string, options: ParseOptions): TestParser {
|
||||
switch (reporter) {
|
||||
case 'dart-json':
|
||||
return parseDartJson
|
||||
return new DartJsonParser(options)
|
||||
case 'dotnet-trx':
|
||||
return parseDotnetTrx
|
||||
return new DotnetTrxParser(options)
|
||||
case 'flutter-machine':
|
||||
return parseDartJson
|
||||
return new DartJsonParser(options)
|
||||
case 'jest-junit':
|
||||
return parseJestJunit
|
||||
return new JestJunitParser(options)
|
||||
default:
|
||||
throw new Error(`Input variable 'reporter' is set to invalid value '${reporter}'`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFiles(pattern: string): Promise<FileContent[]> {
|
||||
const paths = await glob(pattern, {dot: true})
|
||||
return Promise.all(
|
||||
paths.map(async path => {
|
||||
core.info(`Reading test report '${path}'`)
|
||||
const content = await fs.promises.readFile(path, {encoding: 'utf8'})
|
||||
return {path, content}
|
||||
})
|
||||
)
|
||||
export async function getFiles(pattern: string): Promise<string[]> {
|
||||
const tasks = pattern.split(',').map(async pat => glob(pat, {dot: true}))
|
||||
const paths = await Promise.all(tasks)
|
||||
return paths.flat()
|
||||
}
|
||||
|
||||
run()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
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 {Icon, fixEol} from '../../utils/markdown-utils'
|
||||
|
||||
import {
|
||||
ReportEvent,
|
||||
|
|
@ -25,8 +23,9 @@ import {
|
|||
TestRunResult,
|
||||
TestSuiteResult,
|
||||
TestGroupResult,
|
||||
TestCaseResult
|
||||
} from '../../report/test-results'
|
||||
TestCaseResult,
|
||||
TestCaseError
|
||||
} from '../../test-results'
|
||||
|
||||
class TestRun {
|
||||
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> {
|
||||
const testRuns = files.map(f => getTestRun(f.path, f.content))
|
||||
const testRunsResults = testRuns.map(getTestRunResult)
|
||||
const success = testRuns.every(tr => tr.success)
|
||||
const icon = success ? Icon.success : Icon.fail
|
||||
export class DartJsonParser implements TestParser {
|
||||
constructor(readonly options: ParseOptions) {}
|
||||
|
||||
return {
|
||||
success,
|
||||
output: {
|
||||
title: `${options.name.trim()} ${icon}`,
|
||||
summary: getReport(testRunsResults),
|
||||
annotations: options.annotations ? getAnnotations(testRuns, options.workDir, options.trackedFiles) : undefined
|
||||
}
|
||||
async parse(path: string, content: string): Promise<TestRunResult> {
|
||||
const tr = this.getTestRun(path, content)
|
||||
const result = this.getTestRunResult(tr)
|
||||
return Promise.resolve(result)
|
||||
}
|
||||
}
|
||||
|
||||
function getTestRun(path: string, content: string): TestRun {
|
||||
core.info(`Parsing content of '${path}'`)
|
||||
const lines = content.split(/\n\r?/g)
|
||||
const events = lines
|
||||
.map((str, i) => {
|
||||
if (str.trim() === '') {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch (e) {
|
||||
const col = e.columnNumber !== undefined ? `:${e.columnNumber}` : ''
|
||||
new Error(`Invalid JSON at ${path}:${i + 1}${col}\n\n${e}`)
|
||||
private getTestRun(path: string, content: string): TestRun {
|
||||
core.info(`Parsing content of '${path}'`)
|
||||
const lines = content.split(/\n\r?/g)
|
||||
const events = lines
|
||||
.map((str, i) => {
|
||||
if (str.trim() === '') {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch (e) {
|
||||
const col = e.columnNumber !== undefined ? `:${e.columnNumber}` : ''
|
||||
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
|
||||
let totalTime = 0
|
||||
const suites: {[id: number]: TestSuite} = {}
|
||||
const tests: {[id: number]: TestCase} = {}
|
||||
return new TestRunResult(tr.path, suites, tr.time)
|
||||
}
|
||||
|
||||
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
|
||||
private getGroups(suite: TestSuite): TestGroupResult[] {
|
||||
const groups = Object.values(suite.groups).filter(grp => grp.tests.length > 0)
|
||||
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 => this.getTest(t))
|
||||
return new TestGroupResult(group.group.name, tests)
|
||||
})
|
||||
}
|
||||
|
||||
private getTest(tc: TestCase): TestCaseResult {
|
||||
const error = this.getError(tc)
|
||||
return new TestCaseResult(tc.testStart.test.name, tc.result, tc.time, error)
|
||||
}
|
||||
|
||||
private getError(test: TestCase): TestCaseError | undefined {
|
||||
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 {
|
||||
const suites = tr.suites.map(s => {
|
||||
return new TestSuiteResult(s.suite.path, getGroups(s))
|
||||
})
|
||||
|
||||
return new TestRunResult(tr.path, suites, tr.time)
|
||||
}
|
||||
|
||||
function getGroups(suite: TestSuite): TestGroupResult[] {
|
||||
const groups = Object.values(suite.groups).filter(grp => grp.tests.length > 0)
|
||||
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)
|
||||
}
|
||||
}
|
||||
// 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 [_, pathStr, lineStr] = match
|
||||
const path = normalizeFilePath(pathStr)
|
||||
if (trackedFiles.includes(path)) {
|
||||
const line = parseInt(lineStr)
|
||||
return {path, line}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return annotations
|
||||
}
|
||||
|
||||
function getAnnotation(
|
||||
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
|
||||
private getRelativePathFromUrl(file: string, workDir: string): string {
|
||||
const prefix = 'file:///'
|
||||
if (file.startsWith(prefix)) {
|
||||
file = file.substr(prefix.length)
|
||||
}
|
||||
src = {
|
||||
file,
|
||||
line: test.testStart.test.line ?? 0
|
||||
if (file.startsWith(workDir)) {
|
||||
file = file.substr(workDir.length)
|
||||
}
|
||||
}
|
||||
|
||||
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}`
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
||||
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 {ErrorInfo, Outcome, TestMethod, TrxReport} from './dotnet-trx-types'
|
||||
|
||||
import {Annotation, FileContent, ParseOptions, TestResult} from '../parser-types'
|
||||
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 {parseAttribute} from '../../utils/xml-utils'
|
||||
import {Icon, fixEol} from '../../utils/markdown-utils'
|
||||
import {parseIsoDate, parseNetDuration} from '../../utils/parse-utils'
|
||||
|
||||
import {
|
||||
TestExecutionResult,
|
||||
TestRunResult,
|
||||
TestSuiteResult,
|
||||
TestGroupResult,
|
||||
TestCaseResult
|
||||
} from '../../report/test-results'
|
||||
import getReport from '../../report/get-report'
|
||||
TestCaseResult,
|
||||
TestCaseError
|
||||
} from '../../test-results'
|
||||
|
||||
class TestClass {
|
||||
constructor(readonly name: string) {}
|
||||
|
|
@ -42,133 +41,117 @@ class Test {
|
|||
}
|
||||
}
|
||||
|
||||
export async function parseDotnetTrx(files: FileContent[], options: ParseOptions): Promise<TestResult> {
|
||||
const testRuns: TestRunResult[] = []
|
||||
const testClasses: TestClass[] = []
|
||||
export class DotnetTrxParser implements TestParser {
|
||||
constructor(readonly options: ParseOptions) {}
|
||||
|
||||
for (const file of files) {
|
||||
const trx = await getTrxReport(file)
|
||||
const tc = getTestClasses(trx)
|
||||
const tr = getTestRunResult(file.path, trx, tc)
|
||||
testRuns.push(tr)
|
||||
testClasses.push(...tc)
|
||||
async parse(path: string, content: string): Promise<TestRunResult> {
|
||||
const trx = await this.getTrxReport(path, content)
|
||||
const tc = this.getTestClasses(trx)
|
||||
const tr = this.getTestRunResult(path, trx, tc)
|
||||
return tr
|
||||
}
|
||||
|
||||
const success = testRuns.every(tr => tr.result === 'success')
|
||||
const icon = success ? Icon.success : Icon.fail
|
||||
|
||||
return {
|
||||
success,
|
||||
output: {
|
||||
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]
|
||||
private async getTrxReport(path: string, content: string): Promise<TrxReport> {
|
||||
core.info(`Parsing content of '${path}'`)
|
||||
try {
|
||||
return (await parseStringPromise(content)) as TrxReport
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid XML at ${path}\n\n${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
const unitTestsResults = trx.TestRun.Results.flatMap(r => r.UnitTestResult).flatMap(unitTestResult => ({
|
||||
unitTestResult,
|
||||
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
|
||||
private 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 output = r.unitTestResult.Output
|
||||
const error = output?.length > 0 && output[0].ErrorInfo?.length > 0 ? output[0].ErrorInfo[0] : undefined
|
||||
const test = new Test(r.testMethod.$.name, r.unitTestResult.$.outcome, r.unitTestResult.$.duration, error)
|
||||
tc.tests.push(test)
|
||||
|
||||
const unitTestsResults = trx.TestRun.Results.flatMap(r => r.UnitTestResult).flatMap(unitTestResult => ({
|
||||
unitTestResult,
|
||||
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)
|
||||
result.sort((a, b) => a.name.localeCompare(b.name))
|
||||
for (const tc of result) {
|
||||
tc.tests.sort((a, b) => a.name.localeCompare(b.name))
|
||||
private getTestRunResult(path: string, trx: TrxReport, testClasses: TestClass[]): TestRunResult {
|
||||
const times = trx.TestRun.Times[0].$
|
||||
const totalTime = parseIsoDate(times.finish).getTime() - parseIsoDate(times.start).getTime()
|
||||
|
||||
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 annotations: Annotation[] = []
|
||||
for (const tc of testClasses) {
|
||||
for (const t of tc.tests) {
|
||||
if (t.error) {
|
||||
const src = exceptionThrowSource(t.error.StackTrace[0], workDir, trackedFiles)
|
||||
if (src === null) {
|
||||
continue
|
||||
const message = test.error.Message[0]
|
||||
const stackTrace = test.error.StackTrace[0]
|
||||
let path
|
||||
let line
|
||||
|
||||
const src = this.exceptionThrowSource(stackTrace)
|
||||
if (src) {
|
||||
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 {
|
||||
$: {
|
||||
creation: Date
|
||||
queuing: Date
|
||||
start: Date
|
||||
finish: Date
|
||||
creation: string
|
||||
queuing: string
|
||||
start: string
|
||||
finish: string
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ export interface UnitTestResult {
|
|||
$: {
|
||||
testId: string
|
||||
testName: string
|
||||
duration: number
|
||||
duration: string
|
||||
outcome: Outcome
|
||||
}
|
||||
Output: Output[]
|
||||
|
|
|
|||
|
|
@ -1,145 +1,116 @@
|
|||
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 {JunitReport, TestCase, TestSuite} from './jest-junit-types'
|
||||
import {fixEol, Icon} from '../../utils/markdown-utils'
|
||||
import {normalizeFilePath} from '../../utils/file-utils'
|
||||
import {parseAttribute} from '../../utils/xml-utils'
|
||||
|
||||
import {
|
||||
TestExecutionResult,
|
||||
TestRunResult,
|
||||
TestSuiteResult,
|
||||
TestGroupResult,
|
||||
TestCaseResult
|
||||
} from '../../report/test-results'
|
||||
import getReport from '../../report/get-report'
|
||||
TestCaseResult,
|
||||
TestCaseError
|
||||
} from '../../test-results'
|
||||
|
||||
export async function parseJestJunit(files: FileContent[], options: ParseOptions): Promise<TestResult> {
|
||||
const junit: JunitReport[] = []
|
||||
const testRuns: TestRunResult[] = []
|
||||
export class JestJunitParser implements TestParser {
|
||||
constructor(readonly options: ParseOptions) {}
|
||||
|
||||
for (const file of files) {
|
||||
const ju = await getJunitReport(file)
|
||||
const tr = getTestRunResult(file.path, ju)
|
||||
junit.push(ju)
|
||||
testRuns.push(tr)
|
||||
async parse(path: string, content: string): Promise<TestRunResult> {
|
||||
const ju = await this.getJunitReport(path, content)
|
||||
return this.getTestRunResult(path, ju)
|
||||
}
|
||||
|
||||
const success = testRuns.every(tr => tr.result === 'success')
|
||||
const icon = success ? Icon.success : Icon.fail
|
||||
|
||||
return {
|
||||
success,
|
||||
output: {
|
||||
title: `${options.name.trim()} ${icon}`,
|
||||
summary: getReport(testRuns),
|
||||
annotations: options.annotations ? getAnnotations(junit, options.workDir, options.trackedFiles) : undefined
|
||||
private async getJunitReport(path: string, content: string): Promise<JunitReport> {
|
||||
core.info(`Parsing content of '${path}'`)
|
||||
try {
|
||||
return (await parseStringPromise(content)) as JunitReport
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid XML at ${path}\n\n${e}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getJunitReport(file: FileContent): Promise<JunitReport> {
|
||||
core.info(`Parsing content of '${file.path}'`)
|
||||
try {
|
||||
return (await parseStringPromise(file.content, {
|
||||
attrValueProcessors: [parseAttribute]
|
||||
})) as JunitReport
|
||||
} 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)
|
||||
private getTestRunResult(path: string, junit: JunitReport): TestRunResult {
|
||||
const suites = junit.testsuites.testsuite.map(ts => {
|
||||
const name = ts.$.name.trim()
|
||||
const time = parseFloat(ts.$.time) * 1000
|
||||
const sr = new TestSuiteResult(name, this.getGroups(ts), time)
|
||||
return sr
|
||||
})
|
||||
return new TestGroupResult(grp.describe, tests)
|
||||
})
|
||||
}
|
||||
|
||||
function getTestCaseResult(test: TestCase): TestExecutionResult {
|
||||
if (test.failure) return 'failed'
|
||||
if (test.skipped) return 'skipped'
|
||||
return 'success'
|
||||
}
|
||||
const time = parseFloat(junit.testsuites.$.time) * 1000
|
||||
return new TestRunResult(path, suites, time)
|
||||
}
|
||||
|
||||
function getAnnotations(junitReports: JunitReport[], workDir: string, trackedFiles: string[]): Annotation[] {
|
||||
const annotations: Annotation[] = []
|
||||
for (const junit of junitReports) {
|
||||
for (const suite of junit.testsuites.testsuite) {
|
||||
for (const tc of suite.testcase) {
|
||||
if (!tc.failure) {
|
||||
continue
|
||||
}
|
||||
for (const ex of tc.failure) {
|
||||
const src = exceptionThrowSource(ex, workDir, trackedFiles)
|
||||
if (src === null) {
|
||||
continue
|
||||
}
|
||||
annotations.push({
|
||||
annotation_level: 'failure',
|
||||
start_line: src.line,
|
||||
end_line: src.line,
|
||||
path: src.file,
|
||||
message: fixEol(ex),
|
||||
title: `[${suite.$.name}] ${tc.$.name.trim()}`
|
||||
})
|
||||
private 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 = this.getTestCaseResult(tc)
|
||||
const time = parseFloat(tc.$.time) * 1000
|
||||
const error = this.getTestCaseError(tc)
|
||||
return new TestCaseResult(name, result, time, error)
|
||||
})
|
||||
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 {
|
||||
$: {
|
||||
name: string
|
||||
tests: number
|
||||
failures: number // assertion failed
|
||||
errors: number // unhandled exception during test execution
|
||||
time: number
|
||||
tests: string
|
||||
failures: string // assertion failed
|
||||
errors: string // unhandled exception during test execution
|
||||
time: string
|
||||
}
|
||||
testsuite: TestSuite[]
|
||||
}
|
||||
|
|
@ -16,11 +16,11 @@ export interface TestSuites {
|
|||
export interface TestSuite {
|
||||
$: {
|
||||
name: string
|
||||
tests: number
|
||||
errors: number
|
||||
failures: number
|
||||
skipped: number
|
||||
time: number
|
||||
tests: string
|
||||
errors: string
|
||||
failures: string
|
||||
skipped: string
|
||||
time: string
|
||||
timestamp?: Date
|
||||
}
|
||||
testcase: TestCase[]
|
||||
|
|
@ -31,7 +31,7 @@ export interface TestCase {
|
|||
classname: string
|
||||
file?: string
|
||||
name: string
|
||||
time: number
|
||||
time: string
|
||||
}
|
||||
failure?: 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 {TestExecutionResult, TestRunResult, TestSuiteResult} from './test-results'
|
||||
import {Align, Icon, link, table} from '../utils/markdown-utils'
|
||||
import {TestExecutionResult, TestRunResult, TestSuiteResult} from '../test-results'
|
||||
import {Align, formatTime, Icon, link, table} from '../utils/markdown-utils'
|
||||
import {slug} from '../utils/slugger'
|
||||
|
||||
export default function getReport(results: TestRunResult[]): string {
|
||||
const badge = getBadge(results)
|
||||
const runsSummary = results.map(getRunSummary).join('\n\n')
|
||||
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')
|
||||
export interface ReportOptions {
|
||||
listSuites?: 'all' | 'failed'
|
||||
listTests?: 'all' | 'failed' | 'none'
|
||||
}
|
||||
|
||||
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 skipped = results.reduce((sum, tr) => sum + tr.skipped, 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'
|
||||
if (failed > 0) {
|
||||
color = 'critical'
|
||||
} else if (passed === 0 && failed === 0) {
|
||||
color = 'yellow'
|
||||
}
|
||||
|
||||
const hint = failed > 0 ? 'Tests failed' : 'Tests passed successfully'
|
||||
const uri = encodeURIComponent(`tests-${message}-${color}`)
|
||||
const text = failed > 0 ? 'Tests failed' : 'Tests passed successfully'
|
||||
return ``
|
||||
return ``
|
||||
}
|
||||
|
||||
function getRunSummary(tr: TestRunResult): string {
|
||||
core.info('Generating check run summary')
|
||||
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.`
|
||||
function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): string[] {
|
||||
const sections: string[] = []
|
||||
|
||||
const suitesSummary = tr.suites.map((s, i) => {
|
||||
const icon = getResultIcon(s.result)
|
||||
const tsTime = `${s.time}ms`
|
||||
const tsName = s.name
|
||||
const tsAddr = makeSuiteSlug(i, tsName).link
|
||||
const tsNameLink = link(tsName, tsAddr)
|
||||
return [icon, tsNameLink, s.tests, tsTime, s.passed, s.skipped, s.failed]
|
||||
})
|
||||
|
||||
const summary = table(
|
||||
['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`
|
||||
if (testRuns.length > 1) {
|
||||
const tableData = testRuns.map((tr, runIndex) => {
|
||||
const time = formatTime(tr.time)
|
||||
const name = tr.path
|
||||
const addr = makeRunSlug(runIndex).link
|
||||
const nameLink = link(name, addr)
|
||||
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}` : ''
|
||||
return [nameLink, passed, failed, skipped, time]
|
||||
})
|
||||
.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 tsSlug = makeSuiteSlug(index, tsName)
|
||||
const tsSlug = makeSuiteSlug(runIndex, suiteIndex)
|
||||
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} {
|
||||
// use "ts-$index-" as prefix to avoid slug conflicts after escaping the paths
|
||||
return slug(`ts-${index}-${name}`)
|
||||
function makeRunSlug(runIndex: number): {id: string; link: string} {
|
||||
// use prefix to avoid slug conflicts after escaping the paths
|
||||
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 {
|
||||
|
|
|
|||
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 {
|
||||
return this.suites.some(t => t.result === 'failed') ? 'failed' : 'success'
|
||||
}
|
||||
|
||||
get failedSuites(): TestSuiteResult[] {
|
||||
return this.suites.filter(s => s.result === 'failed')
|
||||
}
|
||||
}
|
||||
|
||||
export class TestSuiteResult {
|
||||
|
|
@ -47,6 +51,10 @@ export class TestSuiteResult {
|
|||
get result(): TestExecutionResult {
|
||||
return this.groups.some(t => t.result === 'failed') ? 'failed' : 'success'
|
||||
}
|
||||
|
||||
get failedGroups(): TestGroupResult[] {
|
||||
return this.groups.filter(grp => grp.result === 'failed')
|
||||
}
|
||||
}
|
||||
|
||||
export class TestGroupResult {
|
||||
|
|
@ -68,10 +76,26 @@ export class TestGroupResult {
|
|||
get result(): TestExecutionResult {
|
||||
return this.tests.some(t => t.result === 'failed') ? 'failed' : 'success'
|
||||
}
|
||||
|
||||
get failedTests(): TestCaseResult[] {
|
||||
return this.tests.filter(tc => tc.result === 'failed')
|
||||
}
|
||||
}
|
||||
|
||||
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 interface TestCaseError {
|
||||
path?: string
|
||||
line?: number
|
||||
message?: string
|
||||
stackTrace: string
|
||||
}
|
||||
|
|
@ -11,19 +11,15 @@ export const Icon = {
|
|||
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 {
|
||||
return `[${title}](${address})`
|
||||
}
|
||||
|
||||
type ToString = string | number | boolean | Date
|
||||
export function table(headers: ToString[], align: ToString[], ...rows: ToString[][]): string {
|
||||
const headerRow = `| ${headers.map(tableEscape).join(' | ')} |`
|
||||
const alignRow = `| ${align.join(' | ')} |`
|
||||
const contentRows = rows.map(row => `| ${row.map(tableEscape).join(' | ')} |`).join('\n')
|
||||
const headerRow = `|${headers.map(tableEscape).join('|')}|`
|
||||
const alignRow = `|${align.join('|')}|`
|
||||
const contentRows = rows.map(row => `|${row.map(tableEscape).join('|')}|`).join('\n')
|
||||
return [headerRow, alignRow, contentRows].join('\n')
|
||||
}
|
||||
|
||||
|
|
@ -34,3 +30,19 @@ export function tableEscape(content: ToString): string {
|
|||
export function fixEol(text?: string): string {
|
||||
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