first commit
This commit is contained in:
54
Nitrox.Test/Helper/AssertHelper.cs
Normal file
54
Nitrox.Test/Helper/AssertHelper.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Nitrox.Test.Helper;
|
||||
|
||||
public static class AssertHelper
|
||||
{
|
||||
public static void IsListEqual<TSource>(IOrderedEnumerable<TSource> first, IOrderedEnumerable<TSource> second, Action<TSource, TSource> assertComparer)
|
||||
{
|
||||
Assert.IsNotNull(first);
|
||||
Assert.IsNotNull(second);
|
||||
|
||||
List<TSource> firstList = first.ToList();
|
||||
List<TSource> secondList = second.ToList();
|
||||
|
||||
Assert.AreEqual(firstList.Count, secondList.Count);
|
||||
|
||||
for (int index = 0; index < firstList.Count; index++)
|
||||
{
|
||||
assertComparer(firstList[index], secondList[index]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void IsDictionaryEqual<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second)
|
||||
{
|
||||
Assert.IsNotNull(first);
|
||||
Assert.IsNotNull(second);
|
||||
Assert.AreEqual(first.Count, second.Count);
|
||||
|
||||
for (int index = 0; index < first.Count; index++)
|
||||
{
|
||||
KeyValuePair<TKey, TValue> firstKeyValuePair = first.ElementAt(index);
|
||||
Assert.IsTrue(second.TryGetValue(firstKeyValuePair.Key, out TValue secondValue), $"Second dictionary didn't contain {firstKeyValuePair.Key}");
|
||||
Assert.AreEqual(firstKeyValuePair.Value, secondValue, $"Values didn't match with the same key: {firstKeyValuePair.Key}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void IsDictionaryEqual<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second, Action<KeyValuePair<TKey, TValue>, KeyValuePair<TKey, TValue>> assertComparer)
|
||||
{
|
||||
Assert.IsNotNull(first);
|
||||
Assert.IsNotNull(second);
|
||||
Assert.AreEqual(first.Count, second.Count);
|
||||
|
||||
for (int index = 0; index < first.Count; index++)
|
||||
{
|
||||
KeyValuePair<TKey, TValue> firstKeyValuePair = first.ElementAt(index);
|
||||
Assert.IsTrue(second.TryGetValue(firstKeyValuePair.Key, out TValue secondValue), $"Second dictionary didn't contain {firstKeyValuePair.Key}");
|
||||
|
||||
assertComparer(firstKeyValuePair, new KeyValuePair<TKey, TValue>(firstKeyValuePair.Key, secondValue));
|
||||
}
|
||||
}
|
||||
}
|
70
Nitrox.Test/Helper/Faker/NitroxAbstractFaker.cs
Normal file
70
Nitrox.Test/Helper/Faker/NitroxAbstractFaker.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NitroxModel_Subnautica.Logger;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Packets.Processors.Abstract;
|
||||
using NitroxServer;
|
||||
using NitroxServer_Subnautica;
|
||||
using NitroxServer.ConsoleCommands.Abstract;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxAbstractFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private static readonly Dictionary<Type, Type[]> subtypesByBaseType;
|
||||
|
||||
static NitroxAbstractFaker()
|
||||
{
|
||||
Assembly[] assemblies = { typeof(Packet).Assembly, typeof(SubnauticaInGameLogger).Assembly, typeof(ServerAutoFacRegistrar).Assembly, typeof(SubnauticaServerAutoFacRegistrar).Assembly };
|
||||
HashSet<Type> blacklistedTypes = new() { typeof(Packet), typeof(CorrelatedPacket), typeof(Command), typeof(PacketProcessor) };
|
||||
|
||||
List<Type> types = new();
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
types.AddRange(assembly.GetTypes());
|
||||
}
|
||||
|
||||
subtypesByBaseType = types.Where(type => type.IsAbstract && !type.IsSealed && !blacklistedTypes.Contains(type))
|
||||
.ToDictionary(type => type, type => types.Where(t => type.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface).ToArray())
|
||||
.Where(dict => dict.Value.Length > 0)
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
}
|
||||
|
||||
public readonly int AssignableTypesCount;
|
||||
private readonly Queue<INitroxFaker> assignableFakers = new();
|
||||
|
||||
public NitroxAbstractFaker(Type type)
|
||||
{
|
||||
if (!type.IsAbstract)
|
||||
{
|
||||
throw new ArgumentException("Argument is not abstract", nameof(type));
|
||||
}
|
||||
|
||||
if (!subtypesByBaseType.TryGetValue(type, out Type[] subTypes))
|
||||
{
|
||||
throw new ArgumentException($"Argument is not contained in {nameof(subtypesByBaseType)}", nameof(type));
|
||||
}
|
||||
|
||||
OutputType = type;
|
||||
AssignableTypesCount = subTypes.Length;
|
||||
FakerByType.Add(type, this);
|
||||
foreach (Type subType in subTypes)
|
||||
{
|
||||
assignableFakers.Enqueue(GetOrCreateFaker(subType));
|
||||
}
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => assignableFakers.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Selects an implementing type in a round-robin fashion of the abstract type of this faker. Then creates an instance of it.
|
||||
/// </summary>
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree)
|
||||
{
|
||||
INitroxFaker assignableFaker = assignableFakers.Dequeue();
|
||||
assignableFakers.Enqueue(assignableFaker);
|
||||
return assignableFaker.GenerateUnsafe(typeTree);
|
||||
}
|
||||
}
|
19
Nitrox.Test/Helper/Faker/NitroxActionFaker.cs
Normal file
19
Nitrox.Test/Helper/Faker/NitroxActionFaker.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxActionFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private readonly Func<Bogus.Faker, object> generateAction;
|
||||
|
||||
public NitroxActionFaker(Type type, Func<Bogus.Faker, object> action)
|
||||
{
|
||||
OutputType = type;
|
||||
generateAction = action;
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => Array.Empty<INitroxFaker>();
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> _) => generateAction.Invoke(Faker);
|
||||
}
|
239
Nitrox.Test/Helper/Faker/NitroxAutoFaker.cs
Normal file
239
Nitrox.Test/Helper/Faker/NitroxAutoFaker.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using BinaryPack.Attributes;
|
||||
using NitroxModel.Logger;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxAutoFaker<T> : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private readonly ConstructorInfo constructor;
|
||||
private readonly MemberInfo[] memberInfos;
|
||||
private readonly INitroxFaker[] parameterFakers;
|
||||
|
||||
public NitroxAutoFaker()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
if (!IsValidType(type))
|
||||
{
|
||||
throw new InvalidOperationException($"{type.Name} is not a valid type for {nameof(NitroxAutoFaker<T>)}");
|
||||
}
|
||||
|
||||
OutputType = type;
|
||||
FakerByType.Add(type, this);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
Type[] constructorArgumentTypes = constructor.GetParameters().Select(p => p.ParameterType).ToArray();
|
||||
for (int i = 0; i < memberInfos.Length; i++)
|
||||
{
|
||||
Type dataMemberType = constructorArgumentTypes.Length == memberInfos.Length ? constructorArgumentTypes[i] : memberInfos[i].GetMemberType();
|
||||
|
||||
if (FakerByType.TryGetValue(dataMemberType, out INitroxFaker memberFaker))
|
||||
{
|
||||
parameterFakers[i] = memberFaker;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameterFakers[i] = CreateFaker(dataMemberType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateFakerTree()
|
||||
{
|
||||
List<INitroxFaker> fakerTree = new();
|
||||
|
||||
void ValidateFaker(INitroxFaker nitroxFaker)
|
||||
{
|
||||
if (fakerTree.Contains(nitroxFaker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fakerTree.Add(nitroxFaker);
|
||||
|
||||
if (nitroxFaker is NitroxAbstractFaker abstractFaker)
|
||||
{
|
||||
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())
|
||||
{
|
||||
ValidateFaker(subFaker);
|
||||
}
|
||||
|
||||
fakerTree.Remove(nitroxFaker);
|
||||
}
|
||||
|
||||
foreach (INitroxFaker parameterFaker in parameterFakers)
|
||||
{
|
||||
ValidateFaker(parameterFaker);
|
||||
}
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => parameterFakers;
|
||||
|
||||
public T Generate()
|
||||
{
|
||||
ValidateFakerTree();
|
||||
return (T)GenerateUnsafe(new HashSet<Type>());
|
||||
}
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree)
|
||||
{
|
||||
object[] parameterValues = new object[parameterFakers.Length];
|
||||
|
||||
for (int i = 0; i < parameterValues.Length; i++)
|
||||
{
|
||||
INitroxFaker parameterFaker = parameterFakers[i];
|
||||
|
||||
if (typeTree.Contains(parameterFaker.OutputType))
|
||||
{
|
||||
if (parameterFaker is NitroxCollectionFaker collectionFaker)
|
||||
{
|
||||
parameterValues[i] = Activator.CreateInstance(collectionFaker.OutputCollectionType);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameterValues[i] = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
typeTree.Add(parameterFaker.OutputType);
|
||||
parameterValues[i] = parameterFakers[i].GenerateUnsafe(typeTree);
|
||||
typeTree.Remove(parameterFaker.OutputType);
|
||||
}
|
||||
}
|
||||
|
||||
if (constructor.GetParameters().Length == parameterValues.Length)
|
||||
{
|
||||
return (T)constructor.Invoke(parameterValues);
|
||||
}
|
||||
|
||||
T obj = (T)constructor.Invoke(Array.Empty<object>());
|
||||
for (int index = 0; index < memberInfos.Length; index++)
|
||||
{
|
||||
MemberInfo memberInfo = memberInfos[index];
|
||||
switch (memberInfo.MemberType)
|
||||
{
|
||||
case MemberTypes.Field:
|
||||
((FieldInfo)memberInfo).SetValue(obj, parameterValues[index]);
|
||||
break;
|
||||
case MemberTypes.Property:
|
||||
PropertyInfo propertyInfo = (PropertyInfo)memberInfo;
|
||||
|
||||
if (!propertyInfo.CanWrite &&
|
||||
NitroxCollectionFaker.IsCollection(parameterValues[index].GetType(), out NitroxCollectionFaker.CollectionType collectionType))
|
||||
{
|
||||
dynamic origColl = propertyInfo.GetValue(obj);
|
||||
|
||||
switch (collectionType)
|
||||
{
|
||||
case NitroxCollectionFaker.CollectionType.ARRAY:
|
||||
for (int i = 0; i < ((Array)parameterValues[index]).Length; i++)
|
||||
{
|
||||
origColl[i] = ((Array)parameterValues[index]).GetValue(i);
|
||||
}
|
||||
|
||||
break;
|
||||
case NitroxCollectionFaker.CollectionType.LIST:
|
||||
case NitroxCollectionFaker.CollectionType.DICTIONARY:
|
||||
case NitroxCollectionFaker.CollectionType.SET:
|
||||
foreach (dynamic createdValue in ((IEnumerable)parameterValues[index]))
|
||||
{
|
||||
origColl.Add(createdValue);
|
||||
}
|
||||
|
||||
break;
|
||||
case NitroxCollectionFaker.CollectionType.QUEUE:
|
||||
foreach (dynamic createdValue in ((IEnumerable)parameterValues[index]))
|
||||
{
|
||||
origColl.Enqueue(createdValue);
|
||||
}
|
||||
|
||||
break;
|
||||
case NitroxCollectionFaker.CollectionType.NONE:
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
else if(propertyInfo.CanWrite)
|
||||
{
|
||||
propertyInfo.SetValue(obj, parameterValues[index]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Regex backingFieldNameRegex = new($"\\A<{propertyInfo.Name}>k__BackingField\\Z");
|
||||
FieldInfo backingField = propertyInfo.DeclaringType.GetRuntimeFields().FirstOrDefault(a => backingFieldNameRegex.IsMatch(a.Name));
|
||||
|
||||
if (backingField == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{propertyInfo.DeclaringType}.{propertyInfo.Name} is not accessible for writing");
|
||||
}
|
||||
|
||||
backingField.SetValue(obj, parameterValues[index]);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static bool TryGetConstructorForType(Type type, MemberInfo[] dataMembers, out ConstructorInfo constructorInfo)
|
||||
{
|
||||
foreach (ConstructorInfo constructor in type.GetConstructors())
|
||||
{
|
||||
if (constructor.GetParameters().Length != dataMembers.Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool parameterValid = constructor.GetParameters()
|
||||
.All(parameter => dataMembers.Any(d => d.GetMemberType() == parameter.ParameterType &&
|
||||
d.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
if (parameterValid)
|
||||
{
|
||||
constructorInfo = constructor;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
constructorInfo = null;
|
||||
return false;
|
||||
}
|
||||
}
|
202
Nitrox.Test/Helper/Faker/NitroxCollectionFaker.cs
Normal file
202
Nitrox.Test/Helper/Faker/NitroxCollectionFaker.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using NitroxModel.DataStructures;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxCollectionFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
public enum CollectionType
|
||||
{
|
||||
NONE,
|
||||
ARRAY,
|
||||
LIST,
|
||||
DICTIONARY,
|
||||
SET,
|
||||
QUEUE
|
||||
}
|
||||
|
||||
private const int DEFAULT_SIZE = 2;
|
||||
|
||||
public static bool IsCollection(Type t, out CollectionType collectionType)
|
||||
{
|
||||
if (t.IsArray && t.GetArrayRank() == 1)
|
||||
{
|
||||
collectionType = CollectionType.ARRAY;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (t.IsGenericType)
|
||||
{
|
||||
Type[] genericInterfacesDefinition = t.GetInterfaces()
|
||||
.Where(i => i.IsGenericType)
|
||||
.Select(i => i.GetGenericTypeDefinition())
|
||||
.ToArray();
|
||||
|
||||
if (genericInterfacesDefinition.Any(i => i == typeof(IList<>)))
|
||||
{
|
||||
collectionType = CollectionType.LIST;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (genericInterfacesDefinition.Any(i => i == typeof(IDictionary<,>)))
|
||||
{
|
||||
collectionType = CollectionType.DICTIONARY;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (genericInterfacesDefinition.Any(i => i == typeof(ISet<>)))
|
||||
{
|
||||
collectionType = CollectionType.SET;
|
||||
return true;
|
||||
}
|
||||
|
||||
Type genericTypeDefinition = t.GetGenericTypeDefinition();
|
||||
if (genericTypeDefinition == typeof(Queue<>) || genericTypeDefinition == typeof(ThreadSafeQueue<>)) // Queue has no defining interface
|
||||
{
|
||||
collectionType = CollectionType.QUEUE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
collectionType = CollectionType.NONE;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetCollectionTypes(Type type, out Type[] types)
|
||||
{
|
||||
if (!IsCollection(type, out CollectionType collectionType))
|
||||
{
|
||||
types = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (collectionType == CollectionType.ARRAY)
|
||||
{
|
||||
types = [type.GetElementType()];
|
||||
return true;
|
||||
}
|
||||
|
||||
types = type.GenericTypeArguments;
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GenerateSize = DEFAULT_SIZE;
|
||||
public Type OutputCollectionType;
|
||||
|
||||
private readonly INitroxFaker[] subFakers;
|
||||
private readonly Func<HashSet<Type>, object> generateAction;
|
||||
|
||||
public NitroxCollectionFaker(Type type, CollectionType collectionType)
|
||||
{
|
||||
OutputCollectionType = type;
|
||||
INitroxFaker elementFaker;
|
||||
|
||||
switch (collectionType)
|
||||
{
|
||||
case CollectionType.ARRAY:
|
||||
Type arrayType = OutputType = type.GetElementType();
|
||||
elementFaker = GetOrCreateFaker(arrayType);
|
||||
subFakers = [elementFaker];
|
||||
|
||||
generateAction = typeTree =>
|
||||
{
|
||||
typeTree.Add(arrayType);
|
||||
Array array = Array.CreateInstance(arrayType, GenerateSize);
|
||||
|
||||
for (int i = 0; i < GenerateSize; i++)
|
||||
{
|
||||
array.SetValue(elementFaker.GenerateUnsafe(typeTree), i);
|
||||
}
|
||||
|
||||
typeTree.Remove(arrayType);
|
||||
return array;
|
||||
};
|
||||
break;
|
||||
case CollectionType.LIST:
|
||||
case CollectionType.SET:
|
||||
Type listType = OutputType = type.GenericTypeArguments[0];
|
||||
elementFaker = GetOrCreateFaker(listType);
|
||||
subFakers = [elementFaker];
|
||||
|
||||
generateAction = typeTree =>
|
||||
{
|
||||
typeTree.Add(listType);
|
||||
dynamic list = Activator.CreateInstance(type);
|
||||
|
||||
for (int i = 0; i < GenerateSize; i++)
|
||||
{
|
||||
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
|
||||
dynamic castedObject = castMethod.Invoke(null, [elementFaker.GenerateUnsafe(typeTree)]);
|
||||
list.Add(castedObject);
|
||||
}
|
||||
|
||||
typeTree.Remove(listType);
|
||||
return list;
|
||||
};
|
||||
break;
|
||||
case CollectionType.DICTIONARY:
|
||||
Type[] dicType = type.GenericTypeArguments;
|
||||
OutputType = dicType[1]; // A little hacky but should work as we don't use circular dependencies as keys
|
||||
INitroxFaker keyFaker = GetOrCreateFaker(dicType[0]);
|
||||
INitroxFaker valueFaker = GetOrCreateFaker(dicType[1]);
|
||||
subFakers = [keyFaker, valueFaker];
|
||||
|
||||
generateAction = typeTree =>
|
||||
{
|
||||
typeTree.Add(dicType[0]);
|
||||
typeTree.Add(dicType[1]);
|
||||
IDictionary dict = (IDictionary)Activator.CreateInstance(type);
|
||||
for (int i = 0; i < GenerateSize; i++)
|
||||
{
|
||||
for (int tries = 0; tries < 10; tries++)
|
||||
{
|
||||
object key = keyFaker.GenerateUnsafe(typeTree);
|
||||
|
||||
if (!dict.Contains(key))
|
||||
{
|
||||
dict.Add(key, valueFaker.GenerateUnsafe(typeTree));
|
||||
break;
|
||||
}
|
||||
|
||||
if (tries == 9)
|
||||
{
|
||||
throw new InvalidOperationException($"While generating action for filling Dictionary an unique key of {dicType[0]} couldn't be generated even after 10 tries");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typeTree.Remove(dicType[0]);
|
||||
typeTree.Remove(dicType[1]);
|
||||
return dict;
|
||||
};
|
||||
break;
|
||||
case CollectionType.QUEUE:
|
||||
Type queueType = OutputType = type.GenericTypeArguments[0];
|
||||
elementFaker = GetOrCreateFaker(queueType);
|
||||
subFakers = [elementFaker];
|
||||
|
||||
generateAction = typeTree =>
|
||||
{
|
||||
typeTree.Add(queueType);
|
||||
dynamic queue = Activator.CreateInstance(type);
|
||||
|
||||
for (int i = 0; i < GenerateSize; i++)
|
||||
{
|
||||
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
|
||||
dynamic castedObject = castMethod.Invoke(null, [elementFaker.GenerateUnsafe(typeTree)]);
|
||||
queue!.Enqueue(castedObject);
|
||||
}
|
||||
|
||||
typeTree.Remove(queueType);
|
||||
return queue;
|
||||
};
|
||||
break;
|
||||
case CollectionType.NONE:
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(collectionType), collectionType, null);
|
||||
}
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => subFakers;
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree) => generateAction.Invoke(typeTree);
|
||||
}
|
119
Nitrox.Test/Helper/Faker/NitroxFaker.cs
Normal file
119
Nitrox.Test/Helper/Faker/NitroxFaker.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public interface INitroxFaker
|
||||
{
|
||||
public Type OutputType { get; }
|
||||
public INitroxFaker[] GetSubFakers();
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree);
|
||||
}
|
||||
|
||||
public abstract class NitroxFaker
|
||||
{
|
||||
public Type OutputType { get; protected init; }
|
||||
protected static readonly Bogus.Faker Faker;
|
||||
|
||||
static NitroxFaker()
|
||||
{
|
||||
Faker = new Bogus.Faker();
|
||||
}
|
||||
|
||||
protected static readonly Dictionary<Type, INitroxFaker> FakerByType = new()
|
||||
{
|
||||
// Basic types
|
||||
{ typeof(bool), new NitroxActionFaker(typeof(bool), f => f.Random.Bool()) },
|
||||
{ typeof(byte), new NitroxActionFaker(typeof(byte), f => f.Random.Byte()) },
|
||||
{ typeof(sbyte), new NitroxActionFaker(typeof(sbyte), f => f.Random.SByte()) },
|
||||
{ typeof(short), new NitroxActionFaker(typeof(short), f => f.Random.Short()) },
|
||||
{ typeof(ushort), new NitroxActionFaker(typeof(ushort), f => f.Random.UShort()) },
|
||||
{ typeof(int), new NitroxActionFaker(typeof(int), f => f.Random.Int()) },
|
||||
{ typeof(uint), new NitroxActionFaker(typeof(uint), f => f.Random.UInt()) },
|
||||
{ typeof(long), new NitroxActionFaker(typeof(long), f => f.Random.Long()) },
|
||||
{ typeof(ulong), new NitroxActionFaker(typeof(ulong), f => f.Random.ULong()) },
|
||||
{ typeof(decimal), new NitroxActionFaker(typeof(decimal), f => f.Random.Decimal()) },
|
||||
{ typeof(float), new NitroxActionFaker(typeof(float), f => f.Random.Float()) },
|
||||
{ typeof(double), new NitroxActionFaker(typeof(double), f => f.Random.Double()) },
|
||||
{ typeof(char), new NitroxActionFaker(typeof(char), f => f.Random.Char()) },
|
||||
{ typeof(string), new NitroxActionFaker(typeof(string), f => f.Random.Word()) },
|
||||
|
||||
// Nitrox types
|
||||
{ typeof(NitroxTechType), new NitroxActionFaker(typeof(NitroxTechType), f => new NitroxTechType(f.PickRandom<TechType>().ToString())) },
|
||||
{ typeof(NitroxId), new NitroxActionFaker(typeof(NitroxId), f => new NitroxId(f.Random.Guid())) },
|
||||
};
|
||||
|
||||
public static INitroxFaker GetOrCreateFaker(Type t)
|
||||
{
|
||||
return FakerByType.TryGetValue(t, out INitroxFaker nitroxFaker) ? nitroxFaker : CreateFaker(t);
|
||||
}
|
||||
|
||||
protected static INitroxFaker CreateFaker(Type type)
|
||||
{
|
||||
if (type.IsAbstract)
|
||||
{
|
||||
return new NitroxAbstractFaker(type);
|
||||
}
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
return new NitroxActionFaker(type, f =>
|
||||
{
|
||||
string[] selection = Enum.GetNames(type);
|
||||
if (selection.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("There are no enum values after exclusion to choose from.");
|
||||
}
|
||||
|
||||
string val = f.Random.ArrayElement(selection);
|
||||
return Enum.Parse(type, val);
|
||||
});
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>))
|
||||
{
|
||||
return new NitroxOptionalFaker(type);
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
return new NitroxNullableFaker(type);
|
||||
}
|
||||
|
||||
if (NitroxCollectionFaker.IsCollection(type, out NitroxCollectionFaker.CollectionType collectionType))
|
||||
{
|
||||
return new NitroxCollectionFaker(type, collectionType);
|
||||
}
|
||||
|
||||
ConstructorInfo constructor = typeof(NitroxAutoFaker<>).MakeGenericType(type).GetConstructor(Array.Empty<Type>());
|
||||
|
||||
if (constructor == null)
|
||||
{
|
||||
throw new NullReferenceException($"Could not get generic constructor for {type}");
|
||||
}
|
||||
|
||||
return (INitroxFaker)constructor.Invoke(Array.Empty<object>());
|
||||
}
|
||||
|
||||
protected static bool IsValidType(Type type)
|
||||
{
|
||||
return FakerByType.ContainsKey(type) ||
|
||||
type.GetCustomAttributes(typeof(DataContractAttribute), false).Length >= 1 ||
|
||||
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(nameof(Cast), BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
protected static T Cast<T>(object o)
|
||||
{
|
||||
return (T)o;
|
||||
}
|
||||
}
|
27
Nitrox.Test/Helper/Faker/NitroxNullableFaker.cs
Normal file
27
Nitrox.Test/Helper/Faker/NitroxNullableFaker.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxNullableFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private readonly Func<HashSet<Type>, object> generateAction;
|
||||
|
||||
public NitroxNullableFaker(Type type)
|
||||
{
|
||||
OutputType = type.GenericTypeArguments[0];
|
||||
generateAction = (typeTree) =>
|
||||
{
|
||||
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
|
||||
object castedObject = castMethod.Invoke(null, new[] { GetOrCreateFaker(OutputType).GenerateUnsafe(typeTree) });
|
||||
|
||||
Type nullableType = typeof(Nullable<>).MakeGenericType(OutputType);
|
||||
return Activator.CreateInstance(nullableType, castedObject);
|
||||
};
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => new[] { GetOrCreateFaker(OutputType) };
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree) => generateAction.Invoke(typeTree);
|
||||
}
|
28
Nitrox.Test/Helper/Faker/NitroxOptionalFaker.cs
Normal file
28
Nitrox.Test/Helper/Faker/NitroxOptionalFaker.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
|
||||
namespace Nitrox.Test.Helper.Faker;
|
||||
|
||||
public class NitroxOptionalFaker : NitroxFaker, INitroxFaker
|
||||
{
|
||||
private readonly Func<HashSet<Type>, object> generateAction;
|
||||
|
||||
public NitroxOptionalFaker(Type type)
|
||||
{
|
||||
OutputType = type.GenericTypeArguments[0];
|
||||
generateAction = (typeTree) =>
|
||||
{
|
||||
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
|
||||
object castedObject = castMethod.Invoke(null, new[] { GetOrCreateFaker(OutputType).GenerateUnsafe(typeTree) });
|
||||
|
||||
Type optionalType = typeof(Optional<>).MakeGenericType(OutputType);
|
||||
return Activator.CreateInstance(optionalType, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance, null, new[] { castedObject }, null);
|
||||
};
|
||||
}
|
||||
|
||||
public INitroxFaker[] GetSubFakers() => new[] { GetOrCreateFaker(OutputType) };
|
||||
|
||||
public object GenerateUnsafe(HashSet<Type> typeTree) => generateAction.Invoke(typeTree);
|
||||
}
|
22
Nitrox.Test/Helper/TypeExtension.cs
Normal file
22
Nitrox.Test/Helper/TypeExtension.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Nitrox.Test.Helper;
|
||||
|
||||
public static class TypeExtension
|
||||
{
|
||||
public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
|
||||
{
|
||||
if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Type givenBaseType = givenType.BaseType;
|
||||
if (givenBaseType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsAssignableToGenericType(givenBaseType, genericType);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user