Repository navigation
Fix Ably.PubSub.Device on .NET Framework and add a package-consumption test - #1373
Conversation
A .NET Framework app that installs Ably.PubSub.Device gets the Device netstandard2.0 asset (the Device package has no net46 asset) together with the Core net46 asset. The net46 core granted InternalsVisibleTo only to Ably.PubSub.Server, so PubSubDevice.CreateClient threw MethodAccessException when it called the internal PubSubRealtimeClient constructor. Add the keyless and keyed (Ably.PubSub.snk) Device grants, mirroring the netstandard core, and a shared unit test asserting that each core build grants both doors.
Under Mono MSBuild the SDK-style heads build only netstandard2.0, so a Unix Package run failed _Release_Verify_Files on the missing net6.0 and net7.0 assets. Build the door projects (and through them the core) with the .NET SDK as well, with the same Package properties. Windows is unchanged.
Test.PackageConsumption packs the three packages, restores small Device and Server apps from only those .nupkg files (plus nuget.org for third-party dependencies), asserts the lib/<tfm> asset NuGet selected for each Ably package from project.assets.json, and runs each app on net462, net48, net6.0 and net7.0. The apps create clients through their door and call into the core offline. Test.PackageConsumption.NoPack does the same against packages that are already packed. The expected asset pairs live in one matrix in package-consumption.cake.
The release dry run consumes the packages it has just packed, with the .NET Framework apps on the Windows CLR. The macOS Mono workflow packs and consumes them with the .NET Framework apps on Mono.
The package-consumption runner now resolves dotnet.exe by name on Windows rather than relying on the PATH fallback, and the Unix .NET SDK pass of _Package_Build_All sets ContinuousIntegrationBuild the way the MSBuild pass does.
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Package build support cake-build/tasks/package.cake, src/Ably.PubSub.Core.NETFramework/Properties/AssemblyInfo.cs, src/Ably.PubSub.Tests.Shared/* |
Non-Windows package builds now build the Device and Server project heads. Core grants internal access to both door assemblies, and a shared test checks those grants. |
Consumer apps and offline checks tests/PackageConsumption/Directory.Build.props, tests/PackageConsumption/DeviceApp/*, tests/PackageConsumption/ServerApp/*, tests/PackageConsumption/Shared/ConsumptionChecks.cs |
The Device and Server apps reference their respective packages. Shared checks cover channel access, expected offline REST behavior, REST agent headers, and realtime connection states. |
Cake restore, validation, and execution cake-build/build.cake, cake-build/tasks/package-consumption.cake |
Cake adds package-consumption targets that restore and build the apps, validate package declarations, selected assets, and package sources, then run the apps for each framework case. |
Workflow and usage integration .github/workflows/release-dry-run.yml, .github/workflows/run-tests-macos*.yml, cake-build/README.md |
The release dry-run and Mono workflows invoke package-consumption tests. The macOS workflows use macos-15, and the README documents the test targets and framework matrix. |
Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Bug fix
Sequence Diagram(s)
sequenceDiagram
participant Cake as Cake package-consumption target
participant NuGet as NuGet restore and build
participant Apps as DeviceApp and ServerApp
participant Checks as ConsumptionChecks
Cake->>NuGet: Restore apps from local package source
NuGet->>Apps: Build for selected target frameworks
Cake->>Cake: Validate package assets and restored sources
Cake->>Apps: Run each app on its target runtime
Apps->>Checks: Run offline package-consumption checks
Checks-->>Apps: Return pass or failure result
Suggested reviewers: ttypic
Merge Risk: 
Minimal · up to d81c5
-
The package-consumption change is mergeable after normal checks; the investigated build dependency is satisfied by the preceding restore.
Pre-merge checks: 
4 · 
1
-
Failed checks: 1 warning Passed checks: 4 passed-
Full details: Title check
-
Explanation
The title names two real changes in the PR: the .NET Framework Device fix (InternalsVisibleTo grants for Ably.PubSub.Device in the Core assembly) and the new package-consumption test (Cake targets and sample apps). It omits the macOS runner moves, the Unix SDK build change, and the release workflow wiring, but those are secondary to the two named changes. The title is specific and matches the main objectives.
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 9 functions across 5 files. (11 skipped: 11 unsupported.)
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
-
Finishing Touches
-
Generate docstrings
-
- Commit to this branch
- Create a new PR
Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
A rabbit checks each package seal,
Then hops through frameworks, row by row.
The apps test headers, links, and states,
While Cake records the checks below.
The packed-up paths are ready to go!
|
@coderabbitai review |
|
GitHub retires the macos-14 runner image on 2 November 2026, and its scheduled brownouts were already cancelling these jobs before any step ran. Move both macOS workflows to macos-15 so the .NET test legs and the Mono leg, including the package-consumption step, run again.
Summary
2.0 release blocker.
Ably.PubSub.Devicedidn't work in a .NET Framework app: the first call toPubSubDevice.CreateClient(...)threwSystem.MethodAccessException.Ably.PubSub.Serverworked in the same setup.This PR:
.nupkgfiles into real apps, so this class of bug fails CI from now on.Root cause
NuGet picks, per package, the asset closest to the app's target framework. For a
net462–net48xapp:lib/assets shippedAbly.PubSub.Corenet46,netstandard2.0,net6.0,net7.0net46(fromsrc/Ably.PubSub.Core.NETFramework)Ably.PubSub.Servernet46,netstandard2.0,net6.0,net7.0net46Ably.PubSub.Devicenetstandard2.0,net6.0,net7.0(nonet46)netstandard2.0So a .NET Framework device app runs Device
netstandard2.0+ Corenet46. The door factories call the core's internal client constructors, which needsInternalsVisibleTo. The netstandard core grants it to both doors. Thenet46core (src/Ably.PubSub.Core.NETFramework/Properties/AssemblyInfo.cs) granted it only toAbly.PubSub.Server, so the runtime refused Device's call.CI missed it for two reasons:
Fix
src/Ably.PubSub.Core.NETFramework/Properties/AssemblyInfo.csnow grantsAbly.PubSub.Deviceaccess, mirroringsrc/Ably.PubSub.Core/Properties/AssemblyInfo.cs:PACKAGEbuilds;PACKAGEbuilds, with the samePublicKey=as the existing Server grant (Ably.PubSub.snk). I checked it againstsn -Tpon the packed Device assembly, and it matches.Other places checked:
UNITY_PACKAGEgrants: not needed in the NETFramework core. The Unity plugin is built from the netstandard core.Ably.PubSub.Server.NETFramework: grants nothing, and nothing there needs changing.A small unit guard,
src/Ably.PubSub.Tests.Shared/CoreDoorAccessTests.cs, asserts that the core under test grants both doors. It lives in the shared test project, so it runs against both the netstandard/net6+ core (Tests.DotNET) and thenet46core (Tests.NETFramework).New package-consumption test
New Cake targets in
cake-build/tasks/package-consumption.cake:Test.PackageConsumption: runsPackage, then consumes the packages;Test.PackageConsumption.NoPack: consumes packages that are already in--packageOutput.What they do:
Copy
tests/PackageConsumption/{DeviceApp,ServerApp}totest-results/package-consumption.Restore through a generated
nuget.config:Ably.PubSub.*only from the packed.nupkgfiles, and everything else from nuget.org;Build each app for
net462,net48,net6.0andnet7.0.Read
obj/project.assets.jsonand assert:lib/<tfm>asset NuGet selected from each Ably package;Run each app. .NET Framework apps run on the .NET Framework CLR on Windows and on Mono elsewhere. Each app:
Channels.Get;AblyExceptionbecause a stub handler fails them. The stub also records theAbly-Agentheader, so the test checks theably-pubsub-device/ably-pubsub-serverside flag;Disconnected;Close().Everything runs offline, with no credentials.
One place for expectations: the expected asset pair per app and framework is a single matrix at the top of
package-consumption.cake. The run fails if a row names alib/<tfm>that the nuspec doesn't declare, so the matrix can't silently drift from the nuspecs. If the TFM layout is restructured, update that matrix and the test validates the new layout.Isolation: the test apps aren't in any
.sln, they're outsidesrc/(soCleanSolutionignores them), and they add no nuspec tonuget/. The release pre-flight's exact three-nuspec check is unaffected.Where it runs in CI:
release-dry-run.yml(windows-2022): a new "Consume the packed packages" step runsTest.PackageConsumption.NoPackagainst thedry-run-packagesthe job has just packed. The .NET Framework apps run on the Windows .NET Framework CLR. A release can't be cut from a commit whose packages don't work when installed.run-tests-macos-mono.yml(macos-14): a new step runs the fullTest.PackageConsumption, with the .NET Framework apps on Mono.run-tests-windows-netframework.yml: it would repeat the dry run's full Windows package build, and add no coverage the dry-run step doesn't already give.Supporting change:
Packagenow works on macOS/Linux. Under Mono, MSBuild builds onlynetstandard2.0of the SDK-style heads, because their csproj narrowsTargetFrameworkswhenMSBuildRuntimeTypeisMono. As a result_Release_Verify_Filesfailed on the missingnet6.0/net7.0assets. On non-Windows hosts,_Package_Build_Allnow also builds the Device and Server projects with the .NET SDK, using the same Package properties; the core builds through their project references. Windows, where releases are built, is unchanged.Validation
Package-consumption matrix (macOS, Mono 6.12.0.188, .NET 6.0.29 / 7.0.18 runtimes; packages packed at the committed version 2.0.0)
project.assets.json)lib/netstandard2.0+ Corelib/net46MethodAccessExceptionlib/netstandard2.0+ Corelib/net46MethodAccessExceptionlib/net6.0+ Corelib/net6.0lib/net7.0+ Corelib/net7.0lib/net46+ Corelib/net46lib/net46+ Corelib/net46lib/net6.0+ Corelib/net6.0lib/net7.0+ Corelib/net7.0How each column was produced:
AssemblyInfo.cschange), then run throughTest.PackageConsumption.NoPack. The independent validator reproduced the same result by reverting only that file. The target exits 1 with the two Device .NET Framework failures:./build.sh -- --target=Test.PackageConsumption --version=2.0.0 --packageOutput=<dir>passes all 8 cases.nuget.config, gave the same results before and after.Build and unit tests (macOS)
./build.sh --target=Build.NetStandard./build.sh --target=Build.NetFramework(Mono)./build.sh --target=Test.NetStandard.Unit.WithRetry --framework=net6.0CoreDoorAccessTests(Device, Server) pass./build.sh --target=Test.NetFramework.Unit.WithRetry(Mono)CoreDoorAccessTests(Device, Server) pass against the net46 core. No failures caused by this branch: see below./build.sh -- --target=Test.PackageConsumption --version=2.0.0 --packageOutput=<dir>About the .NET Framework unit failures on Mono:
origin/integration/v2fails 16 of 1440 tests on this Mac.Resource not found: ...test-app-setup.json/StatsInterval.jsonandAgentTests.PlatformRuntime_ShouldDetectCorrectRuntime, which are local Mono environment issues.xunit.console.exe -method, so they are load-sensitive flakes.Independent validation
An independent Opus validator, separate from the code's author, worked in its own worktrees. Every check passed:
project.assets.jsonitself to confirm the asset pairs.AssemblyInfo.csbrings back theMethodAccessExceptionfor DeviceApp net462/net48, and the target exits 1.PublicKeyin the packed net46 Core is byte-identical to the packed Device assembly's public key. All 11 packed Ably assemblies have token70c9e5c3d2c68b16.origin/integration/v2baseline that the .NET Framework failures predate this branch or are flaky.nuget/or the solutions, CI wiring, StyleCop/EditorConfig on added lines, and commit messages all PASS.dotnet.exeon Windows and settingContinuousIntegrationBuildin the Unix SDK pack pass. Both are applied in the last commit.CI on this PR
release-dry-run(windows-2022) passed, including the new "Consume the packed packages" step: 8/8 PASS, with the .NET Framework apps on the Windows .NET Framework CLR (Runtime: .NET Framework 4.0.30319.42000). Device net462/net48 now work on the real CLR.macOSandmacOS (Mono)) didn't run. GitHub rejected them before any step because of a scheduled brownout of themacos-14runner image, which is retired on November 2, 2026 ([macOS] macOS 14 Sonoma retirement: GitHub Actions and ADO arm64 on November 2; ADO Intel on November 23 actions/runner-images#13518). Other branches hit the same failure today, and a re-run hit it again. The Mono consumption leg is validated locally (table above). Re-run those workflows once the brownout window ends.Not verified locally
release-dry-runresult above.net8.0+ consumer apps: the .NET 8 SDK isn't installed here. They would select thenet7.0assets, the same pair as thenet7.0row. Adding them is a one-line matrix change.Follow-ups
macos-14before November 2, 2026 (e.g. tomacos-15). This affectsrun-tests-macos.ymlandrun-tests-macos-mono.ymlon every branch, so it belongs in its own PR.Test.NetFramework.Unit.WithRetryexits 0 even when tests fail.cake-build/helpers/test-retry.cakelooks for failures with the NUnit XPath//test-case[@success='False'], but the xunit v2 report uses<test result="Fail">. So it always finds "0 failed tests to retry", and the task swallows the first run's failure.//test[@result='Fail']and fail the task if failures remain after the retry.net8.0/net10.0rows to the matrix once the CI images' SDKs are pinned. They select thenet7.0assets today.package-consumption.cake. The test then validates the new layout.Reviewer notes
-localsuffix: the release pre-flight insidePackagerequires--versionto equalsrc/CommonAssemblyInfo.cs. Package source mapping and the private global packages folder stop the nuget.org2.0.0from ever being used instead, and the test asserts each package's.nupkg.metadatasource.RollForward=Major, so anet6.0app also runs where only a newer runtime is installed. Asset selection happens at restore, so this doesn't affect what is tested.Summary by CodeRabbit
New Features
Documentation
Chores