diff --git a/src/NUnitEngine/nunit.engine.api/NUnitEngineException.cs b/src/NUnitEngine/nunit.engine.api/Exceptions/NUnitEngineException.cs
similarity index 93%
rename from src/NUnitEngine/nunit.engine.api/NUnitEngineException.cs
rename to src/NUnitEngine/nunit.engine.api/Exceptions/NUnitEngineException.cs
index 820ab3542..aa1812835 100644
--- a/src/NUnitEngine/nunit.engine.api/NUnitEngineException.cs
+++ b/src/NUnitEngine/nunit.engine.api/Exceptions/NUnitEngineException.cs
@@ -41,14 +41,16 @@ public class NUnitEngineException : Exception
///
/// Construct with a message
///
- public NUnitEngineException(string message) : base(message) { }
+ public NUnitEngineException(string message) : base(message)
+ {
+ }
///
/// Construct with a message and inner exception
///
- ///
- ///
- public NUnitEngineException(string message, Exception innerException) : base(message, innerException) { }
+ public NUnitEngineException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
#if !NETSTANDARD1_3
///
diff --git a/src/NUnitEngine/nunit.engine.api/NUnitEngineNotFoundException.cs b/src/NUnitEngine/nunit.engine.api/Exceptions/NUnitEngineNotFoundException.cs
similarity index 100%
rename from src/NUnitEngine/nunit.engine.api/NUnitEngineNotFoundException.cs
rename to src/NUnitEngine/nunit.engine.api/Exceptions/NUnitEngineNotFoundException.cs
diff --git a/src/NUnitEngine/nunit.engine.api/Exceptions/NUnitEngineUnloadException.cs b/src/NUnitEngine/nunit.engine.api/Exceptions/NUnitEngineUnloadException.cs
new file mode 100644
index 000000000..f0bae47d1
--- /dev/null
+++ b/src/NUnitEngine/nunit.engine.api/Exceptions/NUnitEngineUnloadException.cs
@@ -0,0 +1,81 @@
+// ***********************************************************************
+// Copyright (c) 2017 Charlie Poole, Rob Prouse
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ***********************************************************************
+
+using System;
+using System.Collections.Generic;
+#if !NETSTANDARD1_3
+using System.Runtime.Serialization;
+#endif
+
+namespace NUnit.Engine
+{
+
+ ///
+ /// NUnitEngineUnloadException is thrown when a test run has completed successfully
+ /// but one or more errors were encountered when attempting to unload
+ /// and shut down the test run cleanly.
+ ///
+#if !NETSTANDARD1_3
+ [Serializable]
+#endif
+ public class NUnitEngineUnloadException : NUnitEngineException //Inherits from NUnitEngineException for backwards compatibility of calling runners
+ {
+ private const string AggregatedExceptionsMsg =
+ "Multiple exceptions encountered. Retrieve AggregatedExceptions property for more information";
+
+
+ ///
+ /// Construct with a message
+ ///
+ public NUnitEngineUnloadException(string message) : base(message)
+ {
+ }
+
+ ///
+ /// Construct with a message and inner exception
+ ///
+ public NUnitEngineUnloadException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Construct with a message and a collection of exceptions.
+ ///
+ public NUnitEngineUnloadException(ICollection aggregatedExceptions) : base(AggregatedExceptionsMsg)
+ {
+ AggregatedExceptions = aggregatedExceptions;
+ }
+
+#if !NETSTANDARD1_3
+ ///
+ /// Serialization constructor.
+ ///
+ public NUnitEngineUnloadException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+#endif
+
+ ///
+ /// Gets the collection of exceptions .
+ ///
+ public ICollection AggregatedExceptions { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj b/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj
index c6f4f009d..2da76bbe7 100644
--- a/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj
+++ b/src/NUnitEngine/nunit.engine.api/nunit.engine.api.csproj
@@ -56,6 +56,7 @@
Properties\EngineApiVersion.cs
+
@@ -73,7 +74,7 @@
-
+
@@ -82,7 +83,7 @@
-
+
@@ -106,4 +107,4 @@
-->
-
+
\ No newline at end of file
diff --git a/src/NUnitEngine/nunit.engine.netstandard/nunit.engine.netstandard.csproj b/src/NUnitEngine/nunit.engine.netstandard/nunit.engine.netstandard.csproj
index 6ad145c63..4849da455 100644
--- a/src/NUnitEngine/nunit.engine.netstandard/nunit.engine.netstandard.csproj
+++ b/src/NUnitEngine/nunit.engine.netstandard/nunit.engine.netstandard.csproj
@@ -29,6 +29,8 @@
+
+
Extensibility\IFrameworkDriver.cs
@@ -139,9 +141,6 @@
ITestRunner.cs
-
- NUnitEngineException.cs
-
TestFilter.cs
diff --git a/src/NUnitEngine/nunit.engine.tests/Services/DomainManagerTests.cs b/src/NUnitEngine/nunit.engine.tests/Services/DomainManagerTests.cs
index 0a2bbf1e5..338f42e5e 100644
--- a/src/NUnitEngine/nunit.engine.tests/Services/DomainManagerTests.cs
+++ b/src/NUnitEngine/nunit.engine.tests/Services/DomainManagerTests.cs
@@ -100,12 +100,12 @@ public void CanUnloadDomain()
}
[Test]
- public void UnloadingTwiceThrowsNUnitEngineException()
+ public void UnloadingTwiceThrowsNUnitEngineUnloadException()
{
var domain = _domainManager.CreateDomain(_package);
_domainManager.Unload(domain);
- Assert.That(() => _domainManager.Unload(domain), Throws.TypeOf());
+ Assert.That(() => _domainManager.Unload(domain), Throws.TypeOf());
CheckDomainIsUnloaded(domain);
}
diff --git a/src/NUnitEngine/nunit.engine/Internal/ExceptionHelper.cs b/src/NUnitEngine/nunit.engine/Internal/ExceptionHelper.cs
index d4b2a15f6..f521f5fe0 100644
--- a/src/NUnitEngine/nunit.engine/Internal/ExceptionHelper.cs
+++ b/src/NUnitEngine/nunit.engine/Internal/ExceptionHelper.cs
@@ -25,6 +25,7 @@
using System.Collections.Generic;
using System.Reflection;
using System.Text;
+using NUnit.Engine;
namespace NUnit.Common
{
@@ -100,9 +101,18 @@ private static List FlattenExceptionHierarchy(Exception exception)
{
var result = new List();
- if (exception is ReflectionTypeLoadException)
+ var unloadException = exception as NUnitEngineUnloadException;
+ if (unloadException?.AggregatedExceptions != null)
+ {
+ result.AddRange(unloadException.AggregatedExceptions);
+
+ foreach (var aggregatedException in unloadException.AggregatedExceptions)
+ result.AddRange(FlattenExceptionHierarchy(aggregatedException));
+ }
+
+ var reflectionException = exception as ReflectionTypeLoadException;
+ if (reflectionException != null)
{
- var reflectionException = exception as ReflectionTypeLoadException;
result.AddRange(reflectionException.LoaderExceptions);
foreach (var innerException in reflectionException.LoaderExceptions)
diff --git a/src/NUnitEngine/nunit.engine/Runners/AggregatingTestRunner.cs b/src/NUnitEngine/nunit.engine/Runners/AggregatingTestRunner.cs
index 61c87d472..9d1dc6706 100644
--- a/src/NUnitEngine/nunit.engine/Runners/AggregatingTestRunner.cs
+++ b/src/NUnitEngine/nunit.engine/Runners/AggregatingTestRunner.cs
@@ -21,9 +21,14 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ***********************************************************************
+using System;
using System.Collections.Generic;
using NUnit.Engine.Internal;
+#if !NETSTANDARD1_3
+using NUnit.Common;
+#endif
+
namespace NUnit.Engine.Runners
{
///
@@ -39,6 +44,11 @@ public class AggregatingTestRunner : AbstractTestRunner
// of writing this comment) be either TestDomainRunners or ProcessRunners.
private List _runners;
+ // Exceptions from unloading 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 _unloadExceptions = new List();
+
// Public for testing purposes
public virtual int LevelOfParallelism
{
@@ -73,7 +83,7 @@ public AggregatingTestRunner(IServiceLocator services, TestPackage package) : ba
}
#endif
- #region AbstractTestRunner Overrides
+#region AbstractTestRunner Overrides
///
/// Explore a TestPackage and return information about
@@ -115,7 +125,16 @@ protected override TestEngineResult LoadPackage()
public override void UnloadPackage()
{
foreach (ITestEngineRunner runner in Runners)
- runner.Unload();
+ {
+ try
+ {
+ runner.Unload();
+ }
+ catch (Exception e)
+ {
+ _unloadExceptions.Add(e);
+ }
+ }
}
///
@@ -169,8 +188,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, _unloadExceptions);
}
}
@@ -191,7 +211,7 @@ private void RunTestsInParallel(ITestEventListener listener, TestFilter filter,
workerPool.WaitAll();
foreach (var task in tasks)
- results.Add(task.Result());
+ LogResultsFromTask(task, results, _unloadExceptions);
}
#endif
@@ -210,9 +230,21 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
foreach (var runner in Runners)
- runner.Dispose();
+ {
+ try
+ {
+ runner.Dispose();
+ }
+ catch (Exception e)
+ {
+ _unloadExceptions.Add(e);
+ }
+ }
Runners.Clear();
+
+ if (_unloadExceptions.Count > 0)
+ throw new NUnitEngineUnloadException(_unloadExceptions);
}
#endregion
@@ -221,5 +253,19 @@ protected virtual ITestEngineRunner CreateRunner(TestPackage package)
{
return TestRunnerFactory.MakeTestRunner(package);
}
+
+ private static void LogResultsFromTask(TestExecutionTask task, List results, List unloadExceptions)
+ {
+ var result = task.Result;
+ if (result != null)
+ {
+ results.Add(result);
+ }
+
+ if (task.UnloadException != null)
+ {
+ unloadExceptions.Add(task.UnloadException);
+ }
+ }
}
}
diff --git a/src/NUnitEngine/nunit.engine/Runners/TestExecutionTask.cs b/src/NUnitEngine/nunit.engine/Runners/TestExecutionTask.cs
index 64f639016..ede8a385b 100644
--- a/src/NUnitEngine/nunit.engine/Runners/TestExecutionTask.cs
+++ b/src/NUnitEngine/nunit.engine/Runners/TestExecutionTask.cs
@@ -21,6 +21,8 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ***********************************************************************
+using System;
+
namespace NUnit.Engine.Runners
{
public class TestExecutionTask : ITestExecutionTask
@@ -30,6 +32,8 @@ public class TestExecutionTask : ITestExecutionTask
private readonly TestFilter _filter;
private volatile TestEngineResult _result;
private readonly bool _disposeRunner;
+ private bool _hasExecuted = false;
+ private Exception _unloadException;
public TestExecutionTask(ITestEngineRunner runner, ITestEventListener listener, TestFilter filter, bool disposeRunner)
{
@@ -41,14 +45,44 @@ public TestExecutionTask(ITestEngineRunner runner, ITestEventListener listener,
public void Execute()
{
- _result = _runner.Run(_listener, _filter);
- if (_disposeRunner)
- _runner.Dispose();
+ _hasExecuted = true;
+ try
+ {
+ _result = _runner.Run(_listener, _filter);
+ }
+ finally
+ {
+ try
+ {
+ if (_disposeRunner)
+ _runner.Dispose();
+ }
+ catch (Exception e)
+ {
+ _unloadException = e;
+ }
+ }
+ }
+
+ public TestEngineResult Result
+ {
+ get
+ {
+ Guard.OperationValid(_hasExecuted, "Can not access result until task has been executed");
+ return _result;
+ }
}
- public TestEngineResult Result()
+ ///
+ /// Stored exception thrown during test assembly unload.
+ ///
+ public Exception UnloadException
{
- return _result;
+ get
+ {
+ Guard.OperationValid(_hasExecuted, "Can not access thrown exceptions until task has been executed");
+ return _unloadException;
+ }
}
}
}
diff --git a/src/NUnitEngine/nunit.engine/Services/DomainManager.cs b/src/NUnitEngine/nunit.engine/Services/DomainManager.cs
index b0af4c0cc..a4f0585d9 100644
--- a/src/NUnitEngine/nunit.engine/Services/DomainManager.cs
+++ b/src/NUnitEngine/nunit.engine/Services/DomainManager.cs
@@ -167,11 +167,11 @@ public void Unload()
log.Error(msg);
Kill(_unloadThread);
- throw new NUnitEngineException(msg);
+ throw new NUnitEngineUnloadException(msg);
}
if (_unloadException != null)
- throw new NUnitEngineException("Exception encountered unloading AppDomain", _unloadException);
+ throw new NUnitEngineUnloadException("Exception encountered unloading AppDomain", _unloadException);
}
private void UnloadOnThread()