Files
Marc SchierandCopilot b17007bd55 Stop the encoder tests flooding the VSTest channel with per-test output (#4219)
# Description

The `Core.Encoders` test job of run
[`31249610490`](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/31249610490)
printed nothing for 57 minutes and was killed by the 60-minute job
timeout. Issue #4213 recorded that as "hung at or before the first test"
— that reading was wrong, and the evidence to disprove it was already in
the job's own artifact.

The blame collector *had* written a hang dump and it *was* uploaded,
inside the ordinary `dotnet-results-Core.Encoders-*` artifact (11 MB of
it was the dump), where nobody looked for it. Opening it names the cause
precisely.

## What the dump shows

522 threads (408 background) in a process that finishes in 25 seconds
when healthy. Every test had already run; NUnit was in `OneTimeTearDown`
waiting on its event pump:

- the **event pump thread** is blocked in
`NUnitEventListener.TestFinished` →
`TestRunEventsHandler.HandleLogMessage` →
`LengthPrefixCommunicationChannel.Send` → `Socket.Send`, on a socket
that never drained. That send holds the channel's lock;
- `TestRunCache.SendResults` is blocked on that channel lock, and it
holds the cache lock;
- `TestRunCache`'s **one-second timer** then fired repeatedly into that
held cache lock, and the thread pool injected a fresh worker for each —
**399 threads** parked in `Monitor.Enter_Slowpath` behind a single lock;
- the NUnit run thread sits in `EventPump.Stop()` → `Thread.Join()`, so
the run can never complete.

## What put the socket under that much pressure

Volume of test output. Every encoder case dumps its input, its encoded
payload and its decoded result, the NUnit adapter forwards each captured
line to the runner as an individual message over that socket, and the
suite has 2929 cases. This is not marginal:

| | Before | After |
| --- | --- | --- |
| `Core.Encoders` TRX, green local run | **496 MB** | **4 MB** |
| `Core.Encoders` published CI artifact | **156 MB** | ~3 MB |
| Next-largest test job's artifact, for scale | 15 MB | 15 MB |
| `Client.ComplexTypes` TRX | 74 MB | 40 MB |

So the suite was shipping half a gigabyte of text through a socket, one
message per line, on every run and on every OS — and paying for it in
artifact storage even when nothing went wrong.

## The change

`EncoderCommon` gains a buffered `TestOutput` writer. The dumps are
collected per test and handed to `TestContext.Out` in `TearDown` **only
when the test did not pass** — which is the only time anyone reads them.
Nothing is lost: a failing test still reports its full
input/encoded/decoded dump under *Standard Output Messages*, verified by
injecting a deliberate failure. Explicit opt-ins
(`PrettifyAndValidateJson(..., outputFormatted: true)`) and the static
helpers keep writing immediately.

The same base class is shared by the `Client.ComplexTypes` fixtures, so
those are moved over too rather than left with half-buffered,
half-immediate output.

Two supporting changes:

- **`.github/workflows/buildandtest.yml`** — `timeout-minutes: 30` on
the `Test` step. The blame collector did its job here (dump at +50 min,
once the run went quiet), but the run still had not unwound 7 minutes
later when the *job* timeout cancelled everything, so the step needs a
bound below the job's. `Upload test results` already runs `if:
always()`, so the dump is still published.
- **`docs/DeveloperGuide.md`** — a "Test output" convention under
*Running tests*, so the next data-driven fixture does not re-introduce
this.

## Notes for reviewers

- `[Parallelizable]` on these fixtures is `ParallelScope.Self`, i.e.
fixture-level; tests inside a fixture stay sequential, so the
per-fixture buffer is as safe as the existing per-fixture
`RandomSource`/`DataGenerator` state that is already reset in `SetUp`.
- I separately confirmed that `--blame-hang-timeout` *does* fire on a
hang before the first test (minimal NUnit repro, dump at exactly the
configured timeout), which is what rules out the original "hung during
startup/discovery" theory independently of the dump.
- Local verification was scoped to the two affected suites rather than
the whole solution: `Opc.Ua.Core.Encoders.Tests` 2929/2929 on
**net10.0** *and* **net48**, `Opc.Ua.Client.ComplexTypes.Tests`
2773/2773 on net10.0, all building with 0 warnings. The full `UA.slnx`
run is left to CI.

## Related Issues

- Fixes #4213

## Checklist

- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: da92151a-3da2-4caf-a39c-5556e56f0506
2026-08-09 21:49:05 +02:00

770 lines
32 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2025 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/
* ======================================================================*/
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
#define SPAN_SUPPORT
#endif
using System;
using System.IO;
using System.Xml;
using NUnit.Framework;
using Opc.Ua.Core.TestFramework;
namespace Opc.Ua.Core.Encoders.Tests
{
/// <summary>
/// Tests for the IEncoder and IDecoder class.
/// </summary>
[TestFixture]
[Category("Encoder")]
[SetCulture("en-us")]
[SetUICulture("en-us")]
[Parallelizable]
public class EncoderTests : EncoderCommon
{
[DatapointSource]
public int[] ArrayLength = [1, 5, 100];
private static readonly int[] s_value = [1, 2, 3, 4, 5];
private static readonly string[] s_valueArray = ["1", "2", "3", "4", "5"];
private static readonly int[] s_valueArray0 = [2, 3, 10];
/// <summary>
/// Verify encode and decode of a default built in type
/// value as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
[Description("Ensures the Decoder reflects the Spec.: https://reference.opcfoundation.org/Core/Part6/v105/docs/5.2.2.17 -> " +
"SourcePicoseconds: [...] Not present if the SourcePicoseconds bit in the EncodingMask is False. " +
"If the source timestamp is missing the Picoseconds are ignored." +
"ServerPicoseconds: [...] Not present if the ServerPicoseconds bit in the EncodingMask is False. " +
"If the Server timestamp is missing the Picoseconds are ignored.")]
public void EncodeDataValueWithoutValueProperty()
{
DataValue dataValue = new DataValue(Variant.Null).WithSourcePicoseconds(1);
EncodeDataValueWithoutValuePropertyTest(dataValue);
dataValue = new DataValue(Variant.Null, StatusCodes.Good, new DateTime(2001, 01, 01).ToUniversalTime());
EncodeDataValueWithoutValuePropertyTest(dataValue);
dataValue = new DataValue(Variant.Null, StatusCodes.Good, DateTimeUtc.MinValue, new DateTime(2001, 01, 02).ToUniversalTime());
EncodeDataValueWithoutValuePropertyTest(dataValue);
dataValue = new DataValue(Variant.Null).WithServerPicoseconds(2);
EncodeDataValueWithoutValuePropertyTest(dataValue);
dataValue = DataValue.FromStatusCode(StatusCodes.BadNotImplemented);
EncodeDataValueWithoutValuePropertyTest(dataValue);
dataValue = new DataValue(Variant.Null, StatusCodes.Good, new DateTime(2001, 01, 03).ToUniversalTime(), DateTimeUtc.MinValue, 3, 0);
EncodeDataValueWithoutValuePropertyTest(dataValue);
dataValue = new DataValue(Variant.Null, StatusCodes.Good, new DateTime(2001, 01, 04).ToUniversalTime(), DateTimeUtc.MinValue, 0, 4);
EncodeDataValueWithoutValuePropertyTest(dataValue);
dataValue = new DataValue(Variant.Null, StatusCodes.Good, DateTimeUtc.MinValue, new DateTime(2001, 01, 05).ToUniversalTime(), 0, 5);
EncodeDataValueWithoutValuePropertyTest(dataValue);
dataValue = new DataValue(Variant.Null, StatusCodes.Good, DateTimeUtc.MinValue, new DateTime(2001, 01, 06).ToUniversalTime(), 6, 0);
EncodeDataValueWithoutValuePropertyTest(dataValue);
dataValue = new DataValue(
Variant.Null,
StatusCodes.BadNotFound,
new DateTime(2001, 01, 08).ToUniversalTime(),
new DateTime(2001, 01, 07).ToUniversalTime(),
8,
7);
EncodeDataValueWithoutValuePropertyTest(dataValue);
}
private void EncodeDataValueWithoutValuePropertyTest(DataValue dataValue)
{
const EncodingType encoderType = EncodingType.Binary;
const BuiltInType builtInType = BuiltInType.Null;
const MemoryStreamType memoryStreamType = MemoryStreamType.MemoryStream;
const JsonEncodingType jsonEncodingType = JsonEncodingType.Verbose;
string encodeInfo = $"Encoder: {encoderType} Type:{builtInType}";
TestOutput.WriteLine(encodeInfo);
DataValue expected = dataValue;
Assert.That(expected.IsNull, Is.False, "Expected DataValue is Null, " + encodeInfo);
DataValue result = default;
byte[] buffer;
using (MemoryStream encoderStream = CreateEncoderMemoryStream(memoryStreamType))
{
using (
IEncoder encoder = CreateEncoder(
encoderType,
Context,
encoderStream,
typeof(DataValue),
jsonEncodingType))
{
encoder.WriteDataValue("DataValue", expected);
}
buffer = encoderStream.ToArray();
}
using (var decoderStream = new MemoryStream(buffer))
using (IDecoder decoder = CreateDecoder(
encoderType,
false,
Context,
decoderStream,
typeof(DataValue)))
{
result = decoder.ReadDataValue("DataValue");
}
// see: https://reference.opcfoundation.org/Core/Part6/v105/docs/5.2.2.17
if ((expected.SourcePicoseconds != 0 && expected.SourceTimestamp == DateTimeUtc.MinValue) ||
(expected.ServerPicoseconds != 0 && expected.ServerTimestamp == DateTimeUtc.MinValue))
{
Assert.That(expected, Is.Not.EqualTo(result), encodeInfo);
Assert.That(Utils.IsEqual(expected, result), Is.False, "Opc.Ua.Utils.IsEqual failed to compare expected and result. " + encodeInfo);
}
else
{
Assert.That(result.IsNull, Is.False, "Resulting DataValue is Null, " + encodeInfo);
Assert.That(expected, Is.EqualTo(result), encodeInfo);
Assert.That(
Utils.IsEqual(expected, result),
Is.True,
"Opc.Ua.Utils.IsEqual failed to compare expected and result. " + encodeInfo);
}
}
/// <summary>
/// Verify encode and decode of a default built in type
/// value as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
public void ReEncodeBuiltInTypeDefaultVariantInDataValue(
[ValueSource(
nameof(EncodingTypesJsonBinaryXmlAndJsonCompact))] EncodingTypeGroup encoderTypeGroup,
BuiltInType builtInType)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
var defaultValue = Variant.CreateDefault(TypeInfo.Create(builtInType, ValueRanks.Scalar));
EncodeDecodeDataValue(
encoderType,
jsonEncodingType,
useXmlParser,
builtInType,
MemoryStreamType.MemoryStream,
defaultValue);
}
/// <summary>
/// Verify encode and decode of a random built in type
/// value as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
public void ReEncodeBuiltInTypeAsVariantInDataValue(
[ValueSource(
nameof(EncodingTypesJsonBinaryXmlAndJsonCompact))] EncodingTypeGroup encoderTypeGroup,
BuiltInType builtInType)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
Assume.That(builtInType != BuiltInType.DiagnosticInfo);
Variant randomData = DataGenerator.GetRandomVariant(builtInType, false);
EncodeDecodeDataValue(
encoderType,
jsonEncodingType,
useXmlParser,
builtInType,
MemoryStreamType.ArraySegmentStream,
randomData);
}
/// <summary>
/// Verify encode and decode of an array of a
/// random builtin type as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
public void ReEncodeBuiltInTypeArrayAsRandomVariantInDataValue(
[ValueSource(
nameof(EncodingTypesJsonBinaryXmlAndJsonCompact))] EncodingTypeGroup encoderTypeGroup,
BuiltInType builtInType,
bool useBoundaryValues,
int arrayLength)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
// ensure different sized arrays contain different data set
SetRandomSeed(arrayLength);
Variant randomData = DataGenerator.GetRandomArray(
builtInType,
arrayLength,
useBoundaryValues,
true);
EncodeDecodeDataValue(
encoderType,
jsonEncodingType,
useXmlParser,
builtInType,
MemoryStreamType.ArraySegmentStream,
randomData);
}
/// <summary>
/// Verify encode and decode of a zero length array
/// of a builtin type as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
public void ReEncodeBuiltInTypeZeroLengthArrayAsVariantInDataValue(
[ValueSource(
nameof(EncodingTypesJsonBinaryXmlAndJsonCompact))] EncodingTypeGroup encoderTypeGroup,
BuiltInType builtInType)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
Variant randomData = DataGenerator.GetRandomArray(builtInType, 0, false, true);
EncodeDecodeDataValue(
encoderType,
jsonEncodingType,
useXmlParser,
builtInType,
MemoryStreamType.MemoryStream,
randomData);
}
/// <summary>
/// Verify encode and decode of a random built in type
/// as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
[Repeat(kRandomRepeats)]
public void ReEncodeBuiltInTypeRandomVariantInDataValue(
[ValueSource(
nameof(EncodingTypesJsonBinaryXmlAndJsonCompact))] EncodingTypeGroup encoderTypeGroup)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
SetRepeatedRandomSeed();
Variant randomData = DataGenerator.GetRandomVariant();
EncodeDecodeDataValue(
encoderType,
jsonEncodingType,
useXmlParser,
BuiltInType.Variant,
MemoryStreamType.MemoryStream,
randomData);
}
/// <summary>
/// Validate integrity of non reversible Json encoding
/// of a builtin type as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
public void EncodeBuiltInTypeAsVariantInDataValueToCompactJson(
BuiltInType builtInType)
{
Variant randomData = DataGenerator.GetRandomVariant(builtInType, false);
string json = EncodeDataValue(
EncodingType.Json,
builtInType,
MemoryStreamType.MemoryStream,
randomData,
JsonEncodingType.Compact);
PrettifyAndValidateJson(json);
}
/// <summary>
/// Validate integrity of non reversible Json encoding
/// of a builtin type as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
public void EncodeBuiltInTypeAsVariantInDataValueToVerboseJson(BuiltInType builtInType)
{
Variant randomData = DataGenerator.GetRandomVariant(builtInType, false);
string json = EncodeDataValue(
EncodingType.Json,
builtInType,
MemoryStreamType.MemoryStream,
randomData,
JsonEncodingType.Verbose);
PrettifyAndValidateJson(json);
}
/// <summary>
/// Verify non reversible Json encoding
/// of a builtin type array as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
public void EncodeBuiltInTypeZeroLengthArrayAsVariantInDataValueToCompactJson(
BuiltInType builtInType)
{
Variant randomData = DataGenerator.GetRandomArray(builtInType, 0);
string json = EncodeDataValue(
EncodingType.Json,
builtInType,
MemoryStreamType.MemoryStream,
randomData,
JsonEncodingType.Compact);
PrettifyAndValidateJson(json);
}
/// <summary>
/// Validate integrity of non reversible Json encoding
/// of a builtin type array as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
public void EncodeBuiltInTypeArrayAsVariantInDataValueToVerboseJson(
BuiltInType builtInType,
bool useBoundaryValues,
int arrayLength)
{
SetRandomSeed(arrayLength);
Variant randomData = DataGenerator.GetRandomArray(
builtInType,
arrayLength,
useBoundaryValues);
string json = EncodeDataValue(
EncodingType.Json,
builtInType,
MemoryStreamType.MemoryStream,
randomData,
JsonEncodingType.Verbose);
PrettifyAndValidateJson(json);
}
/// <summary>
/// Verify Verbose Json encoding
/// of a builtin type array as Variant in a DataValue.
/// </summary>
[Theory]
[Category("BuiltInType")]
public void EncodeBuiltInTypeZeroLengthArrayAsVariantInDataValueToVerboseJson(
BuiltInType builtInType)
{
Variant randomData = DataGenerator.GetRandomArray(builtInType, 0);
string json = EncodeDataValue(
EncodingType.Json,
builtInType,
MemoryStreamType.MemoryStream,
randomData,
JsonEncodingType.Verbose);
PrettifyAndValidateJson(json);
}
/// <summary>
/// Verify encode and decode of a Variant array
/// </summary>
[Theory]
[Category("BuiltInType")]
public void ReEncodeVariantCollectionInDataValue(
[ValueSource(
nameof(EncodingTypesJsonBinaryXmlAndJsonCompact))] EncodingTypeGroup encoderTypeGroup)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
var variant = Variant.From(
[
Variant.From(4L),
Variant.From("test"),
Variant.From(s_value),
Variant.From(new long[] { 1, 2, 3, 4, 5 }),
Variant.From(s_valueArray),
//TODO: works as expected, but the expected need to be tweaked for the Int32 result
//Variant.From(new TestEnumType[] { TestEnumType.One, TestEnumType.Two, TestEnumType.Hundred }),
Variant.From(EnumValue.From(s_valueArray0))
]);
EncodeDecodeDataValue(
encoderType,
jsonEncodingType,
useXmlParser,
BuiltInType.Variant,
MemoryStreamType.ArraySegmentStream,
variant);
}
private static string WriteByteStringData(IEncoder encoder)
{
encoder.WriteByteString("ByteString1", ByteString.From([0, 1, 2, 3, 4, 5]).Slice(1, 3));
encoder.WriteByteString("ByteString2", ByteString.Empty);
#if !SPAN_SUPPORT
encoder.WriteByteString("ByteString3", default);
#else
encoder.WriteByteString("ByteString3", default(ByteString));
var span = new ReadOnlySpan<byte>([0, 1, 2, 3, 4, 5], 1, 3);
encoder.WriteByteString("ByteString4", span);
var nullspan = new ReadOnlySpan<byte>(null);
encoder.WriteByteString("ByteString5", nullspan);
Assert.That(nullspan.IsEmpty, Is.True);
Assert.That(nullspan == ReadOnlySpan<byte>.Empty, Is.True);
ReadOnlySpan<byte> defaultspan = default;
encoder.WriteByteString("ByteString6", defaultspan);
Assert.That(defaultspan.IsEmpty, Is.True);
#pragma warning disable CA1508 // Actually true
Assert.That(defaultspan == ReadOnlySpan<byte>.Empty, Is.True);
#pragma warning restore CA1508
var emptyspan = new ReadOnlySpan<byte>([]);
encoder.WriteByteString("ByteString7", emptyspan);
Assert.That(emptyspan.IsEmpty, Is.True);
Assert.That(emptyspan != ReadOnlySpan<byte>.Empty, Is.True);
#endif
return encoder.CloseAndReturnText();
}
private static void ReadByteStringData(IDecoder decoder)
{
ByteString result = decoder.ReadByteString("ByteString1");
Assert.That(result, Is.EqualTo(ByteString.From(new byte[] { 1, 2, 3 })));
result = decoder.ReadByteString("ByteString2");
Assert.That(result, Is.EqualTo(ByteString.Empty));
result = decoder.ReadByteString("ByteString3");
Assert.That(result, Is.EqualTo(ByteString.Empty));
#if SPAN_SUPPORT
result = decoder.ReadByteString("ByteString4");
Assert.That(result, Is.EqualTo(new byte[] { 1, 2, 3 }));
result = decoder.ReadByteString("ByteString5");
Assert.That(result, Is.EqualTo(ByteString.Empty));
result = decoder.ReadByteString("ByteString6");
Assert.That(result, Is.EqualTo(ByteString.Empty));
result = decoder.ReadByteString("ByteString7");
Assert.That(result, Is.EqualTo(ByteString.Empty));
#endif
}
[Test]
[Category("WriteByteString")]
public void BinaryEncoder_WriteByteString()
{
using var stream = new MemoryStream();
string text;
using (var encoder = new BinaryEncoder(stream, ServiceMessageContext.Create(Telemetry), true))
{
text = WriteByteStringData(encoder);
}
stream.Position = 0;
using var decoder = new BinaryDecoder(stream, ServiceMessageContext.Create(Telemetry));
ReadByteStringData(decoder);
}
[Test]
[Category("WriteByteString")]
public void XmlEncoder_WriteByteString()
{
using var stream = new MemoryStream();
XmlWriterSettings settings = Utils.DefaultXmlWriterSettings();
using (var writer = XmlWriter.Create(stream, settings))
using (
var encoder = new XmlEncoder(
new XmlQualifiedName("ByteStrings", Types.Namespaces.OpcUaXsd),
writer,
ServiceMessageContext.Create(Telemetry)))
{
string text = WriteByteStringData(encoder);
}
stream.Position = 0;
using var reader = XmlReader.Create(stream, Utils.DefaultXmlReaderSettings());
using var decoder = new XmlDecoder(null, reader, ServiceMessageContext.Create(Telemetry));
ReadByteStringData(decoder);
}
[Test]
[Category("WriteByteString")]
public void JsonEncoder_WriteByteString()
{
using var stream = new MemoryStream();
string text;
using (var encoder = new JsonEncoder(
stream,
ServiceMessageContext.Create(Telemetry)))
{
text = WriteByteStringData(encoder);
}
stream.Position = 0;
using var decoder = new JsonDecoder(stream, ServiceMessageContext.Create(Telemetry));
ReadByteStringData(decoder);
}
/// <summary>
/// Verify encode and decode of a Matrix in a Variant.
/// </summary>
[Theory]
[Category("Array")]
[Repeat(kArrayRepeats)]
public void ReEncodeVariantArrayInDataValue(
[ValueSource(
nameof(EncodingTypesAll))] EncodingTypeGroup encoderTypeGroup,
BuiltInType builtInType)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
SetRepeatedRandomSeed();
Assume.That(builtInType != BuiltInType.Null);
int arrayDimension = RandomSource.NextInt32(99) + 1;
Variant variant = DataGenerator.GetRandomArray(builtInType, arrayDimension, false, true);
EncodeDecodeDataValue(
encoderType,
jsonEncodingType,
useXmlParser,
BuiltInType.Variant,
MemoryStreamType.MemoryStream,
variant);
}
/// <summary>
/// Verify encode and decode of a one dimensional Array.
/// </summary>
[Theory]
[Category("Array")]
[Repeat(kArrayRepeats)]
public void EncodeArray(
[ValueSource(nameof(EncodingTypesAll))] EncodingTypeGroup encoderTypeGroup,
BuiltInType builtInType)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
SetRepeatedRandomSeed();
Assume.That(builtInType != BuiltInType.Null);
int arrayDimension = RandomSource.NextInt32(99) + 1;
Variant randomData = DataGenerator.GetRandomArray(
builtInType,
arrayDimension);
string encodeInfo = $"Encoder: {encoderType} Type:{builtInType}";
IBuiltInType type = TypeInfo.GetSystemType(builtInType);
TestOutput.WriteLine(encodeInfo);
TestOutput.WriteLine("Expected:");
TestOutput.WriteLine(randomData);
byte[] buffer;
using (MemoryStream encoderStream = CreateEncoderMemoryStream(
MemoryStreamType.MemoryStream))
{
using (
IEncoder encoder = CreateEncoder(
encoderType,
Context,
encoderStream,
type?.Type,
jsonEncodingType))
{
encoder.WriteVariantValue(builtInType.ToString(), randomData);
}
buffer = encoderStream.ToArray();
}
switch (encoderType)
{
case EncodingType.Binary:
TestOutput.WriteLine(PrettifyAndValidateBinary(buffer));
break;
case EncodingType.Json:
TestOutput.WriteLine(PrettifyAndValidateJson(buffer));
break;
case EncodingType.Xml:
TestOutput.WriteLine(PrettifyAndValidateXml(buffer));
break;
}
Variant result;
using (var decoderStream = new MemoryStream(buffer))
using (IDecoder decoder = CreateDecoder(
encoderType,
useXmlParser,
Context,
decoderStream,
type?.Type))
{
result = decoder.ReadVariantValue(
builtInType.ToString(),
randomData.TypeInfo);
}
TestOutput.WriteLine("Result:");
TestOutput.WriteLine(result);
Assert.That(result, Is.EqualTo(randomData), encodeInfo);
}
/// <summary>
/// Verify encode and decode of a Matrix in a Variant.
/// </summary>
[Theory]
[Category("Matrix")]
[Repeat(kArrayRepeats)]
public void ReEncodeVariantMatrixInDataValue(
[ValueSource(
nameof(EncodingTypesJsonBinaryXmlAndJsonCompact))] EncodingTypeGroup encoderTypeGroup,
BuiltInType builtInType)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
SetRepeatedRandomSeed();
Assume.That(builtInType != BuiltInType.Null);
// reduce array dimension for arrays with large values
int maxRand = 6;
if (builtInType is BuiltInType.XmlElement or BuiltInType.ExtensionObject)
{
maxRand = 2;
}
int matrixDimension = RandomSource.NextInt32(maxRand) + 2;
int[] dimensions = new int[matrixDimension];
SetMatrixDimensions(dimensions);
int elements = ElementsFromDimension(dimensions);
Variant variant = DataGenerator.GetRandomMatrix(builtInType, elements, dimensions, false, true);
EncodeDecodeDataValue(
encoderType,
jsonEncodingType,
useXmlParser,
BuiltInType.Variant,
MemoryStreamType.MemoryStream,
variant);
}
/// <summary>
/// Verify encode of a Matrix in a Variant to non reversible JSON.
/// </summary>
[Theory]
[Category("Matrix")]
[Repeat(kArrayRepeats)]
public void EncodeBuiltInTypeMatrixAsVariantInDataValueToVerboseJson(
[ValueSource(
nameof(EncodingTypesJsonVerbose))] EncodingTypeGroup encoderTypeGroup,
BuiltInType builtInType)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
SetRepeatedRandomSeed();
Assume.That(builtInType != BuiltInType.Null);
int matrixDimension = RandomSource.NextInt32(3) + 2;
int[] dimensions = new int[matrixDimension];
SetMatrixDimensions(dimensions);
int elements = ElementsFromDimension(dimensions);
Variant variant = DataGenerator.GetRandomMatrix(builtInType, elements, dimensions, false);
string json = EncodeDataValue(
encoderType,
BuiltInType.Variant,
MemoryStreamType.ArraySegmentStream,
variant,
jsonEncodingType);
_ = PrettifyAndValidateJson(json);
}
/// <summary>
/// Verify encode of a Matrix in a multi dimensional array.
/// </summary>
[Theory]
[Category("Matrix")]
[Repeat(kArrayRepeats)]
public void EncodeMatrixInArray(
[ValueSource(nameof(EncodingTypesAll))] EncodingTypeGroup encoderTypeGroup,
BuiltInType builtInType)
{
EncodingType encoderType = encoderTypeGroup.EncoderType;
JsonEncodingType jsonEncodingType = encoderTypeGroup.JsonEncodingType;
bool useXmlParser = encoderTypeGroup.UseXmlParser;
SetRepeatedRandomSeed();
Assume.That(builtInType != BuiltInType.Null);
int matrixDimension = RandomSource.NextInt32(3) + 2;
int[] dimensions = new int[matrixDimension];
SetMatrixDimensions(dimensions);
int elements = ElementsFromDimension(dimensions);
Variant randomData = DataGenerator.GetRandomMatrix(builtInType, elements, dimensions);
string encodeInfo = $"Encoder: {encoderType} Type:{builtInType}";
IBuiltInType type = TypeInfo.GetSystemType(builtInType);
TestOutput.WriteLine(encodeInfo);
TestOutput.WriteLine("Expected:");
TestOutput.WriteLine(randomData);
byte[] buffer;
using (MemoryStream encoderStream = CreateEncoderMemoryStream(
MemoryStreamType.MemoryStream))
{
using (IEncoder encoder = CreateEncoder(
encoderType,
Context,
encoderStream,
type?.Type,
jsonEncodingType))
{
encoder.WriteVariantValue(builtInType.ToString(), randomData);
}
buffer = encoderStream.ToArray();
}
switch (encoderType)
{
case EncodingType.Binary:
TestOutput.WriteLine(PrettifyAndValidateBinary(buffer));
break;
case EncodingType.Json:
TestOutput.WriteLine(PrettifyAndValidateJson(buffer));
break;
case EncodingType.Xml:
TestOutput.WriteLine(PrettifyAndValidateXml(buffer));
break;
}
Variant result;
using (var decoderStream = new MemoryStream(buffer))
using (IDecoder decoder = CreateDecoder(
encoderType,
useXmlParser,
Context,
decoderStream,
type?.Type))
{
result = decoder.ReadVariantValue(
builtInType.ToString(),
randomData.TypeInfo);
}
TestOutput.WriteLine("Result:");
TestOutput.WriteLine(result);
Assert.That(result, Is.EqualTo(randomData), encodeInfo);
}
}
}