Merge pull request #295 from rolfbjarne/xharness-backport-from-xcode8
[xharness] Merge xcode8 changes back to master.
This commit is contained in:
Коммит
52af5a54d0
|
@ -45,39 +45,27 @@ namespace xharness
|
|||
set { log_directory = value; }
|
||||
}
|
||||
|
||||
public LogFiles Logs = new LogFiles ();
|
||||
Log main_log;
|
||||
public Logs Logs = new Logs ();
|
||||
|
||||
public Log MainLog {
|
||||
get { return main_log; }
|
||||
set { main_log = value; }
|
||||
}
|
||||
|
||||
public SimDevice [] Simulators {
|
||||
get { return simulators; }
|
||||
set { simulators = value; }
|
||||
}
|
||||
|
||||
public string BundleIdentifier {
|
||||
get {
|
||||
return bundle_identifier;
|
||||
}
|
||||
}
|
||||
|
||||
string mode;
|
||||
|
||||
LogFile SymbolicateCrashReport (LogFile 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)) {
|
||||
Harness.Log ("Can't symbolicate {0} because the symbolicatecrash script {1} does not exist", report.Path, symbolicatecrash);
|
||||
return report;
|
||||
}
|
||||
|
||||
var output = new StringBuilder ();
|
||||
if (ExecuteCommand (symbolicatecrash, "\"" + report.Path + "\"", true, captured_output: output, environment_variables: new Dictionary<string, string> { { "DEVELOPER_DIR", Path.Combine (Harness.XcodeRoot, "Contents", "Developer") }})) {
|
||||
var rv = Logs.Create (LogDirectory, report.Path + ".symbolicated", "Symbolicated crash report: " + Path.GetFileName (report.Path));
|
||||
File.WriteAllText (rv.Path, output.ToString ());
|
||||
Harness.Log ("Symbolicated {0} successfully.", report.Path);
|
||||
return rv;
|
||||
}
|
||||
|
||||
Harness.Log ("Failed to symbolicate {0}:\n{1}", report.Path, output.ToString ());
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
void FindSimulator ()
|
||||
{
|
||||
if (simulators != null)
|
||||
|
@ -96,7 +84,7 @@ namespace xharness
|
|||
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
|
||||
break;
|
||||
case "ios-simulator":
|
||||
simulator_devicetype = "com.apple.CoreSimulator.SimDeviceType.iPhone-4s";
|
||||
simulator_devicetype = "com.apple.CoreSimulator.SimDeviceType.iPhone-5";
|
||||
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
|
||||
break;
|
||||
case "tvos-simulator":
|
||||
|
@ -116,7 +104,7 @@ namespace xharness
|
|||
};
|
||||
Task.Run (async () =>
|
||||
{
|
||||
await sims.LoadAsync (Logs.Create (LogDirectory, "simulator-list.log", "Simulator list"));
|
||||
await sims.LoadAsync (Logs.CreateStream (LogDirectory, "simulator-list.log", "Simulator list"));
|
||||
}).Wait ();
|
||||
|
||||
var devices = sims.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == simulator_runtime && v.SimDeviceType == simulator_devicetype);
|
||||
|
@ -153,9 +141,9 @@ namespace xharness
|
|||
if (simulators == null)
|
||||
throw new Exception ("Could not find simulator");
|
||||
|
||||
Harness.Log (1, "Found simulator: {0} {1}", simulators [0].Name, simulators [0].UDID);
|
||||
main_log.WriteLine ("Found simulator: {0} {1}", simulators [0].Name, simulators [0].UDID);
|
||||
if (simulators.Length > 1)
|
||||
Harness.Log (1, "Found companion simulator: {0} {1}", simulators [1].Name, simulators [1].UDID);
|
||||
main_log.WriteLine ("Found companion simulator: {0} {1}", simulators [1].Name, simulators [1].UDID);
|
||||
}
|
||||
|
||||
void FindDevice ()
|
||||
|
@ -167,10 +155,12 @@ namespace xharness
|
|||
if (!string.IsNullOrEmpty (device_name))
|
||||
return;
|
||||
|
||||
var devs = new Devices ();
|
||||
var devs = new Devices () {
|
||||
Harness = Harness,
|
||||
};
|
||||
Task.Run (async () =>
|
||||
{
|
||||
await devs.LoadAsync ();
|
||||
await devs.LoadAsync (main_log);
|
||||
}).Wait ();
|
||||
|
||||
string [] deviceClasses;
|
||||
|
@ -194,7 +184,7 @@ namespace xharness
|
|||
throw new Exception ($"Could not find any applicable devices with device class(es): {string.Join (", ", deviceClasses)}");
|
||||
} else if (selected.Count () > 1) {
|
||||
selected_data = selected.First ();
|
||||
Harness.Log ("Found {0} devices for device class(es) {1}: {2}. Selected: '{3}'", selected.Count (), string.Join (", ", deviceClasses), string.Join (", ", selected.Select ((v) => v.Name).ToArray ()), selected_data.Name);
|
||||
main_log.WriteLine ("Found {0} devices for device class(es) {1}: {2}. Selected: '{3}'", selected.Count (), string.Join (", ", deviceClasses), string.Join (", ", selected.Select ((v) => v.Name).ToArray ()), selected_data.Name);
|
||||
} else {
|
||||
selected_data = selected.First ();
|
||||
}
|
||||
|
@ -205,119 +195,11 @@ namespace xharness
|
|||
if (companion.Count () == 0)
|
||||
throw new Exception ($"Could not find the companion device for '{selected_data.Name}'");
|
||||
else if (companion.Count () > 1)
|
||||
Harness.Log ("Found {0} companion devices for {1}?!?", companion.Count (), selected_data.Name);
|
||||
main_log.WriteLine ("Found {0} companion devices for {1}?!?", companion.Count (), selected_data.Name);
|
||||
companion_device_name = companion.First ().Name;
|
||||
}
|
||||
}
|
||||
|
||||
public void AgreeToPrompts (bool delete_first = true)
|
||||
{
|
||||
var TCC_db = Path.Combine (simulator.DataPath, "data", "Library", "TCC", "TCC.db");
|
||||
var sim_services = new string [] {
|
||||
"kTCCServiceAddressBook",
|
||||
"kTCCServicePhotos",
|
||||
"kTCCServiceUbiquity",
|
||||
"kTCCServiceWillow"
|
||||
};
|
||||
|
||||
var failure = false;
|
||||
var tcc_edit_timeout = 5;
|
||||
var watch = new Stopwatch ();
|
||||
watch.Start ();
|
||||
do {
|
||||
failure = false;
|
||||
foreach (var service in sim_services) {
|
||||
if (delete_first && !ExecuteCommand ("sqlite3", string.Format ("{0} \"DELETE FROM access WHERE service = '{1}' and client ='{2}';\"", TCC_db, service, bundle_identifier), true, output_verbosity_level: 1)) {
|
||||
failure = true;
|
||||
}
|
||||
|
||||
if (!failure && !ExecuteCommand ("sqlite3", string.Format ("{0} \"INSERT INTO access VALUES('{1}','{2}',0,1,0,NULL,NULL);\"", TCC_db, service, bundle_identifier), true, output_verbosity_level: 1)) {
|
||||
failure = true;
|
||||
}
|
||||
}
|
||||
if (failure) {
|
||||
if (watch.Elapsed.TotalSeconds > tcc_edit_timeout)
|
||||
break;
|
||||
Harness.Log ("Failed to edit TCC.db, trying again in 1 second... ", (int) (tcc_edit_timeout - watch.Elapsed.TotalSeconds));
|
||||
Thread.Sleep (TimeSpan.FromSeconds (1));
|
||||
}
|
||||
} while (failure);
|
||||
|
||||
if (failure) {
|
||||
Harness.Log ("Failed to edit TCC.db, the test run might hang due to permission request dialogs");
|
||||
} else {
|
||||
Harness.Log ("Successfully edited TCC.db");
|
||||
}
|
||||
}
|
||||
|
||||
bool simulator_prepared;
|
||||
public void PrepareSimulator ()
|
||||
{
|
||||
if (simulator_prepared)
|
||||
return;
|
||||
simulator_prepared = true;
|
||||
|
||||
if (SkipSimulatorSetup) {
|
||||
AgreeToPrompts (false);
|
||||
Harness.Log (0, "Simulator setup skipped.");
|
||||
return;
|
||||
}
|
||||
|
||||
KillEverything ();
|
||||
ShowSimulatorList ();
|
||||
|
||||
// We shutdown and erase all simulators.
|
||||
// We only fixup TCC.db on the main simulator.
|
||||
|
||||
foreach (var sim in simulators) {
|
||||
var udid = sim.UDID;
|
||||
// erase the simulator (make sure the device isn't running first)
|
||||
ExecuteXcodeCommand ("simctl", "shutdown " + udid, true, output_verbosity_level: 1, timeout: TimeSpan.FromMinutes (1));
|
||||
ExecuteXcodeCommand ("simctl", "erase " + udid, true, output_verbosity_level: 1, timeout: TimeSpan.FromMinutes (1));
|
||||
|
||||
// boot & shutdown to make sure it actually works
|
||||
ExecuteXcodeCommand ("simctl", "boot " + udid, true, output_verbosity_level: 1, timeout: TimeSpan.FromMinutes (1));
|
||||
ExecuteXcodeCommand ("simctl", "shutdown " + udid, true, output_verbosity_level: 1, timeout: TimeSpan.FromMinutes (1));
|
||||
}
|
||||
|
||||
// Edit the permissions to prevent dialog boxes in the test app
|
||||
var TCC_db = Path.Combine (simulator.DataPath, "data", "Library", "TCC", "TCC.db");
|
||||
if (!File.Exists (TCC_db)) {
|
||||
Harness.Log ("Opening simulator to create TCC.db");
|
||||
var simulator_app = Path.Combine (Harness.XcodeRoot, "Contents", "Developer", "Applications", "Simulator.app");
|
||||
if (!Directory.Exists (simulator_app))
|
||||
simulator_app = Path.Combine (Harness.XcodeRoot, "Contents", "Developer", "Applications", "iOS Simulator.app");
|
||||
|
||||
ExecuteCommand ("open", "-a \"" + simulator_app + "\" --args -CurrentDeviceUDID " + simulator.UDID, output_verbosity_level: 1);
|
||||
|
||||
var tcc_creation_timeout = 60;
|
||||
var watch = new Stopwatch ();
|
||||
watch.Start ();
|
||||
while (!File.Exists (TCC_db) && watch.Elapsed.TotalSeconds < tcc_creation_timeout) {
|
||||
Harness.Log ("Waiting for simulator to create TCC.db... {0}", (int) (tcc_creation_timeout - watch.Elapsed.TotalSeconds));
|
||||
Thread.Sleep (TimeSpan.FromSeconds (1));
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists (TCC_db)) {
|
||||
AgreeToPrompts (true);
|
||||
} else {
|
||||
Harness.Log ("No TCC.db found for the simulator {0} (SimRuntime={1} and SimDeviceType={1})", simulator.UDID, simulator.SimRuntime, simulator.SimDeviceType);
|
||||
}
|
||||
|
||||
KillEverything ();
|
||||
|
||||
foreach (var sim in simulators) {
|
||||
ExecuteXcodeCommand ("simctl", "shutdown " + sim.UDID, true, output_verbosity_level: 1, timeout: TimeSpan.FromMinutes (1));
|
||||
|
||||
if (!File.Exists (sim.SystemLog)) {
|
||||
Harness.Log ("No system log found for SimRuntime={0} and SimDeviceType={1}", sim.SimRuntime, sim.SimDeviceType);
|
||||
} else {
|
||||
File.WriteAllText (sim.SystemLog, string.Format (" *** This log file was cleared out by Xamarin.iOS's test run at {0} **** \n", DateTime.Now.ToString ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool initialized;
|
||||
public void Initialize ()
|
||||
{
|
||||
|
@ -389,7 +271,7 @@ namespace xharness
|
|||
}
|
||||
}
|
||||
|
||||
public int Install ()
|
||||
public int Install (Log log)
|
||||
{
|
||||
Initialize ();
|
||||
|
||||
|
@ -413,35 +295,26 @@ namespace xharness
|
|||
if (mode == "watchos")
|
||||
args.Append (" --device ios,watchos");
|
||||
|
||||
var success = ExecuteCommand (Harness.MlaunchPath, args.ToString ());
|
||||
return success ? 0 : 1;
|
||||
var rv = ProcessHelper.ExecuteCommandAsync (Harness.MlaunchPath, args.ToString (), log, TimeSpan.FromHours (1)).Result;
|
||||
return rv.Succeeded ? 0 : 1;
|
||||
}
|
||||
|
||||
bool skip_simulator_setup;
|
||||
public bool SkipSimulatorSetup {
|
||||
bool ensure_clean_simulator_state = true;
|
||||
public bool EnsureCleanSimulatorState {
|
||||
get {
|
||||
return skip_simulator_setup || !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("SKIP_SIMULATOR_SETUP"));
|
||||
return ensure_clean_simulator_state || !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("SKIP_SIMULATOR_SETUP"));
|
||||
}
|
||||
set {
|
||||
skip_simulator_setup = value;
|
||||
ensure_clean_simulator_state = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool skip_simulator_cleanup;
|
||||
public bool SkipSimulatorCleanup {
|
||||
get {
|
||||
return skip_simulator_cleanup || !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("SKIP_SIMULATOR_CLEANUP"));
|
||||
}
|
||||
set {
|
||||
skip_simulator_cleanup = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Run ()
|
||||
public async Task<int> RunAsync ()
|
||||
{
|
||||
HashSet<string> start_crashes = null;
|
||||
LogFile device_system_log = null;
|
||||
LogFile listener_log = null;
|
||||
LogStream device_system_log = null;
|
||||
LogStream listener_log = null;
|
||||
Log run_log = main_log;
|
||||
|
||||
Initialize ();
|
||||
|
||||
|
@ -487,8 +360,9 @@ namespace xharness
|
|||
default:
|
||||
throw new NotImplementedException ();
|
||||
}
|
||||
listener_log = Logs.Create (LogDirectory, string.Format ("test-{0:yyyyMMdd_HHmmss}.log", DateTime.Now), "Test log");
|
||||
listener.LogPath = listener_log.Path;
|
||||
listener_log = Logs.CreateStream (LogDirectory, string.Format ("test-{0:yyyyMMdd_HHmmss}.log", DateTime.Now), "Test log");
|
||||
listener.TestLog = listener_log;
|
||||
listener.Log = main_log;
|
||||
listener.AutoExit = true;
|
||||
listener.Address = System.Net.IPAddress.Any;
|
||||
listener.Initialize ();
|
||||
|
@ -502,130 +376,122 @@ namespace xharness
|
|||
if (isSimulator) {
|
||||
FindSimulator ();
|
||||
|
||||
Harness.Log ("*** Executing {0}/{1} in the simulator ***", appName, mode);
|
||||
var systemLogs = new List<CaptureLog> ();
|
||||
foreach (var sim in simulators) {
|
||||
// Upload the system log
|
||||
main_log.WriteLine ("System log for the '{1}' simulator is: {0}", sim.SystemLog, sim.Name);
|
||||
bool isCompanion = sim != simulator;
|
||||
|
||||
PrepareSimulator ();
|
||||
var log = new CaptureLog (sim.SystemLog) {
|
||||
Path = Path.Combine (LogDirectory, sim.UDID + ".log"),
|
||||
Description = isCompanion ? "System log (companion)" : "System log",
|
||||
};
|
||||
log.StartCapture ();
|
||||
Logs.Add (log);
|
||||
systemLogs.Add (log);
|
||||
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", log.Path);
|
||||
}
|
||||
|
||||
main_log.WriteLine ("*** Executing {0}/{1} in the simulator ***", appName, mode);
|
||||
|
||||
if (EnsureCleanSimulatorState) {
|
||||
foreach (var sim in simulators)
|
||||
await sim.PrepareSimulatorAsync (main_log, bundle_identifier);
|
||||
}
|
||||
|
||||
args.Append (" --launchsim");
|
||||
args.AppendFormat (" \"{0}\" ", launchAppPath);
|
||||
args.Append (" --device=:v2:udid=").Append (simulator.UDID).Append (" ");
|
||||
|
||||
start_crashes = CreateCrashReportsSnapshot (true);
|
||||
start_crashes = await Harness.CreateCrashReportsSnapshotAsync (main_log, true);
|
||||
|
||||
listener.StartAsync ();
|
||||
Harness.Log ("Starting test run");
|
||||
var proc = new XProcess () {
|
||||
Harness = Harness,
|
||||
FileName = Harness.MlaunchPath,
|
||||
Arguments = args.ToString (),
|
||||
VerbosityLevel = 0,
|
||||
};
|
||||
proc.Start ();
|
||||
main_log.WriteLine ("Starting test run");
|
||||
|
||||
var launchState = 0; // 0: launching, 1: launch timed out, 2: run timed out, 3: completed
|
||||
var launchMutex = new Mutex ();
|
||||
var runCompleted = new ManualResetEvent (false);
|
||||
var cancellation_source = new CancellationTokenSource ();
|
||||
ThreadPool.QueueUserWorkItem ((v) => {
|
||||
if (!listener.WaitForConnection (TimeSpan.FromMinutes (Harness.LaunchTimeout))) {
|
||||
lock (launchMutex) {
|
||||
if (launchState == 0) {
|
||||
launchState = 1;
|
||||
runCompleted.Set ();
|
||||
}
|
||||
}
|
||||
Harness.Log ("Test launch timed out after {0} minute(s).", Harness.LaunchTimeout);
|
||||
cancellation_source.Cancel ();
|
||||
main_log.WriteLine ("Test launch timed out after {0} minute(s).", Harness.LaunchTimeout);
|
||||
} else {
|
||||
Harness.Log ("Test run started");
|
||||
main_log.WriteLine ("Test run started");
|
||||
}
|
||||
});
|
||||
ThreadPool.QueueUserWorkItem ((v) => {
|
||||
var rv = proc.WaitForExit (TimeSpan.FromMinutes (Harness.Timeout));
|
||||
|
||||
lock (launchMutex) {
|
||||
if (launchState == 0)
|
||||
launchState = rv ? 3 : 2;
|
||||
runCompleted.Set ();
|
||||
}
|
||||
|
||||
if (rv) {
|
||||
Harness.Log ("Test run completed");
|
||||
} else {
|
||||
Harness.Log ("Test run timed out after {0} minute(s).", Harness.Timeout);
|
||||
}
|
||||
});
|
||||
|
||||
runCompleted.WaitOne ();
|
||||
|
||||
switch (launchState) {
|
||||
case 1:
|
||||
case 2:
|
||||
success = false;
|
||||
var result = await ProcessHelper.ExecuteCommandAsync (Harness.MlaunchPath, args.ToString (), run_log, TimeSpan.FromMinutes (Harness.Timeout), cancellation_token: cancellation_source.Token);
|
||||
if (result.TimedOut) {
|
||||
timed_out = true;
|
||||
success = false;
|
||||
main_log.WriteLine ("Test run timed out after {0} minute(s).", Harness.Timeout);
|
||||
} else if (result.Succeeded) {
|
||||
main_log.WriteLine ("Test run completed");
|
||||
success = true;
|
||||
} else {
|
||||
main_log.WriteLine ("Test run failed");
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (!success.Value) {
|
||||
// find pid
|
||||
var pid = -1;
|
||||
var output = proc.ReadCurrentOutput ();
|
||||
foreach (var line in output.ToString ().Split ('\n')) {
|
||||
using (var reader = run_log.GetReader ()) {
|
||||
while (!reader.EndOfStream) {
|
||||
var line = reader.ReadLine ();
|
||||
if (line.StartsWith ("Application launched. PID = ", StringComparison.Ordinal)) {
|
||||
var pidstr = line.Substring ("Application launched. PID = ".Length);
|
||||
if (!int.TryParse (pidstr, out pid))
|
||||
Harness.Log ("Could not parse pid: {0}", pidstr);
|
||||
main_log.WriteLine ("Could not parse pid: {0}", pidstr);
|
||||
} else if (line.Contains ("Xamarin.Hosting: Launched ") && line.Contains (" with pid ")) {
|
||||
var pidstr = line.Substring (line.LastIndexOf (' '));
|
||||
if (!int.TryParse (pidstr, out pid))
|
||||
Harness.Log ("Could not parse pid: {0}", pidstr);
|
||||
main_log.WriteLine ("Could not parse pid: {0}", pidstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pid > 0) {
|
||||
KillPid (proc, pid, TimeSpan.FromSeconds (5), TimeSpan.FromMinutes (launchState == 1 ? Harness.LaunchTimeout : Harness.Timeout), launchState == 1 ? "Launch" : "Completion");
|
||||
var launchTimedout = cancellation_source.IsCancellationRequested;
|
||||
await KillPidAsync (main_log, pid, TimeSpan.FromSeconds (5), TimeSpan.FromMinutes (launchTimedout ? Harness.LaunchTimeout : Harness.Timeout), launchTimedout ? "Launch" : "Completion");
|
||||
} else {
|
||||
Harness.Log ("Could not find pid in mtouch output.");
|
||||
main_log.WriteLine ("Could not find pid in mtouch output.");
|
||||
}
|
||||
// kill mtouch too
|
||||
kill (proc.Id, 9);
|
||||
break;
|
||||
case 3:
|
||||
// Success!
|
||||
break;
|
||||
case 0: // shouldn't happen ever
|
||||
default:
|
||||
throw new Exception ($"Invalid launch state: {launchState}");
|
||||
}
|
||||
|
||||
listener.Cancel ();
|
||||
|
||||
var run_log = Logs.Create (LogDirectory, string.Format ("launch-{0:yyyyMMdd_HHmmss}.log", DateTime.Now), "Launch log");
|
||||
File.WriteAllText (run_log.Path, proc.ReadCurrentOutput ());
|
||||
|
||||
// cleanup after us
|
||||
KillEverything ();
|
||||
if (EnsureCleanSimulatorState)
|
||||
await SimDevice.KillEverythingAsync (main_log);
|
||||
|
||||
foreach (var log in systemLogs)
|
||||
log.StopCapture ();
|
||||
|
||||
} else {
|
||||
FindDevice ();
|
||||
|
||||
Harness.Log ("*** Executing {0}/{1} on device ***", appName, mode);
|
||||
main_log.WriteLine ("*** Executing {0}/{1} on device ***", appName, mode);
|
||||
|
||||
args.Append (" --launchdev");
|
||||
args.AppendFormat (" \"{0}\" ", launchAppPath);
|
||||
|
||||
AddDeviceName (args);
|
||||
|
||||
device_system_log = Logs.Create (LogDirectory, "device.log", "Device log");
|
||||
device_system_log = Logs.CreateStream (LogDirectory, "device.log", "Device log");
|
||||
var logdev = new DeviceLogCapturer () {
|
||||
Harness = Harness,
|
||||
LogPath = device_system_log.Path,
|
||||
Log = device_system_log,
|
||||
DeviceName = device_name,
|
||||
};
|
||||
logdev.StartCapture ();
|
||||
|
||||
start_crashes = CreateCrashReportsSnapshot (false);
|
||||
start_crashes = await Harness.CreateCrashReportsSnapshotAsync (main_log, false);
|
||||
|
||||
listener.StartAsync ();
|
||||
Harness.Log ("Starting test run");
|
||||
ExecuteCommand (Harness.MlaunchPath, args.ToString ());
|
||||
main_log.WriteLine ("Starting test run");
|
||||
// This will not wait for app completion
|
||||
await ProcessHelper.ExecuteCommandAsync (Harness.MlaunchPath, args.ToString (), main_log, TimeSpan.FromMinutes (1));
|
||||
if (listener.WaitForCompletion (TimeSpan.FromMinutes (Harness.Timeout))) {
|
||||
Harness.Log ("Test run completed");
|
||||
main_log.WriteLine ("Test run completed");
|
||||
} else {
|
||||
Harness.Log ("Test run did not complete in {0} minutes.", Harness.Timeout);
|
||||
main_log.WriteLine ("Test run did not complete in {0} minutes.", Harness.Timeout);
|
||||
listener.Cancel ();
|
||||
success = false;
|
||||
timed_out = true;
|
||||
|
@ -634,10 +500,10 @@ namespace xharness
|
|||
logdev.StopCapture ();
|
||||
|
||||
// Upload the system log
|
||||
if (File.Exists (device_system_log.Path)) {
|
||||
Harness.Log (1, "A capture of the device log is: {0}", device_system_log.Path);
|
||||
if (File.Exists (device_system_log.FullPath)) {
|
||||
main_log.WriteLine ("A capture of the device log is: {0}", device_system_log.FullPath);
|
||||
if (Harness.InWrench)
|
||||
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", device_system_log.Path);
|
||||
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", device_system_log.FullPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -645,12 +511,14 @@ namespace xharness
|
|||
|
||||
// check the final status
|
||||
var crashed = false;
|
||||
if (File.Exists (listener_log.Path)) {
|
||||
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", listener_log.Path);
|
||||
var log = File.ReadAllText (listener_log.Path);
|
||||
if (File.Exists (listener_log.FullPath)) {
|
||||
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", listener_log.FullPath);
|
||||
string log;
|
||||
using (var reader = listener_log.GetReader ())
|
||||
log = reader.ReadToEnd ();
|
||||
if (log.Contains ("Tests run")) {
|
||||
var tests_run = string.Empty;
|
||||
var log_lines = File.ReadAllLines (listener_log.Path);
|
||||
var log_lines = log.Split ('\n');
|
||||
var failed = false;
|
||||
foreach (var line in log_lines) {
|
||||
if (line.Contains ("Tests run:")) {
|
||||
|
@ -665,25 +533,26 @@ namespace xharness
|
|||
|
||||
if (failed) {
|
||||
Harness.LogWrench ("@MonkeyWrench: AddSummary: <b>{0} failed: {1}</b><br/>", mode, tests_run);
|
||||
Harness.Log ("Test run failed");
|
||||
main_log.WriteLine ("Test run failed");
|
||||
success = false;
|
||||
} else {
|
||||
Harness.LogWrench ("@MonkeyWrench: AddSummary: {0} succeeded: {1}<br/>", mode, tests_run);
|
||||
Harness.Log ("Test run succeeded");
|
||||
main_log.WriteLine ("Test run succeeded");
|
||||
success = true;
|
||||
}
|
||||
} else if (timed_out) {
|
||||
Harness.LogWrench ("@MonkeyWrench: AddSummary: <b><i>{0} timed out</i></b><br/>", mode);
|
||||
} else {
|
||||
Harness.LogWrench ("@MonkeyWrench: AddSummary: <b><i>{0} crashed</i></b><br/>", mode);
|
||||
Harness.Log ("Test run crashed");
|
||||
main_log.WriteLine ("Test run crashed");
|
||||
crashed = true;
|
||||
}
|
||||
} else if (timed_out) {
|
||||
Harness.LogWrench ("@MonkeyWrench: AddSummary: <b><i>{0} never launched</i></b><br/>", mode);
|
||||
Harness.Log ("Test run never launched");
|
||||
main_log.WriteLine ("Test run never launched");
|
||||
} else {
|
||||
Harness.LogWrench ("@MonkeyWrench: AddSummary: <b><i>{0} crashed at startup (no log)</i></b><br/>", mode);
|
||||
Harness.Log ("Test run crashed before it started (no log file produced)");
|
||||
main_log.WriteLine ("Test run crashed before it started (no log file produced)");
|
||||
crashed = true;
|
||||
}
|
||||
|
||||
|
@ -693,37 +562,39 @@ namespace xharness
|
|||
var watch = new Stopwatch ();
|
||||
watch.Start ();
|
||||
do {
|
||||
var end_crashes = CreateCrashReportsSnapshot (isSimulator);
|
||||
var end_crashes = await Harness.CreateCrashReportsSnapshotAsync (main_log, isSimulator);
|
||||
end_crashes.ExceptWith (start_crashes);
|
||||
if (end_crashes.Count > 0) {
|
||||
Harness.Log ("Found {0} new crash report(s)", end_crashes.Count);
|
||||
main_log.WriteLine ("Found {0} new crash report(s)", end_crashes.Count);
|
||||
List<LogFile> crash_reports;
|
||||
if (isSimulator) {
|
||||
crash_reports = new List<LogFile> (end_crashes.Count);
|
||||
foreach (var path in end_crashes) {
|
||||
var report = Logs.Create (LogDirectory, Path.GetFileName (path), "Crash report: " + Path.GetFileName (path));
|
||||
File.Copy (path, report.Path, true);
|
||||
crash_reports.Add (report);
|
||||
var logPath = Path.Combine (LogDirectory, Path.GetFileName (path));
|
||||
File.Copy (path, logPath, true);
|
||||
crash_reports.Add (Logs.CreateFile ("Crash report: " + Path.GetFileName (path), logPath));
|
||||
}
|
||||
} 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<LogFile> ();
|
||||
foreach (var file in end_crashes) {
|
||||
var crash_report_target = Logs.Create (LogDirectory, Path.GetFileName (file), "Crash report: " + Path.GetFileName (file));
|
||||
if (ExecuteCommand (Harness.MlaunchPath, "--download-crash-report=" + file + " --download-crash-report-to=" + crash_report_target.Path + " --sdkroot " + Harness.XcodeRoot)) {
|
||||
Harness.Log ("Downloaded crash report {0} to {1}", file, crash_report_target.Path);
|
||||
crash_report_target = SymbolicateCrashReport (crash_report_target);
|
||||
var crash_report_target = Logs.CreateFile ("Crash report: " + Path.GetFileName (file), Path.Combine (LogDirectory, Path.GetFileName (file)));
|
||||
var result = await ProcessHelper.ExecuteCommandAsync (Harness.MlaunchPath, "--download-crash-report=" + file + " --download-crash-report-to=" + crash_report_target.Path + " --sdkroot " + Harness.XcodeRoot, main_log, TimeSpan.FromMinutes (1));
|
||||
if (result.Succeeded) {
|
||||
main_log.WriteLine ("Downloaded crash report {0} to {1}", file, crash_report_target.Path);
|
||||
crash_report_target = await Harness.SymbolicateCrashReportAsync (main_log, crash_report_target);
|
||||
Logs.Add (crash_report_target);
|
||||
downloaded_crash_reports.Add (crash_report_target);
|
||||
} else {
|
||||
Harness.Log ("Could not download crash report {0}", file);
|
||||
main_log.WriteLine ("Could not download crash report {0}", file);
|
||||
}
|
||||
}
|
||||
crash_reports = downloaded_crash_reports;
|
||||
}
|
||||
foreach (var cp in crash_reports) {
|
||||
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", cp.Path);
|
||||
Harness.Log (" {0}", cp.Path);
|
||||
main_log.WriteLine (" {0}", cp.Path);
|
||||
}
|
||||
crash_report_search_done = true;
|
||||
} else if (!crashed && !timed_out) {
|
||||
|
@ -732,7 +603,7 @@ namespace xharness
|
|||
if (watch.Elapsed.TotalSeconds > crash_report_search_timeout) {
|
||||
crash_report_search_done = true;
|
||||
} else {
|
||||
Harness.Log ("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));
|
||||
main_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));
|
||||
}
|
||||
}
|
||||
|
@ -741,26 +612,12 @@ namespace xharness
|
|||
if (!success.HasValue)
|
||||
success = false;
|
||||
|
||||
if (isSimulator) {
|
||||
foreach (var sim in simulators) {
|
||||
// Upload the system log
|
||||
if (File.Exists (sim.SystemLog)) {
|
||||
Harness.Log (success.Value ? 1 : 0, "System log for the '{1}' simulator is: {0}", sim.SystemLog, sim.Name);
|
||||
bool isCompanion = sim != simulator;
|
||||
|
||||
var log = Logs.Create (LogDirectory, sim.UDID + ".log", isCompanion ? "System log (companion)" : "System log");
|
||||
File.Copy (sim.SystemLog, log.Path, true);
|
||||
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", log.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (success.Value) {
|
||||
Result = TestExecutingResult.Succeeded;
|
||||
} else if (timed_out) {
|
||||
if (timed_out) {
|
||||
Result = TestExecutingResult.TimedOut;
|
||||
} else if (crashed) {
|
||||
Result = TestExecutingResult.Crashed;
|
||||
} else if (success.Value) {
|
||||
Result = TestExecutingResult.Succeeded;
|
||||
} else {
|
||||
Result = TestExecutingResult.Failed;
|
||||
}
|
||||
|
@ -784,133 +641,21 @@ namespace xharness
|
|||
[DllImport ("/usr/lib/libc.dylib")]
|
||||
static extern void kill (int pid, int sig);
|
||||
|
||||
void KillPid (XProcess proc, int pid, TimeSpan kill_separation, TimeSpan timeout, string type)
|
||||
async Task KillPidAsync (Log log, int pid, TimeSpan kill_separation, TimeSpan timeout, string type)
|
||||
{
|
||||
Harness.Log ("{2} timeout ({1} s) reached, will now send SIGQUIT to the app (PID: {0})", pid, timeout.TotalSeconds, type);
|
||||
log.WriteLine ("{2} timeout ({1} s) reached, will now send SIGQUIT to the app (PID: {0})", pid, timeout.TotalSeconds, type);
|
||||
kill (pid, 3 /* SIGQUIT */); // print managed stack traces.
|
||||
if (!proc.WaitForExit (kill_separation /* wait for at most 5 seconds to see if something happens */)) {
|
||||
Harness.Log ("{2} timeout ({1} s) reached, will now send SIGABRT to the app (PID: {0})", pid, timeout.TotalSeconds, type);
|
||||
if (await ProcessHelper.PollForExitAsync (pid, kill_separation /* wait for at most 5 seconds to see if something happens */))
|
||||
return;
|
||||
|
||||
log.WriteLine ("{2} timeout ({1} s) reached, will now send SIGABRT to the app (PID: {0})", pid, timeout.TotalSeconds, type);
|
||||
kill (pid, 6 /* SIGABRT */); // print native stack traces.
|
||||
if (!proc.WaitForExit (kill_separation /* wait another 5 seconds */)) {
|
||||
Harness.Log ("{2} timeout ({1} s) reached, will now send SIGKILL to the app (PID: {0})", pid, timeout.TotalSeconds, type);
|
||||
if (await ProcessHelper.PollForExitAsync (pid, kill_separation /* wait another 5 seconds */))
|
||||
return;
|
||||
|
||||
log.WriteLine ("{2} timeout ({1} s) reached, will now send SIGKILL to the app (PID: {0})", pid, timeout.TotalSeconds, type);
|
||||
kill (pid, 9 /* SIGKILL */); // terminate unconditionally.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<string> CreateCrashReportsSnapshot (bool simulator)
|
||||
{
|
||||
HashSet<string> rv;
|
||||
|
||||
if (simulator) {
|
||||
var dir = Path.Combine (Environment.GetEnvironmentVariable ("HOME"), "Library", "Logs", "DiagnosticReports");
|
||||
if (Directory.Exists (dir)) {
|
||||
rv = new HashSet<string> (Directory.EnumerateFiles (dir));
|
||||
} else {
|
||||
rv = new HashSet<string> ();
|
||||
}
|
||||
} else {
|
||||
var tmp = Path.GetTempFileName ();
|
||||
if (ExecuteCommand (Harness.MlaunchPath, "--list-crash-reports=" + tmp + " --sdkroot " + Harness.XcodeRoot, true)) {
|
||||
rv = new HashSet<string> (File.ReadAllLines (tmp));
|
||||
} else {
|
||||
rv = new HashSet<string> ();
|
||||
}
|
||||
File.Delete (tmp);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void KillEverything ()
|
||||
{
|
||||
if (SkipSimulatorCleanup)
|
||||
return;
|
||||
|
||||
var to_kill = new string [] { "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService" };
|
||||
foreach (var k in to_kill)
|
||||
ExecuteCommand ("killall", "-9 \"" + k + "\"", true, output_verbosity_level: 1);
|
||||
}
|
||||
|
||||
static bool shown_simulator_list;
|
||||
void ShowSimulatorList ()
|
||||
{
|
||||
if (shown_simulator_list)
|
||||
return;
|
||||
shown_simulator_list = true;
|
||||
if (Harness.Verbosity > 0)
|
||||
ExecuteXcodeCommand ("simctl", "list", ignore_errors: true, timeout: TimeSpan.FromSeconds (10));
|
||||
}
|
||||
|
||||
bool ExecuteXcodeCommand (string executable, string args, bool ignore_errors = false, int output_verbosity_level = 1, TimeSpan? timeout = null)
|
||||
{
|
||||
return ExecuteCommand (Path.Combine (Harness.XcodeRoot, "Contents", "Developer", "usr", "bin", executable), args, ignore_errors, output_verbosity_level, timeout: timeout);
|
||||
}
|
||||
|
||||
bool ExecuteCommand (string filename, string args, bool ignore_errors = false, int output_verbosity_level = 1, StringBuilder captured_output = null, TimeSpan? timeout = null, Dictionary<string, string> environment_variables = null)
|
||||
{
|
||||
int exitcode;
|
||||
return ExecuteCommand (filename, args, out exitcode, ignore_errors, output_verbosity_level, captured_output, timeout, environment_variables);
|
||||
}
|
||||
|
||||
bool ExecuteCommand (string filename, string args, out int exitcode, bool ignore_errors = false, int output_verbosity_level = 1, StringBuilder captured_output = null, TimeSpan? timeout = null, Dictionary<string, string> environment_variables = null)
|
||||
{
|
||||
if (captured_output == null)
|
||||
captured_output = new StringBuilder ();
|
||||
var streamEnds = new CountdownEvent (2);
|
||||
using (var p = new Process ()) {
|
||||
p.StartInfo.FileName = filename;
|
||||
p.StartInfo.Arguments = args;
|
||||
p.StartInfo.UseShellExecute = false;
|
||||
p.StartInfo.RedirectStandardOutput = true;
|
||||
p.StartInfo.RedirectStandardError = true;
|
||||
if (environment_variables != null) {
|
||||
foreach (var kvp in environment_variables)
|
||||
p.StartInfo.EnvironmentVariables.Add (kvp.Key, kvp.Value);
|
||||
}
|
||||
p.OutputDataReceived += (object sender, DataReceivedEventArgs e) =>
|
||||
{
|
||||
if (e.Data == null) {
|
||||
streamEnds.Signal ();
|
||||
} else {
|
||||
lock (captured_output) {
|
||||
captured_output.AppendLine (e.Data);
|
||||
Harness.Log (output_verbosity_level, e.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
p.ErrorDataReceived += (object sender, DataReceivedEventArgs e) =>
|
||||
{
|
||||
if (e.Data == null) {
|
||||
streamEnds.Signal ();
|
||||
} else {
|
||||
lock (captured_output) {
|
||||
captured_output.AppendLine (e.Data);
|
||||
Harness.Log (output_verbosity_level, e.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
Harness.Log (output_verbosity_level, "{0} {1}", p.StartInfo.FileName, p.StartInfo.Arguments);
|
||||
p.Start ();
|
||||
p.BeginOutputReadLine ();
|
||||
p.BeginErrorReadLine ();
|
||||
if (p.WaitForExit (!timeout.HasValue ? int.MaxValue : (int) timeout.Value.TotalMilliseconds )) {
|
||||
streamEnds.Wait ();
|
||||
exitcode = p.ExitCode;
|
||||
if (p.ExitCode != 0 && !ignore_errors)
|
||||
throw new Exception (string.Format ("Failed to execute {0}:\n{1}", filename, captured_output.ToString ()));
|
||||
return p.ExitCode == 0;
|
||||
} else {
|
||||
if (!ignore_errors)
|
||||
throw new Exception (string.Format ("Execution of {0} timed out after {2} minutes:\n{1}", filename, captured_output.ToString (), timeout.Value.TotalMinutes));
|
||||
else
|
||||
Harness.Log ("Execution of {0} timed out after {2} minutes:\n{1}", filename, captured_output.ToString (), timeout.Value.TotalMinutes);
|
||||
exitcode = 0;
|
||||
kill (p.Id, 9);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,16 +9,14 @@ namespace xharness
|
|||
public class DeviceLogCapturer
|
||||
{
|
||||
public Harness Harness;
|
||||
public string LogPath;
|
||||
public Log Log;
|
||||
public string DeviceName;
|
||||
|
||||
StreamWriter writer;
|
||||
Process process;
|
||||
CountdownEvent streamEnds;
|
||||
|
||||
public void StartCapture ()
|
||||
{
|
||||
writer = new StreamWriter (new FileStream (LogPath, FileMode.Create));
|
||||
streamEnds = new CountdownEvent (2);
|
||||
|
||||
process = new Process ();
|
||||
|
@ -35,8 +33,8 @@ namespace xharness
|
|||
if (e.Data == null) {
|
||||
streamEnds.Signal ();
|
||||
} else {
|
||||
lock (writer) {
|
||||
writer.WriteLine (e.Data);
|
||||
lock (Log) {
|
||||
Log.WriteLine (e.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -44,12 +42,12 @@ namespace xharness
|
|||
if (e.Data == null) {
|
||||
streamEnds.Signal ();
|
||||
} else {
|
||||
lock (writer) {
|
||||
writer.WriteLine (e.Data);
|
||||
lock (Log) {
|
||||
Log.WriteLine (e.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
Harness.Log (1, "{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
Log.WriteLine ("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
process.Start ();
|
||||
process.BeginOutputReadLine ();
|
||||
process.BeginErrorReadLine ();
|
||||
|
@ -62,8 +60,6 @@ namespace xharness
|
|||
Harness.Log ("Could not kill 'mtouch --logdev' process in 5 seconds.");
|
||||
}
|
||||
process.Dispose ();
|
||||
writer.Flush ();
|
||||
writer.Dispose ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
namespace xharness
|
||||
|
@ -20,7 +23,7 @@ namespace xharness
|
|||
{
|
||||
public HarnessAction Action { get; set; }
|
||||
public int Verbosity { get; set; }
|
||||
public LogFile HarnessLog { get; set; }
|
||||
public Log HarnessLog { get; set; }
|
||||
|
||||
// This is the maccore/tests directory.
|
||||
string root_directory;
|
||||
|
@ -66,7 +69,7 @@ namespace xharness
|
|||
|
||||
public Harness ()
|
||||
{
|
||||
LaunchTimeout = InWrench ? 1 : 120;
|
||||
LaunchTimeout = InWrench ? 3 : 120;
|
||||
}
|
||||
|
||||
public string XcodeRoot {
|
||||
|
@ -83,19 +86,57 @@ namespace xharness
|
|||
}
|
||||
}
|
||||
|
||||
string DownloadMlaunch ()
|
||||
{
|
||||
// Just hardcode this for now. We should be able to switch to a shipped version of XS soon.
|
||||
var mlaunch_url = "https://files.xamarin.com/~rolf/mlaunch-9d097ff4457cfc9943a91a4e17c07b09a7743625";
|
||||
var mlaunch_path = Path.Combine (Path.GetTempPath (), Path.GetFileName (mlaunch_url), "mlaunch");
|
||||
if (File.Exists (mlaunch_path))
|
||||
return mlaunch_path;
|
||||
try {
|
||||
Log ("Downloading mlaunch...");
|
||||
Directory.CreateDirectory (Path.GetDirectoryName (mlaunch_path));
|
||||
var wc = new System.Net.WebClient ();
|
||||
wc.DownloadFile (mlaunch_url, mlaunch_path + ".tmp");
|
||||
new Mono.Unix.UnixFileInfo (mlaunch_path + ".tmp").FileAccessPermissions |= Mono.Unix.FileAccessPermissions.UserExecute;
|
||||
File.Delete (mlaunch_path);
|
||||
File.Move (mlaunch_path + ".tmp", mlaunch_path);
|
||||
Log ("Downloaded mlaunch.");
|
||||
} catch (Exception e) {
|
||||
Log ("Could not download mlaunch: {0}", e);
|
||||
}
|
||||
return mlaunch_path;
|
||||
}
|
||||
|
||||
string mlaunch;
|
||||
public string MlaunchPath {
|
||||
get {
|
||||
if (mlaunch == null) {
|
||||
var path = Path.GetFullPath (Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (RootDirectory)), "maccore", "tools", "mlaunch", "mlaunch"));
|
||||
var dir = Path.GetFullPath (RootDirectory);
|
||||
while (dir.Length > 3) {
|
||||
var filename = Path.GetFullPath (Path.Combine (dir, "maccore", "tools", "mlaunch", "mlaunch"));
|
||||
if (File.Exists (filename))
|
||||
return mlaunch = filename;
|
||||
dir = Path.GetDirectoryName (dir);
|
||||
}
|
||||
|
||||
string path = string.Empty;
|
||||
Log ("Could not find mlaunch locally, will try downloading it.");
|
||||
try {
|
||||
path = DownloadMlaunch ();
|
||||
} catch (Exception e) {
|
||||
Log ("Could not download mlaunch: {0}", e);
|
||||
}
|
||||
if (!File.Exists (path)) {
|
||||
Log ("Could not find mlaunch locally ({0}), will try in Xamarin Studio.app.", path);
|
||||
Log ("Will try in Xamarin Studio.app.", path);
|
||||
path = "/Applications/Xamarin Studio.app/Contents/Resources/lib/monodevelop/AddIns/MonoDevelop.IPhone/mlaunch.app/Contents/MacOS/mlaunch";
|
||||
}
|
||||
|
||||
if (!File.Exists (path))
|
||||
throw new FileNotFoundException (string.Format ("Could not find mlaunch: {0}", path));
|
||||
|
||||
Log ("Found mlaunch: {0}", path);
|
||||
|
||||
mlaunch = path;
|
||||
}
|
||||
|
||||
|
@ -366,12 +407,16 @@ namespace xharness
|
|||
|
||||
public int Install ()
|
||||
{
|
||||
if (HarnessLog == null)
|
||||
HarnessLog = new ConsoleLog ();
|
||||
|
||||
foreach (var project in IOSTestProjects) {
|
||||
var runner = new AppRunner () {
|
||||
Harness = this,
|
||||
ProjectFile = project.Path,
|
||||
MainLog = HarnessLog,
|
||||
};
|
||||
var rv = runner.Install ();
|
||||
var rv = runner.Install (HarnessLog);
|
||||
if (rv != 0)
|
||||
return rv;
|
||||
}
|
||||
|
@ -380,12 +425,16 @@ namespace xharness
|
|||
|
||||
public int Run ()
|
||||
{
|
||||
if (HarnessLog == null)
|
||||
HarnessLog = new ConsoleLog ();
|
||||
|
||||
foreach (var project in IOSTestProjects) {
|
||||
var runner = new AppRunner () {
|
||||
Harness = this,
|
||||
ProjectFile = project.Path,
|
||||
MainLog = HarnessLog,
|
||||
};
|
||||
var rv = runner.Run ();
|
||||
var rv = runner.RunAsync ().Result;
|
||||
if (rv != 0)
|
||||
return rv;
|
||||
}
|
||||
|
@ -547,5 +596,65 @@ namespace xharness
|
|||
return disable_watchos_on_wrench.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<ProcessExecutionResult> ExecuteXcodeCommandAsync (string executable, string args, TextWriter output, TimeSpan timeout)
|
||||
{
|
||||
return ProcessHelper.ExecuteCommandAsync (Path.Combine (XcodeRoot, "Contents", "Developer", "usr", "bin", executable), args, output, timeout: timeout);
|
||||
}
|
||||
|
||||
public Task<ProcessExecutionResult> ExecuteXcodeCommandAsync (string executable, string args, Log log, TimeSpan timeout)
|
||||
{
|
||||
return ProcessHelper.ExecuteCommandAsync (Path.Combine (XcodeRoot, "Contents", "Developer", "usr", "bin", executable), args, log.GetWriter () , timeout: timeout);
|
||||
}
|
||||
|
||||
public async Task ShowSimulatorList (LogStream log)
|
||||
{
|
||||
await ExecuteXcodeCommandAsync ("simctl", "list", log.GetWriter (), TimeSpan.FromSeconds (10));
|
||||
}
|
||||
|
||||
public async Task<LogFile> SymbolicateCrashReportAsync (Log log, LogFile report)
|
||||
{
|
||||
var symbolicatecrash = Path.Combine (XcodeRoot, "Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash");
|
||||
if (!File.Exists (symbolicatecrash))
|
||||
symbolicatecrash = Path.Combine (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 symbolicated = new LogFile ("Symbolicated crash report", report.Path + ".symbolicated");
|
||||
var environment = new Dictionary<string, string> { { "DEVELOPER_DIR", Path.Combine (XcodeRoot, "Contents", "Developer") } };
|
||||
var rv = await ProcessHelper.ExecuteCommandAsync (symbolicatecrash, Quote (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;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HashSet<string>> CreateCrashReportsSnapshotAsync (Log log, bool simulator)
|
||||
{
|
||||
var rv = new HashSet<string> ();
|
||||
|
||||
if (simulator) {
|
||||
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 result = await ProcessHelper.ExecuteCommandAsync (MlaunchPath, "--list-crash-reports=" + tmp + " --sdkroot " + XcodeRoot, log, TimeSpan.FromMinutes (1));
|
||||
if (result.Succeeded)
|
||||
rv.UnionWith (File.ReadAllLines (tmp));
|
||||
} finally {
|
||||
File.Delete (tmp);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,11 +12,18 @@ namespace xharness
|
|||
public class Jenkins
|
||||
{
|
||||
public Harness Harness;
|
||||
public bool IncludeClassic;
|
||||
public bool IncludeClassiciOS;
|
||||
public bool IncludeClassicMac = true;
|
||||
public bool IncludeBcl;
|
||||
public bool IncludeMac = true;
|
||||
public bool IncludeiOS = true;
|
||||
public bool IncludetvOS = true;
|
||||
public bool IncludewatchOS = true;
|
||||
public bool IncludeMmpTest;
|
||||
|
||||
public LogFiles Logs = new LogFiles ();
|
||||
LogFile SimulatorLoadLog;
|
||||
public Logs Logs = new Logs ();
|
||||
public Log MainLog;
|
||||
Log SimulatorLoadLog;
|
||||
|
||||
public string LogDirectory {
|
||||
get {
|
||||
|
@ -35,7 +42,7 @@ namespace xharness
|
|||
|
||||
Simulators.Harness = Harness;
|
||||
if (SimulatorLoadLog == null)
|
||||
SimulatorLoadLog = Logs.Create (LogDirectory, "simulator-list.log", "Simulator Listing");
|
||||
SimulatorLoadLog = Logs.CreateStream (LogDirectory, "simulator-list.log", "Simulator Listing");
|
||||
try {
|
||||
await Simulators.LoadAsync (SimulatorLoadLog);
|
||||
} catch (Exception e) {
|
||||
|
@ -70,12 +77,18 @@ namespace xharness
|
|||
Simulators.SupportedDeviceTypes.
|
||||
Where ((SimDeviceType v) => v.ProductFamilyId == "Watch").
|
||||
First ();
|
||||
var devices =
|
||||
Simulators.AvailableDevices.
|
||||
Where ((SimDevice d) => d.SimRuntime == latestwatchOSRuntime.Identifier && d.SimDeviceType == watchOSDeviceType.Identifier);
|
||||
var pair = Simulators.AvailableDevicePairs.
|
||||
FirstOrDefault ((SimDevicePair v) => devices.Any ((SimDevice d) => d.UDID == v.Gizmo));
|
||||
var device =
|
||||
Simulators.AvailableDevices.
|
||||
Where ((SimDevice v) => v.SimRuntime == latestwatchOSRuntime.Identifier && v.SimDeviceType == watchOSDeviceType.Identifier).
|
||||
Where ((SimDevice v) => Simulators.AvailableDevicePairs.Any ((pair) => pair.Gizmo == v.UDID)). // filter to watch devices that exists in a device pair
|
||||
First ();
|
||||
runtasks.Add (new RunSimulatorTask (buildTask, device) { Platform = TestPlatform.watchOS, ExecutionResult = TestExecutingResult.Ignored });
|
||||
FirstOrDefault ((SimDevice v) => pair.Gizmo == v.UDID); // select the device in the device pair.
|
||||
var companion =
|
||||
Simulators.AvailableDevices.
|
||||
FirstOrDefault ((SimDevice v) => pair.Companion == v.UDID);
|
||||
runtasks.Add (new RunSimulatorTask (buildTask, device, companion) { Platform = TestPlatform.watchOS, ExecutionResult = TestExecutingResult.Ignored });
|
||||
} else {
|
||||
var latestiOSRuntime =
|
||||
Simulators.SupportedRuntimes.
|
||||
|
@ -87,7 +100,7 @@ namespace xharness
|
|||
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-5").First ()) { Platform = TestPlatform.iOS_Unified32 });
|
||||
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-6s").First ()) { Platform = TestPlatform.iOS_Unified64 });
|
||||
} else {
|
||||
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-4s").First ()) { Platform = TestPlatform.iOS_Classic });
|
||||
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-5").First ()) { Platform = TestPlatform.iOS_Classic });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,6 +117,8 @@ namespace xharness
|
|||
// Missing:
|
||||
// api-diff
|
||||
// msbuild tests
|
||||
|
||||
if (IncludeiOS || IncludetvOS || IncludewatchOS) {
|
||||
var runSimulatorTasks = new List<RunSimulatorTask> ();
|
||||
|
||||
foreach (var project in Harness.IOSTestProjects) {
|
||||
|
@ -113,55 +128,56 @@ namespace xharness
|
|||
if (!IncludeBcl && project.Path.Contains ("bcl-test"))
|
||||
continue;
|
||||
|
||||
var build = new XBuildTask ()
|
||||
{
|
||||
var build = new XBuildTask () {
|
||||
Jenkins = this,
|
||||
ProjectFile = project.Path,
|
||||
ProjectConfiguration = "Debug",
|
||||
ProjectPlatform = "iPhoneSimulator",
|
||||
Platform = TestPlatform.iOS_Classic,
|
||||
};
|
||||
if (IncludeClassic)
|
||||
if (IncludeClassiciOS && IncludeiOS)
|
||||
runSimulatorTasks.AddRange (await CreateRunSimulatorTaskAsync (build));
|
||||
|
||||
var suffixes = new string [] { "-unified", "-tvos", "-watchos" };
|
||||
foreach (var suffix in suffixes) {
|
||||
var derived = new XBuildTask ()
|
||||
{
|
||||
var suffixes = new List<Tuple<string, TestPlatform>> ();
|
||||
if (IncludeiOS)
|
||||
suffixes.Add (new Tuple<string, TestPlatform> ("-unified", TestPlatform.iOS_Unified));
|
||||
if (IncludetvOS)
|
||||
suffixes.Add (new Tuple<string, TestPlatform> ("-tvos", TestPlatform.tvOS));
|
||||
if (IncludewatchOS)
|
||||
suffixes.Add (new Tuple<string, TestPlatform> ("-watchos", TestPlatform.watchOS));
|
||||
foreach (var pair in suffixes) {
|
||||
var derived = new XBuildTask () {
|
||||
Jenkins = this,
|
||||
ProjectFile = AddSuffixToPath (project.Path, suffix),
|
||||
ProjectFile = AddSuffixToPath (project.Path, pair.Item1),
|
||||
ProjectConfiguration = build.ProjectConfiguration,
|
||||
ProjectPlatform = build.ProjectPlatform,
|
||||
Platform = pair.Item2,
|
||||
};
|
||||
switch (suffix) {
|
||||
case "-unified":
|
||||
derived.Platform = TestPlatform.iOS_Unified;
|
||||
break;
|
||||
case "-tvos":
|
||||
derived.Platform = TestPlatform.tvOS;
|
||||
break;
|
||||
case "-watchos":
|
||||
derived.Platform = TestPlatform.watchOS;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException ();
|
||||
}
|
||||
runSimulatorTasks.AddRange (await CreateRunSimulatorTaskAsync (derived));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var taskGroup in runSimulatorTasks.GroupBy ((RunSimulatorTask task) => task.Device)) {
|
||||
Tasks.Add (new AggregatedRunSimulatorTask (taskGroup)
|
||||
{
|
||||
Tasks.Add (new AggregatedRunSimulatorTask (taskGroup) {
|
||||
Jenkins = this,
|
||||
Device = taskGroup.Key,
|
||||
Devices = taskGroup.First ().Simulators,
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var task in runSimulatorTasks) {
|
||||
if (task.TestName == "framework-test")
|
||||
task.ExecutionResult = TestExecutingResult.Ignored;
|
||||
}
|
||||
}
|
||||
|
||||
if (IncludeMac) {
|
||||
foreach (var project in Harness.MacTestProjects) {
|
||||
if (!project.IsExecutableProject)
|
||||
continue;
|
||||
|
||||
if (!IncludeMmpTest && project.Path.Contains ("mmptest"))
|
||||
continue;
|
||||
|
||||
BuildToolTask build;
|
||||
if (project.GenerateVariations) {
|
||||
build = new MdtoolTask ();
|
||||
|
@ -176,8 +192,7 @@ namespace xharness
|
|||
build.ProjectPlatform = "x86";
|
||||
build.SpecifyPlatform = false;
|
||||
build.SpecifyConfiguration = false;
|
||||
var exec = new MacExecuteTask ()
|
||||
{
|
||||
var exec = new MacExecuteTask () {
|
||||
Platform = build.Platform,
|
||||
Jenkins = this,
|
||||
BuildTask = build,
|
||||
|
@ -185,6 +200,7 @@ namespace xharness
|
|||
ProjectConfiguration = build.ProjectConfiguration,
|
||||
ProjectPlatform = build.ProjectPlatform,
|
||||
};
|
||||
if (IncludeClassicMac)
|
||||
Tasks.Add (exec);
|
||||
|
||||
if (project.GenerateVariations) {
|
||||
|
@ -192,11 +208,6 @@ namespace xharness
|
|||
Tasks.Add (CloneExecuteTask (exec, TestPlatform.Mac_UnifiedXM45, "-unifiedXM45"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var task in runSimulatorTasks) {
|
||||
if (task.TestName == "framework-test")
|
||||
task.ExecutionResult = TestExecutingResult.Ignored;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +241,9 @@ namespace xharness
|
|||
{
|
||||
try {
|
||||
Directory.CreateDirectory (LogDirectory);
|
||||
Harness.HarnessLog = Logs.Create (LogDirectory, "Harness.log", "Harness log");
|
||||
Harness.HarnessLog = MainLog = Logs.CreateStream (LogDirectory, "Harness.log", "Harness log");
|
||||
Harness.HarnessLog.Timestamp = true;
|
||||
|
||||
Task.Run (async () =>
|
||||
{
|
||||
await PopulateTasksAsync ();
|
||||
|
@ -242,7 +255,7 @@ namespace xharness
|
|||
GenerateReport ();
|
||||
return Tasks.Any ((v) => v.ExecutionResult == TestExecutingResult.Failed || v.ExecutionResult == TestExecutingResult.Crashed) ? 1 : 0;
|
||||
} catch (Exception ex) {
|
||||
Harness.Log ("Unexpected exception: {0}", ex);
|
||||
MainLog.WriteLine ("Unexpected exception: {0}", ex);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
@ -343,7 +356,7 @@ function toggleContainerVisibility (containerName)
|
|||
writer.WriteLine ("<h1>Test results</h1>");
|
||||
|
||||
foreach (var log in Logs)
|
||||
writer.WriteLine ("<a href='{0}' type='text/plain'>{1}</a><br />", log.Path.Substring (LogDirectory.Length + 1), log.Description);
|
||||
writer.WriteLine ("<a href='{0}' type='text/plain'>{1}</a><br />", log.FullPath.Substring (LogDirectory.Length + 1), log.Description);
|
||||
|
||||
var allSimulatorTasks = new List<RunSimulatorTask> ();
|
||||
var allExecuteTasks = new List<MacExecuteTask> ();
|
||||
|
@ -384,26 +397,20 @@ function toggleContainerVisibility (containerName)
|
|||
continue;
|
||||
}
|
||||
|
||||
//var executionGroup = group as IEnumerable<MacExecuteTask>;
|
||||
//if (executionGroup != null) {
|
||||
// writer.WriteLine ("<a href='#test_{2}'>{0}</a> ({1})<br />", group.Key, string.Join (", ", executionGroup.Select ((v) => string.Format ("<span style='color: {0}'>{1}</span>", GetTestColor (v), v.Mode)).ToArray ()), group.Key.Replace (' ', '-'));
|
||||
// continue;
|
||||
//}
|
||||
|
||||
throw new NotImplementedException ();
|
||||
}
|
||||
}
|
||||
|
||||
//foreach (var group in allExecuteTasks.GroupBy ((MacExecuteTask v) => v.TestName)) {
|
||||
|
||||
//}
|
||||
|
||||
foreach (var group in allTasks.GroupBy ((TestTask v) => v.TestName)) {
|
||||
var firstResult = group.First ().ExecutionResult;
|
||||
var identicalResults = group.All ((v) => v.ExecutionResult == firstResult);
|
||||
// Create a collection of all non-ignored tests in the group (unless all tests were ignored).
|
||||
var relevantGroup = group.Where ((v) => v.ExecutionResult != TestExecutingResult.Ignored);
|
||||
if (!relevantGroup.Any ())
|
||||
relevantGroup = group;
|
||||
var firstResult = relevantGroup.First ().ExecutionResult;
|
||||
var identicalResults = relevantGroup.All ((v) => v.ExecutionResult == firstResult);
|
||||
var defaultHide = !group.Any ((v) => v.Failed);
|
||||
writer.WriteLine ("<h2 id='test_{1}'>{0} (<span style='color: {2}'>{4}</span>) <small><a id='button_container_{1}' href=\"javascript: toggleContainerVisibility ('{1}');\">{3}</a></small> </h2>",
|
||||
group.Key, group.Key.Replace (' ', '-'), GetTestColor (group), defaultHide ? "Show" : "Hide", identicalResults ? firstResult.ToString () : "multiple results");
|
||||
group.Key, group.Key.Replace (' ', '-'), GetTestColor (relevantGroup), defaultHide ? "Show" : "Hide", identicalResults ? firstResult.ToString () : "multiple results");
|
||||
writer.WriteLine ("<div id='test_container_{0}' style='display: {1}'>", group.Key.Replace (' ', '-'), defaultHide ? "none" : "block");
|
||||
foreach (var test in group) {
|
||||
string state;
|
||||
|
@ -415,13 +422,12 @@ function toggleContainerVisibility (containerName)
|
|||
var logs = test.AggregatedLogs;
|
||||
if (logs.Count () > 0) {
|
||||
foreach (var log in logs) {
|
||||
writer.WriteLine ("<a href='{0}' type='text/plain'>{1}</a><br />", log.Path.Substring (LogDirectory.Length + 1), log.Description);
|
||||
log.Flush ();
|
||||
writer.WriteLine ("<a href='{0}' type='text/plain'>{1}</a><br />", log.FullPath.Substring (LogDirectory.Length + 1), log.Description);
|
||||
if (log.Description == "Test log") {
|
||||
var summary = string.Empty;
|
||||
try {
|
||||
if (File.Exists (log.Path)) {
|
||||
using (var fs = new FileStream (log.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
|
||||
using (var reader = new StreamReader (fs)) {
|
||||
using (var reader = log.GetReader ()) {
|
||||
while (!reader.EndOfStream) {
|
||||
string line = reader.ReadLine ();
|
||||
if (line.StartsWith ("Tests run:", StringComparison.Ordinal)) {
|
||||
|
@ -431,11 +437,7 @@ function toggleContainerVisibility (containerName)
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
summary = "No test log (yet).";
|
||||
}
|
||||
if (summary != null)
|
||||
if (!string.IsNullOrEmpty (summary))
|
||||
writer.WriteLine ("<span style='padding-left: 15px;'>{0}</span><br />", summary);
|
||||
} catch (Exception ex) {
|
||||
writer.WriteLine ("<span style='padding-left: 15px;'>Could not parse log file: {0}</span><br />", System.Web.HttpUtility.HtmlEncode (ex.Message));
|
||||
|
@ -443,19 +445,13 @@ function toggleContainerVisibility (containerName)
|
|||
} else if (log.Description == "Build log") {
|
||||
var errors = new HashSet<string> ();
|
||||
try {
|
||||
if (File.Exists (log.Path)) {
|
||||
using (var fs = new FileStream (log.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
|
||||
using (var reader = new StreamReader (fs)) {
|
||||
using (var reader = log.GetReader ()) {
|
||||
while (!reader.EndOfStream) {
|
||||
string line = reader.ReadLine ().Trim ();
|
||||
if (line.Contains (": error"))
|
||||
errors.Add (line);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.Add ("Log file doesn't exist (yet)");
|
||||
}
|
||||
foreach (var error in errors)
|
||||
writer.WriteLine ("<span style='padding-left: 15\tpx;'>{0}</span> <br />", error);
|
||||
} catch (Exception ex) {
|
||||
|
@ -478,7 +474,6 @@ function toggleContainerVisibility (containerName)
|
|||
if (File.Exists (report))
|
||||
File.Delete (report);
|
||||
File.WriteAllBytes (report, stream.ToArray ());
|
||||
Harness.Log (2, "Generated report: {0}", report);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -556,10 +551,10 @@ function toggleContainerVisibility (containerName)
|
|||
|
||||
public TestPlatform Platform { get; set; }
|
||||
|
||||
public LogFiles Logs = new LogFiles ();
|
||||
public Logs Logs = new Logs ();
|
||||
public List<Resource> Resources = new List<Resource> ();
|
||||
|
||||
public virtual IEnumerable<LogFile> AggregatedLogs {
|
||||
public virtual IEnumerable<Log> AggregatedLogs {
|
||||
get {
|
||||
return Logs;
|
||||
}
|
||||
|
@ -663,27 +658,30 @@ function toggleContainerVisibility (containerName)
|
|||
var sln = Path.ChangeExtension (ProjectFile, "sln");
|
||||
args.Append (Harness.Quote (File.Exists (sln) ? sln : ProjectFile));
|
||||
xbuild.StartInfo.Arguments = args.ToString ();
|
||||
Harness.Log ("Building {0} ({1})", TestName, Mode);
|
||||
Jenkins.MainLog.WriteLine ("Building {0} ({1})", TestName, Mode);
|
||||
SetEnvironmentVariables (xbuild);
|
||||
var log = Logs.Create (LogDirectory, "build-" + Platform + ".txt", "Build log");
|
||||
var log = Logs.CreateStream (LogDirectory, "build-" + Platform + ".txt", "Build log");
|
||||
foreach (string key in xbuild.StartInfo.EnvironmentVariables.Keys)
|
||||
log.WriteLine ("{0}={1}", key, xbuild.StartInfo.EnvironmentVariables [key]);
|
||||
log.WriteLine ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
|
||||
if (Harness.DryRun) {
|
||||
Harness.Log ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
|
||||
} else {
|
||||
if (!Harness.DryRun) {
|
||||
try {
|
||||
await xbuild.RunAsync (log.Path, true, TimeSpan.FromMinutes (5));
|
||||
ExecutionResult = xbuild.ExitCode == 0 ? TestExecutingResult.Succeeded : TestExecutingResult.Failed;
|
||||
} catch (TimeoutException e) {
|
||||
log.WriteLine ("Build timed out after {0} seconds.", e.Timeout.TotalSeconds);
|
||||
var timeout = TimeSpan.FromMinutes (5);
|
||||
var result = await xbuild.RunAsync (log, true, timeout);
|
||||
if (result.TimedOut) {
|
||||
ExecutionResult = TestExecutingResult.TimedOut;
|
||||
log.WriteLine ("Build timed out after {0} seconds.", timeout.TotalSeconds);
|
||||
} else if (result.Succeeded) {
|
||||
ExecutionResult = TestExecutingResult.Succeeded;
|
||||
} else {
|
||||
ExecutionResult = TestExecutingResult.Failed;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.WriteLine ("Harness exception: {0}", e);
|
||||
ExecutionResult = TestExecutingResult.HarnessException;
|
||||
}
|
||||
}
|
||||
Harness.Log ("Built {0} ({1})", TestName, Mode);
|
||||
Jenkins.MainLog.WriteLine ("Built {0} ({1})", TestName, Mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -704,27 +702,30 @@ function toggleContainerVisibility (containerName)
|
|||
args.Append ($"/p:Configuration={ProjectConfiguration} ");
|
||||
args.Append (Harness.Quote (ProjectFile));
|
||||
xbuild.StartInfo.Arguments = args.ToString ();
|
||||
Harness.Log ("Building {0} ({1})", TestName, Mode);
|
||||
Jenkins.MainLog.WriteLine ("Building {0} ({1})", TestName, Mode);
|
||||
SetEnvironmentVariables (xbuild);
|
||||
var log = Logs.Create (LogDirectory, "build-" + Platform + ".txt", "Build log");
|
||||
var log = Logs.CreateStream (LogDirectory, "build-" + Platform + ".txt", "Build log");
|
||||
foreach (string key in xbuild.StartInfo.EnvironmentVariables.Keys)
|
||||
log.WriteLine ("{0}={1}", key, xbuild.StartInfo.EnvironmentVariables [key]);
|
||||
log.WriteLine ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
|
||||
if (Harness.DryRun) {
|
||||
Harness.Log ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
|
||||
} else {
|
||||
if (!Harness.DryRun) {
|
||||
try {
|
||||
await xbuild.RunAsync (log.Path, true, TimeSpan.FromMinutes (5));
|
||||
ExecutionResult = xbuild.ExitCode == 0 ? TestExecutingResult.Succeeded : TestExecutingResult.Failed;
|
||||
} catch (TimeoutException e) {
|
||||
log.WriteLine ("Build timed out after {0} seconds.", e.Timeout.TotalSeconds);
|
||||
var timeout = TimeSpan.FromMinutes (5);
|
||||
var result = await xbuild.RunAsync (log, true, timeout);
|
||||
if (result.TimedOut) {
|
||||
ExecutionResult = TestExecutingResult.TimedOut;
|
||||
log.WriteLine ("Build timed out after {0} seconds.", timeout.TotalSeconds);
|
||||
} else if (result.Succeeded) {
|
||||
ExecutionResult = TestExecutingResult.Succeeded;
|
||||
} else {
|
||||
ExecutionResult = TestExecutingResult.Failed;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.WriteLine ("Harness exception: {0}", e);
|
||||
ExecutionResult = TestExecutingResult.HarnessException;
|
||||
}
|
||||
}
|
||||
Harness.Log ("Built {0} ({1})", TestName, Mode);
|
||||
Jenkins.MainLog.WriteLine ("Built {0} ({1})", TestName, Mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -758,7 +759,7 @@ function toggleContainerVisibility (containerName)
|
|||
public string Path;
|
||||
public BuildToolTask BuildTask;
|
||||
|
||||
public override IEnumerable<LogFile> AggregatedLogs {
|
||||
public override IEnumerable<Log> AggregatedLogs {
|
||||
get {
|
||||
return base.AggregatedLogs.Union (BuildTask.Logs);
|
||||
}
|
||||
|
@ -793,25 +794,28 @@ function toggleContainerVisibility (containerName)
|
|||
using (var resource = await Jenkins.DesktopResource.AcquireConcurrentAsync ()) {
|
||||
using (var proc = new Process ()) {
|
||||
proc.StartInfo.FileName = Path;
|
||||
Harness.Log ("Executing {0} ({1})", TestName, Mode);
|
||||
var log = Logs.Create (LogDirectory, "execute-" + Platform + ".txt", "Execution log");
|
||||
Jenkins.MainLog.WriteLine ("Executing {0} ({1})", TestName, Mode);
|
||||
var log = Logs.CreateStream (LogDirectory, "execute-" + Platform + ".txt", "Execution log");
|
||||
log.WriteLine ("{0} {1}", proc.StartInfo.FileName, proc.StartInfo.Arguments);
|
||||
if (Harness.DryRun) {
|
||||
Harness.Log ("{0} {1}", proc.StartInfo.FileName, proc.StartInfo.Arguments);
|
||||
} else {
|
||||
if (!Harness.DryRun) {
|
||||
ExecutionResult = TestExecutingResult.Running;
|
||||
try {
|
||||
await proc.RunAsync (log.Path, true, TimeSpan.FromMinutes (5));
|
||||
ExecutionResult = proc.ExitCode == 0 ? TestExecutingResult.Succeeded : TestExecutingResult.Failed;
|
||||
} catch (TimeoutException e) {
|
||||
log.WriteLine ("Execution timed out after {0} seconds.", e.Timeout.TotalSeconds);
|
||||
var timeout = TimeSpan.FromMinutes (10);
|
||||
var result = await proc.RunAsync (log, true, timeout);
|
||||
if (result.TimedOut) {
|
||||
log.WriteLine ("Execution timed out after {0} seconds.", timeout.TotalSeconds);
|
||||
ExecutionResult = TestExecutingResult.TimedOut;
|
||||
} else if (result.Succeeded) {
|
||||
ExecutionResult = TestExecutingResult.Succeeded;
|
||||
} else {
|
||||
ExecutionResult = TestExecutingResult.Failed;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.WriteLine (e.ToString ());
|
||||
ExecutionResult = TestExecutingResult.HarnessException;
|
||||
}
|
||||
}
|
||||
Harness.Log ("Executed {0} ({1})", TestName, Mode);
|
||||
Jenkins.MainLog.WriteLine ("Executed {0} ({1})", TestName, Mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -820,11 +824,28 @@ function toggleContainerVisibility (containerName)
|
|||
class RunSimulatorTask : TestTask
|
||||
{
|
||||
public SimDevice Device;
|
||||
public SimDevice CompanionDevice;
|
||||
public XBuildTask BuildTask;
|
||||
public string AppRunnerTarget;
|
||||
|
||||
AppRunner runner;
|
||||
|
||||
public SimDevice [] Simulators {
|
||||
get {
|
||||
if (CompanionDevice == null) {
|
||||
return new SimDevice [] { Device };
|
||||
} else {
|
||||
return new SimDevice [] { Device, CompanionDevice };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string BundleIdentifier {
|
||||
get {
|
||||
return runner.BundleIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task BuildAsync ()
|
||||
{
|
||||
if (Finished)
|
||||
|
@ -838,7 +859,7 @@ function toggleContainerVisibility (containerName)
|
|||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<LogFile> AggregatedLogs {
|
||||
public override IEnumerable<Log> AggregatedLogs {
|
||||
get {
|
||||
return base.AggregatedLogs.Union (BuildTask.Logs);
|
||||
}
|
||||
|
@ -869,10 +890,11 @@ function toggleContainerVisibility (containerName)
|
|||
set { throw new NotSupportedException (); }
|
||||
}
|
||||
|
||||
public RunSimulatorTask (XBuildTask build_task, SimDevice device)
|
||||
public RunSimulatorTask (XBuildTask build_task, SimDevice device, SimDevice companion_device = null)
|
||||
{
|
||||
BuildTask = build_task;
|
||||
Device = device;
|
||||
CompanionDevice = companion_device;
|
||||
Jenkins = build_task.Jenkins;
|
||||
ProjectFile = build_task.ProjectFile;
|
||||
|
||||
|
@ -886,7 +908,7 @@ function toggleContainerVisibility (containerName)
|
|||
}
|
||||
}
|
||||
|
||||
public Task PrepareSimulatorAsync (bool initialize)
|
||||
public Task PrepareSimulatorAsync ()
|
||||
{
|
||||
if (Finished)
|
||||
return Task.FromResult (true);
|
||||
|
@ -896,48 +918,42 @@ function toggleContainerVisibility (containerName)
|
|||
return Task.FromResult (true);
|
||||
}
|
||||
|
||||
var clean_state = false;//Platform == TestPlatform.tvOS;
|
||||
runner = new AppRunner ()
|
||||
{
|
||||
Harness = Harness,
|
||||
ProjectFile = ProjectFile,
|
||||
SkipSimulatorSetup = !initialize,
|
||||
SkipSimulatorCleanup = !initialize,
|
||||
EnsureCleanSimulatorState = clean_state,
|
||||
Target = AppRunnerTarget,
|
||||
LogDirectory = LogDirectory,
|
||||
MainLog = Logs.CreateStream (LogDirectory, "run-" + Device.UDID + ".log", "Run log"),
|
||||
};
|
||||
runner.Simulators = new SimDevice [] { Device };
|
||||
runner.Simulators = Simulators;
|
||||
runner.Initialize ();
|
||||
runner.PrepareSimulator ();
|
||||
|
||||
return Task.FromResult (true);
|
||||
}
|
||||
|
||||
protected override Task ExecuteAsync ()
|
||||
protected override async Task ExecuteAsync ()
|
||||
{
|
||||
Harness.Log ("Running simulator '{0}' ({2}) for {1}", Device.Name, ProjectFile, Jenkins.Simulators.SupportedRuntimes.Where ((v) => v.Identifier == Device.SimRuntime).First ().Name);
|
||||
Jenkins.MainLog.WriteLine ("Running simulator '{0}' ({2}) for {1}", Device.Name, ProjectFile, Jenkins.Simulators.SupportedRuntimes.Where ((v) => v.Identifier == Device.SimRuntime).First ().Name);
|
||||
|
||||
if (Finished)
|
||||
return Task.FromResult (true);
|
||||
return;
|
||||
|
||||
if (Harness.DryRun) {
|
||||
Harness.Log ("<running app in simulator>");
|
||||
Jenkins.MainLog.WriteLine ("<running app in simulator>");
|
||||
} else {
|
||||
try {
|
||||
ExecutionResult = (ExecutionResult & ~TestExecutingResult.InProgressMask) | TestExecutingResult.Running;
|
||||
Jenkins.GenerateReport ();
|
||||
runner.Run ();
|
||||
await runner.RunAsync ();
|
||||
ExecutionResult = runner.Result;
|
||||
} catch (Exception ex) {
|
||||
Harness.Log ("Test {0} failed: {1}", Path.GetFileName (ProjectFile), ex);
|
||||
Jenkins.MainLog.WriteLine ("Test {0} failed: {1}", Path.GetFileName (ProjectFile), ex);
|
||||
ExecutionResult = TestExecutingResult.HarnessException;
|
||||
}
|
||||
Logs.AddRange (runner.Logs);
|
||||
}
|
||||
|
||||
foreach (var log in Logs)
|
||||
Console.WriteLine ("Log: {0}: {1}", log.Description, log.Path);
|
||||
|
||||
return Task.FromResult (true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -946,7 +962,7 @@ function toggleContainerVisibility (containerName)
|
|||
// between different simulators (which is slow).
|
||||
class AggregatedRunSimulatorTask : TestTask
|
||||
{
|
||||
public SimDevice Device;
|
||||
public SimDevice[] Devices;
|
||||
|
||||
public IEnumerable<RunSimulatorTask> Tasks;
|
||||
|
||||
|
@ -971,19 +987,26 @@ function toggleContainerVisibility (containerName)
|
|||
build_timer.Stop ();
|
||||
|
||||
using (var desktop = await Jenkins.DesktopResource.AcquireExclusiveAsync ()) {
|
||||
Harness.Log ("Preparing simulator: {0}", Device.Name);
|
||||
run_timer.Start ();
|
||||
|
||||
Jenkins.MainLog.WriteLine ("Preparing simulator: {0}", Devices [0].Name);
|
||||
// We need to set the dialog permissions for all the apps
|
||||
// before launching the simulator, because once launched
|
||||
// the simulator caches the values in-memory.
|
||||
bool first = true;
|
||||
foreach (var task in Tasks) {
|
||||
await task.PrepareSimulatorAsync (first);
|
||||
first = false;
|
||||
}
|
||||
foreach (var task in Tasks)
|
||||
await task.PrepareSimulatorAsync ();
|
||||
|
||||
foreach (var dev in Devices)
|
||||
await dev.PrepareSimulatorAsync (Jenkins.MainLog, Tasks.Where ((v) => !v.Ignored).Select ((v) => v.BundleIdentifier).ToArray ());
|
||||
|
||||
run_timer.Start ();
|
||||
foreach (var task in Tasks)
|
||||
await task.RunAsync ();
|
||||
|
||||
foreach (var dev in Devices)
|
||||
await dev.ShutdownAsync (Jenkins.MainLog);
|
||||
|
||||
await SimDevice.KillEverythingAsync (Jenkins.MainLog);
|
||||
|
||||
run_timer.Stop ();
|
||||
}
|
||||
|
||||
|
@ -1007,7 +1030,6 @@ function toggleContainerVisibility (containerName)
|
|||
int max_concurrent_users = 1;
|
||||
bool exclusive;
|
||||
|
||||
|
||||
public Resource (string name, int max_concurrent_users = 1)
|
||||
{
|
||||
this.Name = name;
|
||||
|
@ -1120,4 +1142,3 @@ function toggleContainerVisibility (containerName)
|
|||
BuildFailure = 0x8000 + Failed,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,306 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
|
||||
namespace xharness
|
||||
{
|
||||
public abstract class Log : IDisposable
|
||||
{
|
||||
public string Description;
|
||||
public bool Timestamp;
|
||||
|
||||
protected Log ()
|
||||
{
|
||||
}
|
||||
|
||||
protected Log (string description)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public abstract string FullPath { get; }
|
||||
|
||||
protected abstract void WriteImpl (string value);
|
||||
|
||||
public virtual StreamReader GetReader ()
|
||||
{
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
|
||||
public virtual TextWriter GetWriter ()
|
||||
{
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
|
||||
public void Write (string value)
|
||||
{
|
||||
if (Timestamp)
|
||||
value = DateTime.Now.ToString ("HH:mm:ss.fffffff") + " " + value;
|
||||
WriteImpl (value);
|
||||
}
|
||||
|
||||
public void WriteLine (string value)
|
||||
{
|
||||
Write (value + "\n");
|
||||
}
|
||||
|
||||
public void WriteLine (string format, params object [] args)
|
||||
{
|
||||
Write (string.Format (format, args) + "\n");
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return Description;
|
||||
}
|
||||
|
||||
public virtual void Flush ()
|
||||
{
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
protected virtual void Dispose (bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose ()
|
||||
{
|
||||
Dispose (true);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class LogFile : Log
|
||||
{
|
||||
public string Path;
|
||||
StreamWriter writer;
|
||||
|
||||
public LogFile (string description, string path)
|
||||
: base (description)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
protected override void WriteImpl (string value)
|
||||
{
|
||||
lock (this) {
|
||||
using (var str = new FileStream (Path, FileMode.Append, FileAccess.Write, FileShare.Read)) {
|
||||
using (var writer = new StreamWriter (str)) {
|
||||
writer.Write (value);
|
||||
writer.Flush ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string FullPath {
|
||||
get {
|
||||
return Path;
|
||||
}
|
||||
}
|
||||
|
||||
public override StreamReader GetReader ()
|
||||
{
|
||||
return new StreamReader (new FileStream (Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
}
|
||||
|
||||
public override TextWriter GetWriter ()
|
||||
{
|
||||
return writer ?? (writer = new StreamWriter (new FileStream (Path, FileMode.Open, FileAccess.Write, FileShare.Read)));
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
base.Dispose (disposing);
|
||||
|
||||
if (writer != null) {
|
||||
writer.Dispose ();
|
||||
writer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LogStream : Log
|
||||
{
|
||||
string path;
|
||||
FileStream fs;
|
||||
StreamWriter writer;
|
||||
|
||||
public FileStream FileStream {
|
||||
get {
|
||||
return fs;
|
||||
}
|
||||
}
|
||||
|
||||
public override StreamReader GetReader ()
|
||||
{
|
||||
return new StreamReader (new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
||||
}
|
||||
|
||||
public override TextWriter GetWriter ()
|
||||
{
|
||||
return writer ?? (writer = new StreamWriter (fs));
|
||||
}
|
||||
|
||||
public LogStream (string description, string path)
|
||||
: base (description)
|
||||
{
|
||||
this.path = path;
|
||||
|
||||
fs = new FileStream (path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
}
|
||||
|
||||
protected override void WriteImpl (string value)
|
||||
{
|
||||
var w = GetWriter ();
|
||||
w.Write (value);
|
||||
w.Flush ();
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
base.Dispose (disposing);
|
||||
|
||||
if (writer != null) {
|
||||
writer.Dispose ();
|
||||
writer = null;
|
||||
}
|
||||
|
||||
if (fs != null) {
|
||||
fs.Dispose ();
|
||||
fs = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string FullPath {
|
||||
get {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Logs : List<Log>
|
||||
{
|
||||
public LogStream CreateStream (string directory, string filename, string name)
|
||||
{
|
||||
Directory.CreateDirectory (directory);
|
||||
var rv = new LogStream (name, Path.GetFullPath (Path.Combine (directory, filename)));
|
||||
Add (rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public LogFile CreateFile (string description, string path)
|
||||
{
|
||||
var rv = new LogFile (description, path);
|
||||
Add (rv);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConsoleLog : Log
|
||||
{
|
||||
protected override void WriteImpl (string value)
|
||||
{
|
||||
Console.Write (value);
|
||||
}
|
||||
|
||||
public override string FullPath {
|
||||
get {
|
||||
throw new NotSupportedException ();
|
||||
}
|
||||
}
|
||||
|
||||
public override TextWriter GetWriter ()
|
||||
{
|
||||
return Console.Out;
|
||||
}
|
||||
}
|
||||
|
||||
public class CaptureLog : Log
|
||||
{
|
||||
public string CapturePath { get; private set; }
|
||||
public string Path { get; set; }
|
||||
|
||||
long startPosition;
|
||||
long endPosition;
|
||||
|
||||
public CaptureLog (string capture_path)
|
||||
{
|
||||
CapturePath = capture_path;
|
||||
}
|
||||
|
||||
public void StartCapture ()
|
||||
{
|
||||
if (File.Exists (CapturePath))
|
||||
startPosition = new FileInfo (CapturePath).Length;
|
||||
}
|
||||
|
||||
public void StopCapture ()
|
||||
{
|
||||
endPosition = new FileInfo (CapturePath).Length;
|
||||
|
||||
Capture ();
|
||||
}
|
||||
|
||||
void Capture ()
|
||||
{
|
||||
if (startPosition == 0)
|
||||
return;
|
||||
|
||||
var currentEndPosition = endPosition;
|
||||
if (currentEndPosition == 0)
|
||||
currentEndPosition = new FileInfo (CapturePath).Length;
|
||||
|
||||
var length = (int) (currentEndPosition - startPosition);
|
||||
var currentLength = new FileInfo (CapturePath).Length;
|
||||
var capturedLength = 0L;
|
||||
|
||||
if (File.Exists (Path))
|
||||
capturedLength = new FileInfo (Path).Length;
|
||||
|
||||
// capture 1k more data than when we stopped, since the system log
|
||||
// is cached in memory and flushed once in a while (so when the app
|
||||
// requests the system log to be captured, it's usually not complete).
|
||||
var availableLength = currentLength - startPosition;
|
||||
if (availableLength <= capturedLength)
|
||||
return; // We've captured before, and nothing new as added since last time.
|
||||
|
||||
// Capture at most 1k more
|
||||
availableLength = Math.Min (availableLength, length + 1024);
|
||||
|
||||
using (var reader = new FileStream (CapturePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
|
||||
using (var writer = new FileStream (Path, FileMode.Create, FileAccess.Write, FileShare.Read)) {
|
||||
var buffer = new byte [4096];
|
||||
reader.Position = startPosition;
|
||||
while (availableLength > 0) {
|
||||
int read = reader.Read (buffer, 0, Math.Min (buffer.Length, length));
|
||||
if (read > 0) {
|
||||
writer.Write (buffer, 0, read);
|
||||
availableLength -= read;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush ()
|
||||
{
|
||||
base.Flush ();
|
||||
|
||||
Capture ();
|
||||
}
|
||||
|
||||
protected override void WriteImpl (string value)
|
||||
{
|
||||
throw new InvalidOperationException ();
|
||||
}
|
||||
|
||||
public override string FullPath {
|
||||
get {
|
||||
return Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace xharness
|
||||
{
|
||||
public class LogFile
|
||||
{
|
||||
public string Description;
|
||||
public string Path;
|
||||
|
||||
void Write (string value)
|
||||
{
|
||||
lock (this) {
|
||||
using (var str = new FileStream (Path, FileMode.Append, FileAccess.Write, FileShare.Read)) {
|
||||
using (var writer = new StreamWriter (str))
|
||||
writer.Write (value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteLine (string value)
|
||||
{
|
||||
Write (value + "\n");
|
||||
}
|
||||
|
||||
public void WriteLine (string format, params object [] args)
|
||||
{
|
||||
Write (string.Format (format, args) + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
public class LogFiles : List<LogFile>
|
||||
{
|
||||
public LogFile Create (string directory, string filename, string name, bool overwrite = true)
|
||||
{
|
||||
var rv = new LogFile ()
|
||||
{
|
||||
Path = Path.GetFullPath (Path.Combine (directory, filename)),
|
||||
Description = name,
|
||||
};
|
||||
Add (rv);
|
||||
|
||||
if (File.Exists (rv.Path)) {
|
||||
if (overwrite)
|
||||
File.Delete (rv.Path);
|
||||
} else {
|
||||
Directory.CreateDirectory (directory);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +1,109 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace xharness
|
||||
{
|
||||
public static class Process_Extensions
|
||||
public class ProcessExecutionResult
|
||||
{
|
||||
public static async Task RunAsync (this Process process, LogFile log)
|
||||
{
|
||||
using (var stream = new StreamWriter (log.Path, false))
|
||||
await RunAsync (process, stream, stream);
|
||||
public bool TimedOut { get; set; }
|
||||
public int ExitCode { get; set; }
|
||||
|
||||
public bool Succeeded { get { return !TimedOut && ExitCode == 0; } }
|
||||
}
|
||||
|
||||
public static async Task RunAsync (this Process process, string outputFile, bool append, TimeSpan? timeout = null)
|
||||
public static class ProcessHelper
|
||||
{
|
||||
public static Task<ProcessExecutionResult> ExecuteCommandAsync (string filename, string args, Log log, TimeSpan timeout, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null)
|
||||
{
|
||||
return ExecuteCommandAsync (filename, args, log.GetWriter (), timeout, environment_variables, cancellation_token);
|
||||
}
|
||||
|
||||
public static async Task<ProcessExecutionResult> ExecuteCommandAsync (string filename, string args, string outputPath, TimeSpan timeout, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null)
|
||||
{
|
||||
Directory.CreateDirectory (Path.GetDirectoryName (outputPath));
|
||||
using (var fs = new FileStream (outputPath, FileMode.Append, FileAccess.Write, FileShare.Read)) {
|
||||
using (var stream = new StreamWriter (fs))
|
||||
return await ExecuteCommandAsync (filename, args, stream, timeout, environment_variables, cancellation_token);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<ProcessExecutionResult> ExecuteCommandAsync (string filename, string args, TextWriter output, 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;
|
||||
return await p.RunAsync (output, output, timeout, environment_variables, cancellation_token);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport ("/usr/lib/libc.dylib")]
|
||||
internal static extern int kill (int pid, int sig);
|
||||
|
||||
public static Task<bool> PollForExitAsync (int pid, TimeSpan timeout)
|
||||
{
|
||||
var rv = new TaskCompletionSource<bool> ();
|
||||
var watch = new Stopwatch ();
|
||||
watch.Start ();
|
||||
Task.Run (async () => {
|
||||
while (watch.ElapsedMilliseconds < timeout.TotalMilliseconds) {
|
||||
if (kill (pid, 0) != 0) {
|
||||
// pid is not valid anymore, program exited
|
||||
rv.SetResult (true);
|
||||
return;
|
||||
}
|
||||
await Task.Delay (TimeSpan.FromMilliseconds (100));
|
||||
}
|
||||
|
||||
rv.SetResult (false);
|
||||
});
|
||||
return rv.Task;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Process_Extensions
|
||||
{
|
||||
public static async Task<ProcessExecutionResult> RunAsync (this Process process, Log log, CancellationToken? cancellation_token = null)
|
||||
{
|
||||
var stream = log.GetWriter ();
|
||||
return await RunAsync (process, stream, stream, cancellation_token: cancellation_token);
|
||||
}
|
||||
|
||||
public static async Task<ProcessExecutionResult> RunAsync (this Process process, string outputFile, bool append, TimeSpan? timeout = null, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null)
|
||||
{
|
||||
Directory.CreateDirectory (Path.GetDirectoryName (outputFile));
|
||||
using (var fs = new FileStream (outputFile, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read)) {
|
||||
using (var stream = new StreamWriter (fs))
|
||||
await RunAsync (process, stream, stream, timeout);
|
||||
return await RunAsync (process, stream, stream, timeout, environment_variables, cancellation_token);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task RunAsync (this Process process, StreamWriter StdoutStream, StreamWriter StderrStream, TimeSpan? timeout = null)
|
||||
public static Task<ProcessExecutionResult> RunAsync (this Process process, Log log, bool append, TimeSpan? timeout = null, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null)
|
||||
{
|
||||
var writer = log.GetWriter ();
|
||||
return RunAsync (process, writer, writer, timeout, environment_variables, cancellation_token);
|
||||
}
|
||||
|
||||
public static async Task<ProcessExecutionResult> RunAsync (this Process process, TextWriter StdoutStream, TextWriter StderrStream, TimeSpan? timeout = null, Dictionary<string, string> environment_variables = null, CancellationToken? cancellation_token = null)
|
||||
{
|
||||
var stdout_completion = new TaskCompletionSource<bool> ();
|
||||
var stderr_completion = new TaskCompletionSource<bool> ();
|
||||
var exit_completion = new TaskCompletionSource<bool> ();
|
||||
var rv = new ProcessExecutionResult ();
|
||||
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
|
||||
if (environment_variables != null) {
|
||||
foreach (var kvp in environment_variables)
|
||||
process.StartInfo.EnvironmentVariables [kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) =>
|
||||
{
|
||||
if (e.Data != null) {
|
||||
|
@ -57,35 +128,42 @@ namespace xharness
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
StdoutStream.WriteLine ("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
process.Start ();
|
||||
|
||||
process.BeginErrorReadLine ();
|
||||
process.BeginOutputReadLine ();
|
||||
|
||||
cancellation_token?.Register (() => {
|
||||
if (!exit_completion.Task.IsCompleted) {
|
||||
StderrStream.WriteLine ($"Execution was cancelled.");
|
||||
ProcessHelper.kill (process.Id, 9);
|
||||
}
|
||||
});
|
||||
|
||||
new Thread (() =>
|
||||
{
|
||||
if (timeout.HasValue) {
|
||||
if (!process.WaitForExit ((int) timeout.Value.TotalMilliseconds)) {
|
||||
process.Kill ();
|
||||
process.WaitForExit ((int) 5); // Wait 5s for the kill to work, just
|
||||
exit_completion.SetException (new TimeoutException { Timeout = timeout.Value });
|
||||
} else {
|
||||
exit_completion.SetResult (true);
|
||||
ProcessHelper.kill (process.Id, 9);
|
||||
process.WaitForExit ((int) TimeSpan.FromSeconds (5).TotalMilliseconds); // Wait 5s for the kill to work
|
||||
rv.TimedOut = true;
|
||||
lock (StderrStream)
|
||||
StderrStream.WriteLine ($"Execution timed out after {timeout.Value.TotalSeconds} seconds and the process was killed.");
|
||||
}
|
||||
} else {
|
||||
process.WaitForExit ();
|
||||
exit_completion.SetResult (true);
|
||||
}
|
||||
exit_completion.TrySetResult (true);
|
||||
}) {
|
||||
IsBackground = true,
|
||||
}.Start ();
|
||||
|
||||
await Task.WhenAll (stderr_completion.Task, stdout_completion.Task, exit_completion.Task);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeoutException : Exception
|
||||
{
|
||||
public TimeSpan Timeout;
|
||||
rv.ExitCode = process.ExitCode;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace xharness
|
|||
Port = newPort;
|
||||
break;
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine ("Failed to listen on port {0}: {1}", newPort, ex.Message);
|
||||
Log.WriteLine ("Failed to listen on port {0}: {1}", newPort, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ namespace xharness
|
|||
bool processed;
|
||||
|
||||
try {
|
||||
Console.WriteLine ("Test log server listening on: {0}:{1}", Address, Port);
|
||||
Log.WriteLine ("Test log server listening on: {0}:{1}", Address, Port);
|
||||
do {
|
||||
var context = server.GetContext ();
|
||||
processed = Processing (context);
|
||||
|
@ -87,7 +87,7 @@ namespace xharness
|
|||
finished = true;
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine ("Unknown upload url: {0}", request.RawUrl);
|
||||
Log.WriteLine ("Unknown upload url: {0}", request.RawUrl);
|
||||
response = $"Unknown upload url: {request.RawUrl}";
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@ namespace xharness
|
|||
|
||||
public IPAddress Address { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string LogPath { get; set; }
|
||||
public Log Log { get; set; }
|
||||
public LogStream TestLog { get; set; }
|
||||
public bool AutoExit { get; set; }
|
||||
|
||||
public abstract void Initialize ();
|
||||
|
@ -30,7 +31,7 @@ namespace xharness
|
|||
|
||||
protected void Connected (string remote)
|
||||
{
|
||||
Console.WriteLine ("Connection from {0} saving logs to {1}", remote, LogPath);
|
||||
Log.WriteLine ("Connection from {0} saving logs to {1}", remote, TestLog.FullPath);
|
||||
connected.Set ();
|
||||
|
||||
if (output_stream != null) {
|
||||
|
@ -38,9 +39,7 @@ namespace xharness
|
|||
output_stream.Dispose ();
|
||||
}
|
||||
|
||||
Directory.CreateDirectory (Path.GetDirectoryName (LogPath));
|
||||
|
||||
var fs = new FileStream (LogPath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
var fs = TestLog.FileStream;
|
||||
// a few extra bits of data only available from this side
|
||||
string header = String.Format ("[Local Date/Time:\t{1}]{0}[Remote Address:\t{2}]{0}",
|
||||
Environment.NewLine, DateTime.Now, remote);
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace xharness
|
|||
|
||||
try {
|
||||
do {
|
||||
Console.WriteLine ("Test log server listening on: {0}:{1}", Address, Port);
|
||||
Log.WriteLine ("Test log server listening on: {0}:{1}", Address, Port);
|
||||
using (TcpClient client = server.AcceptTcpClient ()) {
|
||||
processed = Processing (client);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
|
@ -16,7 +17,7 @@ namespace xharness
|
|||
public List<SimDevice> AvailableDevices = new List<SimDevice> ();
|
||||
public List<SimDevicePair> AvailableDevicePairs = new List<SimDevicePair> ();
|
||||
|
||||
public async Task LoadAsync (LogFile log)
|
||||
public async Task LoadAsync (Log log)
|
||||
{
|
||||
if (SupportedRuntimes.Count > 0)
|
||||
return;
|
||||
|
@ -27,7 +28,7 @@ namespace xharness
|
|||
process.StartInfo.FileName = Harness.MlaunchPath;
|
||||
process.StartInfo.Arguments = string.Format ("--sdkroot {0} --listsim {1}", Harness.XcodeRoot, tmpfile);
|
||||
log.WriteLine ("Launching {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
await process.RunAsync (log.Path, false);
|
||||
await process.RunAsync (log, false);
|
||||
log.WriteLine ("Result:");
|
||||
log.WriteLine (File.ReadAllText (tmpfile));
|
||||
var simulator_data = new XmlDocument ();
|
||||
|
@ -54,6 +55,7 @@ namespace xharness
|
|||
foreach (XmlNode sim in simulator_data.SelectNodes ("/MTouch/Simulator/AvailableDevices/SimDevice")) {
|
||||
AvailableDevices.Add (new SimDevice ()
|
||||
{
|
||||
Harness = Harness,
|
||||
Name = sim.Attributes ["Name"].Value,
|
||||
UDID = sim.Attributes ["UDID"].Value,
|
||||
SimRuntime = sim.SelectSingleNode ("SimRuntime").InnerText,
|
||||
|
@ -105,6 +107,134 @@ namespace xharness
|
|||
public string LogPath;
|
||||
|
||||
public string SystemLog { get { return Path.Combine (LogPath, "system.log"); } }
|
||||
|
||||
public Harness Harness;
|
||||
|
||||
public bool IsWatchSimulator { get { return SimRuntime.StartsWith ("com.apple.CoreSimulator.SimRuntime.watchOS", StringComparison.Ordinal); } }
|
||||
|
||||
public async Task EraseAsync (Log log)
|
||||
{
|
||||
// here we don't care if execution fails.
|
||||
// erase the simulator (make sure the device isn't running first)
|
||||
await Harness.ExecuteXcodeCommandAsync ("simctl", "shutdown " + UDID, log, TimeSpan.FromMinutes (1));
|
||||
await Harness.ExecuteXcodeCommandAsync ("simctl", "erase " + UDID, log, TimeSpan.FromMinutes (1));
|
||||
|
||||
// boot & shutdown to make sure it actually works
|
||||
await Harness.ExecuteXcodeCommandAsync ("simctl", "boot " + UDID, log, TimeSpan.FromMinutes (1));
|
||||
await Harness.ExecuteXcodeCommandAsync ("simctl", "shutdown " + UDID, log, TimeSpan.FromMinutes (1));
|
||||
}
|
||||
|
||||
public async Task ShutdownAsync (Log log)
|
||||
{
|
||||
await Harness.ExecuteXcodeCommandAsync ("simctl", "shutdown " + UDID, log, TimeSpan.FromMinutes (1));
|
||||
}
|
||||
|
||||
public static Task KillEverythingAsync (Log log)
|
||||
{
|
||||
var to_kill = new string [] { "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService" };
|
||||
|
||||
return ProcessHelper.ExecuteCommandAsync ("killall", "-9 " + string.Join (" ", to_kill.Select ((v) => Harness.Quote (v)).ToArray ()), log, TimeSpan.FromSeconds (10));
|
||||
}
|
||||
|
||||
public async Task AgreeToPromptsAsync (Log log, params string[] bundle_identifiers)
|
||||
{
|
||||
if (bundle_identifiers == null || bundle_identifiers.Length == 0) {
|
||||
log.WriteLine ("No bundle identifiers given when requested permission editing.");
|
||||
return;
|
||||
}
|
||||
|
||||
var TCC_db = Path.Combine (DataPath, "data", "Library", "TCC", "TCC.db");
|
||||
var sim_services = new string [] {
|
||||
"kTCCServiceAddressBook",
|
||||
"kTCCServicePhotos",
|
||||
"kTCCServiceMediaLibrary",
|
||||
"kTCCServiceUbiquity",
|
||||
"kTCCServiceWillow"
|
||||
};
|
||||
|
||||
var failure = false;
|
||||
var tcc_edit_timeout = 5;
|
||||
var watch = new Stopwatch ();
|
||||
watch.Start ();
|
||||
|
||||
do {
|
||||
failure = false;
|
||||
foreach (var bundle_identifier in bundle_identifiers) {
|
||||
foreach (var service in sim_services) {
|
||||
var sql = string.Format ("{0} \"INSERT INTO access VALUES('{1}','{2}',0,1,0,NULL,NULL);\"", TCC_db, service, bundle_identifier);
|
||||
var rv = await ProcessHelper.ExecuteCommandAsync ("sqlite3", sql, log, TimeSpan.FromSeconds (5));
|
||||
if (!rv.Succeeded) {
|
||||
failure = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (failure) {
|
||||
if (watch.Elapsed.TotalSeconds > tcc_edit_timeout)
|
||||
break;
|
||||
log.WriteLine ("Failed to edit TCC.db, trying again in 1 second... ", (int) (tcc_edit_timeout - watch.Elapsed.TotalSeconds));
|
||||
await Task.Delay (TimeSpan.FromSeconds (1));
|
||||
}
|
||||
}
|
||||
} while (failure);
|
||||
|
||||
if (failure) {
|
||||
log.WriteLine ("Failed to edit TCC.db, the test run might hang due to permission request dialogs");
|
||||
} else {
|
||||
log.WriteLine ("Successfully edited TCC.db");
|
||||
}
|
||||
}
|
||||
|
||||
async Task OpenSimulator (Log log)
|
||||
{
|
||||
string simulator_app;
|
||||
|
||||
if (IsWatchSimulator) {
|
||||
simulator_app = Path.Combine (Harness.XcodeRoot, "Contents", "Developer", "Applications", "Simulator (Watch).app");
|
||||
} else {
|
||||
simulator_app = Path.Combine (Harness.XcodeRoot, "Contents", "Developer", "Applications", "Simulator.app");
|
||||
if (!Directory.Exists (simulator_app))
|
||||
simulator_app = Path.Combine (Harness.XcodeRoot, "Contents", "Developer", "Applications", "iOS Simulator.app");
|
||||
}
|
||||
|
||||
await ProcessHelper.ExecuteCommandAsync ("open", "-a " + Harness.Quote (simulator_app) + " --args -CurrentDeviceUDID " + UDID, log, TimeSpan.FromSeconds (15));
|
||||
}
|
||||
|
||||
public async Task PrepareSimulatorAsync (Log log, params string[] bundle_identifiers)
|
||||
{
|
||||
// Kill all existing processes
|
||||
await KillEverythingAsync (log);
|
||||
|
||||
// We shutdown and erase all simulators.
|
||||
await EraseAsync (log);
|
||||
|
||||
// Edit the permissions to prevent dialog boxes in the test app
|
||||
var TCC_db = Path.Combine (DataPath, "data", "Library", "TCC", "TCC.db");
|
||||
if (!File.Exists (TCC_db)) {
|
||||
log.WriteLine ("Opening simulator to create TCC.db");
|
||||
await OpenSimulator (log);
|
||||
|
||||
var tcc_creation_timeout = 60;
|
||||
var watch = new Stopwatch ();
|
||||
watch.Start ();
|
||||
while (!File.Exists (TCC_db) && watch.Elapsed.TotalSeconds < tcc_creation_timeout) {
|
||||
log.WriteLine ("Waiting for simulator to create TCC.db... {0}", (int)(tcc_creation_timeout - watch.Elapsed.TotalSeconds));
|
||||
await Task.Delay (TimeSpan.FromSeconds (0.250));
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists (TCC_db)) {
|
||||
await AgreeToPromptsAsync (log, bundle_identifiers);
|
||||
} else {
|
||||
log.WriteLine ("No TCC.db found for the simulator {0} (SimRuntime={1} and SimDeviceType={1})", UDID, SimRuntime, SimDeviceType);
|
||||
}
|
||||
|
||||
// Make sure we're in a clean state
|
||||
await KillEverythingAsync (log);
|
||||
|
||||
// Make 100% sure we're shutdown
|
||||
await ShutdownAsync (log);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SimDevicePair
|
||||
|
@ -120,7 +250,7 @@ namespace xharness
|
|||
|
||||
public List<Device> ConnectedDevices = new List<Device> ();
|
||||
|
||||
public async Task LoadAsync ()
|
||||
public async Task LoadAsync (Log log)
|
||||
{
|
||||
if (ConnectedDevices.Count > 0)
|
||||
return;
|
||||
|
@ -130,7 +260,7 @@ namespace xharness
|
|||
using (var process = new Process ()) {
|
||||
process.StartInfo.FileName = Harness.MlaunchPath;
|
||||
process.StartInfo.Arguments = string.Format ("--sdkroot {0} --listdev={1} --output-format=xml", Harness.XcodeRoot, tmpfile);
|
||||
await process.RunAsync (tmpfile, false);
|
||||
await process.RunAsync (log, false);
|
||||
|
||||
var doc = new XmlDocument ();
|
||||
doc.LoadWithoutNetworkAccess (tmpfile);
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace xharness
|
||||
{
|
||||
public class XProcess
|
||||
{
|
||||
public Harness Harness;
|
||||
|
||||
public int VerbosityLevel = 1;
|
||||
Process process = new Process ();
|
||||
CountdownEvent output_completed = new CountdownEvent (2);
|
||||
object output_lock = new object ();
|
||||
StringBuilder output = new StringBuilder ();
|
||||
|
||||
public string FileName {
|
||||
get { return process.StartInfo.FileName; }
|
||||
set { process.StartInfo.FileName = value; }
|
||||
}
|
||||
|
||||
public string Arguments {
|
||||
get { return process.StartInfo.Arguments; }
|
||||
set { process.StartInfo.Arguments = value; }
|
||||
}
|
||||
|
||||
public Dictionary<string, string> EnvironmentVariables { get; set; }
|
||||
|
||||
public void Start ()
|
||||
{
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
|
||||
if (EnvironmentVariables != null) {
|
||||
foreach (var kvp in EnvironmentVariables)
|
||||
process.StartInfo.EnvironmentVariables.Add (kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) =>
|
||||
{
|
||||
if (e.Data == null) {
|
||||
output_completed.Signal ();
|
||||
} else {
|
||||
lock (output_lock) {
|
||||
output.AppendLine (e.Data);
|
||||
Harness.Log (VerbosityLevel, e.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) =>
|
||||
{
|
||||
if (e.Data == null) {
|
||||
output_completed.Signal ();
|
||||
} else {
|
||||
lock (output_lock) {
|
||||
output.AppendLine (e.Data);
|
||||
Harness.Log (VerbosityLevel, e.Data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Harness.Log ("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
process.Start ();
|
||||
process.BeginErrorReadLine ();
|
||||
process.BeginOutputReadLine ();
|
||||
}
|
||||
|
||||
public bool WaitForExit (TimeSpan timeout)
|
||||
{
|
||||
return process.WaitForExit ((int) timeout.TotalMilliseconds);
|
||||
}
|
||||
|
||||
public int ExitCode {
|
||||
get { return process.ExitCode; }
|
||||
}
|
||||
|
||||
public string ReadCurrentOutput ()
|
||||
{
|
||||
lock (output_lock)
|
||||
return output.ToString ();
|
||||
}
|
||||
|
||||
public int Id {
|
||||
get { return process.Id; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
<Reference Include="System" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="Mono.Posix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
|
@ -56,7 +57,6 @@
|
|||
<Compile Include="SimpleTcpListener.cs" />
|
||||
<Compile Include="BCLTarget.cs" />
|
||||
<Compile Include="DeviceLogCapturer.cs" />
|
||||
<Compile Include="XProcess.cs" />
|
||||
<Compile Include="MakefileGenerator.cs" />
|
||||
<Compile Include="SolutionGenerator.cs" />
|
||||
<Compile Include="MacClassicTarget.cs" />
|
||||
|
@ -68,7 +68,7 @@
|
|||
<Compile Include="Process_Extensions.cs" />
|
||||
<Compile Include="Simulators.cs" />
|
||||
<Compile Include="TestProject.cs" />
|
||||
<Compile Include="LogFile.cs" />
|
||||
<Compile Include="Log.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
Загрузка…
Ссылка в новой задаче