diff --git a/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml b/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml new file mode 100644 index 000000000..92ccc2c21 --- /dev/null +++ b/.azuredevops/pipelines/templates/stages/integration-tests-junit.yml @@ -0,0 +1,55 @@ +# Runs integration tests for Cake.Issues.JUnit on different platforms + +stages: + - stage: IntegrationTestsJUnitStage + displayName: Integration Tests Cake.Issues.JUnit + dependsOn: IntegrationTestsBuildStage + jobs: + - job: TestCakeScriptingJob + displayName: Test Cake Scripting .NET 8 + strategy: + matrix: + Windows_Server_2022: + imageName: 'windows-2022' + Windows_Server_2025: + imageName: 'windows-2025' + macOS_13: + imageName: 'macOS-13' + macOS_14: + imageName: 'macOS-14' + Ubuntu_22_04: + imageName: 'ubuntu-22.04' + Ubuntu_24_04: + imageName: 'ubuntu-24.04' + pool: + vmImage: $(imageName) + steps: + - template: ../steps/install-net8.yml + - template: ../steps/provide-nuget-packages.yml + - powershell: ./build.ps1 --verbosity=diagnostic + workingDirectory: ./tests/Cake.Issues.JUnit/script-runner/net8.0 + displayName: 'Run integration tests' + - job: TestCakeFrostingJob + displayName: Test Cake Frosting .NET 8 + strategy: + matrix: + Windows_Server_2022: + imageName: 'windows-2022' + Windows_Server_2025: + imageName: 'windows-2025' + macOS_13: + imageName: 'macOS-13' + macOS_14: + imageName: 'macOS-14' + Ubuntu_22_04: + imageName: 'ubuntu-22.04' + Ubuntu_24_04: + imageName: 'ubuntu-24.04' + pool: + vmImage: $(imageName) + steps: + - template: ../steps/install-net8.yml + - template: ../steps/provide-nuget-packages.yml + - powershell: ./build.ps1 --verbosity=diagnostic + workingDirectory: ./tests/Cake.Issues.JUnit/frosting/net8.0 + displayName: 'Run integration tests' \ No newline at end of file diff --git a/.github/workflows/integrationtests.yml b/.github/workflows/integrationtests.yml index 12e771c35..010e0f4de 100644 --- a/.github/workflows/integrationtests.yml +++ b/.github/workflows/integrationtests.yml @@ -118,7 +118,66 @@ jobs: run: ./build.sh --verbosity=diagnostic working-directory: ./tests/Cake.Issues.MsBuild/script-runner/${{ env.TFM }} shell: bash - + # Integration Tests Cake.Issues.JUnit Cake Scripting + IntegrationTestsJUnitCakeScripting: + name: Integration Tests Cake.Issues.JUnit Cake Scripting + needs: Build + strategy: + fail-fast: false + matrix: + os: [ + windows-2022, windows-2025, + ubuntu-22.04, ubuntu-24.04, + macos-13, macos-14] + dotnet: [8.x, 9.x] + runs-on: ${{ matrix.os }} + steps: + - name: Get the sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Prepare integration tests + uses: ./.github/actions/prepare-integration-test + with: + dotnet-version: ${{ matrix.dotnet }} + - name: Determine TFM + run: | + DOTNET_VERSION="${{ matrix.dotnet }}" + DOTNET_MAJOR_VERSION="${DOTNET_VERSION%%.*}" + echo "TFM=net${DOTNET_MAJOR_VERSION}.0" >> $GITHUB_ENV + shell: bash + - name: Run integration tests + run: ./build.sh --verbosity=diagnostic + working-directory: ./tests/Cake.Issues.JUnit/script-runner/${{ env.TFM }} + shell: bash + # Integration Tests Cake.Issues.JUnit Cake Frosting + IntegrationTestsJUnitCakeFrosting: + name: Integration Tests Cake.Issues.JUnit Cake Frosting + needs: Build + strategy: + fail-fast: false + matrix: + os: [ + windows-2022, windows-2025, + ubuntu-22.04, ubuntu-24.04, + macos-13, macos-14] + dotnet: [8.x] + runs-on: ${{ matrix.os }} + steps: + - name: Get the sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Prepare integration tests + uses: ./.github/actions/prepare-integration-test + with: + dotnet-version: ${{ matrix.dotnet }} + - name: Determine TFM + run: | + DOTNET_VERSION="${{ matrix.dotnet }}" + DOTNET_MAJOR_VERSION="${DOTNET_VERSION%%.*}" + echo "TFM=net${DOTNET_MAJOR_VERSION}.0" >> $GITHUB_ENV + shell: bash + - name: Run integration tests + run: ./build.sh --verbosity=diagnostic + working-directory: ./tests/Cake.Issues.JUnit/frosting/${{ env.TFM }} + shell: bash # Integration Tests Cake.Issues.PullRequests.GitHubActions Cake Scripting IntegrationTestsPullRequestsGitHubActionsCakeScripting: name: Integration Tests Cake.Issues.PullRequests.GitHubActions Cake Scripting diff --git a/CiStatus.md b/CiStatus.md index f93feddd3..223749161 100644 --- a/CiStatus.md +++ b/CiStatus.md @@ -36,6 +36,23 @@ |Azure Pipelines|Cake Scripting|Ubuntu 22.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.GitRepository&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.GitRepository&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| |Azure Pipelines|Cake Scripting|Ubuntu 24.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.GitRepository&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.GitRepository&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +### Integration Tests Cake.Issues.JUnit + +| CI Server | Runner | Operating System | Develop | Master | +|:--:|:--:|:--:|:--:|:--:| +|Azure Pipelines|Cake Scripting|Windows Server 2022|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Windows_Server_2022)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Windows_Server_2022)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|Windows Server 2025|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Windows_Server_2025)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Windows_Server_2025)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|macOS 12|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20macOS_13)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20macOS_13)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|macOS 14|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20macOS_14)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20macOS_14)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|Ubuntu 22.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Scripting|Ubuntu 24.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Scripting&configuration=Test%20Cake%20Scripting%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|Windows Server 2022|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Windows_Server_2022)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Windows_Server_2022)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|Windows Server 2025|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Windows_Server_2025)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Windows_Server_2025)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|macOS 12|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20macOS_13)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20macOS_13)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|macOS 14|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20macOS_14)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20macOS_14)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|Ubuntu 22.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Ubuntu_22_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| +|Azure Pipelines|Cake Frosting .NET 8|Ubuntu 24.04|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=develop&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=develop)|[![Build Status](https://dev.azure.com/cake-contrib/Cake.Issues/_apis/build/status%2Fcake-contrib.Cake.Issues?branchName=master&stageName=Integration%20Tests%20Cake.Issues.JUnit&jobName=Test%20Cake%20Frosting%20.NET%208&configuration=Test%20Cake%20Frosting%20.NET%208%20Ubuntu_24_04)](https://dev.azure.com/cake-contrib/Cake.Issues/_build/latest?definitionId=2&branchName=master)| + ### Integration Tests Cake.Issues.Markdownlint | CI Server | Runner | Operating System | Develop | Master | diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d71eb25bc..34548c08a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -24,6 +24,7 @@ stages: - template: .azuredevops/pipelines/templates/stages/unit-tests.yml - template: .azuredevops/pipelines/templates/stages/build-for-integration-tests.yml - template: .azuredevops/pipelines/templates/stages/integration-tests-git-repository.yml + - template: .azuredevops/pipelines/templates/stages/integration-tests-junit.yml - template: .azuredevops/pipelines/templates/stages/integration-tests-markdownlint.yml - template: .azuredevops/pipelines/templates/stages/integration-tests-msbuild.yml - template: .azuredevops/pipelines/templates/stages/integration-tests-reporting-console.yml diff --git a/docs/input/documentation/assets/tables/supported-tools-cpp.csv b/docs/input/documentation/assets/tables/supported-tools-cpp.csv new file mode 100644 index 000000000..3c033fb7b --- /dev/null +++ b/docs/input/documentation/assets/tables/supported-tools-cpp.csv @@ -0,0 +1,2 @@ +"Tool","Tool Version","Format","Issue Provider","Supported Since" +"[cpplint](https://github.com/cpplint/cpplint){target='_blank'} :material-alert-decagram:",,"[junit](https://github.com/cpplint/cpplint){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 diff --git a/docs/input/documentation/assets/tables/supported-tools-git.csv b/docs/input/documentation/assets/tables/supported-tools-git.csv new file mode 100644 index 000000000..93e396cac --- /dev/null +++ b/docs/input/documentation/assets/tables/supported-tools-git.csv @@ -0,0 +1,2 @@ +"Tool","Tool Version","Format","Issue Provider","Supported Since" +"[commitlint](https://commitlint.js.org/){target='_blank'} :material-alert-decagram:",,"[junit](https://www.npmjs.com/package/commitlint-format-junit){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 \ No newline at end of file diff --git a/docs/input/documentation/assets/tables/supported-tools-html.csv b/docs/input/documentation/assets/tables/supported-tools-html.csv new file mode 100644 index 000000000..50e555f3e --- /dev/null +++ b/docs/input/documentation/assets/tables/supported-tools-html.csv @@ -0,0 +1,2 @@ +"Tool","Tool Version","Format","Issue Provider","Supported Since" +"[HtmlHint](https://htmlhint.com/){target='_blank'} :material-alert-decagram:",,"[junit](https://htmlhint.com/usage/options/#format){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 \ No newline at end of file diff --git a/docs/input/documentation/assets/tables/supported-tools-kubernetes.csv b/docs/input/documentation/assets/tables/supported-tools-kubernetes.csv index bcbfbbaa3..d6f24e4cd 100644 --- a/docs/input/documentation/assets/tables/supported-tools-kubernetes.csv +++ b/docs/input/documentation/assets/tables/supported-tools-kubernetes.csv @@ -2,4 +2,5 @@ "[checkov](https://www.checkov.io/){target='_blank'}",,"[SARIF](https://www.checkov.io/2.Basics/CLI%20Command%20Reference.html){target='_blank'}","[Cake.Issues.Sarif](issue-providers/sarif/index.md)",4.2.0 "[kics](https://kics.io/){target='_blank'}",,"[sarif](https://github.com/Checkmarx/kics/blob/master/docs/results.md#sarif){target='_blank'}","[Cake.Issues.Sarif](issue-providers/sarif/index.md)",4.2.0 "[Kubeconform](https://github.com/yannh/kubeconform){target='_blank'} :material-alert-decagram:",,"[tap](https://github.com/yannh/kubeconform?tab=readme-ov-file#usage){target='_blank'}","[Cake.Issues.Tap](issue-providers/tap/index.md)",4.2.0 +"[Kubeconform](https://github.com/yannh/kubeconform){target='_blank'} :material-alert-decagram:",,"[junit](https://github.com/yannh/kubeconform?tab=readme-ov-file#usage){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 "[Trivy](https://trivy.dev/){target='_blank'}",,"[SARIF](https://trivy.dev/v0.58/docs/configuration/reporting/#sarif){target='_blank'}","[Cake.Issues.Sarif](issue-providers/sarif/index.md)",4.2.0 \ No newline at end of file diff --git a/docs/input/documentation/assets/tables/supported-tools-markdown.csv b/docs/input/documentation/assets/tables/supported-tools-markdown.csv index 9f64c7f04..ac03f4434 100644 --- a/docs/input/documentation/assets/tables/supported-tools-markdown.csv +++ b/docs/input/documentation/assets/tables/supported-tools-markdown.csv @@ -5,4 +5,5 @@ "[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",>= 0.9.0,"Default","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",0.3.0 "[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",>= 0.19.0,"Default","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",0.8.1 "[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",>= 0.22.0,"Default","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",0.8.2 -"[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",,"[json](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#usage){target='_blank'}","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",1.1.0 \ No newline at end of file +"[markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli){target='_blank'}",,"[json](https://github.com/igorshubovych/markdownlint-cli?tab=readme-ov-file#usage){target='_blank'}","[Cake.Issues.Markdownlint](issue-providers/markdownlint/index.md)",1.1.0 +"[markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2){target='_blank'} :material-alert-decagram:",,"[JUnit](https://github.com/DavidAnson/markdownlint-cli2#markdownlint-cli2-formatter-junit){target='_blank'}","[Cake.Issues.JUnit](issue-providers/junit/index.md)",5.8.0 \ No newline at end of file diff --git a/docs/input/documentation/issue-providers/index.md b/docs/input/documentation/issue-providers/index.md index 90b886152..bf171cd04 100644 --- a/docs/input/documentation/issue-providers/index.md +++ b/docs/input/documentation/issue-providers/index.md @@ -11,6 +11,7 @@ Issue provider addins are responsible for providing the output of an analyzer or - :material-layers-plus: __[ESLint]__ – Issue provider for reading ESLint issues - :material-layers-plus: __[Git Repository]__ – Issue provider for analyzing Git repositories - :material-layers-plus: __[Inspect Code]__ – Issue provider for reading JetBrains Inspect Code / ReSharper issues +- :material-layers-plus: __[JUnit]__ – Issue provider for reading JUnit XML format - :material-layers-plus: __[Markdownlint]__ – Issue provider for reading issues from markdownlint - :material-layers-plus: __[MsBuild]__ – Issue provider for reading MsBuild errors and warnings - :material-layers-plus: __[Sarif]__ – Issue provider for reading SARIF reports @@ -23,6 +24,7 @@ Issue provider addins are responsible for providing the output of an analyzer or [ESLint]: eslint/index.md [Git Repository]: gitrepository/index.md [Inspect Code]: inspectcode/index.md +[JUnit]: junit/index.md [Markdownlint]: markdownlint/index.md [MsBuild]: msbuild/index.md [Sarif]: sarif/index.md diff --git a/docs/input/documentation/issue-providers/junit/features.md b/docs/input/documentation/issue-providers/junit/features.md new file mode 100644 index 000000000..0139b57cc --- /dev/null +++ b/docs/input/documentation/issue-providers/junit/features.md @@ -0,0 +1,139 @@ +--- +title: Features +description: Features of the Cake.Issues.JUnit addin. +icon: material/creation-outline +--- + +The [Cake.Issues.JUnit addin](https://cakebuild.net/extensions/cake-issues-junit/){target="_blank"} provides the following features. + +## Basic features + +- [x] Reads issues from [JUnit]{target="_blank"} XML files. +- [x] Supports both single `testsuite` and `testsuites` root elements. + +## Supported log file formats + +!!! note + There is no official specification for the [JUnit]{target="_blank"} XML file format and various tools generate different flavors of this format. + The `GenericLogFileFormat` supports different patterns to try to extract details, like file, line / column or rule information. + Tool specific log file formats needs to be used to parse specific formats are additional data provided by the tool. + +- [x] `GenericJUnitLogFileFormat alias for reading issues from any file in [JUnit]{target="_blank"} XML format + - [x] Combines `message` and content for issue description. + - [x] Extracts rule name from `type` or `name` attribute. + - [x] Extracts file paths from `classname` attributes or embedded within failure messages. + Supported file paths: + - `::` (e.g., `src/example.cpp:15:5`) + - `:` (e.g., `/path/to/file.txt:123`) + - `(,)` (e.g., `index.html(12,5)`) + - `()` (e.g., `index.html(12)`) + - ` ` (e.g., `about.html line 8`) + - `File: ` (e.g., `File: about.html`) +- [x] `CppLintLogFileFormat` alias for reading JUnit XML files generated by [CppLint](https://github.com/cpplint/cpplint){target="_blank"}. +- [x] `MarkdownlintCli2LogFileFormat` alias for reading JUnit XML files generated by [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2){target="_blank"}. + +## Supported IIssue properties + +=== "GenericJUnitLogFileFormat" + +
+ + - [x] `IIssue.ProviderType` + - [x] `IIssue.ProviderName` + - [ ] `IIssue.Run` (1) + - [x] `IIssue.Identifier` (2) + - [ ] `IIssue.ProjectName` + - [ ] `IIssue.ProjectFileRelativePath` + - [x] `IIssue.AffectedFileRelativePath` (3) + - [x] `IIssue.Line` (4) + - [ ] `IIssue.EndLine` + - [x] `IIssue.Column` (5) + - [ ] `IIssue.EndColumn` + - [ ] `IIssue.FileLink` (6) + - [x] `IIssue.MessageText` + - [ ] `IIssue.MessageHtml` + - [ ] `IIssue.MessageMarkdown` + - [x] `IIssue.Priority` + - [x] `IIssue.PriorityName` + - [x] `IIssue.RuleId` (7) + - [ ] `IIssue.RuleUrl` + +
+ + 1. Can be set while reading issues + 2. Set to `IIssue.MessageText` + 3. Extracted from `classname` attribute or parsed from failure message content + 4. Extracted from failure message and content when available + 5. Extracted from failure message and content when available + 6. Can be set while reading issues + 7. Set to the failure `type` or `name` attribute when available + +=== "CppLintLogFileFormat" + +
+ + - [x] `IIssue.ProviderType` + - [x] `IIssue.ProviderName` + - [ ] `IIssue.Run` (1) + - [x] `IIssue.Identifier` (2) + - [ ] `IIssue.ProjectName` + - [ ] `IIssue.ProjectFileRelativePath` + - [x] `IIssue.AffectedFileRelativePath` (3) + - [x] `IIssue.Line` (4) + - [ ] `IIssue.EndLine` + - [x] `IIssue.Column` (5) + - [ ] `IIssue.EndColumn` + - [ ] `IIssue.FileLink` (6) + - [x] `IIssue.MessageText` + - [ ] `IIssue.MessageHtml` + - [ ] `IIssue.MessageMarkdown` + - [x] `IIssue.Priority` + - [x] `IIssue.PriorityName` + - [x] `IIssue.RuleId` (7) + - [ ] `IIssue.RuleUrl` + +
+ + 1. Can be set while reading issues + 2. Set to `IIssue.MessageText` + 3. Parsed from failure message content + 4. Extracted from failure content when available + 5. Extracted from failure content when available + 6. Can be set while reading issues + 7. Set to the failure `name` attribute + +=== "MarkdownlintCli2LogFileFormat" + +
+ + - [x] `IIssue.ProviderType` + - [x] `IIssue.ProviderName` + - [ ] `IIssue.Run` (1) + - [x] `IIssue.Identifier` (2) + - [ ] `IIssue.ProjectName` + - [ ] `IIssue.ProjectFileRelativePath` + - [x] `IIssue.AffectedFileRelativePath` (3) + - [x] `IIssue.Line` (4) + - [ ] `IIssue.EndLine` + - [x] `IIssue.Column` (5) + - [ ] `IIssue.EndColumn` + - [ ] `IIssue.FileLink` (6) + - [x] `IIssue.MessageText` + - [ ] `IIssue.MessageHtml` + - [ ] `IIssue.MessageMarkdown` + - [x] `IIssue.Priority` + - [x] `IIssue.PriorityName` + - [x] `IIssue.RuleId` (7) + - [ ] `IIssue.RuleUrl` + +
+ + 1. Can be set while reading issues + 2. Set to `IIssue.MessageText` + 3. Extracted from `classname` attribute + 4. Extracted from failure message and content when available + 5. Extracted from failure message and content when available + 6. Can be set while reading issues + 7. Set to the failure `name` attribute when available + +[JUnit]: https://github.com/testmoapp/junitxml diff --git a/docs/input/documentation/issue-providers/junit/index.md b/docs/input/documentation/issue-providers/junit/index.md new file mode 100644 index 000000000..4622a06b8 --- /dev/null +++ b/docs/input/documentation/issue-providers/junit/index.md @@ -0,0 +1,15 @@ +--- +title: JUnit issue provider +description: Issue provider which allows you to read issues from JUnit XML files. +status: new +--- + +Support for reading issues reported by tools that output JUnit XML format +is implemented in the [Cake.Issues.JUnit addin](https://cakebuild.net/extensions/cake-issues-junit/){target="_blank"}. + +
+ +- :material-creation-outline: [Features](features.md) +- :material-api: [API](https://cakebuild.net/extensions/cake-issues-junit){target="_blank"} + +
\ No newline at end of file diff --git a/docs/input/documentation/supported-tools.md b/docs/input/documentation/supported-tools.md index b32ea204f..6f3b1950f 100644 --- a/docs/input/documentation/supported-tools.md +++ b/docs/input/documentation/supported-tools.md @@ -37,6 +37,10 @@ This pages lists tools known to be working with Cake Issues (1) {{ read_csv('assets/tables/supported-tools-copypaste.csv',keep_default_na=False) }} +## C++ + +{{ read_csv('assets/tables/supported-tools-cpp.csv',keep_default_na=False) }} + ## CSS, SCSS, Sass {{ read_csv('assets/tables/supported-tools-css.csv',keep_default_na=False) }} @@ -45,6 +49,10 @@ This pages lists tools known to be working with Cake Issues (1) {{ read_csv('assets/tables/supported-tools-docker.csv',keep_default_na=False) }} +## Git + +{{ read_csv('assets/tables/supported-tools-git.csv',keep_default_na=False) }} + ## GitHub Actions {{ read_csv('assets/tables/supported-tools-github-actions.csv',keep_default_na=False) }} @@ -61,6 +69,10 @@ This pages lists tools known to be working with Cake Issues (1) {{ read_csv('assets/tables/supported-tools-helm.csv',keep_default_na=False) }} +## HTML + +{{ read_csv('assets/tables/supported-tools-html.csv',keep_default_na=False) }} + ## Java {{ read_csv('assets/tables/supported-tools-java.csv',keep_default_na=False) }} diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index baf07a5df..5196d286d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -220,6 +220,10 @@ nav: - Features: documentation/issue-providers/inspectcode/features.md - Examples: documentation/issue-providers/inspectcode/examples.md - API: https://cakebuild.net/extensions/cake-issues-inspectcode" target="_blank + - JUnit: + - documentation/issue-providers/junit/index.md + - Features: documentation/issue-providers/junit/features.md + - API: https://cakebuild.net/extensions/cake-issues-junit" target="_blank - markdownlint: - documentation/issue-providers/markdownlint/index.md - Features: documentation/issue-providers/markdownlint/features.md diff --git a/nuspec/nuget/Cake.Frosting.Issues.JUnit.md b/nuspec/nuget/Cake.Frosting.Issues.JUnit.md new file mode 100644 index 000000000..c25e4bad9 --- /dev/null +++ b/nuspec/nuget/Cake.Frosting.Issues.JUnit.md @@ -0,0 +1,80 @@ +# Support for reading JUnit XML files using the Cake.Issues addin for Cake Frosting + +> **NOTE**: +> This is the version of the addin compatible with [Cake Frosting]. +> For addin compatible with [Cake .NET Tool] see [Cake.Issues.JUnit](https://www.nuget.org/packages/Cake.Issues.JUnit). + +The JUnit XML support for the Cake.Issues addin for Cake allows you to read JUnit XML files from various linters and tools. + +Cake.Issues redefines issue management within the Cake build system by offering a comprehensive, universal, and extensible solution. +The unique capabilities of the addins empower development teams to enforce coding standards, generate insightful reports, +seamlessly incorporate various linting tools, and streamlining the integration with pull requests. +With its [modular architecture] and extensive [set of aliases], Cake.Issues provides a future-proof infrastructure for issue management +in Cake builds, fostering a more efficient and adaptable development process. + +For more information and extensive documentation see the [Cake.Issues website](https://cakeissues.net). +For general information about the Cake build automation system see the [Cake website](http://cakebuild.net). + +## How to use + +Integrating Cake.Issues into your Cake build is straightforward. +With minimal setup, teams can enjoy the benefits of enhanced code quality management seamlessly integrated into their existing build pipeline. + +After adding the addin the JUnit XML file can be parsed: + +```csharp +public class Program : FrostingHost +{ + public static int Main(string[] args) + { + return new CakeHost().UseContext().Run(args); + } +} + +[TaskName("Analyze")] +public sealed class AnalyzeTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var logPath = @"c:\build\junit-results.xml"; + var repoRootPath = @"c:\repo"; + + // Read issues. + var issues = + context.ReadIssues( + context.JUnitIssuesFromFilePath(logPath), + repoRootPath); + + context.Information($"{issues.Count()} issues are found."); + } +} +``` + +## Supported Tools + +The provider works with any tool that outputs standard JUnit XML, including: + +- **cpplint**: C++ linter +- **commitlint-format-junit**: Commit message linter +- **kubeconform**: Kubernetes manifest validator +- **htmlhint**: HTML linter with JUnit format support + +## Support & Discussion + +For questions and to discuss ideas & feature requests, use the [GitHub discussions on the Cake GitHub repository](https://github.com/cake-build/cake/discussions), under the [Extension Q&A](https://github.com/orgs/cake-build/discussions/categories/extension-q-a) category. + +## Contributing + +Contributions are welcome. See [Contribution Guidelines](https://github.com/cake-contrib/Cake.Issues/blob/develop/CONTRIBUTING.md). + +## License + +[MIT License - Copyright © Cake Issues contributors](LICENSE) + +Binary distributions for some addins contain third-party code which is licensed under its own respective license. +See [LICENSE](https://github.com/cake-contrib/Cake.Issues/blob/develop/LICENSE) for details. + +[modular architecture]: https://cakeissues.net/docs/fundamentals/architecture +[set of aliases]: https://cakeissues.net/dsl/ +[Cake Frosting]: https://cakebuild.net/docs/running-builds/runners/cake-frosting +[Cake .NET Tool]: https://cakebuild.net/docs/running-builds/runners/dotnet-tool \ No newline at end of file diff --git a/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec b/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec new file mode 100644 index 000000000..b5caa73df --- /dev/null +++ b/nuspec/nuget/Cake.Frosting.Issues.JUnit.nuspec @@ -0,0 +1,53 @@ + + + + Cake.Frosting.Issues.JUnit + Cake.Frosting.Issues.JUnit + 0.0.0 + Cake Issues contributors + pascalberger, cake-contrib + Support for reading JUnit XML files using the Cake.Issues addin for Cake Frosting + +The JUnit XML support for the Cake.Issues addin for Cake allows you to read JUnit XML files from various linters and tools. + +This addin provides the aliases for reading issues from JUnit XML files and providing them to the Cake.Issues addin. +It also requires the core Cake.Issues addin. + +The provider supports JUnit XML files from various tools including cpplint, commitlint-format-junit, kubeconform, and htmlhint. + +There are also additional addins for generating reports or posting issues to pull requests. + +See the Project Site for an overview of the whole ecosystem of addins for working with issues in Cake scripts. + +NOTE: +This is the version of the addin compatible with Cake Frosting. +For addin compatible with Cake Script Runners see Cake.Issues.JUnit. + + MIT + https://cakeissues.net + icon.png + false + + Copyright © Cake Issues contributors + cake cake-addin cake-issues cake-issueprovider linting junit xml + docs\README.md + https://github.com/cake-contrib/Cake.Issues/releases/tag/5.6.1 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nuspec/nuget/Cake.Frosting.Issues.JUnit.targets b/nuspec/nuget/Cake.Frosting.Issues.JUnit.targets new file mode 100644 index 000000000..c527182ae --- /dev/null +++ b/nuspec/nuget/Cake.Frosting.Issues.JUnit.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/nuspec/nuget/Cake.Issues.JUnit.md b/nuspec/nuget/Cake.Issues.JUnit.md new file mode 100644 index 000000000..c41267de7 --- /dev/null +++ b/nuspec/nuget/Cake.Issues.JUnit.md @@ -0,0 +1,68 @@ +# Support for reading JUnit XML files using the Cake.Issues addin for Cake Build Automation System + +> **NOTE**: +> This is the version of the addin compatible with [Cake .NET Tool]. +> For addin compatible with [Cake Frosting] see [Cake.Frosting.Issues.JUnit](https://www.nuget.org/packages/Cake.Frosting.Issues.JUnit). + +The JUnit XML support for the Cake.Issues addin for Cake allows you to read JUnit XML files from various linters and tools. + +Cake.Issues redefines issue management within the Cake build system by offering a comprehensive, universal, and extensible solution. +The unique capabilities of the addins empower development teams to enforce coding standards, generate insightful reports, +seamlessly incorporate various linting tools, and streamlining the integration with pull requests. +With its [modular architecture] and extensive [set of aliases], Cake.Issues provides a future-proof infrastructure for issue management +in Cake builds, fostering a more efficient and adaptable development process. + +For more information and extensive documentation see the [Cake.Issues website](https://cakeissues.net). +For general information about the Cake build automation system see the [Cake website](http://cakebuild.net). + +## How to use + +Integrating Cake.Issues into your Cake build is straightforward. +With minimal setup, teams can enjoy the benefits of enhanced code quality management seamlessly integrated into their existing build pipeline. + +After adding the addin the JUnit XML file can be parsed: + +```csharp +Task("Analyze").Does(() => +{ + var logPath = @"c:\build\junit-results.xml"; + var repoRootPath = @"c:\repo"; + + // Read issues. + var issues = + ReadIssues( + JUnitIssuesFromFilePath(logPath), + repoRootPath); + + Information("{0} issues are found.", issues.Count()); +}); +``` + +## Supported Tools + +The provider works with any tool that outputs standard JUnit XML, including: + +- **cpplint**: C++ linter +- **commitlint-format-junit**: Commit message linter +- **kubeconform**: Kubernetes manifest validator +- **htmlhint**: HTML linter with JUnit format support + +## Support & Discussion + +For questions and to discuss ideas & feature requests, use the [GitHub discussions on the Cake GitHub repository](https://github.com/cake-build/cake/discussions), under the [Extension Q&A](https://github.com/orgs/cake-build/discussions/categories/extension-q-a) category. + +## Contributing + +Contributions are welcome. See [Contribution Guidelines](https://github.com/cake-contrib/Cake.Issues/blob/develop/CONTRIBUTING.md). + +## License + +[MIT License - Copyright © Cake Issues contributors](LICENSE) + +Binary distributions for some addins contain third-party code which is licensed under its own respective license. +See [LICENSE](https://github.com/cake-contrib/Cake.Issues/blob/develop/LICENSE) for details. + +[modular architecture]: https://cakeissues.net/docs/fundamentals/architecture +[set of aliases]: https://cakeissues.net/dsl/ +[Cake Frosting]: https://cakebuild.net/docs/running-builds/runners/cake-frosting +[Cake .NET Tool]: https://cakebuild.net/docs/running-builds/runners/dotnet-tool \ No newline at end of file diff --git a/nuspec/nuget/Cake.Issues.JUnit.nuspec b/nuspec/nuget/Cake.Issues.JUnit.nuspec new file mode 100644 index 000000000..33d5086ac --- /dev/null +++ b/nuspec/nuget/Cake.Issues.JUnit.nuspec @@ -0,0 +1,47 @@ + + + + Cake.Issues.JUnit + Cake.Issues.JUnit + 0.0.0 + Cake Issues contributors + pascalberger, cake-contrib + Support for reading JUnit XML files using the Cake.Issues addin for Cake Build Automation System + +The JUnit XML support for the Cake.Issues addin for Cake allows you to read JUnit XML files from various linters and tools. + +This addin provides the aliases for reading issues from JUnit XML files and providing them to the Cake.Issues addin. +It also requires the core Cake.Issues addin. + +The provider supports JUnit XML files from various tools including cpplint, commitlint-format-junit, kubeconform, and htmlhint. + +There are also additional addins for generating reports or posting issues to pull requests. + +See the Project Site for an overview of the whole ecosystem of addins for working with issues in Cake scripts. + +NOTE: +This is the version of the addin compatible with Cake Script Runners. +For addin compatible with Cake Frosting see Cake.Frosting.Issues.JUnit. + + MIT + https://cakeissues.net + icon.png + false + + Copyright © Cake Issues contributors + cake cake-addin cake-issues cake-issueprovider linting junit xml + docs\README.md + https://github.com/cake-contrib/Cake.Issues/releases/tag/5.6.1 + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Cake.Issues.JUnit.Tests.csproj b/src/Cake.Issues.JUnit.Tests/Cake.Issues.JUnit.Tests.csproj new file mode 100644 index 000000000..28a16dbe2 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Cake.Issues.JUnit.Tests.csproj @@ -0,0 +1,17 @@ + + + Tests for the Cake.Issues.JUnit addin + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs new file mode 100644 index 000000000..d8578d523 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderFixture.cs @@ -0,0 +1,22 @@ +namespace Cake.Issues.JUnit.Tests; + +using Cake.Issues.Testing; + +internal class JUnitIssuesProviderFixture + : BaseMultiFormatIssueProviderFixture + where T : BaseJUnitLogFileFormat +{ + public JUnitIssuesProviderFixture(string fileResourceName) + : this(fileResourceName, @"c:\Source\Cake.Issues") + { + } + + public JUnitIssuesProviderFixture(string fileResourceName, string repositoryRoot) + : base(fileResourceName) + { + this.ReadIssuesSettings = + new ReadIssuesSettings(repositoryRoot); + } + + protected override string FileResourceNamespace => "Cake.Issues.JUnit.Tests.Testfiles."; +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs new file mode 100644 index 000000000..80ebe1eb1 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs @@ -0,0 +1,407 @@ +namespace Cake.Issues.JUnit.Tests; + +using Cake.Issues.JUnit.LogFileFormat; + +public sealed class JUnitIssuesProviderTests +{ + public sealed class TheCtor + { + [Fact] + public void Should_Throw_If_Log_Is_Null() + { + // Given / When + var result = Record.Exception(() => + new JUnitIssuesProvider( + null, + new JUnitIssuesSettings("Foo".ToByteArray(), new GenericJUnitLogFileFormat(new FakeLog())))); + + // Then + result.IsArgumentNullException("log"); + } + + [Fact] + public void Should_Throw_If_IssueProviderSettings_Are_Null() + { + // Given / When + var result = Record.Exception(() => new JUnitIssuesProvider(new FakeLog(), null)); + + // Then + result.IsArgumentNullException("issueProviderSettings"); + } + } + + public sealed class TheReadIssuesMethod + { + [Fact] + public void Should_Read_Issues_Correct_For_CppLint() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + var issue = issues[0]; + IssueChecker.Check( + issue, + IssueBuilder.NewIssue( + "Lines should be <= 80 characters long\nsrc/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"src/example.cpp", 15) + .OfRule("warning") + .WithPriority(IssuePriority.Error) + .Create()); + + issue = issues[1]; + IssueChecker.Check( + issue, + IssueBuilder.NewIssue( + "Include order issue\nsrc/example.cpp:5: #includes are not properly sorted [build/include_order] [4]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"src/example.cpp", 5) + .OfRule("warning") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_CppLint_Passed_Test() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-passed.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.ShouldBeEmpty(); + } + + [Fact] + public void Should_Handle_CppLint_Single_Error() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-single-error.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + var issue = issues.ShouldHaveSingleItem(); + + IssueChecker.Check( + issue, + IssueBuilder.NewIssue( + "ErrMsg1", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("errors") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_CppLint_Multiple_Errors() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-errors.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + var issue = issues.ShouldHaveSingleItem(); + + IssueChecker.Check( + issue, + IssueBuilder.NewIssue( + "ErrMsg1\nErrMsg2", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("errors") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_CppLint_Mixed_Error_And_Failure() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-mixed-error-failure.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "ErrMsg", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("errors") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "5: FailMsg [category/subcategory] [3]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile("File", 5) + .OfRule("File") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_CppLint_Multiple_Failures_Grouped_By_File() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-failures.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "5: FailMsg1 [category/subcategory] [3]\n19: FailMsg3 [category/subcategory] [3]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile("File1", 5) + .OfRule("File1") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "99: FailMsg2 [category/subcategory] [3]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile("File2", 99) + .OfRule("File2") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_CppLint_XML_Escaping() + { + // Given + var fixture = new JUnitIssuesProviderFixture("cpplint-xml-escaping.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "&", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("errors") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "5: & [category/subcategory] [3]", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile("File1", 5) + .OfRule("File1") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Read_Issues_Correct_For_Kubeconform() + { + // Given + var fixture = new JUnitIssuesProviderFixture("kubeconform.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(2); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "Invalid resource definition\ndeployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: \"\", expected non-empty string", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"deployment.yaml", 10, 15) + .OfRule("ValidationError") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "Port configuration invalid\nservice.yaml:8:5: Port 8080 is already in use by another service", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"service.yaml", 8, 5) + .OfRule("ConfigError") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Read_Issues_Correct_For_HtmlHint() + { + // Given + var fixture = new JUnitIssuesProviderFixture("htmlhint.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(1); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "Found 6 errors\nL2 |\n^ An lang attribute must be present on elements. (html-lang-require)\nL16 | \n^ An alt attribute must be present on elements. (alt-require)\nL14 |\n^
must be present in tag. (main-require)\nL3 |\n^ must be present in tag. (meta-charset-require)\nL3 |\n^ must be present in tag. (meta-description-require)\nL3 |\n^ must be present in tag. (meta-viewport-require)", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule(@"C:\temp\htmlhint\test.html") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Read_Issues_Correct_For_CommitLint() + { + // Given + var fixture = new JUnitIssuesProviderFixture("commitlint.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + var issue = issues.ShouldHaveSingleItem(); + + IssueChecker.Check( + issue, + IssueBuilder.NewIssue( + "type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] (type-enum)\n\nfoo: bar", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .OfRule("error") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Read_Issues_Correct_For_MarkdownlintCli2() + { + // Given + var fixture = new JUnitIssuesProviderFixture("markdownlint-cli2.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.Count.ShouldBe(5); + + IssueChecker.Check( + issues[0], + IssueBuilder.NewIssue( + "Trailing spaces\nLine 3, Column 10, Expected: 0 or 2; Actual: 1", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 3, 10) + .OfRule("MD009/no-trailing-spaces") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[1], + IssueBuilder.NewIssue( + "Multiple consecutive blank lines\nLine 5, Expected: 1; Actual: 2", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 5) + .OfRule("MD012/no-multiple-blanks") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[2], + IssueBuilder.NewIssue( + "Multiple top-level headings in the same document\nLine 6, Context: \"# Description\"", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 6) + .OfRule("MD025/single-title/single-h1") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[3], + IssueBuilder.NewIssue( + "Multiple spaces after hash on atx style heading\nLine 12, Column 1, Context: \"## Summary\"", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 12, 1) + .OfRule("MD019/no-multiple-space-atx") + .WithPriority(IssuePriority.Error) + .Create()); + + IssueChecker.Check( + issues[4], + IssueBuilder.NewIssue( + "Files should end with a single newline character\nLine 14, Column 14", + "Cake.Issues.JUnit.JUnitIssuesProvider", + "JUnit") + .InFile(@"viewme.md", 14, 14) + .OfRule("MD047/single-trailing-newline") + .WithPriority(IssuePriority.Error) + .Create()); + } + + [Fact] + public void Should_Handle_Empty_TestSuite() + { + // Given + var fixture = new JUnitIssuesProviderFixture("empty.xml"); + + // When + var issues = fixture.ReadIssues().ToList(); + + // Then + issues.ShouldBeEmpty(); + } + + [Fact] + public void Should_Handle_Invalid_XML() + { + // Given + var fixture = new JUnitIssuesProviderFixture("invalid.xml"); + + // When / Then + Should.Throw(() => fixture.ReadIssues().ToList()) + .Message.ShouldContain("Failed to parse JUnit XML"); + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs new file mode 100644 index 000000000..5fe0e9eb8 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/JUnitIssuesSettingsTests.cs @@ -0,0 +1,45 @@ +namespace Cake.Issues.JUnit.Tests; + +using Cake.Core.IO; +using Cake.Issues.JUnit.LogFileFormat; + +public sealed class JUnitIssuesSettingsTests +{ + public sealed class TheCtor + { + [Fact] + public void Should_Throw_If_LogFilePath_Is_Null() + { + // Given / When + var result = Record.Exception(() => + new JUnitIssuesSettings((FilePath)null, new GenericJUnitLogFileFormat(new FakeLog()))); + + // Then + result.IsArgumentNullException("logFilePath"); + } + + [Fact] + public void Should_Throw_If_LogFileContent_Is_Null() + { + // Given / When + var result = Record.Exception(() => + new JUnitIssuesSettings((byte[])null, new GenericJUnitLogFileFormat(new FakeLog()))); + + // Then + result.IsArgumentNullException("logFileContent"); + } + + [Fact] + public void Should_Set_LogFileContent() + { + // Given + var logFileContent = "foo".ToByteArray(); + + // When + var settings = new JUnitIssuesSettings(logFileContent, new GenericJUnitLogFileFormat(new FakeLog())); + + // Then + settings.LogFileContent.ShouldBe(logFileContent); + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml new file mode 100644 index 000000000..eace4439c --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/commitlint.xml @@ -0,0 +1,14 @@ + + + + + + + type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] (type-enum) + + foo: bar + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-mixed-error-failure.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-mixed-error-failure.xml new file mode 100644 index 000000000..bcd7fbc62 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-mixed-error-failure.xml @@ -0,0 +1,9 @@ + + + + ErrMsg + + + 5: FailMsg [category/subcategory] [3] + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-errors.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-errors.xml new file mode 100644 index 000000000..a7bdca34e --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-errors.xml @@ -0,0 +1,7 @@ + + + + ErrMsg1 +ErrMsg2 + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-failures.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-failures.xml new file mode 100644 index 000000000..718a45ae5 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-multiple-failures.xml @@ -0,0 +1,10 @@ + + + + 5: FailMsg1 [category/subcategory] [3] +19: FailMsg3 [category/subcategory] [3] + + + 99: FailMsg2 [category/subcategory] [3] + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-passed.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-passed.xml new file mode 100644 index 000000000..d45c77584 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-passed.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-single-error.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-single-error.xml new file mode 100644 index 000000000..21d95e7df --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-single-error.xml @@ -0,0 +1,6 @@ + + + + ErrMsg1 + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-xml-escaping.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-xml-escaping.xml new file mode 100644 index 000000000..49769bc8c --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint-xml-escaping.xml @@ -0,0 +1,9 @@ + + + + &</error> + + + 5: &</failure> [category/subcategory] [3] + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint.xml new file mode 100644 index 000000000..8c004c48e --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/cpplint.xml @@ -0,0 +1,14 @@ + + + + +src/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2] + + + + +src/example.cpp:5: #includes are not properly sorted [build/include_order] [4] + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml new file mode 100644 index 000000000..e96bc4a25 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/empty.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml new file mode 100644 index 000000000..c6dca1bc9 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/htmlhint.xml @@ -0,0 +1,17 @@ + + + + + ^ An lang attribute must be present on elements. (html-lang-require) +L16 | + ^ An alt attribute must be present on elements. (alt-require) +L14 | + ^
must be present in tag. (main-require) +L3 | + ^ must be present in tag. (meta-charset-require) +L3 | + ^ must be present in tag. (meta-description-require) +L3 | + ^ must be present in tag. (meta-viewport-require)]]> + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/invalid.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/invalid.xml new file mode 100644 index 000000000..2b5e4b035 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/invalid.xml @@ -0,0 +1,7 @@ + + + + +This is not valid XML content + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/kubeconform.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/kubeconform.xml new file mode 100644 index 000000000..048fdd11c --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/kubeconform.xml @@ -0,0 +1,17 @@ + + + + + +deployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: "", expected non-empty string + + + + +service.yaml:8:5: Port 8080 is already in use by another service + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/Testfiles/markdownlint-cli2.xml b/src/Cake.Issues.JUnit.Tests/Testfiles/markdownlint-cli2.xml new file mode 100644 index 000000000..4559d7e50 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/Testfiles/markdownlint-cli2.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit.Tests/packages.lock.json b/src/Cake.Issues.JUnit.Tests/packages.lock.json new file mode 100644 index 000000000..56fc71200 --- /dev/null +++ b/src/Cake.Issues.JUnit.Tests/packages.lock.json @@ -0,0 +1,545 @@ +{ + "version": 2, + "dependencies": { + "net8.0": { + "coverlet.msbuild": { + "type": "Direct", + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "Qa7Hg+wrOMDKpXVn2dw4Wlun490bIWsFW0fdNJQFJLZnbU27MCP0HJ2mPgS+3EQBQUb0zKlkwiQzP+j38Hc3Iw==" + }, + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.14.1, )", + "resolved": "17.14.1", + "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==", + "dependencies": { + "Microsoft.CodeCoverage": "17.14.1", + "Microsoft.TestPlatform.TestHost": "17.14.1" + } + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Shouldly": { + "type": "Direct", + "requested": "[4.3.0, )", + "resolved": "4.3.0", + "contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==", + "dependencies": { + "DiffEngine": "11.3.0", + "EmptyFiles": "4.4.0" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.4, )", + "resolved": "3.1.4", + "contentHash": "5mj99LvCqrq3CNi06xYdyIAXOEh+5b33F2nErCzI5zWiDdLHXiPXEWFSUAF8zlIv0ZWqjZNCwHTQeAPYbF3pCg==" + }, + "Xunit.SkippableFact": { + "type": "Direct", + "requested": "[1.5.23, )", + "resolved": "1.5.23", + "contentHash": "JlKobLTlsGcuJ8OtoodxL63bUagHSVBnF+oQ2GgnkwNqK+XYjeYyhQasULi5Ebx1MNDGNbOMplQYr89mR+nItQ==", + "dependencies": { + "Validation": "2.5.51", + "xunit.extensibility.execution": "2.4.0" + } + }, + "DiffEngine": { + "type": "Transitive", + "resolved": "11.3.0", + "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "dependencies": { + "EmptyFiles": "4.4.0", + "System.Management": "6.0.1" + } + }, + "EmptyFiles": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "7.0.4", + "contentHash": "yLEHlNN7O5WiND89r42sepgVwy5W/ZoTiFEdJDV7MHR1lW02LL7Nipz2TD5qM/Kx9W3/k3NP+PAP2qUdOm+leg==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==", + "dependencies": { + "System.Reflection.Metadata": "8.0.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.14.1", + "Newtonsoft.Json": "13.0.3" + } + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "dependencies": { + "System.CodeDom": "6.0.0" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "dependencies": { + "System.Collections.Immutable": "8.0.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "Validation": { + "type": "Transitive", + "resolved": "2.5.51", + "contentHash": "g/Aug7PVWaenlJ0QUyt/mEetngkQNsMCuNeRVXbcJED1nZS7JcK+GTU4kz3jcQ7bFuKfi8PF4ExXH7XSFNuSLQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "cake.issues": { + "type": "Project", + "dependencies": { + "Cake.Core": "[5.0.0, )" + } + }, + "cake.issues.junit": { + "type": "Project", + "dependencies": { + "Cake.Issues": "[1.0.0, )" + } + }, + "cake.issues.testing": { + "type": "Project", + "dependencies": { + "Cake.Issues": "[1.0.0, )", + "Cake.Testing": "[5.0.0, )" + } + }, + "Cake.Core": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Cake.Testing": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "oEERVvRww03yd54aFtbSFYc7w4xou9X1An8za8JVOM8JvOhp8mNqh53a4+ogJ9qVOgTSFzK/MvbVfZQNeJECjg==", + "dependencies": { + "Cake.Core": "5.0.0", + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.1, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + } + }, + "net9.0": { + "coverlet.msbuild": { + "type": "Direct", + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "Qa7Hg+wrOMDKpXVn2dw4Wlun490bIWsFW0fdNJQFJLZnbU27MCP0HJ2mPgS+3EQBQUb0zKlkwiQzP+j38Hc3Iw==" + }, + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.14.1, )", + "resolved": "17.14.1", + "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==", + "dependencies": { + "Microsoft.CodeCoverage": "17.14.1", + "Microsoft.TestPlatform.TestHost": "17.14.1" + } + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Shouldly": { + "type": "Direct", + "requested": "[4.3.0, )", + "resolved": "4.3.0", + "contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==", + "dependencies": { + "DiffEngine": "11.3.0", + "EmptyFiles": "4.4.0" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.4, )", + "resolved": "3.1.4", + "contentHash": "5mj99LvCqrq3CNi06xYdyIAXOEh+5b33F2nErCzI5zWiDdLHXiPXEWFSUAF8zlIv0ZWqjZNCwHTQeAPYbF3pCg==" + }, + "Xunit.SkippableFact": { + "type": "Direct", + "requested": "[1.5.23, )", + "resolved": "1.5.23", + "contentHash": "JlKobLTlsGcuJ8OtoodxL63bUagHSVBnF+oQ2GgnkwNqK+XYjeYyhQasULi5Ebx1MNDGNbOMplQYr89mR+nItQ==", + "dependencies": { + "Validation": "2.5.51", + "xunit.extensibility.execution": "2.4.0" + } + }, + "DiffEngine": { + "type": "Transitive", + "resolved": "11.3.0", + "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "dependencies": { + "EmptyFiles": "4.4.0", + "System.Management": "6.0.1" + } + }, + "EmptyFiles": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "7.0.4", + "contentHash": "yLEHlNN7O5WiND89r42sepgVwy5W/ZoTiFEdJDV7MHR1lW02LL7Nipz2TD5qM/Kx9W3/k3NP+PAP2qUdOm+leg==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==", + "dependencies": { + "System.Reflection.Metadata": "8.0.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.14.1", + "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.14.1", + "Newtonsoft.Json": "13.0.3" + } + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==" + }, + "System.Management": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "dependencies": { + "System.CodeDom": "6.0.0" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==", + "dependencies": { + "System.Collections.Immutable": "8.0.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "Validation": { + "type": "Transitive", + "resolved": "2.5.51", + "contentHash": "g/Aug7PVWaenlJ0QUyt/mEetngkQNsMCuNeRVXbcJED1nZS7JcK+GTU4kz3jcQ7bFuKfi8PF4ExXH7XSFNuSLQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "cake.issues": { + "type": "Project", + "dependencies": { + "Cake.Core": "[5.0.0, )" + } + }, + "cake.issues.junit": { + "type": "Project", + "dependencies": { + "Cake.Issues": "[1.0.0, )" + } + }, + "cake.issues.testing": { + "type": "Project", + "dependencies": { + "Cake.Issues": "[1.0.0, )", + "Cake.Testing": "[5.0.0, )" + } + }, + "Cake.Core": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Cake.Testing": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "oEERVvRww03yd54aFtbSFYc7w4xou9X1An8za8JVOM8JvOhp8mNqh53a4+ogJ9qVOgTSFzK/MvbVfZQNeJECjg==", + "dependencies": { + "Cake.Core": "5.0.0", + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Newtonsoft.Json": { + "type": "CentralTransitive", + "requested": "[13.0.1, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs b/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs new file mode 100644 index 000000000..f5a80d540 --- /dev/null +++ b/src/Cake.Issues.JUnit/BaseJUnitLogFileFormat.cs @@ -0,0 +1,150 @@ +namespace Cake.Issues.JUnit; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Cake.Core.Diagnostics; +using Cake.Core.IO; + +/// +/// Base class for all log file formats supported by the JUnit issue provider. +/// +/// The Cake log instance. +public abstract partial class BaseJUnitLogFileFormat(ICakeLog log) + : BaseLogFileFormat(log) +{ + /// + /// Validates a file path. + /// + /// Full file path. + /// Repository settings. + /// Tuple containing a value if validation was successful, and file path relative to repository root. + protected static (bool Valid, string FilePath) ValidateFilePath(string filePath, IRepositorySettings repositorySettings) + { + filePath.NotNullOrWhiteSpace(); + repositorySettings.NotNull(); + + if (!new FilePath(filePath).IsRelative) + { + // Ignore files from outside the repository. + if (!filePath.IsInRepository(repositorySettings)) + { + return (false, string.Empty); + } + + // Make path relative to repository root. + filePath = filePath.NormalizePath().MakeFilePathRelativeToRepositoryRoot(repositorySettings); + } + + return (true, filePath); + } + + /// + /// Normalizes XML content by removing XML formatting indentation while preserving intentional structure. + /// + /// The XML content to normalize. + /// The normalized content. + protected static string NormalizeXmlContent(string content) + { + if (string.IsNullOrEmpty(content)) + { + return string.Empty; + } + + // Split by lines, trim each line to remove XML indentation, then rejoin + var lines = content.Split(['\r', '\n'], StringSplitOptions.None); + var normalizedLines = new List(); + + foreach (var line in lines) + { + // Trim leading and trailing whitespace (including tabs) from each line + var trimmedLine = line.Trim(); + normalizedLines.Add(trimmedLine); + } + + // Join lines back together and clean up multiple consecutive empty lines + var result = string.Join("\n", normalizedLines); + + // Remove leading and trailing empty lines + result = result.Trim('\n'); + + // Normalize multiple consecutive newlines to double newlines maximum + result = DoubleNewlinesRegex().Replace(result, "\n\n"); + + return result; + } + + /// + /// Parses the JUnit XML document and extracts test suites. + /// + /// The XML log content to parse. + /// Collection of test suite elements. + protected static IEnumerable ParseJUnitXml(string logContent) + { + var doc = XDocument.Parse(logContent); + + // Handle both single testsuite and testsuites root elements + return doc.Root?.Name.LocalName == "testsuites" + ? doc.Root.Elements("testsuite") + : new[] { doc.Root }.Where(x => x?.Name.LocalName == "testsuite"); + } + + /// + /// Recursively processes a testsuite element and its nested testsuites and testcases. + /// + /// The testsuite element to process. + /// The list to add found issues to. + /// Repository settings. + /// Function to process failure elements. + /// Function to process error elements. + protected static void ProcessTestSuite( + XElement testSuite, + List result, + IRepositorySettings repositorySettings, + Func failureProcessor, + Func errorProcessor) + { + if (testSuite == null) + { + return; + } + + // Process direct testcase children + foreach (var testCase in testSuite.Elements("testcase")) + { + var className = testCase.Attribute("classname")?.Value ?? string.Empty; + var testName = testCase.Attribute("name")?.Value ?? string.Empty; + + // Process failures + foreach (var failure in testCase.Elements("failure")) + { + var issue = failureProcessor(failure, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + + // Process errors + foreach (var error in testCase.Elements("error")) + { + var issue = errorProcessor(error, className, testName, IssuePriority.Error, repositorySettings); + if (issue != null) + { + result.Add(issue); + } + } + } + + // Recursively process nested testsuite elements + foreach (var nestedTestSuite in testSuite.Elements("testsuite")) + { + ProcessTestSuite(nestedTestSuite, result, repositorySettings, failureProcessor, errorProcessor); + } + } + + [GeneratedRegex(@"\n{3,}")] + private static partial Regex DoubleNewlinesRegex(); +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/Cake.Issues.JUnit.csproj b/src/Cake.Issues.JUnit/Cake.Issues.JUnit.csproj new file mode 100644 index 000000000..2aa6fa1c5 --- /dev/null +++ b/src/Cake.Issues.JUnit/Cake.Issues.JUnit.csproj @@ -0,0 +1,9 @@ + + + JUnit support for the Cake.Issues Addin for Cake Build Automation System + + + + + + \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.CppLintLogFileFormats.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.CppLintLogFileFormats.cs new file mode 100644 index 000000000..ce55f7bad --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.CppLintLogFileFormats.cs @@ -0,0 +1,27 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Issues.JUnit.LogFileFormat; + +/// +/// Aliases for provider to read issues in JUnit file format generated by CppLint. +/// +public static partial class JUnitIssuesAliases +{ + /// + /// Gets an instance for the log format for cpplint JUnit XML output. + /// Optimized for cpplint's specific JUnit format where test case names represent file names. + /// + /// The context. + /// Instance for the cpplint JUnit XML format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static BaseJUnitLogFileFormat CppLintJUnitLogFileFormat( + this ICakeContext context) + { + context.NotNull(); + + return new CppLintLogFileFormat(context.Log); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.GenericLogFileFormats.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.GenericLogFileFormats.cs new file mode 100644 index 000000000..8e4ddb52e --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.GenericLogFileFormats.cs @@ -0,0 +1,27 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Issues.JUnit.LogFileFormat; + +/// +/// Aliases for provider to read issues in JUnit file format. +/// +public static partial class JUnitIssuesAliases +{ + /// + /// Gets an instance for the log format for any file compatible with JUnit XML format. + /// Does best effort parsing for any JUnit XML format. + /// + /// The context. + /// Instance for the generic JUnit XML format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static BaseJUnitLogFileFormat GenericJUnitLogFileFormat( + this ICakeContext context) + { + context.NotNull(); + + return new GenericJUnitLogFileFormat(context.Log); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.IssueProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.IssueProvider.cs new file mode 100644 index 000000000..0b5e51bb5 --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.IssueProvider.cs @@ -0,0 +1,131 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Core.IO; + +/// +/// Aliases to read issues from JUnit XML files. +/// +public static partial class JUnitIssuesAliases +{ + /// + /// Gets the name of the JUnit issue provider. + /// This name can be used to identify issues based on the property. + /// + /// The context. + /// Name of the JUnit issue provider. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static string JUnitIssuesProviderTypeName( + this ICakeContext context) + { + context.NotNull(); + + return JUnitIssuesProvider.ProviderTypeName; + } + + /// + /// Gets an instance of a provider for issues in a JUnit XML file from disk. + /// + /// The context. + /// Path to the JUnit XML file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided JUnit XML file. + /// Instance of a provider for issues in JUnit XML format. + /// + /// Read issues from a JUnit XML file: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssuesFromFilePath( + this ICakeContext context, + FilePath logFilePath, + BaseJUnitLogFileFormat format) + { + context.NotNull(); + logFilePath.NotNull(); + format.NotNull(); + + return context.JUnitIssues(new JUnitIssuesSettings(logFilePath, format)); + } + + /// + /// Gets an instance of a provider for issues in a JUnit XML file from memory. + /// + /// The context. + /// Content of the JUnit XML file. + /// The log file needs to be in the format as defined by the parameter. + /// Format of the provided JUnit XML file. + /// Instance of a provider for issues in JUnit XML format. + /// + /// Read issues from a JUnit XML file: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssuesFromContent( + this ICakeContext context, + string logFileContent, + BaseJUnitLogFileFormat format) + { + context.NotNull(); + logFileContent.NotNullOrWhiteSpace(); + format.NotNull(); + + return context.JUnitIssues(new JUnitIssuesSettings(logFileContent.ToByteArray(), format)); + } + + /// + /// Gets an instance of a provider for issues in a JUnit XML file using specified settings. + /// + /// The context. + /// Settings for reading the JUnit XML file. + /// Instance of a provider for issues in JUnit XML format. + /// + /// Read issues from a JUnit XML file: + /// + /// + /// + /// + [CakeMethodAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static IIssueProvider JUnitIssues( + this ICakeContext context, + JUnitIssuesSettings settings) + { + context.NotNull(); + settings.NotNull(); + + return new JUnitIssuesProvider(context.Log, settings); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.MarkdownlintLogFileFormats.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.MarkdownlintLogFileFormats.cs new file mode 100644 index 000000000..a1a574138 --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.MarkdownlintLogFileFormats.cs @@ -0,0 +1,27 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core; +using Cake.Core.Annotations; +using Cake.Issues.JUnit.LogFileFormat; + +/// +/// Aliases for provider to read issues in JUnit file format generated by markdownlint-cli2. +/// +public static partial class JUnitIssuesAliases +{ + /// + /// Gets an instance for the log format for markdownlint-cli2 JUnit XML output. + /// Optimized for markdownlint-cli2's specific JUnit format where file paths are in classname attributes. + /// + /// The context. + /// Instance for the markdownlint-cli2 JUnit XML format. + [CakePropertyAlias] + [CakeAliasCategory(IssuesAliasConstants.IssueProviderCakeAliasCategory)] + public static BaseJUnitLogFileFormat MarkdownlintCli2JUnitLogFileFormat( + this ICakeContext context) + { + context.NotNull(); + + return new MarkdownlintCli2LogFileFormat(context.Log); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs b/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs new file mode 100644 index 000000000..a39aa1040 --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesAliases.cs @@ -0,0 +1,11 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core.Annotations; + +/// +/// Contains functionality for reading issues from JUnit XML files. +/// +[CakeAliasCategory(IssuesAliasConstants.MainCakeAliasCategory)] +public static partial class JUnitIssuesAliases +{ +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs new file mode 100644 index 000000000..a559edba5 --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesProvider.cs @@ -0,0 +1,21 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core.Diagnostics; + +/// +/// Provider for issues in JUnit XML format. +/// +/// The Cake log context. +/// Settings for the issue provider. +public class JUnitIssuesProvider(ICakeLog log, JUnitIssuesSettings issueProviderSettings) + : BaseMultiFormatIssueProvider(log, issueProviderSettings) +{ + /// + /// Gets the name of the JUnit issue provider. + /// This name can be used to identify issues based on the property. + /// + public static string ProviderTypeName => typeof(JUnitIssuesProvider).FullName; + + /// + public override string ProviderName => "JUnit"; +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs b/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs new file mode 100644 index 000000000..e56c8440d --- /dev/null +++ b/src/Cake.Issues.JUnit/JUnitIssuesSettings.cs @@ -0,0 +1,33 @@ +namespace Cake.Issues.JUnit; + +using Cake.Core.IO; + +/// +/// Settings for . +/// +public class JUnitIssuesSettings : BaseMultiFormatIssueProviderSettings +{ + /// + /// Initializes a new instance of the class + /// for reading a JUnit log file on disk. + /// + /// Path to the JUnit log file. + /// The JUnit file needs to be in the format as defined by the parameter. + /// Format of the provided JUnit file. + public JUnitIssuesSettings(FilePath logFilePath, BaseJUnitLogFileFormat format) + : base(logFilePath, format) + { + } + + /// + /// Initializes a new instance of the class + /// for a JUnit log file content in memory. + /// + /// Content of the JUnit log file. + /// The JUnit file needs to be in the format as defined by the parameter. + /// Format of the provided JUnit file. + public JUnitIssuesSettings(byte[] logFileContent, BaseJUnitLogFileFormat format) + : base(logFileContent, format) + { + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs new file mode 100644 index 000000000..3470bbdee --- /dev/null +++ b/src/Cake.Issues.JUnit/LogFileFormat/CppLintLogFileFormat.cs @@ -0,0 +1,124 @@ +namespace Cake.Issues.JUnit.LogFileFormat; + +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Cake.Core.Diagnostics; + +/// +/// CppLint log file format for parsing JUnit XML files specifically from cpplint. +/// Optimized for cpplint's specific JUnit format where test case names represent file names. +/// +/// The Cake log instance. +internal partial class CppLintLogFileFormat(ICakeLog log) + : BaseJUnitLogFileFormat(log) +{ + /// + public override IEnumerable ReadIssues( + JUnitIssuesProvider issueProvider, + IRepositorySettings repositorySettings, + JUnitIssuesSettings junitIssuesSettings) + { + issueProvider.NotNull(); + repositorySettings.NotNull(); + junitIssuesSettings.NotNull(); + + var result = new List(); + + var logContent = junitIssuesSettings.LogFileContent.ToStringUsingEncoding(); + + try + { + var testSuites = ParseJUnitXml(logContent); + + foreach (var testSuite in testSuites) + { + if (testSuite == null) + { + continue; + } + + ProcessTestSuite(testSuite, result, repositorySettings, this.ProcessCppLintFailure, this.ProcessCppLintFailure); + } + } + catch (Exception ex) + { + throw new Exception($"Failed to parse JUnit XML: {ex.Message}", ex); + } + + return result; + } + + [GeneratedRegex(@"^(\d+):\s*.*\[.*\].*\[.*\]", RegexOptions.Multiline)] + private static partial Regex LineRegex(); + + [GeneratedRegex(@"^(\d+):", RegexOptions.Multiline)] + private static partial Regex SimpleLineRegex(); + + /// + /// Processes a cpplint test failure or error element and creates an issue. + /// + /// The failure or error XML element. + /// The test class name. + /// The test name. + /// The issue priority. + /// Repository settings. + /// The created issue or null if the failure should be ignored. + private IIssue ProcessCppLintFailure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) + { + var content = NormalizeXmlContent(failureElement.Value) ?? string.Empty; + + if (string.IsNullOrEmpty(content)) + { + return null; + } + + var issueBuilder = IssueBuilder + .NewIssue(content, typeof(JUnitIssuesProvider).FullName, "JUnit") + .WithPriority(priority); + + if (!string.IsNullOrEmpty(testName)) + { + issueBuilder = issueBuilder.OfRule(testName); + } + + // For cpplint-style output, if the test name looks like a file name, + // use the test name as the file name and try to extract line info from the message + if (!string.IsNullOrEmpty(testName)) + { + // Check if the message contains line info in cpplint format like "5: FailMsg [category/subcategory] [3]" + // This is a strong indicator that it's a cpplint-style failure where the test name is the file name + var lineMatch = LineRegex().Match(content); + if (lineMatch.Success && int.TryParse(lineMatch.Groups[1].Value, out var lineNum)) + { + var (valid, filePath) = ValidateFilePath(testName, repositorySettings); + if (valid) + { + issueBuilder = issueBuilder.InFile(filePath, lineNum, null); + } + } + + // Also check for simple line number pattern without the category/subcategory format + else + { + var simpleLineMatch = SimpleLineRegex().Match(content); + if (simpleLineMatch.Success && + int.TryParse(simpleLineMatch.Groups[1].Value, out var simpleLineNum)) + { + // Only treat as file if the test name doesn't contain hyphens (which are common in rule names) + if (!testName.Contains('-') && !testName.Contains('_')) + { + var (valid, filePath) = ValidateFilePath(testName, repositorySettings); + if (valid) + { + issueBuilder = issueBuilder.InFile(filePath, simpleLineNum, null); + } + } + } + } + } + + return issueBuilder.Create(); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs new file mode 100644 index 000000000..782a54188 --- /dev/null +++ b/src/Cake.Issues.JUnit/LogFileFormat/GenericJUnitLogFileFormat.cs @@ -0,0 +1,201 @@ +namespace Cake.Issues.JUnit.LogFileFormat; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Cake.Core.Diagnostics; + +/// +/// Generic log file format for parsing JUnit XML files. +/// Does best effort parsing for any JUnit XML format. +/// +/// The Cake log instance. +internal class GenericJUnitLogFileFormat(ICakeLog log) + : BaseJUnitLogFileFormat(log) +{ + /// + public override IEnumerable ReadIssues( + JUnitIssuesProvider issueProvider, + IRepositorySettings repositorySettings, + JUnitIssuesSettings junitIssuesSettings) + { + issueProvider.NotNull(); + repositorySettings.NotNull(); + junitIssuesSettings.NotNull(); + + var result = new List(); + + var logContent = junitIssuesSettings.LogFileContent.ToStringUsingEncoding(); + + try + { + var testSuites = ParseJUnitXml(logContent); + + foreach (var testSuite in testSuites) + { + if (testSuite == null) + { + continue; + } + + ProcessTestSuite(testSuite, result, repositorySettings, this.ProcessTestFailure, this.ProcessTestFailure); + } + } + catch (Exception ex) + { + throw new Exception($"Failed to parse JUnit XML: {ex.Message}", ex); + } + + return result; + } + + /// + /// Tries to extract file path from a class name. + /// + /// The class name to parse. + /// File information if the class name looks like a file path, null otherwise. + private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromClassName(string className) + { + if (string.IsNullOrEmpty(className)) + { + return null; + } + + // Some tools use file paths as class names + if (className.Contains('/') || className.Contains('\\')) + { + return (className, null, null); + } + + // Convert class names to potential file paths + if (className.Contains('.')) + { + // Java-style package.Class -> package/Class.java (but only if it looks like a real package) + var parts = className.Split('.'); + if (parts.Length > 1 && parts.All(p => !string.IsNullOrEmpty(p))) + { + var potentialPath = string.Join("/", parts) + ".java"; + return (potentialPath, null, null); + } + } + + return null; + } + + /// + /// Tries to extract file path and line information from output text. + /// + /// The output text to parse. + /// File information if found, null otherwise. + private static (string FilePath, int? Line, int? Column)? ExtractFileInfoFromOutput(string output) + { + if (string.IsNullOrEmpty(output)) + { + return null; + } + + // Common patterns for file paths and line numbers in linter output: + // file.txt:123:45: message + // file.txt(123,45): message + // file.txt line 123: message + // /path/to/file.txt:123: message + // file.txt:123 message + string[] patterns = + [ + @"([^\s:]+):(\d+):(\d+)", // file:line:column + @"([^\s:]+):(\d+)", // file:line + @"([^\s\(\)]+)\((\d+),(\d+)\)", // file(line,column) + @"([^\s\(\)]+)\((\d+)\)", // file(line) + @"^([^\s]+)\s+line\s+(\d+)", // file line 123 (must start at beginning of line) + @"File:\s*([^\s]+)", // File: path + ]; + + foreach (var pattern in patterns) + { + var match = Regex.Match(output, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline); + if (match.Success) + { + var filePath = match.Groups[1].Value.Trim(); + + // Skip if it looks like a URL or doesn't look like a file path + if (filePath.StartsWith("http", StringComparison.Ordinal) || filePath.StartsWith("www.", StringComparison.Ordinal)) + { + continue; + } + + int? line = null; + int? column = null; + + if (match.Groups.Count > 2 && int.TryParse(match.Groups[2].Value, out var lineNum)) + { + line = lineNum; + } + + if (match.Groups.Count > 3 && int.TryParse(match.Groups[3].Value, out var colNum)) + { + column = colNum; + } + + return (filePath, line, column); + } + } + + return null; + } + + /// + /// Processes a test failure or error element and creates an issue. + /// + /// The failure or error XML element. + /// The test class name. + /// The test name. + /// The issue priority. + /// Repository settings. + /// The created issue or null if the failure should be ignored. + private IIssue ProcessTestFailure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) + { + var message = failureElement.Attribute("message")?.Value ?? string.Empty; + var type = failureElement.Attribute("type")?.Value ?? string.Empty; + var content = NormalizeXmlContent(failureElement.Value) ?? string.Empty; + + // Combine message and content for full description + var fullMessage = string.IsNullOrEmpty(message) ? content : + string.IsNullOrEmpty(content) ? message : + $"{message}\n{content}"; + + if (string.IsNullOrEmpty(fullMessage)) + { + return null; + } + + var issueBuilder = IssueBuilder + .NewIssue(fullMessage, typeof(JUnitIssuesProvider).FullName, "JUnit") + .WithPriority(priority); + + if (!string.IsNullOrEmpty(type)) + { + issueBuilder = issueBuilder.OfRule(type); + } + else if (!string.IsNullOrEmpty(testName)) + { + issueBuilder = issueBuilder.OfRule(testName); + } + + // Try to extract file information + var fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className); + + if (fileInfo.HasValue) + { + var (filePath, line, column) = fileInfo.Value; + var (valid, validatedPath) = ValidateFilePath(filePath, repositorySettings); + if (valid) + { + issueBuilder = issueBuilder.InFile(validatedPath, line, column); + } + } + + return issueBuilder.Create(); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs new file mode 100644 index 000000000..01fc1da54 --- /dev/null +++ b/src/Cake.Issues.JUnit/LogFileFormat/MarkdownlintCli2LogFileFormat.cs @@ -0,0 +1,153 @@ +namespace Cake.Issues.JUnit.LogFileFormat; + +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Cake.Core.Diagnostics; + +/// +/// Markdownlint-cli2 log file format for parsing JUnit XML files specifically from markdownlint-cli2. +/// Optimized for markdownlint-cli2's specific JUnit format where file paths are in classname attributes. +/// +/// The Cake log instance. +internal class MarkdownlintCli2LogFileFormat(ICakeLog log) + : BaseJUnitLogFileFormat(log) +{ + /// + public override IEnumerable ReadIssues( + JUnitIssuesProvider issueProvider, + IRepositorySettings repositorySettings, + JUnitIssuesSettings junitIssuesSettings) + { + issueProvider.NotNull(); + repositorySettings.NotNull(); + junitIssuesSettings.NotNull(); + + var result = new List(); + + var logContent = junitIssuesSettings.LogFileContent.ToStringUsingEncoding(); + + try + { + var testSuites = ParseJUnitXml(logContent); + + foreach (var testSuite in testSuites) + { + if (testSuite == null) + { + continue; + } + + ProcessTestSuite(testSuite, result, repositorySettings, this.ProcessMarkdownlintCli2Failure, this.ProcessMarkdownlintCli2Failure); + } + } + catch (Exception ex) + { + throw new Exception($"Failed to parse JUnit XML: {ex.Message}", ex); + } + + return result; + } + + /// + /// Tries to extract line and column information from markdownlint-cli2 format text. + /// + /// The output text to parse. + /// Line and column information if found, null otherwise. + private static (int? Line, int? Column)? ExtractLineColumnFromMarkdownlintCli2(string output) + { + if (string.IsNullOrEmpty(output)) + { + return null; + } + + // Patterns for markdownlint-cli2 format: + // Line 3, Column 10, Expected: 0 or 2; Actual: 1 + // Line 5, Expected: 1; Actual: 2 + // Line 6, Context: "# Description" + string[] patterns = + [ + @"Line\s+(\d+),\s+Column\s+(\d+)", // Line 3, Column 10 + @"Line\s+(\d+)", // Line 5 + ]; + + foreach (var pattern in patterns) + { + var match = Regex.Match(output, pattern, RegexOptions.IgnoreCase); + if (match.Success) + { + int? line = null; + int? column = null; + + if (int.TryParse(match.Groups[1].Value, out var lineNum)) + { + line = lineNum; + } + + if (match.Groups.Count > 2 && int.TryParse(match.Groups[2].Value, out var colNum)) + { + column = colNum; + } + + return (line, column); + } + } + + return null; + } + + /// + /// Processes a markdownlint-cli2 test failure or error element and creates an issue. + /// + /// The failure or error XML element. + /// The test class name. + /// The test name. + /// The issue priority. + /// Repository settings. + /// The created issue or null if the failure should be ignored. + private IIssue ProcessMarkdownlintCli2Failure(XElement failureElement, string className, string testName, IssuePriority priority, IRepositorySettings repositorySettings) + { + var message = failureElement.Attribute("message")?.Value ?? string.Empty; + var content = NormalizeXmlContent(failureElement.Value) ?? string.Empty; + + // Combine message and content for full description + var fullMessage = string.IsNullOrEmpty(message) ? content : + string.IsNullOrEmpty(content) ? message : + $"{message}\n{content}"; + + if (string.IsNullOrEmpty(fullMessage)) + { + return null; + } + + var issueBuilder = IssueBuilder + .NewIssue(fullMessage, typeof(JUnitIssuesProvider).FullName, "JUnit") + .WithPriority(priority); + + // Use test name as rule (markdownlint-cli2 puts rule IDs like "MD009/no-trailing-spaces" in test names) + if (!string.IsNullOrEmpty(testName)) + { + issueBuilder = issueBuilder.OfRule(testName); + } + + // For markdownlint-cli2 style output, check if the content contains the specific format + // and use the class name as file path in that case + if (!string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(content)) + { + var lineColumnInfo = ExtractLineColumnFromMarkdownlintCli2(content); + if (lineColumnInfo.HasValue) + { + // This looks like markdownlint-cli2 format, use class name as file path + var (line, column) = lineColumnInfo.Value; + var (valid, filePath) = ValidateFilePath(className, repositorySettings); + if (valid) + { + issueBuilder = issueBuilder.InFile(filePath, line, column); + } + } + } + + return issueBuilder.Create(); + } +} \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/README.md b/src/Cake.Issues.JUnit/README.md new file mode 100644 index 000000000..63d19ab32 --- /dev/null +++ b/src/Cake.Issues.JUnit/README.md @@ -0,0 +1,63 @@ +# Cake.Issues.JUnit + +This provider supports reading issues from JUnit XML format, which is used by various linters and tools. + +## Supported Tools + +The JUnit issue provider can read issues from any tool that outputs JUnit XML format, including: + +- **cpplint**: C++ linter that can output JUnit format +- **commitlint-format-junit**: Commit message linter with JUnit output +- **kubeconform**: Kubernetes manifest validator with JUnit format +- **htmlhint**: HTML linter with JUnit format support +- Many other tools that support JUnit XML output + +## Features + +- Parses both single `testsuite` and `testsuites` root elements +- Extracts file paths from classname attributes or failure messages +- Supports multiple file path patterns: + - `file:line:column` (e.g., `src/file.cpp:15:5`) + - `file(line,column)` (e.g., `index.html(12,5)`) + - `file line number` (e.g., `about.html line 8`) +- Maps test failures and errors to Cake.Issues format +- Handles system-out sections for additional context + +## Usage + +```csharp +#addin nuget:?package=Cake.Issues&version=x.x.x +#addin nuget:?package=Cake.Issues.JUnit&version=x.x.x + +Task("ReadIssues") + .Does(() => +{ + var issues = ReadIssues( + JUnitIssuesFromFilePath(@"c:\build\junit-results.xml"), + @"c:\repo"); + + Information($"{issues.Count()} issues found"); +}); +``` + +## JUnit XML Format + +The provider expects standard JUnit XML format: + +```xml + + + + + Additional failure details with file:line:column information + + + +``` + +The provider will extract: +- File path from `classname` attribute or failure message +- Line/column numbers from failure message patterns +- Issue description from `message` attribute and failure content +- Rule name from failure `type` attribute or test `name` +- Priority based on failure vs error elements \ No newline at end of file diff --git a/src/Cake.Issues.JUnit/packages.lock.json b/src/Cake.Issues.JUnit/packages.lock.json new file mode 100644 index 000000000..4bf07773a --- /dev/null +++ b/src/Cake.Issues.JUnit/packages.lock.json @@ -0,0 +1,151 @@ +{ + "version": 2, + "dependencies": { + "net8.0": { + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.2.0-beta.556, )", + "resolved": "1.2.0-beta.556", + "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "7.0.4", + "contentHash": "yLEHlNN7O5WiND89r42sepgVwy5W/ZoTiFEdJDV7MHR1lW02LL7Nipz2TD5qM/Kx9W3/k3NP+PAP2qUdOm+leg==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "StyleCop.Analyzers.Unstable": { + "type": "Transitive", + "resolved": "1.2.0.556", + "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "cake.issues": { + "type": "Project", + "dependencies": { + "Cake.Core": "[5.0.0, )" + } + }, + "Cake.Core": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + } + }, + "net9.0": { + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "JajbvkrBgtdRghavIjcJuNHMOja4lqBmEezbhZyqWPYh2cpLhT5mPpfC7NQVDO4IehWQum9t/nwF4v+qQGtYWg==" + }, + "StyleCop.Analyzers": { + "type": "Direct", + "requested": "[1.2.0-beta.556, )", + "resolved": "1.2.0-beta.556", + "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.556" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "7.0.4", + "contentHash": "yLEHlNN7O5WiND89r42sepgVwy5W/ZoTiFEdJDV7MHR1lW02LL7Nipz2TD5qM/Kx9W3/k3NP+PAP2qUdOm+leg==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "StyleCop.Analyzers.Unstable": { + "type": "Transitive", + "resolved": "1.2.0.556", + "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "cake.issues": { + "type": "Project", + "dependencies": { + "Cake.Core": "[5.0.0, )" + } + }, + "Cake.Core": { + "type": "CentralTransitive", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "hq0HlI6bdRoMjUQTKioVjJZxQRxT7SuIjLjfTXO7fWe/alEU4OJumxq6LhTqz06pTRC7e5OrQqXyGKJnq5I+rw==", + "dependencies": { + "Microsoft.CSharp": "4.7.0", + "Microsoft.NETCore.Platforms": "7.0.4", + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.CSharp": { + "type": "CentralTransitive", + "requested": "[4.7.0, )", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.slnx b/src/Cake.Issues.slnx index 2bac60431..6afe5ee4b 100644 --- a/src/Cake.Issues.slnx +++ b/src/Cake.Issues.slnx @@ -86,6 +86,17 @@ + + + + + + + + + + + diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build.ps1 b/tests/Cake.Issues.JUnit/frosting/net8.0/build.ps1 new file mode 100644 index 000000000..478356d64 --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build.ps1 @@ -0,0 +1,13 @@ +$RECIPE_PACKAGE_PATH = "packages/cake.issues.reporting.console" +if (Test-Path $RECIPE_PACKAGE_PATH) +{ + Write-Host "Cleaning up cached version of $RECIPE_PACKAGE_PATH..." + Remove-Item $RECIPE_PACKAGE_PATH -Recurse; +} +else +{ + Write-Host "$RECIPE_PACKAGE_PATH not cached..." +} + +dotnet run --project build/Build.csproj -- $args +exit $LASTEXITCODE; \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build.sh b/tests/Cake.Issues.JUnit/frosting/net8.0/build.sh new file mode 100755 index 000000000..e0ec44460 --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build.sh @@ -0,0 +1,11 @@ + +$RECIPE_PACKAGE_PATH = "packages/cake.issues.reporting.console" +if [ -d "$RECIPE_PACKAGE_PATH" ] +then + echo "Cleaning up cached version of $RECIPE_PACKAGE_PATH..." + rm -Rf $RECIPE_PACKAGE_PATH +else + echo "$RECIPE_PACKAGE_PATH not cached..." +fi + +dotnet run --project ./build/Build.csproj -- "$@" diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build/.gitignore b/tests/Cake.Issues.JUnit/frosting/net8.0/build/.gitignore new file mode 100644 index 000000000..d9be55e6d --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build/.gitignore @@ -0,0 +1,12 @@ +BuildArtifacts/ +tools/ +.vs/ +.vscode/ + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Cake +tools +!tools/packages.config \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build/Build.csproj b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Build.csproj new file mode 100644 index 000000000..0b8e80956 --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Build.csproj @@ -0,0 +1,13 @@ + + + Exe + net8.0 + $(MSBuildProjectDirectory) + enable + + + + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs new file mode 100644 index 000000000..52c0d4089 --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/build/Program.cs @@ -0,0 +1,51 @@ +using Cake.Common.Diagnostics; +using Cake.Core; +using Cake.Core.IO; +using Cake.Frosting; + +public static class Program +{ + public static int Main(string[] args) + { + return new CakeHost() + .UseContext() + .Run(args); + } +} + +public class BuildContext : FrostingContext +{ + public BuildContext(ICakeContext context) + : base(context) + { + } +} + +[TaskName("Read-Issues")] +public sealed class ReadIssuesTask : FrostingTask +{ + public override void Run(BuildContext context) + { + var junitLogPath = @"../testdata/cpplint.xml"; + + var issues = context.ReadIssues( + context.JUnitIssuesFromFilePath(junitLogPath, context.CppLintJUnitLogFileFormat()), + @"../"); + + context.Information("Found {0} issues", issues.Count()); + + // Validate that we found expected issues + if (issues.Count() != 2) + { + throw new Exception($"Expected 2 issues but found {issues.Count()}"); + } + + context.Information("All validation checks passed!"); + } +} + +[TaskName("Default")] +[IsDependentOn(typeof(ReadIssuesTask))] +public class DefaultTask : FrostingTask +{ +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/global.json b/tests/Cake.Issues.JUnit/frosting/net8.0/global.json new file mode 100644 index 000000000..a7bc3e2bf --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "allowPrerelease": true, + "version": "8.0.412", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/nuget.config b/tests/Cake.Issues.JUnit/frosting/net8.0/nuget.config new file mode 100644 index 000000000..d4a4e547e --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/nuget.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/frosting/net8.0/testdata/cpplint.xml b/tests/Cake.Issues.JUnit/frosting/net8.0/testdata/cpplint.xml new file mode 100644 index 000000000..8c004c48e --- /dev/null +++ b/tests/Cake.Issues.JUnit/frosting/net8.0/testdata/cpplint.xml @@ -0,0 +1,14 @@ + + + + +src/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2] + + + + +src/example.cpp:5: #includes are not properly sorted [build/include_order] [4] + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/.config/dotnet-tools.json b/tests/Cake.Issues.JUnit/script-runner/net8.0/.config/dotnet-tools.json new file mode 100644 index 000000000..43e6f74ce --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "cake.tool": { + "version": "5.0.0", + "commands": [ + "dotnet-cake" + ] + } + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/.gitignore b/tests/Cake.Issues.JUnit/script-runner/net8.0/.gitignore new file mode 100644 index 000000000..e7c496290 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/.gitignore @@ -0,0 +1,379 @@ + +# Created by https://www.gitignore.io/api/cake,windows,visualstudio,visualstudiocode + +### Cake ### +tools/* +!tools/packages.config + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + + +# End of https://www.gitignore.io/api/cake,windows,visualstudio,visualstudiocode + + +# Project specific folders +BuildArtifacts diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake new file mode 100644 index 000000000..259e1e154 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.cake @@ -0,0 +1,57 @@ +#load "buildData.cake" + +#addin "Cake.Issues&prerelease" +#addin "Cake.Issues.JUnit&prerelease" + +////////////////////////////////////////////////// +// ARGUMENTS +////////////////////////////////////////////////// + +var target = Argument("target", "Default"); + +////////////////////////////////////////////////// +// SETUP / TEARDOWN +////////////////////////////////////////////////// + +Setup(setupContext => +{ + return new BuildData(); +}); + +var repoRootFolder = MakeAbsolute(Directory("./")); + +////////////////////////////////////////////////// +// TARGETS +////////////////////////////////////////////////// + +Task("ReadIssues") + .Does((data) => +{ + var junitLogPath = repoRootFolder.Combine("testdata").CombineWithFilePath("cpplint.xml"); + + data.AddIssues( + ReadIssues( + JUnitIssuesFromFilePath(junitLogPath, CppLintJUnitLogFileFormat), + repoRootFolder) + ); + + Information("Found {0} issues", data.Issues.Count()); + + // Validate that we found expected issues + if (data.Issues.Count() != 2) + { + throw new Exception($"Expected 2 issues but found {data.Issues.Count()}"); + } + + Information("All validation checks passed!"); +}); + +// Run ReadIssues task by default. +Task("Default") + .IsDependentOn("ReadIssues"); + +////////////////////////////////////////////////// +// EXECUTION +////////////////////////////////////////////////// + +RunTarget(target); \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/build.ps1 b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.ps1 new file mode 100644 index 000000000..fe6027689 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.ps1 @@ -0,0 +1,15 @@ +$ErrorActionPreference = 'Stop' + +$SCRIPT_NAME = "build.cake" + +Write-Host "Restoring .NET Core tools" +dotnet tool restore +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "Bootstrapping Cake" +dotnet cake $SCRIPT_NAME --bootstrap +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "Running Build" +dotnet cake $SCRIPT_NAME @args +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/build.sh b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.sh new file mode 100755 index 000000000..921a3241b --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash +SCRIPT_NAME="build.cake" + +echo "Restoring .NET Core tools" +dotnet tool restore + +echo "Bootstrapping Cake" +dotnet cake $SCRIPT_NAME --bootstrap + +echo "Running Build" +dotnet cake $SCRIPT_NAME "$@" \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/buildData.cake b/tests/Cake.Issues.JUnit/script-runner/net8.0/buildData.cake new file mode 100644 index 000000000..8a22d4145 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/buildData.cake @@ -0,0 +1,29 @@ +public class BuildData +{ + private readonly List issues = new List(); + + /// + /// Gets issues determined during building. + /// + public IEnumerable Issues + { + get + { + return issues.AsReadOnly(); + } + } + + /// + /// Add issues to . + /// + /// List of issues which should be added. + public void AddIssues(IEnumerable issues) + { + if (issues == null) + { + throw new NullReferenceException(nameof(issues)); + } + + this.issues.AddRange(issues); + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/global.json b/tests/Cake.Issues.JUnit/script-runner/net8.0/global.json new file mode 100644 index 000000000..a7bc3e2bf --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "allowPrerelease": true, + "version": "8.0.412", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/nuget.config b/tests/Cake.Issues.JUnit/script-runner/net8.0/nuget.config new file mode 100644 index 000000000..0daba1151 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/nuget.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net8.0/testdata/cpplint.xml b/tests/Cake.Issues.JUnit/script-runner/net8.0/testdata/cpplint.xml new file mode 100644 index 000000000..8c004c48e --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net8.0/testdata/cpplint.xml @@ -0,0 +1,14 @@ + + + + +src/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2] + + + + +src/example.cpp:5: #includes are not properly sorted [build/include_order] [4] + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/.config/dotnet-tools.json b/tests/Cake.Issues.JUnit/script-runner/net9.0/.config/dotnet-tools.json new file mode 100644 index 000000000..43e6f74ce --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "cake.tool": { + "version": "5.0.0", + "commands": [ + "dotnet-cake" + ] + } + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake new file mode 100644 index 000000000..259e1e154 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.cake @@ -0,0 +1,57 @@ +#load "buildData.cake" + +#addin "Cake.Issues&prerelease" +#addin "Cake.Issues.JUnit&prerelease" + +////////////////////////////////////////////////// +// ARGUMENTS +////////////////////////////////////////////////// + +var target = Argument("target", "Default"); + +////////////////////////////////////////////////// +// SETUP / TEARDOWN +////////////////////////////////////////////////// + +Setup(setupContext => +{ + return new BuildData(); +}); + +var repoRootFolder = MakeAbsolute(Directory("./")); + +////////////////////////////////////////////////// +// TARGETS +////////////////////////////////////////////////// + +Task("ReadIssues") + .Does((data) => +{ + var junitLogPath = repoRootFolder.Combine("testdata").CombineWithFilePath("cpplint.xml"); + + data.AddIssues( + ReadIssues( + JUnitIssuesFromFilePath(junitLogPath, CppLintJUnitLogFileFormat), + repoRootFolder) + ); + + Information("Found {0} issues", data.Issues.Count()); + + // Validate that we found expected issues + if (data.Issues.Count() != 2) + { + throw new Exception($"Expected 2 issues but found {data.Issues.Count()}"); + } + + Information("All validation checks passed!"); +}); + +// Run ReadIssues task by default. +Task("Default") + .IsDependentOn("ReadIssues"); + +////////////////////////////////////////////////// +// EXECUTION +////////////////////////////////////////////////// + +RunTarget(target); \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/build.ps1 b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.ps1 new file mode 100644 index 000000000..fe6027689 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.ps1 @@ -0,0 +1,15 @@ +$ErrorActionPreference = 'Stop' + +$SCRIPT_NAME = "build.cake" + +Write-Host "Restoring .NET Core tools" +dotnet tool restore +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "Bootstrapping Cake" +dotnet cake $SCRIPT_NAME --bootstrap +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Write-Host "Running Build" +dotnet cake $SCRIPT_NAME @args +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/build.sh b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.sh new file mode 100755 index 000000000..921a3241b --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash +SCRIPT_NAME="build.cake" + +echo "Restoring .NET Core tools" +dotnet tool restore + +echo "Bootstrapping Cake" +dotnet cake $SCRIPT_NAME --bootstrap + +echo "Running Build" +dotnet cake $SCRIPT_NAME "$@" \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/buildData.cake b/tests/Cake.Issues.JUnit/script-runner/net9.0/buildData.cake new file mode 100644 index 000000000..8a22d4145 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/buildData.cake @@ -0,0 +1,29 @@ +public class BuildData +{ + private readonly List issues = new List(); + + /// + /// Gets issues determined during building. + /// + public IEnumerable Issues + { + get + { + return issues.AsReadOnly(); + } + } + + /// + /// Add issues to . + /// + /// List of issues which should be added. + public void AddIssues(IEnumerable issues) + { + if (issues == null) + { + throw new NullReferenceException(nameof(issues)); + } + + this.issues.AddRange(issues); + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json b/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json new file mode 100644 index 000000000..a30898fd0 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "allowPrerelease": true, + "version": "9.0.303", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/nuget.config b/tests/Cake.Issues.JUnit/script-runner/net9.0/nuget.config new file mode 100644 index 000000000..0daba1151 --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/nuget.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Cake.Issues.JUnit/script-runner/net9.0/testdata/cpplint.xml b/tests/Cake.Issues.JUnit/script-runner/net9.0/testdata/cpplint.xml new file mode 100644 index 000000000..8c004c48e --- /dev/null +++ b/tests/Cake.Issues.JUnit/script-runner/net9.0/testdata/cpplint.xml @@ -0,0 +1,14 @@ + + + + +src/example.cpp:15: Lines should be <= 80 characters long [whitespace/line_length] [2] + + + + +src/example.cpp:5: #includes are not properly sorted [build/include_order] [4] + + + + \ No newline at end of file