danielpalme / ReportGenerator

ReportGenerator converts coverage reports generated by coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover, gcov or lcov into human readable reports in various formats.
https://reportgenerator.io
Apache License 2.0
2.59k stars 281 forks source link

Unable to process files when used with SourceLink #443

Closed mwwhited closed 2 years ago

mwwhited commented 3 years ago

Describe the bug When the csproj files reference SourceLinks the filename attributes in the coverage.cobertura.xml are generated to match the related source control system. This is causing the build to try to access the files a URL instead of a simple filepath which in may receive an internal server error 500.

To Reproduce Add the following to any of the csprog files in your referenced solution

  <ItemGroup>
    <PackageReference Include="Microsoft.SourceLink.AzureRepos.Git" Version="1.0.0" PrivateAssets="All"/>
  </ItemGroup>
mwwhited commented 3 years ago

I haven't worked with the other SourceLink versions so I don't know what it does to their URLs.. But for Azure DevOps (Microsoft.SourceLink.AzureRepos.Git) if you select all filename attributes that start with https:// //@filename[starts-with(.,'https://')] and take the path portion of the query string and combined it with the git repository root $(Build.SourcesDirectory) you will get the path to the source files. just updating these attributes to correct path will resolve these files correctly. I'm not sure if people would prefer to rewrite the coverage.cobertura.xml files or just handle this within the reportgenerator... but I can say that correcting these paths will allow the tool to work as is.

danielpalme commented 3 years ago

Thanks for reporting this issue. I will have a look within the next days.

danielpalme commented 3 years ago

I tried to reproduce your issue.

I used this sample project: https://github.com/danielpalme/coveragesample Then I added Microsoft.SourceLink.AzureRepos.Git to all projects and changed the origin url to an Azure DevOps repository.

I executed the tests with:

dotnet test --collect:"XPlat Code Coverage"

The coverage.cobertura.xml file still contains the local path.

What is different in your setup? Can you share a sample project?

mwwhited commented 3 years ago

That is really weird. I will have to take a look at trying to replicate this for you.

danielpalme commented 3 years ago

Have you been able to replicate the issue?

mwwhited commented 3 years ago

Unfortunately my project is in a private repository. It’s caused by using SourceLink for deploying symbols for private nuget packages into Azure DevOps Artifacts.

https://docs.microsoft.com/en-us/azure/devops/pipelines/artifacts/symbols?view=azure-devops

unless you have an azure devops pipeline I’m not sure how you can replicate the build.

mwwhited commented 3 years ago

I have a work around for me as I just wrote a tool to restore the paths before I the report generator runs. Might just have to mark this as can not replicate for now.

danielpalme commented 3 years ago

Setting up a DevOps pipeline would not be a problem. If you can share a (minimal) sample project with a YAML pipeline, I would have a look.

mwwhited commented 2 years ago

I am using Azure DevOps Artifacts for a private Nuget repository. Here is a sanitized version of our internal build script.

I also included a build script at the bottom that I use for local builds. Locally it has the same issue when trying to build the reports without the Internal.FixSourceLinks.Cli dotnet tool (this is what I use to rewrite the coverage.cobertura.xml to use local paths.

Yaml Pipeline


name: $(Date:yyyyMMdd)_$(Rev:rr)_CI

trigger:
  branches:
    include:
    - main

pool:
  vmImage: 'windows-latest'

stages:

- stage: stageBuild
  displayName: Build Module

  variables:
    solution: 'src/SharedFramework/SharedFramework.sln'
    buildConfiguration: 'Release'
    nugetRepository: 'https://pkgs.dev.azure.com/internal-project/_packaging/Internal.Nuget/nuget/v3/index.json'

  jobs:

  - job: jobBuildSharedFramework
    displayName: Build Shared Framework and Adapters
    dependsOn: []
    steps:

    - checkout: self
      clean: true
      persistCredentials: true

    - task: UseDotNet@2
      inputs:
        packageType: 'sdk'
        version: '3.1.x'        

    - task: gitversion/setup@0
      displayName: Install GitVersion
      inputs:
        versionSpec: '5.6.0'

    - task: gitversion/execute@0
      displayName: Use GitVersion
      inputs:
        useConfigFile: true
        configFilePath: 'GitVersion.yml'

    - script: |
              echo FullSemVer=$(fullSemVer)
              echo AssemblySemVer=$(AssemblySemVer)
              echo AssemblySemFileVer=$(assemblySemFileVer)
              echo InformationalVersion=$(InformationalVersion)
              echo ##vso[build.updatebuildnumber]$(fullSemVer)
              echo ##vso[task.setvariable variable=fullSemVer]$(fullSemVer)
              echo ##vso[task.setvariable variable=InformationalVersion]$(InformationalVersion)
              echo ##vso[task.setvariable variable=AssemblySemVer]$(AssemblySemVer)
              echo ##vso[task.setvariable variable=assemblySemFileVer]$(assemblySemFileVer)
              echo ##vso[task.setvariable variable=buildVersionTag]$(buildConfiguration)-v$(fullSemVer)
      displayName: Set Variables

    - task: NuGetAuthenticate@0
      displayName: 'NuGet Authenticate'

    - task: DotNetCoreCLI@2
      displayName: Dotnet Restore for API Services and Azure Functions
      inputs:
        command: restore
        projects: $(solution)
        feedsToUse: 'select'
        vstsFeed: 'INTERNAL-FEED-ID'

    - task: DotNetCoreCLI@2
      displayName: Dotnet Build for API Services and Azure Functions
      inputs:
        command: build
        projects: $(solution)
        arguments: '-c $(buildConfiguration) /p:Version=$(fullSemVer) --no-restore /p:SourceLinkCreate=true /p:ContinuousIntegrationBuild=true'

    - task: DotNetCoreCLI@2
      displayName: Run Unit Tests and Simulations as $(buildConfiguration)
      inputs:
        command: test
        projects: $(solution)
        arguments: '-c $(buildConfiguration) --no-build --no-restore --filter "TestCategory=Unit|TestCategory=Simulate" --collect:"XPlat Code Coverage" --nologo -s "$(Build.SourcesDirectory)\src\SharedFramework\.runsettings"'
        publishTestResults: true

    - task: DotNetCoreCLI@2
      displayName: Package to Staging directory
      inputs:
        command: custom
        custom: 'pack'
        projects: $(solution)
        arguments: '-c $(buildConfiguration) --no-build --no-restore -o $(Build.ArtifactStagingDirectory)/Packages /p:Version=$(fullSemVer)'
        verbosityRestore: Minimal
        verbosityPack: Minimal
        feedsToUse: select
        vstsFeed: personalnugetfeed
        nuGetFeedType: internal
        includeNuGetOrg: true

    - task: NuGetAuthenticate@0
      displayName: 'NuGet Authenticate again'

    - script: |
              dotnet nuget push --skip-duplicate --source $(nugetRepository) --api-key az $(Build.ArtifactStagingDirectory)\Packages\*.nupkg
      displayName: 'NuGet Push Packages'
      condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))

    - task: DotNetCoreCLI@2
      displayName: 'Install dotnet-reportgenerator-globaltool'
      inputs:
        command: custom
        custom: tool
        arguments: install --tool-path . dotnet-reportgenerator-globaltool

    - task: PublishTestResults@2
      displayName: 'Publish Test Results'
      condition: always()
      inputs:
        testRunner: VSTest
        testResultsFiles: '$(Agent.TempDirectory)/**/*.trx'
        testRunTitle: '$(fullSemVer)'
        failTaskOnFailedTests: true

    # The reportgenerator is not compatible with SourceLinked coverage reports.  This tool fixes the coverage.cobertura.xml so it is compatible with sourcelinked projects
    - script: | 
              dotnet tool install Internal.FixSourceLinks.Cli --add-source=$(Build.ArtifactStagingDirectory)/Packages --version $(Build.BuildNumber)
              dotnet fix-sourcelink --include=**\coverage.cobertura.xml --search=$(Agent.TempDirectory) --target=$(Build.SourcesDirectory)
      displayName: 'Fix up coverage.cobertura.xml to resolve SourceLinks'

    - script: 'reportgenerator "-reports:$(Agent.TempDirectory)/**/coverage.cobertura.xml" "-targetDir:$(Agent.TempDirectory)/TestResults/Coverage/Reports" -tag:$(Build.BuildNumber) -reportTypes:HtmlSummary;Cobertura "-title:Results for API Services & Azure Functions - $(buildConfiguration)-v$(Build.BuildNumber)"'
      displayName: 'Create Code Coverage Reports'

    - task: PublishCodeCoverageResults@1
      displayName: 'Publish Code Coverage Reports'
      inputs:
        codeCoverageTool: 'cobertura'
        summaryFileLocation: $(Agent.TempDirectory)/TestResults/Coverage/Reports/Cobertura.xml
        failIfCoverageEmpty: false 

    - task: PublishSymbols@2
      displayName: 'Publish Symbols'
      condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
      inputs:
        SearchPattern: '**/bin/**/*.pdb'
        IndexSources: false
        SymbolServerType: 'TeamServices'
        TreatNotIndexedAsWarning: true

    - task: tagBuildOrRelease@0
      displayName: 'Tag Build'
      condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
      inputs:
        type: 'Build'
        tags: '$(buildVersionTag)'

    - script: |
              git config --global user.name "AzureDevOps Agent"
              git tag "$(buildVersionTag)" --force
              git push origin "$(buildVersionTag)" --force
              echo "Tagging $(Build.Repository.Name) with $(buildVersionTag)"
      displayName: 'Tag Branch'
      condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))

.runsettings

<?xml version="1.0" encoding="utf-8"?>

<RunSettings>
    <RunConfiguration>
        <MaxCpuCount>10</MaxCpuCount>
    </RunConfiguration>

    <!--Configurations for data collectors-->
    <DataCollectionRunSettings>
        <DataCollectors>

            <DataCollector friendlyName="XPlat code coverage">
                <Configuration>
                    <!--Obsolete,GeneratedCodeAttribute,-->
                    <ExcludeByAttribute>ExcludeFromCodeCoverageAttribute</ExcludeByAttribute>

                    <Exclude>[coverlet.*.tests?]*,[*]Coverlet.Core*,[Internal.*.Contracts?]*,[Internal.*.Entities?]*</Exclude>

                    <Format>cobertura</Format>

                    <SingleHit>false</SingleHit>
                    <UseSourceLink>true</UseSourceLink>
                    <IncludeTestAssembly>false</IncludeTestAssembly>
                    <SkipAutoProps>true</SkipAutoProps>

                </Configuration>
            </DataCollector>
        </DataCollectors>
    </DataCollectionRunSettings>

    <InProcDataCollectionRunSettings>
        <InProcDataCollectors>
            <InProcDataCollector assemblyQualifiedName="Coverlet.Collector.DataCollection.CoverletInProcDataCollector, coverlet.collector, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null"
                           friendlyName="XPlat Code Coverage"
                           enabled="True"
                           codebase="coverlet.collector.dll" />
        </InProcDataCollectors>
    </InProcDataCollectionRunSettings>

    <LoggerRunSettings>
        <Loggers>
            <Logger friendlyName="console" enabled="True">
                <Configuration>
                    <Verbosity>normal</Verbosity>
                </Configuration>
            </Logger>
            <Logger friendlyName="trx" enabled="True">
            </Logger>
        </Loggers>
    </LoggerRunSettings>

    <!-- MSTest adapter -->
    <MSTest>
        <MapInconclusiveToFailed>false</MapInconclusiveToFailed>
        <CaptureTraceOutput>true</CaptureTraceOutput>
        <DeleteDeploymentDirectoryAfterTestRunIsComplete>false</DeleteDeploymentDirectoryAfterTestRunIsComplete>
        <DeploymentEnabled>true</DeploymentEnabled>
        <DeployTestSourceDependencies>true</DeployTestSourceDependencies>

    </MSTest>

</RunSettings>

Directory.Build.props

<Project>
  <PropertyGroup>
    <NoWarn>NU1603</NoWarn>

    <IsTestProject>$(ProjectName.EndsWith('.Tests'))</IsTestProject>
    <IsContractProject>$(ProjectName.EndsWith('.Contracts'))</IsContractProject>
  </PropertyGroup>

  <PropertyGroup Condition="'$(IsTestProject)'!='True'">
    <IsPackable>true</IsPackable>
    <PackAsTool>false</PackAsTool>

    <PublishRepositoryUrl>true</PublishRepositoryUrl>
    <EmbedAllSources>true</EmbedAllSources>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>

    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <GenerateDocumentationFile>false</GenerateDocumentationFile>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <Target Name="isNotTest" BeforeTargets="CoreBuild" Condition="'$(IsTestProject)'!='True'">
    <Message Text="===== Project: $(Configuration) - $(ProjectName)"  Importance="high"/>
  </Target>

  <ItemGroup Condition="'$(IsTestProject)'!='True'">
    <PackageReference Include="Microsoft.SourceLink.AzureRepos.Git" Version="1.1.0-beta-20204-02" PrivateAssets="All"/>
  </ItemGroup>
  <PropertyGroup Condition="'$(Configuration)'=='Debug'">
    <DebugType>full</DebugType>
    <DebugSymbols>true</DebugSymbols>
  </PropertyGroup>

  <ItemGroup>
    <Content Include="**\*.md" />
    <Content Include="**\*.plantuml" />
    <Content Include="**\*.puml" />

    <EmbeddedResource Include="**\*.txt" />
    <EmbeddedResource Include="**\*.json" />
    <EmbeddedResource Include="**\*.html" />
    <EmbeddedResource Include="**\*.csv" />
    <EmbeddedResource Include="**\*.sql" />

    <EmbeddedResource Remove="bin\**\*.*" />
    <EmbeddedResource Remove="obj\**\*.*" />
  </ItemGroup>

</Project>

GitVersion.yml

assembly-versioning-scheme: MajorMinorPatch
assembly-file-versioning-scheme: MajorMinorPatchTag
assembly-informational-format: '{CommitDate}-{FullSemVer}'
mode: Mainline
branches:
  master:
    regex: main
    increment: Patch

LocalTestRunner.bat

@echo off

SETLOCAL

SET TestProject=SharedFramework.sln
SET Configuration=Release

IF NOT "%1"=="" IF NOT "%TestFilter%"=="" SET TestFilter=

:check_again
IF "%1"=="" GOTO ready_to_run

SET temp_filter=%1
IF "%TestFilter%"=="" (
    SET TestFilter=TestCategory=%temp_filter%
) ELSE (
    SET TestFilter=%TestFilter%^|TestCategory=%temp_filter%
)

SHIFT
GOTO check_again

:ready_to_run
IF "%TestFilter%"=="" SET TestFilter=TestCategory=Unit^|TestCategory=Simulate

ECHO "%TestFilter%"

dotnet tool install --global dotnet-reportgenerator-globaltool 2>NUL
rmdir /s/q ".\TestResults"

dotnet test "%TestProject%" --configuration %Configuration% -r .\TestResults --nologo -s .runsettings --filter "%TestFilter%"

SET TEST_ERR=%ERRORLEVEL%

dotnet pack "Internal.FixSourceLinks.Cli" --configuration Release -o "Publish\Nuget"
dotnet tool install Internal.FixSourceLinks.Cli --add-source=Publish\Binary --version 1.0.0
dotnet fix-sourcelink --include=.\TestResults\**\coverage.cobertura.xml --target=..\..
dotnet tool uninstall Internal.FixSourceLinks.Cli

reportgenerator "-reports:.\TestResults\**\coverage.cobertura.xml" "-targetDir:.\TestResults\Coverage\Reports" -reportTypes:HtmlSummary;Cobertura "-title:%TestProject% - (%USERNAME%)"

start .\TestResults\Coverage\Reports\summary.html

ECHO TEST_ERR=%TEST_ERR%
IF "%TEST_ERR%"=="0" (
    ECHO "No Errors :)"
)
ENDLOCAL
danielpalme commented 2 years ago

I created a sample project and published it here: https://dev.azure.com/danielpalme/_git/SharedFramework

I'm not able to reproduce the problem, when I run LocalTestRunner.bat on my machine.

danielpalme commented 2 years ago

Closing this for now. Feel free to reopen if you can provide further information.