/* * 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, UUID.Zero.ToString()); AssetBase expected = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture, UUID.Zero.ToString()); 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, UUID.Zero.ToString()); AssetBase expected = new AssetBase(UUID.Random(), "asset one", (sbyte)AssetType.Texture, UUID.Zero.ToString()); 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, UUID.Zero.ToString()); AssetBase expected = new AssetBase(uuid1, "asset two", (sbyte)AssetType.Texture, UUID.Zero.ToString()); 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); } } }