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
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.
+
+