blob: 791f7e9df57e71fe0e6f3daf8cd9805e6563550a [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Downloads test binaries and executes tests using dotnet vstest,
# runs the tests for each project on a background job in parallel,
# then uploads the results to Azure DevOps pipelines
parameters:
osName: 'Windows' # The name of the operating system for display purposes.
framework: '' # The target framework indicating which framework tests will be run on. See: https://docs.microsoft.com/en-us/dotnet/standard/frameworks.
binaryArtifactName: 'testbinaries' # The name of the Azure DevOps build artifact where the test assemblies will be downloaded from. Default 'testbinaries'.
testResultsArtifactName: 'testresults' # The name of the Azure DevOps build artifact where the test results will be published. Default 'testresults'.
vsTestPlatform: 'x64' # Target platform architecture used for test execution. Valid values are x86, x64, and ARM.
testBinaryFilesPattern: '\.*\.Tests\.?[^\\/]*?\.?[^\\/]*?.dll$' # The regex pattern (within $(System.DefaultWorkingDirectory)/**/<TargetFramework>/) where to look for test .dll files, so they can be distinguished from other .dll file types.
testResultsFileName: 'TestResults.trx' # The name of the file (not path) of the test results. Default 'TestResults.trx'.
maximumParallelJobs: 8
maximumAllowedFailures: 0
where: '' # A test filter expression, as defined by dotnet vstest
steps:
- checkout: none # self represents the repo where the initial Pipelines YAML file was found
- powershell: |
function EnsureNotNullOrEmpty([string]$param, [string]$nameOfParam) {
if ([string]::IsNullOrEmpty($param)) {
Write-Host "##vso[task.logissue type=error;]Missing template parameter \"$nameOfParam\""
Write-Host "##vso[task.complete result=Failed;]"
}
}
EnsureNotNullOrEmpty('${{ parameters.osName }}', 'osName')
EnsureNotNullOrEmpty('${{ parameters.framework }}', 'framework')
EnsureNotNullOrEmpty('${{ parameters.binaryArtifactName }}', 'binaryArtifactName')
EnsureNotNullOrEmpty('${{ parameters.testResultsArtifactName }}', 'testResultsArtifactName')
EnsureNotNullOrEmpty('${{ parameters.vsTestPlatform }}', 'vsTestPlatform')
EnsureNotNullOrEmpty('${{ parameters.testBinaryFilesPattern }}', 'testBinaryFilesPattern')
EnsureNotNullOrEmpty('${{ parameters.testResultsFileName }}', 'testResultsFileName')
EnsureNotNullOrEmpty('${{ parameters.maximumParallelJobs }}', 'maximumParallelJobs')
EnsureNotNullOrEmpty('${{ parameters.maximumAllowedFailures }}', 'maximumAllowedFailures')
displayName: 'Validate Template Parameters'
- task: DownloadPipelineArtifact@2
displayName: 'Download Build Artifacts: ${{ parameters.binaryArtifactName }} to $(System.DefaultWorkingDirectory)\$(parameters.framework)'
inputs:
artifactName: '${{ parameters.binaryArtifactName }}_${{ parameters.framework }}'
targetPath: '$(System.DefaultWorkingDirectory)/${{ parameters.framework }}'
- powershell: |
Get-ChildItem -Path $(System.DefaultWorkingDirectory)
- task: UseDotNet@2
displayName: 'Use .NET Core sdk 2.1.807'
inputs:
version: 2.1.807
condition: and(succeeded(), contains('${{ parameters.framework }}', 'netcoreapp2.'))
- task: UseDotNet@2
displayName: 'Use .NET Core sdk 3.1.301'
inputs:
version: 3.1.301
condition: and(succeeded(), contains('${{ parameters.framework }}', 'netcoreapp3.'))
- task: UseDotNet@2
displayName: 'Use .NET sdk 5.0.100'
inputs:
version: 5.0.100
#- template: 'show-all-files.yml' # Uncomment for debugging
- powershell: |
$framework = '${{ parameters.framework }}'
$testBinaryRootDirectory = "$(System.DefaultWorkingDirectory)"
$testResultsArtifactDirectory = "${{ format('$(Build.ArtifactStagingDirectory)/{0}',parameters.testResultsArtifactName) }}"
$testPlatform = '${{ parameters.vsTestPlatform }}'
$testOSName = '${{ parameters.osName }}'
$testBinaryFilesPattern = '${{ parameters.testBinaryFilesPattern }}'
$testResultsFileName = '${{ parameters.testResultsFileName }}'
$maximumParalellJobs = '${{ parameters.maximumParallelJobs }}'
$where = '${{ parameters.where }}'
function SeparateVersionDigits([string]$digits) {
return (&{ for ($i = 0;$i -lt $digits.Length;$i++) { $digits.Substring($i,1) }}) -join '.'
}
# Convert $framework (i.e. net461) into format for dotnet vstest (i.e. .NETFramework,Version=4.6.1)
function ConvertFrameworkName([string]$framework) {
$match = [regex]::Match($framework, '^net(4\d+)$') # .NET Framework 4+
if ($match.Success) {
$ver = SeparateVersionDigits($match.Groups[1].Value)
return ".NETFramework,Version=v$($ver)"
}
$match = [regex]::Match($framework, '^net(?:coreapp)?(\d+\.\d+(?:\.\d+)?)$') # .NET 5 / .NET Core
if ($match.Success) {
$ver = $match.Groups[1].Value
return ".NETCoreApp,Version=v$($ver)"
}
$match = [regex]::Match($framework, '^uap(\d+\.\d+)?$') # Universal Windows Platform
if ($match.Success) {
$ver = $match.Groups[1].Value
$ver = if ([string]::IsNullOrEmpty($ver)) { '10' } else { $ver.Replace('.0','').Replace('.','') }
return "FrameworkUap$($ver)"
}
return $framework
}
function IsSupportedFramework([string]$framework) {
if ($IsWindows -eq $null) {
$IsWindows = $env:OS.StartsWith('Win')
}
if (!$IsWindows -and $framework.StartsWith('net4')) {
return $false
}
return $true
}
function RunTests([string]$framework, [string]$fileRegexPattern) {
if (!(IsSupportedFramework($framework))) { continue }
$testBinaries = Get-ChildItem -Path "$testBinaryRootDirectory" -File -Recurse | Where-Object {$_.FullName -match "$framework"} | Where-Object {$_.FullName -match "$fileRegexPattern"} | Sort-Object -Property FullName
Write-Host $testBinaries
foreach ($testBinary in $testBinaries) {
$testName = [System.IO.Path]::GetFileNameWithoutExtension($testBinary.FullName)
if ($maximumParalellJobs -gt 1) {
# Pause if we have queued too many parallel jobs
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -ge $maximumParalellJobs) {
Write-Host ""
Write-Host " Running tests in parallel on $($running.Count) projects." -ForegroundColor Cyan
Write-Host " Next in queue is $testName on $framework. This will take a bit, please wait..." -ForegroundColor Cyan
$running | Wait-Job -Any | Out-Null
}
}
$fwork = ConvertFrameworkName($framework)
$testResultDirectory = "$testResultsArtifactDirectory/$testOSName/$framework/$testPlatform/$testName"
if (!(Test-Path "$testResultDirectory")) {
New-Item "$testResultDirectory" -ItemType Directory -Force
}
$testExpression = "dotnet vstest ""$($testBinary.FullName)"" --Framework:""$fwork"" --Platform:""$testPlatform""" + `
" --logger:""console;verbosity=normal"" --logger:""trx;LogFileName=$testResultsFileName""" + `
" --ResultsDirectory:""$testResultDirectory"" --Blame"
if (![string]::IsNullOrEmpty($where)) {
$testExpression = "$testExpression --TestCaseFilter:""$where"""
}
Write-Host "Testing '$($testBinary.FullName)' on framework '$fwork' and outputting test results to '$testResultDirectory/$testResultsFileName'..."
Write-Host $testExpression -ForegroundColor Magenta
if ($maximumParalellJobs -le 1) {
Invoke-Expression $testExpression # For running in the foreground
} else {
$testExpression += " > ""$testResultDirectory/dotnet-vstest.log"" 2> ""$testResultDirectory/dotnet-vstest-error.log"""
$scriptBlock = {
param([string]$testExpression)
Invoke-Expression $testExpression
}
# Avoid dotnet vstest collisions by delaying for 500ms
Start-Sleep -Milliseconds 500
# Execute the jobs in parallel
Start-Job -Name "$testName,$framework,$testPlatform" -ScriptBlock $scriptBlock -ArgumentList $testExpression
}
}
}
RunTests -Framework "$framework" -FileRegexPattern "$testBinaryFilesPattern"
if ($maximumParalellJobs -gt 1) {
# Wait for it all to complete
do {
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -gt 0) {
Write-Host ""
Write-Host " Almost finished, only $($running.Count) projects left..." -ForegroundColor Cyan
[int]$number = 0
foreach ($runningJob in $running) {
$number++
$jobName = $runningJob | Select -ExpandProperty Name
Write-Host "$number. $jobName"
}
$running | Wait-Job -Any
}
} until ($running.Count -eq 0)
}
$global:LASTEXITCODE = 0 # Force the script to continue on error
displayName: 'dotnet vstest ${{ parameters.framework }},${{ parameters.vsTestPlatform }}'
ignoreLASTEXITCODE: true
#- template: 'show-all-files.yml' # Uncomment for debugging
- task: PublishPipelineArtifact@1
displayName: 'Publish Artifact: ${{ parameters.testResultsArtifactName }}'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/${{ parameters.testResultsArtifactName }}'
ArtifactName: '${{ parameters.testResultsArtifactName }}_${{ parameters.osName }}_${{ parameters.framework }}_${{ parameters.vsTestPlatform }}'
condition: succeededOrFailed()
# Due to the fact that it is not possible to loop a task and
# it would be a ton of work to make a replacement for the
# Publish Test Results task or the (deprecated) TfsPublisher
# our only other option is to make a task for every supported
# platform and project and update it whenever a new platform
# is targeted or test project is created in Lucene.Net.
- template: 'publish-test-results-for-test-projects.yml'
parameters:
osName: '${{ parameters.osName }}'
framework: '${{ parameters.framework }}'
vsTestPlatform: '${{ parameters.vsTestPlatform }}'
- pwsh: |
$maximumAllowedFailures = '${{ parameters.maximumAllowedFailures }}'
if ([int]$Env:TOTALFAILURES -gt [int]$maximumAllowedFailures) {
Write-Host "##vso[task.logissue type=error;]Test run failed due to too many failed tests. Maximum failures allowed: $maximumAllowedFailures, total failures: $($Env:TOTALFAILURES)."
Write-Host "##vso[task.complete result=Failed;]"
}