Repository navigation
Standard remote execution for NUnit Console and Engine #1909
Replies: 10 comments 3 replies
|
@oznetmaster To avoid extensive interlining, I will try to reply to the various sections of your post separately... hopefully in an order that makes sense! Here, I'll just make a comment on my own overall goals for the engine. The original design of what later became NUnit 3 was created in 2007. That vision is still included in the NUnit docs, although it's quite out of date now. As you see, that design incorporated the eventual execution of tests on separate server machines. As far as I'm concerned that's still a good goal but I don't know if others will agree. It's definitely not a goal for Version 4, because (1) V4 already has an enormous amount of change from V3 and (2) the resources to finish it are extremely limited. You cited issue #167, which I wrote. You'll notice that the very last action on this issue was also by me: I moved the issue out of V4. This was done because there don't appear to be enough people wanting to work on making it happen. As I'm sure you know from your own experience, taking on more than you can actually accomplish is one of the big causes of failed projects. One of the things you might consider, rather than forking the engine, would be to contribute directly changes you feel are needed to support your own project. Of course, that would mean crafting the changes in such a way that it fits in with the general design of the engine, but such is the case for any contributions to an open source project. The current service structure in the V4 engine, is intended to support remote processes at some time in the future. I doubt if I'll be around (in this project, that is) to implement it. But hopefully somebody will. I'll next try to address that structure next. Also... feel free to email me directly if you need to... I'm not sure if any of the emails I have for you are still correct but I can tell you that mine is unchanged. :-) |
|
Actually, a brief note about pluggable agents before getting on to the bigger question of remote agent processes. In case it's not clear, all agents in V4 are pluggable. So, if a remote server were to run tests, that server would need to use As you observe, agents are actually clients of the process that creates them. This inversion seems to be quite common in testing frameworks. It makes it easier to support the one-to-many relationship between the runner and the agents. I borrowed it from Jamie Cansdale, So, whichever process creates the agent must implement the Bottom line... I think handling of agents is pretty well implemented in the current codebase and ready to use. |
|
Under "Existing Foundations" you wrote...
Check!
Yes... pluggable agents have two components a launcher and an agent. The agent is intended to run as a separate process in the same machine as the launcher that invoked it. The launcher, when used directly from
Framework drivers are not used for selecting execution location. Their function is to allow the runners that call it to work with multiple test frameworks. We currently support one pluggable framework driver, which runs NUnit V2 tests. As you note, extension loading is disabled. That's part of the process of splitting agent launchers more completely from the engine. In the V3 engine, of course, all agents were built in and there were a number of circular dependencies. Most have now been removed and all the code needed by an agent is currently in the OK, I promised to outline how the existing code expects to deal with remote agents. Here goes...
I see from your post that you are aware of all this. However, if you look at I admit that's pretty obscure, since the code is never called. :-) However, it's very easy to activate it when needed. Of the three agent types defined by
As mentioned earlier, I removed implementation of I'd love to see this happen, whether for 4.0 or a later feature release. But doing it requires somebody coming on board to do the work. |
|
@oznetmaster This will wrap up my initial comments to your idea... With regard to "Proposed Responsibilites" and "First Prototype" I think I'll wait for your reactions to what I've already posted. As to "Questions for the Project"...
As noted in my earlier comments, I think a new implementation of
Currently, each agent is able to determine and respond to whether it will work for a particular assembly. However, that action could only be invoked directly inside the remote server. However,
Sorry, not sure what you mean here.
Aside for doing what it says it will do, I'm not sure. Assuming it makes use of the NUnit packages that support agents, I think it would automatically be compatible. As to lifecycle, I'd imagine that the server itself should keep running until somebody shuts it down while the individual agent processes that it launches would be for single-use. Can you clarify the kind of extensibility you are thinking of?
Yes. We should talk about what point in the codebase to start from and how often to integrate changes |
|
@nunit/engine-team It would be great to get other folks' views on this idea. |
|
Thanks—that gives us a much clearer starting point. I agree that implementing By “staging,” I meant transferring the test assemblies, dependencies and data to a server-owned workspace, translating paths and cleaning that workspace afterwards. By “transport,” I meant the authenticated connection between the provider and server. I wasn’t proposing to replace NUnit’s agent protocol or introduce new extension contracts unnecessarily. Your suggested lifecycle makes sense: the server persists until explicitly shut down, while each agent process serves one execution session. My additional lifecycle questions concern cancellation, client disconnection and agent failure—particularly preserving results already received and ensuring one client cannot terminate another client’s agents. I also agree that availability checks belong behind For extensibility, I was thinking primarily about target-specific package preparation and execution requirements, including the existing CrestronHomeNUnit embedded host. I suggest proving ordinary machine execution first and introducing additional contracts only where that work demonstrates a need. Which branch or revision would you prefer us to start from? Would you favour an initial small change activating the provider service while preserving local execution, followed by incremental server and remote-provider contributions? |
@CharliePoole, an update on the remote-execution work following your architectural guidance. We now have a fully functional implementation in our development branches of the “console anywhere → remote anywhere” model, with our own comprehensive automated regression and integration suite across the supported platforms. Windows, Linux and macOS can host the Console/coordinator; the execution backends cover Windows, Linux, macOS and Mono/.NET Framework 4.7.2, including actual Crestron processors. Backend selection is independent of the Console’s operating system. The evidence records the specific routes and component versions tested; we are continuing to build out the complete source/target matrix. The implementation follows the architecture you outlined:
The Console continues to call the engine normally. The engine retains its package tree, runner aggregation, discovery, filtering and result handling. Our remote provider implements the agent-facing proxy and forwards operations and events. On a desktop backend, the server loads NUnit’s pluggable launchers through the existing extensibility mechanism and creates the standard single-use NUnit agent processes. Those agents connect back to the server that created them, as you described. The current route uses public extension/launcher composition and requires neither temporary friend access nor a separate local bridge process. The embedded backend supplies the same remote execution boundary within the processor’s existing Mono process, using NUnit’s framework API. Its platform-specific implementation stays with our separate CrestronHomeNUnit work. Generic NUnit components have no Crestron dependency. The changes and additions are divided as follows:
Preserving backward compatibility for ordinary local execution has been an explicit requirement throughout the Console/engine/agent work. We have verified the existing local execution path on Windows, Linux and macOS with NUnit 4/5, testing both with the remote provider absent and with it installed but unselected. Ordinary local packages continue to use TestAgency and the standard local agents. Engine regression results have also been compared with the baseline to distinguish existing platform failures from changes introduced by this work. The remote execution path uses the existing NUnit framework packages. Execution location is selected through the provider. An explicitly requested remote backend fails explicitly if unavailable, so a remote request cannot silently become a local run. We have also implemented deployment as part of test initiation. The workflow accepts an existing test package or prepares one, stages the appropriate server/agent/test bundle, starts execution, retrieves results and attachments, and removes its owned deployment after execution has actually stopped. The caller uses one test workflow. Explicit server definitions remain the starting point; automatic server discovery is unnecessary. Server provisioning and lifetime policy remain outside the engine, so independently provisioned servers can fit the same architecture. We have tested through both nunit-console and the test adapter. Console validation covers Windows, Linux and macOS hosts, including actual remote deployment and execution. Adapter validation covers VSTest command-line execution on all three desktop platforms, Visual Studio on Windows, and VS Code on Windows, macOS and Linux. Remote processor workflow Run/Stop has been exercised through the adapter in Windows editors and both Mac VS Code Dev Kit controllers; the Linux editor checks cover ordinary local NUnit 4/5 tests. This adapter route is our separate experimental CrestronHomeNUnit integration; the stock NUnit adapter continues to handle ordinary local tests. The engine extension alone does not add remote execution to the stock adapter. Our comprehensive test suite covers ordinary NUnit 4/5 discovery and execution, exact filtering, mixed-framework packages, per-test events and results, attachments and native dependencies, local execution compatibility, cancellation and timeouts, connection/server/coordinator failures, recovery, ownership and cleanup. It includes unit and protocol regressions, real standard-agent processes, cross-machine deployment and physical Mono processor runs, with independent post-run checks. Both Mac Dev Kit controllers also passed failed-then-passed result controls, confirming that a later passing test does not conceal a failed workflow. Cleanup is tied to confirmed process termination or embedded execution quiescence. A stop acknowledgement or synthesized completion event does not release the deployment or processor reservation. We intend to align the desktop stop policy with your ongoing #1187 work. Physical power-loss/reboot recovery remains additional coverage to develop. The working implementation currently uses pinned forks and compatible package/source combinations. Once your new beta is released, we will rebase our component forks onto the corresponding upstream revisions, drop changes already incorporated upstream, align the package set and rerun the platform and local-compatibility suites. That will give us a clear, reproducible base for the remaining PRs and a remote-execution preview release. I would welcome your comments on the implementation and especially your guidance on how you would like us to structure the contribution and release:
We can provide the source, test suite, responsibility map and reproducible platform reports for review. I am happy to work through the details after the beta is out; this update can serve as the starting point for that discussion. Prepared jointly by Neil Colvin (@oznetmaster) and OpenAI Codex. |
|
@CharliePoole, thanks for the coordinated beta.5 release. We have completed the rebase and package alignment, rerun the platform checks, and moved our maintained remote-execution prototype onto that baseline. The earlier update's “after the beta” work is now done. The baseline is Console/Engine/Common 4.0.0-beta.5, Engine.Api 4.0.0-beta.101, Extensibility 4.0.0-beta.6 and Agent.Core 4.0.0-beta.6, with our remaining source changes kept separately identifiable. The test payloads use NUnit 5.1.0-alpha.0.19; the upstream engine's own tests retain their declared NUnit 4.6.1. Your release has simplified our changes considerably. We have removed the private API package, the Common package-setting decoder repair and the provisional Console call-site adaptations. There are now zero Console frontend source changes in our working remote-execution stack. The engine still needs the provider integration, and the independent diagnostics/agent/launcher fixes remain separate contributions. The architecture remains exactly the route you outlined:
There is no additional local bridge process. A desktop server loads the standard launchers through NUnit.Extensibility. The embedded Mono/.NET Framework 4.7.2 backend uses the framework API in its existing host; the Crestron-specific implementation remains separate. Deployment is part of the testing workflow, using explicit target definitions, while provisioning and server lifetime policy stay outside the engine. The “console anywhere → remote anywhere” implementation remains functional, with our comprehensive automated suite covering Windows, Linux and macOS coordinators and desktop/embedded backends. The recorded evidence identifies the routes actually exercised rather than implying every possible source/target/runtime combination has been tested. For the beta.5 revalidation specifically:
Ordinary local execution continues through TestAgency and the standard local agents. It passes with the remote extension absent and with it installed but unselected. The earlier Visual Studio/VS Code checks remain recorded against their original component versions; this beta.5 pass reran Console and command-line adapter checks, not editor UI automation. Native macOS/Linux checks execute portable managed outputs built on Windows; we are not claiming fresh native source builds. The independent submissions have been updated to the release bases and their descriptions made current:
Cancellation cleanup still waits for confirmed worker termination or embedded-host quiescence, independently of stop acknowledgments or synthesized completion events. Integration with #1187's engine stop policy remains separate. With beta.5 established, I suggest making the provider contract/routing/ownership change the first focused remote-execution PR, with its local-compatibility regressions, followed by the general remote packages. I would welcome your guidance on three concrete points:
We have the source revisions, regression suite, responsibility map and reproducible platform evidence ready to support that review. The remote implementation is still a development preview; no production migration or remote-package release has been made. Prepared jointly by Neil Colvin (@oznetmaster) and OpenAI Codex. |
|
Ahead of our conversation this evening, we have also demonstrated remote execution through the current TestCentric GUI, using our beta.5-based engine and remote provider. No GUI source changes were needed for remote discovery, selected-test execution, results or output display. We added a .NET Framework build of our client extension and validated against a separate offline backend process. This also exposed an independent GUI cancellation problem, reproduced with the public engine and no remote components. The tested fix is submitted as TestCentric PR #1618. This gives us another working frontend for the architecture to discuss tonight. |
|
@CharliePoole, we would like to engage the adapter maintainers about their engine-v4 plans and requirements for remote execution. For context, our primary intended user workflow is through the NUnit test adapter—discovering, selecting and running ordinary NUnit tests from Test Explorer and Our experimental workflow-adapter integration provides useful evidence, but does not yet establish general remote execution through the stock adapter. We therefore want its maintainers’ input on discovery, test identity, filtering, cancellation, results and host-runtime compatibility before the shared contracts settle. We’ll link the existing engine work and ask where a focused contribution would fit their plans. Any resulting engine-contract proposals would come back here for discussion, and we’ll share the adapter discussion link. Prepared by Neil Colvin with assistance from OpenAI Codex. |
Uh oh!
There was an error while loading. Please reload this page.
Standard remote execution for NUnit Console and Engine
I was one of the authors of NUnit v3, and remote execution was something I always wanted the engine to support. I would like to discuss implementing that capability now, potentially as an extension or, if necessary, in a fork that could later be considered for upstream integration.
The goal is to run ordinary NUnit test packages on a selected machine or processor through the same NUnit Console/Engine discovery, filtering, event and result interfaces. One backend could be a normal machine running supported .NET runtimes; another could be an embedded processor whose application hosts NUnit directly.
CrestronHomeNUnit already implements remote NUnit execution on Crestron processors through a custom host and transport. Its processor host uses NUnitTestAssemblyRunner, while ordinary Windows tests use dotnet test and NUnit3TestAdapter. The proposal would bring that capability into a general NUnit engine model and support ordinary remote machines as well.
Existing foundations
V4 already runs test assemblies in separate agent processes, which seems like the right foundation. The current code has pluggable agent launchers, a RemoteProcess agent type and an internal agent-provider interface.
However, IAgentLauncher returns a local Process, ProcessRunner depends on TestAgency, and the current agent service only supplies local process agents. I also found the earlier Test Server proposal and pluggable agents discussion, which describe much of the intended direction.
Source references:
A framework driver seems less suitable for selecting execution location. V3 extension factories can intercept ordinary NUnit, but they are selected from framework references. The current v4 agent DriverService also has extension loading disabled. A project loader can describe a package and its target settings, but does not supply remote execution on its own.
Possible implementation paths
The first path would avoid a fork: a custom v4 launcher starts a local bridge process, which registers as an agent and forwards execution to the selected remote backend. This might deliver the required capability within the existing extension boundary.
The second path would introduce a public agent-provider contract that can acquire and release an ITestAgent without requiring a local Process. The existing launcher mechanism could remain behind a local-process provider, with a remote provider supplying a remote runner and explicit lifecycle.
I would prefer the extension route if it supports the complete behaviour cleanly. I am also willing to implement a substantial fork if that is what the general capability requires. Keeping the generic changes suitable for eventual upstream consideration is desirable, without assuming that a merge will happen.
Proposed responsibilities
The engine should retain its NUnit package, filter, event and result concepts. A remote backend would handle:
A normal machine backend could run a dedicated worker for the package. A Crestron backend would map that package to an installed processor test package and embedded NUnit host.
Crestron deployment, reservations, activation, restoration and application-specific evidence would remain backend/workflow responsibilities. They should not become mandatory concepts for ordinary remote-machine users.
Location selection should be explicit and independent of the NUnit framework reference. If a remote target is requested and unavailable, it should not silently execute the package locally.
First prototype
I plan to start with an offline experiment using loopback connections and separate local processes:
Successful discovery and execution alone will not be enough. In the inspected current source, the TCP proxy sends stop commands that the agent transport does not handle, and synchronous execution blocks its command loop. The framework bridge also still invokes StopRun(bool), while NUnit 5's public controller method is parameterless. These paths need explicit compatibility and cancellation tests.
Visual Studio/VSTest integration would be a separate step. An engine extension should not be assumed to integrate automatically with NUnit3TestAdapter or Test Explorer.
Questions for the project
I may proceed with the isolated prototype while this discussion develops. The intent is to establish a general remote execution capability while preserving ordinary local execution and keeping platform-specific deployment separate.
All reactions