提交 f40d5585 编写于 作者: I Ivan Basov 提交者: GitHub

create dump files when VS instance crashes or hangs (integration tests) (#20129)

上级 e6f090e9
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
namespace Microsoft.CodeAnalysis.Test.Utilities
{
public class ProcDumpRunner
{
public const string ProcDumpPathEnvironmentVariableKey = "ProcDumpPath";
private const string ProcDumpExeFileName = "procdump.exe";
// /accepteula command line option to automatically accept the Sysinternals license agreement.
// -ma Write a 'Full' dump file. Includes All the Image, Mapped and Private memory.
// -e Write a dump when the process encounters an unhandled exception. Include the 1 to create dump on first chance exceptions.
// -t Write a dump when the process terminates.
// -w Wait for the specified process to launch if it's not running.
private const string ProcDumpSwitches = "/accepteula -ma -e -t -w";
/// <summary>
/// Starts procdump.exe against the process.
/// </summary>
/// <param name="procDumpPath">The path to the procdump executable</param>
/// <param name="processId">process id</param>
/// <param name="processName">process name</param>
/// <param name="loggingMethod">method to log diagnostics to</param>
/// <param name="destinationDirectory">destination directory for dumps</param>
public static void StartProcDump(string procDumpPath, int processId, string processName, string destinationDirectory, Action<string> loggingMethod)
{
if (!string.IsNullOrWhiteSpace(procDumpPath))
{
var procDumpFilePath = Path.Combine(procDumpPath, ProcDumpExeFileName);
var dumpDirectory = Path.Combine(destinationDirectory, "Dumps");
Directory.CreateDirectory(dumpDirectory);
var procDumpProcess = Process.Start(procDumpFilePath, $" {ProcDumpSwitches} {processId} \"{dumpDirectory}\"");
loggingMethod($"Launched ProcDump attached to {processName} (process Id: {processId})");
}
else
{
loggingMethod($"Environment variables do not contain {ProcDumpPathEnvironmentVariableKey} (path to procdump.exe). Will skip attaching procdump to VS instance.");
}
}
}
}
\ No newline at end of file
......@@ -129,6 +129,7 @@
<Compile Include="FX\CappedStringWriter.cs" />
<Compile Include="FX\CultureHelpers.cs" />
<Compile Include="FX\DirectoryHelper.cs" />
<Compile Include="FX\DumpProcRunner.cs" />
<Compile Include="FX\EncodingUtilities.cs" />
<Compile Include="FX\EnsureEnglishUICulture.cs" />
<Compile Include="FX\EnsureInvariantCulture.cs" />
......
......@@ -11,15 +11,19 @@ namespace RunTests
internal struct TestExecutionOptions
{
internal string XunitPath { get; }
internal string ProcDumpPath { get; }
internal string LogFilePath { get; }
internal string Trait { get; }
internal string NoTrait { get; }
internal bool UseHtml { get; }
internal bool Test64 { get; }
internal bool TestVsi { get; }
internal TestExecutionOptions(string xunitPath, string trait, string noTrait, bool useHtml, bool test64, bool testVsi)
internal TestExecutionOptions(string xunitPath, string procDumpPath, string logFilePath, string trait, string noTrait, bool useHtml, bool test64, bool testVsi)
{
XunitPath = xunitPath;
ProcDumpPath = procDumpPath;
LogFilePath = logFilePath;
Trait = trait;
NoTrait = noTrait;
UseHtml = useHtml;
......
......@@ -68,7 +68,7 @@ internal class Options
public string XunitPath { get; set; }
/// <summary>
/// When set the log file ffor executing tests will be written to the prescribed location.
/// When set the log file for executing tests will be written to the prescribed location.
/// </summary>
public string LogFilePath { get; set; }
......
......@@ -41,19 +41,21 @@ public static void OpenFile(string file)
string workingDirectory = null,
bool captureOutput = false,
bool displayWindow = true,
Dictionary<string, string> environmentVariables = null)
Dictionary<string, string> environmentVariables = null,
Action<Process> onProcessStartHandler = null)
{
cancellationToken.ThrowIfCancellationRequested();
var taskCompletionSource = new TaskCompletionSource<ProcessOutput>();
var processStartInfo = CreateProcessStartInfo(executable, arguments, workingDirectory, captureOutput, displayWindow);
return CreateProcessTask(processStartInfo, lowPriority, taskCompletionSource, cancellationToken);
var processStartInfo = CreateProcessStartInfo(executable, arguments, workingDirectory, captureOutput, displayWindow, environmentVariables);
return CreateProcessTask(processStartInfo, lowPriority, taskCompletionSource, onProcessStartHandler, cancellationToken);
}
private static Task<ProcessOutput> CreateProcessTask(
ProcessStartInfo processStartInfo,
bool lowPriority,
TaskCompletionSource<ProcessOutput> taskCompletionSource,
Action<Process> onProcessStartHandler,
CancellationToken cancellationToken)
{
var errorLines = new List<string>();
......@@ -112,6 +114,7 @@ public static void OpenFile(string file)
});
process.Start();
onProcessStartHandler?.Invoke(process);
if (lowPriority)
{
......
......@@ -9,6 +9,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
namespace RunTests
{
......@@ -81,7 +82,13 @@ public async Task<TestResult> RunTestAsync(AssemblyInfo assemblyInfo, Cancellati
// an empty log just in case, so our runner will still fail.
File.Create(resultsFilePath).Close();
var dumpOutputFilePath = Path.Combine(resultsDir, $"{assemblyInfo.DisplayName}.dmp");
// Define environment variables for processes started via ProcessRunner.
var environmentVariables = new Dictionary<string, string>();
environmentVariables.Add(ProcDumpRunner.ProcDumpPathEnvironmentVariableKey, _options.ProcDumpPath);
var outputDirectory = _options.LogFilePath != null
? Path.GetDirectoryName(_options.LogFilePath)
: Directory.GetCurrentDirectory();
var start = DateTime.UtcNow;
var xunitPath = _options.XunitPath;
......@@ -91,7 +98,9 @@ public async Task<TestResult> RunTestAsync(AssemblyInfo assemblyInfo, Cancellati
lowPriority: false,
displayWindow: false,
captureOutput: true,
cancellationToken: cancellationToken);
cancellationToken: cancellationToken,
environmentVariables: environmentVariables,
onProcessStartHandler: (process) => ProcDumpRunner.StartProcDump(_options.ProcDumpPath, process.Id, process.ProcessName, outputDirectory, Logger.Log));
var span = DateTime.UtcNow - start;
if (processOutput.ExitCode != 0)
......
......@@ -23,6 +23,8 @@ internal sealed partial class Program
internal const int ExitSuccess = 0;
internal const int ExitFailure = 1;
private const long MaxTotalDumpSizeInMegabytes = 4096;
internal static int Main(string[] args)
{
Logger.Log("RunTest command line");
......@@ -42,7 +44,9 @@ internal static int Main(string[] args)
cts.Cancel();
};
return Run(options, cts.Token).GetAwaiter().GetResult();
var result = Run(options, cts.Token).GetAwaiter().GetResult();
CheckTotalDumpFilesSize();
return result;
}
private static async Task<int> Run(Options options, CancellationToken cancellationToken)
......@@ -310,6 +314,8 @@ private static ITestExecutor CreateTestExecutor(Options options)
{
var testExecutionOptions = new TestExecutionOptions(
xunitPath: options.XunitPath,
procDumpPath: options.ProcDumpPath,
logFilePath: options.LogFilePath,
trait: options.Trait,
noTrait: options.NoTrait,
useHtml: options.UseHtml,
......@@ -377,5 +383,25 @@ private static async Task SendRunStats(Options options, IDataStorage dataStorage
Logger.Log("Unable to send results");
}
}
/// <summary>
/// Checks the total size of dump file and removes files exceeding a limit.
/// </summary>
private static void CheckTotalDumpFilesSize()
{
var directory = Directory.GetCurrentDirectory();
var dumpFiles = Directory.EnumerateFiles(directory, "*.dmp", SearchOption.AllDirectories).ToArray();
long currentTotalSize = 0;
foreach(var dumpFile in dumpFiles)
{
long fileSizeInMegabytes = (new FileInfo(dumpFile).Length / 1024) / 1024;
currentTotalSize += fileSizeInMegabytes;
if (currentTotalSize > MaxTotalDumpSizeInMegabytes)
{
File.Delete(dumpFile);
}
}
}
}
}
\ No newline at end of file
......@@ -61,6 +61,7 @@
<Compile Include="Program.cs" />
<Compile Include="TestRunner.cs" />
<Compile Include="ConsoleUtil.cs" />
<Compile Include="..\..\..\Test\Utilities\Portable\FX\DumpProcRunner.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.Json.cs" />
......
......@@ -93,7 +93,7 @@ public async Task WpfInteractionAsync()
VisualStudio.InteractiveWindow.SubmitText("b = null; w.Close(); w = null;");
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/19232")]
[Fact]
public void TypingHelpDirectiveWorks()
{
VisualStudio.Workspace.SetUseSuggestionMode(true);
......
......@@ -3,6 +3,8 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
......
......@@ -5,11 +5,14 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using EnvDTE;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Interop;
using Microsoft.VisualStudio.Setup.Configuration;
using Process = System.Diagnostics.Process;
......@@ -37,7 +40,6 @@ static VisualStudioInstanceFactory()
{
throw new PlatformNotSupportedException("The Visual Studio Integration Test Framework is only supported on Visual Studio 15.0 and later.");
}
}
public VisualStudioInstanceFactory()
......@@ -50,8 +52,7 @@ private static void FirstChanceExceptionHandler(object sender, FirstChanceExcept
{
try
{
var assemblyPath = typeof(VisualStudioInstanceFactory).Assembly.Location;
var assemblyDirectory = Path.GetDirectoryName(assemblyPath);
var assemblyDirectory = GetAssemblyDirectory();
var testName = CaptureTestNameAttribute.CurrentName ?? "Unknown";
var logDir = Path.Combine(assemblyDirectory, "xUnitResults", "Screenshots");
var baseFileName = $"{testName}-{eventArgs.Exception.GetType().Name}-{DateTime.Now:HH.mm.ss}";
......@@ -156,6 +157,10 @@ private void UpdateCurrentlyRunningInstance(ImmutableHashSet<string> requiredPac
installationPath = instance.GetInstallationPath();
hostProcess = StartNewVisualStudioProcess(installationPath);
var procDumpPath = Environment.GetEnvironmentVariable(ProcDumpRunner.ProcDumpPathEnvironmentVariableKey);
ProcDumpRunner.StartProcDump(procDumpPath, hostProcess.Id, hostProcess.ProcessName, Path.Combine(GetAssemblyDirectory(), "xUnitResults"), s => Debug.WriteLine(s));
// We wait until the DTE instance is up before we're good
dte = IntegrationHelper.WaitForNotNullAsync(() => IntegrationHelper.TryLocateDteForProcess(hostProcess)).Result;
}
......@@ -274,12 +279,17 @@ private static Process StartNewVisualStudioProcess(string installationPath)
IntegrationHelper.KillProcess("dexplore");
var process = Process.Start(vsExeFile, VsLaunchArgs);
Debug.WriteLine($"Launched a new instance of Visual Studio. (ID: {process.Id})");
return process;
}
private static string GetAssemblyDirectory()
{
var assemblyPath = typeof(VisualStudioInstanceFactory).Assembly.Location;
return Path.GetDirectoryName(assemblyPath);
}
public void Dispose()
{
_currentlyRunningInstance?.Close();
......
......@@ -193,6 +193,10 @@
<Project>{e2e889a5-2489-4546-9194-47c63e49eaeb}</Project>
<Name>Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\Test\Utilities\Portable\TestUtilities.csproj">
<Project>{ccbd3438-3e84-40a9-83ad-533f23bcfca5}</Project>
<Name>TestUtilities</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\Workspaces\Core\Portable\Workspaces.csproj">
<Project>{5f8d2414-064a-4b3a-9b42-8e2a04246be5}</Project>
<Name>Workspaces</Name>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册