From 2c222f70cc9b8a494560db7ced642ea18cb70be3 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Wed, 1 Sep 2021 14:47:30 +0200 Subject: [PATCH 01/11] Collect dumps on FailFast --- .../BlameCollector.cs | 24 +++++---- .../CrashDumperFactory.cs | 5 +- .../ICrashDumper.cs | 4 ++ .../Interfaces/IProcDumpArgsBuilder.cs | 5 +- .../Interfaces/IProcessDumpUtility.cs | 3 +- .../NetClientCrashDumper.cs | 20 +++++++ .../ProcDumpArgsBuilder.cs | 10 +++- .../ProcDumpCrashDumper.cs | 54 +++++++++++++++++-- .../ProcessDumpUtility.cs | 11 ++-- .../BlameCollectorTests.cs | 22 ++++---- .../ProcDumpArgsBuilderTests.cs | 10 ++-- .../ProcessDumpUtilityTests.cs | 2 +- 12 files changed, 125 insertions(+), 45 deletions(-) diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs index 0ac20b7df3..b85075c7af 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/BlameCollector.cs @@ -214,6 +214,14 @@ 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. + // And also prevent collecting dump on exit of the process. + this.processDumpUtility.DetachFromTargetProcess(this.testHostProcessId); + } + try { Action logWarning = m => this.logger.LogWarning(this.context.SessionDataCollectionContext, m); @@ -225,18 +233,11 @@ private void CollectDumpAndAbortTesthost() this.logger.LogError(this.context.SessionDataCollectionContext, $"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); - } - if (this.uploadDumpFiles) { try { - var dumpFiles = this.processDumpUtility.GetDumpFiles(); + var dumpFiles = this.processDumpUtility.GetDumpFiles(false, true); foreach (var dumpFile in dumpFiles) { try @@ -451,8 +452,9 @@ private void SessionEndedHandler(object sender, SessionEndEventArgs args) { // If the last test crashes, it will not invoke a test case end and therefore // In case of crash testStartCount will be greater than testEndCount and we need to write the sequence - // And send the attachment - if (this.testStartCount > this.testEndCount) + // And send the attachment. This won't indicate failure if there are 0 tests in the assembly, or when it fails in setup. + var processCrashedWhenRunningTests = this.testStartCount > this.testEndCount; + if (processCrashedWhenRunningTests) { var filepath = Path.Combine(this.GetTempDirectory(), Constants.AttachmentFileName + "_" + this.attachmentGuid); @@ -472,7 +474,7 @@ private void SessionEndedHandler(object sender, SessionEndEventArgs args) { try { - var dumpFiles = this.processDumpUtility.GetDumpFiles(warnOnNoDumpFiles: this.collectDumpAlways); + var dumpFiles = this.processDumpUtility.GetDumpFiles(warnOnNoDumpFiles: this.collectDumpAlways, processCrashedWhenRunningTests); foreach (var dumpFile in dumpFiles) { if (!string.IsNullOrEmpty(dumpFile)) diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs index 469885579b..7d89deed51 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs @@ -6,6 +6,7 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector using System; using System.Runtime.InteropServices; using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using NuGet.Frameworks; internal class CrashDumperFactory : ICrashDumperFactory @@ -52,13 +53,13 @@ public ICrashDumper Create(string targetFramework) } EqtTrace.Info($"CrashDumperFactory: This is Windows on {targetFramework}, returning the .NETClient dumper which uses env variables to collect crashdumps of testhost and any child process."); - return new NetClientCrashDumper(); + return new NetClientCrashDumper(new FileHelper()); } if (isNet50OrNewer) { EqtTrace.Info($"CrashDumperFactory: This is {RuntimeInformation.OSDescription} on {targetFramework} .NETClient dumper which uses env variables to collect crashdumps of testhost and any child process."); - return new NetClientCrashDumper(); + return new NetClientCrashDumper(new FileHelper()); } throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}, and framework: {targetFramework}."); diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs index dc6b0c402c..b303bfc82a 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ICrashDumper.cs @@ -3,6 +3,8 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { + using System.Collections.Generic; + public interface ICrashDumper { void AttachToTargetProcess(int processId, string outputDirectory, DumpTypeOption dumpType, bool collectAlways); @@ -10,5 +12,7 @@ public interface ICrashDumper void WaitForDumpToFinish(); void DetachFromTargetProcess(int processId); + + IEnumerable GetDumpFiles(bool processCrashed); } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcDumpArgsBuilder.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcDumpArgsBuilder.cs index 29e03c0013..67cf836dd2 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcDumpArgsBuilder.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcDumpArgsBuilder.cs @@ -22,11 +22,8 @@ public interface IProcDumpArgsBuilder /// /// Is full dump enabled /// - /// - /// Collects the dump on process exit even when there is no exception - /// /// Arguments - string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnumerable procDumpExceptionsList, bool isFullDump, bool collectAlways); + string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnumerable procDumpExceptionsList, bool isFullDump); /// /// Arguments for procdump.exe for getting a dump in case of a testhost hang diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs index b646a3bbbf..ec1e14ffef 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Interfaces/IProcessDumpUtility.cs @@ -12,10 +12,11 @@ public interface IProcessDumpUtility /// Get generated dump files /// /// Writes warning when no dump file is found. + /// Process might have crashed. If true it crashed for sure. If false we don't know. /// /// Path of dump file /// - IEnumerable GetDumpFiles(bool warnOnNoDumpFiles = true); + IEnumerable GetDumpFiles(bool warnOnNoDumpFiles, bool processCrashed); /// /// Launch proc dump process diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs index 96dce6fc1e..e7b8fc3b52 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs @@ -3,12 +3,25 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { + using System.Collections.Generic; + using System.IO; + using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + internal class NetClientCrashDumper : ICrashDumper { + private string outputDirectory; + private IFileHelper fileHelper; + + public NetClientCrashDumper(IFileHelper fileHelper) + { + this.fileHelper = fileHelper; + } + public void AttachToTargetProcess(int processId, string outputDirectory, DumpTypeOption dumpType, bool collectAlways) { // we don't need to do anything directly here, we setup the env variables // in the dumper configuration, including the path + this.outputDirectory = outputDirectory; } public void DetachFromTargetProcess(int processId) @@ -16,6 +29,13 @@ public void DetachFromTargetProcess(int processId) // here we might consider renaming the files to have timestamp } + public IEnumerable GetDumpFiles(bool processCrashed) + { + return this.fileHelper.DirectoryExists(this.outputDirectory) + ? this.fileHelper.EnumerateFiles(this.outputDirectory, SearchOption.AllDirectories, new[] { ".dmp" }) + : new List(); + } + public void WaitForDumpToFinish() { } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs index 034fbbcf9a..3f7e866d44 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs @@ -9,15 +9,21 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector public class ProcDumpArgsBuilder : IProcDumpArgsBuilder { /// - public string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnumerable procDumpExceptionsList, bool isFullDump, bool collectAlways) + 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. + // We use -e 1 to make sure we are able to catch StackOverflow and AccessViolationException exceptions. // -g: Run as a native debugger in a managed process (no interop). + // We use -g to be able to intercept StackOverflow and AccessViolationException. // -t: Write a dump when the process terminates. + // Collect the dump all the time, even if CollectAlways is not enabled to produce dumps for Environment.FailFast. We will later ignore the last + // dump file for testhost when we know that test host did not crash. // -ma: Full dump argument. // -f: Filter the exceptions. - StringBuilder procDumpArgument = new StringBuilder($"-accepteula -e 1 -g {(collectAlways ? "-t " : string.Empty)}"); + // Filter the first chance exceptions only to those that are most likely to kill the whole process. + StringBuilder procDumpArgument = new StringBuilder($"-accepteula -e 1 -g -t "); if (isFullDump) { procDumpArgument.Append("-ma "); diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs index fb7b07c38c..be3662c1a6 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs @@ -7,6 +7,7 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector using System.Collections.Generic; using System.Diagnostics; using System.IO; + using System.Linq; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; @@ -29,6 +30,10 @@ public class ProcDumpCrashDumper : ICrashDumper private string tempDirectory; private string dumpFileName; private INativeMethodsHelper nativeMethodsHelper; + private bool collectAlways; + private string outputDirectory; + private Process process; + private string outputFilePrefix; public ProcDumpCrashDumper() : this(new ProcessHelper(), new FileHelper(), new PlatformEnvironment(), new NativeMethodsHelper()) @@ -71,8 +76,11 @@ public void WaitForDumpToFinish() /// public void AttachToTargetProcess(int processId, string outputDirectory, DumpTypeOption dumpType, bool collectAlways) { - var process = Process.GetProcessById(processId); - var outputFile = Path.Combine(outputDirectory, $"{process.ProcessName}_{process.Id}_{DateTime.Now:yyyyMMddTHHmmss}_crashdump.dmp"); + this.collectAlways = collectAlways; + this.outputDirectory = outputDirectory; + this.process = Process.GetProcessById(processId); + this.outputFilePrefix = $"{this.process.ProcessName}_{this.process.Id}_{DateTime.Now:yyyyMMddTHHmmss}_crashdump"; + var outputFile = Path.Combine(outputDirectory, $"{this.outputFilePrefix}.dmp"); 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. @@ -96,8 +104,7 @@ public void AttachToTargetProcess(int processId, string outputDirectory, DumpTyp processId, this.dumpFileName, ProcDumpExceptionsList, - isFullDump: dumpType == DumpTypeOption.Full, - collectAlways: collectAlways); + isFullDump: dumpType == DumpTypeOption.Full); EqtTrace.Info($"ProcDumpCrashDumper.AttachToTargetProcess: Running ProcDump with arguments: '{procDumpArgs}'."); this.procDumpProcess = this.processHelper.LaunchProcess( @@ -140,6 +147,45 @@ public void DetachFromTargetProcess(int targetProcessId) } } + public IEnumerable GetDumpFiles(bool processCrashed) + { + var allDumps = this.fileHelper.DirectoryExists(this.outputDirectory) + ? this.fileHelper.EnumerateFiles(this.outputDirectory, SearchOption.AllDirectories, new[] { ".dmp" }).ToList() + : new List(); + + // We are always collecting dump on exit even when collectAlways option is false, to make sure we collect + // dump for Environment.FailFast. So there always can be a dump if the process already exited. In most cases + // this was just a normal process exit that was not caused by an exception and user is not interested in getting that + // dump because it only pollutes their CI. + if (this.collectAlways) + { + return allDumps; + } + + if (processCrashed) + { + return allDumps; + } + + // There can be more dumps in the crash folder from the child processes that were .NET5 or newer and crashed + // get only the ones that match the path we provide to procdump. And get the last one created. + var allTargetProcessDumps = allDumps + .Where(dump => Path.GetFileNameWithoutExtension(dump) + .StartsWith(this.outputFilePrefix)) + .Select(dump => new FileInfo(dump)) + .OrderBy(dump => dump.LastWriteTime).ThenBy(dump => dump.Name) + .ToList(); + + var skippedDump = allTargetProcessDumps.LastOrDefault(); + + if (skippedDump != null) + { + EqtTrace.Verbose($"ProcDumpCrashDumper.GetDumpFiles: Found {allTargetProcessDumps.Count} dumps for the target process, skipping {skippedDump.Name} because we always collect a dump, even if there is no crash. But the process did not crash and user did not specify CollectAlways=true."); + } + + return allTargetProcessDumps.Take(allTargetProcessDumps.Count - 1).Select(dump => dump.FullName).ToList(); + } + /// /// 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. /// diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs index 2a6b6445a3..34895587da 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs @@ -51,16 +51,19 @@ public ProcessDumpUtility(IProcessHelper processHelper, IFileHelper fileHelper, }; /// - public IEnumerable GetDumpFiles(bool warnOnNoDumpFiles = true) + public IEnumerable GetDumpFiles(bool warnOnNoDumpFiles, bool processCrashed) { if (!this.wasHangDumped) { this.crashDumper.WaitForDumpToFinish(); } - IEnumerable crashDumps = this.fileHelper.DirectoryExists(this.crashDumpDirectory) - ? this.fileHelper.EnumerateFiles(this.crashDumpDirectory, SearchOption.AllDirectories, new[] { ".dmp" }) - : new List(); + if (this.crashDumpDirectory == this.hangDumpDirectory) + { + throw new InvalidOperationException("Crash dump directory and hang dump directory should not be the same."); + } + + IEnumerable crashDumps = this.crashDumper.GetDumpFiles(processCrashed); IEnumerable hangDumps = this.fileHelper.DirectoryExists(this.hangDumpDirectory) ? this.fileHelper.EnumerateFiles(this.hangDumpDirectory, SearchOption.TopDirectoryOnly, new[] { ".dmp" }) diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs index c5add2c5d9..8bf91eb6b5 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/BlameCollectorTests.cs @@ -173,7 +173,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(), It.IsAny>())); - this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true)).Returns(new[] { dumpFile }); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true, false)).Returns(new[] { dumpFile }); this.mockDataCollectionSink.Setup(x => x.SendFileAsync(It.IsAny())).Callback(() => hangBasedDumpcollected.Set()); this.blameDataCollector.Initialize( @@ -185,7 +185,7 @@ public void InitializeWithDumpForHangShouldCaptureADumpOnTimeout() hangBasedDumpcollected.Wait(1000); this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); - this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true, false), Times.Once); this.mockDataCollectionSink.Verify(x => x.SendFileAsync(It.Is(y => y.Path == dumpFile)), Times.Once); } @@ -207,7 +207,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(), It.IsAny>())); - this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true)).Callback(() => hangBasedDumpcollected.Set()).Throws(new Exception("Some exception")); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true, false)).Callback(() => hangBasedDumpcollected.Set()).Throws(new Exception("Some exception")); this.blameDataCollector.Initialize( this.GetDumpConfigurationElement(false, false, true, 0), @@ -218,7 +218,7 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfGet hangBasedDumpcollected.Wait(1000); this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); - this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true, false), Times.Once); } /// @@ -240,7 +240,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(), It.IsAny>())); - this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true)).Returns(new[] { dumpFile }); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true, false)).Returns(new[] { dumpFile }); this.mockDataCollectionSink.Setup(x => x.SendFileAsync(It.IsAny())).Callback(() => hangBasedDumpcollected.Set()).Throws(new Exception("Some other exception")); this.blameDataCollector.Initialize( @@ -252,7 +252,7 @@ public void InitializeWithDumpForHangShouldCaptureKillTestHostOnTimeoutEvenIfAtt hangBasedDumpcollected.Wait(1000); this.mockProcessDumpUtility.Verify(x => x.StartHangBasedProcessDump(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); - this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true, false), Times.Once); this.mockDataCollectionSink.Verify(x => x.SendFileAsync(It.Is(y => y.Path == dumpFile)), Times.Once); } @@ -367,7 +367,7 @@ public void TriggerSessionEndedHandlerShouldGetDumpFileIfProcDumpEnabled() this.context); // Setup - this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(It.IsAny())).Returns(new[] { this.filepath }); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(It.IsAny(), false)).Returns(new[] { this.filepath }); this.mockBlameReaderWriter.Setup(x => x.WriteTestSequence(It.IsAny>(), It.IsAny>(), It.IsAny())) .Returns(this.filepath); @@ -377,7 +377,7 @@ public void TriggerSessionEndedHandlerShouldGetDumpFileIfProcDumpEnabled() this.mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(this.dataCollectionContext)); // Verify GetDumpFiles Call - this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(It.IsAny()), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(It.IsAny(), false), Times.Once); } /// @@ -419,7 +419,7 @@ public void TriggerSessionEndedHandlerShouldGetDumpFileIfCollectDumpOnExitIsEnab this.context); // Setup - this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true)).Returns(new[] { this.filepath }); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true, false)).Returns(new[] { this.filepath }); this.mockBlameReaderWriter.Setup(x => x.WriteTestSequence(It.IsAny>(), It.IsAny>(), It.IsAny())) .Returns(this.filepath); @@ -428,7 +428,7 @@ public void TriggerSessionEndedHandlerShouldGetDumpFileIfCollectDumpOnExitIsEnab this.mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(this.dataCollectionContext)); // Verify GetDumpFiles Call - this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true), Times.Once); + this.mockProcessDumpUtility.Verify(x => x.GetDumpFiles(true, false), Times.Once); } /// @@ -450,7 +450,7 @@ public void TriggerSessionEndedHandlerShouldLogWarningIfGetDumpFileThrowsFileNot // Setup and raise events this.mockBlameReaderWriter.Setup(x => x.WriteTestSequence(It.IsAny>(), It.IsAny>(), It.IsAny())) .Returns(this.filepath); - this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true)).Throws(new FileNotFoundException()); + this.mockProcessDumpUtility.Setup(x => x.GetDumpFiles(true, false)).Throws(new FileNotFoundException()); this.mockDataColectionEvents.Raise(x => x.TestHostLaunched += null, new TestHostLaunchedEventArgs(this.dataCollectionContext, 1234)); this.mockDataColectionEvents.Raise(x => x.TestCaseStart += null, new TestCaseStartEventArgs(new TestCase())); this.mockDataColectionEvents.Raise(x => x.SessionEnd += null, new SessionEndEventArgs(this.dataCollectionContext)); diff --git a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcDumpArgsBuilderTests.cs b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcDumpArgsBuilderTests.cs index 07f331761d..eb1d7114ee 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcDumpArgsBuilderTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcDumpArgsBuilderTests.cs @@ -32,23 +32,23 @@ public void BuildHangBasedProcDumpArgsWithFullDumpEnabledShouldCreateCorrectArgS public void BuildTriggerBasedProcDumpArgsShouldCreateCorrectArgString() { var procDumpArgsBuilder = new ProcDumpArgsBuilder(); - var argString = procDumpArgsBuilder.BuildTriggerBasedProcDumpArgs(this.defaultProcId, this.defaultDumpFileName, new List { "a", "b" }, false, false); - Assert.AreEqual("-accepteula -e 1 -g -f a -f b 1234 dump.dmp", argString); + 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); } [TestMethod] public void BuildTriggerProcDumpArgsWithFullDumpEnabledShouldCreateCorrectArgString() { var procDumpArgsBuilder = new ProcDumpArgsBuilder(); - var argString = procDumpArgsBuilder.BuildTriggerBasedProcDumpArgs(this.defaultProcId, this.defaultDumpFileName, new List { "a", "b" }, true, false); - Assert.AreEqual("-accepteula -e 1 -g -ma -f a -f b 1234 dump.dmp", argString); + var argString = procDumpArgsBuilder.BuildTriggerBasedProcDumpArgs(this.defaultProcId, this.defaultDumpFileName, new List { "a", "b" }, true); + Assert.AreEqual("-accepteula -e 1 -g -t -ma -f a -f b 1234 dump.dmp", argString); } [TestMethod] public void BuildTriggerProcDumpArgsWithAlwaysCollectShouldCreateCorrectArgString() { var procDumpArgsBuilder = new ProcDumpArgsBuilder(); - var argString = procDumpArgsBuilder.BuildTriggerBasedProcDumpArgs(this.defaultProcId, this.defaultDumpFileName, new List { "a", "b" }, true, collectAlways: true); + var argString = procDumpArgsBuilder.BuildTriggerBasedProcDumpArgs(this.defaultProcId, this.defaultDumpFileName, new List { "a", "b" }, true); // adds -t for collect on every process exit Assert.AreEqual("-accepteula -e 1 -g -t -ma -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 6b1d71c063..7d36a4dcf6 100644 --- a/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs +++ b/test/Microsoft.TestPlatform.Extensions.BlameDataCollector.UnitTests/ProcessDumpUtilityTests.cs @@ -63,7 +63,7 @@ public void GetDumpFileWillThrowExceptionIfNoDumpfile() processDumpUtility.StartTriggerBasedProcessDump(processId, testResultsDirectory, false, ".NETCoreApp,Version=v5.0", false); - var ex = Assert.ThrowsException(() => processDumpUtility.GetDumpFiles()); + var ex = Assert.ThrowsException(() => processDumpUtility.GetDumpFiles(false, false)); Assert.AreEqual(ex.Message, Resources.Resources.DumpFileNotGeneratedErrorMessage); } } From a3a94809e00013cabdbd3ac4a95f29e998683890 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Wed, 1 Sep 2021 17:25:20 +0200 Subject: [PATCH 02/11] Fix blame for FailFast --- .../CrashDumperFactory.cs | 4 +- .../HangDumperFactory.cs | 27 +++++ .../NetClientCrashDumper.cs | 5 +- .../ProcDumpArgsBuilder.cs | 15 ++- ...ocDumpCrashDumper.cs => ProcDumpDumper.cs} | 104 ++++++++++++++---- .../ProcessDumpUtility.cs | 14 +-- .../EnableBlameArgumentProcessor.cs | 8 +- 7 files changed, 135 insertions(+), 42 deletions(-) rename src/Microsoft.TestPlatform.Extensions.BlameDataCollector/{ProcDumpCrashDumper.cs => ProcDumpDumper.cs} (62%) diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs index 7d89deed51..708c32973b 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/CrashDumperFactory.cs @@ -35,7 +35,7 @@ public ICrashDumper Create(string targetFramework) if (!isNet50OrNewer) { EqtTrace.Info($"CrashDumperFactory: This is Windows on {targetFramework} which is not net5.0 or newer, returning ProcDumpCrashDumper that uses ProcDump utility."); - return new ProcDumpCrashDumper(); + return new ProcDumpDumper(); } // On net5.0 we don't have the capability to crash dump on exit, which is useful in rare cases @@ -49,7 +49,7 @@ public ICrashDumper Create(string targetFramework) if (forceUsingProcdump) { EqtTrace.Info($"CrashDumperFactory: This is Windows on {targetFramework}. Forcing the use of ProcDumpCrashDumper that uses ProcDump utility, via VSTEST_DUMP_FORCEPROCDUMP={procdumpOverride}."); - return new ProcDumpCrashDumper(); + return new ProcDumpDumper(); } EqtTrace.Info($"CrashDumperFactory: This is Windows on {targetFramework}, returning the .NETClient dumper which uses env variables to collect crashdumps of testhost and any child process."); diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs index ea6e355d4e..1e5a4db51d 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/HangDumperFactory.cs @@ -20,6 +20,9 @@ public IHangDumper Create(string targetFramework) } EqtTrace.Info($"HangDumperFactory: Creating dumper for {RuntimeInformation.OSDescription} with target framework {targetFramework}."); + var procdumpOverride = Environment.GetEnvironmentVariable("VSTEST_DUMP_FORCEPROCDUMP")?.Trim(); + var netdumpOverride = Environment.GetEnvironmentVariable("VSTEST_DUMP_FORCENETDUMP")?.Trim(); + EqtTrace.Verbose($"HangDumperFactory: Overrides for dumpers: VSTEST_DUMP_FORCEPROCDUMP={procdumpOverride};VSTEST_DUMP_FORCENETDUMP={netdumpOverride}"); var tfm = NuGetFramework.Parse(targetFramework); @@ -31,6 +34,30 @@ public IHangDumper Create(string targetFramework) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + // On some system the interop dumper will thrown AccessViolationException, add an option to force procdump. + var forceUsingProcdump = !string.IsNullOrWhiteSpace(procdumpOverride) && procdumpOverride != "0"; + if (forceUsingProcdump) + { + EqtTrace.Info($"HangDumperFactory: This is Windows on Forcing the use of ProcDumpHangDumper that uses ProcDump utility, via VSTEST_DUMP_FORCEPROCDUMP={procdumpOverride}."); + return new ProcDumpDumper(); + } + + // On some system the interop dumper will thrown AccessViolationException, add an option to force procdump. + var forceUsingNetdump = !string.IsNullOrWhiteSpace(netdumpOverride) && netdumpOverride != "0"; + if (forceUsingNetdump) + { + var isLessThan50 = tfm.Framework == ".NETCoreApp" && tfm.Version < Version.Parse("5.0.0.0"); + if (!isLessThan50) + { + EqtTrace.Info($"HangDumperFactory: This is Windows on {tfm.Framework} {tfm.Version}, VSTEST_DUMP_FORCENETDUMP={netdumpOverride} is active, forcing use of .NetClientHangDumper"); + return new NetClientHangDumper(); + } + else + { + EqtTrace.Info($"HangDumperFactory: This is Windows on {tfm.Framework} {tfm.Version}, VSTEST_DUMP_FORCENETDUMP={netdumpOverride} is active, but only applies to .NET 5.0 and newer. Falling back to default hang dumper."); + } + } + EqtTrace.Info($"HangDumperFactory: This is Windows, returning the default WindowsHangDumper that P/Invokes MiniDumpWriteDump."); return new WindowsHangDumper(this.LogWarning); } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs index e7b8fc3b52..1952afe88e 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NetClientCrashDumper.cs @@ -3,6 +3,7 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { + using System; using System.Collections.Generic; using System.IO; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; @@ -32,8 +33,8 @@ public void DetachFromTargetProcess(int processId) public IEnumerable GetDumpFiles(bool processCrashed) { return this.fileHelper.DirectoryExists(this.outputDirectory) - ? this.fileHelper.EnumerateFiles(this.outputDirectory, SearchOption.AllDirectories, new[] { ".dmp" }) - : new List(); + ? this.fileHelper.GetFiles(this.outputDirectory, "*_crashdump*.dmp", SearchOption.AllDirectories) + : Array.Empty(); } public void WaitForDumpToFinish() diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs index 3f7e866d44..5ab8cc57d3 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpArgsBuilder.cs @@ -3,6 +3,7 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector { + using System; using System.Collections.Generic; using System.Text; @@ -23,7 +24,13 @@ public string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnu // -ma: Full dump argument. // -f: Filter the exceptions. // Filter the first chance exceptions only to those that are most likely to kill the whole process. - StringBuilder procDumpArgument = new StringBuilder($"-accepteula -e 1 -g -t "); + + // Fully override parameters to procdump + var procdumpArgumentsFromEnv = Environment.GetEnvironmentVariable("VSTEST_DUMP_PROCDUMPARGUMENTS")?.Trim(); + + // Useful additional arguments are -n 100, to collect all dumps that you can, or -o to overwrite dump, or -f EXCEPTION_NAME to add exception to filter list + var procdumpAdditonalArgumentsFromEnv = Environment.GetEnvironmentVariable("VSTEST_DUMP_PROCDUMPADDITIONALARGUMENTS")?.Trim(); + StringBuilder procDumpArgument = new StringBuilder($"-accepteula -e 1 -g -t {procdumpAdditonalArgumentsFromEnv}"); if (isFullDump) { procDumpArgument.Append("-ma "); @@ -35,7 +42,11 @@ public string BuildTriggerBasedProcDumpArgs(int processId, string filename, IEnu } procDumpArgument.Append($"{processId} {filename}.dmp"); - var argument = procDumpArgument.ToString(); + var argument = string.IsNullOrWhiteSpace(procdumpArgumentsFromEnv) ? procDumpArgument.ToString() : procdumpArgumentsFromEnv; + if (!argument.ToUpperInvariant().Contains("-accepteula".ToUpperInvariant())) + { + argument = $"-accepteula {argument}"; + } return argument; } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpDumper.cs similarity index 62% rename from src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs rename to src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpDumper.cs index be3662c1a6..7c79bb586d 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpCrashDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcDumpDumper.cs @@ -15,7 +15,7 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; - public class ProcDumpCrashDumper : ICrashDumper + public class ProcDumpDumper : ICrashDumper, IHangDumper { private static readonly IEnumerable ProcDumpExceptionsList = new List() { @@ -35,12 +35,12 @@ public class ProcDumpCrashDumper : ICrashDumper private Process process; private string outputFilePrefix; - public ProcDumpCrashDumper() + public ProcDumpDumper() : this(new ProcessHelper(), new FileHelper(), new PlatformEnvironment(), new NativeMethodsHelper()) { } - public ProcDumpCrashDumper(IProcessHelper processHelper, IFileHelper fileHelper, IEnvironment environment, INativeMethodsHelper nativeMethodsHelper) + public ProcDumpDumper(IProcessHelper processHelper, IFileHelper fileHelper, IEnvironment environment, INativeMethodsHelper nativeMethodsHelper) { this.processHelper = processHelper; this.fileHelper = fileHelper; @@ -58,7 +58,7 @@ public ProcDumpCrashDumper(IProcessHelper processHelper, IFileHelper fileHelper, // Otherwise they end up coming on console in pipleine. if (EqtTrace.IsInfoEnabled) { - EqtTrace.Info("ProcDumpCrashDumper.OutputReceivedCallback: Output received from procdump process: " + data); + EqtTrace.Info("ProcDumpDumper.OutputReceivedCallback: Output received from procdump process: " + data); } }; @@ -67,7 +67,7 @@ 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."); + EqtTrace.Info($"ProcDumpDumper.WaitForDumpToFinish: ProcDump was not previously attached, this might indicate error during setup, look for ProcDumpDumper.AttachToTargetProcess."); } this.processHelper?.WaitForProcessExit(this.procDumpProcess); @@ -81,7 +81,7 @@ public void AttachToTargetProcess(int processId, string outputDirectory, DumpTyp this.process = Process.GetProcessById(processId); this.outputFilePrefix = $"{this.process.ProcessName}_{this.process.Id}_{DateTime.Now:yyyyMMddTHHmmss}_crashdump"; var outputFile = Path.Combine(outputDirectory, $"{this.outputFilePrefix}.dmp"); - EqtTrace.Info($"ProcDumpCrashDumper.AttachToTargetProcess: Attaching to process '{processId}' to dump into '{outputFile}'."); + EqtTrace.Info($"ProcDumpDumper.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)) @@ -93,7 +93,7 @@ public void AttachToTargetProcess(int processId, string outputDirectory, DumpTyp { 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}"); + EqtTrace.Error($"ProcDumpDumper.AttachToTargetProcess: {err}"); return; } @@ -106,7 +106,7 @@ public void AttachToTargetProcess(int processId, string outputDirectory, DumpTyp ProcDumpExceptionsList, isFullDump: dumpType == DumpTypeOption.Full); - EqtTrace.Info($"ProcDumpCrashDumper.AttachToTargetProcess: Running ProcDump with arguments: '{procDumpArgs}'."); + EqtTrace.Info($"ProcDumpDumper.AttachToTargetProcess: Running ProcDump with arguments: '{procDumpArgs}'."); this.procDumpProcess = this.processHelper.LaunchProcess( procDumpPath, procDumpArgs, @@ -116,7 +116,7 @@ public void AttachToTargetProcess(int processId, string outputDirectory, DumpTyp null, this.OutputReceivedCallback) as Process; - EqtTrace.Info($"ProcDumpCrashDumper.AttachToTargetProcess: ProcDump started as process with id '{this.procDumpProcess.Id}'."); + EqtTrace.Info($"ProcDumpDumper.AttachToTargetProcess: ProcDump started as process with id '{this.procDumpProcess.Id}'."); } /// @@ -124,25 +124,25 @@ 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."); + EqtTrace.Info($"ProcDumpDumper.DetachFromTargetProcess: ProcDump was not previously attached, this might indicate error during setup, look for ProcDumpDumper.AttachToTargetProcess."); return; } try { - EqtTrace.Info($"ProcDumpCrashDumper.DetachFromTargetProcess: ProcDump detaching from target process '{targetProcessId}'."); + EqtTrace.Info($"ProcDumpDumper.DetachFromTargetProcess: ProcDump detaching from target process '{targetProcessId}'."); new Win32NamedEvent($"Procdump-{targetProcessId}").Set(); } finally { try { - EqtTrace.Info("ProcDumpCrashDumper.DetachFromTargetProcess: Attempting to kill proc dump process."); + EqtTrace.Info("ProcDumpDumper.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}"); + EqtTrace.Warning($"ProcDumpDumper.DetachFromTargetProcess: Failed to kill proc dump process with exception {e}"); } } } @@ -150,13 +150,14 @@ public void DetachFromTargetProcess(int targetProcessId) public IEnumerable GetDumpFiles(bool processCrashed) { var allDumps = this.fileHelper.DirectoryExists(this.outputDirectory) - ? this.fileHelper.EnumerateFiles(this.outputDirectory, SearchOption.AllDirectories, new[] { ".dmp" }).ToList() - : new List(); + ? this.fileHelper.GetFiles(this.outputDirectory, "*_crashdump*.dmp", SearchOption.AllDirectories) + : Array.Empty(); // We are always collecting dump on exit even when collectAlways option is false, to make sure we collect // dump for Environment.FailFast. So there always can be a dump if the process already exited. In most cases // this was just a normal process exit that was not caused by an exception and user is not interested in getting that // dump because it only pollutes their CI. + // The hangdumps and crash dumps actually end up in the same folder, but we can distinguish them based on the _crashdump suffix. if (this.collectAlways) { return allDumps; @@ -176,16 +177,71 @@ public IEnumerable GetDumpFiles(bool processCrashed) .OrderBy(dump => dump.LastWriteTime).ThenBy(dump => dump.Name) .ToList(); - var skippedDump = allTargetProcessDumps.LastOrDefault(); + var dumpToRemove = allTargetProcessDumps.LastOrDefault(); - if (skippedDump != null) + if (dumpToRemove != null) { - EqtTrace.Verbose($"ProcDumpCrashDumper.GetDumpFiles: Found {allTargetProcessDumps.Count} dumps for the target process, skipping {skippedDump.Name} because we always collect a dump, even if there is no crash. But the process did not crash and user did not specify CollectAlways=true."); + EqtTrace.Verbose($"ProcDumpDumper.GetDumpFiles: Found {allTargetProcessDumps.Count} dumps for the target process, removing {dumpToRemove.Name} because we always collect a dump, even if there is no crash. But the process did not crash and user did not specify CollectAlways=true."); + try + { + File.Delete(dumpToRemove.FullName); + } + catch (Exception ex) + { + EqtTrace.Error($"ProcDumpDumper.GetDumpFiles: Removing dump failed with: {ex}"); + EqtTrace.Error(ex); + } } return allTargetProcessDumps.Take(allTargetProcessDumps.Count - 1).Select(dump => dump.FullName).ToList(); } + // Hang dumps the process using procdump. + public void Dump(int processId, string outputDirectory, DumpTypeOption dumpType) + { + var process = Process.GetProcessById(processId); + var outputFile = Path.Combine(outputDirectory, $"{process.ProcessName}_{processId}_{DateTime.Now:yyyyMMddTHHmmss}_hangdump.dmp"); + EqtTrace.Info($"ProcDumpDumper.Dump: Hang dumping 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($"ProcDumpDumper.Dump: {err}"); + return; + } + + var tempDirectory = Path.GetDirectoryName(outputFile); + var dumpFileName = Path.GetFileNameWithoutExtension(outputFile); + + string procDumpArgs = new ProcDumpArgsBuilder().BuildHangBasedProcDumpArgs( + processId, + dumpFileName, + isFullDump: dumpType == DumpTypeOption.Full); + + EqtTrace.Info($"ProcDumpDumper.Dump: Running ProcDump with arguments: '{procDumpArgs}'."); + var procDumpProcess = this.processHelper.LaunchProcess( + procDumpPath, + procDumpArgs, + tempDirectory, + null, + null, + null, + this.OutputReceivedCallback) as Process; + + EqtTrace.Info($"ProcDumpDumper.Dump: ProcDump started as process with id '{procDumpProcess.Id}'."); + + this.processHelper?.WaitForProcessExit(procDumpProcess); + + EqtTrace.Info($"ProcDumpDumper.Dump: ProcDump finished hang dumping process with id '{processId}'."); + } + /// /// 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. /// @@ -202,12 +258,12 @@ private bool TryGetProcDumpExecutable(int processId, out string 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."); + EqtTrace.Verbose("ProcDumpDumper.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."); + EqtTrace.Verbose($"ProcDumpDumper.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; } @@ -239,22 +295,22 @@ private bool TryGetProcDumpExecutable(int processId, out string path) var candidatePath = Path.Combine(procdumpDirectory, filename); if (File.Exists(candidatePath)) { - EqtTrace.Verbose($"ProcDumpCrashDumper.GetProcDumpExecutable: Path to ProcDump '{candidatePath}' exists, using that."); + EqtTrace.Verbose($"ProcDumpDumper.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."); + EqtTrace.Verbose($"ProcDumpDumper.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."); + EqtTrace.Verbose($"ProcDumpDumper.GetProcDumpExecutable: Resolved {filename} to {p} from PATH."); path = p; return true; } - EqtTrace.Verbose($"ProcDumpCrashDumper.GetProcDumpExecutable: Could not find {filename} on PATH."); + EqtTrace.Verbose($"ProcDumpDumper.GetProcDumpExecutable: Could not find {filename} on PATH."); path = filename; return false; } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs index 34895587da..c5bdf064a9 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/ProcessDumpUtility.cs @@ -58,16 +58,14 @@ public IEnumerable GetDumpFiles(bool warnOnNoDumpFiles, bool processCras this.crashDumper.WaitForDumpToFinish(); } - if (this.crashDumpDirectory == this.hangDumpDirectory) - { - throw new InvalidOperationException("Crash dump directory and hang dump directory should not be the same."); - } - - IEnumerable crashDumps = this.crashDumper.GetDumpFiles(processCrashed); + // If the process was hang dumped we killed it ourselves, so it crashed when executing tests, + // but we already have the hang dump, and should not also collect the exit dump that we got + // from killing the process by the hang dumper. + IEnumerable crashDumps = this.crashDumper?.GetDumpFiles(!this.wasHangDumped && processCrashed) ?? new List(); IEnumerable hangDumps = this.fileHelper.DirectoryExists(this.hangDumpDirectory) - ? this.fileHelper.EnumerateFiles(this.hangDumpDirectory, SearchOption.TopDirectoryOnly, new[] { ".dmp" }) - : new List(); + ? this.fileHelper.GetFiles(this.hangDumpDirectory, "*_hangdump*.dmp", SearchOption.TopDirectoryOnly) + : Array.Empty(); var foundDumps = new List(); foreach (var dumpPath in crashDumps.Concat(hangDumps)) diff --git a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs index ff3c662257..d96d48f569 100644 --- a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs @@ -233,8 +233,8 @@ private void InitializeBlame(bool enableCrashDump, bool enableHangDump, Dictiona if (enableCrashDump) { var dumpParameters = collectDumpParameters - .Where(p => new[] { "CollectAlways", "DumpType" }.Contains(p.Key)) - .ToDictionary(p => p.Key, p => p.Value); + .Where(p => new[] { "CollectAlways", "DumpType" }.Contains(p.Key, StringComparer.OrdinalIgnoreCase)) + .ToDictionary(p => p.Key, p => p.Value, StringComparer.OrdinalIgnoreCase); if (!dumpParameters.ContainsKey("DumpType")) { @@ -248,8 +248,8 @@ private void InitializeBlame(bool enableCrashDump, bool enableHangDump, Dictiona if (enableHangDump) { var hangDumpParameters = collectDumpParameters - .Where(p => new[] { "TestTimeout", "HangDumpType" }.Contains(p.Key)) - .ToDictionary(p => p.Key, p => p.Value); + .Where(p => new[] { "TestTimeout", "HangDumpType" }.Contains(p.Key, StringComparer.OrdinalIgnoreCase)) + .ToDictionary(p => p.Key, p => p.Value, StringComparer.OrdinalIgnoreCase); if (!hangDumpParameters.ContainsKey("TestTimeout")) { From 5c27f7e9acfc5069cb2fbce59587679c7e46ae62 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Mon, 6 Sep 2021 12:42:37 +0200 Subject: [PATCH 03/11] Add dump minitool --- TestPlatform.sln | 15 ++ .../DumpMinitool/DumpMinitool.csproj | 9 ++ src/DataCollectors/DumpMinitool/Program.cs | 144 ++++++++++++++++++ .../NativeMethodsHelper.cs | 8 +- .../WindowsHangDumper.cs | 66 +++++++- 5 files changed, 235 insertions(+), 7 deletions(-) create mode 100644 src/DataCollectors/DumpMinitool/DumpMinitool.csproj create mode 100644 src/DataCollectors/DumpMinitool/Program.cs diff --git a/TestPlatform.sln b/TestPlatform.sln index f7a5b0292e..45a68993dd 100644 --- a/TestPlatform.sln +++ b/TestPlatform.sln @@ -164,6 +164,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Adap EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.TestPlatform.Execution.Shared", "src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.shproj", "{7F26EDA3-C8C4-4B7F-A9B6-D278C2F40A13}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DumpMinitool", "src\DataCollectors\DumpMinitool\DumpMinitool.csproj", "{33A20B85-7024-4112-B1E7-00CD0E4A9F96}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.projitems*{10b6ade1-f808-4612-801d-4452f5b52242}*SharedItemsImports = 5 @@ -793,6 +795,18 @@ Global {2DE99835-A3A3-4922-82AD-6D10D284816D}.Release|x64.Build.0 = Release|Any CPU {2DE99835-A3A3-4922-82AD-6D10D284816D}.Release|x86.ActiveCfg = Release|Any CPU {2DE99835-A3A3-4922-82AD-6D10D284816D}.Release|x86.Build.0 = Release|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Debug|x64.ActiveCfg = Debug|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Debug|x64.Build.0 = Debug|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Debug|x86.ActiveCfg = Debug|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Debug|x86.Build.0 = Debug|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|Any CPU.Build.0 = Release|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|x64.ActiveCfg = Release|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|x64.Build.0 = Release|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|x86.ActiveCfg = Release|Any CPU + {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -862,6 +876,7 @@ Global {DCD0C39E-C78C-4A44-B0BD-7325254A2E97} = {376C19DE-31E2-4FF6-88FC-0D0D6233C999} {2DE99835-A3A3-4922-82AD-6D10D284816D} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} {7F26EDA3-C8C4-4B7F-A9B6-D278C2F40A13} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} + {33A20B85-7024-4112-B1E7-00CD0E4A9F96} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0541B30C-FF51-4E28-B172-83F5F3934BCD} diff --git a/src/DataCollectors/DumpMinitool/DumpMinitool.csproj b/src/DataCollectors/DumpMinitool/DumpMinitool.csproj new file mode 100644 index 0000000000..bb3a8c7f20 --- /dev/null +++ b/src/DataCollectors/DumpMinitool/DumpMinitool.csproj @@ -0,0 +1,9 @@ + + + + Exe + net5.0 + true + + + diff --git a/src/DataCollectors/DumpMinitool/Program.cs b/src/DataCollectors/DumpMinitool/Program.cs new file mode 100644 index 0000000000..7d777c1c0f --- /dev/null +++ b/src/DataCollectors/DumpMinitool/Program.cs @@ -0,0 +1,144 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace DumpMinitool +{ + internal class Program + { + static int Main(string[] args) + { + Console.WriteLine($"Dump minitool: Started with arguments {string.Join(" ", args)}"); + if (args?.Length != 6) + { + Console.WriteLine($"There were { args?.Length ?? 0 } parameters. Provide exactly 6 parameters: --file --processId --dumpType "); + return 2; + } + + var outputFile = args[1]; + var processId = int.Parse(args[3]); + var type = Enum.Parse(typeof(DumpTypeOption), args[5]); + + Console.WriteLine($"Output file: '{outputFile}'"); + Console.WriteLine($"Process id: {processId}"); + Console.WriteLine($"Dump type: {type}"); + + var process = Process.GetProcessById(processId); + + using (var stream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) + { + NativeMethods.MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = default; + + 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)) + { + Console.WriteLine("Dumped process."); + return 0; + } + else + { + int err = Marshal.GetHRForLastWin32Error(); + if (err != NativeMethods.ERROR_PARTIAL_COPY) + { + Console.WriteLine($"Error dumping process {err}"); + Marshal.ThrowExceptionForHR(err); + } + else + { + Console.WriteLine($"Error dumping process, was ERROR_PARTIAL_COPY, retrying."); + } + } + } + + Console.WriteLine($"Error dumping process after 5 retries."); + return 1; + } + } + + 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) + } + } + } + + internal enum DumpTypeOption + { + Full, + WithHeap, + Mini, + } +} diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NativeMethodsHelper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NativeMethodsHelper.cs index 4a9a59a66c..90436921f2 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NativeMethodsHelper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/NativeMethodsHelper.cs @@ -19,7 +19,8 @@ public bool Is64Bit(IntPtr processHandle) // WOW64 is the x86 emulator that allows 32 bit Windows - based applications to run seamlessly on 64 bit Windows. // If the function succeeds, the return value is a nonzero value. - if (!IsWow64Process(processHandle, out var isWow64)) + var isWow64Process = IsWow64Process(processHandle, out var isWow64); + if (!isWow64Process) { if (EqtTrace.IsVerboseEnabled) { @@ -27,7 +28,10 @@ public bool Is64Bit(IntPtr processHandle) } } - return !isWow64; + var is64Bit = !isWow64; + EqtTrace.Verbose($"NativeMethodsHelper: is Wow64Process: {isWow64Process} is 64bit: {is64Bit}."); + + return is64Bit; } // A pointer to a value that is set to TRUE if the process is running under WOW64. diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs index 2512bca40d..e73b2c6416 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs @@ -8,11 +8,10 @@ namespace Microsoft.TestPlatform.Extensions.BlameDataCollector using System.Globalization; using System.IO; using System.Linq; + using System.Reflection; using System.Runtime.InteropServices; - using System.Threading; using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; - using Microsoft.VisualStudio.TestPlatform.Utilities; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.Win32.SafeHandles; internal class WindowsHangDumper : IHangDumper @@ -24,6 +23,20 @@ public WindowsHangDumper(Action logWarning) this.logWarning = logWarning ?? (_ => { }); } + private static 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("ProcDumpDumper.OutputReceivedCallback: Output received from procdump process: " + data); + } + }; + public void Dump(int processId, string outputDirectory, DumpTypeOption type) { var process = Process.GetProcessById(processId); @@ -102,6 +115,51 @@ internal static void CollectDump(Process process, string outputFile, DumpTypeOpt EqtTrace.Verbose($"WindowsHangDumper.CollectDump: Selected dump type {type}. Dumping {process.Id} - {process.ProcessName} in {outputFile}. "); + if (RuntimeInformation.OSArchitecture == System.Runtime.InteropServices.Architecture.X86) + { + // This is x86 OS, the current process and the target process must be x86. (Or maybe arm64, but let's not worry about that now). + // Just dump it using PInvoke. + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: We are on x86 Windows, both processes are x86, using PInvoke dumper. "); + CollectDumpUsingMiniDumpWriteDump(process, outputFile, type); + } + else if (RuntimeInformation.OSArchitecture == System.Runtime.InteropServices.Architecture.X64) + { + var targetProcessIs64Bit = new NativeMethodsHelper().Is64Bit(process.Handle); + + var currentProcessIs64Bit = RuntimeInformation.ProcessArchitecture == System.Runtime.InteropServices.Architecture.X64; + + if (targetProcessIs64Bit && currentProcessIs64Bit) + { + // Both processes are the same architecture, dump it using the PInvoke call. + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: We are on x64 Windows, and both processes are x64, using PInvoke dumper. "); + CollectDumpUsingMiniDumpWriteDump(process, outputFile, type); + } + else if (!currentProcessIs64Bit) + { + throw new Exception("Current process is not 64-bit, not supported."); + } + else + { + var args = $"--file \"{outputFile}\" --processId {process.Id} --dumpType {type}"; + EqtTrace.Info($"ProcDumpDumper.CollectDump: Running DumpMinitool.x86.exe with arguments: '{args}'."); + var dumpMiniTool = new ProcessHelper().LaunchProcess( + Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "DumpMinitool.x86.exe"), + args, + Path.GetDirectoryName(outputFile), + null, + null, + null, + OutputReceivedCallback) as Process; + dumpMiniTool.WaitForExit(); + EqtTrace.Info($"ProcDumpDumper.CollectDump: DumpMinitool.x86.exe exited with exitcode: '{dumpMiniTool.ExitCode}'."); + } + } + + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: Finished dumping {process.Id} - {process.ProcessName} in {outputFile}. "); + } + + private static void CollectDumpUsingMiniDumpWriteDump(Process process, string outputFile, DumpTypeOption type) + { // Open the file for writing using (var stream = new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) { @@ -151,8 +209,6 @@ internal static void CollectDump(Process process, string outputFile, DumpTypeOpt } } } - - EqtTrace.Verbose($"WindowsHangDumper.CollectDump: Finished dumping {process.Id} - {process.ProcessName} in {outputFile}. "); } private static class NativeMethods From 67d49380482895e9b7899b2c30be34ce0eae36e2 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Wed, 8 Sep 2021 18:16:02 +0200 Subject: [PATCH 04/11] Add dump minitool --- TestPlatform.sln | 17 +++++++++++- .../DumpMinitool.x86/DumpMinitool.x86.csproj | 26 +++++++++++++++++++ .../DumpMinitool/DumpMinitool.csproj | 21 ++++++++++++--- ...tform.Extensions.BlameDataCollector.csproj | 2 ++ 4 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj diff --git a/TestPlatform.sln b/TestPlatform.sln index 45a68993dd..388358d825 100644 --- a/TestPlatform.sln +++ b/TestPlatform.sln @@ -164,7 +164,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TestPlatform.Adap EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.TestPlatform.Execution.Shared", "src\Microsoft.TestPlatform.Execution.Shared\Microsoft.TestPlatform.Execution.Shared.shproj", "{7F26EDA3-C8C4-4B7F-A9B6-D278C2F40A13}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DumpMinitool", "src\DataCollectors\DumpMinitool\DumpMinitool.csproj", "{33A20B85-7024-4112-B1E7-00CD0E4A9F96}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DumpMinitool", "src\DataCollectors\DumpMinitool\DumpMinitool.csproj", "{33A20B85-7024-4112-B1E7-00CD0E4A9F96}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DumpMinitool.x86", "src\DataCollectors\DumpMinitool.x86\DumpMinitool.x86.csproj", "{2C88C923-3D7A-4492-9241-7A489750CAB7}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -807,6 +809,18 @@ Global {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|x64.Build.0 = Release|Any CPU {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|x86.ActiveCfg = Release|Any CPU {33A20B85-7024-4112-B1E7-00CD0E4A9F96}.Release|x86.Build.0 = Release|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Debug|x64.ActiveCfg = Debug|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Debug|x64.Build.0 = Debug|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Debug|x86.ActiveCfg = Debug|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Debug|x86.Build.0 = Debug|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Release|Any CPU.Build.0 = Release|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Release|x64.ActiveCfg = Release|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Release|x64.Build.0 = Release|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Release|x86.ActiveCfg = Release|Any CPU + {2C88C923-3D7A-4492-9241-7A489750CAB7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -877,6 +891,7 @@ Global {2DE99835-A3A3-4922-82AD-6D10D284816D} = {7D4082EA-7AC9-4DFB-98E8-C5E08BDC0EC3} {7F26EDA3-C8C4-4B7F-A9B6-D278C2F40A13} = {ED0C35EB-7F31-4841-A24F-8EB708FFA959} {33A20B85-7024-4112-B1E7-00CD0E4A9F96} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB} + {2C88C923-3D7A-4492-9241-7A489750CAB7} = {B705537C-B82C-4A30-AFA5-6244D9A7DAEB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0541B30C-FF51-4E28-B172-83F5F3934BCD} diff --git a/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj b/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj new file mode 100644 index 0000000000..6b023e6473 --- /dev/null +++ b/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj @@ -0,0 +1,26 @@ + + + + ..\..\..\ + false + true + + + + net451 + net6.0 + true + AnyCPU + true + Exe + false + win7-x86 + false + + + + + + + + diff --git a/src/DataCollectors/DumpMinitool/DumpMinitool.csproj b/src/DataCollectors/DumpMinitool/DumpMinitool.csproj index bb3a8c7f20..0541f2f2bc 100644 --- a/src/DataCollectors/DumpMinitool/DumpMinitool.csproj +++ b/src/DataCollectors/DumpMinitool/DumpMinitool.csproj @@ -1,9 +1,22 @@ + - + ..\..\..\ + false + true + + + + net451 + net6.0 + true + AnyCPU + false Exe - net5.0 - true + false + win7-x86 + false - + + 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 8b6bd9b02f..38da3fa05a 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj @@ -26,6 +26,8 @@ + + From fe27df3efcda2958a3f1908dc25b773f683140f4 Mon Sep 17 00:00:00 2001 From: Medeni Baykal <433724+Haplois@users.noreply.github.com> Date: Thu, 9 Sep 2021 13:44:51 +0200 Subject: [PATCH 05/11] Added DumpMinitool to packages. --- scripts/build.ps1 | 13 +++++++++++++ scripts/build/TestPlatform.Dependencies.props | 2 +- scripts/verify-nupkgs.ps1 | 9 ++++----- ...estPlatform.Extensions.BlameDataCollector.csproj | 4 ++-- .../nuspec/Microsoft.TestPlatform.Portable.nuspec | 8 ++++++++ src/package/nuspec/Microsoft.TestPlatform.nuspec | 8 ++++++-- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/scripts/build.ps1 b/scripts/build.ps1 index d69c552b71..65980a0555 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -397,8 +397,21 @@ function Publish-Package $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 $blameDataCollectorNetFull\DumpMinitool.exe $fullCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetFull\DumpMinitool.pdb $fullCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetFull\DumpMinitool.exe.config $fullCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetFull\DumpMinitool.x86.exe $fullCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetFull\DumpMinitool.x86.pdb $fullCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetFull\DumpMinitool.x86.exe.config $fullCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetStandard\Microsoft.TestPlatform.Extensions.BlameDataCollector.dll $coreCLRExtensionsDir -Force Copy-Item $blameDataCollectorNetStandard\Microsoft.TestPlatform.Extensions.BlameDataCollector.pdb $coreCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetStandard\DumpMinitool.exe $coreCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetStandard\DumpMinitool.pdb $coreCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetStandard\DumpMinitool.exe.config $coreCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetStandard\DumpMinitool.x86.exe $coreCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetStandard\DumpMinitool.x86.pdb $coreCLRExtensionsDir -Force + Copy-Item $blameDataCollectorNetStandard\DumpMinitool.x86.exe.config $coreCLRExtensionsDir -Force # we use this to dump processes on netcore Copy-Item $blameDataCollectorNetStandard\Microsoft.Diagnostics.NETCore.Client.dll $coreCLRExtensionsDir -Force diff --git a/scripts/build/TestPlatform.Dependencies.props b/scripts/build/TestPlatform.Dependencies.props index d0ac4a95fc..a4cfb75bd0 100644 --- a/scripts/build/TestPlatform.Dependencies.props +++ b/scripts/build/TestPlatform.Dependencies.props @@ -1,4 +1,4 @@ - + 15.8.3247 diff --git a/scripts/verify-nupkgs.ps1 b/scripts/verify-nupkgs.ps1 index 4b887182c3..e2c3050634 100644 --- a/scripts/verify-nupkgs.ps1 +++ b/scripts/verify-nupkgs.ps1 @@ -14,13 +14,13 @@ function Verify-Nuget-Packages($packageDirectory, $version) $expectedNumOfFiles = @{ "Microsoft.CodeCoverage" = 59; "Microsoft.NET.Test.Sdk" = 27; - "Microsoft.TestPlatform" = 484; + "Microsoft.TestPlatform" = 488; "Microsoft.TestPlatform.Build" = 21; - "Microsoft.TestPlatform.CLI" = 367; + "Microsoft.TestPlatform.CLI" = 371; "Microsoft.TestPlatform.Extensions.TrxLogger" = 35; "Microsoft.TestPlatform.ObjectModel" = 238; "Microsoft.TestPlatform.AdapterUtilities" = 62; - "Microsoft.TestPlatform.Portable" = 596; + "Microsoft.TestPlatform.Portable" = 604; "Microsoft.TestPlatform.TestHost" = 214; "Microsoft.TestPlatform.TranslationLayer" = 123; } @@ -46,9 +46,8 @@ function Verify-Nuget-Packages($packageDirectory, $version) foreach($unzipNugetPackageDir in $unzipNugetPackageDirs) { $actualNumOfFiles = (Get-ChildItem -Recurse -File -Path $unzipNugetPackageDir).Count - $versionLen = $TPB_Version.Length + 1 # +1 for dot + $versionLen = $version.Length + 1 # +1 for dot $packageKey = (Get-Item $unzipNugetPackageDir).BaseName -replace ".{$versionLen}$" - Write-VerboseLog "verifying package $packageKey." if( $expectedNumOfFiles[$packageKey] -ne $actualNumOfFiles) 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 38da3fa05a..48228c1af9 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj @@ -26,8 +26,8 @@ - - + + diff --git a/src/package/nuspec/Microsoft.TestPlatform.Portable.nuspec b/src/package/nuspec/Microsoft.TestPlatform.Portable.nuspec index d7a6130aaf..c9db7ca2b4 100644 --- a/src/package/nuspec/Microsoft.TestPlatform.Portable.nuspec +++ b/src/package/nuspec/Microsoft.TestPlatform.Portable.nuspec @@ -131,6 +131,10 @@ + + + + @@ -366,6 +370,10 @@ + + + + diff --git a/src/package/nuspec/Microsoft.TestPlatform.nuspec b/src/package/nuspec/Microsoft.TestPlatform.nuspec index e001d5a950..52891a9a68 100644 --- a/src/package/nuspec/Microsoft.TestPlatform.nuspec +++ b/src/package/nuspec/Microsoft.TestPlatform.nuspec @@ -223,9 +223,13 @@ - + + + + + - + From a17684173f4fe68bf8911603f1d557439fa6efa9 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Thu, 9 Sep 2021 15:46:55 +0200 Subject: [PATCH 06/11] Run 32-bit tool for 32-bit dump and 64-bit tool for 64-bit dump when architecture differs for the target process --- TestPlatform.sln | 2 +- scripts/build/TestPlatform.Dependencies.props | 2 +- src/DataCollectors/DumpMinitool/Program.cs | 29 +++++++++++++++ .../WindowsHangDumper.cs | 36 ++++++++++++++----- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/TestPlatform.sln b/TestPlatform.sln index 388358d825..87ae9693fe 100644 --- a/TestPlatform.sln +++ b/TestPlatform.sln @@ -166,7 +166,7 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.TestPlatform.Exec EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DumpMinitool", "src\DataCollectors\DumpMinitool\DumpMinitool.csproj", "{33A20B85-7024-4112-B1E7-00CD0E4A9F96}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DumpMinitool.x86", "src\DataCollectors\DumpMinitool.x86\DumpMinitool.x86.csproj", "{2C88C923-3D7A-4492-9241-7A489750CAB7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DumpMinitool.x86", "src\DataCollectors\DumpMinitool.x86\DumpMinitool.x86.csproj", "{2C88C923-3D7A-4492-9241-7A489750CAB7}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution diff --git a/scripts/build/TestPlatform.Dependencies.props b/scripts/build/TestPlatform.Dependencies.props index a4cfb75bd0..d0ac4a95fc 100644 --- a/scripts/build/TestPlatform.Dependencies.props +++ b/scripts/build/TestPlatform.Dependencies.props @@ -1,4 +1,4 @@ - + 15.8.3247 diff --git a/src/DataCollectors/DumpMinitool/Program.cs b/src/DataCollectors/DumpMinitool/Program.cs index 7d777c1c0f..815cf6bfa9 100644 --- a/src/DataCollectors/DumpMinitool/Program.cs +++ b/src/DataCollectors/DumpMinitool/Program.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Threading; namespace DumpMinitool { @@ -10,6 +11,7 @@ internal class Program { static int Main(string[] args) { + DebuggerBreakpoint.WaitForDebugger("VSTEST_DUMPTOOL_DEBUG"); Console.WriteLine($"Dump minitool: Started with arguments {string.Join(" ", args)}"); if (args?.Length != 6) { @@ -141,4 +143,31 @@ internal enum DumpTypeOption WithHeap, Mini, } + + internal static class DebuggerBreakpoint + { + internal static void WaitForDebugger(string environmentVariable) + { + if (string.IsNullOrWhiteSpace(environmentVariable)) + { + throw new ArgumentException($"'{nameof(environmentVariable)}' cannot be null or whitespace.", nameof(environmentVariable)); + } + + var debugEnabled = Environment.GetEnvironmentVariable(environmentVariable); + if (!string.IsNullOrEmpty(debugEnabled) && debugEnabled.Equals("1", StringComparison.Ordinal)) + { + Console.WriteLine("Waiting for debugger attach..."); + + var currentProcess = Process.GetCurrentProcess(); + Console.WriteLine("Process Id: {0}, Name: {1}", currentProcess.Id, currentProcess.ProcessName); + + while (!Debugger.IsAttached) + { + Thread.Sleep(1000); + } + + Debugger.Break(); + } + } + } } diff --git a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs index e73b2c6416..d06b1e6ed9 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/WindowsHangDumper.cs @@ -119,7 +119,7 @@ internal static void CollectDump(Process process, string outputFile, DumpTypeOpt { // This is x86 OS, the current process and the target process must be x86. (Or maybe arm64, but let's not worry about that now). // Just dump it using PInvoke. - EqtTrace.Verbose($"WindowsHangDumper.CollectDump: We are on x86 Windows, both processes are x86, using PInvoke dumper. "); + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: We are on x86 Windows, both processes are x86, using PInvoke dumper."); CollectDumpUsingMiniDumpWriteDump(process, outputFile, type); } else if (RuntimeInformation.OSArchitecture == System.Runtime.InteropServices.Architecture.X64) @@ -130,20 +130,40 @@ internal static void CollectDump(Process process, string outputFile, DumpTypeOpt if (targetProcessIs64Bit && currentProcessIs64Bit) { - // Both processes are the same architecture, dump it using the PInvoke call. - EqtTrace.Verbose($"WindowsHangDumper.CollectDump: We are on x64 Windows, and both processes are x64, using PInvoke dumper. "); + // Both processes are x64 architecture, dump it using the PInvoke call. + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: We are on x64 Windows, and both processes are x64, using PInvoke dumper."); CollectDumpUsingMiniDumpWriteDump(process, outputFile, type); } - else if (!currentProcessIs64Bit) + else if (!targetProcessIs64Bit && !currentProcessIs64Bit) { - throw new Exception("Current process is not 64-bit, not supported."); + // Both processes are x86 architecture, dump it using the PInvoke call. + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: We are on x64 Windows, and both processes are x86, using PInvoke dumper."); + CollectDumpUsingMiniDumpWriteDump(process, outputFile, type); } else { + string dumpMinitoolName; + if (!currentProcessIs64Bit && targetProcessIs64Bit) + { + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: We are on x64 Windows, datacollector is x86, and target process is x64, using 64-bit MiniDumptool."); + dumpMinitoolName = "DumpMinitool.exe"; + } + else + { + EqtTrace.Verbose($"WindowsHangDumper.CollectDump: We are on x64 Windows, datacollector is x64, and target process is x86, using 32-bit MiniDumptool."); + dumpMinitoolName = "DumpMinitool.x86.exe"; + } + var args = $"--file \"{outputFile}\" --processId {process.Id} --dumpType {type}"; - EqtTrace.Info($"ProcDumpDumper.CollectDump: Running DumpMinitool.x86.exe with arguments: '{args}'."); + var dumpMinitoolPath = Path.Combine(Path.GetDirectoryName(Assembly.GetCallingAssembly().Location), dumpMinitoolName); + if (!File.Exists(dumpMinitoolPath)) + { + throw new FileNotFoundException("Could not find DumpMinitool", dumpMinitoolPath); + } + + EqtTrace.Info($"ProcDumpDumper.CollectDump: Running DumpMinitool: '{dumpMinitoolPath} {args}'."); var dumpMiniTool = new ProcessHelper().LaunchProcess( - Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "DumpMinitool.x86.exe"), + dumpMinitoolPath, args, Path.GetDirectoryName(outputFile), null, @@ -151,7 +171,7 @@ internal static void CollectDump(Process process, string outputFile, DumpTypeOpt null, OutputReceivedCallback) as Process; dumpMiniTool.WaitForExit(); - EqtTrace.Info($"ProcDumpDumper.CollectDump: DumpMinitool.x86.exe exited with exitcode: '{dumpMiniTool.ExitCode}'."); + EqtTrace.Info($"ProcDumpDumper.CollectDump: {dumpMinitoolName} exited with exitcode: '{dumpMiniTool.ExitCode}'."); } } From d6d3ef681e8b199b19cad90c9e73ed927ea85386 Mon Sep 17 00:00:00 2001 From: nohwnd Date: Thu, 9 Sep 2021 16:42:13 +0200 Subject: [PATCH 07/11] Exclude RID --- src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj | 2 +- src/DataCollectors/DumpMinitool/DumpMinitool.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj b/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj index 6b023e6473..1aa9424397 100644 --- a/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj +++ b/src/DataCollectors/DumpMinitool.x86/DumpMinitool.x86.csproj @@ -14,7 +14,7 @@ true Exe false - win7-x86 + win7-x86 false diff --git a/src/DataCollectors/DumpMinitool/DumpMinitool.csproj b/src/DataCollectors/DumpMinitool/DumpMinitool.csproj index 0541f2f2bc..9188916188 100644 --- a/src/DataCollectors/DumpMinitool/DumpMinitool.csproj +++ b/src/DataCollectors/DumpMinitool/DumpMinitool.csproj @@ -14,7 +14,7 @@ false Exe false - win7-x86 + win7-x86 false From 63e1fd7c85e81590b247a2b838f8af06da6deec8 Mon Sep 17 00:00:00 2001 From: Medeni Baykal <433724+Haplois@users.noreply.github.com> Date: Thu, 9 Sep 2021 16:48:17 +0200 Subject: [PATCH 08/11] Enabled signing. --- src/package/sign/sign.proj | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/package/sign/sign.proj b/src/package/sign/sign.proj index 6d43420713..8c6514a604 100644 --- a/src/package/sign/sign.proj +++ b/src/package/sign/sign.proj @@ -90,7 +90,9 @@ - + + + @@ -364,6 +366,8 @@ + + From 4510a9e7f19ac4fa1b028f033f6383bcd610477c Mon Sep 17 00:00:00 2001 From: Medeni Baykal <433724+Haplois@users.noreply.github.com> Date: Thu, 9 Sep 2021 16:56:39 +0200 Subject: [PATCH 09/11] Source build fixes --- .../Microsoft.TestPlatform.Client.csproj | 2 +- .../Microsoft.TestPlatform.Common.csproj | 2 +- .../Microsoft.TestPlatform.CommunicationUtilities.csproj | 2 +- .../Microsoft.TestPlatform.CoreUtilities.csproj | 2 +- .../Microsoft.TestPlatform.CrossPlatEngine.csproj | 2 +- .../Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj | 2 +- .../Microsoft.TestPlatform.Extensions.HtmlLogger.csproj | 2 +- .../Microsoft.TestPlatform.Extensions.TrxLogger.csproj | 2 +- .../Microsoft.TestPlatform.ObjectModel.csproj | 2 +- .../Microsoft.TestPlatform.PlatformAbstractions.csproj | 2 +- .../Microsoft.TestPlatform.TestHostProvider.csproj | 2 +- .../Microsoft.TestPlatform.Utilities.csproj | 2 +- ...Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj | 2 +- src/SettingsMigrator/SettingsMigrator.csproj | 2 +- src/vstest.console/vstest.console.csproj | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.csproj b/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.csproj index 0ac855cdad..2e464bba56 100644 --- a/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.csproj +++ b/src/Microsoft.TestPlatform.Client/Microsoft.TestPlatform.Client.csproj @@ -7,7 +7,7 @@ Microsoft.VisualStudio.TestPlatform.Client netstandard2.0;net451 - netstandard2.0;net6.0 + net6.0 false diff --git a/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj b/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj index 4b99a6d946..23bcc4fad3 100644 --- a/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj +++ b/src/Microsoft.TestPlatform.Common/Microsoft.TestPlatform.Common.csproj @@ -7,7 +7,7 @@ Microsoft.VisualStudio.TestPlatform.Common netstandard2.0;netstandard1.3;net451 - netstandard2.0;net6.0 + net6.0 true false diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.csproj b/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.csproj index af0d4e1d08..f3d0faf79e 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.csproj +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Microsoft.TestPlatform.CommunicationUtilities.csproj @@ -6,7 +6,7 @@ Microsoft.TestPlatform.CommunicationUtilities netstandard2.0;netstandard1.3;net451 - netstandard2.0;net6.0 + net6.0 true false true diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj index 292c088187..f1df0c9839 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj +++ b/src/Microsoft.TestPlatform.CoreUtilities/Microsoft.TestPlatform.CoreUtilities.csproj @@ -9,7 +9,7 @@ netstandard2.0;netstandard1.3;net451;net45 false $(TargetFrameworks);uap10.0;netstandard1.0 - netstandard2.0;net6.0 + net6.0 false diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj b/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj index 2d715fd706..a05965926c 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj @@ -7,7 +7,7 @@ Microsoft.TestPlatform.CrossPlatEngine netstandard2.0;netstandard1.3;net451 - netstandard2.0;net6.0 + net6.0 true false 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 48228c1af9..754d82898f 100644 --- a/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj +++ b/src/Microsoft.TestPlatform.Extensions.BlameDataCollector/Microsoft.TestPlatform.Extensions.BlameDataCollector.csproj @@ -10,7 +10,7 @@ Microsoft.TestPlatform.Extensions.BlameDataCollector netstandard2.0;net472 - netstandard2.0;net6.0 + net6.0 true true false diff --git a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj index 249ff5570e..564d119eb3 100644 --- a/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj +++ b/src/Microsoft.TestPlatform.Extensions.HtmlLogger/Microsoft.TestPlatform.Extensions.HtmlLogger.csproj @@ -8,7 +8,7 @@ Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger true netstandard2.0;net451 - netstandard2.0;net6.0 + net6.0 false diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.csproj b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.csproj index 95348de4c4..a370d444f5 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.csproj +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/Microsoft.TestPlatform.Extensions.TrxLogger.csproj @@ -8,7 +8,7 @@ Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger true netstandard2.0;net451 - netstandard2.0;net6.0 + net6.0 false diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj index 0a519bfae6..451c871c83 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj @@ -8,7 +8,7 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel net45;net451;netcoreapp2.1;netcoreapp1.0;netstandard2.0;netstandard1.3 $(TargetFrameworks);uap10.0;netstandard1.0 - netstandard2.0;net6.0 + net6.0 Microsoft.TestPlatform.ObjectModel false diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/Microsoft.TestPlatform.PlatformAbstractions.csproj b/src/Microsoft.TestPlatform.PlatformAbstractions/Microsoft.TestPlatform.PlatformAbstractions.csproj index b7a59cb394..2d4f07e3f3 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/Microsoft.TestPlatform.PlatformAbstractions.csproj +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/Microsoft.TestPlatform.PlatformAbstractions.csproj @@ -10,7 +10,7 @@ Microsoft.TestPlatform.PlatformAbstractions net45;net451;netcoreapp1.0;netcoreapp2.1;netstandard1.3;netstandard2.0 $(TargetFrameworks);uap10.0;netstandard1.0 - netstandard2.0;net6.0 + net6.0 true false diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj b/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj index a12114d680..6af482f19c 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj +++ b/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj @@ -7,7 +7,7 @@ Microsoft.TestPlatform.TestHostRuntimeProvider netstandard2.0;net451 - netstandard2.0;net6.0 + net6.0 $(DefineConstants);DOTNET_BUILD_FROM_SOURCE true false diff --git a/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.csproj b/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.csproj index 358a76f666..5ca7b108d1 100644 --- a/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.csproj +++ b/src/Microsoft.TestPlatform.Utilities/Microsoft.TestPlatform.Utilities.csproj @@ -7,7 +7,7 @@ Microsoft.TestPlatform.Utilities netstandard2.0;netstandard1.3;net451 - netstandard2.0;net6.0 + net6.0 false diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj index 9af1703496..70ced3649b 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj @@ -7,7 +7,7 @@ Microsoft.TestPlatform.VsTestConsole.TranslationLayer netstandard2.0;net451 - netstandard2.0;net6.0 + net6.0 true false diff --git a/src/SettingsMigrator/SettingsMigrator.csproj b/src/SettingsMigrator/SettingsMigrator.csproj index d9d37e5c3a..f1a3b92d9e 100644 --- a/src/SettingsMigrator/SettingsMigrator.csproj +++ b/src/SettingsMigrator/SettingsMigrator.csproj @@ -7,7 +7,7 @@ SettingsMigrator net451 - netstandard2.0;net6.0 + net6.0 true true Exe diff --git a/src/vstest.console/vstest.console.csproj b/src/vstest.console/vstest.console.csproj index 1343e6d904..7c55559126 100644 --- a/src/vstest.console/vstest.console.csproj +++ b/src/vstest.console/vstest.console.csproj @@ -8,7 +8,7 @@ vstest.console netcoreapp2.1;net451 - netcoreapp2.1;net6.0 + net6.0 true Exe false From aa5a01b3509d2e7702c3ce4cd9d793c1ca37fa5b Mon Sep 17 00:00:00 2001 From: Medeni Baykal <433724+Haplois@users.noreply.github.com> Date: Thu, 9 Sep 2021 17:51:59 +0200 Subject: [PATCH 10/11] Added DumpMiniTool to files to sign checked. --- scripts/verify-sign.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/verify-sign.ps1 b/scripts/verify-sign.ps1 index 3cf75f485b..b4f3ea4997 100644 --- a/scripts/verify-sign.ps1 +++ b/scripts/verify-sign.ps1 @@ -30,7 +30,14 @@ $env:TP_TOOLS_DIR = Join-Path $env:TP_ROOT_DIR "tools" Write-Verbose "Setup build configuration." $TPB_SignCertificate = $Certificate $TPB_Configuration = $Configuration -$TPB_AssembliesPattern = @("*test*.dll", "*qualitytools*.dll", "*test*.exe", "*datacollector*.dll", "*datacollector*.exe", "QTAgent*.exe", "Microsoft.VisualStudio*.dll", "Microsoft.TestPlatform.Build.dll", "Microsoft.DiaSymReader.dll", "Microsoft.IntelliTrace*.dll", "concrt140.dll", "msvcp140.dll", "vccorlib140.dll", "vcruntime140.dll", "codecoveragemessages.dll", "covrun32.dll", "msdia140.dll", "covrun64.dll", "IntelliTrace.exe", "ProcessSnapshotCleanup.exe", "TDEnvCleanup.exe", "CodeCoverage.exe", "Microsoft.ShDocVw.dll", "UIAComwrapper.dll", "Interop.UIAutomationClient.dll", "SettingsMigrator.exe", "Newtonsoft.Json.dll") +$TPB_AssembliesPattern = @( + "*test*.dll", "*qualitytools*.dll", "*test*.exe", "*datacollector*.dll", "*datacollector*.exe", + "QTAgent*.exe", "Microsoft.VisualStudio*.dll", "Microsoft.TestPlatform.Build.dll", "Microsoft.DiaSymReader.dll", + "Microsoft.IntelliTrace*.dll", "concrt140.dll", "msvcp140.dll", "vccorlib140.dll", "vcruntime140.dll", "codecoveragemessages.dll", + "covrun32.dll", "msdia140.dll", "covrun64.dll", "IntelliTrace.exe", "ProcessSnapshotCleanup.exe", "TDEnvCleanup.exe", + "CodeCoverage.exe", "Microsoft.ShDocVw.dll", "UIAComwrapper.dll", "Interop.UIAutomationClient.dll", "SettingsMigrator.exe", + "Newtonsoft.Json.dll", "DumpMinitool.*" +) function Verify-Assemblies { From be2dc9c8033a30e81b5bca5eca6d0db4a5e02094 Mon Sep 17 00:00:00 2001 From: Medeni Baykal <433724+Haplois@users.noreply.github.com> Date: Thu, 9 Sep 2021 19:20:48 +0200 Subject: [PATCH 11/11] Update verify-sign.ps1 --- scripts/verify-sign.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/verify-sign.ps1 b/scripts/verify-sign.ps1 index b4f3ea4997..98d437db01 100644 --- a/scripts/verify-sign.ps1 +++ b/scripts/verify-sign.ps1 @@ -36,7 +36,7 @@ $TPB_AssembliesPattern = @( "Microsoft.IntelliTrace*.dll", "concrt140.dll", "msvcp140.dll", "vccorlib140.dll", "vcruntime140.dll", "codecoveragemessages.dll", "covrun32.dll", "msdia140.dll", "covrun64.dll", "IntelliTrace.exe", "ProcessSnapshotCleanup.exe", "TDEnvCleanup.exe", "CodeCoverage.exe", "Microsoft.ShDocVw.dll", "UIAComwrapper.dll", "Interop.UIAutomationClient.dll", "SettingsMigrator.exe", - "Newtonsoft.Json.dll", "DumpMinitool.*" + "Newtonsoft.Json.dll", "DumpMinitool*.exe" ) function Verify-Assemblies