413 lines
15 KiB
C#
413 lines
15 KiB
C#
/*
|
|
* Copyright (c) Contributors, http://opensimulator.org/
|
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the OpenSimulator Project nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
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 they are both null, they are equal
|
|
if (actual == null && expected == null)
|
|
return true;
|
|
|
|
//If only one is null, then they aren't
|
|
if (actual == null || expected == null)
|
|
{
|
|
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
|
failingActual = actual;
|
|
failingExpected = expected;
|
|
return false;
|
|
}
|
|
|
|
//prevent loops...
|
|
if (propertyNames.Count > 50)
|
|
{
|
|
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
|
failingActual = actual;
|
|
failingExpected = expected;
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
IComparable comp = actual as IComparable;
|
|
if (comp != null)
|
|
{
|
|
if (comp.CompareTo(expected) != 0)
|
|
{
|
|
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
|
failingActual = actual;
|
|
failingExpected = expected;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//Now try the much more annoying IComparable<T>
|
|
Type icomparableInterface = actual.GetType().GetInterface("IComparable`1");
|
|
if (icomparableInterface != null)
|
|
{
|
|
int result = (int)icomparableInterface.GetMethod("CompareTo").Invoke(actual, new[] { expected });
|
|
if (result != 0)
|
|
{
|
|
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
|
failingActual = actual;
|
|
failingExpected = expected;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
IEnumerable arr = actual as IEnumerable;
|
|
if (arr != null)
|
|
{
|
|
List<object> actualList = arr.Cast<object>().ToList();
|
|
List<object> expectedList = ((IEnumerable)expected).Cast<object>().ToList();
|
|
if (actualList.Count != expectedList.Count)
|
|
{
|
|
propertyNames.Push("Count");
|
|
failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
|
|
failingActual = actualList.Count;
|
|
failingExpected = expectedList.Count;
|
|
propertyNames.Pop();
|
|
return false;
|
|
}
|
|
//actualList and expectedList should be the same size.
|
|
for (int i = 0; i < actualList.Count; i++)
|
|
{
|
|
propertyNames.Push("[" + i + "]");
|
|
if (!ObjectCompare(expectedList[i], actualList[i], propertyNames))
|
|
return false;
|
|
propertyNames.Pop();
|
|
}
|
|
//Everything seems okay...
|
|
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);
|
|
|
|
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.
|
|
ignores.Add(((MemberExpression)express).Member.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
[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", (sbyte)AssetType.Texture);
|
|
AssetBase expected = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture);
|
|
|
|
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", (sbyte)AssetType.Texture);
|
|
AssetBase expected = new AssetBase(UUID.Random(), "asset one", (sbyte)AssetType.Texture);
|
|
|
|
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", (sbyte)AssetType.Texture);
|
|
AssetBase expected = new AssetBase(uuid1, "asset two", (sbyte)AssetType.Texture);
|
|
|
|
var constraint = Constraints.PropertyCompareConstraint(expected);
|
|
|
|
Assert.That(constraint.Matches(actual), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void UUIDShouldMatch()
|
|
{
|
|
UUID uuid1 = UUID.Random();
|
|
UUID uuid2 = UUID.Parse(uuid1.ToString());
|
|
|
|
var constraint = Constraints.PropertyCompareConstraint(uuid1);
|
|
|
|
Assert.That(constraint.Matches(uuid2), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void UUIDShouldNotMatch()
|
|
{
|
|
UUID uuid1 = UUID.Random();
|
|
UUID uuid2 = UUID.Random();
|
|
|
|
var constraint = Constraints.PropertyCompareConstraint(uuid1);
|
|
|
|
Assert.That(constraint.Matches(uuid2), 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);
|
|
}
|
|
|
|
[Test]
|
|
public void ShouldCompareLists()
|
|
{
|
|
List<int> expected = new List<int> { 1, 2, 3 };
|
|
List<int> actual = new List<int> { 1, 2, 3 };
|
|
|
|
var constraint = Constraints.PropertyCompareConstraint(expected);
|
|
Assert.That(constraint.Matches(actual), Is.True);
|
|
}
|
|
|
|
|
|
[Test]
|
|
public void ShouldFailToCompareListsThatAreDifferent()
|
|
{
|
|
List<int> expected = new List<int> { 1, 2, 3 };
|
|
List<int> actual = new List<int> { 1, 2, 4 };
|
|
|
|
var constraint = Constraints.PropertyCompareConstraint(expected);
|
|
Assert.That(constraint.Matches(actual), Is.False);
|
|
}
|
|
|
|
[Test]
|
|
public void ShouldFailToCompareListsThatAreDifferentLengths()
|
|
{
|
|
List<int> expected = new List<int> { 1, 2, 3 };
|
|
List<int> actual = new List<int> { 1, 2 };
|
|
|
|
var constraint = Constraints.PropertyCompareConstraint(expected);
|
|
Assert.That(constraint.Matches(actual), Is.False);
|
|
}
|
|
|
|
public class Recursive
|
|
{
|
|
public Recursive Other { get; set; }
|
|
}
|
|
|
|
[Test]
|
|
public void ErrorsOutOnRecursive()
|
|
{
|
|
Recursive parent = new Recursive();
|
|
Recursive child = new Recursive();
|
|
parent.Other = child;
|
|
child.Other = parent;
|
|
|
|
var constraint = Constraints.PropertyCompareConstraint(child);
|
|
Assert.That(constraint.Matches(child), Is.False);
|
|
}
|
|
}
|
|
} |