1 // ****************************************************************
2 // Copyright 2007, Charlie Poole
3 // This is free software licensed under the NUnit license. You may
4 // obtain a copy of the license at http://nunit.org/?p=license&r=2.4
5 // ****************************************************************
6 
7 using System;
8 using System.IO;
9 using System.Text;
10 using System.Collections;
11 using System.Globalization;
12 using NUnit.Framework.Constraints;
13 
14 namespace NUnit.Framework
15 {
16 	/// <summary>
17 	/// TextMessageWriter writes constraint descriptions and messages
18 	/// in displayable form as a text stream. It tailors the display
19 	/// of individual message components to form the standard message
20 	/// format of NUnit assertion failure messages.
21 	/// </summary>
22     public class TextMessageWriter : MessageWriter
23     {
24         #region Message Formats and Constants
25         private static readonly int DEFAULT_LINE_LENGTH = 78;
26 
27 		// Prefixes used in all failure messages. All must be the same
28 		// length, which is held in the PrefixLength field. Should not
29 		// contain any tabs or newline characters.
30 		/// <summary>
31 		/// Prefix used for the expected value line of a message
32 		/// </summary>
33 		public static readonly string Pfx_Expected = "  Expected: ";
34 		/// <summary>
35 		/// Prefix used for the actual value line of a message
36 		/// </summary>
37 		public static readonly string Pfx_Actual = "  But was:  ";
38 		/// <summary>
39 		/// Length of a message prefix
40 		/// </summary>
41 		public static readonly int PrefixLength = Pfx_Expected.Length;
42 
43 		private static readonly string Fmt_Connector = " {0} ";
44         private static readonly string Fmt_Predicate = "{0} ";
45         //private static readonly string Fmt_Label = "{0}";
46 		private static readonly string Fmt_Modifier = ", {0}";
47 
48         private static readonly string Fmt_Null = "null";
49         private static readonly string Fmt_EmptyString = "<string.Empty>";
50         private static readonly string Fmt_EmptyCollection = "<empty>";
51 
52         private static readonly string Fmt_String = "\"{0}\"";
53         private static readonly string Fmt_Char = "'{0}'";
54 		private static readonly string Fmt_DateTime = "yyyy-MM-dd HH:mm:ss.fff";
55         private static readonly string Fmt_ValueType = "{0}";
56         private static readonly string Fmt_Default = "<{0}>";
57         #endregion
58 
59 		private int maxLineLength = DEFAULT_LINE_LENGTH;
60 
61         #region Constructors
62 		/// <summary>
63 		/// Construct a TextMessageWriter
64 		/// </summary>
TextMessageWriter()65         public TextMessageWriter() { }
66 
67         /// <summary>
68         /// Construct a TextMessageWriter, specifying a user message
69         /// and optional formatting arguments.
70         /// </summary>
71         /// <param name="userMessage"></param>
72         /// <param name="args"></param>
TextMessageWriter(string userMessage, params object[] args)73 		public TextMessageWriter(string userMessage, params object[] args)
74         {
75 			if ( userMessage != null && userMessage != string.Empty)
76 				this.WriteMessageLine(userMessage, args);
77         }
78         #endregion
79 
80         #region Properties
81         /// <summary>
82         /// Gets or sets the maximum line length for this writer
83         /// </summary>
84         public override int MaxLineLength
85         {
86             get { return maxLineLength; }
87             set { maxLineLength = value; }
88         }
89         #endregion
90 
91         #region Public Methods - High Level
92         /// <summary>
93         /// Method to write single line  message with optional args, usually
94         /// written to precede the general failure message, at a givel
95         /// indentation level.
96         /// </summary>
97         /// <param name="level">The indentation level of the message</param>
98         /// <param name="message">The message to be written</param>
99         /// <param name="args">Any arguments used in formatting the message</param>
WriteMessageLine(int level, string message, params object[] args)100         public override void WriteMessageLine(int level, string message, params object[] args)
101         {
102             if (message != null)
103             {
104                 while (level-- >= 0) Write("  ");
105 
106                 if (args != null && args.Length > 0)
107                     message = string.Format(message, args);
108 
109                 WriteLine(message);
110             }
111         }
112 
113         /// <summary>
114         /// Display Expected and Actual lines for a constraint. This
115         /// is called by MessageWriter's default implementation of
116         /// WriteMessageTo and provides the generic two-line display.
117         /// </summary>
118         /// <param name="constraint">The constraint that failed</param>
DisplayDifferences(Constraint constraint)119         public override void DisplayDifferences(Constraint constraint)
120         {
121             WriteExpectedLine(constraint);
122             WriteActualLine(constraint);
123         }
124 
125 		/// <summary>
126 		/// Display Expected and Actual lines for given values. This
127 		/// method may be called by constraints that need more control over
128 		/// the display of actual and expected values than is provided
129 		/// by the default implementation.
130 		/// </summary>
131 		/// <param name="expected">The expected value</param>
132 		/// <param name="actual">The actual value causing the failure</param>
DisplayDifferences(object expected, object actual)133 		public override void DisplayDifferences(object expected, object actual)
134 		{
135 			WriteExpectedLine(expected);
136 			WriteActualLine(actual);
137 		}
138 
139 		/// <summary>
140 		/// Display Expected and Actual lines for given values, including
141 		/// a tolerance value on the expected line.
142 		/// </summary>
143 		/// <param name="expected">The expected value</param>
144 		/// <param name="actual">The actual value causing the failure</param>
145 		/// <param name="tolerance">The tolerance within which the test was made</param>
DisplayDifferences(object expected, object actual, object tolerance)146 		public override void DisplayDifferences(object expected, object actual, object tolerance)
147 		{
148 			WriteExpectedLine(expected, tolerance);
149 			WriteActualLine(actual);
150 		}
151 
152 		/// <summary>
153         /// Display the expected and actual string values on separate lines.
154         /// If the mismatch parameter is >=0, an additional line is displayed
155         /// line containing a caret that points to the mismatch point.
156         /// </summary>
157         /// <param name="expected">The expected string value</param>
158         /// <param name="actual">The actual string value</param>
159         /// <param name="mismatch">The point at which the strings don't match or -1</param>
160         /// <param name="ignoreCase">If true, case is ignored in string comparisons</param>
161         /// <param name="clipping">If true, clip the strings to fit the max line length</param>
DisplayStringDifferences(string expected, string actual, int mismatch, bool ignoreCase, bool clipping)162         public override void DisplayStringDifferences(string expected, string actual, int mismatch, bool ignoreCase, bool clipping)
163         {
164             // Maximum string we can display without truncating
165             int maxDisplayLength = MaxLineLength
166                 - PrefixLength   // Allow for prefix
167                 - 2;             // 2 quotation marks
168 
169             if ( clipping )
170                 MsgUtils.ClipExpectedAndActual(ref expected, ref actual, maxDisplayLength, mismatch);
171 
172             expected = MsgUtils.ConvertWhitespace(expected);
173             actual = MsgUtils.ConvertWhitespace(actual);
174 
175             // The mismatch position may have changed due to clipping or white space conversion
176             mismatch = MsgUtils.FindMismatchPosition(expected, actual, 0, ignoreCase);
177 
178 			Write( Pfx_Expected );
179 			WriteExpectedValue( expected );
180 			if ( ignoreCase )
181 				WriteModifier( "ignoring case" );
182 			WriteLine();
183 			WriteActualLine( actual );
184             //DisplayDifferences(expected, actual);
185             if (mismatch >= 0)
186                 WriteCaretLine(mismatch);
187         }
188         #endregion
189 
190         #region Public Methods - Low Level
191 		/// <summary>
192 		/// Writes the text for a connector.
193 		/// </summary>
194 		/// <param name="connector">The connector.</param>
WriteConnector(string connector)195 		public override void WriteConnector(string connector)
196         {
197             Write(Fmt_Connector, connector);
198         }
199 
200 		/// <summary>
201 		/// Writes the text for a predicate.
202 		/// </summary>
203 		/// <param name="predicate">The predicate.</param>
WritePredicate(string predicate)204 		public override void WritePredicate(string predicate)
205         {
206             Write(Fmt_Predicate, predicate);
207         }
208 
209         //public override void WriteLabel(string label)
210         //{
211         //    Write(Fmt_Label, label);
212         //}
213 
214         /// <summary>
215         /// Write the text for a modifier.
216         /// </summary>
217         /// <param name="modifier">The modifier.</param>
WriteModifier(string modifier)218 		public override void WriteModifier(string modifier)
219 		{
220 			Write(Fmt_Modifier, modifier);
221 		}
222 
223 
224 		/// <summary>
225 		/// Writes the text for an expected value.
226 		/// </summary>
227 		/// <param name="expected">The expected value.</param>
WriteExpectedValue(object expected)228 		public override void WriteExpectedValue(object expected)
229         {
230             WriteValue(expected);
231         }
232 
233 		/// <summary>
234 		/// Writes the text for an actual value.
235 		/// </summary>
236 		/// <param name="actual">The actual value.</param>
WriteActualValue(object actual)237 		public override void WriteActualValue(object actual)
238         {
239             WriteValue(actual);
240         }
241 
242 		/// <summary>
243 		/// Writes the text for a generalized value.
244 		/// </summary>
245 		/// <param name="val">The value.</param>
WriteValue(object val)246 		public override void WriteValue(object val)
247         {
248             if (val == null)
249                 Write(Fmt_Null);
250             else if (val.GetType().IsArray)
251                 WriteArray((Array)val);
252             else if (val is ICollection)
253                 WriteCollectionElements((ICollection)val, 0, 10);
254             else if (val is string)
255                 WriteString((string)val);
256             else if (val is char)
257                 WriteChar((char)val);
258             else if (val is double)
259                 WriteDouble((double)val);
260             else if (val is float)
261                 WriteFloat((float)val);
262             else if (val is decimal)
263                 WriteDecimal((decimal)val);
264 			else if (val is DateTime)
265 				WriteDateTime((DateTime)val);
266             else if (val.GetType().IsValueType)
267                 Write(Fmt_ValueType, val);
268             else
269                 Write(Fmt_Default, val);
270         }
271 
272         /// <summary>
273         /// Writes the text for a collection value,
274         /// starting at a particular point, to a max length
275         /// </summary>
276         /// <param name="collection">The collection containing elements to write.</param>
277         /// <param name="start">The starting point of the elements to write</param>
278         /// <param name="max">The maximum number of elements to write</param>
WriteCollectionElements(ICollection collection, int start, int max)279 		public override void WriteCollectionElements(ICollection collection, int start, int max)
280 		{
281 			if ( collection.Count == 0 )
282 			{
283 				Write(Fmt_EmptyCollection);
284 				return;
285 			}
286 
287 			int count = 0;
288 			int index = 0;
289 			Write("< ");
290 
291 			foreach (object obj in collection)
292 			{
293 				if ( index++ >= start )
294 				{
295 					if (count > 0)
296 						Write(", ");
297 					WriteValue(obj);
298 					if ( ++count >= max )
299 						break;
300 				}
301 			}
302 
303 			if ( index < collection.Count )
304 				Write("...");
305 
306 			Write(" >");
307 		}
308 
WriteArray(Array array)309 		private void WriteArray(Array array)
310         {
311 			if ( array.Length == 0 )
312 			{
313 				Write( Fmt_EmptyCollection );
314 				return;
315 			}
316 
317 			int rank = array.Rank;
318             int[] products = new int[rank];
319 
320             for (int product = 1, r = rank; --r >= 0; )
321                 products[r] = product *= array.GetLength(r);
322 
323             int count = 0;
324             foreach (object obj in array)
325             {
326                 if (count > 0)
327                     Write(", ");
328 
329                 bool startSegment = false;
330                 for (int r = 0; r < rank; r++)
331                 {
332                     startSegment = startSegment || count % products[r] == 0;
333                     if (startSegment) Write("< ");
334                 }
335 
336                 WriteValue(obj);
337 
338                 ++count;
339 
340                 bool nextSegment = false;
341                 for (int r = 0; r < rank; r++)
342                 {
343                     nextSegment = nextSegment || count % products[r] == 0;
344                     if (nextSegment) Write(" >");
345                 }
346             }
347         }
348 
WriteString(string s)349         private void WriteString(string s)
350         {
351             if (s == string.Empty)
352                 Write(Fmt_EmptyString);
353             else
354                 Write(Fmt_String, s);
355         }
356 
WriteChar(char c)357         private void WriteChar(char c)
358         {
359             Write(Fmt_Char, c);
360         }
361 
WriteDouble(double d)362         private void WriteDouble(double d)
363         {
364 
365             if (double.IsNaN(d) || double.IsInfinity(d))
366                 Write(d);
367             else
368             {
369                 string s = d.ToString("G17", CultureInfo.InvariantCulture);
370 
371                 if (s.IndexOf('.') > 0)
372                     Write(s + "d");
373                 else
374                     Write(s + ".0d");
375             }
376         }
377 
WriteFloat(float f)378         private void WriteFloat(float f)
379         {
380             if (float.IsNaN(f) || float.IsInfinity(f))
381                 Write(f);
382             else
383             {
384                 string s = f.ToString("G9", CultureInfo.InvariantCulture);
385 
386                 if (s.IndexOf('.') > 0)
387                     Write(s + "f");
388                 else
389                     Write(s + ".0f");
390             }
391         }
392 
WriteDecimal(Decimal d)393         private void WriteDecimal(Decimal d)
394         {
395             Write(d.ToString("G29", CultureInfo.InvariantCulture) + "m");
396         }
397 
WriteDateTime(DateTime dt)398 		private void WriteDateTime(DateTime dt)
399 		{
400 			Write(dt.ToString(Fmt_DateTime, CultureInfo.InvariantCulture));
401 		}
402         #endregion
403 
404         #region Helper Methods
405         /// <summary>
406         /// Write the generic 'Expected' line for a constraint
407         /// </summary>
408         /// <param name="constraint">The constraint that failed</param>
WriteExpectedLine(Constraint constraint)409         private void WriteExpectedLine(Constraint constraint)
410         {
411             Write(Pfx_Expected);
412             constraint.WriteDescriptionTo(this);
413             WriteLine();
414         }
415 
416 		/// <summary>
417 		/// Write the generic 'Expected' line for a given value
418 		/// </summary>
419 		/// <param name="expected">The expected value</param>
WriteExpectedLine(object expected)420 		private void WriteExpectedLine(object expected)
421 		{
422             WriteExpectedLine(expected, null);
423 		}
424 
425 		/// <summary>
426 		/// Write the generic 'Expected' line for a given value
427 		/// and tolerance.
428 		/// </summary>
429 		/// <param name="expected">The expected value</param>
430 		/// <param name="tolerance">The tolerance within which the test was made</param>
WriteExpectedLine(object expected, object tolerance)431 		private void WriteExpectedLine(object expected, object tolerance)
432 		{
433 			Write(Pfx_Expected);
434 			WriteExpectedValue(expected);
435 
436             if (tolerance != null)
437             {
438                 WriteConnector("+/-");
439                 WriteExpectedValue(tolerance);
440             }
441 
442 			WriteLine();
443 		}
444 
445 		/// <summary>
446 		/// Write the generic 'Actual' line for a constraint
447 		/// </summary>
448 		/// <param name="constraint">The constraint for which the actual value is to be written</param>
WriteActualLine(Constraint constraint)449 		private void WriteActualLine(Constraint constraint)
450 		{
451 			Write(Pfx_Actual);
452 			constraint.WriteActualValueTo(this);
453 			WriteLine();
454 		}
455 
456 		/// <summary>
457 		/// Write the generic 'Actual' line for a given value
458 		/// </summary>
459 		/// <param name="actual">The actual value causing a failure</param>
WriteActualLine(object actual)460 		private void WriteActualLine(object actual)
461 		{
462 			Write(Pfx_Actual);
463 			WriteActualValue(actual);
464 			WriteLine();
465 		}
466 
WriteCaretLine(int mismatch)467 		private void WriteCaretLine(int mismatch)
468         {
469             // We subtract 2 for the initial 2 blanks and add back 1 for the initial quote
470             WriteLine("  {0}^", new string('-', PrefixLength + mismatch - 2 + 1));
471         }
472         #endregion
473     }
474 }
475