| # 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 |
| |
| # NOTE: Depends on environment variables $(IsNightly) and $(IsWeekly) |
| |
| 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 |
| |
| - pwsh: | |
| 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 }}' |
| |
| #- pwsh: Get-ChildItem -Path $(System.DefaultWorkingDirectory) # Uncomment for debugging |
| |
| - 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 |
| - pwsh: | |
| $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 }}' |
| $tempDirectory = "$(Agent.TempDirectory)" |
| $isNightly = if ($env:ISNIGHTLY -eq 'true') { 'true' } else { 'false' } |
| $isWeekly = if ($env:ISWEEKLY -eq 'true') { 'true' } else { 'false' } |
| |
| 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" -and $_.FullName -match "$fileRegexPattern" -and !$_.Name.EndsWith('.resources.dll') } | 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 |
| } |
| } |
| |
| $testResultDirectory = "$testResultsArtifactDirectory/$testOSName/$framework/$testPlatform/$testName" |
| if (!(Test-Path "$testResultDirectory")) { |
| New-Item "$testResultDirectory" -ItemType Directory -Force |
| } |
| # Test binaries are copied to a temp directory so locking behavior of other processes doesn't interfere with this test run |
| $tempTestDirectory = "$tempDirectory/$framework/$testName" |
| if (!(Test-Path "$tempTestDirectory")) { |
| New-Item "$tempTestDirectory" -ItemType Directory -Force |
| } |
| $testTarget = "$tempTestDirectory/$($testBinary.Name)" |
| $sourceDirectory = $testBinary.Directory.FullName |
| |
| Copy-Item -Path "$sourceDirectory/*" -Destination "$tempTestDirectory" -Recurse -Force |
| |
| if ($isNightly -ne 'true' -and $isWeekly -ne 'true') { |
| $blameHangTimeout = "--blame-hang-timeout 10minutes" |
| } else { |
| $blameHangTimeout = "--blame-hang-timeout 50minutes" |
| } |
| |
| Write-Host "Running with $blameHangTimeout" |
| |
| $testExpression = "dotnet test ""$testTarget"" --framework ""$framework"" --blame --no-build --no-restore" + ` |
| " --logger:""console;verbosity=normal"" --logger:""trx;LogFileName=$testResultsFileName""" + ` |
| " --results-directory:""$testResultDirectory""" + ` |
| " --blame-hang --blame-hang-dump-type mini $blameHangTimeout" |
| |
| if (![string]::IsNullOrEmpty($where)) { |
| $testExpression = "$testExpression --filter ""$where""" |
| } |
| |
| $testExpression = "$testExpression -- RunConfiguration.TargetPlatform=$testPlatform" |
| |
| Write-Host "Testing '$($testBinary.FullName)' on framework '$framework' 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: | |
| $failed = $false |
| if ($env:HOSTCRASHED -eq 'true') { |
| Write-Host "##vso[task.logissue type=error;]Test host process(es) crashed: $($env:CRASHEDRUNS)." |
| $failed = $true |
| } |
| $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)." |
| $failed = $true |
| } |
| if ($failed) { |
| Write-Host "##vso[task.complete result=Failed;]" |
| } |