/src/NUnit/util/AggregatingTestRunner.cs
C# | 453 lines | 323 code | 82 blank | 48 comment | 40 complexity | 5d47f6a52c08813f528ab18a445b7969 MD5 | raw file
1// **************************************************************** 2// Copyright 2007, Charlie Poole 3// This is free software licensed under the NUnit license. You may 4// obtain a copy of the license at http://nunit.org 5// **************************************************************** 6 7namespace NUnit.Util 8{ 9 using System; 10 using System.Collections; 11 using System.IO; 12 using NUnit.Core; 13 14 #region AggregatingTestRunner 15 /// <summary> 16 /// AggregatingTestRunner allows running multiple TestRunners 17 /// and combining the results. 18 /// </summary> 19 20 public abstract class AggregatingTestRunner : MarshalByRefObject, TestRunner, EventListener 21 { 22 private Logger log; 23 private Logger Log 24 { 25 get 26 { 27 if (log == null) 28 log = InternalTrace.GetLogger(this.GetType()); 29 30 return log; 31 } 32 } 33 34 static int AggregateTestID = 1000; 35 36 #region Instance Variables 37 38 /// <summary> 39 /// Our runner ID 40 /// </summary> 41 protected int runnerID; 42 43 /// <summary> 44 /// The downstream TestRunners 45 /// </summary> 46 protected ArrayList runners; 47 48 /// <summary> 49 /// Indicates whether we should run test assemblies in parallel 50 /// </summary> 51 private bool runInParallel; 52 53 /// <summary> 54 /// The loaded test suite 55 /// </summary> 56 protected TestNode aggregateTest; 57 58 /// <summary> 59 /// The result of the last run 60 /// </summary> 61 private TestResult testResult; 62 63 /// <summary> 64 /// The event listener for the currently running test 65 /// </summary> 66 protected EventListener listener; 67 68 protected TestName testName; 69 70 #endregion 71 72 #region Constructors 73 public AggregatingTestRunner() : this( 0 ) { } 74 public AggregatingTestRunner( int runnerID ) 75 { 76 this.runnerID = runnerID; 77 this.testName = new TestName(); 78 testName.TestID = new TestID( AggregateTestID ); 79 testName.RunnerID = this.runnerID; 80 testName.FullName = testName.Name = "Not Loaded"; 81 } 82 #endregion 83 84 #region Properties 85 86 public virtual int ID 87 { 88 get { return runnerID; } 89 } 90 91 public virtual bool Running 92 { 93 get 94 { 95 foreach( TestRunner runner in runners ) 96 if ( runner.Running ) 97 return true; 98 99 return false; 100 } 101 } 102 103 public virtual IList AssemblyInfo 104 { 105 get 106 { 107 ArrayList info = new ArrayList(); 108 foreach( TestRunner runner in runners ) 109 info.AddRange( runner.AssemblyInfo ); 110 return info; 111 } 112 } 113 114 public virtual ITest Test 115 { 116 get 117 { 118 if ( aggregateTest == null && runners != null ) 119 { 120 // Count non-null tests, in case we specified a fixture 121 int count = 0; 122 foreach( TestRunner runner in runners ) 123 if ( runner.Test != null ) 124 ++count; 125 126 // Copy non-null tests to an array 127 int index = 0; 128 ITest[] tests = new ITest[count]; 129 foreach( TestRunner runner in runners ) 130 if ( runner.Test != null ) 131 tests[index++] = runner.Test; 132 133 // Return master node containing all the tests 134 aggregateTest = new TestNode( testName, tests ); 135 } 136 137 return aggregateTest; 138 } 139 } 140 141 public virtual TestResult TestResult 142 { 143 get { return testResult; } 144 } 145 #endregion 146 147 #region Load and Unload Methods 148 public bool Load(TestPackage package) 149 { 150 Log.Info("Loading " + package.Name); 151 152 this.testName.FullName = this.testName.Name = package.FullName; 153 runners = new ArrayList(); 154 155 int nfound = 0; 156 int index = 0; 157 158 string targetAssemblyName = null; 159 if (package.TestName != null && package.Assemblies.Contains(package.TestName)) 160 { 161 targetAssemblyName = package.TestName; 162 package.TestName = null; 163 } 164 165 // NOTE: This is experimental. A normally created test package 166 // will never have this setting. 167 if (package.Settings.Contains("RunInParallel")) 168 { 169 this.runInParallel = true; 170 package.Settings.Remove("RunInParallel"); 171 } 172 173 //string basePath = package.BasePath; 174 //if (basePath == null) 175 // basePath = Path.GetDirectoryName(package.FullName); 176 177 //string configFile = package.ConfigurationFile; 178 //if (configFile == null && package.Name != null && !package.IsSingleAssembly) 179 // configFile = Path.ChangeExtension(package.Name, ".config"); 180 181 foreach (string assembly in package.Assemblies) 182 { 183 if (targetAssemblyName == null || targetAssemblyName == assembly) 184 { 185 TestRunner runner = CreateRunner(this.runnerID * 100 + index + 1); 186 187 TestPackage p = new TestPackage(assembly); 188 p.AutoBinPath = package.AutoBinPath; 189 p.ConfigurationFile = package.ConfigurationFile; 190 p.BasePath = package.BasePath; 191 p.PrivateBinPath = package.PrivateBinPath; 192 p.TestName = package.TestName; 193 foreach (object key in package.Settings.Keys) 194 p.Settings[key] = package.Settings[key]; 195 196 if (package.TestName == null) 197 { 198 runners.Add(runner); 199 if (runner.Load(p)) 200 nfound++; 201 } 202 else if (runner.Load(p)) 203 { 204 runners.Add(runner); 205 nfound++; 206 } 207 } 208 } 209 210 Log.Info("Load complete"); 211 212 if (package.TestName == null && targetAssemblyName == null) 213 return nfound == package.Assemblies.Count; 214 else 215 return nfound > 0; 216 } 217 218 protected abstract TestRunner CreateRunner(int runnerID); 219 220 public virtual void Unload() 221 { 222 if (aggregateTest != null) 223 { 224 Log.Info("Unloading " + Path.GetFileName(aggregateTest.TestName.Name)); 225 foreach (TestRunner runner in runners) 226 runner.Unload(); 227 aggregateTest = null; 228 Log.Info("Unload complete"); 229 } 230 } 231 #endregion 232 233 #region CountTestCases 234 public virtual int CountTestCases( ITestFilter filter ) 235 { 236 int count = 0; 237 foreach( TestRunner runner in runners ) 238 count += runner.CountTestCases( filter ); 239 return count; 240 } 241 #endregion 242 243 #region Methods for Running Tests 244 public virtual TestResult Run( EventListener listener ) 245 { 246 return Run( listener, TestFilter.Empty ); 247 } 248 249 // All forms of Run and BeginRun eventually come here 250 public virtual TestResult Run(EventListener listener, ITestFilter filter ) 251 { 252 Log.Info("Run - EventListener={0}", listener.GetType().Name); 253 254 // Save active listener for derived classes 255 this.listener = listener; 256 257 ITest[] tests = new ITest[runners.Count]; 258 for( int index = 0; index < runners.Count; index++ ) 259 tests[index] = ((TestRunner)runners[index]).Test; 260 261 string name = this.testName.Name; 262 int count = this.CountTestCases(filter); 263 Log.Info("Signalling RunStarted({0},{1})", name, count); 264 this.listener.RunStarted(name, count); 265 266 long startTime = DateTime.Now.Ticks; 267 268 TestResult result = new TestResult(new TestInfo(testName, tests)); 269 270 if (this.runInParallel) 271 { 272 foreach (TestRunner runner in runners) 273 if (filter.Pass(runner.Test)) 274 runner.BeginRun(this, filter); 275 276 result = this.EndRun(); 277 } 278 else 279 { 280 foreach (TestRunner runner in runners) 281 if (filter.Pass(runner.Test)) 282 result.AddResult(runner.Run(this, filter)); 283 } 284 285 long stopTime = DateTime.Now.Ticks; 286 double time = ((double)(stopTime - startTime)) / (double)TimeSpan.TicksPerSecond; 287 result.Time = time; 288 289 this.listener.RunFinished( result ); 290 291 this.testResult = result; 292 293 return result; 294 } 295 296 public virtual void BeginRun( EventListener listener ) 297 { 298 BeginRun( listener, TestFilter.Empty ); 299 } 300 301 public virtual void BeginRun( EventListener listener, ITestFilter filter ) 302 { 303 // Save active listener for derived classes 304 this.listener = listener; 305 306 Log.Info("BeginRun"); 307 308 // ThreadedTestRunner will call our Run method on a separate thread 309 ThreadedTestRunner threadedRunner = new ThreadedTestRunner(this); 310 threadedRunner.BeginRun(listener, filter); 311 } 312 313 public virtual TestResult EndRun() 314 { 315 Log.Info("EndRun"); 316 TestResult suiteResult = new TestResult(Test as TestInfo); 317 foreach( TestRunner runner in runners ) 318 suiteResult.Results.Add( runner.EndRun() ); 319 320 return suiteResult; 321 } 322 323 public virtual void CancelRun() 324 { 325 foreach( TestRunner runner in runners ) 326 runner.CancelRun(); 327 } 328 329 public virtual void Wait() 330 { 331 foreach( TestRunner runner in runners ) 332 runner.Wait(); 333 } 334 #endregion 335 336 #region EventListener Members 337 public void TestStarted(TestName testName) 338 { 339 this.listener.TestStarted( testName ); 340 } 341 342 public void RunStarted(string name, int testCount) 343 { 344 // TODO: We may want to count how many runs are started 345 // Ignore - we provide our own 346 } 347 348 public void RunFinished(Exception exception) 349 { 350 // Ignore - we provide our own 351 } 352 353 void NUnit.Core.EventListener.RunFinished(TestResult result) 354 { 355 if (this.runInParallel) 356 { 357 foreach (TestRunner runner in runners) 358 if (runner.Running) 359 return; 360 361 this.testResult = new TestResult(this.aggregateTest); 362 foreach (TestRunner runner in runners) 363 this.testResult.AddResult(runner.TestResult); 364 365 listener.RunFinished(this.TestResult); 366 } 367 } 368 369 public void SuiteFinished(TestResult result) 370 { 371 this.listener.SuiteFinished( result ); 372 } 373 374 public void TestFinished(TestResult result) 375 { 376 this.listener.TestFinished( result ); 377 } 378 379 public void UnhandledException(Exception exception) 380 { 381 this.listener.UnhandledException( exception ); 382 } 383 384 public void TestOutput(TestOutput testOutput) 385 { 386 this.listener.TestOutput( testOutput ); 387 } 388 389 public void SuiteStarted(TestName suiteName) 390 { 391 this.listener.SuiteStarted( suiteName ); 392 } 393 #endregion 394 395 #region InitializeLifetimeService Override 396 public override object InitializeLifetimeService() 397 { 398 return null; 399 } 400 #endregion 401 402 #region IDisposable Members 403 404 public void Dispose() 405 { 406 foreach (TestRunner runner in runners) 407 if (runner != null) 408 runner.Dispose(); 409 } 410 411 #endregion 412 } 413 #endregion 414 415 #region MultipleTestDomainRunner 416 /// <summary> 417 /// Summary description for MultipleTestDomainRunner. 418 /// </summary> 419 public class MultipleTestDomainRunner : AggregatingTestRunner 420 { 421 #region Constructors 422 public MultipleTestDomainRunner() : base(0) { } 423 424 public MultipleTestDomainRunner(int runnerID) : base(runnerID) { } 425 #endregion 426 427 #region CreateRunner 428 protected override TestRunner CreateRunner(int runnerID) 429 { 430 return new TestDomain(runnerID); 431 } 432 #endregion 433 } 434 #endregion 435 436 #region MultipleTestProcessRunner 437 public class MultipleTestProcessRunner : AggregatingTestRunner 438 { 439 #region Constructors 440 public MultipleTestProcessRunner() : base(0) { } 441 442 public MultipleTestProcessRunner(int runnerID) : base(runnerID) { } 443 #endregion 444 445 #region CreateRunner 446 protected override TestRunner CreateRunner(int runnerID) 447 { 448 return new ProcessRunner(runnerID); 449 } 450 #endregion 451 } 452 #endregion 453}