├── global.json
├── .gitattributes
├── src
├── package
│ ├── packageIcon.png
│ ├── JUnitXml.TestLogger.props
│ ├── JUnitXml.TestLogger.nuspec
│ └── package.csproj
├── JUnit.Xml.TestLogger
│ ├── Assembly.cs
│ ├── JUnit.Xml.TestLogger.csproj
│ ├── JUnitXmlTestLogger.cs
│ └── JunitXmlSerializer.cs
└── JUnit.Xml.TestLogger.TestAdapter
│ └── JUnit.Xml.TestLogger.TestAdapter.csproj
├── docs
├── jenkins-recommendation.md
├── assets
│ ├── gitlab-test-popup-with-default-failure.png
│ ├── gitlab-test-popup-with-verbose-failure.png
│ ├── gitlab-test-summary-with-class-option.png
│ ├── gitlab-test-summary-with-default-option.png
│ ├── circleci-test-expanded-with-failure-default.png
│ ├── circleci-test-expanded-with-failure-verbose.png
│ ├── circleci-test-collapsed-with-methodname-class.png
│ ├── circleci-test-collapsed-with-methodname-default.png
│ └── TestResults.xml
├── gitlab-recommendation.md
└── circleci-recommendation.md
├── .editorconfig
├── CHANGELOG.md
├── .travis.yml
├── NuGet.config
├── test
├── assets
│ ├── NuGet.Debug.config
│ ├── NuGet.Release.config
│ ├── JUnit.Xml.TestLogger.NetCore.Tests
│ │ ├── JUnit.Xml.TestLogger.NetCore.Tests.csproj
│ │ ├── UnitTest2.cs
│ │ └── UnitTest1.cs
│ ├── JUnit.Xml.TestLogger.NetFull.Tests
│ │ └── JUnit.Xml.TestLogger.NetFull.Tests.csproj
│ ├── JUnit.Xml.TestLogger.NetMulti.Tests
│ │ └── JUnit.Xml.TestLogger.NetMulti.Tests.csproj
│ └── JUnit.xsd
├── JUnit.Xml.TestLogger.UnitTests
│ ├── JUnit.Xml.TestLogger.UnitTests.csproj
│ └── JUnitXmlTestSerializerTests.cs
└── JUnit.Xml.TestLogger.AcceptanceTests
│ ├── JUnitTestLoggerResultDirectoryAcceptanceTests.cs
│ ├── JunitXmlValidator.cs
│ ├── JUnitTestLoggerPathTests.cs
│ ├── JUnit.Xml.TestLogger.AcceptanceTests.csproj
│ ├── DotnetTestFixture.cs
│ ├── JUnitTestLoggerAcceptanceTests.cs
│ └── JUnitTestLoggerFormatOptionsAcceptanceTests.cs
├── scripts
├── stylecop.json
├── dependencies.props
├── stylecop.test.ruleset
├── stylecop.ruleset
└── settings.targets
├── .github
├── dependabot.yml
└── workflows
│ └── dotnet.yml
├── .appveyor.yml
├── LICENSE.md
├── .vscode
├── launch.json
└── tasks.json
├── .gitignore
├── README.md
└── junit.testlogger.sln
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "allowPrerelease": false
4 | }
5 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto
--------------------------------------------------------------------------------
/src/package/packageIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spekt/junit.testlogger/HEAD/src/package/packageIcon.png
--------------------------------------------------------------------------------
/docs/jenkins-recommendation.md:
--------------------------------------------------------------------------------
1 | # Jenkins Recommendation
2 |
3 | **TODO** If you are a Jenkins user who is willing to help, please see Issue #38
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root=true
3 |
4 | [*]
5 | trim_trailing_whitespace = true
6 | indent_style = space
7 |
8 | [*.cs]
9 | indent_size = 4
--------------------------------------------------------------------------------
/docs/assets/gitlab-test-popup-with-default-failure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spekt/junit.testlogger/HEAD/docs/assets/gitlab-test-popup-with-default-failure.png
--------------------------------------------------------------------------------
/docs/assets/gitlab-test-popup-with-verbose-failure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spekt/junit.testlogger/HEAD/docs/assets/gitlab-test-popup-with-verbose-failure.png
--------------------------------------------------------------------------------
/docs/assets/gitlab-test-summary-with-class-option.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spekt/junit.testlogger/HEAD/docs/assets/gitlab-test-summary-with-class-option.png
--------------------------------------------------------------------------------
/docs/assets/gitlab-test-summary-with-default-option.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spekt/junit.testlogger/HEAD/docs/assets/gitlab-test-summary-with-default-option.png
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | A changelog is maintained on the releases page of the [JUnit Test Logger GitHub repository](https://github.com/spekt/junit.testlogger/).
4 |
--------------------------------------------------------------------------------
/docs/assets/circleci-test-expanded-with-failure-default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spekt/junit.testlogger/HEAD/docs/assets/circleci-test-expanded-with-failure-default.png
--------------------------------------------------------------------------------
/docs/assets/circleci-test-expanded-with-failure-verbose.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spekt/junit.testlogger/HEAD/docs/assets/circleci-test-expanded-with-failure-verbose.png
--------------------------------------------------------------------------------
/docs/assets/circleci-test-collapsed-with-methodname-class.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spekt/junit.testlogger/HEAD/docs/assets/circleci-test-collapsed-with-methodname-class.png
--------------------------------------------------------------------------------
/docs/assets/circleci-test-collapsed-with-methodname-default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spekt/junit.testlogger/HEAD/docs/assets/circleci-test-collapsed-with-methodname-default.png
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 |
3 | matrix:
4 | include:
5 | - dotnet: 3.1
6 | mono: latest
7 |
8 | solution: junit.testlogger.sln
9 |
10 | script:
11 | - ./build.sh
12 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/assets/NuGet.Debug.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/assets/NuGet.Release.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/scripts/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "documentationRules": {
5 | "companyName": "Spekt Contributors",
6 | "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the MIT license. See LICENSE file in the project root for full license information.",
7 | "xmlHeader": false,
8 | "fileNamingConvention": "metadata"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "nuget" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/scripts/dependencies.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 2.0.0
5 | 15.7.2
6 | 4.9.0
7 | 3.1.140
8 |
9 |
10 | 15.5.0
11 | 3.10.1
12 | 3.10.0
13 |
14 | 1.1.118
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/JUnit.Xml.TestLogger.UnitTests/JUnit.Xml.TestLogger.UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | ..\..\
4 | true
5 |
6 |
7 |
8 |
9 | netcoreapp3.1
10 | true
11 | true
12 | false
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/JUnit.Xml.TestLogger/Assembly.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | //
5 | // Skip code analysis errors.
6 | //
7 |
8 | using System.Diagnostics.CodeAnalysis;
9 |
10 | [assembly: ExcludeFromCodeCoverage]
11 |
12 | namespace System.Diagnostics.CodeAnalysis
13 | {
14 | [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
15 | internal sealed class ExcludeFromCodeCoverageAttribute : Attribute { }
16 | }
17 |
--------------------------------------------------------------------------------
/scripts/stylecop.test.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/scripts/stylecop.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '3.0.{build}'
2 | image: Visual Studio 2019
3 |
4 | clone_depth: 1
5 |
6 | build_script:
7 | - cmd: dotnet pack -p:PackageVersion=%APPVEYOR_BUILD_VERSION%
8 | - cmd: dotnet pack -c Release -p:PackageVersion=%APPVEYOR_BUILD_VERSION%
9 | - cmd: dotnet test -p:PackageVersion=%APPVEYOR_BUILD_VERSION% test/JUnit.Xml.TestLogger.UnitTests/JUnit.Xml.TestLogger.UnitTests.csproj
10 | - cmd: dotnet test -p:PackageVersion=%APPVEYOR_BUILD_VERSION% test/JUnit.Xml.TestLogger.AcceptanceTests/JUnit.Xml.TestLogger.AcceptanceTests.csproj
11 |
12 | test: off
13 |
14 | artifacts:
15 | - path: 'src\package\bin\Release\*.nupkg'
16 |
17 | deploy:
18 | provider: NuGet
19 | server: https://www.myget.org/F/spekt/api/v2
20 | api_key:
21 | secure: 2C7HbSlU1kcOJ3nzZCpKR97cfWAg/8t38XDf8ywCbJI1ymt93ulfPqT67ugWuMla
22 | artifact: /.*\.nupkg/
23 | skip_symbols: true
24 |
--------------------------------------------------------------------------------
/test/assets/JUnit.Xml.TestLogger.NetCore.Tests/JUnit.Xml.TestLogger.NetCore.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ..\..\..\
5 |
6 |
7 |
8 |
9 | netcoreapp3.1
10 |
11 |
12 | false
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/JUnit.Xml.TestLogger/JUnit.Xml.TestLogger.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | ..\..\
4 |
5 |
6 |
7 |
8 | netstandard1.5
9 | Microsoft.VisualStudio.TestPlatform.Extension.JUnit.Xml.TestLogger
10 |
11 |
12 |
13 | Microsoft.VisualStudio.TestPlatform.Extension.JUnit.Xml.TestLogger
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/JUnit.Xml.TestLogger/JUnitXmlTestLogger.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace Microsoft.VisualStudio.TestPlatform.Extension.Junit.Xml.TestLogger
5 | {
6 | using Microsoft.VisualStudio.TestPlatform.ObjectModel;
7 | using Spekt.TestLogger;
8 |
9 | [FriendlyName(FriendlyName)]
10 | [ExtensionUri(ExtensionUri)]
11 | public class JUnitXmlTestLogger : TestLogger
12 | {
13 | ///
14 | /// Uri used to uniquely identify the logger.
15 | ///
16 | public const string ExtensionUri = "logger://Microsoft/TestPlatform/JUnitXmlLogger/v1";
17 |
18 | ///
19 | /// Alternate user friendly string to uniquely identify the console logger.
20 | ///
21 | public const string FriendlyName = "junit";
22 |
23 | public JUnitXmlTestLogger()
24 | : base(new JunitXmlSerializer())
25 | {
26 | }
27 |
28 | protected override string DefaultTestResultFile => "TestResults.xml";
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/package/JUnitXml.TestLogger.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Microsoft.VisualStudio.TestPlatform.Extension.JUnit.Xml.TestLogger.dll
6 | PreserveNewest
7 | False
8 |
9 |
10 | Microsoft.VisualStudio.TestPlatform.Extension.JUnit.Xml.TestAdapter.dll
11 | PreserveNewest
12 | False
13 |
14 |
15 | Spekt.TestLogger.dll
16 | PreserveNewest
17 | False
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/package/JUnitXml.TestLogger.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | JunitXml.TestLogger
5 | $version$
6 | JunitXml.TestLogger
7 | Siphonophora,codito,faizan2304,smadala,lahma
8 | Siphonophora,codito,faizan2304,smadala
9 | https://github.com/spekt/junit.testlogger
10 | Xml logger for JUnit v5 compliant xml report when test is running with "dotnet test" or "dotnet vstest".
11 | packageIcon.png
12 | MIT
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/JUnit.Xml.TestLogger.TestAdapter/JUnit.Xml.TestLogger.TestAdapter.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ..\..\
5 |
6 |
7 |
8 |
9 | netstandard1.5
10 | Microsoft.VisualStudio.TestPlatform.Extension.JUnit.Xml.TestAdapter
11 |
12 |
13 | false
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/test/assets/JUnit.Xml.TestLogger.NetFull.Tests/JUnit.Xml.TestLogger.NetFull.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ..\..\..\
5 |
6 |
7 |
8 |
9 | net46
10 | /usr/lib/mono/4.5/
11 |
12 |
13 | false
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | UnitTest1.cs
26 |
27 |
28 | UnitTest2.cs
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/test/assets/JUnit.Xml.TestLogger.NetMulti.Tests/JUnit.Xml.TestLogger.NetMulti.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ..\..\..\
5 |
6 |
7 |
8 |
9 | net46;netcoreapp3.1
10 | /usr/lib/mono/4.5/
11 |
12 |
13 | false
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | UnitTest1.cs
26 |
27 |
28 | UnitTest2.cs
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/test/JUnit.Xml.TestLogger.AcceptanceTests/bin/Debug/netcoreapp2.0/JUnit.Xml.TestLogger.AcceptanceTests.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}/test/JUnit.Xml.TestLogger.AcceptanceTests",
16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
17 | "console": "internalConsole",
18 | "stopAtEntry": false,
19 | "internalConsoleOptions": "openOnSessionStart"
20 | },
21 | {
22 | "name": ".NET Core Attach",
23 | "type": "coreclr",
24 | "request": "attach",
25 | "processId": "${command:pickProcess}"
26 | }
27 | ,]
28 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/junit.testlogger.sln"
11 | ],
12 | "problemMatcher": "$msCompile",
13 | "group": {
14 | "kind": "build",
15 | "isDefault": true
16 | }
17 | },
18 | {
19 | "label": "pack",
20 | "command": "dotnet",
21 | "type": "process",
22 | "args": [
23 | "pack",
24 | "${workspaceFolder}/junit.testlogger.sln"
25 | ],
26 | "problemMatcher": "$msCompile",
27 | "group": "build"
28 | },
29 | {
30 | "label": "test-unit",
31 | "dependsOn": [
32 | "pack"
33 | ],
34 | "command": "dotnet",
35 | "type": "process",
36 | "args": [
37 | "test",
38 | "${workspaceFolder}/test/JUnit.Xml.TestLogger.UnitTests/JUnit.Xml.TestLogger.UnitTests.csproj"
39 | ],
40 | "problemMatcher": "$msCompile",
41 | "group": "test"
42 | },
43 | {
44 | "label": "test-all",
45 | "dependsOn": [
46 | "test-unit"
47 | ],
48 | "command": "dotnet",
49 | "type": "process",
50 | "args": [
51 | "test",
52 | "${workspaceFolder}/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnit.Xml.TestLogger.AcceptanceTests.csproj"
53 | ],
54 | "problemMatcher": "$msCompile",
55 | "group": {
56 | "kind": "test",
57 | "isDefault": true
58 | }
59 | }
60 | ]
61 | }
--------------------------------------------------------------------------------
/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnitTestLoggerResultDirectoryAcceptanceTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace JUnit.Xml.TestLogger.AcceptanceTests
5 | {
6 | using System;
7 | using System.IO;
8 | using Microsoft.VisualStudio.TestTools.UnitTesting;
9 |
10 | ///
11 | /// Acceptance tests evaluate the most recent output of the build.ps1 script, NOT the most
12 | /// recent build performed by visual studio or dotnet.build
13 | ///
14 | /// These acceptance tests look at the directory name argument.
15 | ///
16 | [TestClass]
17 | public class JUnitTestLoggerResultDirectoryAcceptanceTests
18 | {
19 | [ClassInitialize]
20 | public static void SuiteInitialize(TestContext context)
21 | {
22 | DotnetTestFixture.RootDirectory = Path.GetFullPath(
23 | Path.Combine(
24 | Environment.CurrentDirectory,
25 | "..",
26 | "..",
27 | "..",
28 | "..",
29 | "assets",
30 | "JUnit.Xml.TestLogger.NetCore.Tests"));
31 | DotnetTestFixture.TestAssemblyName = "JUnit.Xml.TestLogger.NetCore.Tests.dll";
32 | var testResultsPath = Path.Combine(DotnetTestFixture.RootDirectory, "artifacts");
33 | DotnetTestFixture.Execute("test-results.xml", testResultsPath);
34 | }
35 |
36 | [TestMethod]
37 | public void TestRunWithResultDirectoryAndFileNameShouldCreateResultsFile()
38 | {
39 | var expectedResultsPath = Path.Combine(DotnetTestFixture.RootDirectory, "artifacts", "test-results.xml");
40 | Assert.IsTrue(File.Exists(expectedResultsPath), $"Results file at '{expectedResultsPath}' not found.");
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | name: .NET
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | build:
11 | strategy:
12 | matrix:
13 | os: [ubuntu-latest, windows-latest]
14 | runs-on: ${{ matrix.os }}
15 | env:
16 | APP_BUILD_VERSION: ${{ format('3.1.{0}', github.run_number) }}
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Setup .NET 7.0
20 | uses: actions/setup-dotnet@v1
21 | with:
22 | dotnet-version: 7.0.x
23 | - name: Setup .NET 3.1.x
24 | uses: actions/setup-dotnet@v1
25 | with:
26 | dotnet-version: "3.1.x"
27 | - name: Package (debug)
28 | run: dotnet pack -p:PackageVersion=${{ env.APP_BUILD_VERSION }}
29 | - name: Package (release)
30 | run: dotnet pack -c Release -p:PackageVersion=${{ env.APP_BUILD_VERSION }}
31 | - name: Unit test
32 | run: dotnet test -p:PackageVersion=${{ env.APP_BUILD_VERSION }} test/JUnit.Xml.TestLogger.UnitTests/JUnit.Xml.TestLogger.UnitTests.csproj -p:CollectCoverage=true -p:CoverletOutputFormat=opencover
33 | - name: Acceptance test
34 | if: ${{ matrix.os == 'windows-latest' }}
35 | run: dotnet test -p:PackageVersion=${{ env.APP_BUILD_VERSION }} test/JUnit.Xml.TestLogger.AcceptanceTests/JUnit.Xml.TestLogger.AcceptanceTests.csproj
36 | - name: Codecov
37 | uses: codecov/codecov-action@v3.1.0
38 | with:
39 | files: test/JUnit.Xml.TestLogger.UnitTests/coverage.opencover.xml
40 | - name: Publish packages
41 | if: ${{ github.event_name == 'push' && matrix.os == 'windows-latest' }}
42 | run: |
43 | $packageFile = (Get-ChildItem src/package/bin/Release/*.nupkg).FullName
44 | dotnet nuget push "$packageFile" --api-key ${{ secrets.GITHUB_TOKEN }} --source https://nuget.pkg.github.com/spekt/index.json
45 | dotnet nuget push "$packageFile" --api-key ${{ secrets.SPEKT_MYGET_KEY }} --source https://www.myget.org/F/spekt/api/v3/index.json
46 |
--------------------------------------------------------------------------------
/docs/gitlab-recommendation.md:
--------------------------------------------------------------------------------
1 | # GitLab CI/CD Recommendation
2 |
3 | GitLab uses just a few pieces of the XML report to generate the displayed user interface. The formatting options available in this project can improve the usefulness of this UI.
4 |
5 | ## Default Display
6 |
7 | The summary view shows passing and failing tests by method name (including any parameters). GitLab generates its summary of New failing tests, existing failing tests, and newly passing tests based on this string.
8 |
9 | 
10 |
11 | The popup displayed when clicking on a test, shows only the body of the failure.
12 |
13 | 
14 |
15 | ## Improved Output With Formatting Options
16 |
17 | The test summary can be modified by setting the method format. The option below used 'MethodFormat=Class'. This can be particularly helpful when using test fixture data (i.e. there are parameters passed to the class) which you would like to display.
18 |
19 | 
20 |
21 | The popup is much more useful with the inclusion of the 'Expected X, Actual Y' data. This is added to the failure body using 'FailureBodyFormat=Verbose'
22 |
23 | 
24 |
25 | ## Example .gitlab-ci.yml
26 |
27 | Below is example .yml which implements the options shown above. Additionally, this example collects the output from all test projects into a single folder, and uploads their reports to gitlab.
28 |
29 | ``` yml
30 | Test:
31 | stage: Test
32 | script:
33 | - 'dotnet test --test-adapter-path:. --logger:"junit;LogFilePath=..\artifacts\{assembly}-test-result.xml;MethodFormat=Class;FailureBodyFormat=Verbose"'
34 | artifacts:
35 | when: always
36 | paths:
37 | - .\artifacts\*test-result.xml
38 | reports:
39 | junit:
40 | - .\artifacts\*test-result.xml
41 | ```
42 |
43 | ## Notes
44 |
45 | Screen shots and behavior are current as of GitLab Enterprise Edition 13.0.0-pre.
46 |
--------------------------------------------------------------------------------
/test/JUnit.Xml.TestLogger.AcceptanceTests/JunitXmlValidator.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace JUnit.Xml.TestLogger.AcceptanceTests
5 | {
6 | using System;
7 | using System.Collections.Generic;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 | using System.Xml;
13 | using System.Xml.Linq;
14 | using System.Xml.Schema;
15 |
16 | public class JunitXmlValidator
17 | {
18 | ///
19 | /// Field is provided only to simplify debugging test failures.
20 | ///
21 | private readonly List failures = new List();
22 |
23 | public bool IsValid(string xml)
24 | {
25 | var xmlReader = new StringReader(xml);
26 | var xsdReader = new StringReader(
27 | File.ReadAllText(
28 | Path.Combine("..", "..", "..", "..", "assets", "JUnit.xsd")));
29 |
30 | var schema = XmlSchema.Read(
31 | xsdReader,
32 | (sender, args) => { throw new XmlSchemaValidationException(args.Message, args.Exception); });
33 |
34 | var xmlReaderSettings = new XmlReaderSettings();
35 | xmlReaderSettings.Schemas.Add(schema);
36 | xmlReaderSettings.ValidationType = ValidationType.Schema;
37 |
38 | var veh = new ValidationEventHandler(this.XmlValidationEventHandler);
39 |
40 | xmlReaderSettings.ValidationEventHandler += veh;
41 | using (XmlReader reader = XmlReader.Create(xmlReader, xmlReaderSettings))
42 | {
43 | while (reader.Read())
44 | {
45 | }
46 | }
47 |
48 | xmlReaderSettings.ValidationEventHandler -= veh;
49 |
50 | return this.failures.Any() == false;
51 | }
52 |
53 | public bool IsValid(XDocument doc) => this.IsValid(doc.ToString());
54 |
55 | private void XmlValidationEventHandler(object sender, ValidationEventArgs e)
56 | {
57 | this.failures.Add(e.Exception);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnitTestLoggerPathTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace JUnit.Xml.TestLogger.AcceptanceTests
5 | {
6 | using System;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Xml.Linq;
10 | using System.Xml.XPath;
11 | using Microsoft.VisualStudio.TestTools.UnitTesting;
12 |
13 | ///
14 | /// Acceptance tests evaluate the most recent output of the build.ps1 script, NOT the most
15 | /// recent build performed by visual studio or dotnet.build
16 | ///
17 | /// These acceptance tests look at the path parameter and tokens.
18 | ///
19 | [TestClass]
20 | public class JUnitTestLoggerPathTests
21 | {
22 | public JUnitTestLoggerPathTests()
23 | {
24 | }
25 |
26 | [ClassInitialize]
27 | public static void SuiteInitialize(TestContext context)
28 | {
29 | DotnetTestFixture.RootDirectory = Path.GetFullPath(
30 | Path.Combine(
31 | Environment.CurrentDirectory,
32 | "..",
33 | "..",
34 | "..",
35 | "..",
36 | "assets",
37 | "JUnit.Xml.TestLogger.NetMulti.Tests"));
38 | DotnetTestFixture.TestAssemblyName = "JUnit.Xml.TestLogger.NetMulti.Tests.dll";
39 | DotnetTestFixture.Execute("{assembly}.{framework}.test-results.xml");
40 | }
41 |
42 | [TestMethod]
43 | public void TestRunWithLoggerAndFilePathShouldCreateResultsFile()
44 | {
45 | string[] expectedResultsFiles = new string[]
46 | {
47 | Path.Combine(DotnetTestFixture.RootDirectory, "JUnit.Xml.TestLogger.NetMulti.Tests.NETFramework46.test-results.xml"),
48 | Path.Combine(DotnetTestFixture.RootDirectory, "JUnit.Xml.TestLogger.NetMulti.Tests.NETCoreApp31.test-results.xml")
49 | };
50 | foreach (string resultsFile in expectedResultsFiles)
51 | {
52 | Assert.IsTrue(File.Exists(resultsFile), $"{resultsFile} does not exist.");
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnit.Xml.TestLogger.AcceptanceTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ..\..\
5 | true
6 |
7 |
8 |
9 |
10 | netcoreapp3.1
11 | true
12 | true
13 | false
14 |
15 |
16 |
17 |
18 | $(MSBuildThisFileDirectory)../assets/NuGet.$(Configuration).config
19 | $(MSBuildThisFileDirectory)../assets/JUnit.Xml.TestLogger.NetFull.Tests/JUnit.Xml.TestLogger.NetFull.Tests.csproj
20 | $(MSBuildThisFileDirectory)../assets/JUnit.Xml.TestLogger.NetCore.Tests/JUnit.Xml.TestLogger.NetCore.Tests.csproj
21 | $(MSBuildThisFileDirectory)../assets/JUnit.Xml.TestLogger.NetMulti.Tests/JUnit.Xml.TestLogger.NetMulti.Tests.csproj
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ############################################################################
2 | # For ignoring files that appear under a specific folder, you should
3 | # usually add a .gitignore file to that specific folder.
4 | # This file is reserved for common file patterns that should be
5 | # ignored everywhere.
6 | ############################################################################
7 |
8 | ## Ignore Visual Studio temporary files, build results, and
9 | ## files generated by popular Visual Studio add-ons.
10 |
11 | # User-specific files
12 | *.suo
13 | *.user
14 | *.sln.docstates
15 | *.sln.metaproj
16 | *.sln.metaproj.tmp
17 | .vs/
18 | *.cache
19 |
20 | # Build results
21 | [Bb]in/
22 | [Oo]bj/
23 | !**/LocProjects/[Bb]in/
24 |
25 | *.tlog
26 | msbuild*.log
27 | msbuild*.wrn
28 | msbuild*.err
29 | build*.log
30 | build*.wrn
31 | build*.err
32 | *loggerFile.xml
33 |
34 | # MSTest test Results
35 | [Tt]est[Rr]esult*/
36 | [Bb]uild[Ll]og.*
37 |
38 | # Visual C++ cache files
39 | ipch/
40 | *.aps
41 | *.ncb
42 | *.opensdf
43 | *.sdf
44 | *.cachefile
45 |
46 | # Visual Studio profiler
47 | *.psess
48 | *.vsp
49 | *.vspx
50 |
51 | # ReSharper is a .NET coding add-in
52 | _ReSharper*/
53 | *.[Rr]e[Ss]harper
54 |
55 | # DotCover is a Code Coverage Tool
56 | *.dotCover
57 |
58 | # NCrunch
59 | *.ncrunch*
60 | .*crunch*.local.xml
61 |
62 | # DocProject is a documentation generator add-in
63 | DocProject/buildhelp/
64 | DocProject/Help/*.HxT
65 | DocProject/Help/*.HxC
66 | DocProject/Help/*.hhc
67 | DocProject/Help/*.hhk
68 | DocProject/Help/*.hhp
69 | DocProject/Help/Html2
70 | DocProject/Help/html
71 |
72 | # Backup & report files from converting an old project file to a newer
73 | # Visual Studio version. Backup files are not needed, because we have git ;-)
74 | _UpgradeReport_Files/
75 | Backup*/
76 | UpgradeLog*.XML
77 | UpgradeLog*.htm
78 |
79 | #GlobalAssemblyInfo produced for Microbuild
80 | GlobalAssemblyInfo.cs
81 |
82 | #lock files for UWP projects
83 | project.lock.json
84 | project.fragment.lock.json
85 |
86 | # =========================
87 | # Windows detritus
88 | # =========================
89 |
90 | # Windows image file caches
91 | Thumbs.db
92 | ehthumbs.db
93 |
94 |
95 | # ===========================
96 | # Custom ignores
97 | # ===========================
98 | packages
99 | nugetPackage
100 | [tT]ools
101 |
102 | # Rider
103 | .idea
104 |
105 | # Test results
106 | test/**/*test-results.xml
107 | CodeMaid.config
108 |
--------------------------------------------------------------------------------
/test/JUnit.Xml.TestLogger.UnitTests/JUnitXmlTestSerializerTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace JUnit.Xml.TestLogger.UnitTests
5 | {
6 | using System;
7 | using System.Diagnostics;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Xml.Linq;
11 | using System.Xml.XPath;
12 | using Microsoft.VisualStudio.TestPlatform.Extension.Junit.Xml.TestLogger;
13 | using Microsoft.VisualStudio.TestTools.UnitTesting;
14 | using TestSuite = Microsoft.VisualStudio.TestPlatform.Extension.Junit.Xml.TestLogger.JunitXmlSerializer.TestSuite;
15 |
16 | [TestClass]
17 | public class JUnitXmlTestSerializerTests
18 | {
19 | private const string DummyTestResultsDirectory = "/tmp/testresults";
20 |
21 | [TestMethod]
22 | public void InitializeShouldThrowIfEventsIsNull()
23 | {
24 | Assert.ThrowsException(() => new JUnitXmlTestLogger().Initialize(null, DummyTestResultsDirectory));
25 | }
26 |
27 | [TestMethod]
28 | public void CreateTestSuiteShouldReturnEmptyGroupsIfTestSuitesAreExclusive()
29 | {
30 | var suite1 = CreateTestSuite("a.b");
31 | var suite2 = CreateTestSuite("c.d");
32 |
33 | var result = JunitXmlSerializer.GroupTestSuites(new[] { suite1, suite2 }).ToArray();
34 |
35 | Assert.AreEqual(2, result.Length);
36 | Assert.AreEqual("a", result[0].Name);
37 | Assert.AreEqual("c", result[1].Name);
38 | }
39 |
40 | [TestMethod]
41 | public void CreateTestSuiteShouldGroupTestSuitesByName()
42 | {
43 | var suites = new[] { CreateTestSuite("a.b.c"), CreateTestSuite("a.b.e"), CreateTestSuite("c.d") };
44 | var expectedXmlForA = @"";
45 | var expectedXmlForC = @"";
46 |
47 | var result = JunitXmlSerializer.GroupTestSuites(suites).ToArray();
48 |
49 | Assert.AreEqual(2, result.Length);
50 | Assert.AreEqual("c", result[0].Name);
51 | Assert.AreEqual(expectedXmlForC, result[0].Element.ToString(SaveOptions.DisableFormatting));
52 | Assert.AreEqual("a", result[1].Name);
53 | Assert.AreEqual(expectedXmlForA, result[1].Element.ToString(SaveOptions.DisableFormatting));
54 | }
55 |
56 | private static TestSuite CreateTestSuite(string name)
57 | {
58 | return new TestSuite
59 | {
60 | Element = new XElement("test-suite"),
61 | Name = "n",
62 | FullName = name,
63 | Total = 5,
64 | Passed = 1,
65 | Failed = 1,
66 | Inconclusive = 1,
67 | Skipped = 1,
68 | Error = 1
69 | };
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/package/package.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ..\..\
5 | packageIcon.png
6 |
7 |
8 |
9 |
10 | netstandard1.5
11 | $(PackageVersion)
12 | JUnitXml.TestLogger
13 |
14 |
15 | false
16 | false
17 | false
18 | false
19 | false
20 | false
21 | false
22 | false
23 | false
24 |
25 |
26 | false
27 | false
28 | false
29 | false
30 | true
31 |
32 |
33 | $(NoWarn);2008;NU5127
34 |
35 |
36 | bin\$(Configuration)\$(TargetFramework)\JUnitXml.TestLogger.nuspec
37 | version=$(Version)
38 | false
39 | false
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | PreserveNewest
54 |
55 |
56 | PreserveNewest
57 |
58 |
59 |
60 |
61 |
62 | Always
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/scripts/settings.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildThisFileDirectory)../../
5 | 3.0.0
6 |
7 |
8 |
10 | $(SourcePrefix)-dev
11 | $(SourcePrefix)-dev
12 | true
13 | false
14 | false
15 | false
16 |
17 |
24 | true
25 | $(NoWarn),1573,1591,1712
26 |
27 |
28 | true
29 |
30 |
31 |
32 | $(DefineConstants);RELEASE
33 |
34 |
35 |
36 | $(DefineConstants);CODE_ANALYSIS
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | stylecop.json
45 |
46 |
47 |
48 | $(StylecopVersion)
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | $(MSTestVersion)
58 |
59 |
60 | $(MSTestVersion)
61 |
62 |
63 | $(MoqVersion)
64 |
65 |
66 | $(NETTestSdkVersion)
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | $(SourceRoot)scripts/stylecop.ruleset
75 | $(SourceRoot)scripts/stylecop.test.ruleset
76 |
77 |
78 |
--------------------------------------------------------------------------------
/test/assets/JUnit.Xml.TestLogger.NetCore.Tests/UnitTest2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using NUnit.Framework;
4 |
5 | namespace JUnit.Xml.TestLogger.Tests2
6 | {
7 | [TestFixture]
8 | public class UnitTest1
9 | {
10 | [Test]
11 | [Description("Passing test description")]
12 | public async Task PassTest11()
13 | {
14 | await Task.Delay(TimeSpan.FromMilliseconds(400));
15 | }
16 |
17 | [Test]
18 | public void FailTest11()
19 | {
20 | Assert.False(true);
21 | }
22 |
23 | [Test]
24 | public void Inconclusive()
25 | {
26 | Assert.Inconclusive("test inconclusive");
27 | }
28 |
29 | [Test]
30 | [Ignore("ignore reason")]
31 | public void Ignored()
32 | {
33 | }
34 | }
35 |
36 | public class UnitTest2
37 | {
38 | [Test]
39 | [Category("passing category")]
40 | public void PassTest21()
41 | {
42 | Assert.That(2, Is.EqualTo(2));
43 | }
44 |
45 | [Test]
46 | [Category("failing category")]
47 | public void FailTest22()
48 | {
49 | Assert.False(true);
50 | }
51 |
52 | [Test]
53 | public void Inconclusive()
54 | {
55 | Assert.Inconclusive();
56 | }
57 |
58 | [Test]
59 | [Ignore("ignore reason")]
60 | public void IgnoredTest()
61 | {
62 | }
63 |
64 | [Test]
65 | public void WarningTest()
66 | {
67 | Assert.Warn("Warning");
68 | }
69 |
70 | [Test]
71 | [Explicit]
72 | public void ExplicitTest()
73 | {
74 | }
75 | }
76 |
77 | [TestFixture]
78 | public class SuccessFixture
79 | {
80 | [Test]
81 | public void SuccessTest()
82 | {
83 | }
84 | }
85 |
86 | [TestFixture]
87 | public class SuccessAndInconclusiveFixture
88 | {
89 | [Test]
90 | public void SuccessTest()
91 | {
92 | }
93 |
94 | [Test]
95 | public void InconclusiveTest()
96 | {
97 | Assert.Inconclusive();
98 | }
99 | }
100 |
101 | [TestFixture]
102 | public class FailingOneTimeSetUp
103 | {
104 | [OneTimeSetUp]
105 | public void OneTimeSetUp()
106 | {
107 | throw new InvalidOperationException();
108 | }
109 |
110 | [Test]
111 | public void TestA()
112 | {
113 | }
114 | }
115 |
116 | [TestFixture]
117 | public class FailingTestSetup
118 | {
119 | [SetUp]
120 | public void SetUp()
121 | {
122 | throw new InvalidOperationException();
123 | }
124 |
125 | [Test]
126 | public void TestB()
127 | {
128 | }
129 | }
130 |
131 | [TestFixture]
132 | public class FailingTearDown
133 | {
134 | [TearDown]
135 | public void TearDown()
136 | {
137 | throw new InvalidOperationException();
138 | }
139 |
140 | [Test]
141 | public void TestC()
142 | {
143 | }
144 | }
145 |
146 | [TestFixture]
147 | public class FailingOneTimeTearDown
148 | {
149 | [OneTimeTearDown]
150 | public void OneTimeTearDown()
151 | {
152 | throw new InvalidOperationException();
153 | }
154 |
155 | [Test]
156 | public void TestD()
157 | {
158 | }
159 | }
160 |
161 | [TestFixture]
162 | [TestFixtureSource("FixtureArgs")]
163 | public class ParametrizedFixture
164 | {
165 | public ParametrizedFixture(string word, int num)
166 | {
167 | }
168 |
169 | [Test]
170 | public void TestE()
171 | {
172 | }
173 |
174 | static object[] FixtureArgs =
175 | {
176 | new object[] {"Question", 1},
177 | new object[] {"Answer", 42}
178 | };
179 | }
180 |
181 | [TestFixture]
182 | public class ParametrizedTestCases
183 | {
184 | [Test]
185 | public void TestData([Values(1, 2)] int x, [Values("A", "B")] string s)
186 | {
187 | Assert.That(x, Is.Not.EqualTo(2), "failing for second case");
188 | Assert.That(s, Is.Not.Null);
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/test/JUnit.Xml.TestLogger.AcceptanceTests/DotnetTestFixture.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace JUnit.Xml.TestLogger.AcceptanceTests
5 | {
6 | using System;
7 | using System.Diagnostics;
8 | using System.IO;
9 |
10 | public class DotnetTestFixture
11 | {
12 | private const string DotnetVersion = "netcoreapp3.1";
13 |
14 | public static string RootDirectory { get; set; } = Path.GetFullPath(
15 | Path.Combine(
16 | Environment.CurrentDirectory,
17 | "..",
18 | "..",
19 | "..",
20 | "..",
21 | "assets",
22 | "JUnit.Xml.TestLogger.NetCore.Tests"));
23 |
24 | public static string TestAssemblyName { get; set; } = "JUnit.Xml.TestLogger.NetCore.Tests.dll";
25 |
26 | public static string TestAssembly
27 | {
28 | get
29 | {
30 | #if DEBUG
31 | var config = "Debug";
32 | #else
33 | var config = "Release";
34 | #endif
35 | return Path.Combine(RootDirectory, "bin", config, DotnetVersion, TestAssemblyName);
36 | }
37 | }
38 |
39 | public static void Execute(string resultsFile)
40 | {
41 | var testProject = RootDirectory;
42 | var testLogger = $"--logger:\"junit;LogFilePath={resultsFile}\"";
43 |
44 | // Delete stale results file
45 | var testLogFile = Path.Combine(testProject, resultsFile);
46 |
47 | // Strip out tokens
48 | var sanitizedResultFile = System.Text.RegularExpressions.Regex.Replace(resultsFile, @"{.*}\.*", string.Empty);
49 | foreach (string fileName in Directory.GetFiles(testProject))
50 | {
51 | if (fileName.Contains("test-results.xml"))
52 | {
53 | File.Delete(fileName);
54 | }
55 | }
56 |
57 | // Log the contents of test output directory. Useful to verify if the logger is copied
58 | Console.WriteLine("------------");
59 | Console.WriteLine("Contents of test output directory:");
60 | foreach (var f in Directory.GetFiles(Path.Combine(testProject, $"bin/Debug/{DotnetVersion}")))
61 | {
62 | Console.WriteLine(" " + f);
63 | }
64 |
65 | Console.WriteLine();
66 |
67 | // Run dotnet test with logger
68 | using (var p = new Process())
69 | {
70 | p.StartInfo.UseShellExecute = false;
71 | p.StartInfo.RedirectStandardOutput = true;
72 | p.StartInfo.FileName = "dotnet";
73 | p.StartInfo.Arguments = $"test --no-build {testLogger} {testProject}";
74 | p.Start();
75 |
76 | Console.WriteLine("dotnet arguments: " + p.StartInfo.Arguments);
77 |
78 | // To avoid deadlocks, always read the output stream first and then wait.
79 | string output = p.StandardOutput.ReadToEnd();
80 | p.WaitForExit();
81 | Console.WriteLine("dotnet output: " + output);
82 | Console.WriteLine("------------");
83 | }
84 | }
85 |
86 | public static void Execute(string resultsFileName, string filePath)
87 | {
88 | var testProject = RootDirectory;
89 | var testLogger = $"--logger \"junit;LogFileName={resultsFileName}\" --results-directory \"{filePath}\"";
90 |
91 | // Log the contents of test output directory. Useful to verify if the logger is copied
92 | Console.WriteLine("------------");
93 | Console.WriteLine("Contents of test output directory:");
94 | foreach (var f in Directory.GetFiles(Path.Combine(testProject, $"bin/Debug/{DotnetVersion}")))
95 | {
96 | Console.WriteLine(" " + f);
97 | }
98 |
99 | Console.WriteLine();
100 |
101 | // Run dotnet test with logger
102 | using (var p = new Process())
103 | {
104 | p.StartInfo.UseShellExecute = false;
105 | p.StartInfo.RedirectStandardOutput = true;
106 | p.StartInfo.FileName = "dotnet";
107 | p.StartInfo.Arguments = $"test --no-build {testLogger} {testProject}";
108 | p.Start();
109 |
110 | Console.WriteLine("dotnet arguments: " + p.StartInfo.Arguments);
111 |
112 | // To avoid deadlocks, always read the output stream first and then wait.
113 | string output = p.StandardOutput.ReadToEnd();
114 | p.WaitForExit();
115 | Console.WriteLine("dotnet output: " + output);
116 | Console.WriteLine("------------");
117 | }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > [!IMPORTANT]
2 | > Development of v4.x and subsequent versions of the Junit logger is moved to the [testlogger](https://github.com/spekt/testlogger) repository.
3 | > Kindly report any new issues or contribute your patches in that repo.
4 |
5 | # JUnit Test Logger
6 |
7 | JUnit xml report extension for [Visual Studio Test Platform](https://github.com/microsoft/vstest).
8 |
9 | [](https://travis-ci.com/spekt/junit.testlogger)
10 | [](https://ci.appveyor.com/project/spekt/junit-testlogger)
11 | [](https://www.nuget.org/packages/JunitXml.TestLogger/)
12 |
13 | ## Packages
14 |
15 | | Logger | Stable Package | Pre-release Package |
16 | | ------ | -------------- | ------------------- |
17 | | JUnit | [](https://www.nuget.org/packages/JUnitXml.TestLogger/) | [](https://www.myget.org/feed/spekt/package/nuget/JunitXml.TestLogger) |
18 |
19 | If you're looking for `Nunit`, `Xunit` or `appveyor` loggers, visit following repositories:
20 |
21 | -
22 | -
23 | -
24 |
25 | ## Usage
26 |
27 | The JUnit Test Logger generates xml reports in the [Ant Junit Format](https://github.com/windyroad/JUnit-Schema), which the JUnit 5 repository refers to as the de-facto standard. While the generated xml complies with that schema, it does not contain values in every case. For example, the logger currently does not log any `properties`. Please [refer to a sample file](docs/assets/TestResults.xml) to see an example. If you find that the format is missing data required by your CI/CD system, please open an issue or PR.
28 |
29 | To use the logger, follow these steps:
30 |
31 | 1. Add a reference to the [JUnit Logger](https://www.nuget.org/packages/JUnitXml.TestLogger) nuget package in test project
32 | 2. Use the following command line in tests
33 |
34 | ```none
35 | > dotnet test --logger:junit
36 | ```
37 |
38 | 3. Test results are generated in the `TestResults` directory relative to the `test.csproj`
39 |
40 | A path for the report file can be specified as follows:
41 |
42 | ```none
43 | > dotnet test --logger:"junit;LogFilePath=test-result.xml"
44 | ```
45 |
46 | `test-result.xml` will be generated in the same directory as `test.csproj`.
47 |
48 | **Note:** the arguments to `--logger` should be in quotes since `;` is treated as a command delimiter in shell.
49 |
50 | All common options to the logger are documented [in the wiki][config-wiki]. E.g.
51 | token expansion for `{assembly}` or `{framework}` in result file. If you are writing multiple
52 | files to the same directory or testing multiple frameworks, these options can prevent
53 | test logs from over-writing each other.
54 |
55 | [config-wiki]: https://github.com/spekt/testlogger/wiki/Logger-Configuration
56 |
57 | ### Customizing Junit XML Contents
58 |
59 | There are several options to customize how the junit xml is populated. These options exist to
60 | provide additional control over the xml file so that the logged test results can be optimized for different CI/CD systems.
61 |
62 | Platform Specific Recommendations:
63 |
64 | - [GitLab CI/CD Recommendation](/docs/gitlab-recommendation.md)
65 | - [Jenkins Recommendation](/docs/jenkins-recommendation.md)
66 | - [CircleCI Recommendation](/docs/circleci-recommendation.md)
67 |
68 | After the logger name, command line arguments are provided as key/value pairs with the following general format. **Note** the quotes are required and key names are case sensitive.
69 |
70 | ```none
71 | > dotnet test --test-adapter-path:. --logger:"junit;key1=value1;key2=value2"
72 | ```
73 |
74 | #### MethodFormat
75 |
76 | This option alters the `testcase name` attribute. By default, this contains only the method. Class, will add the class to the name. Full, will add the assembly/namespace/class to the method.
77 |
78 | We recommend this option for [GitLab](/docs/gitlab-recommendation.md) users.
79 |
80 | ##### Allowed Values
81 |
82 | - MethodFormat=Default
83 | - MethodFormat=Class
84 | - MethodFormat=Full
85 |
86 | #### FailureBodyFormat
87 |
88 | When set to default, the body of a `failure` element will contain only the exception which is captured by vstest. Verbose will prepend the body with 'Expected X, Actual Y' similar to how it is displayed in the standard test output. 'Expected X, Actual Y' are normally only contained in the failure message. Additionally, Verbose will include standard output from the test in the failure message.
89 |
90 | We recommend this option for [GitLab](/docs/gitlab-recommendation.md) and [CircleCI](/docs/circleci-recommendation.md) users.
91 |
92 | ##### Allowed Values
93 |
94 | - FailureBodyFormat=Default
95 | - FailureBodyFormat=Verbose
96 |
97 | ## License
98 |
99 | MIT
100 |
--------------------------------------------------------------------------------
/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnitTestLoggerAcceptanceTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace JUnit.Xml.TestLogger.AcceptanceTests
5 | {
6 | using System;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Xml.Linq;
10 | using System.Xml.XPath;
11 | using Microsoft.VisualStudio.TestTools.UnitTesting;
12 |
13 | ///
14 | /// Acceptance tests evaluate the most recent output of the build.ps1 script, NOT the most
15 | /// recent build performed by visual studio or dotnet.build
16 | ///
17 | /// These acceptance tests look at the specific structure and contents of the produced Xml.
18 | ///
19 | [TestClass]
20 | public class JUnitTestLoggerAcceptanceTests
21 | {
22 | private readonly string resultsFile;
23 | private readonly XDocument resultsXml;
24 |
25 | public JUnitTestLoggerAcceptanceTests()
26 | {
27 | this.resultsFile = Path.Combine(DotnetTestFixture.RootDirectory, "test-results.xml");
28 | this.resultsXml = XDocument.Load(this.resultsFile);
29 | }
30 |
31 | [ClassInitialize]
32 | public static void SuiteInitialize(TestContext context)
33 | {
34 | DotnetTestFixture.Execute("test-results.xml");
35 | }
36 |
37 | [TestMethod]
38 | public void TestRunWithLoggerAndFilePathShouldCreateResultsFile()
39 | {
40 | Assert.IsTrue(File.Exists(this.resultsFile));
41 | }
42 |
43 | [TestMethod]
44 | public void TestResultFileShouldContainTestSuitesInformation()
45 | {
46 | var node = this.resultsXml.XPathSelectElement("/testsuites");
47 |
48 | Assert.IsNotNull(node);
49 | }
50 |
51 | [TestMethod]
52 | public void TestResultFileShouldContainTestSuiteInformation()
53 | {
54 | var node = this.resultsXml.XPathSelectElement("/testsuites/testsuite");
55 |
56 | Assert.IsNotNull(node);
57 | Assert.AreEqual("JUnit.Xml.TestLogger.NetCore.Tests.dll", node.Attribute(XName.Get("name")).Value);
58 | Assert.AreEqual(Environment.MachineName, node.Attribute(XName.Get("hostname")).Value);
59 |
60 | Assert.AreEqual("52", node.Attribute(XName.Get("tests")).Value);
61 | Assert.AreEqual("14", node.Attribute(XName.Get("failures")).Value);
62 | Assert.AreEqual("8", node.Attribute(XName.Get("skipped")).Value);
63 |
64 | // Errors is zero becasue we don't get errors as a test outcome from .net
65 | Assert.AreEqual("0", node.Attribute(XName.Get("errors")).Value);
66 |
67 | Convert.ToDouble(node.Attribute(XName.Get("time")).Value);
68 | Convert.ToDateTime(node.Attribute(XName.Get("timestamp")).Value);
69 | }
70 |
71 | [TestMethod]
72 | public void TestResultFileShouldContainTestCases()
73 | {
74 | var node = this.resultsXml.XPathSelectElements("/testsuites/testsuite").Descendants();
75 | var testcases = node.Where(x => x.Name.LocalName == "testcase").ToList();
76 |
77 | // Check all test cases
78 | Assert.IsNotNull(node);
79 | Assert.AreEqual(52, testcases.Count());
80 | Assert.IsTrue(testcases.All(x => double.TryParse(x.Attribute("time").Value, out _)));
81 |
82 | // Check failures
83 | var failures = testcases
84 | .Where(x => x.Descendants().Any(y => y.Name.LocalName == "failure"))
85 | .ToList();
86 |
87 | Assert.AreEqual(14, failures.Count());
88 | Assert.IsTrue(failures.All(x => x.Descendants().Count() == 1));
89 | Assert.IsTrue(failures.All(x => x.Descendants().First().Attribute("type").Value == "failure"));
90 |
91 | // Check failures
92 | var skips = testcases
93 | .Where(x => x.Descendants().Any(y => y.Name.LocalName == "skipped"))
94 | .ToList();
95 |
96 | Assert.AreEqual(8, skips.Count());
97 | }
98 |
99 | [TestMethod]
100 | public void TestResultFileShouldContainStandardOut()
101 | {
102 | var node = this.resultsXml.XPathSelectElement("/testsuites/testsuite/system-out");
103 |
104 | Assert.IsTrue(node.Value.Contains("{2010CAE3-7BC0-4841-A5A3-7D5F947BB9FB}"));
105 | Assert.IsTrue(node.Value.Contains("{998AC9EC-7429-42CD-AD55-72037E7AF3D8}"));
106 | Assert.IsTrue(node.Value.Contains("{EEEE1DA6-6296-4486-BDA5-A50A19672F0F}"));
107 | Assert.IsTrue(node.Value.Contains("{C33FF4B5-75E1-4882-B968-DF9608BFE7C2}"));
108 | }
109 |
110 | [TestMethod]
111 | public void TestResultFileShouldContainErrordOut()
112 | {
113 | var node = this.resultsXml.XPathSelectElement("/testsuites/testsuite/system-err");
114 |
115 | Assert.IsTrue(node.Value.Contains("{D46DFA10-EEDD-49E5-804D-FE43051331A7}"));
116 | Assert.IsTrue(node.Value.Contains("{33F5FD22-6F40-499D-98E4-481D87FAEAA1}"));
117 | }
118 |
119 | [TestMethod]
120 | public void LoggedXmlValidatesAgainstXsdSchema()
121 | {
122 | var validator = new JunitXmlValidator();
123 | var result = validator.IsValid(File.ReadAllText(this.resultsFile));
124 | Assert.IsTrue(result);
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/docs/circleci-recommendation.md:
--------------------------------------------------------------------------------
1 | # CircleCI Recommendation
2 |
3 | ## Summary
4 |
5 | ```yml
6 | - run:
7 | name: Run Tests
8 | command: |
9 | dotnet test ... --logger:"junit;LogFilePath=TestResults/test-result.xml;FailureBodyFormat=Verbose;MethodFormat=Class"
10 | - store_test_results:
11 | path: TestResults/
12 | - store_artifacts:
13 | path: TestResults/
14 | ```
15 |
16 | See also: [this sample repository][circleci-windows-project-example].
17 |
18 | ## Details
19 |
20 | CircleCI uses just a few pieces of the JUnit XML report to generate the displayed user interface.
21 | A basic example `junit.xml` file which can be parsed by CircleCI can be seen
22 | [here][circleci-junit-xml-report].
23 |
24 | For each failed test (i.e. for each JUnit `` element containing a failure or an error),
25 | CircleCI only shows the testcase's failure/error message,
26 | any text within the failure/error element,
27 | plus any `` or `` text.
28 | It does not show the testcase's properties.
29 | It does not show data from the testsuite.
30 |
31 | By default, the logger records test console output in the JUnit testsuite,
32 | which CircleCI ignores;
33 | this can be improved upon by setting `FailureBodyFormat` when invoking the logger.
34 | Setting MethodFormat provides no additional information or functionality.
35 |
36 | ### FailureBodyFormat
37 |
38 | Setting `FailureBodyFormat=Verbose` when invoking the logger ensures that
39 | any test console output is visible in the CircleCI UI for (failed) tests.
40 | This can provide important/useful information,
41 | especially if the test failure message is insufficiently informative on its own.
42 |
43 | - `Default`:
44 |
45 | 
46 |
47 | - `Verbose`:
48 |
49 | 
50 |
51 | ### MethodFormat
52 |
53 | CircleCI shows the class names of each failed test so setting
54 | `MethodFormat=Class` or `MethodFormat=Full`
55 | provides no further information on the CircleCI UI;
56 | the effect is purely cosmetic and is a matter of personal preference.
57 |
58 | - `Default`:
59 |
60 | 
61 |
62 | - `Class` and `Full`:
63 |
64 | 
65 |
66 | ### Store Test Results
67 |
68 | To enable CircleCI to show test results on the job's page,
69 | to interpret generated test result files to be used for gathering
70 | [Test Insights][circleci-test-insights]
71 | and
72 | [Test Splitting][circleci-test-splitting],
73 | the files must be stored using the
74 | [`store_test_results` step][circleci-store-test-results-step].
75 |
76 | ```yml
77 | - store_test_results:
78 | path: TestResults/
79 | ```
80 |
81 | ### Store Artifacts
82 |
83 | To enable further troubleshooting,
84 | the test results file should _also_ be stored as an artifact using the
85 | [`store_artifacts` step][circleci-store-artifacts-step].
86 | This allows the user to view and download the contents of the raw `*.xml` file,
87 | which is useful where the user requires information from the file that CircleCI does not display.
88 |
89 | ```yml
90 | - store_artifacts:
91 | path: TestResults/
92 | ```
93 |
94 | ### Test Splitting
95 |
96 | CircleCI can enable a suite a tests to be split across multiple executors in parallel by using (timing) data logged by this junit logger.
97 |
98 | The simplified summary (above) does not include this functionality but this is recommended for non-trivial test workloads,
99 | especially where the test execution time significantly exceeds the time taken to set up ready for the first test.
100 | This technique reduces the total (elapsed-time) duration of the tests and thus provides a faster test feedback loop.
101 |
102 | For an example of a CircleCI Windows project that demonstrates parallel test execution, take a look at
103 | [this sample repository][circleci-windows-project-example].
104 |
105 | -----
106 |
107 | ## Footnote
108 |
109 | As of July 20 2023:
110 |
111 | - Only the data in a `` element is interpreted by CircleCI.
112 | - e.g. `` from within a `` is shown.
113 | - Data outside of a `` element is not interpreted by CircleCI.
114 | - The `` from a `` aren't interpreted by CircleCI.
115 | - By default, the logger only puts console text (`` and ``) in elements at the `` level (not into ``) so CircleCI does not interpret it.
116 |
117 | e.g.
118 |
119 | ```xml
120 |
121 |
122 |
123 |
124 |
125 |
126 | So can this
127 | This is also shown by CircleCI
128 | Same for system-err too
129 |
130 | CircleCI ignores data here
131 | Same for system-err too
132 |
133 |
134 | ```
135 |
136 | [circleci-junit-xml-report]: https://circleci.com/docs/use-the-circleci-cli-to-split-tests/#junit-xml-reports
137 | [circleci-test-insights]: https://circleci.com/docs/insights-tests/
138 | [circleci-test-splitting]: https://circleci.com/docs/use-the-circleci-cli-to-split-tests/#split-by-timing-data
139 | [circleci-store-test-results-step]: https://circleci.com/docs/configuration-reference/#storetestresults
140 | [circleci-store-artifacts-step]: https://circleci.com/docs/configuration-reference/#storeartifacts
141 | [circleci-windows-project-example]: https://github.com/jenny-miggin/circleci-demo-windows-test-splitting
142 |
--------------------------------------------------------------------------------
/test/assets/JUnit.Xml.TestLogger.NetCore.Tests/UnitTest1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using NUnit.Framework;
4 |
5 | namespace JUnit.Xml.TestLogger.NetFull.Tests
6 | {
7 | [TestFixture]
8 | public class UnitTest1
9 | {
10 | [Test]
11 | [Description("Passing test description")]
12 | public async Task PassTest11()
13 | {
14 | Console.WriteLine("{2010CAE3-7BC0-4841-A5A3-7D5F947BB9FB}");
15 | Console.WriteLine("{998AC9EC-7429-42CD-AD55-72037E7AF3D8}");
16 | await Task.Delay(TimeSpan.FromMilliseconds(400));
17 | }
18 |
19 | [Test]
20 | public void FailTest11()
21 | {
22 | Console.WriteLine("{EEEE1DA6-6296-4486-BDA5-A50A19672F0F}");
23 | Console.WriteLine("{C33FF4B5-75E1-4882-B968-DF9608BFE7C2}");
24 | Console.Error.WriteLine("{D46DFA10-EEDD-49E5-804D-FE43051331A7}");
25 | Console.Error.WriteLine("{33F5FD22-6F40-499D-98E4-481D87FAEAA1}");
26 |
27 | Assert.False(true);
28 | }
29 |
30 | [Test]
31 | public void Inconclusive()
32 | {
33 | Assert.Inconclusive("test inconclusive");
34 | }
35 |
36 | [Test]
37 | [Ignore("ignore reason")]
38 | public void Ignored()
39 | {
40 | }
41 |
42 | [Test]
43 | [Property("Property name", "Property value")]
44 | public void WithProperty()
45 | {
46 | }
47 |
48 | [Test]
49 | public void NoProperty()
50 | {
51 | }
52 |
53 | [Test]
54 | [Category("Junit Test Category")]
55 | public void WithCategory()
56 | {
57 | }
58 |
59 | [Test]
60 | [Category("Category2")]
61 | [Category("Category1")]
62 | public void MultipleCategories()
63 | {
64 | }
65 |
66 | [Test]
67 | [Category("JUnit Test Category")]
68 | [Property("Property name", "Property value")]
69 | public void WithCategoryAndProperty()
70 | {
71 | }
72 |
73 | [Test]
74 | [Property("Property name", "Property value 1")]
75 | [Property("Property name", "Property value 2")]
76 | public void WithProperties()
77 | {
78 | }
79 | }
80 |
81 | public class UnitTest2
82 | {
83 | [Test]
84 | [Category("passing category")]
85 | public void PassTest21()
86 | {
87 | Assert.That(2, Is.EqualTo(2));
88 | }
89 |
90 | [Test]
91 | [Category("failing category")]
92 | public void FailTest22()
93 | {
94 | Assert.False(true);
95 | }
96 |
97 | [Test]
98 | public void Inconclusive()
99 | {
100 | Assert.Inconclusive();
101 | }
102 |
103 | [Test]
104 | [Ignore("ignore reason")]
105 | public void IgnoredTest()
106 | {
107 | }
108 |
109 | [Test]
110 | public void WarningTest()
111 | {
112 | Assert.Warn("Warning");
113 | }
114 |
115 | [Test]
116 | [Explicit]
117 | public void ExplicitTest()
118 | {
119 | }
120 | }
121 |
122 | [TestFixture]
123 | public class SuccessFixture
124 | {
125 | [Test]
126 | public void SuccessTest()
127 | {
128 | }
129 | }
130 |
131 | [TestFixture]
132 | public class SuccessAndInconclusiveFixture
133 | {
134 | [Test]
135 | public void SuccessTest()
136 | {
137 | }
138 |
139 | [Test]
140 | public void InconclusiveTest()
141 | {
142 | Assert.Inconclusive();
143 | }
144 | }
145 |
146 | [TestFixture]
147 | public class FailingOneTimeSetUp
148 | {
149 | [OneTimeSetUp]
150 | public void OneTimeSetUp()
151 | {
152 | throw new InvalidOperationException();
153 | }
154 |
155 | [Test]
156 | public void TestA()
157 | {
158 | }
159 | }
160 |
161 | [TestFixture]
162 | public class FailingTestSetup
163 | {
164 | [SetUp]
165 | public void SetUp()
166 | {
167 | throw new InvalidOperationException();
168 | }
169 |
170 | [Test]
171 | public void TestB()
172 | {
173 | }
174 | }
175 |
176 | [TestFixture]
177 | public class FailingTearDown
178 | {
179 | [TearDown]
180 | public void TearDown()
181 | {
182 | throw new InvalidOperationException();
183 | }
184 |
185 | [Test]
186 | public void TestC()
187 | {
188 | }
189 | }
190 |
191 | [TestFixture]
192 | public class FailingOneTimeTearDown
193 | {
194 | [OneTimeTearDown]
195 | public void OneTimeTearDown()
196 | {
197 | throw new InvalidOperationException();
198 | }
199 |
200 | [Test]
201 | public void TestD()
202 | {
203 | }
204 | }
205 |
206 | [TestFixture]
207 | [TestFixtureSource("FixtureArgs")]
208 | public class ParametrizedFixture
209 | {
210 | public ParametrizedFixture(string word, int num)
211 | {
212 | }
213 |
214 | [Test]
215 | public void TestE()
216 | {
217 | }
218 |
219 | static object[] FixtureArgs =
220 | {
221 | new object[] {"Question", 1},
222 | new object[] {"Answer", 42}
223 | };
224 | }
225 |
226 | [TestFixture]
227 | public class ParametrizedTestCases
228 | {
229 | [Test]
230 | public void TestData([Values(1, 2)] int x, [Values("A", "B")] string s)
231 | {
232 | Assert.That(x, Is.Not.EqualTo(2), "failing for second case");
233 | Assert.That(s, Is.Not.Null);
234 | }
235 | }
236 | }
--------------------------------------------------------------------------------
/junit.testlogger.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2010
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DB72843C-27A3-4646-BE18-164E33702212}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JUnit.Xml.TestLogger", "src\JUnit.Xml.TestLogger\JUnit.Xml.TestLogger.csproj", "{D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JUnit.Xml.TestLogger.TestAdapter", "src\JUnit.Xml.TestLogger.TestAdapter\JUnit.Xml.TestLogger.TestAdapter.csproj", "{72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "package", "src\package\package.csproj", "{B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D244BD05-A6C4-4E04-A392-A21091E4597F}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JUnit.Xml.TestLogger.AcceptanceTests", "test\JUnit.Xml.TestLogger.AcceptanceTests\JUnit.Xml.TestLogger.AcceptanceTests.csproj", "{A47EDBAD-CF86-4F2D-9CE6-02510954CE10}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JUnit.Xml.TestLogger.UnitTests", "test\JUnit.Xml.TestLogger.UnitTests\JUnit.Xml.TestLogger.UnitTests.csproj", "{FC0600B1-5E2E-4C19-AFD9-BF418A739E17}"
19 | EndProject
20 | Global
21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
22 | Debug|Any CPU = Debug|Any CPU
23 | Debug|x64 = Debug|x64
24 | Debug|x86 = Debug|x86
25 | Release|Any CPU = Release|Any CPU
26 | Release|x64 = Release|x64
27 | Release|x86 = Release|x86
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Debug|x64.ActiveCfg = Debug|Any CPU
33 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Debug|x64.Build.0 = Debug|Any CPU
34 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Debug|x86.ActiveCfg = Debug|Any CPU
35 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Debug|x86.Build.0 = Debug|Any CPU
36 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Release|x64.ActiveCfg = Release|Any CPU
39 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Release|x64.Build.0 = Release|Any CPU
40 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Release|x86.ActiveCfg = Release|Any CPU
41 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2}.Release|x86.Build.0 = Release|Any CPU
42 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Debug|x64.ActiveCfg = Debug|Any CPU
45 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Debug|x64.Build.0 = Debug|Any CPU
46 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Debug|x86.ActiveCfg = Debug|Any CPU
47 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Debug|x86.Build.0 = Debug|Any CPU
48 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Release|x64.ActiveCfg = Release|Any CPU
51 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Release|x64.Build.0 = Release|Any CPU
52 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Release|x86.ActiveCfg = Release|Any CPU
53 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D}.Release|x86.Build.0 = Release|Any CPU
54 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Debug|x64.ActiveCfg = Debug|Any CPU
57 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Debug|x64.Build.0 = Debug|Any CPU
58 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Debug|x86.ActiveCfg = Debug|Any CPU
59 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Debug|x86.Build.0 = Debug|Any CPU
60 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Release|Any CPU.Build.0 = Release|Any CPU
62 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Release|x64.ActiveCfg = Release|Any CPU
63 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Release|x64.Build.0 = Release|Any CPU
64 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Release|x86.ActiveCfg = Release|Any CPU
65 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3}.Release|x86.Build.0 = Release|Any CPU
66 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Debug|Any CPU.Build.0 = Debug|Any CPU
68 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Debug|x64.ActiveCfg = Debug|Any CPU
69 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Debug|x64.Build.0 = Debug|Any CPU
70 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Debug|x86.ActiveCfg = Debug|Any CPU
71 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Debug|x86.Build.0 = Debug|Any CPU
72 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Release|Any CPU.ActiveCfg = Release|Any CPU
73 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Release|Any CPU.Build.0 = Release|Any CPU
74 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Release|x64.ActiveCfg = Release|Any CPU
75 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Release|x64.Build.0 = Release|Any CPU
76 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Release|x86.ActiveCfg = Release|Any CPU
77 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10}.Release|x86.Build.0 = Release|Any CPU
78 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
79 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Debug|Any CPU.Build.0 = Debug|Any CPU
80 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Debug|x64.ActiveCfg = Debug|Any CPU
81 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Debug|x64.Build.0 = Debug|Any CPU
82 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Debug|x86.ActiveCfg = Debug|Any CPU
83 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Debug|x86.Build.0 = Debug|Any CPU
84 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Release|Any CPU.ActiveCfg = Release|Any CPU
85 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Release|Any CPU.Build.0 = Release|Any CPU
86 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Release|x64.ActiveCfg = Release|Any CPU
87 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Release|x64.Build.0 = Release|Any CPU
88 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Release|x86.ActiveCfg = Release|Any CPU
89 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17}.Release|x86.Build.0 = Release|Any CPU
90 | EndGlobalSection
91 | GlobalSection(SolutionProperties) = preSolution
92 | HideSolutionNode = FALSE
93 | EndGlobalSection
94 | GlobalSection(NestedProjects) = preSolution
95 | {D53B993F-BAA5-4DBA-AAE2-D44DC28D37B2} = {DB72843C-27A3-4646-BE18-164E33702212}
96 | {72D6CC14-E2CE-4271-8CEE-FBEF8E6EC31D} = {DB72843C-27A3-4646-BE18-164E33702212}
97 | {B07E01EB-F697-4D97-BBE2-C2C49B2EFBF3} = {DB72843C-27A3-4646-BE18-164E33702212}
98 | {A47EDBAD-CF86-4F2D-9CE6-02510954CE10} = {D244BD05-A6C4-4E04-A392-A21091E4597F}
99 | {FC0600B1-5E2E-4C19-AFD9-BF418A739E17} = {D244BD05-A6C4-4E04-A392-A21091E4597F}
100 | EndGlobalSection
101 | GlobalSection(ExtensibilityGlobals) = postSolution
102 | SolutionGuid = {3AD427EE-A607-4561-B08D-B30A09DE69BA}
103 | EndGlobalSection
104 | EndGlobal
105 |
--------------------------------------------------------------------------------
/test/JUnit.Xml.TestLogger.AcceptanceTests/JUnitTestLoggerFormatOptionsAcceptanceTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace JUnit.Xml.TestLogger.AcceptanceTests
5 | {
6 | using System;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Xml.Linq;
10 | using System.Xml.XPath;
11 | using Microsoft.VisualStudio.TestTools.UnitTesting;
12 | using Spekt.TestLogger.Core;
13 |
14 | ///
15 | /// Acceptance tests evaluate the most recent output of the build.ps1 script, NOT the most
16 | /// recent build performed by visual studio or dotnet.build
17 | ///
18 | /// These acceptance tests look at the specific places output is expected to change because of
19 | /// the format option specified. Accordingly, these tests cannot protect against other changes
20 | /// occurring due to the formatting option.
21 | ///
22 | [TestClass]
23 | public class JUnitTestLoggerFormatOptionsAcceptanceTests
24 | {
25 | [TestMethod]
26 | public void FailureBodyFormat_Default_ShouldntStartWithMessage()
27 | {
28 | DotnetTestFixture.Execute("failure-default-test-results.xml;FailureBodyFormat=Default");
29 | string resultsFile = Path.Combine(DotnetTestFixture.RootDirectory, "failure-default-test-results.xml");
30 | XDocument resultsXml = XDocument.Load(resultsFile);
31 |
32 | var failures = resultsXml.XPathSelectElements("/testsuites/testsuite")
33 | .Descendants()
34 | .Where(x => x.Name.LocalName == "failure")
35 | .ToList();
36 |
37 | foreach (var failure in failures)
38 | {
39 | // Strip new line and carrige return. These may be inconsistent depending on
40 | // environment settings
41 | var message = failure.Attribute("message").Value.Replace("\r", string.Empty).Replace("\n", string.Empty);
42 | var body = failure.Value.Replace("\r", string.Empty).Replace("\n", string.Empty);
43 |
44 | Assert.IsFalse(body.StartsWith(message));
45 | }
46 |
47 | Assert.IsTrue(new JunitXmlValidator().IsValid(resultsXml));
48 | }
49 |
50 | [TestMethod]
51 | public void FailureBodyFormat_Verbose_ShouldNotContainConsoleOut()
52 | {
53 | DotnetTestFixture.Execute("failure-verbose-test-results.xml;FailureBodyFormat=Default");
54 | string resultsFile = Path.Combine(DotnetTestFixture.RootDirectory, "failure-verbose-test-results.xml");
55 | XDocument resultsXml = XDocument.Load(resultsFile);
56 |
57 | var failures = resultsXml.XPathSelectElements("/testsuites/testsuite")
58 | .Descendants()
59 | .Where(x => x.Name.LocalName == "failure")
60 | .ToList();
61 |
62 | Assert.AreEqual(0, failures.Count(x => x.Value.Contains("{EEEE1DA6-6296-4486-BDA5-A50A19672F0F}")));
63 | Assert.AreEqual(0, failures.Count(x => x.Value.Contains("{C33FF4B5-75E1-4882-B968-DF9608BFE7C2}")));
64 | Assert.IsTrue(new JunitXmlValidator().IsValid(resultsXml));
65 | }
66 |
67 | [TestMethod]
68 | public void FailureBodyFormat_Verbose_ShouldStartWithMessage()
69 | {
70 | DotnetTestFixture.Execute("failure-verbose-test-results.xml;FailureBodyFormat=Verbose");
71 | string resultsFile = Path.Combine(DotnetTestFixture.RootDirectory, "failure-verbose-test-results.xml");
72 | XDocument resultsXml = XDocument.Load(resultsFile);
73 |
74 | var failures = resultsXml.XPathSelectElements("/testsuites/testsuite")
75 | .Descendants()
76 | .Where(x => x.Name.LocalName == "failure")
77 | .ToList();
78 |
79 | foreach (var failure in failures)
80 | {
81 | // Strip new line and carrige return. These may be inconsistent depending on
82 | // environment settings
83 | var message = failure.Attribute("message").Value.Replace("\r", string.Empty).Replace("\n", string.Empty);
84 | var body = failure.Value.Replace("\r", string.Empty).Replace("\n", string.Empty);
85 |
86 | Assert.IsTrue(body.Trim().StartsWith(message.Trim()));
87 | }
88 |
89 | Assert.IsTrue(new JunitXmlValidator().IsValid(resultsXml));
90 | }
91 |
92 | [TestMethod]
93 | public void FailureBodyFormat_Verbose_ShouldContainConsoleOut()
94 | {
95 | DotnetTestFixture.Execute("failure-verbose-test-results.xml;FailureBodyFormat=Verbose");
96 | string resultsFile = Path.Combine(DotnetTestFixture.RootDirectory, "failure-verbose-test-results.xml");
97 | XDocument resultsXml = XDocument.Load(resultsFile);
98 |
99 | var failures = resultsXml.XPathSelectElements("/testsuites/testsuite")
100 | .Descendants()
101 | .Where(x => x.Name.LocalName == "failure")
102 | .ToList();
103 |
104 | Assert.AreEqual(1, failures.Count(x => x.Value.Contains("{EEEE1DA6-6296-4486-BDA5-A50A19672F0F}")));
105 | Assert.AreEqual(1, failures.Count(x => x.Value.Contains("{C33FF4B5-75E1-4882-B968-DF9608BFE7C2}")));
106 | Assert.IsTrue(new JunitXmlValidator().IsValid(resultsXml));
107 | }
108 |
109 | [TestMethod]
110 | public void MethodFormat_Default_ShouldBeOnlyTheMethod()
111 | {
112 | DotnetTestFixture.Execute("method-default-test-results.xml;MethodFormat=Default");
113 | string resultsFile = Path.Combine(DotnetTestFixture.RootDirectory, "method-default-test-results.xml");
114 | XDocument resultsXml = XDocument.Load(resultsFile);
115 |
116 | var testcases = resultsXml.XPathSelectElements("/testsuites/testsuite")
117 | .Descendants()
118 | .Where(x => x.Name.LocalName == "testcase")
119 | .ToList();
120 |
121 | foreach (var testcase in testcases)
122 | {
123 | var parsedName = new TestCaseNameParser().Parse(testcase.Attribute("name").Value);
124 |
125 | // A method name only will not be parsable into two pieces
126 | Assert.AreEqual(parsedName.Type, TestCaseNameParser.TestCaseParserUnknownType);
127 | }
128 |
129 | Assert.IsTrue(new JunitXmlValidator().IsValid(resultsXml));
130 | }
131 |
132 | [TestMethod]
133 | public void MethodFormat_Class_ShouldIncludeClass()
134 | {
135 | DotnetTestFixture.Execute("method-class-test-results.xml;MethodFormat=Class");
136 | string resultsFile = Path.Combine(DotnetTestFixture.RootDirectory, "method-class-test-results.xml");
137 | XDocument resultsXml = XDocument.Load(resultsFile);
138 |
139 | var testcases = resultsXml.XPathSelectElements("/testsuites/testsuite")
140 | .Descendants()
141 | .Where(x => x.Name.LocalName == "testcase")
142 | .ToList();
143 |
144 | foreach (var testcase in testcases)
145 | {
146 | // Note the new parser can't handle the names with just class.method
147 | var parsedName = new LegacyTestCaseNameParser().Parse(testcase.Attribute("name").Value);
148 |
149 | // If the name is parsable into two pieces, then we have a two piece name and
150 | // consider that to be a passing result.
151 | Assert.AreNotEqual(parsedName.Type, TestCaseNameParser.TestCaseParserUnknownType);
152 | }
153 |
154 | Assert.IsTrue(new JunitXmlValidator().IsValid(resultsXml));
155 | }
156 |
157 | [TestMethod]
158 | public void MethodFormat_Full_ShouldIncludeNamespaceAndClass()
159 | {
160 | DotnetTestFixture.Execute("method-full-test-results.xml;MethodFormat=Full");
161 | string resultsFile = Path.Combine(DotnetTestFixture.RootDirectory, "method-full-test-results.xml");
162 | XDocument resultsXml = XDocument.Load(resultsFile);
163 |
164 | var testcases = resultsXml.XPathSelectElements("/testsuites/testsuite")
165 | .Descendants()
166 | .Where(x => x.Name.LocalName == "testcase")
167 | .ToList();
168 |
169 | foreach (var testcase in testcases)
170 | {
171 | var parsedName = new TestCaseNameParser().Parse(testcase.Attribute("name").Value);
172 |
173 | // We expect the full name would be the class name plus the parsed method
174 | var expectedFullName = parsedName.Namespace + "." + parsedName.Type + "." + parsedName.Method;
175 |
176 | // If the name is parsable into two pieces, then we have at least a two piece name
177 | Assert.AreNotEqual(parsedName.Type, TestCaseNameParser.TestCaseParserUnknownType);
178 | Assert.AreEqual(expectedFullName, testcase.Attribute("name").Value);
179 | }
180 |
181 | Assert.IsTrue(new JunitXmlValidator().IsValid(resultsXml));
182 | }
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/test/assets/JUnit.xsd:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | JUnit test result schema for the Apache Ant JUnit and JUnitReport tasks
8 | Copyright © 2011, Windy Road Technology Pty. Limited
9 | The Apache Ant JUnit XML Schema is distributed under the terms of the Apache License Version 2.0 http://www.apache.org/licenses/
10 | Permission to waive conditions of this license may be requested from Windy Road Support (http://windyroad.org/support).
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Contains an aggregation of testsuite results
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Derived from testsuite/@name in the non-aggregated documents
31 |
32 |
33 |
34 |
35 | Starts at '0' for the first testsuite and is incremented by 1 for each following testsuite
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Contains the results of exexuting a testsuite
48 |
49 |
50 |
51 |
52 | Properties (e.g., environment settings) set during test execution
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Indicates that the test errored. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test. Contains as a text node relevant data for the error, e.g., a stack trace
78 |
79 |
80 |
81 |
82 |
83 |
84 | The error message. e.g., if a java exception is thrown, the return value of getMessage()
85 |
86 |
87 |
88 |
89 | The type of error that occured. e.g., if a java execption is thrown the full class name of the exception.
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | Indicates that the test failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals. Contains as a text node relevant data for the failure, e.g., a stack trace
99 |
100 |
101 |
102 |
103 |
104 |
105 | The message specified in the assert
106 |
107 |
108 |
109 |
110 | The type of the assert.
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | Name of the test method
121 |
122 |
123 |
124 |
125 | Full class name for the class the test method is in.
126 |
127 |
128 |
129 |
130 | Time taken (in seconds) to execute the test
131 |
132 |
133 |
134 |
135 |
136 |
137 | Data that was written to standard out while the test was executed
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | Data that was written to standard error while the test was executed
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | Full class name of the test for non-aggregated testsuite documents. Class name without the package for aggregated testsuites documents
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | when the test was executed. Timezone may not be specified.
169 |
170 |
171 |
172 |
173 | Host on which the tests were executed. 'localhost' should be used if the hostname cannot be determined.
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | The total number of tests in the suite
184 |
185 |
186 |
187 |
188 | The total number of tests in the suite that failed. A failure is a test which the code has explicitly failed by using the mechanisms for that purpose. e.g., via an assertEquals
189 |
190 |
191 |
192 |
193 | The total number of tests in the suite that errored. An errored test is one that had an unanticipated problem. e.g., an unchecked throwable; or a problem with the implementation of the test.
194 |
195 |
196 |
197 |
198 | The total number of ignored or skipped tests in the suite.
199 |
200 |
201 |
202 |
203 | Time taken (in seconds) to execute the tests in the suite
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/docs/assets/TestResults.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | --TearDown
10 | at JUnit.Xml.TestLogger.NetFull.Tests.FailingTearDown.TearDown() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest1.cs:line 182
11 |
12 |
13 | at JUnit.Xml.TestLogger.NetFull.Tests.FailingTestSetup.SetUp() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest1.cs:line 167
14 |
15 |
16 |
17 |
18 |
19 |
20 | at JUnit.Xml.TestLogger.NetFull.Tests.ParametrizedTestCases.TestData(Int32 x, String s) in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest1.cs:line 232
21 |
22 |
23 | at JUnit.Xml.TestLogger.NetFull.Tests.ParametrizedTestCases.TestData(Int32 x, String s) in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest1.cs:line 232
24 |
25 |
26 |
27 |
28 |
29 | at JUnit.Xml.TestLogger.NetFull.Tests.UnitTest1.FailTest11() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest1.cs:line 27
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | at JUnit.Xml.TestLogger.NetFull.Tests.UnitTest2.FailTest22() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest1.cs:line 94
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | --TearDown
60 | at JUnit.Xml.TestLogger.Tests2.FailingTearDown.TearDown() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest2.cs:line 137
61 |
62 |
63 | at JUnit.Xml.TestLogger.Tests2.FailingTestSetup.SetUp() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest2.cs:line 122
64 |
65 |
66 |
67 |
68 |
69 |
70 | at JUnit.Xml.TestLogger.Tests2.ParametrizedTestCases.TestData(Int32 x, String s) in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest2.cs:line 187
71 |
72 |
73 | at JUnit.Xml.TestLogger.Tests2.ParametrizedTestCases.TestData(Int32 x, String s) in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest2.cs:line 187
74 |
75 |
76 |
77 |
78 |
79 | at JUnit.Xml.TestLogger.Tests2.UnitTest1.FailTest11() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest2.cs:line 20
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | at JUnit.Xml.TestLogger.Tests2.UnitTest2.FailTest22() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest2.cs:line 49
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | {EEEE1DA6-6296-4486-BDA5-A50A19672F0F}
99 | {C33FF4B5-75E1-4882-B968-DF9608BFE7C2}
100 |
101 | {2010CAE3-7BC0-4841-A5A3-7D5F947BB9FB}
102 | {998AC9EC-7429-42CD-AD55-72037E7AF3D8}
103 |
104 |
105 | Test Framework Informational Messages:
106 | NUnit Adapter 3.10.0.21: Test execution started
107 | Running all tests in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\bin\Debug\netcoreapp3.1\JUnit.Xml.TestLogger.NetCore.Tests.dll
108 | NUnit3TestExecutor converted 52 of 52 NUnit test cases
109 | NUnit Adapter 3.10.0.21: Test execution complete
110 |
111 | Warning - SetUp failed for test fixture JUnit.Xml.TestLogger.NetFull.Tests.FailingOneTimeSetUp
112 | Warning - System.InvalidOperationException : Operation is not valid due to the current state of the object.
113 | Warning - at JUnit.Xml.TestLogger.NetFull.Tests.FailingOneTimeSetUp.OneTimeSetUp() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest1.cs:line 152
114 | Warning - TearDown failed for test fixture JUnit.Xml.TestLogger.NetFull.Tests.FailingOneTimeTearDown
115 | Warning - TearDown : System.InvalidOperationException : Operation is not valid due to the current state of the object.
116 | Warning - --TearDown
117 | at JUnit.Xml.TestLogger.NetFull.Tests.FailingOneTimeTearDown.OneTimeTearDown() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest1.cs:line 197
118 | Warning - {D46DFA10-EEDD-49E5-804D-FE43051331A7}
119 | Warning - {33F5FD22-6F40-499D-98E4-481D87FAEAA1}
120 | Warning - SetUp failed for test fixture JUnit.Xml.TestLogger.Tests2.FailingOneTimeSetUp
121 | Warning - System.InvalidOperationException : Operation is not valid due to the current state of the object.
122 | Warning - at JUnit.Xml.TestLogger.Tests2.FailingOneTimeSetUp.OneTimeSetUp() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest2.cs:line 107
123 | Warning - TearDown failed for test fixture JUnit.Xml.TestLogger.Tests2.FailingOneTimeTearDown
124 | Warning - TearDown : System.InvalidOperationException : Operation is not valid due to the current state of the object.
125 | Warning - --TearDown
126 | at JUnit.Xml.TestLogger.Tests2.FailingOneTimeTearDown.OneTimeTearDown() in C:\Users\REDACTED\Documents\GitHub\junit.testlogger\test\assets\JUnit.Xml.TestLogger.NetCore.Tests\UnitTest2.cs:line 152
127 |
128 |
129 |
--------------------------------------------------------------------------------
/src/JUnit.Xml.TestLogger/JunitXmlSerializer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Spekt Contributors. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace Microsoft.VisualStudio.TestPlatform.Extension.Junit.Xml.TestLogger
5 | {
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Globalization;
9 | using System.IO;
10 | using System.Linq;
11 | using System.Text;
12 | using System.Xml.Linq;
13 | using Microsoft.VisualStudio.TestPlatform.ObjectModel;
14 | using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
15 | using Spekt.TestLogger.Core;
16 | using Spekt.TestLogger.Utilities;
17 |
18 | public class JunitXmlSerializer : ITestResultSerializer
19 | {
20 | // Dicionary keys for command line arguments.
21 | public const string MethodFormatKey = "MethodFormat";
22 |
23 | public const string FailureBodyFormatKey = "FailureBodyFormat";
24 |
25 | private const string ResultStatusPassed = "Passed";
26 | private const string ResultStatusFailed = "Failed";
27 |
28 | public enum MethodFormat
29 | {
30 | ///
31 | /// The method format will be the method only (i.e. Class.Method())
32 | ///
33 | Default,
34 |
35 | ///
36 | /// The method format will include the class and method name (i.e. Class.Method())
37 | ///
38 | Class,
39 |
40 | ///
41 | /// The method format will include the namespace, class and method (i.e. Namespace.Class.Method())
42 | ///
43 | Full,
44 | }
45 |
46 | public enum FailureBodyFormat
47 | {
48 | ///
49 | /// The failure body will incldue only the error stack trace.
50 | ///
51 | Default,
52 |
53 | ///
54 | /// The failure body will incldue the Expected/Actual messages.
55 | ///
56 | Verbose,
57 | }
58 |
59 | public IInputSanitizer InputSanitizer { get; } = new InputSanitizerXml();
60 |
61 | public MethodFormat MethodFormatOption { get; private set; } = MethodFormat.Default;
62 |
63 | public FailureBodyFormat FailureBodyFormatOption { get; private set; } = FailureBodyFormat.Default;
64 |
65 | public static IEnumerable GroupTestSuites(IEnumerable suites)
66 | {
67 | var groups = suites;
68 | var roots = new List();
69 | while (groups.Any())
70 | {
71 | groups = groups.GroupBy(r =>
72 | {
73 | var name = r.FullName.SubstringBeforeDot();
74 | if (string.IsNullOrEmpty(name))
75 | {
76 | roots.Add(r);
77 | }
78 |
79 | return name;
80 | })
81 | .OrderBy(g => g.Key)
82 | .Where(g => !string.IsNullOrEmpty(g.Key))
83 | .Select(g => AggregateTestSuites(g, "TestSuite", g.Key.SubstringAfterDot(), g.Key))
84 | .ToList();
85 | }
86 |
87 | return roots;
88 | }
89 |
90 | public string Serialize(
91 | LoggerConfiguration loggerConfiguration,
92 | TestRunConfiguration runConfiguration,
93 | List results,
94 | List messages)
95 | {
96 | this.Configure(loggerConfiguration);
97 | var doc = new XDocument(this.CreateTestSuitesElement(results, runConfiguration, messages));
98 | return doc.ToString();
99 | }
100 |
101 | private static TestSuite AggregateTestSuites(
102 | IEnumerable suites,
103 | string testSuiteType,
104 | string name,
105 | string fullName)
106 | {
107 | var element = new XElement("test-suite");
108 |
109 | int total = 0;
110 | int passed = 0;
111 | int failed = 0;
112 | int skipped = 0;
113 | int inconclusive = 0;
114 | int error = 0;
115 | var time = TimeSpan.Zero;
116 |
117 | foreach (var result in suites)
118 | {
119 | total += result.Total;
120 | passed += result.Passed;
121 | failed += result.Failed;
122 | skipped += result.Skipped;
123 | inconclusive += result.Inconclusive;
124 | error += result.Error;
125 | time += result.Time;
126 |
127 | element.Add(result.Element);
128 | }
129 |
130 | element.SetAttributeValue("type", testSuiteType);
131 | element.SetAttributeValue("name", name);
132 | element.SetAttributeValue("fullname", fullName);
133 | element.SetAttributeValue("total", total);
134 | element.SetAttributeValue("passed", passed);
135 | element.SetAttributeValue("failed", failed);
136 | element.SetAttributeValue("inconclusive", inconclusive);
137 | element.SetAttributeValue("skipped", skipped);
138 |
139 | var resultString = failed > 0 ? ResultStatusFailed : ResultStatusPassed;
140 | element.SetAttributeValue("result", resultString);
141 | element.SetAttributeValue("duration", time.TotalSeconds);
142 |
143 | return new TestSuite
144 | {
145 | Element = element,
146 | Name = name,
147 | FullName = fullName,
148 | Total = total,
149 | Passed = passed,
150 | Failed = failed,
151 | Inconclusive = inconclusive,
152 | Skipped = skipped,
153 | Error = error,
154 | Time = time
155 | };
156 | }
157 |
158 | ///
159 | /// Produces a consistently indented output, taking into account that incoming messages
160 | /// often have new lines within a message.
161 | ///
162 | private static string Indent(IReadOnlyCollection messages)
163 | {
164 | var indent = " ";
165 |
166 | // Splitting on any line feed or carrage return because a message may include new lines
167 | // that are inconsistent with the Environment.NewLine. We then remove all blank lines so
168 | // it shouldn't cause an issue that this generates extra line breaks.
169 | return
170 | indent +
171 | string.Join(
172 | $"{Environment.NewLine}{indent}",
173 | messages.SelectMany(m =>
174 | m.Text.Split(new string[] { "\r", "\n" }, StringSplitOptions.None)
175 | .Where(x => !string.IsNullOrWhiteSpace(x))
176 | .Select(x => x.Trim())));
177 | }
178 |
179 | private XElement CreateTestSuitesElement(
180 | List results,
181 | TestRunConfiguration runConfiguration,
182 | List messages)
183 | {
184 | var assemblies = results.Select(x => x.AssemblyPath).Distinct().ToList();
185 | var testsuiteElements = assemblies
186 | .Select(a => this.CreateTestSuiteElement(
187 | results.Where(x => x.AssemblyPath == a).ToList(),
188 | runConfiguration,
189 | messages));
190 |
191 | return new XElement("testsuites", testsuiteElements);
192 | }
193 |
194 | private XElement CreateTestSuiteElement(List results, TestRunConfiguration runConfiguration, List messages)
195 | {
196 | var testCaseElements = results.Select(a => this.CreateTestCaseElement(a));
197 |
198 | StringBuilder stdOut = new StringBuilder();
199 | foreach (var m in results.SelectMany(x => x.Messages))
200 | {
201 | if (TestResultMessage.StandardOutCategory.Equals(m.Category, StringComparison.OrdinalIgnoreCase))
202 | {
203 | stdOut.AppendLine(m.Text);
204 | }
205 | }
206 |
207 | var frameworkInfo = messages.Where(x => x.Level == TestMessageLevel.Informational);
208 | if (frameworkInfo.Any())
209 | {
210 | stdOut.AppendLine(string.Empty);
211 | stdOut.AppendLine("Test Framework Informational Messages:");
212 |
213 | foreach (var m in frameworkInfo)
214 | {
215 | stdOut.AppendLine(m.Message);
216 | }
217 | }
218 |
219 | StringBuilder stdErr = new StringBuilder();
220 | foreach (var m in messages.Where(x => x.Level != TestMessageLevel.Informational))
221 | {
222 | stdErr.AppendLine($"{m.Level} - {m.Message}");
223 | }
224 |
225 | // Adding required properties, system-out, and system-err elements in the correct
226 | // positions as required by the xsd. In system-out collapse consequtive newlines to a
227 | // single newline.
228 | var element = new XElement(
229 | "testsuite",
230 | new XElement("properties"),
231 | testCaseElements,
232 | new XElement("system-out", stdOut.ToString()),
233 | new XElement("system-err", stdErr.ToString()));
234 |
235 | element.SetAttributeValue("name", Path.GetFileName(results.First().AssemblyPath));
236 |
237 | element.SetAttributeValue("tests", results.Count);
238 | element.SetAttributeValue("skipped", results.Where(x => x.Outcome == TestOutcome.Skipped).Count());
239 | element.SetAttributeValue("failures", results.Where(x => x.Outcome == TestOutcome.Failed).Count());
240 | element.SetAttributeValue("errors", 0); // looks like this isn't supported by .net?
241 | element.SetAttributeValue("time", results.Sum(x => x.Duration.TotalSeconds).ToString(CultureInfo.InvariantCulture));
242 | element.SetAttributeValue("timestamp", runConfiguration.StartTime.ToString("s"));
243 | element.SetAttributeValue("hostname", Environment.MachineName);
244 | element.SetAttributeValue("id", 0); // we never output multiple, so this is always zero.
245 | element.SetAttributeValue("package", Path.GetFileName(results.First().AssemblyPath));
246 |
247 | return element;
248 | }
249 |
250 | private XElement CreateTestCaseElement(TestResultInfo result)
251 | {
252 | var testcaseElement = new XElement("testcase");
253 |
254 | var namespaceClass = result.Namespace + "." + result.Type;
255 |
256 | testcaseElement.SetAttributeValue("classname", namespaceClass);
257 |
258 | if (this.MethodFormatOption == MethodFormat.Full)
259 | {
260 | testcaseElement.SetAttributeValue("name", namespaceClass + "." + result.Method);
261 | }
262 | else if (this.MethodFormatOption == MethodFormat.Class)
263 | {
264 | testcaseElement.SetAttributeValue("name", result.Type + "." + result.Method);
265 | }
266 | else
267 | {
268 | testcaseElement.SetAttributeValue("name", result.Method);
269 | }
270 |
271 | // Ensure time value is never zero because gitlab treats 0 like its null. 0.1 micro
272 | // seconds should be low enough it won't interfere with anyone monitoring test duration.
273 | testcaseElement.SetAttributeValue(
274 | "time",
275 | Math.Max(0.0000001f, result.Duration.TotalSeconds).ToString("0.0000000", CultureInfo.InvariantCulture));
276 |
277 | if (result.Outcome == TestOutcome.Failed)
278 | {
279 | var failureBodySB = new StringBuilder();
280 |
281 | if (this.FailureBodyFormatOption == FailureBodyFormat.Verbose)
282 | {
283 | failureBodySB.AppendLine(result.ErrorMessage);
284 |
285 | // Stack trace label included to mimic the normal test output
286 | failureBodySB.AppendLine("Stack Trace:");
287 | }
288 |
289 | failureBodySB.AppendLine(result.ErrorStackTrace);
290 |
291 | if (this.FailureBodyFormatOption == FailureBodyFormat.Verbose &&
292 | result.Messages.Count > 0)
293 | {
294 | failureBodySB.AppendLine("Standard Output:");
295 |
296 | failureBodySB.AppendLine(Indent(result.Messages));
297 | }
298 |
299 | var failureElement = new XElement("failure", failureBodySB.ToString().Trim());
300 |
301 | failureElement.SetAttributeValue("type", "failure"); // TODO are there failure types?
302 | failureElement.SetAttributeValue("message", result.ErrorMessage);
303 |
304 | testcaseElement.Add(failureElement);
305 | }
306 | else if (result.Outcome == TestOutcome.Skipped)
307 | {
308 | var skippedElement = new XElement("skipped");
309 |
310 | testcaseElement.Add(skippedElement);
311 | }
312 |
313 | return testcaseElement;
314 | }
315 |
316 | ///
317 | /// Performs logger specific configuration based the user's CLI flags, which are provided
318 | /// through .
319 | ///
320 | private void Configure(LoggerConfiguration loggerConfiguration)
321 | {
322 | if (loggerConfiguration.Values.TryGetValue(MethodFormatKey, out string methodFormat))
323 | {
324 | if (string.Equals(methodFormat.Trim(), "Class", StringComparison.OrdinalIgnoreCase))
325 | {
326 | this.MethodFormatOption = MethodFormat.Class;
327 | }
328 | else if (string.Equals(methodFormat.Trim(), "Full", StringComparison.OrdinalIgnoreCase))
329 | {
330 | this.MethodFormatOption = MethodFormat.Full;
331 | }
332 | else if (string.Equals(methodFormat.Trim(), "Default", StringComparison.OrdinalIgnoreCase))
333 | {
334 | this.MethodFormatOption = MethodFormat.Default;
335 | }
336 | else
337 | {
338 | Console.WriteLine($"JunitXML Logger: The provided Method Format '{methodFormat}' is not a recognized option. Using default");
339 | }
340 | }
341 |
342 | if (loggerConfiguration.Values.TryGetValue(FailureBodyFormatKey, out string failureFormat))
343 | {
344 | if (string.Equals(failureFormat.Trim(), "Verbose", StringComparison.OrdinalIgnoreCase))
345 | {
346 | this.FailureBodyFormatOption = FailureBodyFormat.Verbose;
347 | }
348 | else if (string.Equals(failureFormat.Trim(), "Default", StringComparison.OrdinalIgnoreCase))
349 | {
350 | this.FailureBodyFormatOption = FailureBodyFormat.Default;
351 | }
352 | else
353 | {
354 | Console.WriteLine($"JunitXML Logger: The provided Failure Body Format '{failureFormat}' is not a recognized option. Using default");
355 | }
356 | }
357 | }
358 |
359 | public class TestSuite
360 | {
361 | public XElement Element { get; set; }
362 |
363 | public string Name { get; set; }
364 |
365 | public string FullName { get; set; }
366 |
367 | public int Total { get; set; }
368 |
369 | public int Passed { get; set; }
370 |
371 | public int Failed { get; set; }
372 |
373 | public int Inconclusive { get; set; }
374 |
375 | public int Skipped { get; set; }
376 |
377 | public int Error { get; set; }
378 |
379 | public TimeSpan Time { get; set; }
380 | }
381 | }
382 | }
383 |
--------------------------------------------------------------------------------