Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating custom AutoBogus for WorldPersistenceTest and PacketsSerializableTest #2018

Merged
merged 10 commits into from
Apr 22, 2023
Prev Previous commit
Next Next commit
Implement the new faker in the PacketsSerializableTest
  • Loading branch information
Jannify committed Apr 8, 2023
commit 93dc9f8d9713fc1d68139e026b1866a2470ea699
54 changes: 31 additions & 23 deletions Nitrox.Test/Helper/Faker/NitroxAutoFaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using BinaryPack.Attributes;
using NitroxModel.Logger;

namespace Nitrox.Test.Helper.Faker;

Expand All @@ -16,6 +18,7 @@ public class NitroxAutoFaker<T> : NitroxFaker, INitroxFaker
public NitroxAutoFaker()
{
Type type = typeof(T);
Log.Info(type.ToString());
if (!IsValidType(type))
{
throw new InvalidOperationException($"{type.Name} is not a valid type for {nameof(NitroxAutoFaker<T>)}");
Expand All @@ -24,10 +27,24 @@ public NitroxAutoFaker()
OutputType = type;
FakerByType.Add(type, this);

memberInfos = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(member => member.GetCustomAttributes<DataMemberAttribute>().Any()).ToArray();
if (type.GetCustomAttributes(typeof(DataContractAttribute), false).Length > 0)
{
memberInfos = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(member => member.GetCustomAttributes<DataMemberAttribute>().Any()).ToArray();
}
else
{
memberInfos = type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
.Where(member => member.MemberType is MemberTypes.Field or MemberTypes.Property &&
!member.GetCustomAttributes<IgnoredMemberAttribute>().Any())
.ToArray();
}

constructor = GetConstructorForType(type, memberInfos);
if (!TryGetConstructorForType(type, memberInfos, out constructor) &&
!TryGetConstructorForType(type, Array.Empty<MemberInfo>(), out constructor))
{
throw new NullReferenceException($"Could not find a constructor with no parameters for {type}");
}

parameterFakers = new INitroxFaker[memberInfos.Length];

Expand Down Expand Up @@ -62,8 +79,11 @@ void ValidateFaker(INitroxFaker nitroxFaker)

if (nitroxFaker is NitroxAbstractFaker abstractFaker)
{
NitroxCollectionFaker collectionFaker = (NitroxCollectionFaker)fakerTree.Last(f => f.GetType() == typeof(NitroxCollectionFaker));
collectionFaker.GenerateSize = Math.Max(collectionFaker.GenerateSize, abstractFaker.AssignableTypesCount);
NitroxCollectionFaker collectionFaker = (NitroxCollectionFaker)fakerTree.LastOrDefault(f => f.GetType() == typeof(NitroxCollectionFaker));
if (collectionFaker != null)
{
collectionFaker.GenerateSize = Math.Max(collectionFaker.GenerateSize, abstractFaker.AssignableTypesCount);
}
}

foreach (INitroxFaker subFaker in nitroxFaker.GetSubFakers())
Expand Down Expand Up @@ -174,12 +194,9 @@ public object GenerateUnsafe(HashSet<Type> typeTree)
return obj;
}

private static ConstructorInfo GetConstructorForType(Type type, MemberInfo[] dataMembers)
private static bool TryGetConstructorForType(Type type, MemberInfo[] dataMembers, out ConstructorInfo constructorInfo)
{
ConstructorInfo constructorToUse = null;
ConstructorInfo[] constructors = type.GetConstructors();

foreach (ConstructorInfo constructor in constructors)
foreach (ConstructorInfo constructor in type.GetConstructors())
{
if (constructor.GetParameters().Length != dataMembers.Length)
{
Expand All @@ -192,21 +209,12 @@ private static ConstructorInfo GetConstructorForType(Type type, MemberInfo[] dat

if (parameterValid)
{
constructorToUse = constructor;
break;
}
}

if (constructorToUse == null)
{
constructorToUse = type.GetConstructor(Array.Empty<Type>());

if (constructorToUse == null)
{
throw new NullReferenceException($"Could not find a constructor with no parameters for {type}");
constructorInfo = constructor;
return true;
}
}

return constructorToUse;
constructorInfo = null;
return false;
}
}
11 changes: 6 additions & 5 deletions Nitrox.Test/Helper/Faker/NitroxFaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ public interface INitroxFaker

public abstract class NitroxFaker
{
public Type OutputType { get; init; }
public bool ShouldSkip = false;
public Type OutputType { get; protected init; }
protected static readonly Bogus.Faker Faker;

static NitroxFaker()
Expand Down Expand Up @@ -50,7 +49,7 @@ static NitroxFaker()
{ typeof(NitroxId), new NitroxActionFaker(typeof(NitroxId), f => new NitroxId(f.Random.Guid())) },
};

protected static INitroxFaker GetOrCreateFaker(Type t)
public static INitroxFaker GetOrCreateFaker(Type t)
{
return FakerByType.TryGetValue(t, out INitroxFaker nitroxFaker) ? nitroxFaker : CreateFaker(t);
}
Expand Down Expand Up @@ -106,11 +105,13 @@ protected static bool IsValidType(Type type)
{
return FakerByType.ContainsKey(type) ||
type.GetCustomAttributes(typeof(DataContractAttribute), false).Length >= 1 ||
(NitroxCollectionFaker.TryGetCollectionTypes(type, out Type[] collectionTypes) && collectionTypes.All(IsValidType) ||
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
type.GetCustomAttributes(typeof(SerializableAttribute), false).Length >= 1 ||
(NitroxCollectionFaker.TryGetCollectionTypes(type, out Type[] collectionTypes) && collectionTypes.All(IsValidType)) ||
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}

protected static readonly MethodInfo CastMethodBase = typeof(NitroxFaker).GetMethod("Cast", BindingFlags.NonPublic | BindingFlags.Static);

// ReSharper disable once UnusedMember.Global
protected static T Cast<T>(object o)
{
Expand Down
66 changes: 27 additions & 39 deletions Nitrox.Test/Model/Packets/PacketsSerializableTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,15 @@
using KellermanSoftware.CompareNetObjects;
using KellermanSoftware.CompareNetObjects.TypeComparers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nitrox.Test.Helper.Faker;
using NitroxModel_Subnautica.Logger;
using NitroxModel.DataStructures;

namespace NitroxModel.Packets;

[TestClass]
public class PacketsSerializableTest
{
private static Assembly subnauticaModelAssembly;

[TestInitialize]
public void Initialize()
{
subnauticaModelAssembly = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName("NitroxModel-Subnautica.dll"));
}

[TestMethod]
public void InitSerializerTest()
{
Expand All @@ -30,62 +24,56 @@ public void InitSerializerTest()
[TestMethod]
public void PacketSerializationTest()
{
IEnumerable<Type> types = typeof(Packet).Assembly.GetTypes().Concat(subnauticaModelAssembly.GetTypes());
Dictionary<Type, Type[]> subtypesByBaseType = types
.Where(type => type.IsAbstract && !type.IsSealed && !type.ContainsGenericParameters && type != typeof(Packet))
.ToDictionary(type => type, type => types.Where(t => type.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface).ToArray());
IEnumerable<Type> packetTypes = types.Where(p => typeof(Packet).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract);
ComparisonConfig config = new();
config.SkipInvalidIndexers = true;
config.AttributesToIgnore.Add(typeof(IgnoredMemberAttribute));
config.CustomComparers.Add(new CustomComparer<NitroxId, NitroxId>((id1, id2) => id1.Equals(id2)));
CompareLogic compareLogic = new CompareLogic(config);
Jannify marked this conversation as resolved.
Show resolved Hide resolved

IEnumerable<Type> types = typeof(Packet).Assembly.GetTypes().Concat(typeof(SubnauticaInGameLogger).Assembly.GetTypes());
Type[] packetTypes = types.Where(p => typeof(Packet).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract).ToArray();

// We want to ignore packets with no members when using ShouldNotCompare
/*PacketAutoBinder binder = new(subtypesByBaseType);
Type[] emptyPackets = packetTypes.Where(x => binder.GetMembers(x).Count == 0 ||
binder.GetMembers(x).All(m => m.Value.GetMemberType().IsEnum))
.ToArray();*/
Type[] emptyPackets = packetTypes.Where(t => !t.GetMembers(BindingFlags.Public | BindingFlags.Instance)
.Any(member => member.MemberType is MemberTypes.Field or MemberTypes.Property &&
!member.GetCustomAttributes<IgnoredMemberAttribute>().Any()))
.ToArray();

// We generate two different versions of each packet to verify comparison is actually working
List<Tuple<Packet, Packet>> generatedPackets = new();
Jannify marked this conversation as resolved.
Show resolved Hide resolved

foreach (Type type in packetTypes)
{
/*NitroxAutoFakerNonGeneric faker = new(type, subtypesByBaseType, binder);
dynamic faker = NitroxFaker.GetOrCreateFaker(type);

if (subtypesByBaseType.ContainsKey(type))
Packet packet = faker.Generate();
Packet packet2 = null;

if (!emptyPackets.Contains(type))
{
for (int i = 0; i < subtypesByBaseType[type].Length; i++)
ComparisonResult result;
do
{
Packet packet = faker.Generate<Packet>(subtypesByBaseType[type][i]);
Packet packet2 = faker.Generate<Packet>(subtypesByBaseType[type][i]);
generatedPackets.Add(new Tuple<Packet, Packet>(packet, packet2));
}
packet2 = faker.Generate();
result = compareLogic.Compare(packet, packet2);
} while (result == null || result.AreEqual);
}
else
{
Packet packet = faker.Generate<Packet>(type);
Packet packet2 = faker.Generate<Packet>(type);
generatedPackets.Add(new Tuple<Packet, Packet>(packet, packet2));
}*/

generatedPackets.Add(new Tuple<Packet, Packet>(packet, packet2));
}

Packet.InitSerializer();



ComparisonConfig config = new();
config.SkipInvalidIndexers = true;
config.AttributesToIgnore.Add(typeof(IgnoredMemberAttribute));
config.CustomComparers.Add(new CustomComparer<NitroxId, NitroxId>((id1, id2) => id1.Equals(id2)));

foreach (Tuple<Packet, Packet> packet in generatedPackets)
{
Packet deserialized = Packet.Deserialize(packet.Item1.Serialize());

packet.Item1.ShouldCompare(deserialized, $"with {packet.Item1.GetType()}", config);

/*if (!emptyPackets.Contains(packet.Item1.GetType()))
if (!emptyPackets.Contains(packet.Item1.GetType()))
{
packet.Item2.ShouldNotCompare(deserialized, $"with {packet.Item1.GetType()}", config);
}*/
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using NitroxClient.Communication.Abstract;
using System;
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel_Subnautica.DataStructures;
using NitroxModel.DataStructures.Util;
using NitroxModel_Subnautica.Packets;
using UnityEngine;
Expand Down Expand Up @@ -38,10 +40,10 @@ public override void Process(ExosuitArmActionPacket packet)
exosuitModuleEvent.UseDrill(gameObject.GetComponent<ExosuitDrillArm>(), packet.ArmAction);
break;
case TechType.ExosuitGrapplingArmModule:
exosuitModuleEvent.UseGrappling(gameObject.GetComponent<ExosuitGrapplingArm>(), packet.ArmAction, packet.OpVector);
exosuitModuleEvent.UseGrappling(gameObject.GetComponent<ExosuitGrapplingArm>(), packet.ArmAction, packet.OpVector?.ToUnity());
break;
case TechType.ExosuitTorpedoArmModule:
exosuitModuleEvent.UseTorpedo(gameObject.GetComponent<ExosuitTorpedoArm>(), packet.ArmAction, packet.OpVector, packet.OpRotation);
exosuitModuleEvent.UseTorpedo(gameObject.GetComponent<ExosuitTorpedoArm>(), packet.ArmAction, packet.OpVector?.ToUnity(), packet.OpRotation?.ToUnity());
break;
default:
Log.Error($"Got an arm tech that is not handled: {packet.TechType} with action: {packet.ArmAction} for id {packet.ArmId}");
Expand Down
3 changes: 2 additions & 1 deletion NitroxClient/GameLogic/Cyclops.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel_Subnautica.DataStructures;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
Expand Down Expand Up @@ -141,7 +142,7 @@ private void BroadcastDamageState(SubRoot subRoot, Optional<DamageInfo> info)
damage.dealer != null ? NitroxEntity.GetId(damage.dealer) : null,
damage.originalDamage,
damage.damage,
damage.position,
damage.position.ToDto(),
damage.type);
}

Expand Down
3 changes: 2 additions & 1 deletion NitroxClient/GameLogic/ExosuitModuleEvent.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel_Subnautica.DataStructures;
using NitroxModel.DataStructures;
using NitroxModel_Subnautica.Packets;
using UnityEngine;
Expand Down Expand Up @@ -70,7 +71,7 @@ public void UseDrill(ExosuitDrillArm drillArm, ExosuitArmAction armAction)
public void BroadcastArmAction(TechType techType, IExosuitArm exosuitArm, ExosuitArmAction armAction, Vector3? opVector, Quaternion? opRotation)
{
NitroxId id = NitroxEntity.GetId(exosuitArm.GetGameObject());
ExosuitArmActionPacket packet = new ExosuitArmActionPacket(techType, id, armAction, opVector, opRotation);
ExosuitArmActionPacket packet = new ExosuitArmActionPacket(techType, id, armAction, opVector?.ToDto(), opRotation?.ToDto());
packetSender.Send(packet);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
using UnityEngine;
using System;
using UnityEngine;

namespace NitroxModel_Subnautica.DataStructures.GameLogic.Creatures.Actions
{
public interface SerializableCreatureAction
{
CreatureAction GetCreatureAction(GameObject gameObject);
}

// SerializableCreatureAction is not implemented yet but test require that at least one class inherits it
[Serializable]
public class EmptyCreatureAction: SerializableCreatureAction
{
public CreatureAction GetCreatureAction(GameObject gameObject) => null;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Runtime.Serialization;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.Unity;
using UnityEngine;

namespace NitroxModel_Subnautica.DataStructures.GameLogic
Expand All @@ -22,7 +23,7 @@ public class CyclopsDamageInfoData
public float Damage { get; set; }

[DataMember(Order = 5)]
public Vector3 Position { get; set; }
public NitroxVector3 Position { get; set; }

[DataMember(Order = 6)]
public DamageType Type { get; set; }
Expand All @@ -32,7 +33,7 @@ protected CyclopsDamageInfoData()
// Constructor for serialization. Has to be "protected" for json serialization.
}

public CyclopsDamageInfoData(NitroxId receiverId, NitroxId dealerId, float originalDamage, float damage, Vector3 position, DamageType type)
public CyclopsDamageInfoData(NitroxId receiverId, NitroxId dealerId, float originalDamage, float damage, NitroxVector3 position, DamageType type)
{
ReceiverId = receiverId;
DealerId = dealerId;
Expand Down
7 changes: 4 additions & 3 deletions NitroxModel-Subnautica/Packets/ExosuitArmActionPacket.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.Unity;
using NitroxModel.Packets;
using UnityEngine;

Expand All @@ -11,10 +12,10 @@ public class ExosuitArmActionPacket : Packet
public TechType TechType { get; }
public NitroxId ArmId { get; }
public ExosuitArmAction ArmAction { get; }
public Vector3? OpVector { get; }
public Quaternion? OpRotation { get; }
public NitroxVector3? OpVector { get; }
public NitroxQuaternion? OpRotation { get; }

public ExosuitArmActionPacket(TechType techType, NitroxId armId, ExosuitArmAction armAction, Vector3? opVector, Quaternion? opRotation)
public ExosuitArmActionPacket(TechType techType, NitroxId armId, ExosuitArmAction armAction, NitroxVector3? opVector, NitroxQuaternion? opRotation)
{
TechType = techType;
ArmId = armId;
Expand Down
1 change: 1 addition & 0 deletions NitroxModel/DataStructures/NitroxVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace NitroxModel.DataStructures;
/// <summary>
/// Serializable version of <see cref="Version" /> with only major and minor properties.
/// </summary>
[Serializable]
public readonly struct NitroxVersion : IComparable<NitroxVersion>
{
public ushort Major { get; init; }
Expand Down
6 changes: 6 additions & 0 deletions NitroxModel/Packets/VehicleMovement.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using BinaryPack.Attributes;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.Unity;
using NitroxModel.Networking;
Expand All @@ -10,9 +11,14 @@ public class VehicleMovement : Movement
{
public override ushort PlayerId { get; }
public VehicleMovementData VehicleMovementData { get; }

[IgnoredMember]
public override NitroxVector3 Position => VehicleMovementData.Position;
[IgnoredMember]
public override NitroxVector3 Velocity => VehicleMovementData.Velocity;
[IgnoredMember]
public override NitroxQuaternion BodyRotation => VehicleMovementData.Rotation;
[IgnoredMember]
public override NitroxQuaternion AimingRotation => VehicleMovementData.Rotation;

public VehicleMovement(ushort playerId, VehicleMovementData vehicleMovementData)
Expand Down