diff --git a/src/NUnitEngine/nunit.engine.core/Agents/RemoteTestAgent.cs b/src/NUnitEngine/nunit.engine.core/Agents/RemoteTestAgent.cs
index 9d254c503..14df177e1 100644
--- a/src/NUnitEngine/nunit.engine.core/Agents/RemoteTestAgent.cs
+++ b/src/NUnitEngine/nunit.engine.core/Agents/RemoteTestAgent.cs
@@ -38,12 +38,13 @@ namespace NUnit.Engine.Agents
/// to it. Rather, it reports back to the sponsoring TestAgency upon
/// startup so that the agency may in turn provide it to clients for use.
///
- public class RemoteTestAgent : TestAgent, ITestEngineRunner
+ public class RemoteTestAgent : MarshalByRefObject, ITestAgent, IDisposable
{
private static readonly Logger log = InternalTrace.GetLogger(typeof(RemoteTestAgent));
private readonly Guid _agentId;
private readonly string _agencyUrl;
+ private readonly IServiceLocator _services;
private ITestEngineRunner _runner;
private TestPackage _package;
@@ -57,10 +58,23 @@ public class RemoteTestAgent : TestAgent, ITestEngineRunner
/// Construct a RemoteTestAgent
///
public RemoteTestAgent(Guid agentId, string agencyUrl, IServiceLocator services)
- : base(services)
{
_agentId = agentId;
_agencyUrl = agencyUrl;
+ _services = services;
+ }
+
+ ///
+ /// Overridden to cause object to live indefinitely
+ ///
+ public override object InitializeLifetimeService()
+ {
+ return null;
+ }
+
+ public void Dispose()
+ {
+ ShutDown();
}
public int ProcessId
@@ -68,11 +82,11 @@ public int ProcessId
get { return System.Diagnostics.Process.GetCurrentProcess().Id; }
}
- public override ITestEngineRunner CreateRunner(TestPackage package)
+ public TestEngineResult Load(TestPackage package)
{
_package = package;
- _runner = Services.GetService().MakeTestRunner(_package);
- return this;
+ _runner = _services.GetService().MakeTestRunner(_package);
+ return _runner.Load();
}
public bool Start()
@@ -107,7 +121,7 @@ public bool Start()
return true;
}
- public override void Stop()
+ public void ShutDown()
{
log.Info("Stopping");
@@ -187,18 +201,6 @@ public TestEngineResult Run(ITestEventListener listener, TestFilter filter)
return _runner.Run(listener, filter);
}
- ///
- /// Start a run of the tests in the loaded TestPackage. The tests are run
- /// asynchronously and the listener interface is notified as it progresses.
- ///
- /// An ITestEventHandler to receive events
- /// A TestFilter used to select tests
- /// A that will provide the result of the test execution
- public AsyncTestEngineResult RunAsync(ITestEventListener listener, TestFilter filter)
- {
- return _runner.RunAsync(listener, filter);
- }
-
///
/// Cancel the ongoing test run. If no test is running, the call is ignored.
///
diff --git a/src/NUnitEngine/nunit.engine.core/Agents/TestAgent.cs b/src/NUnitEngine/nunit.engine.core/Agents/TestAgent.cs
deleted file mode 100644
index b4d52e04c..000000000
--- a/src/NUnitEngine/nunit.engine.core/Agents/TestAgent.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-// ***********************************************************************
-// Copyright (c) 2011 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.
-// ***********************************************************************
-
-#if !NETSTANDARD1_6 && !NETSTANDARD2_0
-using System;
-
-namespace NUnit.Engine.Agents
-{
- ///
- /// Abstract base for all types of TestAgents.
- /// A TestAgent provides services of locating,
- /// loading and running tests in a particular
- /// context such as an application domain or process.
- ///
- public abstract class TestAgent : MarshalByRefObject, ITestAgent, IDisposable
- {
- private readonly IServiceLocator services;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The identifier of the agent.
- /// The services available to the agent.
- public TestAgent(IServiceLocator services)
- {
- this.services = services;
- }
-
- ///
- /// The services available to the agent
- ///
- protected IServiceLocator Services
- {
- get { return services; }
- }
-
- ///
- /// Stops the agent, releasing any resources
- ///
- public abstract void Stop();
-
- ///
- /// Creates a test runner
- ///
- public abstract ITestEngineRunner CreateRunner(TestPackage package);
-
- public void Dispose()
- {
- GC.SuppressFinalize(this);
- Dispose(true);
- }
-
- private bool _disposed = false;
-
- ///
- /// Dispose is overridden to stop the agent
- ///
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposed)
- {
- if (disposing)
- Stop();
-
- _disposed = true;
- }
- }
-
- ///
- /// Overridden to cause object to live indefinitely
- ///
- public override object InitializeLifetimeService()
- {
- return null;
- }
- }
-}
-#endif
diff --git a/src/NUnitEngine/nunit.engine.core/AsyncTestEngineResult.cs b/src/NUnitEngine/nunit.engine.core/AsyncTestEngineResult.cs
index 642f04e1b..6b5e57ece 100644
--- a/src/NUnitEngine/nunit.engine.core/AsyncTestEngineResult.cs
+++ b/src/NUnitEngine/nunit.engine.core/AsyncTestEngineResult.cs
@@ -39,6 +39,15 @@ public class AsyncTestEngineResult : ITestRun
private volatile TestEngineResult _result;
private readonly ManualResetEvent _waitHandle = new ManualResetEvent(false);
+#if !NETSTANDARD1_6
+ public static AsyncTestEngineResult RunAsync(Func func)
+ {
+ var testRun = new AsyncTestEngineResult();
+ ThreadPool.QueueUserWorkItem(_ => testRun.SetResult(func.Invoke()));
+ return testRun;
+ }
+#endif
+
///
/// Get the result of this run.
///
diff --git a/src/NUnitEngine/nunit.engine.core/ITestAgent.cs b/src/NUnitEngine/nunit.engine.core/ITestAgent.cs
index 622c1ca32..a422f4904 100644
--- a/src/NUnitEngine/nunit.engine.core/ITestAgent.cs
+++ b/src/NUnitEngine/nunit.engine.core/ITestAgent.cs
@@ -24,18 +24,53 @@
namespace NUnit.Engine
{
///
- /// The ITestAgent interface is implemented by remote test agents.
+ /// Defines the current communication protocol between the engine and the agent. This is an implementation detail
+ /// which is in the process of changing as the dependency on .NET Framework's remoting is removed.
///
public interface ITestAgent
{
///
/// Stops the agent, releasing any resources
///
- void Stop();
+ void ShutDown();
///
- /// Creates a test runner
+ /// Loads a package which will be used for all subsequent calls to the other methods (except
+ /// ). This method must be called at least once before calling methods that operate on a
+ /// test package. It may be called any number of times after those methods.
///
- ITestEngineRunner CreateRunner(TestPackage package);
+ TestEngineResult Load(TestPackage package);
+
+ ///
+ /// Unloads any loaded package. If none is loaded, the call is ignored.
+ ///
+ void Unload();
+
+ ///
+ /// Reloads the loaded package.
+ ///
+ /// Thrown if no package is loaded.
+ TestEngineResult Reload();
+
+ ///
+ /// Counts the test cases in the loaded package that would be run under the specified filter.
+ ///
+ int CountTestCases(TestFilter filter);
+
+ ///
+ /// Runs the tests in the loaded package. The listener interface is notified as the run progresses.
+ ///
+ TestEngineResult Run(ITestEventListener listener, TestFilter filter);
+
+ ///
+ /// Cancel the current test run. If no test is running, the call is ignored.
+ ///
+ /// Indicates whether tests that have not completed should be killed.
+ void StopRun(bool force);
+
+ ///
+ /// Returns information about the test cases in the loaded package that would be run under the specified filter.
+ ///
+ TestEngineResult Explore(TestFilter filter);
}
}
diff --git a/src/NUnitEngine/nunit.engine.core/Polyfills.cs b/src/NUnitEngine/nunit.engine.core/Polyfills.cs
new file mode 100644
index 000000000..a3f0e1bf1
--- /dev/null
+++ b/src/NUnitEngine/nunit.engine.core/Polyfills.cs
@@ -0,0 +1,11 @@
+#if NET20
+
+namespace System
+{
+ // This would cause conflicts if it was public in the API assembly. This is an engine implementation detail since
+ // even though it's public because this assembly should not be compiled against by anyone that references the
+ // engine.
+ public delegate TResult Func();
+}
+
+#endif
diff --git a/src/NUnitEngine/nunit.engine.core/Runners/AbstractTestRunner.cs b/src/NUnitEngine/nunit.engine.core/Runners/AbstractTestRunner.cs
index 0ca6519a2..afa6d9038 100644
--- a/src/NUnitEngine/nunit.engine.core/Runners/AbstractTestRunner.cs
+++ b/src/NUnitEngine/nunit.engine.core/Runners/AbstractTestRunner.cs
@@ -22,8 +22,6 @@
// ***********************************************************************
using System;
-using System.ComponentModel;
-using NUnit.Engine.Services;
namespace NUnit.Engine.Runners
{
@@ -110,19 +108,7 @@ public virtual void UnloadPackage()
/// An that will provide the result of the test execution
protected virtual AsyncTestEngineResult RunTestsAsync(ITestEventListener listener, TestFilter filter)
{
- var testRun = new AsyncTestEngineResult();
-
- using (var worker = new BackgroundWorker())
- {
- worker.DoWork += (s, ea) =>
- {
- var result = RunTests(listener, filter);
- testRun.SetResult(result);
- };
- worker.RunWorkerAsync();
- }
-
- return testRun;
+ return AsyncTestEngineResult.RunAsync(() => RunTests(listener, filter));
}
#endif
diff --git a/src/NUnitEngine/nunit.engine.tests/Services/AgentStoreTests.DummyTestAgent.cs b/src/NUnitEngine/nunit.engine.tests/Services/AgentStoreTests.DummyTestAgent.cs
index 4fa0e44bf..584750943 100644
--- a/src/NUnitEngine/nunit.engine.tests/Services/AgentStoreTests.DummyTestAgent.cs
+++ b/src/NUnitEngine/nunit.engine.tests/Services/AgentStoreTests.DummyTestAgent.cs
@@ -30,17 +30,42 @@ public partial class AgentStoreTests
{
private sealed class DummyTestAgent : ITestAgent
{
- public ITestEngineRunner CreateRunner(TestPackage package)
+ public int CountTestCases(TestFilter filter)
{
throw new NotImplementedException();
}
- public bool Start()
+ public TestEngineResult Explore(TestFilter filter)
{
throw new NotImplementedException();
}
- public void Stop()
+ public TestEngineResult Load(TestPackage package)
+ {
+ throw new NotImplementedException();
+ }
+
+ public TestEngineResult Reload()
+ {
+ throw new NotImplementedException();
+ }
+
+ public TestEngineResult Run(ITestEventListener listener, TestFilter filter)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ShutDown()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void StopRun(bool force)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Unload()
{
throw new NotImplementedException();
}
diff --git a/src/NUnitEngine/nunit.engine/Services/TestAgency.AgentLease.cs b/src/NUnitEngine/nunit.engine/Services/TestAgency.AgentLease.cs
index f5e0760af..14e868be9 100644
--- a/src/NUnitEngine/nunit.engine/Services/TestAgency.AgentLease.cs
+++ b/src/NUnitEngine/nunit.engine/Services/TestAgency.AgentLease.cs
@@ -23,15 +23,17 @@
#if !NETSTANDARD1_6 && !NETSTANDARD2_0
using System;
+using System.Threading;
namespace NUnit.Engine.Services
{
public partial class TestAgency
{
- private sealed class AgentLease : IAgentLease
+ private sealed class AgentLease : IAgentLease, ITestEngineRunner
{
private readonly TestAgency _agency;
private readonly ITestAgent _remoteAgent;
+ private TestEngineResult _createRunnerLoadResult;
public AgentLease(TestAgency agency, Guid id, ITestAgent remoteAgent)
{
@@ -42,14 +44,56 @@ public AgentLease(TestAgency agency, Guid id, ITestAgent remoteAgent)
public Guid Id { get; }
+ public void Dispose()
+ {
+ _agency.Release(Id, _remoteAgent);
+ }
+
public ITestEngineRunner CreateRunner(TestPackage package)
{
- return _remoteAgent.CreateRunner(package);
+ _createRunnerLoadResult = _remoteAgent.Load(package);
+ return this;
}
- public void Dispose()
+ public int CountTestCases(TestFilter filter)
{
- _agency.Release(Id, _remoteAgent);
+ return _remoteAgent.CountTestCases(filter);
+ }
+
+ public TestEngineResult Explore(TestFilter filter)
+ {
+ return _remoteAgent.Explore(filter);
+ }
+
+ public TestEngineResult Load()
+ {
+ return Interlocked.Exchange(ref _createRunnerLoadResult, null)
+ ?? _remoteAgent.Reload();
+ }
+
+ public void Unload()
+ {
+ _remoteAgent.Unload();
+ }
+
+ public TestEngineResult Reload()
+ {
+ return _remoteAgent.Reload();
+ }
+
+ public TestEngineResult Run(ITestEventListener listener, TestFilter filter)
+ {
+ return _remoteAgent.Run(listener, filter);
+ }
+
+ public AsyncTestEngineResult RunAsync(ITestEventListener listener, TestFilter filter)
+ {
+ return AsyncTestEngineResult.RunAsync(() => Run(listener, filter));
+ }
+
+ public void StopRun(bool force)
+ {
+ _remoteAgent.StopRun(force);
}
}
}
diff --git a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs
index 1374ecc64..4a2fcdcd2 100644
--- a/src/NUnitEngine/nunit.engine/Services/TestAgency.cs
+++ b/src/NUnitEngine/nunit.engine/Services/TestAgency.cs
@@ -202,7 +202,7 @@ private void Release(Guid agentId, ITestAgent agent)
try
{
log.Debug("Stopping remote agent");
- agent.Stop();
+ agent.ShutDown();
}
catch (SocketException ex)
{