You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
473 lines
18 KiB
473 lines
18 KiB
using NUnit.Framework;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
using UnityObject = UnityEngine.Object;
|
|
|
|
namespace UnityEngine.Rendering.HighDefinition.Tests
|
|
{
|
|
class CopyToTests
|
|
{
|
|
class DummyMonoBehaviour : MonoBehaviour { }
|
|
|
|
GameObject m_Root;
|
|
List<UnityObject> m_ToClean;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
m_Root = new GameObject("TEST");
|
|
m_Root.SetActive(false);
|
|
m_ToClean = new List<UnityObject> { m_Root };
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
if (m_ToClean != null)
|
|
{
|
|
foreach (var obj in m_ToClean)
|
|
CoreUtils.Destroy(obj);
|
|
m_ToClean = null;
|
|
}
|
|
}
|
|
|
|
class TestTypeCase : TestCaseData
|
|
{
|
|
public TestTypeCase(Type type) : base(type)
|
|
=> SetName($"{type.Name}.CopyTo");
|
|
}
|
|
|
|
//Resources for test
|
|
static TestCaseData[] s_TypeDatas =
|
|
{
|
|
// test that this automatic test works as expected
|
|
new TestTypeCase(typeof(VisibilitySelfTest)),
|
|
new TestTypeCase(typeof(FilterSelfTest)),
|
|
new TestTypeCase(typeof(TypeSelfTest)),
|
|
|
|
// test object we want to test the CopyTo
|
|
new TestTypeCase(typeof(ScalableSettingValue<int>)),
|
|
new TestTypeCase(typeof(InfluenceVolume)),
|
|
new TestTypeCase(typeof(HDAdditionalCameraData)),
|
|
new TestTypeCase(typeof(HDAdditionalLightData)),
|
|
};
|
|
|
|
//This test will compute the type given a combination of LightType and HDAdditionalLightdata.
|
|
//It will set the two types on a Light and HDAdditionalLightData components before attemting to compute the type with the two public API accessors.
|
|
[Test, TestCaseSource(nameof(s_TypeDatas))]
|
|
public void TestType(Type type)
|
|
{
|
|
MethodInfo copyToMethodInfo = type.GetMethod("CopyTo", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
if (copyToMethodInfo == null)
|
|
Assert.Fail($"{type.Name} don't have CopyTo method.");
|
|
|
|
object originalObject = GenerateObject(type);
|
|
object modifiedObject = GenerateObjectWithMutation(type);
|
|
|
|
copyToMethodInfo.Invoke(originalObject, new[] { modifiedObject });
|
|
|
|
AssertAllFieldMatches(originalObject, modifiedObject);
|
|
}
|
|
|
|
#region Creation and Mutation
|
|
object GenerateObject(Type type)
|
|
{
|
|
if (typeof(Component).IsAssignableFrom(type))
|
|
{
|
|
if (type == typeof(Transform))
|
|
{
|
|
//cannot create Transform
|
|
GameObject go = new GameObject("DummyForTransform");
|
|
go.SetActive(false);
|
|
m_ToClean.Add(go);
|
|
return go.transform;
|
|
}
|
|
else if (type.GetCustomAttributes(typeof(DisallowMultipleComponent), inherit: true).Length > 0
|
|
|| type == typeof(Camera)
|
|
|| type == typeof(Light))
|
|
{
|
|
//cannot add multiple instance
|
|
GameObject go = new GameObject("DummyForUnique");
|
|
go.SetActive(false);
|
|
m_ToClean.Add(go);
|
|
return go.AddComponent(type);
|
|
}
|
|
else if (type == typeof(Component)
|
|
|| type == typeof(Behaviour)
|
|
|| type == typeof(MonoBehaviour))
|
|
//fallback on DummyMonoBehaviour for case that cannot be created directly
|
|
type = typeof(DummyMonoBehaviour);
|
|
return m_Root.AddComponent(type);
|
|
}
|
|
else
|
|
return Activator.CreateInstance(type); //currently only default constructor are handled
|
|
}
|
|
|
|
object GenerateObjectWithMutation(Type type)
|
|
{
|
|
object createdObject = GenerateObject(type);
|
|
|
|
type = createdObject.GetType();
|
|
foreach (FieldInfo fieldInfo in GetAllField(type))
|
|
ChangeValue(createdObject, fieldInfo);
|
|
|
|
return createdObject;
|
|
}
|
|
|
|
IEnumerable<FieldInfo> GetAllField(Type type)
|
|
=> type
|
|
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
.Where(fieldInfo =>
|
|
{
|
|
// remove const and readonly
|
|
if (fieldInfo.IsLiteral)
|
|
return false;
|
|
|
|
// remove events, delegate, Action, Func (all functors)
|
|
if (typeof(Delegate).IsAssignableFrom(fieldInfo.FieldType))
|
|
return false;
|
|
|
|
// remove explicitely excluded field with attribute [CopyFilterAttribute(CopyFilterAttribute.Filter.Exclude)]
|
|
foreach (Attribute attribute in fieldInfo.GetCustomAttributes(typeof(CopyFilterAttribute)))
|
|
if ((attribute as CopyFilterAttribute).filter == CopyFilterAttribute.Filter.Exclude)
|
|
return false;
|
|
|
|
return true;
|
|
});
|
|
|
|
void ChangeValue(object instance, FieldInfo fieldInfo)
|
|
{
|
|
object initialValue = fieldInfo.GetValue(instance);
|
|
|
|
if (fieldInfo.FieldType == typeof(string))
|
|
// string: construct and replace by new string by ammending something
|
|
fieldInfo.SetValue(instance, String.IsNullOrEmpty(initialValue as string) ? "str" : ((string)initialValue) + "_1");
|
|
else if (fieldInfo.FieldType == typeof(Texture)
|
|
|| fieldInfo.FieldType == typeof(Texture2D))
|
|
{
|
|
// Texture or Texture2D: change refered object. Need special constructor for those
|
|
Texture generated = new Texture2D(1, 1);
|
|
m_ToClean.Add(generated);
|
|
fieldInfo.SetValue(instance, generated);
|
|
}
|
|
else if (typeof(Texture).IsAssignableFrom(fieldInfo.FieldType))
|
|
Assert.Fail($"Unsuported Type {fieldInfo.FieldType}. Add it in CopyToTests.ChangeValue");
|
|
else if (fieldInfo.FieldType.IsArray)
|
|
// Array: create and use array +1 in size and default values
|
|
fieldInfo.SetValue(instance, Activator.CreateInstance(fieldInfo.FieldType, new object[] { ((instance as Array)?.Length ?? 0) + 1 }));
|
|
else if (fieldInfo.FieldType.IsClass)
|
|
// MonoBehaviour or System.Object: change refered object
|
|
fieldInfo.SetValue(instance, GenerateObject(fieldInfo.FieldType));
|
|
else if (fieldInfo.FieldType == typeof(bool))
|
|
// boolean
|
|
fieldInfo.SetValue(instance, !(bool)initialValue);
|
|
else if (fieldInfo.FieldType == typeof(char)
|
|
|| fieldInfo.FieldType == typeof(byte)
|
|
|| fieldInfo.FieldType == typeof(uint)
|
|
|| fieldInfo.FieldType == typeof(ulong))
|
|
// unsigned numerals
|
|
fieldInfo.SetValue(instance, Convert.ChangeType(((ulong)Convert.ChangeType(initialValue, typeof(ulong)) > 0) ? 0 : 1, fieldInfo.FieldType));
|
|
else if (fieldInfo.FieldType == typeof(int)
|
|
|| fieldInfo.FieldType == typeof(long))
|
|
// signed numerals
|
|
fieldInfo.SetValue(instance, Convert.ChangeType(((long)Convert.ChangeType(initialValue, typeof(long)) > 0) ? 0 : 1, fieldInfo.FieldType));
|
|
else if (fieldInfo.FieldType.IsEnum)
|
|
{
|
|
// enum
|
|
Type underlyingType = Enum.GetUnderlyingType(fieldInfo.FieldType);
|
|
long castedInitValue = (long)Convert.ChangeType(Convert.ChangeType(initialValue, underlyingType), typeof(long));
|
|
object changedValue = Convert.ChangeType(castedInitValue > 0 ? 0 : 1, underlyingType);
|
|
fieldInfo.SetValue(instance, changedValue);
|
|
}
|
|
else if (fieldInfo.FieldType == typeof(float)
|
|
|| fieldInfo.FieldType == typeof(double))
|
|
// floating points
|
|
fieldInfo.SetValue(instance, Convert.ChangeType(((double)Convert.ChangeType(initialValue, typeof(double)) > .5f) ? 0f : 1f, fieldInfo.FieldType));
|
|
else if (fieldInfo.FieldType.IsValueType)
|
|
{
|
|
// struct: recurse on inner field
|
|
object initialStruct = fieldInfo.GetValue(instance);
|
|
foreach (FieldInfo fi in GetAllField(fieldInfo.FieldType))
|
|
ChangeValue(initialStruct, fi);
|
|
fieldInfo.SetValue(instance, initialStruct);
|
|
}
|
|
else
|
|
Assert.Fail($"Unsuported Type {fieldInfo.FieldType}. Add it in CopyToTests.ChangeValue");
|
|
|
|
if (fieldInfo.FieldType.IsArray)
|
|
{
|
|
IList arrayA = initialValue as IList;
|
|
IList arrayB = fieldInfo.GetValue(instance) as IList;
|
|
|
|
if (arrayA == null ^ arrayB == null)
|
|
return;
|
|
|
|
if (arrayA != null) // case where both non null
|
|
{
|
|
if (arrayA.Count == arrayB.Count)
|
|
{
|
|
for (int i = 0; i < arrayA.Count; ++i)
|
|
if (!arrayA[i].Equals(arrayB[i]))
|
|
return;
|
|
|
|
Assert.Fail($"Value of {fieldInfo.Name} cannot have been modified.");
|
|
}
|
|
}
|
|
else //both are null
|
|
Assert.Fail($"Value of {fieldInfo.Name} cannot have been modified.");
|
|
}
|
|
else
|
|
Assert.AreNotEqual(initialValue, fieldInfo.GetValue(instance), $"Value of {fieldInfo.Name} cannot have been modified.");
|
|
}
|
|
|
|
void AssertAllFieldMatches(object a, object b)
|
|
{
|
|
Assert.AreEqual(a.GetType(), b.GetType(), "Type mismatch");
|
|
|
|
foreach (FieldInfo fieldInfo in GetAllField(a.GetType()))
|
|
{
|
|
if (fieldInfo.GetCustomAttribute(typeof(ObsoleteAttribute), false) != null)
|
|
{
|
|
// This field is marked as obsolete. Don't check it.
|
|
continue;
|
|
}
|
|
AssertOneFieldMatch(a, b, fieldInfo);
|
|
}
|
|
}
|
|
|
|
void AssertOneFieldMatch(object a, object b, FieldInfo fieldInfo)
|
|
{
|
|
bool CheckContentCopy = fieldInfo
|
|
.GetCustomAttributes(typeof(CopyFilterAttribute))
|
|
.Any(attribute => (attribute as CopyFilterAttribute).filter == CopyFilterAttribute.Filter.CheckContent);
|
|
|
|
if (CheckContentCopy)
|
|
{
|
|
if (fieldInfo.FieldType.IsArray) //always check array as if they are in CheckContent
|
|
{
|
|
IList arrayA = fieldInfo.GetValue(a) as IList;
|
|
IList arrayB = fieldInfo.GetValue(b) as IList;
|
|
|
|
if (arrayA == null ^ arrayB == null)
|
|
Assert.Fail($"Only one of the array is null in {fieldInfo.Name}.");
|
|
|
|
if (arrayA != null) // case where both non null
|
|
{
|
|
Assert.AreEqual(arrayA.Count, arrayB.Count, $"Arrays in {fieldInfo.Name} have different size.");
|
|
|
|
for (int i = 0; i < arrayA.Count; ++i)
|
|
Assert.AreEqual(arrayA[i], arrayB[i], $"Value of {fieldInfo.Name}[{i}] is different.");
|
|
}
|
|
}
|
|
else
|
|
AssertAllFieldMatches(fieldInfo.GetValue(a), fieldInfo.GetValue(b));
|
|
}
|
|
else
|
|
Assert.AreEqual(fieldInfo.GetValue(a), fieldInfo.GetValue(b), $"Value of {fieldInfo.Name} is different.");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Self Testing
|
|
class TypeSelfTest
|
|
{
|
|
enum CustomEnum { _ }
|
|
|
|
struct InnerInnerStruct
|
|
{
|
|
int m_Int;
|
|
double m_Double;
|
|
}
|
|
|
|
struct InnerStruct
|
|
{
|
|
int m_Int;
|
|
double m_Double;
|
|
InnerInnerStruct m_InnerInnerStruct;
|
|
}
|
|
|
|
struct Struct
|
|
{
|
|
int m_Int;
|
|
double m_Double;
|
|
InnerStruct m_InnerStruct;
|
|
}
|
|
|
|
class Class
|
|
{
|
|
int m_Int;
|
|
double m_Double;
|
|
|
|
public void CopyTo(Class other)
|
|
{
|
|
other.m_Int = m_Int;
|
|
other.m_Double = m_Double;
|
|
}
|
|
}
|
|
|
|
class CustomMonoBehaviour : MonoBehaviour
|
|
{
|
|
int m_Int;
|
|
double m_Double;
|
|
}
|
|
|
|
string m_String;
|
|
Texture m_Texture;
|
|
Texture2D m_Texture2D;
|
|
Component m_Component; //interesting as we cannot instanciate it directly
|
|
Transform m_Transform; //interesting as we only can have one per GameObject
|
|
BoxCollider m_Collider; //other Component
|
|
Behaviour m_Behaviour; //interesting as we cannot instanciate it directly
|
|
MonoBehaviour m_MonoBehaviour; //interesting as we cannot instanciate it directly
|
|
CustomMonoBehaviour m_CustomMonoBehaviour;
|
|
Class m_ClassRef;
|
|
[ValueCopy]
|
|
Class m_Class = new Class();
|
|
bool m_Bool;
|
|
int m_Int;
|
|
uint m_UInt;
|
|
byte m_Byte;
|
|
long m_Long;
|
|
ulong m_ULong;
|
|
char m_Char;
|
|
CustomEnum m_CustomEnum;
|
|
float m_Float;
|
|
double m_Double;
|
|
Struct m_Struct;
|
|
|
|
int[] m_IntRef = new int[0];
|
|
[ValueCopy]
|
|
int[] m_Ints = new int[0];
|
|
[ValueCopy]
|
|
Struct[] m_Structs = new Struct[0];
|
|
[ValueCopy]
|
|
Texture[] m_Textures = new Texture[0];
|
|
[ValueCopy]
|
|
Component[] m_Components = new Component[0];
|
|
[ValueCopy]
|
|
Transform[] m_Transforms = new Transform[0];
|
|
[ValueCopy]
|
|
CustomMonoBehaviour[] m_CustomMonoBehaviours = new CustomMonoBehaviour[0];
|
|
[ValueCopy]
|
|
Class[] m_Classes = new Class[0];
|
|
|
|
void CopyTo(TypeSelfTest other)
|
|
{
|
|
other.m_String = m_String;
|
|
other.m_Texture = m_Texture;
|
|
other.m_Texture2D = m_Texture2D;
|
|
other.m_Component = m_Component;
|
|
other.m_Transform = m_Transform;
|
|
other.m_Collider = m_Collider;
|
|
other.m_Behaviour = m_Behaviour;
|
|
other.m_MonoBehaviour = m_MonoBehaviour;
|
|
other.m_CustomMonoBehaviour = m_CustomMonoBehaviour;
|
|
other.m_ClassRef = m_ClassRef;
|
|
other.m_Class = new Class();
|
|
m_Class.CopyTo(other.m_Class);
|
|
other.m_Bool = m_Bool;
|
|
other.m_Int = m_Int;
|
|
other.m_UInt = m_UInt;
|
|
other.m_Byte = m_Byte;
|
|
other.m_Long = m_Long;
|
|
other.m_ULong = m_ULong;
|
|
other.m_Char = m_Char;
|
|
other.m_CustomEnum = m_CustomEnum;
|
|
other.m_Float = m_Float;
|
|
other.m_Double = m_Double;
|
|
other.m_Struct = m_Struct;
|
|
other.m_IntRef = m_IntRef;
|
|
other.m_Ints = new int[m_Ints.Length];
|
|
m_Ints.CopyTo(other.m_Ints, 0);
|
|
other.m_Structs = new Struct[m_Structs.Length];
|
|
m_Structs.CopyTo(other.m_Structs, 0);
|
|
other.m_Textures = new Texture[m_Textures.Length];
|
|
m_Textures.CopyTo(other.m_Textures, 0);
|
|
other.m_Components = new Component[m_Components.Length];
|
|
m_Components.CopyTo(other.m_Components, 0);
|
|
other.m_Transforms = new Transform[m_Transforms.Length];
|
|
m_Transforms.CopyTo(other.m_Transforms, 0);
|
|
other.m_CustomMonoBehaviours = new CustomMonoBehaviour[m_CustomMonoBehaviours.Length];
|
|
m_CustomMonoBehaviours.CopyTo(other.m_CustomMonoBehaviours, 0);
|
|
other.m_Classes = new Class[m_Classes.Length];
|
|
m_Classes.CopyTo(other.m_Classes, 0);
|
|
}
|
|
}
|
|
|
|
class FilterSelfTest
|
|
{
|
|
int m_FieldIncluded;
|
|
|
|
#pragma warning disable 0067 //never used
|
|
[ExcludeCopy]
|
|
int m_FieldExcluded;
|
|
#pragma warning restore 0067
|
|
|
|
int m_PropertyIncluded { get; set; }
|
|
|
|
#pragma warning disable 0067 //never used
|
|
[field: ExcludeCopy]
|
|
int m_PropertyExcluded { get; set; }
|
|
|
|
event Action m_Event1;
|
|
event Func<int> m_Event2;
|
|
|
|
delegate void MyDelegate(int i, string s);
|
|
event MyDelegate m_Event3;
|
|
|
|
Action m_Action;
|
|
Func<int> m_Func;
|
|
MyDelegate m_Delegate;
|
|
|
|
|
|
[ExcludeCopy]
|
|
int m_CustomBackingField;
|
|
#pragma warning restore 0067
|
|
|
|
int m_PropertyWithoutBackingField
|
|
{
|
|
get => m_CustomBackingField;
|
|
set => m_CustomBackingField = value;
|
|
}
|
|
|
|
void CopyTo(FilterSelfTest other)
|
|
{
|
|
other.m_FieldIncluded = m_FieldIncluded;
|
|
other.m_PropertyIncluded = m_PropertyIncluded;
|
|
}
|
|
}
|
|
|
|
class VisibilitySelfBaseTest
|
|
{
|
|
private int m_BasePrivate;
|
|
protected int m_BaseProtected;
|
|
internal int m_BaseInternal;
|
|
public int m_BasePublic;
|
|
}
|
|
|
|
class VisibilitySelfTest : VisibilitySelfBaseTest
|
|
{
|
|
private int m_ChildPrivate;
|
|
protected int m_ChildProtected;
|
|
internal int m_ChildInternal;
|
|
public int m_ChildPublic;
|
|
|
|
void CopyTo(VisibilitySelfTest other)
|
|
{
|
|
other.m_BaseProtected = m_BaseProtected;
|
|
other.m_BaseInternal = m_BaseInternal;
|
|
other.m_BasePublic = m_BasePublic;
|
|
|
|
other.m_ChildPrivate = m_ChildPrivate;
|
|
other.m_ChildProtected = m_ChildProtected;
|
|
other.m_ChildInternal = m_ChildInternal;
|
|
other.m_ChildPublic = m_ChildPublic;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|