[xharness] Unify simulator selection code between Jenkins and Wrench and automatically create device pairs if none applicable is found. (#1202)

* [xharness] Don't crash if we can't find a simulator.

* [xharness] Create a device pair if none applicable is found.

* [xharness] Use an enum instead of string values for the target.

* [xharness] Unify the simulator selection code between Jenkins and Wrench.
This commit is contained in:
Rolf Bjarne Kvinge 2016-11-16 15:23:11 +01:00 коммит произвёл GitHub
Родитель a2570c6b84
Коммит 15b1204874
7 изменённых файлов: 254 добавлений и 152 удалений

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

@ -13,6 +13,19 @@ using System.Xml.Xsl;
namespace xharness
{
public enum AppRunnerTarget
{
None,
Simulator_iOS,
Simulator_iOS32,
Simulator_iOS64,
Simulator_tvOS,
Simulator_watchOS,
Device_iOS,
Device_tvOS,
Device_watchOS,
}
public class AppRunner
{
public Harness Harness;
@ -35,9 +48,9 @@ namespace xharness
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; }
AppRunnerTarget target;
public AppRunnerTarget Target {
get { return target == AppRunnerTarget.None ? Harness.Target : target; }
set { target = value; }
}
@ -68,97 +81,18 @@ namespace xharness
string mode;
void FindSimulator ()
async Task<bool> FindSimulatorAsync ()
{
if (simulators != null)
return;
return true;
string [] simulator_devicetypes;
string simulator_runtime;
switch (Target) {
case "ios-simulator-32":
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.iPhone-5" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
break;
case "ios-simulator-64":
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.iPhone-5s" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
break;
case "ios-simulator":
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.iPhone-5" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
break;
case "tvos-simulator":
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.tvOS-" + Xamarin.SdkVersions.TVOS.Replace ('.', '-');
break;
case "watchos-simulator":
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm", "com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-38mm" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.watchOS-" + Xamarin.SdkVersions.WatchOS.Replace ('.', '-');
break;
default:
throw new Exception (string.Format ("Unknown simulator target: {0}", Harness.Target));
}
var sims = new Simulators () {
Harness = Harness,
};
Task.Run (async () =>
{
await sims.LoadAsync (Logs.CreateStream (LogDirectory, "simulator-list.log", "Simulator list"));
}).Wait ();
await sims.LoadAsync (Logs.CreateStream (LogDirectory, "simulator-list.log", "Simulator list"));
simulators = await sims.FindAsync (target, main_log);
var devices = sims.AvailableDevices.Where ((SimDevice v) =>
{
if (v.SimRuntime != simulator_runtime)
return false;
if (!simulator_devicetypes.Contains (v.SimDeviceType))
return false;
if (Target == "watchos-simulator")
return sims.AvailableDevicePairs.Any ((SimDevicePair pair) => pair.Companion == v.UDID || pair.Gizmo == v.UDID);
return true;
});
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) {
if (candidate == null)
throw new Exception ($"Could not find simulator for runtime={simulator_runtime} and device type={string.Join (";", simulator_devicetypes)}.");
simulators = new SimDevice [] { candidate };
}
if (simulators == null)
throw new Exception ("Could not find simulator");
main_log.WriteLine ("Found simulator: {0} {1}", simulators [0].Name, simulators [0].UDID);
if (simulators.Length > 1)
main_log.WriteLine ("Found companion simulator: {0} {1}", simulators [1].Name, simulators [1].UDID);
return simulators != null;
}
void FindDevice ()
@ -231,42 +165,42 @@ namespace xharness
bundle_identifier = info_plist.GetCFBundleIdentifier ();
switch (Target) {
case "ios-simulator-32":
case AppRunnerTarget.Simulator_iOS32:
mode = "sim32";
platform = "iPhoneSimulator";
isSimulator = true;
break;
case "ios-simulator-64":
case AppRunnerTarget.Simulator_iOS64:
mode = "sim64";
platform = "iPhoneSimulator";
isSimulator = true;
break;
case "ios-simulator":
case AppRunnerTarget.Simulator_iOS:
mode = "classic";
platform = "iPhoneSimulator";
isSimulator = true;
break;
case "ios-device":
case AppRunnerTarget.Device_iOS:
mode = "ios";
platform = "iPhone";
isSimulator = false;
break;
case "tvos-simulator":
case AppRunnerTarget.Simulator_tvOS:
mode = "tvos";
platform = "iPhoneSimulator";
isSimulator = true;
break;
case "tvos-device":
case AppRunnerTarget.Device_tvOS:
mode = "tvos";
platform = "iPhone";
isSimulator = false;
break;
case "watchos-simulator":
case AppRunnerTarget.Simulator_watchOS:
mode = "watchos";
platform = "iPhoneSimulator";
isSimulator = true;
break;
case "watchos-device":
case AppRunnerTarget.Device_watchOS:
mode = "watchos";
platform = "iPhone";
isSimulator = false;
@ -369,7 +303,7 @@ namespace xharness
}
// update the information of the main node to add information about the mode and the test that is excuted. This will later create
// nicer reports in jenkins
mainResultNode.Attributes["name"].Value = Target;
mainResultNode.Attributes["name"].Value = Target.AsString ();
// store a clean version of the logs, later this will be used by the bots to show results in github/web
var path = listener_log.FullPath;
path = path.Replace (".log", ".xml");
@ -532,7 +466,8 @@ namespace xharness
bool launch_failure = false;
if (isSimulator) {
FindSimulator ();
if (!await FindSimulatorAsync ())
return 1;
if (mode != "watchos") {
var stderr_tty = Marshal.PtrToStringAuto (ttyname (2));

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

@ -0,0 +1,59 @@
using System;
namespace xharness
{
public static class Extensions
{
public static string AsString (this AppRunnerTarget @this)
{
switch (@this) {
case AppRunnerTarget.None:
return null;
case AppRunnerTarget.Device_iOS:
return "ios-device";
case AppRunnerTarget.Device_tvOS:
return "tvos-device";
case AppRunnerTarget.Device_watchOS:
return "watchos-device";
case AppRunnerTarget.Simulator_iOS:
return "ios-simulator";
case AppRunnerTarget.Simulator_iOS32:
return "ios-simulator-32";
case AppRunnerTarget.Simulator_iOS64:
return "ios-simulator-64";
case AppRunnerTarget.Simulator_tvOS:
return "tvos-simulator";
case AppRunnerTarget.Simulator_watchOS:
return "watchos-simulator";
default:
throw new NotImplementedException ();
}
}
public static AppRunnerTarget ParseAsAppRunnerTarget (this string @this)
{
switch (@this) {
case "ios-device":
return AppRunnerTarget.Device_iOS;
case "tvos-device":
return AppRunnerTarget.Device_tvOS;
case "watchos-device":
return AppRunnerTarget.Device_watchOS;
case "ios-simulator":
return AppRunnerTarget.Simulator_iOS;
case "ios-simulator-32":
return AppRunnerTarget.Simulator_iOS32;
case "ios-simulator-64":
return AppRunnerTarget.Simulator_iOS64;
case "tvos-simulator":
return AppRunnerTarget.Simulator_tvOS;
case "watchos-simulator":
return AppRunnerTarget.Simulator_watchOS;
case null:
case "":
return AppRunnerTarget.None;
default:
throw new NotImplementedException ();
}
}
}
}

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

@ -57,7 +57,7 @@ namespace xharness
public string IOS_DESTDIR { get; set; }
// Run
public string Target { get; set; }
public AppRunnerTarget Target { get; set; }
public string SdkRoot { get; set; } = "/Applications/Xcode.app";
public string Configuration { get; set; } = "Debug";
public string LogFile { get; set; }

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

@ -57,56 +57,42 @@ namespace xharness
}
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) { Platform = TestPlatform.tvOS });
} 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 watchOSDeviceTypes =
Simulators.SupportedDeviceTypes.
Where ((SimDeviceType v) => v.ProductFamilyId == "Watch");
var devices =
Simulators.AvailableDevices.
Where ((SimDevice d) => d.SimRuntime == latestwatchOSRuntime.Identifier && watchOSDeviceTypes.Any ((v) => d.SimDeviceType == v.Identifier));
var pair = Simulators.AvailableDevicePairs.
FirstOrDefault ((SimDevicePair v) => devices.Any ((SimDevice d) => d.UDID == v.Gizmo));
if (pair == null) {
var msg = string.Format ("Could not find a device pair for any of these devices: {0}", string.Join (", ", devices.Select ((v) => v.Name).ToArray ()));
SimulatorLoadLog.WriteLine (msg);
throw new Exception (msg);
}
var device =
Simulators.AvailableDevices.
FirstOrDefault ((SimDevice v) => pair.Gizmo == v.UDID); // select the device in the device pair.
var companion =
Simulators.AvailableDevices.
FirstOrDefault ((SimDevice v) => pair.Companion == v.UDID);
runtasks.Add (new RunSimulatorTask (buildTask, device, companion) { Platform = TestPlatform.watchOS });
} else {
var latestiOSRuntime =
Simulators.SupportedRuntimes.
Where ((SimRuntime v) => v.Identifier.StartsWith ("com.apple.CoreSimulator.SimRuntime.iOS-", StringComparison.Ordinal)).
OrderBy ((SimRuntime v) => v.Version).
Last ();
AppRunnerTarget [] targets;
TestPlatform [] platforms;
SimDevice [] devices;
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-5").First ()) { Platform = TestPlatform.iOS_Unified32 });
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-6s").First ()) { Platform = TestPlatform.iOS_Unified64 });
if (fn.EndsWith ("-tvos", StringComparison.Ordinal)) {
targets = new AppRunnerTarget [] { AppRunnerTarget.Simulator_tvOS };
platforms = new TestPlatform [] { TestPlatform.tvOS };
} else if (fn.EndsWith ("-watchos", StringComparison.Ordinal)) {
targets = new AppRunnerTarget [] { AppRunnerTarget.Simulator_watchOS };
platforms = new TestPlatform [] { TestPlatform.watchOS };
} else {
targets = new AppRunnerTarget [] { AppRunnerTarget.Simulator_iOS32, AppRunnerTarget.Simulator_iOS64 };
platforms = new TestPlatform [] { TestPlatform.iOS_Unified32, TestPlatform.iOS_Unified64 };
}
for (int i = 0; i < targets.Length; i++) {
try {
devices = await Simulators.FindAsync (targets [i], SimulatorLoadLog);
if (devices == null) {
SimulatorLoadLog.WriteLine ($"Failed to find simulator for {targets [i]}.");
var task = new RunSimulatorTask (buildTask) { ExecutionResult = TestExecutingResult.Failed };
var log = task.Logs.CreateFile ("Run log", Path.Combine (task.LogDirectory, "run-" + DateTime.Now.Ticks + ".log"));
File.WriteAllText (log.Path, "Failed to find simulators.");
runtasks.Add (task);
continue;
}
} catch (Exception e) {
SimulatorLoadLog.WriteLine ($"Failed to find simulator for {targets [i]}");
SimulatorLoadLog.WriteLine (e);
var task = new RunSimulatorTask (buildTask) { ExecutionResult = TestExecutingResult.Failed };
var log = task.Logs.CreateFile ("Run log", Path.Combine (task.LogDirectory, "run-" + DateTime.Now.Ticks + ".log"));
File.WriteAllText (log.Path, "Failed to find simulators.");
runtasks.Add (task);
continue;
}
runtasks.Add (new RunSimulatorTask (buildTask, devices [0], devices.Length > 1 ? devices [1] : null) { Platform = platforms [i] });
}
return runtasks;
@ -1154,7 +1140,7 @@ function toggleContainerVisibility (containerName)
public SimDevice Device;
public SimDevice CompanionDevice;
public XBuildTask BuildTask;
public string AppRunnerTarget;
public AppRunnerTarget AppRunnerTarget;
AppRunner runner;
@ -1229,11 +1215,11 @@ function toggleContainerVisibility (containerName)
var project = Path.GetFileNameWithoutExtension (ProjectFile);
if (project.EndsWith ("-tvos", StringComparison.Ordinal)) {
AppRunnerTarget = "tvos-simulator";
AppRunnerTarget = AppRunnerTarget.Simulator_tvOS;
} else if (project.EndsWith ("-watchos", StringComparison.Ordinal)) {
AppRunnerTarget = "watchos-simulator";
AppRunnerTarget = AppRunnerTarget.Simulator_watchOS;
} else {
AppRunnerTarget = "ios-simulator";
AppRunnerTarget = AppRunnerTarget.Simulator_iOS;
}
}

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

@ -36,7 +36,7 @@ namespace xharness
}
},
{ "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 },
{ "target=", "Where to run the project ([ios|watchos|tvos]-[device|simulator|simulator-32|simulator-64]).", (v) => harness.Target = v.ParseAsAppRunnerTarget () },
{ "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 },

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

@ -80,6 +80,127 @@ namespace xharness
File.Delete (tmpfile);
}
}
public async Task<SimDevice []> FindAsync (AppRunnerTarget target, Log log)
{
SimDevice [] simulators = null;
string [] simulator_devicetypes;
string simulator_runtime;
string [] companion_devicetypes = null;
string companion_runtime = null;
switch (target) {
case AppRunnerTarget.Simulator_iOS32:
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.iPhone-5" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
break;
case AppRunnerTarget.Simulator_iOS64:
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.iPhone-5s" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
break;
case AppRunnerTarget.Simulator_iOS:
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.iPhone-5" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
break;
case AppRunnerTarget.Simulator_tvOS:
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.tvOS-" + Xamarin.SdkVersions.TVOS.Replace ('.', '-');
break;
case AppRunnerTarget.Simulator_watchOS:
simulator_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm", "com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-2-38mm" };
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.watchOS-" + Xamarin.SdkVersions.WatchOS.Replace ('.', '-');
companion_devicetypes = new string [] { "com.apple.CoreSimulator.SimDeviceType.iPhone-6s" };
companion_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + Xamarin.SdkVersions.iOS.Replace ('.', '-');
break;
default:
throw new Exception (string.Format ("Unknown simulator target: {0}", Harness.Target));
}
var devices = AvailableDevices.Where ((SimDevice v) =>
{
if (v.SimRuntime != simulator_runtime)
return false;
if (!simulator_devicetypes.Contains (v.SimDeviceType))
return false;
if (target == AppRunnerTarget.Simulator_watchOS)
return AvailableDevicePairs.Any ((SimDevicePair pair) => pair.Companion == v.UDID || pair.Gizmo == v.UDID);
return true;
});
SimDevice candidate = null;
foreach (var device in devices) {
var data = device;
var secondaryData = (SimDevice) null;
var nodeCompanions = AvailableDevicePairs.Where ((SimDevicePair v) => v.Companion == device.UDID);
var nodeGizmos = AvailableDevicePairs.Where ((SimDevicePair v) => v.Gizmo == device.UDID);
if (nodeCompanions.Any ()) {
var gizmo_udid = nodeCompanions.First ().Gizmo;
var node = AvailableDevices.Where ((SimDevice v) => v.UDID == gizmo_udid);
secondaryData = node.First ();
} else if (nodeGizmos.Any ()) {
var companion_udid = nodeGizmos.First ().Companion;
var node = 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 && candidate == null && target == AppRunnerTarget.Simulator_watchOS) {
// We might be only missing device pairs to match phone + watch.
var watchDevices = AvailableDevices.Where ((SimDevice v) => { return v.SimRuntime == simulator_runtime && simulator_devicetypes.Contains (v.SimDeviceType); });
var companionDevices = AvailableDevices.Where ((SimDevice v) => { return v.SimRuntime == companion_runtime && companion_devicetypes.Contains (v.SimDeviceType); });
if (!watchDevices.Any () || !companionDevices.Any ()) {
log.WriteLine ($"Could not find both watch devices for <runtime={simulator_runtime} and device type={string.Join (";", simulator_devicetypes)}> and companion device for <runtime={companion_runtime} and device type {string.Join (";", companion_devicetypes)}>");
return null;
}
var watchDevice = watchDevices.First ();
var companionDevice = companionDevices.First ();
log.WriteLine ($"Creating device pair for '{watchDevice.Name}' and '{companionDevice.Name}'");
var rv = await Harness.ExecuteXcodeCommandAsync ("simctl", $"pair {watchDevice.UDID} {companionDevice.UDID}", log, TimeSpan.FromMinutes (1));
if (!rv.Succeeded) {
log.WriteLine ($"Could not create device pair, so could not find simulator for runtime={simulator_runtime} and device type={string.Join ("; ", simulator_devicetypes)}.");
return null;
}
AvailableDevicePairs.Add (new SimDevicePair ()
{
Companion = companionDevice.UDID,
Gizmo = watchDevice.UDID,
UDID = $"<created for {companionDevice.UDID} and {watchDevice.UDID}",
});
simulators = new SimDevice [] { watchDevice, companionDevice };
}
if (simulators == null) {
if (candidate == null) {
log.WriteLine ($"Could not find simulator for runtime={simulator_runtime} and device type={string.Join (";", simulator_devicetypes)}.");
return null;
}
simulators = new SimDevice [] { candidate };
}
if (simulators == null) {
log.WriteLine ("Could not find simulator");
return null;
}
log.WriteLine ("Found simulator: {0} {1}", simulators [0].Name, simulators [0].UDID);
if (simulators.Length > 1)
log.WriteLine ("Found companion simulator: {0} {1}", simulators [1].Name, simulators [1].UDID);
return simulators;
}
}
public class SimRuntime

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

@ -69,6 +69,7 @@
<Compile Include="TestProject.cs" />
<Compile Include="Log.cs" />
<Compile Include="GitHub.cs" />
<Compile Include="Extensions.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\..\jenkins\nunit-summary.xslt">