271 lines
9.0 KiB
C#
271 lines
9.0 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Globalization;
|
|
|
|
namespace MusicMetaTagger.Client.AllMusicGuide.Tests
|
|
{
|
|
/// <summary>
|
|
/// A static unit testing helper class that can test two objects
|
|
/// to see if they are equal.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This class does not rely on Object.Equals to insure that reference types
|
|
/// are equal. Instead, it compares all public properties of
|
|
/// reference types recursively (eventually arriving at value types). If the properties are the same,
|
|
/// then the objects are assuemd to be equal. This may be changed
|
|
/// to an optional behavior later on (if anyone has a need for that kind of thing).
|
|
/// </remarks>
|
|
/// <author>MBH</author>
|
|
/// <dateAuthored>11/29/04</dateAuthored>
|
|
public class EqualityTester
|
|
{
|
|
/// <summary>
|
|
/// When set to true, DateTime objects may differ by < 1 second and still
|
|
/// be considered equal.
|
|
/// </summary>
|
|
private static bool _fuzzyDateMatching;
|
|
|
|
/// <summary>
|
|
/// This is a hack to insure 100% test coverage
|
|
/// </summary>
|
|
static EqualityTester()
|
|
{
|
|
new EqualityTester();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a static class, so it cannot be instantiated directly
|
|
/// </summary>
|
|
private EqualityTester()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// When set to true, DateTime objects may differ by < 1 second and still
|
|
/// be considered equal.
|
|
/// </summary>
|
|
public static bool FuzzyDateMatching
|
|
{
|
|
get { return _fuzzyDateMatching; }
|
|
set { _fuzzyDateMatching = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asserts that the result of enumerating two IEnumerable objects
|
|
/// are identical.
|
|
/// </summary>
|
|
/// <param name = "o1">The first IEnumerable object</param>
|
|
/// <param name = "o2">The second IEnumerable object</param>
|
|
private static void AssertIEnumerablesEqual(object o1, object o2)
|
|
{
|
|
var enumerator1 = ((IEnumerable) o1).GetEnumerator();
|
|
var enumerator2 = ((IEnumerable) o2).GetEnumerator();
|
|
|
|
var enumerator1Valid = enumerator1.MoveNext();
|
|
var enumerator2Valid = enumerator2.MoveNext();
|
|
|
|
while (enumerator1Valid && enumerator2Valid)
|
|
{
|
|
//The two values from the enumerators
|
|
|
|
var value1 = enumerator1.Current;
|
|
|
|
var value2 = enumerator2.Current;
|
|
|
|
AssertEqual(value1, value2);
|
|
|
|
enumerator1Valid = enumerator1.MoveNext();
|
|
enumerator2Valid = enumerator2.MoveNext();
|
|
}
|
|
|
|
//If an enumerator is still valid, that means it had more items in it than the other
|
|
if (enumerator1Valid)
|
|
throw (new NotSupportedException("The first IEnumerable object had more items in it than the second."));
|
|
|
|
if (enumerator2Valid)
|
|
throw (new NotSupportedException("The second IEnumerable object had more items in it than the first."));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asserts that two reference-types are equal by comparing their properties.
|
|
/// </summary>
|
|
/// <param name = "o1">The first object.</param>
|
|
/// <param name = "o2">The second object.</param>
|
|
/// <exception cref = "NotSupportedException"></exception>
|
|
private static void AssertReferencesEqual(object o1, object o2)
|
|
{
|
|
//At this point, it really shouldn't be possible for two things to be of differing types, so no check is performed.
|
|
|
|
var t = o1.GetType();
|
|
|
|
//Strings are a special case
|
|
if (t == typeof (string))
|
|
{
|
|
//If the strings aren't equal, fail
|
|
if (!o1.Equals(o2))
|
|
{
|
|
throw (new NotSupportedException("Strings are not equal: '" + o1 + "' != '" + o2 + "'"));
|
|
}
|
|
}
|
|
|
|
if (t.GetInterface("IEnumerable") != null || t.IsArray)
|
|
AssertIEnumerablesEqual(o1, o2);
|
|
|
|
//Assert that all public properties are equal
|
|
var properties = t.GetProperties();
|
|
|
|
var isICollection = t.GetInterface("ICollection") != null;
|
|
|
|
foreach (var property in properties)
|
|
{
|
|
//If this is a collection, skip the SyncRoot property since
|
|
//that can vary even if collections have identicial members
|
|
if (isICollection && property.Name == "SyncRoot")
|
|
continue;
|
|
|
|
//Skip Capacity on ICollections, too, since that can change
|
|
if (isICollection && property.Name == "Capacity")
|
|
continue;
|
|
|
|
var getMethod = property.GetGetMethod();
|
|
|
|
if (getMethod.GetParameters().Length > 0)
|
|
//this property is an indexer and cannot be checked. If the object implemeneted
|
|
//IEnumerable, it was probably checked above.
|
|
continue;
|
|
|
|
{
|
|
var value1 = getMethod.Invoke(o1, new object[] {});
|
|
|
|
var value2 = getMethod.Invoke(o2, new object[] {});
|
|
|
|
//See if either result is null
|
|
if (value1 == null || value2 == null)
|
|
{
|
|
//If either one is null, they both better be or the test failed.
|
|
if (value1 != null || value2 != null)
|
|
throw (new NotSupportedException("Object property accessor returned one null, one not-null for " + getMethod.Name));
|
|
continue;
|
|
}
|
|
|
|
if (value1.GetType().IsValueType)
|
|
{
|
|
try
|
|
{
|
|
AssertValuesEqual(value1, value2);
|
|
}
|
|
catch (NotSupportedException ex)
|
|
{
|
|
var errorMessage = String.Format("Property '{0}' failed equality check. The inner exception is the reason.",
|
|
property.Name);
|
|
throw (new NotSupportedException(errorMessage, ex));
|
|
}
|
|
}
|
|
//Don't recurse down the array graph, it's self referencing and will overflow the stack.
|
|
else if (!(t.IsArray && value1.GetType().IsArray))
|
|
{
|
|
try
|
|
{
|
|
AssertReferencesEqual(value1, value2);
|
|
}
|
|
catch (NotSupportedException ex)
|
|
{
|
|
var errorMessage = String.Format("Property '{0}' failed equality check. The inner exception is the reason.",
|
|
property.Name);
|
|
throw (new NotSupportedException(errorMessage, ex));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asserts that two value types are equal.
|
|
/// </summary>
|
|
/// <param name = "o1">The first value.</param>
|
|
/// <param name = "o2">The second value</param>
|
|
/// <exception cref = "NotSupportedException">Thrown if the values aren't equal.</exception>
|
|
private static void AssertValuesEqual(object o1, object o2)
|
|
{
|
|
if (o1 is DateTime)
|
|
{
|
|
AssertDateTimesEqual((DateTime) o1, (DateTime) o2);
|
|
}
|
|
else if (!o1.Equals(o2))
|
|
throw (new NotSupportedException(String.Format("Values not equal: '{0}' != '{1}'", o1, o2)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asserts that two DateTime objects are equal.
|
|
/// </summary>
|
|
/// <param name = "dateTime1"></param>
|
|
/// <param name = "dateTime2"></param>
|
|
private static void AssertDateTimesEqual(DateTime dateTime1, DateTime dateTime2)
|
|
{
|
|
if (_fuzzyDateMatching)
|
|
{
|
|
if (dateTime1 - dateTime2 > TimeSpan.FromSeconds(1) || dateTime1 - dateTime2 < TimeSpan.FromSeconds(-1))
|
|
throw (new NotSupportedException(
|
|
String.Format("DateTime's differ by more than 1 second with fuzzy matching enabled: '{0}' != '{1}'.", dateTime1,
|
|
dateTime2)));
|
|
}
|
|
else if (dateTime1 != dateTime2)
|
|
throw (new NotSupportedException(String.Format("Values not equal: '{0}' != '{1}'", dateTime1.ToString(CultureInfo.InvariantCulture),
|
|
dateTime2.ToString(CultureInfo.InvariantCulture))));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asserts that all public properties on the specified objects
|
|
/// are equal.
|
|
/// </summary>
|
|
/// <param name = "o1">The first object</param>
|
|
/// <param name = "o2">The second object</param>
|
|
/// <exception cref = "InvalidOperationException">Thrown if the two objects are of different types, indicating an internal error.</exception>
|
|
/// <exception cref = "NotSupportedException">Thrown if the objects aren't equal.</exception>
|
|
/// <remarks>
|
|
/// o1 and o2 must be of the same type.
|
|
/// </remarks>
|
|
public static void AssertEqual(object o1, object o2)
|
|
{
|
|
if (o1 == null || o2 == null)
|
|
{
|
|
//Both values better be null...
|
|
if (o1 != null || o2 != null)
|
|
throw (new NotSupportedException("One value type was null while the other was not."));
|
|
return;
|
|
}
|
|
|
|
if (o1.GetType() != o2.GetType())
|
|
throw (new InvalidOperationException("AssertEqual called with objects of differing types: " + o1.GetType().Name +
|
|
", " + o2.GetType().Name));
|
|
|
|
var t = o1.GetType();
|
|
|
|
if (t.IsValueType)
|
|
AssertValuesEqual(o1, o2);
|
|
else
|
|
AssertReferencesEqual(o1, o2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recursively tests that all public properties on the specified objects are equal.
|
|
/// </summary>
|
|
/// <param name = "o1"></param>
|
|
/// <param name = "o2"></param>
|
|
/// <exception cref = "InvalidOperationException">Thrown if the two objects are not of the same type.</exception>
|
|
/// <returns>true if the objects are equal, false otherwise.</returns>
|
|
public static bool AreEqual(object o1, object o2)
|
|
{
|
|
try
|
|
{
|
|
AssertEqual(o1, o2);
|
|
}
|
|
catch (NotSupportedException)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
} |