1 // ***********************************************************************
2 // Copyright (c) 2009-2014 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Diagnostics;
34 using System.Globalization;
36 using System.Reflection;
39 using NUnit.Compatibility;
40 using NUnit.Framework.Interfaces;
41 using NUnit.Framework.Internal;
43 namespace NUnit.Framework.Api
46 /// FrameworkController provides a facade for use in loading, browsing
47 /// and running tests without requiring a reference to the NUnit
48 /// framework. All calls are encapsulated in constructors for
49 /// this class and its nested classes, which only require the
50 /// types of the Common Type System as arguments.
52 /// The controller supports four actions: Load, Explore, Count and Run.
53 /// They are intended to be called by a driver, which should allow for
54 /// proper sequencing of calls. Load must be called before any of the
55 /// other actions. The driver may support other actions, such as
56 /// reload on run, by combining these calls.
59 public class FrameworkController : LongLivedMarshalByRefObject
61 #if !PORTABLE && !SILVERLIGHT
62 private const string LOG_FILE_FORMAT = "InternalTrace.{0}.{1}.log";
65 // Pre-loaded test assembly, if passed in constructor
66 private readonly Assembly _testAssembly;
71 /// Construct a FrameworkController using the default builder and runner.
73 /// <param name="assemblyNameOrPath">The AssemblyName or path to the test assembly</param>
74 /// <param name="idPrefix">A prefix used for all test ids created under this controller.</param>
75 /// <param name="settings">A Dictionary of settings to use in loading and running the tests</param>
76 public FrameworkController(string assemblyNameOrPath, string idPrefix, IDictionary settings)
78 this.Builder = new DefaultTestAssemblyBuilder();
79 this.Runner = new NUnitTestAssemblyRunner(this.Builder);
81 Test.IdPrefix = idPrefix;
82 Initialize(assemblyNameOrPath, settings);
86 /// Construct a FrameworkController using the default builder and runner.
88 /// <param name="assembly">The test assembly</param>
89 /// <param name="idPrefix">A prefix used for all test ids created under this controller.</param>
90 /// <param name="settings">A Dictionary of settings to use in loading and running the tests</param>
91 public FrameworkController(Assembly assembly, string idPrefix, IDictionary settings)
92 : this(assembly.FullName, idPrefix, settings)
94 _testAssembly = assembly;
98 /// Construct a FrameworkController, specifying the types to be used
99 /// for the runner and builder. This constructor is provided for
100 /// purposes of development.
102 /// <param name="assemblyNameOrPath">The full AssemblyName or the path to the test assembly</param>
103 /// <param name="idPrefix">A prefix used for all test ids created under this controller.</param>
104 /// <param name="settings">A Dictionary of settings to use in loading and running the tests</param>
105 /// <param name="runnerType">The Type of the test runner</param>
106 /// <param name="builderType">The Type of the test builder</param>
107 public FrameworkController(string assemblyNameOrPath, string idPrefix, IDictionary settings, string runnerType, string builderType)
109 Builder = (ITestAssemblyBuilder)Reflect.Construct(Type.GetType(builderType));
110 Runner = (ITestAssemblyRunner)Reflect.Construct(Type.GetType(runnerType), new object[] { Builder });
112 Test.IdPrefix = idPrefix ?? "";
113 Initialize(assemblyNameOrPath, settings);
117 /// Construct a FrameworkController, specifying the types to be used
118 /// for the runner and builder. This constructor is provided for
119 /// purposes of development.
121 /// <param name="assembly">The test assembly</param>
122 /// <param name="idPrefix">A prefix used for all test ids created under this controller.</param>
123 /// <param name="settings">A Dictionary of settings to use in loading and running the tests</param>
124 /// <param name="runnerType">The Type of the test runner</param>
125 /// <param name="builderType">The Type of the test builder</param>
126 public FrameworkController(Assembly assembly, string idPrefix, IDictionary settings, string runnerType, string builderType)
127 : this(assembly.FullName, idPrefix, settings, runnerType, builderType)
129 _testAssembly = assembly;
132 private void Initialize(string assemblyPath, IDictionary settings)
134 AssemblyNameOrPath = assemblyPath;
136 var newSettings = settings as IDictionary<string, object>;
137 Settings = newSettings ?? settings.Cast<DictionaryEntry>().ToDictionary(de => (string)de.Key, de => de.Value);
139 if (Settings.ContainsKey(PackageSettings.InternalTraceLevel))
141 var traceLevel = (InternalTraceLevel)Enum.Parse(typeof(InternalTraceLevel), (string)Settings[PackageSettings.InternalTraceLevel], true);
143 if (Settings.ContainsKey(PackageSettings.InternalTraceWriter))
144 InternalTrace.Initialize((TextWriter)Settings[PackageSettings.InternalTraceWriter], traceLevel);
145 #if !PORTABLE && !SILVERLIGHT
148 var workDirectory = Settings.ContainsKey(PackageSettings.WorkDirectory) ? (string)Settings[PackageSettings.WorkDirectory] : Env.DefaultWorkDirectory;
149 var logName = string.Format(LOG_FILE_FORMAT, Process.GetCurrentProcess().Id, Path.GetFileName(assemblyPath));
150 InternalTrace.Initialize(Path.Combine(workDirectory, logName), traceLevel);
161 /// Gets the ITestAssemblyBuilder used by this controller instance.
163 /// <value>The builder.</value>
164 public ITestAssemblyBuilder Builder { get; private set; }
167 /// Gets the ITestAssemblyRunner used by this controller instance.
169 /// <value>The runner.</value>
170 public ITestAssemblyRunner Runner { get; private set; }
173 /// Gets the AssemblyName or the path for which this FrameworkController was created
175 public string AssemblyNameOrPath { get; private set; }
178 /// Gets the Assembly for which this
180 public Assembly Assembly { get; private set; }
183 /// Gets a dictionary of settings for the FrameworkController
185 internal IDictionary<string, object> Settings { get; private set; }
189 #region Public Action methods Used by nunit.driver for running portable tests
192 /// Loads the tests in the assembly
194 /// <returns></returns>
195 public string LoadTests()
197 if (_testAssembly != null)
198 Runner.Load(_testAssembly, Settings);
200 Runner.Load(AssemblyNameOrPath, Settings);
202 return Runner.LoadedTest.ToXml(false).OuterXml;
206 /// Returns info about the tests in an assembly
208 /// <param name="filter">A string containing the XML representation of the filter to use</param>
209 /// <returns>The XML result of exploring the tests</returns>
210 public string ExploreTests(string filter)
212 Guard.ArgumentNotNull(filter, "filter");
214 if (Runner.LoadedTest == null)
215 throw new InvalidOperationException("The Explore method was called but no test has been loaded");
217 // TODO: Make use of the filter
218 return Runner.LoadedTest.ToXml(true).OuterXml;
222 /// Runs the tests in an assembly
224 /// <param name="filter">A string containing the XML representation of the filter to use</param>
225 /// <returns>The XML result of the test run</returns>
226 public string RunTests(string filter)
228 Guard.ArgumentNotNull(filter, "filter");
230 TNode result = Runner.Run(new TestProgressReporter(null), TestFilter.FromXml(filter)).ToXml(true);
232 // Insert elements as first child in reverse order
233 if (Settings != null) // Some platforms don't have settings
234 InsertSettingsElement(result, Settings);
235 #if !PORTABLE && !SILVERLIGHT
236 InsertEnvironmentElement(result);
239 // Ensure that the CallContext of the thread is not polluted
240 // by our TestExecutionContext, which is not serializable.
241 TestExecutionContext.ClearCurrentContext();
243 return result.OuterXml;
248 class ActionCallback : ICallbackEventHandler
250 readonly Action<string> _callback;
252 public ActionCallback(Action<string> callback)
254 _callback = callback;
257 public string GetCallbackResult()
259 throw new NotImplementedException();
262 public void RaiseCallbackEvent(string report)
264 if(_callback != null)
265 _callback.Invoke(report);
270 /// Runs the tests in an assembly syncronously reporting back the test results through the callback
271 /// or through the return value
273 /// <param name="callback">The callback that receives the test results</param>
274 /// <param name="filter">A string containing the XML representation of the filter to use</param>
275 /// <returns>The XML result of the test run</returns>
276 public string RunTests(Action<string> callback, string filter)
278 Guard.ArgumentNotNull(filter, "filter");
280 var handler = new ActionCallback(callback);
282 TNode result = Runner.Run(new TestProgressReporter(handler), TestFilter.FromXml(filter)).ToXml(true);
284 // Insert elements as first child in reverse order
285 if (Settings != null) // Some platforms don't have settings
286 InsertSettingsElement(result, Settings);
287 #if !PORTABLE && !SILVERLIGHT
288 InsertEnvironmentElement(result);
291 // Ensure that the CallContext of the thread is not polluted
292 // by our TestExecutionContext, which is not serializable.
293 TestExecutionContext.ClearCurrentContext();
295 return result.OuterXml;
299 /// Runs the tests in an assembly asyncronously reporting back the test results through the callback
301 /// <param name="callback">The callback that receives the test results</param>
302 /// <param name="filter">A string containing the XML representation of the filter to use</param>
303 private void RunAsync(Action<string> callback, string filter)
305 Guard.ArgumentNotNull(filter, "filter");
307 var handler = new ActionCallback(callback);
309 Runner.RunAsync(new TestProgressReporter(handler), TestFilter.FromXml(filter));
314 /// Stops the test run
316 /// <param name="force">True to force the stop, false for a cooperative stop</param>
317 public void StopRun(bool force)
319 Runner.StopRun(force);
323 /// Counts the number of test cases in the loaded TestSuite
325 /// <param name="filter">A string containing the XML representation of the filter to use</param>
326 /// <returns>The number of tests</returns>
327 public int CountTests(string filter)
329 Guard.ArgumentNotNull(filter, "filter");
331 return Runner.CountTestCases(TestFilter.FromXml(filter));
336 #region Private Action Methods Used by Nested Classes
338 private void LoadTests(ICallbackEventHandler handler)
340 handler.RaiseCallbackEvent(LoadTests());
343 private void ExploreTests(ICallbackEventHandler handler, string filter)
345 Guard.ArgumentNotNull(filter, "filter");
347 if (Runner.LoadedTest == null)
348 throw new InvalidOperationException("The Explore method was called but no test has been loaded");
350 // TODO: Make use of the filter
351 handler.RaiseCallbackEvent(Runner.LoadedTest.ToXml(true).OuterXml);
354 private void RunTests(ICallbackEventHandler handler, string filter)
356 Guard.ArgumentNotNull(filter, "filter");
358 TNode result = Runner.Run(new TestProgressReporter(handler), TestFilter.FromXml(filter)).ToXml(true);
360 // Insert elements as first child in reverse order
361 if (Settings != null) // Some platforms don't have settings
362 InsertSettingsElement(result, Settings);
363 #if !PORTABLE && !SILVERLIGHT
364 InsertEnvironmentElement(result);
367 // Ensure that the CallContext of the thread is not polluted
368 // by our TestExecutionContext, which is not serializable.
369 TestExecutionContext.ClearCurrentContext();
371 handler.RaiseCallbackEvent(result.OuterXml);
374 private void RunAsync(ICallbackEventHandler handler, string filter)
376 Guard.ArgumentNotNull(filter, "filter");
378 Runner.RunAsync(new TestProgressReporter(handler), TestFilter.FromXml(filter));
381 private void StopRun(ICallbackEventHandler handler, bool force)
386 private void CountTests(ICallbackEventHandler handler, string filter)
388 handler.RaiseCallbackEvent(CountTests(filter).ToString());
391 #if !PORTABLE && !SILVERLIGHT
393 /// Inserts environment element
395 /// <param name="targetNode">Target node</param>
396 /// <returns>The new node</returns>
397 public static TNode InsertEnvironmentElement(TNode targetNode)
399 TNode env = new TNode("environment");
400 targetNode.ChildNodes.Insert(0, env);
402 env.AddAttribute("framework-version", Assembly.GetExecutingAssembly().GetName().Version.ToString());
403 env.AddAttribute("clr-version", Environment.Version.ToString());
404 env.AddAttribute("os-version", Environment.OSVersion.ToString());
405 env.AddAttribute("platform", Environment.OSVersion.Platform.ToString());
407 env.AddAttribute("cwd", Environment.CurrentDirectory);
408 env.AddAttribute("machine-name", Environment.MachineName);
409 env.AddAttribute("user", Environment.UserName);
410 env.AddAttribute("user-domain", Environment.UserDomainName);
412 env.AddAttribute("culture", CultureInfo.CurrentCulture.ToString());
413 env.AddAttribute("uiculture", CultureInfo.CurrentUICulture.ToString());
414 env.AddAttribute("os-architecture", GetProcessorArchitecture());
419 private static string GetProcessorArchitecture()
421 return IntPtr.Size == 8 ? "x64" : "x86";
426 /// Inserts settings element
428 /// <param name="targetNode">Target node</param>
429 /// <param name="settings">Settings dictionary</param>
430 /// <returns>The new node</returns>
431 public static TNode InsertSettingsElement(TNode targetNode, IDictionary<string, object> settings)
433 TNode settingsNode = new TNode("settings");
434 targetNode.ChildNodes.Insert(0, settingsNode);
436 foreach (string key in settings.Keys)
437 AddSetting(settingsNode, key, settings[key]);
440 // Add default values for display
441 if (!settings.ContainsKey(PackageSettings.NumberOfTestWorkers))
442 AddSetting(settingsNode, PackageSettings.NumberOfTestWorkers, NUnitTestAssemblyRunner.DefaultLevelOfParallelism);
448 private static void AddSetting(TNode settingsNode, string name, object value)
450 TNode setting = new TNode("setting");
451 setting.AddAttribute("name", name);
452 setting.AddAttribute("value", value.ToString());
454 settingsNode.ChildNodes.Add(setting);
459 #region Nested Action Classes
461 #region TestContollerAction
464 /// FrameworkControllerAction is the base class for all actions
465 /// performed against a FrameworkController.
467 public abstract class FrameworkControllerAction : LongLivedMarshalByRefObject
473 #region LoadTestsAction
476 /// LoadTestsAction loads a test into the FrameworkController
478 public class LoadTestsAction : FrameworkControllerAction
481 /// LoadTestsAction loads the tests in an assembly.
483 /// <param name="controller">The controller.</param>
484 /// <param name="handler">The callback handler.</param>
485 public LoadTestsAction(FrameworkController controller, object handler)
487 controller.LoadTests((ICallbackEventHandler)handler);
493 #region ExploreTestsAction
496 /// ExploreTestsAction returns info about the tests in an assembly
498 public class ExploreTestsAction : FrameworkControllerAction
501 /// Initializes a new instance of the <see cref="ExploreTestsAction"/> class.
503 /// <param name="controller">The controller for which this action is being performed.</param>
504 /// <param name="filter">Filter used to control which tests are included (NYI)</param>
505 /// <param name="handler">The callback handler.</param>
506 public ExploreTestsAction(FrameworkController controller, string filter, object handler)
508 controller.ExploreTests((ICallbackEventHandler)handler, filter);
514 #region CountTestsAction
517 /// CountTestsAction counts the number of test cases in the loaded TestSuite
518 /// held by the FrameworkController.
520 public class CountTestsAction : FrameworkControllerAction
523 /// Construct a CountsTestAction and perform the count of test cases.
525 /// <param name="controller">A FrameworkController holding the TestSuite whose cases are to be counted</param>
526 /// <param name="filter">A string containing the XML representation of the filter to use</param>
527 /// <param name="handler">A callback handler used to report results</param>
528 public CountTestsAction(FrameworkController controller, string filter, object handler)
530 controller.CountTests((ICallbackEventHandler)handler, filter);
536 #region RunTestsAction
539 /// RunTestsAction runs the loaded TestSuite held by the FrameworkController.
541 public class RunTestsAction : FrameworkControllerAction
544 /// Construct a RunTestsAction and run all tests in the loaded TestSuite.
546 /// <param name="controller">A FrameworkController holding the TestSuite to run</param>
547 /// <param name="filter">A string containing the XML representation of the filter to use</param>
548 /// <param name="handler">A callback handler used to report results</param>
549 public RunTestsAction(FrameworkController controller, string filter, object handler)
551 controller.RunTests((ICallbackEventHandler)handler, filter);
557 #region RunAsyncAction
560 /// RunAsyncAction initiates an asynchronous test run, returning immediately
562 public class RunAsyncAction : FrameworkControllerAction
565 /// Construct a RunAsyncAction and run all tests in the loaded TestSuite.
567 /// <param name="controller">A FrameworkController holding the TestSuite to run</param>
568 /// <param name="filter">A string containing the XML representation of the filter to use</param>
569 /// <param name="handler">A callback handler used to report results</param>
570 public RunAsyncAction(FrameworkController controller, string filter, object handler)
572 controller.RunAsync((ICallbackEventHandler)handler, filter);
578 #region StopRunAction
581 /// StopRunAction stops an ongoing run.
583 public class StopRunAction : FrameworkControllerAction
586 /// Construct a StopRunAction and stop any ongoing run. If no
587 /// run is in process, no error is raised.
589 /// <param name="controller">The FrameworkController for which a run is to be stopped.</param>
590 /// <param name="force">True the stop should be forced, false for a cooperative stop.</param>
591 /// <param name="handler">>A callback handler used to report results</param>
592 /// <remarks>A forced stop will cause threads and processes to be killed as needed.</remarks>
593 public StopRunAction(FrameworkController controller, bool force, object handler)
595 controller.StopRun((ICallbackEventHandler)handler, force);