From 736491a2f0486abcf667ce14dd90281a6cb002af Mon Sep 17 00:00:00 2001 From: shimat Date: Mon, 20 Jul 2026 02:00:42 +0900 Subject: [PATCH 1/2] Add a package smoke test gate before publishing to nuget.org Nothing today actually restores the packed .nupkg files as a real consumer would before pushing to nuget.org - the existing "test" jobs in windows.yml/manylinux.yml/macos.yml only ever exercise loose build output with the native binary copied in by hand, never the packaging path itself (RID asset placement, dependency graph). Added test/OpenCvSharp.PackageSmokeTest, a plain console app that references OpenCvSharp5 (+ a platform runtime package) via PackageReference rather than ProjectReference, with the package version and runtime package id supplied at build time via MSBuild properties so it always targets whatever is about to be published. publish_nuget.yml now runs it against a local feed built from the release-packages artifact, on both windows-latest and ubuntu-latest, and Publish's needs now include this job so a broken package blocks the nuget.org push instead of only being noticed after the fact. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/publish_nuget.yml | 57 ++++++++++++++++++- .../OpenCvSharp.PackageSmokeTest.csproj | 31 ++++++++++ test/OpenCvSharp.PackageSmokeTest/Program.cs | 50 ++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 test/OpenCvSharp.PackageSmokeTest/OpenCvSharp.PackageSmokeTest.csproj create mode 100644 test/OpenCvSharp.PackageSmokeTest/Program.cs diff --git a/.github/workflows/publish_nuget.yml b/.github/workflows/publish_nuget.yml index ee50509ef..f2a49553c 100644 --- a/.github/workflows/publish_nuget.yml +++ b/.github/workflows/publish_nuget.yml @@ -222,8 +222,63 @@ jobs: *.snupkg retention-days: 7 - Publish: + # --------------------------------------------------------------------------- + # Consume the actual, about-to-be-published .nupkg files as a real PackageReference + # (against a local feed, not a ProjectReference against source) before anything gets + # pushed to nuget.org. Catches packaging mistakes - wrong RID asset placement, a missing + # native library, a broken dependency graph - that the source-level "test" jobs in + # windows.yml/manylinux.yml/macos.yml never exercise, since those only ever run against + # loose build output with the native binary copied in by hand. + # --------------------------------------------------------------------------- + PackageSmokeTest: + name: Package smoke test (${{ matrix.os }}) needs: Prepare + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + runtime_package: OpenCvSharp5.Windows + - os: ubuntu-latest + runtime_package: OpenCvSharp5.official.runtime.linux-x64 + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v7 + + - name: Download release packages + uses: actions/download-artifact@v8 + with: + name: release-packages + path: release-packages + + - name: Install .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + + - name: Determine package version + id: version + shell: bash + run: | + file=$(ls release-packages/OpenCvSharp5.[0-9]*.nupkg | head -1) + name=$(basename "$file") + version=${name#OpenCvSharp5.} + version=${version%.nupkg} + echo "Package version under test: $version" + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Run package smoke test + shell: bash + run: | + cd test/OpenCvSharp.PackageSmokeTest + dotnet nuget add source "${{ github.workspace }}/release-packages" --name smoke-local + dotnet run -c Release \ + -p:SmokeTestPackageVersion=${{ steps.version.outputs.version }} \ + -p:SmokeTestRuntimePackage=${{ matrix.runtime_package }} + + Publish: + needs: [Prepare, PackageSmokeTest] runs-on: ubuntu-24.04 environment: nuget-production diff --git a/test/OpenCvSharp.PackageSmokeTest/OpenCvSharp.PackageSmokeTest.csproj b/test/OpenCvSharp.PackageSmokeTest/OpenCvSharp.PackageSmokeTest.csproj new file mode 100644 index 000000000..54e379eb3 --- /dev/null +++ b/test/OpenCvSharp.PackageSmokeTest/OpenCvSharp.PackageSmokeTest.csproj @@ -0,0 +1,31 @@ + + + + Exe + net10.0 + enable + enable + false + + + + + 0.0.0-dev + OpenCvSharp5.official.runtime.linux-x64 + + + + + + + + diff --git a/test/OpenCvSharp.PackageSmokeTest/Program.cs b/test/OpenCvSharp.PackageSmokeTest/Program.cs new file mode 100644 index 000000000..ee204a496 --- /dev/null +++ b/test/OpenCvSharp.PackageSmokeTest/Program.cs @@ -0,0 +1,50 @@ +// Pre-publish smoke test: exercises the actual .nupkg files about to be pushed to nuget.org, +// referenced the same way a real consumer would (PackageReference against a NuGet feed, not +// ProjectReference against source), to catch packaging mistakes (wrong RID asset placement, +// missing native library, broken dependency graph) that a source-level test can never see. +// Asserts and sets the process exit code instead of using a test framework, since the point is +// just "does a freshly `dotnet add package`-d app work at all", not exhaustive API coverage. +using System.Globalization; +using OpenCvSharp; + +try +{ + var version = Cv2.GetVersionString(); + + using var mat = new Mat(256, 256, MatType.CV_8UC1, Scalar.All(0)); + Cv2.Rectangle(mat, new Rect(40, 40, 120, 120), Scalar.All(255), thickness: -1); + Cv2.Circle(mat, new Point(180, 180), 30, Scalar.All(200), thickness: -1); + + using var orb = ORB.Create(500); + using var descriptors = new Mat(); + orb.DetectAndCompute(mat, default, out var keypoints, descriptors); + + foreach (var ext in new[] { ".jpg", ".png", ".tiff" }) + { + var encoded = Cv2.ImEncode(ext, mat, out var buf); + if (!encoded || buf.Length == 0) + throw new InvalidOperationException($"ImEncode({ext}) failed or produced an empty buffer"); + + using var decoded = Cv2.ImDecode(buf, ImreadModes.Unchanged); + if (decoded.Empty() || decoded.Size() != mat.Size()) + throw new InvalidOperationException( + $"ImDecode({ext}) round trip failed: empty={decoded.Empty()}, size={decoded.Size()}"); + } + + using var blob = Cv2.Dnn.BlobFromImage(mat, size: new Size(64, 64)); + if (blob.Empty()) + throw new InvalidOperationException("Cv2.Dnn.BlobFromImage produced an empty blob"); + + var blobShape = string.Join('x', Enumerable.Range(0, blob.Dims).Select(blob.Size)); + + Console.WriteLine(string.Format( + CultureInfo.InvariantCulture, + "ok: OpenCV {0}, ORB keypoints: {1}, dnn blob shape: {2}", + version, keypoints.Length, blobShape)); + return 0; +} +catch (Exception ex) +{ + Console.Error.WriteLine("error: " + ex); + return 1; +} From 0d0c1e9af24d96298effa4acc300f3445e2a8d24 Mon Sep 17 00:00:00 2001 From: shimat Date: Mon, 20 Jul 2026 02:19:38 +0900 Subject: [PATCH 2/2] Address CodeRabbit feedback: avoid global NuGet.Config mutation and raw expression interpolation dotnet nuget add source wrote to the runner's global NuGet.Config (state that could leak on a self-hosted runner, or be defeated by a repo-level NuGet.Config with ), and the step interpolated ${{ }} expressions directly into the bash script (zizmor template-injection finding). Replaced the local feed with -p:RestoreAdditionalProjectSources (scoped to this restore only) and routed the version/runtime-package values through env: instead of inline expression interpolation. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/publish_nuget.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish_nuget.yml b/.github/workflows/publish_nuget.yml index f2a49553c..d7398791a 100644 --- a/.github/workflows/publish_nuget.yml +++ b/.github/workflows/publish_nuget.yml @@ -270,12 +270,15 @@ jobs: - name: Run package smoke test shell: bash + env: + SMOKE_TEST_VERSION: ${{ steps.version.outputs.version }} + SMOKE_TEST_RUNTIME_PACKAGE: ${{ matrix.runtime_package }} run: | cd test/OpenCvSharp.PackageSmokeTest - dotnet nuget add source "${{ github.workspace }}/release-packages" --name smoke-local dotnet run -c Release \ - -p:SmokeTestPackageVersion=${{ steps.version.outputs.version }} \ - -p:SmokeTestRuntimePackage=${{ matrix.runtime_package }} + -p:RestoreAdditionalProjectSources="$GITHUB_WORKSPACE/release-packages" \ + -p:SmokeTestPackageVersion="$SMOKE_TEST_VERSION" \ + -p:SmokeTestRuntimePackage="$SMOKE_TEST_RUNTIME_PACKAGE" Publish: needs: [Prepare, PackageSmokeTest]