diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 95a8a73bd1..c3ee2506eb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -63,13 +63,15 @@ jobs: testResultsFormat: VSTest testResultsFiles: '**\*.trx' condition: succeededOrFailed() - -- job: Linux - pool: - vmImage: 'ubuntu-16.04' - variables: - buildConfiguration: 'Release' - steps: - - script: ./build.sh -c $(buildConfiguration) - displayName: './build.sh -c $(buildConfiguration)' + +# Linux build does not work when we mix runtimes and +# we don't use the results to do anything, skipping it for now +# - job: Linux +# pool: +# vmImage: 'ubuntu-16.04' +# variables: +# buildConfiguration: 'Release' +# steps: +# - script: ./build.sh -c $(buildConfiguration) +# displayName: './build.sh -c $(buildConfiguration)' diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 9ed75970e6..c33d03f594 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -94,6 +94,7 @@ Write-Verbose "Setup build configuration." $TPB_Solution = "TestPlatform.sln" $TPB_TestAssets_Solution = Join-Path $env:TP_ROOT_DIR "test\TestAssets\TestAssets.sln" $TPB_TargetFramework = "net451" +$TPB_TargetFramework472 = "net472" $TPB_TargetFrameworkCore20 = "netcoreapp2.1" $TPB_TargetFrameworkUap = "uap10.0" $TPB_TargetFrameworkNS2_0 = "netstandard2.0" @@ -321,7 +322,7 @@ function Publish-Package Publish-PackageInternal $settingsMigratorProject $TPB_TargetFramework $fullCLRPackageDir Write-Log "Package: Publish src\datacollector\datacollector.csproj" - Publish-PackageInternal $dataCollectorProject $TPB_TargetFramework $fullCLRPackageDir + Publish-PackageInternal $dataCollectorProject $TPB_TargetFramework472 $fullCLRPackageDir Publish-PackageInternal $dataCollectorProject $TPB_TargetFrameworkCore20 $coreCLR20PackageDir # Publish testhost @@ -351,7 +352,7 @@ function Publish-Package Set-ScriptFailedOnError # Copy over the Full CLR built datacollector package assemblies to the Core CLR package folder along with testhost - Publish-PackageInternal $dataCollectorProject $TPB_TargetFramework $fullDestDir + Publish-PackageInternal $dataCollectorProject $TPB_TargetFramework472 $fullDestDir New-Item -ItemType directory -Path $fullCLRPackageDir -Force | Out-Null Copy-Item $testhostFullPackageDir\* $fullCLRPackageDir -Force -recurse @@ -405,12 +406,22 @@ function Publish-Package # Copy Blame Datacollector to Extensions folder. $TPB_TargetFrameworkStandard = "netstandard2.0" $blameDataCollector = Join-Path $env:TP_ROOT_DIR "src\Microsoft.TestPlatform.Extensions.BlameDataCollector\bin\$TPB_Configuration" - $blameDataCollectorNetFull = Join-Path $blameDataCollector $TPB_TargetFramework + $blameDataCollectorNetFull = Join-Path $blameDataCollector $TPB_TargetFramework472 $blameDataCollectorNetStandard = Join-Path $blameDataCollector $TPB_TargetFrameworkStandard Copy-Item $blameDataCollectorNetFull\Microsoft.TestPlatform.Extensions.BlameDataCollector.dll $fullCLRExtensionsDir -Force Copy-Item $blameDataCollectorNetFull\Microsoft.TestPlatform.Extensions.BlameDataCollector.pdb $fullCLRExtensionsDir -Force Copy-Item $blameDataCollectorNetStandard\Microsoft.TestPlatform.Extensions.BlameDataCollector.dll $coreCLRExtensionsDir -Force Copy-Item $blameDataCollectorNetStandard\Microsoft.TestPlatform.Extensions.BlameDataCollector.pdb $coreCLRExtensionsDir -Force + # we use this to dump processes on netcore + Copy-Item $blameDataCollectorNetStandard\Microsoft.Diagnostics.NETCore.Client.dll $coreCLRExtensionsDir -Force + + # $null = New-Item -Force "$fullCLRExtensionsDir\procdump" -ItemType Directory + # $null = New-Item -Force "$coreCLRExtensionsDir\procdump" -ItemType Directory + # Copy-Item $blameDataCollectorNetFull\procdump.exe $fullCLRExtensionsDir\procdump -Force + # Copy-Item $blameDataCollectorNetFull\procdump64.exe $fullCLRExtensionsDir\procdump -Force + # Copy-Item $blameDataCollectorNetStandard\procdump.exe $coreCLRExtensionsDir\procdump -Force + # Copy-Item $blameDataCollectorNetStandard\procdump64.exe $coreCLRExtensionsDir\procdump -Force + # Copy-Item $blameDataCollectorNetStandard\procdump $coreCLRExtensionsDir\procdump -Force # Copy blame data collector resource dlls if($TPB_LocalizedBuild) { diff --git a/scripts/build.sh b/scripts/build.sh index fe93fa812b..009b5c3d82 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -93,7 +93,7 @@ done # TP_ROOT_DIR=$(cd "$(dirname "$0")"; pwd -P) TP_TOOLS_DIR="$TP_ROOT_DIR/tools" -TP_DOTNET_DIR="${DOTNET_CORE_SDK_DIR:-${TP_TOOLS_DIR}/dotnet}" +TP_DOTNET_DIR="${DOTNET_CORE_SDK_DIR:-${TP_TOOLS_DIR}/dotnet-linux}" TP_PACKAGES_DIR="${NUGET_PACKAGES:-${TP_ROOT_DIR}/packages}" TP_OUT_DIR="$TP_ROOT_DIR/artifacts" TP_PACKAGE_PROJ_DIR="$TP_ROOT_DIR/src/package/package" @@ -186,12 +186,12 @@ function install_cli() chmod u+x $install_script log "install_cli: Get the latest dotnet cli toolset..." - $install_script --install-dir "$TP_TOOLS_DIR/dotnet" --no-path --channel "master" --version $DOTNET_CLI_VERSION + $install_script --install-dir "$TP_DOTNET_DIR" --no-path --channel "master" --version $DOTNET_CLI_VERSION # Get netcoreapp1.1 shared components - $install_script --install-dir "$TP_TOOLS_DIR/dotnet" --no-path --channel "release/2.1.0" --version "2.1.0" --shared-runtime + $install_script --install-dir "$TP_DOTNET_DIR" --no-path --channel "release/2.1.0" --version "2.1.0" --runtime dotnet #log "install_cli: Get shared components which is compatible with dotnet cli version $DOTNET_CLI_VERSION..." - #$install_script --install-dir "$TP_TOOLS_DIR/dotnet" --no-path --channel "master" --version $DOTNET_RUNTIME_VERSION --shared-runtime + #$install_script --install-dir "$TP_DOTNET_DIR" --no-path --channel "master" --version $DOTNET_RUNTIME_VERSION --runtime dotnet fi local dotnet_path=$(_get_dotnet_path) diff --git a/scripts/verify-nupkgs.ps1 b/scripts/verify-nupkgs.ps1 index 8eb5d9adaa..fa76cc696e 100644 --- a/scripts/verify-nupkgs.ps1 +++ b/scripts/verify-nupkgs.ps1 @@ -16,7 +16,7 @@ function Verify-Nuget-Packages($packageDirectory) "Microsoft.NET.Test.Sdk" = 13; "Microsoft.TestPlatform" = 437; "Microsoft.TestPlatform.Build" = 19; - "Microsoft.TestPlatform.CLI" = 317; + "Microsoft.TestPlatform.CLI" = 318; "Microsoft.TestPlatform.Extensions.TrxLogger" = 33; "Microsoft.TestPlatform.ObjectModel" = 62; "Microsoft.TestPlatform.Portable" = 502; diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs index c446b2d82e..5c5ed317a3 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs @@ -12,6 +12,7 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector using System.Xml; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; @@ -45,6 +46,8 @@ public class BlameCollector : DataCollector, ITestExecutionEnvironmentSpecifier private IInactivityTimer inactivityTimer; private TimeSpan inactivityTimespan = TimeSpan.FromMinutes(DefaultInactivityTimeInMinutes); private int testHostProcessId; + private bool dumpWasCollectedByHangDumper; + private string targetFramework; /// /// Initializes a new instance of the class. @@ -62,7 +65,7 @@ public BlameCollector() /// BlameReaderWriter instance. /// /// - /// ProcessDumpUtility instance. + /// IProcessDumpUtility instance. /// /// /// InactivityTimer instance. @@ -138,6 +141,12 @@ public override void Initialize( { this.ValidateAndAddHangBasedProcessDumpParameters(collectHangBasedDumpNode); } + + var tfm = this.configurationElement[Constants.TargetFramework]?.InnerText; + if (!string.IsNullOrWhiteSpace(tfm)) + { + this.targetFramework = tfm; + } } this.attachmentGuid = Guid.NewGuid().ToString().Replace("-", string.Empty); @@ -157,8 +166,10 @@ public override void Initialize( /// private void CollectDumpAndAbortTesthost() { - EqtTrace.Info(string.Format(CultureInfo.CurrentUICulture, Resources.Resources.InactivityTimeout, (int)this.inactivityTimespan.TotalMinutes)); this.inactivityTimerAlreadyFired = true; + var message = string.Format(CultureInfo.CurrentUICulture, Resources.Resources.InactivityTimeout, (int)this.inactivityTimespan.TotalMinutes); + EqtTrace.Warning(message); + this.logger.LogWarning(this.context.SessionDataCollectionContext, message); try { @@ -170,20 +181,20 @@ private void CollectDumpAndAbortTesthost() EqtTrace.Verbose("Inactivity timer is already disposed."); } - if (this.collectProcessDumpOnTrigger) - { - // Detach procdump from the testhost process to prevent testhost process from crashing - // if/when we try to kill the existing proc dump process. - this.processDumpUtility.DetachFromTargetProcess(this.testHostProcessId); - } - try { - this.processDumpUtility.StartHangBasedProcessDump(this.testHostProcessId, this.attachmentGuid, this.GetResultsDirectory(), this.processFullDumpEnabled); + this.processDumpUtility.StartHangBasedProcessDump(this.testHostProcessId, this.attachmentGuid, this.GetTempDirectory(), this.processFullDumpEnabled, this.targetFramework); } catch (Exception ex) { - EqtTrace.Error($"BlameCollector.CollectDumpAndAbortTesthost: Failed with error {ex}"); + ConsoleOutput.Instance.Error(true, $"Blame: Creating hang dump failed with error {ex}."); + } + + if (this.collectProcessDumpOnTrigger) + { + // Detach procdump from the testhost process to prevent testhost process from crashing + // if/when we try to kill the existing proc dump process. + this.processDumpUtility.DetachFromTargetProcess(this.testHostProcessId); } try @@ -191,6 +202,7 @@ private void CollectDumpAndAbortTesthost() var dumpFile = this.processDumpUtility.GetDumpFile(); if (!string.IsNullOrEmpty(dumpFile)) { + this.dumpWasCollectedByHangDumper = true; var fileTransferInformation = new FileTransferInformation(this.context.SessionDataCollectionContext, dumpFile, true, this.fileHelper); this.dataCollectionSink.SendFileAsync(fileTransferInformation); } @@ -207,7 +219,17 @@ private void CollectDumpAndAbortTesthost() try { - Process.GetProcessById(this.testHostProcessId).Kill(); + var p = Process.GetProcessById(this.testHostProcessId); + try + { + if (!p.HasExited) + { + p.Kill(); + } + } + catch (InvalidOperationException) + { + } } catch (Exception ex) { @@ -274,7 +296,7 @@ private void ValidateAndAddHangBasedProcessDumpParameters(XmlElement collectDump break; - case XmlAttribute attribute when string.Equals(attribute.Name, Constants.DumpTypeKey, StringComparison.OrdinalIgnoreCase): + case XmlAttribute attribute when string.Equals(attribute.Name, Constants.HangDumpTypeKey, StringComparison.OrdinalIgnoreCase): if (string.Equals(attribute.Value, Constants.FullConfigurationValue, StringComparison.OrdinalIgnoreCase) || string.Equals(attribute.Value, Constants.MiniConfigurationValue, StringComparison.OrdinalIgnoreCase)) { @@ -365,7 +387,8 @@ private void SessionEndedHandler(object sender, SessionEndEventArgs args) // And send the attachment if (this.testStartCount > this.testEndCount) { - var filepath = Path.Combine(this.GetResultsDirectory(), Constants.AttachmentFileName + "_" + this.attachmentGuid); + var filepath = Path.Combine(this.GetTempDirectory(), Constants.AttachmentFileName + "_" + this.attachmentGuid); + filepath = this.blameReaderWriter.WriteTestSequence(this.testSequence, this.testObjectDictionary, filepath); var fileTranferInformation = new FileTransferInformation(this.context.SessionDataCollectionContext, filepath, true); this.dataCollectionSink.SendFileAsync(fileTranferInformation); @@ -374,7 +397,10 @@ private void SessionEndedHandler(object sender, SessionEndEventArgs args) if (this.collectProcessDumpOnTrigger) { // If there was a test case crash or if we need to collect dump on process exit. - if (this.testStartCount > this.testEndCount || this.collectDumpAlways) + // + // Do not try to collect dump when we already collected one from the hang dump + // we won't dump the killed process again and that would just show a warning on the command line + if ((this.testStartCount > this.testEndCount || this.collectDumpAlways) && !this.dumpWasCollectedByHangDumper) { try { @@ -404,7 +430,6 @@ private void SessionEndedHandler(object sender, SessionEndEventArgs args) if (this.collectProcessDumpOnTrigger) { this.processDumpUtility.DetachFromTargetProcess(this.testHostProcessId); - this.processDumpUtility.TerminateProcess(); } this.DeregisterEvents(); @@ -428,7 +453,7 @@ private void TestHostLaunchedHandler(object sender, TestHostLaunchedEventArgs ar try { - this.processDumpUtility.StartTriggerBasedProcessDump(args.TestHostProcessId, this.attachmentGuid, this.GetResultsDirectory(), this.processFullDumpEnabled); + this.processDumpUtility.StartTriggerBasedProcessDump(args.TestHostProcessId, this.attachmentGuid, this.GetTempDirectory(), this.processFullDumpEnabled, ".NETFramework,Version=v4.0"); } catch (TestPlatformException e) { @@ -481,24 +506,15 @@ private void DeregisterEvents() this.events.TestCaseEnd -= this.EventsTestCaseEnd; } - private string GetResultsDirectory() + private string GetTempDirectory() { - try + var tmp = Path.GetTempPath(); + if (!Directory.Exists(tmp)) { - XmlElement resultsDirectoryElement = this.configurationElement["ResultsDirectory"]; - string resultsDirectory = resultsDirectoryElement != null ? resultsDirectoryElement.InnerText : string.Empty; - - return Environment.ExpandEnvironmentVariables(resultsDirectory); + Directory.CreateDirectory(tmp); } - catch (NullReferenceException exception) - { - if (EqtTrace.IsErrorEnabled) - { - EqtTrace.Error("Blame Collector : " + exception); - } - return string.Empty; - } + return tmp; } } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Constants.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Constants.cs index 219cb5271b..3365773fb1 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Constants.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Constants.cs @@ -53,6 +53,11 @@ internal static class Constants /// public const string DumpModeKey = "CollectDump"; + /// + /// Configuration key name for hang dump mode + /// + public const string HangDumpModeKey = "CollectHangDump"; + /// /// Proc dump 32 bit version /// @@ -63,6 +68,11 @@ internal static class Constants /// public const string Procdump64Process = "procdump64.exe"; + /// + /// Proc dump 64 bit version + /// + public const string ProcdumpUnixProcess = "procdump"; + /// /// Configuration key name for collect dump always /// @@ -85,6 +95,11 @@ internal static class Constants /// public const string DumpTypeKey = "DumpType"; + /// + /// Configuration key name for hang dump type + /// + public const string HangDumpTypeKey = "HangDumpType"; + /// /// Configuration value for true /// @@ -104,5 +119,10 @@ internal static class Constants /// Configuration value for mini /// public const string MiniConfigurationValue = "Mini"; + + /// + /// The target framework of test host. + /// + public const string TargetFramework = "Framework"; } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs new file mode 100644 index 0000000000..cd0e82fa06 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs @@ -0,0 +1,24 @@ +// 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.Extensions.BlameDataCollector +{ + using System; + using System.Runtime.InteropServices; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + internal class CrashDumperFactory : ICrashDumperFactory + { + public ICrashDumper Create(string targetFramework) + { + EqtTrace.Info($"CrashDumperFactory: Creating dumper for {RuntimeInformation.OSDescription} with target framework {targetFramework}."); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + EqtTrace.Info($"CrashDumperFactory: This is Windows, returning ProcDumpCrashDumper that uses ProcDump utility."); + return new ProcDumpCrashDumper(); + } + + throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}"); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/DumpTypeOption.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/DumpTypeOption.cs new file mode 100644 index 0000000000..997c65955d --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/DumpTypeOption.cs @@ -0,0 +1,12 @@ +// 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.Extensions.BlameDataCollector +{ + public enum DumpTypeOption + { + Full, + WithHeap, + Mini, + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs new file mode 100644 index 0000000000..e6d8575529 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs @@ -0,0 +1,49 @@ +// 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.Extensions.BlameDataCollector +{ + using System; + using System.Runtime.InteropServices; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + + internal class HangDumperFactory : IHangDumperFactory + { + public IHangDumper Create(string targetFramework) + { + EqtTrace.Info($"HangDumperFactory: Creating dumper for {RuntimeInformation.OSDescription} with target framework {targetFramework}."); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + EqtTrace.Info($"HangDumperFactory: This is Windows, returning the default WindowsHangDumper that P/Invokes MiniDumpWriteDump."); + return new WindowsHangDumper(); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + if (!string.IsNullOrWhiteSpace(targetFramework) && targetFramework.Contains("v2.1")) + { + EqtTrace.Info($"HangDumperFactory: This is Linux on netcoreapp2.1, returning SigtrapDumper."); + + return new SigtrapDumper(); + } + + EqtTrace.Info($"HangDumperFactory: This is Linux netcoreapp3.1 or newer, returning the standard NETClient library dumper."); + return new NetClientDumper(); + } + + // this is not supported yet + // if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + // { + + // if (frameworkVersion != default && frameworkVersion <= new Version("5.0")) + // { + // return new SigtrapDumper(); + // } + + // EqtTrace.Info($"HangDumperFactory: This is OSX on netcoreapp3.1 or newer, returning the standard NETClient library dumper."); + // return new NetClientDumper(); + // } + throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}"); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs new file mode 100644 index 0000000000..5f0e522d59 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs @@ -0,0 +1,14 @@ +// 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.Extensions.BlameDataCollector +{ + public interface ICrashDumper + { + void AttachToTargetProcess(int processId, string outputFile, DumpTypeOption dumpType); + + void WaitForDumpToFinish(); + + void DetachFromTargetProcess(int processId); + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumperFactory.cs new file mode 100644 index 0000000000..c992ec7e34 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumperFactory.cs @@ -0,0 +1,10 @@ +// 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.Extensions.BlameDataCollector +{ + public interface ICrashDumperFactory + { + ICrashDumper Create(string targetFramework); + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumper.cs new file mode 100644 index 0000000000..53a8c4a695 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumper.cs @@ -0,0 +1,10 @@ +// 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.Extensions.BlameDataCollector +{ + public interface IHangDumper + { + void Dump(int processId, string outputFile, DumpTypeOption dumpType); + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumperFactory.cs new file mode 100644 index 0000000000..02978a2131 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/IHangDumperFactory.cs @@ -0,0 +1,10 @@ +// 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.Extensions.BlameDataCollector +{ + public interface IHangDumperFactory + { + IHangDumper Create(string targetFramework); + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcDumpArgsBuilder.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcDumpArgsBuilder.cs index 8face92e8d..67cf836dd2 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcDumpArgsBuilder.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcDumpArgsBuilder.cs @@ -23,7 +23,7 @@ public interface IProcDumpArgsBuilder /// Is full dump enabled /// /// Arguments - string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnumerable procDumpExceptionsList, bool isFullDump = false); + string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnumerable procDumpExceptionsList, bool isFullDump); /// /// Arguments for procdump.exe for getting a dump in case of a testhost hang @@ -38,6 +38,6 @@ public interface IProcDumpArgsBuilder /// Is full dump enabled /// /// Arguments - string BuildHangBasedProcDumpArgs(int processId, string filename, bool isFullDump = false); + string BuildHangBasedProcDumpArgs(int processId, string filename, bool isFullDump); } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs index f1c56abb08..aff139119f 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs @@ -28,7 +28,10 @@ public interface IProcessDumpUtility /// /// Is full dump enabled /// - void StartTriggerBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump = false); + /// + /// The target framework of the process + /// + void StartTriggerBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump, string targetFramework); /// /// Launch proc dump process to capture dump in case of a testhost hang and wait for it to exit @@ -45,7 +48,10 @@ public interface IProcessDumpUtility /// /// Is full dump enabled /// - void StartHangBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump = false); + /// + /// The target framework of the process + /// + void StartHangBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump, string targetFramework); /// /// Detaches the proc dump process from the target process @@ -56,10 +62,5 @@ public interface IProcessDumpUtility /// Process Id of the process to detach from /// void DetachFromTargetProcess(int targetProcessId); - - /// - /// Terminate the proc dump process - /// - void TerminateProcess(); } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj index 878c99bfc6..9d7a332540 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj @@ -9,10 +9,12 @@ Microsoft.TestPlatform.Extensions.BlameDataCollector - netstandard2.0;net451 + netstandard2.0;net472 netstandard2.0 true true + + true @@ -40,5 +42,21 @@ Resources.Designer.cs + + + 0.2.0-preview.20220.1 + + + diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientDumper.cs new file mode 100644 index 0000000000..2455e0e37f --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientDumper.cs @@ -0,0 +1,16 @@ +// 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.Extensions.BlameDataCollector +{ + using Microsoft.Diagnostics.NETCore.Client; + + internal class NetClientDumper : IHangDumper + { + public void Dump(int processId, string outputFile, DumpTypeOption type) + { + var client = new DiagnosticsClient(processId); + client.WriteDump(type == DumpTypeOption.Full ? DumpType.Full : DumpType.Normal, outputFile); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs index 69b6e8933b..ade08f80e1 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs @@ -9,7 +9,7 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector public class ProcDumpArgsBuilder : IProcDumpArgsBuilder { /// - public string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnumerable procDumpExceptionsList, bool isFullDump = false) + public string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnumerable procDumpExceptionsList, bool isFullDump) { // -accepteula: Auto accept end-user license agreement // -e: Write a dump when the process encounters an unhandled exception. Include the 1 to create dump on first chance exceptions. @@ -35,7 +35,7 @@ public string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnu } /// - public string BuildHangBasedProcDumpArgs(int processId, string filename, bool isFullDump = false) + public string BuildHangBasedProcDumpArgs(int processId, string filename, bool isFullDump) { // -accepteula: Auto accept end-user license agreement // -ma: Full dump argument. diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs new file mode 100644 index 0000000000..9baf0ad0f8 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs @@ -0,0 +1,230 @@ +// 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.Extensions.BlameDataCollector +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + + public class ProcDumpCrashDumper : ICrashDumper + { + private static readonly IEnumerable ProcDumpExceptionsList = new List() + { + "STACK_OVERFLOW", + "ACCESS_VIOLATION" + }; + + private IProcessHelper processHelper; + private IFileHelper fileHelper; + private IEnvironment environment; + private Process procDumpProcess; + private string tempDirectory; + private string dumpFileName; + private INativeMethodsHelper nativeMethodsHelper; + + public ProcDumpCrashDumper() + : this(new ProcessHelper(), new FileHelper(), new PlatformEnvironment(), new NativeMethodsHelper()) + { + } + + public ProcDumpCrashDumper(IProcessHelper processHelper, IFileHelper fileHelper, IEnvironment environment, INativeMethodsHelper nativeMethodsHelper) + { + this.processHelper = processHelper; + this.fileHelper = fileHelper; + this.environment = environment; + this.nativeMethodsHelper = nativeMethodsHelper; + } + + protected Action OutputReceivedCallback => (process, data) => + { + // useful for visibility when debugging this tool + // Console.ForegroundColor = ConsoleColor.Cyan; + // Console.WriteLine(data); + // Console.ForegroundColor = ConsoleColor.White; + // Log all standard output message of procdump in diag files. + // Otherwise they end up coming on console in pipleine. + if (EqtTrace.IsInfoEnabled) + { + EqtTrace.Info("ProcDumpCrashDumper.OutputReceivedCallback: Output received from procdump process: " + data); + } + }; + + /// + public void WaitForDumpToFinish() + { + if (this.processHelper == null) + { + EqtTrace.Info($"ProcDumpCrashDumper.WaitForDumpToFinish: ProcDump was not previously attached, this might indicate error during setup, look for ProcDumpCrashDumper.AttachToTargetProcess."); + } + + this.processHelper?.WaitForProcessExit(this.procDumpProcess); + } + + /// + public void AttachToTargetProcess(int processId, string outputFile, DumpTypeOption dumpType) + { + EqtTrace.Info($"ProcDumpCrashDumper.AttachToTargetProcess: Attaching to process '{processId}' to dump into '{outputFile}'."); + + // Procdump will append .dmp at the end of the dump file. We generate this internally so it is rather a safety check. + if (!outputFile.EndsWith(".dmp", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Procdump crash dump file must end with .dmp extension."); + } + + if (!this.TryGetProcDumpExecutable(processId, out var procDumpPath)) + { + var err = $"{procDumpPath} could not be found, please set PROCDUMP_PATH environment variable to a directory that contains {procDumpPath} executable, or make sure that the executable is available on PATH."; + ConsoleOutput.Instance.Warning(false, err); + EqtTrace.Error($"ProcDumpCrashDumper.AttachToTargetProcess: {err}"); + return; + } + + this.tempDirectory = Path.GetDirectoryName(outputFile); + this.dumpFileName = Path.GetFileNameWithoutExtension(outputFile); + + string procDumpArgs = new ProcDumpArgsBuilder().BuildTriggerBasedProcDumpArgs( + processId, + this.dumpFileName, + ProcDumpExceptionsList, + isFullDump: dumpType == DumpTypeOption.Full); + + EqtTrace.Info($"ProcDumpCrashDumper.AttachToTargetProcess: Running ProcDump with arguments: '{procDumpArgs}'."); + this.procDumpProcess = this.processHelper.LaunchProcess( + procDumpPath, + procDumpArgs, + this.tempDirectory, + null, + null, + null, + this.OutputReceivedCallback) as Process; + + EqtTrace.Info($"ProcDumpCrashDumper.AttachToTargetProcess: ProcDump started as process with id '{this.procDumpProcess.Id}'."); + } + + /// + public void DetachFromTargetProcess(int targetProcessId) + { + if (this.procDumpProcess == null) + { + EqtTrace.Info($"ProcDumpCrashDumper.DetachFromTargetProcess: ProcDump was not previously attached, this might indicate error during setup, look for ProcDumpCrashDumper.AttachToTargetProcess."); + return; + } + + try + { + EqtTrace.Info($"ProcDumpCrashDumper.DetachFromTargetProcess: ProcDump detaching from target process '{targetProcessId}'."); + new Win32NamedEvent($"Procdump-{targetProcessId}").Set(); + } + finally + { + try + { + EqtTrace.Info("ProcDumpCrashDumper.DetachFromTargetProcess: Attempting to kill proc dump process."); + this.processHelper.TerminateProcess(this.procDumpProcess); + } + catch (Exception e) + { + EqtTrace.Warning($"ProcDumpCrashDumper.DetachFromTargetProcess: Failed to kill proc dump process with exception {e}"); + } + } + } + + /// + /// Try get proc dump executable path from env variable or PATH, if it does not success the result is false, and the name of the exe we tried to find. + /// + /// + /// Process Id to determine the bittness + /// + /// + /// Path to procdump or the name of the executable we tried to resolve when we don't find it + /// + /// proc dump executable path + private bool TryGetProcDumpExecutable(int processId, out string path) + { + var procdumpDirectory = Environment.GetEnvironmentVariable("PROCDUMP_PATH"); + var searchPath = false; + if (string.IsNullOrWhiteSpace(procdumpDirectory)) + { + EqtTrace.Verbose("ProcDumpCrashDumper.GetProcDumpExecutable: PROCDUMP_PATH env variable is empty will try to run ProcDump from PATH."); + searchPath = true; + } + else if (!Directory.Exists(procdumpDirectory)) + { + EqtTrace.Verbose($"ProcDumpCrashDumper.GetProcDumpExecutable: PROCDUMP_PATH env variable '{procdumpDirectory}' is not a directory, or the directory does not exist. Will try to run ProcDump from PATH."); + searchPath = true; + } + + string filename; + if (this.environment.OperatingSystem == PlatformOperatingSystem.Windows) + { + // Launch proc dump according to process architecture + if (this.environment.Architecture == PlatformArchitecture.X86) + { + filename = Constants.ProcdumpProcess; + } + else + { + filename = this.nativeMethodsHelper.Is64Bit(this.processHelper.GetProcessHandle(processId)) ? + Constants.Procdump64Process : Constants.ProcdumpProcess; + } + } + else if (this.environment.OperatingSystem == PlatformOperatingSystem.Unix) + { + filename = Constants.ProcdumpUnixProcess; + } + else + { + throw new NotSupportedException($"Not supported platform {this.environment.OperatingSystem}"); + } + + if (!searchPath) + { + var candidatePath = Path.Combine(procdumpDirectory, filename); + if (File.Exists(candidatePath)) + { + EqtTrace.Verbose($"ProcDumpCrashDumper.GetProcDumpExecutable: Path to ProcDump '{candidatePath}' exists, using that."); + path = candidatePath; + return true; + } + + EqtTrace.Verbose($"ProcDumpCrashDumper.GetProcDumpExecutable: Path '{candidatePath}' does not exist will try to run {filename} from PATH."); + } + + if (this.TryGetExecutablePath(filename, out var p)) + { + EqtTrace.Verbose($"ProcDumpCrashDumper.GetProcDumpExecutable: Resolved {filename} to {p} from PATH."); + path = p; + return true; + } + + EqtTrace.Verbose($"ProcDumpCrashDumper.GetProcDumpExecutable: Could not find {filename} on PATH."); + path = filename; + return false; + } + + private bool TryGetExecutablePath(string executable, out string executablePath) + { + executablePath = string.Empty; + var pathString = Environment.GetEnvironmentVariable("PATH"); + foreach (string path in pathString.Split(Path.PathSeparator)) + { + string exeFullPath = Path.Combine(path.Trim(), executable); + if (this.fileHelper.Exists(exeFullPath)) + { + executablePath = exeFullPath; + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs index fbbb973ae9..251e6029f1 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs @@ -4,42 +4,37 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { using System; - using System.Collections.Generic; - using System.Diagnostics; using System.IO; + using System.Runtime.InteropServices; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; + using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; - public class ProcessDumpUtility : IProcessDumpUtility + internal class ProcessDumpUtility : IProcessDumpUtility { - private static readonly IEnumerable ProcDumpExceptionsList = new List() - { - "STACK_OVERFLOW", - "ACCESS_VIOLATION" - }; - - private IProcessHelper processHelper; - private IFileHelper fileHelper; - private IEnvironment environment; - private Process procDumpProcess; - private string testResultsDirectory; - private string dumpFileName; - private INativeMethodsHelper nativeMethodsHelper; + private readonly IProcessHelper processHelper; + private readonly IFileHelper fileHelper; + private readonly IHangDumperFactory hangDumperFactory; + private readonly ICrashDumperFactory crashDumperFactory; + private ICrashDumper crashDumper; + private string hangDumpPath; + private string crashDumpPath; + private bool wasHangDumped; public ProcessDumpUtility() - : this(new ProcessHelper(), new FileHelper(), new PlatformEnvironment(), new NativeMethodsHelper()) + : this(new ProcessHelper(), new FileHelper(), new HangDumperFactory(), new CrashDumperFactory()) { } - public ProcessDumpUtility(IProcessHelper processHelper, IFileHelper fileHelper, IEnvironment environment, INativeMethodsHelper nativeMethodsHelper) + public ProcessDumpUtility(IProcessHelper processHelper, IFileHelper fileHelper, IHangDumperFactory hangDumperFactory, ICrashDumperFactory crashDumperFactory) { this.processHelper = processHelper; this.fileHelper = fileHelper; - this.environment = environment; - this.nativeMethodsHelper = nativeMethodsHelper; + this.hangDumperFactory = hangDumperFactory; + this.crashDumperFactory = crashDumperFactory; } protected Action OutputReceivedCallback => (process, data) => @@ -55,151 +50,104 @@ public ProcessDumpUtility(IProcessHelper processHelper, IFileHelper fileHelper, /// public string GetDumpFile() { - if (this.procDumpProcess == null) + string dumpPath; + if (!this.wasHangDumped) { - return string.Empty; + this.crashDumper.WaitForDumpToFinish(); + dumpPath = this.crashDumpPath; } - - this.processHelper.WaitForProcessExit(this.procDumpProcess); - - // Dump files can never be more than 1 because procdump will generate single file, but GetFiles function returns an array - var dumpFiles = this.fileHelper.GetFiles(this.testResultsDirectory, this.dumpFileName + "*", SearchOption.TopDirectoryOnly); - if (dumpFiles.Length > 0) + else { - // Log to diagnostics if multiple files just in case - if (dumpFiles.Length != 1) - { - EqtTrace.Warning("ProcessDumpUtility.GetDumpFile: Multiple dump files found."); - } - - return dumpFiles[0]; + dumpPath = this.hangDumpPath; } - if (EqtTrace.IsErrorEnabled) + EqtTrace.Info($"ProcessDumpUtility.GetDumpFile: Looking for dump file '{dumpPath}'."); + var found = this.fileHelper.Exists(dumpPath); + if (found) { - int exitCode; - EqtTrace.Error("ProcessDumpUtility.GetDumpFile: No dump file generated."); - if (this.processHelper.TryGetExitCode(this.procDumpProcess, out exitCode)) - { - EqtTrace.Error("ProcessDumpUtility.GetDumpFile: Proc dump exited with code: {0}", exitCode); - } + EqtTrace.Info($"ProcessDumpUtility.GetDumpFile: Found dump file '{dumpPath}'."); + return dumpPath; } + EqtTrace.Error($"ProcessDumpUtility.GetDumpFile: Dump file '{dumpPath}' was not found."); throw new FileNotFoundException(Resources.Resources.DumpFileNotGeneratedErrorMessage); } /// - public void StartTriggerBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump = false) + public void StartHangBasedProcessDump(int processId, string dumpFileGuid, string tempDirectory, bool isFullDump, string targetFramework) { - this.dumpFileName = $"{this.processHelper.GetProcessName(processId)}_{processId}_{dumpFileGuid}"; - this.testResultsDirectory = testResultsDirectory; - - string procDumpArgs = new ProcDumpArgsBuilder().BuildTriggerBasedProcDumpArgs( - processId, - this.dumpFileName, - ProcDumpExceptionsList, - isFullDump); - - if (EqtTrace.IsInfoEnabled) - { - EqtTrace.Info($"ProcessDumpUtility : The proc dump argument is {procDumpArgs}"); - } + this.HangDump(processId, dumpFileGuid, tempDirectory, isFullDump ? DumpTypeOption.Full : DumpTypeOption.Mini, targetFramework); + } - this.procDumpProcess = this.processHelper.LaunchProcess( - this.GetProcDumpExecutable(processId), - procDumpArgs, - testResultsDirectory, - null, - null, - null, - this.OutputReceivedCallback) as Process; + /// + public void StartTriggerBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump, string targetFramework) + { + this.CrashDump(processId, dumpFileGuid, testResultsDirectory, isFullDump ? DumpTypeOption.Full : DumpTypeOption.Mini, targetFramework); } /// - public void StartHangBasedProcessDump(int processId, string dumpFileGuid, string testResultsDirectory, bool isFullDump = false) + public void DetachFromTargetProcess(int targetProcessId) + { + this.crashDumper?.DetachFromTargetProcess(targetProcessId); + } + + private void CrashDump(int processId, string dumpFileGuid, string tempDirectory, DumpTypeOption dumpType, string targetFramework) { - this.dumpFileName = $"{this.processHelper.GetProcessName(processId)}_{processId}_{dumpFileGuid}_hangdump"; - this.testResultsDirectory = testResultsDirectory; + var dumpPath = this.GetDumpPath(processId, dumpFileGuid, tempDirectory, isHangDump: false, out var processName); - string procDumpArgs = new ProcDumpArgsBuilder().BuildHangBasedProcDumpArgs( - processId, - this.dumpFileName, - isFullDump); + EqtTrace.Info($"ProcessDumpUtility.CrashDump: Creating {dumpType.ToString().ToLowerInvariant()} dump of process {processName} ({processId}) into temporary path '{dumpPath}'."); + this.crashDumpPath = dumpPath; - if (EqtTrace.IsInfoEnabled) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - EqtTrace.Info($"ProcessDumpUtility : The hang based proc dump invocation argument is {procDumpArgs}"); + throw new NotSupportedException($"Operating system {RuntimeInformation.OSDescription} is not supported for crash dumps."); } - this.procDumpProcess = this.processHelper.LaunchProcess( - this.GetProcDumpExecutable(processId), - procDumpArgs, - testResultsDirectory, - null, - null, - null, - this.OutputReceivedCallback) as Process; + this.crashDumper = this.crashDumperFactory.Create(targetFramework); + ConsoleOutput.Instance.Information(false, $"Blame: Attaching crash dump utility to process {processName} ({processId})."); + this.crashDumper.AttachToTargetProcess(processId, dumpPath, dumpType); } - /// - public void DetachFromTargetProcess(int targetProcessId) + private void HangDump(int processId, string dumpFileGuid, string tempDirectory, DumpTypeOption dumpType, string targetFramework) { - new Win32NamedEvent($"Procdump-{targetProcessId}").Set(); - } + this.wasHangDumped = true; + + // the below format is extremely ugly maybe we can use: + + // https://github.com/microsoft/testfx/issues/678 + // which will order the files correctly gives more info when transported out of + // the context of the run, and keeps the file name unique-enough for our purposes" + // $"{processName}_{processId}_{dumpFileGuid}_hangdump.dmp" + // var dumpFileName = $"crash_{processName}_{DateTime.Now:yyyyMMddTHHmmss}_{processId}.dmp"; + // var dumpFileName = $"{prefix}_{processName}_{DateTime.Now:yyyyMMddTHHmmss}_{processId}.dmp"; + var dumpPath = this.GetDumpPath(processId, dumpFileGuid, tempDirectory, isHangDump: true, out var processName); + + EqtTrace.Info($"ProcessDumpUtility.HangDump: Creating {dumpType.ToString().ToLowerInvariant()} dump of process {processName} ({processId}) into temporary path '{dumpPath}'."); + this.hangDumpPath = dumpPath; + + var dumper = this.hangDumperFactory.Create(targetFramework); - /// - public void TerminateProcess() - { try { - EqtTrace.Info("ProcessDumpUtility : Attempting to kill proc dump process."); - this.processHelper.TerminateProcess(this.procDumpProcess); + ConsoleOutput.Instance.Information(false, $"Blame: Creating hang dump of process {processName} ({processId})."); + dumper.Dump(processId, dumpPath, dumpType); + EqtTrace.Info($"ProcessDumpUtility.HangDump: Process {processName} ({processId}) was dumped into temporary path '{dumpPath}'."); } - catch (Exception e) + catch (Exception ex) { - EqtTrace.Warning($"ProcessDumpUtility : Failed to kill proc dump process with exception {e}"); + EqtTrace.Error($"Blame: Failed with error {ex}."); + throw; } } - /// - /// Get proc dump executable path - /// - /// - /// Process Id - /// - /// proc dump executable path - private string GetProcDumpExecutable(int processId) + private string GetDumpPath(int processId, string dumpFileGuid, string tempDirectory, bool isHangDump, out string processName) { - var procdumpPath = Environment.GetEnvironmentVariable("PROCDUMP_PATH"); + processName = this.processHelper.GetProcessName(processId); + var suffix = isHangDump ? "hang" : "crash"; + var dumpFileName = $"{processName}_{processId}_{dumpFileGuid}_{suffix}dump.dmp"; - if (!string.IsNullOrWhiteSpace(procdumpPath)) - { - string filename = string.Empty; - - // Launch proc dump according to process architecture - if (this.environment.Architecture == PlatformArchitecture.X86) - { - filename = Constants.ProcdumpProcess; - } - else - { - filename = this.nativeMethodsHelper.Is64Bit(this.processHelper.GetProcessHandle(processId)) ? - Constants.Procdump64Process : Constants.ProcdumpProcess; - } - - var procDumpExe = Path.Combine(procdumpPath, filename); - - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose("Using proc dump at: {0}", procDumpExe); - } - - return procDumpExe; - } - else - { - throw new TestPlatformException(Resources.Resources.ProcDumpEnvVarEmpty); - } + var path = Path.GetFullPath(tempDirectory); + return Path.Combine(path, dumpFileName); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs new file mode 100644 index 0000000000..64f6afbb48 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/SigtrapDumper.cs @@ -0,0 +1,15 @@ +// 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.Extensions.BlameDataCollector +{ + using System.Diagnostics; + + internal class SigtrapDumper : IHangDumper + { + public void Dump(int processId, string outputFile, DumpTypeOption type) + { + Process.Start("kill", $"-s SIGTRAP {processId}"); + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs new file mode 100644 index 0000000000..682a92be88 --- /dev/null +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs @@ -0,0 +1,121 @@ +// 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.Extensions.BlameDataCollector +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Runtime.InteropServices; + using Microsoft.Win32.SafeHandles; + + internal class WindowsHangDumper : IHangDumper + { + public void Dump(int processId, string outputFile, DumpTypeOption type) + { + var process = Process.GetProcessById(processId); + CollectDump(process, outputFile, type); + } + + internal static void CollectDump(Process process, string outputFile, DumpTypeOption type) + { + // Open the file for writing + using (var stream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) + { + NativeMethods.MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = default(NativeMethods.MINIDUMP_EXCEPTION_INFORMATION); + + NativeMethods.MINIDUMP_TYPE dumpType = NativeMethods.MINIDUMP_TYPE.MiniDumpNormal; + switch (type) + { + case DumpTypeOption.Full: + dumpType = NativeMethods.MINIDUMP_TYPE.MiniDumpWithFullMemory | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithDataSegs | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithHandleData | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithUnloadedModules | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithFullMemoryInfo | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithThreadInfo | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithTokenInformation; + break; + case DumpTypeOption.WithHeap: + dumpType = NativeMethods.MINIDUMP_TYPE.MiniDumpWithPrivateReadWriteMemory | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithDataSegs | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithHandleData | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithUnloadedModules | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithFullMemoryInfo | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithThreadInfo | + NativeMethods.MINIDUMP_TYPE.MiniDumpWithTokenInformation; + break; + case DumpTypeOption.Mini: + dumpType = NativeMethods.MINIDUMP_TYPE.MiniDumpWithThreadInfo; + break; + } + + // Retry the write dump on ERROR_PARTIAL_COPY + for (int i = 0; i < 5; i++) + { + // Dump the process! + if (NativeMethods.MiniDumpWriteDump(process.Handle, (uint)process.Id, stream.SafeFileHandle, dumpType, ref exceptionInfo, IntPtr.Zero, IntPtr.Zero)) + { + break; + } + else + { + int err = Marshal.GetHRForLastWin32Error(); + if (err != NativeMethods.ERROR_PARTIAL_COPY) + { + Marshal.ThrowExceptionForHR(err); + } + } + } + } + } + + private static class NativeMethods + { + public const int ERROR_PARTIAL_COPY = unchecked((int)0x8007012b); + + [DllImport("Dbghelp.dll", SetLastError = true)] + public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId, SafeFileHandle hFile, MINIDUMP_TYPE DumpType, ref MINIDUMP_EXCEPTION_INFORMATION ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam); + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct MINIDUMP_EXCEPTION_INFORMATION + { + public uint ThreadId; + public IntPtr ExceptionPointers; + public int ClientPointers; + } + + [Flags] +#pragma warning disable SA1201 // Elements must appear in the correct order + public enum MINIDUMP_TYPE : uint +#pragma warning restore SA1201 // Elements must appear in the correct order + { + MiniDumpNormal = 0, + MiniDumpWithDataSegs = 1 << 0, + MiniDumpWithFullMemory = 1 << 1, + MiniDumpWithHandleData = 1 << 2, + MiniDumpFilterMemory = 1 << 3, + MiniDumpScanMemory = 1 << 4, + MiniDumpWithUnloadedModules = 1 << 5, + MiniDumpWithIndirectlyReferencedMemory = 1 << 6, + MiniDumpFilterModulePaths = 1 << 7, + MiniDumpWithProcessThreadData = 1 << 8, + MiniDumpWithPrivateReadWriteMemory = 1 << 9, + MiniDumpWithoutOptionalData = 1 << 10, + MiniDumpWithFullMemoryInfo = 1 << 11, + MiniDumpWithThreadInfo = 1 << 12, + MiniDumpWithCodeSegs = 1 << 13, + MiniDumpWithoutAuxiliaryState = 1 << 14, + MiniDumpWithFullAuxiliaryState = 1 << 15, + MiniDumpWithPrivateWriteCopyMemory = 1 << 16, + MiniDumpIgnoreInaccessibleMemory = 1 << 17, + MiniDumpWithTokenInformation = 1 << 18, + MiniDumpWithModuleHeaders = 1 << 19, + MiniDumpFilterTriage = 1 << 20, + MiniDumpWithAvxXStateContext = 1 << 21, + MiniDumpWithIptTrace = 1 << 22, + MiniDumpValidTypeFlags = (-1) ^ ((~1) << 22) + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/XmlReaderWriter.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/XmlReaderWriter.cs index 9627f15dcb..039bce5805 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/XmlReaderWriter.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/XmlReaderWriter.cs @@ -127,13 +127,13 @@ public List ReadTestSequence(string filePath) foreach (XmlNode node in root) { var testCase = new BlameTestObject - { - FullyQualifiedName = + { + FullyQualifiedName = node.Attributes[Constants.TestNameAttribute].Value, - Source = node.Attributes[Constants.TestSourceAttribute].Value, - DisplayName = node.Attributes[Constants.TestDisplayNameAttribute].Value, - IsCompleted = node.Attributes[Constants.TestCompletedAttribute].Value == "True" ? true : false - }; + Source = node.Attributes[Constants.TestSourceAttribute].Value, + DisplayName = node.Attributes[Constants.TestDisplayNameAttribute].Value, + IsCompleted = node.Attributes[Constants.TestCompletedAttribute].Value == "True" ? true : false + }; testCaseList.Add(testCase); } } diff --git a/src/Microsoft.TestPlatform.ObjectModel/Constants.cs b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs index 15cc686f07..0d6da5d243 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Constants.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Constants.cs @@ -35,6 +35,16 @@ public static class Constants /// public const string BlameCollectDumpKey = "CollectDump"; + /// + /// Name of collect dump option for blame. + /// + public const string BlameCollectHangDumpKey = "CollectHangDump"; + + /// + /// Name of collect hang dump option for blame. + /// + public const string CollectDumpOnTestSessionHang = "CollectDumpOnTestSessionHang"; + /// /// Name of data collection settings node in RunSettings. /// diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/PlatformOperationSystem.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/PlatformOperationSystem.cs index e8cd1ec404..16679783b2 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/PlatformOperationSystem.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/Interfaces/System/PlatformOperationSystem.cs @@ -9,6 +9,7 @@ namespace Microsoft.VisualStudio.TestPlatform.PlatformAbstractions public enum PlatformOperatingSystem { Windows, - Unix + Unix, + OSX } } diff --git a/src/datacollector/datacollector.csproj b/src/datacollector/datacollector.csproj index 2aa91a6f9b..24e3df7c4d 100644 --- a/src/datacollector/datacollector.csproj +++ b/src/datacollector/datacollector.csproj @@ -9,7 +9,7 @@ datacollector - netcoreapp2.1;net451 + netcoreapp2.1;net472 netcoreapp2.1 true AnyCPU diff --git a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs index 8adcf14e02..fbb4c458bb 100644 --- a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs @@ -144,6 +144,7 @@ internal EnableBlameArgumentExecutor(IRunSettingsProvider runSettingsManager, IE public void Initialize(string argument) { var enableDump = false; + var enableHangDump = false; var exceptionMessage = string.Format(CultureInfo.CurrentUICulture, CommandLineResources.InvalidBlameArgument, argument); Dictionary collectDumpParameters = null; @@ -151,21 +152,33 @@ public void Initialize(string argument) { // Get blame argument list. var blameArgumentList = ArgumentProcessorUtilities.GetArgumentList(argument, ArgumentProcessorUtilities.SemiColonArgumentSeparator, exceptionMessage); + Func isDumpCollect = a => Constants.BlameCollectDumpKey.Equals(a, StringComparison.OrdinalIgnoreCase); + Func isHangDumpCollect = a => Constants.BlameCollectHangDumpKey.Equals(a, StringComparison.OrdinalIgnoreCase); // Get collect dump key. - var collectDumpKey = blameArgumentList[0]; - bool isCollectDumpKeyValid = ValidateCollectDumpKey(collectDumpKey); + var hasCollectDumpKey = blameArgumentList.Any(isDumpCollect); + var hasCollectHangDumpKey = blameArgumentList.Any(isHangDumpCollect); // Check if dump should be enabled or not. - enableDump = isCollectDumpKeyValid && IsDumpCollectionSupported(); + enableDump = hasCollectDumpKey && IsDumpCollectionSupported(); - // Get collect dump parameters. - var collectDumpParameterArgs = blameArgumentList.Skip(1); - collectDumpParameters = ArgumentProcessorUtilities.GetArgumentParameters(collectDumpParameterArgs, ArgumentProcessorUtilities.EqualNameValueSeparator, exceptionMessage); + // Check if dump should be enabled or not. + enableHangDump = hasCollectHangDumpKey && IsHangDumpCollectionSupported(); + + if (!enableDump && !enableHangDump) + { + Output.Warning(false, string.Format(CultureInfo.CurrentUICulture, CommandLineResources.BlameIncorrectOption, argument)); + } + else + { + // Get collect dump parameters. + var collectDumpParameterArgs = blameArgumentList.Where(a => !isDumpCollect(a) && !isHangDumpCollect(a)); + collectDumpParameters = ArgumentProcessorUtilities.GetArgumentParameters(collectDumpParameterArgs, ArgumentProcessorUtilities.EqualNameValueSeparator, exceptionMessage); + } } // Initialize blame. - InitializeBlame(enableDump, collectDumpParameters); + InitializeBlame(enableDump, enableHangDump, collectDumpParameters); } /// @@ -181,9 +194,9 @@ public ArgumentProcessorResult Execute() /// /// Initialize blame. /// - /// Enable dump. + /// Enable dump. /// Blame parameters. - private void InitializeBlame(bool enableDump, Dictionary collectDumpParameters) + private void InitializeBlame(bool enableCrashDump, bool enableHangDump, Dictionary collectDumpParameters) { // Add Blame Logger LoggerUtilities.AddLoggerToRunSettings(BlameFriendlyName, null, this.runSettingsManager); @@ -217,9 +230,38 @@ private void InitializeBlame(bool enableDump, Dictionary collect node.InnerText = resultsDirectory; // Add collect dump node in configuration element. - if (enableDump) + if (enableCrashDump) { - AddCollectDumpNode(collectDumpParameters, XmlDocument, outernode); + var dumpParameters = collectDumpParameters + .Where(p => new[] { "CollectAlways", "DumpType" }.Contains(p.Key)) + .ToDictionary(p => p.Key, p => p.Value); + + if (!dumpParameters.ContainsKey("DumpType")) + { + dumpParameters.Add("DumpType", "Full"); + } + + AddCollectDumpNode(dumpParameters, XmlDocument, outernode); + } + + // Add collect hang dump node in configuration element. + if (enableHangDump) + { + var hangDumpParameters = collectDumpParameters + .Where(p => new[] { "TestTimeout", "HangDumpType" }.Contains(p.Key)) + .ToDictionary(p => p.Key, p => p.Value); + + if (!hangDumpParameters.ContainsKey("TestTimeout")) + { + hangDumpParameters.Add("TestTimeout", TimeSpan.FromHours(1).TotalMilliseconds.ToString()); + } + + if (!hangDumpParameters.ContainsKey("HangDumpType")) + { + hangDumpParameters.Add("HangDumpType", "Full"); + } + + AddCollectHangDumpNode(hangDumpParameters, XmlDocument, outernode); } // Add blame configuration element to blame collector. @@ -263,14 +305,15 @@ private string GetResultsDirectory(string settings) } /// - /// Checks if dump collection is supported. + /// Checks if crash dump collection is supported. /// /// Dump collection supported flag. private bool IsDumpCollectionSupported() { - var dumpCollectionSupported = this.environment.OperatingSystem == PlatformOperatingSystem.Windows && - this.environment.Architecture != PlatformArchitecture.ARM64 && - this.environment.Architecture != PlatformArchitecture.ARM; + var dumpCollectionSupported = + this.environment.OperatingSystem == PlatformOperatingSystem.Windows + && this.environment.Architecture != PlatformArchitecture.ARM64 + && this.environment.Architecture != PlatformArchitecture.ARM; if (!dumpCollectionSupported) { @@ -281,20 +324,22 @@ private bool IsDumpCollectionSupported() } /// - /// Check if collect dump key is valid. + /// Checks if hang dump collection is supported. /// - /// Collect dump key. - /// Flag for collect dump key valid or not. - private bool ValidateCollectDumpKey(string collectDumpKey) + /// Dump collection supported flag. + private bool IsHangDumpCollectionSupported() { - var isCollectDumpKeyValid = collectDumpKey != null && collectDumpKey.Equals(Constants.BlameCollectDumpKey, StringComparison.OrdinalIgnoreCase); + var dumpCollectionSupported = + this.environment.OperatingSystem != PlatformOperatingSystem.OSX + && this.environment.Architecture != PlatformArchitecture.ARM64 + && this.environment.Architecture != PlatformArchitecture.ARM; - if (!isCollectDumpKeyValid) + if (!dumpCollectionSupported) { - Output.Warning(false, string.Format(CultureInfo.CurrentUICulture, CommandLineResources.BlameIncorrectOption, collectDumpKey)); + Output.Warning(false, CommandLineResources.BlameCollectDumpTestTimeoutNotSupportedForPlatform); } - return isCollectDumpKeyValid; + return dumpCollectionSupported; } /// @@ -318,6 +363,27 @@ private void AddCollectDumpNode(Dictionary parameters, XmlDocume outernode.AppendChild(dumpNode); } + /// + /// Adds collect dump node in outer node. + /// + /// Parameters. + /// Xml document. + /// Outer node. + private void AddCollectHangDumpNode(Dictionary parameters, XmlDocument XmlDocument, XmlElement outernode) + { + var dumpNode = XmlDocument.CreateElement(Constants.CollectDumpOnTestSessionHang); + if (parameters != null && parameters.Count > 0) + { + foreach (KeyValuePair entry in parameters) + { + var attribute = XmlDocument.CreateAttribute(entry.Key); + attribute.Value = entry.Value; + dumpNode.Attributes.Append(attribute); + } + } + outernode.AppendChild(dumpNode); + } + #endregion } } diff --git a/src/vstest.console/Resources/Resources.Designer.cs b/src/vstest.console/Resources/Resources.Designer.cs index 17706664d3..914815a63d 100644 --- a/src/vstest.console/Resources/Resources.Designer.cs +++ b/src/vstest.console/Resources/Resources.Designer.cs @@ -8,11 +8,9 @@ // //------------------------------------------------------------------------------ -namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Resources -{ +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Resources { using System; - using System.Reflection; - + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -188,7 +186,7 @@ internal static string BatchSizeRequired { } /// - /// Looks up a localized string similar to CollectDump option for Blame is not supported for this platform.. + /// Looks up a localized string similar to Collecting crash dumps by option CollectDump for Blame is not supported for this platform.. /// internal static string BlameCollectDumpNotSupportedForPlatform { get { @@ -196,6 +194,15 @@ internal static string BlameCollectDumpNotSupportedForPlatform { } } + /// + /// Looks up a localized string similar to Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform.. + /// + internal static string BlameCollectDumpTestTimeoutNotSupportedForPlatform { + get { + return ResourceManager.GetString("BlameCollectDumpTestTimeoutNotSupportedForPlatform", resourceCulture); + } + } + /// /// Looks up a localized string similar to The blame parameter specified with blame, {0} is invalid. Ignoring this parameter.. /// diff --git a/src/vstest.console/Resources/Resources.resx b/src/vstest.console/Resources/Resources.resx index 911ce94288..69c4f72391 100644 --- a/src/vstest.console/Resources/Resources.resx +++ b/src/vstest.console/Resources/Resources.resx @@ -162,7 +162,7 @@ The /BatchSize argument requires the size of the batch. Example: /BatchSize:10 - CollectDump option for Blame is not supported for this platform. + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. --BuildBasePath|/BuildBasePath:<BuildBasePath> @@ -738,4 +738,7 @@ The test run parameter argument '{0}' is invalid. Please use the format below. Format: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.cs.xlf b/src/vstest.console/Resources/xlf/Resources.cs.xlf index 82fb135964..91f277dfea 100644 --- a/src/vstest.console/Resources/xlf/Resources.cs.xlf +++ b/src/vstest.console/Resources/xlf/Resources.cs.xlf @@ -1587,9 +1587,9 @@ - CollectDump option for Blame is not supported for this platform. - Možnost CollectDump pro Blame není pro tuto platformu podporovaná. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + Možnost CollectDump pro Blame není pro tuto platformu podporovaná. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ Formát: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.de.xlf b/src/vstest.console/Resources/xlf/Resources.de.xlf index 19a87f42c1..7c6f616094 100644 --- a/src/vstest.console/Resources/xlf/Resources.de.xlf +++ b/src/vstest.console/Resources/xlf/Resources.de.xlf @@ -1587,9 +1587,9 @@ - CollectDump option for Blame is not supported for this platform. - Die CollectDump-Option für Blame wird für diese Plattform nicht unterstützt. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + Die CollectDump-Option für Blame wird für diese Plattform nicht unterstützt. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ Format: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.es.xlf b/src/vstest.console/Resources/xlf/Resources.es.xlf index f58643a025..f4c78a54fc 100644 --- a/src/vstest.console/Resources/xlf/Resources.es.xlf +++ b/src/vstest.console/Resources/xlf/Resources.es.xlf @@ -1590,9 +1590,9 @@ - CollectDump option for Blame is not supported for this platform. - No se admite la opción CollectDump para Blame en esta plataforma. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + No se admite la opción CollectDump para Blame en esta plataforma. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1666,6 +1666,11 @@ Formato: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.fr.xlf b/src/vstest.console/Resources/xlf/Resources.fr.xlf index 873b61b7a3..79180cd137 100644 --- a/src/vstest.console/Resources/xlf/Resources.fr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.fr.xlf @@ -1587,9 +1587,9 @@ - CollectDump option for Blame is not supported for this platform. - L'option CollectDump pour Blame n'est pas prise en charge pour cette plateforme. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + L'option CollectDump pour Blame n'est pas prise en charge pour cette plateforme. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ Format : TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.it.xlf b/src/vstest.console/Resources/xlf/Resources.it.xlf index a7a9617665..a3731612c8 100644 --- a/src/vstest.console/Resources/xlf/Resources.it.xlf +++ b/src/vstest.console/Resources/xlf/Resources.it.xlf @@ -1587,9 +1587,9 @@ - CollectDump option for Blame is not supported for this platform. - L'opzione CollectDump per Blame non è supportata per questa piattaforma. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + L'opzione CollectDump per Blame non è supportata per questa piattaforma. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ Formato: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ja.xlf b/src/vstest.console/Resources/xlf/Resources.ja.xlf index 78b08c8589..316747b07e 100644 --- a/src/vstest.console/Resources/xlf/Resources.ja.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ja.xlf @@ -1587,9 +1587,9 @@ - CollectDump option for Blame is not supported for this platform. - このプラットフォームでは、Blame の CollectDump オプションはサポートされていません。 - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + このプラットフォームでは、Blame の CollectDump オプションはサポートされていません。 + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ 形式: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ko.xlf b/src/vstest.console/Resources/xlf/Resources.ko.xlf index ac3c4e28f4..00c2886a12 100644 --- a/src/vstest.console/Resources/xlf/Resources.ko.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ko.xlf @@ -1587,9 +1587,9 @@ - CollectDump option for Blame is not supported for this platform. - Blame에 대한 CollectDump 옵션이 이 플랫폼에서 지원되지 않습니다. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + Blame에 대한 CollectDump 옵션이 이 플랫폼에서 지원되지 않습니다. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ 형식: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.pl.xlf b/src/vstest.console/Resources/xlf/Resources.pl.xlf index 0917b5986e..c96422c0af 100644 --- a/src/vstest.console/Resources/xlf/Resources.pl.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pl.xlf @@ -1587,9 +1587,9 @@ - CollectDump option for Blame is not supported for this platform. - Opcja CollectDump dla narzędzia Blame nie jest obsługiwana na tej platformie. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + Opcja CollectDump dla narzędzia Blame nie jest obsługiwana na tej platformie. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ Format: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf index e0ce7ff3aa..09af9d0d06 100644 --- a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf @@ -1587,9 +1587,9 @@ Altere o prefixo de nível de diagnóstico do agente de console, como mostrado a - CollectDump option for Blame is not supported for this platform. - A opção CollectDump para Blame não é compatível com esta plataforma. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + A opção CollectDump para Blame não é compatível com esta plataforma. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ Altere o prefixo de nível de diagnóstico do agente de console, como mostrado a Formato: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ru.xlf b/src/vstest.console/Resources/xlf/Resources.ru.xlf index f497ef06b2..6b565e8df6 100644 --- a/src/vstest.console/Resources/xlf/Resources.ru.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ru.xlf @@ -1587,9 +1587,9 @@ - CollectDump option for Blame is not supported for this platform. - Параметр CollectDump для Blame не поддерживается на этой платформе. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + Параметр CollectDump для Blame не поддерживается на этой платформе. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ Формат: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.tr.xlf b/src/vstest.console/Resources/xlf/Resources.tr.xlf index 6c81624453..1f5cc57c62 100644 --- a/src/vstest.console/Resources/xlf/Resources.tr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.tr.xlf @@ -1587,9 +1587,9 @@ Günlükler için izleme düzeyini aşağıda gösterildiği gibi değiştirin - CollectDump option for Blame is not supported for this platform. - Blame için CollectDump seçeneği bu platformda desteklenmez. - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + Blame için CollectDump seçeneği bu platformda desteklenmez. + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ Günlükler için izleme düzeyini aşağıda gösterildiği gibi değiştirin Biçim: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.xlf b/src/vstest.console/Resources/xlf/Resources.xlf index d9147b5b1e..982ccc1780 100644 --- a/src/vstest.console/Resources/xlf/Resources.xlf +++ b/src/vstest.console/Resources/xlf/Resources.xlf @@ -778,7 +778,7 @@ - CollectDump option for Blame is not supported for this platform. + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. CollectDump option for Blame is not supported for this platform. @@ -854,6 +854,11 @@ Format : TestRunParameters.Parameter(name="<name>", value="<value>") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf index 6c25c30bec..adbf83e90d 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf @@ -1587,9 +1587,9 @@ - CollectDump option for Blame is not supported for this platform. - 此平台不支持用于追责的 CollectDump 选项。 - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + 此平台不支持用于追责的 CollectDump 选项。 + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1663,6 +1663,11 @@ 格式: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf index 20f977da4b..4ab044cf95 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf @@ -1588,9 +1588,9 @@ - CollectDump option for Blame is not supported for this platform. - 對此平台不支援 Blame 的 CollectDump 選項。 - + Collecting crash dumps by option CollectDump for Blame is not supported for this platform. + 對此平台不支援 Blame 的 CollectDump 選項。 + The blame parameter specified with blame, {0} is invalid. Ignoring this parameter. @@ -1664,6 +1664,11 @@ 格式: TestRunParameters.Parameter(name=\"<name>\", value=\"<value>\") + + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + Collecting hang dumps by option CollectDump with TestTimeout for Blame is not supported for this platform. + + \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs index 4cb8180e67..367010a292 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs @@ -171,7 +171,7 @@ public void InitializeWithDumpForHangShouldCaptureADumpOnTimeout() this.mockFileHelper.Setup(x => x.Exists(It.Is(y => y == "abc_hang.dmp"))).Returns(true); this.mockFileHelper.Setup(x => x.GetFullPath(It.Is(y => y == "abc_hang.dmp"))).Returns("abc_hang.dmp"); - this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Returns(dumpFile); this.mockDataCollectionSink.Setup(x => x.SendFileAsync(It.IsAny())).Callback(() => hangBasedDumpcollected.Set()); @@ -183,7 +183,7 @@ public void InitializeWithDumpForHangShouldCaptureADumpOnTimeout() this.context); hangBasedDumpcollected.Wait(1000); - this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); this.mockProcessDumpUtility.Verify(x => x.GetDumpFile(), Times.Once); this.mockDataCollectionSink.Verify(x => x.SendFileAsync(It.Is(y => y.Path == dumpFile)), Times.Once); } @@ -205,7 +205,7 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfGet this.mockFileHelper.Setup(x => x.Exists(It.Is(y => y == "abc_hang.dmp"))).Returns(true); this.mockFileHelper.Setup(x => x.GetFullPath(It.Is(y => y == "abc_hang.dmp"))).Returns("abc_hang.dmp"); - this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Callback(() => hangBasedDumpcollected.Set()).Throws(new Exception("Some exception")); this.blameDataCollector.Initialize( @@ -216,7 +216,7 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfGet this.context); hangBasedDumpcollected.Wait(1000); - this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); this.mockProcessDumpUtility.Verify(x => x.GetDumpFile(), Times.Once); } @@ -238,7 +238,7 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfAtt this.mockFileHelper.Setup(x => x.Exists(It.Is(y => y == "abc_hang.dmp"))).Returns(true); this.mockFileHelper.Setup(x => x.GetFullPath(It.Is(y => y == "abc_hang.dmp"))).Returns("abc_hang.dmp"); - this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); + this.mockProcessDumpUtility.Setup(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); this.mockProcessDumpUtility.Setup(x => x.GetDumpFile()).Returns(dumpFile); this.mockDataCollectionSink.Setup(x => x.SendFileAsync(It.IsAny())).Callback(() => hangBasedDumpcollected.Set()).Throws(new Exception("Some other exception")); @@ -250,7 +250,7 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfAtt this.context); hangBasedDumpcollected.Wait(1000); - this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); this.mockProcessDumpUtility.Verify(x => x.GetDumpFile(), Times.Once); this.mockDataCollectionSink.Verify(x => x.SendFileAsync(It.Is(y => y.Path == dumpFile)), Times.Once); } @@ -394,13 +394,13 @@ public void TriggerSessionEndedHandlerShouldEnsureProcDumpProcessIsTerminated() this.context); // Mock proc dump utility terminate process call - this.mockProcessDumpUtility.Setup(x => x.TerminateProcess()); + this.mockProcessDumpUtility.Setup(x => x.DetachFromTargetProcess(It.IsAny())); // Raise this.mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(this.dataCollectionContext)); // Verify GetDumpFiles Call - this.mockProcessDumpUtility.Verify(x => x.TerminateProcess(), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.DetachFromTargetProcess(It.IsAny()), Times.Once); } /// @@ -501,7 +501,7 @@ public void TriggerTestHostLaunchedHandlerShouldStartProcDumpUtilityIfProcDumpEn this.mockDataColectionEvents.Raise(x => x.TestHostLaunched += null, new TestHostLaunchedEventArgs(this.dataCollectionContext, 1234)); // Verify StartProcessDumpCall - this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), false)); + this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), false, It.IsAny())); } /// @@ -522,7 +522,7 @@ public void TriggerTestHostLaunchedHandlerShouldStartProcDumpUtilityForFullDumpI this.mockDataColectionEvents.Raise(x => x.TestHostLaunched += null, new TestHostLaunchedEventArgs(this.dataCollectionContext, 1234)); // Verify StartProcessDumpCall - this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), true)); + this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), true, It.IsAny())); } /// @@ -551,7 +551,7 @@ public void TriggerTestHostLaunchedHandlerShouldStartProcDumpUtilityForFullDumpI this.mockDataColectionEvents.Raise(x => x.TestHostLaunched += null, new TestHostLaunchedEventArgs(this.dataCollectionContext, 1234)); // Verify StartProcessDumpCall - this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), true)); + this.mockProcessDumpUtility.Verify(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), true, It.IsAny())); } /// @@ -648,7 +648,7 @@ public void TriggerTestHostLaunchedHandlerShouldCatchTestPlatFormExceptionsAndRe // Make StartProcessDump throw exception var tpex = new TestPlatformException("env var exception"); - this.mockProcessDumpUtility.Setup(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), false)) + this.mockProcessDumpUtility.Setup(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), false, It.IsAny())) .Throws(tpex); // Raise TestHostLaunched @@ -674,7 +674,7 @@ public void TriggerTestHostLaunchedHandlerShouldCatchAllUnexpectedExceptionsAndR // Make StartProcessDump throw exception var ex = new Exception("start process failed"); - this.mockProcessDumpUtility.Setup(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), false)) + this.mockProcessDumpUtility.Setup(x => x.StartTriggerBasedProcessDump(1234, It.IsAny(), It.IsAny(), false, It.IsAny())) .Throws(ex); // Raise TestHostLaunched diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj index d069e68891..2852753cfa 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests.csproj @@ -9,7 +9,7 @@ Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests - netcoreapp2.1;net451 + netcoreapp2.1;net472 Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests Exe true diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcDumpArgsBuilderTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcDumpArgsBuilderTests.cs index bd8efcaa7c..5ea0ade9a7 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcDumpArgsBuilderTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcDumpArgsBuilderTests.cs @@ -16,7 +16,7 @@ public class ProcDumpArgsBuilderTests public void BuildHangBasedProcDumpArgsShouldCreateCorrectArgString() { var procDumpArgsBuilder = new ProcDumpArgsBuilder(); - var argString = procDumpArgsBuilder.BuildHangBasedProcDumpArgs(this.defaultProcId, this.defaultDumpFileName); + var argString = procDumpArgsBuilder.BuildHangBasedProcDumpArgs(this.defaultProcId, this.defaultDumpFileName, false); Assert.AreEqual("-accepteula -n 1 1234 dump.dmp", argString); } @@ -32,7 +32,7 @@ public void BuildHangBasedProcDumpArgsWithFullDumpEnabledShouldCreateCorrectArgS public void BuildTriggerBasedProcDumpArgsShouldCreateCorrectArgString() { var procDumpArgsBuilder = new ProcDumpArgsBuilder(); - var argString = procDumpArgsBuilder.BuildTriggerBasedProcDumpArgs(this.defaultProcId, this.defaultDumpFileName, new List { "a", "b" }); + var argString = procDumpArgsBuilder.BuildTriggerBasedProcDumpArgs(this.defaultProcId, this.defaultDumpFileName, new List { "a", "b" }, false); Assert.AreEqual("-accepteula -e 1 -g -t -f a -f b 1234 dump.dmp", argString); } diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs index f22af4865a..3ef927e5ad 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs @@ -6,9 +6,6 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests using System; using System.Diagnostics; using System.IO; - - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -23,9 +20,8 @@ public class ProcessDumpUtilityTests { private Mock mockFileHelper; private Mock mockProcessHelper; - private Mock mockProcDumpProcess; - private Mock mockPlatformEnvironment; - private Mock mockNativeMethodsHelper; + private Mock mockHangDumperFactory; + private Mock mockCrashDumperFactory; /// /// Initializes a new instance of the class. @@ -34,11 +30,8 @@ public ProcessDumpUtilityTests() { this.mockFileHelper = new Mock(); this.mockProcessHelper = new Mock(); - this.mockProcDumpProcess = new Mock(); - this.mockPlatformEnvironment = new Mock(); - this.mockNativeMethodsHelper = new Mock(); - - Environment.SetEnvironmentVariable("PROCDUMP_PATH", "D:\\procdump"); + this.mockHangDumperFactory = new Mock(); + this.mockCrashDumperFactory = new Mock(); } /// @@ -56,297 +49,23 @@ public void GetDumpFileWillThrowExceptionIfNoDumpfile() .Returns(new string[] { }); this.mockProcessHelper.Setup(x => x.GetProcessName(processId)) .Returns(process); - this.mockProcessHelper.Setup(x => x.LaunchProcess(It.IsAny(), It.IsAny(), It.IsAny(), null, null, null, It.IsAny>())) - .Returns(this.mockProcDumpProcess.Object); - - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - processDumpUtility.StartTriggerBasedProcessDump(processId, guid, testResultsDirectory); - - var ex = Assert.ThrowsException(() => processDumpUtility.GetDumpFile()); - Assert.AreEqual(ex.Message, Resources.Resources.DumpFileNotGeneratedErrorMessage); - } - - /// - /// GetDumpFile will return empty list of strings if proc dump never started - /// - [TestMethod] - public void GetDumpFileWillReturnEmptyIfProcDumpDidntStart() - { - var guid = "guid"; - var process = "process"; - var processId = 12345; - var testResultsDirectory = "D:\\TestResults"; - - this.mockProcessHelper.Setup(x => x.LaunchProcess(It.IsAny(), It.IsAny(), It.IsAny(), null, null, null, It.IsAny>())) - .Throws(new Exception()); - this.mockProcessHelper.Setup(x => x.GetProcessName(processId)) - .Returns(process); - - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - Assert.ThrowsException(() => processDumpUtility.StartTriggerBasedProcessDump(processId, guid, testResultsDirectory)); - Assert.AreEqual(string.Empty, processDumpUtility.GetDumpFile()); - } - - /// - /// GetDumpFile will wait for proc dump process to exit before getting file - /// - [TestMethod] - public void GetDumpFileWillWaitForProcessToExitAndGetDumpFile() - { - var guid = "guid"; - var process = "process"; - var processId = 12345; - var testResultsDirectory = "D:\\TestResults"; - - this.mockFileHelper.Setup(x => x.GetFiles(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(new string[] { "dump.dmp" }); - this.mockProcessHelper.Setup(x => x.GetProcessName(processId)) - .Returns(process); - this.mockProcessHelper.Setup(x => x.LaunchProcess(It.IsAny(), It.IsAny(), It.IsAny(), null, null, null, It.IsAny>())) - .Returns(this.mockProcDumpProcess.Object); - - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - processDumpUtility.StartTriggerBasedProcessDump(processId, guid, testResultsDirectory); - processDumpUtility.GetDumpFile(); - - this.mockProcessHelper.Verify(x => x.WaitForProcessExit(It.IsAny()), Times.Once); - } - - /// - /// StartProcessDump should start proc dump binary with correct arguments, while GetDumpFile returns full path - /// - [TestMethod] - public void StartProcessDumpWillStartProcDumpExeWithCorrectParamsAndGetDumpFileReturnsFullPath() - { - var guid = "guid"; - var process = "process"; - var processId = 12345; - var filename = $"{process}_{processId}_{guid}.dmp"; - var args = $"-accepteula -e 1 -g -t -f STACK_OVERFLOW -f ACCESS_VIOLATION {processId} {filename}"; - var testResultsDirectory = "D:\\TestResults"; - - this.mockProcessHelper.Setup(x => x.LaunchProcess(It.IsAny(), It.IsAny(), It.IsAny(), null, null, null, It.IsAny>())) - .Returns(this.mockProcDumpProcess.Object); - this.mockProcessHelper.Setup(x => x.GetProcessName(processId)) - .Returns(process); - - this.mockFileHelper.Setup(x => x.GetFiles(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(new string[] { Path.Combine(testResultsDirectory, filename) }); - - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - processDumpUtility.StartTriggerBasedProcessDump(processId, guid, testResultsDirectory); - - this.mockProcessHelper.Verify(x => x.LaunchProcess(It.IsAny(), args, It.IsAny(), null, null, null, It.IsAny>()), Times.Once); - Assert.AreEqual(Path.Combine(testResultsDirectory, filename), processDumpUtility.GetDumpFile()); - } - - /// - /// StartProcessDump should start proc dump binary with correct full dump arguments, while GetDumpFile returns full path - /// - [TestMethod] - public void StartProcessDumpWillStartProcDumpExeWithCorrectParamsForFullDump() - { - var guid = "guid"; - var process = "process"; - var processId = 12345; - var filename = $"{process}_{processId}_{guid}.dmp"; - var args = $"-accepteula -e 1 -g -t -ma -f STACK_OVERFLOW -f ACCESS_VIOLATION {processId} {filename}"; - var testResultsDirectory = "D:\\TestResults"; - - this.mockProcessHelper.Setup(x => x.LaunchProcess(It.IsAny(), It.IsAny(), It.IsAny(), null, null, null, It.IsAny>())) - .Returns(this.mockProcDumpProcess.Object); - this.mockProcessHelper.Setup(x => x.GetProcessName(processId)) - .Returns(process); - this.mockFileHelper.Setup(x => x.GetFiles(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(new string[] { Path.Combine(testResultsDirectory, filename) }); - - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - processDumpUtility.StartTriggerBasedProcessDump(processId, guid, testResultsDirectory, isFullDump: true); - - this.mockProcessHelper.Verify(x => x.LaunchProcess(It.IsAny(), args, It.IsAny(), null, null, null, It.IsAny>()), Times.Once); - Assert.AreEqual(Path.Combine(testResultsDirectory, filename), processDumpUtility.GetDumpFile()); - } - - /// - /// StartProcessDump should start proc dump binary with correct arguments for hang based dump, while GetDumpFile returns full path - /// - [TestMethod] - public void StartProcessDumpForHangWillStartProcDumpExeWithCorrectParams() - { - var guid = "guid"; - var process = "process"; - var processId = 12345; - var filename = $"{process}_{processId}_{guid}_hangdump.dmp"; - var args = $"-accepteula -n 1 -ma {processId} {filename}"; - var testResultsDirectory = "D:\\TestResults"; - - this.mockProcessHelper.Setup(x => x.LaunchProcess(It.IsAny(), It.IsAny(), It.IsAny(), null, null, null, It.IsAny>())) - .Returns(this.mockProcDumpProcess.Object); - this.mockProcessHelper.Setup(x => x.GetProcessName(processId)) - .Returns(process); - this.mockFileHelper.Setup(x => x.GetFiles(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(new string[] { Path.Combine(testResultsDirectory, filename) }); - - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - processDumpUtility.StartHangBasedProcessDump(processId, guid, testResultsDirectory, isFullDump: true); - - this.mockProcessHelper.Verify(x => x.LaunchProcess(It.IsAny(), args, It.IsAny(), null, null, null, It.IsAny>()), Times.Once); - Assert.AreEqual(Path.Combine(testResultsDirectory, filename), processDumpUtility.GetDumpFile()); - } - - /// - /// Start process dump will throw error if PROCDUMP_PATH env variable is not set - /// - [TestMethod] - public void StartProcessDumpWillThrowErrorIfProcdumpEnvVarNotSet() - { - Environment.SetEnvironmentVariable("PROCDUMP_PATH", null); - - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - var ex = Assert.ThrowsException(() => processDumpUtility.StartTriggerBasedProcessDump(1234, "guid", "D:\\")); - Assert.AreEqual(ex.Message, Resources.Resources.ProcDumpEnvVarEmpty); - } - - /// - /// Start process dump will start exe according to Test host Process in 32Bit OS - /// - [TestMethod] - public void StartProcessDumpWillStartExeCorrespondingToTestHostProcessIn32BitOS() - { - var guid = "guid"; - // var process = "process"; - var processId = 12345; - var testResultsDirectory = "D:\\TestResults"; + this.mockHangDumperFactory.Setup(x => x.Create(It.IsAny())) + .Returns(new Mock().Object); - this.mockPlatformEnvironment.Setup(x => x.Architecture).Returns(PlatformArchitecture.X86); - this.mockProcessHelper.Setup(x => x.GetProcessHandle(processId)) - .Returns(new IntPtr(0)); + this.mockCrashDumperFactory.Setup(x => x.Create(It.IsAny())) + .Returns(new Mock().Object); var processDumpUtility = new ProcessDumpUtility( this.mockProcessHelper.Object, this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - processDumpUtility.StartTriggerBasedProcessDump(processId, guid, testResultsDirectory); + this.mockHangDumperFactory.Object, + this.mockCrashDumperFactory.Object); - this.mockProcessHelper.Verify(x => x.LaunchProcess(Path.Combine("D:\\procdump", "procdump.exe"), It.IsAny(), It.IsAny(), null, null, null, It.IsAny>())); - } - - /// - /// Start process dump will start exe according to 64 Bit Test host Process in 64Bit OS - /// - [TestMethod] - public void StartProcessDumpWillStartExeCorrespondingTo64BitTestHostProcessIn64BitOS() - { - IntPtr x64ProcessHandle = new IntPtr(0); - int processId = 1234; - this.mockPlatformEnvironment.Setup(x => x.Architecture).Returns(PlatformArchitecture.X64); + processDumpUtility.StartTriggerBasedProcessDump(processId, guid, testResultsDirectory, false, ".NETCoreApp,Version=v5.0"); - this.mockProcessHelper.Setup(x => x.GetProcessHandle(processId)) - .Returns(x64ProcessHandle); - this.mockNativeMethodsHelper.Setup(x => x.Is64Bit(x64ProcessHandle)) - .Returns(true); - - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - processDumpUtility.StartTriggerBasedProcessDump(processId, "guid", "D:\\"); - - this.mockProcessHelper.Verify(x => x.LaunchProcess(Path.Combine("D:\\procdump", "procdump64.exe"), It.IsAny(), It.IsAny(), null, null, null, It.IsAny>())); - } - - /// - /// Start process dump will start exe according to 32 Bit Test host Process in 64 bit OS - /// - [TestMethod] - public void StartProcessDumpWillStartExeCorrespondingTo32BitTestHostProcessIn64BitOS() - { - IntPtr x86ProcessHandle = new IntPtr(0); - int processId = 12345; - this.mockPlatformEnvironment.Setup(x => x.Architecture).Returns(PlatformArchitecture.X64); - - this.mockProcessHelper.Setup(x => x.GetProcessHandle(processId)) - .Returns(x86ProcessHandle); - this.mockNativeMethodsHelper.Setup(x => x.Is64Bit(x86ProcessHandle)) - .Returns(false); - - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - processDumpUtility.StartTriggerBasedProcessDump(processId, "guid", "D:\\"); - - this.mockProcessHelper.Verify(x => x.LaunchProcess(Path.Combine("D:\\procdump", "procdump.exe"), It.IsAny(), It.IsAny(), null, null, null, It.IsAny>())); - } - - /// - /// Ensure terminate process calls terminate on proc dump process - /// - [TestMethod] - public void TerminateProcessDumpShouldCallTerminateOnProcDumpProcess() - { - var processDumpUtility = new ProcessDumpUtility( - this.mockProcessHelper.Object, - this.mockFileHelper.Object, - this.mockPlatformEnvironment.Object, - this.mockNativeMethodsHelper.Object); - - // Mock process helper - this.mockProcessHelper.Setup(x => x.TerminateProcess(It.IsAny())); - - // Raise - processDumpUtility.TerminateProcess(); - - // Verify - this.mockProcessHelper.Verify(x => x.TerminateProcess(It.IsAny()), Times.Once); - } - - [TestCleanup] - public void TestCleanup() - { - Environment.SetEnvironmentVariable("PROCDUMP_PATH", null); + var ex = Assert.ThrowsException(() => processDumpUtility.GetDumpFile()); + Assert.AreEqual(ex.Message, Resources.Resources.DumpFileNotGeneratedErrorMessage); } } } \ No newline at end of file diff --git a/test/datacollector.PlatformTests/datacollector.PlatformTests.csproj b/test/datacollector.PlatformTests/datacollector.PlatformTests.csproj index b7522cc1b6..9def0ed7d6 100644 --- a/test/datacollector.PlatformTests/datacollector.PlatformTests.csproj +++ b/test/datacollector.PlatformTests/datacollector.PlatformTests.csproj @@ -8,7 +8,7 @@ Microsoft.VisualStudio.TestPlatform.DataCollector.PlatformTests - netcoreapp2.1;net451 + netcoreapp2.1;net472 Exe datacollector.PlatformTests diff --git a/test/datacollector.UnitTests/DataCollectorMainTests.cs b/test/datacollector.UnitTests/DataCollectorMainTests.cs index 8fd29734c2..917c3748f5 100644 --- a/test/datacollector.UnitTests/DataCollectorMainTests.cs +++ b/test/datacollector.UnitTests/DataCollectorMainTests.cs @@ -63,7 +63,7 @@ public void RunShouldTimeoutBasedDefaulValueIfEnvVariableNotSet() public void RunShouldInitializeTraceWithTraceLevelOffIfDiagArgIsEmpty() { // Setting EqtTrace.TraceLevel to a value other than info. -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Verbose; #else EqtTrace.TraceLevel = PlatformTraceLevel.Verbose; @@ -79,7 +79,7 @@ public void RunShouldInitializeTraceWithTraceLevelOffIfDiagArgIsEmpty() public void RunShouldInitializeTraceWithVerboseTraceLevelIfInvalidTraceLevelPassed() { // Setting EqtTrace.TraceLevel to a value other than info. -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Info; #else EqtTrace.TraceLevel = PlatformTraceLevel.Info; @@ -95,7 +95,7 @@ public void RunShouldInitializeTraceWithVerboseTraceLevelIfInvalidTraceLevelPass public void RunShouldInitializeTraceWithCorrectVerboseTraceLevel() { // Setting EqtTrace.TraceLevel to a value other than info. -#if NET451 +#if NETFRAMEWORK EqtTrace.TraceLevel = TraceLevel.Verbose; #else EqtTrace.TraceLevel = PlatformTraceLevel.Verbose; diff --git a/test/datacollector.UnitTests/datacollector.UnitTests.csproj b/test/datacollector.UnitTests/datacollector.UnitTests.csproj index 1a687d31f5..8e21bb9594 100644 --- a/test/datacollector.UnitTests/datacollector.UnitTests.csproj +++ b/test/datacollector.UnitTests/datacollector.UnitTests.csproj @@ -10,7 +10,7 @@ Exe - netcoreapp2.1;net451 + netcoreapp2.1;net472 datacollector.UnitTests diff --git a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs index 9a9dde9b7f..807c188eb2 100644 --- a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs @@ -124,7 +124,7 @@ public void InitializeShouldWarnIfPlatformNotSupportedForCollectDumpOption() } [TestMethod] - public void InitializeShouldWarnIfIncorrectorParameterIsSpecifiedForCollectDumpOption() + public void InitializeShouldWarnIfIncorrectParameterIsSpecifiedForCollectDumpOption() { var invalidParameter = "CollectDumpXX"; var runsettingsString = string.Format(DefaultRunSettings, ""); @@ -182,7 +182,7 @@ public void InitializeShouldCreateEntryForBlameAlongWithCollectDumpEntryIfEnable this.executor.Initialize("CollectDump"); Assert.IsNotNull(this.settingsProvider.ActiveRunSettings); - Assert.AreEqual("\r\n\r\n \r\n \r\n \r\n \r\n C:\\dir\\TestResults\r\n \r\n \r\n \r\n \r\n \r\n \r\n C:\\dir\\TestResults\r\n \r\n \r\n \r\n \r\n \r\n \r\n", this.settingsProvider.ActiveRunSettings.SettingsXml); + Assert.AreEqual("\r\n\r\n \r\n \r\n \r\n \r\n C:\\dir\\TestResults\r\n \r\n \r\n \r\n \r\n \r\n \r\n C:\\dir\\TestResults\r\n \r\n \r\n \r\n \r\n \r\n \r\n", this.settingsProvider.ActiveRunSettings.SettingsXml); } [TestMethod] @@ -204,6 +204,25 @@ public void InitializeShouldCreateEntryForBlameAlongWithCollectDumpParametersIfE Assert.AreEqual("\r\n\r\n \r\n \r\n \r\n \r\n C:\\dir\\TestResults\r\n \r\n \r\n \r\n \r\n \r\n \r\n C:\\dir\\TestResults\r\n \r\n \r\n \r\n \r\n \r\n \r\n", this.settingsProvider.ActiveRunSettings.SettingsXml); } + [TestMethod] + public void InitializeShouldCreateEntryForBlameAlongWithCollectHangDumpEntryIfEnabled() + { + var runsettingsString = string.Format(DefaultRunSettings, ""); + var runsettings = new RunSettings(); + runsettings.LoadSettingsXml(DefaultRunSettings); + this.settingsProvider.SetActiveRunSettings(runsettings); + + this.mockEnvronment.Setup(x => x.OperatingSystem) + .Returns(PlatformOperatingSystem.Windows); + this.mockEnvronment.Setup(x => x.Architecture) + .Returns(PlatformArchitecture.X64); + + this.executor.Initialize("CollectHangDump"); + + Assert.IsNotNull(this.settingsProvider.ActiveRunSettings); + Assert.AreEqual("\r\n\r\n \r\n \r\n \r\n \r\n C:\\dir\\TestResults\r\n \r\n \r\n \r\n \r\n \r\n \r\n C:\\dir\\TestResults\r\n \r\n \r\n \r\n \r\n \r\n \r\n", this.settingsProvider.ActiveRunSettings.SettingsXml); + } + internal class TestableEnableBlameArgumentExecutor : EnableBlameArgumentExecutor { internal TestableEnableBlameArgumentExecutor(IRunSettingsProvider runSettingsManager, IEnvironment environment, IOutput output)