mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-24 11:01:55 +08:00
## Summary - update NuGet dependencies to their latest compatible stable releases while retaining documented compatibility, native, security, and mocking pins - align the nbgv CLI with the central `Nerdbank.GitVersioning` version (3.10.94) and fail explicitly on tool installation/update errors - update the synchronized OpenUSD family to `0.14.0-alpha`; adapt the Vision blank-frame fixture to the new `RenderDiagnosticsState` constructor argument - retain `Microsoft.CodeAnalysis` API packages at the existing 5.0/4.14 host bands: Roslyn 5.9 requires Immutable/Metadata 10.0.1+, while the retained older compiler-host closure is intentionally pinned to 9.0.0 ## Dependency updates - `Dekaf` 1.17.0 → 1.21.1 - legacy ASP.NET Core 2.3 dependencies → 2.3.13; net8/net9 authentication and WebApi test packages → 8.0.31/9.0.20 - `Microsoft.CodeAnalysis.Analyzers` 4.14.0 → 5.9.0 - `TUnit` 1.68.0 → 1.69.0 - `Avalonia` / `Avalonia.Desktop` 12.1.2 → 12.1.3 - schema-test `System.Collections.Immutable` overrides 10.0.11 → 10.0.12 - `AzureSignTool` 3.0.0 → 7.0.1 and `NuGetKeyVaultSignTool` 3.0.45 → 3.2.3 - eight direct OpenUSD packages 0.12.0-alpha → 0.14.0-alpha ## Validation - `dotnet restore UA.slnx --disable-parallel` - clean `UA.slnx` Release build with `CustomTestTarget=net10.0` - `UA.slnx` Release builds with `net48` and `netstandard2.1` - OpenUSD and Vision test suites on net8/net9/net10: 882/882/921 and 484 passed + 1 environment skip on each target - full `Opc.Ua.Tools.Tests` net10.0: 963 passed, 1 skipped - source-generator package validation against a locally packed release package set - updated signing tools installed in an isolated directory; existing `sign` CLI options verified without credentials - nbgv 3.10.94 bootstrap exercised in simulated GitHub CI; disposable committed fixture verified canonical and case-invalid release refs with a four-component version ## Limits No protected signing/publication, Linux/macOS native rendering, or full stress/nightly suites were run locally.
230 lines
8.9 KiB
C#
230 lines
8.9 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
using System;
|
|
using System.Reflection;
|
|
using NUnit.Framework;
|
|
using Opc.Ua.Vision.OpenUsd.Rendering;
|
|
using OpenUsd.Rendering;
|
|
using OpenUsd.Rendering.Silk;
|
|
|
|
namespace Opc.Ua.Vision.Tests
|
|
{
|
|
/// <summary>
|
|
/// Pins the guarantee that the OpenUsd provider will never surface a
|
|
/// blank frame as a successful image. The guard exists because the
|
|
/// underlying Silk backend has been observed to return blank frames
|
|
/// after session reuse, and vision consumers must not treat that as
|
|
/// a valid capture. A refactor that changes the definition of
|
|
/// "blank" in any way must update these tests deliberately.
|
|
/// </summary>
|
|
[TestFixture]
|
|
public sealed class BlankFrameGuardTests
|
|
{
|
|
[Test]
|
|
public void DrawCountZeroIsBlankAndReasonExplainsWhy()
|
|
{
|
|
SilkFrameCaptureResult capture = MakeCapture(
|
|
width: 4, height: 4,
|
|
rgba: MakeGradient(4, 4),
|
|
drawCount: 0,
|
|
meshCount: 5);
|
|
|
|
BlankFrameCheck check = BlankFrameGuard.Check(capture);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(check.IsBlank, Is.True);
|
|
Assert.That(check.DrawCount, Is.EqualTo(0));
|
|
Assert.That(check.MeshCount, Is.EqualTo(5));
|
|
Assert.That(check.IsUniform, Is.False,
|
|
"IsUniform must remain false because the guard short-circuits before scanning pixels.");
|
|
Assert.That(check.Reason, Is.Not.Null);
|
|
Assert.That(check.Reason, Does.Contain("drawCount=0"));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void MeshCountZeroIsBlankEvenWhenDrawCountIsPositive()
|
|
{
|
|
SilkFrameCaptureResult capture = MakeCapture(
|
|
width: 4, height: 4,
|
|
rgba: MakeGradient(4, 4),
|
|
drawCount: 3,
|
|
meshCount: 0);
|
|
|
|
BlankFrameCheck check = BlankFrameGuard.Check(capture);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(check.IsBlank, Is.True);
|
|
Assert.That(check.DrawCount, Is.EqualTo(3));
|
|
Assert.That(check.MeshCount, Is.EqualTo(0));
|
|
Assert.That(check.Reason, Does.Contain("meshCount=0"));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void UniformRgbaBufferIsBlankAndFlaggedAsUniform()
|
|
{
|
|
byte[] rgba = new byte[4 * 4 * 4];
|
|
for (int i = 0; i < rgba.Length; i += 4)
|
|
{
|
|
rgba[i] = 0x20;
|
|
rgba[i + 1] = 0x30;
|
|
rgba[i + 2] = 0x40;
|
|
rgba[i + 3] = 0xFF;
|
|
}
|
|
SilkFrameCaptureResult capture = MakeCapture(4, 4, rgba, drawCount: 1, meshCount: 1);
|
|
|
|
BlankFrameCheck check = BlankFrameGuard.Check(capture);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(check.IsBlank, Is.True);
|
|
Assert.That(check.IsUniform, Is.True);
|
|
Assert.That(check.DrawCount, Is.EqualTo(1));
|
|
Assert.That(check.MeshCount, Is.EqualTo(1));
|
|
Assert.That(check.Reason, Is.Not.Null);
|
|
Assert.That(check.Reason, Does.Contain("same value"));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void NonUniformBufferWithPositiveDrawAndMeshCountsIsNotBlank()
|
|
{
|
|
byte[] rgba = MakeGradient(4, 4);
|
|
SilkFrameCaptureResult capture = MakeCapture(4, 4, rgba, drawCount: 12, meshCount: 3);
|
|
|
|
BlankFrameCheck check = BlankFrameGuard.Check(capture);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(check.IsBlank, Is.False);
|
|
Assert.That(check.IsUniform, Is.False);
|
|
Assert.That(check.DrawCount, Is.EqualTo(12));
|
|
Assert.That(check.MeshCount, Is.EqualTo(3));
|
|
Assert.That(check.Reason, Is.Null,
|
|
"A non-blank frame must not surface a reason - callers switch on Reason != null.");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void CheckThrowsArgumentNullExceptionForNullResult()
|
|
{
|
|
Assert.That(
|
|
() => BlankFrameGuard.Check(null!),
|
|
Throws.TypeOf<ArgumentNullException>());
|
|
}
|
|
|
|
[Test]
|
|
public void SingletonNonBlackPixelIsEnoughToBeConsideredNonBlank()
|
|
{
|
|
byte[] rgba = new byte[4 * 4 * 4];
|
|
for (int i = 0; i < rgba.Length; i += 4)
|
|
{
|
|
rgba[i + 3] = 0xFF;
|
|
}
|
|
rgba[0] = 0x01;
|
|
SilkFrameCaptureResult capture = MakeCapture(4, 4, rgba, drawCount: 1, meshCount: 1);
|
|
|
|
BlankFrameCheck check = BlankFrameGuard.Check(capture);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(check.IsBlank, Is.False,
|
|
"A single differing pixel must be enough to disqualify the frame as uniform.");
|
|
Assert.That(check.IsUniform, Is.False);
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void EmptyRgbaBufferIsTreatedAsUniformAndBlank()
|
|
{
|
|
byte[] rgba = new byte[3];
|
|
SilkFrameCaptureResult capture = MakeCapture(0, 0, rgba, drawCount: 1, meshCount: 1);
|
|
|
|
BlankFrameCheck check = BlankFrameGuard.Check(capture);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(check.IsBlank, Is.True,
|
|
"A buffer smaller than a single RGBA8 pixel has nothing to render, so " +
|
|
"the guard must classify it as blank rather than pretend it is a good frame.");
|
|
Assert.That(check.IsUniform, Is.True,
|
|
"The empty-buffer branch of IsUniformRgba8 returns true; the guard " +
|
|
"must propagate that as IsUniform=true so callers can distinguish it " +
|
|
"from a no-draws blank.");
|
|
});
|
|
}
|
|
|
|
private static byte[] MakeGradient(int width, int height)
|
|
{
|
|
byte[] rgba = new byte[width * height * 4];
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
int i = ((y * width) + x) * 4;
|
|
rgba[i] = (byte)(x * 8);
|
|
rgba[i + 1] = (byte)(y * 8);
|
|
rgba[i + 2] = (byte)((x + y) * 4);
|
|
rgba[i + 3] = 0xFF;
|
|
}
|
|
}
|
|
return rgba;
|
|
}
|
|
|
|
private static SilkFrameCaptureResult MakeCapture(
|
|
int width, int height, byte[] rgba, int drawCount, int meshCount)
|
|
{
|
|
var stats = new SilkSceneGpuStatistics(meshCount, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL);
|
|
var render = new SilkMeshRenderResult(drawCount, 0, stats);
|
|
ConstructorInfo? ctor = typeof(SilkFrameCaptureResult).GetConstructor(
|
|
BindingFlags.NonPublic | BindingFlags.Instance,
|
|
binder: null,
|
|
types: new[]
|
|
{
|
|
typeof(int),
|
|
typeof(int),
|
|
typeof(byte[]),
|
|
typeof(SilkMeshRenderResult),
|
|
typeof(ulong),
|
|
typeof(uint),
|
|
typeof(RenderDiagnosticsState)
|
|
},
|
|
modifiers: null);
|
|
Assert.That(ctor, Is.Not.Null,
|
|
"SilkFrameCaptureResult's internal capture constructor must exist for BlankFrameGuard tests to construct fixtures.");
|
|
return (SilkFrameCaptureResult)ctor!.Invoke(
|
|
new object[] { width, height, rgba, render, 0UL, 0U, RenderDiagnosticsState.Empty });
|
|
}
|
|
}
|
|
}
|