PropertyCompareConstraint.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. using System.Drawing;
  31. using System.Linq;
  32. using System.Linq.Expressions;
  33. using System.Reflection;
  34. using NUnit.Framework;
  35. using NUnit.Framework.Constraints;
  36. using NUnit.Framework.SyntaxHelpers;
  37. using OpenMetaverse;
  38. using OpenSim.Framework;
  39. namespace OpenSim.Data.Tests
  40. {
  41. public static class Constraints
  42. {
  43. //This is here because C# has a gap in the language, you can't infer type from a constructor
  44. public static PropertyCompareConstraint<T> PropertyCompareConstraint<T>(T expected)
  45. {
  46. return new PropertyCompareConstraint<T>(expected);
  47. }
  48. }
  49. public class PropertyCompareConstraint<T> : NUnit.Framework.Constraints.Constraint
  50. {
  51. private readonly object _expected;
  52. //the reason everywhere uses propertyNames.Reverse().ToArray() is because the stack is backwards of the order we want to display the properties in.
  53. private string failingPropertyName = string.Empty;
  54. private object failingExpected;
  55. private object failingActual;
  56. public PropertyCompareConstraint(T expected)
  57. {
  58. _expected = expected;
  59. }
  60. public override bool Matches(object actual)
  61. {
  62. return ObjectCompare(_expected, actual, new Stack<string>());
  63. }
  64. private bool ObjectCompare(object expected, object actual, Stack<string> propertyNames)
  65. {
  66. //If they are both null, they are equal
  67. if (actual == null && expected == null)
  68. return true;
  69. //If only one is null, then they aren't
  70. if (actual == null || expected == null)
  71. {
  72. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  73. failingActual = actual;
  74. failingExpected = expected;
  75. return false;
  76. }
  77. //prevent loops...
  78. if (propertyNames.Count > 50)
  79. {
  80. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  81. failingActual = actual;
  82. failingExpected = expected;
  83. return false;
  84. }
  85. if (actual.GetType() != expected.GetType())
  86. {
  87. propertyNames.Push("GetType()");
  88. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  89. propertyNames.Pop();
  90. failingActual = actual.GetType();
  91. failingExpected = expected.GetType();
  92. return false;
  93. }
  94. if (actual.GetType() == typeof(Color))
  95. {
  96. Color actualColor = (Color) actual;
  97. Color expectedColor = (Color) expected;
  98. if (actualColor.R != expectedColor.R)
  99. {
  100. propertyNames.Push("R");
  101. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  102. propertyNames.Pop();
  103. failingActual = actualColor.R;
  104. failingExpected = expectedColor.R;
  105. return false;
  106. }
  107. if (actualColor.G != expectedColor.G)
  108. {
  109. propertyNames.Push("G");
  110. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  111. propertyNames.Pop();
  112. failingActual = actualColor.G;
  113. failingExpected = expectedColor.G;
  114. return false;
  115. }
  116. if (actualColor.B != expectedColor.B)
  117. {
  118. propertyNames.Push("B");
  119. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  120. propertyNames.Pop();
  121. failingActual = actualColor.B;
  122. failingExpected = expectedColor.B;
  123. return false;
  124. }
  125. if (actualColor.A != expectedColor.A)
  126. {
  127. propertyNames.Push("A");
  128. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  129. propertyNames.Pop();
  130. failingActual = actualColor.A;
  131. failingExpected = expectedColor.A;
  132. return false;
  133. }
  134. return true;
  135. }
  136. IComparable comp = actual as IComparable;
  137. if (comp != null)
  138. {
  139. if (comp.CompareTo(expected) != 0)
  140. {
  141. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  142. failingActual = actual;
  143. failingExpected = expected;
  144. return false;
  145. }
  146. return true;
  147. }
  148. //Now try the much more annoying IComparable<T>
  149. Type icomparableInterface = actual.GetType().GetInterface("IComparable`1");
  150. if (icomparableInterface != null)
  151. {
  152. int result = (int)icomparableInterface.GetMethod("CompareTo").Invoke(actual, new[] { expected });
  153. if (result != 0)
  154. {
  155. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  156. failingActual = actual;
  157. failingExpected = expected;
  158. return false;
  159. }
  160. return true;
  161. }
  162. IEnumerable arr = actual as IEnumerable;
  163. if (arr != null)
  164. {
  165. List<object> actualList = arr.Cast<object>().ToList();
  166. List<object> expectedList = ((IEnumerable)expected).Cast<object>().ToList();
  167. if (actualList.Count != expectedList.Count)
  168. {
  169. propertyNames.Push("Count");
  170. failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray());
  171. failingActual = actualList.Count;
  172. failingExpected = expectedList.Count;
  173. propertyNames.Pop();
  174. return false;
  175. }
  176. //actualList and expectedList should be the same size.
  177. for (int i = 0; i < actualList.Count; i++)
  178. {
  179. propertyNames.Push("[" + i + "]");
  180. if (!ObjectCompare(expectedList[i], actualList[i], propertyNames))
  181. return false;
  182. propertyNames.Pop();
  183. }
  184. //Everything seems okay...
  185. return true;
  186. }
  187. //Skip static properties. I had a nasty problem comparing colors because of all of the public static colors.
  188. PropertyInfo[] properties = expected.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
  189. foreach (var property in properties)
  190. {
  191. if (ignores.Contains(property.Name))
  192. continue;
  193. object actualValue = property.GetValue(actual, null);
  194. object expectedValue = property.GetValue(expected, null);
  195. propertyNames.Push(property.Name);
  196. if (!ObjectCompare(expectedValue, actualValue, propertyNames))
  197. return false;
  198. propertyNames.Pop();
  199. }
  200. return true;
  201. }
  202. public override void WriteDescriptionTo(MessageWriter writer)
  203. {
  204. writer.WriteExpectedValue(failingExpected);
  205. }
  206. public override void WriteActualValueTo(MessageWriter writer)
  207. {
  208. writer.WriteActualValue(failingActual);
  209. writer.WriteLine();
  210. writer.Write(" On Property: " + failingPropertyName);
  211. }
  212. //These notes assume the lambda: (x=>x.Parent.Value)
  213. //ignores should really contain like a fully dotted version of the property name, but I'm starting with small steps
  214. readonly List<string> ignores = new List<string>();
  215. public PropertyCompareConstraint<T> IgnoreProperty(Expression<Func<T, object>> func)
  216. {
  217. Expression express = func.Body;
  218. PullApartExpression(express);
  219. return this;
  220. }
  221. private void PullApartExpression(Expression express)
  222. {
  223. //This deals with any casts... like implicit casts to object. Not all UnaryExpression are casts, but this is a first attempt.
  224. if (express is UnaryExpression)
  225. PullApartExpression(((UnaryExpression)express).Operand);
  226. if (express is MemberExpression)
  227. {
  228. //If the inside of the lambda is the access to x, we've hit the end of the chain.
  229. // We should track by the fully scoped parameter name, but this is the first rev of doing this.
  230. ignores.Add(((MemberExpression)express).Member.Name);
  231. }
  232. }
  233. }
  234. [TestFixture]
  235. public class PropertyCompareConstraintTest
  236. {
  237. public class HasInt
  238. {
  239. public int TheValue { get; set; }
  240. }
  241. [Test]
  242. public void IntShouldMatch()
  243. {
  244. HasInt actual = new HasInt { TheValue = 5 };
  245. HasInt expected = new HasInt { TheValue = 5 };
  246. var constraint = Constraints.PropertyCompareConstraint(expected);
  247. Assert.That(constraint.Matches(actual), Is.True);
  248. }
  249. [Test]
  250. public void IntShouldNotMatch()
  251. {
  252. HasInt actual = new HasInt { TheValue = 5 };
  253. HasInt expected = new HasInt { TheValue = 4 };
  254. var constraint = Constraints.PropertyCompareConstraint(expected);
  255. Assert.That(constraint.Matches(actual), Is.False);
  256. }
  257. [Test]
  258. public void IntShouldIgnore()
  259. {
  260. HasInt actual = new HasInt { TheValue = 5 };
  261. HasInt expected = new HasInt { TheValue = 4 };
  262. var constraint = Constraints.PropertyCompareConstraint(expected).IgnoreProperty(x => x.TheValue);
  263. Assert.That(constraint.Matches(actual), Is.True);
  264. }
  265. [Test]
  266. public void AssetShouldMatch()
  267. {
  268. UUID uuid1 = UUID.Random();
  269. AssetBase actual = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture);
  270. AssetBase expected = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture);
  271. var constraint = Constraints.PropertyCompareConstraint(expected);
  272. Assert.That(constraint.Matches(actual), Is.True);
  273. }
  274. [Test]
  275. public void AssetShouldNotMatch()
  276. {
  277. UUID uuid1 = UUID.Random();
  278. AssetBase actual = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture);
  279. AssetBase expected = new AssetBase(UUID.Random(), "asset one", (sbyte)AssetType.Texture);
  280. var constraint = Constraints.PropertyCompareConstraint(expected);
  281. Assert.That(constraint.Matches(actual), Is.False);
  282. }
  283. [Test]
  284. public void AssetShouldNotMatch2()
  285. {
  286. UUID uuid1 = UUID.Random();
  287. AssetBase actual = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture);
  288. AssetBase expected = new AssetBase(uuid1, "asset two", (sbyte)AssetType.Texture);
  289. var constraint = Constraints.PropertyCompareConstraint(expected);
  290. Assert.That(constraint.Matches(actual), Is.False);
  291. }
  292. [Test]
  293. public void UUIDShouldMatch()
  294. {
  295. UUID uuid1 = UUID.Random();
  296. UUID uuid2 = UUID.Parse(uuid1.ToString());
  297. var constraint = Constraints.PropertyCompareConstraint(uuid1);
  298. Assert.That(constraint.Matches(uuid2), Is.True);
  299. }
  300. [Test]
  301. public void UUIDShouldNotMatch()
  302. {
  303. UUID uuid1 = UUID.Random();
  304. UUID uuid2 = UUID.Random();
  305. var constraint = Constraints.PropertyCompareConstraint(uuid1);
  306. Assert.That(constraint.Matches(uuid2), Is.False);
  307. }
  308. [Test]
  309. public void TestColors()
  310. {
  311. Color actual = Color.Red;
  312. Color expected = Color.FromArgb(actual.A, actual.R, actual.G, actual.B);
  313. var constraint = Constraints.PropertyCompareConstraint(expected);
  314. Assert.That(constraint.Matches(actual), Is.True);
  315. }
  316. [Test]
  317. public void ShouldCompareLists()
  318. {
  319. List<int> expected = new List<int> { 1, 2, 3 };
  320. List<int> actual = new List<int> { 1, 2, 3 };
  321. var constraint = Constraints.PropertyCompareConstraint(expected);
  322. Assert.That(constraint.Matches(actual), Is.True);
  323. }
  324. [Test]
  325. public void ShouldFailToCompareListsThatAreDifferent()
  326. {
  327. List<int> expected = new List<int> { 1, 2, 3 };
  328. List<int> actual = new List<int> { 1, 2, 4 };
  329. var constraint = Constraints.PropertyCompareConstraint(expected);
  330. Assert.That(constraint.Matches(actual), Is.False);
  331. }
  332. [Test]
  333. public void ShouldFailToCompareListsThatAreDifferentLengths()
  334. {
  335. List<int> expected = new List<int> { 1, 2, 3 };
  336. List<int> actual = new List<int> { 1, 2 };
  337. var constraint = Constraints.PropertyCompareConstraint(expected);
  338. Assert.That(constraint.Matches(actual), Is.False);
  339. }
  340. public class Recursive
  341. {
  342. public Recursive Other { get; set; }
  343. }
  344. [Test]
  345. public void ErrorsOutOnRecursive()
  346. {
  347. Recursive parent = new Recursive();
  348. Recursive child = new Recursive();
  349. parent.Other = child;
  350. child.Other = parent;
  351. var constraint = Constraints.PropertyCompareConstraint(child);
  352. Assert.That(constraint.Matches(child), Is.False);
  353. }
  354. }
  355. }