Skip to content

Fix --list-extensions showing wrong target framework for extensions - #1861

Merged
CharliePoole merged 2 commits into
nunit:mainfrom
HarnageaGabriel:fix/issue-839-list-extensions-target-framework
Aug 18, 2026
Merged

Fix --list-extensions showing wrong target framework for extensions#1861
CharliePoole merged 2 commits into
nunit:mainfrom
HarnageaGabriel:fix/issue-839-list-extensions-target-framework

Conversation

@HarnageaGabriel

Copy link
Copy Markdown
Collaborator

Fixes #839

Problem

--list-extensions never displayed a target framework for installed extensions. The user-visible symptom reported in #839 was that a TeamCity extension targeting .NET Standard 2.0 appeared to be running under .NET 4.0/4.7 — the runtime the console itself was running on, not the extension's own declared target.

Root cause

ExtensionAssembly (src/NUnitCommon/nunit.extensibility/ExtensionAssembly.cs) already reads an extension assembly's own TargetFrameworkAttribute correctly via TestCentric.Metadata, exposed as FrameworkName. But that value was never surfaced anywhere — ConsoleRunner.DisplayExtension (src/NUnitConsole/nunit4-console/ConsoleRunner.cs) never printed a framework field at all.

The natural fix would be adding TargetFramework to NUnit.Extensibility.IExtensionNode and threading it through ExtensionNode. However, nunit4-console only ever sees extension nodes through the console's IExtensionService/IExtensionNode, which resolve (transitively, through nunit.engine's NUnit.Engine.Api package reference) to the externally published, version-pinned NUnit.Extensibility.Api NuGet package rather than this repo's local project source. Extending the local interface doesn't reach the console without also bumping and republishing that package — out of scope for this fix.

Fix

ConsoleRunner now reads each listed extension's declared target framework directly from its own assembly file (node.AssemblyPath), using the same TestCentric.Metadata technique already used by ExtensionAssembly. This reports what the extension itself declares, independent of the console's own runtime.

Note: the issue also speculated about a secondary bug where an extension targeting .NET 4.7 might display as .NET 4.0 — that would be in TestCentric.Metadata's own attribute/runtime-version parsing (an external dependency), not in this repo, so it isn't addressed here.

Testing

  • dotnet build on the affected projects (0 warnings, 0 errors).
  • Verified via a standalone harness against net462 and netstandard2.0 builds of the repo's own FakeExtensions test data that the new code path correctly reports .NETFramework,Version=v4.6.2 vs .NETStandard,Version=v2.0 for each assembly's own target, rather than the console's runtime.
  • Wasn't able to get --extensionDirectory to pick up the loose FakeExtensions build locally to screenshot full CLI output (addin discovery/packaging format), so verification was done at the metadata-reading level directly. dotnet test for nunit4-console.tests currently fails to run in this environment due to a pre-existing, unrelated NUnit3TestAdapter/nunit.engine.api v3.0.0.0 binding issue (reproduces identically on main without this change).

DisplayExtension() never showed a target framework at all, and the only
existing per-extension framework computation (ExtensionAssembly.FrameworkName)
lives behind the local NUnit.Extensibility.IExtensionNode interface, which
the console only ever sees through the older, externally pinned
NUnit.Engine.Api/NUnit.Extensibility.Api package version consumed
transitively by nunit.engine -- so extending that interface doesn't reach
ConsoleRunner without a coordinated package version bump across the repo.

Read the extension's declared target framework straight from its own
assembly metadata (TestCentric.Metadata, same library and technique already
used by ExtensionAssembly) instead, so the console reports what the
extension assembly itself declares rather than the runtime it happens to be
loaded under.

Fixes nunit#839
@CharliePoole

Copy link
Copy Markdown
Member

@HarnageaGabriel I approved your PR to run so we can see how it works. I'd actually prefer a unit test as well, but lets hold off on that.

I agree with what you stated about the "natural fix", which is what I would really prefer...

The natural fix would be adding TargetFramework to NUnit.Extensibility.IExtensionNode and threading it through ExtensionNode. However, nunit4-console only ever sees extension nodes through the console's IExtensionService/IExtensionNode, which resolve (transitively, through nunit.engine's NUnit.Engine.Api package reference) to the externally published, version-pinned NUnit.Extensibility.Api NuGet package rather than this repo's local project source. Extending the local interface doesn't reach the console without also bumping and republishing that package — out of scope for this fix.

I think we could make this work but let's see how the present fix works first.

@CharliePoole

Copy link
Copy Markdown
Member

@HarnageaGabriel Your build has run afoul of ongoing changes to the build process. The recipe used by all the repos I manage has been updated, but not all the repos have been updated to work with it. I'll get a change in so it builds correctly and then retry your PR.

@CharliePoole

Copy link
Copy Markdown
Member

@HarnageaGabriel I just pushed a new version of the cake recipe, which may help. Change your build.cake to use version 2.0.0-beta.4.9 of the recipe. No guarantees, I'm afraid, since you are doing something new by accessing the extensibility framework from within the console runner. It's exactly the purpose for which I made NUnit.Extensibility a separate assembly, but it hasn't been tried out yet.

If that doesn't work, I'll pull your changes down and play with the code myself.

@HarnageaGabriel

Copy link
Copy Markdown
Collaborator Author

Thanks for the pointer — bumped build.cake to NUnit.Cake.Recipe 2.0.0-beta.4.9 and pushed. Let me know if it needs another retry or if you'd rather pull the branch down yourself.

@CharliePoole
CharliePoole self-requested a review August 18, 2026 14:25

@CharliePoole CharliePoole left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works great now that the build is fixed. We can merge it or work for a better solution involving the extensibility API. Or we can do both. See general discussion for more about this choice.

_outWriter.WriteLine(ColorStyle.Value, node.AssemblyVersion.ToString());

_outWriter.Write(INDENT8 + "Framework: ");
_outWriter.WriteLine(ColorStyle.Value, GetTargetFrameworkDisplayName(node.AssemblyPath));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an ideal solution, this would simply use something like node.TargetFrameworkName and method GetTargetFrameworkDisplayName wouldn't be needed. OTOH, we could deploy this immediately and its availability would assist in debugging problems we tend to run into with loading extensions.

@CharliePoole

Copy link
Copy Markdown
Member

@HarnageaGabriel

This is all passing with the use of the (interim) recipe version 2.0.0-beta.4.9. Nice work!

Theoretically speaking, we have three possible paths here.

  1. Merge this
  2. Continue to work toward a solution where you merely need to examine a field in the API to get the framework.
  3. Do 1 and then do 2.

Not merging what we have seems silly to me so I discount 2 and I'll go ahead and merge this.

So the question remains... would you like to work on the "improved solution" going forward? You pointed out some of the difficulties with the API in your analysis, but I think they are all soluble. Equally important, they are difficulties we will eventually need to solve anyway, so why not now?

Let me know and we can start discussing the next steps.

Charlie

@OsirisTerje I wanted you to take a look at this issue because the solution @HarnageaGabriel provided is the same sort of thing that the adapter may need to do at some point - not for the same reason, but for some as yet unknown problem with extensions.

@CharliePoole
CharliePoole merged commit 52adf5b into nunit:main Aug 18, 2026
3 checks passed
@HarnageaGabriel

Copy link
Copy Markdown
Collaborator Author

@CharliePoole I'm interested in taking a crack at the improved solution — threading TargetFramework through IExtensionNode properly. Could you point me at the repo where NUnit.Extensibility.Api actually lives/gets published from? Want to understand the versioning/release process before proposing changes there, and happy to loop in @OsirisTerje if there's overlap with the adapter's needs. No fixed timeline on my end, just want to get the scope right.

@CharliePoole

Copy link
Copy Markdown
Member

@HarnageaGabriel That's great. For a fuller discussion of the overall process, we should probably get out of this thread. :-) Send me an email at charliepoole AT gmail DOT com and I'll respond with more info.

As to the repo... it's this one! look at src/NUnitCommon/nunit.extensibility and src/NUnitCommon/nunit.extensibility.api. It's possible that these will be moved to a separate repo, but for now they are here. Two other packages, NUnit.Engine.Api and NUnit.Agent.Core have been moved to separate repos and the extensibility packages may be moved as well, but only after some other pending changes are made.

As for the adapter, that's a bit further in the future, but I can imagine it needing to use the extensibility interface directly rather than through the engine, so I wanted to point this issue out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error with --list-extensions listing for extension that target .NET Standard

2 participants