Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
TouchRunner.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under Apache 2.0 License. See LICENSE.md for details.
3 //
4 // TouchRunner.cs: MonoTouch.Dialog-based driver to run unit tests
5 //
6 // Authors:
7 // Sebastien Pouliot <sebastien@xamarin.com>
8 //
9 // Copyright 2011-2013 Xamarin Inc.
10 //
11 // Licensed under the Apache License, Version 2.0 (the "License");
12 // you may not use this file except in compliance with the License.
13 // You may obtain a copy of the License at
14 //
15 // http://www.apache.org/licenses/LICENSE-2.0
16 //
17 // Unless required by applicable law or agreed to in writing, software
18 // distributed under the License is distributed on an "AS IS" BASIS,
19 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 // See the License for the specific language governing permissions and
21 // limitations under the License.
22 //
23 
24 using System;
25 using System.IO;
26 using System.Collections;
27 using System.Collections.Generic;
28 using System.Net.Sockets;
29 using System.Reflection;
30 using System.Threading;
31 using System.Threading.Tasks;
32 using SiliconStudio.Paradox.Graphics.Regression;
33 #if XAMCORE_2_0
34 using Foundation;
35 using ObjCRuntime;
36 using UIKit;
37 using Constants = global::ObjCRuntime.Constants;
38 #else
39 using MonoTouch.Foundation;
40 using MonoTouch.ObjCRuntime;
41 using MonoTouch.UIKit;
42 using Constants = global::MonoTouch.Constants;
43 #endif
44 
45 using MonoTouch.Dialog;
46 
47 using NUnit.Framework.Api;
48 using NUnit.Framework.Internal;
50 using NUnit.Framework.Internal.WorkItems;
51 
52 namespace SiliconStudio.Paradox.UnitTesting.UI {
53 
54  public class TouchRunner : ITestListener {
55 
56  UIWindow window;
57  int passed;
58  int failed;
59  int ignored;
60  int inconclusive;
61  TestSuite suite = new TestSuite (String.Empty);
62  ITestFilter filter;
63 
64  [CLSCompliant (false)]
65  public TouchRunner (UIWindow window)
66  {
67  if (window == null)
68  throw new ArgumentNullException ("window");
69 
70  this.window = window;
71  filter = TestFilter.Empty;
72  }
73 
74  public bool AutoStart {
75  get { return TouchOptions.Current.AutoStart; }
76  set { TouchOptions.Current.AutoStart = value; }
77  }
78 
79  public ITestFilter Filter {
80  get { return filter; }
81  set { filter = value; }
82  }
83 
84  public bool TerminateAfterExecution {
85  get { return TouchOptions.Current.TerminateAfterExecution; }
86  set { TouchOptions.Current.TerminateAfterExecution = value; }
87  }
88 
89  [CLSCompliant (false)]
90  public UINavigationController NavigationController {
91  get { return (UINavigationController) window.RootViewController; }
92  }
93 
94  List<Assembly> assemblies = new List<Assembly> ();
95  ManualResetEvent mre = new ManualResetEvent (false);
96 
97  public void Add (Assembly assembly)
98  {
99  if (assembly == null)
100  throw new ArgumentNullException ("assembly");
101 
102  assemblies.Add (assembly);
103  }
104 
105  static void TerminateWithSuccess ()
106  {
107  Selector selector = new Selector ("terminateWithSuccess");
108  UIApplication.SharedApplication.PerformSelector (selector, UIApplication.SharedApplication, 0);
109  }
110 
111  [CLSCompliant (false)]
112  public UIViewController GetViewController ()
113  {
114  var menu = new RootElement ("Test Runner");
115 
116  var runMode = new Section("Run Mode");
117  var interactiveCheckBox = new CheckboxElement("Enable Interactive Mode");
118  interactiveCheckBox.Tapped += () => GraphicsTestBase.ForceInteractiveMode = interactiveCheckBox.Value;
119  runMode.Add(interactiveCheckBox);
120  menu.Add(runMode);
121 
122  Section main = new Section ("Loading test suites...");
123  menu.Add (main);
124 
125  Section options = new Section () {
126  new StyledStringElement ("Options", Options) { Accessory = UITableViewCellAccessory.DisclosureIndicator },
127  new StyledStringElement ("Credits", Credits) { Accessory = UITableViewCellAccessory.DisclosureIndicator }
128  };
129  menu.Add (options);
130 
131  // large unit tests applications can take more time to initialize
132  // than what the iOS watchdog will allow them on devices
133  ThreadPool.QueueUserWorkItem (delegate {
134  foreach (Assembly assembly in assemblies)
135  Load (assembly, null);
136 
137  window.InvokeOnMainThread (delegate {
138 
139  while (suite.Tests.Count == 1 && (suite.Tests[0] is TestSuite))
140  suite = (TestSuite)suite.Tests[0];
141 
142  foreach (TestSuite ts in suite.Tests) {
143  main.Add (Setup (ts));
144  }
145  mre.Set ();
146 
147  main.Caption = null;
148  menu.Reload (main, UITableViewRowAnimation.Fade);
149 
150  options.Insert (0, new StringElement ("Run Everything", Run));
151  menu.Reload (options, UITableViewRowAnimation.Fade);
152  });
153  assemblies.Clear ();
154  });
155 
156  var dv = new DialogViewController (menu) { Autorotate = true };
157 
158  // AutoStart running the tests (with either the supplied 'writer' or the options)
159  if (AutoStart) {
160  ThreadPool.QueueUserWorkItem (delegate {
161  mre.WaitOne ();
162  window.BeginInvokeOnMainThread (delegate {
163  Run ();
164  // optionally end the process, e.g. click "Touch.Unit" -> log tests results, return to springboard...
165  // http://stackoverflow.com/questions/1978695/uiapplication-sharedapplication-terminatewithsuccess-is-not-there
166  if (TerminateAfterExecution)
167  TerminateWithSuccess ();
168  });
169  });
170  }
171  return dv;
172  }
173 
174  async void Run ()
175  {
176  if (!OpenWriter ("Run Everything"))
177  return;
178  try {
179  await Run (suite);
180  }
181  finally {
182  CloseWriter ();
183  }
184  }
185 
186  void Options ()
187  {
188  NavigationController.PushViewController (TouchOptions.Current.GetViewController (), true);
189  }
190 
191  void Credits ()
192  {
193  var title = new MultilineElement ("Touch.Unit Runner\nCopyright 2011-2012 Xamarin Inc.\nAll rights reserved.");
194  title.Alignment = UITextAlignment.Center;
195 
196  var root = new RootElement ("Credits") {
197  new Section () { title },
198  new Section () {
199  new HtmlElement ("About Xamarin", "http://www.xamarin.com"),
200  new HtmlElement ("About MonoTouch", "http://ios.xamarin.com"),
201  new HtmlElement ("About MonoTouch.Dialog", "https://github.com/migueldeicaza/MonoTouch.Dialog"),
202  new HtmlElement ("About NUnitLite", "http://www.nunitlite.org"),
203  new HtmlElement ("About Font Awesome", "http://fortawesome.github.com/Font-Awesome")
204  }
205  };
206 
207  var dv = new DialogViewController (root, true) { Autorotate = true };
208  NavigationController.PushViewController (dv, true);
209  }
210 
211  #region writer
212 
213  public TestResult Result { get; set; }
214 
215  public TextWriter Writer { get; set; }
216 
217  static string SelectHostName (string[] names, int port)
218  {
219  if (names.Length == 0)
220  return null;
221 
222  if (names.Length == 1)
223  return names [0];
224 
225  object lock_obj = new object ();
226  string result = null;
227  int failures = 0;
228 
229  using (var evt = new ManualResetEvent (false)) {
230  for (int i = names.Length - 1; i >= 0; i--) {
231  var name = names [i];
232  ThreadPool.QueueUserWorkItem ((v) =>
233  {
234  try {
235  var client = new TcpClient (name, port);
236  using (var writer = new StreamWriter (client.GetStream ())) {
237  writer.WriteLine ("ping");
238  }
239  lock (lock_obj) {
240  if (result == null)
241  result = name;
242  }
243  evt.Set ();
244  } catch (Exception) {
245  lock (lock_obj) {
246  failures++;
247  if (failures == names.Length)
248  evt.Set ();
249  }
250  }
251  });
252  }
253 
254  // Wait for 1 success or all failures
255  evt.WaitOne ();
256  }
257 
258  return result;
259  }
260 
261  public bool OpenWriter (string message)
262  {
263  TouchOptions options = TouchOptions.Current;
264  DateTime now = DateTime.Now;
265  // let the application provide it's own TextWriter to ease automation with AutoStart property
266  if (Writer == null) {
267  //if (options.ShowUseNetworkLogger) {
268  // var hostname = SelectHostName (options.HostName.Split (','), options.HostPort);
269  //
270  // if (hostname != null) {
271  // Console.WriteLine ("[{0}] Sending '{1}' results to {2}:{3}", now, message, hostname, options.HostPort);
272  // try {
273  // Writer = new TcpTextWriter (hostname, options.HostPort);
274  // }
275  // catch (SocketException) {
276  // UIAlertView alert = new UIAlertView ("Network Error",
277  // String.Format ("Cannot connect to {0}:{1}. Continue on console ?", hostname, options.HostPort),
278  // null, "Cancel", "Continue");
279  // int button = -1;
280  // alert.Clicked += delegate(object sender, UIButtonEventArgs e) {
281  // button = (int)e.ButtonIndex;
282  // };
283  // alert.Show ();
284  // while (button == -1)
285  // NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.5));
286  // Console.WriteLine (button);
287  // Console.WriteLine ("[Host unreachable: {0}]", button == 0 ? "Execution cancelled" : "Switching to console output");
288  // if (button == 0)
289  // return false;
290  // else
291  // Writer = Console.Out;
292  // }
293  // }
294  //} else {
295  Writer = Console.Out;
296  //}
297  }
298 
299  Writer.WriteLine ("[Runner executing:\t{0}]", message);
300  Writer.WriteLine ("[MonoTouch Version:\t{0}]", Constants.Version);
301  Writer.WriteLine ("[Assembly:\t{0}.dll ({1} bits)]", typeof (NSObject).Assembly.GetName ().Name, IntPtr.Size * 8);
302  Writer.WriteLine ("[GC:\t{0}{1}]", GC.MaxGeneration == 0 ? "Boehm": "sgen",
303  NSObject.IsNewRefcountEnabled () ? "+NewRefCount" : String.Empty);
304  UIDevice device = UIDevice.CurrentDevice;
305  Writer.WriteLine ("[{0}:\t{1} v{2}]", device.Model, device.SystemName, device.SystemVersion);
306  Writer.WriteLine ("[Device Name:\t{0}]", device.Name);
307  Writer.WriteLine ("[Device UDID:\t{0}]", UniqueIdentifier);
308  Writer.WriteLine ("[Device Locale:\t{0}]", NSLocale.CurrentLocale.Identifier);
309  Writer.WriteLine ("[Device Date/Time:\t{0}]", now); // to match earlier C.WL output
310 
311  Writer.WriteLine ("[Bundle:\t{0}]", NSBundle.MainBundle.BundleIdentifier);
312  // FIXME: add data about how the app was compiled (e.g. ARMvX, LLVM, GC and Linker options)
313  passed = 0;
314  ignored = 0;
315  failed = 0;
316  inconclusive = 0;
317  return true;
318  }
319 
320  [System.Runtime.InteropServices.DllImport ("/usr/lib/libobjc.dylib")]
321  static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector);
322 
323  // Apple blacklisted `uniqueIdentifier` (for the appstore) but it's still
324  // something useful to have inside the test logs
325  static string UniqueIdentifier {
326  get {
327  IntPtr handle = UIDevice.CurrentDevice.Handle;
328  if (UIDevice.CurrentDevice.RespondsToSelector (new Selector ("uniqueIdentifier")))
329  return NSString.FromHandle (objc_msgSend (handle, Selector.GetHandle("uniqueIdentifier")));
330  return "unknown";
331  }
332  }
333 
334  public void CloseWriter ()
335  {
336  int total = passed + inconclusive + failed; // ignored are *not* run
337  Writer.WriteLine ("Tests run: {0} Passed: {1} Inconclusive: {2} Failed: {3} Ignored: {4}", total, passed, inconclusive, failed, ignored);
338 
339  Writer.Close ();
340  Writer = null;
341  }
342 
343  #endregion
344 
345  Dictionary<TestSuite, TouchViewController> suites_dvc = new Dictionary<TestSuite, TouchViewController> ();
346  Dictionary<TestSuite, TestSuiteElement> suite_elements = new Dictionary<TestSuite, TestSuiteElement> ();
347  Dictionary<TestMethod, TestCaseElement> case_elements = new Dictionary<TestMethod, TestCaseElement> ();
348 
349  public void Show (TestSuite suite)
350  {
351  NavigationController.PushViewController (suites_dvc [suite], true);
352  }
353 
354  TestSuiteElement Setup (TestSuite suite)
355  {
356  while (suite.Tests.Count == 1 && (suite.Tests[0] is TestSuite))
357  suite = (TestSuite)suite.Tests[0];
358 
359  TestSuiteElement tse = new TestSuiteElement (suite, this);
360  suite_elements.Add (suite, tse);
361 
362  var root = new RootElement ("Tests");
363 
364  Section section = new Section (suite.Name);
365  foreach (ITest test in suite.Tests) {
366  TestSuite ts = (test as TestSuite);
367  if (ts != null) {
368  section.Add (Setup (ts));
369  } else {
370  TestMethod tc = (test as TestMethod);
371  if (tc != null) {
372  section.Add (Setup (tc));
373  } else {
374  throw new NotImplementedException (test.GetType ().ToString ());
375  }
376  }
377  }
378 
379  root.Add (section);
380 
381  if (section.Count > 1) {
382  Section options = new Section () {
383  new StringElement ("Run all", async delegate () {
384  if (OpenWriter (suite.Name)) {
385  await Run (suite);
386  CloseWriter ();
387  suites_dvc [suite].Filter ();
388  }
389  })
390  };
391  root.Add (options);
392  }
393 
394  suites_dvc.Add (suite, new TouchViewController (root));
395  return tse;
396  }
397 
398  TestCaseElement Setup (TestMethod test)
399  {
400  TestCaseElement tce = new TestCaseElement (test, this);
401  case_elements.Add (test, tce);
402  return tce;
403  }
404 
405  public void TestStarted (ITest test)
406  {
407  if (test is TestSuite) {
408  Writer.WriteLine ();
409  Writer.WriteLine (test.Name);
410  }
411  }
412 
413  public void TestFinished(ITestResult r)
414  {
415  UIApplication.SharedApplication.InvokeOnMainThread(() => UpdateTestResult(r));
416  }
417 
418  public void UpdateTestResult(ITestResult r)
419  {
420  TestResult result = r as TestResult;
421  TestSuite ts = result.Test as TestSuite;
422  if (ts != null) {
423  TestSuiteElement tse;
424  if (suite_elements.TryGetValue (ts, out tse))
425  tse.Update (result);
426  } else {
427  TestMethod tc = result.Test as TestMethod;
428  if (tc != null)
429  case_elements [tc].Update (result);
430  }
431 
432  if (result.Test is TestSuite) {
433  if (!result.IsFailure () && !result.IsSuccess () && !result.IsInconclusive () && !result.IsIgnored ())
434  Writer.WriteLine ("\t[INFO] {0}", result.Message);
435 
436  string name = result.Test.Name;
437  if (!String.IsNullOrEmpty (name))
438  Writer.WriteLine ("{0} : {1} ms", name, result.Duration.TotalMilliseconds);
439  } else {
440  if (result.IsSuccess ()) {
441  Writer.Write ("\t[PASS] ");
442  passed++;
443  } else if (result.IsIgnored ()) {
444  Writer.Write ("\t[IGNORED] ");
445  ignored++;
446  } else if (result.IsFailure ()) {
447  Writer.Write ("\t[FAIL] ");
448  failed++;
449  } else if (result.IsInconclusive ()) {
450  Writer.Write ("\t[INCONCLUSIVE] ");
451  inconclusive++;
452  } else {
453  Writer.Write ("\t[INFO] ");
454  }
455  Writer.Write (result.Test.Name);
456 
457  string message = result.Message;
458  if (!String.IsNullOrEmpty (message)) {
459  Writer.Write (" : {0}", message.Replace ("\r\n", "\\r\\n"));
460  }
461  Writer.WriteLine ();
462 
463  string stacktrace = result.StackTrace;
464  if (!String.IsNullOrEmpty (result.StackTrace)) {
465  string[] lines = stacktrace.Split (new char [] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
466  foreach (string line in lines)
467  Writer.WriteLine ("\t\t{0}", line);
468  }
469  }
470  }
471 
472  NamespaceAssemblyBuilder builder = new NamespaceAssemblyBuilder(new NUnitLiteTestAssemblyBuilder());
473  Dictionary<string, object> empty = new Dictionary<string, object> ();
474 
475  public bool Load (string assemblyName, IDictionary settings)
476  {
477  return AddSuite (builder.Build (assemblyName, settings ?? empty));
478  }
479 
480  public bool Load (Assembly assembly, IDictionary settings)
481  {
482  return AddSuite (builder.Build (assembly, settings ?? empty));
483  }
484 
485  bool AddSuite (TestSuite ts)
486  {
487  if (ts == null)
488  return false;
489  suite.Add (ts);
490  return true;
491  }
492 
493  public Task<TestResult> Run (Test test)
494  {
495  return Task.Run(() =>
496  {
497  Result = null;
498  TestExecutionContext current = TestExecutionContext.CurrentContext;
499  current.WorkDirectory = Environment.CurrentDirectory;
500  //current.Listener = this; // Internal on Android
501  current.GetType().GetField("listener", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(current, this);
502  current.TestObject = test is TestSuite ? null : Reflect.Construct((test as TestMethod).Method.ReflectedType, null);
503  WorkItem wi = test.CreateWorkItem(filter);
504  wi.Execute(current);
505  Result = wi.Result;
506  return Result;
507  });
508  }
509 
510  public ITest LoadedTest {
511  get {
512  return suite;
513  }
514  }
515 
516  public void TestOutput (TestOutput testOutput)
517  {
518  }
519  }
520 }
bool Load(Assembly assembly, IDictionary settings)
Definition: TouchRunner.cs:480
bool Load(string assemblyName, IDictionary settings)
Definition: TouchRunner.cs:475
global::MonoTouch.Constants Constants
Definition: TouchRunner.cs:42