#region Copyright (c) 2002-2003, James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole, Philip A. Craig, Douglas de la Torre
/************************************************************************************
'
' Copyright 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
' Copyright 2000-2002 Philip A. Craig
' Copyright 2001 Douglas de la Torre
'
' This software is provided 'as-is', without any express or implied warranty. In no
' event will the authors be held liable for any damages arising from the use of this
' software.
'
' Permission is granted to anyone to use this software for any purpose, including
' commercial applications, and to alter it and redistribute it freely, subject to the
' following restrictions:
'
' 1. The origin of this software must not be misrepresented; you must not claim that
' you wrote the original software. If you use this software in a product, an
' acknowledgment (see the following) in the product documentation is required.
'
' Portions Copyright 2002 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov
' Copyright 2000-2002 Philip A. Craig, or Copyright 2001 Douglas de la Torre
'
' 2. Altered source versions must be plainly marked as such, and must not be
' misrepresented as being the original software.
'
' 3. This notice may not be removed or altered from any source distribution.
'
'***********************************************************************************/
#endregion
using System;
using System.Text;
namespace NUnit.Framework
{
///
/// Summary description for AssertionFailureMessage.
///
public class AssertionFailureMessage
{
///
/// Protected constructor, used since this class is only used via
/// static methods
///
protected AssertionFailureMessage()
{}
///
/// Number of characters before a highlighted position before
/// clipping will occur. Clipped text is replaced with an
/// elipses "..."
///
static protected int PreClipLength
{
get
{
return 35;
}
}
///
/// Number of characters after a highlighted position before
/// clipping will occur. Clipped text is replaced with an
/// elipses "..."
///
static protected int PostClipLength
{
get
{
return 35;
}
}
///
/// Called to test if the position will cause clipping
/// to occur in the early part of a string.
///
///
///
static private bool IsPreClipped( int iPosition )
{
if( iPosition > PreClipLength )
{
return true;
}
return false;
}
///
/// Called to test if the position will cause clipping
/// to occur in the later part of a string past the
/// specified position.
///
///
///
///
static private bool IsPostClipped( string sString, int iPosition )
{
if( sString.Length - iPosition > PostClipLength )
{
return true;
}
return false;
}
///
/// Property called to insert newline characters into a string
///
static private string NewLine
{
get
{
return "\r\n\t";
}
}
///
/// 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
///
///
///
///
static protected string ClipAroundPosition( string sString, int iPosition )
{
if( null == sString || 0 == sString.Length )
{
return "";
}
return BuildBefore( sString, iPosition ) + BuildAfter( sString, iPosition );
}
///
/// Clips the string before the specified position, and appends
/// ellipses (...) to show that clipping has occurred
///
///
///
///
static protected string PreClip( string sString, int iPosition )
{
return "..." + sString.Substring( iPosition - PreClipLength, PreClipLength );
}
///
/// Clips the string after the specified position, and appends
/// ellipses (...) to show that clipping has occurred
///
///
///
///
static protected string PostClip( string sString, int iPosition )
{
return sString.Substring( iPosition, PostClipLength ) + "...";
}
///
/// Builds the first half of a string, limiting the number of
/// characters before the position, and removing newline
/// characters. If the leading string is truncated, the
/// ellipses (...) characters are appened.
///
///
///
///
static private string BuildBefore( string sString, int iPosition )
{
if( IsPreClipped(iPosition) )
{
return PreClip( sString, iPosition );
}
return sString.Substring( 0, iPosition );
}
///
/// Builds the last half of a string, limiting the number of
/// characters after the position, and removing newline
/// characters. If the string is truncated, the
/// ellipses (...) characters are appened.
///
///
///
///
static private string BuildAfter( string sString, int iPosition )
{
if( IsPostClipped(sString, iPosition) )
{
return PostClip( sString, iPosition );
}
return sString.Substring( iPosition );
}
///
/// Text that is rendered for the expected value
///
///
static protected string ExpectedText()
{
return "expected:<";
}
///
/// Text rendered for the actual value. This text should
/// be the same length as the Expected text, so leading
/// spaces should pad this string to ensure they match.
///
///
static protected string ButWasText()
{
return " but was:<";
}
///
/// Raw line that communicates the expected value, and the actual value
///
///
///
///
static protected void AppendExpectedAndActual( StringBuilder sbOutput, Object expected, Object actual )
{
sbOutput.Append( NewLine );
sbOutput.Append( ExpectedText() );
sbOutput.Append( DisplayString( expected ) );
sbOutput.Append( ">" );
sbOutput.Append( NewLine );
sbOutput.Append( ButWasText() );
sbOutput.Append( DisplayString( actual ) );
sbOutput.Append( ">" );
}
///
/// Display an object as a string
///
///
///
static protected string DisplayString( object obj )
{
if ( obj == null )
return "(null)";
else if ( obj is string )
return Quoted( (string)obj );
else
return obj.ToString();
}
///
/// Quote a string
///
///
///
static protected string Quoted( string text )
{
return string.Format( "\"{0}\"", text );
}
///
/// Draws a marker under the expected/actual strings that highlights
/// where in the string a mismatch occurred.
///
///
///
static protected void AppendPositionMarker( StringBuilder sbOutput, int iPosition )
{
sbOutput.Append( new String( '-', ButWasText().Length + 1 ) );
if( iPosition > 0 )
{
sbOutput.Append( new string( '-', iPosition ) );
}
sbOutput.Append( "^" );
}
///
/// Tests two objects to determine if they are strings.
///
///
///
///
static protected bool InputsAreStrings( Object expected, Object actual )
{
if( null != expected &&
null != actual &&
expected is string &&
actual is string )
{
return true;
}
return false;
}
///
/// Tests if two strings are different lengths.
///
///
///
/// True if string lengths are different
static protected bool LengthsDifferent( string sExpected, string sActual )
{
if( sExpected.Length != sActual.Length )
{
return true;
}
return false;
}
///
/// Tests if two arrays are different lengths.
///
///
///
/// True if array lengths are different
static protected bool LengthsDifferent( object[] expected, object[] actual )
{
if( expected.Length != actual.Length )
{
return true;
}
return false;
}
///
/// Used to construct a message when the lengths of two strings are
/// different. Also includes the strings themselves, to allow them
/// to be compared visually.
///
///
///
///
static protected void BuildLengthsDifferentMessage( StringBuilder sbOutput, string sExpected, string sActual )
{
BuildContentDifferentMessage( sbOutput, sExpected, sActual );
}
///
/// Reports the length of two strings that are different lengths
///
///
///
///
static protected void BuildStringLengthDifferentReport( StringBuilder sbOutput, string sExpected, string sActual )
{
sbOutput.Append( "String lengths differ. Expected length=" );
sbOutput.Append( sExpected.Length );
sbOutput.Append( ", but was length=" );
sbOutput.Append( sActual.Length );
sbOutput.Append( "." );
sbOutput.Append( NewLine );
}
///
/// Reports the length of two strings that are the same length
///
///
///
///
static protected void BuildStringLengthSameReport( StringBuilder sbOutput, string sExpected, string sActual )
{
sbOutput.Append( "String lengths are both " );
sbOutput.Append( sExpected.Length );
sbOutput.Append( "." );
sbOutput.Append( NewLine );
}
///
/// Reports whether the string lengths are the same or different, and
/// what the string lengths are.
///
///
///
///
static protected void BuildStringLengthReport( StringBuilder sbOutput, string sExpected, string sActual )
{
if( sExpected.Length != sActual.Length )
{
BuildStringLengthDifferentReport( sbOutput, sExpected, sActual );
}
else
{
BuildStringLengthSameReport( sbOutput, sExpected, sActual );
}
}
///
/// Reports the length of two arrays that are different lengths
///
///
///
///
static protected void BuildArrayLengthDifferentReport( StringBuilder sbOutput, Array expected, Array actual )
{
sbOutput.Append( "Array lengths differ. Expected length=" );
sbOutput.Append( expected.Length );
sbOutput.Append( ", but was length=" );
sbOutput.Append( actual.Length );
sbOutput.Append( "." );
sbOutput.Append( NewLine );
}
///
/// Reports the length of two arrays that are the same length
///
///
///
///
static protected void BuildArrayLengthSameReport( StringBuilder sbOutput, Array expected, Array actual )
{
sbOutput.Append( "Array lengths are both " );
sbOutput.Append( expected.Length );
sbOutput.Append( "." );
sbOutput.Append( NewLine );
}
///
/// Reports whether the array lengths are the same or different, and
/// what the array lengths are.
///
///
///
///
static protected void BuildArrayLengthReport( StringBuilder sbOutput, Array expected, Array actual )
{
if( expected.Length != actual.Length )
{
BuildArrayLengthDifferentReport( sbOutput, expected, actual );
}
else
{
BuildArrayLengthSameReport( sbOutput, expected, actual );
}
}
///
///
///
///
///
///
///
static private void BuildContentDifferentAtPosition( StringBuilder sbOutput, string sExpected, string sActual, int iPosition )
{
BuildStringLengthReport( sbOutput, sExpected, sActual );
sbOutput.Append( "Strings differ at index " );
sbOutput.Append( iPosition );
sbOutput.Append( "." );
sbOutput.Append( NewLine );
//
// Clips the strings, then turns any hidden whitespace into visible
// characters
//
string sClippedExpected = ConvertWhitespace(ClipAroundPosition( sExpected, iPosition ));
string sClippedActual = ConvertWhitespace(ClipAroundPosition( sActual, iPosition ));
AppendExpectedAndActual(
sbOutput,
sClippedExpected,
sClippedActual );
sbOutput.Append( NewLine );
// Add a line showing where they differ. If the string lengths are
// different, they start differing just past the length of the
// shorter string
AppendPositionMarker(
sbOutput,
FindMismatchPosition( sClippedExpected, sClippedActual, 0 ) );
sbOutput.Append( NewLine );
}
///
/// 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.
///
///
///
static protected string ConvertWhitespace( string sInput )
{
if( null != sInput )
{
sInput = sInput.Replace( "\r", "\\r" );
sInput = sInput.Replace( "\n", "\\n" );
sInput = sInput.Replace( "\t", "\\t" );
}
return sInput;
}
///
/// Shows the position two strings start to differ. Comparison
/// starts at the start index.
///
///
///
///
/// -1 if no mismatch found, or the index where mismatch found
static private int FindMismatchPosition( string sExpected, string sActual, int iStart )
{
int iLength = Math.Min( sExpected.Length, sActual.Length );
for( int i=iStart; i
/// 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.
///
///
///
///
static protected void BuildContentDifferentMessage( StringBuilder sbOutput, string sExpected, string sActual )
{
//
// If they mismatch at a specified position, report the
// difference.
//
int iMismatch = FindMismatchPosition( sExpected, sActual, 0 );
if( -1 != iMismatch )
{
BuildContentDifferentAtPosition(
sbOutput,
sExpected,
sActual,
iMismatch );
return;
}
//
// If the lengths differ, but they match up to the length,
// show the difference just past the length of the shorter
// string
//
if( sExpected.Length != sActual.Length )
{
BuildContentDifferentAtPosition(
sbOutput,
sExpected,
sActual,
Math.Min(sExpected.Length, sActual.Length) );
}
}
///
/// Called to append a message when the input strings are different.
/// A different message is rendered when the lengths are mismatched,
/// and when the lengths match but content is mismatched.
///
///
///
///
static private void BuildStringsDifferentMessage( StringBuilder sbOutput, string expected, string actual )
{
sbOutput.Append( NewLine );
if( LengthsDifferent( expected, actual ) )
{
BuildLengthsDifferentMessage( sbOutput, expected, actual );
}
else
{
BuildContentDifferentMessage( sbOutput, expected, actual );
}
}
///
/// Called to append a message when the input arrays are different.
/// A different message is rendered when the lengths are mismatched,
/// and when the lengths match but content is mismatched.
///
///
///
///
static private void BuildArraysDifferentMessage( StringBuilder sbOutput, int index, Array expected, Array actual )
{
sbOutput.Append( NewLine );
BuildArrayLengthReport( sbOutput, expected, actual );
sbOutput.Append( "Arrays differ at index " );
sbOutput.Append( index );
sbOutput.Append( "." );
sbOutput.Append( NewLine );
if ( index < expected.Length && index < actual.Length )
{
if( InputsAreStrings( expected.GetValue(index), actual.GetValue(index) ) )
{
BuildStringsDifferentMessage(
sbOutput,
(string)expected.GetValue(index),
(string)actual.GetValue(index) );
}
else
{
AppendExpectedAndActual( sbOutput, expected.GetValue(index), actual.GetValue(index) );
}
}
else if( expected.Length < actual.Length )
{
sbOutput.Append( NewLine );
sbOutput.Append( " extra:<" );
DisplayElements( sbOutput, actual, index, 3 );
sbOutput.Append( ">" );
}
else
{
sbOutput.Append( NewLine );
sbOutput.Append( " missing:<" );
DisplayElements( sbOutput, expected, index, 3 );
sbOutput.Append( ">" );
}
return;
}
static private void DisplayElements( StringBuilder sbOutput, Array array, int index, int max )
{
for( int i = 0; i < max; i++ )
{
sbOutput.Append( DisplayString( array.GetValue(index++) ) );
if ( index >= array.Length )
return;
sbOutput.Append( "," );
}
sbOutput.Append( "..." );
}
///
/// Used to create a StringBuilder that is used for constructing
/// the output message when text is different. Handles initialization
/// when a message is provided. If message is null, an empty
/// StringBuilder is returned.
///
///
///
static protected StringBuilder CreateStringBuilder( string message, params object[] args )
{
StringBuilder sbOutput;
if (message != null)
{
if ( args != null && args.Length > 0 )
sbOutput = new StringBuilder( string.Format( message, args ) );
else
sbOutput = new StringBuilder( message );
}
else
{
sbOutput = new StringBuilder();
}
return sbOutput;
}
///
/// Called to create a message 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.
///
///
///
///
///
///
static public string FormatMessageForFailNotEquals(Object expected, Object actual,
string message, params object[] args)
{
StringBuilder sbOutput = CreateStringBuilder( message, args );
if( null != message )
{
if( message.Length > 0 )
{
sbOutput.Append( " " );
}
}
if( InputsAreStrings( expected, actual ) )
{
BuildStringsDifferentMessage(
sbOutput,
(string)expected,
(string)actual );
}
else
{
AppendExpectedAndActual( sbOutput, expected, actual );
}
return sbOutput.ToString();
}
///
/// Called to create a message when two arrays are not equal.
///
///
///
///
///
static public string FormatMessageForFailArraysNotEqual(int index, Array expected, Array actual,
string message, params object[] args)
{
StringBuilder sbOutput = CreateStringBuilder( message, args );
if( null != message )
{
if( message.Length > 0 )
{
sbOutput.Append( " " );
}
}
BuildArraysDifferentMessage(
sbOutput,
index,
expected,
actual );
return sbOutput.ToString();
}
}
}