a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
475 lines
16 KiB
C#
475 lines
16 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.Text;
|
|
using System.Collections;
|
|
using System.Globalization;
|
|
using NUnit.Framework.Constraints;
|
|
|
|
namespace NUnit.Framework
|
|
{
|
|
/// <summary>
|
|
/// TextMessageWriter writes constraint descriptions and messages
|
|
/// in displayable form as a text stream. It tailors the display
|
|
/// of individual message components to form the standard message
|
|
/// format of NUnit assertion failure messages.
|
|
/// </summary>
|
|
public class TextMessageWriter : MessageWriter
|
|
{
|
|
#region Message Formats and Constants
|
|
private static readonly int DEFAULT_LINE_LENGTH = 78;
|
|
|
|
// Prefixes used in all failure messages. All must be the same
|
|
// length, which is held in the PrefixLength field. Should not
|
|
// contain any tabs or newline characters.
|
|
/// <summary>
|
|
/// Prefix used for the expected value line of a message
|
|
/// </summary>
|
|
public static readonly string Pfx_Expected = " Expected: ";
|
|
/// <summary>
|
|
/// Prefix used for the actual value line of a message
|
|
/// </summary>
|
|
public static readonly string Pfx_Actual = " But was: ";
|
|
/// <summary>
|
|
/// Length of a message prefix
|
|
/// </summary>
|
|
public static readonly int PrefixLength = Pfx_Expected.Length;
|
|
|
|
private static readonly string Fmt_Connector = " {0} ";
|
|
private static readonly string Fmt_Predicate = "{0} ";
|
|
//private static readonly string Fmt_Label = "{0}";
|
|
private static readonly string Fmt_Modifier = ", {0}";
|
|
|
|
private static readonly string Fmt_Null = "null";
|
|
private static readonly string Fmt_EmptyString = "<string.Empty>";
|
|
private static readonly string Fmt_EmptyCollection = "<empty>";
|
|
|
|
private static readonly string Fmt_String = "\"{0}\"";
|
|
private static readonly string Fmt_Char = "'{0}'";
|
|
private static readonly string Fmt_DateTime = "yyyy-MM-dd HH:mm:ss.fff";
|
|
private static readonly string Fmt_ValueType = "{0}";
|
|
private static readonly string Fmt_Default = "<{0}>";
|
|
#endregion
|
|
|
|
private int maxLineLength = DEFAULT_LINE_LENGTH;
|
|
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Construct a TextMessageWriter
|
|
/// </summary>
|
|
public TextMessageWriter() { }
|
|
|
|
/// <summary>
|
|
/// Construct a TextMessageWriter, specifying a user message
|
|
/// and optional formatting arguments.
|
|
/// </summary>
|
|
/// <param name="userMessage"></param>
|
|
/// <param name="args"></param>
|
|
public TextMessageWriter(string userMessage, params object[] args)
|
|
{
|
|
if ( userMessage != null && userMessage != string.Empty)
|
|
this.WriteMessageLine(userMessage, args);
|
|
}
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>
|
|
/// Gets or sets the maximum line length for this writer
|
|
/// </summary>
|
|
public override int MaxLineLength
|
|
{
|
|
get { return maxLineLength; }
|
|
set { maxLineLength = value; }
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods - High Level
|
|
/// <summary>
|
|
/// Method to write single line message with optional args, usually
|
|
/// written to precede the general failure message, at a givel
|
|
/// indentation level.
|
|
/// </summary>
|
|
/// <param name="level">The indentation level of the message</param>
|
|
/// <param name="message">The message to be written</param>
|
|
/// <param name="args">Any arguments used in formatting the message</param>
|
|
public override void WriteMessageLine(int level, string message, params object[] args)
|
|
{
|
|
if (message != null)
|
|
{
|
|
while (level-- >= 0) Write(" ");
|
|
|
|
if (args != null && args.Length > 0)
|
|
message = string.Format(message, args);
|
|
|
|
WriteLine(message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display Expected and Actual lines for a constraint. This
|
|
/// is called by MessageWriter's default implementation of
|
|
/// WriteMessageTo and provides the generic two-line display.
|
|
/// </summary>
|
|
/// <param name="constraint">The constraint that failed</param>
|
|
public override void DisplayDifferences(Constraint constraint)
|
|
{
|
|
WriteExpectedLine(constraint);
|
|
WriteActualLine(constraint);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display Expected and Actual lines for given values. This
|
|
/// method may be called by constraints that need more control over
|
|
/// the display of actual and expected values than is provided
|
|
/// by the default implementation.
|
|
/// </summary>
|
|
/// <param name="expected">The expected value</param>
|
|
/// <param name="actual">The actual value causing the failure</param>
|
|
public override void DisplayDifferences(object expected, object actual)
|
|
{
|
|
WriteExpectedLine(expected);
|
|
WriteActualLine(actual);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display Expected and Actual lines for given values, including
|
|
/// a tolerance value on the expected line.
|
|
/// </summary>
|
|
/// <param name="expected">The expected value</param>
|
|
/// <param name="actual">The actual value causing the failure</param>
|
|
/// <param name="tolerance">The tolerance within which the test was made</param>
|
|
public override void DisplayDifferences(object expected, object actual, object tolerance)
|
|
{
|
|
WriteExpectedLine(expected, tolerance);
|
|
WriteActualLine(actual);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display the expected and actual string values on separate lines.
|
|
/// If the mismatch parameter is >=0, an additional line is displayed
|
|
/// line containing a caret that points to the mismatch point.
|
|
/// </summary>
|
|
/// <param name="expected">The expected string value</param>
|
|
/// <param name="actual">The actual string value</param>
|
|
/// <param name="mismatch">The point at which the strings don't match or -1</param>
|
|
/// <param name="ignoreCase">If true, case is ignored in string comparisons</param>
|
|
/// <param name="clipping">If true, clip the strings to fit the max line length</param>
|
|
public override void DisplayStringDifferences(string expected, string actual, int mismatch, bool ignoreCase, bool clipping)
|
|
{
|
|
// Maximum string we can display without truncating
|
|
int maxDisplayLength = MaxLineLength
|
|
- PrefixLength // Allow for prefix
|
|
- 2; // 2 quotation marks
|
|
|
|
if ( clipping )
|
|
MsgUtils.ClipExpectedAndActual(ref expected, ref actual, maxDisplayLength, mismatch);
|
|
|
|
expected = MsgUtils.ConvertWhitespace(expected);
|
|
actual = MsgUtils.ConvertWhitespace(actual);
|
|
|
|
// The mismatch position may have changed due to clipping or white space conversion
|
|
mismatch = MsgUtils.FindMismatchPosition(expected, actual, 0, ignoreCase);
|
|
|
|
Write( Pfx_Expected );
|
|
WriteExpectedValue( expected );
|
|
if ( ignoreCase )
|
|
WriteModifier( "ignoring case" );
|
|
WriteLine();
|
|
WriteActualLine( actual );
|
|
//DisplayDifferences(expected, actual);
|
|
if (mismatch >= 0)
|
|
WriteCaretLine(mismatch);
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods - Low Level
|
|
/// <summary>
|
|
/// Writes the text for a connector.
|
|
/// </summary>
|
|
/// <param name="connector">The connector.</param>
|
|
public override void WriteConnector(string connector)
|
|
{
|
|
Write(Fmt_Connector, connector);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the text for a predicate.
|
|
/// </summary>
|
|
/// <param name="predicate">The predicate.</param>
|
|
public override void WritePredicate(string predicate)
|
|
{
|
|
Write(Fmt_Predicate, predicate);
|
|
}
|
|
|
|
//public override void WriteLabel(string label)
|
|
//{
|
|
// Write(Fmt_Label, label);
|
|
//}
|
|
|
|
/// <summary>
|
|
/// Write the text for a modifier.
|
|
/// </summary>
|
|
/// <param name="modifier">The modifier.</param>
|
|
public override void WriteModifier(string modifier)
|
|
{
|
|
Write(Fmt_Modifier, modifier);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Writes the text for an expected value.
|
|
/// </summary>
|
|
/// <param name="expected">The expected value.</param>
|
|
public override void WriteExpectedValue(object expected)
|
|
{
|
|
WriteValue(expected);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the text for an actual value.
|
|
/// </summary>
|
|
/// <param name="actual">The actual value.</param>
|
|
public override void WriteActualValue(object actual)
|
|
{
|
|
WriteValue(actual);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the text for a generalized value.
|
|
/// </summary>
|
|
/// <param name="val">The value.</param>
|
|
public override void WriteValue(object val)
|
|
{
|
|
if (val == null)
|
|
Write(Fmt_Null);
|
|
else if (val.GetType().IsArray)
|
|
WriteArray((Array)val);
|
|
else if (val is ICollection)
|
|
WriteCollectionElements((ICollection)val, 0, 10);
|
|
else if (val is string)
|
|
WriteString((string)val);
|
|
else if (val is char)
|
|
WriteChar((char)val);
|
|
else if (val is double)
|
|
WriteDouble((double)val);
|
|
else if (val is float)
|
|
WriteFloat((float)val);
|
|
else if (val is decimal)
|
|
WriteDecimal((decimal)val);
|
|
else if (val is DateTime)
|
|
WriteDateTime((DateTime)val);
|
|
else if (val.GetType().IsValueType)
|
|
Write(Fmt_ValueType, val);
|
|
else
|
|
Write(Fmt_Default, val);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the text for a collection value,
|
|
/// starting at a particular point, to a max length
|
|
/// </summary>
|
|
/// <param name="collection">The collection containing elements to write.</param>
|
|
/// <param name="start">The starting point of the elements to write</param>
|
|
/// <param name="max">The maximum number of elements to write</param>
|
|
public override void WriteCollectionElements(ICollection collection, int start, int max)
|
|
{
|
|
if ( collection.Count == 0 )
|
|
{
|
|
Write(Fmt_EmptyCollection);
|
|
return;
|
|
}
|
|
|
|
int count = 0;
|
|
int index = 0;
|
|
Write("< ");
|
|
|
|
foreach (object obj in collection)
|
|
{
|
|
if ( index++ >= start )
|
|
{
|
|
if (count > 0)
|
|
Write(", ");
|
|
WriteValue(obj);
|
|
if ( ++count >= max )
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( index < collection.Count )
|
|
Write("...");
|
|
|
|
Write(" >");
|
|
}
|
|
|
|
private void WriteArray(Array array)
|
|
{
|
|
if ( array.Length == 0 )
|
|
{
|
|
Write( Fmt_EmptyCollection );
|
|
return;
|
|
}
|
|
|
|
int rank = array.Rank;
|
|
int[] products = new int[rank];
|
|
|
|
for (int product = 1, r = rank; --r >= 0; )
|
|
products[r] = product *= array.GetLength(r);
|
|
|
|
int count = 0;
|
|
foreach (object obj in array)
|
|
{
|
|
if (count > 0)
|
|
Write(", ");
|
|
|
|
bool startSegment = false;
|
|
for (int r = 0; r < rank; r++)
|
|
{
|
|
startSegment = startSegment || count % products[r] == 0;
|
|
if (startSegment) Write("< ");
|
|
}
|
|
|
|
WriteValue(obj);
|
|
|
|
++count;
|
|
|
|
bool nextSegment = false;
|
|
for (int r = 0; r < rank; r++)
|
|
{
|
|
nextSegment = nextSegment || count % products[r] == 0;
|
|
if (nextSegment) Write(" >");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void WriteString(string s)
|
|
{
|
|
if (s == string.Empty)
|
|
Write(Fmt_EmptyString);
|
|
else
|
|
Write(Fmt_String, s);
|
|
}
|
|
|
|
private void WriteChar(char c)
|
|
{
|
|
Write(Fmt_Char, c);
|
|
}
|
|
|
|
private void WriteDouble(double d)
|
|
{
|
|
|
|
if (double.IsNaN(d) || double.IsInfinity(d))
|
|
Write(d);
|
|
else
|
|
{
|
|
string s = d.ToString("G17", CultureInfo.InvariantCulture);
|
|
|
|
if (s.IndexOf('.') > 0)
|
|
Write(s + "d");
|
|
else
|
|
Write(s + ".0d");
|
|
}
|
|
}
|
|
|
|
private void WriteFloat(float f)
|
|
{
|
|
if (float.IsNaN(f) || float.IsInfinity(f))
|
|
Write(f);
|
|
else
|
|
{
|
|
string s = f.ToString("G9", CultureInfo.InvariantCulture);
|
|
|
|
if (s.IndexOf('.') > 0)
|
|
Write(s + "f");
|
|
else
|
|
Write(s + ".0f");
|
|
}
|
|
}
|
|
|
|
private void WriteDecimal(Decimal d)
|
|
{
|
|
Write(d.ToString("G29", CultureInfo.InvariantCulture) + "m");
|
|
}
|
|
|
|
private void WriteDateTime(DateTime dt)
|
|
{
|
|
Write(dt.ToString(Fmt_DateTime, CultureInfo.InvariantCulture));
|
|
}
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
/// <summary>
|
|
/// Write the generic 'Expected' line for a constraint
|
|
/// </summary>
|
|
/// <param name="constraint">The constraint that failed</param>
|
|
private void WriteExpectedLine(Constraint constraint)
|
|
{
|
|
Write(Pfx_Expected);
|
|
constraint.WriteDescriptionTo(this);
|
|
WriteLine();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write the generic 'Expected' line for a given value
|
|
/// </summary>
|
|
/// <param name="expected">The expected value</param>
|
|
private void WriteExpectedLine(object expected)
|
|
{
|
|
WriteExpectedLine(expected, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write the generic 'Expected' line for a given value
|
|
/// and tolerance.
|
|
/// </summary>
|
|
/// <param name="expected">The expected value</param>
|
|
/// <param name="tolerance">The tolerance within which the test was made</param>
|
|
private void WriteExpectedLine(object expected, object tolerance)
|
|
{
|
|
Write(Pfx_Expected);
|
|
WriteExpectedValue(expected);
|
|
|
|
if (tolerance != null)
|
|
{
|
|
WriteConnector("+/-");
|
|
WriteExpectedValue(tolerance);
|
|
}
|
|
|
|
WriteLine();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write the generic 'Actual' line for a constraint
|
|
/// </summary>
|
|
/// <param name="constraint">The constraint for which the actual value is to be written</param>
|
|
private void WriteActualLine(Constraint constraint)
|
|
{
|
|
Write(Pfx_Actual);
|
|
constraint.WriteActualValueTo(this);
|
|
WriteLine();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write the generic 'Actual' line for a given value
|
|
/// </summary>
|
|
/// <param name="actual">The actual value causing a failure</param>
|
|
private void WriteActualLine(object actual)
|
|
{
|
|
Write(Pfx_Actual);
|
|
WriteActualValue(actual);
|
|
WriteLine();
|
|
}
|
|
|
|
private void WriteCaretLine(int mismatch)
|
|
{
|
|
// We subtract 2 for the initial 2 blanks and add back 1 for the initial quote
|
|
WriteLine(" {0}^", new string('-', PrefixLength + mismatch - 2 + 1));
|
|
}
|
|
#endregion
|
|
}
|
|
}
|