-
Notifications
You must be signed in to change notification settings - Fork 149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AggregatingTestRunner to handle app domain unload failure in same fashion as single assembly runner #321
AggregatingTestRunner to handle app domain unload failure in same fashion as single assembly runner #321
Changes from 1 commit
7dfed38
22b27db
af489eb
ca59351
5fd644b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ | |
// *********************************************************************** | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
#if !NETSTANDARD1_3 | ||
using System.Runtime.Serialization; | ||
#endif | ||
|
@@ -38,23 +39,45 @@ namespace NUnit.Engine | |
#endif | ||
public class NUnitEngineException : Exception | ||
{ | ||
private const string AggregatedExceptionsMsg = | ||
"Multiple exceptions encountered. Retrieve AggregatedExceptions property for more information"; | ||
|
||
/// <summary> | ||
/// Construct with a message | ||
/// </summary> | ||
public NUnitEngineException(string message) : base(message) { } | ||
public NUnitEngineException(string message) : base(message) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Construct with a message and inner exception | ||
/// </summary> | ||
/// <param name="message"></param> | ||
/// <param name="innerException"></param> | ||
public NUnitEngineException(string message, Exception innerException) : base(message, innerException) { } | ||
public NUnitEngineException(string message, Exception innerException) : base(message, innerException) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Construct with a message and collection of inner exceptions | ||
/// </summary> | ||
public NUnitEngineException(ICollection<Exception> aggregatedExceptions) | ||
: base(AggregatedExceptionsMsg, new NUnitEngineException(AggregatedExceptionsMsg)) | ||
{ | ||
AggregatedExceptions = aggregatedExceptions; | ||
} | ||
|
||
#if !NETSTANDARD1_3 | ||
/// <summary> | ||
/// Serialization constructor | ||
/// </summary> | ||
public NUnitEngineException(SerializationInfo info, StreamingContext context) : base(info, context) { } | ||
#endif | ||
|
||
/// <summary> | ||
/// Gets the Exception instance that caused the current exception. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment is wrong. |
||
/// </summary> | ||
public ICollection<Exception> AggregatedExceptions { get; } | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,9 @@ | |
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
// *********************************************************************** | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using NUnit.Common; | ||
using NUnit.Engine.Internal; | ||
|
||
namespace NUnit.Engine.Runners | ||
|
@@ -39,6 +41,11 @@ public class AggregatingTestRunner : AbstractTestRunner | |
// of writing this comment) be either TestDomainRunners or ProcessRunners. | ||
private List<ITestEngineRunner> _runners; | ||
|
||
// Exceptions from individual runners are caught and rethrown | ||
// on AggregatingTestRunner disposal, to allow TestResults to be | ||
// written and execution of other runners to continue. | ||
private readonly List<Exception> _thrownExceptions = new List<Exception>(); | ||
|
||
// Public for testing purposes | ||
public virtual int LevelOfParallelism | ||
{ | ||
|
@@ -115,7 +122,16 @@ protected override TestEngineResult LoadPackage() | |
public override void UnloadPackage() | ||
{ | ||
foreach (ITestEngineRunner runner in Runners) | ||
runner.Unload(); | ||
{ | ||
try | ||
{ | ||
runner.Unload(); | ||
} | ||
catch (Exception e) | ||
{ | ||
_thrownExceptions.Add(e); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
|
@@ -169,8 +185,9 @@ private void RunTestsSequentially(ITestEventListener listener, TestFilter filter | |
{ | ||
foreach (ITestEngineRunner runner in Runners) | ||
{ | ||
results.Add(runner.Run(listener, filter)); | ||
if (disposeRunners) runner.Dispose(); | ||
var task = new TestExecutionTask(runner, listener, filter, disposeRunners); | ||
task.Execute(); | ||
LogResultsFromTask(task, results, _thrownExceptions); | ||
} | ||
} | ||
|
||
|
@@ -191,7 +208,7 @@ private void RunTestsInParallel(ITestEventListener listener, TestFilter filter, | |
workerPool.WaitAll(); | ||
|
||
foreach (var task in tasks) | ||
results.Add(task.Result()); | ||
LogResultsFromTask(task, results, _thrownExceptions); | ||
} | ||
#endif | ||
|
||
|
@@ -210,16 +227,40 @@ protected override void Dispose(bool disposing) | |
base.Dispose(disposing); | ||
|
||
foreach (var runner in Runners) | ||
runner.Dispose(); | ||
{ | ||
try | ||
{ | ||
runner.Dispose(); | ||
} | ||
catch (NUnitEngineException e) | ||
{ | ||
_thrownExceptions.Add(e); | ||
} | ||
} | ||
|
||
Runners.Clear(); | ||
throw new NUnitEngineException(_thrownExceptions); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just come across this - D'oh! Of course, that should only throw when there are exceptions to be thrown - will fix this before merge. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, throwing is the whole point of this change. I want to go back and question why we are throwing in the case of failure to unload. I mentioned this elsewhere (on the issue I think) and I wonder why we can't just report this as an error (or warning) associated with the individual assembly as we do in other cases. The difference here, of course, is that only the engine can detect the error as opposed to the framework. Worth discussing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the avoidance of doubt, to be clear of the things I'm aiming to do here:
The current issue is that the AggregatingTestRunner is throwing the exception mid-run, where as the other ITestEngineRunners would all throw the exception at the end of the test run, as the ITestEngineRunner is disposed, and once the full results have already been returned to the runner. I think that design currently works. A few reasons:
I don't think that this exception should escape the runner - the current behaviour of handling this within the runner seems correct to me. The main issue in my eyes, is that the exception can currently cause a test run not to be completed, and results which have already ran to be lost. This shouldn't happen. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (The term 'runner' is overloaded in this conversation! I've edited to be clear when I mean ITestEngineRunner - and when I mean runner as in 'console runner'/GUI etc.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see your point about the separation of running from unloading. In that case, I suppose it's up to the runner to decide what to do with the exceptions - error, warn, etc. That would mean that unload exceptions need to be distinguishable from others of course. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about an NUnitUnloadException which inherits from NUnitEngineExceptions, to not break any existing runners by throwning a new Exception type? 🙂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like it! |
||
} | ||
|
||
#endregion | ||
#endregion | ||
|
||
protected virtual ITestEngineRunner CreateRunner(TestPackage package) | ||
{ | ||
return TestRunnerFactory.MakeTestRunner(package); | ||
} | ||
|
||
private static void LogResultsFromTask(TestExecutionTask task, List<TestEngineResult> results, List<Exception> thrownExceptions) | ||
{ | ||
var result = task.Result(); | ||
if (result != null) | ||
{ | ||
results.Add(result); | ||
} | ||
|
||
if (task.ThrownException != null) | ||
{ | ||
thrownExceptions.Add(task.ThrownException); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear what you are doing here. Seems like you end up with an
NUnitEngineException
with anInnerException
that's also anNUnitEngineException
, both having the same message. The outer also gets a collection of aggregated exceptions. What's the point of theInnerException
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I was maybe thinking of
NUnitException
in the framework, where I thought we'd always expect an inner exception? I'll get rid of it.