using System.DoubleNumerics; using System.Drawing; using NUnit.Framework; using Speckle.Core.Api; using Speckle.Core.Helpers; using Speckle.Core.Models; namespace Speckle.Core.Tests.Unit.Serialisation; /// /// Test fixture that documents what property typing changes maintain backwards/cross/forwards compatibility, and are "non-breaking" changes. /// This doesn't guarantee things work this way for SpecklePy /// Nor does it encompass other tricks (like deserialize callback, or computed json ignored properties) /// [TestFixture] [Description("For certain types, changing property from one type to another should be implicitly backwards compatible")] public class SerializerNonBreakingChanges : PrimitiveTestFixture { [Test, TestCaseSource(nameof(Int8TestCases)), TestCaseSource(nameof(Int32TestCases))] public void IntToColor(int argb) { var from = new IntValueMock { value = argb }; var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value.ToArgb(), Is.EqualTo(argb)); } [Test, TestCaseSource(nameof(Int8TestCases)), TestCaseSource(nameof(Int32TestCases))] public void ColorToInt(int argb) { var from = new ColorValueMock { value = Color.FromArgb(argb) }; var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value, Is.EqualTo(argb)); } [ Test, TestCaseSource(nameof(Int8TestCases)), TestCaseSource(nameof(Int32TestCases)), TestCaseSource(nameof(Int64TestCases)) ] public void IntToDouble(long testCase) { var from = new IntValueMock { value = testCase }; var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value, Is.EqualTo(testCase)); } [ Test, TestCaseSource(nameof(Int8TestCases)), TestCaseSource(nameof(Int32TestCases)), TestCaseSource(nameof(Int64TestCases)) ] public void IntToString(long testCase) { var from = new IntValueMock { value = testCase }; var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value, Is.EqualTo(testCase.ToString())); } private static readonly double[][] s_arrayTestCases = { Array.Empty(), new double[] { 0, 1, int.MaxValue, int.MinValue }, new[] { default, double.Epsilon, double.MaxValue, double.MinValue } }; [Test, TestCaseSource(nameof(s_arrayTestCases))] public void ArrayToList(double[] testCase) { var from = new ArrayDoubleValueMock { value = testCase }; var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value, Is.EquivalentTo(testCase)); } [Test, TestCaseSource(nameof(s_arrayTestCases))] public void ListToArray(double[] testCase) { var from = new ListDoubleValueMock { value = testCase.ToList() }; var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value, Is.EquivalentTo(testCase)); } [Test, TestCaseSource(nameof(MyEnums))] public void EnumToInt(MyEnum testCase) { var from = new EnumValueMock { value = testCase }; var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value, Is.EqualTo((int)testCase)); } [Test, TestCaseSource(nameof(MyEnums))] public void IntToEnum(MyEnum testCase) { var from = new IntValueMock { value = (int)testCase }; var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value, Is.EqualTo(testCase)); } [Test] [TestCaseSource(nameof(Float64TestCases))] [TestCaseSource(nameof(Float32TestCases))] public void DoubleToDouble(double testCase) { var from = new DoubleValueMock { value = testCase }; var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value, Is.EqualTo(testCase)); } [Test] [TestCase(123, 255)] [TestCase(256, 1)] [TestCase(256, float.MinValue)] public void ListToMatrix64(int seed, double scalar) { Random rand = new(seed); List testCase = Enumerable.Range(0, 16).Select(_ => rand.NextDouble() * scalar).ToList(); ListDoubleValueMock from = new() { value = testCase, }; //Test List -> Matrix var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value.M11, Is.EqualTo(testCase[0])); Assert.That(res.value.M44, Is.EqualTo(testCase[^1])); //Test Matrix -> List var backAgain = res.SerializeAsTAndDeserialize(); Assert.That(backAgain.value, Is.Not.Null); Assert.That(backAgain.value, Is.EquivalentTo(testCase)); } [Test] [TestCase(123, 255)] [TestCase(256, 1)] [DefaultFloatingPointTolerance(Constants.EPS)] public void Matrix32ToMatrix64(int seed, float scalar) { Random rand = new(seed); List testCase = Enumerable.Range(0, 16).Select(_ => rand.NextDouble() * scalar).ToList(); ListDoubleValueMock from = new() { value = testCase, }; //Test List -> Matrix var res = from.SerializeAsTAndDeserialize(); Assert.That(res.value.M11, Is.EqualTo(testCase[0])); Assert.That(res.value.M44, Is.EqualTo(testCase[^1])); //Test Matrix -> List var backAgain = res.SerializeAsTAndDeserialize(); Assert.That(backAgain.value, Is.Not.Null); Assert.That(backAgain.value, Is.EquivalentTo(testCase)); } } public class TValueMock : SerializerMock { public T value { get; set; } } public class ListDoubleValueMock : SerializerMock { public List value { get; set; } } public class ArrayDoubleValueMock : SerializerMock { public double[] value { get; set; } } public class IntValueMock : SerializerMock { public long value { get; set; } } public class StringValueMock : SerializerMock { public string value { get; set; } } public class DoubleValueMock : SerializerMock { public double value { get; set; } } public class Matrix64ValueMock : SerializerMock { public Matrix4x4 value { get; set; } } public class Matrix32ValueMock : SerializerMock { public System.Numerics.Matrix4x4 value { get; set; } } public class ColorValueMock : SerializerMock { public Color value { get; set; } } public class EnumValueMock : SerializerMock { public MyEnum value { get; set; } } public enum MyEnum { Zero, One, Two, Three, Neg = -1, Min = int.MinValue, Max = int.MaxValue } public abstract class SerializerMock : Base { private string _speckle_type; protected SerializerMock() { _speckle_type = base.speckle_type; } public override string speckle_type => _speckle_type; public void SerializeAs() where T : Base, new() { T target = new(); _speckle_type = target.speckle_type; } internal TTo SerializeAsTAndDeserialize() where TTo : Base, new() { SerializeAs(); var json = Operations.Serialize(this); Base result = Operations.Deserialize(json); Assert.That(result, Is.Not.Null); Assert.That(result, Is.TypeOf()); return (TTo)result; } } public abstract class PrimitiveTestFixture { public static readonly sbyte[] Int8TestCases = { default, sbyte.MaxValue, sbyte.MinValue }; public static readonly short[] Int16TestCases = { short.MaxValue, short.MinValue }; public static readonly int[] Int32TestCases = { int.MinValue, int.MaxValue }; public static readonly long[] Int64TestCases = { long.MaxValue, long.MinValue }; public static double[] Float64TestCases { get; } = { default, double.Epsilon, double.MaxValue, double.MinValue, double.PositiveInfinity, double.NegativeInfinity, double.NaN }; public static float[] Float32TestCases { get; } = { default, float.Epsilon, float.MaxValue, float.MinValue, float.PositiveInfinity, float.NegativeInfinity, float.NaN }; public static Half[] Float16TestCases { get; } = { default, Half.Epsilon, Half.MaxValue, Half.MinValue, Half.PositiveInfinity, Half.NegativeInfinity, Half.NaN }; public static float[] FloatIntegralTestCases { get; } = { 0, 1, int.MaxValue, int.MinValue }; public static MyEnum[] MyEnums { get; } = Enum.GetValues(typeof(MyEnum)).Cast().ToArray(); }