From 56334183c95a521bdd0bb332b85533bc56b0513e Mon Sep 17 00:00:00 2001 From: Neil Colvin Date: Wed, 21 Oct 2026 12:39:38 +0100 Subject: [PATCH 1/2] Expose the agent provider contract for engine extensions Co-authored-by: OpenAI Codex --- src/NUnit.Engine.Api/ITestAgentProvider.cs | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/NUnit.Engine.Api/ITestAgentProvider.cs diff --git a/src/NUnit.Engine.Api/ITestAgentProvider.cs b/src/NUnit.Engine.Api/ITestAgentProvider.cs new file mode 100644 index 0000000..490d3f6 --- /dev/null +++ b/src/NUnit.Engine.Api/ITestAgentProvider.cs @@ -0,0 +1,53 @@ +// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt + +using System; + +namespace NUnit.Engine.Services +{ + /// + /// An object implementing ITestAgentProvider is able to provide + /// test agents, which satisfy the criteria specified in a TestPackage. + /// The engine loads implementations through /NUnit/Engine/AgentProviders. + /// Capability queries do not acquire execution ownership. An acquired agent + /// remains associated with its provider until ReleaseAgent succeeds. + /// + public interface ITestAgentProvider : ITestAgentInfo + { + /// + /// Returns true if an agent can be found, which is suitable + /// for running the provided test package. + /// + /// A TestPackage + /// True if the provider can supply a compatible agent. + /// Capability queries must not acquire execution ownership. + bool IsAgentAvailable(TestPackage package); + + /// + /// Return an agent, which best matches the criteria defined + /// in a TestPackage. + /// + /// The test package to be run + /// An ITestAgent + /// If the package is invalid. + /// If no compatible agent is available. + ITestAgent GetAgent(TestPackage package); + + /// + /// Releases the test agent back to the supplier, which provided it. + /// + /// An agent previously provided by a call to GetAgent. + /// + /// If agent was never provided by the factory or was previously released. + /// + /// + /// Return the agent through the same provider which acquired it so that + /// ownership bookkeeping remains consistent. A stop request, a closed + /// connection or disposal of a runner is not confirmation of release. + /// If release fails, retain ownership until the provider can reconcile it. + /// The provider defines how it stops its agent. Successful release alone + /// does not prove that external deployment files can be deleted safely; + /// remote providers must confirm execution has stopped before allowing cleanup. + /// + void ReleaseAgent(ITestAgent agent); + } +} From 549f2315210dd0af6ade3f9320852a22cafa5e07 Mon Sep 17 00:00:00 2001 From: Neil Colvin Date: Wed, 28 Oct 2026 05:25:52 +0000 Subject: [PATCH 2/2] docs: clarify agent provider lifecycle and ownership Qualify discovery and capability queries, document acquisition and failed release responsibility, and apply resource cleanup guidance to all providers. Remove the obsolete provider-loading path claim without changing signatures. Prepared by Neil Colvin with assistance from OpenAI Codex. --- src/NUnit.Engine.Api/ITestAgentProvider.cs | 92 +++++++++++++++++----- 1 file changed, 71 insertions(+), 21 deletions(-) diff --git a/src/NUnit.Engine.Api/ITestAgentProvider.cs b/src/NUnit.Engine.Api/ITestAgentProvider.cs index 490d3f6..aef9013 100644 --- a/src/NUnit.Engine.Api/ITestAgentProvider.cs +++ b/src/NUnit.Engine.Api/ITestAgentProvider.cs @@ -5,48 +5,98 @@ namespace NUnit.Engine.Services { /// - /// An object implementing ITestAgentProvider is able to provide - /// test agents, which satisfy the criteria specified in a TestPackage. - /// The engine loads implementations through /NUnit/Engine/AgentProviders. - /// Capability queries do not acquire execution ownership. An acquired agent - /// remains associated with its provider until ReleaseAgent succeeds. + /// Provides test agents suitable for a and accepts + /// their return when the caller no longer requires them. /// + /// + /// + /// This contract applies to in-process, local-process and remote agents. It does + /// not require the caller to own a process or perform a particular registration + /// handshake. The provider is responsible for the implementation-specific setup + /// and lifetime of the agents it supplies. + /// + /// + /// The caller is responsible for returning each acquired agent through the same + /// provider. Capability queries do not transfer that responsibility to the caller; + /// see for their initialization and discovery limits. + /// + /// public interface ITestAgentProvider : ITestAgentInfo { /// - /// Returns true if an agent can be found, which is suitable + /// Determines whether the provider can currently supply an agent suitable /// for running the provided test package. /// /// A TestPackage - /// True if the provider can supply a compatible agent. - /// Capability queries must not acquire execution ownership. + /// True if the provider can currently supply a compatible agent. + /// + /// + /// This query does not return an agent or make the caller responsible for + /// releasing one. It does not guarantee that no extension or agent implementation + /// is loaded or instantiated internally. Depending on the information supplied + /// by an extension, the provider may use declared metadata or instantiate the + /// extension to determine its capabilities. Other implementations may need to + /// initialize provider resources or contact another host. + /// + /// + /// A successful query does not reserve an agent. Availability may change before + /// is called, so the caller must still handle acquisition failure. + /// The provider remains responsible for resources created while evaluating the query. + /// + /// bool IsAgentAvailable(TestPackage package); /// - /// Return an agent, which best matches the criteria defined - /// in a TestPackage. + /// Acquires an agent suitable for the criteria specified in a . /// /// The test package to be run - /// An ITestAgent + /// The acquired agent, which must be returned through this provider. /// If the package is invalid. - /// If no compatible agent is available. + /// + /// No compatible agent is available. Call first + /// to check suitability, while allowing for availability to change before acquisition. + /// + /// + /// + /// Successful acquisition makes the caller responsible for returning this exact + /// agent reference using . The provider retains responsibility + /// for its underlying implementation and resources; acquiring an agent does not + /// imply that the caller owns its process, transport or deployment. + /// + /// + /// If acquisition throws, the caller has no returned agent to release. The provider + /// remains responsible for resources created during the failed attempt and for + /// reconciling an outcome that cannot yet be confirmed. + /// + /// ITestAgent GetAgent(TestPackage package); /// - /// Releases the test agent back to the supplier, which provided it. + /// Returns a previously acquired test agent to the provider that supplied it. /// /// An agent previously provided by a call to GetAgent. /// - /// If agent was never provided by the factory or was previously released. + /// The reference was not acquired from this provider or was previously released. /// /// - /// Return the agent through the same provider which acquired it so that - /// ownership bookkeeping remains consistent. A stop request, a closed - /// connection or disposal of a runner is not confirmation of release. - /// If release fails, retain ownership until the provider can reconcile it. - /// The provider defines how it stops its agent. Successful release alone - /// does not prove that external deployment files can be deleted safely; - /// remote providers must confirm execution has stopped before allowing cleanup. + /// + /// Return the exact reference through the same provider that supplied it so that + /// ownership bookkeeping remains consistent. The provider decides whether to stop + /// the agent, retain it for reuse or return it to another source. After successful + /// release, the caller must not continue using the reference. + /// + /// + /// A stop request, a closed connection or disposal of a runner is not confirmation + /// of release. If release throws, preserve the association with the originating + /// provider until it can reconcile the outcome. This does not guarantee that an + /// unconfirmed operation can be repeated without reconciliation. + /// + /// + /// Successful release does not by itself prove that the agent terminated or that + /// external resources, including deployed files, are no longer in use. Before + /// removing resources used by an agent, the responsible provider must establish + /// that the agent no longer needs them. This applies to all provider types. + /// /// void ReleaseAgent(ITestAgent agent); }