a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
534 lines
18 KiB
C#
534 lines
18 KiB
C#
// ****************************************************************
|
|
// This is free software licensed under the NUnit license. You
|
|
// may obtain a copy of the license as well as information regarding
|
|
// copyright ownership at http://nunit.org/?p=license&r=2.4.
|
|
// ****************************************************************
|
|
|
|
using System;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Collections;
|
|
|
|
namespace NUnit.Framework
|
|
{
|
|
/// <summary>
|
|
/// AssertionFailureMessage encapsulates a failure message
|
|
/// issued as a result of an Assert failure.
|
|
/// </summary>
|
|
[Obsolete( "Use MessageWriter for new work" )]
|
|
public class AssertionFailureMessage : StringWriter
|
|
{
|
|
#region Static Constants
|
|
|
|
/// <summary>
|
|
/// Number of characters before a highlighted position before
|
|
/// clipping will occur. Clipped text is replaced with an
|
|
/// elipsis "..."
|
|
/// </summary>
|
|
static public readonly int PreClipLength = 35;
|
|
|
|
/// <summary>
|
|
/// Number of characters after a highlighted position before
|
|
/// clipping will occur. Clipped text is replaced with an
|
|
/// elipsis "..."
|
|
/// </summary>
|
|
static public readonly int PostClipLength = 35;
|
|
|
|
/// <summary>
|
|
/// Prefix used to start an expected value line.
|
|
/// Must be same length as actualPrefix.
|
|
/// </summary>
|
|
static protected readonly string expectedPrefix = "expected:";
|
|
|
|
/// <summary>
|
|
/// Prefix used to start an actual value line.
|
|
/// Must be same length as expectedPrefix.
|
|
/// </summary>
|
|
static protected readonly string actualPrefix = " but was:";
|
|
|
|
static private readonly string expectedAndActualFmt = "\t{0} {1}";
|
|
static private readonly string diffStringLengthsFmt
|
|
= "\tString lengths differ. Expected length={0}, but was length={1}.";
|
|
static private readonly string sameStringLengthsFmt
|
|
= "\tString lengths are both {0}.";
|
|
static private readonly string diffArrayLengthsFmt
|
|
= "Array lengths differ. Expected length={0}, but was length={1}.";
|
|
static private readonly string sameArrayLengthsFmt
|
|
= "Array lengths are both {0}.";
|
|
static private readonly string stringsDifferAtIndexFmt
|
|
= "\tStrings differ at index {0}.";
|
|
static private readonly string arraysDifferAtIndexFmt
|
|
= "Arrays differ at index {0}.";
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Construct an AssertionFailureMessage with a message
|
|
/// and optional arguments.
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
/// <param name="args"></param>
|
|
public AssertionFailureMessage( string message, params object[] args )
|
|
{
|
|
if ( message != null && message != string.Empty )
|
|
if ( args != null )
|
|
WriteLine( message, args );
|
|
else
|
|
WriteLine( message );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construct an empty AssertionFailureMessage
|
|
/// </summary>
|
|
public AssertionFailureMessage() : this( null, null ) { }
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Add an expected value line to the message containing
|
|
/// the text provided as an argument.
|
|
/// </summary>
|
|
/// <param name="text">Text describing what was expected.</param>
|
|
public void WriteExpectedLine( string text )
|
|
{
|
|
WriteLine( string.Format( expectedAndActualFmt, expectedPrefix, text ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an actual value line to the message containing
|
|
/// the text provided as an argument.
|
|
/// </summary>
|
|
/// <param name="text">Text describing the actual value.</param>
|
|
public void WriteActualLine( string text )
|
|
{
|
|
WriteLine( string.Format( expectedAndActualFmt, actualPrefix, text ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an expected value line to the message containing
|
|
/// a string representation of the object provided.
|
|
/// </summary>
|
|
/// <param name="expected">An object representing the expected value</param>
|
|
public void DisplayExpectedValue( object expected )
|
|
{
|
|
WriteExpectedLine( FormatObjectForDisplay( expected ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an expected value line to the message containing a double
|
|
/// and the tolerance used in making the comparison.
|
|
/// </summary>
|
|
/// <param name="expected">The expected value</param>
|
|
/// <param name="tolerance">The tolerance specified in the Assert</param>
|
|
public void DisplayExpectedValue( double expected, double tolerance )
|
|
{
|
|
WriteExpectedLine( FormatObjectForDisplay( expected ) + " +/- " + tolerance.ToString() );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an actual value line to the message containing
|
|
/// a string representation of the object provided.
|
|
/// </summary>
|
|
/// <param name="actual">An object representing what was actually found</param>
|
|
public void DisplayActualValue( object actual )
|
|
{
|
|
WriteActualLine( FormatObjectForDisplay( actual ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display two lines that communicate the expected value, and the actual value
|
|
/// </summary>
|
|
/// <param name="expected">The expected value</param>
|
|
/// <param name="actual">The actual value found</param>
|
|
public void DisplayExpectedAndActual( Object expected, Object actual )
|
|
{
|
|
DisplayExpectedValue( expected );
|
|
DisplayActualValue( actual );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display two lines that communicate the expected value, the actual value and
|
|
/// the tolerance used in comparing two doubles.
|
|
/// </summary>
|
|
/// <param name="expected">The expected value</param>
|
|
/// <param name="actual">The actual value found</param>
|
|
/// <param name="tolerance">The tolerance specified in the Assert</param>
|
|
public void DisplayExpectedAndActual( double expected, double actual, double tolerance )
|
|
{
|
|
DisplayExpectedValue( expected, tolerance );
|
|
DisplayActualValue( actual );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws a marker under the expected/actual strings that highlights
|
|
/// where in the string a mismatch occurred.
|
|
/// </summary>
|
|
/// <param name="iPosition">The position of the mismatch</param>
|
|
public void DisplayPositionMarker( int iPosition )
|
|
{
|
|
WriteLine( "\t{0}^", new String( '-', expectedPrefix.Length + iPosition + 3 ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reports whether the string lengths are the same or different, and
|
|
/// what the string lengths are.
|
|
/// </summary>
|
|
/// <param name="sExpected">The expected string</param>
|
|
/// <param name="sActual">The actual string value</param>
|
|
protected void BuildStringLengthReport( string sExpected, string sActual )
|
|
{
|
|
if( sExpected.Length != sActual.Length )
|
|
WriteLine( diffStringLengthsFmt, sExpected.Length, sActual.Length );
|
|
else
|
|
WriteLine( sameStringLengthsFmt, sExpected.Length );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called to create additional message lines when two objects have been
|
|
/// found to be unequal. If the inputs are strings, a special message is
|
|
/// rendered that can help track down where the strings are different,
|
|
/// based on differences in length, or differences in content.
|
|
///
|
|
/// If the inputs are not strings, the ToString method of the objects
|
|
/// is used to show what is different about them.
|
|
/// </summary>
|
|
/// <param name="expected">The expected value</param>
|
|
/// <param name="actual">The actual value</param>
|
|
/// <param name="caseInsensitive">True if a case-insensitive comparison is being performed</param>
|
|
public void DisplayDifferences( object expected, object actual, bool caseInsensitive )
|
|
{
|
|
if( InputsAreStrings( expected, actual ) )
|
|
{
|
|
DisplayStringDifferences(
|
|
(string)expected,
|
|
(string)actual,
|
|
caseInsensitive );
|
|
}
|
|
else
|
|
{
|
|
DisplayExpectedAndActual( expected, actual );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called to create additional message lines when two doubles have been
|
|
/// found to be unequal, within the specified tolerance.
|
|
/// </summary>
|
|
public void DisplayDifferencesWithTolerance( double expected, double actual, double tolerance )
|
|
{
|
|
DisplayExpectedAndActual( expected, actual, tolerance );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a message that can be displayed when the content of two
|
|
/// strings are different, but the string lengths are the same. The
|
|
/// message will clip the strings to a reasonable length, centered
|
|
/// around the first position where they are mismatched, and draw
|
|
/// a line marking the position of the difference to make comparison
|
|
/// quicker.
|
|
/// </summary>
|
|
/// <param name="sExpected">The expected string value</param>
|
|
/// <param name="sActual">The actual string value</param>
|
|
/// <param name="caseInsensitive">True if a case-insensitive comparison is being performed</param>
|
|
protected void DisplayStringDifferences( string sExpected, string sActual, bool caseInsensitive )
|
|
{
|
|
//
|
|
// If they mismatch at a specified position, report the
|
|
// difference.
|
|
//
|
|
int iPosition = caseInsensitive
|
|
? FindMismatchPosition( sExpected.ToLower(), sActual.ToLower(), 0 )
|
|
: FindMismatchPosition( sExpected, sActual, 0 );
|
|
//
|
|
// If the lengths differ, but they match up to the length,
|
|
// show the difference just past the length of the shorter
|
|
// string
|
|
//
|
|
if( iPosition == -1 )
|
|
iPosition = Math.Min( sExpected.Length, sActual.Length );
|
|
|
|
BuildStringLengthReport( sExpected, sActual );
|
|
|
|
WriteLine( stringsDifferAtIndexFmt, iPosition );
|
|
|
|
//
|
|
// Clips the strings, then turns any hidden whitespace into visible
|
|
// characters
|
|
//
|
|
string sClippedExpected = ConvertWhitespace(ClipAroundPosition( sExpected, iPosition ));
|
|
string sClippedActual = ConvertWhitespace(ClipAroundPosition( sActual, iPosition ));
|
|
|
|
DisplayExpectedAndActual(
|
|
sClippedExpected,
|
|
sClippedActual );
|
|
|
|
// Add a line showing where they differ. If the string lengths are
|
|
// different, they start differing just past the length of the
|
|
// shorter string
|
|
DisplayPositionMarker( caseInsensitive
|
|
? FindMismatchPosition( sClippedExpected.ToLower(), sClippedActual.ToLower(), 0 )
|
|
: FindMismatchPosition( sClippedExpected, sClippedActual, 0 ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display a standard message showing the differences found between
|
|
/// two arrays that were expected to be equal.
|
|
/// </summary>
|
|
/// <param name="expected">The expected array value</param>
|
|
/// <param name="actual">The actual array value</param>
|
|
/// <param name="index">The index at which a difference was found</param>
|
|
public void DisplayArrayDifferences( Array expected, Array actual, int index )
|
|
{
|
|
if( expected.Length != actual.Length )
|
|
WriteLine( diffArrayLengthsFmt, expected.Length, actual.Length );
|
|
else
|
|
WriteLine( sameArrayLengthsFmt, expected.Length );
|
|
|
|
WriteLine( arraysDifferAtIndexFmt, index );
|
|
|
|
if ( index < expected.Length && index < actual.Length )
|
|
{
|
|
DisplayDifferences( GetValueFromCollection(expected, index ), GetValueFromCollection(actual, index), false );
|
|
}
|
|
else if( expected.Length < actual.Length )
|
|
DisplayListElements( " extra:", actual, index, 3 );
|
|
else
|
|
DisplayListElements( " missing:", expected, index, 3 );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display a standard message showing the differences found between
|
|
/// two collections that were expected to be equal.
|
|
/// </summary>
|
|
/// <param name="expected">The expected collection value</param>
|
|
/// <param name="actual">The actual collection value</param>
|
|
/// <param name="index">The index at which a difference was found</param>
|
|
// NOTE: This is a temporary method for use until the code from NUnitLite
|
|
// is integrated into NUnit.
|
|
public void DisplayCollectionDifferences( ICollection expected, ICollection actual, int index )
|
|
{
|
|
if( expected.Count != actual.Count )
|
|
WriteLine( diffArrayLengthsFmt, expected.Count, actual.Count );
|
|
else
|
|
WriteLine( sameArrayLengthsFmt, expected.Count );
|
|
|
|
WriteLine( arraysDifferAtIndexFmt, index );
|
|
|
|
if ( index < expected.Count && index < actual.Count )
|
|
{
|
|
DisplayDifferences( GetValueFromCollection(expected, index ), GetValueFromCollection(actual, index), false );
|
|
}
|
|
// else if( expected.Count < actual.Count )
|
|
// DisplayListElements( " extra:", actual, index, 3 );
|
|
// else
|
|
// DisplayListElements( " missing:", expected, index, 3 );
|
|
}
|
|
|
|
private static object GetValueFromCollection(ICollection collection, int index)
|
|
{
|
|
Array array = collection as Array;
|
|
|
|
if (array != null && array.Rank > 1)
|
|
return array.GetValue(GetArrayIndicesFromCollectionIndex(array, index));
|
|
|
|
if (collection is IList)
|
|
return ((IList)collection)[index];
|
|
|
|
foreach (object obj in collection)
|
|
if (--index < 0)
|
|
return obj;
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get an array of indices representing the point in a collection or
|
|
/// array corresponding to a single int index into the collection.
|
|
/// </summary>
|
|
/// <param name="collection">The collection to which the indices apply</param>
|
|
/// <param name="index">Index in the collection</param>
|
|
/// <returns>Array of indices</returns>
|
|
private static int[] GetArrayIndicesFromCollectionIndex(ICollection collection, int index)
|
|
{
|
|
Array array = collection as Array;
|
|
int rank = array == null ? 1 : array.Rank;
|
|
int[] result = new int[rank];
|
|
|
|
for (int r = array.Rank; --r > 0; )
|
|
{
|
|
int l = array.GetLength(r);
|
|
result[r] = index % l;
|
|
index /= l;
|
|
}
|
|
|
|
result[0] = index;
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Displays elements from a list on a line
|
|
/// </summary>
|
|
/// <param name="label">Text to prefix the line with</param>
|
|
/// <param name="list">The list of items to display</param>
|
|
/// <param name="index">The index in the list of the first element to display</param>
|
|
/// <param name="max">The maximum number of elements to display</param>
|
|
public void DisplayListElements( string label, IList list, int index, int max )
|
|
{
|
|
Write( "{0}<", label );
|
|
|
|
if ( list == null )
|
|
Write( "null" );
|
|
else if ( list.Count == 0 )
|
|
Write( "empty" );
|
|
else
|
|
{
|
|
for( int i = 0; i < max && index < list.Count; i++ )
|
|
{
|
|
Write( FormatObjectForDisplay( list[index++] ) );
|
|
|
|
if ( index < list.Count )
|
|
Write( "," );
|
|
}
|
|
|
|
if ( index < list.Count )
|
|
Write( "..." );
|
|
}
|
|
|
|
WriteLine( ">" );
|
|
}
|
|
|
|
#region Static Methods
|
|
|
|
/// <summary>
|
|
/// Formats an object for display in a message line
|
|
/// </summary>
|
|
/// <param name="obj">The object to be displayed</param>
|
|
/// <returns></returns>
|
|
static public string FormatObjectForDisplay( object obj )
|
|
{
|
|
if ( obj == null )
|
|
return "<(null)>";
|
|
else if ( obj is string )
|
|
return string.Format( "<\"{0}\">", obj );
|
|
else if ( obj is double )
|
|
return string.Format( "<{0}>", ((double)obj).ToString( "G17" ) );
|
|
else if ( obj is float )
|
|
return string.Format( "<{0}>", ((float)obj).ToString( "G9" ) );
|
|
else
|
|
return string.Format( "<{0}>", obj );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests two objects to determine if they are strings.
|
|
/// </summary>
|
|
/// <param name="expected"></param>
|
|
/// <param name="actual"></param>
|
|
/// <returns></returns>
|
|
static protected bool InputsAreStrings( Object expected, Object actual )
|
|
{
|
|
return expected != null && actual != null &&
|
|
expected is string && actual is string;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders up to M characters before, and up to N characters after
|
|
/// the specified index position. If leading or trailing text is
|
|
/// clipped, and elipses "..." is added where the missing text would
|
|
/// be.
|
|
///
|
|
/// Clips strings to limit previous or post newline characters,
|
|
/// since these mess up the comparison
|
|
/// </summary>
|
|
/// <param name="sString"></param>
|
|
/// <param name="iPosition"></param>
|
|
/// <returns></returns>
|
|
static protected string ClipAroundPosition( string sString, int iPosition )
|
|
{
|
|
if( sString == null || sString.Length == 0 )
|
|
return "";
|
|
|
|
bool preClip = iPosition > PreClipLength;
|
|
bool postClip = iPosition + PostClipLength < sString.Length;
|
|
|
|
int start = preClip
|
|
? iPosition - PreClipLength : 0;
|
|
int length = postClip
|
|
? iPosition + PostClipLength - start : sString.Length - start;
|
|
|
|
if ( start + length > iPosition + PostClipLength )
|
|
length = iPosition + PostClipLength - start;
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
if ( preClip ) sb.Append("...");
|
|
sb.Append( sString.Substring( start, length ) );
|
|
if ( postClip ) sb.Append("...");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shows the position two strings start to differ. Comparison
|
|
/// starts at the start index.
|
|
/// </summary>
|
|
/// <param name="sExpected"></param>
|
|
/// <param name="sActual"></param>
|
|
/// <param name="iStart"></param>
|
|
/// <returns>-1 if no mismatch found, or the index where mismatch found</returns>
|
|
static private int FindMismatchPosition( string sExpected, string sActual, int iStart )
|
|
{
|
|
int iLength = Math.Min( sExpected.Length, sActual.Length );
|
|
for( int i=iStart; i<iLength; i++ )
|
|
{
|
|
//
|
|
// If they mismatch at a specified position, report the
|
|
// difference.
|
|
//
|
|
if( sExpected[i] != sActual[i] )
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
//
|
|
// Strings have same content up to the length of the shorter string.
|
|
// Mismatch occurs because string lengths are different, so show
|
|
// that they start differing where the shortest string ends
|
|
//
|
|
if( sExpected.Length != sActual.Length )
|
|
{
|
|
return iLength;
|
|
}
|
|
|
|
//
|
|
// Same strings
|
|
//
|
|
Assert.IsTrue( sExpected.Equals( sActual ) );
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Turns CR, LF, or TAB into visual indicator to preserve visual marker
|
|
/// position. This is done by replacing the '\r' into '\\' and 'r'
|
|
/// characters, and the '\n' into '\\' and 'n' characters, and '\t' into
|
|
/// '\\' and 't' characters.
|
|
///
|
|
/// Thus the single character becomes two characters for display.
|
|
/// </summary>
|
|
/// <param name="sInput"></param>
|
|
/// <returns></returns>
|
|
static protected string ConvertWhitespace( string sInput )
|
|
{
|
|
if( null != sInput )
|
|
{
|
|
sInput = sInput.Replace( "\\", "\\\\" );
|
|
sInput = sInput.Replace( "\r", "\\r" );
|
|
sInput = sInput.Replace( "\n", "\\n" );
|
|
sInput = sInput.Replace( "\t", "\\t" );
|
|
}
|
|
return sInput;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|