From 47fee2757ff8d1bc852f6bf2d4a613d831fe1f9a Mon Sep 17 00:00:00 2001 From: nohwnd Date: Fri, 5 Jun 2020 17:31:59 +0200 Subject: [PATCH 01/12] Hide -release in console --- scripts/verify-nupkgs.ps1 | 3 ++- scripts/vsts-prebuild.ps1 | 17 ++++++++++++++--- src/vstest.console/CommandLine/Executor.cs | 18 ++++++++++++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/scripts/verify-nupkgs.ps1 b/scripts/verify-nupkgs.ps1 index fa76cc696e..44a739be81 100644 --- a/scripts/verify-nupkgs.ps1 +++ b/scripts/verify-nupkgs.ps1 @@ -12,7 +12,8 @@ function Unzip function Verify-Nuget-Packages($packageDirectory) { Write-Log "Starting Verify-Nuget-Packages." - $expectedNumOfFiles = @{"Microsoft.CodeCoverage" = 29; + $expectedNumOfFiles = @{ + "Microsoft.CodeCoverage" = 29; "Microsoft.NET.Test.Sdk" = 13; "Microsoft.TestPlatform" = 437; "Microsoft.TestPlatform.Build" = 19; diff --git a/scripts/vsts-prebuild.ps1 b/scripts/vsts-prebuild.ps1 index f49ad135a2..fdda72bf51 100644 --- a/scripts/vsts-prebuild.ps1 +++ b/scripts/vsts-prebuild.ps1 @@ -1,7 +1,13 @@ # Sets variables which are used across the build tasks. -$buildSuffix = $args[0] -$IsRtmBuild = $args[1] + + +[Parameter(Mandatory)] +[string] $BuildSuffix +[Parameter(Mandatory)] +[string] $IsRtmBuild +[Parameter(Mandatory)] +$Branch $TP_ROOT_DIR = (Get-Item (Split-Path $MyInvocation.MyCommand.Path)).Parent.FullName @@ -11,9 +17,14 @@ $buildPrefix = $TpVersion.Trim() if ($IsRtmBuild.ToLower() -eq "false") { + if ($null -ne $Branch -and $Branch -like "rel/*") + { + $BuildSuffix -replace "preview", "release" + } + $packageVersion = $buildPrefix+"-"+$buildSuffix } -else +else { $packageVersion = $buildPrefix $buildSuffix = [string]::Empty diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index 52beddf73e..7c4b033d30 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -99,7 +99,8 @@ internal int Execute(params string[] args) } else { - this.PrintSplashScreen(); + var isDiag = args != null && args.Any(arg => arg.StartsWith("--diag", StringComparison.OrdinalIgnoreCase)); + this.PrintSplashScreen(isDiag); } int exitCode = 0; @@ -375,10 +376,19 @@ private bool ExecuteArgumentProcessor(IArgumentProcessor processor, ref int exit /// /// Displays the Company and Copyright splash title info immediately after launch /// - private void PrintSplashScreen() + private void PrintSplashScreen(bool isDiag) { - string assemblyVersion = string.Empty; - assemblyVersion = Product.Version; + string assemblyVersion = Product.Version; + if (!isDiag) + { + var end = Product.Version?.IndexOf("-release"); + + if (end >= 0) + { + assemblyVersion = Product.Version?.Substring(0, end.Value); + } + } + string commandLineBanner = string.Format(CultureInfo.CurrentUICulture, CommandLineResources.MicrosoftCommandLineTitle, assemblyVersion); this.Output.WriteLine(commandLineBanner, OutputLevel.Information); this.Output.WriteLine(CommandLineResources.CopyrightCommandLineTitle, OutputLevel.Information); From 9ed42e49af871cc87f87e6f18e48827f07751043 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Mon, 8 Jun 2020 10:06:44 +0200 Subject: [PATCH 02/12] Add param block --- scripts/vsts-prebuild.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/vsts-prebuild.ps1 b/scripts/vsts-prebuild.ps1 index fdda72bf51..e49252e6ee 100644 --- a/scripts/vsts-prebuild.ps1 +++ b/scripts/vsts-prebuild.ps1 @@ -1,13 +1,13 @@ # Sets variables which are used across the build tasks. - - -[Parameter(Mandatory)] -[string] $BuildSuffix -[Parameter(Mandatory)] -[string] $IsRtmBuild -[Parameter(Mandatory)] -$Branch +param ( + [Parameter(Mandatory)] + [string] $BuildSuffix, + [Parameter(Mandatory)] + [string] $IsRtmBuild, + [Parameter(Mandatory)] + $Branch +) $TP_ROOT_DIR = (Get-Item (Split-Path $MyInvocation.MyCommand.Path)).Parent.FullName From 6de7aca8b34334bc589f42e87126c153f01f57c0 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Mon, 8 Jun 2020 11:13:49 +0200 Subject: [PATCH 03/12] Match on whole branch name --- scripts/vsts-prebuild.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vsts-prebuild.ps1 b/scripts/vsts-prebuild.ps1 index e49252e6ee..57ceae1ac8 100644 --- a/scripts/vsts-prebuild.ps1 +++ b/scripts/vsts-prebuild.ps1 @@ -17,7 +17,7 @@ $buildPrefix = $TpVersion.Trim() if ($IsRtmBuild.ToLower() -eq "false") { - if ($null -ne $Branch -and $Branch -like "rel/*") + if ($null -ne $Branch -and $Branch -like "refs/heads/rel/*") { $BuildSuffix -replace "preview", "release" } From b60cf6f6d0a875b6a58a0837c864ead038398f9a Mon Sep 17 00:00:00 2001 From: nohwnd Date: Mon, 8 Jun 2020 11:30:26 +0200 Subject: [PATCH 04/12] Set var --- scripts/vsts-prebuild.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/vsts-prebuild.ps1 b/scripts/vsts-prebuild.ps1 index 57ceae1ac8..ba170867ad 100644 --- a/scripts/vsts-prebuild.ps1 +++ b/scripts/vsts-prebuild.ps1 @@ -19,7 +19,7 @@ if ($IsRtmBuild.ToLower() -eq "false") { if ($null -ne $Branch -and $Branch -like "refs/heads/rel/*") { - $BuildSuffix -replace "preview", "release" + $BuildSuffix = $BuildSuffix -replace "preview", "release" } $packageVersion = $buildPrefix+"-"+$buildSuffix From 1fbf4a3b661610d513063c7a241424a0cb72e2ea Mon Sep 17 00:00:00 2001 From: nohwnd Date: Mon, 8 Jun 2020 12:30:29 +0200 Subject: [PATCH 05/12] Change assertion --- test/vstest.console.UnitTests/ExecutorUnitTests.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/vstest.console.UnitTests/ExecutorUnitTests.cs b/test/vstest.console.UnitTests/ExecutorUnitTests.cs index 0585398afb..73987b13d8 100644 --- a/test/vstest.console.UnitTests/ExecutorUnitTests.cs +++ b/test/vstest.console.UnitTests/ExecutorUnitTests.cs @@ -50,11 +50,10 @@ public void ExecutorPrintsSplashScreenTest() Assert.IsNotNull(mockOutput.Messages.First().Message, "First Printed Message cannot be null or empty"); // Just check first 20 characters - don't need to check whole thing as assembly version is variable - Assert.IsTrue( - mockOutput.Messages.First() - .Message.Contains(CommandLineResources.MicrosoftCommandLineTitle.Substring(0, 20)), - "First Printed message must be Microsoft Copyright"); - + // "First Printed message must be Microsoft Copyright"); + StringAssert.Contains(mockOutput.Messages.First() + .Message, CommandLineResources.MicrosoftCommandLineTitle.Substring(0, 20)); + Assert.IsTrue(mockOutput.Messages.First().Message.EndsWith(assemblyVersion)); } From 501939e9fa61f0c65fb466d6367f6d5000b86fec Mon Sep 17 00:00:00 2001 From: nohwnd Date: Mon, 8 Jun 2020 13:07:06 +0200 Subject: [PATCH 06/12] Trim version --- test/vstest.console.UnitTests/ExecutorUnitTests.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/vstest.console.UnitTests/ExecutorUnitTests.cs b/test/vstest.console.UnitTests/ExecutorUnitTests.cs index 73987b13d8..f5748b8c49 100644 --- a/test/vstest.console.UnitTests/ExecutorUnitTests.cs +++ b/test/vstest.console.UnitTests/ExecutorUnitTests.cs @@ -21,6 +21,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests using Utilities; using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; + using System; [TestClass] public class ExecutorUnitTests @@ -51,10 +52,13 @@ public void ExecutorPrintsSplashScreenTest() // Just check first 20 characters - don't need to check whole thing as assembly version is variable // "First Printed message must be Microsoft Copyright"); - StringAssert.Contains(mockOutput.Messages.First() - .Message, CommandLineResources.MicrosoftCommandLineTitle.Substring(0, 20)); - - Assert.IsTrue(mockOutput.Messages.First().Message.EndsWith(assemblyVersion)); + StringAssert.Contains(mockOutput.Messages.First().Message, + CommandLineResources.MicrosoftCommandLineTitle.Substring(0, 20)); + + var suffixIndex = assemblyVersion.IndexOf("-"); + var version = suffixIndex == -1 ? assemblyVersion : assemblyVersion.Substring(0, suffixIndex); + StringAssert.Contains(mockOutput.Messages.First().Message, + version); } [TestMethod] From fceebbc13011a5b77e9232b89796ead2853894a0 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2020 11:52:06 +0200 Subject: [PATCH 07/12] Update dependencies from https://github.com/dotnet/arcade build 20200602.3 (#2456) Microsoft.DotNet.Arcade.Sdk , Microsoft.DotNet.Build.Tasks.Feed , Microsoft.DotNet.Helix.Sdk , Microsoft.DotNet.SignTool , Microsoft.DotNet.SwaggerGenerator.MSBuild From Version 5.0.0-beta.20052.1 -> To Version 1.0.0-beta.20302.3 Co-authored-by: dotnet-maestro[bot] --- NuGet.config | 10 +- eng/Version.Details.xml | 20 +- eng/Versions.props | 6 +- eng/common/CheckSymbols.ps1 | 53 +- eng/common/PublishToSymbolServers.proj | 6 +- eng/common/SetupNugetSources.ps1 | 2 +- eng/common/SetupNugetSources.sh | 2 +- eng/common/SourceLinkValidation.ps1 | 269 +++++----- eng/common/build.ps1 | 108 ++-- eng/common/build.sh | 33 +- eng/common/cross/build-android-rootfs.sh | 106 ++-- eng/common/cross/build-rootfs.sh | 112 ++-- eng/common/cross/toolchain.cmake | 96 ++-- eng/common/darc-init.sh | 30 +- eng/common/dotnet-install.ps1 | 25 +- eng/common/dotnet-install.sh | 37 +- eng/common/enable-cross-org-publishing.ps1 | 11 +- eng/common/generate-graph-files.ps1 | 51 +- eng/common/init-tools-native.ps1 | 37 +- eng/common/init-tools-native.sh | 50 +- eng/common/internal-feed-operations.ps1 | 28 +- eng/common/internal-feed-operations.sh | 4 +- eng/common/internal/Tools.csproj | 1 - eng/common/msbuild.ps1 | 5 +- eng/common/native/CommonLibrary.psm1 | 24 +- eng/common/native/common-library.sh | 10 +- eng/common/native/find-native-compiler.sh | 121 ----- eng/common/native/install-cmake-test.sh | 4 +- eng/common/native/install-cmake.sh | 4 +- eng/common/native/install-tool.ps1 | 12 +- eng/common/performance/perfhelixpublish.proj | 29 +- eng/common/performance/performance-setup.ps1 | 46 +- eng/common/performance/performance-setup.sh | 49 +- eng/common/pipeline-logging-functions.ps1 | 70 ++- eng/common/pipeline-logging-functions.sh | 11 +- .../post-build/add-build-to-channel.ps1 | 48 -- .../post-build/check-channel-consistency.ps1 | 30 -- eng/common/post-build/darc-gather-drop.ps1 | 15 +- eng/common/post-build/nuget-validation.ps1 | 11 +- eng/common/post-build/post-build-utils.ps1 | 27 +- eng/common/post-build/promote-build.ps1 | 18 +- eng/common/post-build/setup-maestro-vars.ps1 | 26 + .../post-build/sourcelink-validation.ps1 | 42 +- eng/common/post-build/symbols-validation.ps1 | 101 ++-- .../post-build/trigger-subscriptions.ps1 | 87 ++-- eng/common/sdk-task.ps1 | 32 +- eng/common/sdl/execute-all-sdl-tools.ps1 | 180 +++---- eng/common/sdl/extract-artifact-packages.ps1 | 97 ++-- eng/common/sdl/init-sdl.ps1 | 44 +- eng/common/sdl/packages.config | 2 +- eng/common/sdl/push-gdn.ps1 | 94 ++-- eng/common/sdl/run-sdl.ps1 | 88 ++-- eng/common/templates/job/execute-sdl.yml | 52 +- eng/common/templates/job/job.yml | 156 +++--- .../templates/job/publish-build-assets.yml | 6 - eng/common/templates/jobs/jobs.yml | 74 ++- .../channels/generic-internal-channel.yml | 99 ++-- .../channels/generic-public-channel.yml | 92 ++-- .../templates/post-build/common-variables.yml | 27 +- .../templates/post-build/post-build.yml | 488 ++++++++---------- .../post-build/setup-maestro-vars.yml | 63 +-- .../templates/steps/add-build-to-channel.yml | 13 - eng/common/templates/steps/publish-logs.yml | 23 - eng/common/templates/steps/send-to-helix.yml | 9 +- eng/common/tools.ps1 | 279 +++++----- eng/common/tools.sh | 112 ++-- global.json | 6 +- 67 files changed, 1477 insertions(+), 2446 deletions(-) delete mode 100644 eng/common/native/find-native-compiler.sh delete mode 100644 eng/common/post-build/add-build-to-channel.ps1 delete mode 100644 eng/common/post-build/check-channel-consistency.ps1 create mode 100644 eng/common/post-build/setup-maestro-vars.ps1 delete mode 100644 eng/common/templates/steps/add-build-to-channel.yml delete mode 100644 eng/common/templates/steps/publish-logs.yml diff --git a/NuGet.config b/NuGet.config index 23206d2001..15e1ddd568 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,19 +1,19 @@ - + - - + + - + - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 4cf005dce8..740e33cc0f 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -3,25 +3,25 @@ - + https://github.com/dotnet/arcade - 98cc270f5e08096729fc7d54ffafa9e3c6576316 + 9b71be0663493cd0e111b55536a2e1eeb272f54c - + https://github.com/dotnet/arcade - 98cc270f5e08096729fc7d54ffafa9e3c6576316 + 9b71be0663493cd0e111b55536a2e1eeb272f54c - + https://github.com/dotnet/arcade - 98cc270f5e08096729fc7d54ffafa9e3c6576316 + 9b71be0663493cd0e111b55536a2e1eeb272f54c - + https://github.com/dotnet/arcade - 98cc270f5e08096729fc7d54ffafa9e3c6576316 + 9b71be0663493cd0e111b55536a2e1eeb272f54c - + https://github.com/dotnet/arcade - 98cc270f5e08096729fc7d54ffafa9e3c6576316 + 9b71be0663493cd0e111b55536a2e1eeb272f54c https://github.com/dotnet/arcade-services diff --git a/eng/Versions.props b/eng/Versions.props index a02fa656b3..842c083ffd 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -60,8 +60,8 @@ 2.4.1 2.0.3 2.4.1 - 5.0.0-beta.20052.1 - 5.0.0-beta.20052.1 + 2.2.0-beta.20302.3 + 1.0.0-beta.20302.3 1.22.0 1.1.2 2.0.0 @@ -73,7 +73,7 @@ 1.7.0 1.1.0-beta.19556.4 1.0.0-beta2-19554-01 - 5.0.0-beta.20052.1 + 1.0.0-beta.20302.3 1.0.0-beta.20055.1 diff --git a/eng/common/CheckSymbols.ps1 b/eng/common/CheckSymbols.ps1 index 5442eff386..b8d84607b8 100644 --- a/eng/common/CheckSymbols.ps1 +++ b/eng/common/CheckSymbols.ps1 @@ -5,12 +5,11 @@ param( ) Add-Type -AssemblyName System.IO.Compression.FileSystem -. $PSScriptRoot\pipeline-logging-functions.ps1 function FirstMatchingSymbolDescriptionOrDefault { param( [string] $FullPath, # Full path to the module that has to be checked - [string] $TargetServerParameter, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols + [string] $TargetServerParam, # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols [string] $SymbolsPath ) @@ -22,36 +21,36 @@ function FirstMatchingSymbolDescriptionOrDefault { # checking and which type of file was uploaded. # The file itself is returned - $SymbolPath = $SymbolsPath + '\' + $FileName + $SymbolPath = $SymbolsPath + "\" + $FileName # PDB file for the module - $PdbPath = $SymbolPath.Replace($Extension, '.pdb') + $PdbPath = $SymbolPath.Replace($Extension, ".pdb") # PDB file for R2R module (created by crossgen) - $NGenPdb = $SymbolPath.Replace($Extension, '.ni.pdb') + $NGenPdb = $SymbolPath.Replace($Extension, ".ni.pdb") # DBG file for a .so library - $SODbg = $SymbolPath.Replace($Extension, '.so.dbg') + $SODbg = $SymbolPath.Replace($Extension, ".so.dbg") # DWARF file for a .dylib - $DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf') + $DylibDwarf = $SymbolPath.Replace($Extension, ".dylib.dwarf") - .\dotnet-symbol.exe --symbols --modules --windows-pdbs $TargetServerParameter $FullPath -o $SymbolsPath | Out-Null + .\dotnet-symbol.exe --symbols --modules --windows-pdbs $TargetServerParam $FullPath -o $SymbolsPath | Out-Null if (Test-Path $PdbPath) { - return 'PDB' + return "PDB" } elseif (Test-Path $NGenPdb) { - return 'NGen PDB' + return "NGen PDB" } elseif (Test-Path $SODbg) { - return 'DBG for SO' + return "DBG for SO" } elseif (Test-Path $DylibDwarf) { - return 'Dwarf for Dylib' + return "Dwarf for Dylib" } elseif (Test-Path $SymbolPath) { - return 'Module' + return "Module" } else { return $null @@ -69,7 +68,7 @@ function CountMissingSymbols { } # Extensions for which we'll look for symbols - $RelevantExtensions = @('.dll', '.exe', '.so', '.dylib') + $RelevantExtensions = @(".dll", ".exe", ".so", ".dylib") # How many files are missing symbol information $MissingSymbols = 0 @@ -77,7 +76,7 @@ function CountMissingSymbols { $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $PackageGuid = New-Guid $ExtractPath = Join-Path -Path $ExtractPath -ChildPath $PackageGuid - $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath 'Symbols' + $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath "Symbols" [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) @@ -87,31 +86,31 @@ function CountMissingSymbols { Get-ChildItem -Recurse $ExtractPath | Where-Object {$RelevantExtensions -contains $_.Extension} | ForEach-Object { - if ($_.FullName -Match '\\ref\\') { + if ($_.FullName -Match "\\ref\\") { Write-Host "`t Ignoring reference assembly file" $_.FullName return } - $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault -FullPath $_.FullName -TargetServerParameter '--microsoft-symbol-server' -SymbolsPath $SymbolsPath - $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault -FullPath $_.FullName -TargetServerParameter '--internal-server' -SymbolsPath $SymbolsPath + $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--microsoft-symbol-server" $SymbolsPath + $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--internal-server" $SymbolsPath Write-Host -NoNewLine "`t Checking file" $_.FullName "... " if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { - Write-Host "Symbols found on MSDL (${$SymbolsOnMSDL}) and SymWeb (${$SymbolsOnSymWeb})" + Write-Host "Symbols found on MSDL (" $SymbolsOnMSDL ") and SymWeb (" $SymbolsOnSymWeb ")" } else { $MissingSymbols++ if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { - Write-Host 'No symbols found on MSDL or SymWeb!' + Write-Host "No symbols found on MSDL or SymWeb!" } else { if ($SymbolsOnMSDL -eq $null) { - Write-Host 'No symbols found on MSDL!' + Write-Host "No symbols found on MSDL!" } else { - Write-Host 'No symbols found on SymWeb!' + Write-Host "No symbols found on SymWeb!" } } } @@ -130,26 +129,26 @@ function CheckSymbolsAvailable { Get-ChildItem "$InputPath\*.nupkg" | ForEach-Object { $FileName = $_.Name - + # These packages from Arcade-Services include some native libraries that # our current symbol uploader can't handle. Below is a workaround until # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. - if ($FileName -Match 'Microsoft\.DotNet\.Darc\.') { + if ($FileName -Match "Microsoft\.DotNet\.Darc\.") { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } - elseif ($FileName -Match 'Microsoft\.DotNet\.Maestro\.Tasks\.') { + elseif ($FileName -Match "Microsoft\.DotNet\.Maestro\.Tasks\.") { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } - + Write-Host "Validating $FileName " $Status = CountMissingSymbols "$InputPath\$FileName" if ($Status -ne 0) { - Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Missing symbols for $Status modules in the package $FileName" + Write-Error "Missing symbols for $Status modules in the package $FileName" } Write-Host diff --git a/eng/common/PublishToSymbolServers.proj b/eng/common/PublishToSymbolServers.proj index 311e2bbe0f..5d55e312b0 100644 --- a/eng/common/PublishToSymbolServers.proj +++ b/eng/common/PublishToSymbolServers.proj @@ -37,8 +37,6 @@ 3650 true - true - true false @@ -58,7 +56,7 @@ DryRun="false" ConvertPortablePdbsToWindowsPdbs="false" PdbConversionTreatAsWarning="" - Condition="$(PublishToSymbolServer) and $(PublishToMSDL)"/> + Condition="$(PublishToSymbolServer)"/> diff --git a/eng/common/msbuild.ps1 b/eng/common/msbuild.ps1 index c640123000..b37fd3d5e9 100644 --- a/eng/common/msbuild.ps1 +++ b/eng/common/msbuild.ps1 @@ -1,6 +1,6 @@ [CmdletBinding(PositionalBinding=$false)] Param( - [string] $verbosity = 'minimal', + [string] $verbosity = "minimal", [bool] $warnAsError = $true, [bool] $nodeReuse = $true, [switch] $ci, @@ -18,8 +18,9 @@ try { MSBuild @extraArgs } catch { + Write-Host $_ + Write-Host $_.Exception Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/native/CommonLibrary.psm1 b/eng/common/native/CommonLibrary.psm1 index d7d1a65109..41416862d9 100644 --- a/eng/common/native/CommonLibrary.psm1 +++ b/eng/common/native/CommonLibrary.psm1 @@ -145,12 +145,9 @@ function Get-File { New-Item -path $DownloadDirectory -force -itemType "Directory" | Out-Null } - $TempPath = "$Path.tmp" if (Test-Path -IsValid -Path $Uri) { - Write-Verbose "'$Uri' is a file path, copying temporarily to '$TempPath'" - Copy-Item -Path $Uri -Destination $TempPath - Write-Verbose "Moving temporary file to '$Path'" - Move-Item -Path $TempPath -Destination $Path + Write-Verbose "'$Uri' is a file path, copying file to '$Path'" + Copy-Item -Path $Uri -Destination $Path return $? } else { @@ -160,10 +157,8 @@ function Get-File { while($Attempt -Lt $DownloadRetries) { try { - Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $TempPath - Write-Verbose "Downloaded to temporary location '$TempPath'" - Move-Item -Path $TempPath -Destination $Path - Write-Verbose "Moved temporary file to '$Path'" + Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $Path + Write-Verbose "Downloaded to '$Path'" return $True } catch { @@ -364,21 +359,16 @@ function Expand-Zip { return $False } } - - $TempOutputDirectory = Join-Path "$(Split-Path -Parent $OutputDirectory)" "$(Split-Path -Leaf $OutputDirectory).tmp" - if (Test-Path $TempOutputDirectory) { - Remove-Item $TempOutputDirectory -Force -Recurse + if (-Not (Test-Path $OutputDirectory)) { + New-Item -path $OutputDirectory -Force -itemType "Directory" | Out-Null } - New-Item -Path $TempOutputDirectory -Force -ItemType "Directory" | Out-Null Add-Type -assembly "system.io.compression.filesystem" - [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$TempOutputDirectory") + [io.compression.zipfile]::ExtractToDirectory("$ZipPath", "$OutputDirectory") if ($? -Eq $False) { Write-Error "Unable to extract '$ZipPath'" return $False } - - Move-Item -Path $TempOutputDirectory -Destination $OutputDirectory } catch { Write-Host $_ diff --git a/eng/common/native/common-library.sh b/eng/common/native/common-library.sh index bf272dcf55..271bddfac5 100644 --- a/eng/common/native/common-library.sh +++ b/eng/common/native/common-library.sh @@ -34,7 +34,7 @@ function ExpandZip { echo "'Force flag enabled, but '$output_directory' exists. Removing directory" rm -rf $output_directory if [[ $? != 0 ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to remove '$output_directory'" + echo Unable to remove '$output_directory'>&2 return 1 fi fi @@ -45,7 +45,7 @@ function ExpandZip { echo "Extracting archive" tar -xf $zip_path -C $output_directory if [[ $? != 0 ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Unable to extract '$zip_path'" + echo "Unable to extract '$zip_path'" >&2 return 1 fi @@ -117,7 +117,7 @@ function DownloadAndExtract { # Download file GetFile "$uri" "$temp_tool_path" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to download '$uri' to '$temp_tool_path'." + echo "Failed to download '$uri' to '$temp_tool_path'." >&2 return 1 fi @@ -125,7 +125,7 @@ function DownloadAndExtract { echo "extracting from $temp_tool_path to $installDir" ExpandZip "$temp_tool_path" "$installDir" $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Failed to extract '$temp_tool_path' to '$installDir'." + echo "Failed to extract '$temp_tool_path' to '$installDir'." >&2 return 1 fi @@ -148,7 +148,7 @@ function NewScriptShim { fi if [[ ! -f $tool_file_path ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' "Specified tool file path:'$tool_file_path' does not exist" + echo "Specified tool file path:'$tool_file_path' does not exist" >&2 return 1 fi diff --git a/eng/common/native/find-native-compiler.sh b/eng/common/native/find-native-compiler.sh deleted file mode 100644 index aed19d07d5..0000000000 --- a/eng/common/native/find-native-compiler.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env bash -# -# This file locates the native compiler with the given name and version and sets the environment variables to locate it. -# - -source="${BASH_SOURCE[0]}" - -# resolve $SOURCE until the file is no longer a symlink -while [[ -h $source ]]; do - scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" - source="$(readlink "$source")" - - # if $source was a relative symlink, we need to resolve it relative to the path where the - # symlink file was located - [[ $source != /* ]] && source="$scriptroot/$source" -done -scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" - -if [ $# -lt 0 ] -then - echo "Usage..." - echo "find-native-compiler.sh " - echo "Specify the name of compiler (clang or gcc)." - echo "Specify the major version of compiler." - echo "Specify the minor version of compiler." - exit 1 -fi - -. $scriptroot/../pipeline-logging-functions.sh - -compiler="$1" -cxxCompiler="$compiler++" -majorVersion="$2" -minorVersion="$3" - -if [ "$compiler" = "gcc" ]; then cxxCompiler="g++"; fi - -check_version_exists() { - desired_version=-1 - - # Set up the environment to be used for building with the desired compiler. - if command -v "$compiler-$1.$2" > /dev/null; then - desired_version="-$1.$2" - elif command -v "$compiler$1$2" > /dev/null; then - desired_version="$1$2" - elif command -v "$compiler-$1$2" > /dev/null; then - desired_version="-$1$2" - fi - - echo "$desired_version" -} - -if [ -z "$CLR_CC" ]; then - - # Set default versions - if [ -z "$majorVersion" ]; then - # note: gcc (all versions) and clang versions higher than 6 do not have minor version in file name, if it is zero. - if [ "$compiler" = "clang" ]; then versions=( 9 8 7 6.0 5.0 4.0 3.9 3.8 3.7 3.6 3.5 ) - elif [ "$compiler" = "gcc" ]; then versions=( 9 8 7 6 5 4.9 ); fi - - for version in "${versions[@]}"; do - parts=(${version//./ }) - desired_version="$(check_version_exists "${parts[0]}" "${parts[1]}")" - if [ "$desired_version" != "-1" ]; then majorVersion="${parts[0]}"; break; fi - done - - if [ -z "$majorVersion" ]; then - if command -v "$compiler" > /dev/null; then - if [ "$(uname)" != "Darwin" ]; then - Write-PipelineTelemetryError -category "Build" -type "warning" "Specific version of $compiler not found, falling back to use the one in PATH." - fi - export CC="$(command -v "$compiler")" - export CXX="$(command -v "$cxxCompiler")" - else - Write-PipelineTelemetryError -category "Build" "No usable version of $compiler found." - exit 1 - fi - else - if [ "$compiler" = "clang" ] && [ "$majorVersion" -lt 5 ]; then - if [ "$build_arch" = "arm" ] || [ "$build_arch" = "armel" ]; then - if command -v "$compiler" > /dev/null; then - Write-PipelineTelemetryError -category "Build" -type "warning" "Found clang version $majorVersion which is not supported on arm/armel architectures, falling back to use clang from PATH." - export CC="$(command -v "$compiler")" - export CXX="$(command -v "$cxxCompiler")" - else - Write-PipelineTelemetryError -category "Build" "Found clang version $majorVersion which is not supported on arm/armel architectures, and there is no clang in PATH." - exit 1 - fi - fi - fi - fi - else - desired_version="$(check_version_exists "$majorVersion" "$minorVersion")" - if [ "$desired_version" = "-1" ]; then - Write-PipelineTelemetryError -category "Build" "Could not find specific version of $compiler: $majorVersion $minorVersion." - exit 1 - fi - fi - - if [ -z "$CC" ]; then - export CC="$(command -v "$compiler$desired_version")" - export CXX="$(command -v "$cxxCompiler$desired_version")" - if [ -z "$CXX" ]; then export CXX="$(command -v "$cxxCompiler")"; fi - fi -else - if [ ! -f "$CLR_CC" ]; then - Write-PipelineTelemetryError -category "Build" "CLR_CC is set but path '$CLR_CC' does not exist" - exit 1 - fi - export CC="$CLR_CC" - export CXX="$CLR_CXX" -fi - -if [ -z "$CC" ]; then - Write-PipelineTelemetryError -category "Build" "Unable to find $compiler." - exit 1 -fi - -export CCC_CC="$CC" -export CCC_CXX="$CXX" -export SCAN_BUILD_COMMAND="$(command -v "scan-build$desired_version")" diff --git a/eng/common/native/install-cmake-test.sh b/eng/common/native/install-cmake-test.sh index 12339a4076..53ddf4e686 100644 --- a/eng/common/native/install-cmake-test.sh +++ b/eng/common/native/install-cmake-test.sh @@ -101,7 +101,7 @@ fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' + echo "Installation failed" >&2 exit 1 fi @@ -110,7 +110,7 @@ fi NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' + echo "Shim generation failed" >&2 exit 1 fi diff --git a/eng/common/native/install-cmake.sh b/eng/common/native/install-cmake.sh index 18041be876..5f1a182fa9 100644 --- a/eng/common/native/install-cmake.sh +++ b/eng/common/native/install-cmake.sh @@ -101,7 +101,7 @@ fi DownloadAndExtract $uri $tool_install_directory $force $download_retries $retry_wait_time_seconds if [[ $? != 0 ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Installation failed' + echo "Installation failed" >&2 exit 1 fi @@ -110,7 +110,7 @@ fi NewScriptShim $shim_path $tool_file_path true if [[ $? != 0 ]]; then - Write-PipelineTelemetryError -category 'NativeToolsBootstrap' 'Shim generation failed' + echo "Shim generation failed" >&2 exit 1 fi diff --git a/eng/common/native/install-tool.ps1 b/eng/common/native/install-tool.ps1 index f397e1c75d..635ab3fd41 100644 --- a/eng/common/native/install-tool.ps1 +++ b/eng/common/native/install-tool.ps1 @@ -46,8 +46,6 @@ Param ( [int] $RetryWaitTimeInSeconds = 30 ) -. $PSScriptRoot\..\pipeline-logging-functions.ps1 - # Import common library modules Import-Module -Name (Join-Path $CommonLibraryDirectory "CommonLibrary.psm1") @@ -95,7 +93,7 @@ try { -Verbose:$Verbose if ($InstallStatus -Eq $False) { - Write-PipelineTelemetryError "Installation failed" -Category "NativeToolsetBootstrapping" + Write-Error "Installation failed" exit 1 } } @@ -105,7 +103,7 @@ try { Write-Error "There are multiple copies of $ToolName in $($ToolInstallDirectory): `n$(@($ToolFilePath | out-string))" exit 1 } elseif (@($ToolFilePath).Length -Lt 1) { - Write-Host "$ToolName was not found in $ToolFilePath." + Write-Error "$ToolName was not found in $ToolFilePath." exit 1 } @@ -119,14 +117,14 @@ try { -Verbose:$Verbose if ($GenerateShimStatus -Eq $False) { - Write-PipelineTelemetryError "Generate shim failed" -Category "NativeToolsetBootstrapping" + Write-Error "Generate shim failed" return 1 } exit 0 } catch { - Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category "NativeToolsetBootstrapping" -Message $_ + Write-Host $_ + Write-Host $_.Exception exit 1 } diff --git a/eng/common/performance/perfhelixpublish.proj b/eng/common/performance/perfhelixpublish.proj index 1db5e8a84d..e5826b5323 100644 --- a/eng/common/performance/perfhelixpublish.proj +++ b/eng/common/performance/perfhelixpublish.proj @@ -6,8 +6,7 @@ py -3 %HELIX_CORRELATION_PAYLOAD%\Core_Root\CoreRun.exe %HELIX_CORRELATION_PAYLOAD%\Baseline_Core_Root\CoreRun.exe - - $(HelixPreCommands);call %HELIX_CORRELATION_PAYLOAD%\performance\tools\machine-setup.cmd;set PYTHONPATH=%HELIX_WORKITEM_PAYLOAD%\scripts%3B%HELIX_WORKITEM_PAYLOAD% + $(HelixPreCommands);call %HELIX_CORRELATION_PAYLOAD%\performance\tools\machine-setup.cmd %HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts %HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts_Baseline %HELIX_CORRELATION_PAYLOAD%\performance\src\tools\ResultsComparer\ResultsComparer.csproj @@ -41,13 +40,6 @@ $HELIX_WORKITEM_ROOT/testResults.xml - - --corerun %HELIX_CORRELATION_PAYLOAD%\dotnet-mono\shared\Microsoft.NETCore.App\5.0.0\corerun.exe - - - --corerun $(BaseDirectory)/dotnet-mono/shared/Microsoft.NETCore.App/5.0.0/corerun - - --corerun $(CoreRun) @@ -107,23 +99,4 @@ 4:00 - - - - $(WorkItemDirectory)\ScenarioCorrelation - $(Python) %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\crossgen\test.py crossgen --test-name System.Private.Xml.dll --core-root %HELIX_CORRELATION_PAYLOAD%\Core_Root - - - $(WorkItemDirectory)\ScenarioCorrelation - $(Python) %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\crossgen\test.py crossgen --test-name System.Linq.Expressions.dll --core-root %HELIX_CORRELATION_PAYLOAD%\Core_Root - - - $(WorkItemDirectory)\ScenarioCorrelation - $(Python) %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\crossgen\test.py crossgen --test-name Microsoft.CodeAnalysis.VisualBasic.dll --core-root %HELIX_CORRELATION_PAYLOAD%\Core_Root - - - $(WorkItemDirectory)\ScenarioCorrelation - $(Python) %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\crossgen\test.py crossgen --test-name Microsoft.CodeAnalysis.CSharp.dll --core-root %HELIX_CORRELATION_PAYLOAD%\Core_Root - - \ No newline at end of file diff --git a/eng/common/performance/performance-setup.ps1 b/eng/common/performance/performance-setup.ps1 index 31a99e4901..ec41965fc8 100644 --- a/eng/common/performance/performance-setup.ps1 +++ b/eng/common/performance/performance-setup.ps1 @@ -3,22 +3,18 @@ Param( [string] $CoreRootDirectory, [string] $BaselineCoreRootDirectory, [string] $Architecture="x64", - [string] $Framework="net5.0", + [string] $Framework="netcoreapp5.0", [string] $CompilationMode="Tiered", [string] $Repository=$env:BUILD_REPOSITORY_NAME, [string] $Branch=$env:BUILD_SOURCEBRANCH, [string] $CommitSha=$env:BUILD_SOURCEVERSION, [string] $BuildNumber=$env:BUILD_BUILDNUMBER, - [string] $RunCategories="Libraries Runtime", + [string] $RunCategories="coreclr corefx", [string] $Csproj="src\benchmarks\micro\MicroBenchmarks.csproj", [string] $Kind="micro", - [switch] $LLVM, - [switch] $MonoInterpreter, - [switch] $MonoAOT, [switch] $Internal, [switch] $Compare, - [string] $MonoDotnet="", - [string] $Configurations="CompilationMode=$CompilationMode RunKind=$Kind" + [string] $Configurations="CompilationMode=$CompilationMode" ) $RunFromPerformanceRepo = ($Repository -eq "dotnet/performance") -or ($Repository -eq "dotnet-performance") @@ -35,8 +31,7 @@ $HelixSourcePrefix = "pr" $Queue = "Windows.10.Amd64.ClientRS4.DevEx.15.8.Open" -# TODO: Implement a better logic to determine if Framework is .NET Core or >= .NET 5. -if ($Framework.StartsWith("netcoreapp") -or ($Framework -eq "net5.0")) { +if ($Framework.StartsWith("netcoreapp")) { $Queue = "Windows.10.Amd64.ClientRS5.Open" } @@ -54,32 +49,9 @@ if ($Internal) { $HelixSourcePrefix = "official" } -if($MonoDotnet -ne "") -{ - $Configurations += " LLVM=$LLVM MonoInterpreter=$MonoInterpreter MonoAOT=$MonoAOT" - if($ExtraBenchmarkDotNetArguments -eq "") - { - #FIX ME: We need to block these tests as they don't run on mono for now - $ExtraBenchmarkDotNetArguments = "--exclusion-filter *Perf_Image* *Perf_NamedPipeStream*" - } - else - { - #FIX ME: We need to block these tests as they don't run on mono for now - $ExtraBenchmarkDotNetArguments += " --exclusion-filter *Perf_Image* *Perf_NamedPipeStream*" - } -} - -# FIX ME: This is a workaround until we get this from the actual pipeline -$CommonSetupArguments="--channel master --queue $Queue --build-number $BuildNumber --build-configs $Configurations --architecture $Architecture" +$CommonSetupArguments="--frameworks $Framework --queue $Queue --build-number $BuildNumber --build-configs $Configurations" $SetupArguments = "--repository https://github.com/$Repository --branch $Branch --get-perf-hash --commit-sha $CommitSha $CommonSetupArguments" - -#This grabs the LKG version number of dotnet and passes it to our scripts -$VersionJSON = Get-Content global.json | ConvertFrom-Json -$DotNetVersion = $VersionJSON.tools.dotnet -$SetupArguments = "--dotnet-versions $DotNetVersion $SetupArguments" - - if ($RunFromPerformanceRepo) { $SetupArguments = "--perf-hash $CommitSha $CommonSetupArguments" @@ -89,13 +61,6 @@ else { git clone --branch master --depth 1 --quiet https://github.com/dotnet/performance $PerformanceDirectory } -if($MonoDotnet -ne "") -{ - $UsingMono = "true" - $MonoDotnetPath = (Join-Path $PayloadDirectory "dotnet-mono") - Move-Item -Path $MonoDotnet -Destination $MonoDotnetPath -} - if ($UseCoreRun) { $NewCoreRoot = (Join-Path $PayloadDirectory "Core_Root") Move-Item -Path $CoreRootDirectory -Destination $NewCoreRoot @@ -131,7 +96,6 @@ Write-PipelineSetVariable -Name 'UseCoreRun' -Value "$UseCoreRun" -IsMultiJobVar Write-PipelineSetVariable -Name 'UseBaselineCoreRun' -Value "$UseBaselineCoreRun" -IsMultiJobVariable $false Write-PipelineSetVariable -Name 'RunFromPerfRepo' -Value "$RunFromPerformanceRepo" -IsMultiJobVariable $false Write-PipelineSetVariable -Name 'Compare' -Value "$Compare" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'MonoDotnet' -Value "$UsingMono" -IsMultiJobVariable $false # Helix Arguments Write-PipelineSetVariable -Name 'Creator' -Value "$Creator" -IsMultiJobVariable $false diff --git a/eng/common/performance/performance-setup.sh b/eng/common/performance/performance-setup.sh index 9409e4d85e..2f2092166e 100644 --- a/eng/common/performance/performance-setup.sh +++ b/eng/common/performance/performance-setup.sh @@ -4,7 +4,7 @@ source_directory=$BUILD_SOURCESDIRECTORY core_root_directory= baseline_core_root_directory= architecture=x64 -framework=net5.0 +framework=netcoreapp5.0 compilation_mode=tiered repository=$BUILD_REPOSITORY_NAME branch=$BUILD_SOURCEBRANCH @@ -12,18 +12,13 @@ commit_sha=$BUILD_SOURCEVERSION build_number=$BUILD_BUILDNUMBER internal=false compare=false -mono_dotnet= kind="micro" -llvm=false -monointerpreter=false -monoaot=false -run_categories="Libraries Runtime" +run_categories="coreclr corefx" csproj="src\benchmarks\micro\MicroBenchmarks.csproj" -configurations="CompliationMode=$compilation_mode RunKind=$kind" +configurations= run_from_perf_repo=false use_core_run=true use_baseline_core_run=true -using_mono=false while (($# > 0)); do lowerI="$(echo $1 | awk '{print tolower($0)}')" @@ -70,7 +65,6 @@ while (($# > 0)); do ;; --kind) kind=$2 - configurations="CompliationMode=$compilation_mode RunKind=$kind" shift 2 ;; --runcategories) @@ -85,22 +79,6 @@ while (($# > 0)); do internal=true shift 1 ;; - --llvm) - llvm=true - shift 1 - ;; - --monointerpreter) - monointerpreter=true - shift 1 - ;; - --monoaot) - monoaot=true - shift 1 - ;; - --monodotnet) - mono_dotnet=$2 - shift 2 - ;; --compare) compare=true shift 1 @@ -129,7 +107,6 @@ while (($# > 0)); do echo " --kind Related to csproj. The kind of benchmarks that should be run. Defaults to micro" echo " --runcategories Related to csproj. Categories of benchmarks to run. Defaults to \"coreclr corefx\"" echo " --internal If the benchmarks are running as an official job." - echo " --monodotnet Pass the path to the mono dotnet for mono performance testing." echo "" exit 0 ;; @@ -187,20 +164,9 @@ if [[ "$internal" == true ]]; then fi fi -if [[ "$mono_dotnet" != "" ]]; then - configurations="$configurations LLVM=$llvm MonoInterpreter=$monointerpreter MonoAOT=$monoaot" -fi - -common_setup_arguments="--channel master --queue $queue --build-number $build_number --build-configs $configurations --architecture $architecture" +common_setup_arguments="--frameworks $framework --queue $queue --build-number $build_number --build-configs $configurations" setup_arguments="--repository https://github.com/$repository --branch $branch --get-perf-hash --commit-sha $commit_sha $common_setup_arguments" - -# Get the tools section from the global.json. -# This grabs the LKG version number of dotnet and passes it to our scripts -dotnet_version=`cat global.json | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["tools"]["dotnet"])'` -setup_arguments="--dotnet-versions $dotnet_version $setup_arguments" - - if [[ "$run_from_perf_repo" = true ]]; then payload_directory= workitem_directory=$source_directory @@ -213,12 +179,6 @@ else mv $docs_directory $workitem_directory fi -if [[ "$mono_dotnet" != "" ]]; then - using_mono=true - mono_dotnet_path=$payload_directory/dotnet-mono - mv $mono_dotnet $mono_dotnet_path -fi - if [[ "$use_core_run" = true ]]; then new_core_root=$payload_directory/Core_Root mv $core_root_directory $new_core_root @@ -254,4 +214,3 @@ Write-PipelineSetVariable -name "HelixSourcePrefix" -value "$helix_source_prefix Write-PipelineSetVariable -name "Kind" -value "$kind" -is_multi_job_variable false Write-PipelineSetVariable -name "_BuildConfig" -value "$architecture.$kind.$framework" -is_multi_job_variable false Write-PipelineSetVariable -name "Compare" -value "$compare" -is_multi_job_variable false -Write-PipelineSetVariable -name "MonoDotnet" -value "$using_mono" -is_multi_job_variable false diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1 index 8484451f3a..af5f48aace 100644 --- a/eng/common/pipeline-logging-functions.ps1 +++ b/eng/common/pipeline-logging-functions.ps1 @@ -12,7 +12,6 @@ $script:loggingCommandEscapeMappings = @( # TODO: WHAT ABOUT "="? WHAT ABOUT "%" # TODO: BUG: Escape % ??? # TODO: Add test to verify don't need to escape "=". -# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTelemetryError { [CmdletBinding()] param( @@ -26,55 +25,49 @@ function Write-PipelineTelemetryError { [string]$SourcePath, [string]$LineNumber, [string]$ColumnNumber, - [switch]$AsOutput, - [switch]$Force) + [switch]$AsOutput) - $PSBoundParameters.Remove('Category') | Out-Null + $PSBoundParameters.Remove("Category") | Out-Null + + $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" + $PSBoundParameters.Remove("Message") | Out-Null + $PSBoundParameters.Add("Message", $Message) - if($Force -Or ((Test-Path variable:ci) -And $ci)) { - $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message" - } - $PSBoundParameters.Remove('Message') | Out-Null - $PSBoundParameters.Add('Message', $Message) Write-PipelineTaskError @PSBoundParameters } -# Specify "-Force" to force pipeline formatted output even if "$ci" is false or not set function Write-PipelineTaskError { [CmdletBinding()] param( - [Parameter(Mandatory = $true)] - [string]$Message, - [Parameter(Mandatory = $false)] - [string]$Type = 'error', - [string]$ErrCode, - [string]$SourcePath, - [string]$LineNumber, - [string]$ColumnNumber, - [switch]$AsOutput, - [switch]$Force - ) - - if(!$Force -And (-Not (Test-Path variable:ci) -Or !$ci)) { + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $false)] + [string]$Type = 'error', + [string]$ErrCode, + [string]$SourcePath, + [string]$LineNumber, + [string]$ColumnNumber, + [switch]$AsOutput) + + if(!$ci) { if($Type -eq 'error') { - Write-Host $Message -ForegroundColor Red - return + Write-Host $Message -ForegroundColor Red + return } elseif ($Type -eq 'warning') { - Write-Host $Message -ForegroundColor Yellow - return + Write-Host $Message -ForegroundColor Yellow + return } - } - - if(($Type -ne 'error') -and ($Type -ne 'warning')) { + } + + if(($Type -ne 'error') -and ($Type -ne 'warning')) { Write-Host $Message return - } - $PSBoundParameters.Remove('Force') | Out-Null - if(-not $PSBoundParameters.ContainsKey('Type')) { + } + if(-not $PSBoundParameters.ContainsKey('Type')) { $PSBoundParameters.Add('Type', 'error') - } - Write-LogIssue @PSBoundParameters + } + Write-LogIssue @PSBoundParameters } function Write-PipelineSetVariable { @@ -87,7 +80,7 @@ function Write-PipelineTaskError { [switch]$AsOutput, [bool]$IsMultiJobVariable=$true) - if((Test-Path variable:ci) -And $ci) { + if($ci) { Write-LoggingCommand -Area 'task' -Event 'setvariable' -Data $Value -Properties @{ 'variable' = $Name 'isSecret' = $Secret @@ -102,8 +95,7 @@ function Write-PipelineTaskError { [Parameter(Mandatory=$true)] [string]$Path, [switch]$AsOutput) - - if((Test-Path variable:ci) -And $ci) { + if($ci) { Write-LoggingCommand -Area 'task' -Event 'prependpath' -Data $Path -AsOutput:$AsOutput } } @@ -239,4 +231,4 @@ function Write-LogIssue { } Write-Host $command -ForegroundColor $foregroundColor -BackgroundColor $backgroundColor -} +} \ No newline at end of file diff --git a/eng/common/pipeline-logging-functions.sh b/eng/common/pipeline-logging-functions.sh index 33c3f0d807..1c560a5061 100644 --- a/eng/common/pipeline-logging-functions.sh +++ b/eng/common/pipeline-logging-functions.sh @@ -2,7 +2,6 @@ function Write-PipelineTelemetryError { local telemetry_category='' - local force=false local function_args=() local message='' while [[ $# -gt 0 ]]; do @@ -12,9 +11,6 @@ function Write-PipelineTelemetryError { telemetry_category=$2 shift ;; - -force|-f) - force=true - ;; -*) function_args+=("$1 $2") shift @@ -26,22 +22,19 @@ function Write-PipelineTelemetryError { shift done - if [[ $force != true ]] && [[ "$ci" != true ]]; then + if [[ "$ci" != true ]]; then echo "$message" >&2 return fi message="(NETCORE_ENGINEERING_TELEMETRY=$telemetry_category) $message" function_args+=("$message") - if [[ $force == true ]]; then - function_args+=("-force") - fi Write-PipelineTaskError $function_args } function Write-PipelineTaskError { - if [[ $force != true ]] && [[ "$ci" != true ]]; then + if [[ "$ci" != true ]]; then echo "$@" >&2 return fi diff --git a/eng/common/post-build/add-build-to-channel.ps1 b/eng/common/post-build/add-build-to-channel.ps1 deleted file mode 100644 index de2d957922..0000000000 --- a/eng/common/post-build/add-build-to-channel.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -param( - [Parameter(Mandatory=$true)][int] $BuildId, - [Parameter(Mandatory=$true)][int] $ChannelId, - [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' -) - -try { - . $PSScriptRoot\post-build-utils.ps1 - - # Check that the channel we are going to promote the build to exist - $channelInfo = Get-MaestroChannel -ChannelId $ChannelId - - if (!$channelInfo) { - Write-PipelineTelemetryCategory -Category 'PromoteBuild' -Message "Channel with BAR ID $ChannelId was not found in BAR!" - ExitWithExitCode 1 - } - - # Get info about which channel(s) the build has already been promoted to - $buildInfo = Get-MaestroBuild -BuildId $BuildId - - if (!$buildInfo) { - Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "Build with BAR ID $BuildId was not found in BAR!" - ExitWithExitCode 1 - } - - # Find whether the build is already assigned to the channel or not - if ($buildInfo.channels) { - foreach ($channel in $buildInfo.channels) { - if ($channel.Id -eq $ChannelId) { - Write-Host "The build with BAR ID $BuildId is already on channel $ChannelId!" - ExitWithExitCode 0 - } - } - } - - Write-Host "Promoting build '$BuildId' to channel '$ChannelId'." - - Assign-BuildToChannel -BuildId $BuildId -ChannelId $ChannelId - - Write-Host 'done.' -} -catch { - Write-Host $_ - Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" - ExitWithExitCode 1 -} diff --git a/eng/common/post-build/check-channel-consistency.ps1 b/eng/common/post-build/check-channel-consistency.ps1 deleted file mode 100644 index 38abc5392d..0000000000 --- a/eng/common/post-build/check-channel-consistency.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -param( - [Parameter(Mandatory=$true)][string] $PromoteToChannels, # List of channels that the build should be promoted to - [Parameter(Mandatory=$true)][array] $AvailableChannelIds # List of channel IDs available in the YAML implementation -) - -try { - . $PSScriptRoot\post-build-utils.ps1 - - if ($PromoteToChannels -eq "") { - Write-PipelineTaskError -Type 'warning' -Message "This build won't publish assets as it's not configured to any Maestro channel. If that wasn't intended use Darc to configure a default channel using add-default-channel for this branch or to promote it to a channel using add-build-to-channel. See https://github.com/dotnet/arcade/blob/master/Documentation/Darc.md#assigning-an-individual-build-to-a-channel for more info." - ExitWithExitCode 0 - } - - # Check that every channel that Maestro told to promote the build to - # is available in YAML - $PromoteToChannelsIds = $PromoteToChannels -split "\D" | Where-Object { $_ } - - foreach ($id in $PromoteToChannelsIds) { - if (($id -ne 0) -and ($id -notin $AvailableChannelIds)) { - Write-PipelineTaskError -Message "Channel $id is not present in the post-build YAML configuration! This is an error scenario. Please contact @dnceng." - } - } - - Write-Host 'done.' -} -catch { - Write-Host $_ - Write-PipelineTelemetryError -Category 'CheckChannelConsistency' -Message "There was an error while trying to check consistency of Maestro default channels for the build and post-build YAML configuration." - ExitWithExitCode 1 -} diff --git a/eng/common/post-build/darc-gather-drop.ps1 b/eng/common/post-build/darc-gather-drop.ps1 index 81ff2a4377..89854d3c1c 100644 --- a/eng/common/post-build/darc-gather-drop.ps1 +++ b/eng/common/post-build/darc-gather-drop.ps1 @@ -2,20 +2,20 @@ param( [Parameter(Mandatory=$true)][int] $BarBuildId, # ID of the build which assets should be downloaded [Parameter(Mandatory=$true)][string] $DropLocation, # Where the assets should be downloaded to [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, # Token used to access Maestro API - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', # Maestro API URL - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' # Version of Maestro API to use + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = "https://maestro-prod.westus2.cloudapp.azure.com", # Maestro API URL + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = "2019-01-16" # Version of Maestro API to use ) -try { - . $PSScriptRoot\post-build-utils.ps1 +. $PSScriptRoot\post-build-utils.ps1 - Write-Host 'Installing DARC ...' +try { + Write-Host "Installing DARC ..." . $PSScriptRoot\..\darc-init.ps1 $exitCode = $LASTEXITCODE if ($exitCode -ne 0) { - Write-PipelineTelemetryError -Category "Darc" -Message "Something failed while running 'darc-init.ps1'. Check for errors above. Exiting now..." + Write-PipelineTaskError "Something failed while running 'darc-init.ps1'. Check for errors above. Exiting now..." ExitWithExitCode $exitCode } @@ -38,7 +38,8 @@ try { --latest-location } catch { + Write-Host $_ + Write-Host $_.Exception Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category "Darc" -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/nuget-validation.ps1 b/eng/common/post-build/nuget-validation.ps1 index dab3534ab5..78ed0d540f 100644 --- a/eng/common/post-build/nuget-validation.ps1 +++ b/eng/common/post-build/nuget-validation.ps1 @@ -6,19 +6,20 @@ param( [Parameter(Mandatory=$true)][string] $ToolDestinationPath # Where the validation tool should be downloaded to ) -try { - . $PSScriptRoot\post-build-utils.ps1 +. $PSScriptRoot\post-build-utils.ps1 - $url = 'https://raw.githubusercontent.com/NuGet/NuGetGallery/3e25ad135146676bcab0050a516939d9958bfa5d/src/VerifyMicrosoftPackage/verify.ps1' +try { + $url = "https://raw.githubusercontent.com/NuGet/NuGetGallery/jver-verify/src/VerifyMicrosoftPackage/verify.ps1" - New-Item -ItemType 'directory' -Path ${ToolDestinationPath} -Force + New-Item -ItemType "directory" -Path ${ToolDestinationPath} -Force Invoke-WebRequest $url -OutFile ${ToolDestinationPath}\verify.ps1 & ${ToolDestinationPath}\verify.ps1 ${PackagesPath}\*.nupkg } catch { + Write-PipelineTaskError "NuGet package validation failed. Please check error logs." + Write-Host $_ Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category 'NuGetValidation' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/post-build-utils.ps1 b/eng/common/post-build/post-build-utils.ps1 index 7d49744795..551ae113f8 100644 --- a/eng/common/post-build/post-build-utils.ps1 +++ b/eng/common/post-build/post-build-utils.ps1 @@ -1,17 +1,16 @@ # Most of the functions in this file require the variables `MaestroApiEndPoint`, # `MaestroApiVersion` and `MaestroApiAccessToken` to be globally available. -$ErrorActionPreference = 'Stop' +$ErrorActionPreference = "Stop" Set-StrictMode -Version 2.0 # `tools.ps1` checks $ci to perform some actions. Since the post-build # scripts don't necessarily execute in the same agent that run the # build.ps1/sh script this variable isn't automatically set. $ci = $true -$disableConfigureToolsetImport = $true . $PSScriptRoot\..\tools.ps1 -function Create-MaestroApiRequestHeaders([string]$ContentType = 'application/json') { +function Create-MaestroApiRequestHeaders([string]$ContentType = "application/json") { Validate-MaestroVars $headers = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' @@ -51,20 +50,20 @@ function Get-MaestroSubscriptions([string]$SourceRepository, [int]$ChannelId) { return $result } -function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { +function Trigger-Subscription([string]$SubscriptionId) { Validate-MaestroVars $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken - $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}/builds/${BuildId}?api-version=$MaestroApiVersion" - Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null + $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" + Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null } -function Trigger-Subscription([string]$SubscriptionId) { +function Assign-BuildToChannel([int]$BuildId, [int]$ChannelId) { Validate-MaestroVars $apiHeaders = Create-MaestroApiRequestHeaders -AuthToken $MaestroApiAccessToken - $apiEndpoint = "$MaestroApiEndPoint/api/subscriptions/$SubscriptionId/trigger?api-version=$MaestroApiVersion" - Invoke-WebRequest -Uri $apiEndpoint -Headers $apiHeaders -Method Post | Out-Null + $apiEndpoint = "$MaestroApiEndPoint/api/channels/${ChannelId}/builds/${BuildId}?api-version=$MaestroApiVersion" + Invoke-WebRequest -Method Post -Uri $apiEndpoint -Headers $apiHeaders | Out-Null } function Validate-MaestroVars { @@ -73,18 +72,18 @@ function Validate-MaestroVars { Get-Variable MaestroApiVersion -Scope Global | Out-Null Get-Variable MaestroApiAccessToken -Scope Global | Out-Null - if (!($MaestroApiEndPoint -Match '^http[s]?://maestro-(int|prod).westus2.cloudapp.azure.com$')) { - Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiEndPoint is not a valid Maestro URL. '$MaestroApiEndPoint'" + if (!($MaestroApiEndPoint -Match "^http[s]?://maestro-(int|prod).westus2.cloudapp.azure.com$")) { + Write-PipelineTaskError "MaestroApiEndPoint is not a valid Maestro URL. '$MaestroApiEndPoint'" ExitWithExitCode 1 } - if (!($MaestroApiVersion -Match '^[0-9]{4}-[0-9]{2}-[0-9]{2}$')) { - Write-PipelineTelemetryError -Category 'MaestroVars' -Message "MaestroApiVersion does not match a version string in the format yyyy-MM-DD. '$MaestroApiVersion'" + if (!($MaestroApiVersion -Match "^[0-9]{4}-[0-9]{2}-[0-9]{2}$")) { + Write-PipelineTaskError "MaestroApiVersion does not match a version string in the format yyyy-MM-DD. '$MaestroApiVersion'" ExitWithExitCode 1 } } catch { - Write-PipelineTelemetryError -Category 'MaestroVars' -Message 'Error: Variables `MaestroApiEndPoint`, `MaestroApiVersion` and `MaestroApiAccessToken` are required while using this script.' + Write-PipelineTaskError "Error: Variables `MaestroApiEndPoint`, `MaestroApiVersion` and `MaestroApiAccessToken` are required while using this script." Write-Host $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/promote-build.ps1 b/eng/common/post-build/promote-build.ps1 index ce45635fbd..e5ae85f251 100644 --- a/eng/common/post-build/promote-build.ps1 +++ b/eng/common/post-build/promote-build.ps1 @@ -2,18 +2,18 @@ param( [Parameter(Mandatory=$true)][int] $BuildId, [Parameter(Mandatory=$true)][int] $ChannelId, [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = "https://maestro-prod.westus2.cloudapp.azure.com", + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = "2019-01-16" ) -try { - . $PSScriptRoot\post-build-utils.ps1 +. $PSScriptRoot\post-build-utils.ps1 +try { # Check that the channel we are going to promote the build to exist $channelInfo = Get-MaestroChannel -ChannelId $ChannelId if (!$channelInfo) { - Write-PipelineTelemetryCategory -Category 'PromoteBuild' -Message "Channel with BAR ID $ChannelId was not found in BAR!" + Write-Host "Channel with BAR ID $ChannelId was not found in BAR!" ExitWithExitCode 1 } @@ -21,7 +21,7 @@ try { $buildInfo = Get-MaestroBuild -BuildId $BuildId if (!$buildInfo) { - Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "Build with BAR ID $BuildId was not found in BAR!" + Write-Host "Build with BAR ID $BuildId was not found in BAR!" ExitWithExitCode 1 } @@ -39,10 +39,10 @@ try { Assign-BuildToChannel -BuildId $BuildId -ChannelId $ChannelId - Write-Host 'done.' + Write-Host "done." } catch { + Write-Host "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" Write-Host $_ - Write-PipelineTelemetryError -Category 'PromoteBuild' -Message "There was an error while trying to promote build '$BuildId' to channel '$ChannelId'" - ExitWithExitCode 1 + Write-Host $_.ScriptStackTrace } diff --git a/eng/common/post-build/setup-maestro-vars.ps1 b/eng/common/post-build/setup-maestro-vars.ps1 new file mode 100644 index 0000000000..d7f64dc63c --- /dev/null +++ b/eng/common/post-build/setup-maestro-vars.ps1 @@ -0,0 +1,26 @@ +param( + [Parameter(Mandatory=$true)][string] $ReleaseConfigsPath # Full path to ReleaseConfigs.txt asset +) + +. $PSScriptRoot\post-build-utils.ps1 + +try { + $Content = Get-Content $ReleaseConfigsPath + + $BarId = $Content | Select -Index 0 + + $Channels = "" + $Content | Select -Index 1 | ForEach-Object { $Channels += "$_ ," } + + $IsStableBuild = $Content | Select -Index 2 + + Write-PipelineSetVariable -Name 'BARBuildId' -Value $BarId + Write-PipelineSetVariable -Name 'InitialChannels' -Value "$Channels" + Write-PipelineSetVariable -Name 'IsStableBuild' -Value $IsStableBuild +} +catch { + Write-Host $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace + ExitWithExitCode 1 +} diff --git a/eng/common/post-build/sourcelink-validation.ps1 b/eng/common/post-build/sourcelink-validation.ps1 index cc9d059d04..bbfdacca13 100644 --- a/eng/common/post-build/sourcelink-validation.ps1 +++ b/eng/common/post-build/sourcelink-validation.ps1 @@ -34,9 +34,9 @@ $ValidatePackage = { # Extensions for which we'll look for SourceLink information # For now we'll only care about Portable & Embedded PDBs - $RelevantExtensions = @('.dll', '.exe', '.pdb') + $RelevantExtensions = @(".dll", ".exe", ".pdb") - Write-Host -NoNewLine 'Validating ' ([System.IO.Path]::GetFileName($PackagePath)) '...' + Write-Host -NoNewLine "Validating" ([System.IO.Path]::GetFileName($PackagePath)) "... " $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId @@ -58,7 +58,7 @@ $ValidatePackage = { $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName # We ignore resource DLLs - if ($FileName.EndsWith('.resources.dll')) { + if ($FileName.EndsWith(".resources.dll")) { return } @@ -96,7 +96,7 @@ $ValidatePackage = { $Uri = $Link -as [System.URI] # Only GitHub links are valid - if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) { + if ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match "github" -or $Uri.Host -match "githubusercontent")) { $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode } else { @@ -143,19 +143,19 @@ $ValidatePackage = { } if ($FailedFiles -eq 0) { - Write-Host 'Passed.' + Write-Host "Passed." return 0 } else { - Write-PipelineTelemetryError -Category 'SourceLink' -Message "$PackagePath has broken SourceLink links." + Write-Host "$PackagePath has broken SourceLink links." return 1 } } function ValidateSourceLinkLinks { - if ($GHRepoName -ne '' -and !($GHRepoName -Match '^[^\s\/]+/[^\s\/]+$')) { - if (!($GHRepoName -Match '^[^\s-]+-[^\s]+$')) { - Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHRepoName should be in the format / or -. '$GHRepoName'" + if ($GHRepoName -ne "" -and !($GHRepoName -Match "^[^\s\/]+/[^\s\/]+$")) { + if (!($GHRepoName -Match "^[^\s-]+-[^\s]+$")) { + Write-PipelineTaskError "GHRepoName should be in the format / or -. '$GHRepoName'" ExitWithExitCode 1 } else { @@ -163,14 +163,14 @@ function ValidateSourceLinkLinks { } } - if ($GHCommit -ne '' -and !($GHCommit -Match '^[0-9a-fA-F]{40}$')) { - Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" + if ($GHCommit -ne "" -and !($GHCommit -Match "^[0-9a-fA-F]{40}$")) { + Write-PipelineTaskError "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'" ExitWithExitCode 1 } - if ($GHRepoName -ne '' -and $GHCommit -ne '') { - $RepoTreeURL = -Join('http://api.github.com/repos/', $GHRepoName, '/git/trees/', $GHCommit, '?recursive=1') - $CodeExtensions = @('.cs', '.vb', '.fs', '.fsi', '.fsx', '.fsscript') + if ($GHRepoName -ne "" -and $GHCommit -ne "") { + $RepoTreeURL = -Join("http://api.github.com/repos/", $GHRepoName, "/git/trees/", $GHCommit, "?recursive=1") + $CodeExtensions = @(".cs", ".vb", ".fs", ".fsi", ".fsx", ".fsscript") try { # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash @@ -188,8 +188,8 @@ function ValidateSourceLinkLinks { Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL . Execution will proceed without caching." } } - elseif ($GHRepoName -ne '' -or $GHCommit -ne '') { - Write-Host 'For using the http caching mechanism both GHRepoName and GHCommit should be informed.' + elseif ($GHRepoName -ne "" -or $GHCommit -ne "") { + Write-Host "For using the http caching mechanism both GHRepoName and GHCommit should be informed." } if (Test-Path $ExtractPath) { @@ -217,18 +217,18 @@ function ValidateSourceLinkLinks { $ValidationFailures = 0 foreach ($Job in @(Get-Job)) { $jobResult = Wait-Job -Id $Job.Id | Receive-Job - if ($jobResult -ne '0') { + if ($jobResult -ne "0") { $ValidationFailures++ } } if ($ValidationFailures -gt 0) { - Write-PipelineTelemetryError -Category 'SourceLink' -Message "$ValidationFailures package(s) failed validation." + Write-PipelineTaskError " $ValidationFailures package(s) failed validation." ExitWithExitCode 1 } } function InstallSourcelinkCli { - $sourcelinkCliPackageName = 'sourcelink' + $sourcelinkCliPackageName = "sourcelink" $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" @@ -239,7 +239,7 @@ function InstallSourcelinkCli { } else { Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..." - Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity "minimal" --global } } @@ -250,8 +250,8 @@ try { ValidateSourceLinkLinks } catch { + Write-Host $_ Write-Host $_.Exception Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category 'SourceLink' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/symbols-validation.ps1 b/eng/common/post-build/symbols-validation.ps1 index 8e9527113c..096ac321d1 100644 --- a/eng/common/post-build/symbols-validation.ps1 +++ b/eng/common/post-build/symbols-validation.ps1 @@ -1,11 +1,13 @@ param( [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation - [Parameter(Mandatory=$true)][string] $DotnetSymbolVersion, # Version of dotnet symbol to use - [Parameter(Mandatory=$false)][switch] $ContinueOnError, # If we should keep checking symbols after an error - [Parameter(Mandatory=$false)][switch] $Clean # Clean extracted symbols directory after checking symbols + [Parameter(Mandatory=$true)][string] $DotnetSymbolVersion # Version of dotnet symbol to use ) +. $PSScriptRoot\post-build-utils.ps1 + +Add-Type -AssemblyName System.IO.Compression.FileSystem + function FirstMatchingSymbolDescriptionOrDefault { param( [string] $FullPath, # Full path to the module that has to be checked @@ -21,19 +23,19 @@ function FirstMatchingSymbolDescriptionOrDefault { # checking and which type of file was uploaded. # The file itself is returned - $SymbolPath = $SymbolsPath + '\' + $FileName + $SymbolPath = $SymbolsPath + "\" + $FileName # PDB file for the module - $PdbPath = $SymbolPath.Replace($Extension, '.pdb') + $PdbPath = $SymbolPath.Replace($Extension, ".pdb") # PDB file for R2R module (created by crossgen) - $NGenPdb = $SymbolPath.Replace($Extension, '.ni.pdb') + $NGenPdb = $SymbolPath.Replace($Extension, ".ni.pdb") # DBG file for a .so library - $SODbg = $SymbolPath.Replace($Extension, '.so.dbg') + $SODbg = $SymbolPath.Replace($Extension, ".so.dbg") # DWARF file for a .dylib - $DylibDwarf = $SymbolPath.Replace($Extension, '.dylib.dwarf') + $DylibDwarf = $SymbolPath.Replace($Extension, ".dylib.dwarf") $dotnetSymbolExe = "$env:USERPROFILE\.dotnet\tools" $dotnetSymbolExe = Resolve-Path "$dotnetSymbolExe\dotnet-symbol.exe" @@ -41,19 +43,19 @@ function FirstMatchingSymbolDescriptionOrDefault { & $dotnetSymbolExe --symbols --modules --windows-pdbs $TargetServerParam $FullPath -o $SymbolsPath | Out-Null if (Test-Path $PdbPath) { - return 'PDB' + return "PDB" } elseif (Test-Path $NGenPdb) { - return 'NGen PDB' + return "NGen PDB" } elseif (Test-Path $SODbg) { - return 'DBG for SO' + return "DBG for SO" } elseif (Test-Path $DylibDwarf) { - return 'Dwarf for Dylib' + return "Dwarf for Dylib" } elseif (Test-Path $SymbolPath) { - return 'Module' + return "Module" } else { return $null @@ -72,7 +74,7 @@ function CountMissingSymbols { } # Extensions for which we'll look for symbols - $RelevantExtensions = @('.dll', '.exe', '.so', '.dylib') + $RelevantExtensions = @(".dll", ".exe", ".so", ".dylib") # How many files are missing symbol information $MissingSymbols = 0 @@ -80,54 +82,43 @@ function CountMissingSymbols { $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) $PackageGuid = New-Guid $ExtractPath = Join-Path -Path $ExtractPath -ChildPath $PackageGuid - $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath 'Symbols' + $SymbolsPath = Join-Path -Path $ExtractPath -ChildPath "Symbols" - try { - [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) - } - catch { - Write-Host "Something went wrong extracting $PackagePath" - Write-Host $_ - return -1 - } + [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) Get-ChildItem -Recurse $ExtractPath | Where-Object {$RelevantExtensions -contains $_.Extension} | ForEach-Object { - if ($_.FullName -Match '\\ref\\') { - Write-Host "`t Ignoring reference assembly file " $_.FullName + if ($_.FullName -Match "\\ref\\") { + Write-Host "`t Ignoring reference assembly file" $_.FullName return } - $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault $_.FullName '--microsoft-symbol-server' $SymbolsPath - $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault $_.FullName '--internal-server' $SymbolsPath + $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--microsoft-symbol-server" $SymbolsPath + $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--internal-server" $SymbolsPath - Write-Host -NoNewLine "`t Checking file " $_.FullName "... " + Write-Host -NoNewLine "`t Checking file" $_.FullName "... " if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { - Write-Host "Symbols found on MSDL ($SymbolsOnMSDL) and SymWeb ($SymbolsOnSymWeb)" + Write-Host "Symbols found on MSDL (" $SymbolsOnMSDL ") and SymWeb (" $SymbolsOnSymWeb ")" } else { $MissingSymbols++ if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { - Write-Host 'No symbols found on MSDL or SymWeb!' + Write-Host "No symbols found on MSDL or SymWeb!" } else { if ($SymbolsOnMSDL -eq $null) { - Write-Host 'No symbols found on MSDL!' + Write-Host "No symbols found on MSDL!" } else { - Write-Host 'No symbols found on SymWeb!' + Write-Host "No symbols found on SymWeb!" } } } } - if ($Clean) { - Remove-Item $ExtractPath -Recurse -Force - } - Pop-Location return $MissingSymbols @@ -138,51 +129,38 @@ function CheckSymbolsAvailable { Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue } - $TotalFailures = 0 - Get-ChildItem "$InputPath\*.nupkg" | ForEach-Object { $FileName = $_.Name - + # These packages from Arcade-Services include some native libraries that # our current symbol uploader can't handle. Below is a workaround until # we get issue: https://github.com/dotnet/arcade/issues/2457 sorted. - if ($FileName -Match 'Microsoft\.DotNet\.Darc\.') { + if ($FileName -Match "Microsoft\.DotNet\.Darc\.") { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } - elseif ($FileName -Match 'Microsoft\.DotNet\.Maestro\.Tasks\.') { + elseif ($FileName -Match "Microsoft\.DotNet\.Maestro\.Tasks\.") { Write-Host "Ignoring Arcade-services file: $FileName" Write-Host return } - + Write-Host "Validating $FileName " $Status = CountMissingSymbols "$InputPath\$FileName" - + if ($Status -ne 0) { - Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Missing symbols for $Status modules in the package $FileName" - - if ($ContinueOnError) { - $TotalFailures++ - } - else { - ExitWithExitCode 1 - } + Write-PipelineTaskError "Missing symbols for $Status modules in the package $FileName" + ExitWithExitCode $exitCode } Write-Host } - - if ($TotalFailures -ne 0) { - Write-PipelineTelemetryError -Category 'CheckSymbols' -Message "Symbols missing for $TotalFailures packages" - ExitWithExitCode 1 - } } function InstallDotnetSymbol { - $dotnetSymbolPackageName = 'dotnet-symbol' + $dotnetSymbolPackageName = "dotnet-symbol" $dotnetRoot = InitializeDotNetCli -install:$true $dotnet = "$dotnetRoot\dotnet.exe" @@ -193,22 +171,19 @@ function InstallDotnetSymbol { } else { Write-Host "Installing dotnet-symbol version $dotnetSymbolVersion..." - Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.' + Write-Host "You may need to restart your command window if this is the first dotnet tool you have installed." & "$dotnet" tool install $dotnetSymbolPackageName --version $dotnetSymbolVersion --verbosity "minimal" --global } } try { - . $PSScriptRoot\post-build-utils.ps1 - - Add-Type -AssemblyName System.IO.Compression.FileSystem - InstallDotnetSymbol CheckSymbolsAvailable } catch { + Write-Host $_ + Write-Host $_.Exception Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category 'CheckSymbols' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/post-build/trigger-subscriptions.ps1 b/eng/common/post-build/trigger-subscriptions.ps1 index 55dea518ac..926d5b4551 100644 --- a/eng/common/post-build/trigger-subscriptions.ps1 +++ b/eng/common/post-build/trigger-subscriptions.ps1 @@ -2,63 +2,56 @@ param( [Parameter(Mandatory=$true)][string] $SourceRepo, [Parameter(Mandatory=$true)][int] $ChannelId, [Parameter(Mandatory=$true)][string] $MaestroApiAccessToken, - [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro-prod.westus2.cloudapp.azure.com', - [Parameter(Mandatory=$false)][string] $MaestroApiVersion = '2019-01-16' + [Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = "https://maestro-prod.westus2.cloudapp.azure.com", + [Parameter(Mandatory=$false)][string] $MaestroApiVersion = "2019-01-16" ) -try { - . $PSScriptRoot\post-build-utils.ps1 +. $PSScriptRoot\post-build-utils.ps1 - # Get all the $SourceRepo subscriptions - $normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '') - $subscriptions = Get-MaestroSubscriptions -SourceRepository $normalizedSourceRepo -ChannelId $ChannelId +# Get all the $SourceRepo subscriptions +$normalizedSourceRepo = $SourceRepo.Replace('dnceng@', '') +$subscriptions = Get-MaestroSubscriptions -SourceRepository $normalizedSourceRepo -ChannelId $ChannelId - if (!$subscriptions) { - Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'" - ExitWithExitCode 0 - } +if (!$subscriptions) { + Write-Host "No subscriptions found for source repo '$normalizedSourceRepo' in channel '$ChannelId'" + ExitWithExitCode 0 +} - $subscriptionsToTrigger = New-Object System.Collections.Generic.List[string] - $failedTriggeredSubscription = $false +$subscriptionsToTrigger = New-Object System.Collections.Generic.List[string] +$failedTriggeredSubscription = $false - # Get all enabled subscriptions that need dependency flow on 'everyBuild' - foreach ($subscription in $subscriptions) { - if ($subscription.enabled -and $subscription.policy.updateFrequency -like 'everyBuild' -and $subscription.channel.id -eq $ChannelId) { - Write-Host "Should trigger this subscription: ${$subscription.id}" - [void]$subscriptionsToTrigger.Add($subscription.id) - } +# Get all enabled subscriptions that need dependency flow on 'everyBuild' +foreach ($subscription in $subscriptions) { + if ($subscription.enabled -and $subscription.policy.updateFrequency -like 'everyBuild' -and $subscription.channel.id -eq $ChannelId) { + Write-Host "Should trigger this subscription: $subscription.id" + [void]$subscriptionsToTrigger.Add($subscription.id) } +} - foreach ($subscriptionToTrigger in $subscriptionsToTrigger) { - try { - Write-Host "Triggering subscription '$subscriptionToTrigger'." - - Trigger-Subscription -SubscriptionId $subscriptionToTrigger - - Write-Host 'done.' - } - catch - { - Write-Host "There was an error while triggering subscription '$subscriptionToTrigger'" - Write-Host $_ - Write-Host $_.ScriptStackTrace - $failedTriggeredSubscription = $true - } +foreach ($subscriptionToTrigger in $subscriptionsToTrigger) { + try { + Write-Host "Triggering subscription '$subscriptionToTrigger'." + + Trigger-Subscription -SubscriptionId $subscriptionToTrigger + + Write-Host "done." + } + catch + { + Write-Host "There was an error while triggering subscription '$subscriptionToTrigger'" + Write-Host $_ + Write-Host $_.ScriptStackTrace + $failedTriggeredSubscription = $true } +} - if ($subscriptionsToTrigger.Count -eq 0) { - Write-Host "No subscription matched source repo '$normalizedSourceRepo' and channel ID '$ChannelId'." - } - elseif ($failedTriggeredSubscription) { - Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message 'At least one subscription failed to be triggered...' - ExitWithExitCode 1 - } - else { - Write-Host 'All subscriptions were triggered successfully!' - } +if ($subscriptionsToTrigger.Count -eq 0) { + Write-Host "No subscription matched source repo '$normalizedSourceRepo' and channel ID '$ChannelId'." } -catch { - Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category 'TriggerSubscriptions' -Message $_ +elseif ($failedTriggeredSubscription) { + Write-Host "At least one subscription failed to be triggered..." ExitWithExitCode 1 } +else { + Write-Host "All subscriptions were triggered successfully!" +} diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index f997be4331..d0eec5163e 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -1,8 +1,8 @@ [CmdletBinding(PositionalBinding=$false)] Param( - [string] $configuration = 'Debug', + [string] $configuration = "Debug", [string] $task, - [string] $verbosity = 'minimal', + [string] $verbosity = "minimal", [string] $msbuildEngine = $null, [switch] $restore, [switch] $prepareMachine, @@ -32,7 +32,7 @@ function Print-Usage() { } function Build([string]$target) { - $logSuffix = if ($target -eq 'Execute') { '' } else { ".$target" } + $logSuffix = if ($target -eq "Execute") { "" } else { ".$target" } $log = Join-Path $LogDir "$task$logSuffix.binlog" $outputPath = Join-Path $ToolsetDir "$task\\" @@ -46,45 +46,33 @@ function Build([string]$target) { } try { - if ($help -or (($null -ne $properties) -and ($properties.Contains('/help') -or $properties.Contains('/?')))) { + if ($help -or (($null -ne $properties) -and ($properties.Contains("/help") -or $properties.Contains("/?")))) { Print-Usage exit 0 } if ($task -eq "") { - Write-PipelineTelemetryError -Category 'Build' -Message "Missing required parameter '-task '" -ForegroundColor Red + Write-Host "Missing required parameter '-task '" -ForegroundColor Red Print-Usage ExitWithExitCode 1 } - if( $msbuildEngine -eq "vs") { - # Ensure desktop MSBuild is available for sdk tasks. - if( -not ($GlobalJson.tools.PSObject.Properties.Name -contains "vs" )) { - $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty - } - if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { - $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "16.5.0-alpha" -MemberType NoteProperty - } - - $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true - $global:_MSBuildExe = "$($xcopyMSBuildToolsFolder)\MSBuild\Current\Bin\MSBuild.exe" - } - $taskProject = GetSdkTaskProject $task if (!(Test-Path $taskProject)) { - Write-PipelineTelemetryError -Category 'Build' -Message "Unknown task: $task" -ForegroundColor Red + Write-Host "Unknown task: $task" -ForegroundColor Red ExitWithExitCode 1 } if ($restore) { - Build 'Restore' + Build "Restore" } - Build 'Execute' + Build "Execute" } catch { + Write-Host $_ + Write-Host $_.Exception Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category 'Build' -Message $_ ExitWithExitCode 1 } diff --git a/eng/common/sdl/execute-all-sdl-tools.ps1 b/eng/common/sdl/execute-all-sdl-tools.ps1 index b7f61f9a2f..01799d63ff 100644 --- a/eng/common/sdl/execute-all-sdl-tools.ps1 +++ b/eng/common/sdl/execute-all-sdl-tools.ps1 @@ -1,114 +1,100 @@ Param( - [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) - [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) - [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified - [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) - [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master - [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located - [string] $ArtifactsDirectory = (Join-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY ('artifacts')), # Required: the directory where build artifacts are located - [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault - [string[]] $SourceToolsList, # Optional: list of SDL tools to run on source code - [string[]] $ArtifactToolsList, # Optional: list of SDL tools to run on built artifacts - [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. - [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. - [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) - [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed - [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. - [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. - [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. - [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. - [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. - [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. - [string] $GuardianLoggerLevel='Standard', # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error - [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") - [string[]] $PoliCheckAdditionalRunConfigParams # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") + [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified) + [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified) + [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified + [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade) + [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master + [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located + [string] $ArtifactsDirectory = (Join-Path $env:BUILD_SOURCESDIRECTORY ("artifacts")), # Required: the directory where build artifacts are located + [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault + [string[]] $SourceToolsList, # Optional: list of SDL tools to run on source code + [string[]] $ArtifactToolsList, # Optional: list of SDL tools to run on built artifacts + [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs. + [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs. + [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber) + [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed + [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs. + [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs. + [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs. + [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs. + [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs. + [string] $GuardianLoggerLevel="Standard", # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error + [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1") + [string[]] $PoliCheckAdditionalRunConfigParams # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1") ) -try { - $ErrorActionPreference = 'Stop' - Set-StrictMode -Version 2.0 - $disableConfigureToolsetImport = $true - $LASTEXITCODE = 0 +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 +$LASTEXITCODE = 0 - # `tools.ps1` checks $ci to perform some actions. Since the SDL - # scripts don't necessarily execute in the same agent that run the - # build.ps1/sh script this variable isn't automatically set. - $ci = $true - . $PSScriptRoot\..\tools.ps1 - - #Replace repo names to the format of org/repo - if (!($Repository.contains('/'))) { - $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; - } - else{ - $RepoName = $Repository; - } +#Replace repo names to the format of org/repo +if (!($Repository.contains('/'))) { + $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2'; +} +else{ + $RepoName = $Repository; +} - if ($GuardianPackageName) { - $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path 'tools' 'guardian.cmd')) - } else { - $guardianCliLocation = $GuardianCliLocation - } +if ($GuardianPackageName) { + $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path "tools" "guardian.cmd")) +} else { + $guardianCliLocation = $GuardianCliLocation +} - $workingDirectory = (Split-Path $SourceDirectory -Parent) - $ValidPath = Test-Path $guardianCliLocation +$workingDirectory = (Split-Path $SourceDirectory -Parent) +$ValidPath = Test-Path $guardianCliLocation - if ($ValidPath -eq $False) - { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Invalid Guardian CLI Location.' - ExitWithExitCode 1 - } +if ($ValidPath -eq $False) +{ + Write-Host "Invalid Guardian CLI Location." + exit 1 +} - & $(Join-Path $PSScriptRoot 'init-sdl.ps1') -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel - $gdnFolder = Join-Path $workingDirectory '.gdn' +& $(Join-Path $PSScriptRoot "init-sdl.ps1") -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel +$gdnFolder = Join-Path $workingDirectory ".gdn" - if ($TsaOnboard) { - if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { - Write-Host "$guardianCliLocation tsa-onboard --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" - & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel - if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-onboard failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE - } - } else { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not onboard to TSA -- not all required values ($TsaCodebaseName, $TsaNotificationEmail, $TsaCodebaseAdmin, $TsaBugAreaPath) were specified.' - ExitWithExitCode 1 +if ($TsaOnboard) { + if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) { + Write-Host "$guardianCliLocation tsa-onboard --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian tsa-onboard failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE } + } else { + Write-Host "Could not onboard to TSA -- not all required values ($$TsaCodebaseName, $$TsaNotificationEmail, $$TsaCodebaseAdmin, $$TsaBugAreaPath) were specified." + exit 1 } +} - if ($ArtifactToolsList -and $ArtifactToolsList.Count -gt 0) { - & $(Join-Path $PSScriptRoot 'run-sdl.ps1') -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $ArtifactsDirectory -GdnFolder $gdnFolder -ToolsList $ArtifactToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams - } - if ($SourceToolsList -and $SourceToolsList.Count -gt 0) { - & $(Join-Path $PSScriptRoot 'run-sdl.ps1') -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $SourceDirectory -GdnFolder $gdnFolder -ToolsList $SourceToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams - } +if ($ArtifactToolsList -and $ArtifactToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot "run-sdl.ps1") -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $ArtifactsDirectory -GdnFolder $gdnFolder -ToolsList $ArtifactToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams +} +if ($SourceToolsList -and $SourceToolsList.Count -gt 0) { + & $(Join-Path $PSScriptRoot "run-sdl.ps1") -GuardianCliLocation $guardianCliLocation -WorkingDirectory $workingDirectory -TargetDirectory $SourceDirectory -GdnFolder $gdnFolder -ToolsList $SourceToolsList -AzureDevOpsAccessToken $AzureDevOpsAccessToken -UpdateBaseline $UpdateBaseline -GuardianLoggerLevel $GuardianLoggerLevel -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams +} - if ($UpdateBaseline) { - & (Join-Path $PSScriptRoot 'push-gdn.ps1') -Repository $RepoName -BranchName $BranchName -GdnFolder $GdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason 'Update baseline' - } +if ($UpdateBaseline) { + & (Join-Path $PSScriptRoot "push-gdn.ps1") -Repository $RepoName -BranchName $BranchName -GdnFolder $GdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason "Update baseline" +} - if ($TsaPublish) { - if ($TsaBranchName -and $BuildNumber) { - if (-not $TsaRepositoryName) { - $TsaRepositoryName = "$($Repository)-$($BranchName)" - } - Write-Host "$guardianCliLocation tsa-publish --all-tools --repository-name `"$TsaRepositoryName`" --branch-name `"$TsaBranchName`" --build-number `"$BuildNumber`" --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" - & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel - if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-publish failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE - } - } else { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not publish to TSA -- not all required values ($TsaBranchName, $BuildNumber) were specified.' - ExitWithExitCode 1 +if ($TsaPublish) { + if ($TsaBranchName -and $BuildNumber) { + if (-not $TsaRepositoryName) { + $TsaRepositoryName = "$($Repository)-$($BranchName)" + } + Write-Host "$guardianCliLocation tsa-publish --all-tools --repository-name `"$TsaRepositoryName`" --branch-name `"$TsaBranchName`" --build-number `"$BuildNumber`" --codebase-name `"$TsaCodebaseName`" --notification-alias `"$TsaNotificationEmail`" --codebase-admin `"$TsaCodebaseAdmin`" --instance-url `"$TsaInstanceUrl`" --project-name `"$TsaProjectName`" --area-path `"$TsaBugAreaPath`" --iteration-path `"$TsaIterationPath`" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel" + & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian tsa-publish failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE } + } else { + Write-Host "Could not publish to TSA -- not all required values ($$TsaBranchName, $$BuildNumber) were specified." + exit 1 } } -catch { - Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ - exit 1 -} diff --git a/eng/common/sdl/extract-artifact-packages.ps1 b/eng/common/sdl/extract-artifact-packages.ps1 index 7f28d9c59e..6e6825013b 100644 --- a/eng/common/sdl/extract-artifact-packages.ps1 +++ b/eng/common/sdl/extract-artifact-packages.ps1 @@ -3,12 +3,54 @@ param( [Parameter(Mandatory=$true)][string] $ExtractPath # Full path to directory where the packages will be extracted ) -$ErrorActionPreference = 'Stop' +$ErrorActionPreference = "Stop" Set-StrictMode -Version 2.0 -$disableConfigureToolsetImport = $true +# `tools.ps1` checks $ci to perform some actions. Since the post-build +# scripts don't necessarily execute in the same agent that run the +# build.ps1/sh script this variable isn't automatically set. +$ci = $true +. $PSScriptRoot\..\tools.ps1 -function ExtractArtifacts { +$ExtractPackage = { + param( + [string] $PackagePath # Full path to a NuGet package + ) + + if (!(Test-Path $PackagePath)) { + Write-PipelineTaskError "Input file does not exist: $PackagePath" + ExitWithExitCode 1 + } + + $RelevantExtensions = @(".dll", ".exe", ".pdb") + Write-Host -NoNewLine "Extracting" ([System.IO.Path]::GetFileName($PackagePath)) "... " + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId + + Add-Type -AssemblyName System.IO.Compression.FileSystem + + [System.IO.Directory]::CreateDirectory($ExtractPath); + + try { + $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + + $zip.Entries | + Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | + ForEach-Object { + $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.Name + + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) + } + } + catch { + + } + finally { + $zip.Dispose() + } + } + function ExtractArtifacts { if (!(Test-Path $InputPath)) { Write-Host "Input Path does not exist: $InputPath" ExitWithExitCode 0 @@ -25,56 +67,11 @@ function ExtractArtifacts { } try { - # `tools.ps1` checks $ci to perform some actions. Since the SDL - # scripts don't necessarily execute in the same agent that run the - # build.ps1/sh script this variable isn't automatically set. - $ci = $true - . $PSScriptRoot\..\tools.ps1 - - $ExtractPackage = { - param( - [string] $PackagePath # Full path to a NuGet package - ) - - if (!(Test-Path $PackagePath)) { - Write-PipelineTelemetryError -Category 'Build' -Message "Input file does not exist: $PackagePath" - ExitWithExitCode 1 - } - - $RelevantExtensions = @('.dll', '.exe', '.pdb') - Write-Host -NoNewLine 'Extracting ' ([System.IO.Path]::GetFileName($PackagePath)) '...' - - $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) - $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId - - Add-Type -AssemblyName System.IO.Compression.FileSystem - - [System.IO.Directory]::CreateDirectory($ExtractPath); - - try { - $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) - - $zip.Entries | - Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} | - ForEach-Object { - $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.Name - - [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true) - } - } - catch { - Write-Host $_ - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ - ExitWithExitCode 1 - } - finally { - $zip.Dispose() - } - } Measure-Command { ExtractArtifacts } } catch { Write-Host $_ - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ + Write-Host $_.Exception + Write-Host $_.ScriptStackTrace ExitWithExitCode 1 } diff --git a/eng/common/sdl/init-sdl.ps1 b/eng/common/sdl/init-sdl.ps1 index a68bf0b88e..c737eb0e71 100644 --- a/eng/common/sdl/init-sdl.ps1 +++ b/eng/common/sdl/init-sdl.ps1 @@ -1,37 +1,31 @@ Param( [string] $GuardianCliLocation, [string] $Repository, - [string] $BranchName='master', + [string] $BranchName="master", [string] $WorkingDirectory, [string] $AzureDevOpsAccessToken, - [string] $GuardianLoggerLevel='Standard' + [string] $GuardianLoggerLevel="Standard" ) -$ErrorActionPreference = 'Stop' +$ErrorActionPreference = "Stop" Set-StrictMode -Version 2.0 -$disableConfigureToolsetImport = $true $LASTEXITCODE = 0 -# `tools.ps1` checks $ci to perform some actions. Since the SDL -# scripts don't necessarily execute in the same agent that run the -# build.ps1/sh script this variable isn't automatically set. -$ci = $true -. $PSScriptRoot\..\tools.ps1 - # Don't display the console progress UI - it's a huge perf hit $ProgressPreference = 'SilentlyContinue' # Construct basic auth from AzDO access token; construct URI to the repository's gdn folder stored in that repository; construct location of zip file $encodedPat = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$AzureDevOpsAccessToken")) $escapedRepository = [Uri]::EscapeDataString("/$Repository/$BranchName/.gdn") -$uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0" +$uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0-preview.1" $zipFile = "$WorkingDirectory/gdn.zip" Add-Type -AssemblyName System.IO.Compression.FileSystem -$gdnFolder = (Join-Path $WorkingDirectory '.gdn') -try { +$gdnFolder = (Join-Path $WorkingDirectory ".gdn") +Try +{ # We try to download the zip; if the request fails (e.g. the file doesn't exist), we catch it and init guardian instead - Write-Host 'Downloading gdn folder from internal config repostiory...' + Write-Host "Downloading gdn folder from internal config repostiory..." Invoke-WebRequest -Headers @{ "Accept"="application/zip"; "Authorization"="Basic $encodedPat" } -Uri $uri -OutFile $zipFile if (Test-Path $gdnFolder) { # Remove the gdn folder if it exists (it shouldn't unless there's too much caching; this is just in case) @@ -39,29 +33,19 @@ try { } [System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile, $WorkingDirectory) Write-Host $gdnFolder - ExitWithExitCode 0 -} catch [System.Net.WebException] { } # Catch and ignore webexception -try { +} Catch [System.Net.WebException] { # if the folder does not exist, we'll do a guardian init and push it to the remote repository - Write-Host 'Initializing Guardian...' + Write-Host "Initializing Guardian..." Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel" & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian init failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE + Write-Error "Guardian init failed with exit code $LASTEXITCODE." } # We create the mainbaseline so it can be edited later Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline" & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian baseline failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE + Write-Error "Guardian baseline failed with exit code $LASTEXITCODE." } - & $(Join-Path $PSScriptRoot 'push-gdn.ps1') -Repository $Repository -BranchName $BranchName -GdnFolder $gdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason 'Initialize gdn folder' - ExitWithExitCode 0 -} -catch { - Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ - ExitWithExitCode 1 -} + & $(Join-Path $PSScriptRoot "push-gdn.ps1") -Repository $Repository -BranchName $BranchName -GdnFolder $gdnFolder -AzureDevOpsAccessToken $AzureDevOpsAccessToken -PushReason "Initialize gdn folder" +} \ No newline at end of file diff --git a/eng/common/sdl/packages.config b/eng/common/sdl/packages.config index 968b39bef5..256ffbfb93 100644 --- a/eng/common/sdl/packages.config +++ b/eng/common/sdl/packages.config @@ -1,4 +1,4 @@ - + diff --git a/eng/common/sdl/push-gdn.ps1 b/eng/common/sdl/push-gdn.ps1 index d8fd2d82a6..79c707d6d8 100644 --- a/eng/common/sdl/push-gdn.ps1 +++ b/eng/common/sdl/push-gdn.ps1 @@ -1,69 +1,51 @@ Param( [string] $Repository, - [string] $BranchName='master', + [string] $BranchName="master", [string] $GdnFolder, [string] $AzureDevOpsAccessToken, [string] $PushReason ) -$ErrorActionPreference = 'Stop' +$ErrorActionPreference = "Stop" Set-StrictMode -Version 2.0 -$disableConfigureToolsetImport = $true $LASTEXITCODE = 0 -try { - # `tools.ps1` checks $ci to perform some actions. Since the SDL - # scripts don't necessarily execute in the same agent that run the - # build.ps1/sh script this variable isn't automatically set. - $ci = $true - . $PSScriptRoot\..\tools.ps1 - - # We create the temp directory where we'll store the sdl-config repository - $sdlDir = Join-Path $env:TEMP 'sdl' - if (Test-Path $sdlDir) { - Remove-Item -Force -Recurse $sdlDir - } - - Write-Host "git clone https://dnceng:`$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir" - git clone https://dnceng:$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir - if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Git clone failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE - } - # We copy the .gdn folder from our local run into the git repository so it can be committed - $sdlRepositoryFolder = Join-Path (Join-Path (Join-Path $sdlDir $Repository) $BranchName) '.gdn' - if (Get-Command Robocopy) { - Robocopy /S $GdnFolder $sdlRepositoryFolder - } else { - rsync -r $GdnFolder $sdlRepositoryFolder - } - # cd to the sdl-config directory so we can run git there - Push-Location $sdlDir - # git add . --> git commit --> git push - Write-Host 'git add .' - git add . - if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Git add failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE - } - Write-Host "git -c user.email=`"dn-bot@microsoft.com`" -c user.name=`"Dotnet Bot`" commit -m `"$PushReason for $Repository/$BranchName`"" - git -c user.email="dn-bot@microsoft.com" -c user.name="Dotnet Bot" commit -m "$PushReason for $Repository/$BranchName" - if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Git commit failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE - } - Write-Host 'git push' - git push - if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Git push failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE - } +# We create the temp directory where we'll store the sdl-config repository +$sdlDir = Join-Path $env:TEMP "sdl" +if (Test-Path $sdlDir) { + Remove-Item -Force -Recurse $sdlDir +} - # Return to the original directory - Pop-Location +Write-Host "git clone https://dnceng:`$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir" +git clone https://dnceng:$AzureDevOpsAccessToken@dev.azure.com/dnceng/internal/_git/sdl-tool-cfg $sdlDir +if ($LASTEXITCODE -ne 0) { + Write-Error "Git clone failed with exit code $LASTEXITCODE." +} +# We copy the .gdn folder from our local run into the git repository so it can be committed +$sdlRepositoryFolder = Join-Path (Join-Path (Join-Path $sdlDir $Repository) $BranchName) ".gdn" +if (Get-Command Robocopy) { + Robocopy /S $GdnFolder $sdlRepositoryFolder +} else { + rsync -r $GdnFolder $sdlRepositoryFolder } -catch { - Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Category 'Sdl' -Message $_ - ExitWithExitCode 1 +# cd to the sdl-config directory so we can run git there +Push-Location $sdlDir +# git add . --> git commit --> git push +Write-Host "git add ." +git add . +if ($LASTEXITCODE -ne 0) { + Write-Error "Git add failed with exit code $LASTEXITCODE." } +Write-Host "git -c user.email=`"dn-bot@microsoft.com`" -c user.name=`"Dotnet Bot`" commit -m `"$PushReason for $Repository/$BranchName`"" +git -c user.email="dn-bot@microsoft.com" -c user.name="Dotnet Bot" commit -m "$PushReason for $Repository/$BranchName" +if ($LASTEXITCODE -ne 0) { + Write-Error "Git commit failed with exit code $LASTEXITCODE." +} +Write-Host "git push" +git push +if ($LASTEXITCODE -ne 0) { + Write-Error "Git push failed with exit code $LASTEXITCODE." +} + +# Return to the original directory +Pop-Location \ No newline at end of file diff --git a/eng/common/sdl/run-sdl.ps1 b/eng/common/sdl/run-sdl.ps1 index fe95ab35aa..9bc25314ae 100644 --- a/eng/common/sdl/run-sdl.ps1 +++ b/eng/common/sdl/run-sdl.ps1 @@ -5,69 +5,55 @@ Param( [string] $GdnFolder, [string[]] $ToolsList, [string] $UpdateBaseline, - [string] $GuardianLoggerLevel='Standard', + [string] $GuardianLoggerLevel="Standard", [string[]] $CrScanAdditionalRunConfigParams, [string[]] $PoliCheckAdditionalRunConfigParams ) -$ErrorActionPreference = 'Stop' +$ErrorActionPreference = "Stop" Set-StrictMode -Version 2.0 -$disableConfigureToolsetImport = $true $LASTEXITCODE = 0 -try { - # `tools.ps1` checks $ci to perform some actions. Since the SDL - # scripts don't necessarily execute in the same agent that run the - # build.ps1/sh script this variable isn't automatically set. - $ci = $true - . $PSScriptRoot\..\tools.ps1 +# We store config files in the r directory of .gdn +Write-Host $ToolsList +$gdnConfigPath = Join-Path $GdnFolder "r" +$ValidPath = Test-Path $GuardianCliLocation - # We store config files in the r directory of .gdn - Write-Host $ToolsList - $gdnConfigPath = Join-Path $GdnFolder 'r' - $ValidPath = Test-Path $GuardianCliLocation - - if ($ValidPath -eq $False) - { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location." - ExitWithExitCode 1 - } - - $configParam = @('--config') +if ($ValidPath -eq $False) +{ + Write-Host "Invalid Guardian CLI Location." + exit 1 +} - foreach ($tool in $ToolsList) { - $gdnConfigFile = Join-Path $gdnConfigPath "$tool-configure.gdnconfig" - Write-Host $tool - # We have to manually configure tools that run on source to look at the source directory only - if ($tool -eq 'credscan') { - Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" TargetDirectory < $TargetDirectory `" `" OutputType < pre `" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams})" - & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " TargetDirectory < $TargetDirectory " "OutputType < pre" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams}) - if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian configure for $tool failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE - } +$configParam = @("--config") + +foreach ($tool in $ToolsList) { + $gdnConfigFile = Join-Path $gdnConfigPath "$tool-configure.gdnconfig" + Write-Host $tool + # We have to manually configure tools that run on source to look at the source directory only + if ($tool -eq "credscan") { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" TargetDirectory < $TargetDirectory `" `" OutputType < pre `" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams})" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " TargetDirectory < $TargetDirectory " "OutputType < pre" $(If ($CrScanAdditionalRunConfigParams) {$CrScanAdditionalRunConfigParams}) + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian configure for $tool failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE } - if ($tool -eq 'policheck') { - Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" Target < $TargetDirectory `" $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams})" - & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " Target < $TargetDirectory " $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams}) - if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian configure for $tool failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE - } + } + if ($tool -eq "policheck") { + Write-Host "$GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args `" Target < $TargetDirectory `" $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams})" + & $GuardianCliLocation configure --working-directory $WorkingDirectory --tool $tool --output-path $gdnConfigFile --logger-level $GuardianLoggerLevel --noninteractive --force --args " Target < $TargetDirectory " $(If ($PoliCheckAdditionalRunConfigParams) {$PoliCheckAdditionalRunConfigParams}) + if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian configure for $tool failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE } - - $configParam+=$gdnConfigFile } - Write-Host "$GuardianCliLocation run --working-directory $WorkingDirectory --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam" - & $GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam - if ($LASTEXITCODE -ne 0) { - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian run for $ToolsList using $configParam failed with exit code $LASTEXITCODE." - ExitWithExitCode $LASTEXITCODE - } + $configParam+=$gdnConfigFile } -catch { - Write-Host $_.ScriptStackTrace - Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_ - ExitWithExitCode 1 + +Write-Host "$GuardianCliLocation run --working-directory $WorkingDirectory --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam" +& $GuardianCliLocation run --working-directory $WorkingDirectory --tool $tool --baseline mainbaseline --update-baseline $UpdateBaseline --logger-level $GuardianLoggerLevel $configParam +if ($LASTEXITCODE -ne 0) { + Write-Host "Guardian run for $ToolsList using $configParam failed with exit code $LASTEXITCODE." + exit $LASTEXITCODE } diff --git a/eng/common/templates/job/execute-sdl.yml b/eng/common/templates/job/execute-sdl.yml index c64c4f5686..52e2ff021d 100644 --- a/eng/common/templates/job/execute-sdl.yml +++ b/eng/common/templates/job/execute-sdl.yml @@ -1,12 +1,10 @@ parameters: - enable: 'false' # Whether the SDL validation job should execute or not overrideParameters: '' # Optional: to override values for parameters. additionalParameters: '' # Optional: parameters that need user specific values eg: '-SourceToolsList @("abc","def") -ArtifactToolsList @("ghi","jkl")' # There is some sort of bug (has been reported) in Azure DevOps where if this parameter is named # 'continueOnError', the parameter value is not correctly picked up. # This can also be remedied by the caller (post-build.yml) if it does not use a nested parameter sdlContinueOnError: false # optional: determines whether to continue the build if the step errors; - downloadArtifacts: true # optional: determines if the artifacts should be dowloaded dependsOn: '' # Optional: dependencies of the job artifactNames: '' # Optional: patterns supplied to DownloadBuildArtifacts # Usage: @@ -18,53 +16,37 @@ jobs: - job: Run_SDL dependsOn: ${{ parameters.dependsOn }} displayName: Run SDL tool - condition: eq( ${{ parameters.enable }}, 'true') variables: - group: DotNet-VSTS-Bot - - name: AzDOProjectName - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] - - name: AzDOPipelineId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] - - name: AzDOBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: name: Hosted VS2017 steps: - checkout: self clean: true - - ${{ if ne(parameters.downloadArtifacts, 'false')}}: - - ${{ if ne(parameters.artifactNames, '') }}: - - ${{ each artifactName in parameters.artifactNames }}: - - task: DownloadBuildArtifacts@0 - displayName: Download Build Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: ${{ artifactName }} - downloadPath: $(Build.ArtifactStagingDirectory)\artifacts - - ${{ if eq(parameters.artifactNames, '') }}: + - ${{ if ne(parameters.artifactNames, '') }}: + - ${{ each artifactName in parameters.artifactNames }}: - task: DownloadBuildArtifacts@0 displayName: Download Build Artifacts inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - downloadType: specific files - itemPattern: "**" + buildType: current + artifactName: ${{ artifactName }} downloadPath: $(Build.ArtifactStagingDirectory)\artifacts + - ${{ if eq(parameters.artifactNames, '') }}: + - task: DownloadBuildArtifacts@0 + displayName: Download Build Artifacts + inputs: + buildType: current + downloadType: specific files + itemPattern: "**" + downloadPath: $(Build.ArtifactStagingDirectory)\artifacts - powershell: eng/common/sdl/extract-artifact-packages.ps1 - -InputPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts - -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\BlobArtifacts + -InputPath $(Build.SourcesDirectory)\artifacts\BlobArtifacts + -ExtractPath $(Build.SourcesDirectory)\artifacts\BlobArtifacts displayName: Extract Blob Artifacts continueOnError: ${{ parameters.sdlContinueOnError }} - powershell: eng/common/sdl/extract-artifact-packages.ps1 - -InputPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts - -ExtractPath $(Build.ArtifactStagingDirectory)\artifacts\PackageArtifacts + -InputPath $(Build.SourcesDirectory)\artifacts\PackageArtifacts + -ExtractPath $(Build.SourcesDirectory)\artifacts\PackageArtifacts displayName: Extract Package Artifacts continueOnError: ${{ parameters.sdlContinueOnError }} - task: NuGetToolInstaller@1 @@ -83,7 +65,7 @@ jobs: continueOnError: ${{ parameters.sdlContinueOnError }} - ${{ if eq(parameters.overrideParameters, '') }}: - powershell: eng/common/sdl/execute-all-sdl-tools.ps1 - -GuardianPackageName Microsoft.Guardian.Cli.win10-x64.0.20.1 + -GuardianPackageName Microsoft.Guardian.Cli.0.7.2 -NugetPackageDirectory $(Build.SourcesDirectory)\.packages -AzureDevOpsAccessToken $(dn-bot-dotnet-build-rw-code-rw) ${{ parameters.additionalParameters }} diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml index fc39647f4b..ffda80a197 100644 --- a/eng/common/templates/job/job.yml +++ b/eng/common/templates/job/job.yml @@ -1,36 +1,67 @@ -# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, -# and some (Microbuild) should only be applied to non-PR cases for internal builds. - parameters: # Job schema parameters - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job cancelTimeoutInMinutes: '' + condition: '' - container: '' + continueOnError: false + + container: '' + dependsOn: '' + displayName: '' - pool: '' + steps: [] + + pool: '' + strategy: '' + timeoutInMinutes: '' + variables: [] + workspace: '' -# Job base template specific parameters - # See schema documentation - https://github.com/dotnet/arcade/blob/master/Documentation/AzureDevOps/TemplateSchema.md - artifacts: '' + # Job base template specific parameters + # Optional: Enable installing Microbuild plugin + # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix + # _TeamName - the name of your team + # _SignType - 'test' or 'real' enableMicrobuild: false + + # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false + + # Optional: Enable publishing to the build asset registry enablePublishBuildAssets: false - enablePublishTestResults: false + + # Optional: Prevent gather/push manifest from executing when using publishing pipelines enablePublishUsingPipelines: false - useBuildManifest: false - mergeTestResults: false - testRunTitle: $(AgentOsName)-$(BuildConfiguration)-xunit + + # Optional: Include PublishTestResults task + enablePublishTestResults: false + + # Optional: enable sending telemetry + enableTelemetry: false + + # Optional: define the helix repo for telemetry (example: 'dotnet/arcade') + helixRepo: '' + + # Optional: define the helix type for telemetry (example: 'build/product/') + helixType: '' + + # Required: name of the job name: '' - preSteps: [] + + # Optional: should run as a public build even in the internal project + # if 'true', the build won't run any of the internal only steps, even if it is running in non-public projects. runAsPublic: false +# Internal resources (telemetry, microbuild) can only be accessed from non-public projects, +# and some (Microbuild) should only be applied to non-PR cases for internal builds. + jobs: - job: ${{ parameters.name }} @@ -62,7 +93,7 @@ jobs: timeoutInMinutes: ${{ parameters.timeoutInMinutes }} variables: - - ${{ if ne(parameters.enableTelemetry, 'false') }}: + - ${{ if eq(parameters.enableTelemetry, 'true') }}: - name: DOTNET_CLI_TELEMETRY_PROFILE value: '$(Build.Repository.Uri)' - ${{ each variable in parameters.variables }}: @@ -94,12 +125,21 @@ jobs: workspace: ${{ parameters.workspace }} steps: - - ${{ if ne(parameters.preSteps, '') }}: - - ${{ each preStep in parameters.preSteps }}: - - ${{ preStep }} + - ${{ if eq(parameters.enableTelemetry, 'true') }}: + # Telemetry tasks are built from https://github.com/dotnet/arcade-extensions + - task: sendStartTelemetry@0 + displayName: 'Send Helix Start Telemetry' + inputs: + helixRepo: ${{ parameters.helixRepo }} + ${{ if ne(parameters.helixType, '') }}: + helixType: ${{ parameters.helixType }} + buildConfig: $(_BuildConfig) + runAsPublic: ${{ parameters.runAsPublic }} + continueOnError: ${{ parameters.continueOnError }} + condition: always() - - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if eq(parameters.enableMicrobuild, 'true') }}: + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: MicroBuildSigningPlugin@2 displayName: Install MicroBuild plugin inputs: @@ -111,16 +151,9 @@ jobs: continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), in(variables['_SignType'], 'real', 'test'), eq(variables['Agent.Os'], 'Windows_NT')) + - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - task: NuGetAuthenticate@0 - - ${{ if or(eq(parameters.artifacts.download, 'true'), ne(parameters.artifacts.download, '')) }}: - - task: DownloadPipelineArtifact@2 - inputs: - buildType: current - artifactName: ${{ coalesce(parameters.artifacts.download.name, 'Artifacts_$(Agent.OS)_$(_BuildConfig)') }} - targetPath: ${{ coalesce(parameters.artifacts.download.path, 'artifacts') }} - itemPattern: ${{ coalesce(parameters.artifacts.download.pattern, '**') }} - - ${{ each step in parameters.steps }}: - ${{ step }} @@ -133,60 +166,20 @@ jobs: env: TeamName: $(_TeamName) - - ${{ if ne(parameters.artifacts.publish, '') }}: - - ${{ if or(eq(parameters.artifacts.publish.artifacts, 'true'), ne(parameters.artifacts.publish.artifacts, '')) }}: - - task: CopyFiles@2 - displayName: Gather binaries for publish to artifacts - inputs: - SourceFolder: 'artifacts/bin' - Contents: '**' - TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/bin' - - task: CopyFiles@2 - displayName: Gather packages for publish to artifacts - inputs: - SourceFolder: 'artifacts/packages' - Contents: '**' - TargetFolder: '$(Build.ArtifactStagingDirectory)/artifacts/packages' - - task: PublishBuildArtifacts@1 - displayName: Publish pipeline artifacts - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts' - PublishLocation: Container - ArtifactName: ${{ coalesce(parameters.artifacts.publish.artifacts.name , 'Artifacts_$(Agent.Os)_$(_BuildConfig)') }} - continueOnError: true - condition: always() - - ${{ if or(eq(parameters.artifacts.publish.logs, 'true'), ne(parameters.artifacts.publish.logs, '')) }}: - - publish: artifacts/log - artifact: ${{ coalesce(parameters.artifacts.publish.logs.name, 'Logs_Build_$(Agent.Os)_$(_BuildConfig)') }} - displayName: Publish logs - continueOnError: true - condition: always() - - ${{ if or(eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: - - ${{ if and(ne(parameters.enablePublishUsingPipelines, 'true'), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - task: CopyFiles@2 - displayName: Gather Asset Manifests - inputs: - SourceFolder: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/AssetManifest' - TargetFolder: '$(Build.ArtifactStagingDirectory)/AssetManifests' - continueOnError: ${{ parameters.continueOnError }} - condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) - - - task: PublishBuildArtifacts@1 - displayName: Push Asset Manifests - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)/AssetManifests' - PublishLocation: Container - ArtifactName: AssetManifests - continueOnError: ${{ parameters.continueOnError }} - condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) - - - ${{ if ne(parameters.enablePublishBuildArtifacts, 'false') }}: + - ${{ if eq(parameters.enableTelemetry, 'true') }}: + # Telemetry tasks are built from https://github.com/dotnet/arcade-extensions + - task: sendEndTelemetry@0 + displayName: 'Send Helix End Telemetry' + continueOnError: ${{ parameters.continueOnError }} + condition: always() + + - ${{ if eq(parameters.enablePublishBuildArtifacts, 'true') }}: - task: PublishBuildArtifacts@1 displayName: Publish Logs inputs: PathtoPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)' PublishLocation: Container - ArtifactName: ${{ coalesce(parameters.enablePublishBuildArtifacts.artifactName, '$(Agent.Os)_$(Agent.JobName)' ) }} + ArtifactName: $(Agent.Os)_$(Agent.JobName) continueOnError: true condition: always() @@ -197,8 +190,6 @@ jobs: testResultsFormat: 'xUnit' testResultsFiles: '*.xml' searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)' - testRunTitle: ${{ parameters.testRunTitle }} - mergeTestResults: ${{ parameters.mergeTestResults }} continueOnError: true condition: always() @@ -219,12 +210,3 @@ jobs: ArtifactName: AssetManifests continueOnError: ${{ parameters.continueOnError }} condition: and(succeeded(), eq(variables['_DotNetPublishToBlobFeed'], 'true')) - - - ${{ if eq(parameters.useBuildManifest, true) }}: - - task: PublishBuildArtifacts@1 - displayName: Publish Build Manifest - inputs: - PathToPublish: '$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/manifest.props' - PublishLocation: Container - ArtifactName: BuildManifests - continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/common/templates/job/publish-build-assets.yml b/eng/common/templates/job/publish-build-assets.yml index 055304ad89..b722975f9c 100644 --- a/eng/common/templates/job/publish-build-assets.yml +++ b/eng/common/templates/job/publish-build-assets.yml @@ -37,12 +37,6 @@ jobs: - name: _BuildConfig value: ${{ parameters.configuration }} - group: Publish-Build-Assets - # Skip component governance and codesign validation for SDL. These jobs - # create no content. - - name: skipComponentGovernanceDetection - value: true - - name: runCodesignValidationInjection - value: false steps: - ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: diff --git a/eng/common/templates/jobs/jobs.yml b/eng/common/templates/jobs/jobs.yml index c08225a9a9..6a2f98c036 100644 --- a/eng/common/templates/jobs/jobs.yml +++ b/eng/common/templates/jobs/jobs.yml @@ -1,10 +1,19 @@ parameters: - # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md + # Optional: 'true' if failures in job.yml job should not fail the job continueOnError: false + # Optional: Enable installing Microbuild plugin + # if 'true', these "variables" must be specified in the variables object or as part of the queue matrix + # _TeamName - the name of your team + # _SignType - 'test' or 'real' + enableMicrobuild: false + # Optional: Include PublishBuildArtifacts task enablePublishBuildArtifacts: false + # Optional: Enable publishing to the build asset registry + enablePublishBuildAssets: false + # Optional: Enable publishing using release pipelines enablePublishUsingPipelines: false @@ -14,9 +23,19 @@ parameters: # Optional: Include toolset dependencies in the generated graph files includeToolset: false + # Optional: Include PublishTestResults task + enablePublishTestResults: false + + # Optional: enable sending telemetry + # if enabled then the 'helixRepo' parameter should also be specified + enableTelemetry: false + # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job jobs: [] + # Optional: define the helix repo for telemetry (example: 'dotnet/arcade') + helixRepo: '' + # Optional: Override automatically derived dependsOn value for "publish build assets" job publishBuildAssetsDependsOn: '' @@ -43,30 +62,29 @@ jobs: name: ${{ job.job }} -- ${{ if and(eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - ${{ if or(eq(parameters.enablePublishBuildAssets, true), eq(parameters.artifacts.publish.manifests, 'true'), ne(parameters.artifacts.publish.manifests, '')) }}: - - template: ../job/publish-build-assets.yml - parameters: - continueOnError: ${{ parameters.continueOnError }} - dependsOn: - - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: - - ${{ each job in parameters.publishBuildAssetsDependsOn }}: - - ${{ job.job }} - - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: - - ${{ each job in parameters.jobs }}: - - ${{ job.job }} - pool: - vmImage: vs2017-win2016 - runAsPublic: ${{ parameters.runAsPublic }} - publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} - enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} - - - ${{ if eq(parameters.graphFileGeneration.enabled, true) }}: - - template: ../job/generate-graph-files.yml - parameters: - continueOnError: ${{ parameters.continueOnError }} - includeToolset: ${{ parameters.graphFileGeneration.includeToolset }} - dependsOn: - - Asset_Registry_Publish - pool: - vmImage: vs2017-win2016 +- ${{ if and(eq(parameters.enablePublishBuildAssets, true), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - template: ../job/publish-build-assets.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + dependsOn: + - ${{ if ne(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.publishBuildAssetsDependsOn }}: + - ${{ job.job }} + - ${{ if eq(parameters.publishBuildAssetsDependsOn, '') }}: + - ${{ each job in parameters.jobs }}: + - ${{ job.job }} + pool: + vmImage: vs2017-win2016 + runAsPublic: ${{ parameters.runAsPublic }} + publishUsingPipelines: ${{ parameters.enablePublishUsingPipelines }} + enablePublishBuildArtifacts: ${{ parameters.enablePublishBuildArtifacts }} + +- ${{ if and(eq(parameters.graphFileGeneration.enabled, true), eq(parameters.runAsPublic, 'false'), ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - template: ../job/generate-graph-files.yml + parameters: + continueOnError: ${{ parameters.continueOnError }} + includeToolset: ${{ parameters.graphFileGeneration.includeToolset }} + dependsOn: + - Asset_Registry_Publish + pool: + vmImage: vs2017-win2016 diff --git a/eng/common/templates/post-build/channels/generic-internal-channel.yml b/eng/common/templates/post-build/channels/generic-internal-channel.yml index 258ba4b770..ad9375f5e5 100644 --- a/eng/common/templates/post-build/channels/generic-internal-channel.yml +++ b/eng/common/templates/post-build/channels/generic-internal-channel.yml @@ -1,7 +1,4 @@ parameters: - artifactsPublishingAdditionalParameters: '' - dependsOn: - - Validate publishInstallersAndChecksums: false symbolPublishingAdditionalParameters: '' stageName: '' @@ -13,54 +10,37 @@ parameters: stages: - stage: ${{ parameters.stageName }} - dependsOn: ${{ parameters.dependsOn }} + dependsOn: validate variables: - template: ../common-variables.yml displayName: ${{ parameters.channelName }} Publishing jobs: - template: ../setup-maestro-vars.yml - - job: publish_symbols + - job: displayName: Symbol Publishing dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )) variables: - group: DotNet-Symbol-Server-Pats - - name: AzDOProjectName - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] - - name: AzDOPipelineId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] - - name: AzDOBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: vmImage: 'windows-2019' steps: - - task: DownloadBuildArtifacts@0 - displayName: Download Build Assets - continueOnError: true - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - downloadType: 'specific' - itemPattern: | - PdbArtifacts/** - BlobArtifacts/** - downloadPath: '$(Build.ArtifactStagingDirectory)' - # This is necessary whenever we want to publish/restore to an AzDO private feed - # Since sdk-task.ps1 tries to restore packages we need to do this authentication here - # otherwise it'll complain about accessing a private feed. - task: NuGetAuthenticate@0 displayName: 'Authenticate to AzDO Feeds' - - task: PowerShell@2 - displayName: Enable cross-org publishing + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts inputs: - filePath: eng\common\enable-cross-org-publishing.ps1 - arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) + artifactName: 'BlobArtifacts' + continueOnError: true + + - task: DownloadBuildArtifacts@0 + displayName: Download PDB Artifacts + inputs: + artifactName: 'PDBArtifacts' + continueOnError: true - task: PowerShell@2 displayName: Publish @@ -73,48 +53,39 @@ stages: /p:BlobBasePath='$(Build.ArtifactStagingDirectory)/BlobArtifacts/' /p:SymbolPublishingExclusionsFile='$(Build.SourcesDirectory)/eng/SymbolPublishingExclusionsFile.txt' /p:Configuration=Release - /p:PublishToMSDL=false ${{ parameters.symbolPublishingAdditionalParameters }} - - template: ../../steps/publish-logs.yml - parameters: - StageLabel: '${{ parameters.stageName }}' - JobLabel: 'SymbolPublishing' - - job: publish_assets displayName: Publish Assets dependsOn: setupMaestroVars - timeoutInMinutes: 120 variables: + - group: DotNet-Blob-Feed + - group: AzureDevOps-Artifact-Feeds-Pats - name: BARBuildId value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - name: IsStableBuild value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.IsStableBuild'] ] - - name: AzDOProjectName - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] - - name: AzDOPipelineId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] - - name: AzDOBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }})) pool: vmImage: 'windows-2019' steps: - task: DownloadBuildArtifacts@0 - displayName: Download Build Assets - continueOnError: true + displayName: Download Package Artifacts inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - downloadType: 'specific' - itemPattern: | - PackageArtifacts/** - BlobArtifacts/** - AssetManifests/** - downloadPath: '$(Build.ArtifactStagingDirectory)' + buildType: current + artifactName: PackageArtifacts + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: current + artifactName: BlobArtifacts + + - task: DownloadBuildArtifacts@0 + displayName: Download Asset Manifests + inputs: + buildType: current + artifactName: AssetManifests - task: NuGetToolInstaller@1 displayName: 'Install NuGet.exe' @@ -153,6 +124,7 @@ stages: /p:ChecksumsAzureAccountKey=$(InternalChecksumsBlobFeedKey) /p:InstallersTargetStaticFeed=$(InternalInstallersBlobFeedUrl) /p:InstallersAzureAccountKey=$(InternalInstallersBlobFeedKey) + /p:PublishToAzureDevOpsNuGetFeeds=true /p:AzureDevOpsStaticShippingFeed='${{ parameters.shippingFeed }}' /p:AzureDevOpsStaticShippingFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' /p:AzureDevOpsStaticTransportFeed='${{ parameters.transportFeed }}' @@ -162,11 +134,6 @@ stages: /p:PublishToMSDL=false ${{ parameters.artifactsPublishingAdditionalParameters }} - - template: ../../steps/publish-logs.yml - parameters: - StageLabel: '${{ parameters.stageName }}' - JobLabel: 'AssetsPublishing' - - - template: ../../steps/add-build-to-channel.yml + - template: ../../steps/promote-build.yml parameters: ChannelId: ${{ parameters.channelId }} diff --git a/eng/common/templates/post-build/channels/generic-public-channel.yml b/eng/common/templates/post-build/channels/generic-public-channel.yml index bf98d990e8..c4bc1897d8 100644 --- a/eng/common/templates/post-build/channels/generic-public-channel.yml +++ b/eng/common/templates/post-build/channels/generic-public-channel.yml @@ -1,7 +1,5 @@ parameters: artifactsPublishingAdditionalParameters: '' - dependsOn: - - Validate publishInstallersAndChecksums: false symbolPublishingAdditionalParameters: '' stageName: '' @@ -10,47 +8,36 @@ parameters: transportFeed: '' shippingFeed: '' symbolsFeed: '' - # If the channel name is empty, no links will be generated - akaMSChannelName: '' stages: - stage: ${{ parameters.stageName }} - dependsOn: ${{ parameters.dependsOn }} + dependsOn: validate variables: - template: ../common-variables.yml displayName: ${{ parameters.channelName }} Publishing jobs: - template: ../setup-maestro-vars.yml - - job: publish_symbols + - job: displayName: Symbol Publishing dependsOn: setupMaestroVars - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }} )) variables: - group: DotNet-Symbol-Server-Pats - - name: AzDOProjectName - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] - - name: AzDOPipelineId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] - - name: AzDOBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] pool: vmImage: 'windows-2019' steps: - task: DownloadBuildArtifacts@0 - displayName: Download Build Assets + displayName: Download Blob Artifacts + inputs: + artifactName: 'BlobArtifacts' continueOnError: true + + - task: DownloadBuildArtifacts@0 + displayName: Download PDB Artifacts inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - downloadType: 'specific' - itemPattern: | - PdbArtifacts/** - BlobArtifacts/** - downloadPath: '$(Build.ArtifactStagingDirectory)' + artifactName: 'PDBArtifacts' + continueOnError: true # This is necessary whenever we want to publish/restore to an AzDO private feed # Since sdk-task.ps1 tries to restore packages we need to do this authentication here @@ -77,47 +64,37 @@ stages: /p:Configuration=Release ${{ parameters.symbolPublishingAdditionalParameters }} - - template: ../../steps/publish-logs.yml - parameters: - StageLabel: '${{ parameters.stageName }}' - JobLabel: 'SymbolPublishing' - - job: publish_assets displayName: Publish Assets dependsOn: setupMaestroVars - timeoutInMinutes: 120 variables: - name: BARBuildId value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ] - name: IsStableBuild value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.IsStableBuild'] ] - - name: AzDOProjectName - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] - - name: AzDOPipelineId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] - - name: AzDOBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] - - name: ArtifactsCategory - value: ${{ coalesce(variables._DotNetArtifactsCategory, '.NETCore') }} - condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'], format('[{0}]', ${{ parameters.channelId }} )) + condition: contains(dependencies.setupMaestroVars.outputs['setReleaseVars.InitialChannels'], format('[{0}]', ${{ parameters.channelId }})) pool: vmImage: 'windows-2019' steps: - task: DownloadBuildArtifacts@0 - displayName: Download Build Assets + displayName: Download Package Artifacts + inputs: + buildType: current + artifactName: PackageArtifacts continueOnError: true + + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - downloadType: 'specific' - itemPattern: | - PackageArtifacts/** - BlobArtifacts/** - AssetManifests/** - downloadPath: '$(Build.ArtifactStagingDirectory)' + buildType: current + artifactName: BlobArtifacts + continueOnError: true + + - task: DownloadBuildArtifacts@0 + displayName: Download Asset Manifests + inputs: + buildType: current + artifactName: AssetManifests - task: NuGetToolInstaller@1 displayName: 'Install NuGet.exe' @@ -137,7 +114,7 @@ stages: inputs: filePath: eng\common\sdk-task.ps1 arguments: -task PublishArtifactsInManifest -restore -msbuildEngine dotnet - /p:ArtifactsCategory=$(ArtifactsCategory) + /p:ArtifactsCategory=$(_DotNetArtifactsCategory) /p:IsStableBuild=$(IsStableBuild) /p:IsInternalBuild=$(IsInternalBuild) /p:RepositoryName=$(Build.Repository.Name) @@ -157,22 +134,15 @@ stages: /p:InstallersAzureAccountKey=$(dotnetcli-storage-key) /p:ChecksumsTargetStaticFeed=$(ChecksumsBlobFeedUrl) /p:ChecksumsAzureAccountKey=$(dotnetclichecksums-storage-key) + /p:PublishToAzureDevOpsNuGetFeeds=true /p:AzureDevOpsStaticShippingFeed='${{ parameters.shippingFeed }}' /p:AzureDevOpsStaticShippingFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' /p:AzureDevOpsStaticTransportFeed='${{ parameters.transportFeed }}' /p:AzureDevOpsStaticTransportFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' /p:AzureDevOpsStaticSymbolsFeed='${{ parameters.symbolsFeed }}' /p:AzureDevOpsStaticSymbolsFeedKey='$(dn-bot-dnceng-artifact-feeds-rw)' - /p:LatestLinkShortUrlPrefix=dotnet/'${{ parameters.akaMSChannelName }}' - /p:AkaMSClientId=$(akams-client-id) - /p:AkaMSClientSecret=$(akams-client-secret) ${{ parameters.artifactsPublishingAdditionalParameters }} - - template: ../../steps/publish-logs.yml - parameters: - StageLabel: '${{ parameters.stageName }}' - JobLabel: 'AssetsPublishing' - - - template: ../../steps/add-build-to-channel.yml + - template: ../../steps/promote-build.yml parameters: ChannelId: ${{ parameters.channelId }} diff --git a/eng/common/templates/post-build/common-variables.yml b/eng/common/templates/post-build/common-variables.yml index c99fd75037..1883d2b170 100644 --- a/eng/common/templates/post-build/common-variables.yml +++ b/eng/common/templates/post-build/common-variables.yml @@ -4,13 +4,13 @@ variables: - group: DotNet-DotNetCli-Storage - group: DotNet-MSRC-Storage - group: Publish-Build-Assets - + # .NET Core 3.1 Dev - name: PublicDevRelease_31_Channel_Id value: 128 - # .NET 5 Dev - - name: Net_5_Dev_Channel_Id + # .NET Core 5 Dev + - name: NetCore_5_Dev_Channel_Id value: 131 # .NET Eng - Validation @@ -29,14 +29,6 @@ variables: - name: NetCore_3_Tools_Channel_Id value: 344 - # .NET Core 3.0 Internal Servicing - - name: InternalServicing_30_Channel_Id - value: 184 - - # .NET Core 3.0 Release - - name: PublicRelease_30_Channel_Id - value: 19 - # .NET Core 3.1 Release - name: PublicRelease_31_Channel_Id value: 129 @@ -49,10 +41,6 @@ variables: - name: NetCore_31_Blazor_Features_Channel_Id value: 531 - # .NET Core Experimental - - name: NetCore_Experimental_Channel_Id - value: 562 - # Whether the build is internal or not - name: IsInternalBuild value: ${{ and(ne(variables['System.TeamProject'], 'public'), contains(variables['Build.SourceBranch'], 'internal')) }} @@ -63,7 +51,7 @@ variables: - name: MaestroApiAccessToken value: $(MaestroAccessToken) - name: MaestroApiVersion - value: "2020-02-20" + value: "2019-01-16" - name: SourceLinkCLIVersion value: 3.0.0 @@ -90,10 +78,3 @@ variables: value: https://dotnetclimsrc.blob.core.windows.net/dotnet/index.json - name: InternalInstallersBlobFeedKey value: $(dotnetclimsrc-access-key) - - # Skip component governance and codesign validation for SDL. These jobs - # create no content. - - name: skipComponentGovernanceDetection - value: true - - name: runCodesignValidationInjection - value: false diff --git a/eng/common/templates/post-build/post-build.yml b/eng/common/templates/post-build/post-build.yml index b51bc5375e..e1cff6e702 100644 --- a/eng/common/templates/post-build/post-build.yml +++ b/eng/common/templates/post-build/post-build.yml @@ -9,272 +9,156 @@ parameters: continueOnError: false params: '' artifactNames: '' - downloadArtifacts: true # These parameters let the user customize the call to sdk-task.ps1 for publishing # symbols & general artifacts as well as for signing validation symbolPublishingAdditionalParameters: '' artifactsPublishingAdditionalParameters: '' signingValidationAdditionalParameters: '' - useBuildManifest: false # Which stages should finish execution before post-build stages start - validateDependsOn: - - build - publishDependsOn: - - Validate + dependsOn: [build] - # Channel ID's instantiated in this file. - # When adding a new channel implementation the call to `check-channel-consistency.ps1` - # needs to be updated with the new channel ID - NetEngLatestChannelId: 2 - NetEngValidationChannelId: 9 - NetDev5ChannelId: 131 - GeneralTestingChannelId: 529 - NETCoreToolingDevChannelId: 548 - NETCoreToolingReleaseChannelId: 549 - NETInternalToolingChannelId: 551 - NETCoreExperimentalChannelId: 562 - NetEngServicesIntChannelId: 678 - NetEngServicesProdChannelId: 679 - Net5Preview3ChannelId: 739 - Net5Preview4ChannelId: 856 - Net5Preview5ChannelId: 857 - NetCoreSDK313xxChannelId: 759 - NetCoreSDK313xxInternalChannelId: 760 - NetCoreSDK314xxChannelId: 921 - NetCoreSDK314xxInternalChannelId: 922 - stages: -- stage: Validate - dependsOn: ${{ parameters.validateDependsOn }} +- stage: validate + dependsOn: ${{ parameters.dependsOn }} displayName: Validate - variables: - - template: common-variables.yml jobs: - - template: setup-maestro-vars.yml - - - job: - displayName: Post-build Checks - dependsOn: setupMaestroVars - variables: - - name: TargetChannels - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.TargetChannels'] ] - pool: - vmImage: 'windows-2019' - steps: - - task: PowerShell@2 - displayName: Maestro Channels Consistency - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/check-channel-consistency.ps1 - arguments: -PromoteToChannels "$(TargetChannels)" - -AvailableChannelIds ${{parameters.NetEngLatestChannelId}},${{parameters.NetEngValidationChannelId}},${{parameters.NetDev5ChannelId}},${{parameters.GeneralTestingChannelId}},${{parameters.NETCoreToolingDevChannelId}},${{parameters.NETCoreToolingReleaseChannelId}},${{parameters.NETInternalToolingChannelId}},${{parameters.NETCoreExperimentalChannelId}},${{parameters.NetEngServicesIntChannelId}},${{parameters.NetEngServicesProdChannelId}},${{parameters.Net5Preview3ChannelId}},${{parameters.Net5Preview4ChannelId}},${{parameters.Net5Preview5ChannelId}},${{parameters.NetCoreSDK313xxChannelId}},${{parameters.NetCoreSDK313xxInternalChannelId}},${{parameters.NetCoreSDK314xxChannelId}},${{parameters.NetCoreSDK314xxInternalChannelId}} - - - job: - displayName: NuGet Validation - dependsOn: setupMaestroVars - condition: eq( ${{ parameters.enableNugetValidation }}, 'true') - pool: - vmImage: 'windows-2019' - variables: - - name: AzDOProjectName - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] - - name: AzDOPipelineId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] - - name: AzDOBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] - steps: - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: PackageArtifacts - - - task: PowerShell@2 - displayName: Validate - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 - arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ - -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ - - - job: - displayName: Signing Validation - dependsOn: setupMaestroVars - condition: eq( ${{ parameters.enableSigningValidation }}, 'true') - variables: - - template: common-variables.yml - - name: AzDOProjectName - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] - - name: AzDOPipelineId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] - - name: AzDOBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] - pool: - vmImage: 'windows-2019' - steps: - - ${{ if eq(parameters.useBuildManifest, true) }}: + - ${{ if eq(parameters.enableNugetValidation, 'true') }}: + - job: + displayName: NuGet Validation + pool: + vmImage: 'windows-2019' + steps: - task: DownloadBuildArtifacts@0 - displayName: Download build manifest + displayName: Download Package Artifacts inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: BuildManifests - - task: DownloadBuildArtifacts@0 - displayName: Download Package Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: PackageArtifacts + buildType: current + artifactName: PackageArtifacts - # This is necessary whenever we want to publish/restore to an AzDO private feed - # Since sdk-task.ps1 tries to restore packages we need to do this authentication here - # otherwise it'll complain about accessing a private feed. - - task: NuGetAuthenticate@0 - displayName: 'Authenticate to AzDO Feeds' + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/nuget-validation.ps1 + arguments: -PackagesPath $(Build.ArtifactStagingDirectory)/PackageArtifacts/ + -ToolDestinationPath $(Agent.BuildDirectory)/Extract/ - - task: PowerShell@2 - displayName: Enable cross-org publishing - inputs: - filePath: eng\common\enable-cross-org-publishing.ps1 - arguments: -token $(dn-bot-dnceng-artifact-feeds-rw) + - ${{ if eq(parameters.enableSigningValidation, 'true') }}: + - job: + displayName: Signing Validation + pool: + vmImage: 'windows-2019' + steps: + # This is necessary whenever we want to publish/restore to an AzDO private feed + # Since sdk-task.ps1 tries to restore packages we need to do this authentication here + # otherwise it'll complain about accessing a private feed. + - task: NuGetAuthenticate@0 + displayName: 'Authenticate to AzDO Feeds' - # Signing validation will optionally work with the buildmanifest file which is downloaded from - # Azure DevOps above. - - task: PowerShell@2 - displayName: Validate - inputs: - filePath: eng\common\sdk-task.ps1 - arguments: -task SigningValidation -restore -msbuildEngine vs - /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' - /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' - ${{ parameters.signingValidationAdditionalParameters }} + - task: DownloadBuildArtifacts@0 + displayName: Download Package Artifacts + inputs: + buildType: current + artifactName: PackageArtifacts - - template: ../steps/publish-logs.yml - parameters: - StageLabel: 'Validation' - JobLabel: 'Signing' + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: eng\common\sdk-task.ps1 + arguments: -task SigningValidation -restore -msbuildEngine dotnet + /p:PackageBasePath='$(Build.ArtifactStagingDirectory)/PackageArtifacts' + /p:SignCheckExclusionsFile='$(Build.SourcesDirectory)/eng/SignCheckExclusionsFile.txt' + /p:Configuration=Release + ${{ parameters.signingValidationAdditionalParameters }} - - job: - displayName: SourceLink Validation - dependsOn: setupMaestroVars - condition: eq( ${{ parameters.enableSourceLinkValidation }}, 'true') - variables: - - template: common-variables.yml - - name: AzDOProjectName - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOProjectName'] ] - - name: AzDOPipelineId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOPipelineId'] ] - - name: AzDOBuildId - value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.AzDOBuildId'] ] - pool: - vmImage: 'windows-2019' - steps: - - task: DownloadBuildArtifacts@0 - displayName: Download Blob Artifacts - inputs: - buildType: specific - buildVersionToDownload: specific - project: $(AzDOProjectName) - pipeline: $(AzDOPipelineId) - buildId: $(AzDOBuildId) - artifactName: BlobArtifacts + - ${{ if eq(parameters.enableSourceLinkValidation, 'true') }}: + - job: + displayName: SourceLink Validation + variables: + - template: common-variables.yml + pool: + vmImage: 'windows-2019' + steps: + - task: DownloadBuildArtifacts@0 + displayName: Download Blob Artifacts + inputs: + buildType: current + artifactName: BlobArtifacts - - task: PowerShell@2 - displayName: Validate - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 - arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ - -ExtractPath $(Agent.BuildDirectory)/Extract/ - -GHRepoName $(Build.Repository.Name) - -GHCommit $(Build.SourceVersion) - -SourcelinkCliVersion $(SourceLinkCLIVersion) - continueOnError: true + - task: PowerShell@2 + displayName: Validate + inputs: + filePath: $(Build.SourcesDirectory)/eng/common/post-build/sourcelink-validation.ps1 + arguments: -InputPath $(Build.ArtifactStagingDirectory)/BlobArtifacts/ + -ExtractPath $(Agent.BuildDirectory)/Extract/ + -GHRepoName $(Build.Repository.Name) + -GHCommit $(Build.SourceVersion) + -SourcelinkCliVersion $(SourceLinkCLIVersion) + continueOnError: true - - template: /eng/common/templates/job/execute-sdl.yml - parameters: - enable: ${{ parameters.SDLValidationParameters.enable }} - dependsOn: setupMaestroVars - additionalParameters: ${{ parameters.SDLValidationParameters.params }} - continueOnError: ${{ parameters.SDLValidationParameters.continueOnError }} - artifactNames: ${{ parameters.SDLValidationParameters.artifactNames }} - downloadArtifacts: ${{ parameters.SDLValidationParameters.downloadArtifacts }} + - ${{ if eq(parameters.SDLValidationParameters.enable, 'true') }}: + - template: /eng/common/templates/job/execute-sdl.yml + parameters: + additionalParameters: ${{ parameters.SDLValidationParameters.params }} + continueOnError: ${{ parameters.SDLValidationParameters.continueOnError }} + artifactNames: ${{ parameters.SDLValidationParameters.artifactNames }} - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NetCore_Dev5_Publish' - channelName: '.NET 5 Dev' - akaMSChannelName: 'net5/dev' - channelId: ${{ parameters.NetDev5ChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json' + stageName: 'NetCore_Dev31_Publish' + channelName: '.NET Core 3.1 Dev' + channelId: 128 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net5_Preview3_Publish' - channelName: '.NET 5 Preview 3' - akaMSChannelName: 'net5/preview3' - channelId: ${{ parameters.Net5Preview3ChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json' + stageName: 'Net_Eng_Latest_Publish' + channelName: '.NET Eng - Latest' + channelId: 2 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net5_Preview4_Publish' - channelName: '.NET 5 Preview 4' - akaMSChannelName: 'net5/preview4' - channelId: ${{ parameters.Net5Preview4ChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json' + stageName: 'Net_Eng_Validation_Publish' + channelName: '.NET Eng - Validation' + channelId: 9 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net5_Preview5_Publish' - channelName: '.NET 5 Preview 5' - akaMSChannelName: 'net5/preview5' - channelId: ${{ parameters.Net5Preview5ChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5-symbols/nuget/v3/index.json' + stageName: 'NetCore_3_Tools_Validation_Publish' + channelName: '.NET 3 Tools - Validation' + channelId: 390 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net_Eng_Latest_Publish' - channelName: '.NET Eng - Latest' - akaMSChannelName: 'eng/daily' - channelId: ${{ parameters.NetEngLatestChannelId }} + stageName: 'NetCore_3_Tools_Publish' + channelName: '.NET 3 Tools' + channelId: 344 transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' @@ -282,27 +166,47 @@ stages: - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net_Eng_Validation_Publish' - channelName: '.NET Eng - Validation' - akaMSChannelName: 'eng/validation' - channelId: ${{ parameters.NetEngValidationChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + stageName: 'NetCore_Release31_Publish' + channelName: '.NET Core 3.1 Release' + channelId: 129 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' + +- template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NetCore_Blazor31_Features_Publish' + channelName: '.NET Core 3.1 Blazor Features' + channelId: 531 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor-symbols/nuget/v3/index.json' + +- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml + parameters: + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NetCore_31_Internal_Servicing_Publishing' + channelName: '.NET Core 3.1 Internal Servicing' + channelId: 550 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'General_Testing_Publish' channelName: 'General Testing' - akaMSChannelName: 'generaltesting' - channelId: ${{ parameters.GeneralTestingChannelId }} + channelId: 529 transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/general-testing-symbols/nuget/v3/index.json' @@ -310,12 +214,11 @@ stages: - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'NETCore_Tooling_Dev_Publishing' channelName: '.NET Core Tooling Dev' - channelId: ${{ parameters.NETCoreToolingDevChannelId }} + channelId: 548 transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' @@ -323,77 +226,95 @@ stages: - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'NETCore_Tooling_Release_Publishing' channelName: '.NET Core Tooling Release' - channelId: ${{ parameters.NETCoreToolingReleaseChannelId }} + channelId: 549 transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' +- template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_311xx_Publishing' + channelName: '.NET Core SDK 3.1.1xx' + channelId: 560 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' + - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NET_Internal_Tooling_Publishing' - channelName: '.NET Internal Tooling' - channelId: ${{ parameters.NETInternalToolingChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal-symbols/nuget/v3/index.json' + stageName: 'NETCore_SDK_311xx_Internal_Publishing' + channelName: '.NET Core SDK 3.1.1xx Internal' + channelId: 559 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_Experimental_Publishing' - channelName: '.NET Core Experimental' - channelId: ${{ parameters.NETCoreExperimentalChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental-symbols/nuget/v3/index.json' + stageName: 'NETCore_SDK_312xx_Publishing' + channelName: '.NET Core SDK 3.1.2xx' + channelId: 558 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' -- template: \eng\common\templates\post-build\channels\generic-public-channel.yml +- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net_Eng_Services_Int_Publish' - channelName: '.NET Eng Services - Int' - channelId: ${{ parameters.NetEngServicesIntChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + stageName: 'NETCore_SDK_312xx_Internal_Publishing' + channelName: '.NET Core SDK 3.1.2xx Internal' + channelId: 557 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'Net_Eng_Services_Prod_Publish' - channelName: '.NET Eng Services - Prod' - channelId: ${{ parameters.NetEngServicesProdChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng-symbols/nuget/v3/index.json' + stageName: 'NETCore_SDK_313xx_Publishing' + channelName: '.NET Core SDK 3.1.3xx' + channelId: 759 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' +- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml + parameters: + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'NETCore_SDK_313xx_Internal_Publishing' + channelName: '.NET Core SDK 3.1.3xx Internal' + channelId: 760 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' + - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'NETCore_SDK_314xx_Publishing' channelName: '.NET Core SDK 3.1.4xx' - channelId: ${{ parameters.NetCoreSDK314xxChannelId }} + channelId: 921 transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' @@ -401,38 +322,47 @@ stages: - template: \eng\common\templates\post-build\channels\generic-internal-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} stageName: 'NETCore_SDK_314xx_Internal_Publishing' channelName: '.NET Core SDK 3.1.4xx Internal' - channelId: ${{ parameters.NetCoreSDK314xxInternalChannelId }} + channelId: 922 transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' - template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_313xx_Publishing' - channelName: '.NET Core SDK 3.1.3xx' - channelId: ${{ parameters.NetCoreSDK313xxChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-symbols/nuget/v3/index.json' + stageName: 'VS16_6_Publishing' + channelName: 'VS 16.6' + channelId: 1010 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' -- template: \eng\common\templates\post-build\channels\generic-internal-channel.yml +- template: \eng\common\templates\post-build\channels\generic-public-channel.yml parameters: artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} - dependsOn: ${{ parameters.publishDependsOn }} publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} - stageName: 'NETCore_SDK_313xx_Internal_Publishing' - channelName: '.NET Core SDK 3.1.3xx Internal' - channelId: ${{ parameters.NetCoreSDK313xxInternalChannelId }} - transportFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-transport/nuget/v3/index.json' - shippingFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal/nuget/v3/index.json' - symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/_packaging/dotnet3.1-internal-symbols/nuget/v3/index.json' + stageName: 'VS16_7_Publishing' + channelName: 'VS 16.7' + channelId: 1011 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' + +- template: \eng\common\templates\post-build\channels\generic-public-channel.yml + parameters: + artifactsPublishingAdditionalParameters: ${{ parameters.artifactsPublishingAdditionalParameters }} + publishInstallersAndChecksums: ${{ parameters.publishInstallersAndChecksums }} + symbolPublishingAdditionalParameters: ${{ parameters.symbolPublishingAdditionalParameters }} + stageName: 'VS_Master_Publishing' + channelName: 'VS Master' + channelId: 1012 + transportFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-transport/nuget/v3/index.json' + shippingFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json' + symbolsFeed: 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools-symbols/nuget/v3/index.json' \ No newline at end of file diff --git a/eng/common/templates/post-build/setup-maestro-vars.yml b/eng/common/templates/post-build/setup-maestro-vars.yml index b3d29d4498..56242b068e 100644 --- a/eng/common/templates/post-build/setup-maestro-vars.yml +++ b/eng/common/templates/post-build/setup-maestro-vars.yml @@ -1,22 +1,11 @@ jobs: - job: setupMaestroVars displayName: Setup Maestro Vars - variables: - - template: common-variables.yml - - name: BuildId - value: $[ coalesce(variables.BARBuildId, 0) ] - - name: PromoteToMaestroChannels - value: $[ coalesce(variables.PromoteToChannelIds, 0) ] - - name: PromoteToMaestroChannel - value: $[ coalesce(variables.PromoteToMaestroChannelId, 0) ] pool: vmImage: 'windows-2019' steps: - - checkout: none - - task: DownloadBuildArtifacts@0 displayName: Download Release Configs - condition: and(eq(variables.PromoteToMaestroChannels, 0), eq(variables.PromoteToMaestroChannel, 0)) inputs: buildType: current artifactName: ReleaseConfigs @@ -25,53 +14,5 @@ jobs: name: setReleaseVars displayName: Set Release Configs Vars inputs: - targetType: inline - script: | - try { - if ($Env:PromoteToMaestroChannels -eq 0 -and $Env:PromoteToMaestroChannel -eq 0) { - $Content = Get-Content $(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt - - $BarId = $Content | Select -Index 0 - $Channels = $Content | Select -Index 1 - $IsStableBuild = $Content | Select -Index 2 - - $AzureDevOpsProject = $Env:System_TeamProject - $AzureDevOpsBuildDefinitionId = $Env:System_DefinitionId - $AzureDevOpsBuildId = $Env:Build_BuildId - } - else { - $buildApiEndpoint = "${Env:MaestroApiEndPoint}/api/builds/${Env:BARBuildId}?api-version=${Env:MaestroApiVersion}" - - $apiHeaders = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]' - $apiHeaders.Add('Accept', 'application/json') - $apiHeaders.Add('Authorization',"Bearer ${Env:MAESTRO_API_TOKEN}") - - $buildInfo = try { Invoke-WebRequest -Method Get -Uri $buildApiEndpoint -Headers $apiHeaders | ConvertFrom-Json } catch { Write-Host "Error: $_" } - - $BarId = $Env:BARBuildId - $Channels = $Env:PromoteToMaestroChannels -split "," - $Channels = $Channels -join "][" - $Channels = "[$Channels][$Env:PromoteToMaestroChannel]" - - $IsStableBuild = $buildInfo.stable - $AzureDevOpsProject = $buildInfo.azureDevOpsProject - $AzureDevOpsBuildDefinitionId = $buildInfo.azureDevOpsBuildDefinitionId - $AzureDevOpsBuildId = $buildInfo.azureDevOpsBuildId - } - - Write-Host "##vso[task.setvariable variable=BARBuildId;isOutput=true]$BarId" - Write-Host "##vso[task.setvariable variable=TargetChannels;isOutput=true]$Channels" - Write-Host "##vso[task.setvariable variable=IsStableBuild;isOutput=true]$IsStableBuild" - - Write-Host "##vso[task.setvariable variable=AzDOProjectName;isOutput=true]$AzureDevOpsProject" - Write-Host "##vso[task.setvariable variable=AzDOPipelineId;isOutput=true]$AzureDevOpsBuildDefinitionId" - Write-Host "##vso[task.setvariable variable=AzDOBuildId;isOutput=true]$AzureDevOpsBuildId" - } - catch { - Write-Host $_ - Write-Host $_.Exception - Write-Host $_.ScriptStackTrace - exit 1 - } - env: - MAESTRO_API_TOKEN: $(MaestroApiAccessToken) + filePath: $(Build.SourcesDirectory)/eng/common/post-build/setup-maestro-vars.ps1 + arguments: -ReleaseConfigsPath '$(Build.StagingDirectory)/ReleaseConfigs/ReleaseConfigs.txt' diff --git a/eng/common/templates/steps/add-build-to-channel.yml b/eng/common/templates/steps/add-build-to-channel.yml deleted file mode 100644 index f67a210d62..0000000000 --- a/eng/common/templates/steps/add-build-to-channel.yml +++ /dev/null @@ -1,13 +0,0 @@ -parameters: - ChannelId: 0 - -steps: -- task: PowerShell@2 - displayName: Add Build to Channel - inputs: - filePath: $(Build.SourcesDirectory)/eng/common/post-build/add-build-to-channel.ps1 - arguments: -BuildId $(BARBuildId) - -ChannelId ${{ parameters.ChannelId }} - -MaestroApiAccessToken $(MaestroApiAccessToken) - -MaestroApiEndPoint $(MaestroApiEndPoint) - -MaestroApiVersion $(MaestroApiVersion) diff --git a/eng/common/templates/steps/publish-logs.yml b/eng/common/templates/steps/publish-logs.yml deleted file mode 100644 index f91751fe78..0000000000 --- a/eng/common/templates/steps/publish-logs.yml +++ /dev/null @@ -1,23 +0,0 @@ -parameters: - StageLabel: '' - JobLabel: '' - -steps: -- task: Powershell@2 - displayName: Prepare Binlogs to Upload - inputs: - targetType: inline - script: | - New-Item -ItemType Directory $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ - Move-Item -Path $(Build.SourcesDirectory)/artifacts/log/Debug/* $(Build.SourcesDirectory)/PostBuildLogs/${{parameters.StageLabel}}/${{parameters.JobLabel}}/ - continueOnError: true - condition: always() - -- task: PublishBuildArtifacts@1 - displayName: Publish Logs - inputs: - PathtoPublish: '$(Build.SourcesDirectory)/PostBuildLogs' - PublishLocation: Container - ArtifactName: PostBuildLogs - continueOnError: true - condition: always() diff --git a/eng/common/templates/steps/send-to-helix.yml b/eng/common/templates/steps/send-to-helix.yml index 5eceb48725..05df886f55 100644 --- a/eng/common/templates/steps/send-to-helix.yml +++ b/eng/common/templates/steps/send-to-helix.yml @@ -10,7 +10,7 @@ parameters: HelixPostCommands: '' # optional -- commands to run after Helix work item execution WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects - WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects + WorkItemTimeout: '' # optional -- a timeout in seconds for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload XUnitProjects: '' # optional -- semicolon delimited list of XUnitProjects to parse and send to Helix; requires XUnitRuntimeTargetFramework, XUnitPublishTargetFramework, XUnitRunnerVersion, and IncludeDotNetCli=true XUnitWorkItemTimeout: '' # optional -- the workitem timeout in seconds for all workitems created from the xUnit projects specified by XUnitProjects @@ -18,12 +18,11 @@ parameters: XUnitRuntimeTargetFramework: '' # optional -- framework to use for the xUnit console runner XUnitRunnerVersion: '' # optional -- version of the xUnit nuget package you wish to use on Helix; required for XUnitProjects IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion - DotNetCliPackageType: '' # optional -- either 'sdk' or 'runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases-index.json - DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases-index.json + DotNetCliPackageType: '' # optional -- either 'sdk' or 'runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json + DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json EnableXUnitReporter: false # optional -- true enables XUnit result reporting to Mission Control WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set - HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting int) Creator: '' # optional -- if the build is external, use this to specify who is sending the job DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() @@ -56,7 +55,6 @@ steps: DotNetCliVersion: ${{ parameters.DotNetCliVersion }} EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} - HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT')) @@ -87,7 +85,6 @@ steps: DotNetCliVersion: ${{ parameters.DotNetCliVersion }} EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} - HelixBaseUri: ${{ parameters.HelixBaseUri }} Creator: ${{ parameters.Creator }} SYSTEM_ACCESSTOKEN: $(System.AccessToken) condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT')) diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index d8dfc5e004..bc228dfdf9 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -5,13 +5,11 @@ [bool]$ci = if (Test-Path variable:ci) { $ci } else { $false } # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. -[string]$configuration = if (Test-Path variable:configuration) { $configuration } else { 'Debug' } - -# Set to true to opt out of outputting binary log while running in CI -[bool]$excludeCIBinarylog = if (Test-Path variable:excludeCIBinarylog) { $excludeCIBinarylog } else { $false } +[string]$configuration = if (Test-Path variable:configuration) { $configuration } else { "Debug" } # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. -[bool]$binaryLog = if (Test-Path variable:binaryLog) { $binaryLog } else { $ci -and !$excludeCIBinarylog } +# Binary log must be enabled on CI. +[bool]$binaryLog = if (Test-Path variable:binaryLog) { $binaryLog } else { $ci } # Set to true to use the pipelines logger which will enable Azure logging output. # https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md @@ -26,7 +24,7 @@ [bool]$restore = if (Test-Path variable:restore) { $restore } else { $true } # Adjusts msbuild verbosity level. -[string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { 'minimal' } +[string]$verbosity = if (Test-Path variable:verbosity) { $verbosity } else { "minimal" } # Set to true to reuse msbuild nodes. Recommended to not reuse on CI. [bool]$nodeReuse = if (Test-Path variable:nodeReuse) { $nodeReuse } else { !$ci } @@ -43,22 +41,22 @@ # Enable repos to use a particular version of the on-line dotnet-install scripts. # default URL: https://dot.net/v1/dotnet-install.ps1 -[string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } +[string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { "v1" } # True to use global NuGet cache instead of restoring packages to repository-local directory. [bool]$useGlobalNuGetCache = if (Test-Path variable:useGlobalNuGetCache) { $useGlobalNuGetCache } else { !$ci } # An array of names of processes to stop on script exit if prepareMachine is true. -$processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @('msbuild', 'dotnet', 'vbcscompiler') } - -$disableConfigureToolsetImport = if (Test-Path variable:disableConfigureToolsetImport) { $disableConfigureToolsetImport } else { $null } +$processesToStopOnExit = if (Test-Path variable:processesToStopOnExit) { $processesToStopOnExit } else { @("msbuild", "dotnet", "vbcscompiler") } set-strictmode -version 2.0 -$ErrorActionPreference = 'Stop' +$ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -function Create-Directory ([string[]] $path) { - New-Item -Path $path -Force -ItemType 'Directory' | Out-Null +function Create-Directory([string[]] $path) { + if (!(Test-Path $path)) { + New-Item -path $path -force -itemType "Directory" | Out-Null + } } function Unzip([string]$zipfile, [string]$outpath) { @@ -98,10 +96,7 @@ function Exec-Process([string]$command, [string]$commandArgs) { } } -# createSdkLocationFile parameter enables a file being generated under the toolset directory -# which writes the sdk's location into. This is only necessary for cmd --> powershell invocations -# as dot sourcing isn't possible. -function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { +function InitializeDotNetCli([bool]$install) { if (Test-Path variable:global:_DotNetInstallDir) { return $global:_DotNetInstallDir } @@ -124,7 +119,9 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { # Find the first path on %PATH% that contains the dotnet.exe if ($useInstalledDotNetCli -and (-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -eq $null)) { - $dotnetCmd = Get-Command 'dotnet.exe' -ErrorAction SilentlyContinue + $dotnetExecutable = GetExecutableFileName 'dotnet' + $dotnetCmd = Get-Command $dotnetExecutable -ErrorAction SilentlyContinue + if ($dotnetCmd -ne $null) { $env:DOTNET_INSTALL_DIR = Split-Path $dotnetCmd.Path -Parent } @@ -137,13 +134,13 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { if ((-not $globalJsonHasRuntimes) -and ($env:DOTNET_INSTALL_DIR -ne $null) -and (Test-Path(Join-Path $env:DOTNET_INSTALL_DIR "sdk\$dotnetSdkVersion"))) { $dotnetRoot = $env:DOTNET_INSTALL_DIR } else { - $dotnetRoot = Join-Path $RepoRoot '.dotnet' + $dotnetRoot = Join-Path $RepoRoot ".dotnet" if (-not (Test-Path(Join-Path $dotnetRoot "sdk\$dotnetSdkVersion"))) { if ($install) { InstallDotNetSdk $dotnetRoot $dotnetSdkVersion } else { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Unable to find dotnet with SDK version '$dotnetSdkVersion'" ExitWithExitCode 1 } } @@ -151,24 +148,6 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { $env:DOTNET_INSTALL_DIR = $dotnetRoot } - # Creates a temporary file under the toolset dir. - # The following code block is protecting against concurrent access so that this function can - # be called in parallel. - if ($createSdkLocationFile) { - do { - $sdkCacheFileTemp = Join-Path $ToolsetDir $([System.IO.Path]::GetRandomFileName()) - } - until (!(Test-Path $sdkCacheFileTemp)) - Set-Content -Path $sdkCacheFileTemp -Value $dotnetRoot - - try { - Rename-Item -Force -Path $sdkCacheFileTemp 'sdk.txt' - } catch { - # Somebody beat us - Remove-Item -Path $sdkCacheFileTemp - } - } - # Add dotnet to PATH. This prevents any bare invocation of dotnet in custom # build steps from using anything other than what we've downloaded. # It also ensures that VS msbuild will use the downloaded sdk targets. @@ -177,6 +156,15 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { # Make Sure that our bootstrapped dotnet cli is available in future steps of the Azure Pipelines build Write-PipelinePrependPath -Path $dotnetRoot + # Work around issues with Azure Artifacts credential provider + # https://github.com/dotnet/arcade/issues/3932 + if ($ci) { + $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 + $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' + Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' + } + Write-PipelineSetVariable -Name 'DOTNET_MULTILEVEL_LOOKUP' -Value '0' Write-PipelineSetVariable -Name 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' -Value '1' @@ -184,53 +172,27 @@ function InitializeDotNetCli([bool]$install, [bool]$createSdkLocationFile) { } function GetDotNetInstallScript([string] $dotnetRoot) { - $installScript = Join-Path $dotnetRoot 'dotnet-install.ps1' + $installScript = Join-Path $dotnetRoot "dotnet-install.ps1" if (!(Test-Path $installScript)) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit - - $maxRetries = 5 - $retries = 1 - - $uri = "https://dot.net/$dotnetInstallScriptVersion/dotnet-install.ps1" - - while($true) { - try { - Write-Host "GET $uri" - Invoke-WebRequest $uri -OutFile $installScript - break - } - catch { - Write-Host "Failed to download '$uri'" - Write-Error $_.Exception.Message -ErrorAction Continue - } - - if (++$retries -le $maxRetries) { - $delayInSeconds = [math]::Pow(2, $retries) - 1 # Exponential backoff - Write-Host "Retrying. Waiting for $delayInSeconds seconds before next attempt ($retries of $maxRetries)." - Start-Sleep -Seconds $delayInSeconds - } - else { - throw "Unable to download file in $maxRetries attempts." - } - - } + Invoke-WebRequest "https://dot.net/$dotnetInstallScriptVersion/dotnet-install.ps1" -OutFile $installScript } return $installScript } -function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = '') { +function InstallDotNetSdk([string] $dotnetRoot, [string] $version, [string] $architecture = "") { InstallDotNet $dotnetRoot $version $architecture } -function InstallDotNet([string] $dotnetRoot, - [string] $version, - [string] $architecture = '', - [string] $runtime = '', - [bool] $skipNonVersionedFiles = $false, - [string] $runtimeSourceFeed = '', - [string] $runtimeSourceFeedKey = '') { +function InstallDotNet([string] $dotnetRoot, + [string] $version, + [string] $architecture = "", + [string] $runtime = "", + [bool] $skipNonVersionedFiles = $false, + [string] $runtimeSourceFeed = "", + [string] $runtimeSourceFeedKey = "") { $installScript = GetDotNetInstallScript $dotnetRoot $installParameters = @{ @@ -246,7 +208,7 @@ function InstallDotNet([string] $dotnetRoot, & $installScript @installParameters } catch { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet runtime '$runtime' from public location." + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Failed to install dotnet runtime '$runtime' from public location." # Only the runtime can be installed from a custom [private] location. if ($runtime -and ($runtimeSourceFeed -or $runtimeSourceFeedKey)) { @@ -262,7 +224,7 @@ function InstallDotNet([string] $dotnetRoot, & $installScript @installParameters } catch { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Failed to install dotnet runtime '$runtime' from custom location '$runtimeSourceFeed'." + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Failed to install dotnet runtime '$runtime' from custom location '$runtimeSourceFeed'." ExitWithExitCode 1 } } else { @@ -283,21 +245,25 @@ function InstallDotNet([string] $dotnetRoot, # Throws on failure. # function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $null) { + if (-not (IsWindowsPlatform)) { + throw "Cannot initialize Visual Studio on non-Windows" + } + if (Test-Path variable:global:_MSBuildExe) { return $global:_MSBuildExe } if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } - $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { '15.9' } + $vsMinVersionStr = if ($vsRequirements.version) { $vsRequirements.version } else { "15.9" } $vsMinVersion = [Version]::new($vsMinVersionStr) # Try msbuild command available in the environment. if ($env:VSINSTALLDIR -ne $null) { - $msbuildCmd = Get-Command 'msbuild.exe' -ErrorAction SilentlyContinue + $msbuildCmd = Get-Command "msbuild.exe" -ErrorAction SilentlyContinue if ($msbuildCmd -ne $null) { # Workaround for https://github.com/dotnet/roslyn/issues/35793 # Due to this issue $msbuildCmd.Version returns 0.0.0.0 for msbuild.exe 16.2+ - $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split([char[]]@('-', '+'))[0]) + $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split(@('-', '+'))[0]) if ($msbuildVersion -ge $vsMinVersion) { return $global:_MSBuildExe = $msbuildCmd.Path @@ -317,7 +283,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = InitializeVisualStudioEnvironmentVariables $vsInstallDir $vsMajorVersion } else { - if (Get-Member -InputObject $GlobalJson.tools -Name 'xcopy-msbuild') { + if (Get-Member -InputObject $GlobalJson.tools -Name "xcopy-msbuild") { $xcopyMSBuildVersion = $GlobalJson.tools.'xcopy-msbuild' $vsMajorVersion = $xcopyMSBuildVersion.Split('.')[0] } else { @@ -325,12 +291,9 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = $xcopyMSBuildVersion = "$vsMajorVersion.$($vsMinVersion.Minor).0-alpha" } - $vsInstallDir = $null - if ($xcopyMSBuildVersion.Trim() -ine "none") { - $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install - } + $vsInstallDir = InitializeXCopyMSBuild $xcopyMSBuildVersion $install if ($vsInstallDir -eq $null) { - throw 'Unable to find Visual Studio that has required version and components installed' + throw "Unable to find Visual Studio that has required version and components installed" } } @@ -354,7 +317,7 @@ function InstallXCopyMSBuild([string]$packageVersion) { } function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { - $packageName = 'RoslynTools.MSBuild' + $packageName = "RoslynTools.MSBuild" $packageDir = Join-Path $ToolsDir "msbuild\$packageVersion" $packagePath = Join-Path $packageDir "$packageName.$packageVersion.nupkg" @@ -370,7 +333,7 @@ function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { Unzip $packagePath $packageDir } - return Join-Path $packageDir 'tools' + return Join-Path $packageDir "tools" } # @@ -387,37 +350,36 @@ function InitializeXCopyMSBuild([string]$packageVersion, [bool]$install) { # or $null if no instance meeting the requirements is found on the machine. # function LocateVisualStudio([object]$vsRequirements = $null){ + if (-not (IsWindowsPlatform)) { + throw "Cannot run vswhere on non-Windows platforms." + } + if (Get-Member -InputObject $GlobalJson.tools -Name 'vswhere') { $vswhereVersion = $GlobalJson.tools.vswhere } else { - $vswhereVersion = '2.5.2' + $vswhereVersion = "2.5.2" } $vsWhereDir = Join-Path $ToolsDir "vswhere\$vswhereVersion" - $vsWhereExe = Join-Path $vsWhereDir 'vswhere.exe' + $vsWhereExe = Join-Path $vsWhereDir "vswhere.exe" if (!(Test-Path $vsWhereExe)) { Create-Directory $vsWhereDir - Write-Host 'Downloading vswhere' - try { - Invoke-WebRequest "https://netcorenativeassets.blob.core.windows.net/resource-packages/external/windows/vswhere/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe - } - catch { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ - } + Write-Host "Downloading vswhere" + Invoke-WebRequest "https://github.com/Microsoft/vswhere/releases/download/$vswhereVersion/vswhere.exe" -OutFile $vswhereExe } if (!$vsRequirements) { $vsRequirements = $GlobalJson.tools.vs } - $args = @('-latest', '-prerelease', '-format', 'json', '-requires', 'Microsoft.Component.MSBuild', '-products', '*') + $args = @("-latest", "-prerelease", "-format", "json", "-requires", "Microsoft.Component.MSBuild", "-products", "*") - if (Get-Member -InputObject $vsRequirements -Name 'version') { - $args += '-version' + if (Get-Member -InputObject $vsRequirements -Name "version") { + $args += "-version" $args += $vsRequirements.version } - if (Get-Member -InputObject $vsRequirements -Name 'components') { + if (Get-Member -InputObject $vsRequirements -Name "components") { foreach ($component in $vsRequirements.components) { - $args += '-requires' + $args += "-requires" $args += $component } } @@ -443,27 +405,28 @@ function InitializeBuildTool() { # Initialize dotnet cli if listed in 'tools' $dotnetRoot = $null - if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { + if (Get-Member -InputObject $GlobalJson.tools -Name "dotnet") { $dotnetRoot = InitializeDotNetCli -install:$restore } - if ($msbuildEngine -eq 'dotnet') { + if ($msbuildEngine -eq "dotnet") { if (!$dotnetRoot) { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "/global.json must specify 'tools.dotnet'." + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "/global.json must specify 'tools.dotnet'." ExitWithExitCode 1 } - $buildTool = @{ Path = Join-Path $dotnetRoot 'dotnet.exe'; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'netcoreapp2.1' } + $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') + $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'netcoreapp2.1' } } elseif ($msbuildEngine -eq "vs") { try { $msbuildPath = InitializeVisualStudioMSBuild -install:$restore } catch { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message $_ + Write-PipelineTelemetryError -Category "InitializeToolset" -Message $_ ExitWithExitCode 1 } $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "net472" } } else { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." ExitWithExitCode 1 } @@ -472,15 +435,15 @@ function InitializeBuildTool() { function GetDefaultMSBuildEngine() { # Presence of tools.vs indicates the repo needs to build using VS msbuild on Windows. - if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { - return 'vs' + if (Get-Member -InputObject $GlobalJson.tools -Name "vs") { + return "vs" } - if (Get-Member -InputObject $GlobalJson.tools -Name 'dotnet') { - return 'dotnet' + if (Get-Member -InputObject $GlobalJson.tools -Name "dotnet") { + return "dotnet" } - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "-msbuildEngine must be specified, or /global.json must specify 'tools.dotnet' or 'tools.vs'." ExitWithExitCode 1 } @@ -488,11 +451,10 @@ function GetNuGetPackageCachePath() { if ($env:NUGET_PACKAGES -eq $null) { # Use local cache on CI to ensure deterministic build, # use global cache in dev builds to avoid cost of downloading packages. - # For directory normalization, see also: https://github.com/NuGet/Home/issues/7968 if ($useGlobalNuGetCache) { - $env:NUGET_PACKAGES = Join-Path $env:UserProfile '.nuget\packages\' + $env:NUGET_PACKAGES = Join-Path $env:UserProfile ".nuget\packages" } else { - $env:NUGET_PACKAGES = Join-Path $RepoRoot '.packages\' + $env:NUGET_PACKAGES = Join-Path $RepoRoot ".packages" } } @@ -505,7 +467,7 @@ function GetSdkTaskProject([string]$taskName) { } function InitializeNativeTools() { - if (-Not (Test-Path variable:DisableNativeToolsetInstalls) -And (Get-Member -InputObject $GlobalJson -Name "native-tools")) { + if (Get-Member -InputObject $GlobalJson -Name "native-tools") { $nativeArgs= @{} if ($ci) { $nativeArgs = @{ @@ -534,20 +496,20 @@ function InitializeToolset() { } if (-not $restore) { - Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Toolset version $toolsetVersion has not been restored." + Write-PipelineTelemetryError -Category "InitializeToolset" -Message "Toolset version $toolsetVersion has not been restored." ExitWithExitCode 1 } $buildTool = InitializeBuildTool - $proj = Join-Path $ToolsetDir 'restore.proj' - $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'ToolsetRestore.binlog') } else { '' } + $proj = Join-Path $ToolsetDir "restore.proj" + $bl = if ($binaryLog) { "/bl:" + (Join-Path $LogDir "ToolsetRestore.binlog") } else { "" } '' | Set-Content $proj MSBuild-Core $proj $bl /t:__WriteToolsetLocation /clp:ErrorsOnly`;NoSummary /p:__ToolsetLocationOutputFile=$toolsetLocationFile - $path = Get-Content $toolsetLocationFile -Encoding UTF8 -TotalCount 1 + $path = Get-Content $toolsetLocationFile -TotalCount 1 if (!(Test-Path $path)) { throw "Invalid toolset path: $path" } @@ -563,7 +525,7 @@ function ExitWithExitCode([int] $exitCode) { } function Stop-Processes() { - Write-Host 'Killing running build processes...' + Write-Host "Killing running build processes..." foreach ($processName in $processesToStopOnExit) { Get-Process -Name $processName -ErrorAction SilentlyContinue | Stop-Process } @@ -580,18 +542,13 @@ function MSBuild() { # Work around issues with Azure Artifacts credential provider # https://github.com/dotnet/arcade/issues/3932 - if ($ci -and $buildTool.Tool -eq 'dotnet') { + if ($ci -and $buildTool.Tool -eq "dotnet") { dotnet nuget locals http-cache -c - - $env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS = 20 - $env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS = 20 - Write-PipelineSetVariable -Name 'NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS' -Value '20' - Write-PipelineSetVariable -Name 'NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS' -Value '20' } $toolsetBuildProject = InitializeToolset $path = Split-Path -parent $toolsetBuildProject - $path = Join-Path $path (Join-Path $buildTool.Framework 'Microsoft.DotNet.Arcade.Sdk.dll') + $path = Join-Path $path (Join-Path $buildTool.Framework "Microsoft.DotNet.Arcade.Sdk.dll") $args += "/logger:$path" } @@ -605,13 +562,13 @@ function MSBuild() { # function MSBuild-Core() { if ($ci) { - if (!$binaryLog -and !$excludeCIBinarylog) { - Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.' + if (!$binaryLog) { + Write-PipelineTaskError -Message "Binary log must be enabled in CI build." ExitWithExitCode 1 } if ($nodeReuse) { - Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.' + Write-PipelineTaskError -Message "Node reuse must be disabled in CI build." ExitWithExitCode 1 } } @@ -621,10 +578,10 @@ function MSBuild-Core() { $cmdArgs = "$($buildTool.Command) /m /nologo /clp:Summary /v:$verbosity /nr:$nodeReuse /p:ContinuousIntegrationBuild=$ci" if ($warnAsError) { - $cmdArgs += ' /warnaserror /p:TreatWarningsAsErrors=true' + $cmdArgs += " /warnaserror /p:TreatWarningsAsErrors=true" } else { - $cmdArgs += ' /p:TreatWarningsAsErrors=false' + $cmdArgs += " /p:TreatWarningsAsErrors=false" } foreach ($arg in $args) { @@ -633,12 +590,10 @@ function MSBuild-Core() { } } - $env:ARCADE_BUILD_TOOL_COMMAND = "$($buildTool.Path) $cmdArgs" - $exitCode = Exec-Process $buildTool.Path $cmdArgs if ($exitCode -ne 0) { - Write-PipelineTelemetryError -Category 'Build' -Message 'Build failed.' + Write-PipelineTaskError -Message "Build failed." $buildLog = GetMSBuildBinaryLogCommandLineArgument $args if ($buildLog -ne $null) { @@ -653,12 +608,12 @@ function GetMSBuildBinaryLogCommandLineArgument($arguments) { foreach ($argument in $arguments) { if ($argument -ne $null) { $arg = $argument.Trim() - if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) { - return $arg.Substring('/bl:'.Length) + if ($arg.StartsWith("/bl:", "OrdinalIgnoreCase")) { + return $arg.Substring("/bl:".Length) } - if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) { - return $arg.Substring('/binaryLogger:'.Length) + if ($arg.StartsWith("/binaryLogger:", "OrdinalIgnoreCase")) { + return $arg.Substring("/binaryLogger:".Length) } } } @@ -666,16 +621,29 @@ function GetMSBuildBinaryLogCommandLineArgument($arguments) { return $null } +function GetExecutableFileName($baseName) { + if (IsWindowsPlatform) { + return "$baseName.exe" + } + else { + return $baseName + } +} + +function IsWindowsPlatform() { + return [environment]::OSVersion.Platform -eq [PlatformID]::Win32NT +} + . $PSScriptRoot\pipeline-logging-functions.ps1 -$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..') -$EngRoot = Resolve-Path (Join-Path $PSScriptRoot '..') -$ArtifactsDir = Join-Path $RepoRoot 'artifacts' -$ToolsetDir = Join-Path $ArtifactsDir 'toolset' -$ToolsDir = Join-Path $RepoRoot '.tools' -$LogDir = Join-Path (Join-Path $ArtifactsDir 'log') $configuration -$TempDir = Join-Path (Join-Path $ArtifactsDir 'tmp') $configuration -$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot 'global.json') | ConvertFrom-Json +$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") +$EngRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +$ArtifactsDir = Join-Path $RepoRoot "artifacts" +$ToolsetDir = Join-Path $ArtifactsDir "toolset" +$ToolsDir = Join-Path $RepoRoot ".tools" +$LogDir = Join-Path (Join-Path $ArtifactsDir "log") $configuration +$TempDir = Join-Path (Join-Path $ArtifactsDir "tmp") $configuration +$GlobalJson = Get-Content -Raw -Path (Join-Path $RepoRoot "global.json") | ConvertFrom-Json # true if global.json contains a "runtimes" section $globalJsonHasRuntimes = if ($GlobalJson.tools.PSObject.Properties.Name -Match 'runtimes') { $true } else { $false } @@ -688,18 +656,3 @@ Write-PipelineSetVariable -Name 'Artifacts.Toolset' -Value $ToolsetDir Write-PipelineSetVariable -Name 'Artifacts.Log' -Value $LogDir Write-PipelineSetVariable -Name 'TEMP' -Value $TempDir Write-PipelineSetVariable -Name 'TMP' -Value $TempDir - -# Import custom tools configuration, if present in the repo. -# Note: Import in global scope so that the script set top-level variables without qualification. -if (!$disableConfigureToolsetImport) { - $configureToolsetScript = Join-Path $EngRoot 'configure-toolset.ps1' - if (Test-Path $configureToolsetScript) { - . $configureToolsetScript - if ((Test-Path variable:failOnConfigureToolsetError) -And $failOnConfigureToolsetError) { - if ((Test-Path variable:LastExitCode) -And ($LastExitCode -ne 0)) { - Write-PipelineTelemetryError -Category 'Build' -Message 'configure-toolset.ps1 returned a non-zero exit code' - ExitWithExitCode $LastExitCode - } - } - } -} diff --git a/eng/common/tools.sh b/eng/common/tools.sh index e94fce22ec..acbb0c5b3f 100644 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -18,17 +18,9 @@ fi # Build configuration. Common values include 'Debug' and 'Release', but the repository may use other names. configuration=${configuration:-'Debug'} -# Set to true to opt out of outputting binary log while running in CI -exclude_ci_binary_log=${exclude_ci_binary_log:-false} - -if [[ "$ci" == true && "$exclude_ci_binary_log" == false ]]; then - binary_log_default=true -else - binary_log_default=false -fi - # Set to true to output binary log from msbuild. Note that emitting binary log slows down the build. -binary_log=${binary_log:-$binary_log_default} +# Binary log must be enabled on CI. +binary_log=${binary_log:-$ci} # Turns on machine preparation/clean up code that changes the machine state (e.g. kills build processes). prepare_machine=${prepare_machine:-false} @@ -49,7 +41,7 @@ fi # Configures warning treatment in msbuild. warn_as_error=${warn_as_error:-true} -# True to attempt using .NET Core already that meets requirements specified in global.json +# True to attempt using .NET Core already that meets requirements specified in global.json # installed on the machine instead of downloading one. use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} @@ -85,11 +77,11 @@ function ResolvePath { function ReadGlobalVersion { local key=$1 - local line=$(awk "/$key/ {print; exit}" "$global_json_file") + local line=`grep -m 1 "$key" "$global_json_file"` local pattern="\"$key\" *: *\"(.*)\"" if [[ ! $line =~ $pattern ]]; then - Write-PipelineTelemetryError -category 'Build' "Error: Cannot find \"$key\" in $global_json_file" + Write-PipelineTelemetryError -category 'InitializeToolset' "Error: Cannot find \"$key\" in $global_json_file" ExitWithExitCode 1 fi @@ -160,6 +152,15 @@ function InitializeDotNetCli { # build steps from using anything other than what we've downloaded. Write-PipelinePrependPath -path "$dotnet_root" + # Work around issues with Azure Artifacts credential provider + # https://github.com/dotnet/arcade/issues/3932 + if [[ "$ci" == true ]]; then + export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 + export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 + Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" + Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" + fi + Write-PipelineSetVariable -name "DOTNET_MULTILEVEL_LOOKUP" -value "0" Write-PipelineSetVariable -name "DOTNET_SKIP_FIRST_TIME_EXPERIENCE" -value "1" @@ -180,7 +181,7 @@ function InstallDotNetSdk { function InstallDotNet { local root=$1 local version=$2 - + GetDotNetInstallScript "$root" local install_script=$_GetDotNetInstallScript @@ -233,28 +234,6 @@ function InstallDotNet { } } -function with_retries { - local maxRetries=5 - local retries=1 - echo "Trying to run '$@' for maximum of $maxRetries attempts." - while [[ $((retries++)) -le $maxRetries ]]; do - "$@" - - if [[ $? == 0 ]]; then - echo "Ran '$@' successfully." - return 0 - fi - - timeout=$((2**$retries-1)) - echo "Failed to execute '$@'. Waiting $timeout seconds before next attempt ($retries out of $maxRetries)." 1>&2 - sleep $timeout - done - - echo "Failed to execute '$@' for $maxRetries times." 1>&2 - - return 1 -} - function GetDotNetInstallScript { local root=$1 local install_script="$root/dotnet-install.sh" @@ -267,13 +246,13 @@ function GetDotNetInstallScript { # Use curl if available, otherwise use wget if command -v curl > /dev/null; then - with_retries curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || { + curl "$install_script_url" -sSL --retry 10 --create-dirs -o "$install_script" || { local exit_code=$? Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." ExitWithExitCode $exit_code } - else - with_retries wget -v -O "$install_script" "$install_script_url" || { + else + wget -q -O "$install_script" "$install_script_url" || { local exit_code=$? Write-PipelineTelemetryError -category 'InitializeToolset' "Failed to acquire dotnet install script (exit code '$exit_code')." ExitWithExitCode $exit_code @@ -288,11 +267,11 @@ function InitializeBuildTool { if [[ -n "${_InitializeBuildTool:-}" ]]; then return fi - + InitializeDotNetCli $restore # return values - _InitializeBuildTool="$_InitializeDotNetCli/dotnet" + _InitializeBuildTool="$_InitializeDotNetCli/dotnet" _InitializeBuildToolCommand="msbuild" _InitializeBuildToolFramework="netcoreapp2.1" } @@ -311,9 +290,6 @@ function GetNuGetPackageCachePath { } function InitializeNativeTools() { - if [[ -n "${DisableNativeToolsetInstalls:-}" ]]; then - return - fi if grep -Fq "native-tools" $global_json_file then local nativeArgs="" @@ -356,14 +332,14 @@ function InitializeToolset { if [[ "$binary_log" == true ]]; then bl="/bl:$log_dir/ToolsetRestore.binlog" fi - + echo '' > "$proj" MSBuild-Core "$proj" $bl /t:__WriteToolsetLocation /clp:ErrorsOnly\;NoSummary /p:__ToolsetLocationOutputFile="$toolset_location_file" local toolset_build_proj=`cat "$toolset_location_file"` if [[ ! -a "$toolset_build_proj" ]]; then - Write-PipelineTelemetryError -category 'Build' "Invalid toolset path: $toolset_build_proj" + Write-PipelineTelemetryError -category 'InitializeToolset' "Invalid toolset path: $toolset_build_proj" ExitWithExitCode 3 fi @@ -394,12 +370,7 @@ function MSBuild { # Work around issues with Azure Artifacts credential provider # https://github.com/dotnet/arcade/issues/3932 if [[ "$ci" == true ]]; then - "$_InitializeBuildTool" nuget locals http-cache -c - - export NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 - export NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 - Write-PipelineSetVariable -name "NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS" -value "20" - Write-PipelineSetVariable -name "NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS" -value "20" + dotnet nuget locals http-cache -c fi local toolset_dir="${_InitializeToolset%/*}" @@ -412,13 +383,13 @@ function MSBuild { function MSBuild-Core { if [[ "$ci" == true ]]; then - if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then - Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch." + if [[ "$binary_log" != true ]]; then + Write-PipelineTaskError "Binary log must be enabled in CI build." ExitWithExitCode 1 fi if [[ "$node_reuse" == true ]]; then - Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build." + Write-PipelineTaskError "Node reuse must be disabled in CI build." ExitWithExitCode 1 fi fi @@ -430,17 +401,11 @@ function MSBuild-Core { warnaserror_switch="/warnaserror" fi - function RunBuildTool { - export ARCADE_BUILD_TOOL_COMMAND="$_InitializeBuildTool $@" - - "$_InitializeBuildTool" "$@" || { - local exit_code=$? - Write-PipelineTaskError "Build failed (exit code '$exit_code')." - ExitWithExitCode $exit_code - } + "$_InitializeBuildTool" "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" || { + local exit_code=$? + Write-PipelineTaskError "Build failed (exit code '$exit_code')." + ExitWithExitCode $exit_code } - - RunBuildTool "$_InitializeBuildToolCommand" /m /nologo /clp:Summary /v:$verbosity /nr:$node_reuse $warnaserror_switch /p:TreatWarningsAsErrors=$warn_as_error /p:ContinuousIntegrationBuild=$ci "$@" } ResolvePath "${BASH_SOURCE[0]}" @@ -459,7 +424,7 @@ temp_dir="$artifacts_dir/tmp/$configuration" global_json_file="$repo_root/global.json" # determine if global.json contains a "runtimes" entry global_json_has_runtimes=false -dotnetlocal_key=$(awk "/runtimes/ {print; exit}" "$global_json_file") || true +dotnetlocal_key=`grep -m 1 "runtimes" "$global_json_file"` || true if [[ -n "$dotnetlocal_key" ]]; then global_json_has_runtimes=true fi @@ -479,18 +444,3 @@ Write-PipelineSetVariable -name "Artifacts.Toolset" -value "$toolset_dir" Write-PipelineSetVariable -name "Artifacts.Log" -value "$log_dir" Write-PipelineSetVariable -name "Temp" -value "$temp_dir" Write-PipelineSetVariable -name "TMP" -value "$temp_dir" - -# Import custom tools configuration, if present in the repo. -if [ -z "${disable_configure_toolset_import:-}" ]; then - configure_toolset_script="$eng_root/configure-toolset.sh" - if [[ -a "$configure_toolset_script" ]]; then - . "$configure_toolset_script" - fi -fi - -# TODO: https://github.com/dotnet/arcade/issues/1468 -# Temporary workaround to avoid breaking change. -# Remove once repos are updated. -if [[ -n "${useInstalledDotNetCli:-}" ]]; then - use_installed_dotnet_cli="$useInstalledDotNetCli" -fi diff --git a/global.json b/global.json index d2637af033..301cd03aa1 100644 --- a/global.json +++ b/global.json @@ -9,7 +9,7 @@ "dotnet": "3.1.101" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "5.0.0-beta.20052.1", - "Microsoft.DotNet.Helix.Sdk": "5.0.0-beta.20052.1" + "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20302.3", + "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.20302.3" } -} \ No newline at end of file +} From c449a1e6103ab251f2624dea664952da811e9c48 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Tue, 9 Jun 2020 12:04:02 +0200 Subject: [PATCH 08/12] Update feeds --- NuGet.config | 2 ++ global.json | 4 ++-- scripts/build.ps1 | 2 +- scripts/build.sh | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/NuGet.config b/NuGet.config index 15e1ddd568..b98ec75cd0 100644 --- a/NuGet.config +++ b/NuGet.config @@ -14,6 +14,8 @@ + + diff --git a/global.json b/global.json index 301cd03aa1..9472a35fce 100644 --- a/global.json +++ b/global.json @@ -1,12 +1,12 @@ { "sdk": { - "version": "3.1.101", + "version": "3.1.300", "rollForward": "minor", "allowPrerelease": false, "architecture": "x64" }, "tools": { - "dotnet": "3.1.101" + "dotnet": "3.1.300" }, "msbuild-sdks": { "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20302.3", diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 9a6fcf9ff5..19e760fd24 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -82,7 +82,7 @@ $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 # Dotnet build doesn't support --packages yet. See https://github.com/dotnet/cli/issues/2712 $env:NUGET_PACKAGES = $env:TP_PACKAGES_DIR $env:NUGET_EXE_Version = "3.4.3" -$env:DOTNET_CLI_VERSION = "3.1.101" +$env:DOTNET_CLI_VERSION = "3.1.300" # $env:DOTNET_RUNTIME_VERSION = "LATEST" $env:VSWHERE_VERSION = "2.0.2" $env:MSBUILD_VERSION = "15.0" diff --git a/scripts/build.sh b/scripts/build.sh index 009b5c3d82..84694df7a1 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -111,7 +111,7 @@ VERSION=$(test -z $VERSION && grep TPVersionPrefix $TP_ROOT_DIR/scripts/build/Te export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 # Dotnet build doesnt support --packages yet. See https://github.com/dotnet/cli/issues/2712 export NUGET_PACKAGES=$TP_PACKAGES_DIR -DOTNET_CLI_VERSION="3.1.101" +DOTNET_CLI_VERSION="3.1.300" #DOTNET_RUNTIME_VERSION="LATEST" # From 2474ad2f7ae7dbda0dd1850b3065d04bdadf5434 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Tue, 9 Jun 2020 12:59:15 +0200 Subject: [PATCH 09/12] Revert to previous dotnet version --- global.json | 4 ++-- scripts/build.ps1 | 2 +- scripts/build.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/global.json b/global.json index 9472a35fce..301cd03aa1 100644 --- a/global.json +++ b/global.json @@ -1,12 +1,12 @@ { "sdk": { - "version": "3.1.300", + "version": "3.1.101", "rollForward": "minor", "allowPrerelease": false, "architecture": "x64" }, "tools": { - "dotnet": "3.1.300" + "dotnet": "3.1.101" }, "msbuild-sdks": { "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20302.3", diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 19e760fd24..9a6fcf9ff5 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -82,7 +82,7 @@ $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 # Dotnet build doesn't support --packages yet. See https://github.com/dotnet/cli/issues/2712 $env:NUGET_PACKAGES = $env:TP_PACKAGES_DIR $env:NUGET_EXE_Version = "3.4.3" -$env:DOTNET_CLI_VERSION = "3.1.300" +$env:DOTNET_CLI_VERSION = "3.1.101" # $env:DOTNET_RUNTIME_VERSION = "LATEST" $env:VSWHERE_VERSION = "2.0.2" $env:MSBUILD_VERSION = "15.0" diff --git a/scripts/build.sh b/scripts/build.sh index 84694df7a1..009b5c3d82 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -111,7 +111,7 @@ VERSION=$(test -z $VERSION && grep TPVersionPrefix $TP_ROOT_DIR/scripts/build/Te export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 # Dotnet build doesnt support --packages yet. See https://github.com/dotnet/cli/issues/2712 export NUGET_PACKAGES=$TP_PACKAGES_DIR -DOTNET_CLI_VERSION="3.1.300" +DOTNET_CLI_VERSION="3.1.101" #DOTNET_RUNTIME_VERSION="LATEST" # From df62aca07cacc5c018dc8e828f03a0cd79ee52da Mon Sep 17 00:00:00 2001 From: Sanan Yuzbashiyev Date: Fri, 12 Jun 2020 10:15:00 +0200 Subject: [PATCH 10/12] Added new exception handling (#2461) * Test space added * Exception handler was added to catch AccessDeniedException while trying to create TestResults folder * Remove unnecessary space * Deleted unnecessary test. Changed console message and corrected folder path in that message * Remove unnecessary dot * Removed unnecessary lines and usings and coreccted exception message * Removed unnecessary line * Updating resource files * New exception handling was added * Formatted exception message --- .../Execution/BaseRunTests.cs | 9 +++++++-- .../Resources/Resources.Designer.cs | 9 +++++++++ .../Resources/Resources.resx | 3 +++ .../Resources/xlf/Resources.cs.xlf | 5 +++++ .../Resources/xlf/Resources.de.xlf | 5 +++++ .../Resources/xlf/Resources.es.xlf | 5 +++++ .../Resources/xlf/Resources.fr.xlf | 5 +++++ .../Resources/xlf/Resources.it.xlf | 5 +++++ .../Resources/xlf/Resources.ja.xlf | 5 +++++ .../Resources/xlf/Resources.ko.xlf | 5 +++++ .../Resources/xlf/Resources.pl.xlf | 5 +++++ .../Resources/xlf/Resources.pt-BR.xlf | 5 +++++ .../Resources/xlf/Resources.ru.xlf | 5 +++++ .../Resources/xlf/Resources.tr.xlf | 5 +++++ .../Resources/xlf/Resources.xlf | 5 +++++ .../Resources/xlf/Resources.zh-Hans.xlf | 5 +++++ .../Resources/xlf/Resources.zh-Hant.xlf | 5 +++++ 17 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs index 7539321f54..15a0295aa4 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs @@ -458,6 +458,7 @@ private bool RunTestInternalWithExecutors(IEnumerable> execut var exceptionsHitDuringRunTests = false; var executorsFromDeprecatedLocations = false; double totalTimeTakenByAdapters = 0; + foreach (var executorUriExtensionTuple in executorUriExtensionMap) { // Get the executor from the cache. @@ -530,7 +531,11 @@ private bool RunTestInternalWithExecutors(IEnumerable> execut totalTimeTakenByAdapters += totalTimeTaken.TotalSeconds; } catch (Exception e) - { + { + string exceptionMessage = (e is UnauthorizedAccessException) + ? string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.AccessDenied, e.Message) + : ExceptionUtilities.GetExceptionMessage(e); + exceptionsHitDuringRunTests = true; if (EqtTrace.IsErrorEnabled) @@ -547,7 +552,7 @@ private bool RunTestInternalWithExecutors(IEnumerable> execut CultureInfo.CurrentCulture, CrossPlatEngineResources.ExceptionFromRunTests, executorUriExtensionTuple.Item1, - ExceptionUtilities.GetExceptionMessage(e))); + exceptionMessage)); } finally { diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs index 76c0fcc402..24d87bbeb9 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs @@ -60,6 +60,15 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges.. + /// + internal static string AccessDenied { + get { + return ResourceManager.GetString("AccessDenied", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot attach the debugger to the default test host with process ID: {0}.. /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx index 021500345d..4ef18b3697 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx @@ -201,4 +201,7 @@ Cannot attach the debugger to the default test host with process ID: {0}. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf index 26b0d791bb..3dde276b94 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf @@ -212,6 +212,11 @@ Ladicí program se nedá připojit k výchozímu hostiteli testů s ID procesu {0}. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf index 2ef7652f8f..b1bf3a3b0c 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf @@ -212,6 +212,11 @@ Der Debugger kann nicht an den Standardtesthost mit der Prozess-ID {0} angefügt werden. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf index e0f56661dd..928172e158 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf @@ -212,6 +212,11 @@ No se puede asociar el depurador al host de prueba predeterminado con el id. de proceso: {0}. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf index 776f9d9059..2e83f08d2a 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf @@ -212,6 +212,11 @@ Impossible d'attacher le débogueur à l'hôte de test par défaut ayant l'ID de processus : {0}. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf index cc27b23bae..dffd022ea8 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf @@ -212,6 +212,11 @@ Non è possibile collegare il debugger all'host di test predefinito con ID processo {0}. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf index cf40c15ca4..766d29e925 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf @@ -212,6 +212,11 @@ デバッガーをプロセス ID: {0} の既定のテスト ホストにアタッチできません。 + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf index da1762741c..76bcf330df 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf @@ -212,6 +212,11 @@ 프로세스 ID가 {0}인 기본 테스트 호스트에 디버거를 연결할 수 없습니다. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf index 4cbade75c7..72768e3c3d 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf @@ -212,6 +212,11 @@ Nie można dołączyć debugera do domyślnego hosta testowego z identyfikatorem procesu: {0}. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf index 3b6daa0e99..577f310486 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf @@ -212,6 +212,11 @@ Não é possível anexar o depurador ao host de teste padrão com a ID de processo: {0}. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf index 0019acf1f1..95b127dc5b 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf @@ -212,6 +212,11 @@ Не удается подключить отладчик к узлу тестирования по умолчанию с идентификатором процесса {0}. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf index 9002b18d5e..1edfb4a487 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf @@ -212,6 +212,11 @@ Hata ayıklayıcı, {0} işlem kimliğine sahip varsayılan test ana bilgisayarına eklenemiyor. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf index 453f023f7f..7c88354463 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf @@ -123,6 +123,11 @@ Cannot attach the debugger to the default test host with process ID: {0}. + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf index f58621c99c..74844b0d61 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf @@ -212,6 +212,11 @@ 无法将调试程序附加到进程 ID 为“{0}”的默认测试主机。 + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf index a8b29cc527..e95fe80cd9 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf @@ -212,6 +212,11 @@ 無法將偵錯工具連結到處理序識別碼為 {0} 的預設測試主機。 + + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. + {0} Access denied while trying to create "TestResults" folder in mentioned location. You are getting this exception because you are running vstest.console.exe from a folder which requires having write access. To solve the issue: please run vstest.console.exe from a folder where you have write privileges. For more information, please look at the error message: + + \ No newline at end of file From c1b6b2c1ab4c8f181f3cb1a436afb6428009c7ab Mon Sep 17 00:00:00 2001 From: Jakub Chocholowicz <59966772+jakubch1@users.noreply.github.com> Date: Thu, 2 Jul 2020 12:07:01 +0200 Subject: [PATCH 11/12] Adding test run attachments processing (#2463) * v1 * Merging v1 * Rename to MultiTestRunsFinalization * New version * More changes * More changes * Next changes * Fix * test * More changes * Dmc chagnes * next * small changes * compiled * More changes * acceptance tests green * Review comments #1 * Resolving more comments * Tests for design mode client * Tests for events handler * revert not related changes * More changes * Compiling OK, tests OK * Unit tests for manager * More changes * More tests * tests for reqeust sender * more tests * Tests for cancelling * Acceptance tests done * Remove not used stuff * Fix comments * Fix race condition in test * Fix another race condition * Fix converting to xml * fix next test * fix test * Next changes * Review changes #1 * Fixing multi test finalization manager tests * Fixes * Fix last unit test * Fix acceptance tests * Progress feature, compiling + unit tests * acceptance tests changes * More changes * Fixing resources accesability * Fix test * Fix race conditions in acceptance tests * RFC changes merged * Log warning in case of unexpected message id * Fix spelling * Additional comment * Restore some stuff in interfaces * Big renaming * Added processingSettings * Fix naming * Move explanation to --- ...stRunAttachmentsProcessingEventsHandler.cs | 77 +++ .../DesignMode/DesignModeClient.cs | 43 +- .../RequestHelper/ITestRequestManager.cs | 12 + .../ITestRunAttachmentsProcessingManager.cs | 33 + .../Telemetry/TelemetryDataConstants.cs | 10 + .../Messages/MessageType.cs | 20 + ...RunAttachmentsProcessingCompletePayload.cs | 26 + ...RunAttachmentsProcessingProgressPayload.cs | 18 + .../Interfaces/ITestPlatformEventSource.cs | 36 + .../Tracing/TestPlatformEventSource.cs | 42 ++ .../TestPlatformInstrumentationEvents.cs | 30 + .../TestRunAttachmentsProcessingManager.cs | 180 +++++ .../Parallel/ParallelProxyExecutionManager.cs | 14 +- .../Parallel/ParallelRunDataAggregator.cs | 2 +- .../Parallel/ParallelRunEventsHandler.cs | 2 +- ...ProxyExecutionManagerWithDataCollection.cs | 7 +- .../ParallelDataCollectionEventsHandler.cs | 37 +- ...nAttachmentsProcessingCompleteEventArgs.cs | 42 ++ ...nAttachmentsProcessingProgressEventArgs.cs | 52 ++ ...stRunAttachmentsProcessingEventsHandler.cs | 32 + .../TestRunAttachmentsProcessingPayload.cs | 26 + .../IDataCollectorAttachmentProcessor.cs | 50 ++ .../IDataCollectorAttachments.cs | 1 + .../CodeCoverageDataAttachmentsHandler.cs | 68 +- .../ITranslationLayerRequestSender.cs | 5 +- .../ITranslationLayerRequestSenderAsync.cs | 34 +- .../Interfaces/IVsTestConsoleWrapper.cs | 13 +- .../Interfaces/IVsTestConsoleWrapperAsync.cs | 12 + ...form.VsTestConsole.TranslationLayer.csproj | 2 +- .../Resources/Resources.Designer.cs | 25 +- .../Resources/Resources.resx | 3 + .../Resources/xlf/Resources.cs.xlf | 5 + .../Resources/xlf/Resources.de.xlf | 5 + .../Resources/xlf/Resources.es.xlf | 5 + .../Resources/xlf/Resources.fr.xlf | 5 + .../Resources/xlf/Resources.it.xlf | 5 + .../Resources/xlf/Resources.ja.xlf | 5 + .../Resources/xlf/Resources.ko.xlf | 5 + .../Resources/xlf/Resources.pl.xlf | 5 + .../Resources/xlf/Resources.pt-BR.xlf | 5 + .../Resources/xlf/Resources.ru.xlf | 5 + .../Resources/xlf/Resources.tr.xlf | 5 + .../Resources/xlf/Resources.xlf | 5 + .../Resources/xlf/Resources.zh-Hans.xlf | 5 + .../Resources/xlf/Resources.zh-Hant.xlf | 5 + .../VsTestConsoleRequestSender.cs | 76 ++- .../VsTestConsoleWrapper.cs | 157 ++++- .../VsTestConsoleWrapperAsync.cs | 259 -------- .../TestPlatformHelpers/TestRequestManager.cs | 67 +- .../CodeCoverageTests.cs | 443 +++++++++++++ .../EventHandler/RunEventHandler.cs | 11 + ...estRunAttachmentsProcessingEventHandler.cs | 109 +++ ...AttachmentsProcessingEventsHandlerTests.cs | 67 ++ .../DesignMode/DesignModeClientTests.cs | 87 +++ ...estRunAttachmentsProcessingManagerTests.cs | 625 ++++++++++++++++++ .../ParallelProxyExecutionManagerTests.cs | 27 + ...arallelDataCollectionEventsHandlerTests.cs | 95 +++ .../Utility/ConverterTests.cs | 2 +- ...CodeCoverageDataAttachmentsHandlerTests.cs | 88 ++- .../VsTestConsoleRequestSenderTests.cs | 284 +++++++- .../VsTestConsoleWrapperAsyncTests.cs | 34 +- .../VsTestConsoleWrapperTests.cs | 21 + ...llyQualifiedTestsArgumentProcessorTests.cs | 19 +- .../ListTestsArgumentProcessorTests.cs | 17 +- .../RunSpecificTestsArgumentProcessorTests.cs | 47 +- .../RunTestsArgumentProcessorTests.cs | 17 +- .../TestRequestManagerTests.cs | 165 ++++- 67 files changed, 3250 insertions(+), 491 deletions(-) create mode 100644 src/Microsoft.TestPlatform.Client/AttachmentsProcessing/TestRunAttachmentsProcessingEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ITestRunAttachmentsProcessingManager.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunAttachmentsProcessingCompletePayload.cs create mode 100644 src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunAttachmentsProcessingProgressPayload.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/AttachmentsProcessing/TestRunAttachmentsProcessingManager.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunAttachmentsProcessingCompleteEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunAttachmentsProcessingProgressEventArgs.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunAttachmentsProcessingEventsHandler.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/TestRunAttachmentsProcessingPayload.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/DataCollector/IDataCollectorAttachmentProcessor.cs delete mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapperAsync.cs create mode 100644 test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/CodeCoverageTests.cs create mode 100644 test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs create mode 100644 test/Microsoft.TestPlatform.Client.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingEventsHandlerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingManagerTests.cs create mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ParallelDataCollectionEventsHandlerTests.cs diff --git a/src/Microsoft.TestPlatform.Client/AttachmentsProcessing/TestRunAttachmentsProcessingEventsHandler.cs b/src/Microsoft.TestPlatform.Client/AttachmentsProcessing/TestRunAttachmentsProcessingEventsHandler.cs new file mode 100644 index 0000000000..6bceff7f24 --- /dev/null +++ b/src/Microsoft.TestPlatform.Client/AttachmentsProcessing/TestRunAttachmentsProcessingEventsHandler.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.Client.TestRunAttachmentsProcessing +{ + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using System.Collections.Generic; + + /// + /// The test run attachments processing events handler. + /// + /// + public class TestRunAttachmentsProcessingEventsHandler : ITestRunAttachmentsProcessingEventsHandler + { + private readonly ICommunicationManager communicationManager; + + /// + /// Initializes a new instance of the class. + /// + /// The communication manager. + public TestRunAttachmentsProcessingEventsHandler(ICommunicationManager communicationManager) + { + this.communicationManager = communicationManager; + } + + /// + public void HandleTestRunAttachmentsProcessingComplete(TestRunAttachmentsProcessingCompleteEventArgs attachmentsProcessingCompleteEventArgs, IEnumerable lastChunk) + { + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("Test run attachments processing completed."); + } + + var payload = new TestRunAttachmentsProcessingCompletePayload() + { + AttachmentsProcessingCompleteEventArgs = attachmentsProcessingCompleteEventArgs, + Attachments = lastChunk + }; + + this.communicationManager.SendMessage(MessageType.TestRunAttachmentsProcessingComplete, payload); + } + + /// + public void HandleTestRunAttachmentsProcessingProgress(TestRunAttachmentsProcessingProgressEventArgs attachmentsProcessingProgressEventArgs) + { + var payload = new TestRunAttachmentsProcessingProgressPayload() + { + AttachmentsProcessingProgressEventArgs = attachmentsProcessingProgressEventArgs, + }; + + this.communicationManager.SendMessage(MessageType.TestRunAttachmentsProcessingProgress, payload); + } + + /// + public void HandleProcessedAttachmentsChunk(IEnumerable attachments) + { + throw new System.NotImplementedException(); + } + + /// + public void HandleLogMessage(TestMessageLevel level, string message) + { + var testMessagePayload = new TestMessagePayload { MessageLevel = level, Message = message }; + this.communicationManager.SendMessage(MessageType.TestMessage, testMessagePayload); + } + + /// + public void HandleRawMessage(string rawMessage) + { + // No-Op + } + } +} diff --git a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs index e4366b4c8b..6b16b27a55 100644 --- a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs +++ b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs @@ -9,7 +9,7 @@ namespace Microsoft.VisualStudio.TestPlatform.Client.DesignMode using System.Net; using System.Threading; using System.Threading.Tasks; - + using Microsoft.VisualStudio.TestPlatform.Client.TestRunAttachmentsProcessing; using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.Common.Logging; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; @@ -199,6 +199,14 @@ private void ProcessRequests(ITestRequestManager testRequestManager) break; } + case MessageType.TestRunAttachmentsProcessingStart: + { + var testRunAttachmentsProcessingPayload = + this.communicationManager.DeserializePayload(message); + this.StartTestRunAttachmentsProcessing(testRunAttachmentsProcessingPayload, testRequestManager); + break; + } + case MessageType.CancelDiscovery: { testRequestManager.CancelDiscovery(); @@ -217,6 +225,12 @@ private void ProcessRequests(ITestRequestManager testRequestManager) break; } + case MessageType.TestRunAttachmentsProcessingCancel: + { + testRequestManager.CancelTestRunAttachmentsProcessing(); + break; + } + case MessageType.CustomTestHostLaunchCallback: { this.onCustomTestHostLaunchAckReceived?.Invoke(message); @@ -458,6 +472,33 @@ private void StartDiscovery(DiscoveryRequestPayload discoveryRequestPayload, ITe }); } + private void StartTestRunAttachmentsProcessing(TestRunAttachmentsProcessingPayload attachmentsProcessingPayload, ITestRequestManager testRequestManager) + { + Task.Run( + delegate + { + try + { + testRequestManager.ProcessTestRunAttachments(attachmentsProcessingPayload, new TestRunAttachmentsProcessingEventsHandler(this.communicationManager), this.protocolConfig); + } + catch (Exception ex) + { + EqtTrace.Error("DesignModeClient: Exception in StartTestRunAttachmentsProcessing: " + ex); + + var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = ex.ToString() }; + this.communicationManager.SendMessage(MessageType.TestMessage, testMessagePayload); + + var payload = new TestRunAttachmentsProcessingCompletePayload() + { + Attachments = null + }; + + // Send run complete to translation layer + this.communicationManager.SendMessage(MessageType.TestRunAttachmentsProcessingComplete, payload); + } + }); + } + #region IDisposable Support private bool disposedValue = false; // To detect redundant calls diff --git a/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs b/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs index 45e6a655f6..0a0e68e301 100644 --- a/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs +++ b/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs @@ -44,6 +44,13 @@ public interface ITestRequestManager : IDisposable /// Protocol related information void RunTests(TestRunRequestPayload testRunRequestPayLoad, ITestHostLauncher customTestHostLauncher, ITestRunEventsRegistrar testRunEventsRegistrar, ProtocolConfig protocolConfig); + /// + /// Processes test run attachments + /// + /// Test run attachments processing payload + /// Test run attachments processing events handler + void ProcessTestRunAttachments(TestRunAttachmentsProcessingPayload testRunAttachmentsProcessingPayload, ITestRunAttachmentsProcessingEventsHandler testRunAttachmentsProcessingEventsHandler, ProtocolConfig protocolConfig); + /// /// Cancel the current TestRun request /// @@ -58,5 +65,10 @@ public interface ITestRequestManager : IDisposable /// Cancels the current discovery request /// void CancelDiscovery(); + + /// + /// Cancels the current test run attachments processing request + /// + void CancelTestRunAttachmentsProcessing(); } } diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ITestRunAttachmentsProcessingManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ITestRunAttachmentsProcessingManager.cs new file mode 100644 index 0000000000..d6b88d89c8 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ITestRunAttachmentsProcessingManager.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine +{ + /// + /// Orchestrates test run attachments processing operations. + /// + internal interface ITestRunAttachmentsProcessingManager + { + /// + /// Processes attachments and provides results through handler + /// + /// Collection of attachments + /// EventHandler for handling test run attachments processing event + /// Cancellation token + Task ProcessTestRunAttachmentsAsync(IRequestData requestData, IEnumerable attachments, ITestRunAttachmentsProcessingEventsHandler eventHandler, CancellationToken cancellationToken); + + /// + /// Processes attachments + /// + /// Collection of attachments + /// Cancellation token + /// Collection of attachments. + Task> ProcessTestRunAttachmentsAsync(IRequestData requestData, IEnumerable attachments, CancellationToken cancellationToken); + } +} diff --git a/src/Microsoft.TestPlatform.Common/Telemetry/TelemetryDataConstants.cs b/src/Microsoft.TestPlatform.Common/Telemetry/TelemetryDataConstants.cs index b7a277e1e1..be35c610c2 100644 --- a/src/Microsoft.TestPlatform.Common/Telemetry/TelemetryDataConstants.cs +++ b/src/Microsoft.TestPlatform.Common/Telemetry/TelemetryDataConstants.cs @@ -90,9 +90,19 @@ public static class TelemetryDataConstants public static string NumberOfAdapterUsedToDiscoverTests = "VS.TestDiscovery.AdaptersUsedCount"; + // *********************Attachments Processing**************************** + public static string NumberOfAttachmentsSentForProcessing = "VS.AttachmentsProcessing.InitialAttachmentsCount"; + + public static string NumberOfAttachmentsAfterProcessing = "VS.AttachmentsProcessing.FinalAttachmentsCount"; + + public static string TimeTakenInSecForAttachmentsProcessing = "VS.AttachmentsProcessing.TotalTimeTakenInSec"; + public static string AttachmentsProcessingState = "VS.AttachmentsProcessing.State"; + // **************Events Name ********************************** public static string TestDiscoveryCompleteEvent = "vs/testplatform/testdiscoverysession"; public static string TestExecutionCompleteEvent = "vs/testplatform/testrunsession"; + + public static string TestAttachmentsProcessingCompleteEvent = "vs/testplatform/testattachmentsprocessingsession"; } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs index 52a74e7f0b..76cfeec873 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs @@ -123,6 +123,26 @@ public static class MessageType /// public const string CustomTestHostLaunchCallback = "TestExecution.CustomTestHostLaunchCallback"; + /// + /// Test run attachments processing + /// + public const string TestRunAttachmentsProcessingStart = "TestRunAttachmentsProcessing.Start"; + + /// + /// Test run attachments processing callback + /// + public const string TestRunAttachmentsProcessingComplete = "TestRunAttachmentsProcessing.Complete"; + + /// + /// Test run attachments processing progress + /// + public const string TestRunAttachmentsProcessingProgress = "TestRunAttachmentsProcessing.Progress"; + + /// + /// Cancel test run attachments processing + /// + public const string TestRunAttachmentsProcessingCancel = "TestRunAttachmentsProcessing.Cancel"; + /// /// Extensions Initialization /// diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunAttachmentsProcessingCompletePayload.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunAttachmentsProcessingCompletePayload.cs new file mode 100644 index 0000000000..40ac9c4c8b --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunAttachmentsProcessingCompletePayload.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel +{ + using System.Collections.Generic; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Test run attachments processing complete payload. + /// + public class TestRunAttachmentsProcessingCompletePayload + { + /// + /// Gets or sets the test run attachments processing complete args. + /// + public TestRunAttachmentsProcessingCompleteEventArgs AttachmentsProcessingCompleteEventArgs { get; set; } + + /// + /// Gets or sets the attachments. + /// + public IEnumerable Attachments { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunAttachmentsProcessingProgressPayload.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunAttachmentsProcessingProgressPayload.cs new file mode 100644 index 0000000000..80082ae30d --- /dev/null +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/TestRunAttachmentsProcessingProgressPayload.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel +{ + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + + /// + /// Test run attachments processing complete payload. + /// + public class TestRunAttachmentsProcessingProgressPayload + { + /// + /// Gets or sets the test run attachments processing complete args. + /// + public TestRunAttachmentsProcessingProgressEventArgs AttachmentsProcessingProgressEventArgs { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/Interfaces/ITestPlatformEventSource.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/Interfaces/ITestPlatformEventSource.cs index 137924573b..7f88ccbf83 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/Interfaces/ITestPlatformEventSource.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/Interfaces/ITestPlatformEventSource.cs @@ -182,5 +182,41 @@ public interface ITestPlatformEventSource /// Mark the completion of Metrics Dispose. /// void MetricsDisposeStop(); + + /// + /// The test run attachments processing request start. + /// + void TestRunAttachmentsProcessingRequestStart(); + + /// + /// The test run attachments processing request stop. + /// + void TestRunAttachmentsProcessingRequestStop(); + + /// + /// The test run attachments processing start. + /// + /// + /// The number of attachments. + /// + void TestRunAttachmentsProcessingStart(long numberOfAttachments); + + /// + /// The test run attachments processing stop. + /// + /// + /// The number of attachments. + /// + void TestRunAttachmentsProcessingStop(long numberOfAttachments); + + /// + /// Mark the start of translation layer test run attachments processing request. + /// + void TranslationLayerTestRunAttachmentsProcessingStart(); + + /// + /// Mark the completion of translation layer test run attachments processing request. + /// + void TranslationLayerTestRunAttachmentsProcessingStop(); } } diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformEventSource.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformEventSource.cs index 10e71e7a91..74adb6a2af 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformEventSource.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformEventSource.cs @@ -237,5 +237,47 @@ public void MetricsDisposeStop() { this.WriteEvent(TestPlatformInstrumentationEvents.MetricsDisposeStopEventId); } + + /// + [Event(TestPlatformInstrumentationEvents.TestRunAttachmentsProcessingRequestStartEventId)] + public void TestRunAttachmentsProcessingRequestStart() + { + this.WriteEvent(TestPlatformInstrumentationEvents.TestRunAttachmentsProcessingRequestStartEventId); + } + + /// + [Event(TestPlatformInstrumentationEvents.TestRunAttachmentsProcessingRequestStopEventId)] + public void TestRunAttachmentsProcessingRequestStop() + { + this.WriteEvent(TestPlatformInstrumentationEvents.TestRunAttachmentsProcessingRequestStopEventId); + } + + /// + [Event(TestPlatformInstrumentationEvents.TestRunAttachmentsProcessingStartEventId)] + public void TestRunAttachmentsProcessingStart(long numberOfAttachments) + { + this.WriteEvent(TestPlatformInstrumentationEvents.TestRunAttachmentsProcessingStartEventId, numberOfAttachments); + } + + /// + [Event(TestPlatformInstrumentationEvents.TestRunAttachmentsProcessingStopEventId)] + public void TestRunAttachmentsProcessingStop(long numberOfAttachments) + { + this.WriteEvent(TestPlatformInstrumentationEvents.TestRunAttachmentsProcessingStopEventId, numberOfAttachments); + } + + /// + [Event(TestPlatformInstrumentationEvents.TranslationLayerTestRunAttachmentsProcessingStartEventId)] + public void TranslationLayerTestRunAttachmentsProcessingStart() + { + this.WriteEvent(TestPlatformInstrumentationEvents.TranslationLayerTestRunAttachmentsProcessingStartEventId); + } + + /// + [Event(TestPlatformInstrumentationEvents.TranslationLayerTestRunAttachmentsProcessingStopEventId)] + public void TranslationLayerTestRunAttachmentsProcessingStop() + { + this.WriteEvent(TestPlatformInstrumentationEvents.TranslationLayerTestRunAttachmentsProcessingStopEventId); + } } } diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformInstrumentationEvents.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformInstrumentationEvents.cs index 629d8cd45c..1aa2311048 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformInstrumentationEvents.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformInstrumentationEvents.cs @@ -157,5 +157,35 @@ internal class TestPlatformInstrumentationEvents /// Event fired on Metrics Dispose completes. /// public const int MetricsDisposeStopEventId = 0x39; + + /// + /// The session attachments processing start event id. + /// + public const int TestRunAttachmentsProcessingStartEventId = 0x40; + + /// + /// The session attachments processing stop event id. + /// + public const int TestRunAttachmentsProcessingStopEventId = 0x41; + + /// + /// The session attachments processing request start event id. + /// + public const int TestRunAttachmentsProcessingRequestStartEventId = 0x42; + + /// + /// The session attachments processing request stop event id. + /// + public const int TestRunAttachmentsProcessingRequestStopEventId = 0x43; + + /// + /// Events fired on session attachments processing start of translation layer. + /// + public const int TranslationLayerTestRunAttachmentsProcessingStartEventId = 0x44; + + /// + /// Events fired on session attachments processing complete in translation layer. + /// + public const int TranslationLayerTestRunAttachmentsProcessingStopEventId = 0x45; } } \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/AttachmentsProcessing/TestRunAttachmentsProcessingManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/AttachmentsProcessing/TestRunAttachmentsProcessingManager.cs new file mode 100644 index 0000000000..6eff75838a --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/AttachmentsProcessing/TestRunAttachmentsProcessingManager.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestRunAttachmentsProcessing +{ + /// + /// Orchestrates test run attachments processing operations. + /// + public class TestRunAttachmentsProcessingManager : ITestRunAttachmentsProcessingManager + { + private static string AttachmentsProcessingCompleted = "Completed"; + private static string AttachmentsProcessingCanceled = "Canceled"; + private static string AttachmentsProcessingFailed = "Failed"; + + private readonly ITestPlatformEventSource testPlatformEventSource; + private readonly IDataCollectorAttachmentProcessor[] dataCollectorAttachmentsProcessors; + + /// + /// Initializes a new instance of the class. + /// + public TestRunAttachmentsProcessingManager(ITestPlatformEventSource testPlatformEventSource, params IDataCollectorAttachmentProcessor[] dataCollectorAttachmentsProcessors) + { + this.testPlatformEventSource = testPlatformEventSource ?? throw new ArgumentNullException(nameof(testPlatformEventSource)); + this.dataCollectorAttachmentsProcessors = dataCollectorAttachmentsProcessors ?? throw new ArgumentNullException(nameof(dataCollectorAttachmentsProcessors)); + } + + /// + public async Task ProcessTestRunAttachmentsAsync(IRequestData requestData, IEnumerable attachments, ITestRunAttachmentsProcessingEventsHandler eventHandler, CancellationToken cancellationToken) + { + await InternalProcessTestRunAttachmentsAsync(requestData, new Collection(attachments.ToList()), eventHandler, cancellationToken).ConfigureAwait(false); + } + /// + public Task> ProcessTestRunAttachmentsAsync(IRequestData requestData, IEnumerable attachments, CancellationToken cancellationToken) + { + return InternalProcessTestRunAttachmentsAsync(requestData, new Collection(attachments.ToList()), null, cancellationToken); + } + + private async Task> InternalProcessTestRunAttachmentsAsync(IRequestData requestData, Collection attachments, ITestRunAttachmentsProcessingEventsHandler eventHandler, CancellationToken cancellationToken) + { + Stopwatch stopwatch = Stopwatch.StartNew(); + + try + { + testPlatformEventSource.TestRunAttachmentsProcessingStart(attachments?.Count ?? 0); + requestData.MetricsCollection.Add(TelemetryDataConstants.NumberOfAttachmentsSentForProcessing, attachments?.Count ?? 0); + + cancellationToken.ThrowIfCancellationRequested(); + + var taskCompletionSource = new TaskCompletionSource>(); + using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled())) + { + Task> task = Task.Run(async () => await ProcessAttachmentsAsync(new Collection(attachments.ToList()), eventHandler, cancellationToken)); + + var completedTask = await Task.WhenAny(task, taskCompletionSource.Task).ConfigureAwait(false); + + if (completedTask == task) + { + return FinalizeOperation(requestData, new TestRunAttachmentsProcessingCompleteEventArgs(false, null), await task, stopwatch, eventHandler); + } + else + { + eventHandler?.HandleLogMessage(TestMessageLevel.Informational, "Attachments processing was cancelled."); + return FinalizeOperation(requestData, new TestRunAttachmentsProcessingCompleteEventArgs(true, null), attachments, stopwatch, eventHandler); + } + } + } + catch (OperationCanceledException) + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("TestRunAttachmentsProcessingManager: operation was cancelled."); + } + return FinalizeOperation(requestData, new TestRunAttachmentsProcessingCompleteEventArgs(true, null), attachments, stopwatch, eventHandler); + } + catch (Exception e) + { + EqtTrace.Error("TestRunAttachmentsProcessingManager: Exception in ProcessTestRunAttachmentsAsync: " + e); + + eventHandler?.HandleLogMessage(TestMessageLevel.Error, e.Message); + return FinalizeOperation(requestData, new TestRunAttachmentsProcessingCompleteEventArgs(false, e), attachments, stopwatch, eventHandler); + } + } + + private async Task> ProcessAttachmentsAsync(Collection attachments, ITestRunAttachmentsProcessingEventsHandler eventsHandler, CancellationToken cancellationToken) + { + if (attachments == null || !attachments.Any()) return attachments; + + var logger = CreateMessageLogger(eventsHandler); + + for (int i = 0; i < dataCollectorAttachmentsProcessors.Length; i++) + { + var dataCollectorAttachmentsProcessor = dataCollectorAttachmentsProcessors[i]; + int attachmentsHandlerIndex = i + 1; + + ICollection attachmentProcessorUris = dataCollectorAttachmentsProcessor.GetExtensionUris()?.ToList(); + if (attachmentProcessorUris != null && attachmentProcessorUris.Any()) + { + var attachmentsToBeProcessed = attachments.Where(dataCollectionAttachment => attachmentProcessorUris.Any(uri => uri.Equals(dataCollectionAttachment.Uri))).ToArray(); + if (attachmentsToBeProcessed.Any()) + { + foreach (var attachment in attachmentsToBeProcessed) + { + attachments.Remove(attachment); + } + + IProgress progressReporter = new Progress((int progress) => + eventsHandler?.HandleTestRunAttachmentsProcessingProgress( + new TestRunAttachmentsProcessingProgressEventArgs(attachmentsHandlerIndex, attachmentProcessorUris, progress, dataCollectorAttachmentsProcessors.Length))); + + ICollection processedAttachments = await dataCollectorAttachmentsProcessor.ProcessAttachmentSetsAsync(new Collection(attachmentsToBeProcessed), progressReporter, logger, cancellationToken).ConfigureAwait(false); + + foreach (var attachment in processedAttachments) + { + attachments.Add(attachment); + } + } + } + } + + return attachments; + } + + private Collection FinalizeOperation(IRequestData requestData, TestRunAttachmentsProcessingCompleteEventArgs completeArgs, Collection attachments, Stopwatch stopwatch, ITestRunAttachmentsProcessingEventsHandler eventHandler) + { + testPlatformEventSource.TestRunAttachmentsProcessingStop(attachments.Count); + requestData.MetricsCollection.Add(TelemetryDataConstants.NumberOfAttachmentsAfterProcessing, attachments.Count); + requestData.MetricsCollection.Add(TelemetryDataConstants.AttachmentsProcessingState, completeArgs.Error != null ? AttachmentsProcessingFailed : completeArgs.IsCanceled ? AttachmentsProcessingCanceled : AttachmentsProcessingCompleted); + + stopwatch.Stop(); + requestData.MetricsCollection.Add(TelemetryDataConstants.TimeTakenInSecForAttachmentsProcessing, stopwatch.Elapsed.TotalSeconds); + + completeArgs.Metrics = requestData.MetricsCollection.Metrics; + eventHandler?.HandleTestRunAttachmentsProcessingComplete(completeArgs, attachments); + + return attachments; + } + + private IMessageLogger CreateMessageLogger(ITestRunAttachmentsProcessingEventsHandler eventsHandler) + { + return eventsHandler != null ? (IMessageLogger)new AttachmentsProcessingMessageLogger(eventsHandler) : new NullMessageLogger(); + } + + private class AttachmentsProcessingMessageLogger : IMessageLogger + { + private readonly ITestRunAttachmentsProcessingEventsHandler eventsHandler; + + public AttachmentsProcessingMessageLogger(ITestRunAttachmentsProcessingEventsHandler eventsHandler) + { + this.eventsHandler = eventsHandler ?? throw new ArgumentNullException(nameof(eventsHandler)); + } + + public void SendMessage(TestMessageLevel testMessageLevel, string message) + { + eventsHandler.HandleLogMessage(testMessageLevel, message); + } + } + + private class NullMessageLogger : IMessageLogger + { + public void SendMessage(TestMessageLevel testMessageLevel, string message) + { + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs index 3e0e0da772..d5cc0cecb4 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs @@ -14,11 +14,14 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestRunAttachmentsProcessing; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestPlatform.Utilities; /// /// ParallelProxyExecutionManager that manages parallel execution @@ -256,12 +259,19 @@ private ParallelRunEventsHandler GetEventsHandler(IProxyExecutionManager concurr { if (concurrentManager is ProxyExecutionManagerWithDataCollection) { + var concurrentManagerWithDataCollection = concurrentManager as ProxyExecutionManagerWithDataCollection; + + // TODO : use TestPluginCache to iterate over all IDataCollectorAttachments + var attachmentsProcessingManager = new TestRunAttachmentsProcessingManager(TestPlatformEventSource.Instance, new CodeCoverageDataAttachmentsHandler()); + return new ParallelDataCollectionEventsHandler( this.requestData, - concurrentManager, + concurrentManagerWithDataCollection, this.currentRunEventsHandler, this, - this.currentRunDataAggregator); + this.currentRunDataAggregator, + attachmentsProcessingManager, + concurrentManagerWithDataCollection.CancellationToken); } return new ParallelRunEventsHandler( diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs index 2cc3d3c26c..b2d7f238b8 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunDataAggregator.cs @@ -49,7 +49,7 @@ public ParallelRunDataAggregator() public TimeSpan ElapsedTime { get; set; } - public Collection RunContextAttachments { get; } + public Collection RunContextAttachments { get; set; } public List RunCompleteArgsAttachments { get; } diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunEventsHandler.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunEventsHandler.cs index bacf1da410..5a66d9528e 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunEventsHandler.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelRunEventsHandler.cs @@ -31,7 +31,7 @@ internal class ParallelRunEventsHandler : ITestRunEventsHandler2 private IDataSerializer dataSerializer; - private IRequestData requestData; + protected IRequestData requestData; public ParallelRunEventsHandler(IRequestData requestData, IProxyExecutionManager proxyExecutionManager, diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManagerWithDataCollection.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManagerWithDataCollection.cs index 6aa6684e2d..179a6df370 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManagerWithDataCollection.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManagerWithDataCollection.cs @@ -5,7 +5,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client { using System; using System.Collections.Generic; - + using System.Threading; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; @@ -70,6 +70,11 @@ internal IProxyDataCollectionManager ProxyDataCollectionManager get; private set; } + /// + /// Gets the cancellation token for execution. + /// + internal CancellationToken CancellationToken => CancellationTokenSource.Token; + /// /// Ensure that the Execution component of engine is ready for execution usually by loading extensions. /// Skip default adapters flag. diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ParallelDataCollectionEventsHandler.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ParallelDataCollectionEventsHandler.cs index 0f5432cd78..a50d5fe6ff 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ParallelDataCollectionEventsHandler.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/DataCollection/ParallelDataCollectionEventsHandler.cs @@ -3,30 +3,33 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection { - using System; using System.Collections.Generic; using System.Collections.ObjectModel; - using System.Linq; - + using System.Threading; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; - using Microsoft.VisualStudio.TestPlatform.Utilities; internal class ParallelDataCollectionEventsHandler : ParallelRunEventsHandler { private readonly ParallelRunDataAggregator runDataAggregator; + private readonly ITestRunAttachmentsProcessingManager attachmentsProcessingManager; + private readonly CancellationToken cancellationToken; public ParallelDataCollectionEventsHandler(IRequestData requestData, IProxyExecutionManager proxyExecutionManager, ITestRunEventsHandler actualRunEventsHandler, IParallelProxyExecutionManager parallelProxyExecutionManager, - ParallelRunDataAggregator runDataAggregator) : + ParallelRunDataAggregator runDataAggregator, + ITestRunAttachmentsProcessingManager attachmentsProcessingManager, + CancellationToken cancellationToken) : this(requestData, proxyExecutionManager, actualRunEventsHandler, parallelProxyExecutionManager, runDataAggregator, JsonDataSerializer.Instance) { + this.attachmentsProcessingManager = attachmentsProcessingManager; + this.cancellationToken = cancellationToken; } internal ParallelDataCollectionEventsHandler(IRequestData requestData, @@ -53,33 +56,13 @@ public override void HandleTestRunComplete( if (parallelRunComplete) { - // TODO : use TestPluginCache to iterate over all IDataCollectorAttachments - { - var coverageHandler = new CodeCoverageDataAttachmentsHandler(); - Uri attachementUri = coverageHandler.GetExtensionUri(); - if (attachementUri != null) - { - var coverageAttachments = runDataAggregator.RunContextAttachments - .Where(dataCollectionAttachment => attachementUri.Equals(dataCollectionAttachment.Uri)).ToArray(); - - foreach (var coverageAttachment in coverageAttachments) - { - runDataAggregator.RunContextAttachments.Remove(coverageAttachment); - } - - ICollection attachments = coverageHandler.HandleDataCollectionAttachmentSets(new Collection(coverageAttachments)); - foreach (var attachment in attachments) - { - runDataAggregator.RunContextAttachments.Add(attachment); - } - } - } + runDataAggregator.RunContextAttachments = attachmentsProcessingManager.ProcessTestRunAttachmentsAsync(requestData, runDataAggregator.RunContextAttachments, cancellationToken).Result ?? runDataAggregator.RunContextAttachments; var completedArgs = new TestRunCompleteEventArgs(this.runDataAggregator.GetAggregatedRunStats(), this.runDataAggregator.IsCanceled, this.runDataAggregator.IsAborted, this.runDataAggregator.GetAggregatedException(), - this.runDataAggregator.RunContextAttachments, + runDataAggregator.RunContextAttachments, this.runDataAggregator.ElapsedTime); // Add Metrics from Test Host diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunAttachmentsProcessingCompleteEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunAttachmentsProcessingCompleteEventArgs.cs new file mode 100644 index 0000000000..b5e446ff68 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunAttachmentsProcessingCompleteEventArgs.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + + [DataContract] + public class TestRunAttachmentsProcessingCompleteEventArgs : EventArgs + { + /// + /// Default constructor. + /// + /// Specifies whether the attachments processing is canceled. + /// Specifies the error encountered during the execution of the attachments processing. + public TestRunAttachmentsProcessingCompleteEventArgs(bool isCanceled, Exception error) + { + this.IsCanceled = isCanceled; + this.Error = error; + } + + /// + /// Gets a value indicating whether the attachments processing is canceled or not. + /// + [DataMember] + public bool IsCanceled { get; private set; } + + /// + /// Gets the error encountered during the attachments processing of the test runs. Null if there is no error. + /// + [DataMember] + public Exception Error { get; private set; } + + /// + /// Get or Sets the Metrics (used for telemetry) + /// + [DataMember] + public IDictionary Metrics { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunAttachmentsProcessingProgressEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunAttachmentsProcessingProgressEventArgs.cs new file mode 100644 index 0000000000..8218336e5b --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/TestRunAttachmentsProcessingProgressEventArgs.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + + [DataContract] + public class TestRunAttachmentsProcessingProgressEventArgs : EventArgs + { + /// + /// Default constructor. + /// + /// Specifies current attachment processor index. + /// Specifies current processor Uris. + /// Specifies current processor progress. + /// Specifies the overall number of processors. + public TestRunAttachmentsProcessingProgressEventArgs(long currentAttachmentProcessorIndex, ICollection currentAttachmentProcessorUris, long currentAttachmentProcessorProgress, long attachmentProcessorsCount) + { + CurrentAttachmentProcessorIndex = currentAttachmentProcessorIndex; + CurrentAttachmentProcessorUris = currentAttachmentProcessorUris; + CurrentAttachmentProcessorProgress = currentAttachmentProcessorProgress; + AttachmentProcessorsCount = attachmentProcessorsCount; + } + + /// + /// Gets a current attachment processor index. + /// + [DataMember] + public long CurrentAttachmentProcessorIndex { get; private set; } + + /// + /// Gets a current attachment processor URI. + /// + [DataMember] + public ICollection CurrentAttachmentProcessorUris { get; private set; } + + /// + /// Gets a current attachment processor progress. + /// + [DataMember] + public long CurrentAttachmentProcessorProgress { get; private set; } + + /// + /// Gets the overall number of attachment processors. + /// + [DataMember] + public long AttachmentProcessorsCount { get; private set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunAttachmentsProcessingEventsHandler.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunAttachmentsProcessingEventsHandler.cs new file mode 100644 index 0000000000..f6235a92e9 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestRunAttachmentsProcessingEventsHandler.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + /// + /// Interface contract for handling test run attachments processing events + /// + public interface ITestRunAttachmentsProcessingEventsHandler : ITestMessageEventHandler + { + /// + /// Dispatch TestRunAttachmentsProcessingComplete event to listeners. + /// + /// AttachmentsProcessing Complete event args. + /// Last set of processed attachment sets. + void HandleTestRunAttachmentsProcessingComplete(TestRunAttachmentsProcessingCompleteEventArgs attachmentsProcessingCompleteEventArgs, IEnumerable lastChunk); + + /// + /// Dispatch ProcessedAttachmentsChunk event to listeners. + /// + /// Processed attachment sets. + void HandleProcessedAttachmentsChunk(IEnumerable attachments); + + /// + /// Dispatch TestRunAttachmentsProcessingProgress event to listeners. + /// + /// AttachmentsProcessing Progress event args. + void HandleTestRunAttachmentsProcessingProgress(TestRunAttachmentsProcessingProgressEventArgs AttachmentsProcessingProgressEventArgs); + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/TestRunAttachmentsProcessingPayload.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/TestRunAttachmentsProcessingPayload.cs new file mode 100644 index 0000000000..6abdb68082 --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/TestRunAttachmentsProcessingPayload.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client +{ + using System.Collections.Generic; + using System.Runtime.Serialization; + + /// + /// Class used to define the TestRunAttachmentsProcessingPayload sent by the Vstest.console translation layers into design mode + /// + public class TestRunAttachmentsProcessingPayload + { + /// + /// Collection of attachments. + /// + [DataMember] + public IEnumerable Attachments { get; set; } + + /// + /// Gets or sets whether Metrics should be collected or not. + /// + [DataMember] + public bool CollectMetrics { get; set; } + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/IDataCollectorAttachmentProcessor.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/IDataCollectorAttachmentProcessor.cs new file mode 100644 index 0000000000..dfdc77c9fe --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/IDataCollectorAttachmentProcessor.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + /// Interface for data collectors add-ins that choose to reprocess generated attachments + /// + public interface IDataCollectorAttachmentProcessor + { + /// + /// Gets the attachments Uris, which are handled by attachment processor + /// + IEnumerable GetExtensionUris(); + + /// + /// Indicates whether attachment processor is supporting incremental processing of attachments + /// + /// + /// `SupportsIncrementalProcessing` should indicate if attachment processor is supporting incremental processing of attachments. It means that `ProcessAttachmentSetsAsync` should be [associative](https://en.wikipedia.org/wiki/Associative_property). + /// By default `SupportsIncrementalProcessing` should be `False`, unless processing can take longer time and it's beneficial to start the process as soon as possible. + /// + /// If `SupportsIncrementalProcessing` is `True` Test Platform may try to speed up whole process by reprocessing data collector attachments as soon as possible when any two test executions are done.For example let's assume we have 5 test executions which are generating 5 data collector attachments: `a1`, `a2`, `a3`, `a4` and `a5`. Test platform could perform invocations: + /// * `var result1 = await ProcessAttachmentSetsAsync([a1, a2, a3], ...);` when first 3 executions are done + /// * `var result2 = await ProcessAttachmentSetsAsync(result1.Concat([a4]), ...);` when 4th execution is done + /// * `var finalResult = await ProcessAttachmentSetsAsync(result2.Concat([a5]), ...);` when last test execution is done + /// + /// If `SupportsIncrementalProcessing` is `False` then Test Platform will wait for all test executions to finish and call `ProcessAttachmentSetsAsync` only once: + /// * `var finalResult = await ProcessAttachmentSetsAsync([a1, a2, a3, a4, a5], ...);` + /// + bool SupportsIncrementalProcessing { get; } + + /// + /// Reprocess attachments generated by independent test executions + /// + /// Attachments to be processed + /// Progress reporter. Accepts integers from 0 to 100 + /// Message logger + /// Cancellation token + /// Attachments after reprocessing + Task> ProcessAttachmentSetsAsync(ICollection attachments, IProgress progressReporter, IMessageLogger logger, CancellationToken cancellationToken); + } +} diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/IDataCollectorAttachments.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/IDataCollectorAttachments.cs index e0cd5e97c9..a342d74ea6 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/IDataCollectorAttachments.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/IDataCollectorAttachments.cs @@ -9,6 +9,7 @@ namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection /// /// Interface for data collectors add-ins that choose to handle attachment(s) generated /// + [Obsolete("Interface is deprecated. Please use IDataCollectorAttachmentProcessor instead")] public interface IDataCollectorAttachments { /// diff --git a/src/Microsoft.TestPlatform.Utilities/CodeCoverageDataAttachmentsHandler.cs b/src/Microsoft.TestPlatform.Utilities/CodeCoverageDataAttachmentsHandler.cs index d75d81ce88..2b911f6926 100644 --- a/src/Microsoft.TestPlatform.Utilities/CodeCoverageDataAttachmentsHandler.cs +++ b/src/Microsoft.TestPlatform.Utilities/CodeCoverageDataAttachmentsHandler.cs @@ -9,11 +9,14 @@ namespace Microsoft.VisualStudio.TestPlatform.Utilities using System.IO; using System.Linq; using System.Reflection; + using System.Threading; + using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; - public class CodeCoverageDataAttachmentsHandler : IDataCollectorAttachments + public class CodeCoverageDataAttachmentsHandler : IDataCollectorAttachmentProcessor { private const string CoverageUri = "datacollector://microsoft/CodeCoverage/2.0"; private const string CoverageFileExtension = ".coverage"; @@ -25,42 +28,50 @@ public class CodeCoverageDataAttachmentsHandler : IDataCollectorAttachments private static readonly Uri CodeCoverageDataCollectorUri = new Uri(CoverageUri); - public Uri GetExtensionUri() + public bool SupportsIncrementalProcessing => true; + + public IEnumerable GetExtensionUris() { - return CodeCoverageDataCollectorUri; - } + yield return CodeCoverageDataCollectorUri; + } - public ICollection HandleDataCollectionAttachmentSets(ICollection dataCollectionAttachments) + public Task> ProcessAttachmentSetsAsync(ICollection attachments, IProgress progressReporter, IMessageLogger logger, CancellationToken cancellationToken) { - if (dataCollectionAttachments != null && dataCollectionAttachments.Any()) + if (attachments != null && attachments.Any()) { - var codeCoverageFiles = dataCollectionAttachments.Select(coverageAttachment => coverageAttachment.Attachments[0].Uri.LocalPath).ToArray(); - var outputFile = MergeCodeCoverageFiles(codeCoverageFiles); + var codeCoverageFiles = attachments.Select(coverageAttachment => coverageAttachment.Attachments[0].Uri.LocalPath).ToArray(); + var outputFile = MergeCodeCoverageFiles(codeCoverageFiles, progressReporter, cancellationToken); var attachmentSet = new AttachmentSet(CodeCoverageDataCollectorUri, CoverageFriendlyName); if (!string.IsNullOrEmpty(outputFile)) { attachmentSet.Attachments.Add(new UriDataAttachment(new Uri(outputFile), CoverageFriendlyName)); - return new Collection { attachmentSet }; + return Task.FromResult((ICollection)new Collection { attachmentSet }); } // In case merging fails(esp in dotnet core we cannot merge), so return filtered list of Code Coverage Attachments - return dataCollectionAttachments; + return Task.FromResult(attachments); } - return new Collection(); + return Task.FromResult((ICollection)new Collection()); } - private string MergeCodeCoverageFiles(IList files) + private string MergeCodeCoverageFiles(IList files, IProgress progressReporter, CancellationToken cancellationToken) { - string fileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + CoverageFileExtension); + if(files.Count == 1) + { + return files[0]; + } + + string tempFileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + CoverageFileExtension); string outputfileName = files[0]; - File.Create(fileName).Dispose(); + File.Create(tempFileName).Dispose(); var assemblyPath = Path.Combine(Path.GetDirectoryName(typeof(CodeCoverageDataAttachmentsHandler).GetTypeInfo().Assembly.GetAssemblyLocation()), CodeCoverageAnalysisAssemblyName + ".dll"); try { + cancellationToken.ThrowIfCancellationRequested(); Assembly assembly = new PlatformAssemblyLoadContext().LoadAssemblyFromPath(assemblyPath); var type = assembly.GetType(CodeCoverageAnalysisAssemblyName + "." + CoverageInfoTypeName); @@ -68,19 +79,40 @@ private string MergeCodeCoverageFiles(IList files) if (methodInfo != null) { + IList filesToDelete = new List(files.Count) { tempFileName }; + for (int i = 1; i < files.Count; i++) { - methodInfo.Invoke(null, new object[] { files[i], outputfileName, fileName, true }); - File.Copy(fileName, outputfileName, true); + cancellationToken.ThrowIfCancellationRequested(); + progressReporter?.Report(100 * i / files.Count); + + cancellationToken.ThrowIfCancellationRequested(); + methodInfo.Invoke(null, new object[] { files[i], outputfileName, tempFileName, true }); - File.Delete(files[i]); + cancellationToken.ThrowIfCancellationRequested(); + File.Copy(tempFileName, outputfileName, true); + + filesToDelete.Add(files[i]); } - File.Delete(fileName); + cancellationToken.ThrowIfCancellationRequested(); + foreach (string fileName in filesToDelete) + { + File.Delete(fileName); + } } + progressReporter?.Report(100); return outputfileName; } + catch (OperationCanceledException) + { + if (EqtTrace.IsWarningEnabled) + { + EqtTrace.Warning("CodeCoverageDataCollectorAttachmentsHandler: operation was cancelled."); + } + throw; + } catch (Exception ex) { if (EqtTrace.IsErrorEnabled) diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs index aabf810570..35e50a9b10 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs @@ -5,7 +5,8 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces { using System; using System.Collections.Generic; - + using System.Threading; + using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; @@ -13,7 +14,7 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces /// /// Defines contract to send test platform requests to test host /// - internal interface ITranslationLayerRequestSender : IDisposable + internal interface ITranslationLayerRequestSender : IDisposable, ITranslationLayerRequestSenderAsync { /// /// Initializes the communication for sending requests diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSenderAsync.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSenderAsync.cs index a49ce93fab..23166126d7 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSenderAsync.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSenderAsync.cs @@ -5,6 +5,7 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces { using System; using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -22,16 +23,6 @@ internal interface ITranslationLayerRequestSenderAsync : IDisposable /// Task InitializeCommunicationAsync(int clientConnectionTimeout); - /// - /// See - /// - void Close(); - - /// - /// See - /// - void InitializeExtensions(IEnumerable pathToAdditionalExtensions); - /// /// Asynchronous equivalent of ITranslationLayerRequestSender.DiscoverTests/>. /// @@ -58,23 +49,12 @@ internal interface ITranslationLayerRequestSenderAsync : IDisposable Task StartTestRunWithCustomHostAsync(IEnumerable testCases, string runSettings, TestPlatformOptions options, ITestRunEventsHandler runEventsHandler, ITestHostLauncher customTestHostLauncher); /// - /// See . - /// - void EndSession(); - - /// - /// See . - /// - void CancelTestRun(); - - /// - /// See . - /// - void AbortTestRun(); - - /// - /// See . + /// Provides back all attachments to TestPlatform for additional processing (for example merging) /// - void OnProcessExited(); + /// Collection of attachments + /// Enables metrics collection + /// Events handler + /// Cancellation token + Task ProcessTestRunAttachmentsAsync(IEnumerable attachments, bool collectMetrics, ITestRunAttachmentsProcessingEventsHandler testRunAttachmentsProcessingCompleteEventsHandler, CancellationToken cancellationToken); } } diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs index b1469d3f8d..79e09b98dd 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs @@ -4,7 +4,6 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces { using System.Collections.Generic; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; @@ -12,7 +11,7 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces /// /// Controller for various test operations on the test runner. /// - public interface IVsTestConsoleWrapper + public interface IVsTestConsoleWrapper : IVsTestConsoleWrapperAsync { /// /// Starts the test runner process and readies for requests. @@ -40,12 +39,12 @@ public interface IVsTestConsoleWrapper /// Settings XML for test discovery /// Options to be passed into the platform. /// EventHandler to receive discovery events - void DiscoverTests(IEnumerable sources, string discoverySettings, TestPlatformOptions options, ITestDiscoveryEventsHandler2 discoveryEventsHandler); + void DiscoverTests(IEnumerable sources, string discoverySettings, TestPlatformOptions options, ITestDiscoveryEventsHandler2 discoveryEventsHandler); /// /// Cancels the last discovery request. /// - void CancelDiscovery(); + new void CancelDiscovery(); /// /// Starts a test run given a list of sources. @@ -122,16 +121,16 @@ public interface IVsTestConsoleWrapper /// /// Cancel the last test run. /// - void CancelTestRun(); + new void CancelTestRun(); /// /// Abort the last test run. /// - void AbortTestRun(); + new void AbortTestRun(); /// /// Ends the test session and stops processing requests. /// - void EndSession(); + new void EndSession(); } } diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapperAsync.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapperAsync.cs index a303a3504f..452de38cb6 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapperAsync.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapperAsync.cs @@ -4,6 +4,7 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces { using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -91,6 +92,17 @@ public interface IVsTestConsoleWrapperAsync /// void AbortTestRun(); + /// + /// Provides back all attachments to TestPlatform for additional processing (for example merging) + /// + /// Collection of attachments + /// XML processing settings + /// Indicates that all test executions are done and all data is provided + /// Enables metrics collection (used for telemetry) + /// EventHandler to receive session complete event + /// Cancellation token + Task ProcessTestRunAttachmentsAsync(IEnumerable attachments, string processingSettings, bool isLastBatch, bool collectMetrics, ITestRunAttachmentsProcessingEventsHandler eventsHandler, CancellationToken cancellationToken); + /// /// See . /// diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj index f625e66f36..e2952d93f7 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj @@ -37,7 +37,7 @@ - ResXFileCodeGenerator + PublicResXFileCodeGenerator Resources.Designer.cs diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.Designer.cs index b0401e605b..a218efe7f5 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.Designer.cs @@ -10,7 +10,6 @@ namespace Microsoft.VisualStudio.TestPlatform.VsTestConsole.TranslationLayer.Resources { using System; - using System.Reflection; /// @@ -20,7 +19,7 @@ namespace Microsoft.VisualStudio.TestPlatform.VsTestConsole.TranslationLayer.Res // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -40,7 +39,8 @@ internal Resources() { public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.TestPlatform.VsTestConsole.TranslationLayer.Resources.Resources", typeof(Resources).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.TestPlatform.VsTestConsole.TranslationLayer.Resources.Reso" + + "urces", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; @@ -60,7 +60,16 @@ internal Resources() { resourceCulture = value; } } - + + /// + /// Looks up a localized string similar to The active Test Run Attachments Processing was aborted.. + /// + public static string AbortedTestRunAttachmentsProcessing { + get { + return ResourceManager.GetString("AbortedTestRunAttachmentsProcessing", resourceCulture); + } + } + /// /// Looks up a localized string similar to The active Tests Discovery was aborted.. /// @@ -69,7 +78,7 @@ public static string AbortedTestsDiscovery { return ResourceManager.GetString("AbortedTestsDiscovery", resourceCulture); } } - + /// /// Looks up a localized string similar to The active Tests Run was aborted.. /// @@ -78,7 +87,7 @@ public static string AbortedTestsRun { return ResourceManager.GetString("AbortedTestsRun", resourceCulture); } } - + /// /// Looks up a localized string similar to Failed to receive message from vstest.console process. /// @@ -87,7 +96,7 @@ public static string FailedToReceiveMessage { return ResourceManager.GetString("FailedToReceiveMessage", resourceCulture); } } - + /// /// Looks up a localized string similar to File {0} does not exists. /// @@ -96,7 +105,7 @@ public static string InvalidFilePath { return ResourceManager.GetString("InvalidFilePath", resourceCulture); } } - + /// /// Looks up a localized string similar to vstest.console process exited abnormally. /// diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.resx b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.resx index a52549c171..eb053d0a74 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The active Test Run Attachments Processing was aborted. + The active Tests Discovery was aborted. diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.cs.xlf index ab7c034843..378546e5aa 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.cs.xlf @@ -61,6 +61,11 @@ Soubor {0} neexistuje. + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.de.xlf index 361f2a9f80..ead9e18cd3 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.de.xlf @@ -61,6 +61,11 @@ Die Datei "{0}" ist nicht vorhanden. + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.es.xlf index 5566e1cd03..704f3fc1d1 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.es.xlf @@ -61,6 +61,11 @@ El archivo {0} no existe + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.fr.xlf index 5bef4c27d2..788ee98fbb 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.fr.xlf @@ -61,6 +61,11 @@ Le fichier {0} n'existe pas + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.it.xlf index b1e559d790..471c34b6ce 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.it.xlf @@ -61,6 +61,11 @@ Il file {0} non esiste + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ja.xlf index 8b58d1d21c..9bbf726d85 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ja.xlf @@ -61,6 +61,11 @@ ファイル {0} が存在しません + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ko.xlf index 028bc0fa22..250d2e203d 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ko.xlf @@ -61,6 +61,11 @@ {0} 파일이 없습니다. + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.pl.xlf index 7c09aa494f..d9a8a303ef 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.pl.xlf @@ -61,6 +61,11 @@ Plik {0} nie istnieje + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.pt-BR.xlf index 475cfc8773..ac67cd51d7 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.pt-BR.xlf @@ -61,6 +61,11 @@ O arquivo {0} não existe + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ru.xlf index 980e35e1fc..40d4023b26 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.ru.xlf @@ -61,6 +61,11 @@ Файл {0} не существует. + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.tr.xlf index 2b9f819abc..f1817063cc 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.tr.xlf @@ -61,6 +61,11 @@ {0} dosyası yok + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.xlf index e63797562a..7c79b0d188 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.xlf @@ -23,6 +23,11 @@ File {0} does not exists + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.zh-Hans.xlf index d566b9b20c..35db394ca4 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.zh-Hans.xlf @@ -61,6 +61,11 @@ 文件 {0} 不存在 + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.zh-Hant.xlf index b420856a52..77b761a7ee 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/xlf/Resources.zh-Hant.xlf @@ -61,6 +61,11 @@ 檔案 {0} 不存在 + + The active Test Run Attachments Processing was aborted. + The active Test Run Attachments Processing was aborted. + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs index 838cbdcb4b..f61b47fdce 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleRequestSender.cs @@ -26,7 +26,7 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer /// /// VstestConsoleRequestSender for sending requests to Vstest.console.exe /// - internal class VsTestConsoleRequestSender : ITranslationLayerRequestSender, ITranslationLayerRequestSenderAsync + internal class VsTestConsoleRequestSender : ITranslationLayerRequestSender { private readonly ICommunicationManager communicationManager; @@ -383,6 +383,12 @@ public void EndSession() this.communicationManager.SendMessage(MessageType.SessionEnd); } + /// + public Task ProcessTestRunAttachmentsAsync(IEnumerable attachments, bool collectMetrics, ITestRunAttachmentsProcessingEventsHandler testSessionEventsHandler, CancellationToken cancellationToken) + { + return this.SendMessageAndListenAndReportAttachmentsProcessingResultAsync(attachments, collectMetrics, testSessionEventsHandler, cancellationToken); + } + /// /// Closes the communication channel /// @@ -725,6 +731,74 @@ private async Task SendMessageAndListenAndReportTestResultsAsync(string messageT this.testPlatformEventSource.TranslationLayerExecutionStop(); } + private async Task SendMessageAndListenAndReportAttachmentsProcessingResultAsync(IEnumerable attachments, bool collectMetrics, ITestRunAttachmentsProcessingEventsHandler eventHandler, CancellationToken cancellationToken) + { + try + { + var payload = new TestRunAttachmentsProcessingPayload + { + Attachments = attachments, + CollectMetrics = collectMetrics + }; + + this.communicationManager.SendMessage(MessageType.TestRunAttachmentsProcessingStart, payload); + var isTestRunAttachmentsProcessingComplete = false; + + using (cancellationToken.Register(() => this.communicationManager.SendMessage(MessageType.TestRunAttachmentsProcessingCancel))) + { + // Cycle through the messages that the vstest.console sends. + // Currently each of the operations are not separate tasks since they should not each take much time. + // This is just a notification. + while (!isTestRunAttachmentsProcessingComplete) + { + var message = await this.TryReceiveMessageAsync().ConfigureAwait(false); + + if (string.Equals(MessageType.TestRunAttachmentsProcessingComplete, message.MessageType)) + { + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("VsTestConsoleRequestSender.SendMessageAndListenAndReportAttachments: Process complete."); + } + + var testRunAttachmentsProcessingCompletePayload = this.dataSerializer.DeserializePayload(message); + + eventHandler.HandleTestRunAttachmentsProcessingComplete(testRunAttachmentsProcessingCompletePayload.AttachmentsProcessingCompleteEventArgs, testRunAttachmentsProcessingCompletePayload.Attachments); + isTestRunAttachmentsProcessingComplete = true; + } + else if (string.Equals(MessageType.TestRunAttachmentsProcessingProgress, message.MessageType)) + { + var testRunAttachmentsProcessingProgressPayload = this.dataSerializer.DeserializePayload(message); + eventHandler.HandleTestRunAttachmentsProcessingProgress(testRunAttachmentsProcessingProgressPayload.AttachmentsProcessingProgressEventArgs); + } + else if (string.Equals(MessageType.TestMessage, message.MessageType)) + { + var testMessagePayload = this.dataSerializer.DeserializePayload(message); + eventHandler.HandleLogMessage(testMessagePayload.MessageLevel, testMessagePayload.Message); + } + else + { + EqtTrace.Warning($"VsTestConsoleRequestSender.SendMessageAndListenAndReportAttachments: Unexpected message received {message.MessageType}."); + } + } + } + } + catch (Exception exception) + { + EqtTrace.Error("Aborting Test Session End Operation: {0}", exception); + eventHandler.HandleLogMessage(TestMessageLevel.Error, TranslationLayerResources.AbortedTestRunAttachmentsProcessing); + eventHandler.HandleTestRunAttachmentsProcessingComplete(new TestRunAttachmentsProcessingCompleteEventArgs(false, exception), null); + + // Earlier we were closing the connection with vstest.console in case of exceptions + // Removing that code because vstest.console might be in a healthy state and letting the client + // know of the error, so that the TL can wait for the next instruction from the client itself. + // Also, connection termination might not kill the process which could result in files being locked by testhost. + } + finally + { + this.testPlatformEventSource.TranslationLayerTestRunAttachmentsProcessingStop(); + } + } + private Message TryReceiveMessage() { Message message = null; diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs index 42f587d0f4..eb8780bdf6 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapper.cs @@ -7,7 +7,8 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer using System.Diagnostics; using System.Globalization; using System.Linq; - + using System.Threading; + using System.Threading.Tasks; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing; @@ -275,6 +276,148 @@ public void EndSession() #endregion + #region IVsTestConsoleWrapperAsync + + /// + public async Task StartSessionAsync() + { + EqtTrace.Info("VsTestConsoleWrapperAsync.StartSessionAsync: Starting VsTestConsoleWrapper session"); + + this.testPlatformEventSource.TranslationLayerInitializeStart(); + + var timeout = EnvironmentHelper.GetConnectionTimeout(); + // Start communication + var port = await this.requestSender.InitializeCommunicationAsync(timeout * 1000); + + if (port > 0) + { + // Fill the parameters + this.consoleParameters.ParentProcessId = Process.GetCurrentProcess().Id; + this.consoleParameters.PortNumber = port; + + // Start vstest.console.exe process + this.vstestConsoleProcessManager.StartProcess(this.consoleParameters); + } + else + { + // Close the sender as it failed to host server + this.requestSender.Close(); + throw new TransationLayerException("Error hosting communication channel and connecting to console"); + } + } + + /// + public async Task InitializeExtensionsAsync(IEnumerable pathToAdditionalExtensions) + { + await this.EnsureInitializedAsync(); + this.pathToAdditionalExtensions = pathToAdditionalExtensions.ToList(); + this.requestSender.InitializeExtensions(this.pathToAdditionalExtensions); + } + + /// + public async Task DiscoverTestsAsync(IEnumerable sources, string discoverySettings, ITestDiscoveryEventsHandler discoveryEventsHandler) + { + this.testPlatformEventSource.TranslationLayerDiscoveryStart(); + await this.EnsureInitializedAsync(); + + // Converts ITestDiscoveryEventsHandler to ITestDiscoveryEventsHandler2 + var discoveryCompleteEventsHandler2 = new DiscoveryEventsHandleConverter(discoveryEventsHandler); + await this.requestSender.DiscoverTestsAsync(sources, discoverySettings, options: null, discoveryEventsHandler: discoveryCompleteEventsHandler2); + } + + + /// + public async Task DiscoverTestsAsync(IEnumerable sources, string discoverySettings, TestPlatformOptions options, ITestDiscoveryEventsHandler2 discoveryEventsHandler) + { + this.testPlatformEventSource.TranslationLayerDiscoveryStart(); + await this.EnsureInitializedAsync(); + await this.requestSender.DiscoverTestsAsync(sources, discoverySettings, options, discoveryEventsHandler); + } + + /// + public async Task RunTestsAsync(IEnumerable sources, string runSettings, ITestRunEventsHandler testRunEventsHandler) + { + await RunTestsAsync(sources, runSettings, null, testRunEventsHandler); + } + + /// + public async Task RunTestsAsync(IEnumerable sources, string runSettings, TestPlatformOptions options, ITestRunEventsHandler testRunEventsHandler) + { + var sourceList = sources.ToList(); + this.testPlatformEventSource.TranslationLayerExecutionStart(0, sourceList.Count, 0, runSettings ?? string.Empty); + + await this.EnsureInitializedAsync(); + await this.requestSender.StartTestRunAsync(sourceList, runSettings, options, testRunEventsHandler); + } + + /// + public async Task RunTestsAsync(IEnumerable testCases, string runSettings, ITestRunEventsHandler testRunEventsHandler) + { + var testCaseList = testCases.ToList(); + this.testPlatformEventSource.TranslationLayerExecutionStart(0, 0, testCaseList.Count, runSettings ?? string.Empty); + + await this.EnsureInitializedAsync(); + await this.requestSender.StartTestRunAsync(testCaseList, runSettings, options: null, runEventsHandler: testRunEventsHandler); + } + + /// + public async Task RunTestsAsync(IEnumerable testCases, string runSettings, TestPlatformOptions options, ITestRunEventsHandler testRunEventsHandler) + { + var testCaseList = testCases.ToList(); + this.testPlatformEventSource.TranslationLayerExecutionStart(0, 0, testCaseList.Count, runSettings ?? string.Empty); + + await this.EnsureInitializedAsync(); + await this.requestSender.StartTestRunAsync(testCaseList, runSettings, options, testRunEventsHandler); + } + + /// + public async Task RunTestsWithCustomTestHostAsync(IEnumerable sources, string runSettings, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) + { + await RunTestsWithCustomTestHostAsync(sources, runSettings, null, testRunEventsHandler, customTestHostLauncher); + } + + /// + public async Task RunTestsWithCustomTestHostAsync(IEnumerable sources, string runSettings, TestPlatformOptions options, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) + { + var sourceList = sources.ToList(); + this.testPlatformEventSource.TranslationLayerExecutionStart(1, sourceList.Count, 0, runSettings ?? string.Empty); + + await this.EnsureInitializedAsync(); + await this.requestSender.StartTestRunWithCustomHostAsync(sourceList, runSettings, options, testRunEventsHandler, customTestHostLauncher); + } + + /// + public async Task RunTestsWithCustomTestHostAsync(IEnumerable testCases, string runSettings, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) + { + var testCaseList = testCases.ToList(); + this.testPlatformEventSource.TranslationLayerExecutionStart(1, 0, testCaseList.Count, runSettings ?? string.Empty); + + await this.EnsureInitializedAsync(); + await this.requestSender.StartTestRunWithCustomHostAsync(testCaseList, runSettings, options: null, runEventsHandler: testRunEventsHandler, customTestHostLauncher: customTestHostLauncher); + } + + /// + public async Task RunTestsWithCustomTestHostAsync(IEnumerable testCases, string runSettings, TestPlatformOptions options, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) + { + var testCaseList = testCases.ToList(); + this.testPlatformEventSource.TranslationLayerExecutionStart(1, 0, testCaseList.Count, runSettings ?? string.Empty); + + await this.EnsureInitializedAsync(); + await this.requestSender.StartTestRunWithCustomHostAsync(testCaseList, runSettings, options, testRunEventsHandler, customTestHostLauncher); + } + + /// + public async Task ProcessTestRunAttachmentsAsync(IEnumerable attachments, string processingSettings, bool isLastBatch, bool collectMetrics, ITestRunAttachmentsProcessingEventsHandler testSessionEventsHandler, CancellationToken cancellationToken) + { + this.testPlatformEventSource.TranslationLayerTestRunAttachmentsProcessingStart(); + + await this.EnsureInitializedAsync().ConfigureAwait(false); + await requestSender.ProcessTestRunAttachmentsAsync(attachments, collectMetrics, testSessionEventsHandler, cancellationToken).ConfigureAwait(false); + } + + #endregion + + private void EnsureInitialized() { if (!this.vstestConsoleProcessManager.IsProcessInitialized()) @@ -297,6 +440,18 @@ private void EnsureInitialized() } } + private async Task EnsureInitializedAsync() + { + if (!this.vstestConsoleProcessManager.IsProcessInitialized()) + { + EqtTrace.Info("VsTestConsoleWrapper.EnsureInitializedAsync: Process is not started."); + await this.StartSessionAsync(); + + EqtTrace.Info("VsTestConsoleWrapper.EnsureInitializedAsync: Send a request to initialize extensions."); + this.requestSender.InitializeExtensions(this.pathToAdditionalExtensions); + } + } + private bool WaitForConnection() { EqtTrace.Info("VsTestConsoleWrapper.WaitForConnection: Waiting for connection to command line runner."); diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapperAsync.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapperAsync.cs deleted file mode 100644 index ca0a357c75..0000000000 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/VsTestConsoleWrapperAsync.cs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer -{ - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Threading.Tasks; - - using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; - using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; - using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing; - using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; - - /// - /// An implementation of to invoke test operations - /// via the vstest.console test runner. - /// - public class VsTestConsoleWrapperAsync : IVsTestConsoleWrapperAsync - { - #region Private Members - - private readonly IProcessManager vstestConsoleProcessManager; - - private readonly ITranslationLayerRequestSenderAsync requestSender; - - /// - /// Path to additional extensions to reinitialize vstest.console - /// - private IEnumerable pathToAdditionalExtensions; - - /// - /// Additional parameters for vstest.console.exe - /// - private readonly ConsoleParameters consoleParameters; - - private readonly ITestPlatformEventSource testPlatformEventSource; - - #endregion - - #region Constructor - - /// - /// Initializes a new instance of the class. - /// - /// - /// Path to the test runner vstest.console.exe. - /// - public VsTestConsoleWrapperAsync(string vstestConsolePath) : - this(vstestConsolePath, ConsoleParameters.Default) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Path to the test runner vstest.console.exe. - /// The parameters to be passed onto the runner process - public VsTestConsoleWrapperAsync(string vstestConsolePath, ConsoleParameters consoleParameters) : - this(new VsTestConsoleRequestSender(), new VsTestConsoleProcessManager(vstestConsolePath), consoleParameters, TestPlatformEventSource.Instance) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Sender for test messages. - /// Process manager. - /// The parameters to be passed onto the runner process - /// Performance event source - internal VsTestConsoleWrapperAsync(ITranslationLayerRequestSenderAsync requestSender, IProcessManager processManager, ConsoleParameters consoleParameters, ITestPlatformEventSource testPlatformEventSource) - { - this.requestSender = requestSender; - this.vstestConsoleProcessManager = processManager; - this.consoleParameters = consoleParameters; - this.testPlatformEventSource = testPlatformEventSource; - this.pathToAdditionalExtensions = new List(); - - this.vstestConsoleProcessManager.ProcessExited += (sender, args) => this.requestSender.OnProcessExited(); - } - - #endregion - - #region IVsTestConsoleWrapper - - /// - public async Task StartSessionAsync() - { - EqtTrace.Info("VsTestConsoleWrapperAsync.StartSessionAsync: Starting VsTestConsoleWrapper session"); - - this.testPlatformEventSource.TranslationLayerInitializeStart(); - - var timeout = EnvironmentHelper.GetConnectionTimeout(); - // Start communication - var port = await this.requestSender.InitializeCommunicationAsync(timeout * 1000); - - if (port > 0) - { - // Fill the parameters - this.consoleParameters.ParentProcessId = Process.GetCurrentProcess().Id; - this.consoleParameters.PortNumber = port; - - // Start vstest.console.exe process - this.vstestConsoleProcessManager.StartProcess(this.consoleParameters); - } - else - { - // Close the sender as it failed to host server - this.requestSender.Close(); - throw new TransationLayerException("Error hosting communication channel and connecting to console"); - } - } - - /// - public async Task InitializeExtensionsAsync(IEnumerable pathToAdditionalExtensions) - { - await this.EnsureInitializedAsync(); - this.pathToAdditionalExtensions = pathToAdditionalExtensions.ToList(); - this.requestSender.InitializeExtensions(this.pathToAdditionalExtensions); - } - - /// - public async Task DiscoverTestsAsync(IEnumerable sources, string discoverySettings, ITestDiscoveryEventsHandler discoveryEventsHandler) - { - this.testPlatformEventSource.TranslationLayerDiscoveryStart(); - await this.EnsureInitializedAsync(); - - // Converts ITestDiscoveryEventsHandler to ITestDiscoveryEventsHandler2 - var discoveryCompleteEventsHandler2 = new DiscoveryEventsHandleConverter(discoveryEventsHandler); - await this.requestSender.DiscoverTestsAsync(sources, discoverySettings, options: null, discoveryEventsHandler: discoveryCompleteEventsHandler2); - } - - - /// - public async Task DiscoverTestsAsync(IEnumerable sources, string discoverySettings, TestPlatformOptions options, ITestDiscoveryEventsHandler2 discoveryEventsHandler) - { - this.testPlatformEventSource.TranslationLayerDiscoveryStart(); - await this.EnsureInitializedAsync(); - await this.requestSender.DiscoverTestsAsync(sources, discoverySettings, options, discoveryEventsHandler); - } - - /// - public void CancelDiscovery() - { - // TODO: Cancel Discovery - // this.requestSender.CancelDiscovery(); - } - - /// - public async Task RunTestsAsync(IEnumerable sources, string runSettings, ITestRunEventsHandler testRunEventsHandler) - { - await RunTestsAsync(sources, runSettings, null, testRunEventsHandler); - } - - /// - public async Task RunTestsAsync(IEnumerable sources, string runSettings, TestPlatformOptions options, ITestRunEventsHandler testRunEventsHandler) - { - var sourceList = sources.ToList(); - this.testPlatformEventSource.TranslationLayerExecutionStart(0, sourceList.Count, 0, runSettings ?? string.Empty); - - await this.EnsureInitializedAsync(); - await this.requestSender.StartTestRunAsync(sourceList, runSettings, options, testRunEventsHandler); - } - - /// - public async Task RunTestsAsync(IEnumerable testCases, string runSettings, ITestRunEventsHandler testRunEventsHandler) - { - var testCaseList = testCases.ToList(); - this.testPlatformEventSource.TranslationLayerExecutionStart(0, 0, testCaseList.Count, runSettings ?? string.Empty); - - await this.EnsureInitializedAsync(); - await this.requestSender.StartTestRunAsync(testCaseList, runSettings, options: null, runEventsHandler: testRunEventsHandler); - } - - /// - public async Task RunTestsAsync(IEnumerable testCases, string runSettings, TestPlatformOptions options, ITestRunEventsHandler testRunEventsHandler) - { - var testCaseList = testCases.ToList(); - this.testPlatformEventSource.TranslationLayerExecutionStart(0, 0, testCaseList.Count, runSettings ?? string.Empty); - - await this.EnsureInitializedAsync(); - await this.requestSender.StartTestRunAsync(testCaseList, runSettings, options, testRunEventsHandler); - } - - /// - public async Task RunTestsWithCustomTestHostAsync(IEnumerable sources, string runSettings, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) - { - await RunTestsWithCustomTestHostAsync(sources, runSettings, null, testRunEventsHandler, customTestHostLauncher); - } - - /// - public async Task RunTestsWithCustomTestHostAsync(IEnumerable sources, string runSettings, TestPlatformOptions options, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) - { - var sourceList = sources.ToList(); - this.testPlatformEventSource.TranslationLayerExecutionStart(1, sourceList.Count, 0, runSettings ?? string.Empty); - - await this.EnsureInitializedAsync(); - await this.requestSender.StartTestRunWithCustomHostAsync(sourceList, runSettings, options, testRunEventsHandler, customTestHostLauncher); - } - - /// - public async Task RunTestsWithCustomTestHostAsync(IEnumerable testCases, string runSettings, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) - { - var testCaseList = testCases.ToList(); - this.testPlatformEventSource.TranslationLayerExecutionStart(1, 0, testCaseList.Count, runSettings ?? string.Empty); - - await this.EnsureInitializedAsync(); - await this.requestSender.StartTestRunWithCustomHostAsync(testCaseList, runSettings, options: null, runEventsHandler: testRunEventsHandler, customTestHostLauncher: customTestHostLauncher); - } - - /// - public async Task RunTestsWithCustomTestHostAsync(IEnumerable testCases, string runSettings, TestPlatformOptions options, ITestRunEventsHandler testRunEventsHandler, ITestHostLauncher customTestHostLauncher) - { - var testCaseList = testCases.ToList(); - this.testPlatformEventSource.TranslationLayerExecutionStart(1, 0, testCaseList.Count, runSettings ?? string.Empty); - - await this.EnsureInitializedAsync(); - await this.requestSender.StartTestRunWithCustomHostAsync(testCaseList, runSettings, options, testRunEventsHandler, customTestHostLauncher); - } - - /// - public void CancelTestRun() - { - this.requestSender.CancelTestRun(); - } - - /// - public void AbortTestRun() - { - this.requestSender.AbortTestRun(); - } - - /// - public void EndSession() - { - this.requestSender.EndSession(); - this.requestSender.Close(); - } - - #endregion - - private async Task EnsureInitializedAsync() - { - if (!this.vstestConsoleProcessManager.IsProcessInitialized()) - { - EqtTrace.Info("VsTestConsoleWrapper.EnsureInitializedAsync: Process is not started."); - await this.StartSessionAsync(); - - EqtTrace.Info("VsTestConsoleWrapper.EnsureInitializedAsync: Send a request to initialize extensions."); - this.requestSender.InitializeExtensions(this.pathToAdditionalExtensions); - } - } - - } -} diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index 778006ebf5..3fc25a44e8 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -8,10 +8,12 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers using System.IO; using System.Linq; using System.Reflection; + using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.XPath; using Microsoft.VisualStudio.TestPlatform.Client; + using Microsoft.VisualStudio.TestPlatform.Client.TestRunAttachmentsProcessing; using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors.Utilities; @@ -24,9 +26,11 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestRunAttachmentsProcessing; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; @@ -49,6 +53,7 @@ internal class TestRequestManager : ITestRequestManager private readonly Task metricsPublisher; private bool isDisposed; private IProcessHelper processHelper; + private ITestRunAttachmentsProcessingManager attachmentsProcessingManager; /// /// Maintains the current active execution request @@ -62,6 +67,12 @@ internal class TestRequestManager : ITestRequestManager /// private IDiscoveryRequest currentDiscoveryRequest; + /// + /// Maintains the current active test run attachments processing cancellation token source + /// Assumption : There can only be one active attachments processing request. + /// + private CancellationTokenSource currentAttachmentsProcessingCancellationTokenSource; + #region Constructor public TestRequestManager() @@ -72,11 +83,12 @@ public TestRequestManager() TestPlatformEventSource.Instance, new InferHelper(AssemblyMetadataProvider.Instance), MetricsPublisherFactory.GetMetricsPublisher(IsTelemetryOptedIn(), CommandLineOptions.Instance.IsDesignMode), - new ProcessHelper()) + new ProcessHelper(), + new TestRunAttachmentsProcessingManager(TestPlatformEventSource.Instance, new CodeCoverageDataAttachmentsHandler())) { } - internal TestRequestManager(CommandLineOptions commandLineOptions, ITestPlatform testPlatform, TestRunResultAggregator testRunResultAggregator, ITestPlatformEventSource testPlatformEventSource, InferHelper inferHelper, Task metricsPublisher, IProcessHelper processHelper) + internal TestRequestManager(CommandLineOptions commandLineOptions, ITestPlatform testPlatform, TestRunResultAggregator testRunResultAggregator, ITestPlatformEventSource testPlatformEventSource, InferHelper inferHelper, Task metricsPublisher, IProcessHelper processHelper, ITestRunAttachmentsProcessingManager attachmentsProcessingManager) { this.testPlatform = testPlatform; this.commandLineOptions = commandLineOptions; @@ -85,6 +97,7 @@ internal TestRequestManager(CommandLineOptions commandLineOptions, ITestPlatform this.inferHelper = inferHelper; this.metricsPublisher = metricsPublisher; this.processHelper = processHelper; + this.attachmentsProcessingManager = attachmentsProcessingManager; } #endregion @@ -160,9 +173,7 @@ public void DiscoverTests(DiscoveryRequestPayload discoveryPayload, ITestDiscove // Collect Commands this.LogCommandsTelemetryPoints(requestData); } - - - + // create discovery request var criteria = new DiscoveryCriteria(discoveryPayload.Sources, batchSize, this.commandLineOptions.TestStatsEventTimeout, runsettings) { @@ -301,6 +312,45 @@ public void RunTests(TestRunRequestPayload testRunRequestPayload, ITestHostLaunc } } + /// + public void ProcessTestRunAttachments(TestRunAttachmentsProcessingPayload attachmentsProcessingPayload, ITestRunAttachmentsProcessingEventsHandler attachmentsProcessingEventsHandler, ProtocolConfig protocolConfig) + { + EqtTrace.Info("TestRequestManager.ProcessTestRunAttachments: Test run attachments processing started."); + + this.telemetryOptedIn = attachmentsProcessingPayload.CollectMetrics; + var requestData = this.GetRequestData(protocolConfig); + + // Make sure to run the run request inside a lock as the below section is not thread-safe + // There can be only one discovery, execution or attachments processing request at a given point in time + lock (this.syncObject) + { + try + { + EqtTrace.Info("TestRequestManager.ProcessTestRunAttachments: Synchronization context taken"); + this.testPlatformEventSource.TestRunAttachmentsProcessingRequestStart(); + + this.currentAttachmentsProcessingCancellationTokenSource = new CancellationTokenSource(); + + Task task = this.attachmentsProcessingManager.ProcessTestRunAttachmentsAsync(requestData, attachmentsProcessingPayload.Attachments, attachmentsProcessingEventsHandler, this.currentAttachmentsProcessingCancellationTokenSource.Token); + task.Wait(); + } + finally + { + if (this.currentAttachmentsProcessingCancellationTokenSource != null) + { + this.currentAttachmentsProcessingCancellationTokenSource.Dispose(); + this.currentAttachmentsProcessingCancellationTokenSource = null; + } + + EqtTrace.Info("TestRequestManager.ProcessTestRunAttachments: Test run attachments processing completed."); + this.testPlatformEventSource.TestRunAttachmentsProcessingRequestStop(); + + // Post the attachments processing complete event + this.metricsPublisher.Result.PublishMetrics(TelemetryDataConstants.TestAttachmentsProcessingCompleteEvent, requestData.MetricsCollection.Metrics); + } + } + } + private void LogTelemetryForLegacySettings(IRequestData requestData, string runsettings) { requestData.MetricsCollection.Add(TelemetryDataConstants.TestSettingsUsed, InferRunSettingsHelper.IsTestSettingsEnabled(runsettings)); @@ -342,6 +392,13 @@ public void AbortTestRun() this.currentTestRunRequest?.Abort(); } + /// + public void CancelTestRunAttachmentsProcessing() + { + EqtTrace.Info("TestRequestManager.CancelTestRunAttachmentsProcessing: Sending cancel request."); + this.currentAttachmentsProcessingCancellationTokenSource?.Cancel(); + } + #endregion public void Dispose() diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/CodeCoverageTests.cs b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/CodeCoverageTests.cs new file mode 100644 index 0000000000..81c1fe51ae --- /dev/null +++ b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/CodeCoverageTests.cs @@ -0,0 +1,443 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests +{ + using Castle.Core.Internal; + using Microsoft.TestPlatform.TestUtilities; + using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System.Xml; + + /// + /// The Test run attachments processing tests using VsTestConsoleWrapper API's + /// + [TestClass] + public class CodeCoverageTests : AcceptanceTestBase + { + /* + * Below value is just safe coverage result for which all tests are passing. + * Inspecting this value gives us confidence that there is no big drop in overall coverage. + */ + private const double ExpectedMinimalModuleCoverage = 30.0; + + private IVsTestConsoleWrapper vstestConsoleWrapper; + private RunEventHandler runEventHandler; + private TestRunAttachmentsProcessingEventHandler testRunAttachmentsProcessingEventHandler; + + private void Setup() + { + this.vstestConsoleWrapper = this.GetVsTestConsoleWrapper(); + this.runEventHandler = new RunEventHandler(); + this.testRunAttachmentsProcessingEventHandler = new TestRunAttachmentsProcessingEventHandler(); + } + + [TestCleanup] + public void Cleanup() + { + this.vstestConsoleWrapper?.EndSession(); + } + + [TestMethod] + [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] + public void TestRunWithCodeCoverage(RunnerInfo runnerInfo) + { + // arrange + AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + this.Setup(); + + // act + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies(), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + + // assert + Assert.AreEqual(6, this.runEventHandler.TestResults.Count); + + int expectedNumberOfAttachments = testEnvironment.RunnerFramework.Equals(IntegrationTestBase.CoreRunnerFramework) && + testEnvironment.TargetFramework.Equals(IntegrationTestBase.CoreRunnerFramework) ? 2 : 1; + Assert.AreEqual(expectedNumberOfAttachments, this.runEventHandler.Attachments.Count); + + AssertCoverageResults(this.runEventHandler.Attachments); + } + + [TestMethod] + [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] + public void TestRunWithCodeCoverageParallel(RunnerInfo runnerInfo) + { + // arrange + AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + this.Setup(); + + // act + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies(), this.GetCodeCoverageRunSettings(4), this.runEventHandler); + + // assert + Assert.AreEqual(6, this.runEventHandler.TestResults.Count); + Assert.AreEqual(testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework) ? 1 : 2, this.runEventHandler.Attachments.Count); + + AssertCoverageResults(this.runEventHandler.Attachments); + } + + [TestMethod] + [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] + public async Task TestRunWithCodeCoverageAndAttachmentsProcessing(RunnerInfo runnerInfo) + { + // arrange + AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + this.Setup(); + + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Take(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Skip(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + + Assert.AreEqual(6, this.runEventHandler.TestResults.Count); + Assert.AreEqual(2, this.runEventHandler.Attachments.Count); + + // act + await this.vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(runEventHandler.Attachments, null, true, true, testRunAttachmentsProcessingEventHandler, CancellationToken.None); + + // Assert + testRunAttachmentsProcessingEventHandler.EnsureSuccess(); + Assert.AreEqual(testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework) ? 1 : 2, this.testRunAttachmentsProcessingEventHandler.Attachments.Count); + + AssertCoverageResults(this.testRunAttachmentsProcessingEventHandler.Attachments); + + Assert.IsFalse(testRunAttachmentsProcessingEventHandler.CompleteArgs.IsCanceled); + Assert.IsNull(testRunAttachmentsProcessingEventHandler.CompleteArgs.Error); + + for (int i = 0; i < testRunAttachmentsProcessingEventHandler.ProgressArgs.Count; i++) + { + VisualStudio.TestPlatform.ObjectModel.Client.TestRunAttachmentsProcessingProgressEventArgs progressArgs = testRunAttachmentsProcessingEventHandler.ProgressArgs[i]; + Assert.AreEqual(1, progressArgs.CurrentAttachmentProcessorIndex); + Assert.AreEqual(1, progressArgs.CurrentAttachmentProcessorUris.Count); + Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", progressArgs.CurrentAttachmentProcessorUris.First().AbsoluteUri); + Assert.AreEqual(1, progressArgs.AttachmentProcessorsCount); + if (testRunAttachmentsProcessingEventHandler.ProgressArgs.Count == 2) + { + Assert.AreEqual(i == 0 ? 50 : 100, progressArgs.CurrentAttachmentProcessorProgress); + } + } + + Assert.AreEqual("Completed", testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics[TelemetryDataConstants.AttachmentsProcessingState]); + Assert.AreEqual(2L, testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics[TelemetryDataConstants.NumberOfAttachmentsSentForProcessing]); + Assert.AreEqual(testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework) ? 1L : 2L, testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics[TelemetryDataConstants.NumberOfAttachmentsAfterProcessing]); + Assert.IsTrue(testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics.ContainsKey(TelemetryDataConstants.TimeTakenInSecForAttachmentsProcessing)); + + Assert.IsTrue(File.Exists(runEventHandler.Attachments.First().Attachments.First().Uri.LocalPath)); + Assert.IsTrue(File.Exists(runEventHandler.Attachments.Last().Attachments.First().Uri.LocalPath) != testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework)); + } + + [TestMethod] + [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] + public async Task TestRunWithCodeCoverageAndAttachmentsProcessingNoMetrics(RunnerInfo runnerInfo) + { + // arrange + AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + this.Setup(); + + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Take(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Skip(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + + Assert.AreEqual(6, this.runEventHandler.TestResults.Count); + Assert.AreEqual(2, this.runEventHandler.Attachments.Count); + + // act + await this.vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(runEventHandler.Attachments, null, true, false, testRunAttachmentsProcessingEventHandler, CancellationToken.None); + + // Assert + testRunAttachmentsProcessingEventHandler.EnsureSuccess(); + Assert.AreEqual(testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework) ? 1 : 2, this.testRunAttachmentsProcessingEventHandler.Attachments.Count); + + AssertCoverageResults(this.testRunAttachmentsProcessingEventHandler.Attachments); + + Assert.IsFalse(testRunAttachmentsProcessingEventHandler.CompleteArgs.IsCanceled); + Assert.IsNull(testRunAttachmentsProcessingEventHandler.CompleteArgs.Error); + + for (int i = 0; i < testRunAttachmentsProcessingEventHandler.ProgressArgs.Count; i++) + { + VisualStudio.TestPlatform.ObjectModel.Client.TestRunAttachmentsProcessingProgressEventArgs progressArgs = testRunAttachmentsProcessingEventHandler.ProgressArgs[i]; + Assert.AreEqual(1, progressArgs.CurrentAttachmentProcessorIndex); + Assert.AreEqual(1, progressArgs.CurrentAttachmentProcessorUris.Count); + Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", progressArgs.CurrentAttachmentProcessorUris.First().AbsoluteUri); + Assert.AreEqual(1, progressArgs.AttachmentProcessorsCount); + if (testRunAttachmentsProcessingEventHandler.ProgressArgs.Count == 2) + { + Assert.AreEqual(i == 0 ? 50 : 100, progressArgs.CurrentAttachmentProcessorProgress); + } + } + + Assert.IsTrue(testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics.IsNullOrEmpty()); + + Assert.IsTrue(File.Exists(runEventHandler.Attachments.First().Attachments.First().Uri.LocalPath)); + Assert.IsTrue(File.Exists(runEventHandler.Attachments.Last().Attachments.First().Uri.LocalPath) != testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework)); + } + + [TestMethod] + [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] + public async Task TestRunWithCodeCoverageAndAttachmentsProcessingModuleDuplicated(RunnerInfo runnerInfo) + { + // arrange + AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + this.Setup(); + + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Take(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Skip(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Skip(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + + Assert.AreEqual(9, this.runEventHandler.TestResults.Count); + Assert.AreEqual(3, this.runEventHandler.Attachments.Count); + + // act + await this.vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(runEventHandler.Attachments, null, true, true, testRunAttachmentsProcessingEventHandler, CancellationToken.None); + + // Assert + testRunAttachmentsProcessingEventHandler.EnsureSuccess(); + Assert.AreEqual(testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework) ? 1 : 3, this.testRunAttachmentsProcessingEventHandler.Attachments.Count); + + AssertCoverageResults(this.testRunAttachmentsProcessingEventHandler.Attachments); + + Assert.IsFalse(testRunAttachmentsProcessingEventHandler.CompleteArgs.IsCanceled); + Assert.IsNull(testRunAttachmentsProcessingEventHandler.CompleteArgs.Error); + + for (int i = 0; i < testRunAttachmentsProcessingEventHandler.ProgressArgs.Count; i++) + { + VisualStudio.TestPlatform.ObjectModel.Client.TestRunAttachmentsProcessingProgressEventArgs progressArgs = testRunAttachmentsProcessingEventHandler.ProgressArgs[i]; + Assert.AreEqual(1, progressArgs.CurrentAttachmentProcessorIndex); + Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", progressArgs.CurrentAttachmentProcessorUris.First().AbsoluteUri); + Assert.AreEqual(1, progressArgs.AttachmentProcessorsCount); + + if (testRunAttachmentsProcessingEventHandler.ProgressArgs.Count == 3) + { + Assert.AreEqual(i == 0 ? 33 : i == 1 ? 66 : 100, progressArgs.CurrentAttachmentProcessorProgress); + } + } + + Assert.AreEqual("Completed", testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics[TelemetryDataConstants.AttachmentsProcessingState]); + Assert.AreEqual(3L, testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics[TelemetryDataConstants.NumberOfAttachmentsSentForProcessing]); + Assert.AreEqual(testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework) ? 1L : 3L, testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics[TelemetryDataConstants.NumberOfAttachmentsAfterProcessing]); + Assert.IsTrue(testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics.ContainsKey(TelemetryDataConstants.TimeTakenInSecForAttachmentsProcessing)); + + Assert.IsTrue(File.Exists(runEventHandler.Attachments.First().Attachments.First().Uri.LocalPath)); + Assert.IsTrue(File.Exists(runEventHandler.Attachments.Last().Attachments.First().Uri.LocalPath) != testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework)); + } + + [TestMethod] + [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] + public async Task TestRunWithCodeCoverageAndAttachmentsProcessingCancelled(RunnerInfo runnerInfo) + { + // arrange + AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + this.Setup(); + + if (!testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework)) return; + + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Take(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + Assert.AreEqual(3, this.runEventHandler.TestResults.Count); + Assert.AreEqual(1, this.runEventHandler.Attachments.Count); + + List attachments = Enumerable.Range(0, 1000).Select(i => this.runEventHandler.Attachments.First()).ToList(); + + CancellationTokenSource cts = new CancellationTokenSource(); + + Task attachmentsProcessing = this.vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(attachments, null, true, true, testRunAttachmentsProcessingEventHandler, cts.Token); + + while (true) + { + try + { + if (testRunAttachmentsProcessingEventHandler.ProgressArgs.Count >= 3) + break; + } + catch + { + // ignore + } + await Task.Delay(100); + } + + // act + cts.Cancel(); + + // Assert + await attachmentsProcessing; + testRunAttachmentsProcessingEventHandler.EnsureSuccess(); + + Assert.AreEqual(1000, this.testRunAttachmentsProcessingEventHandler.Attachments.Count); + + Assert.IsTrue(testRunAttachmentsProcessingEventHandler.CompleteArgs.IsCanceled); + Assert.IsNull(testRunAttachmentsProcessingEventHandler.CompleteArgs.Error); + + Assert.IsTrue((testEnvironment.RunnerFramework.Equals(IntegrationTestBase.DesktopRunnerFramework) ? 3 : 0) <= testRunAttachmentsProcessingEventHandler.ProgressArgs.Count); + for (int i = 0; i < testRunAttachmentsProcessingEventHandler.ProgressArgs.Count; i++) + { + VisualStudio.TestPlatform.ObjectModel.Client.TestRunAttachmentsProcessingProgressEventArgs progressArgs = testRunAttachmentsProcessingEventHandler.ProgressArgs[i]; + Assert.AreEqual(1, progressArgs.CurrentAttachmentProcessorIndex); + Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", progressArgs.CurrentAttachmentProcessorUris.First().AbsoluteUri); + Assert.AreEqual(1, progressArgs.AttachmentProcessorsCount); + + if (i == 0) + { + Assert.AreEqual(0, progressArgs.CurrentAttachmentProcessorProgress); + } + } + + Assert.AreEqual("Canceled", testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics[TelemetryDataConstants.AttachmentsProcessingState]); + Assert.AreEqual(1000L, testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics[TelemetryDataConstants.NumberOfAttachmentsSentForProcessing]); + Assert.AreEqual(1000L, testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics[TelemetryDataConstants.NumberOfAttachmentsAfterProcessing]); + Assert.IsTrue(testRunAttachmentsProcessingEventHandler.CompleteArgs.Metrics.ContainsKey(TelemetryDataConstants.TimeTakenInSecForAttachmentsProcessing)); + + Assert.IsTrue(File.Exists(runEventHandler.Attachments.First().Attachments.First().Uri.LocalPath)); + } + + [TestMethod] + [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] + public async Task EndSessionShouldEnsureVstestConsoleProcessDies(RunnerInfo runnerInfo) + { + var numOfProcesses = Process.GetProcessesByName("vstest.console").Length; + + AcceptanceTestBase.SetTestEnvironment(this.testEnvironment, runnerInfo); + this.Setup(); + + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Take(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + this.vstestConsoleWrapper.RunTests(this.GetTestAssemblies().Skip(1), this.GetCodeCoverageRunSettings(1), this.runEventHandler); + + Assert.AreEqual(6, this.runEventHandler.TestResults.Count); + Assert.AreEqual(2, this.runEventHandler.Attachments.Count); + + await this.vstestConsoleWrapper.ProcessTestRunAttachmentsAsync(runEventHandler.Attachments, null, true, true, testRunAttachmentsProcessingEventHandler, CancellationToken.None); + + // act + this.vstestConsoleWrapper?.EndSession(); + + // Assert + Assert.AreEqual(numOfProcesses, Process.GetProcessesByName("vstest.console").Length); + + this.vstestConsoleWrapper = null; + } + + private IList GetTestAssemblies() + { + return GetProjects().Select(p => this.GetAssetFullPath(p)).ToList(); + } + + private IList GetProjects() + { + return new List { "SimpleTestProject.dll", "SimpleTestProject2.dll" }; + } + + /// + /// Default RunSettings + /// + /// + public string GetCodeCoverageRunSettings(int cpuCount) + { + string runSettingsXml = $@" + + + {FrameworkArgValue} + {GetCodeCoveragePath()} + {cpuCount} + + + + + + + + + .*CPPUnitTestFramework.* + + + + + True + True + True + False + + + + + + "; + return runSettingsXml; + } + + private void AssertCoverageResults(IList attachments) + { + if (attachments.Count == 1) + { + var xmlCoverage = GetXmlCoverage(attachments.First()); + + foreach (var project in GetProjects()) + { + var moduleNode = GetModuleNode(xmlCoverage.DocumentElement, project.ToLower()); + AssertCoverage(moduleNode, ExpectedMinimalModuleCoverage); + } + } + } + + private XmlDocument GetXmlCoverage(AttachmentSet attachment) + { + string output = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".xml"); + + var analyze = Process.Start(new ProcessStartInfo + { + FileName = GetCodeCoverageExePath(), + Arguments = $"analyze /include_skipped_functions /include_skipped_modules /output:\"{output}\" \"{attachment.Attachments.First().Uri.LocalPath}\"", + RedirectStandardOutput = true, + UseShellExecute = false + }); + + string analysisOutput = analyze.StandardOutput.ReadToEnd(); + + analyze.WaitForExit(); + Assert.IsTrue(0 == analyze.ExitCode, $"Code Coverage analyze failed: {analysisOutput}"); + + XmlDocument coverage = new XmlDocument(); + coverage.Load(output); + return coverage; + } + + private string GetCodeCoveragePath() + { + return Path.Combine(IntegrationTestEnvironment.TestPlatformRootDirectory, "artifacts", IntegrationTestEnvironment.BuildConfiguration, "Microsoft.CodeCoverage"); + } + + private string GetCodeCoverageExePath() + { + return Path.Combine(GetCodeCoveragePath(), "CodeCoverage", "CodeCoverage.exe"); + } + + private XmlNode GetModuleNode(XmlNode node, string name) + { + return GetNode(node, "module", name); + } + + private XmlNode GetNode(XmlNode node, string type, string name) + { + return node.SelectSingleNode($"//{type}[@name='{name}']"); + } + + private void AssertCoverage(XmlNode node, double expectedCoverage) + { + var coverage = double.Parse(node.Attributes["block_coverage"].Value); + Console.WriteLine($"Checking coverage for {node.Name} {node.Attributes["name"].Value}. Expected at least: {expectedCoverage}. Result: {coverage}"); + Assert.IsTrue(coverage > expectedCoverage, $"Coverage check failed for {node.Name} {node.Attributes["name"].Value}. Expected at least: {expectedCoverage}. Found: {coverage}"); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/EventHandler/RunEventHandler.cs b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/EventHandler/RunEventHandler.cs index f3e1793d30..c4bd80e385 100644 --- a/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/EventHandler/RunEventHandler.cs +++ b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/EventHandler/RunEventHandler.cs @@ -18,6 +18,11 @@ public class RunEventHandler : ITestRunEventsHandler2 /// public List TestResults { get; private set; } + /// + /// Gets the attachments. + /// + public List Attachments { get; private set; } + /// /// Gets the metrics. /// @@ -39,6 +44,7 @@ public RunEventHandler() { this.TestResults = new List(); this.Errors = new List(); + this.Attachments = new List(); } public void EnsureSuccess() @@ -69,6 +75,11 @@ public void HandleTestRunComplete( this.TestResults.AddRange(lastChunkArgs.NewTestResults); } + if (testRunCompleteArgs.AttachmentSets != null) + { + this.Attachments.AddRange(testRunCompleteArgs.AttachmentSets); + } + this.Metrics = testRunCompleteArgs.Metrics; } diff --git a/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs new file mode 100644 index 0000000000..8a0b6513c6 --- /dev/null +++ b/test/Microsoft.TestPlatform.AcceptanceTests/TranslationLayerTests/EventHandler/TestRunAttachmentsProcessingEventHandler.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.AcceptanceTests.TranslationLayerTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + /// + public class TestRunAttachmentsProcessingEventHandler : ITestRunAttachmentsProcessingEventsHandler + { + public List Attachments { get; private set; } + + public TestRunAttachmentsProcessingCompleteEventArgs CompleteArgs { get; private set; } + + public List ProgressArgs { get; private set; } + + /// + /// Gets the log message. + /// + public string LogMessage { get; private set; } + + public List Errors { get; set; } + + /// + /// Gets the test message level. + /// + public TestMessageLevel TestMessageLevel { get; private set; } + + public TestRunAttachmentsProcessingEventHandler() + { + this.Errors = new List(); + this.Attachments = new List(); + this.ProgressArgs = new List(); + } + + public void EnsureSuccess() + { + if (this.Errors.Any()) + { + throw new InvalidOperationException($"Test run reported errors:{Environment.NewLine}{string.Join(Environment.NewLine + Environment.NewLine, this.Errors)}"); + } + } + + public void HandleLogMessage(TestMessageLevel level, string message) + { + this.LogMessage = message; + this.TestMessageLevel = level; + if (level == TestMessageLevel.Error) + { + this.Errors.Add(message); + } + } + + public void HandleRawMessage(string rawMessage) + { + // No op + } + + public int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo) + { + // No op + return -1; + } + + public bool AttachDebuggerToProcess(int pid) + { + // No op + return true; + } + + public void HandleTestRunAttachmentsProcessingComplete(ICollection attachments) + { + if(attachments != null) + { + this.Attachments.AddRange(attachments); + } + } + + public void HandleTestRunAttachmentsProcessingComplete(TestRunAttachmentsProcessingCompleteEventArgs attachmentsProcessingCompleteEventArgs, IEnumerable lastChunk) + { + if (lastChunk != null) + { + this.Attachments.AddRange(lastChunk); + } + + if (attachmentsProcessingCompleteEventArgs.Error != null) + { + Errors.Add(attachmentsProcessingCompleteEventArgs.Error.Message); + } + + CompleteArgs = attachmentsProcessingCompleteEventArgs; + } + + public void HandleProcessedAttachmentsChunk(IEnumerable attachments) + { + throw new NotImplementedException(); + } + + public void HandleTestRunAttachmentsProcessingProgress(TestRunAttachmentsProcessingProgressEventArgs AttachmentsProcessingProgressEventArgs) + { + ProgressArgs.Add(AttachmentsProcessingProgressEventArgs); + } + } +} diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingEventsHandlerTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingEventsHandlerTests.cs new file mode 100644 index 0000000000..2988e594c6 --- /dev/null +++ b/test/Microsoft.TestPlatform.Client.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingEventsHandlerTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +namespace Microsoft.TestPlatform.Client.UnitTests.TestRunAttachmentsProcessing +{ + using Microsoft.VisualStudio.TestPlatform.Client.TestRunAttachmentsProcessing; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Moq; + + [TestClass] + public class TestRunAttachmentsProcessingEventsHandlerTests + { + private readonly Mock mockCommunicationManager; + private readonly ITestRunAttachmentsProcessingEventsHandler handler; + + public TestRunAttachmentsProcessingEventsHandlerTests() + { + this.mockCommunicationManager = new Mock(); + this.handler = new TestRunAttachmentsProcessingEventsHandler(mockCommunicationManager.Object); + } + + [TestMethod] + public void EventsHandlerHandleLogMessageShouldSendTestMessage() + { + string message = "error message"; + + handler.HandleLogMessage(TestMessageLevel.Error, message); + + mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestMessage, It.Is(p => p.MessageLevel == TestMessageLevel.Error && p.Message == message))); + } + + [TestMethod] + public void EventsHandlerHandleTestRunAttachmentsProcessingCompleteShouldSendAttachmentsProcessingCompleteMessage() + { + var attachments = new[] { new AttachmentSet(new System.Uri("http://www.bing.com/"), "code coverage") }; + var args = new TestRunAttachmentsProcessingCompleteEventArgs(false, null); + + handler.HandleTestRunAttachmentsProcessingComplete(args, attachments); + + mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestRunAttachmentsProcessingComplete, It.Is(p => p.Attachments == attachments && p.AttachmentsProcessingCompleteEventArgs == args))); + } + + [TestMethod] + public void EventsHandlerHandleTestRunAttachmentsProcessingProgressShouldSendAttachmentsProcessingProgressMessage() + { + var args = new TestRunAttachmentsProcessingProgressEventArgs(1, new[] { new System.Uri("http://www.bing.com/") }, 90, 2); + + handler.HandleTestRunAttachmentsProcessingProgress(args); + + mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestRunAttachmentsProcessingProgress, It.Is(p => p.AttachmentsProcessingProgressEventArgs == args))); + } + + [TestMethod] + public void EventsHandlerHandleRawMessageShouldDoNothing() + { + handler.HandleRawMessage("any"); + + mockCommunicationManager.Verify(cm => cm.SendMessage(It.IsAny()), Times.Never); + mockCommunicationManager.Verify(cm => cm.SendMessage(It.IsAny(), It.IsAny()), Times.Never); + } + } +} diff --git a/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs index 98befe71f8..1066cc4f3d 100644 --- a/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs +++ b/test/Microsoft.TestPlatform.Client.UnitTests/DesignMode/DesignModeClientTests.cs @@ -9,6 +9,7 @@ namespace Microsoft.VisualStudio.TestPlatform.Client.UnitTests.DesignMode using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.Client.DesignMode; + using Microsoft.VisualStudio.TestPlatform.Client.TestRunAttachmentsProcessing; using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; @@ -405,6 +406,92 @@ public void DesignModeClientConnectShouldSendTestMessageAndDiscoverCompleteOnTes this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.DiscoveryComplete, It.IsAny()), Times.Once()); } + [TestMethod] + public void DesignModeClientConnectShouldSendTestMessageAndAttachmentsProcessingCompleteOnExceptionInAttachmentsProcessing() + { + var payload = new TestRunAttachmentsProcessingPayload(); + var startAttachmentsProcessing = new Message { MessageType = MessageType.TestRunAttachmentsProcessingStart, Payload = JToken.FromObject(payload) }; + this.mockCommunicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(true); + this.mockCommunicationManager.SetupSequence(cm => cm.ReceiveMessage()).Returns(startAttachmentsProcessing); + this.mockCommunicationManager + .Setup(cm => cm.SendMessage(MessageType.TestRunAttachmentsProcessingComplete, It.IsAny())) + .Callback(() => complateEvent.Set()); + this.mockTestRequestManager.Setup( + rm => rm.ProcessTestRunAttachments( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Throws(new Exception()); + + this.designModeClient.ConnectToClientAndProcessRequests(PortNumber, this.mockTestRequestManager.Object); + + Assert.IsTrue(this.complateEvent.WaitOne(Timeout), "AttachmentsProcessing not completed."); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestMessage, It.IsAny()), Times.Once()); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestRunAttachmentsProcessingComplete, It.Is(p => p.Attachments == null)), Times.Once()); + } + + [TestMethod] + public void DesignModeClientConnectShouldSendTestMessageAndDiscoverCompleteOnTestPlatformExceptionInAttachmentsProcessing() + { + var payload = new TestRunAttachmentsProcessingPayload(); + var startAttachmentsProcessing = new Message { MessageType = MessageType.TestRunAttachmentsProcessingStart, Payload = JToken.FromObject(payload) }; + this.mockCommunicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(true); + this.mockCommunicationManager.SetupSequence(cm => cm.ReceiveMessage()).Returns(startAttachmentsProcessing); + this.mockCommunicationManager + .Setup(cm => cm.SendMessage(MessageType.TestRunAttachmentsProcessingComplete, It.IsAny())) + .Callback(() => complateEvent.Set()); + this.mockTestRequestManager.Setup( + rm => rm.ProcessTestRunAttachments( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Throws(new TestPlatformException("Hello world")); + + this.designModeClient.ConnectToClientAndProcessRequests(PortNumber, this.mockTestRequestManager.Object); + + Assert.IsTrue(this.complateEvent.WaitOne(Timeout), "AttachmentsProcessing not completed."); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestMessage, It.IsAny()), Times.Once()); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestRunAttachmentsProcessingComplete, It.Is(p => p.Attachments == null)), Times.Once()); + } + + [TestMethod] + public void DesignModeClientConnectShouldCallRequestManagerForAttachmentsProcessingStart() + { + var payload = new TestRunAttachmentsProcessingPayload(); + var startAttachmentsProcessing = new Message { MessageType = MessageType.TestRunAttachmentsProcessingStart, Payload = JToken.FromObject(payload) }; + this.mockCommunicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(true); + this.mockCommunicationManager.SetupSequence(cm => cm.ReceiveMessage()).Returns(startAttachmentsProcessing); + + this.mockTestRequestManager + .Setup( + rm => rm.ProcessTestRunAttachments( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Callback(() => complateEvent.Set()); + + this.designModeClient.ConnectToClientAndProcessRequests(PortNumber, this.mockTestRequestManager.Object); + + Assert.IsTrue(this.complateEvent.WaitOne(Timeout), "AttachmentsProcessing not completed."); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestMessage, It.IsAny()), Times.Never); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestRunAttachmentsProcessingComplete, It.IsAny()), Times.Never); + this.mockTestRequestManager.Verify(rm => rm.ProcessTestRunAttachments(It.IsAny(), It.IsAny(), It.IsAny())); + } + + [TestMethod] + public void DesignModeClientConnectShouldCallRequestManagerForAttachmentsProcessingCancel() + { + var cancelAttachmentsProcessing = new Message { MessageType = MessageType.TestRunAttachmentsProcessingCancel }; + this.mockCommunicationManager.Setup(cm => cm.WaitForServerConnection(It.IsAny())).Returns(true); + this.mockCommunicationManager.SetupSequence(cm => cm.ReceiveMessage()).Returns(cancelAttachmentsProcessing); + + this.designModeClient.ConnectToClientAndProcessRequests(PortNumber, this.mockTestRequestManager.Object); + + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestMessage, It.IsAny()), Times.Never); + this.mockCommunicationManager.Verify(cm => cm.SendMessage(MessageType.TestRunAttachmentsProcessingComplete, It.IsAny()), Times.Never); + this.mockTestRequestManager.Verify(rm => rm.CancelTestRunAttachmentsProcessing()); + } + [TestMethod] public void DesignModeClientConnectShouldSendTestMessageAndExecutionCompleteOnExceptionInTestRun() { diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingManagerTests.cs new file mode 100644 index 0000000000..a51adcc7d6 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/AttachmentsProcessing/TestRunAttachmentsProcessingManagerTests.cs @@ -0,0 +1,625 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.CrossPlatEngine.UnitTests.TestRunAttachmentsProcessing +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; + using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestRunAttachmentsProcessing; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + + [TestClass] + public class TestRunAttachmentsProcessingManagerTests + { + private const string uri1 = "datacollector://microsoft/some1/1.0"; + private const string uri2 = "datacollector://microsoft/some2/2.0"; + private const string uri3 = "datacollector://microsoft/some3/2.0"; + + private readonly Mock mockRequestData; + private readonly Mock mockMetricsCollection; + private readonly Mock mockEventSource; + private readonly Mock mockAttachmentHandler1; + private readonly Mock mockAttachmentHandler2; + private readonly Mock mockEventsHandler; + private readonly TestRunAttachmentsProcessingManager manager; + private readonly CancellationTokenSource cancellationTokenSource; + + public TestRunAttachmentsProcessingManagerTests() + { + mockRequestData = new Mock(); + mockMetricsCollection = new Mock(); + mockRequestData.Setup(r => r.MetricsCollection).Returns(mockMetricsCollection.Object); + + mockEventSource = new Mock(); + mockAttachmentHandler1 = new Mock(); + mockAttachmentHandler2 = new Mock(); + mockEventsHandler = new Mock(); + + mockAttachmentHandler1.Setup(h => h.GetExtensionUris()).Returns(new[] { new Uri(uri1) }); + mockAttachmentHandler2.Setup(h => h.GetExtensionUris()).Returns(new[] { new Uri(uri2) }); + + manager = new TestRunAttachmentsProcessingManager(mockEventSource.Object, mockAttachmentHandler1.Object, mockAttachmentHandler2.Object); + + cancellationTokenSource = new CancellationTokenSource(); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachmentsThroughEventsHandler_IfNoAttachmentsOnInput() + { + // arrange + List inputAttachments = new List(); + + // act + await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, mockEventsHandler.Object, cancellationTokenSource.Token); + + // assert + VerifyCompleteEvent(false, false); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingComplete(It.Is(a => !a.IsCanceled), It.Is>(c => c.Count == 0))); + mockEventsHandler.Verify(h => h.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.IsAny()), Times.Never); + mockEventSource.Verify(s => s.TestRunAttachmentsProcessingStart(0)); + mockEventSource.Verify(s => s.TestRunAttachmentsProcessingStop(0)); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 0, outputCount: 0); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnNoAttachments_IfNoAttachmentsOnInput() + { + // arrange + List inputAttachments = new List(); + + // act + var result = await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, cancellationTokenSource.Token); + + // assert + Assert.AreEqual(0, result.Count); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 0, outputCount: 0); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturn1NotProcessedAttachmentThroughEventsHandler_If1NotRelatedAttachmentOnInput() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri3), "uri3_input") + }; + + // act + await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, mockEventsHandler.Object, cancellationTokenSource.Token); + + // assert + VerifyCompleteEvent(false, false, inputAttachments[0]); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.IsAny()), Times.Never); + mockEventsHandler.Verify(h => h.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturn1NotProcessedAttachment_If1NotRelatedAttachmentOnInput() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri3), "uri3_input") + }; + + // act + var result = await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, cancellationTokenSource.Token); + + // assert + Assert.AreEqual(1, result.Count); + Assert.IsTrue(result.Contains(inputAttachments[0])); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturn1ProcessedAttachmentThroughEventsHandler_IfRelatedAttachmentOnInput() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input") + }; + + List outputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_output") + }; + + mockAttachmentHandler1.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(outputAttachments); + + // act + await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, mockEventsHandler.Object, cancellationTokenSource.Token); + + // assert + VerifyCompleteEvent(false, false, outputAttachments[0]); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.IsAny()), Times.Never); + mockEventsHandler.Verify(h => h.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[0])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturn1ProcessedAttachment_IfRelatedAttachmentOnInput() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input") + }; + + List outputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_output") + }; + + mockAttachmentHandler1.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(outputAttachments); + + // act + var result = await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, cancellationTokenSource.Token); + + // assert + Assert.AreEqual(1, result.Count); + Assert.IsTrue(result.Contains(outputAttachments[0])); + mockEventSource.Verify(s => s.TestRunAttachmentsProcessingStart(1)); + mockEventSource.Verify(s => s.TestRunAttachmentsProcessingStop(1)); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[0])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachmentsThroughEventsHandler_IfRelatedAttachmentOnInputButHandlerThrowsException() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input") + }; + + mockAttachmentHandler1.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).Throws(new Exception("exception message")); + + // act + await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, mockEventsHandler.Object, cancellationTokenSource.Token); + + // assert + VerifyCompleteEvent(false, true, inputAttachments[0]); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.IsAny()), Times.Never); + mockEventsHandler.Verify(h => h.HandleLogMessage(TestMessageLevel.Error, "exception message"), Times.Once); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[0])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1, status: "Failed"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachments_IfRelatedAttachmentOnInputButHandlerThrowsException() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input") + }; + + mockAttachmentHandler1.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).Throws(new Exception("exception message")); + + // act + var result = await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, cancellationTokenSource.Token); + + // assert + Assert.AreEqual(1, result.Count); + Assert.IsTrue(result.Contains(inputAttachments[0])); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[0])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1, status: "Failed"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachmentsThroughEventsHandler_IfOperationIsCancelled() + { + // arrange + cancellationTokenSource.Cancel(); + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input") + }; + + // act + await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, mockEventsHandler.Object, cancellationTokenSource.Token); + + // assert + VerifyCompleteEvent(true, false, inputAttachments[0]); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.IsAny()), Times.Never); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1, status: "Canceled"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachments_IfOperationIsCancelled() + { + // arrange + cancellationTokenSource.Cancel(); + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input") + }; + + // act + var result = await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, cancellationTokenSource.Token); + + // assert + Assert.AreEqual(1, result.Count); + Assert.IsTrue(result.Contains(inputAttachments[0])); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1, status: "Canceled"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnProcessedAttachmentsThroughEventsHandler_IfRelatedAttachmentsOnInput() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input1"), + new AttachmentSet(new Uri(uri1), "uri1_input2"), + new AttachmentSet(new Uri(uri2), "uri2_input1"), + new AttachmentSet(new Uri(uri2), "uri2_input2"), + new AttachmentSet(new Uri(uri3), "uri3_input1"), + }; + + List outputAttachmentsForHandler1 = new List + { + new AttachmentSet(new Uri(uri1), "uri1_output") + }; + + List outputAttachmentsForHandler2 = new List + { + new AttachmentSet(new Uri(uri2), "uri2_output") + }; + + mockAttachmentHandler1.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(outputAttachmentsForHandler1); + mockAttachmentHandler2.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(outputAttachmentsForHandler2); + + // act + await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, mockEventsHandler.Object, cancellationTokenSource.Token); + + // assert + VerifyCompleteEvent(false, false, inputAttachments[4], outputAttachmentsForHandler1.First(), outputAttachmentsForHandler2.First()); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.IsAny()), Times.Never); + mockEventsHandler.Verify(h => h.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 2 && c.Contains(inputAttachments[0]) && c.Contains(inputAttachments[1])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 2 && c.Contains(inputAttachments[2]) && c.Contains(inputAttachments[3])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + + VerifyMetrics(inputCount: 5, outputCount: 3); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnProcessedAttachments_IfRelatedAttachmentsOnInput() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input1"), + new AttachmentSet(new Uri(uri1), "uri1_input2"), + new AttachmentSet(new Uri(uri2), "uri2_input1"), + new AttachmentSet(new Uri(uri2), "uri2_input2"), + new AttachmentSet(new Uri(uri3), "uri3_input1"), + }; + + List outputAttachmentsForHandler1 = new List + { + new AttachmentSet(new Uri(uri1), "uri1_output") + }; + + List outputAttachmentsForHandler2 = new List + { + new AttachmentSet(new Uri(uri2), "uri2_output") + }; + + mockAttachmentHandler1.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(outputAttachmentsForHandler1); + mockAttachmentHandler2.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).ReturnsAsync(outputAttachmentsForHandler2); + + // act + var result = await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, cancellationTokenSource.Token); + + // assert + Assert.AreEqual(3, result.Count); + Assert.IsTrue(result.Contains(inputAttachments[4])); + Assert.IsTrue(result.Contains(outputAttachmentsForHandler1[0])); + Assert.IsTrue(result.Contains(outputAttachmentsForHandler2[0])); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 2 && c.Contains(inputAttachments[0]) && c.Contains(inputAttachments[1])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 2 && c.Contains(inputAttachments[2]) && c.Contains(inputAttachments[3])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + + VerifyMetrics(inputCount: 5, outputCount: 3); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachmentsThroughEventsHandler_IfOperationCancelled() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input") + }; + + ICollection outputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_output") + }; + + var innerTaskCompletionSource = new TaskCompletionSource(); + + mockAttachmentHandler1.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).Returns((ICollection i1, IProgress progress, IMessageLogger logger, CancellationToken cancellation) => + { + try + { + for (int i = 0; i < 100; ++i) + { + Task.Delay(100).Wait(); + Console.WriteLine($"Iteration: {i}"); + logger.SendMessage(TestMessageLevel.Informational, $"Iteration: {i}"); + + cancellation.ThrowIfCancellationRequested(); + progress.Report(i + 1); + + if (i == 3) + { + cancellationTokenSource.Cancel(); + Task.Delay(500).Wait(); + } + } + } + finally + { + innerTaskCompletionSource.TrySetResult(null); + } + + return Task.FromResult(outputAttachments); + }); + + // act + await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, mockEventsHandler.Object, cancellationTokenSource.Token); + Console.WriteLine("Attachments processing done"); + await innerTaskCompletionSource.Task; + + // assert + VerifyCompleteEvent(true, false, inputAttachments[0]); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.IsAny()), Times.Exactly(4)); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgs(a, 1)))); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgs(a, 2)))); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgs(a, 3)))); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgs(a, 4)))); + mockEventsHandler.Verify(h => h.HandleLogMessage(TestMessageLevel.Informational, "Attachments processing was cancelled.")); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[0])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1, status: "Canceled"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnInitialAttachments_IfOperationCancelled() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input") + }; + + ICollection outputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_output") + }; + + var innerTaskCompletionSource = new TaskCompletionSource(); + + mockAttachmentHandler1.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).Returns((ICollection i1, IProgress p, IMessageLogger logger, CancellationToken cancellation) => + { + try + { + for (int i = 0; i < 1000; ++i) + { + Task.Delay(100).Wait(); + Console.WriteLine($"Iteration: {i}"); + logger.SendMessage(TestMessageLevel.Informational, $"Iteration: {i}"); + + cancellation.ThrowIfCancellationRequested(); + + if (i == 3) + { + cancellationTokenSource.Cancel(); + Task.Delay(500).Wait(); + } + } + } + finally + { + innerTaskCompletionSource.TrySetResult(null); + } + + return Task.FromResult(outputAttachments); + }); + + // act + var result = await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, cancellationTokenSource.Token); + Console.WriteLine("Attachments processing done"); + await innerTaskCompletionSource.Task; + + // assert + Assert.IsNotNull(result); + Assert.AreEqual(1, result.Count); + Assert.IsTrue(result.Contains(inputAttachments[0])); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris(), Times.Never); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[0])), It.IsAny>(), It.IsAny(), cancellationTokenSource.Token)); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never); + + VerifyMetrics(inputCount: 1, outputCount: 1, status: "Canceled"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsync_ShouldReturnProperlySendProgressEvents_IfHandlersPropagesEvents() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input"), + new AttachmentSet(new Uri(uri2), "uri2_input") + }; + + ICollection outputAttachments1 = new List + { + new AttachmentSet(new Uri(uri1), "uri1_output") + }; + + ICollection outputAttachments2 = new List + { + new AttachmentSet(new Uri(uri2), "uri2_output") + }; + + var innerTaskCompletionSource = new TaskCompletionSource(); + + int counter = 0; + mockEventsHandler.Setup(h => h.HandleTestRunAttachmentsProcessingProgress(It.IsAny())).Callback(() => + { + counter++; + if(counter == 6) + { + innerTaskCompletionSource.TrySetResult(null); + } + }); + + mockAttachmentHandler1.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).Returns((ICollection i1, IProgress progress, IMessageLogger logger, CancellationToken cancellation) => + { + progress.Report(25); + progress.Report(50); + progress.Report(75); + logger.SendMessage(TestMessageLevel.Error, "error"); + progress.Report(100); + return Task.FromResult(outputAttachments1); + }); + + mockAttachmentHandler2.Setup(h => h.ProcessAttachmentSetsAsync(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())).Returns((ICollection i1, IProgress progress, IMessageLogger logger, CancellationToken cancellation) => + { + progress.Report(50); + logger.SendMessage(TestMessageLevel.Informational, "info"); + progress.Report(100); + return Task.FromResult(outputAttachments2); + }); + + // act + await manager.ProcessTestRunAttachmentsAsync(mockRequestData.Object, inputAttachments, mockEventsHandler.Object, CancellationToken.None); + + // assert + await innerTaskCompletionSource.Task; + VerifyCompleteEvent(false, false, outputAttachments1.First(), outputAttachments2.First()); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.IsAny()), Times.Exactly(6)); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgsForTwoHandlers(a, 1, 25, uri1)))); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgsForTwoHandlers(a, 1, 50, uri1)))); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgsForTwoHandlers(a, 1, 75, uri1)))); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgsForTwoHandlers(a, 1, 100, uri1)))); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgsForTwoHandlers(a, 2, 50, uri2)))); + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingProgress(It.Is(a => VerifyProgressArgsForTwoHandlers(a, 2, 100, uri2)))); + mockAttachmentHandler1.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler2.Verify(h => h.GetExtensionUris()); + mockAttachmentHandler1.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[0])), It.IsAny>(), It.IsAny(), CancellationToken.None)); + mockAttachmentHandler2.Verify(h => h.ProcessAttachmentSetsAsync(It.Is>(c => c.Count == 1 && c.Contains(inputAttachments[1])), It.IsAny>(), It.IsAny(), CancellationToken.None)); + + mockEventsHandler.Verify(h => h.HandleLogMessage(TestMessageLevel.Informational, "info")); + mockEventsHandler.Verify(h => h.HandleLogMessage(TestMessageLevel.Error, "error")); + + VerifyMetrics(inputCount: 2, outputCount: 2); + } + + private void VerifyMetrics(int inputCount, int outputCount, string status = "Completed") + { + mockEventSource.Verify(s => s.TestRunAttachmentsProcessingStart(inputCount)); + mockEventSource.Verify(s => s.TestRunAttachmentsProcessingStop(outputCount)); + + mockMetricsCollection.Verify(m => m.Add(TelemetryDataConstants.NumberOfAttachmentsSentForProcessing, inputCount)); + mockMetricsCollection.Verify(m => m.Add(TelemetryDataConstants.NumberOfAttachmentsAfterProcessing, outputCount)); + mockMetricsCollection.Verify(m => m.Add(TelemetryDataConstants.AttachmentsProcessingState, status)); + mockMetricsCollection.Verify(m => m.Add(TelemetryDataConstants.TimeTakenInSecForAttachmentsProcessing, It.IsAny())); + } + + private void VerifyCompleteEvent(bool isCanceled, bool containsError, params AttachmentSet[] expectedSets) + { + mockEventsHandler.Verify(h => h.HandleTestRunAttachmentsProcessingComplete( + It.Is(a => a.IsCanceled == isCanceled && (a.Error != null) == containsError), + It.Is>(c => c.Count == expectedSets.Length && expectedSets.All(e => c.Contains(e))))); + } + + private bool VerifyProgressArgs(TestRunAttachmentsProcessingProgressEventArgs args, int progress) + { + Assert.AreEqual(1, args.CurrentAttachmentProcessorIndex); + Assert.AreEqual(2, args.AttachmentProcessorsCount); + Assert.AreEqual(1, args.CurrentAttachmentProcessorUris.Count); + Assert.AreEqual(uri1, args.CurrentAttachmentProcessorUris.First().AbsoluteUri); + return progress == args.CurrentAttachmentProcessorProgress; + } + + private bool VerifyProgressArgsForTwoHandlers(TestRunAttachmentsProcessingProgressEventArgs args, long handlerIndex, long progress, string uri) + { + return progress == args.CurrentAttachmentProcessorProgress && + args.CurrentAttachmentProcessorIndex == handlerIndex && + args.CurrentAttachmentProcessorUris.First().AbsoluteUri == uri && + args.AttachmentProcessorsCount == 2; + } + } +} diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs index 9a7a439a80..78b7376bcf 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelProxyExecutionManagerTests.cs @@ -15,6 +15,7 @@ namespace TestPlatform.CrossPlatEngine.UnitTests.Client using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; @@ -171,6 +172,32 @@ public void HandlePartialRunCompleteShouldCreateNewProxyExecutionManagerIfDataCo Assert.IsTrue(this.proxyManagerFuncCalled); } + [TestMethod] + public void HandlePartialRunCompleteShouldCreateNewProxyExecutionManagerIfDataCollectionEnabledAndCreatorWithDataCollection() + { + var completeArgs = new TestRunCompleteEventArgs(null, true, true, null, null, TimeSpan.Zero); + this.mockTestHostManager = new Mock(); + this.mockRequestSender = new Mock(); + this.mockDataCollectionManager = new Mock(); + var proxyDataCollectionManager = new ProxyExecutionManagerWithDataCollection(this.mockRequestData.Object, this.mockRequestSender.Object, this.mockTestHostManager.Object, this.mockDataCollectionManager.Object); + var managers = new List>(); + this.proxyManagerFunc = () => + { + this.proxyManagerFuncCalled = true; + var manager = new Mock(this.mockRequestData.Object, this.mockRequestSender.Object, this.mockTestHostManager.Object, this.mockDataCollectionManager.Object); + managers.Add(manager); + return manager.Object; + }; + var parallelExecutionManager = this.SetupExecutionManager(this.proxyManagerFunc, 2, setupTestCases: true); + + this.proxyManagerFuncCalled = false; + parallelExecutionManager.HandlePartialRunComplete(proxyDataCollectionManager, completeArgs, null, null, null); + Assert.IsTrue(this.proxyManagerFuncCalled); + + var handler = parallelExecutionManager.GetHandlerForGivenManager(managers.Last().Object); + Assert.IsTrue(handler is ParallelDataCollectionEventsHandler); + } + [TestMethod] public void HandlePartialRunCompleteShouldCreateNewProxyExecutionManagerIfIsAbortedIsTrue() { diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ParallelDataCollectionEventsHandlerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ParallelDataCollectionEventsHandlerTests.cs new file mode 100644 index 0000000000..6823e8efc1 --- /dev/null +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/DataCollection/ParallelDataCollectionEventsHandlerTests.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.CrossPlatEngine.UnitTests.DataCollection +{ + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; + using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Threading; + using System.Threading.Tasks; + + [TestClass] + public class ParallelDataCollectionEventsHandlerTests + { + private const string uri1 = "datacollector://microsoft/some1/1.0"; + private const string uri2 = "datacollector://microsoft/some2/2.0"; + private const string uri3 = "datacollector://microsoft/some3/2.0"; + + private readonly Mock mockRequestData; + private readonly Mock mockProxyExecutionManager; + private readonly Mock mockTestRunEventsHandler; + private readonly Mock mockParallelProxyExecutionManager; + private readonly Mock mockTestRunAttachmentsProcessingManager; + private readonly CancellationTokenSource cancellationTokenSource; + private readonly ParallelDataCollectionEventsHandler parallelDataCollectionEventsHandler; + + public ParallelDataCollectionEventsHandlerTests() + { + mockRequestData = new Mock(); + mockProxyExecutionManager = new Mock(); + mockTestRunEventsHandler = new Mock(); + mockParallelProxyExecutionManager = new Mock(); + mockTestRunAttachmentsProcessingManager = new Mock(); + cancellationTokenSource = new CancellationTokenSource(); + parallelDataCollectionEventsHandler = new ParallelDataCollectionEventsHandler(mockRequestData.Object, mockProxyExecutionManager.Object, mockTestRunEventsHandler.Object, + mockParallelProxyExecutionManager.Object, new ParallelRunDataAggregator(), mockTestRunAttachmentsProcessingManager.Object, cancellationTokenSource.Token); + + mockParallelProxyExecutionManager.Setup(m => m.HandlePartialRunComplete(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())).Returns(true); + } + + [TestMethod] + public void HandleTestRunComplete_ShouldCallProcessTestRunAttachmentsAsyncWithAttachmentsAndUseResults() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input1"), + new AttachmentSet(new Uri(uri2), "uri2_input1"), + new AttachmentSet(new Uri(uri3), "uri3_input1") + }; + + Collection outputAttachments = new Collection + { + new AttachmentSet(new Uri(uri1), "uri1_input1") + }; + + mockTestRunAttachmentsProcessingManager.Setup(f => f.ProcessTestRunAttachmentsAsync(mockRequestData.Object, It.IsAny>(), It.IsAny())).Returns(Task.FromResult(outputAttachments)); + + // act + parallelDataCollectionEventsHandler.HandleTestRunComplete(new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromSeconds(1)), null, inputAttachments, null); + + // assert + mockTestRunEventsHandler.Verify(h => h.HandleTestRunComplete(It.IsAny(), It.IsAny(), It.Is>(c => c.Count == 1 && c.Contains(outputAttachments[0])), It.IsAny>())); + mockTestRunAttachmentsProcessingManager.Verify(f => f.ProcessTestRunAttachmentsAsync(mockRequestData.Object, It.Is>(a => a.Count == 3), cancellationTokenSource.Token)); + } + + [TestMethod] + public void HandleTestRunComplete_ShouldCallProcessTestRunAttachmentsAsyncWithAttachmentsAndNotUserResults_IfManagerReturnsNull() + { + // arrange + List inputAttachments = new List + { + new AttachmentSet(new Uri(uri1), "uri1_input1"), + new AttachmentSet(new Uri(uri2), "uri2_input1"), + new AttachmentSet(new Uri(uri3), "uri3_input1") + }; + + mockTestRunAttachmentsProcessingManager.Setup(f => f.ProcessTestRunAttachmentsAsync(mockRequestData.Object, It.IsAny>(), It.IsAny())).Returns(Task.FromResult((Collection)null)); + + // act + parallelDataCollectionEventsHandler.HandleTestRunComplete(new TestRunCompleteEventArgs(null, false, false, null, null, TimeSpan.FromSeconds(1)), null, inputAttachments, null); + + // assert + mockTestRunEventsHandler.Verify(h => h.HandleTestRunComplete(It.IsAny(), It.IsAny(), It.Is>(c => c.Count == 3), It.IsAny>())); + mockTestRunAttachmentsProcessingManager.Verify(f => f.ProcessTestRunAttachmentsAsync(mockRequestData.Object, It.Is>(a => a.Count == 3), cancellationTokenSource.Token)); + } + } +} diff --git a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs index 8d1e341c9e..1734b9c15d 100644 --- a/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.TrxLogger.UnitTests/Utility/ConverterTests.cs @@ -145,7 +145,7 @@ public void ToTestElementShouldContainExpectedTestMethodPropertiesIfFqnDoesNotEn } [TestMethod] - public void ToResultFilesShouldAddAttachementsWithRelativeURI() + public void ToResultFilesShouldAddAttachmentsWithRelativeURI() { UriDataAttachment uriDataAttachment1 = new UriDataAttachment(new Uri($"/mnt/c/abc.txt", UriKind.Relative), "Description 1"); diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAttachmentsHandlerTests.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAttachmentsHandlerTests.cs index 7d63ed0a18..ff89d295bd 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAttachmentsHandlerTests.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/CodeCoverageDataAttachmentsHandlerTests.cs @@ -1,50 +1,108 @@ -using System.Collections.Generic; - -namespace Microsoft.TestPlatform.Utilities.UnitTests +namespace Microsoft.TestPlatform.Utilities.UnitTests { + using Moq; + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; - using System; - using System.Collections.ObjectModel; [TestClass] public class CodeCoverageDataAttachmentsHandlerTests { - private CodeCoverageDataAttachmentsHandler coverageDataAttachmentsHandler; + private readonly Mock> mockProgressReporter; + private readonly CodeCoverageDataAttachmentsHandler coverageDataAttachmentsHandler; public CodeCoverageDataAttachmentsHandlerTests() { + mockProgressReporter = new Mock>(); coverageDataAttachmentsHandler = new CodeCoverageDataAttachmentsHandler(); } [TestMethod] - public void HandleDataCollectionAttachmentSetsShouldReturnEmptySetWhenNoAttachmentsOrAttachmentsAreNull() + public async Task HandleDataCollectionAttachmentSetsShouldReturnEmptySetWhenNoAttachmentsOrAttachmentsAreNull() { Collection attachment = new Collection(); - ICollection resultAttachmentSets = - coverageDataAttachmentsHandler.HandleDataCollectionAttachmentSets(attachment); + ICollection resultAttachmentSets = await + coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(attachment, mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); Assert.IsTrue(resultAttachmentSets.Count == 0); - resultAttachmentSets = coverageDataAttachmentsHandler.HandleDataCollectionAttachmentSets(null); + resultAttachmentSets = await coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(null, mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); Assert.IsTrue(resultAttachmentSets.Count == 0); + + mockProgressReporter.Verify(p => p.Report(It.IsAny()), Times.Never); } [TestMethod] - public void HandleDataCollectionAttachmentSetsShouldReturnEmptySetWhenNoCodeCoverageAttachments() + public async Task HandleDataCollectionAttachmentSetsShouldReturnInputIfOnly1Attachment() { - Collection attachment = new Collection(); var attachmentSet = new AttachmentSet(new Uri("//badrui//"), string.Empty); + attachmentSet.Attachments.Add(new UriDataAttachment(new Uri("C:\\temp\\aa"), "coverage")); - ICollection resultAttachmentSets = - coverageDataAttachmentsHandler.HandleDataCollectionAttachmentSets(attachment); + Collection attachment = new Collection { attachmentSet }; + ICollection resultAttachmentSets = await + coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(attachment, mockProgressReporter.Object, null, CancellationToken.None); Assert.IsNotNull(resultAttachmentSets); - Assert.IsTrue(resultAttachmentSets.Count == 0); + Assert.IsTrue(resultAttachmentSets.Count == 1); + Assert.AreEqual("datacollector://microsoft/CodeCoverage/2.0", resultAttachmentSets.First().Uri.AbsoluteUri); + Assert.AreEqual("file:///C:/temp/aa", resultAttachmentSets.First().Attachments.First().Uri.AbsoluteUri); + } + + [TestMethod] + public async Task HandleDataCollectionAttachmentSetsShouldThrowIfCancellationRequested() + { + var attachmentSet = new AttachmentSet(new Uri("//badrui//"), string.Empty); + attachmentSet.Attachments.Add(new UriDataAttachment(new Uri("C:\\temp\\aa"), "coverage")); + CancellationTokenSource cts = new CancellationTokenSource(); + cts.Cancel(); + + Collection attachment = new Collection + { + attachmentSet, + attachmentSet + }; + + await Assert.ThrowsExceptionAsync(async () => await coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(attachment, mockProgressReporter.Object, null, cts.Token)); + + Assert.AreEqual(2, attachment.Count); + + mockProgressReporter.Verify(p => p.Report(It.IsAny()), Times.Never); + } + + [TestMethod] + public async Task HandleDataCollectionAttachmentSetsShouldReturnExistingAttachmentsIfFailedToLoadLibrary() + { + var attachmentSet1 = new AttachmentSet(new Uri("//badrui//"), string.Empty); + attachmentSet1.Attachments.Add(new UriDataAttachment(new Uri("C:\\temp\\aa"), "coverage")); + + var attachmentSet2 = new AttachmentSet(new Uri("//badruj//"), string.Empty); + attachmentSet2.Attachments.Add(new UriDataAttachment(new Uri("C:\\temp\\ab"), "coverage")); + + CancellationTokenSource cts = new CancellationTokenSource(); + + Collection attachment = new Collection + { + attachmentSet1, + attachmentSet2 + }; + + var result = await coverageDataAttachmentsHandler.ProcessAttachmentSetsAsync(attachment, mockProgressReporter.Object, null, cts.Token); + + Assert.AreEqual(2, result.Count); + Assert.IsTrue(result.Contains(attachmentSet1)); + Assert.IsTrue(result.Contains(attachmentSet2)); + + mockProgressReporter.Verify(p => p.Report(It.IsAny()), Times.Never); } } } diff --git a/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs b/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs index 457856254d..add71d422e 100644 --- a/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs +++ b/test/TranslationLayer.UnitTests/VsTestConsoleRequestSenderTests.cs @@ -33,8 +33,6 @@ public class VsTestConsoleRequestSenderTests { private readonly ITranslationLayerRequestSender requestSender; - private readonly ITranslationLayerRequestSenderAsync requestSenderAsync; - private readonly Mock mockCommunicationManager; private readonly int WaitTimeout = 2000; @@ -49,10 +47,6 @@ public VsTestConsoleRequestSenderTests() this.mockCommunicationManager.Object, JsonDataSerializer.Instance, new Mock().Object); - this.requestSenderAsync = new VsTestConsoleRequestSender( - this.mockCommunicationManager.Object, - JsonDataSerializer.Instance, - new Mock().Object); } #region Communication Tests @@ -104,7 +98,7 @@ public async Task InitializeCommunicationAsyncShouldReturnInvalidPortNumberIfHos this.mockCommunicationManager.Setup(cm => cm.HostServer(new IPEndPoint(IPAddress.Loopback, 0))).Throws(new Exception("Fail")); this.mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Returns(Task.FromResult(false)); - var portOutput = await this.requestSenderAsync.InitializeCommunicationAsync(this.WaitTimeout); + var portOutput = await this.requestSender.InitializeCommunicationAsync(this.WaitTimeout); Assert.IsTrue(portOutput < 0, "Negative port number must be returned if Hosting Server fails."); this.mockCommunicationManager.Verify(cm => cm.HostServer(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); @@ -143,7 +137,7 @@ public async Task InitializeCommunicationAsyncShouldFailConnectionIfMessageRecei this.mockCommunicationManager.Setup(cm => cm.AcceptClientAsync()).Returns(Task.FromResult(false)).Callback(() => { }); this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).ThrowsAsync(new Exception("Fail")); - var portOutput = await this.requestSenderAsync.InitializeCommunicationAsync(this.WaitTimeout); + var portOutput = await this.requestSender.InitializeCommunicationAsync(this.WaitTimeout); // Connection must not succeed as handshake failed Assert.AreEqual(-1, portOutput, "Connection must fail if handshake failed."); @@ -189,7 +183,7 @@ public async Task InitializeCommunicationAsyncShouldFailConnectionIfSessionConne this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(discoveryMessage)); - var portOutput = await this.requestSenderAsync.InitializeCommunicationAsync(this.WaitTimeout); + var portOutput = await this.requestSender.InitializeCommunicationAsync(this.WaitTimeout); Assert.AreEqual(-1, portOutput, "Connection must fail if version check failed."); this.mockCommunicationManager.Verify(cm => cm.HostServer(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); @@ -239,7 +233,7 @@ public async Task InitializeCommunicationAsyncShouldFailConnectionIfSendMessageF this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(sessionConnected)); this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.VersionCheck, this.protocolVersion)).Throws(new Exception("Fail")); - var portOutput = await this.requestSenderAsync.InitializeCommunicationAsync(this.WaitTimeout); + var portOutput = await this.requestSender.InitializeCommunicationAsync(this.WaitTimeout); Assert.AreEqual(-1, portOutput, "Connection must fail if version check failed."); this.mockCommunicationManager.Verify(cm => cm.HostServer(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); @@ -310,7 +304,7 @@ public async Task InitializeCommunicationAsyncShouldFailConnectionIfProtocolIsNo this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(sessionConnected)); this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.VersionCheck)).Callback(changedMessage); - var portOutput = await this.requestSenderAsync.InitializeCommunicationAsync(this.WaitTimeout); + var portOutput = await this.requestSender.InitializeCommunicationAsync(this.WaitTimeout); Assert.AreEqual(-1, portOutput, "Connection must fail if version check failed."); this.mockCommunicationManager.Verify(cm => cm.HostServer(new IPEndPoint(IPAddress.Loopback, 0)), Times.Once); @@ -361,7 +355,7 @@ public async Task DiscoverTestsAsyncShouldCompleteWithZeroTests() }; this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(discoveryComplete)); - await this.requestSenderAsync.DiscoverTestsAsync(new List() { "1.dll" }, null, null, mockHandler.Object); + await this.requestSender.DiscoverTestsAsync(new List() { "1.dll" }, null, null, mockHandler.Object); mockHandler.Verify(mh => mh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once, "Discovery Complete must be called"); mockHandler.Verify(mh => mh.HandleDiscoveredTests(It.IsAny>()), Times.Never, "DiscoveredTests must not be called"); @@ -427,7 +421,7 @@ public async Task DiscoverTestsAsyncShouldCompleteWithSingleTest() mockHandler.Setup(mh => mh.HandleDiscoveredTests(It.IsAny>())).Callback( () => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(discoveryComplete))); - await this.requestSenderAsync.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), mockHandler.Object); mockHandler.Verify(mh => mh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once, "Discovery Complete must be called"); mockHandler.Verify(mh => mh.HandleDiscoveredTests(It.IsAny>()), Times.Once, "DiscoveredTests must be called"); @@ -498,7 +492,7 @@ public async Task DiscoverTestsAsyncShouldReportBackTestsWithTraitsInTestsFoundM this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult((discoveryComplete))); }); - await this.requestSenderAsync.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), mockHandler.Object); Assert.IsNotNull(receivedTestCases); Assert.AreEqual(1, receivedTestCases.Count); @@ -570,7 +564,7 @@ public async Task DiscoverTestsAsyncShouldReportBackTestsWithTraitsInDiscoveryCo receivedTestCases = tests?.ToList(); }); - await this.requestSenderAsync.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), mockHandler.Object); Assert.IsNotNull(receivedTestCases); Assert.AreEqual(1, receivedTestCases.Count); @@ -635,7 +629,7 @@ public async Task DiscoverTestsAsyncShouldCompleteWithTestMessage() mockHandler.Setup(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny())).Callback( () => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(discoveryComplete))); - await this.requestSenderAsync.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.DiscoverTestsAsync(new List() { "1.dll" }, null, new TestPlatformOptions(), mockHandler.Object); mockHandler.Verify(mh => mh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once, "Discovery Complete must be called"); mockHandler.Verify(mh => mh.HandleDiscoveredTests(It.IsAny>()), Times.Once, "DiscoveredTests must be called"); @@ -665,7 +659,7 @@ public async Task DiscoverTestsAsyncShouldAbortOnExceptionInSendMessage() var payload = new DiscoveryRequestPayload { Sources = sources, RunSettings = null }; this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.StartDiscovery, payload)).Throws(new IOException()); - await this.requestSenderAsync.DiscoverTestsAsync(sources, null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.DiscoverTestsAsync(sources, null, new TestPlatformOptions(), mockHandler.Object); mockHandler.Verify(mh => mh.HandleDiscoveryComplete(It.IsAny(), null), Times.Once, "Discovery Complete must be called"); mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, It.IsAny()), Times.Once, "TestMessage event must be called"); @@ -710,16 +704,16 @@ public async Task DiscoverTestsAsyncShouldLogErrorWhenProcessExited() var testCase = new TestCase("hello", new Uri("world://how"), "1.dll"); var testCaseList = new List() { testCase }; var testsFound = CreateMessage(MessageType.TestCasesFound, testCaseList); - await this.requestSenderAsync.InitializeCommunicationAsync(this.WaitTimeout); + await this.requestSender.InitializeCommunicationAsync(this.WaitTimeout); this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Callback( (CancellationToken c) => { - Task.Run(() => this.requestSenderAsync.OnProcessExited()).Wait(); + Task.Run(() => this.requestSender.OnProcessExited()).Wait(); Assert.IsTrue(c.IsCancellationRequested); }).Returns(Task.FromResult((Message)null)); - await this.requestSenderAsync.DiscoverTestsAsync(sources, null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.DiscoverTestsAsync(sources, null, new TestPlatformOptions(), mockHandler.Object); mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, It.IsAny()), Times.Once); } @@ -778,7 +772,7 @@ public async Task StartTestRunAsyncShouldCompleteWithZeroTests() var runComplete = CreateMessage(MessageType.ExecutionComplete, payload); this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(runComplete)); - await this.requestSenderAsync.StartTestRunAsync(new List() { "1.dll" }, null, null, mockHandler.Object); + await this.requestSender.StartTestRunAsync(new List() { "1.dll" }, null, null, mockHandler.Object); mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), It.IsAny(), null, null), Times.Once, "Run Complete must be called"); @@ -888,7 +882,7 @@ public async Task StartTestRunAsyncShouldCompleteWithSingleTestAndMessage() this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(runComplete)); }); - await this.requestSenderAsync.StartTestRunAsync(new List() { "1.dll" }, null, null, mockHandler.Object); + await this.requestSender.StartTestRunAsync(new List() { "1.dll" }, null, null, mockHandler.Object); mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), It.IsAny(), null, null), Times.Once, "Run Complete must be called"); @@ -1060,7 +1054,7 @@ public async Task StartTestRunAsyncWithCustomHostShouldComplete() this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(runprocessInfoPayload)); - await this.requestSenderAsync.StartTestRunWithCustomHostAsync(new List() { "1.dll" }, null, null, mockHandler.Object, mockLauncher.Object); + await this.requestSender.StartTestRunWithCustomHostAsync(new List() { "1.dll" }, null, null, mockHandler.Object, mockLauncher.Object); mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), It.IsAny(), null, null), Times.Once, "Run Complete must be called"); @@ -1161,7 +1155,7 @@ public async Task StartTestRunAsyncWithCustomHostShouldNotAbortAndSendErrorToVst this.mockCommunicationManager.Setup(cm => cm.SendMessage(It.IsAny(), It.IsAny(), this.protocolVersion)). Callback(() => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(runComplete))); - await this.requestSenderAsync.StartTestRunWithCustomHostAsync(new List() { "1.dll" }, null, null, mockHandler.Object, mockLauncher.Object); + await this.requestSender.StartTestRunWithCustomHostAsync(new List() { "1.dll" }, null, null, mockHandler.Object, mockLauncher.Object); mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), It.IsAny(), null, null), Times.Once, "Run Complete must be called"); @@ -1258,7 +1252,7 @@ public async Task StartTestRunAsyncWithSelectedTestsShouldCompleteWithZeroTests( var runComplete = CreateMessage(MessageType.ExecutionComplete, payload); this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(runComplete)); - await this.requestSenderAsync.StartTestRunAsync(new List(), null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.StartTestRunAsync(new List(), null, new TestPlatformOptions(), mockHandler.Object); mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), It.IsAny(), null, null), Times.Once, "Run Complete must be called"); @@ -1366,7 +1360,7 @@ public async Task StartTestRunAsyncWithSelectedTestsShouldCompleteWithSingleTest this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(runComplete)); }); - await this.requestSenderAsync.StartTestRunAsync(testCaseList, null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.StartTestRunAsync(testCaseList, null, new TestPlatformOptions(), mockHandler.Object); mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), It.IsAny(), null, null), Times.Once, "Run Complete must be called"); @@ -1474,7 +1468,7 @@ public async Task StartTestRunAsyncWithSelectedTestsHavingTraitsShouldReturnTest receivedChangeEventArgs = stats; }); - await this.requestSenderAsync.StartTestRunAsync(testCaseList, null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.StartTestRunAsync(testCaseList, null, new TestPlatformOptions(), mockHandler.Object); Assert.IsNotNull(receivedChangeEventArgs); Assert.IsTrue(receivedChangeEventArgs.NewTestResults.Count() > 0); @@ -1588,7 +1582,7 @@ public async Task StartTestRunAsyncWithSelectedTestsHavingTraitsShouldReturnTest this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(runComplete)); }); - await this.requestSenderAsync.StartTestRunAsync(testCaseList, null, new TestPlatformOptions(), mockHandler.Object); + await this.requestSender.StartTestRunAsync(testCaseList, null, new TestPlatformOptions(), mockHandler.Object); Assert.IsNotNull(receivedChangeEventArgs); Assert.IsTrue(receivedChangeEventArgs.NewTestResults.Any()); @@ -1715,7 +1709,7 @@ public async Task StartTestRunWithCustomHostAsyncWithSelectedTestsShouldComplete this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(runprocessInfoPayload)); - await this.requestSenderAsync.StartTestRunWithCustomHostAsync(testCaseList, null, new TestPlatformOptions(), mockHandler.Object, mockLauncher.Object); + await this.requestSender.StartTestRunWithCustomHostAsync(testCaseList, null, new TestPlatformOptions(), mockHandler.Object, mockLauncher.Object); mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), It.IsAny(), null, null), Times.Once, "Run Complete must be called"); @@ -1797,8 +1791,8 @@ public async Task StartTestRunWithCustomHostAsyncInParallelShouldCallCustomHostM } }); - await this.requestSenderAsync.InitializeCommunicationAsync(this.WaitTimeout); - await this.requestSenderAsync.StartTestRunWithCustomHostAsync(sources, null, null, mockHandler.Object, mockLauncher.Object); + await this.requestSender.InitializeCommunicationAsync(this.WaitTimeout); + await this.requestSender.StartTestRunWithCustomHostAsync(sources, null, null, mockHandler.Object, mockLauncher.Object); mockLauncher.Verify(ml => ml.LaunchTestHost(It.IsAny()), Times.Exactly(2)); } @@ -1830,7 +1824,7 @@ public async Task StartTestRunAsyncShouldAbortOnExceptionInSendMessage() this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.TestRunAllSourcesWithDefaultHost, payload, this.protocolVersion)).Throws(exception); - await this.requestSenderAsync.StartTestRunAsync(sources, null, null, mockHandler.Object); + await this.requestSender.StartTestRunAsync(sources, null, null, mockHandler.Object); mockHandler.Verify(mh => mh.HandleTestRunComplete(It.IsAny(), null, null, null), Times.Once, "Test Run Complete must be called"); mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, It.IsAny()), Times.Once, "TestMessage event must be called"); @@ -1883,22 +1877,240 @@ public async Task StartTestRunAsyncShouldLogErrorOnProcessExited() RunAttachments = null, TestRunCompleteArgs = dummyCompleteArgs }; - await this.requestSenderAsync.InitializeCommunicationAsync(this.WaitTimeout); + await this.requestSender.InitializeCommunicationAsync(this.WaitTimeout); this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())) .Callback((CancellationToken c) => { - Task.Run(() => this.requestSenderAsync.OnProcessExited()).Wait(); + Task.Run(() => this.requestSender.OnProcessExited()).Wait(); Assert.IsTrue(c.IsCancellationRequested); }).Returns(Task.FromResult((Message)null)); - await this.requestSenderAsync.StartTestRunAsync(sources, null, null, mockHandler.Object); + await this.requestSender.StartTestRunAsync(sources, null, null, mockHandler.Object); mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, It.IsAny()), Times.Once); } #endregion + #region Attachments Processing Tests + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsyncShouldCompleteWithZeroAttachments() + { + await this.InitializeCommunicationAsync(); + + var mockHandler = new Mock(); + + var payload = new TestRunAttachmentsProcessingCompletePayload() + { + AttachmentsProcessingCompleteEventArgs = new TestRunAttachmentsProcessingCompleteEventArgs(false, null), + Attachments = new AttachmentSet[0] + }; + + var attachmentsProcessingComplete = new Message() + { + MessageType = MessageType.TestRunAttachmentsProcessingComplete, + Payload = JToken.FromObject(payload) + }; + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(attachmentsProcessingComplete)); + + await this.requestSender.ProcessTestRunAttachmentsAsync(new List { new AttachmentSet(new Uri("http://www.bing.com"), "a") }, true, mockHandler.Object, CancellationToken.None); + + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingStart, It.IsAny())); + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingCancel), Times.Never); + mockHandler.Verify(mh => mh.HandleTestRunAttachmentsProcessingComplete(It.Is(a => !a.IsCanceled && a.Error == null), It.Is>(a => a.Count == 0)), Times.Once, "Attachments Processing Complete must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never, "TestMessage event must not be called"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsyncShouldCompleteWithOneAttachment() + { + await this.InitializeCommunicationAsync(); + + var mockHandler = new Mock(); + + var payload = new TestRunAttachmentsProcessingCompletePayload() + { + AttachmentsProcessingCompleteEventArgs = new TestRunAttachmentsProcessingCompleteEventArgs(true, new Exception("msg")), + Attachments = new List { new AttachmentSet(new Uri("http://www.bing.com"), "out") } + }; + var attachmentsProcessingComplete = new Message() + { + MessageType = MessageType.TestRunAttachmentsProcessingComplete, + Payload = JToken.FromObject(payload) + }; + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(attachmentsProcessingComplete)); + + await this.requestSender.ProcessTestRunAttachmentsAsync(new List { new AttachmentSet(new Uri("http://www.bing.com"), "a") }, true, mockHandler.Object, CancellationToken.None); + + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingStart, It.IsAny())); + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingCancel), Times.Never); + mockHandler.Verify(mh => mh.HandleTestRunAttachmentsProcessingComplete(It.Is(a => a.IsCanceled && a.Error != null), It.Is>(a => a.Count == 1)), Times.Once, "Attachments Processing Complete must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny()), Times.Never, "TestMessage event must not be called"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsyncShouldCompleteWithOneAttachmentAndTestMessage() + { + await this.InitializeCommunicationAsync(); + + var mockHandler = new Mock(); + + var payload = new TestRunAttachmentsProcessingCompletePayload() + { + AttachmentsProcessingCompleteEventArgs = new TestRunAttachmentsProcessingCompleteEventArgs(false, null), + Attachments = new List { new AttachmentSet(new Uri("http://www.bing.com"), "out") } + }; + + var attachmentsProcessingComplete = new Message() + { + MessageType = MessageType.TestRunAttachmentsProcessingComplete, + Payload = JToken.FromObject(payload) + }; + + var mpayload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Informational, Message = "Hello" }; + var message = CreateMessage(MessageType.TestMessage, mpayload); + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(message)); + mockHandler.Setup(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny())).Callback( + () => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(attachmentsProcessingComplete))); + + await this.requestSender.ProcessTestRunAttachmentsAsync(new List { new AttachmentSet(new Uri("http://www.bing.com"), "a") }, false, mockHandler.Object, CancellationToken.None); + + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingStart, It.IsAny())); + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingCancel), Times.Never); + mockHandler.Verify(mh => mh.HandleTestRunAttachmentsProcessingComplete(It.IsAny(), It.Is>(a => a.Count == 1)), Times.Once, "Attachments Processing Complete must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Informational, "Hello"), Times.Once, "TestMessage event must be called"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsyncShouldCompleteWithOneAttachmentAndProgressMessage() + { + await this.InitializeCommunicationAsync(); + + var mockHandler = new Mock(); + + var completePayload = new TestRunAttachmentsProcessingCompletePayload() + { + AttachmentsProcessingCompleteEventArgs = new TestRunAttachmentsProcessingCompleteEventArgs(false, null), + Attachments = new List { new AttachmentSet(new Uri("http://www.bing.com"), "out") } + }; + + var attachmentsProcessingComplete = new Message() + { + MessageType = MessageType.TestRunAttachmentsProcessingComplete, + Payload = JToken.FromObject(completePayload) + }; + + var progressPayload = new TestRunAttachmentsProcessingProgressPayload() + { + AttachmentsProcessingProgressEventArgs = new TestRunAttachmentsProcessingProgressEventArgs(1, new[] { new Uri("http://www.bing.com/") }, 50, 2) + }; + + var attachmentsProcessingProgress = new Message() + { + MessageType = MessageType.TestRunAttachmentsProcessingProgress, + Payload = JToken.FromObject(progressPayload) + }; + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(attachmentsProcessingProgress)); + + mockHandler.Setup(mh => mh.HandleTestRunAttachmentsProcessingProgress(It.IsAny())).Callback( + () => this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(attachmentsProcessingComplete))); + + await this.requestSender.ProcessTestRunAttachmentsAsync(new List { new AttachmentSet(new Uri("http://www.bing.com"), "a") }, false, mockHandler.Object, CancellationToken.None); + + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingStart, It.IsAny())); + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingCancel), Times.Never); + mockHandler.Verify(mh => mh.HandleTestRunAttachmentsProcessingComplete(It.IsAny(), It.Is>(a => a.Count == 1)), Times.Once, "Attachments Processing Complete must be called"); + mockHandler.Verify(mh => mh.HandleTestRunAttachmentsProcessingProgress(It.Is(a => a.CurrentAttachmentProcessorIndex == 1 && a.CurrentAttachmentProcessorUris.First() == new Uri("http://www.bing.com/") && a.CurrentAttachmentProcessorProgress == 50 && a.AttachmentProcessorsCount == 2)), Times.Once, "Attachments processing Progress must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Informational, "Hello"), Times.Never); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsyncShouldSendCancelMessageIfCancellationTokenCancelled() + { + await this.InitializeCommunicationAsync(); + + var cts = new CancellationTokenSource(); + + var mockHandler = new Mock(); + + var payload = new TestRunAttachmentsProcessingCompletePayload() + { + Attachments = new List { new AttachmentSet(new Uri("http://www.bing.com"), "out") } + }; + var attachmentsProcessingComplete = new Message() + { + MessageType = MessageType.TestRunAttachmentsProcessingComplete, + Payload = JToken.FromObject(payload) + }; + + var mpayload = new TestMessagePayload() { MessageLevel = TestMessageLevel.Informational, Message = "Hello" }; + var message = CreateMessage(MessageType.TestMessage, mpayload); + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(message)); + mockHandler.Setup(mh => mh.HandleLogMessage(It.IsAny(), It.IsAny())).Callback(() => + { + cts.Cancel(); + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(attachmentsProcessingComplete)); + }); + + await this.requestSender.ProcessTestRunAttachmentsAsync(new List { new AttachmentSet(new Uri("http://www.bing.com"), "a") }, false, mockHandler.Object, cts.Token); + + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingStart, It.IsAny())); + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingCancel)); + mockHandler.Verify(mh => mh.HandleTestRunAttachmentsProcessingComplete(It.IsAny(), It.Is>(a => a.Count == 1)), Times.Once, "Attachments Processing Complete must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Informational, "Hello"), Times.Once, "TestMessage event must be called"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsyncShouldSendCancelMessageIfCancellationTokenCancelledAtTheBeginning() + { + await this.InitializeCommunicationAsync(); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + + var mockHandler = new Mock(); + + var payload = new TestRunAttachmentsProcessingCompletePayload() + { + Attachments = new List { new AttachmentSet(new Uri("http://www.bing.com"), "out") } + }; + var attachmentsProcessingComplete = new Message() + { + MessageType = MessageType.TestRunAttachmentsProcessingComplete, + Payload = JToken.FromObject(payload) + }; + + this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(attachmentsProcessingComplete)); + + await this.requestSender.ProcessTestRunAttachmentsAsync(new List { new AttachmentSet(new Uri("http://www.bing.com"), "a") }, true, mockHandler.Object, cts.Token); + + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingStart, It.IsAny())); + mockCommunicationManager.Verify(c => c.SendMessage(MessageType.TestRunAttachmentsProcessingCancel)); + mockHandler.Verify(mh => mh.HandleTestRunAttachmentsProcessingComplete(It.IsAny(), It.Is>(a => a.Count == 1)), Times.Once, "Attachments Processing Complete must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Informational, "Hello"), Times.Never, "TestMessage event must be called"); + } + + [TestMethod] + public async Task ProcessTestRunAttachmentsAsyncShouldAbortOnExceptionInSendMessage() + { + var mockHandler = new Mock(); + this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.TestRunAttachmentsProcessingStart, It.IsAny())).Throws(new IOException()); + + await this.requestSender.ProcessTestRunAttachmentsAsync(new List { new AttachmentSet(new Uri("http://www.bing.com"), "out") }, false, mockHandler.Object, CancellationToken.None); + + mockHandler.Verify(mh => mh.HandleTestRunAttachmentsProcessingComplete(It.Is(a => !a.IsCanceled && a.Error is IOException), null), Times.Once, "Attachments Processing Complete must be called"); + mockHandler.Verify(mh => mh.HandleLogMessage(TestMessageLevel.Error, It.IsAny()), Times.Once, "TestMessage event must be called"); + this.mockCommunicationManager.Verify(cm => cm.StopServer(), Times.Never); + } + + #endregion + + + #region Private Methods /// @@ -1980,7 +2192,7 @@ private async Task InitializeCommunicationAsync() this.mockCommunicationManager.Setup(cm => cm.ReceiveMessageAsync(It.IsAny())).Returns(Task.FromResult(sessionConnected)); this.mockCommunicationManager.Setup(cm => cm.SendMessage(MessageType.VersionCheck, this.protocolVersion)).Callback(changedMessage); - var portOutput = await this.requestSenderAsync.InitializeCommunicationAsync(this.WaitTimeout); + var portOutput = await this.requestSender.InitializeCommunicationAsync(this.WaitTimeout); Assert.AreEqual(dummyPortInput, portOutput, "Connection must succeed."); } diff --git a/test/TranslationLayer.UnitTests/VsTestConsoleWrapperAsyncTests.cs b/test/TranslationLayer.UnitTests/VsTestConsoleWrapperAsyncTests.cs index d272a5753b..fed30c3eb9 100644 --- a/test/TranslationLayer.UnitTests/VsTestConsoleWrapperAsyncTests.cs +++ b/test/TranslationLayer.UnitTests/VsTestConsoleWrapperAsyncTests.cs @@ -5,6 +5,8 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Threading; using System.Threading.Tasks; using Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; @@ -12,6 +14,7 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -19,11 +22,13 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests [TestClass] public class VsTestConsoleWrapperAsyncTests { - private IVsTestConsoleWrapperAsync consoleWrapper; + private IVsTestConsoleWrapper consoleWrapper; private Mock mockProcessManager; - private Mock mockRequestSender; + private Mock mockRequestSender; + + private Mock mockProcessHelper; private readonly List testSources = new List { "Hello", "World" }; @@ -40,13 +45,15 @@ public void TestInitialize() { this.consoleParameters = new ConsoleParameters(); - this.mockRequestSender = new Mock(); + this.mockRequestSender = new Mock(); this.mockProcessManager = new Mock(); - this.consoleWrapper = new VsTestConsoleWrapperAsync( + this.mockProcessHelper = new Mock(); + this.consoleWrapper = new VsTestConsoleWrapper( this.mockRequestSender.Object, this.mockProcessManager.Object, this.consoleParameters, - new Mock().Object); + new Mock().Object, + this.mockProcessHelper.Object); this.mockRequestSender.Setup(rs => rs.InitializeCommunicationAsync(It.IsAny())).Returns(Task.FromResult(100)); } @@ -302,6 +309,23 @@ await this.consoleWrapper.RunTestsWithCustomTestHostAsync( this.mockRequestSender.Verify(rs => rs.StartTestRunWithCustomHostAsync(this.testCases, "RunSettings", options, It.IsAny(), It.IsAny()), Times.Once); } + [TestMethod] + public async Task ProcessTestRunAttachmentsAsyncShouldSucceed() + { + var attachments = new Collection(); + var cancellationToken = new CancellationToken(); + + await this.consoleWrapper.ProcessTestRunAttachmentsAsync( + attachments, + null, + true, + true, + new Mock().Object, + cancellationToken); + + this.mockRequestSender.Verify(rs => rs.ProcessTestRunAttachmentsAsync(attachments, true, It.IsAny(), cancellationToken)); + } + [TestMethod] public void EndSessionShouldSucceed() { diff --git a/test/TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs b/test/TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs index 2691bb43c7..ee9c0f6a23 100644 --- a/test/TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs +++ b/test/TranslationLayer.UnitTests/VsTestConsoleWrapperTests.cs @@ -13,7 +13,10 @@ namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.UnitTests using Moq; using System; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Diagnostics; + using System.Threading; + using System.Threading.Tasks; [TestClass] public class VsTestConsoleWrapperTests @@ -53,6 +56,7 @@ public void TestInitialize() this.mockRequestSender.Setup(rs => rs.WaitForRequestHandlerConnection(It.IsAny())).Returns(true); this.mockRequestSender.Setup(rs => rs.InitializeCommunication()).Returns(100); + this.mockRequestSender.Setup(rs => rs.InitializeCommunicationAsync(It.IsAny())).Returns(Task.FromResult(100)); } [TestMethod] @@ -303,6 +307,23 @@ public void RunTestsWithSelectedTestsAndOptionsUsingACustomHostShouldPassOnOptio this.mockRequestSender.Verify(rs => rs.StartTestRunWithCustomHost(this.testCases, "RunSettings", options, It.IsAny(), It.IsAny()), Times.Once); } + [TestMethod] + public async Task ProcessTestRunAttachmentsAsyncShouldSucceed() + { + var attachments = new Collection(); + var cancellationToken = new CancellationToken(); + + await this.consoleWrapper.ProcessTestRunAttachmentsAsync( + attachments, + null, + true, + true, + new Mock().Object, + cancellationToken); + + this.mockRequestSender.Verify(rs => rs.ProcessTestRunAttachmentsAsync(attachments, true, It.IsAny(), cancellationToken)); + } + [TestMethod] public void EndSessionShouldSucceed() { diff --git a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs index ca191e2c8f..4a12456ce3 100644 --- a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs @@ -18,6 +18,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors using Microsoft.VisualStudio.TestPlatform.CommandLine.Publisher; using Microsoft.VisualStudio.TestPlatform.CommandLineUtilities; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -44,6 +45,7 @@ public class ListFullyQualifiedTestsArgumentProcessorTests private Task mockMetricsPublisherTask; private Mock mockMetricsPublisher; private Mock mockProcessHelper; + private Mock mockAttachmentsProcessingManager; private static ListFullyQualifiedTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestManager, IOutput output) { @@ -78,6 +80,7 @@ public ListFullyQualifiedTestsArgumentProcessorTests() this.mockAssemblyMetadataProvider.Setup(x => x.GetFrameWork(It.IsAny())).Returns(new FrameworkName(Constants.DotNetFramework40)); this.inferHelper = new InferHelper(this.mockAssemblyMetadataProvider.Object); this.mockProcessHelper = new Mock(); + this.mockAttachmentsProcessingManager = new Mock(); } /// @@ -124,7 +127,7 @@ public void ExecutorInitializeWithValidSourceShouldAddItToTestSources() { CommandLineOptions.Instance.FileHelper = this.mockFileHelper.Object; CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, this.mockFileHelper.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager, null); executor.Initialize(this.dummyTestFilePath); @@ -136,7 +139,7 @@ public void ExecutorInitializeWithValidSourceShouldAddItToTestSources() public void ExecutorExecuteForNoSourcesShouldReturnFail() { CommandLineOptions.Instance.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsException(() => executor.Execute()); @@ -153,7 +156,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() this.ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager, null); @@ -170,7 +173,7 @@ public void ExecutorExecuteShouldThrowSettingsException() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); this.ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); @@ -189,7 +192,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() this.ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); @@ -207,7 +210,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() this.ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager, null); @@ -299,7 +302,7 @@ private void RunListFullyQualifiedTestArgumentProcessorWithTraits(Mock mockMetricsPublisherTask; private Mock mockMetricsPublisher; private Mock mockProcessHelper; + private Mock mockAttachmentsProcessingManager; private static ListTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestManager, IOutput output) { @@ -76,6 +78,7 @@ public ListTestsArgumentProcessorTests() this.mockAssemblyMetadataProvider.Setup(x => x.GetFrameWork(It.IsAny())).Returns(new FrameworkName(Constants.DotNetFramework40)); this.inferHelper = new InferHelper(this.mockAssemblyMetadataProvider.Object); this.mockProcessHelper = new Mock(); + this.mockAttachmentsProcessingManager = new Mock(); } /// @@ -125,7 +128,7 @@ public void ExecutorInitializeWithValidSourceShouldAddItToTestSources() { CommandLineOptions.Instance.FileHelper = this.mockFileHelper.Object; CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, this.mockFileHelper.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager, null); executor.Initialize(this.dummyTestFilePath); @@ -138,7 +141,7 @@ public void ExecutorExecuteForNoSourcesShouldReturnFail() { CommandLineOptions.Instance.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsException(() => executor.Execute()); @@ -155,7 +158,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsException(() => executor.Execute()); @@ -172,7 +175,7 @@ public void ExecutorExecuteShouldThrowSettingsException() this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); Assert.ThrowsException(() => listTestsArgumentExecutor.Execute()); @@ -189,7 +192,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); Assert.ThrowsException(() => listTestsArgumentExecutor.Execute()); @@ -206,7 +209,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsException(() => executor.Execute()); @@ -263,7 +266,7 @@ private void RunListTestArgumentProcessorExecuteWithMockSetup(Mock mockMetricsPublisherTask; private Mock mockMetricsPublisher; private Mock mockProcessHelper; + private Mock mockAttachmentsProcessingManager; private RunSpecificTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestManager) { @@ -67,6 +69,7 @@ public RunSpecificTestsArgumentProcessorTests() this.mockProcessHelper = new Mock(); this.mockProcessHelper.Setup(x => x.GetCurrentProcessId()).Returns(1234); this.mockProcessHelper.Setup(x => x.GetProcessName(It.IsAny())).Returns("dotnet.exe"); + this.mockAttachmentsProcessingManager = new Mock(); } [TestMethod] @@ -111,7 +114,7 @@ public void InitializeShouldThrowIfArgumentIsNull() { CommandLineOptions.Instance.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => { executor.Initialize(null); }); @@ -122,7 +125,7 @@ public void InitializeShouldThrowIfArgumentIsEmpty() { CommandLineOptions.Instance.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => { executor.Initialize(String.Empty); }); @@ -133,7 +136,7 @@ public void InitializeShouldThrowIfArgumentIsWhiteSpace() { CommandLineOptions.Instance.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => { executor.Initialize(" "); }); @@ -144,7 +147,7 @@ public void InitializeShouldThrowIfArgumentsAreEmpty() { CommandLineOptions.Instance.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => { executor.Initialize(" , "); }); @@ -155,7 +158,7 @@ public void ExecutorShouldSplitTestsSeparatedByComma() { CommandLineOptions.Instance.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -166,7 +169,7 @@ public void ExecutorExecuteForNoSourcesShouldThrowCommandLineException() { CommandLineOptions.Instance.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -189,7 +192,7 @@ public void ExecutorExecuteForValidSourceWithTestCaseFilterShouldRunTests() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); CommandLineOptions.Instance.TestCaseFilterValue = "Filter"; @@ -214,7 +217,7 @@ public void ExecutorExecuteShouldThrowTestPlatformExceptionThrownDuringDiscovery mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -232,7 +235,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationExceptionThrownDuringDisco mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -250,7 +253,7 @@ public void ExecutorExecuteShouldThrowSettingsExceptionThrownDuringDiscovery() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -273,7 +276,7 @@ public void ExecutorExecuteShouldThrowTestPlatformExceptionThrownDuringExecution mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -298,7 +301,7 @@ public void ExecutorExecuteShouldThrowSettingsExceptionThrownDuringExecution() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -324,7 +327,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationExceptionThrownDuringExecu this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -345,7 +348,7 @@ public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogWar mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(new List())); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -366,7 +369,7 @@ public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogApp mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(new List())); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -393,7 +396,7 @@ public void ExecutorExecuteShouldForValidSourcesAndValidSelectedTestsRunsTestsAn mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -419,7 +422,7 @@ public void ExecutorShouldRunTestsWhenTestsAreCommaSeparated() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1, Test2"); @@ -446,7 +449,7 @@ public void ExecutorShouldRunTestsWhenTestsAreFiltered() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -472,7 +475,7 @@ public void ExecutorShouldWarnWhenTestsAreNotAvailable() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1, Test2"); @@ -499,7 +502,7 @@ public void ExecutorShouldRunTestsWhenTestsAreCommaSeparatedWithEscape() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1(a\\,b), Test2(c\\,d)"); @@ -529,7 +532,7 @@ public void ExecutorShouldDisplayWarningIfNoTestsAreExecuted() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -558,7 +561,7 @@ public void ExecutorShouldNotDisplayWarningIfTestsAreExecuted() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockDiscoveryRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object);; var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); diff --git a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs index 3143356d41..d3b3814117 100644 --- a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs @@ -23,6 +23,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities; @@ -47,6 +48,7 @@ public class RunTestsArgumentProcessorTests private Task mockMetricsPublisherTask; private Mock mockMetricsPublisher; private Mock mockProcessHelper; + private Mock mockAttachmentsProcessingManager; public RunTestsArgumentProcessorTests() { @@ -64,6 +66,7 @@ public RunTestsArgumentProcessorTests() .Returns(Architecture.X86); this.mockAssemblyMetadataProvider.Setup(x => x.GetFrameWork(It.IsAny())).Returns(new FrameworkName(Constants.DotNetFramework40)); this.mockProcessHelper = new Mock(); + this.mockAttachmentsProcessingManager = new Mock(); } [TestMethod] @@ -109,7 +112,7 @@ public void ExecutorExecuteShouldReturnSuccessWithoutExecutionInDesignMode() CommandLineOptions.Instance.Reset(); CommandLineOptions.Instance.IsDesignMode = true; - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, runSettingsProvider, testRequestManager, this.mockOutput.Object); Assert.AreEqual(ArgumentProcessorResult.Success, executor.Execute()); @@ -119,7 +122,7 @@ public void ExecutorExecuteShouldReturnSuccessWithoutExecutionInDesignMode() public void ExecutorExecuteForNoSourcesShouldThrowCommandLineException() { CommandLineOptions.Instance.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -148,7 +151,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -164,7 +167,7 @@ public void ExecutorExecuteShouldThrowSettingsException() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -180,7 +183,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -196,7 +199,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())).Returns(mockTestRunRequest.Object); this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsException(() => executor.Execute()); @@ -251,7 +254,7 @@ private ArgumentProcessorResult RunRunArgumentProcessorExecuteWithMockSetup(ITes this.ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object); + var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, this.mockProcessHelper.Object, this.mockAttachmentsProcessingManager.Object); var executor = GetExecutor(testRequestManager); return executor.Execute(); diff --git a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs index 6f5bdca8cc..e8f7ba2a30 100644 --- a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs +++ b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; - namespace vstest.console.UnitTests.TestPlatformHelpers { using System; @@ -25,6 +22,7 @@ namespace vstest.console.UnitTests.TestPlatformHelpers using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Runtime.Versioning; @@ -35,8 +33,9 @@ namespace vstest.console.UnitTests.TestPlatformHelpers using vstest.console.UnitTests.TestDoubles; using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; - [TestClass] + [TestClass] public class TestRequestManagerTests { private DummyLoggerEvents mockLoggerEvents; @@ -49,12 +48,11 @@ public class TestRequestManagerTests private InferHelper inferHelper; private ITestRequestManager testRequestManager; private Mock mockTestPlatformEventSource; - private Mock mockRequestData; - private Mock mockMetricsCollection; private ProtocolConfig protocolConfig; private Task mockMetricsPublisherTask; private Mock mockMetricsPublisher; private Mock mockProcessHelper; + private Mock mockAttachmentsProcessingManager; private const string DefaultRunsettings = @" @@ -79,6 +77,7 @@ public TestRequestManagerTests() this.mockMetricsPublisher = new Mock(); this.mockMetricsPublisherTask = Task.FromResult(this.mockMetricsPublisher.Object); + this.mockAttachmentsProcessingManager = new Mock(); this.testRequestManager = new TestRequestManager( this.commandLineOptions, this.mockTestPlatform.Object, @@ -86,10 +85,8 @@ public TestRequestManagerTests() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); - this.mockMetricsCollection = new Mock(); - this.mockRequestData = new Mock(); - this.mockRequestData.Setup(rd => rd.MetricsCollection).Returns(this.mockMetricsCollection.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); this.mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(this.mockDiscoveryRequest.Object); this.mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny())) @@ -99,7 +96,7 @@ public TestRequestManagerTests() this.mockAssemblyMetadataProvider.Setup(a => a.GetFrameWork(It.IsAny())) .Returns(new FrameworkName(Constants.DotNetFramework40)); this.mockProcessHelper.Setup(x => x.GetCurrentProcessId()).Returns(1234); - this.mockProcessHelper.Setup(x => x.GetProcessName(It.IsAny())).Returns("dotnet.exe"); + this.mockProcessHelper.Setup(x => x.GetProcessName(It.IsAny())).Returns("dotnet.exe"); } [TestCleanup] @@ -122,7 +119,8 @@ public void TestRequestManagerShouldNotInitializeConsoleLoggerIfDesignModeIsSet( new Mock().Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); Assert.IsFalse(this.mockLoggerEvents.EventsSubscribed()); } @@ -203,8 +201,9 @@ public void DiscoverTestsShouldCallTestPlatformAndSucceed() TestRunResultAggregator.Instance, this.mockTestPlatformEventSource.Object, this.inferHelper, - this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockMetricsPublisherTask, + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); this.testRequestManager.DiscoverTests(payload, mockDiscoveryRegistrar.Object, this.protocolConfig); @@ -253,7 +252,8 @@ public void DiscoverTestsShouldPassSameProtocolConfigInRequestData() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); // Act this.testRequestManager.DiscoverTests(payload, mockDiscoveryRegistrar.Object, mockProtocolConfig); @@ -300,7 +300,8 @@ public void DiscoverTestsShouldCollectMetrics() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); // Act @@ -349,7 +350,8 @@ public void DiscoverTestsShouldCollectTargetDeviceLocalMachineIfTargetDeviceStri this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); // Act @@ -392,7 +394,8 @@ public void DiscoverTestsShouldCollectTargetDeviceIfTargetDeviceIsDevice() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); // Act @@ -435,7 +438,8 @@ public void DiscoverTestsShouldCollectTargetDeviceIfTargetDeviceIsEmulator() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); // Act @@ -478,7 +482,8 @@ public void DiscoverTestsShouldCollectCommands() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); CommandLineOptions.Instance.Parallel = true; CommandLineOptions.Instance.EnableCodeCoverage = true; @@ -533,7 +538,8 @@ public void DiscoverTestsShouldCollectTestSettings() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); CommandLineOptions.Instance.SettingsFile = @"c://temp/.testsettings"; @@ -580,7 +586,8 @@ public void DiscoverTestsShouldCollectVsmdiFile() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); CommandLineOptions.Instance.SettingsFile = @"c://temp/.vsmdi"; @@ -627,7 +634,8 @@ public void DiscoverTestsShouldCollectTestRunConfigFile() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); CommandLineOptions.Instance.SettingsFile = @"c://temp/.testrunConfig"; @@ -949,7 +957,8 @@ public void RunTestsShouldCollectCommands() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); CommandLineOptions.Instance.Parallel = true; CommandLineOptions.Instance.EnableCodeCoverage = true; @@ -1016,7 +1025,8 @@ public void RunTestsShouldCollectTelemetryForLegacySettings() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); // Act. this.testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, mockProtocolConfig); @@ -1065,7 +1075,8 @@ public void RunTestsShouldCollectTelemetryForTestSettingsEmbeddedInsideRunSettin this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); // Act. this.testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, mockProtocolConfig); @@ -1112,7 +1123,8 @@ public void RunTestsShouldCollectMetrics() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); // Act. this.testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, mockProtocolConfig); @@ -1159,7 +1171,8 @@ public void RunTestsWithSourcesShouldCallTestPlatformAndSucceed() this.mockTestPlatformEventSource.Object, this.inferHelper, this.mockMetricsPublisherTask, - this.mockProcessHelper.Object); + this.mockProcessHelper.Object, + this.mockAttachmentsProcessingManager.Object); this.testRequestManager.RunTests(payload, mockCustomlauncher.Object, mockRunEventsRegistrar.Object, this.protocolConfig); @@ -2164,6 +2177,102 @@ public void DiscoverTestsShouldOverrideOnlyAssemblyNameIfConsoleLoggerAlreadyPre Assert.IsNotNull(loggerSettingsList[1].CodeBase); } + [TestMethod] + public void ProcessTestRunAttachmentsShouldSucceedWithTelemetryEnabled() + { + var mockEventsHandler = new Mock(); + mockAttachmentsProcessingManager + .Setup(m => m.ProcessTestRunAttachmentsAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) + .Returns((IRequestData r, ICollection a, ITestRunAttachmentsProcessingEventsHandler h, CancellationToken token) => Task.Run(() => + { + r.MetricsCollection.Add(TelemetryDataConstants.NumberOfAttachmentsSentForProcessing, 5); + r.MetricsCollection.Add(TelemetryDataConstants.NumberOfAttachmentsAfterProcessing, 1); + })); + + var payload = new TestRunAttachmentsProcessingPayload() + { + Attachments = new List { new AttachmentSet(new Uri("http://www.bing.com"), "out") }, + CollectMetrics = true + }; + + testRequestManager.ProcessTestRunAttachments(payload, mockEventsHandler.Object, this.protocolConfig); + + mockAttachmentsProcessingManager.Verify(m => m.ProcessTestRunAttachmentsAsync(It.Is(r => r.IsTelemetryOptedIn), payload.Attachments, mockEventsHandler.Object, It.IsAny())); + mockTestPlatformEventSource.Verify(es => es.TestRunAttachmentsProcessingRequestStart()); + mockTestPlatformEventSource.Verify(es => es.TestRunAttachmentsProcessingRequestStop()); + + mockMetricsPublisher.Verify(p => p.PublishMetrics(TelemetryDataConstants.TestAttachmentsProcessingCompleteEvent, + It.Is>(m => m.Count == 2 && + m.ContainsKey(TelemetryDataConstants.NumberOfAttachmentsSentForProcessing) && (int)m[TelemetryDataConstants.NumberOfAttachmentsSentForProcessing] == 5 && + m.ContainsKey(TelemetryDataConstants.NumberOfAttachmentsAfterProcessing) && (int)m[TelemetryDataConstants.NumberOfAttachmentsAfterProcessing] == 1))); + } + + [TestMethod] + public void ProcessTestRunAttachmentsShouldSucceedWithTelemetryDisabled() + { + var mockEventsHandler = new Mock(); + mockAttachmentsProcessingManager + .Setup(m => m.ProcessTestRunAttachmentsAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(true)); + + var payload = new TestRunAttachmentsProcessingPayload() + { + Attachments = new List { new AttachmentSet(new Uri("http://www.bing.com"), "out") }, + CollectMetrics = false + }; + + testRequestManager.ProcessTestRunAttachments(payload, mockEventsHandler.Object, this.protocolConfig); + + mockAttachmentsProcessingManager.Verify(m => m.ProcessTestRunAttachmentsAsync(It.Is(r => !r.IsTelemetryOptedIn), payload.Attachments, mockEventsHandler.Object, It.IsAny())); + mockTestPlatformEventSource.Verify(es => es.TestRunAttachmentsProcessingRequestStart()); + mockTestPlatformEventSource.Verify(es => es.TestRunAttachmentsProcessingRequestStop()); + } + + [TestMethod] + public async Task CancelTestRunAttachmentsProcessingShouldSucceedIfRequestInProgress() + { + var mockEventsHandler = new Mock(); + mockAttachmentsProcessingManager + .Setup(m => m.ProcessTestRunAttachmentsAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) + .Returns((IRequestData r, ICollection a, ITestRunAttachmentsProcessingEventsHandler h, CancellationToken token) => Task.Run(() => + { + int i = 0; + while (!token.IsCancellationRequested) + { + i++; + Console.WriteLine($"Iteration {i}"); + Task.Delay(5).Wait(); + } + + r.MetricsCollection.Add(TelemetryDataConstants.AttachmentsProcessingState, "Canceled"); + })); + + var payload = new TestRunAttachmentsProcessingPayload() + { + Attachments = new List { new AttachmentSet(new Uri("http://www.bing.com"), "out") }, + CollectMetrics = true + }; + + Task task = Task.Run(() => testRequestManager.ProcessTestRunAttachments(payload, mockEventsHandler.Object, this.protocolConfig)); + await Task.Delay(50); + testRequestManager.CancelTestRunAttachmentsProcessing(); + + await task; + + mockAttachmentsProcessingManager.Verify(m => m.ProcessTestRunAttachmentsAsync(It.IsAny(), payload.Attachments, mockEventsHandler.Object, It.IsAny())); + mockTestPlatformEventSource.Verify(es => es.TestRunAttachmentsProcessingRequestStart()); + mockTestPlatformEventSource.Verify(es => es.TestRunAttachmentsProcessingRequestStop()); + + mockMetricsPublisher.Verify(p => p.PublishMetrics(TelemetryDataConstants.TestAttachmentsProcessingCompleteEvent, + It.Is>(m => m.Count == 1 && m.ContainsKey(TelemetryDataConstants.AttachmentsProcessingState) && (string)m[TelemetryDataConstants.AttachmentsProcessingState] == "Canceled"))); + } + + [TestMethod] + public void CancelTestRunAttachmentsProcessingShouldSucceedIfNoRequest() + { + testRequestManager.CancelTestRunAttachmentsProcessing(); + } + private static DiscoveryRequestPayload CreateDiscoveryPayload(string runsettings) { var discoveryPayload = new DiscoveryRequestPayload From 1d3039474ca085b64800e5b85556e4a903213308 Mon Sep 17 00:00:00 2001 From: Codrin-Victor Poienaru Date: Wed, 19 Aug 2020 17:49:50 +0200 Subject: [PATCH 12/12] Fixed code coverage compatibility issue (#2527) Fixed code coverage compatibility issue --- scripts/build/TestPlatform.Dependencies.props | 2 +- scripts/build/TestPlatform.Settings.targets | 2 +- .../DynamicCoverageDataCollectorImpl.cs | 22 +++++++++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/scripts/build/TestPlatform.Dependencies.props b/scripts/build/TestPlatform.Dependencies.props index 288d633c84..fde8e257c3 100644 --- a/scripts/build/TestPlatform.Dependencies.props +++ b/scripts/build/TestPlatform.Dependencies.props @@ -11,7 +11,7 @@ - 16.7.0-dev + 16.7.1-dev 2.1.0 2.1.0 diff --git a/scripts/build/TestPlatform.Settings.targets b/scripts/build/TestPlatform.Settings.targets index 3cea7cbf3e..ab042be951 100644 --- a/scripts/build/TestPlatform.Settings.targets +++ b/scripts/build/TestPlatform.Settings.targets @@ -5,7 +5,7 @@ - 16.7.0 + 16.7.1