Adding in Reflection-based testing, to ensure that all properties are covered.
parent
a851b68333
commit
c18f7560d9
|
@ -0,0 +1,298 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Constraints;
|
||||
using NUnit.Framework.SyntaxHelpers;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
|
||||
namespace OpenSim.Data.Tests
|
||||
{
|
||||
public static class Constraints
|
||||
{
|
||||
//This is here because C# has a gap in the language, you can't infer type from a constructor
|
||||
public static PropertyCompareConstraint<T> PropertyCompareConstraint<T>(T expected)
|
||||
{
|
||||
return new PropertyCompareConstraint<T>(expected);
|
||||
}
|
||||
}
|
||||
|
||||
public class PropertyCompareConstraint<T> : NUnit.Framework.Constraints.Constraint
|
||||
{
|
||||
private readonly object _expected;
|
||||
//the reason everywhere uses propertyNames.Reverse().ToArray() is because the stack is backwards of the order we want to display the properties in.
|
||||
private string failingPropertyName = string.Empty;
|
||||
private object failingExpected;
|
||||
private object failingActual;
|
||||
|
||||
public PropertyCompareConstraint(T expected)
|
||||
{
|
||||
_expected = expected;
|
||||
}
|
||||
|
||||
public override bool Matches(object actual)
|
||||
{
|
||||
return ObjectCompare(_expected, actual, new Stack<string>());
|
||||
}
|
||||
|
||||
private bool ObjectCompare(object expected, object actual, Stack<string> propertyNames)
|
||||
{
|
||||
if (actual.GetType() != expected.GetType())
|
||||
{
|
||||
propertyNames.Push("GetType()");
|
||||
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
||||
propertyNames.Pop();
|
||||
failingActual = actual.GetType();
|
||||
failingExpected = expected.GetType();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(actual.GetType() == typeof(Color))
|
||||
{
|
||||
Color actualColor = (Color) actual;
|
||||
Color expectedColor = (Color) expected;
|
||||
if (actualColor.R != expectedColor.R)
|
||||
{
|
||||
propertyNames.Push("R");
|
||||
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
||||
propertyNames.Pop();
|
||||
failingActual = actualColor.R;
|
||||
failingExpected = expectedColor.R;
|
||||
return false;
|
||||
}
|
||||
if (actualColor.G != expectedColor.G)
|
||||
{
|
||||
propertyNames.Push("G");
|
||||
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
||||
propertyNames.Pop();
|
||||
failingActual = actualColor.G;
|
||||
failingExpected = expectedColor.G;
|
||||
return false;
|
||||
}
|
||||
if (actualColor.B != expectedColor.B)
|
||||
{
|
||||
propertyNames.Push("B");
|
||||
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
||||
propertyNames.Pop();
|
||||
failingActual = actualColor.B;
|
||||
failingExpected = expectedColor.B;
|
||||
return false;
|
||||
}
|
||||
if (actualColor.A != expectedColor.A)
|
||||
{
|
||||
propertyNames.Push("A");
|
||||
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
||||
propertyNames.Pop();
|
||||
failingActual = actualColor.A;
|
||||
failingExpected = expectedColor.A;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Skip static properties. I had a nasty problem comparing colors because of all of the public static colors.
|
||||
PropertyInfo[] properties = expected.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (ignores.Contains(property.Name))
|
||||
continue;
|
||||
|
||||
object actualValue = property.GetValue(actual, null);
|
||||
object expectedValue = property.GetValue(expected, null);
|
||||
|
||||
//If they are both null, they are equal
|
||||
if (actualValue == null && expectedValue == null)
|
||||
continue;
|
||||
|
||||
//If only one is null, then they aren't
|
||||
if (actualValue == null || expectedValue == null)
|
||||
{
|
||||
propertyNames.Push(property.Name);
|
||||
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
||||
propertyNames.Pop();
|
||||
failingActual = actualValue;
|
||||
failingExpected = expectedValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
IComparable comp = actualValue as IComparable;
|
||||
if (comp != null)
|
||||
{
|
||||
if (comp.CompareTo(expectedValue) != 0)
|
||||
{
|
||||
propertyNames.Push(property.Name);
|
||||
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
||||
propertyNames.Pop();
|
||||
failingActual = actualValue;
|
||||
failingExpected = expectedValue;
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
IEnumerable arr = actualValue as IEnumerable;
|
||||
if (arr != null)
|
||||
{
|
||||
List<object> actualList = arr.Cast<object>().ToList();
|
||||
List<object> expectedList = ((IEnumerable)expectedValue).Cast<object>().ToList();
|
||||
if (actualList.Count != expectedList.Count)
|
||||
{
|
||||
propertyNames.Push(property.Name);
|
||||
propertyNames.Push("Count");
|
||||
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
||||
failingActual = actualList.Count;
|
||||
failingExpected = expectedList.Count;
|
||||
propertyNames.Pop();
|
||||
propertyNames.Pop();
|
||||
}
|
||||
//Todo: A value-wise comparison of all of the values.
|
||||
//Everything seems okay...
|
||||
continue;
|
||||
}
|
||||
|
||||
propertyNames.Push(property.Name);
|
||||
if (!ObjectCompare(expectedValue, actualValue, propertyNames))
|
||||
return false;
|
||||
propertyNames.Pop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void WriteDescriptionTo(MessageWriter writer)
|
||||
{
|
||||
writer.WriteExpectedValue(failingExpected);
|
||||
}
|
||||
|
||||
public override void WriteActualValueTo(MessageWriter writer)
|
||||
{
|
||||
writer.WriteActualValue(failingActual);
|
||||
writer.WriteLine();
|
||||
writer.Write(" On Property: " + failingPropertyName);
|
||||
}
|
||||
|
||||
//These notes assume the lambda: (x=>x.Parent.Value)
|
||||
//ignores should really contain like a fully dotted version of the property name, but I'm starting with small steps
|
||||
readonly List<string> ignores = new List<string>();
|
||||
public PropertyCompareConstraint<T> IgnoreProperty(Expression<Func<T, object>> func)
|
||||
{
|
||||
Expression express = func.Body;
|
||||
PullApartExpression(express);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void PullApartExpression(Expression express)
|
||||
{
|
||||
//This deals with any casts... like implicit casts to object. Not all UnaryExpression are casts, but this is a first attempt.
|
||||
if (express is UnaryExpression)
|
||||
PullApartExpression(((UnaryExpression)express).Operand);
|
||||
if (express is MemberExpression)
|
||||
{
|
||||
//If the inside of the lambda is the access to x, we've hit the end of the chain.
|
||||
// We should track by the fully scoped parameter name, but this is the first rev of doing this.
|
||||
if (((MemberExpression)express).Expression is ParameterExpression)
|
||||
{
|
||||
ignores.Add(((MemberExpression)express).Member.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Otherwise there could be more parameters inside...
|
||||
PullApartExpression(((MemberExpression)express).Expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class PropertyCompareConstraintTest
|
||||
{
|
||||
public class HasInt
|
||||
{
|
||||
public int TheValue { get; set; }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IntShouldMatch()
|
||||
{
|
||||
HasInt actual = new HasInt { TheValue = 5 };
|
||||
HasInt expected = new HasInt { TheValue = 5 };
|
||||
var constraint = Constraints.PropertyCompareConstraint(expected);
|
||||
|
||||
Assert.That(constraint.Matches(actual), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IntShouldNotMatch()
|
||||
{
|
||||
HasInt actual = new HasInt { TheValue = 5 };
|
||||
HasInt expected = new HasInt { TheValue = 4 };
|
||||
var constraint = Constraints.PropertyCompareConstraint(expected);
|
||||
|
||||
Assert.That(constraint.Matches(actual), Is.False);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void IntShouldIgnore()
|
||||
{
|
||||
HasInt actual = new HasInt { TheValue = 5 };
|
||||
HasInt expected = new HasInt { TheValue = 4 };
|
||||
var constraint = Constraints.PropertyCompareConstraint(expected).IgnoreProperty(x=>x.TheValue);
|
||||
|
||||
Assert.That(constraint.Matches(actual), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetShouldMatch()
|
||||
{
|
||||
UUID uuid1 = UUID.Random();
|
||||
AssetBase actual = new AssetBase(uuid1, "asset one");
|
||||
AssetBase expected = new AssetBase(uuid1, "asset one");
|
||||
|
||||
var constraint = Constraints.PropertyCompareConstraint(expected);
|
||||
|
||||
Assert.That(constraint.Matches(actual), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetShouldNotMatch()
|
||||
{
|
||||
UUID uuid1 = UUID.Random();
|
||||
AssetBase actual = new AssetBase(uuid1, "asset one");
|
||||
AssetBase expected = new AssetBase(UUID.Random(), "asset one");
|
||||
|
||||
var constraint = Constraints.PropertyCompareConstraint(expected);
|
||||
|
||||
Assert.That(constraint.Matches(actual), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AssetShouldNotMatch2()
|
||||
{
|
||||
UUID uuid1 = UUID.Random();
|
||||
AssetBase actual = new AssetBase(uuid1, "asset one");
|
||||
AssetBase expected = new AssetBase(uuid1, "asset two");
|
||||
|
||||
var constraint = Constraints.PropertyCompareConstraint(expected);
|
||||
|
||||
Assert.That(constraint.Matches(actual), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestColors()
|
||||
{
|
||||
Color actual = Color.Red;
|
||||
Color expected = Color.FromArgb(actual.A, actual.R, actual.G, actual.B);
|
||||
|
||||
var constraint = Constraints.PropertyCompareConstraint(expected);
|
||||
|
||||
Assert.That(constraint.Matches(actual), Is.True);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using OpenMetaverse;
|
||||
using OpenSim.Framework;
|
||||
|
||||
namespace OpenSim.Data.Tests
|
||||
{
|
||||
public static class ScrambleForTesting
|
||||
{
|
||||
private static readonly Random random = new Random();
|
||||
public static void Scramble(object obj)
|
||||
{
|
||||
PropertyInfo[] properties = obj.GetType().GetProperties();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
//Skip indexers of classes. We will assume that everything that has an indexer
|
||||
// is also IEnumberable. May not always be true, but should be true normally.
|
||||
if(property.GetIndexParameters().Length > 0)
|
||||
continue;
|
||||
|
||||
RandomizeProperty(obj, property, null);
|
||||
}
|
||||
//Now if it implments IEnumberable, it's probably some kind of list, so we should randomize
|
||||
// everything inside of it.
|
||||
IEnumerable enumerable = obj as IEnumerable;
|
||||
if(enumerable != null)
|
||||
{
|
||||
foreach (object value in enumerable)
|
||||
{
|
||||
Scramble(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RandomizeProperty(object obj, PropertyInfo property, object[] index)
|
||||
{
|
||||
Type t = property.PropertyType;
|
||||
if (!property.CanWrite)
|
||||
return;
|
||||
object value = property.GetValue(obj, index);
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
if (t == typeof (string))
|
||||
property.SetValue(obj, RandomName(), index);
|
||||
else if (t == typeof (UUID))
|
||||
property.SetValue(obj, UUID.Random(), index);
|
||||
else if (t == typeof (sbyte))
|
||||
property.SetValue(obj, (sbyte)random.Next(sbyte.MinValue, sbyte.MaxValue), index);
|
||||
else if (t == typeof (short))
|
||||
property.SetValue(obj, (short)random.Next(short.MinValue, short.MaxValue), index);
|
||||
else if (t == typeof (int))
|
||||
property.SetValue(obj, random.Next(), index);
|
||||
else if (t == typeof (long))
|
||||
property.SetValue(obj, random.Next() * int.MaxValue, index);
|
||||
else if (t == typeof (byte))
|
||||
property.SetValue(obj, (byte)random.Next(byte.MinValue, byte.MaxValue), index);
|
||||
else if (t == typeof (ushort))
|
||||
property.SetValue(obj, (ushort)random.Next(ushort.MinValue, ushort.MaxValue), index);
|
||||
else if (t == typeof (uint))
|
||||
property.SetValue(obj, Convert.ToUInt32(random.Next()), index);
|
||||
else if (t == typeof (ulong))
|
||||
property.SetValue(obj, Convert.ToUInt64(random.Next()) * Convert.ToUInt64(UInt32.MaxValue), index);
|
||||
else if (t == typeof (bool))
|
||||
property.SetValue(obj, true, index);
|
||||
else if (t == typeof (byte[]))
|
||||
{
|
||||
byte[] bytes = new byte[30];
|
||||
random.NextBytes(bytes);
|
||||
property.SetValue(obj, bytes, index);
|
||||
}
|
||||
else
|
||||
Scramble(value);
|
||||
}
|
||||
|
||||
private static string RandomName()
|
||||
{
|
||||
StringBuilder name = new StringBuilder();
|
||||
int size = random.Next(5, 12);
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
char ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
|
||||
name.Append(ch);
|
||||
}
|
||||
return name.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class ScrableForTestingTest
|
||||
{
|
||||
[Test]
|
||||
public void TestScramble()
|
||||
{
|
||||
AssetBase actual = new AssetBase(UUID.Random(), "asset one");
|
||||
ScrambleForTesting.Scramble(actual);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue