[tests] Implement support in xharness for running the simulator tests grouped by simulator, and write out an html report.

This commit is contained in:
Rolf Bjarne Kvinge 2016-06-06 12:48:53 +02:00
Родитель 68717b4100
Коммит d135612f59
14 изменённых файлов: 1245 добавлений и 233 удалений

Просмотреть файл

@ -4,3 +4,4 @@
time make world
make -j8 -C tools/apidiff jenkins-api-diff
make -C tests jenkins

Просмотреть файл

@ -65,6 +65,7 @@ test.config: Makefile $(TOP)/Make.config
@echo "IOS_DESTDIR=$(abspath $(IOS_DESTDIR))" >> $@
@echo "MAC_DESTDIR=$(abspath $(MAC_DESTDIR))" >> $@
@echo "WATCH_MONO_PATH=$(abspath $(WATCH_MONO_PATH))" >> $@
@echo "JENKINS_RESULTS_DIRECTORY=$(abspath $(JENKINS_RESULTS_DIRECTORY))" >> $@
clean-local::
$(Q) $(SYSTEM_XBUILD) /t:Clean /p:Platform=iPhoneSimulator /p:Configuration=$(CONFIG) $(XBUILD_VERBOSITY) tests.sln
@ -348,3 +349,5 @@ else
@echo "iOS tests have been disabled [$@]"
endif
jenkins: xharness/xharness.exe
$(Q) $(SYSTEM_MONO) --debug $< $(XHARNESS_VERBOSITY) --jenkins --autoconf --rootdir $(CURDIR) --sdkroot $(XCODE_DEVELOPER_ROOT)

Просмотреть файл

@ -6,6 +6,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace xharness
@ -15,7 +16,7 @@ namespace xharness
public Harness Harness;
public string ProjectFile;
XmlDocument simulator_data;
public TestExecutingResult Result { get; private set; }
string appName;
string appPath;
@ -27,64 +28,65 @@ namespace xharness
string device_name;
string companion_device_name;
class IData {
public XmlNode Node;
public string udid { get { return Node.Attributes ["UDID"].Value; } }
}
class SimulatorData : IData {
public string datapath { get { return Node.SelectSingleNode ("DataPath").InnerText; } }
public string logpath { get { return Node.SelectSingleNode ("LogPath").InnerText; } }
public string devicetype { get { return Node.SelectSingleNode ("SimDeviceType").InnerText; } }
public string runtime { get { return Node.SelectSingleNode ("SimRuntime").InnerText; } }
public string system_log { get { return Path.Combine (logpath, "system.log"); } }
public string name { get { return Node.Attributes ["Name"].Value; } }
}
class DeviceData : IData {
public string DeviceIdentifier { get { return Node.SelectSingleNode ("DeviceIdentifier")?.InnerText; } }
public string DeviceClass { get { return Node.SelectSingleNode ("DeviceClass")?.InnerText; } }
public string CompanionIdentifier { get { return Node.SelectSingleNode ("CompanionIdentifier")?.InnerText; } }
public string Name { get { return Node.SelectSingleNode ("Name")?.InnerText; } }
}
// For watch apps we end up with 2 simulators, the watch simulator (the main one), and the iphone simulator (the companion one).
SimulatorData[] simulators;
SimulatorData simulator { get { return simulators [0]; } }
SimulatorData companion_simulator { get { return simulators.Length == 2 ? simulators [1] : null; } }
SimDevice[] simulators;
SimDevice simulator { get { return simulators [0]; } }
SimDevice companion_simulator { get { return simulators.Length == 2 ? simulators [1] : null; } }
string target;
public string Target {
get { return target ?? Harness.Target; }
set { target = value; }
}
string log_directory;
public string LogDirectory {
get { return log_directory ?? Harness.LogDirectory; }
set { log_directory = value; }
}
public LogFiles Logs = new LogFiles ();
public SimDevice [] Simulators {
get { return simulators; }
set { simulators = value; }
}
string mode;
string SymbolicateCrashReport (string report)
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, 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 + "\"", true, captured_output: output, environment_variables: new Dictionary<string, string> { { "DEVELOPER_DIR", Path.Combine (Harness.XcodeRoot, "Contents", "Developer") }})) {
File.WriteAllText (report + ".symbolicated", output.ToString ());
Harness.Log ("Symbolicated {0} successfully.", report);
return report + ".symbolicated";
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, output.ToString ());
Harness.Log ("Failed to symbolicate {0}:\n{1}", report.Path, output.ToString ());
return report;
}
void FindSimulator ()
{
if (simulators != null)
return;
string simulator_devicetype;
string simulator_runtime;
switch (Harness.Target) {
switch (Target) {
case "ios-simulator-32":
simulator_devicetype = "com.apple.CoreSimulator.SimDeviceType.iPhone-5";
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
@ -108,51 +110,47 @@ namespace xharness
default:
throw new Exception (string.Format ("Unknown simulator target: {0}", Harness.Target));
}
var tmpfile = Path.GetTempFileName ();
try {
ExecuteCommand (Harness.MlaunchPath, string.Format ("--sdkroot {0} --listsim {1}", Harness.XcodeRoot, tmpfile), output_verbosity_level: 1);
simulator_data = new XmlDocument ();
simulator_data.LoadWithoutNetworkAccess (tmpfile);
SimulatorData candidate = null;
simulators = null;
foreach (XmlNode sim in simulator_data.SelectNodes ("/MTouch/Simulator/AvailableDevices/SimDevice")) {
var data = new SimulatorData { Node = sim };
if (data.runtime == simulator_runtime && data.devicetype == simulator_devicetype) {
var udid = data.udid;
var secondaryData = (SimulatorData) null;
var nodeCompanion = simulator_data.SelectSingleNode ("/MTouch/Simulator/AvailableDevicePairs/SimDevicePair/Companion[text() = '" + udid + "']");
var nodeGizmo = simulator_data.SelectSingleNode ("/MTouch/Simulator/AvailableDevicePairs/SimDevicePair/Gizmo[text() = '" + udid + "']");
if (nodeCompanion != null) {
var gizmo_udid = nodeCompanion.ParentNode.SelectSingleNode ("Gizmo").InnerText;
var node = simulator_data.SelectSingleNode ("/MTouch/Simulator/AvailableDevices/SimDevice[@UDID = '" + gizmo_udid + "']");
secondaryData = new SimulatorData () { Node = node };
} else if (nodeGizmo != null) {
var companion_udid = nodeGizmo.ParentNode.SelectSingleNode ("Companion").InnerText;
var node = simulator_data.SelectSingleNode ("/MTouch/Simulator/AvailableDevices/SimDevice[@UDID = '" + companion_udid + "']");
secondaryData = new SimulatorData () { Node = node };
}
if (secondaryData != null) {
simulators = new SimulatorData[] { data, secondaryData };
break;
} else {
candidate = data;
}
}
var sims = new Simulators ();
Task.Run (async () =>
{
await sims.LoadAsync ();
}).Wait ();
var devices = sims.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == simulator_runtime && v.SimDeviceType == simulator_devicetype);
SimDevice candidate = null;
simulators = null;
foreach (var device in devices) {
var data = device;
var secondaryData = (SimDevice) null;
var nodeCompanions = sims.AvailableDevicePairs.Where ((SimDevicePair v) => v.Companion == device.UDID);
var nodeGizmos = sims.AvailableDevicePairs.Where ((SimDevicePair v) => v.Gizmo == device.UDID);
if (nodeCompanions.Any ()) {
var gizmo_udid = nodeCompanions.First ().Gizmo;
var node = sims.AvailableDevices.Where ((SimDevice v) => v.UDID == gizmo_udid);
secondaryData = node.First ();
} else if (nodeGizmos.Any ()) {
var companion_udid = nodeGizmos.First ().Companion;
var node = sims.AvailableDevices.Where ((SimDevice v) => v.UDID == companion_udid);
secondaryData = node.First ();
}
if (secondaryData != null) {
simulators = new SimDevice [] { data, secondaryData };
break;
} else {
candidate = data;
}
if (simulators == null)
simulators = new SimulatorData[] { candidate };
} finally {
File.Delete (tmpfile);
}
if (simulators == null)
simulators = new SimDevice [] { candidate };
if (simulators == null)
throw new Exception ("Could not find simulator");
Harness.Log (1, "Found simulator: {0} {1}", simulators [0].name, simulators [0].udid);
Harness.Log (1, "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);
Harness.Log (1, "Found companion simulator: {0} {1}", simulators [1].Name, simulators [1].UDID);
}
void FindDevice ()
@ -163,57 +161,87 @@ namespace xharness
device_name = Environment.GetEnvironmentVariable ("DEVICE_NAME");
if (!string.IsNullOrEmpty (device_name))
return;
var tmpfile = Path.GetTempFileName ();
try {
ExecuteCommand (Harness.MlaunchPath, string.Format ("--sdkroot {0} --listdev={1} --output-format=xml", Harness.XcodeRoot, tmpfile), output_verbosity_level: 1);
var doc = new XmlDocument ();
doc.LoadWithoutNetworkAccess (tmpfile);
var nodes = new List<DeviceData> ();
foreach (XmlNode dev in doc.SelectNodes ("/MTouch/Device")) {
nodes.Add (new DeviceData { Node = dev });
}
if (nodes.Count == 0)
throw new Exception ("No devices connected");
var devs = new Devices ();
Task.Run (async () =>
{
await devs.LoadAsync ();
}).Wait ();
string [] deviceClasses;
switch (mode) {
case "ios":
deviceClasses = new string [] { "iPhone", "iPad" };
break;
case "watchos":
deviceClasses = new string [] { "Watch" };
break;
case "tvos":
deviceClasses = new string [] { "AppleTV" }; // Untested
break;
default:
throw new Exception ($"unknown mode: {mode}");
}
string [] deviceClasses;
switch (mode) {
case "ios":
deviceClasses = new string [] { "iPhone", "iPad" };
break;
case "watchos":
deviceClasses = new string [] { "Watch" };
break;
case "tvos":
deviceClasses = new string [] { "AppleTV" }; // Untested
break;
default:
throw new Exception ($"unknown mode: {mode}");
}
var selected = nodes.Where ((v) => deviceClasses.Contains (v.DeviceClass));
DeviceData selected_data;
if (selected.Count () == 0) {
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);
} else {
selected_data = selected.First ();
}
device_name = selected_data.Name;
var selected = devs.ConnectedDevices.Where ((v) => deviceClasses.Contains (v.DeviceClass));
Device selected_data;
if (selected.Count () == 0) {
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);
} else {
selected_data = selected.First ();
}
device_name = selected_data.Name;
if (mode == "watchos") {
var companion = nodes.Where ((v) => v.DeviceIdentifier == selected_data.CompanionIdentifier);
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);
companion_device_name = companion.First ().Name;
if (mode == "watchos") {
var companion = devs.ConnectedDevices.Where ((v) => v.DeviceIdentifier == selected_data.CompanionIdentifier);
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);
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;
} else {
if (!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;
}
}
}
} finally {
File.Delete (tmpfile);
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");
}
}
@ -221,6 +249,7 @@ namespace xharness
{
if (SkipSimulatorSetup) {
Harness.Log (0, "Simulator setup skipped.");
AgreeToPrompts (false);
return;
}
@ -231,7 +260,7 @@ namespace xharness
// We only fixup TCC.db on the main simulator.
foreach (var sim in simulators) {
var udid = sim.udid;
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));
@ -242,14 +271,14 @@ namespace xharness
}
// Edit the permissions to prevent dialog boxes in the test app
var TCC_db = Path.Combine (simulator.datapath, "data", "Library", "TCC", "TCC.db");
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);
ExecuteCommand ("open", "-a \"" + simulator_app + "\" --args -CurrentDeviceUDID " + simulator.UDID, output_verbosity_level: 1);
var tcc_creation_timeout = 60;
var watch = new Stopwatch ();
@ -261,56 +290,22 @@ namespace xharness
}
if (File.Exists (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 (!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;
} else {
if (!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");
}
AgreeToPrompts (true);
} else {
Harness.Log ("No TCC.db found for the simulator {0} (SimRuntime={1} and SimDeviceType={1})", simulator.udid, simulator.runtime, simulator.devicetype);
}
foreach (var sim in simulators) {
if (!File.Exists (sim.system_log)) {
Harness.Log ("No system log found for SimRuntime={0} and SimDeviceType={1}", sim.runtime, sim.devicetype);
} else {
File.WriteAllText (sim.system_log, string.Format (" *** This log file was cleared out by Xamarin.iOS's test run at {0} **** \n", DateTime.Now.ToString ()));
}
Harness.Log ("No TCC.db found for the simulator {0} (SimRuntime={1} and SimDeviceType={1})", simulator.UDID, simulator.SimRuntime, simulator.SimDeviceType);
}
KillEverything ();
ExecuteXcodeCommand ("simctl", "shutdown " + simulator.udid, true, output_verbosity_level: 1, timeout: TimeSpan.FromMinutes (1));
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 ()));
}
}
}
void Initialize ()
@ -323,7 +318,7 @@ namespace xharness
info_plist.LoadWithoutNetworkAccess (Path.Combine (Path.GetDirectoryName (ProjectFile), info_plist_path));
bundle_identifier = info_plist.GetCFBundleIdentifier ();
switch (Harness.Target) {
switch (Target) {
case "ios-simulator-32":
mode = "sim32";
platform = "iPhoneSimulator";
@ -407,16 +402,31 @@ namespace xharness
return success ? 0 : 1;
}
bool SkipSimulatorSetup {
bool skip_simulator_setup;
public bool SkipSimulatorSetup {
get {
return !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("SKIP_SIMULATOR_SETUP"));
return skip_simulator_setup || !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("SKIP_SIMULATOR_SETUP"));
}
set {
skip_simulator_setup = 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 ()
{
HashSet<string> start_crashes = null;
string device_system_log = null;
LogFile device_system_log = null;
LogFile listener_log = null;
Initialize ();
@ -462,8 +472,8 @@ namespace xharness
default:
throw new NotImplementedException ();
}
listener.LogPath = Path.GetDirectoryName (Harness.LogFile);
listener.LogFile = Path.GetFileName (Harness.LogFile);
listener_log = Logs.Create (LogDirectory, string.Format ("test-{0:yyyyMMdd_HHmmss}.log", DateTime.Now), "Test log");
listener.LogPath = listener_log.Path;
listener.AutoExit = true;
listener.Address = System.Net.IPAddress.Any;
listener.Initialize ();
@ -471,9 +481,6 @@ namespace xharness
args.AppendFormat (" -argument=-app-arg:-hostport:{0}", listener.Port);
args.AppendFormat (" -setenv=NUNIT_HOSTPORT={0}", listener.Port);
if (File.Exists (Harness.LogFile))
File.Delete (Harness.LogFile);
bool? success = null;
bool timed_out = false;
@ -486,7 +493,7 @@ namespace xharness
args.Append (" --launchsim");
args.AppendFormat (" \"{0}\" ", launchAppPath);
args.Append (" --device=:v2:udid=").Append (simulator.udid).Append (" ");
args.Append (" --device=:v2:udid=").Append (simulator.UDID).Append (" ");
start_crashes = CreateCrashReportsSnapshot (true);
@ -572,6 +579,9 @@ namespace xharness
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 ();
} else {
@ -584,10 +594,10 @@ namespace xharness
AddDeviceName (args);
device_system_log = Harness.LogFile + ".device.log";
device_system_log = Logs.Create (LogDirectory, "device.log", "Device log");
var logdev = new DeviceLogCapturer () {
Harness = Harness,
LogPath = device_system_log,
LogPath = device_system_log.Path,
DeviceName = device_name,
};
logdev.StartCapture ();
@ -609,10 +619,10 @@ namespace xharness
logdev.StopCapture ();
// Upload the system log
if (File.Exists (device_system_log)) {
Harness.Log (1, "A capture of the device log is: {0}", device_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 (Harness.InWrench)
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", Path.GetFullPath (device_system_log));
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", device_system_log.Path);
}
}
@ -620,12 +630,12 @@ namespace xharness
// check the final status
var crashed = false;
if (File.Exists (Harness.LogFile)) {
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", Path.GetFullPath (Harness.LogFile));
var log = File.ReadAllText (Harness.LogFile);
if (File.Exists (listener_log.Path)) {
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", listener_log.Path);
var log = File.ReadAllText (listener_log.Path);
if (log.Contains ("Tests run")) {
var tests_run = string.Empty;
var log_lines = File.ReadAllLines (Harness.LogFile);
var log_lines = File.ReadAllLines (listener_log.Path);
var failed = false;
foreach (var line in log_lines) {
if (line.Contains ("Tests run:")) {
@ -672,25 +682,33 @@ namespace xharness
end_crashes.ExceptWith (start_crashes);
if (end_crashes.Count > 0) {
Harness.Log ("Found {0} new crash report(s)", end_crashes.Count);
if (!isSimulator) {
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);
}
} 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 HashSet<string> ();
var downloaded_crash_reports = new List<LogFile> ();
foreach (var file in end_crashes) {
var crash_report_target = Path.Combine (Path.GetDirectoryName (ProjectFile), Path.GetFileName (file));
if (ExecuteCommand (Harness.MlaunchPath, "--download-crash-report=" + file + " --download-crash-report-to=" + crash_report_target + " --sdkroot " + Harness.XcodeRoot)) {
Harness.Log ("Downloaded crash report {0} to {1}", file, crash_report_target);
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);
downloaded_crash_reports.Add (crash_report_target);
} else {
Harness.Log ("Could not download crash report {0}", file);
}
}
end_crashes = downloaded_crash_reports;
crash_reports = downloaded_crash_reports;
}
foreach (var cp in end_crashes) {
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", Path.GetFullPath (cp));
Harness.Log (" {0}", cp);
foreach (var cp in crash_reports) {
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", cp.Path);
Harness.Log (" {0}", cp.Path);
}
crash_report_search_done = true;
} else if (!crashed && !timed_out) {
@ -711,17 +729,27 @@ namespace xharness
if (isSimulator) {
foreach (var sim in simulators) {
// Upload the system log
if (File.Exists (sim.system_log)) {
Harness.Log (success.Value ? 1 : 0, "System log for the '{1}' simulator is: {0}", sim.system_log, sim.name);
if (Harness.InWrench) {
var syslog = Harness.LogFile + (sim == simulator ? ".system.log" : ".companion.system.log");
File.Copy (sim.system_log, syslog, true);
Harness.LogWrench ("@MonkeyWrench: AddFile: {0}", Path.GetFullPath (syslog));
}
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) {
Result = TestExecutingResult.TimedOut;
} else if (crashed) {
Result = TestExecutingResult.Crashed;
} else {
Result = TestExecutingResult.Failed;
}
return success.Value ? 0 : 1;
}
@ -776,6 +804,9 @@ namespace xharness
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);

Просмотреть файл

@ -13,12 +13,14 @@ namespace xharness
Configure,
Run,
Install,
Jenkins,
}
public class Harness
{
public HarnessAction Action { get; set; }
public int Verbosity { get; set; }
public LogFile HarnessLog { get; set; }
// This is the maccore/tests directory.
string root_directory;
@ -33,7 +35,7 @@ namespace xharness
}
}
public List<string> TestProjects { get; set; } = new List<string> ();
public List<TestProject> TestProjects { get; set; } = new List<TestProject> ();
public List<string> HardCodedTestProjects { get; set; } = new List<string> ();
public List<string> BclTests { get; set; } = new List<string> ();
@ -47,14 +49,18 @@ namespace xharness
public string WATCH_MONO_PATH { get; set; } // Use same name as in Makefiles, so that a grep finds it.
public string TVOS_MONO_PATH { get; set; } // Use same name as in Makefiles, so that a grep finds it.
public bool INCLUDE_WATCH { get; set; }
public string JENKINS_RESULTS_DIRECTORY { get; set; } // Use same name as in Makefiles, so that a grep finds it.
// Run
public string Target { get; set; }
public string SdkRoot { get; set; } = "/Applications/Xcode.app";
public string Configuration { get; set; } = "Debug";
public string LogFile { get; set; }
public string LogDirectory { get; set; } = Environment.CurrentDirectory;
public double Timeout { get; set; } = 10; // in minutes
public double LaunchTimeout { get; set; } // in minutes
public bool DryRun { get; set; } // Most things don't support this. If you need it somewhere, implement it!
public string JenkinsConfiguration { get; set; }
public Harness ()
{
@ -133,8 +139,8 @@ namespace xharness
//var fsharp_library_projects = new string[] { "fsharplibrary" };
//var bcl_suites = new string[] { "mscorlib", "System", "System.Core", "System.Data", "System.Net.Http", "System.Numerics", "System.Runtime.Serialization", "System.Transactions", "System.Web.Services", "System.Xml", "System.Xml.Linq", "Mono.Security", "System.ComponentModel.DataAnnotations", "System.Json", "System.ServiceModel.Web", "Mono.Data.Sqlite" };
foreach (var p in test_suites)
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".csproj")));
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, "introspection", "Mac", "introspection-mac.csproj")));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".csproj"))));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, "introspection", "Mac", "introspection-mac.csproj"))));
foreach (var p in hard_coded_test_suites)
HardCodedTestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".csproj")));
//foreach (var p in fsharp_test_suites)
@ -161,19 +167,20 @@ namespace xharness
var fsharp_library_projects = new string [] { "fsharplibrary" };
var bcl_suites = new string [] { "mscorlib", "System", "System.Core", "System.Data", "System.Net.Http", "System.Numerics", "System.Runtime.Serialization", "System.Transactions", "System.Web.Services", "System.Xml", "System.Xml.Linq", "Mono.Security", "System.ComponentModel.DataAnnotations", "System.Json", "System.ServiceModel.Web", "Mono.Data.Sqlite" };
foreach (var p in test_suites)
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".csproj")));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".csproj"))));
foreach (var p in fsharp_test_suites)
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".fsproj")));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".fsproj"))));
foreach (var p in library_projects)
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".csproj")));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".csproj")), false));
foreach (var p in fsharp_library_projects)
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".fsproj")));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, p + "/" + p + ".fsproj")), false));
foreach (var p in bcl_suites)
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, "bcl-test/" + p + "/" + p + ".csproj")));
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, "introspection", "iOS", "introspection-ios.csproj")));
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, "linker-ios", "dont link", "dont link.csproj")));
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, "linker-ios", "link all", "link all.csproj")));
TestProjects.Add (Path.GetFullPath (Path.Combine (RootDirectory, "linker-ios", "link sdk", "link sdk.csproj")));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, "bcl-test/" + p + "/" + p + ".csproj"))));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, "introspection", "iOS", "introspection-ios.csproj"))));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, "linker-ios", "dont link", "dont link.csproj"))));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, "linker-ios", "link all", "link all.csproj"))));
TestProjects.Add (new TestProject (Path.GetFullPath (Path.Combine (RootDirectory, "linker-ios", "link sdk", "link sdk.csproj"))));
BclTests.AddRange (bcl_suites);
WatchOSContainerTemplate = Path.GetFullPath (Path.Combine (RootDirectory, "watchos/Container"));
@ -186,6 +193,7 @@ namespace xharness
WATCH_MONO_PATH = make_config ["WATCH_MONO_PATH"];
TVOS_MONO_PATH = MONO_PATH;
INCLUDE_WATCH = make_config.ContainsKey ("INCLUDE_WATCH") && !string.IsNullOrEmpty (make_config ["INCLUDE_WATCH"]);
JENKINS_RESULTS_DIRECTORY = make_config ["JENKINS_RESULTS_DIRECTORY"];
}
static Dictionary<string, string> make_config = new Dictionary<string, string> ();
@ -250,7 +258,8 @@ namespace xharness
CreateBCLProjects ();
foreach (var file in TestProjects) {
foreach (var proj in TestProjects) {
var file = proj.Path;
if (!File.Exists (file))
throw new FileNotFoundException (file);
@ -303,7 +312,8 @@ namespace xharness
CreateBCLProjects ();
foreach (var file in TestProjects) {
foreach (var proj in TestProjects) {
var file = proj.Path;
if (!File.Exists (file))
throw new FileNotFoundException (file);
@ -347,7 +357,7 @@ namespace xharness
foreach (var project in TestProjects) {
var runner = new AppRunner () {
Harness = this,
ProjectFile = project,
ProjectFile = project.Path,
};
var rv = runner.Install ();
if (rv != 0)
@ -361,7 +371,7 @@ namespace xharness
foreach (var project in TestProjects) {
var runner = new AppRunner () {
Harness = this,
ProjectFile = project,
ProjectFile = project.Path,
};
var rv = runner.Run ();
if (rv != 0)
@ -375,6 +385,7 @@ namespace xharness
if (Verbosity < min_level)
return;
Console.WriteLine (message);
HarnessLog?.WriteLine (message);
}
public void Log (int min_level, string message, params object[] args)
@ -382,6 +393,7 @@ namespace xharness
if (Verbosity < min_level)
return;
Console.WriteLine (message, args);
HarnessLog?.WriteLine (message, args);
}
public void Log (string message)
@ -425,11 +437,25 @@ namespace xharness
return Run ();
case HarnessAction.Install:
return Install ();
case HarnessAction.Jenkins:
return Jenkins ();
default:
throw new NotImplementedException (Action.ToString ());
}
}
public int Jenkins ()
{
if (AutoConf)
AutoConfigure ();
var jenkins = new Jenkins ()
{
Harness = this,
};
return jenkins.Run ();
}
public void Save (XmlDocument doc, string path)
{
if (!File.Exists (path)) {

644
tests/xharness/Jenkins.cs Normal file
Просмотреть файл

@ -0,0 +1,644 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
namespace xharness
{
public class Jenkins
{
public Harness Harness;
public bool IncludeClassic;
public string LogDirectory {
get {
return Path.Combine (Harness.JENKINS_RESULTS_DIRECTORY, "tests");
}
}
public Simulators Simulators = new Simulators ();
List<TestTask> Tasks = new List<TestTask> ();
internal static Resource DesktopResource = new Resource { Name = "Desktop" };
async Task<IEnumerable<RunSimulatorTask>> CreateRunSimulatorTaskAsync (XBuildTask buildTask)
{
var runtasks = new List<RunSimulatorTask> ();
Simulators.Harness = Harness;
await Simulators.LoadAsync ();
var fn = Path.GetFileNameWithoutExtension (buildTask.ProjectFile);
if (fn.EndsWith ("-tvos", StringComparison.Ordinal)) {
var latesttvOSRuntime =
Simulators.SupportedRuntimes.
Where ((SimRuntime v) => v.Identifier.StartsWith ("com.apple.CoreSimulator.SimRuntime.tvOS-", StringComparison.Ordinal)).
OrderBy ((SimRuntime v) => v.Version).
Last ();
var tvOSDeviceType =
Simulators.SupportedDeviceTypes.
Where ((SimDeviceType v) => v.ProductFamilyId == "TV").
First ();
var device =
Simulators.AvailableDevices.
Where ((SimDevice v) => v.SimRuntime == latesttvOSRuntime.Identifier && v.SimDeviceType == tvOSDeviceType.Identifier).
First ();
runtasks.Add (new RunSimulatorTask (buildTask, device));
} else if (fn.EndsWith ("-watchos", StringComparison.Ordinal)) {
var latestwatchOSRuntime =
Simulators.SupportedRuntimes.
Where ((SimRuntime v) => v.Identifier.StartsWith ("com.apple.CoreSimulator.SimRuntime.watchOS-", StringComparison.Ordinal)).
OrderBy ((SimRuntime v) => v.Version).
Last ();
var watchOSDeviceType =
Simulators.SupportedDeviceTypes.
Where ((SimDeviceType v) => v.ProductFamilyId == "Watch").
First ();
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));
} else {
var latestiOSRuntime =
Simulators.SupportedRuntimes.
Where ((SimRuntime v) => v.Identifier.StartsWith ("com.apple.CoreSimulator.SimRuntime.iOS-", StringComparison.Ordinal)).
OrderBy ((SimRuntime v) => v.Version).
Last ();
if (fn.EndsWith ("-unified", StringComparison.Ordinal)) {
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-5").First ()));
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-6s").First ()));
} else {
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-4s").First ()));
}
}
return runtasks;
}
async Task PopulateTasksAsync ()
{
var runTasks = new List<RunSimulatorTask> ();
foreach (var project in Harness.TestProjects) {
if (!project.IsExecutableProject)
continue;
var build = new XBuildTask ()
{
Jenkins = this,
ProjectFile = project.Path,
ProjectConfiguration = "Debug",
ProjectPlatform = "iPhoneSimulator",
};
if (IncludeClassic)
runTasks.AddRange (await CreateRunSimulatorTaskAsync (build));
var suffixes = new string [] { "-unified", "-tvos", "-watchos" };
foreach (var suffix in suffixes) {
var derived = new XBuildTask ()
{
Jenkins = this,
ProjectFile = Path.Combine (Path.GetDirectoryName (project.Path), Path.GetFileNameWithoutExtension (project.Path) + suffix + Path.GetExtension (project.Path)),
ProjectConfiguration = build.ProjectConfiguration,
ProjectPlatform = build.ProjectPlatform,
};
runTasks.AddRange (await CreateRunSimulatorTaskAsync (derived));
}
}
foreach (var taskGroup in runTasks.GroupBy ((RunSimulatorTask task) => task.Device)) {
Tasks.Add (new AggregatedRunSimulatorTask (taskGroup)
{
Jenkins = this,
Device = taskGroup.Key,
});
}
}
public int Run ()
{
try {
Directory.CreateDirectory (LogDirectory);
Harness.HarnessLog = new LogFile ()
{
Description = "Harness log",
Path = Path.Combine (LogDirectory, "Harness.log"),
};
Task.Run (async () =>
{
await PopulateTasksAsync ();
}).Wait ();
var tasks = new List<Task> ();
foreach (var task in Tasks)
tasks.Add (task.RunAsync ());
Task.WaitAll (tasks.ToArray ());
GenerateReport ();
return Tasks.Any ((v) => v.ExecutionResult == TestExecutingResult.Failed || v.ExecutionResult == TestExecutingResult.Crashed) ? 1 : 0;
} catch (Exception ex) {
Harness.Log ("Unexpected exception: {0}", ex);
return 2;
}
}
string GetTestColor (IEnumerable<TestTask> tests)
{
if (tests.All ((v) => v.Succeeded))
return "green";
else if (tests.Any ((v) => v.Crashed))
return "maroon";
else if (tests.Any ((v) => v.Failed))
return "red";
else if (tests.Any ((v) => v.ExecutionResult == TestExecutingResult.TimedOut))
return "purple";
else if (tests.Any ((v) => v.ExecutionState == TestExecutionState.Running))
return "blue";
else if (tests.Any ((v) => v.ExecutionState == TestExecutionState.NotStarted))
return "gray";
else
return "black";
}
string GetTestColor (TestTask test)
{
switch (test.ExecutionState) {
case TestExecutionState.NotStarted:
return "gray";
case TestExecutionState.Running:
return "blue";
case TestExecutionState.Finished:
switch (test.ExecutionResult) {
case TestExecutingResult.Crashed:
return "maroon";
case TestExecutingResult.Failed:
return "red";
case TestExecutingResult.HarnessException:
return "yellow";
case TestExecutingResult.Succeeded:
return "green";
case TestExecutingResult.TimedOut:
return "purple";
case TestExecutingResult.None: // shouldn't happen
default:
return "pink";
}
default:
return "pink";
}
}
object report_lock = new object ();
public void GenerateReport ()
{
var id_counter = 0;
lock (report_lock) {
var report = Path.Combine (LogDirectory, "index.html");
if (File.Exists (report))
File.Delete (report);
using (var writer = new StreamWriter (report)) {
writer.WriteLine ("<!DOCTYPE html>");
writer.WriteLine ("<html>");
writer.WriteLine ("<title>Test results</title>");
writer.WriteLine (@"<script type='text/javascript'>
function toggleLogVisibility (logName)
{
var button = document.getElementById ('button_' + logName);
var logs = document.getElementById ('logs_' + logName);
if (logs.style.display == 'none') {
logs.style.display = 'block';
button.innerText = 'Hide details';
} else {
logs.style.display = 'none';
button.innerText = 'Show details';
}
}
</script>");
writer.WriteLine ("<body>");
writer.WriteLine ("<h1>Test results</h1>");
writer.WriteLine ("<a href='{0}'>Harness log</a> <br />", Harness.HarnessLog.Path.Substring (LogDirectory.Length + 1));
var allSimulatorTasks = new List<RunSimulatorTask> ();
foreach (var task in Tasks) {
var aggregated = task as AggregatedRunSimulatorTask;
if (aggregated != null) {
allSimulatorTasks.AddRange (aggregated.Tasks);
} else {
task.WriteReport (writer);
}
}
var failedTests = allSimulatorTasks.Where ((v) => v.ExecutionState == TestExecutionState.Finished && !v.Succeeded);
var stillRunning = allSimulatorTasks.Any ((v) => v.ExecutionState != TestExecutionState.Finished);
if (failedTests.Count () == 0) {
if (stillRunning) {
writer.WriteLine ("<h2>All tests passed (but still running tests)</h2>");
} else {
writer.WriteLine ("<h2 style='color: green'>All tests passed</h2>");
}
} else {
writer.WriteLine ("<h2 style='color: red'>{0} tests failed</h2>", failedTests.Count ());
foreach (var group in failedTests.GroupBy ((v) => v.TestName))
writer.WriteLine ("<a href='#test_{2}'>{0}</a> ({1})<br />", group.Key, string.Join (", ", ((IEnumerable<RunSimulatorTask>) group).Select ((v) => string.Format ("<span style='color: {0}'>{1}</span>", GetTestColor (v), v.Mode)).ToArray ()), group.Key.Replace (' ', '-'));
}
foreach (var group in allSimulatorTasks.GroupBy ((RunSimulatorTask v) => v.TestName)) {
writer.WriteLine ("<h2 id='test_{1}' style='color: {2}'>{0}</h2> <br />", group.Key, group.Key.Replace (' ', '-'), GetTestColor (group));
foreach (var test in group) {
string state;
if (test.ExecutionState == TestExecutionState.Finished) {
state = test.ExecutionResult.ToString ();
} else {
state = test.ExecutionState.ToString ();
}
if (test.ExecutionState != TestExecutionState.NotStarted) {
var log_id = id_counter++;
writer.WriteLine ("{0} (<span style='color: {3}'>{1}</span>) <a id='button_{2}' href=\"javascript: toggleLogVisibility ('{2}');\">Show details</a><br />", test.Mode, state, log_id, GetTestColor (test));
writer.WriteLine ("<div id='logs_{0}' style='display: none; padding-bottom: 10px; padding-top: 10px; padding-left: 20px;'>", log_id);
writer.WriteLine ("Duration: {0} <br />", test.Duration);
if (test.Logs.Count > 0) {
foreach (var log in test.Logs) {
writer.WriteLine ("<a href='{0}' type='text/plain'>{1}</a><br />", log.Path.Substring (LogDirectory.Length + 1), log.Description);
if (log.Description == "Test log") {
var summary = string.Empty;
try {
foreach (var line in File.ReadAllLines (log.Path)) {
if (line.StartsWith ("Tests run:", StringComparison.Ordinal)) {
summary = line;
} else if (line.Trim ().StartsWith ("[FAIL]", StringComparison.Ordinal)) {
writer.WriteLine ("<span style='padding-left: 20px;'>{0}</span><br />", line.Trim ());
}
}
if (summary != null)
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));
}
} else if (log.Description == "Build log") {
var errors = new HashSet<string> ();
try {
foreach (var line in File.ReadAllLines (log.Path)) {
var ln = line.Trim ();
if (ln.Contains (": error"))
errors.Add (ln);
}
foreach (var error in errors)
writer.WriteLine ("<span style='padding-left: 15\tpx;'>{0}</span> <br />", error);
} catch (Exception ex) {
writer.WriteLine ("<span style='padding-left: 15px;'>Could not parse log file: {0}</span><br />", System.Web.HttpUtility.HtmlEncode (ex.Message));
}
}
}
} else {
writer.WriteLine ("No logs<br />");
}
writer.WriteLine ("</div>");
} else {
writer.WriteLine ("{0} ({1}) <br />", test.Mode, state);
}
}
}
writer.WriteLine ("</body>");
writer.WriteLine ("</html>");
}
Harness.Log (2, "Generated report: {0}", report);
}
}
}
abstract class TestTask
{
public Jenkins Jenkins;
public Harness Harness { get { return Jenkins.Harness; } }
public string ProjectFile;
public string ProjectConfiguration;
public string ProjectPlatform;
Stopwatch duration = new Stopwatch ();
public TimeSpan Duration {
get {
return duration.Elapsed;
}
}
public TestExecutionState ExecutionState { get; protected set; }
public TestExecutingResult ExecutionResult { get; protected set; }
public bool Succeeded { get { return ExecutionResult == TestExecutingResult.Succeeded; } }
public bool Failed { get { return ExecutionResult == TestExecutingResult.Failed; } }
public bool Crashed { get { return ExecutionResult == TestExecutingResult.Crashed; } }
public virtual string Mode { get; set; }
public string TestName {
get {
var rv = Path.GetFileNameWithoutExtension (ProjectFile);
if (rv.EndsWith ("-watchos", StringComparison.Ordinal)) {
return rv.Substring (0, rv.Length - 8);
} else if (rv.EndsWith ("-tvos", StringComparison.Ordinal)) {
return rv.Substring (0, rv.Length - 5);
} else if (rv.EndsWith ("-unified", StringComparison.Ordinal)) {
return rv.Substring (0, rv.Length - 8);
} else {
return rv;
}
}
}
public TestPlatform Platform {
get {
var fn = Path.GetFileNameWithoutExtension (ProjectFile);
if (fn.EndsWith ("-watchos", StringComparison.Ordinal)) {
return TestPlatform.watchOS;
} else if (fn.EndsWith ("-tvos", StringComparison.Ordinal)) {
return TestPlatform.tvOS;
} else if (fn.EndsWith ("-unified", StringComparison.Ordinal)) {
return TestPlatform.iOS_Unified;
} else {
return TestPlatform.iOS_Classic;
}
}
}
public LogFiles Logs = new LogFiles ();
public List<Resource> Resources = new List<Resource> ();
public string LogDirectory {
get {
return Path.Combine (Jenkins.LogDirectory, TestName);
}
}
public async Task RunAsync ()
{
if (ExecutionState == TestExecutionState.Finished)
return;
ExecutionState = TestExecutionState.Running;
Jenkins.GenerateReport ();
duration.Start ();
await ExecuteAsync ();
duration.Stop ();
ExecutionState = TestExecutionState.Finished;
if (ExecutionResult == TestExecutingResult.None)
throw new Exception ("Result not set!");
Jenkins.GenerateReport ();
}
protected abstract Task ExecuteAsync ();
public abstract void WriteReport (StreamWriter writer);
}
class XBuildTask : TestTask
{
public override string Mode {
get { return Platform.ToString (); }
set { throw new NotSupportedException (); }
}
protected override async Task ExecuteAsync ()
{
using (var xbuild = new Process ()) {
xbuild.StartInfo.FileName = "xbuild";
xbuild.StartInfo.Arguments = $"/verbosity:diagnostic /p:Platform={ProjectPlatform} /p:Configuration={ProjectConfiguration} {Harness.Quote (ProjectFile)}";
Harness.Log ("Building {0} ({1})", TestName, Mode);
var log = Logs.Create (LogDirectory, "build-" + Platform + ".txt", "Build log");
log.WriteLine ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
if (Harness.DryRun) {
Harness.Log ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
} else {
await xbuild.RunAsync (log.Path, true);
ExecutionResult = xbuild.ExitCode == 0 ? TestExecutingResult.Succeeded : TestExecutingResult.Failed;
}
Harness.Log ("Built {0} ({1})", TestName, Mode);
}
}
public override void WriteReport (StreamWriter writer)
{
throw new NotImplementedException ();
}
}
class RunSimulatorTask : TestTask
{
public SimDevice Device;
public XBuildTask BuildTask;
public bool SkipSetupAndCleanup;
public string AppRunnerTarget;
public override string Mode {
get {
switch (Platform) {
case TestPlatform.tvOS:
case TestPlatform.watchOS:
return Platform.ToString ();
case TestPlatform.iOS_Classic:
return "iOS Classic";
case TestPlatform.iOS_Unified:
if (Jenkins.Simulators.SupportedDeviceTypes.Find ((SimDeviceType v) => v.Identifier == Device.SimDeviceType).Supports64Bits) {
return "iOS Unified 32-bits";
} else {
return "iOS Unified 64-bits";
}
default:
throw new NotImplementedException ();
}
}
set { throw new NotSupportedException (); }
}
public RunSimulatorTask (XBuildTask build_task, SimDevice device)
{
BuildTask = build_task;
Device = device;
Jenkins = build_task.Jenkins;
ProjectFile = build_task.ProjectFile;
var project = Path.GetFileNameWithoutExtension (ProjectFile);
if (project.EndsWith ("-tvos", StringComparison.Ordinal)) {
AppRunnerTarget = "tvos-simulator";
} else if (project.EndsWith ("-watchos", StringComparison.Ordinal)) {
AppRunnerTarget = "watchos-simulator";
} else {
AppRunnerTarget = "ios-simulator";
}
}
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);
await BuildTask.RunAsync ();
Logs.AddRange (BuildTask.Logs);
if (!BuildTask.Succeeded) {
ExecutionResult = TestExecutingResult.Failed;
return;
}
if (Harness.DryRun) {
Harness.Log ("<running app in simulator>");
} else {
var runner = new AppRunner ()
{
Harness = Harness,
ProjectFile = ProjectFile,
SkipSimulatorSetup = SkipSetupAndCleanup,
SkipSimulatorCleanup = SkipSetupAndCleanup,
Target = AppRunnerTarget,
LogDirectory = LogDirectory,
};
runner.Simulators = new SimDevice [] { Device };
try {
runner.Run ();
ExecutionResult = runner.Result;
} catch (Exception ex) {
Harness.Log ("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);
}
public override void WriteReport (StreamWriter writer)
{
writer.WriteLine ("<h2>{0}</h2>", Mode);
writer.WriteLine ("Build: {0}", BuildTask.ExecutionResult);
if (!BuildTask.Failed) {
writer.WriteLine ("Run: {0}", ExecutionResult);
}
}
}
// This class groups simulator run tasks according to the
// simulator they'll run from, so that we minimize switching
// between different simulators (which is slow).
class AggregatedRunSimulatorTask : TestTask
{
public SimDevice Device;
public IEnumerable<RunSimulatorTask> Tasks;
public AggregatedRunSimulatorTask (IEnumerable<RunSimulatorTask> tasks)
{
this.Tasks = tasks;
}
protected override async Task ExecuteAsync ()
{
using (var desktop = await Jenkins.DesktopResource.AcquireAsync ()) {
Harness.Log ("Preparing simulator: {0}", Device.Name);
bool first = true;
foreach (var task in Tasks) {
task.SkipSetupAndCleanup = !first;
first = false;
await task.RunAsync ();
}
}
ExecutionResult = Tasks.Any ((v) => !v.Succeeded) ? TestExecutingResult.Failed : TestExecutingResult.Succeeded;
}
public override void WriteReport (StreamWriter writer)
{
foreach (var task in Tasks)
task.WriteReport (writer);
}
}
// This is a very simple class to manage the general concept of 'resource'.
// Performance isn't important, so this is very simple.
// Currently it's only used to make sure everything that happens on the desktop
// is serialized (Jenkins.DesktopResource), but in the future the idea is to
// make each connected device a separate resource, which will make it possible
// to run tests in parallel across devices (and at the same time use the desktop
// to build the next test project).
class Resource
{
public string Name;
ConcurrentQueue<TaskCompletionSource<IDisposable>> queue = new ConcurrentQueue<TaskCompletionSource<IDisposable>> ();
int users;
int max_users = 1;
public Task<IDisposable> AcquireAsync ()
{
lock (queue) {
if (users == max_users) {
var tcs = new TaskCompletionSource<IDisposable> (new AcquiredResource (this));
queue.Enqueue (tcs);
return tcs.Task;
} else {
users++;
return Task.FromResult<IDisposable> (new AcquiredResource (this));
}
}
}
void Release ()
{
TaskCompletionSource<IDisposable> tcs;
lock (queue) {
users--;
if (queue.TryDequeue (out tcs))
tcs.SetResult ((IDisposable) tcs.Task.AsyncState);
}
}
class AcquiredResource : IDisposable
{
Resource resource;
public AcquiredResource (Resource resource)
{
this.resource = resource;
}
void IDisposable.Dispose ()
{
resource.Release ();
}
}
}
public enum TestPlatform
{
iOS_Classic,
iOS_Unified,
tvOS,
watchOS,
}
public enum TestExecutionState
{
NotStarted,
Running,
Finished,
}
public enum TestExecutingResult
{
None,
Succeeded,
Crashed,
Failed,
TimedOut,
HarnessException,
}
}

46
tests/xharness/LogFile.cs Normal file
Просмотреть файл

@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.IO;
namespace xharness
{
public class LogFile
{
public string Description;
public string Path;
public void WriteLine (string value)
{
lock (this)
File.AppendAllText (Path, value + "\n");
}
public void WriteLine (string format, params object [] args)
{
lock (this)
File.AppendAllText (Path, 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;
}
}
}

Просмотреть файл

@ -369,15 +369,15 @@ namespace xharness
// exec sim project target
if (target.IsMultiArchitecture) {
writer.WriteTarget ("exec{0}-sim64-{1}", "$(UNIT_SERVER)", make_escaped_suffix, make_escaped_name);
writer.WriteLine ("\t$(Q) $(SYSTEM_MONO) --debug xharness/xharness.exe $(XHARNESS_VERBOSITY) --run \"{0}\" --target {1}-simulator-64 --sdkroot $(XCODE_DEVELOPER_ROOT) --logfile \"$@.log\" --configuration $(CONFIG)", target.ProjectPath, target.Platform);
writer.WriteLine ("\t$(Q) $(SYSTEM_MONO) --debug xharness/xharness.exe $(XHARNESS_VERBOSITY) --run \"{0}\" --target {1}-simulator-64 --sdkroot $(XCODE_DEVELOPER_ROOT) --logdirectory \"$(abspath $(CURDIR))\\logs\\$@\" --configuration $(CONFIG)", target.ProjectPath, target.Platform);
writer.WriteLine ();
writer.WriteTarget ("exec{0}-sim32-{1}", "$(UNIT_SERVER)", make_escaped_suffix, make_escaped_name);
writer.WriteLine ("\t$(Q) $(SYSTEM_MONO) --debug xharness/xharness.exe $(XHARNESS_VERBOSITY) --run \"{0}\" --target {1}-simulator-32 --sdkroot $(XCODE_DEVELOPER_ROOT) --logfile \"$@.log\" --configuration $(CONFIG)", target.ProjectPath, target.Platform);
writer.WriteLine ("\t$(Q) $(SYSTEM_MONO) --debug xharness/xharness.exe $(XHARNESS_VERBOSITY) --run \"{0}\" --target {1}-simulator-32 --sdkroot $(XCODE_DEVELOPER_ROOT) --logdirectory \"$(abspath $(CURDIR))\\logs\\$@\" --configuration $(CONFIG)", target.ProjectPath, target.Platform);
writer.WriteLine ();
} else {
writer.WriteTarget ("exec{0}-sim{2}-{1}", "$(UNIT_SERVER)", make_escaped_suffix, make_escaped_name, target.MakefileWhereSuffix);
writer.WriteLine ("\t$(Q) $(SYSTEM_MONO) --debug xharness/xharness.exe $(XHARNESS_VERBOSITY) --run \"{0}\" --target {1}-simulator --sdkroot $(XCODE_DEVELOPER_ROOT) --logfile \"$@.log\" --configuration $(CONFIG)", target.ProjectPath, target.Platform);
writer.WriteLine ("\t$(Q) $(SYSTEM_MONO) --debug xharness/xharness.exe $(XHARNESS_VERBOSITY) --run \"{0}\" --target {1}-simulator --sdkroot $(XCODE_DEVELOPER_ROOT) --logdirectory \"$(abspath $(CURDIR))\\logs\\$@\" --configuration $(CONFIG)", target.ProjectPath, target.Platform);
writer.WriteLine ();
}
@ -456,7 +456,7 @@ namespace xharness
}
writer.WriteTarget ("exec{0}-dev{2}-{1}", "xharness/xharness.exe", make_escaped_suffix, make_escaped_name, target.MakefileWhereSuffix);
writer.WriteLine ("\t$(Q) rm -f \"$@.log\"");
writer.WriteLine ("\t$(Q) $(SYSTEM_MONO) --debug xharness/xharness.exe $(XHARNESS_VERBOSITY) --run \"{0}\" --target {1}-device --sdkroot $(XCODE_DEVELOPER_ROOT) --logfile \"$@.log\" --configuration $(CONFIG)", target.ProjectPath, target.Platform);
writer.WriteLine ("\t$(Q) $(SYSTEM_MONO) --debug xharness/xharness.exe $(XHARNESS_VERBOSITY) --run \"{0}\" --target {1}-device --sdkroot $(XCODE_DEVELOPER_ROOT) --configuration $(CONFIG)", target.ProjectPath, target.Platform);
writer.WriteLine ();
if (target is ClassicTarget) {

Просмотреть файл

@ -0,0 +1,69 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace xharness
{
public static class Process_Extensions
{
public static async Task RunAsync (this Process process, LogFile log)
{
using (var stream = new StreamWriter (log.Path, false))
await RunAsync (process, stream, stream);
}
public static async Task RunAsync (this Process process, string outputFile, bool append)
{
Directory.CreateDirectory (Path.GetDirectoryName (outputFile));
using (var stream = new StreamWriter (outputFile, append))
await RunAsync (process, stream, stream);
}
public static async Task RunAsync (this Process process, StreamWriter StdoutStream, StreamWriter StderrStream)
{
var stdout_completion = new TaskCompletionSource<bool> ();
var stderr_completion = new TaskCompletionSource<bool> ();
var exit_completion = new TaskCompletionSource<bool> ();
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) =>
{
if (e.Data != null) {
StdoutStream.WriteLine (e.Data);
} else {
stdout_completion.SetResult (true);
}
};
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) =>
{
if (e.Data != null) {
lock (StderrStream)
StderrStream.WriteLine (e.Data);
} else {
stderr_completion.SetResult (true);
}
};
process.Start ();
process.BeginErrorReadLine ();
process.BeginOutputReadLine ();
new Thread (() =>
{
process.WaitForExit ();
exit_completion.SetResult (true);
}) {
IsBackground = true,
}.Start ();
await Task.WhenAll (stderr_completion.Task, stdout_completion.Task, exit_completion.Task);
}
}
}

Просмотреть файл

@ -19,27 +19,35 @@ namespace xharness
{ "configure", "Creates project files and makefiles.", (v) => harness.Action = HarnessAction.Configure },
{ "autoconf", "Automatically decide what to configure.", (v) => harness.AutoConf = true },
{ "rootdir=", "The root directory for the tests.", (v) => harness.RootDirectory = v },
{ "project=", "Add a project file to process. This can be specified multiple times.", (v) => harness.TestProjects.Add (v) },
{ "project=", "Add a project file to process. This can be specified multiple times.", (v) => harness.TestProjects.Add (new TestProject (v)) },
{ "watchos-container-template=", "The directory to use as a template for a watchos container app.", (v) => harness.WatchOSContainerTemplate = v },
{ "watchos-app-template=", "The directory to use as a template for a watchos app.", (v) => harness.WatchOSAppTemplate = v },
// Run
{ "run=", "Executes a project.", (v) =>
{
harness.Action = HarnessAction.Run;
harness.TestProjects.Add (v);
harness.TestProjects.Add (new TestProject (v));
}
},
{ "install=", "Installs a project.", (v) =>
{
harness.Action = HarnessAction.Install;
harness.TestProjects.Add (v);
harness.TestProjects.Add (new TestProject (v));
}
},
{ "sdkroot=", "Where Xcode is", (v) => harness.SdkRoot = v },
{ "target=", "Where to run the project ([ios|watchos|tvos]-[device|simulator|simulator-32|simulator-64]).", (v) => harness.Target = v },
{ "configuration=", "Which configuration to run (defaults to Debug).", (v) => harness.Configuration = v },
{ "logdirectory=", "Where to store logs.", (v) => harness.LogDirectory = v },
{ "logfile=", "Where to store the log.", (v) => harness.LogFile = v },
{ "timeout=", "Timeout for a test run (in minutes). Default is 10 minutes.", (v) => harness.Timeout = double.Parse (v) },
{ "jenkins:", "Execute test run for jenkins.", (v) =>
{
harness.JenkinsConfiguration = v;
harness.Action = HarnessAction.Jenkins;
}
},
{ "dry-run", "Only print what would be done.", (v) => harness.DryRun = true },
};
showHelp = () => {

Просмотреть файл

@ -16,19 +16,12 @@ namespace xharness
public IPAddress Address { get; set; }
public int Port { get; set; }
public string LogPath { get; set; }
public string LogFile { get; set; } = DateTime.UtcNow.Ticks.ToString () + ".log";
public bool AutoExit { get; set; }
public abstract void Initialize ();
protected abstract void Start ();
protected abstract void Stop ();
public string LogFilePath {
get {
return Path.Combine (LogPath, LogFile);
}
}
public FileStream OutputStream {
get {
return output_stream;
@ -37,7 +30,7 @@ namespace xharness
protected void Connected (string remote)
{
Console.WriteLine ("Connection from {0} saving logs to {1}", remote, LogFilePath);
Console.WriteLine ("Connection from {0} saving logs to {1}", remote, LogPath);
connected.Set ();
if (output_stream != null) {
@ -45,7 +38,9 @@ namespace xharness
output_stream.Dispose ();
}
var fs = new FileStream (LogFilePath, FileMode.Create, FileAccess.Write, FileShare.Read);
Directory.CreateDirectory (Path.GetDirectoryName (LogPath));
var fs = new FileStream (LogPath, FileMode.Create, FileAccess.Write, FileShare.Read);
// 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);

Просмотреть файл

@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Xml;
namespace xharness
{
public class Simulators
{
public Harness Harness;
public List<SimRuntime> SupportedRuntimes = new List<SimRuntime> ();
public List<SimDeviceType> SupportedDeviceTypes = new List<SimDeviceType> ();
public List<SimDevice> AvailableDevices = new List<SimDevice> ();
public List<SimDevicePair> AvailableDevicePairs = new List<SimDevicePair> ();
public async Task LoadAsync ()
{
if (SupportedRuntimes.Count > 0)
return;
var tmpfile = Path.GetTempFileName ();
try {
using (var process = new Process ()) {
process.StartInfo.FileName = Harness.MlaunchPath;
process.StartInfo.Arguments = string.Format ("--sdkroot {0} --listsim {1}", Harness.XcodeRoot, tmpfile);
await process.RunAsync ("/dev/null", false);
var simulator_data = new XmlDocument ();
simulator_data.LoadWithoutNetworkAccess (tmpfile);
foreach (XmlNode sim in simulator_data.SelectNodes ("/MTouch/Simulator/SupportedRuntimes/SimRuntime")) {
SupportedRuntimes.Add (new SimRuntime ()
{
Name = sim.SelectSingleNode ("Name").InnerText,
Identifier = sim.SelectSingleNode ("Identifier").InnerText,
Version = long.Parse (sim.SelectSingleNode ("Version").InnerText),
});
}
foreach (XmlNode sim in simulator_data.SelectNodes ("/MTouch/Simulator/SupportedDeviceTypes/SimDeviceType")) {
SupportedDeviceTypes.Add (new SimDeviceType ()
{
Name = sim.SelectSingleNode ("Name").InnerText,
Identifier = sim.SelectSingleNode ("Identifier").InnerText,
ProductFamilyId = sim.SelectSingleNode ("ProductFamilyId").InnerText,
MinRuntimeVersion = long.Parse (sim.SelectSingleNode ("MinRuntimeVersion").InnerText),
MaxRuntimeVersion = long.Parse (sim.SelectSingleNode ("MaxRuntimeVersion").InnerText),
Supports64Bits = bool.Parse (sim.SelectSingleNode ("Supports64Bits").InnerText),
});
}
foreach (XmlNode sim in simulator_data.SelectNodes ("/MTouch/Simulator/AvailableDevices/SimDevice")) {
AvailableDevices.Add (new SimDevice ()
{
Name = sim.Attributes ["Name"].Value,
UDID = sim.Attributes ["UDID"].Value,
SimRuntime = sim.SelectSingleNode ("SimRuntime").InnerText,
SimDeviceType = sim.SelectSingleNode ("SimDeviceType").InnerText,
DataPath = sim.SelectSingleNode ("DataPath").InnerText,
LogPath = sim.SelectSingleNode ("LogPath").InnerText,
});
}
foreach (XmlNode sim in simulator_data.SelectNodes ("/MTouch/Simulator/AvailableDevicePairs/SimDevicePair")) {
AvailableDevicePairs.Add (new SimDevicePair ()
{
UDID = sim.Attributes ["UDID"].Value,
Companion = sim.SelectSingleNode ("Companion").InnerText,
Gizmo = sim.SelectSingleNode ("Gizmo").InnerText,
});
}
}
} finally {
File.Delete (tmpfile);
}
}
}
public class SimRuntime
{
public string Name;
public string Identifier;
public long Version;
}
public class SimDeviceType
{
public string Name;
public string Identifier;
public string ProductFamilyId;
public long MinRuntimeVersion;
public long MaxRuntimeVersion;
public bool Supports64Bits;
}
public class SimDevice
{
public string UDID;
public string Name;
public string SimRuntime;
public string SimDeviceType;
public string DataPath;
public string LogPath;
public string SystemLog { get { return Path.Combine (LogPath, "system.log"); } }
}
public class SimDevicePair
{
public string UDID;
public string Companion;
public string Gizmo;
}
public class Devices
{
public Harness Harness;
public List<Device> ConnectedDevices = new List<Device> ();
public async Task LoadAsync ()
{
if (ConnectedDevices.Count > 0)
return;
var tmpfile = Path.GetTempFileName ();
try {
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);
var doc = new XmlDocument ();
doc.LoadWithoutNetworkAccess (tmpfile);
foreach (XmlNode dev in doc.SelectNodes ("/MTouch/Device")) {
ConnectedDevices.Add (new Device ()
{
DeviceIdentifier = dev.SelectSingleNode ("DeviceIdentifier")?.InnerText,
DeviceClass = dev.SelectSingleNode ("DeviceClass")?.InnerText,
CompanionIdentifier = dev.SelectSingleNode ("CompanionIdentifier")?.InnerText,
Name = dev.SelectSingleNode ("Name")?.InnerText,
});
}
}
} finally {
File.Delete (tmpfile);
}
}
}
public class Device
{
public string DeviceIdentifier;
public string DeviceClass;
public string CompanionIdentifier;
public string Name;
}
}

Просмотреть файл

@ -0,0 +1,22 @@
using System;
namespace xharness
{
public class TestProject
{
public string Path;
public bool IsExecutableProject;
public TestProject ()
{
}
public TestProject (string path, bool isExecutableProject = true)
{
Path = path;
IsExecutableProject = isExecutableProject;
}
}
}

Просмотреть файл

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace xharness
{

Просмотреть файл

@ -20,7 +20,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<Commandlineparameters>--verbose --configure --autoconf --rootdir ../</Commandlineparameters>
<Commandlineparameters>--verbose --jenkins --autoconf --rootdir ../ --sdkroot /Applications/Xcode73.app</Commandlineparameters>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
@ -33,6 +33,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
@ -63,6 +64,11 @@
<Compile Include="MacTarget.cs" />
<Compile Include="SimpleHttpListener.cs" />
<Compile Include="SimpleListener.cs" />
<Compile Include="Jenkins.cs" />
<Compile Include="Process_Extensions.cs" />
<Compile Include="Simulators.cs" />
<Compile Include="TestProject.cs" />
<Compile Include="LogFile.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>