Skip to content

Commit

Permalink
DAR: Include OSX crash logs in exception (#1363)
Browse files Browse the repository at this point in the history
This PR modifies DAR to try and find and include OSX crash log information if a test fails due to a debug adapter crashing.
  • Loading branch information
gregg-miskelly authored Oct 18, 2022
1 parent 4908ae6 commit 3511d55
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 3 deletions.
73 changes: 70 additions & 3 deletions test/DebugAdapterRunner/DebugAdapterCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -218,6 +219,7 @@ public override void Run(DebugAdapterRunner runner)
}

string messageStart;
string messageSuffix = string.Empty;
if (runner.DebugAdapter.HasExited)
{
if (runner.HasAsserted())
Expand All @@ -226,7 +228,21 @@ public override void Run(DebugAdapterRunner runner)
}
else
{
messageStart = string.Format(CultureInfo.CurrentCulture, "The debugger process has exited with code '{0}' without sending all expected responses.", runner.DebugAdapter.ExitCode);
int exitCode = runner.DebugAdapter.ExitCode;
messageStart = string.Format(CultureInfo.CurrentCulture, "The debugger process has exited with code '{0}' without sending all expected responses.", exitCode);

// OSX will normally write out nice crash reports that we can include if the debug adapter crashes.
// Try to include them in the exception.
// NOTE that OSX uses exit code 128+<signal_number> if there is a crash. The highest documented signal
// number is 31 (User defined signal 2). See 'man signal' for more numbers.
// The most common is 139 (SigSegV).
if (exitCode > 128 && exitCode <= 128+31 && RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
if (TryFindOSXCrashReport(runner, out string crashReport))
{
messageSuffix = "\n\nCrash report:\n" + crashReport;
}
}
}
}
else if (getMessageExeception is TimeoutException)
Expand Down Expand Up @@ -265,8 +281,8 @@ public override void Run(DebugAdapterRunner runner)
actualResponseText += string.Format(CultureInfo.CurrentCulture, "{0}. {1}\n", (i + 1), JsonConvert.SerializeObject(responseList[i]));
}

string errorMessage = string.Format(CultureInfo.CurrentCulture, "{0}\nExpected =\n{1}\nActual Responses =\n{2}",
messageStart, expectedResponseText, actualResponseText);
string errorMessage = string.Format(CultureInfo.CurrentCulture, "{0}\nExpected =\n{1}\nActual Responses =\n{2}{3}",
messageStart, expectedResponseText, actualResponseText, messageSuffix);

throw new DARException(errorMessage);
}
Expand Down Expand Up @@ -331,5 +347,56 @@ public override void Run(DebugAdapterRunner runner)
}
}
}

private static bool TryFindOSXCrashReport(DebugAdapterRunner runner, out string crashReport)
{
crashReport = null;

string homeDir = Environment.GetEnvironmentVariable("HOME");
if (String.IsNullOrEmpty(homeDir))
return false;

string crashReportDirectory = Path.Combine(homeDir, "Library/Logs/DiagnosticReports/");
if (!Directory.Exists(crashReportDirectory))
return false;

string debugAdapterFilePath = runner.DebugAdapter?.StartInfo?.FileName;
if (String.IsNullOrEmpty(debugAdapterFilePath))
return false;
string debugAdapterFileName = Path.GetFileName(debugAdapterFilePath);

string crashReportPath = null;
for (int retry = 0; retry < 15; retry++)
{
IEnumerable<string> crashReportFiles = Directory.EnumerateFiles(crashReportDirectory, debugAdapterFileName + "*")
.Select(name => Path.Combine(crashReportDirectory, name));

(string Path, DateTime CreationTime) latestCrashReport = crashReportFiles
.Select<string, (string Path, DateTime CreationTime)>(path => new(path, File.GetCreationTime(path)))
.OrderByDescending(tuple => tuple.CreationTime)
.FirstOrDefault();
if (latestCrashReport.Path == null ||
latestCrashReport.CreationTime < runner.StartTime)
{
// It can take a little while for the crash report to get written, so wait a second and try again
// NOTE: From what I have seen, crash logs are written in one shot, so, unless we get very unlucky,
// we should get the whole thing.
Thread.Sleep(1000);
continue;
}

crashReportPath = latestCrashReport.Path;
break;
}

if (crashReportPath == null)
{
// Crash log was never found
return false;
}

crashReport = File.ReadAllText(crashReportPath);
return true;
}
}
}
2 changes: 2 additions & 0 deletions test/DebugAdapterRunner/DebugAdapterRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class DebugAdapterRunner
// The current thread id which is automatically updated when the tool receives a stopped event
public int CurrentThreadId;

public DateTime StartTime { get; } = DateTime.Now;

// Keep a trace of the debug adapter output if requested
private StringBuilder _debugAdapterOutput = new StringBuilder();

Expand Down

0 comments on commit 3511d55

Please sign in to comment.