/* * 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 PropertyCompareConstraint(T expected) { return new PropertyCompareConstraint(expected); } } public class PropertyCompareConstraint : 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()); } private bool ObjectCompare(object expected, object actual, Stack 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 actualList = arr.Cast().ToList(); List expectedList = ((IEnumerable)expectedValue).Cast().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 ignores = new List(); public PropertyCompareConstraint IgnoreProperty(Expression> 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); } } }