Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

394 lines
15 KiB
C#

// ****************************************************************
// Copyright 2007, Charlie Poole
// This is free software licensed under the NUnit license. You may
// obtain a copy of the license at http://nunit.org/?p=license&r=2.4
// ****************************************************************
using System;
using System.IO;
using System.Collections;
namespace NUnit.Framework.Constraints
{
/// <summary>
/// EqualConstraint is able to compare an actual value with the
/// expected value provided in its constructor.
/// </summary>
public class EqualConstraint : Constraint
{
private static IDictionary constraintHelpers = new Hashtable();
private readonly object expected;
private ArrayList failurePoints;
private static readonly string StringsDiffer_1 =
"String lengths are both {0}. Strings differ at index {1}.";
private static readonly string StringsDiffer_2 =
"Expected string length {0} but was {1}. Strings differ at index {2}.";
private static readonly string StreamsDiffer_1 =
"Stream lengths are both {0}. Streams differ at offset {1}.";
private static readonly string StreamsDiffer_2 =
"Expected Stream length {0} but was {1}.";// Streams differ at offset {2}.";
private static readonly string CollectionType_1 =
"Expected and actual are both {0}";
private static readonly string CollectionType_2 =
"Expected is {0}, actual is {1}";
private static readonly string ValuesDiffer_1 =
"Values differ at index {0}";
private static readonly string ValuesDiffer_2 =
"Values differ at expected index {0}, actual index {1}";
private static readonly int BUFFER_SIZE = 4096;
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="EqualConstraint"/> class.
/// </summary>
/// <param name="expected">The expected value.</param>
public EqualConstraint(object expected)
{
this.expected = expected;
}
#endregion
#region Public Methods
/// <summary>
/// Test whether the constraint is satisfied by a given value
/// </summary>
/// <param name="actual">The value to be tested</param>
/// <returns>True for success, false for failure</returns>
public override bool Matches(object actual)
{
this.actual = actual;
this.failurePoints = new ArrayList();
return ObjectsEqual( expected, actual );
}
/// <summary>
/// Write a failure message. Overridden to provide custom
/// failure messages for EqualConstraint.
/// </summary>
/// <param name="writer">The MessageWriter to write to</param>
public override void WriteMessageTo(MessageWriter writer)
{
DisplayDifferences(writer, expected, actual, 0);
}
/// <summary>
/// Write description of this constraint
/// </summary>
/// <param name="writer">The MessageWriter to write to</param>
public override void WriteDescriptionTo(MessageWriter writer)
{
writer.WriteExpectedValue( expected );
if ( tolerance != null )
{
writer.WriteConnector("+/-");
writer.WriteExpectedValue(tolerance);
}
if ( this.caseInsensitive )
writer.WriteModifier("ignoring case");
}
private void DisplayDifferences(MessageWriter writer, object expected, object actual, int depth)
{
if (expected is string && actual is string)
DisplayStringDifferences(writer, (string)expected, (string)actual);
else if (expected is ICollection && actual is ICollection)
DisplayCollectionDifferences(writer, (ICollection)expected, (ICollection)actual, depth);
else if (expected is Stream && actual is Stream)
DisplayStreamDifferences(writer, (Stream)expected, (Stream)actual, depth);
else if ( tolerance != null )
writer.DisplayDifferences( expected, actual, tolerance );
else
writer.DisplayDifferences(expected, actual);
}
#endregion
#region ObjectsEqual
private bool ObjectsEqual(object expected, object actual)
{
if (expected == null && actual == null)
return true;
if (expected == null || actual == null)
return false;
Type expectedType = expected.GetType();
Type actualType = actual.GetType();
if (expectedType.IsArray && actualType.IsArray && !compareAsCollection)
return ArraysEqual((Array)expected, (Array)actual);
if (expected is ICollection && actual is ICollection)
return CollectionsEqual((ICollection)expected, (ICollection)actual);
if (expected is Stream && actual is Stream)
return StreamsEqual((Stream)expected, (Stream)actual);
if (compareWith != null)
return compareWith.Compare( expected, actual ) == 0;
if (expected is DirectoryInfo && actual is DirectoryInfo)
return DirectoriesEqual((DirectoryInfo)expected, (DirectoryInfo)actual);
if (Numerics.IsNumericType(expected) && Numerics.IsNumericType(actual))
{
return Numerics.AreEqual(expected, actual, ref tolerance);
}
if (expected is string && actual is string)
{
return StringsEqual( (string) expected, (string)actual );
}
if (expected is DateTime && actual is DateTime && tolerance is TimeSpan)
{
return ((DateTime)expected - (DateTime)actual).Duration() <= (TimeSpan)tolerance;
}
return expected.Equals(actual);
}
/// <summary>
/// Helper method to compare two arrays
/// </summary>
protected virtual bool ArraysEqual(Array expected, Array actual)
{
int rank = expected.Rank;
if (rank != actual.Rank)
return false;
for (int r = 1; r < rank; r++)
if (expected.GetLength(r) != actual.GetLength(r))
return false;
return CollectionsEqual((ICollection)expected, (ICollection)actual);
}
private bool CollectionsEqual(ICollection expected, ICollection actual)
{
IEnumerator expectedEnum = expected.GetEnumerator();
IEnumerator actualEnum = actual.GetEnumerator();
int count;
for (count = 0; expectedEnum.MoveNext() && actualEnum.MoveNext(); count++)
{
if (!ObjectsEqual(expectedEnum.Current, actualEnum.Current))
break;
}
if (count == expected.Count && count == actual.Count)
return true;
failurePoints.Insert(0, count);
return false;
}
private bool StreamsEqual( Stream expected, Stream actual )
{
if (expected.Length != actual.Length) return false;
byte[] bufferExpected = new byte[BUFFER_SIZE];
byte[] bufferActual = new byte[BUFFER_SIZE];
BinaryReader binaryReaderExpected = new BinaryReader(expected);
BinaryReader binaryReaderActual = new BinaryReader(actual);
binaryReaderExpected.BaseStream.Seek(0, SeekOrigin.Begin);
binaryReaderActual.BaseStream.Seek(0, SeekOrigin.Begin);
for(long readByte = 0; readByte < expected.Length; readByte += BUFFER_SIZE )
{
binaryReaderExpected.Read(bufferExpected, 0, BUFFER_SIZE);
binaryReaderActual.Read(bufferActual, 0, BUFFER_SIZE);
for (int count=0; count < BUFFER_SIZE; ++count)
{
if (bufferExpected[count] != bufferActual[count])
{
failurePoints.Insert( 0, readByte + count );
//FailureMessage.WriteLine("\tIndex : {0}", readByte + count);
return false;
}
}
}
return true;
}
private bool StringsEqual( string expected, string actual )
{
string s1 = caseInsensitive ? expected.ToLower() : expected;
string s2 = caseInsensitive ? actual.ToLower() : actual;
return s1.Equals( s2 );
}
/// <summary>
/// Method to compare two DirectoryInfo objects
/// </summary>
/// <param name="expected">first directory to compare</param>
/// <param name="actual">second directory to compare</param>
/// <returns>true if equivalent, false if not</returns>
private bool DirectoriesEqual(DirectoryInfo expected, DirectoryInfo actual)
{
return expected.Attributes == actual.Attributes
&& expected.CreationTime == actual.CreationTime
&& expected.FullName == actual.FullName
&& expected.LastAccessTime == actual.LastAccessTime;
}
#endregion
#region DisplayStringDifferences
private void DisplayStringDifferences(MessageWriter writer, string expected, string actual)
{
int mismatch = MsgUtils.FindMismatchPosition(expected, actual, 0, this.caseInsensitive);
if (expected.Length == actual.Length)
writer.WriteMessageLine(StringsDiffer_1, expected.Length, mismatch);
else
writer.WriteMessageLine(StringsDiffer_2, expected.Length, actual.Length, mismatch);
writer.DisplayStringDifferences(expected, actual, mismatch, caseInsensitive, clipStrings);
}
#endregion
#region DisplayStreamDifferences
private void DisplayStreamDifferences(MessageWriter writer, Stream expected, Stream actual, int depth)
{
if ( expected.Length == actual.Length )
{
long offset = (long)failurePoints[depth];
writer.WriteMessageLine(StreamsDiffer_1, expected.Length, offset);
}
else
writer.WriteMessageLine(StreamsDiffer_2, expected.Length, actual.Length);
}
#endregion
#region DisplayCollectionDifferences
/// <summary>
/// Display the failure information for two collections that did not match.
/// </summary>
/// <param name="writer">The MessageWriter on which to display</param>
/// <param name="expected">The expected collection.</param>
/// <param name="actual">The actual collection</param>
/// <param name="depth">The depth of this failure in a set of nested collections</param>
private void DisplayCollectionDifferences(MessageWriter writer, ICollection expected, ICollection actual, int depth)
{
int failurePoint = failurePoints.Count > depth ? (int)failurePoints[depth] : -1;
DisplayCollectionTypesAndSizes(writer, expected, actual, depth);
if (failurePoint >= 0)
{
DisplayFailurePoint(writer, expected, actual, failurePoint, depth);
if (failurePoint < expected.Count && failurePoint < actual.Count)
DisplayDifferences(
writer,
GetValueFromCollection(expected, failurePoint),
GetValueFromCollection(actual, failurePoint),
++depth);
else if (expected.Count < actual.Count)
{
writer.Write( " Extra: " );
writer.WriteCollectionElements( actual, failurePoint, 3 );
}
else
{
writer.Write( " Missing: " );
writer.WriteCollectionElements( expected, failurePoint, 3 );
}
}
}
/// <summary>
/// Displays a single line showing the types and sizes of the expected
/// and actual collections or arrays. If both are identical, the value is
/// only shown once.
/// </summary>
/// <param name="writer">The MessageWriter on which to display</param>
/// <param name="expected">The expected collection or array</param>
/// <param name="actual">The actual collection or array</param>
/// <param name="indent">The indentation level for the message line</param>
private void DisplayCollectionTypesAndSizes(MessageWriter writer, ICollection expected, ICollection actual, int indent)
{
string sExpected = MsgUtils.GetTypeRepresentation(expected);
if (!(expected is Array))
sExpected += string.Format(" with {0} elements", expected.Count);
string sActual = MsgUtils.GetTypeRepresentation(actual);
if (!(actual is Array))
sActual += string.Format(" with {0} elements", actual.Count);
if (sExpected == sActual)
writer.WriteMessageLine(indent, CollectionType_1, sExpected);
else
writer.WriteMessageLine(indent, CollectionType_2, sExpected, sActual);
}
/// <summary>
/// Displays a single line showing the point in the expected and actual
/// arrays at which the comparison failed. If the arrays have different
/// structures or dimensions, both values are shown.
/// </summary>
/// <param name="writer">The MessageWriter on which to display</param>
/// <param name="expected">The expected array</param>
/// <param name="actual">The actual array</param>
/// <param name="failurePoint">Index of the failure point in the underlying collections</param>
/// <param name="indent">The indentation level for the message line</param>
private void DisplayFailurePoint(MessageWriter writer, ICollection expected, ICollection actual, int failurePoint, int indent)
{
Array expectedArray = expected as Array;
Array actualArray = actual as Array;
int expectedRank = expectedArray != null ? expectedArray.Rank : 1;
int actualRank = actualArray != null ? actualArray.Rank : 1;
bool useOneIndex = expectedRank == actualRank;
if (expectedArray != null && actualArray != null)
for (int r = 1; r < expectedRank && useOneIndex; r++)
if (expectedArray.GetLength(r) != actualArray.GetLength(r))
useOneIndex = false;
int[] expectedIndices = MsgUtils.GetArrayIndicesFromCollectionIndex(expected, failurePoint);
if (useOneIndex)
{
writer.WriteMessageLine(indent, ValuesDiffer_1, MsgUtils.GetArrayIndicesAsString(expectedIndices));
}
else
{
int[] actualIndices = MsgUtils.GetArrayIndicesFromCollectionIndex(actual, failurePoint);
writer.WriteMessageLine(indent, ValuesDiffer_2,
MsgUtils.GetArrayIndicesAsString(expectedIndices), MsgUtils.GetArrayIndicesAsString(actualIndices));
}
}
private static object GetValueFromCollection(ICollection collection, int index)
{
Array array = collection as Array;
if (array != null && array.Rank > 1)
return array.GetValue(MsgUtils.GetArrayIndicesFromCollectionIndex(array, index));
if (collection is IList)
return ((IList)collection)[index];
foreach (object obj in collection)
if (--index < 0)
return obj;
return null;
}
#endregion
}
}