File: System\Windows\Forms\BinaryFormatUtilitiesTests.cs
Web Access
Project: src\src\System.Windows.Forms\tests\UnitTests\System.Windows.Forms.Tests.csproj (System.Windows.Forms.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable enable
 
using System.Collections;
using System.Drawing;
using System.Runtime.Serialization;
using Utilities = System.Windows.Forms.DataObject.Composition.BinaryFormatUtilities;
 
namespace System.Windows.Forms.Tests;
 
public partial class BinaryFormatUtilitiesTests : IDisposable
{
    private readonly MemoryStream _stream;
 
    public BinaryFormatUtilitiesTests() => _stream = new();
 
    public void Dispose() => _stream.Dispose();
 
    private object? RoundTripObject(object value)
    {
        Utilities.WriteObjectToStream(_stream, value, restrictSerialization: false);
        return ReadObjectFromStream();
    }
 
    private object? RoundTripObject_RestrictedFormat(object value)
    {
        Utilities.WriteObjectToStream(_stream, value, restrictSerialization: true);
        return ReadObjectFromStream(restrictDeserialization: true);
    }
 
    private void WriteObjectToStream(object value, bool restrictSerialization = false) =>
        Utilities.WriteObjectToStream(_stream, value, restrictSerialization);
 
    private object? ReadObjectFromStream(bool restrictDeserialization = false)
    {
        _stream.Position = 0;
        return Utilities.ReadObjectFromStream(_stream, restrictDeserialization);
    }
 
    // Primitive types as defined by the NRBF spec.
    // https://learn.microsoft.com/dotnet/api/system.formats.nrbf.primitivetyperecord
    public static TheoryData<object> PrimitiveObjects_TheoryData =>
    [
        (byte)1,
        (sbyte)2,
        (short)3,
        (ushort)4,
        5,
        (uint)6,
        (long)7,
        (ulong)8,
        (float)9.0,
        10.0,
        'a',
        true
    ];
 
    public static TheoryData<object> KnownObjects_TheoryData =>
    [
        "string",
        DateTime.Now,
        TimeSpan.FromHours(1),
        -(nint)11,
        (nuint)12,
        decimal.MaxValue,
        new PointF(1, 2),
        new RectangleF(1, 2, 3, 4),
        new Point(-1, int.MaxValue),
        new Rectangle(-1, int.MinValue, 10, 13),
        new Size(int.MaxValue, int.MinValue),
        new SizeF(float.MaxValue, float.MinValue),
        Color.Red
    ];
 
    public static TheoryData<IList> PrimitiveListObjects_TheoryData =>
    [
        new List<bool> { false, true },
        new List<char> { char.MinValue, char.MaxValue },
        new List<byte> { byte.MinValue, byte.MaxValue },
        new List<sbyte> { sbyte.MinValue, sbyte.MaxValue },
        new List<short> { short.MinValue, short.MaxValue },
        new List<ushort> { ushort.MinValue, ushort.MaxValue },
        new List<int> { int.MinValue, int.MaxValue },
        new List<uint> { uint.MinValue, uint.MaxValue },
        new List<long> { long.MinValue, long.MaxValue },
        new List<ulong> { ulong.MinValue, ulong.MaxValue },
        new List<float> { float.MinValue, float.MaxValue },
        new List<double> { double.MinValue, double.MaxValue },
        new List<decimal> { decimal.MinValue, decimal.MaxValue },
        new List<DateTime> { DateTime.MinValue, DateTime.MaxValue },
        new List<TimeSpan> { TimeSpan.MinValue, TimeSpan.MaxValue },
        new List<string> { "a", "b", "c" }
    ];
 
    public static TheoryData<Array> PrimitiveArrayObjects_TheoryData =>
    [
        new bool[] { false, true },
        new char[] { char.MinValue, char.MaxValue },
        new byte[] { byte.MinValue, byte.MaxValue },
        new sbyte[] { sbyte.MinValue, sbyte.MaxValue },
        new short[] { short.MinValue, short.MaxValue },
        new ushort[] { ushort.MinValue, ushort.MaxValue },
        new int[] { int.MinValue, int.MaxValue },
        new uint[] { uint.MinValue, uint.MaxValue },
        new long[] { long.MinValue, long.MaxValue },
        new ulong[] { ulong.MinValue, ulong.MaxValue },
        new float[] { float.MinValue, float.MaxValue },
        new double[] { double.MinValue, double.MaxValue },
        new decimal[] { decimal.MinValue, decimal.MaxValue },
        new DateTime[] { DateTime.MinValue, DateTime.MaxValue },
        new TimeSpan[] { TimeSpan.MinValue, TimeSpan.MaxValue },
        new string[] { "a", "b", "c" }
    ];
 
    public static TheoryData<ArrayList> PrimitiveArrayListObjects_TheoryData =>
    [
        new ArrayList { null },
        new ArrayList { null, "something" },
        new ArrayList { false, true },
        new ArrayList { char.MinValue, char.MaxValue },
        new ArrayList { byte.MinValue, byte.MaxValue },
        new ArrayList { sbyte.MinValue, sbyte.MaxValue },
        new ArrayList { short.MinValue, short.MaxValue },
        new ArrayList { ushort.MinValue, ushort.MaxValue },
        new ArrayList { int.MinValue, int.MaxValue },
        new ArrayList { uint.MinValue, uint.MaxValue },
        new ArrayList { long.MinValue, long.MaxValue },
        new ArrayList { ulong.MinValue, ulong.MaxValue },
        new ArrayList { float.MinValue, float.MaxValue },
        new ArrayList { double.MinValue, double.MaxValue },
        new ArrayList { decimal.MinValue, decimal.MaxValue },
        new ArrayList { DateTime.MinValue, DateTime.MaxValue },
        new ArrayList { TimeSpan.MinValue, TimeSpan.MaxValue },
        new ArrayList { "a", "b", "c" }
    ];
 
    public static TheoryData<Hashtable> PrimitiveTypeHashtables_TheoryData =>
    [
        new Hashtable { { "bool", true } },
        new Hashtable { { "char", 'a' } },
        new Hashtable { { "byte", (byte)1 } },
        new Hashtable { { "sbyte", (sbyte)2 } },
        new Hashtable { { "short", (short)3 } },
        new Hashtable { { "ushort", (ushort)4 } },
        new Hashtable { { "int", 5 } },
        new Hashtable { { "uint", (uint)6 } },
        new Hashtable { { "long", (long)7 } },
        new Hashtable { { "ulong", (ulong)8 } },
        new Hashtable { { "float", 9.0f } },
        new Hashtable { { "double", 10.0 } },
        new Hashtable { { "decimal", (decimal)11 } },
        new Hashtable { { "DateTime", DateTime.Now } },
        new Hashtable { { "TimeSpan", TimeSpan.FromHours(1) } },
        new Hashtable { { "string", "test" } }
    ];
 
    public static TheoryData<NotSupportedException> NotSupportedException_TestData =>
    [
        new NotSupportedException(),
        new NotSupportedException("Error message"),
        new NotSupportedException(null)
    ];
 
    public static TheoryData<IList> Lists_UnsupportedTestData =>
    [
        new List<object>(),
        new List<nint>(),
        new List<(int, int)>()
    ];
 
    [Theory]
    [MemberData(nameof(PrimitiveObjects_TheoryData))]
    [MemberData(nameof(KnownObjects_TheoryData))]
    public void BinaryFormatUtilities_RoundTrip_Simple(object value) =>
        RoundTripObject(value).Should().Be(value);
 
    [Theory]
    [MemberData(nameof(PrimitiveObjects_TheoryData))]
    [MemberData(nameof(KnownObjects_TheoryData))]
    public void BinaryFormatUtilities_RoundTripRestrictedFormat_Simple(object value) =>
        RoundTripObject_RestrictedFormat(value).Should().Be(value);
 
    [Theory]
    [MemberData(nameof(NotSupportedException_TestData))]
    public void BinaryFormatUtilities_RoundTrip_NotSupportedException(NotSupportedException value) =>
        RoundTripObject(value).Should().BeEquivalentTo(value);
 
    [Theory]
    [MemberData(nameof(NotSupportedException_TestData))]
    public void BinaryFormatUtilities_RoundTripRestrictedFormat_NotSupportedException(NotSupportedException value) =>
        RoundTripObject_RestrictedFormat(value).Should().BeEquivalentTo(value);
 
    [Fact]
    public void BinaryFormatUtilities_RoundTrip_NotSupportedException_DataLoss()
    {
        NotSupportedException value = new("Error message", new ArgumentException());
        RoundTripObject(value).Should().BeEquivalentTo(new NotSupportedException("Error message", innerException: null));
    }
 
    [Fact]
    public void BinaryFormatUtilities_RoundTripRestrictedFormat_NotSupportedException_DataLoss()
    {
        NotSupportedException value = new("Error message", new ArgumentException());
        RoundTripObject_RestrictedFormat(value).Should().BeEquivalentTo(new NotSupportedException("Error message", innerException: null));
    }
 
    [Theory]
    [MemberData(nameof(PrimitiveListObjects_TheoryData))]
    public void BinaryFormatUtilities_RoundTrip_PrimitiveList(IList value) =>
        RoundTripObject(value).Should().BeEquivalentTo(value);
 
    [Theory]
    [MemberData(nameof(PrimitiveListObjects_TheoryData))]
    public void BinaryFormatUtilities_RoundTripRestrictedFormat_PrimitiveList(IList value) =>
        RoundTripObject_RestrictedFormat(value).Should().BeEquivalentTo(value);
 
    [Theory]
    [MemberData(nameof(PrimitiveArrayObjects_TheoryData))]
    public void BinaryFormatUtilities_RoundTrip_PrimitiveArray(Array value) =>
        RoundTripObject(value).Should().BeEquivalentTo(value);
 
    [Theory]
    [MemberData(nameof(PrimitiveArrayListObjects_TheoryData))]
    public void BinaryFormatUtilities_RoundTrip_PrimitiveArrayList(ArrayList value) =>
        RoundTripObject(value).Should().BeEquivalentTo(value);
 
    [Theory]
    [MemberData(nameof(PrimitiveArrayListObjects_TheoryData))]
    public void BinaryFormatUtilities_RoundTripRestrictedFormat_PrimitiveArrayList(ArrayList value) =>
        RoundTripObject_RestrictedFormat(value).Should().BeEquivalentTo(value);
 
    [Theory]
    [MemberData(nameof(PrimitiveTypeHashtables_TheoryData))]
    public void BinaryFormatUtilities_RoundTrip_PrimitiveHashtable(Hashtable value) =>
        RoundTripObject(value).Should().BeEquivalentTo(value);
 
    [Theory]
    [MemberData(nameof(PrimitiveTypeHashtables_TheoryData))]
    public void BinaryFormatUtilities_RoundTripRestrictedFormat_PrimitiveHashtable(Hashtable value) =>
        RoundTripObject_RestrictedFormat(value).Should().BeEquivalentTo(value);
 
    [Fact]
    public void BinaryFormatUtilities_RoundTrip_ImageList()
    {
        using ImageList sourceList = new();
        using Bitmap image = new(10, 10);
        sourceList.Images.Add(image);
        using ImageListStreamer value = sourceList.ImageStream!;
 
        var result = RoundTripObject(value).Should().BeOfType<ImageListStreamer>().Which;
 
        using ImageList newList = new();
        newList.ImageStream = result;
        newList.Images.Count.Should().Be(1);
    }
 
    [Fact]
    public void BinaryFormatUtilities_RoundTripRestrictedFormat_ImageList()
    {
        using ImageList sourceList = new();
        using Bitmap image = new(10, 10);
        sourceList.Images.Add(image);
        using ImageListStreamer value = sourceList.ImageStream!;
 
        var result = RoundTripObject_RestrictedFormat(value).Should().BeOfType<ImageListStreamer>().Which;
 
        using ImageList newList = new();
        newList.ImageStream = result;
        newList.Images.Count.Should().Be(1);
    }
 
    [Fact]
    public void BinaryFormatUtilities_RoundTrip_Bitmap()
    {
        using Bitmap value = new(10, 10);
        RoundTripObject(value).Should().BeOfType<Bitmap>().Subject.Size.Should().Be(value.Size);
    }
 
    [Fact]
    public void BinaryFormatUtilities_RoundTripRestrictedFormat_Bitmap()
    {
        using Bitmap value = new(10, 10);
        RoundTripObject_RestrictedFormat(value).Should().BeOfType<Bitmap>().Subject.Size.Should().Be(value.Size);
    }
 
    [Theory]
    [MemberData(nameof(Lists_UnsupportedTestData))]
    public void BinaryFormatUtilities_RoundTrip_Unsupported(IList value)
    {
        ((Action)(() => WriteObjectToStream(value))).Should().Throw<NotSupportedException>();
 
        using (BinaryFormatterScope scope = new(enable: true))
        {
            WriteObjectToStream(value);
            ReadObjectFromStream().Should().BeEquivalentTo(value);
        }
 
        ((Action)(() => ReadObjectFromStream())).Should().Throw<NotSupportedException>();
    }
 
    [Theory]
    [MemberData(nameof(Lists_UnsupportedTestData))]
    public void BinaryFormatUtilities_RoundTripRestrictedFormat_Unsupported(IList value)
    {
        ((Action)(() => WriteObjectToStream(value, restrictSerialization: true))).Should().Throw<NotSupportedException>();
 
        using BinaryFormatterScope scope = new(enable: true);
        ((Action)(() => WriteObjectToStream(value, restrictSerialization: true))).Should().Throw<SerializationException>();
    }
}