1124 строки
38 KiB
C#
1124 строки
38 KiB
C#
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.Text;
|
|
|
|
namespace xharness
|
|
{
|
|
public class Jenkins
|
|
{
|
|
public Harness Harness;
|
|
public bool IncludeClassic;
|
|
public bool IncludeBcl;
|
|
|
|
public LogFiles Logs = new LogFiles ();
|
|
LogFile SimulatorLoadLog;
|
|
|
|
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 ("Desktop", Environment.ProcessorCount);
|
|
|
|
async Task<IEnumerable<RunSimulatorTask>> CreateRunSimulatorTaskAsync (XBuildTask buildTask)
|
|
{
|
|
var runtasks = new List<RunSimulatorTask> ();
|
|
|
|
Simulators.Harness = Harness;
|
|
if (SimulatorLoadLog == null)
|
|
SimulatorLoadLog = Logs.Create (LogDirectory, "simulator-list.log", "Simulator Listing");
|
|
try {
|
|
await Simulators.LoadAsync (SimulatorLoadLog);
|
|
} catch (Exception e) {
|
|
SimulatorLoadLog.WriteLine ("Failed to load simulators:");
|
|
SimulatorLoadLog.WriteLine (e.ToString ());
|
|
return runtasks;
|
|
}
|
|
|
|
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 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) { Platform = TestPlatform.watchOS, ExecutionResult = TestExecutingResult.Ignored });
|
|
} 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 ()) { Platform = TestPlatform.iOS_Unified32 });
|
|
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-6s").First ()) { Platform = TestPlatform.iOS_Unified64 });
|
|
} else {
|
|
runtasks.Add (new RunSimulatorTask (buildTask, Simulators.AvailableDevices.Where ((SimDevice v) => v.SimRuntime == latestiOSRuntime.Identifier && v.SimDeviceType == "com.apple.CoreSimulator.SimDeviceType.iPhone-4s").First ()) { Platform = TestPlatform.iOS_Classic });
|
|
}
|
|
}
|
|
|
|
return runtasks;
|
|
}
|
|
|
|
static string AddSuffixToPath (string path, string suffix)
|
|
{
|
|
return Path.Combine (Path.GetDirectoryName (path), Path.GetFileNameWithoutExtension (path) + suffix + Path.GetExtension (path));
|
|
}
|
|
|
|
async Task PopulateTasksAsync ()
|
|
{
|
|
// Missing:
|
|
// api-diff
|
|
// msbuild tests
|
|
var runSimulatorTasks = new List<RunSimulatorTask> ();
|
|
|
|
foreach (var project in Harness.IOSTestProjects) {
|
|
if (!project.IsExecutableProject)
|
|
continue;
|
|
|
|
if (!IncludeBcl && project.Path.Contains ("bcl-test"))
|
|
continue;
|
|
|
|
var build = new XBuildTask ()
|
|
{
|
|
Jenkins = this,
|
|
ProjectFile = project.Path,
|
|
ProjectConfiguration = "Debug",
|
|
ProjectPlatform = "iPhoneSimulator",
|
|
Platform = TestPlatform.iOS_Classic,
|
|
};
|
|
if (IncludeClassic)
|
|
runSimulatorTasks.AddRange (await CreateRunSimulatorTaskAsync (build));
|
|
|
|
var suffixes = new string [] { "-unified", "-tvos", "-watchos" };
|
|
foreach (var suffix in suffixes) {
|
|
var derived = new XBuildTask ()
|
|
{
|
|
Jenkins = this,
|
|
ProjectFile = AddSuffixToPath (project.Path, suffix),
|
|
ProjectConfiguration = build.ProjectConfiguration,
|
|
ProjectPlatform = build.ProjectPlatform,
|
|
};
|
|
switch (suffix) {
|
|
case "-unified":
|
|
derived.Platform = TestPlatform.iOS_Unified;
|
|
break;
|
|
case "-tvos":
|
|
derived.Platform = TestPlatform.tvOS;
|
|
break;
|
|
case "-watchos":
|
|
derived.Platform = TestPlatform.watchOS;
|
|
break;
|
|
default:
|
|
throw new NotImplementedException ();
|
|
}
|
|
runSimulatorTasks.AddRange (await CreateRunSimulatorTaskAsync (derived));
|
|
}
|
|
}
|
|
|
|
foreach (var taskGroup in runSimulatorTasks.GroupBy ((RunSimulatorTask task) => task.Device)) {
|
|
Tasks.Add (new AggregatedRunSimulatorTask (taskGroup)
|
|
{
|
|
Jenkins = this,
|
|
Device = taskGroup.Key,
|
|
});
|
|
}
|
|
|
|
foreach (var project in Harness.MacTestProjects) {
|
|
if (!project.IsExecutableProject)
|
|
continue;
|
|
|
|
BuildToolTask build;
|
|
if (project.GenerateVariations) {
|
|
build = new MdtoolTask ();
|
|
build.Platform = TestPlatform.Mac_Classic;
|
|
} else {
|
|
build = new XBuildTask ();
|
|
build.Platform = TestPlatform.Mac;
|
|
}
|
|
build.Jenkins = this;
|
|
build.ProjectFile = project.Path;
|
|
build.ProjectConfiguration = "Debug";
|
|
build.ProjectPlatform = "x86";
|
|
build.SpecifyPlatform = false;
|
|
build.SpecifyConfiguration = false;
|
|
var exec = new MacExecuteTask ()
|
|
{
|
|
Platform = build.Platform,
|
|
Jenkins = this,
|
|
BuildTask = build,
|
|
ProjectFile = build.ProjectFile,
|
|
ProjectConfiguration = build.ProjectConfiguration,
|
|
ProjectPlatform = build.ProjectPlatform,
|
|
};
|
|
Tasks.Add (exec);
|
|
|
|
if (project.GenerateVariations) {
|
|
Tasks.Add (CloneExecuteTask (exec, TestPlatform.Mac_Unified, "-unified"));
|
|
Tasks.Add (CloneExecuteTask (exec, TestPlatform.Mac_UnifiedXM45, "-unifiedXM45"));
|
|
}
|
|
}
|
|
|
|
|
|
foreach (var task in runSimulatorTasks) {
|
|
if (task.TestName == "framework-test")
|
|
task.ExecutionResult = TestExecutingResult.Ignored;
|
|
}
|
|
}
|
|
|
|
static MacExecuteTask CloneExecuteTask (MacExecuteTask task, TestPlatform platform, string suffix)
|
|
{
|
|
var build = new XBuildTask ()
|
|
{
|
|
Platform = platform,
|
|
Jenkins = task.Jenkins,
|
|
ProjectFile = AddSuffixToPath (task.ProjectFile, suffix),
|
|
ProjectConfiguration = task.ProjectConfiguration,
|
|
ProjectPlatform = task.ProjectPlatform,
|
|
SpecifyPlatform = task.BuildTask.SpecifyPlatform,
|
|
SpecifyConfiguration = task.BuildTask.SpecifyConfiguration,
|
|
};
|
|
|
|
var execute = new MacExecuteTask ()
|
|
{
|
|
Platform = build.Platform,
|
|
Jenkins = build.Jenkins,
|
|
ProjectFile = build.ProjectFile,
|
|
ProjectConfiguration = build.ProjectConfiguration,
|
|
ProjectPlatform = build.ProjectPlatform,
|
|
BuildTask = build,
|
|
};
|
|
|
|
return execute;
|
|
}
|
|
|
|
public int Run ()
|
|
{
|
|
try {
|
|
Directory.CreateDirectory (LogDirectory);
|
|
Harness.HarnessLog = Logs.Create (LogDirectory, "Harness.log", "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.TimedOut))
|
|
return "purple";
|
|
else if (tests.Any ((v) => v.BuildFailure))
|
|
return "darkred";
|
|
else if (tests.Any ((v) => v.Failed))
|
|
return "red";
|
|
else if (tests.All ((v) => v.Building))
|
|
return "darkblue";
|
|
else if (tests.All ((v) => v.InProgress))
|
|
return "blue";
|
|
else if (tests.Any ((v) => v.NotStarted))
|
|
return "black";
|
|
else if (tests.Any ((v) => v.Ignored))
|
|
return "gray";
|
|
else
|
|
return "black";
|
|
}
|
|
|
|
string GetTestColor (TestTask test)
|
|
{
|
|
if (test.NotStarted) {
|
|
return "black";
|
|
} else if (test.InProgress) {
|
|
if (test.Building) {
|
|
return "darkblue";
|
|
} else if (test.Running) {
|
|
return "lightblue";
|
|
} else {
|
|
return "blue";
|
|
}
|
|
} else {
|
|
if (test.Crashed) {
|
|
return "maroon";
|
|
} else if (test.HarnessException) {
|
|
return "yellow";
|
|
} else if (test.TimedOut) {
|
|
return "purple";
|
|
} else if (test.BuildFailure) {
|
|
return "darkred";
|
|
} else if (test.Failed) {
|
|
return "red";
|
|
} else if (test.Succeeded) {
|
|
return "green";
|
|
} else if (test.Ignored) {
|
|
return "gray";
|
|
} else {
|
|
return "pink";
|
|
}
|
|
}
|
|
}
|
|
|
|
object report_lock = new object ();
|
|
public void GenerateReport ()
|
|
{
|
|
var id_counter = 0;
|
|
using (var stream = new MemoryStream ()) {
|
|
using (var writer = new StreamWriter (stream)) {
|
|
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';
|
|
}
|
|
}
|
|
function toggleContainerVisibility (containerName)
|
|
{
|
|
var button = document.getElementById ('button_container_' + containerName);
|
|
var div = document.getElementById ('test_container_' + containerName);
|
|
if (div.style.display == 'none') {
|
|
div.style.display = 'block';
|
|
button.innerText = 'Hide';
|
|
} else {
|
|
div.style.display = 'none';
|
|
button.innerText = 'Show';
|
|
}
|
|
}
|
|
</script>");
|
|
writer.WriteLine ("<body>");
|
|
writer.WriteLine ("<h1>Test results</h1>");
|
|
|
|
foreach (var log in Logs)
|
|
writer.WriteLine ("<a href='{0}' type='text/plain'>{1}</a><br />", log.Path.Substring (LogDirectory.Length + 1), log.Description);
|
|
|
|
var allSimulatorTasks = new List<RunSimulatorTask> ();
|
|
var allExecuteTasks = new List<MacExecuteTask> ();
|
|
foreach (var task in Tasks) {
|
|
var aggregated = task as AggregatedRunSimulatorTask;
|
|
if (aggregated != null) {
|
|
allSimulatorTasks.AddRange (aggregated.Tasks);
|
|
continue;
|
|
}
|
|
|
|
var execute = task as MacExecuteTask;
|
|
if (execute != null) {
|
|
allExecuteTasks.Add (execute);
|
|
continue;
|
|
}
|
|
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
var allTasks = new List<TestTask> ();
|
|
allTasks.AddRange (allExecuteTasks);
|
|
allTasks.AddRange (allSimulatorTasks);
|
|
|
|
var failedTests = allTasks.Where ((v) => v.Failed);
|
|
var stillInProgress = allTasks.Any ((v) => v.InProgress);
|
|
if (failedTests.Count () == 0) {
|
|
if (stillInProgress) {
|
|
writer.WriteLine ("<h2>All tests passed (but still tests in progress)</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)) {
|
|
var enumerableGroup = group as IEnumerable<TestTask>;
|
|
if (enumerableGroup != null) {
|
|
writer.WriteLine ("<a href='#test_{2}'>{0}</a> ({1})<br />", group.Key, string.Join (", ", enumerableGroup.Select ((v) => string.Format ("<span style='color: {0}'>{1}</span>", GetTestColor (v), string.IsNullOrEmpty (v.Mode) ? v.ExecutionResult.ToString () : v.Mode)).ToArray ()), group.Key.Replace (' ', '-'));
|
|
continue;
|
|
}
|
|
|
|
//var executionGroup = group as IEnumerable<MacExecuteTask>;
|
|
//if (executionGroup != null) {
|
|
// writer.WriteLine ("<a href='#test_{2}'>{0}</a> ({1})<br />", group.Key, string.Join (", ", executionGroup.Select ((v) => string.Format ("<span style='color: {0}'>{1}</span>", GetTestColor (v), v.Mode)).ToArray ()), group.Key.Replace (' ', '-'));
|
|
// continue;
|
|
//}
|
|
|
|
throw new NotImplementedException ();
|
|
}
|
|
}
|
|
|
|
//foreach (var group in allExecuteTasks.GroupBy ((MacExecuteTask v) => v.TestName)) {
|
|
|
|
//}
|
|
|
|
foreach (var group in allTasks.GroupBy ((TestTask v) => v.TestName)) {
|
|
var firstResult = group.First ().ExecutionResult;
|
|
var identicalResults = group.All ((v) => v.ExecutionResult == firstResult);
|
|
var defaultHide = !group.Any ((v) => v.Failed);
|
|
writer.WriteLine ("<h2 id='test_{1}'>{0} (<span style='color: {2}'>{4}</span>) <small><a id='button_container_{1}' href=\"javascript: toggleContainerVisibility ('{1}');\">{3}</a></small> </h2>",
|
|
group.Key, group.Key.Replace (' ', '-'), GetTestColor (group), defaultHide ? "Show" : "Hide", identicalResults ? firstResult.ToString () : "multiple results");
|
|
writer.WriteLine ("<div id='test_container_{0}' style='display: {1}'>", group.Key.Replace (' ', '-'), defaultHide ? "none" : "block");
|
|
foreach (var test in group) {
|
|
string state;
|
|
state = test.ExecutionResult.ToString ();
|
|
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);
|
|
var logs = test.AggregatedLogs;
|
|
if (logs.Count () > 0) {
|
|
foreach (var log in logs) {
|
|
writer.WriteLine ("<a href='{0}' type='text/plain'>{1}</a><br />", log.Path.Substring (LogDirectory.Length + 1), log.Description);
|
|
if (log.Description == "Test log") {
|
|
var summary = string.Empty;
|
|
try {
|
|
if (File.Exists (log.Path)) {
|
|
using (var fs = new FileStream (log.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
|
|
using (var reader = new StreamReader (fs)) {
|
|
while (!reader.EndOfStream) {
|
|
string line = reader.ReadLine ();
|
|
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 ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
summary = "No test log (yet).";
|
|
}
|
|
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 {
|
|
if (File.Exists (log.Path)) {
|
|
using (var fs = new FileStream (log.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
|
|
using (var reader = new StreamReader (fs)) {
|
|
while (!reader.EndOfStream) {
|
|
string line = reader.ReadLine ().Trim ();
|
|
if (line.Contains (": error"))
|
|
errors.Add (line);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
errors.Add ("Log file doesn't exist (yet)");
|
|
}
|
|
foreach (var error in errors)
|
|
writer.WriteLine ("<span style='padding-left: 15\tpx;'>{0}</span> <br />", error);
|
|
} catch (Exception ex) {
|
|
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>");
|
|
}
|
|
writer.WriteLine ("</div>");
|
|
}
|
|
writer.WriteLine ("</body>");
|
|
writer.WriteLine ("</html>");
|
|
}
|
|
lock (report_lock) {
|
|
var report = Path.Combine (LogDirectory, "index.html");
|
|
if (File.Exists (report))
|
|
File.Delete (report);
|
|
File.WriteAllBytes (report, stream.ToArray ());
|
|
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;
|
|
}
|
|
}
|
|
|
|
TestExecutingResult execution_result;
|
|
public TestExecutingResult ExecutionResult {
|
|
get {
|
|
return execution_result;
|
|
}
|
|
set {
|
|
execution_result = value;
|
|
Jenkins.GenerateReport ();
|
|
}
|
|
}
|
|
|
|
public bool NotStarted { get { return (ExecutionResult & TestExecutingResult.StateMask) == TestExecutingResult.NotStarted; } }
|
|
public bool InProgress { get { return (ExecutionResult & TestExecutingResult.StateMask) == TestExecutingResult.InProgress; } }
|
|
public bool Finished { get { return (ExecutionResult & TestExecutingResult.StateMask) == TestExecutingResult.Finished; } }
|
|
|
|
public bool Building { get { return (ExecutionResult & TestExecutingResult.InProgressMask) == TestExecutingResult.Building; } }
|
|
public bool Built { get { return (ExecutionResult & TestExecutingResult.InProgressMask) == TestExecutingResult.Built; } }
|
|
public bool Running { get { return (ExecutionResult & TestExecutingResult.InProgressMask) == TestExecutingResult.Running; } }
|
|
|
|
public bool Succeeded { get { return (ExecutionResult & TestExecutingResult.Succeeded) == TestExecutingResult.Succeeded; } }
|
|
public bool Failed { get { return (ExecutionResult & TestExecutingResult.Failed) == TestExecutingResult.Failed; } }
|
|
public bool Ignored { get { return (ExecutionResult & TestExecutingResult.Ignored) == TestExecutingResult.Ignored; } }
|
|
|
|
public bool Crashed { get { return (ExecutionResult & TestExecutingResult.Crashed) == TestExecutingResult.Crashed; } }
|
|
public bool TimedOut { get { return (ExecutionResult & TestExecutingResult.TimedOut) == TestExecutingResult.TimedOut; } }
|
|
public bool BuildFailure { get { return (ExecutionResult & TestExecutingResult.BuildFailure) == TestExecutingResult.BuildFailure; } }
|
|
public bool HarnessException { get { return (ExecutionResult & TestExecutingResult.HarnessException) == TestExecutingResult.HarnessException; } }
|
|
|
|
public virtual string Mode { get; set; }
|
|
|
|
public virtual string TestName {
|
|
get {
|
|
var rv = Path.GetFileNameWithoutExtension (ProjectFile);
|
|
switch (Platform) {
|
|
case TestPlatform.Mac:
|
|
case TestPlatform.Mac_Classic:
|
|
return rv;
|
|
case TestPlatform.Mac_Unified:
|
|
return rv.Substring (0, rv.Length - "-unified".Length);
|
|
case TestPlatform.Mac_UnifiedXM45:
|
|
return rv.Substring (0, rv.Length - "-unifiedXM45".Length);
|
|
default:
|
|
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; set; }
|
|
|
|
public LogFiles Logs = new LogFiles ();
|
|
public List<Resource> Resources = new List<Resource> ();
|
|
|
|
public virtual IEnumerable<LogFile> AggregatedLogs {
|
|
get {
|
|
return Logs;
|
|
}
|
|
}
|
|
|
|
public string LogDirectory {
|
|
get {
|
|
return Path.Combine (Jenkins.LogDirectory, TestName);
|
|
}
|
|
}
|
|
|
|
Task build_task;
|
|
async Task RunInternalAsync ()
|
|
{
|
|
if (Finished)
|
|
return;
|
|
|
|
ExecutionResult = (ExecutionResult & ~TestExecutingResult.StateMask) | TestExecutingResult.InProgress;
|
|
|
|
Jenkins.GenerateReport ();
|
|
|
|
duration.Start ();
|
|
|
|
build_task = ExecuteAsync ();
|
|
await build_task;
|
|
|
|
duration.Stop ();
|
|
|
|
ExecutionResult = (ExecutionResult & ~TestExecutingResult.StateMask) | TestExecutingResult.Finished;
|
|
if ((ExecutionResult & ~TestExecutingResult.StateMask) == 0)
|
|
throw new Exception ("Result not set!");
|
|
|
|
Jenkins.GenerateReport ();
|
|
}
|
|
|
|
public Task RunAsync ()
|
|
{
|
|
if (build_task == null)
|
|
build_task = RunInternalAsync ();
|
|
return build_task;
|
|
}
|
|
|
|
protected abstract Task ExecuteAsync ();
|
|
|
|
public override string ToString ()
|
|
{
|
|
return ExecutionResult.ToString ();
|
|
}
|
|
}
|
|
|
|
abstract class BuildToolTask : TestTask
|
|
{
|
|
public bool SpecifyPlatform = true;
|
|
public bool SpecifyConfiguration = true;
|
|
|
|
public override string Mode {
|
|
get { return Platform.ToString (); }
|
|
set { throw new NotSupportedException (); }
|
|
}
|
|
|
|
protected void SetEnvironmentVariables (Process process)
|
|
{
|
|
switch (Platform) {
|
|
case TestPlatform.iOS_Classic:
|
|
case TestPlatform.iOS_Unified:
|
|
case TestPlatform.iOS_Unified32:
|
|
case TestPlatform.iOS_Unified64:
|
|
case TestPlatform.tvOS:
|
|
case TestPlatform.watchOS:
|
|
process.StartInfo.EnvironmentVariables ["MD_APPLE_SDK_ROOT"] = Harness.XcodeRoot;
|
|
process.StartInfo.EnvironmentVariables ["MD_MTOUCH_SDK_ROOT"] = Path.Combine (Harness.IOS_DESTDIR, "Library", "Frameworks", "Xamarin.iOS.framework", "Versions", "Current");
|
|
process.StartInfo.EnvironmentVariables ["XBUILD_FRAMEWORK_FOLDERS_PATH"] = Path.Combine (Harness.IOS_DESTDIR, "Library", "Frameworks", "Mono.framework", "External", "xbuild-frameworks");
|
|
process.StartInfo.EnvironmentVariables ["MSBuildExtensionsPath"] = Path.Combine (Harness.IOS_DESTDIR, "Library", "Frameworks", "Mono.framework", "External", "xbuild");
|
|
break;
|
|
case TestPlatform.Mac:
|
|
case TestPlatform.Mac_Classic:
|
|
case TestPlatform.Mac_Unified:
|
|
case TestPlatform.Mac_UnifiedXM45:
|
|
process.StartInfo.EnvironmentVariables ["MD_APPLE_SDK_ROOT"] = Harness.XcodeRoot;
|
|
process.StartInfo.EnvironmentVariables ["XBUILD_FRAMEWORK_FOLDERS_PATH"] = Path.Combine (Harness.MAC_DESTDIR, "Library", "Frameworks", "Mono.framework", "External", "xbuild-frameworks");
|
|
process.StartInfo.EnvironmentVariables ["MSBuildExtensionsPath"] = Path.Combine (Harness.MAC_DESTDIR, "Library", "Frameworks", "Mono.framework", "External", "xbuild");
|
|
process.StartInfo.EnvironmentVariables ["XamarinMacFrameworkRoot"] = Path.Combine (Harness.MAC_DESTDIR, "Library", "Frameworks", "Xamarin.Mac.framework", "Versions", "Current");
|
|
process.StartInfo.EnvironmentVariables ["XAMMAC_FRAMEWORK_PATH"] = Path.Combine (Harness.MAC_DESTDIR, "Library", "Frameworks", "Xamarin.Mac.framework", "Versions", "Current");
|
|
break;
|
|
default:
|
|
throw new NotImplementedException ();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class MdtoolTask : BuildToolTask
|
|
{
|
|
protected override async Task ExecuteAsync ()
|
|
{
|
|
using (var resource = await Jenkins.DesktopResource.AcquireConcurrentAsync ()) {
|
|
using (var xbuild = new Process ()) {
|
|
xbuild.StartInfo.FileName = "/Applications/Xamarin Studio.app/Contents/MacOS/mdtool";
|
|
var args = new StringBuilder ();
|
|
args.Append ("build ");
|
|
var sln = Path.ChangeExtension (ProjectFile, "sln");
|
|
args.Append (Harness.Quote (File.Exists (sln) ? sln : ProjectFile));
|
|
xbuild.StartInfo.Arguments = args.ToString ();
|
|
Harness.Log ("Building {0} ({1})", TestName, Mode);
|
|
SetEnvironmentVariables (xbuild);
|
|
var log = Logs.Create (LogDirectory, "build-" + Platform + ".txt", "Build log");
|
|
foreach (string key in xbuild.StartInfo.EnvironmentVariables.Keys)
|
|
log.WriteLine ("{0}={1}", key, xbuild.StartInfo.EnvironmentVariables [key]);
|
|
log.WriteLine ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
|
|
if (Harness.DryRun) {
|
|
Harness.Log ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
|
|
} else {
|
|
try {
|
|
await xbuild.RunAsync (log.Path, true, TimeSpan.FromMinutes (5));
|
|
ExecutionResult = xbuild.ExitCode == 0 ? TestExecutingResult.Succeeded : TestExecutingResult.Failed;
|
|
} catch (TimeoutException e) {
|
|
log.WriteLine ("Build timed out after {0} seconds.", e.Timeout.TotalSeconds);
|
|
ExecutionResult = TestExecutingResult.TimedOut;
|
|
} catch (Exception e) {
|
|
log.WriteLine ("Harness exception: {0}", e);
|
|
ExecutionResult = TestExecutingResult.HarnessException;
|
|
}
|
|
}
|
|
Harness.Log ("Built {0} ({1})", TestName, Mode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class XBuildTask : BuildToolTask
|
|
{
|
|
protected override async Task ExecuteAsync ()
|
|
{
|
|
using (var resource = await Jenkins.DesktopResource.AcquireConcurrentAsync ()) {
|
|
using (var xbuild = new Process ()) {
|
|
xbuild.StartInfo.FileName = "xbuild";
|
|
var args = new StringBuilder ();
|
|
args.Append ("/verbosity:diagnostic ");
|
|
if (SpecifyPlatform)
|
|
args.Append ($"/p:Platform={ProjectPlatform} ");
|
|
if (SpecifyConfiguration)
|
|
args.Append ($"/p:Configuration={ProjectConfiguration} ");
|
|
args.Append (Harness.Quote (ProjectFile));
|
|
xbuild.StartInfo.Arguments = args.ToString ();
|
|
Harness.Log ("Building {0} ({1})", TestName, Mode);
|
|
SetEnvironmentVariables (xbuild);
|
|
var log = Logs.Create (LogDirectory, "build-" + Platform + ".txt", "Build log");
|
|
foreach (string key in xbuild.StartInfo.EnvironmentVariables.Keys)
|
|
log.WriteLine ("{0}={1}", key, xbuild.StartInfo.EnvironmentVariables [key]);
|
|
log.WriteLine ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
|
|
if (Harness.DryRun) {
|
|
Harness.Log ("{0} {1}", xbuild.StartInfo.FileName, xbuild.StartInfo.Arguments);
|
|
} else {
|
|
try {
|
|
await xbuild.RunAsync (log.Path, true, TimeSpan.FromMinutes (5));
|
|
ExecutionResult = xbuild.ExitCode == 0 ? TestExecutingResult.Succeeded : TestExecutingResult.Failed;
|
|
} catch (TimeoutException e) {
|
|
log.WriteLine ("Build timed out after {0} seconds.", e.Timeout.TotalSeconds);
|
|
ExecutionResult = TestExecutingResult.TimedOut;
|
|
} catch (Exception e) {
|
|
log.WriteLine ("Harness exception: {0}", e);
|
|
ExecutionResult = TestExecutingResult.HarnessException;
|
|
}
|
|
}
|
|
Harness.Log ("Built {0} ({1})", TestName, Mode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
abstract class MacTask : TestTask
|
|
{
|
|
public override string Mode {
|
|
get {
|
|
switch (Platform) {
|
|
case TestPlatform.Mac:
|
|
return TestName;
|
|
case TestPlatform.Mac_Classic:
|
|
return "Classic";
|
|
case TestPlatform.Mac_Unified:
|
|
return "Unified";
|
|
case TestPlatform.Mac_UnifiedXM45:
|
|
return "Unified XM45";
|
|
default:
|
|
throw new NotImplementedException ();
|
|
}
|
|
}
|
|
set {
|
|
throw new NotSupportedException ();
|
|
}
|
|
}
|
|
}
|
|
|
|
class MacExecuteTask : MacTask
|
|
{
|
|
public string Path;
|
|
public BuildToolTask BuildTask;
|
|
|
|
public override IEnumerable<LogFile> AggregatedLogs {
|
|
get {
|
|
return base.AggregatedLogs.Union (BuildTask.Logs);
|
|
}
|
|
}
|
|
|
|
protected override async Task ExecuteAsync ()
|
|
{
|
|
ExecutionResult = TestExecutingResult.Building;
|
|
await BuildTask.RunAsync ();
|
|
if (!BuildTask.Succeeded) {
|
|
ExecutionResult = TestExecutingResult.BuildFailure;
|
|
return;
|
|
}
|
|
|
|
ExecutionResult = TestExecutingResult.Built;
|
|
|
|
var projectDir = System.IO.Path.GetDirectoryName (ProjectFile);
|
|
var name = System.IO.Path.GetFileName (projectDir);
|
|
if (string.Equals ("mac", name, StringComparison.OrdinalIgnoreCase))
|
|
name = System.IO.Path.GetFileName (System.IO.Path.GetDirectoryName (projectDir));
|
|
var suffix = string.Empty;
|
|
switch (Platform) {
|
|
case TestPlatform.Mac_Unified:
|
|
suffix = "-unified";
|
|
break;
|
|
case TestPlatform.Mac_UnifiedXM45:
|
|
suffix = "-unifiedXM45";
|
|
break;
|
|
}
|
|
Path = System.IO.Path.Combine (System.IO.Path.GetDirectoryName (ProjectFile), "bin", BuildTask.ProjectPlatform, BuildTask.ProjectConfiguration + suffix, name + ".app", "Contents", "MacOS", name);
|
|
|
|
using (var resource = await Jenkins.DesktopResource.AcquireConcurrentAsync ()) {
|
|
using (var proc = new Process ()) {
|
|
proc.StartInfo.FileName = Path;
|
|
Harness.Log ("Executing {0} ({1})", TestName, Mode);
|
|
var log = Logs.Create (LogDirectory, "execute-" + Platform + ".txt", "Execution log");
|
|
log.WriteLine ("{0} {1}", proc.StartInfo.FileName, proc.StartInfo.Arguments);
|
|
if (Harness.DryRun) {
|
|
Harness.Log ("{0} {1}", proc.StartInfo.FileName, proc.StartInfo.Arguments);
|
|
} else {
|
|
ExecutionResult = TestExecutingResult.Running;
|
|
try {
|
|
await proc.RunAsync (log.Path, true, TimeSpan.FromMinutes (5));
|
|
ExecutionResult = proc.ExitCode == 0 ? TestExecutingResult.Succeeded : TestExecutingResult.Failed;
|
|
} catch (TimeoutException e) {
|
|
log.WriteLine ("Execution timed out after {0} seconds.", e.Timeout.TotalSeconds);
|
|
ExecutionResult = TestExecutingResult.TimedOut;
|
|
} catch (Exception e) {
|
|
log.WriteLine (e.ToString ());
|
|
ExecutionResult = TestExecutingResult.HarnessException;
|
|
}
|
|
}
|
|
Harness.Log ("Executed {0} ({1})", TestName, Mode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class RunSimulatorTask : TestTask
|
|
{
|
|
public SimDevice Device;
|
|
public XBuildTask BuildTask;
|
|
public string AppRunnerTarget;
|
|
|
|
AppRunner runner;
|
|
|
|
public async Task BuildAsync ()
|
|
{
|
|
if (Finished)
|
|
return;
|
|
ExecutionResult |= TestExecutingResult.Building;
|
|
await BuildTask.RunAsync ();
|
|
if (BuildTask.Succeeded) {
|
|
ExecutionResult = (ExecutionResult & ~TestExecutingResult.InProgressMask) | TestExecutingResult.Built;
|
|
} else {
|
|
ExecutionResult = (ExecutionResult & ~(TestExecutingResult.InProgressMask | TestExecutingResult.StateMask)) | TestExecutingResult.BuildFailure;
|
|
}
|
|
}
|
|
|
|
public override IEnumerable<LogFile> AggregatedLogs {
|
|
get {
|
|
return base.AggregatedLogs.Union (BuildTask.Logs);
|
|
}
|
|
}
|
|
|
|
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_Unified32:
|
|
return "iOS Unified 32-bits";
|
|
case TestPlatform.iOS_Unified64:
|
|
return "iOS Unified 64-bits";
|
|
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";
|
|
}
|
|
}
|
|
|
|
public Task PrepareSimulatorAsync (bool initialize)
|
|
{
|
|
if (Finished)
|
|
return Task.FromResult (true);
|
|
|
|
if (!BuildTask.Succeeded) {
|
|
ExecutionResult = TestExecutingResult.BuildFailure;
|
|
return Task.FromResult (true);
|
|
}
|
|
|
|
runner = new AppRunner ()
|
|
{
|
|
Harness = Harness,
|
|
ProjectFile = ProjectFile,
|
|
SkipSimulatorSetup = !initialize,
|
|
SkipSimulatorCleanup = !initialize,
|
|
Target = AppRunnerTarget,
|
|
LogDirectory = LogDirectory,
|
|
};
|
|
runner.Simulators = new SimDevice [] { Device };
|
|
runner.Initialize ();
|
|
runner.PrepareSimulator ();
|
|
|
|
return Task.FromResult (true);
|
|
}
|
|
|
|
protected override Task ExecuteAsync ()
|
|
{
|
|
Harness.Log ("Running simulator '{0}' ({2}) for {1}", Device.Name, ProjectFile, Jenkins.Simulators.SupportedRuntimes.Where ((v) => v.Identifier == Device.SimRuntime).First ().Name);
|
|
|
|
if (Finished)
|
|
return Task.FromResult (true);
|
|
|
|
if (Harness.DryRun) {
|
|
Harness.Log ("<running app in simulator>");
|
|
} else {
|
|
try {
|
|
ExecutionResult = (ExecutionResult & ~TestExecutingResult.InProgressMask) | TestExecutingResult.Running;
|
|
Jenkins.GenerateReport ();
|
|
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);
|
|
|
|
return Task.FromResult (true);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
// Due to parallelization this isn't the same as the sum of the duration for all the build tasks.
|
|
Stopwatch build_timer = new Stopwatch ();
|
|
public TimeSpan BuildDuration { get { return build_timer.Elapsed; } }
|
|
|
|
Stopwatch run_timer = new Stopwatch ();
|
|
public TimeSpan RunDuration { get { return run_timer.Elapsed; } }
|
|
|
|
public AggregatedRunSimulatorTask (IEnumerable<RunSimulatorTask> tasks)
|
|
{
|
|
this.Tasks = tasks;
|
|
}
|
|
|
|
protected override async Task ExecuteAsync ()
|
|
{
|
|
// First build everything. This is required for the run simulator
|
|
// task to properly configure the simulator.
|
|
build_timer.Start ();
|
|
await Task.WhenAll (Tasks.Select ((v) => v.BuildAsync ()).Distinct ());
|
|
build_timer.Stop ();
|
|
|
|
using (var desktop = await Jenkins.DesktopResource.AcquireExclusiveAsync ()) {
|
|
Harness.Log ("Preparing simulator: {0}", Device.Name);
|
|
// We need to set the dialog permissions for all the apps
|
|
// before launching the simulator, because once launched
|
|
// the simulator caches the values in-memory.
|
|
bool first = true;
|
|
foreach (var task in Tasks) {
|
|
await task.PrepareSimulatorAsync (first);
|
|
first = false;
|
|
}
|
|
|
|
run_timer.Start ();
|
|
foreach (var task in Tasks)
|
|
await task.RunAsync ();
|
|
run_timer.Stop ();
|
|
}
|
|
|
|
ExecutionResult = Tasks.Any ((v) => !v.Succeeded) ? TestExecutingResult.Failed : TestExecutingResult.Succeeded;
|
|
}
|
|
}
|
|
|
|
// 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>> ();
|
|
ConcurrentQueue<TaskCompletionSource<IDisposable>> exclusive_queue = new ConcurrentQueue<TaskCompletionSource<IDisposable>> ();
|
|
int users;
|
|
int max_concurrent_users = 1;
|
|
bool exclusive;
|
|
|
|
|
|
public Resource (string name, int max_concurrent_users = 1)
|
|
{
|
|
this.Name = name;
|
|
this.max_concurrent_users = max_concurrent_users;
|
|
}
|
|
|
|
public Task<IDisposable> AcquireConcurrentAsync ()
|
|
{
|
|
lock (queue) {
|
|
if (!exclusive && users < max_concurrent_users) {
|
|
users++;
|
|
return Task.FromResult<IDisposable> (new AcquiredResource (this));
|
|
} else {
|
|
var tcs = new TaskCompletionSource<IDisposable> (new AcquiredResource (this));
|
|
queue.Enqueue (tcs);
|
|
return tcs.Task;
|
|
}
|
|
}
|
|
}
|
|
|
|
public Task<IDisposable> AcquireExclusiveAsync ()
|
|
{
|
|
lock (queue) {
|
|
if (users == 0) {
|
|
users++;
|
|
exclusive = true;
|
|
return Task.FromResult<IDisposable> (new AcquiredResource (this));
|
|
} else {
|
|
var tcs = new TaskCompletionSource<IDisposable> (new AcquiredResource (this));
|
|
exclusive_queue.Enqueue (tcs);
|
|
return tcs.Task;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Release ()
|
|
{
|
|
TaskCompletionSource<IDisposable> tcs;
|
|
|
|
lock (queue) {
|
|
users--;
|
|
exclusive = false;
|
|
if (queue.TryDequeue (out tcs)) {
|
|
users++;
|
|
tcs.SetResult ((IDisposable) tcs.Task.AsyncState);
|
|
} else if (users == 0 && exclusive_queue.TryDequeue (out tcs)) {
|
|
users++;
|
|
exclusive = true;
|
|
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
|
|
{
|
|
None,
|
|
|
|
iOS_Classic,
|
|
iOS_Unified,
|
|
iOS_Unified32,
|
|
iOS_Unified64,
|
|
tvOS,
|
|
watchOS,
|
|
|
|
Mac,
|
|
Mac_Classic,
|
|
Mac_Unified,
|
|
Mac_UnifiedXM45,
|
|
}
|
|
|
|
[Flags]
|
|
public enum TestExecutingResult
|
|
{
|
|
NotStarted = 0,
|
|
InProgress = 0x1,
|
|
Finished = 0x2,
|
|
StateMask = NotStarted + InProgress + Finished,
|
|
|
|
// In progress state
|
|
Building = 0x10 + InProgress,
|
|
Built = 0x20 + InProgress,
|
|
Running = 0x40 + InProgress,
|
|
InProgressMask = 0x10 + 0x20 + 0x40,
|
|
|
|
// Finished results
|
|
Succeeded = 0x100 + Finished,
|
|
Failed = 0x200 + Finished,
|
|
Ignored = 0x400 + Finished,
|
|
|
|
// Finished & Failed results
|
|
Crashed = 0x1000 + Failed,
|
|
TimedOut = 0x2000 + Failed,
|
|
HarnessException = 0x4000 + Failed,
|
|
BuildFailure = 0x8000 + Failed,
|
|
}
|
|
}
|
|
|