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) {