#nullable enable using System; using System.Collections.Immutable; using Barotrauma; using Barotrauma.Networking; using FluentAssertions; using FsCheck; using Microsoft.Xna.Framework; using Xunit; namespace TestProject { // ReSharper disable UnusedMember.Local NotAccessedField.Local UnusedMember.Global public sealed class INetSerializableStructTests { private class CustomGenerators { // no null strings!!! public static Arbitrary StringGeneratorOverride() => Arb.Default.String().Generator.Where(s => s != null).ToArbitrary(); } public INetSerializableStructTests() { Arb.Register(); Arb.Register(); } [Fact] public void TestBitField() { // 0-length bitfield test SerializeDeserializeBitField(Array.Empty()); // Normal bitfield test Prop.ForAll(SerializeDeserializeBitField).VerboseCheckThrowOnFailure(); // Large bitfield test Prop.ForAll( Arb.Generate().Resize(1000).Where(arr => arr.Length >= 800) .ToArbitrary(), SerializeDeserializeBitField).VerboseCheckThrowOnFailure(); } [Fact] public void TestRanged() { Prop.ForAll( Arb.Generate().Where(i => i <= 100 && i >= -100).ToArbitrary(), Arb.Generate().Where(f => f <= 100f && f >= -100f).ToArbitrary(), SerializeDeserializeRanged).QuickCheckThrowOnFailure(); } [Fact] public void TestOptional() { Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestNested() { Prop.ForAll((arg1, arg2, arg3) => SerializeDeserializeNullableTuple(arg1, new TupleNullableStruct { One = arg2, Two = arg3 })).QuickCheckThrowOnFailure(); Prop.ForAll((arg1, arg2) => SerializeDeserialize(new TupleNullableStruct { One = arg1, Two = arg2 })).QuickCheckThrowOnFailure(); Prop.ForAll((arg1, arg2) => SerializeDeserialize(new TupleNullableStruct { One = arg1, Two = arg2 })).QuickCheckThrowOnFailure(); } [Fact] public void TestVector2() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestColor() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestEnum() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestEnumFlags() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestArray() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestImmutableArray() { Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); Prop.ForAll>(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestNullable() { Prop.ForAll(SerializeDeserializeNullableTuple).QuickCheckThrowOnFailure(); } [Fact] public void TestBoolean() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestByte() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestUInt16() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestInt16() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestUInt32() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestInt32() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestUInt64() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestInt64() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestSingle() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestDouble() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } [Fact] public void TestString() { Prop.ForAll(SerializeDeserialize).QuickCheckThrowOnFailure(); } private enum EnumTest { One = 1, Two = 2, Three = 3, Thousand = 1000 } [Flags] private enum EnumFlagsTest { None = 0, Bit0 = 1 << 0, Bit1 = 1 << 1, Bit2 = 1 << 2, Bit3 = 1 << 3 } private struct TestRangedStruct : INetSerializableStruct { [NetworkSerialize(MinValueInt = -100, MaxValueInt = 100)] public int IntValue; [NetworkSerialize(MinValueFloat = -100, MaxValueFloat = 100, NumberOfBits = 16)] public float FloatValue; } #pragma warning disable CS0649 private struct TestStruct : INetSerializableStruct { [NetworkSerialize] public T Value; public T NotSerializedValue; public T NotSerializedFunction() => throw new NotImplementedException(); } [NetworkSerialize] private readonly record struct TestRecord(T Value) : INetSerializableStruct; private struct TupleNullableStruct : INetSerializableStruct { [NetworkSerialize] public T? One; [NetworkSerialize] public U? Two; public (T, U) NotSerializedValue; public (T, U) NotSerializedFunction() => throw new NotImplementedException(); } #pragma warning restore CS0649 private static void SerializeDeserializeRanged(int intValue, float floatValue) { ReadWriteMessage msg = new ReadWriteMessage(); TestRangedStruct writeStruct = new TestRangedStruct { IntValue = intValue, FloatValue = floatValue }; msg.WriteNetSerializableStruct(writeStruct); msg.BitPosition = 0; TestRangedStruct readStruct = INetSerializableStruct.Read(msg); readStruct.FloatValue.Should().BeApproximately(floatValue, 0.25f); // should be enough precision readStruct.IntValue.Should().Be(intValue); } private static void SerializeDeserializeImpl(T toWrite) where T : INetSerializableStruct { ReadWriteMessage msg = new ReadWriteMessage(); msg.WriteNetSerializableStruct(toWrite); msg.BitPosition = 0; T read = INetSerializableStruct.Read(msg); read.Should().BeEquivalentTo(toWrite, options => options .ComparingByMembers() .ComparingByMembers(typeof(Option<>))); } private static void SerializeDeserializeStruct(T arg) where T : notnull => SerializeDeserializeImpl(new TestStruct { Value = arg }); private static void SerializeDeserializeRecord(T arg) where T : notnull => SerializeDeserializeImpl(new TestRecord(arg)); private static void SerializeDeserialize(T arg) where T : notnull { SerializeDeserializeStruct(arg); SerializeDeserializeRecord(arg); } private static void SerializeDeserializeNullableTuple(T arg1, U arg2) { ReadWriteMessage msg = new ReadWriteMessage(); TupleNullableStruct writeStruct = new TupleNullableStruct { One = arg1, Two = arg2 }; msg.WriteNetSerializableStruct(writeStruct); msg.BitPosition = 0; TupleNullableStruct readStruct = INetSerializableStruct.Read>(msg); readStruct.Should().BeEquivalentTo(writeStruct, options => options .ComparingByMembers>() .ComparingByMembers(typeof(Option<>))); } private static void SerializeDeserializeBitField(bool[] arg) { ReadWriteMessage msg = new ReadWriteMessage(); WriteOnlyBitField bitFieldWrite = new WriteOnlyBitField(); foreach (bool b in arg) { bitFieldWrite.WriteBoolean(b); } bitFieldWrite.WriteToMessage(msg); msg.BitPosition = 0; ReadOnlyBitField bitFieldRead = new ReadOnlyBitField(msg); foreach (bool b in arg) { bitFieldRead.ReadBoolean().Should().Be(b); } } } }