1 // ****************************************************************
\r
2 // Copyright 2007, Charlie Poole
\r
3 // This is free software licensed under the NUnit license. You may
\r
4 // obtain a copy of the license at http://nunit.org/?p=license&r=2.4
\r
5 // ****************************************************************
\r
10 using System.Collections;
\r
11 using System.Globalization;
\r
12 using NUnit.Framework.Constraints;
\r
14 namespace NUnit.Framework
\r
17 /// TextMessageWriter writes constraint descriptions and messages
\r
18 /// in displayable form as a text stream. It tailors the display
\r
19 /// of individual message components to form the standard message
\r
20 /// format of NUnit assertion failure messages.
\r
22 public class TextMessageWriter : MessageWriter
\r
24 #region Message Formats and Constants
\r
25 private static readonly int DEFAULT_LINE_LENGTH = 78;
\r
27 // Prefixes used in all failure messages. All must be the same
\r
28 // length, which is held in the PrefixLength field. Should not
\r
29 // contain any tabs or newline characters.
\r
31 /// Prefix used for the expected value line of a message
\r
33 public static readonly string Pfx_Expected = " Expected: ";
\r
35 /// Prefix used for the actual value line of a message
\r
37 public static readonly string Pfx_Actual = " But was: ";
\r
39 /// Length of a message prefix
\r
41 public static readonly int PrefixLength = Pfx_Expected.Length;
\r
43 private static readonly string Fmt_Connector = " {0} ";
\r
44 private static readonly string Fmt_Predicate = "{0} ";
\r
45 //private static readonly string Fmt_Label = "{0}";
\r
46 private static readonly string Fmt_Modifier = ", {0}";
\r
48 private static readonly string Fmt_Null = "null";
\r
49 private static readonly string Fmt_EmptyString = "<string.Empty>";
\r
50 private static readonly string Fmt_EmptyCollection = "<empty>";
\r
52 private static readonly string Fmt_String = "\"{0}\"";
\r
53 private static readonly string Fmt_Char = "'{0}'";
\r
54 private static readonly string Fmt_DateTime = "yyyy-MM-dd HH:mm:ss.fff";
\r
55 private static readonly string Fmt_ValueType = "{0}";
\r
56 private static readonly string Fmt_Default = "<{0}>";
\r
59 private int maxLineLength = DEFAULT_LINE_LENGTH;
\r
61 #region Constructors
\r
63 /// Construct a TextMessageWriter
\r
65 public TextMessageWriter() { }
\r
68 /// Construct a TextMessageWriter, specifying a user message
\r
69 /// and optional formatting arguments.
\r
71 /// <param name="userMessage"></param>
\r
72 /// <param name="args"></param>
\r
73 public TextMessageWriter(string userMessage, params object[] args)
\r
75 if ( userMessage != null && userMessage != string.Empty)
\r
76 this.WriteMessageLine(userMessage, args);
\r
82 /// Gets or sets the maximum line length for this writer
\r
84 public override int MaxLineLength
\r
86 get { return maxLineLength; }
\r
87 set { maxLineLength = value; }
\r
91 #region Public Methods - High Level
\r
93 /// Method to write single line message with optional args, usually
\r
94 /// written to precede the general failure message, at a givel
\r
95 /// indentation level.
\r
97 /// <param name="level">The indentation level of the message</param>
\r
98 /// <param name="message">The message to be written</param>
\r
99 /// <param name="args">Any arguments used in formatting the message</param>
\r
100 public override void WriteMessageLine(int level, string message, params object[] args)
\r
102 if (message != null)
\r
104 while (level-- >= 0) Write(" ");
\r
106 if (args != null && args.Length > 0)
\r
107 message = string.Format(message, args);
\r
109 WriteLine(message);
\r
114 /// Display Expected and Actual lines for a constraint. This
\r
115 /// is called by MessageWriter's default implementation of
\r
116 /// WriteMessageTo and provides the generic two-line display.
\r
118 /// <param name="constraint">The constraint that failed</param>
\r
119 public override void DisplayDifferences(Constraint constraint)
\r
121 WriteExpectedLine(constraint);
\r
122 WriteActualLine(constraint);
\r
126 /// Display Expected and Actual lines for given values. This
\r
127 /// method may be called by constraints that need more control over
\r
128 /// the display of actual and expected values than is provided
\r
129 /// by the default implementation.
\r
131 /// <param name="expected">The expected value</param>
\r
132 /// <param name="actual">The actual value causing the failure</param>
\r
133 public override void DisplayDifferences(object expected, object actual)
\r
135 WriteExpectedLine(expected);
\r
136 WriteActualLine(actual);
\r
140 /// Display Expected and Actual lines for given values, including
\r
141 /// a tolerance value on the expected line.
\r
143 /// <param name="expected">The expected value</param>
\r
144 /// <param name="actual">The actual value causing the failure</param>
\r
145 /// <param name="tolerance">The tolerance within which the test was made</param>
\r
146 public override void DisplayDifferences(object expected, object actual, object tolerance)
\r
148 WriteExpectedLine(expected, tolerance);
\r
149 WriteActualLine(actual);
\r
153 /// Display the expected and actual string values on separate lines.
\r
154 /// If the mismatch parameter is >=0, an additional line is displayed
\r
155 /// line containing a caret that points to the mismatch point.
\r
157 /// <param name="expected">The expected string value</param>
\r
158 /// <param name="actual">The actual string value</param>
\r
159 /// <param name="mismatch">The point at which the strings don't match or -1</param>
\r
160 /// <param name="ignoreCase">If true, case is ignored in string comparisons</param>
\r
161 /// <param name="clipping">If true, clip the strings to fit the max line length</param>
\r
162 public override void DisplayStringDifferences(string expected, string actual, int mismatch, bool ignoreCase, bool clipping)
\r
164 // Maximum string we can display without truncating
\r
165 int maxDisplayLength = MaxLineLength
\r
166 - PrefixLength // Allow for prefix
\r
167 - 2; // 2 quotation marks
\r
170 MsgUtils.ClipExpectedAndActual(ref expected, ref actual, maxDisplayLength, mismatch);
\r
172 expected = MsgUtils.ConvertWhitespace(expected);
\r
173 actual = MsgUtils.ConvertWhitespace(actual);
\r
175 // The mismatch position may have changed due to clipping or white space conversion
\r
176 mismatch = MsgUtils.FindMismatchPosition(expected, actual, 0, ignoreCase);
\r
178 Write( Pfx_Expected );
\r
179 WriteExpectedValue( expected );
\r
181 WriteModifier( "ignoring case" );
\r
183 WriteActualLine( actual );
\r
184 //DisplayDifferences(expected, actual);
\r
186 WriteCaretLine(mismatch);
\r
190 #region Public Methods - Low Level
\r
192 /// Writes the text for a connector.
\r
194 /// <param name="connector">The connector.</param>
\r
195 public override void WriteConnector(string connector)
\r
197 Write(Fmt_Connector, connector);
\r
201 /// Writes the text for a predicate.
\r
203 /// <param name="predicate">The predicate.</param>
\r
204 public override void WritePredicate(string predicate)
\r
206 Write(Fmt_Predicate, predicate);
\r
209 //public override void WriteLabel(string label)
\r
211 // Write(Fmt_Label, label);
\r
215 /// Write the text for a modifier.
\r
217 /// <param name="modifier">The modifier.</param>
\r
218 public override void WriteModifier(string modifier)
\r
220 Write(Fmt_Modifier, modifier);
\r
225 /// Writes the text for an expected value.
\r
227 /// <param name="expected">The expected value.</param>
\r
228 public override void WriteExpectedValue(object expected)
\r
230 WriteValue(expected);
\r
234 /// Writes the text for an actual value.
\r
236 /// <param name="actual">The actual value.</param>
\r
237 public override void WriteActualValue(object actual)
\r
239 WriteValue(actual);
\r
243 /// Writes the text for a generalized value.
\r
245 /// <param name="val">The value.</param>
\r
246 public override void WriteValue(object val)
\r
250 else if (val.GetType().IsArray)
\r
251 WriteArray((Array)val);
\r
252 else if (val is ICollection)
\r
253 WriteCollectionElements((ICollection)val, 0, 10);
\r
254 else if (val is string)
\r
255 WriteString((string)val);
\r
256 else if (val is char)
\r
257 WriteChar((char)val);
\r
258 else if (val is double)
\r
259 WriteDouble((double)val);
\r
260 else if (val is float)
\r
261 WriteFloat((float)val);
\r
262 else if (val is decimal)
\r
263 WriteDecimal((decimal)val);
\r
264 else if (val is DateTime)
\r
265 WriteDateTime((DateTime)val);
\r
266 else if (val.GetType().IsValueType)
\r
267 Write(Fmt_ValueType, val);
\r
269 Write(Fmt_Default, val);
\r
273 /// Writes the text for a collection value,
\r
274 /// starting at a particular point, to a max length
\r
276 /// <param name="collection">The collection containing elements to write.</param>
\r
277 /// <param name="start">The starting point of the elements to write</param>
\r
278 /// <param name="max">The maximum number of elements to write</param>
\r
279 public override void WriteCollectionElements(ICollection collection, int start, int max)
\r
281 if ( collection.Count == 0 )
\r
283 Write(Fmt_EmptyCollection);
\r
291 foreach (object obj in collection)
\r
293 if ( index++ >= start )
\r
298 if ( ++count >= max )
\r
303 if ( index < collection.Count )
\r
309 private void WriteArray(Array array)
\r
311 if ( array.Length == 0 )
\r
313 Write( Fmt_EmptyCollection );
\r
317 int rank = array.Rank;
\r
318 int[] products = new int[rank];
\r
320 for (int product = 1, r = rank; --r >= 0; )
\r
321 products[r] = product *= array.GetLength(r);
\r
324 foreach (object obj in array)
\r
329 bool startSegment = false;
\r
330 for (int r = 0; r < rank; r++)
\r
332 startSegment = startSegment || count % products[r] == 0;
\r
333 if (startSegment) Write("< ");
\r
340 bool nextSegment = false;
\r
341 for (int r = 0; r < rank; r++)
\r
343 nextSegment = nextSegment || count % products[r] == 0;
\r
344 if (nextSegment) Write(" >");
\r
349 private void WriteString(string s)
\r
351 if (s == string.Empty)
\r
352 Write(Fmt_EmptyString);
\r
354 Write(Fmt_String, s);
\r
357 private void WriteChar(char c)
\r
359 Write(Fmt_Char, c);
\r
362 private void WriteDouble(double d)
\r
365 if (double.IsNaN(d) || double.IsInfinity(d))
\r
369 string s = d.ToString("G17", CultureInfo.InvariantCulture);
\r
371 if (s.IndexOf('.') > 0)
\r
378 private void WriteFloat(float f)
\r
380 if (float.IsNaN(f) || float.IsInfinity(f))
\r
384 string s = f.ToString("G9", CultureInfo.InvariantCulture);
\r
386 if (s.IndexOf('.') > 0)
\r
393 private void WriteDecimal(Decimal d)
\r
395 Write(d.ToString("G29", CultureInfo.InvariantCulture) + "m");
\r
398 private void WriteDateTime(DateTime dt)
\r
400 Write(dt.ToString(Fmt_DateTime, CultureInfo.InvariantCulture));
\r
404 #region Helper Methods
\r
406 /// Write the generic 'Expected' line for a constraint
\r
408 /// <param name="constraint">The constraint that failed</param>
\r
409 private void WriteExpectedLine(Constraint constraint)
\r
411 Write(Pfx_Expected);
\r
412 constraint.WriteDescriptionTo(this);
\r
417 /// Write the generic 'Expected' line for a given value
\r
419 /// <param name="expected">The expected value</param>
\r
420 private void WriteExpectedLine(object expected)
\r
422 WriteExpectedLine(expected, null);
\r
426 /// Write the generic 'Expected' line for a given value
\r
429 /// <param name="expected">The expected value</param>
\r
430 /// <param name="tolerance">The tolerance within which the test was made</param>
\r
431 private void WriteExpectedLine(object expected, object tolerance)
\r
433 Write(Pfx_Expected);
\r
434 WriteExpectedValue(expected);
\r
436 if (tolerance != null)
\r
438 WriteConnector("+/-");
\r
439 WriteExpectedValue(tolerance);
\r
446 /// Write the generic 'Actual' line for a constraint
\r
448 /// <param name="constraint">The constraint for which the actual value is to be written</param>
\r
449 private void WriteActualLine(Constraint constraint)
\r
452 constraint.WriteActualValueTo(this);
\r
457 /// Write the generic 'Actual' line for a given value
\r
459 /// <param name="actual">The actual value causing a failure</param>
\r
460 private void WriteActualLine(object actual)
\r
463 WriteActualValue(actual);
\r
467 private void WriteCaretLine(int mismatch)
\r
469 // We subtract 2 for the initial 2 blanks and add back 1 for the initial quote
\r
470 WriteLine(" {0}^", new string('-', PrefixLength + mismatch - 2 + 1));
\r