[tests] Implement support in xharness for running the simulator tests grouped by simulator, and write out an html report.
This commit is contained in:
Родитель
68717b4100
Коммит
d135612f59
|
@ -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)) {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
Загрузка…
Ссылка в новой задаче