From 3703ad0967b7aaca9db047f89df9cb83c8b29311 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Tue, 27 Oct 2026 06:11:28 +0100 Subject: [PATCH] GDS tests: compare PushTest trust lists independent of entry order ReadTrustList returns the store contents in directory enumeration order. The custom certificate group store under %TEMP%/OPC/GDS/CA/custom is shared and never cleaned, so it gains a CA certificate and CRL with every GDS start. Once a list holds more than one entry, the order-sensitive Utils.IsEqual check in UpdateTrustListOfCustomGroupAsync depends on the file-name hash order on Linux and failed every fixture of a CI job. Compare each list of the TrustList as a multiset instead. Co-Authored-By: Claude Opus 5.5 --- tests/Opc.Ua.Gds.Tests/PushTest.cs | 57 ++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/tests/Opc.Ua.Gds.Tests/PushTest.cs b/tests/Opc.Ua.Gds.Tests/PushTest.cs index 769edf9b18..4eaecfc6b2 100644 --- a/tests/Opc.Ua.Gds.Tests/PushTest.cs +++ b/tests/Opc.Ua.Gds.Tests/PushTest.cs @@ -199,6 +199,35 @@ namespace Opc.Ua.Gds.Tests m_securityPolicyUri = securityPolicyUri; } + /// + /// Compares two TrustLists ignoring the order of the entries in each list. + /// ReadTrustList returns the store contents in directory enumeration order, + /// which is not stable across a rewrite of the store (on Linux it follows + /// the file name hash), so an order-sensitive compare fails as soon as a + /// list holds more than one entry. + /// + private static bool AreEquivalent(TrustListDataType expected, TrustListDataType actual) + { + return expected.SpecifiedLists == actual.SpecifiedLists && + AreEquivalent(expected.TrustedCertificates, actual.TrustedCertificates) && + AreEquivalent(expected.TrustedCrls, actual.TrustedCrls) && + AreEquivalent(expected.IssuerCertificates, actual.IssuerCertificates) && + AreEquivalent(expected.IssuerCrls, actual.IssuerCrls); + + static bool AreEquivalent(ArrayOf expected, ArrayOf actual) + { + return expected.Count == actual.Count && + Sorted(expected).SequenceEqual(Sorted(actual), StringComparer.Ordinal); + } + + static IEnumerable Sorted(ArrayOf entries) + { + return (entries.ToArray() ?? []) + .Select(entry => Utils.ToHexString(entry.ToArray())) + .OrderBy(entry => entry, StringComparer.Ordinal); + } + } + private static bool IsCurveSupported(ECCurve curve) { ECDsa? key = null; @@ -464,13 +493,13 @@ namespace Opc.Ua.Gds.Tests Assert.That(applyChangesRequired, Is.True); await m_pushClient.PushClient.ApplyChangesAsync().ConfigureAwait(false); TrustListDataType expectEmptyTrustList = await m_pushClient.PushClient.ReadTrustListAsync().ConfigureAwait(false); - Assert.That(Utils.IsEqual(expectEmptyTrustList, emptyTrustList), Is.True); + Assert.That(AreEquivalent(expectEmptyTrustList, emptyTrustList), Is.True); applyChangesRequired = await m_pushClient.PushClient .UpdateTrustListAsync(fullTrustList).ConfigureAwait(false); Assert.That(applyChangesRequired, Is.True); await m_pushClient.PushClient.ApplyChangesAsync().ConfigureAwait(false); TrustListDataType expectFullTrustList = await m_pushClient.PushClient.ReadTrustListAsync().ConfigureAwait(false); - Assert.That(Utils.IsEqual(expectFullTrustList, fullTrustList), Is.True); + Assert.That(AreEquivalent(expectFullTrustList, fullTrustList), Is.True); } [Test] @@ -493,13 +522,13 @@ namespace Opc.Ua.Gds.Tests .UpdateTrustListAsync(groupId, emptyTrustList, 0).ConfigureAwait(false); Assert.That(applyChangesRequired, Is.False); TrustListDataType expectEmptyTrustList = await m_pushClient.PushClient.ReadTrustListAsync(groupId).ConfigureAwait(false); - Assert.That(Utils.IsEqual(expectEmptyTrustList, emptyTrustList), Is.True); + Assert.That(AreEquivalent(expectEmptyTrustList, emptyTrustList), Is.True); applyChangesRequired = await m_pushClient.PushClient .UpdateTrustListAsync(groupId, fullTrustList, 0).ConfigureAwait(false); Assert.That(applyChangesRequired, Is.False); TrustListDataType expectFullTrustList = await m_pushClient.PushClient.ReadTrustListAsync(groupId).ConfigureAwait(false); - Assert.That(Utils.IsEqual(expectFullTrustList, fullTrustList), Is.True); + Assert.That(AreEquivalent(expectFullTrustList, fullTrustList), Is.True); } [Test] @@ -535,12 +564,12 @@ namespace Opc.Ua.Gds.Tests Assert.That( afterAddTrustList.IssuerCertificates.Count, Is.EqualTo(beforeTrustList.IssuerCertificates.Count)); - Assert.That(Utils.IsEqual(beforeTrustList, afterAddTrustList), Is.False); + Assert.That(AreEquivalent(beforeTrustList, afterAddTrustList), Is.False); await m_pushClient.PushClient.RemoveCertificateAsync(groupId, trustedCert.Thumbprint, true).ConfigureAwait(false); TrustListDataType afterRemoveTrustList = await m_pushClient.PushClient.ReadTrustListAsync(groupId).ConfigureAwait(false); - Assert.That(Utils.IsEqual(beforeTrustList, afterRemoveTrustList), Is.True); + Assert.That(AreEquivalent(beforeTrustList, afterRemoveTrustList), Is.True); } /// @@ -593,11 +622,11 @@ namespace Opc.Ua.Gds.Tests Assert.That( afterAddTrustList.IssuerCertificates.Count, Is.EqualTo(beforeTrustList.IssuerCertificates.Count)); - Assert.That(Utils.IsEqual(beforeTrustList, afterAddTrustList), Is.False); + Assert.That(AreEquivalent(beforeTrustList, afterAddTrustList), Is.False); await m_pushClient.PushClient.RemoveCertificateAsync(trustedCert.Thumbprint, true).ConfigureAwait(false); await m_pushClient.PushClient.ApplyChangesAsync().ConfigureAwait(false); TrustListDataType afterRemoveTrustList = await m_pushClient.PushClient.ReadTrustListAsync().ConfigureAwait(false); - Assert.That(Utils.IsEqual(beforeTrustList, afterRemoveTrustList), Is.True); + Assert.That(AreEquivalent(beforeTrustList, afterRemoveTrustList), Is.True); } [Test] @@ -613,7 +642,7 @@ namespace Opc.Ua.Gds.Tests afterAddTrustList.TrustedCertificates.Count, Is.GreaterThan(beforeTrustList.TrustedCertificates.Count)); Assert.That(beforeTrustList.TrustedCrls.Count, Is.EqualTo(afterAddTrustList.TrustedCrls.Count)); - Assert.That(Utils.IsEqual(beforeTrustList, afterAddTrustList), Is.False); + Assert.That(AreEquivalent(beforeTrustList, afterAddTrustList), Is.False); ServiceResultException serviceResultException = await Assert .ThrowsAsync(() => m_pushClient.PushClient.RemoveCertificateAsync(m_caCert.Thumbprint, false).AsTask()); @@ -622,11 +651,11 @@ namespace Opc.Ua.Gds.Tests Is.EqualTo(StatusCodes.BadInvalidArgument), serviceResultException.Message); TrustListDataType afterRemoveTrustList = await m_pushClient.PushClient.ReadTrustListAsync().ConfigureAwait(false); - Assert.That(Utils.IsEqual(beforeTrustList, afterRemoveTrustList), Is.False); + Assert.That(AreEquivalent(beforeTrustList, afterRemoveTrustList), Is.False); await m_pushClient.PushClient.RemoveCertificateAsync(m_caCert.Thumbprint, true).ConfigureAwait(false); await m_pushClient.PushClient.ApplyChangesAsync().ConfigureAwait(false); afterRemoveTrustList = await m_pushClient.PushClient.ReadTrustListAsync().ConfigureAwait(false); - Assert.That(Utils.IsEqual(beforeTrustList, afterRemoveTrustList), Is.True); + Assert.That(AreEquivalent(beforeTrustList, afterRemoveTrustList), Is.True); } [Test] @@ -651,16 +680,16 @@ namespace Opc.Ua.Gds.Tests afterAddTrustList.IssuerCertificates.Count, Is.GreaterThan(beforeTrustList.IssuerCertificates.Count)); Assert.That(beforeTrustList.IssuerCrls.Count, Is.EqualTo(afterAddTrustList.IssuerCrls.Count)); - Assert.That(Utils.IsEqual(beforeTrustList, afterAddTrustList), Is.False); + Assert.That(AreEquivalent(beforeTrustList, afterAddTrustList), Is.False); await Assert.ThatAsync( () => m_pushClient.PushClient.RemoveCertificateAsync(m_caCert.Thumbprint, true).AsTask(), Throws.Exception).ConfigureAwait(false); TrustListDataType afterRemoveTrustList = await m_pushClient.PushClient.ReadTrustListAsync().ConfigureAwait(false); - Assert.That(Utils.IsEqual(beforeTrustList, afterRemoveTrustList), Is.False); + Assert.That(AreEquivalent(beforeTrustList, afterRemoveTrustList), Is.False); await m_pushClient.PushClient.RemoveCertificateAsync(m_caCert.Thumbprint, false).ConfigureAwait(false); await m_pushClient.PushClient.ApplyChangesAsync().ConfigureAwait(false); afterRemoveTrustList = await m_pushClient.PushClient.ReadTrustListAsync().ConfigureAwait(false); - Assert.That(Utils.IsEqual(beforeTrustList, afterRemoveTrustList), Is.True); + Assert.That(AreEquivalent(beforeTrustList, afterRemoveTrustList), Is.True); } [Test]