【问题标题】:Azure DevOps Code Coverage for last Commit上次提交的 Azure DevOps 代码覆盖率
【发布时间】:2019-08-01 10:21:16
【问题描述】:

我配置了一个自定义 PowerShell 任务来分析 Azure DevOps 存储库的代码覆盖率。步骤是:

  1. 搜索特定的测试程序集 (*Test.dll)
  2. 运行 coverlet 并通过测试程序集
  3. 证明$LASTEXITCODE 不等于2(覆盖率低于阈值)
  4. 如果$LASTEXITCODE 等于 2
    1. 运行 ReportGenerator 并使用被套 cobertura 摘要
    2. 向最后一个提交者发送带有报告的电子邮件(从 gi​​t git --git-dir=$git log -1 --format="%ae" 获取)

我遇到的问题: Commiter 对整个仓库的代码覆盖率不感兴趣,他想知道他的提交的覆盖率。

我想达到的目标: 如何检查提交是否包含测试程序集?我只想分析上次提交的测试程序集。

  1. 如果没有测试程序集:什么都不做
  2. 如果有测试程序集:只分析这个特定的程序集并告知开发人员他的代码覆盖率

PowerShell 脚本:

param([string]$Root, [int]$Threshold = 80, [string]$FromMail, [string]$Output = "Report", [string[]]$Include = @("*Tests.dll"), [string[]]$Exclude)

#VARIABLES
$format = "cobertura"                                   #FORMAT OF THE GENERATED COVERAGE REPORT (json [default]/lcov/opencover/cobertura/teamcity)
$thresholdType = "line"                                 #COVERAGE TYPE TO APPLY THE THRESHOLD TO (line/branch/method)
$coverletOutput = "cobertura.xml"                       #OUTPUT OF THE GENERATED COVERAGE REPORT
$reportTypes = "HtmlInline_AzurePipelines;Cobertura"    #THE OUTPUT FORMATS AND SCOPE (SEPARATED BY SEMICOLON) (Badges/Cobertura/CsvSummary/Html/HtmlChart/HtmlInline/HtmlInline_AzurePipelines/HtmlInline_AzurePipelines_Dark/HtmlSummary/Latex/LatexSummary/MHtml/PngChart/SonarQube/TeamCitySummary/TextSummary/Xml/XmlSummary)

#CODE COVERAGE SCRIPT
#-----------------------------------------------------------------------------------------------------#
##The script should analyze the code coverage of a test assembly and create a `.xml` report.
##Requeried tools: [coverlet](https://github.com/tonerdo/coverlet/blob/master/Documentation/GlobalTool.md), [ReportGenerator](https://automationrhapsody.com/code-coverage-manual-automated-tests-opencover-net-applications/), [git](https://git-scm.com/downloads)
##Root = is the directory where the script seeks recursively for files with `$Include` patterns
##Threshold = is the threshold of the code coverage that will accept the test
##FromMail = is the mail address from which the script should send the coverage warning
##Output = is the output path of the `.xml` report file
##Include = is a pattern list for the recursive search of test assemblies which should be included for the code coverage analysis (for instance `@("*Tests.dll", "*Unit.dll")`)
##Exclude = is a pattern list of subdirectories which should be excluded for the code coverage analysis (for instance `@("*\obj\*", "*\Release\*")`)
#-----------------------------------------------------------------------------------------------------#

#JOIN INCLUDE & EXCLUDE FOR PRINTS
$includeJoin = $($Include -join "', '")
$excludeJoin = $($Exclude -join "', '")

Write-Host "Root:`t`t$Root`nThreshold:`t$Threshold`nFromMail:`t$FromMail`nOutput:`t$Output`nInclude:`t'$includeJoin'`nExclude:`t'$excludeJoin'"

#CHECK ARGUMENTS
if ($Root -eq "" -or $Threshold -lt 0 -or $FromMail -eq "" -or $Output -eq "" -or $null -eq $Include) {
    Write-Host "##vso[task.logissue type=error;][ps1] error: missing root directory, coverage threshold, output directory or include pattern list of unit test .dll," -ForegroundColor Red
    exit(-1)
}
if ($null -eq $Exclude) { $Exclude = @() }

#CHECK VALID E-MAIL
try { $_ = new-object net.mail.mailaddress($FromMail) }
catch { Write-Host "##vso[task.logissue type=error;][ps1] error: invalid mail address '$FromMail'" -ForegroundColor Red; exit(-1) }

#CHECK COMMANDS
[string[]] $cmds = "coverlet", "reportgenerator", "git"
foreach ($cmd in $cmds) {
    if (Get-Command $cmd -errorAction SilentlyContinue) { Write-Host "[$cmd] path: '$($(Get-Command $cmd).Path)'" -ForegroundColor Green }
    else { Write-Host "##vso[task.logissue type=error;][$cmd] error: '$cmd' command not exist" -ForegroundColor Red; exit(-1) }
}

#SET $PWD
Set-Location -Path $Root

#FIND GIT REPOSITORY (FOR COMMIT & E-MAIL)
$git = Get-ChildItem $pwd -Include ".git" -Recurse -Directory -Force -ErrorAction SilentlyContinue | Select-Object -First 1
if ($null -eq $git) { Write-Host "##vso[task.logissue type=error;][git] error: missing repository in directory '$($pwd.Path)' and his subdirectories" -ForegroundColor Red; exit(-1) }

#SEARCH FOR $INCLUDE FILES IN $ROOT
Write-Host "[ps1] search directory: '$Root'" -ForegroundColor Yellow
Write-Host "[ps1] search include: '$includeJoin'" -ForegroundColor Yellow
$files = Get-ChildItem -Path $Root -Include $Include -Recurse -File -Name -ErrorAction SilentlyContinue

#SEARCH FOR $EXCLUDE IN $FILES
$Exclude | Where-Object { $ex = $_; $files = $files | Where-Object { $_ -notlike $ex } }
Write-Host "[ps1] search exclude: '$excludeJoin'" -ForegroundColor Yellow
Write-Host "[ps1] search results:" -ForegroundColor Yellow
$files | Where-Object { Write-Host "`t-$_" -ForegroundColor Gray }

#CHECK FILES FOUND
if ($files.Count -eq 0) { Write-Host "##vso[task.logissue type=error;][ps1] error: error: no files with include pattern '$includeJoin' found in '$Root'" -ForegroundColor Red; exit(-1) }

#START COVERLET
foreach ($file in $files) {
    Write-Host "[coverlet] analyse: '$file'" -ForegroundColor Yellow
    $path = '"{0}"' -f $file
    coverlet $path --target "dotnet" --targetargs "vstest $path --logger:trx" --format $format --threshold $Threshold --threshold-type $thresholdType --output $coverletOutput
    $exitCoverlet = $LASTEXITCODE
    Write-Host "[coverlet] exit code for '$file': $exitCoverlet" -ForegroundColor Yellow
    if ($exitCoverlet -ne 0) { break }
}

#COVERAGE IS TO LOW (2)
if ($exitCoverlet -eq 2) {

    #START REPORT GENERATOR
    reportgenerator -reports:$coverletOutput -reporttypes:$reportTypes -targetdir:$('"{0}"' -f $Output)
    $exitReportGenerator = $LASTEXITCODE
    Write-Host "[reportgenerator] exit code: $exitReportGenerator" -ForegroundColor Yellow

    #SEND MAIL
    $from = $FromMail
    $to = git --git-dir=$git log -1 --format="%ae"
    $attachments = Get-ChildItem -Path "$Output" -Filter *.htm -Recurse | ForEach-Object { $_.FullName }
    $index = Get-ChildItem -Path "$Output" -Filter index.htm -Recurse | ForEach-Object { $_.FullName }
    $commit = git --git-dir=$git log -p $git -1 --pretty=%B
    $subject = "Code Coverage in Commit '$commit'"
    $body = "The code coverage of your commit '$commit' is under the threshold of $Threshold %.<br>Show attachments for more details.<br><br>" + $(Get-Content $index)
    $smtpServer = "smtp.server.de"
    $smtpPort = "25"
    Write-Output "##vso[task.logissue type=warning;][ps1] code coverage is to low, send mail to: $to"
    Send-MailMessage -From $from -to $to -Subject $subject -Body $body -BodyAsHtml -SmtpServer $smtpServer -port $smtpPort -Attachments $attachments
}

Azure DevOps 服务器版本:17.143.28912.1 (AzureDevOps2019.0.1)

代理:Self-Hosted Agent vsts-agent-win-x64-2.144.2


编辑:提交的测试程序集的代码覆盖率

我使用以下 sn-p 步骤修改我的第一个脚本:

  1. 从上次git commit中读出所有更改的文件
  2. 使用过滤器*Test*搜索所有项目文件(*csproj*vbproj
  3. 检查项目文件是否包含更改的文件
  4. 删除.proj 扩展名,将其替换为.dll
  5. 使用来自用户的给定 $OutputAssembly (bin\Release) 创建程序集路径

截图:

#GET THE LAST COMMITED FILES
$commitedFiles = git --git-dir=$GitPath diff-tree --no-commit-id --name-only -r $lastCommit

#SEARCH FOR PROJECT FILES IN $PWD WITH FILTER
$Filter = "*Test*"
$projs = Get-ChildItem -Path $pwd -Recurse -Filter $Filter -Include @("*csproj", "*vbproj")

#SEARCH FOR $EXCLUDE IN $FILES
$Exclude = @("*\obj\*")
$Exclude | Where-Object { $ex = $_; $projs = $projs | Where-Object { $_ -notlike $ex } }
Write-Host "[ps1] search exclude: '$excludeJoin'" -ForegroundColor Yellow
Write-Host "[ps1] search results:" -ForegroundColor Yellow
$projs | Where-Object { Write-Host "`t-$_" -ForegroundColor Gray }

#CHECK PROJECT FILES FOUND
if ($projs.Count -eq 0) { Write-Host "##vso[task.logissue type=error;][ps1] error: error: no projects with filter '$Filter' and include pattern '$includeJoin' found in '$Root'" -ForegroundColor Red; exit(-1) }

#ASSEMBLIES LIST
$assemblies = @()

#LOOP ALL .PROJ FILES
foreach ( $proj in $projs ) {

    #LOOP ALL LINES IN .PROJ FILE
    foreach ( $line in (Get-Content $proj) ) { 
        if ( $line -match 'Compile\s+Include="([^"]+)"' ) {

            #COMPILED FILE IN .PROJ
            $file = Split-Path $matches[1] -Leaf

            #LOOP ALL COMMITED FILES
            foreach($commitedFile in $commitedFiles){

                #GET FILE NAME
                $name = Split-Path $commitedFile -Leaf

                #ADD ASSEMBLY BASED ON .PROJ BASENAME
                if($name -eq $file) { $assemblies += $proj.BaseName + ".dll" }
            }     
        }
    }
}

#FEEDBACK CHANGED ASSEMBLIES
Write-Host "[ps1] changed assemblies:" -ForegroundColor Yellow
$assemblies | Where-Object { Write-Host "`t-$_" -ForegroundColor Gray }

#LOOP ALL ASSEMBLIES
$OutputAssembly = "bin\Release"
foreach ($assembly in $assemblies){

    $path = [IO.Path]::Combine($Root , $OutputAssembly, $assembly)

    #CHECK ASSEMBLY PATH
    if (-not (Test-Path -Path $path)) { 
        Write-Host "##vso[task.logissue type=warning;][ps1] warning: missing assembly '$assembly' at: '$path'" -ForegroundColor Yellow;
    }
    else {

        #START COVERLET
    }
}

【问题讨论】:

    标签: powershell azure-devops azure-pipelines


    【解决方案1】:

    如果我能很好地理解这个问题,你可以这样做:

    # Get the last commit SHA1
    $lastCommit = "$(Build.SourceVersion)"
    
    # Get the last commit files
    $files = git diff-tree --no-commit-id --name-only -r $lastCommit
    
    if($files -match "Test.cs")
    {
         # Do something...
    }
    else
    {
         # Do something else...
    }
    

    因为通常如果你有Test.dll,那么源代码应该是Test.cs

    【讨论】:

    • 事情变得更加复杂,正如我一开始所想的那样。原则上您的建议有效,我修改了我的问题。通常程序集名称写在AssemblyInfo.cs 文件中(对于.csproj)。但在大多数情况下,*csproj 等同于程序集名称。所以目标是检查哪个 *csproj 包含已提交的文件。这可以检查测试程序集的代码覆盖率,但我不确定是否也检查书面生产代码的代码覆盖率。但我认为,这需要System.Reflection 来解决。到目前为止,谢谢。
    猜你喜欢
    • 2020-07-08
    • 2022-01-12
    • 2021-05-25
    • 1970-01-01
    • 2021-08-13
    • 2019-07-04
    • 2021-02-11
    • 2020-04-29
    • 2020-11-25
    相关资源
    最近更新 更多