2
// ExternalTestRunner.cs
7
// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
9
// Permission is hereby granted, free of charge, to any person obtaining
10
// a copy of this software and associated documentation files (the
11
// "Software"), to deal in the Software without restriction, including
12
// without limitation the rights to use, copy, modify, merge, publish,
13
// distribute, sublicense, and/or sell copies of the Software, and to
14
// permit persons to whom the Software is furnished to do so, subject to
15
// the following conditions:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31
using System.Reflection;
33
using System.Collections;
34
using System.Collections.Generic;
35
using System.Threading;
38
using MonoDevelop.Core;
39
using MonoDevelop.Core.Execution;
42
using NF = NUnit.Framework;
43
using NC = NUnit.Core;
45
namespace MonoDevelop.NUnit.External
47
class ExternalTestRunner: RemoteProcessObject
49
NUnitTestRunner runner;
51
public ExternalTestRunner ( )
53
// In some cases MS.NET can't properly resolve assemblies even if they
54
// are already loaded. For example, when deserializing objects from remoting.
55
AppDomain.CurrentDomain.AssemblyResolve += delegate (object s, ResolveEventArgs args) {
56
foreach (Assembly am in AppDomain.CurrentDomain.GetAssemblies ()) {
57
if (am.GetName ().FullName == args.Name)
63
// Preload the runner assembly. Required because TestNameFilter is implemented there
64
string asm = Path.Combine (Path.GetDirectoryName (GetType ().Assembly.Location), "NUnitRunner.dll");
65
Assembly.LoadFrom (asm);
68
public UnitTestResult Run (IRemoteEventListener listener, ITestFilter filter, string path, string suiteName, List<string> supportAssemblies)
70
NUnitTestRunner runner = GetRunner (path);
71
EventListenerWrapper listenerWrapper = listener != null ? new EventListenerWrapper (listener) : null;
73
TestResult res = runner.Run (listenerWrapper, filter, path, suiteName, supportAssemblies);
74
return listenerWrapper.GetLocalTestResult (res);
77
public NunitTestInfo GetTestInfo (string path, List<string> supportAssemblies)
79
NUnitTestRunner runner = GetRunner (path);
80
return runner.GetTestInfo (path, supportAssemblies);
83
NUnitTestRunner GetRunner (string assemblyPath)
85
TestPackage package = new TestPackage (assemblyPath);
86
package.Settings ["ShadowCopyFiles"] = false;
87
DomainManager dm = new DomainManager ();
88
AppDomain domain = dm.CreateDomain (package);
89
string asm = Path.Combine (Path.GetDirectoryName (GetType ().Assembly.Location), "NUnitRunner.dll");
90
runner = (NUnitTestRunner) domain.CreateInstanceFromAndUnwrap (asm, "MonoDevelop.NUnit.External.NUnitTestRunner");
91
runner.Initialize (typeof(NF.Assert).Assembly.Location, typeof(NC.Test).Assembly.Location);
96
class EventListenerWrapper: MarshalByRefObject, EventListener
98
IRemoteEventListener wrapped;
99
StringBuilder consoleOutput;
100
StringBuilder consoleError;
102
public EventListenerWrapper (IRemoteEventListener wrapped)
104
this.wrapped = wrapped;
107
public void RunFinished (Exception exception)
111
public void RunFinished (TestResult results)
115
public void RunStarted (string name, int testCount)
119
public void SuiteFinished (TestSuiteResult result)
122
wrapped.SuiteFinished (GetTestName (result.Test), GetLocalTestResult (result));
124
Stack<string> testSuites = new Stack<string>();
125
public void SuiteStarted (TestName suite)
127
testSuites.Push (suite.FullName);
128
wrapped.SuiteStarted (GetTestName (suite));
131
public void TestFinished (TestCaseResult result)
133
wrapped.TestFinished (GetTestName (result.Test), GetLocalTestResult (result));
136
public void TestOutput (TestOutput testOutput)
138
if (consoleOutput == null) {
139
Console.WriteLine (testOutput.Text);
142
else if (testOutput.Type == TestOutputType.Out)
143
consoleOutput.Append (testOutput.Text);
145
consoleError.Append (testOutput.Text);
148
public void TestStarted (TestName testCase)
150
wrapped.TestStarted (GetTestName (testCase));
151
consoleOutput = new StringBuilder ();
152
consoleError = new StringBuilder ();
155
public override object InitializeLifetimeService ()
160
string GetTestName (ITest t)
164
// Theoretically t.TestName.FullName should work, but when a test class inherits from a base
165
// class that contains tests the full name is that one of the base class, which is wrong.
166
// I suspect that is a NUnit bug, when this is fixed this code should be overworked and the testSuites stack be removed.
167
// see: Bug 677228 - RemotingException isn't counted as failure
168
if (t.TestType != "Test Case" || testSuites.Count == 0)
169
return t.TestName.FullName;
170
return testSuites.Peek () + "." + t.TestName.Name;
173
public string GetTestName (TestName t)
180
public UnitTestResult GetLocalTestResult (TestResult t)
182
UnitTestResult res = new UnitTestResult ();
183
res.Message = t.Message;
185
if (t is TestSuiteResult) {
187
CountResults ((TestSuiteResult)t, ref s, ref f, ref i);
188
res.TotalFailures = f;
189
res.TotalSuccess = s;
190
res.TotalIgnored = i;
192
res.Status |= ResultStatus.Failure;
194
res.Status |= ResultStatus.Success;
196
res.Status |= ResultStatus.Ignored;
199
res.Status = ResultStatus.Failure;
200
res.TotalFailures = 1;
202
else if (!t.Executed) {
203
res.Status = ResultStatus.Ignored;
204
res.TotalIgnored = 1;
207
res.Status = ResultStatus.Success;
208
res.TotalSuccess = 1;
211
if (string.IsNullOrEmpty (res.Message)) {
213
res.Message = GettextCatalog.GetString ("Test failed");
214
else if (!t.Executed)
215
res.Message = GettextCatalog.GetString ("Test ignored");
217
res.Message = GettextCatalog.GetString ("Test successful") + "\n\n";
218
res.Message += GettextCatalog.GetString ("Execution time: {0:0.00}ms", t.Time);
222
res.StackTrace = t.StackTrace;
223
res.Time = TimeSpan.FromSeconds (t.Time);
225
if (consoleOutput != null) {
226
res.ConsoleOutput = consoleOutput.ToString ();
227
res.ConsoleError = consoleError.ToString ();
228
consoleOutput = null;
235
void CountResults (TestSuiteResult ts, ref int s, ref int f, ref int i)
237
if (ts.Results == null)
240
foreach (TestResult t in ts.Results) {
241
if (t is TestCaseResult) {
244
else if (!t.Executed)
248
} else if (t is TestSuiteResult) {
249
CountResults ((TestSuiteResult) t, ref s, ref f, ref i);
254
public void UnhandledException (Exception exception)
259
interface IRemoteEventListener
261
void TestStarted (string testCase);
262
void TestFinished (string test, UnitTestResult result);
263
void SuiteStarted (string suite);
264
void SuiteFinished (string suite, UnitTestResult result);
267
class LocalTestMonitor: MarshalByRefObject, IRemoteEventListener
272
UnitTest runningTest;
274
UnitTestResult singleTestResult;
275
public bool Canceled;
277
public LocalTestMonitor (TestContext context, ExternalTestRunner runner, UnitTest rootTest, string rootFullName, bool singleTestRun)
279
this.rootFullName = rootFullName;
280
this.rootTest = rootTest;
281
this.context = context;
282
this.singleTestRun = singleTestRun;
285
public UnitTest RunningTest {
286
get { return runningTest; }
289
internal UnitTestResult SingleTestResult {
291
if (singleTestResult == null)
292
singleTestResult = new UnitTestResult ();
293
return singleTestResult;
296
singleTestResult = value;
300
void IRemoteEventListener.TestStarted (string testCase)
302
if (singleTestRun || Canceled)
305
UnitTest t = GetLocalTest (testCase);
310
context.Monitor.BeginTest (t);
311
t.Status = TestStatus.Running;
314
void IRemoteEventListener.TestFinished (string test, UnitTestResult result)
319
SingleTestResult = result;
323
UnitTest t = GetLocalTest (test);
327
t.RegisterResult (context, result);
328
context.Monitor.EndTest (t, result);
329
t.Status = TestStatus.Ready;
333
void IRemoteEventListener.SuiteStarted (string suite)
335
if (singleTestRun || Canceled)
338
UnitTest t = GetLocalTest (suite);
342
t.Status = TestStatus.Running;
343
context.Monitor.BeginTest (t);
346
void IRemoteEventListener.SuiteFinished (string suite, UnitTestResult result)
348
if (singleTestRun || Canceled)
351
UnitTest t = GetLocalTest (suite);
355
t.RegisterResult (context, result);
356
t.Status = TestStatus.Ready;
357
context.Monitor.EndTest (t, result);
360
UnitTest GetLocalTest (string sname)
362
if (sname == null) return null;
363
if (sname == "<root>") return rootTest;
365
if (sname.StartsWith (rootFullName)) {
366
sname = sname.Substring (rootFullName.Length);
368
if (sname.StartsWith (".")) sname = sname.Substring (1);
369
UnitTest tt = FindTest (rootTest, sname);
373
UnitTest FindTest (UnitTest t, string testPath)
378
UnitTestGroup group = t as UnitTestGroup;
382
UnitTest returnTest = group.Tests [testPath];
383
if (returnTest != null)
386
string[] paths = testPath.Split (new char[] {'.'}, 2);
387
if (paths.Length == 2) {
388
string nextPathSection = paths[0];
389
string nextTestCandidate = paths[1];
391
UnitTest childTest = group.Tests [nextPathSection];
392
if (childTest != null)
393
return FindTest (childTest, nextTestCandidate);
398
public override object InitializeLifetimeService ()