Skip to content

The suite on AnyUnit, and a browser-wasm leg - #66

Merged
jbtule merged 6 commits into
masterfrom
anyunit-port
Sep 22, 2026
Merged

jbtule merged 6 commits into
masterfrom
anyunit-port

Conversation

@jbtule

@jbtule jbtule commented Sep 22, 2026

Copy link
Copy Markdown
Member

Closes #63 (steps 3 and 4; steps 1, 2 and 5 landed in #64).

NUnit + NUnit3TestAdapter + Microsoft.NET.Test.Sdk give way to AnyUnit 1.3.0 on Microsoft.Testing.Platform, and a browser-wasm project runs the same sources under bun.

leg tests
net8.0 190
net10.0 190
net47 196
browser-wasm 101

No assertion was rewritten

All 460 assertion call sites are untouched. AnyUnit supplies the classic asserts as extension methods on IAssert, and C# will not dispatch an extension method when an argument is dynamic:

error CS1973: 'IAssert' has no applicable method named 'AreEqual' but appears to have an
extension method by that name. Extension methods cannot be dynamically dispatched.

That is 420 of the errors you get from a naive port, and it is unavoidable here, since what this suite tests is dynamic. NUnit's static Assert.AreEqual never hit it — a static call with dynamic arguments is simply bound at run time.

Support/Helper.cs grows ClassicAssert: ordinary instance methods forwarding to AnyUnit's extensions, with Helper shadowing its inherited Assert property with one. An instance call with dynamic arguments binds at run time exactly as the NUnit static call did. The engine assigns through IAssertionHelper, which still binds to the base's property, so the runner is unaffected. It also restores the exact-arity overloads that method-group conversions need (new Action(Assert.Fail)).

Sixty lines in one file, against rewriting 460 assertions or reshaping AnyUnit's public interface for a problem peculiar to this suite. Reported upstream as jbtule/AnyUnit#62, which is where the classic asserts came from.

The usings are plain using AnyUnit; rather than a fully-qualified AnyUnit.AssertionException because AnyUnit 1.3.0 moved its built-in style to its own namespace — jbtule/AnyUnit#66, a change this port asked for.

The wasm leg

Tests/UnitTestImpromptuInterface.Wasm links the same sources, less two files — for their dependencies rather than anything about proxies:

  • Linq.cs needs IronPython, a desktop-only hosting stack
  • SingleMethodInvoke.cs needs Moq, whose Castle DynamicProxy is a second run-time emitter

This matters more than coverage: this library emits proxy types at run time, and whether that works on wasm was a bug report (#43) rather than a known boundary. It does work, interpreted and AOT-published alike, and this leg keeps it that way. CI runs it in its own ubuntu job, since it needs the wasm-tools workload and a JS runtime.

Still open afterwards

#62 (removing the Dynamitey package dependency) is untouched by this.

🤖 Generated with Claude Code

https://claude.ai/code/session_016mwfq4oeZW8SiD4HjTHYdg

jbtule and others added 2 commits September 22, 2026 12:50
…erts

Every assertion call site is unchanged. AnyUnit supplies the classic
asserts as extension methods on IAssert, and C# will not dispatch an
extension method when an argument is dynamic (CS1973) - which is most of
this suite, since what it tests is dynamic. Support/Helper.cs shadows the
injected IAssert with ClassicAssert, whose ordinary instance methods
forward to them; an instance call with dynamic arguments binds at run
time, as it did against NUnit's static Assert.

222 tests, same as NUnit ran; the only failures are the wall-clock
*Timed benchmarks that fail under NUnit too (#63 moves them to
BenchmarkDotNet).

Needs AnyUnit 1.3.0, unreleased: built here against a local feed. net47
and the NuGet.config cleanup are not done yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016mwfq4oeZW8SiD4HjTHYdg
NUnit, NUnit3TestAdapter and Microsoft.NET.Test.Sdk give way to AnyUnit
1.3.0 on Microsoft.Testing.Platform. Every assertion call site is
unchanged: AnyUnit supplies the classic asserts as extension methods on
IAssert, and C# will not dispatch an extension method when an argument is
dynamic (CS1973) - which is most of this suite, since what it tests is
dynamic. Support/Helper.cs shadows the injected IAssert with
ClassicAssert, whose ordinary instance methods forward to them; an
instance call with dynamic arguments binds at run time, as it did against
NUnit's static Assert.

That the usings are now `using AnyUnit;` rather than a qualified
AnyUnit.AssertionException is thanks to AnyUnit 1.3.0 giving its built-in
style its own namespace - a change this port asked for.

The wasm project links the same sources, less two files, for their
dependencies rather than anything about proxies: Linq.cs needs IronPython
and SingleMethodInvoke.cs needs Moq, whose Castle DynamicProxy is a
second run-time emitter.

190 tests on net8.0 and net10.0, 196 on net47, 101 on browser-wasm.

Closes #63.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016mwfq4oeZW8SiD4HjTHYdg
Copilot AI lite review requested due to automatic review settings September 22, 2026 17:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Two critical issues and one moderate AOT coverage gap remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity

Open (2)
What changed in this PR

Migrates the test suite from NUnit to AnyUnit/Microsoft.Testing.Platform and adds browser-WASM execution through Bun.

Changes:

  • Replaces NUnit dependencies with AnyUnit and classic assertion compatibility.
  • Adds a linked browser-WASM test project and Bun runner.
  • Updates global test configuration and CI.
File Summary
Tests/​UnitTestImpromptuInterface/​UnitTestImpromptuInterface.csproj Configures AnyUnit test execution.
Tests/​UnitTestImpromptuInterface/​Support/​Helper.cs Adds classic assertion forwarding. Critical (3 votes): the shared helper remains compiled by the NUnit-based Clay project, which lacks AnyUnit references and will fail to resolve these types.
Tests/​UnitTestImpromptuInterface/​Support/​FixtureSetup.cs Updates test-runner imports.
Tests/​UnitTestImpromptuInterface/​SingleMethodInvoke.cs Updates test framework imports.
Tests/​UnitTestImpromptuInterface/​Savable.cs Updates test framework imports.
Tests/​UnitTestImpromptuInterface/​PrivateTest.cs Updates test framework imports.
Tests/​UnitTestImpromptuInterface/​MimicTest.cs Updates imports and assertion-helper inheritance.
Tests/​UnitTestImpromptuInterface/​Linq.cs Updates test framework imports.
Tests/​UnitTestImpromptuInterface/​ImpromptuDynamic.cs Updates AnyUnit imports.
Tests/​UnitTestImpromptuInterface/​Generics.cs Updates test framework imports.
Tests/​UnitTestImpromptuInterface/​Collectable.cs Updates test framework imports.
Tests/​UnitTestImpromptuInterface/​Basic.cs Updates AnyUnit imports.
Tests/​UnitTestImpromptuInterface.Wasm/​wwwroot/​runtests.mjs Boots and runs the WASM test assembly.
Tests/​UnitTestImpromptuInterface.Wasm/​UnitTestImpromptuInterface.Wasm.csproj Defines the browser-WASM test project.
global.json Selects Microsoft.Testing.Platform.
.github/​workflows/​build.yml Adds the browser-WASM CI job. Critical (3 votes): the script copy path is incorrect for the configured working directory. Moderate (1 vote): the job does not publish or exercise AOT output despite claiming AOT coverage.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/build.yml Resolved
…ready in the output

Two real ones:

- Microsoft.CSharp is inbox on net10.0-browser, and that is this project's
  only target framework, so referencing it is NU1510 — fatal once CI=true
  makes warnings errors, which is why it passed locally and failed in CI.
- The workflow copied runtests.mjs from a path that does not exist from
  the working directory (three .. segments land in bin/wwwroot). The
  Blazor SDK already copies the project's wwwroot into the output,
  harness included, so the copy goes away rather than gets corrected.

Verified with CI=true: builds clean, 101 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016mwfq4oeZW8SiD4HjTHYdg
@jbtule

jbtule commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

Both review points were real; fixed in the commit above.

The wasm job's build failure was NU1510: PackageReference Microsoft.CSharp will not be pruned — it is inbox on net10.0-browser, which is this project's only target framework, so listing it is a warning, and CI=true turns warnings into errors. That is why it built locally and failed in CI. Reference removed.

The cp path was wrong as described — and unnecessary: the Blazor SDK already copies the project's wwwroot into the output, harness included. Verified the file is there after a plain build, so the copy is deleted rather than corrected.

Re-verified with CI=true dotnet build ... --no-incremental (clean) and the wasm run (101 passed).

On Support/Helper.cs and the Clay project — correct that Tests/UnitTestImpromptuInterface.Clay compiles that file and has no AnyUnit packages. It is not in ImpromptuInterface.sln, so nothing builds it, which is why nothing here went red; it has also not been touched since 2017 and depends on ClaySharp, unshipped since 2012. #67 deletes it outright (it is also the source of the repo's remaining log4net advisory). Merging #67 first makes this moot; the alternative of keeping an NUnit-compatible helper alive for a project nobody builds is not worth it.

…the SDK

I claimed the Blazor SDK copies wwwroot into the build output and removed
the workflow's copy on that basis. It does not - only a publish does - and
the file I saw in the output was one I had copied by hand while testing,
so the check confirmed nothing. CI was right: "Module not found
runtests.mjs".

A None item with CopyToOutputDirectory does it, as the same harness in
FSharp.Interop.Dlr's Tests.Wasm does, so the workflow stays a bare
`bun runtests.mjs`. Verified from a clean bin/obj under CI=true: copied,
101 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016mwfq4oeZW8SiD4HjTHYdg
@jbtule

jbtule commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

Correction to my previous comment: I was wrong that the Blazor SDK copies wwwroot into the build output. It does not — only a publish does. The file I checked for was one I had copied by hand earlier while testing the leg, so that check confirmed nothing, and CI said so plainly: error: Module not found "runtests.mjs".

The harness is now copied by a None item with CopyToOutputDirectory, matching how the same harness is handled in FSharp.Interop.Dlr's Tests.Wasm, so the workflow step stays a bare bun runtests.mjs with no path juggling.

Verified from a deleted bin/obj under CI=true: builds clean, harness present in the output, 101 tests pass.

The NU1510 fix from the previous commit stands on its own.

Savable.cs calls BuildProxy.SaveableProxyMaker("II_Generated"), so running
the tests from the repository root writes II_Generated.dll there, and a
`git add -A` took it along with the port commit. Second time today the
same way (see #65); the .gitignore entry stops the third.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016mwfq4oeZW8SiD4HjTHYdg
…coverage

Each leg now runs the test executable directly rather than through
`dotnet test` - AnyUnit's MTP adapter makes it an exe, and that is what
takes --report-anyunit-json - and uploads its results. A summary job
renders every platform's results into one table in the run summary
through anyunit-report, AnyUnit's own converter, and uploads an HTML
report:

  190 tests across 2 platforms - 291 results
  | Platform             |  ✅ | ❌ | ⚠️ | ⊘ |  ∅ | Not run |
  | net10-browser-wasm   |  89 |  0 |  0 | 0 | 12 |      89 |
  | net8-osx-arm64       | 175 |  0 |  0 | 0 | 15 |       0 |

The wasm row's "not run" is the two files that project leaves out, for
IronPython and Moq.

Coverage runs through coverlet's MTP extension, so the test exe
instruments itself with no external collector, restricted to the library
because coverlet instruments anything it can find sources for. Currently
84.0% of lines and 69.5% of branches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016mwfq4oeZW8SiD4HjTHYdg
@jbtule
jbtule merged commit 61c8f61 into master Sep 22, 2026
14 checks passed
@jbtule
jbtule deleted the anyunit-port branch September 22, 2026 18:38
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.

Plan: modernize the build — MinVer, one GitHub Actions workflow, AnyUnit (incl. browser-wasm), BenchmarkDotNet

2 participants