[Harness] Make CrashSnapshotReporter injectable and add unit tests (#8129)
* Move AppInstallMonitorLog * Move Helpers.GenerateStableGuid * Move DirectoryUtilities * Move DirectoryUtilities.RootDirectory * Add CrashSnapshot ctor * refactor CrashReportSnapshot * Add WrenchLog * Move stuff out of Harness * Remove RootDirectory * Revert RootDirectory * Remove Initialize() calls * Make CrashReportSnapshot injectable * Remove Harness dependency from CrashReportSnapshot * Add device snapshot tests * Trim down IHarness and make properties of Harness readonly * Memoize xcoderoot * Use MlaunchArguments * Add MlaunchArguments docs * Add CrashReportSnapshot docs * Rename method * Rename class * Refactor CrashSnapshotReporter * Inject AppRunner * Indent parameters * Indent parameters * Fix unit tests * Fix gathering crash logs * Add crash logs to output * Fix getting xcode root * Fix alignment Co-authored-by: Premek Vysoky <prvysoky@microsoft.com>
This commit is contained in:
Родитель
fcbb90982f
Коммит
ec420e6740
|
@ -54,9 +54,11 @@ namespace Xharness {
|
|||
readonly ISimulatorsLoaderFactory simulatorsLoaderFactory;
|
||||
readonly ISimpleListenerFactory listenerFactory;
|
||||
readonly IDeviceLoaderFactory devicesLoaderFactory;
|
||||
readonly ICrashSnapshotReporterFactory snapshotReporterFactory;
|
||||
readonly ICaptureLogFactory captureLogFactory;
|
||||
readonly IDeviceLogCapturerFactory deviceLogCapturerFactory;
|
||||
readonly IResultParser resultParser;
|
||||
|
||||
readonly RunMode mode;
|
||||
readonly bool isSimulator;
|
||||
readonly AppRunnerTarget target;
|
||||
|
@ -95,6 +97,7 @@ namespace Xharness {
|
|||
ISimulatorsLoaderFactory simulatorsFactory,
|
||||
ISimpleListenerFactory simpleListenerFactory,
|
||||
IDeviceLoaderFactory devicesFactory,
|
||||
ICrashSnapshotReporterFactory snapshotReporterFactory,
|
||||
ICaptureLogFactory captureLogFactory,
|
||||
IDeviceLogCapturerFactory deviceLogCapturerFactory,
|
||||
IResultParser resultParser,
|
||||
|
@ -116,6 +119,7 @@ namespace Xharness {
|
|||
this.simulatorsLoaderFactory = simulatorsFactory ?? throw new ArgumentNullException (nameof (simulatorsFactory));
|
||||
this.listenerFactory = simpleListenerFactory ?? throw new ArgumentNullException (nameof (simpleListenerFactory));
|
||||
this.devicesLoaderFactory = devicesFactory ?? throw new ArgumentNullException (nameof (devicesFactory));
|
||||
this.snapshotReporterFactory = snapshotReporterFactory ?? throw new ArgumentNullException (nameof (snapshotReporterFactory));
|
||||
this.captureLogFactory = captureLogFactory ?? throw new ArgumentNullException (nameof (captureLogFactory));
|
||||
this.deviceLogCapturerFactory = deviceLogCapturerFactory ?? throw new ArgumentNullException (nameof (deviceLogCapturerFactory));
|
||||
this.resultParser = resultParser ?? throw new ArgumentNullException (nameof (resultParser));
|
||||
|
@ -426,15 +430,15 @@ namespace Xharness {
|
|||
|
||||
public async Task<int> RunAsync ()
|
||||
{
|
||||
CrashReportSnapshot crash_reports;
|
||||
ILog deviceSystemLog = null;
|
||||
ILog listener_log = null;
|
||||
ILog run_log = MainLog;
|
||||
|
||||
if (!isSimulator)
|
||||
FindDevice ();
|
||||
|
||||
crash_reports = new CrashReportSnapshot (harness, MainLog, Logs, isDevice: !isSimulator, deviceName);
|
||||
var crashLogs = new Logs (Logs.Directory);
|
||||
|
||||
ICrashSnapshotReporter crash_reports = snapshotReporterFactory.Create (MainLog, crashLogs, isDevice: !isSimulator, deviceName);
|
||||
|
||||
var args = new List<string> ();
|
||||
if (!string.IsNullOrEmpty (harness.XcodeRoot)) {
|
||||
|
@ -654,7 +658,7 @@ namespace Xharness {
|
|||
|
||||
AddDeviceName (args);
|
||||
|
||||
deviceSystemLog = Logs.Create ($"device-{deviceName}-{Helpers.Timestamp}.log", "Device log");
|
||||
var deviceSystemLog = Logs.Create ($"device-{deviceName}-{Helpers.Timestamp}.log", "Device log");
|
||||
var deviceLogCapturer = deviceLogCapturerFactory.Create (harness.HarnessLog,deviceSystemLog, deviceName);
|
||||
deviceLogCapturer.StartCapture ();
|
||||
|
||||
|
@ -754,8 +758,10 @@ namespace Xharness {
|
|||
if (!success.Value) {
|
||||
int pid = 0;
|
||||
string crash_reason = null;
|
||||
foreach (var crash in crash_reports.Logs) {
|
||||
foreach (var crashLog in crashLogs) {
|
||||
try {
|
||||
Logs.Add (crashLog);
|
||||
|
||||
if (pid == 0) {
|
||||
// Find the pid
|
||||
using (var log_reader = MainLog.GetReader ()) {
|
||||
|
@ -775,7 +781,7 @@ namespace Xharness {
|
|||
}
|
||||
}
|
||||
|
||||
using (var crash_reader = crash.GetReader ()) {
|
||||
using (var crash_reader = crashLog.GetReader ()) {
|
||||
var text = crash_reader.ReadToEnd ();
|
||||
|
||||
var reader = System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonReader (Encoding.UTF8.GetBytes (text), new XmlDictionaryReaderQuotas ());
|
||||
|
@ -796,14 +802,14 @@ namespace Xharness {
|
|||
variation,
|
||||
$"App Crash {AppInformation.AppName} {variation}",
|
||||
$"App crashed {crash_reason}.",
|
||||
crash_reports.Log.FullPath,
|
||||
MainLog.FullPath,
|
||||
harness.XmlJargon);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
harness.Log (2, "Failed to process crash report '{1}': {0}", e.Message, crash.Description);
|
||||
harness.Log (2, "Failed to process crash report '{1}': {0}", e.Message, crashLog.Description);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty (crash_reason)) {
|
||||
|
@ -820,7 +826,7 @@ namespace Xharness {
|
|||
variation,
|
||||
$"App Crash {AppInformation.AppName} {variation}",
|
||||
$"App crashed: {FailureMessage}",
|
||||
crash_reports.Log.FullPath,
|
||||
MainLog.FullPath,
|
||||
harness.XmlJargon);
|
||||
}
|
||||
} else if (launch_failure) {
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xharness.Logging;
|
||||
|
||||
namespace Xharness
|
||||
{
|
||||
public class CrashReportSnapshot
|
||||
{
|
||||
readonly IHarness harness;
|
||||
readonly bool isDevice;
|
||||
readonly string deviceName;
|
||||
|
||||
public ILog Log { get; }
|
||||
public ILogs Logs { get; }
|
||||
|
||||
public HashSet<string> InitialSet { get; private set; }
|
||||
|
||||
public CrashReportSnapshot (IHarness harness, ILog log, ILogs logs, bool isDevice, string deviceName)
|
||||
{
|
||||
this.harness = harness ?? throw new ArgumentNullException (nameof (harness));
|
||||
this.Log = log ?? throw new ArgumentNullException (nameof (log));
|
||||
this.Logs = logs ?? throw new ArgumentNullException (nameof (logs));
|
||||
this.isDevice = isDevice;
|
||||
this.deviceName = deviceName;
|
||||
}
|
||||
|
||||
public async Task StartCaptureAsync ()
|
||||
{
|
||||
InitialSet = await CreateCrashReportsSnapshotAsync ();
|
||||
}
|
||||
|
||||
public async Task EndCaptureAsync (TimeSpan timeout)
|
||||
{
|
||||
// Check for crash reports
|
||||
var crash_report_search_done = false;
|
||||
var crash_report_search_timeout = timeout.TotalSeconds;
|
||||
var watch = new Stopwatch ();
|
||||
watch.Start ();
|
||||
do {
|
||||
var end_crashes = await CreateCrashReportsSnapshotAsync ();
|
||||
end_crashes.ExceptWith (InitialSet);
|
||||
if (end_crashes.Count > 0) {
|
||||
Log.WriteLine ("Found {0} new crash report(s)", end_crashes.Count);
|
||||
List<ILogFile> crash_reports;
|
||||
if (!isDevice) {
|
||||
crash_reports = new List<ILogFile> (end_crashes.Count);
|
||||
foreach (var path in end_crashes) {
|
||||
Logs.AddFile (path, $"Crash report: {Path.GetFileName (path)}");
|
||||
}
|
||||
} else {
|
||||
// Download crash reports from the device. We put them in the project directory so that they're automatically deleted on wrench
|
||||
// (if we put them in /tmp, they'd never be deleted).
|
||||
var downloaded_crash_reports = new List<ILogFile> ();
|
||||
foreach (var file in end_crashes) {
|
||||
var name = Path.GetFileName (file);
|
||||
var crash_report_target = Logs.Create (name, $"Crash report: {name}", timestamp: false);
|
||||
var sb = new List<string> ();
|
||||
sb.Add ($"--download-crash-report={file}");
|
||||
sb.Add ($"--download-crash-report-to={crash_report_target.Path}");
|
||||
sb.Add ("--sdkroot");
|
||||
sb.Add (harness.XcodeRoot);
|
||||
if (!string.IsNullOrEmpty (deviceName)) {
|
||||
sb.Add ("--devname");
|
||||
sb.Add (deviceName);
|
||||
}
|
||||
var result = await harness.ProcessManager.ExecuteCommandAsync (harness.MlaunchPath, sb, Log, TimeSpan.FromMinutes (1));
|
||||
if (result.Succeeded) {
|
||||
Log.WriteLine ("Downloaded crash report {0} to {1}", file, crash_report_target.Path);
|
||||
crash_report_target = await SymbolicateCrashReportAsync (crash_report_target);
|
||||
downloaded_crash_reports.Add (crash_report_target);
|
||||
} else {
|
||||
Log.WriteLine ("Could not download crash report {0}", file);
|
||||
}
|
||||
}
|
||||
crash_reports = downloaded_crash_reports;
|
||||
}
|
||||
foreach (var cp in crash_reports) {
|
||||
WrenchLog.WriteLine ("AddFile: {0}", cp.Path);
|
||||
Log.WriteLine (" {0}", cp.Path);
|
||||
}
|
||||
crash_report_search_done = true;
|
||||
} else {
|
||||
if (watch.Elapsed.TotalSeconds > crash_report_search_timeout) {
|
||||
crash_report_search_done = true;
|
||||
} else {
|
||||
Log.WriteLine ("No crash reports, waiting a second to see if the crash report service just didn't complete in time ({0})", (int) (crash_report_search_timeout - watch.Elapsed.TotalSeconds));
|
||||
Thread.Sleep (TimeSpan.FromSeconds (1));
|
||||
}
|
||||
}
|
||||
} while (!crash_report_search_done);
|
||||
}
|
||||
|
||||
async Task<ILogFile> SymbolicateCrashReportAsync (ILogFile report)
|
||||
{
|
||||
var symbolicatecrash = Path.Combine (harness.XcodeRoot, "Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash");
|
||||
if (!File.Exists (symbolicatecrash))
|
||||
symbolicatecrash = Path.Combine (harness.XcodeRoot, "Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash");
|
||||
|
||||
if (!File.Exists (symbolicatecrash)) {
|
||||
Log.WriteLine ("Can't symbolicate {0} because the symbolicatecrash script {1} does not exist", report.Path, symbolicatecrash);
|
||||
return report;
|
||||
}
|
||||
|
||||
var name = Path.GetFileName (report.Path);
|
||||
var symbolicated = Logs.Create (Path.ChangeExtension (name, ".symbolicated.log"), $"Symbolicated crash report: {name}", timestamp: false);
|
||||
var environment = new Dictionary<string, string> { { "DEVELOPER_DIR", Path.Combine (harness.XcodeRoot, "Contents", "Developer") } };
|
||||
var rv = await harness.ProcessManager.ExecuteCommandAsync (symbolicatecrash, new [] { report.Path }, symbolicated, TimeSpan.FromMinutes (1), environment);
|
||||
if (rv.Succeeded) {;
|
||||
Log.WriteLine ("Symbolicated {0} successfully.", report.Path);
|
||||
return symbolicated;
|
||||
} else {
|
||||
Log.WriteLine ("Failed to symbolicate {0}.", report.Path);
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<HashSet<string>> CreateCrashReportsSnapshotAsync ()
|
||||
{
|
||||
var rv = new HashSet<string> ();
|
||||
|
||||
if (!isDevice) {
|
||||
var dir = Path.Combine (Environment.GetEnvironmentVariable ("HOME"), "Library", "Logs", "DiagnosticReports");
|
||||
if (Directory.Exists (dir))
|
||||
rv.UnionWith (Directory.EnumerateFiles (dir));
|
||||
} else {
|
||||
var tmp = Path.GetTempFileName ();
|
||||
try {
|
||||
var sb = new List<string> ();
|
||||
sb.Add ($"--list-crash-reports={tmp}");
|
||||
sb.Add ("--sdkroot");
|
||||
sb.Add (harness.XcodeRoot);
|
||||
if (!string.IsNullOrEmpty (deviceName)) {
|
||||
sb.Add ("--devname");
|
||||
sb.Add (deviceName);
|
||||
}
|
||||
var result = await harness.ProcessManager.ExecuteCommandAsync (harness.MlaunchPath, sb, Log, TimeSpan.FromMinutes (1));
|
||||
if (result.Succeeded)
|
||||
rv.UnionWith (File.ReadAllLines (tmp));
|
||||
} finally {
|
||||
File.Delete (tmp);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xharness.Execution;
|
||||
using Xharness.Execution.Mlaunch;
|
||||
using Xharness.Logging;
|
||||
|
||||
namespace Xharness
|
||||
{
|
||||
public interface ICrashSnapshotReporterFactory {
|
||||
ICrashSnapshotReporter Create (ILog log, ILogs logs, bool isDevice, string deviceName);
|
||||
}
|
||||
|
||||
public class CrashSnapshotReporterFactory : ICrashSnapshotReporterFactory {
|
||||
readonly IProcessManager processManager;
|
||||
readonly string xcodeRoot;
|
||||
readonly string mlaunchPath;
|
||||
|
||||
public CrashSnapshotReporterFactory (IProcessManager processManager, string xcodeRoot, string mlaunchPath)
|
||||
{
|
||||
this.processManager = processManager ?? throw new ArgumentNullException (nameof (processManager));
|
||||
this.xcodeRoot = xcodeRoot ?? throw new ArgumentNullException (nameof (xcodeRoot));
|
||||
this.mlaunchPath = mlaunchPath ?? throw new ArgumentNullException (nameof (mlaunchPath));
|
||||
}
|
||||
|
||||
public ICrashSnapshotReporter Create (ILog log, ILogs logs, bool isDevice, string deviceName) =>
|
||||
new CrashSnapshotReporter (processManager, log, logs, xcodeRoot, mlaunchPath, isDevice, deviceName);
|
||||
}
|
||||
|
||||
public interface ICrashSnapshotReporter {
|
||||
Task EndCaptureAsync (TimeSpan timeout);
|
||||
Task StartCaptureAsync ();
|
||||
}
|
||||
|
||||
public class CrashSnapshotReporter : ICrashSnapshotReporter {
|
||||
readonly IProcessManager processManager;
|
||||
readonly ILog log;
|
||||
readonly ILogs logs;
|
||||
readonly string xcodeRoot;
|
||||
readonly string mlaunchPath;
|
||||
readonly bool isDevice;
|
||||
readonly string deviceName;
|
||||
readonly Func<string> tempFileProvider;
|
||||
readonly string symbolicateCrashPath;
|
||||
|
||||
HashSet<string> initialCrashes;
|
||||
|
||||
public CrashSnapshotReporter (IProcessManager processManager,
|
||||
ILog log,
|
||||
ILogs logs,
|
||||
string xcodeRoot,
|
||||
string mlaunchPath,
|
||||
bool isDevice,
|
||||
string deviceName,
|
||||
Func<string> tempFileProvider = null)
|
||||
{
|
||||
this.processManager = processManager ?? throw new ArgumentNullException (nameof (processManager));
|
||||
this.log = log ?? throw new ArgumentNullException (nameof (log));
|
||||
this.logs = logs ?? throw new ArgumentNullException (nameof (logs));
|
||||
this.xcodeRoot = xcodeRoot ?? throw new ArgumentNullException (nameof (xcodeRoot));
|
||||
this.mlaunchPath = mlaunchPath ?? throw new ArgumentNullException (nameof (mlaunchPath));
|
||||
this.isDevice = isDevice;
|
||||
this.deviceName = deviceName;
|
||||
this.tempFileProvider = tempFileProvider ?? Path.GetTempFileName;
|
||||
|
||||
symbolicateCrashPath = Path.Combine (xcodeRoot, "Contents", "SharedFrameworks", "DTDeviceKitBase.framework", "Versions", "A", "Resources", "symbolicatecrash");
|
||||
if (!File.Exists (symbolicateCrashPath))
|
||||
symbolicateCrashPath = Path.Combine (xcodeRoot, "Contents", "SharedFrameworks", "DVTFoundation.framework", "Versions", "A", "Resources", "symbolicatecrash");
|
||||
if (!File.Exists (symbolicateCrashPath))
|
||||
symbolicateCrashPath = null;
|
||||
}
|
||||
|
||||
public async Task StartCaptureAsync ()
|
||||
{
|
||||
initialCrashes = await CreateCrashReportsSnapshotAsync ();
|
||||
}
|
||||
|
||||
public async Task EndCaptureAsync (TimeSpan timeout)
|
||||
{
|
||||
// Check for crash reports
|
||||
var stopwatch = Stopwatch.StartNew ();
|
||||
|
||||
do {
|
||||
var newCrashFiles = await CreateCrashReportsSnapshotAsync ();
|
||||
newCrashFiles.ExceptWith (initialCrashes);
|
||||
|
||||
if (newCrashFiles.Count == 0) {
|
||||
if (stopwatch.Elapsed.TotalSeconds > timeout.TotalSeconds) {
|
||||
break;
|
||||
} else {
|
||||
log.WriteLine (
|
||||
"No crash reports, waiting a second to see if the crash report service just didn't complete in time ({0})",
|
||||
(int) (timeout.TotalSeconds - stopwatch.Elapsed.TotalSeconds));
|
||||
|
||||
Thread.Sleep (TimeSpan.FromSeconds (1));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
log.WriteLine ("Found {0} new crash report(s)", newCrashFiles.Count);
|
||||
|
||||
IEnumerable<ILogFile> crashReports;
|
||||
if (!isDevice) {
|
||||
crashReports = new List<ILogFile> (newCrashFiles.Count);
|
||||
foreach (var path in newCrashFiles) {
|
||||
logs.AddFile (path, $"Crash report: {Path.GetFileName (path)}");
|
||||
}
|
||||
} else {
|
||||
// Download crash reports from the device. We put them in the project directory so that they're automatically deleted on wrench
|
||||
// (if we put them in /tmp, they'd never be deleted).
|
||||
crashReports = newCrashFiles
|
||||
.Select (async crash => await ProcessCrash (crash))
|
||||
.Select (t => t.Result)
|
||||
.Where (c => c != null);
|
||||
}
|
||||
|
||||
foreach (var cp in crashReports) {
|
||||
WrenchLog.WriteLine ("AddFile: {0}", cp.Path);
|
||||
log.WriteLine (" {0}", cp.Path);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
async Task<ILogFile> ProcessCrash (string crashFile)
|
||||
{
|
||||
var name = Path.GetFileName (crashFile);
|
||||
var crashReportFile = logs.Create (name, $"Crash report: {name}", timestamp: false);
|
||||
var args = new MlaunchArguments (
|
||||
new DownloadCrashReportArgument (crashFile),
|
||||
new DownloadCrashReportToArgument (crashReportFile.Path),
|
||||
new SdkRootArgument (xcodeRoot));
|
||||
|
||||
if (!string.IsNullOrEmpty (deviceName)) {
|
||||
args.Add (new DeviceNameArgument(deviceName));
|
||||
}
|
||||
|
||||
var result = await processManager.ExecuteCommandAsync (mlaunchPath, args, log, TimeSpan.FromMinutes (1));
|
||||
|
||||
if (result.Succeeded) {
|
||||
log.WriteLine ("Downloaded crash report {0} to {1}", crashFile, crashReportFile.Path);
|
||||
return await GetSymbolicateCrashReportAsync (crashReportFile);
|
||||
} else {
|
||||
log.WriteLine ("Could not download crash report {0}", crashFile);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<ILogFile> GetSymbolicateCrashReportAsync (ILogFile report)
|
||||
{
|
||||
if (symbolicateCrashPath == null) {
|
||||
log.WriteLine ("Can't symbolicate {0} because the symbolicatecrash script {1} does not exist", report.Path, symbolicateCrashPath);
|
||||
return report;
|
||||
}
|
||||
|
||||
var name = Path.GetFileName (report.Path);
|
||||
var symbolicated = logs.Create (Path.ChangeExtension (name, ".symbolicated.log"), $"Symbolicated crash report: {name}", timestamp: false);
|
||||
var environment = new Dictionary<string, string> { { "DEVELOPER_DIR", Path.Combine (xcodeRoot, "Contents", "Developer") } };
|
||||
var result = await processManager.ExecuteCommandAsync (symbolicateCrashPath, new [] { report.Path }, symbolicated, TimeSpan.FromMinutes (1), environment);
|
||||
if (result.Succeeded) {
|
||||
log.WriteLine ("Symbolicated {0} successfully.", report.Path);
|
||||
return symbolicated;
|
||||
} else {
|
||||
log.WriteLine ("Failed to symbolicate {0}.", report.Path);
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
async Task<HashSet<string>> CreateCrashReportsSnapshotAsync ()
|
||||
{
|
||||
var crashes = new HashSet<string> ();
|
||||
|
||||
if (!isDevice) {
|
||||
var dir = Path.Combine (Environment.GetEnvironmentVariable ("HOME"), "Library", "Logs", "DiagnosticReports");
|
||||
if (Directory.Exists (dir))
|
||||
crashes.UnionWith (Directory.EnumerateFiles (dir));
|
||||
} else {
|
||||
var tempFile = tempFileProvider ();
|
||||
try {
|
||||
var args = new MlaunchArguments (
|
||||
new ListCrashReportsArgument (tempFile),
|
||||
new SdkRootArgument (xcodeRoot));
|
||||
|
||||
if (!string.IsNullOrEmpty (deviceName)) {
|
||||
args.Add (new DeviceNameArgument(deviceName));
|
||||
}
|
||||
|
||||
var result = await processManager.ExecuteCommandAsync (mlaunchPath, args, log, TimeSpan.FromMinutes (1));
|
||||
if (result.Succeeded)
|
||||
crashes.UnionWith (File.ReadAllLines (tempFile));
|
||||
} finally {
|
||||
File.Delete (tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
return crashes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,13 +12,13 @@ namespace Xharness.Execution {
|
|||
public class ProcessExecutionResult {
|
||||
public bool TimedOut { get; set; }
|
||||
public int ExitCode { get; set; }
|
||||
|
||||
public bool Succeeded => !TimedOut && ExitCode == 0;
|
||||
}
|
||||
|
||||
// interface that helps to manage the different processes in the app.
|
||||
public interface IProcessManager {
|
||||
Task<ProcessExecutionResult> ExecuteCommandAsync (string filename, IList<string> args, ILog log, TimeSpan timeout, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null);
|
||||
Task<ProcessExecutionResult> ExecuteCommandAsync (string filename, MlaunchArguments args, ILog log, TimeSpan timeout, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null);
|
||||
Task<ProcessExecutionResult> RunAsync (Process process, ILog log, CancellationToken? cancellationToken = null, bool? diagnostics = null);
|
||||
Task<ProcessExecutionResult> RunAsync (Process process, ILog log, TimeSpan? timeout = null, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null, bool? diagnostics = null);
|
||||
Task<ProcessExecutionResult> RunAsync (Process process, MlaunchArguments args, ILog log, TimeSpan? timeout = null, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null, bool? diagnostics = null);
|
||||
|
|
|
@ -1,35 +1,90 @@
|
|||
namespace Xharness.Execution.Mlaunch {
|
||||
|
||||
/// <summary>
|
||||
/// Specify the location of Apple SDKs, default to 'xcode-select' value.
|
||||
/// </summary>
|
||||
public sealed class SdkRootArgument : SingleValueArgument {
|
||||
public SdkRootArgument (string sdkPath) : base ("sdkroot", sdkPath)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List the currently connected devices and their UDIDs.
|
||||
/// </summary>
|
||||
public sealed class ListDevicesArgument : SingleValueArgument {
|
||||
public ListDevicesArgument (string outputFile) : base ("listdev", outputFile)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List the available simulators. The output is xml, and written to the specified file.
|
||||
/// </summary>
|
||||
public sealed class ListSimulatorsArgument : SingleValueArgument {
|
||||
public ListSimulatorsArgument (string outputFile) : base ("listsim", outputFile)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists crash reports on the specified device
|
||||
/// </summary>
|
||||
public sealed class ListCrashReportsArgument : SingleValueArgument {
|
||||
public ListCrashReportsArgument (string outputFile) : base ("list-crash-reports", outputFile)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify which device (when many are present) the [install|lauch|kill|log]dev command applies
|
||||
/// </summary>
|
||||
public sealed class DeviceNameArgument : SingleValueArgument {
|
||||
public DeviceNameArgument (string deviceName) : base ("devname", deviceName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the output format for some commands as Default.
|
||||
/// </summary>
|
||||
public sealed class DefaultOutputFormatArgument : SingleValueArgument {
|
||||
public DefaultOutputFormatArgument () : base ("output-format", "Default")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the output format for some commands as XML.
|
||||
/// </summary>
|
||||
public sealed class XmlOutputFormatArgument : SingleValueArgument {
|
||||
public XmlOutputFormatArgument () : base ("output-format", "XML")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a crash report from the specified device.
|
||||
/// </summary>
|
||||
public sealed class DownloadCrashReportArgument : SingleValueArgument {
|
||||
public DownloadCrashReportArgument (string deviceName) : base ("download-crash-report", deviceName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the file to save the downloaded crash report.
|
||||
/// </summary>
|
||||
public sealed class DownloadCrashReportToArgument : SingleValueArgument {
|
||||
public DownloadCrashReportToArgument (string outputFile) : base ("download-crash-report-to", outputFile)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Include additional data (which can take some time to fetch) when listing the connected devices.
|
||||
/// Only applicable when output format is xml.
|
||||
/// </summary>
|
||||
public sealed class ListExtraDataArgument : OptionArgument {
|
||||
public ListExtraDataArgument () : base ("list-extra-data")
|
||||
{
|
||||
|
|
|
@ -17,7 +17,12 @@ namespace Xharness.Execution {
|
|||
{
|
||||
}
|
||||
|
||||
public async Task<ProcessExecutionResult> ExecuteCommandAsync (string filename, IList<string> args, ILog log, TimeSpan timeout, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null)
|
||||
public async Task<ProcessExecutionResult> ExecuteCommandAsync (string filename,
|
||||
IList<string> args,
|
||||
ILog log,
|
||||
TimeSpan timeout,
|
||||
Dictionary<string, string> environment_variables = null,
|
||||
CancellationToken? cancellation_token = null)
|
||||
{
|
||||
using (var p = new Process ()) {
|
||||
p.StartInfo.FileName = filename;
|
||||
|
@ -26,6 +31,20 @@ namespace Xharness.Execution {
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<ProcessExecutionResult> ExecuteCommandAsync (string filename,
|
||||
MlaunchArguments args,
|
||||
ILog log,
|
||||
TimeSpan timeout,
|
||||
Dictionary<string, string> environment_variables = null,
|
||||
CancellationToken? cancellation_token = null)
|
||||
{
|
||||
using (var p = new Process ()) {
|
||||
p.StartInfo.FileName = filename;
|
||||
p.StartInfo.Arguments = args.AsCommandLine ();
|
||||
return await RunAsync (p, log, timeout, environment_variables, cancellation_token);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport ("/usr/lib/libc.dylib")]
|
||||
internal static extern int kill (int pid, int sig);
|
||||
|
||||
|
|
|
@ -104,7 +104,6 @@ namespace Xharness
|
|||
public class Harness : IHarness {
|
||||
readonly AppRunnerTarget target;
|
||||
readonly string buildConfiguration = "Debug";
|
||||
string sdkRoot;
|
||||
|
||||
public HarnessAction Action { get; }
|
||||
public int Verbosity { get; }
|
||||
|
@ -142,6 +141,15 @@ namespace Xharness
|
|||
}
|
||||
}
|
||||
|
||||
string sdkRoot;
|
||||
string SdkRoot {
|
||||
get => sdkRoot;
|
||||
set {
|
||||
sdkRoot = value;
|
||||
XcodeRoot = FindXcode (sdkRoot);
|
||||
}
|
||||
}
|
||||
|
||||
public List<iOSTestProject> IOSTestProjects { get; }
|
||||
public List<MacTestProject> MacTestProjects { get; } = new List<MacTestProject> ();
|
||||
|
||||
|
@ -171,6 +179,8 @@ namespace Xharness
|
|||
public string DOTNET { get; private set; }
|
||||
|
||||
// Run
|
||||
|
||||
public string XcodeRoot { get; private set; }
|
||||
public string LogDirectory { get; } = Environment.CurrentDirectory;
|
||||
public double Timeout { get; } = 15; // in minutes
|
||||
public double LaunchTimeout { get; } // in minutes
|
||||
|
@ -180,7 +190,7 @@ namespace Xharness
|
|||
public string MarkdownSummaryPath { get; }
|
||||
public string PeriodicCommand { get; }
|
||||
public string PeriodicCommandArguments { get; }
|
||||
public TimeSpan PeriodicCommandInterval { get;}
|
||||
public TimeSpan PeriodicCommandInterval { get; }
|
||||
// whether tests that require access to system resources (system contacts, photo library, etc) should be executed or not
|
||||
public bool? IncludeSystemPermissionTests { get; set; }
|
||||
|
||||
|
@ -207,7 +217,7 @@ namespace Xharness
|
|||
PeriodicCommand = configuration.PeriodicCommand;
|
||||
PeriodicCommandArguments = configuration.PeriodicCommandArguments;
|
||||
PeriodicCommandInterval = configuration.PeriodicCommandInterval;
|
||||
sdkRoot = configuration.SdkRoot;
|
||||
SdkRoot = configuration.SdkRoot;
|
||||
target = configuration.Target;
|
||||
Timeout = configuration.TimeoutInMinutes;
|
||||
useSystemXamarinIOSMac = configuration.UseSystemXamarinIOSMac;
|
||||
|
@ -253,21 +263,18 @@ namespace Xharness
|
|||
|
||||
static string FindXcode (string path)
|
||||
{
|
||||
var p = path;
|
||||
do {
|
||||
if (p == "/") {
|
||||
throw new Exception (string.Format ("Could not find Xcode.app in {0}", path));
|
||||
} else if (File.Exists (Path.Combine (p, "Contents", "MacOS", "Xcode"))) {
|
||||
return p;
|
||||
}
|
||||
p = Path.GetDirectoryName (p);
|
||||
} while (true);
|
||||
}
|
||||
if (string.IsNullOrEmpty (path))
|
||||
return path;
|
||||
|
||||
public string XcodeRoot {
|
||||
get {
|
||||
return FindXcode (sdkRoot);
|
||||
}
|
||||
do {
|
||||
if (path == "/") {
|
||||
throw new Exception (string.Format ("Could not find Xcode.app in {0}", path));
|
||||
} else if (File.Exists (Path.Combine (path, "Contents", "MacOS", "Xcode"))) {
|
||||
return path;
|
||||
}
|
||||
|
||||
path = Path.GetDirectoryName (path);
|
||||
} while (true);
|
||||
}
|
||||
|
||||
Version xcode_version;
|
||||
|
@ -301,8 +308,8 @@ namespace Xharness
|
|||
INCLUDE_MAC = make_config.ContainsKey ("INCLUDE_MAC") && !string.IsNullOrEmpty (make_config ["INCLUDE_MAC"]);
|
||||
MAC_DESTDIR = make_config ["MAC_DESTDIR"];
|
||||
IOS_DESTDIR = make_config ["IOS_DESTDIR"];
|
||||
if (string.IsNullOrEmpty (sdkRoot))
|
||||
sdkRoot = make_config ["XCODE_DEVELOPER_ROOT"];
|
||||
if (string.IsNullOrEmpty (SdkRoot))
|
||||
SdkRoot = make_config ["XCODE_DEVELOPER_ROOT"];
|
||||
MONO_IOS_SDK_DESTDIR = make_config ["MONO_IOS_SDK_DESTDIR"];
|
||||
MONO_MAC_SDK_DESTDIR = make_config ["MONO_MAC_SDK_DESTDIR"];
|
||||
ENABLE_XAMARIN = make_config.ContainsKey ("ENABLE_XAMARIN") && !string.IsNullOrEmpty (make_config ["ENABLE_XAMARIN"]);
|
||||
|
@ -601,6 +608,7 @@ namespace Xharness
|
|||
new SimulatorsLoaderFactory (this, ProcessManager),
|
||||
new SimpleListenerFactory (),
|
||||
new DeviceLoaderFactory (this, ProcessManager),
|
||||
new CrashSnapshotReporterFactory (ProcessManager, XcodeRoot, MlaunchPath),
|
||||
new CaptureLogFactory (),
|
||||
new DeviceLogCapturerFactory (ProcessManager, XcodeRoot, MlaunchPath),
|
||||
new XmlResultParser (),
|
||||
|
@ -629,6 +637,7 @@ namespace Xharness
|
|||
new SimulatorsLoaderFactory (this, ProcessManager),
|
||||
new SimpleListenerFactory (),
|
||||
new DeviceLoaderFactory (this, ProcessManager),
|
||||
new CrashSnapshotReporterFactory (ProcessManager, XcodeRoot, MlaunchPath),
|
||||
new CaptureLogFactory (),
|
||||
new DeviceLogCapturerFactory (ProcessManager, XcodeRoot, MlaunchPath),
|
||||
new XmlResultParser (),
|
||||
|
@ -655,6 +664,7 @@ namespace Xharness
|
|||
new SimulatorsLoaderFactory (this, ProcessManager),
|
||||
new SimpleListenerFactory (),
|
||||
new DeviceLoaderFactory (this, ProcessManager),
|
||||
new CrashSnapshotReporterFactory (ProcessManager, XcodeRoot, MlaunchPath),
|
||||
new CaptureLogFactory (),
|
||||
new DeviceLogCapturerFactory (ProcessManager, XcodeRoot, MlaunchPath),
|
||||
new XmlResultParser (),
|
||||
|
@ -782,6 +792,7 @@ namespace Xharness
|
|||
}
|
||||
|
||||
bool? disable_watchos_on_wrench;
|
||||
|
||||
public bool DisableWatchOSOnWrench {
|
||||
get {
|
||||
if (!disable_watchos_on_wrench.HasValue)
|
||||
|
|
|
@ -225,8 +225,13 @@ namespace Xharness.Jenkins
|
|||
throw new NotImplementedException ();
|
||||
}
|
||||
|
||||
for (int i = 0; i < targets.Length; i++)
|
||||
runtasks.Add (new RunSimulatorTask (simulators, buildTask, simulators.SelectDevices (targets [i], SimulatorLoadLog, false)) { Platform = platforms [i], Ignored = ignored[i] || buildTask.Ignored });
|
||||
for (int i = 0; i < targets.Length; i++) {
|
||||
var sims = simulators.SelectDevices (targets [i], SimulatorLoadLog, false);
|
||||
runtasks.Add (new RunSimulatorTask (simulators, buildTask, processManager, sims) {
|
||||
Platform = platforms [i],
|
||||
Ignored = ignored[i] || buildTask.Ignored
|
||||
});
|
||||
}
|
||||
|
||||
return runtasks;
|
||||
}
|
||||
|
@ -547,7 +552,7 @@ namespace Xharness.Jenkins
|
|||
}
|
||||
|
||||
var testVariations = CreateTestVariations (runSimulatorTasks, (buildTask, test, candidates) =>
|
||||
new RunSimulatorTask (simulators, buildTask, candidates?.Cast<SimulatorDevice> () ?? test.Candidates)).ToList ();
|
||||
new RunSimulatorTask (simulators, buildTask, processManager, candidates?.Cast<SimulatorDevice> () ?? test.Candidates)).ToList ();
|
||||
|
||||
foreach (var tv in testVariations) {
|
||||
if (!tv.Ignored)
|
||||
|
@ -587,7 +592,7 @@ namespace Xharness.Jenkins
|
|||
TestName = project.Name,
|
||||
};
|
||||
build64.CloneTestProject (project);
|
||||
projectTasks.Add (new RunDeviceTask (devices, build64, devices.Connected64BitIOS.Where (d => d.IsSupported (project))) { Ignored = !IncludeiOS64 });
|
||||
projectTasks.Add (new RunDeviceTask (devices, build64, processManager, devices.Connected64BitIOS.Where (d => d.IsSupported (project))) { Ignored = !IncludeiOS64 });
|
||||
|
||||
var build32 = new MSBuildTask {
|
||||
Jenkins = this,
|
||||
|
@ -597,7 +602,7 @@ namespace Xharness.Jenkins
|
|||
TestName = project.Name,
|
||||
};
|
||||
build32.CloneTestProject (project);
|
||||
projectTasks.Add (new RunDeviceTask (devices, build32, devices.Connected32BitIOS.Where (d => d.IsSupported (project))) { Ignored = !IncludeiOS32 });
|
||||
projectTasks.Add (new RunDeviceTask (devices, build32, processManager, devices.Connected32BitIOS.Where (d => d.IsSupported (project))) { Ignored = !IncludeiOS32 });
|
||||
|
||||
var todayProject = project.AsTodayExtensionProject ();
|
||||
var buildToday = new MSBuildTask {
|
||||
|
@ -608,7 +613,7 @@ namespace Xharness.Jenkins
|
|||
TestName = project.Name,
|
||||
};
|
||||
buildToday.CloneTestProject (todayProject);
|
||||
projectTasks.Add (new RunDeviceTask (devices, buildToday, devices.Connected64BitIOS.Where (d => d.IsSupported (project))) { Ignored = !IncludeiOSExtensions, BuildOnly = ForceExtensionBuildOnly });
|
||||
projectTasks.Add (new RunDeviceTask (devices, buildToday, processManager, devices.Connected64BitIOS.Where (d => d.IsSupported (project))) { Ignored = !IncludeiOSExtensions, BuildOnly = ForceExtensionBuildOnly });
|
||||
}
|
||||
|
||||
if (!project.SkiptvOSVariation) {
|
||||
|
@ -621,7 +626,7 @@ namespace Xharness.Jenkins
|
|||
TestName = project.Name,
|
||||
};
|
||||
buildTV.CloneTestProject (tvOSProject);
|
||||
projectTasks.Add (new RunDeviceTask (devices, buildTV, devices.ConnectedTV.Where (d => d.IsSupported (project))) { Ignored = !IncludetvOS });
|
||||
projectTasks.Add (new RunDeviceTask (devices, buildTV, processManager, devices.ConnectedTV.Where (d => d.IsSupported (project))) { Ignored = !IncludetvOS });
|
||||
}
|
||||
|
||||
if (!project.SkipwatchOSVariation) {
|
||||
|
@ -635,7 +640,7 @@ namespace Xharness.Jenkins
|
|||
TestName = project.Name,
|
||||
};
|
||||
buildWatch32.CloneTestProject (watchOSProject);
|
||||
projectTasks.Add (new RunDeviceTask (devices, buildWatch32, devices.ConnectedWatch) { Ignored = !IncludewatchOS });
|
||||
projectTasks.Add (new RunDeviceTask (devices, buildWatch32, processManager, devices.ConnectedWatch) { Ignored = !IncludewatchOS });
|
||||
}
|
||||
|
||||
if (!project.SkipwatchOSARM64_32Variation) {
|
||||
|
@ -647,7 +652,7 @@ namespace Xharness.Jenkins
|
|||
TestName = project.Name,
|
||||
};
|
||||
buildWatch64_32.CloneTestProject (watchOSProject);
|
||||
projectTasks.Add (new RunDeviceTask (devices, buildWatch64_32, devices.ConnectedWatch32_64.Where (d => d.IsSupported (project))) { Ignored = !IncludewatchOS });
|
||||
projectTasks.Add (new RunDeviceTask (devices, buildWatch64_32, processManager, devices.ConnectedWatch32_64.Where (d => d.IsSupported (project))) { Ignored = !IncludewatchOS });
|
||||
}
|
||||
}
|
||||
foreach (var task in projectTasks) {
|
||||
|
@ -658,7 +663,7 @@ namespace Xharness.Jenkins
|
|||
rv.AddRange (projectTasks);
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<TestTask>> (CreateTestVariations (rv, (buildTask, test, candidates) => new RunDeviceTask (devices, buildTask, candidates?.Cast<IHardwareDevice> () ?? test.Candidates)));
|
||||
return Task.FromResult<IEnumerable<TestTask>> (CreateTestVariations (rv, (buildTask, test, candidates) => new RunDeviceTask (devices, buildTask, processManager, candidates?.Cast<IHardwareDevice> () ?? test.Candidates)));
|
||||
}
|
||||
|
||||
static string AddSuffixToPath (string path, string suffix)
|
||||
|
@ -933,6 +938,8 @@ namespace Xharness.Jenkins
|
|||
|
||||
//Tasks.AddRange (await CreateRunSimulatorTasksAsync ());
|
||||
|
||||
var crashReportSnapshotFactory = new CrashSnapshotReporterFactory (processManager, Harness.XcodeRoot, Harness.MlaunchPath);
|
||||
|
||||
var buildiOSMSBuild_net461 = new MSBuildTask ()
|
||||
{
|
||||
Jenkins = this,
|
||||
|
@ -944,7 +951,7 @@ namespace Xharness.Jenkins
|
|||
SolutionPath = Path.GetFullPath (Path.Combine (Harness.RootDirectory, "..", "msbuild", "Xamarin.MacDev.Tasks.sln")),
|
||||
SupportsParallelExecution = false,
|
||||
};
|
||||
var nunitExecutioniOSMSBuild_net461 = new NUnitExecuteTask (buildiOSMSBuild_net461)
|
||||
var nunitExecutioniOSMSBuild_net461 = new NUnitExecuteTask (buildiOSMSBuild_net461, processManager)
|
||||
{
|
||||
TestLibrary = Path.Combine (Harness.RootDirectory, "..", "msbuild", "tests", "Xamarin.iOS.Tasks.Tests", "bin", "Debug-net461", "net461", "Xamarin.iOS.Tasks.Tests.dll"),
|
||||
TestProject = buildiOSMSBuild_net461.TestProject,
|
||||
|
@ -968,7 +975,7 @@ namespace Xharness.Jenkins
|
|||
SolutionPath = Path.GetFullPath (Path.Combine (Harness.RootDirectory, "..", "msbuild", "Xamarin.MacDev.Tasks.sln")),
|
||||
SupportsParallelExecution = false,
|
||||
};
|
||||
var nunitExecutioniOSMSBuild_netstandard2 = new NUnitExecuteTask (buildiOSMSBuild_netstandard2) {
|
||||
var nunitExecutioniOSMSBuild_netstandard2 = new NUnitExecuteTask (buildiOSMSBuild_netstandard2, processManager) {
|
||||
TestLibrary = Path.Combine (Harness.RootDirectory, "..", "msbuild", "tests", "Xamarin.iOS.Tasks.Tests", "bin", "Debug-netstandard2.0", "net461", "Xamarin.iOS.Tasks.Tests.dll"),
|
||||
TestProject = buildiOSMSBuild_netstandard2.TestProject,
|
||||
ProjectConfiguration = "Debug-netstandard2.0",
|
||||
|
@ -990,7 +997,7 @@ namespace Xharness.Jenkins
|
|||
Platform = TestPlatform.iOS,
|
||||
};
|
||||
buildInstallSources.SolutionPath = Path.GetFullPath (Path.Combine (Harness.RootDirectory, "..", "tools", "install-source", "install-source.sln")); // this is required for nuget restore to be executed
|
||||
var nunitExecutionInstallSource = new NUnitExecuteTask (buildInstallSources)
|
||||
var nunitExecutionInstallSource = new NUnitExecuteTask (buildInstallSources, processManager)
|
||||
{
|
||||
TestLibrary = Path.Combine (Harness.RootDirectory, "..", "tools", "install-source", "InstallSourcesTests", "bin", "Release", "InstallSourcesTests.dll"),
|
||||
TestProject = buildInstallSources.TestProject,
|
||||
|
@ -1047,7 +1054,7 @@ namespace Xharness.Jenkins
|
|||
var ignored_main = ignored;
|
||||
if (project.IsNUnitProject) {
|
||||
var dll = Path.Combine (Path.GetDirectoryName (build.TestProject.Path), project.Xml.GetOutputAssemblyPath (build.ProjectPlatform, build.ProjectConfiguration).Replace ('\\', '/'));
|
||||
exec = new NUnitExecuteTask (build) {
|
||||
exec = new NUnitExecuteTask (build, processManager) {
|
||||
Ignored = ignored_main,
|
||||
TestLibrary = dll,
|
||||
TestProject = project,
|
||||
|
@ -1058,13 +1065,14 @@ namespace Xharness.Jenkins
|
|||
};
|
||||
execs = new [] { exec };
|
||||
} else {
|
||||
exec = new MacExecuteTask (build) {
|
||||
exec = new MacExecuteTask (build, processManager, crashReportSnapshotFactory) {
|
||||
Ignored = ignored_main,
|
||||
BCLTest = project.IsBclTest,
|
||||
TestName = project.Name,
|
||||
IsUnitTest = true,
|
||||
};
|
||||
execs = CreateTestVariations (new [] { exec }, (buildTask, test, candidates) => new MacExecuteTask (buildTask) { IsUnitTest = true } );
|
||||
execs = CreateTestVariations (new [] { exec }, (buildTask, test, candidates) =>
|
||||
new MacExecuteTask (buildTask, processManager, crashReportSnapshotFactory) { IsUnitTest = true } );
|
||||
}
|
||||
|
||||
foreach (var e in execs)
|
||||
|
@ -1084,7 +1092,7 @@ namespace Xharness.Jenkins
|
|||
Target = "dependencies",
|
||||
WorkingDirectory = Path.GetFullPath (Path.Combine (Harness.RootDirectory, "mtouch")),
|
||||
};
|
||||
var nunitExecutionMTouch = new NUnitExecuteTask (buildMTouch)
|
||||
var nunitExecutionMTouch = new NUnitExecuteTask (buildMTouch, processManager)
|
||||
{
|
||||
TestLibrary = Path.Combine (Harness.RootDirectory, "mtouch", "bin", "Debug", "mtouch.dll"),
|
||||
TestProject = new TestProject (Path.GetFullPath (Path.Combine (Harness.RootDirectory, "mtouch", "mtouch.csproj"))),
|
||||
|
@ -1105,7 +1113,7 @@ namespace Xharness.Jenkins
|
|||
Target = "build-unit-tests",
|
||||
WorkingDirectory = Path.GetFullPath (Path.Combine (Harness.RootDirectory, "generator")),
|
||||
};
|
||||
var runGenerator = new NUnitExecuteTask (buildGenerator) {
|
||||
var runGenerator = new NUnitExecuteTask (buildGenerator, processManager) {
|
||||
TestLibrary = Path.Combine (Harness.RootDirectory, "generator", "bin", "Debug", "generator-tests.dll"),
|
||||
TestProject = new TestProject (Path.GetFullPath (Path.Combine (Harness.RootDirectory, "generator", "generator-tests.csproj"))),
|
||||
Platform = TestPlatform.iOS,
|
||||
|
@ -1123,7 +1131,7 @@ namespace Xharness.Jenkins
|
|||
SpecifyConfiguration = false,
|
||||
Platform = TestPlatform.iOS,
|
||||
};
|
||||
var runDotNetGenerator = new DotNetTestTask (buildDotNetGenerator) {
|
||||
var runDotNetGenerator = new DotNetTestTask (buildDotNetGenerator, processManager) {
|
||||
TestProject = buildDotNetGenerator.TestProject,
|
||||
Platform = TestPlatform.iOS,
|
||||
TestName = "Generator tests",
|
||||
|
@ -1172,7 +1180,7 @@ namespace Xharness.Jenkins
|
|||
Ignored = !IncludeXtro,
|
||||
Timeout = TimeSpan.FromMinutes (15),
|
||||
};
|
||||
var runXtroReporter = new RunXtroTask (buildXtroTests) {
|
||||
var runXtroReporter = new RunXtroTask (buildXtroTests, processManager, crashReportSnapshotFactory) {
|
||||
Jenkins = this,
|
||||
Platform = TestPlatform.Mac,
|
||||
TestName = buildXtroTests.TestName,
|
||||
|
@ -1190,7 +1198,7 @@ namespace Xharness.Jenkins
|
|||
Ignored = !IncludeCecil,
|
||||
Timeout = TimeSpan.FromMinutes (5),
|
||||
};
|
||||
var runCecilTests = new NUnitExecuteTask (buildCecilTests) {
|
||||
var runCecilTests = new NUnitExecuteTask (buildCecilTests, processManager) {
|
||||
TestLibrary = Path.Combine (buildCecilTests.WorkingDirectory, "bin", "Debug", "cecil-tests.dll"),
|
||||
TestProject = new TestProject (Path.Combine (buildCecilTests.WorkingDirectory, "cecil-tests.csproj")),
|
||||
Platform = TestPlatform.iOS,
|
||||
|
@ -1219,7 +1227,7 @@ namespace Xharness.Jenkins
|
|||
Platform = TestPlatform.All,
|
||||
ProjectConfiguration = "Debug",
|
||||
};
|
||||
var runSampleTests = new NUnitExecuteTask (buildSampleTests) {
|
||||
var runSampleTests = new NUnitExecuteTask (buildSampleTests, processManager) {
|
||||
TestLibrary = Path.Combine (Harness.RootDirectory, "sampletester", "bin", "Debug", "sampletester.dll"),
|
||||
TestProject = new TestProject (Path.GetFullPath (Path.Combine (Harness.RootDirectory, "sampletester", "sampletester.csproj"))),
|
||||
Platform = TestPlatform.All,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Xharness.Execution;
|
||||
using Xharness.Logging;
|
||||
|
||||
namespace Xharness.Jenkins.TestTasks {
|
||||
class DotNetTestTask : RunTestTask {
|
||||
public DotNetTestTask (DotNetBuildTask build_task)
|
||||
: base (build_task)
|
||||
public DotNetTestTask (DotNetBuildTask build_task, IProcessManager processManager)
|
||||
: base (build_task, processManager)
|
||||
{
|
||||
DotNetBuildTask.SetDotNetEnvironmentVariables (Environment);
|
||||
}
|
||||
|
|
|
@ -12,14 +12,16 @@ namespace Xharness.Jenkins.TestTasks
|
|||
{
|
||||
class MacExecuteTask : MacTask
|
||||
{
|
||||
protected ICrashSnapshotReporterFactory CrashReportSnapshotFactory { get; }
|
||||
|
||||
public string Path;
|
||||
public bool BCLTest;
|
||||
public bool IsUnitTest;
|
||||
public IProcessManager ProcessManager { get; set; } = new ProcessManager ();
|
||||
|
||||
public MacExecuteTask (BuildToolTask build_task)
|
||||
: base (build_task)
|
||||
public MacExecuteTask (BuildToolTask build_task, IProcessManager processManager, ICrashSnapshotReporterFactory crashReportSnapshotFactory)
|
||||
: base (build_task, processManager)
|
||||
{
|
||||
this.CrashReportSnapshotFactory = crashReportSnapshotFactory ?? throw new ArgumentNullException (nameof (crashReportSnapshotFactory));
|
||||
}
|
||||
|
||||
public override bool SupportsParallelExecution {
|
||||
|
@ -91,7 +93,7 @@ namespace Xharness.Jenkins.TestTasks
|
|||
if (!Harness.DryRun) {
|
||||
ExecutionResult = TestExecutingResult.Running;
|
||||
|
||||
var snapshot = new CrashReportSnapshot (Harness, log, Logs, isDevice: false, deviceName: null);
|
||||
var snapshot = CrashReportSnapshotFactory.Create (log, Logs, isDevice: false, deviceName: null);
|
||||
await snapshot.StartCaptureAsync ();
|
||||
|
||||
ProcessExecutionResult result = null;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
using System;
|
||||
using Xharness.Execution;
|
||||
|
||||
namespace Xharness.Jenkins.TestTasks
|
||||
{
|
||||
abstract class MacTask : RunTestTask
|
||||
{
|
||||
public MacTask (BuildToolTask build_task)
|
||||
: base (build_task)
|
||||
public MacTask (BuildToolTask build_task, IProcessManager processManager)
|
||||
: base (build_task, processManager)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ namespace Xharness.Jenkins.TestTasks
|
|||
public bool ProduceHtmlReport = true;
|
||||
public bool InProcess;
|
||||
|
||||
public NUnitExecuteTask (BuildToolTask build_task)
|
||||
: base (build_task)
|
||||
public NUnitExecuteTask (BuildToolTask build_task, IProcessManager processManager)
|
||||
: base (build_task, processManager)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -40,8 +40,8 @@ namespace Xharness.Jenkins.TestTasks
|
|||
}
|
||||
}
|
||||
|
||||
public RunDeviceTask (IDeviceLoader devices, MSBuildTask build_task, IEnumerable<IHardwareDevice> candidates)
|
||||
: base (build_task, candidates.OrderBy ((v) => v.DebugSpeed))
|
||||
public RunDeviceTask (IDeviceLoader devices, MSBuildTask build_task, IProcessManager processManager, IEnumerable<IHardwareDevice> candidates)
|
||||
: base (build_task, processManager, candidates.OrderBy ((v) => v.DebugSpeed))
|
||||
{
|
||||
switch (build_task.Platform) {
|
||||
case TestPlatform.iOS:
|
||||
|
@ -85,6 +85,7 @@ namespace Xharness.Jenkins.TestTasks
|
|||
new SimulatorsLoaderFactory (Harness, processManager),
|
||||
new SimpleListenerFactory (),
|
||||
new DeviceLoaderFactory (Harness, processManager),
|
||||
new CrashSnapshotReporterFactory (ProcessManager, Harness.XcodeRoot, Harness.MlaunchPath),
|
||||
new CaptureLogFactory (),
|
||||
new DeviceLogCapturerFactory (processManager, Harness.XcodeRoot, Harness.MlaunchPath),
|
||||
new XmlResultParser (),
|
||||
|
@ -150,6 +151,7 @@ namespace Xharness.Jenkins.TestTasks
|
|||
new SimulatorsLoaderFactory (Harness, processManager),
|
||||
new SimpleListenerFactory (),
|
||||
new DeviceLoaderFactory (Harness, processManager),
|
||||
new CrashSnapshotReporterFactory (ProcessManager, Harness.XcodeRoot, Harness.MlaunchPath),
|
||||
new CaptureLogFactory (),
|
||||
new DeviceLogCapturerFactory (processManager, Harness.XcodeRoot, Harness.MlaunchPath),
|
||||
new XmlResultParser (),
|
||||
|
|
|
@ -29,8 +29,8 @@ namespace Xharness.Jenkins.TestTasks
|
|||
}
|
||||
}
|
||||
|
||||
public RunSimulatorTask (ISimulatorsLoader simulators, MSBuildTask build_task, IEnumerable<ISimulatorDevice> candidates = null)
|
||||
: base (build_task, candidates)
|
||||
public RunSimulatorTask (ISimulatorsLoader simulators, MSBuildTask build_task, IProcessManager processManager, IEnumerable<ISimulatorDevice> candidates = null)
|
||||
: base (build_task, processManager, candidates)
|
||||
{
|
||||
var project = Path.GetFileNameWithoutExtension (ProjectFile);
|
||||
if (project.EndsWith ("-tvos", StringComparison.Ordinal)) {
|
||||
|
@ -81,6 +81,7 @@ namespace Xharness.Jenkins.TestTasks
|
|||
new SimulatorsLoaderFactory (Harness, processManager),
|
||||
new SimpleListenerFactory (),
|
||||
new DeviceLoaderFactory (Harness, processManager),
|
||||
new CrashSnapshotReporterFactory (ProcessManager, Harness.XcodeRoot, Harness.MlaunchPath),
|
||||
new CaptureLogFactory (),
|
||||
new DeviceLogCapturerFactory (processManager, Harness.XcodeRoot, Harness.MlaunchPath),
|
||||
new XmlResultParser (),
|
||||
|
|
|
@ -12,16 +12,18 @@ namespace Xharness.Jenkins.TestTasks
|
|||
{
|
||||
internal abstract class RunTestTask : TestTask
|
||||
{
|
||||
protected IProcessManager ProcessManager { get; }
|
||||
IResultParser ResultParser { get; } = new XmlResultParser ();
|
||||
|
||||
public readonly BuildToolTask BuildTask;
|
||||
public TimeSpan Timeout = TimeSpan.FromMinutes (10);
|
||||
public double TimeoutMultiplier { get; set; } = 1;
|
||||
IProcessManager ProcessManager { get; } = new ProcessManager ();
|
||||
IResultParser ResultParser { get; } = new XmlResultParser ();
|
||||
public string WorkingDirectory;
|
||||
|
||||
public RunTestTask (BuildToolTask build_task)
|
||||
public RunTestTask (BuildToolTask build_task, IProcessManager processManager)
|
||||
{
|
||||
this.BuildTask = build_task;
|
||||
this.ProcessManager = processManager ?? throw new ArgumentNullException (nameof (processManager));
|
||||
|
||||
Jenkins = build_task.Jenkins;
|
||||
TestProject = build_task.TestProject;
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xharness.Collections;
|
||||
using Xharness.Execution;
|
||||
using Xharness.Hardware;
|
||||
using Xharness.Logging;
|
||||
|
||||
|
@ -23,8 +24,8 @@ namespace Xharness.Jenkins.TestTasks
|
|||
|
||||
public string BundleIdentifier => runner.AppInformation.BundleIdentifier;
|
||||
|
||||
public RunXITask (BuildToolTask build_task, IEnumerable<TDevice> candidates)
|
||||
: base (build_task)
|
||||
public RunXITask (BuildToolTask build_task, IProcessManager processManager, IEnumerable<TDevice> candidates)
|
||||
: base (build_task, processManager)
|
||||
{
|
||||
this.Candidates = candidates;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Xharness.Execution;
|
||||
using Xharness.Logging;
|
||||
|
||||
namespace Xharness.Jenkins.TestTasks
|
||||
{
|
||||
class RunXtroTask : MacExecuteTask
|
||||
{
|
||||
public RunXtroTask (BuildToolTask build_task) : base (build_task)
|
||||
public RunXtroTask (BuildToolTask build_task, IProcessManager processManager, ICrashSnapshotReporterFactory crashReportSnapshotFactory)
|
||||
: base (build_task, processManager, crashReportSnapshotFactory)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -29,7 +31,7 @@ namespace Xharness.Jenkins.TestTasks
|
|||
if (!Harness.DryRun) {
|
||||
ExecutionResult = TestExecutingResult.Running;
|
||||
|
||||
var snapshot = new CrashReportSnapshot (Harness, log, Logs, isDevice: false, deviceName: null);
|
||||
var snapshot = CrashReportSnapshotFactory.Create (log, Logs, isDevice: false, deviceName: null);
|
||||
await snapshot.StartCaptureAsync ();
|
||||
|
||||
try {
|
||||
|
|
|
@ -51,11 +51,13 @@ namespace Xharness.Tests {
|
|||
Mock<ISimulatorsLoader> simulators;
|
||||
Mock<IDeviceLoader> devices;
|
||||
Mock<ISimpleListener> simpleListener;
|
||||
Mock<ICrashSnapshotReporter> snapshotReporter;
|
||||
|
||||
ILog mainLog;
|
||||
ISimulatorsLoaderFactory simulatorsFactory;
|
||||
IDeviceLoaderFactory devicesFactory;
|
||||
ISimpleListenerFactory listenerFactory;
|
||||
ICrashSnapshotReporterFactory snapshotReporterFactory;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp ()
|
||||
|
@ -64,6 +66,7 @@ namespace Xharness.Tests {
|
|||
simulators = new Mock<ISimulatorsLoader> ();
|
||||
devices = new Mock<IDeviceLoader> ();
|
||||
simpleListener = new Mock<ISimpleListener> ();
|
||||
snapshotReporter = new Mock<ICrashSnapshotReporter> ();
|
||||
|
||||
var mock1 = new Mock<ISimulatorsLoaderFactory> ();
|
||||
mock1.Setup (m => m.CreateLoader ()).Returns (simulators.Object);
|
||||
|
@ -79,6 +82,10 @@ namespace Xharness.Tests {
|
|||
.Returns ((ListenerTransport.Tcp, simpleListener.Object, null));
|
||||
listenerFactory = mock3.Object;
|
||||
|
||||
var mock4 = new Mock<ICrashSnapshotReporterFactory> ();
|
||||
mock4.Setup (m => m.Create (It.IsAny<ILog>(), It.IsAny<ILogs>(), It.IsAny<bool>(), It.IsAny<string>())).Returns (snapshotReporter.Object);
|
||||
snapshotReporterFactory = mock4.Object;
|
||||
|
||||
mainLog = new Mock<ILog> ().Object;
|
||||
|
||||
Directory.CreateDirectory (appPath);
|
||||
|
@ -91,6 +98,7 @@ namespace Xharness.Tests {
|
|||
simulatorsFactory,
|
||||
listenerFactory,
|
||||
devicesFactory,
|
||||
snapshotReporterFactory,
|
||||
Mock.Of<ICaptureLogFactory> (),
|
||||
Mock.Of<IDeviceLogCapturerFactory> (),
|
||||
Mock.Of<IResultParser> (),
|
||||
|
@ -114,6 +122,7 @@ namespace Xharness.Tests {
|
|||
simulatorsFactory,
|
||||
listenerFactory,
|
||||
devicesFactory,
|
||||
snapshotReporterFactory,
|
||||
Mock.Of<ICaptureLogFactory> (),
|
||||
Mock.Of<IDeviceLogCapturerFactory> (),
|
||||
Mock.Of<IResultParser> (),
|
||||
|
@ -136,6 +145,7 @@ namespace Xharness.Tests {
|
|||
simulatorsFactory,
|
||||
listenerFactory,
|
||||
devicesFactory,
|
||||
snapshotReporterFactory,
|
||||
Mock.Of<ICaptureLogFactory> (),
|
||||
Mock.Of<IDeviceLogCapturerFactory> (),
|
||||
Mock.Of<IResultParser> (),
|
||||
|
@ -158,6 +168,7 @@ namespace Xharness.Tests {
|
|||
simulatorsFactory,
|
||||
listenerFactory,
|
||||
devicesFactory,
|
||||
snapshotReporterFactory,
|
||||
Mock.Of<ICaptureLogFactory> (),
|
||||
Mock.Of<IDeviceLogCapturerFactory> (),
|
||||
Mock.Of<IResultParser> (),
|
||||
|
@ -199,6 +210,7 @@ namespace Xharness.Tests {
|
|||
simulatorsFactory,
|
||||
listenerFactory,
|
||||
devicesFactory,
|
||||
snapshotReporterFactory,
|
||||
Mock.Of<ICaptureLogFactory> (),
|
||||
Mock.Of<IDeviceLogCapturerFactory> (),
|
||||
Mock.Of<IResultParser> (),
|
||||
|
@ -259,6 +271,7 @@ namespace Xharness.Tests {
|
|||
simulatorsFactory,
|
||||
listenerFactory,
|
||||
devicesFactory,
|
||||
snapshotReporterFactory,
|
||||
Mock.Of<ICaptureLogFactory> (),
|
||||
Mock.Of<IDeviceLogCapturerFactory> (),
|
||||
Mock.Of<IResultParser> (),
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Xharness.Execution;
|
||||
using Xharness.Execution.Mlaunch;
|
||||
using Xharness.Logging;
|
||||
|
||||
namespace Xharness.Tests {
|
||||
[TestFixture]
|
||||
public class CrashReportSnapshotTests {
|
||||
readonly string mlaunchPath = "./mlaunch";
|
||||
string tempXcodeRoot;
|
||||
string symbolicatePath;
|
||||
|
||||
Mock<IProcessManager> processManager;
|
||||
Mock<ILog> log;
|
||||
Mock<ILogs> logs;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp ()
|
||||
{
|
||||
processManager = new Mock<IProcessManager> ();
|
||||
log = new Mock<ILog> ();
|
||||
logs = new Mock<ILogs> ();
|
||||
|
||||
tempXcodeRoot = Path.Combine (Path.GetTempPath (), Guid.NewGuid ().ToString());
|
||||
symbolicatePath = Path.Combine (tempXcodeRoot, "Contents", "SharedFrameworks", "DTDeviceKitBase.framework", "Versions", "A", "Resources");
|
||||
|
||||
// Create fake place for device logs
|
||||
Directory.CreateDirectory (tempXcodeRoot);
|
||||
|
||||
// Create fake symbolicate binary
|
||||
Directory.CreateDirectory (symbolicatePath);
|
||||
File.Create (Path.Combine (symbolicatePath, "symbolicatecrash"));
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown () {
|
||||
Directory.Delete (tempXcodeRoot, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeviceCaptureTest ()
|
||||
{
|
||||
var tempFilePath = Path.GetTempFileName ();
|
||||
|
||||
const string deviceName = "Sample-iPhone";
|
||||
const string crashLogPath = "/path/to/crash.log";
|
||||
const string symbolicateLogPath = "/path/to/" + deviceName+ ".symbolicated.log";
|
||||
|
||||
var crashReport = Mock.Of<ILogFile> (x => x.Path == crashLogPath);
|
||||
var symbolicateReport = Mock.Of<ILogFile> (x => x.Path == symbolicateLogPath);
|
||||
|
||||
// Crash report is added
|
||||
logs.Setup (x => x.Create (deviceName, "Crash report: " + deviceName, It.IsAny<bool> ()))
|
||||
.Returns (crashReport);
|
||||
|
||||
// Symbolicate report is added
|
||||
logs.Setup (x => x.Create ("crash.symbolicated.log", "Symbolicated crash report: crash.log", It.IsAny<bool> ()))
|
||||
.Returns (symbolicateReport);
|
||||
|
||||
// List of crash reports is retrieved
|
||||
processManager
|
||||
.Setup (x => x.ExecuteCommandAsync (
|
||||
mlaunchPath,
|
||||
It.Is<MlaunchArguments> (args => args.AsCommandLine () == $"--list-crash-reports={tempFilePath} --sdkroot={tempXcodeRoot} --devname={deviceName}"),
|
||||
log.Object,
|
||||
TimeSpan.FromMinutes (1),
|
||||
null,
|
||||
null))
|
||||
.ReturnsAsync (new ProcessExecutionResult () { ExitCode = 0 });
|
||||
|
||||
// Device crash log is downloaded
|
||||
processManager
|
||||
.Setup (x => x.ExecuteCommandAsync (
|
||||
mlaunchPath,
|
||||
It.Is<MlaunchArguments> (args => args.AsCommandLine () == $"--download-crash-report={deviceName} --download-crash-report-to={crashLogPath} --sdkroot={tempXcodeRoot} --devname={deviceName}"),
|
||||
log.Object,
|
||||
TimeSpan.FromMinutes (1),
|
||||
null,
|
||||
null))
|
||||
.ReturnsAsync (new ProcessExecutionResult () { ExitCode = 0 });
|
||||
|
||||
// Symbolicate is ran
|
||||
processManager
|
||||
.Setup (x => x.ExecuteCommandAsync (
|
||||
Path.Combine (symbolicatePath, "symbolicatecrash"),
|
||||
It.Is<IList<string>> (args => args.First () == crashLogPath),
|
||||
symbolicateReport,
|
||||
TimeSpan.FromMinutes (1),
|
||||
It.IsAny <Dictionary<string, string>>(),
|
||||
null))
|
||||
.ReturnsAsync (new ProcessExecutionResult () { ExitCode = 0 });
|
||||
|
||||
// Act
|
||||
var snapshotReport = new CrashSnapshotReporter (processManager.Object,
|
||||
log.Object,
|
||||
logs.Object,
|
||||
tempXcodeRoot,
|
||||
mlaunchPath,
|
||||
true,
|
||||
deviceName,
|
||||
() => tempFilePath);
|
||||
|
||||
File.WriteAllLines (tempFilePath, new [] { "crash 1", "crash 2" });
|
||||
|
||||
await snapshotReport.StartCaptureAsync ();
|
||||
|
||||
File.WriteAllLines (tempFilePath, new [] { "Sample-iPhone" });
|
||||
|
||||
await snapshotReport.EndCaptureAsync (TimeSpan.FromSeconds (10));
|
||||
|
||||
// Verify all calls above
|
||||
processManager.VerifyAll ();
|
||||
logs.VerifyAll ();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@
|
|||
<Compile Include="BCLTestImporter\Tests\TestAssemblyDefinitionTest.cs" />
|
||||
<Compile Include="BCLTestImporter\Tests\TestProjectDefinitionTest.cs" />
|
||||
<Compile Include="Tests\AppRunnerTests.cs" />
|
||||
<Compile Include="Tests\CrashReportSnapshotTests.cs" />
|
||||
<Compile Include="Tests\XmlResultParserTests.cs" />
|
||||
<Compile Include="Logging\Tests\LogsTest.cs" />
|
||||
<Compile Include="Logging\Tests\LogFileTest.cs" />
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
<Compile Include="Collections\BlockingEnumerableCollection.cs" />
|
||||
<Compile Include="Collections\ILoadAsync.cs" />
|
||||
<Compile Include="Collections\IAsyncEnumerable.cs" />
|
||||
<Compile Include="CrashReportSnapshot.cs" />
|
||||
<Compile Include="CrashSnapshotReporter.cs" />
|
||||
<Compile Include="DeviceLogCapturer.cs" />
|
||||
<Compile Include="Execution\IProcessManager.cs" />
|
||||
<Compile Include="Execution\Mlaunch\Arguments.cs" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче