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

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
}
}