diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c895791..bc0d5a9 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,93 @@ 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 + 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 + 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 + # 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 (${{ 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 + 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 + + + + + + +