diff --git a/.github/workflows/publish_nuget.yml b/.github/workflows/publish_nuget.yml index ee50509ef..d7398791a 100644 --- a/.github/workflows/publish_nuget.yml +++ b/.github/workflows/publish_nuget.yml @@ -222,8 +222,66 @@ 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 + env: + SMOKE_TEST_VERSION: ${{ steps.version.outputs.version }} + SMOKE_TEST_RUNTIME_PACKAGE: ${{ matrix.runtime_package }} + run: | + cd test/OpenCvSharp.PackageSmokeTest + dotnet run -c Release \ + -p:RestoreAdditionalProjectSources="$GITHUB_WORKSPACE/release-packages" \ + -p:SmokeTestPackageVersion="$SMOKE_TEST_VERSION" \ + -p:SmokeTestRuntimePackage="$SMOKE_TEST_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; +}