From be03e961e28fb55b3a53b3f063228e3a9086cf9b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Apr 2026 19:50:44 +0000 Subject: [PATCH 1/2] Add per-RID NuGet package smoke test to CI Introduces a standalone console project (tests/Rerun.Net.PackageSmokeTest) that consumes the built Rerun.Net NuGet package via a PackageReference and exercises the full managed -> native FFI path: version string lookup plus a RecordingStream -> Save -> Log -> Dispose round-trip that produces a non-empty .rrd file. A new package-smoke-test CI job downloads the nuget-package artifact and runs the smoke test on every supported RID (win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64) using native ARM64 runners where needed. Deploy now waits on package-smoke-test so a broken package cannot be published. The project is intentionally not part of Rerun.Net.slnx: the package it depends on only exists after the `package` job runs, so it is built standalone with an isolated NUGET_PACKAGES cache and a scoped NuGet.config pointing at the freshly produced artifacts/ folder. --- .github/workflows/ci.yml | 80 ++++++++++++++++- tests/Rerun.Net.PackageSmokeTest/NuGet.config | 18 ++++ tests/Rerun.Net.PackageSmokeTest/Program.cs | 90 +++++++++++++++++++ .../Rerun.Net.PackageSmokeTest.csproj | 31 +++++++ 4 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 tests/Rerun.Net.PackageSmokeTest/NuGet.config create mode 100644 tests/Rerun.Net.PackageSmokeTest/Program.cs create mode 100644 tests/Rerun.Net.PackageSmokeTest/Rerun.Net.PackageSmokeTest.csproj diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c895791..6244d04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,6 +140,8 @@ jobs: package: needs: [build-native, test] runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} steps: - uses: actions/checkout@v4 with: @@ -183,8 +185,84 @@ jobs: name: nuget-package path: artifacts/*.nupkg + # Smoke test the built NuGet package on every supported RID. + # + # This is NOT a rebuild of the solution - it consumes the .nupkg produced + # by the `package` job via a PackageReference, then runs a tiny console + # app that exercises the native FFI path. The purpose is to catch + # packaging-level regressions that project-reference tests miss: + # * missing runtimes/{rid}/native/* entries in the .nupkg + # * wrong architecture binaries shipped for a RID + # * DllImport resolver failing to find the native library + # when the package is consumed as a NuGet dependency + package-smoke-test: + needs: package + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + rid: win-x64 + - os: windows-11-arm + rid: win-arm64 + - os: ubuntu-latest + rid: linux-x64 + - os: ubuntu-24.04-arm + rid: linux-arm64 + - os: macos-13 + rid: osx-x64 + - os: macos-latest + rid: osx-arm64 + runs-on: ${{ matrix.os }} + env: + # Force an isolated package cache so we always consume the freshly + # built .nupkg, never a cached earlier version. + NUGET_PACKAGES: ${{ github.workspace }}/.smoke-nuget-cache + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + steps: + - uses: actions/checkout@v4 + # No submodules needed - the smoke test project has zero + # dependencies on the vendored rerun source tree. + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Download built NuGet package + uses: actions/download-artifact@v4 + with: + name: nuget-package + path: artifacts + + - name: List downloaded package + shell: bash + run: ls -la artifacts + + - name: Restore smoke test against local package + shell: bash + working-directory: tests/Rerun.Net.PackageSmokeTest + run: | + dotnet restore \ + -p:RerunNetVersion=${{ needs.package.outputs.version }} + + - name: Build smoke test + shell: bash + working-directory: tests/Rerun.Net.PackageSmokeTest + run: | + dotnet build --no-restore -c Release \ + -p:RerunNetVersion=${{ needs.package.outputs.version }} + + - name: Run smoke test (${{ matrix.rid }}) + shell: bash + working-directory: tests/Rerun.Net.PackageSmokeTest + run: | + dotnet run --no-build -c Release \ + -p:RerunNetVersion=${{ needs.package.outputs.version }} + deploy: - needs: [package, codegen-check] + needs: [package, package-smoke-test, codegen-check] if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest permissions: diff --git a/tests/Rerun.Net.PackageSmokeTest/NuGet.config b/tests/Rerun.Net.PackageSmokeTest/NuGet.config new file mode 100644 index 0000000..bff5658 --- /dev/null +++ b/tests/Rerun.Net.PackageSmokeTest/NuGet.config @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/tests/Rerun.Net.PackageSmokeTest/Program.cs b/tests/Rerun.Net.PackageSmokeTest/Program.cs new file mode 100644 index 0000000..183aa80 --- /dev/null +++ b/tests/Rerun.Net.PackageSmokeTest/Program.cs @@ -0,0 +1,90 @@ +// Package smoke test for Rerun.Net. +// +// Consumes the published Rerun.Net NuGet package and exercises the full +// managed -> native code path on whatever RID this process happens to be +// running on. The goal is to prove, per platform, that: +// +// 1. The package restores cleanly from a PackageReference. +// 2. The runtimes/{rid}/native/{libname} binary shipped inside the .nupkg +// is discovered and loaded by the custom DllImportResolver / .NET host. +// 3. A round-trip RecordingStream -> Save -> disposed flow actually +// produces a non-empty .rrd file. +// +// Exits 0 on success, non-zero on failure. Any exception is printed to +// stderr so the CI log makes the failure obvious. + +using System.Runtime.InteropServices; +using Rerun.Net; +using Rerun.Net.Archetypes; +using Rerun.Net.Components; +using Rerun.Net.Datatypes; + +Console.WriteLine("=== Rerun.Net package smoke test ==="); +Console.WriteLine($"RuntimeIdentifier : {RuntimeInformation.RuntimeIdentifier}"); +Console.WriteLine($"OSDescription : {RuntimeInformation.OSDescription}"); +Console.WriteLine($"OSArchitecture : {RuntimeInformation.OSArchitecture}"); +Console.WriteLine($"ProcessArch : {RuntimeInformation.ProcessArchitecture}"); +Console.WriteLine($"FrameworkDesc : {RuntimeInformation.FrameworkDescription}"); + +string? rrdPath = null; +try +{ + // Step 1: touch the native library via the version entry point. This is + // the cheapest call that forces rerun_c to load; if the native binary + // for this RID is missing or the wrong architecture, we fail here with + // a DllNotFoundException or BadImageFormatException before touching + // anything else. + var version = RecordingStream.VersionString(); + Console.WriteLine($"rerun_c version : {version}"); + if (string.IsNullOrWhiteSpace(version)) + throw new InvalidOperationException("rr_version_string returned an empty string."); + + // Step 2: exercise the full RecordingStream -> Save -> Log -> Dispose + // path to prove Arrow FFI marshalling and sink wiring work in the + // packaged build, not just for version strings. + rrdPath = Path.Combine( + Path.GetTempPath(), + $"rerun_net_pkg_smoke_{Guid.NewGuid():N}.rrd"); + + using (var rec = new RecordingStream("rerun_net_package_smoke_test")) + { + rec.Save(rrdPath); + rec.SetTimeSequence("frame", 0); + + var points = new Points3D( + new Position3D(new Vec3D([0f, 0f, 0f])), + new Position3D(new Vec3D([1f, 1f, 1f])), + new Position3D(new Vec3D([2f, 2f, 2f]))) + .WithColors( + new Color(new Rgba32(0xFF0000FFu)), + new Color(new Rgba32(0x00FF00FFu)), + new Color(new Rgba32(0x0000FFFFu))) + .WithRadii(new Radius(0.5f)); + + rec.Log("smoke/points", points); + } + + var info = new FileInfo(rrdPath); + if (!info.Exists) + throw new InvalidOperationException($"RRD file was not created at {rrdPath}."); + if (info.Length == 0) + throw new InvalidOperationException($"RRD file at {rrdPath} is empty."); + + Console.WriteLine($"Wrote {info.Length} bytes to {rrdPath}"); + Console.WriteLine("=== SMOKE TEST OK ==="); + return 0; +} +catch (Exception ex) +{ + Console.Error.WriteLine("=== SMOKE TEST FAILED ==="); + Console.Error.WriteLine(ex); + return 1; +} +finally +{ + if (rrdPath is not null && File.Exists(rrdPath)) + { + try { File.Delete(rrdPath); } + catch { /* best-effort cleanup */ } + } +} diff --git a/tests/Rerun.Net.PackageSmokeTest/Rerun.Net.PackageSmokeTest.csproj b/tests/Rerun.Net.PackageSmokeTest/Rerun.Net.PackageSmokeTest.csproj new file mode 100644 index 0000000..47dea86 --- /dev/null +++ b/tests/Rerun.Net.PackageSmokeTest/Rerun.Net.PackageSmokeTest.csproj @@ -0,0 +1,31 @@ + + + + + + Exe + Rerun.Net.PackageSmokeTest + Rerun.Net.PackageSmokeTest + false + + 0.0.0-ci-local + + + + + + + From f256507177de02622366af1bcb22474ff347dbae Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Apr 2026 06:20:12 +0000 Subject: [PATCH 2/2] Fix osx-x64 smoke test: use macos-latest with Rosetta macos-13 (Intel) runners have been deprecated. Switch to macos-latest (ARM64) and install the x64 .NET SDK via setup-dotnet's architecture parameter so the smoke test process runs under Rosetta as x86_64. Also made the dotnet-arch explicit for every matrix entry to keep the setup step self-documenting. https://claude.ai/code/session_01GKjza9KsbFiKKxyzXxeAqs --- .github/workflows/ci.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6244d04..bc0d5a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -203,16 +203,24 @@ jobs: include: - os: windows-latest rid: win-x64 + dotnet-arch: x64 - os: windows-11-arm rid: win-arm64 + dotnet-arch: arm64 - os: ubuntu-latest rid: linux-x64 + dotnet-arch: x64 - os: ubuntu-24.04-arm rid: linux-arm64 - - os: macos-13 + dotnet-arch: arm64 + # No Intel macOS runners remain; run the x64 SDK under Rosetta + # on an ARM64 runner instead. + - os: macos-latest rid: osx-x64 + dotnet-arch: x64 - os: macos-latest rid: osx-arm64 + dotnet-arch: arm64 runs-on: ${{ matrix.os }} env: # Force an isolated package cache so we always consume the freshly @@ -225,10 +233,11 @@ jobs: # No submodules needed - the smoke test project has zero # dependencies on the vendored rerun source tree. - - name: Setup .NET + - name: Setup .NET (${{ matrix.dotnet-arch }}) uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' + architecture: ${{ matrix.dotnet-arch }} - name: Download built NuGet package uses: actions/download-artifact@v4