| # 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;]" |
| } |