Merge pull request #1410 from alesliehughes/master
[mono.git] / mcs / nunit24 / NUnitFramework / framework / AssertionFailureMessage.cs
1 // ****************************************************************\r
2 // This is free software licensed under the NUnit license. You\r
3 // may obtain a copy of the license as well as information regarding\r
4 // copyright ownership at http://nunit.org/?p=license&r=2.4.\r
5 // ****************************************************************\r
6 \r
7 using System;\r
8 using System.Text;\r
9 using System.IO;\r
10 using System.Collections;\r
11 \r
12 namespace NUnit.Framework\r
13 {\r
14         /// <summary>\r
15         /// AssertionFailureMessage encapsulates a failure message\r
16         /// issued as a result of an Assert failure.\r
17         /// </summary>\r
18         [Obsolete( "Use MessageWriter for new work" )]\r
19         public class AssertionFailureMessage : StringWriter\r
20         {\r
21                 #region Static Constants\r
22 \r
23                 /// <summary>\r
24                 /// Number of characters before a highlighted position before\r
25                 /// clipping will occur.  Clipped text is replaced with an\r
26                 /// elipsis "..."\r
27                 /// </summary>\r
28                 static public readonly int PreClipLength = 35;\r
29 \r
30                 /// <summary>\r
31                 /// Number of characters after a highlighted position before\r
32                 /// clipping will occur.  Clipped text is replaced with an\r
33                 /// elipsis "..."\r
34                 /// </summary>\r
35                 static public readonly int PostClipLength = 35;\r
36 \r
37                 /// <summary>\r
38                 /// Prefix used to start an expected value line.\r
39                 /// Must be same length as actualPrefix.\r
40                 /// </summary>\r
41                 static protected readonly string expectedPrefix = "expected:";\r
42                 \r
43                 /// <summary>\r
44                 /// Prefix used to start an actual value line.\r
45                 /// Must be same length as expectedPrefix.\r
46                 /// </summary>\r
47                 static protected readonly string actualPrefix   = " but was:";\r
48 \r
49                 static private readonly string expectedAndActualFmt = "\t{0} {1}";\r
50                 static private readonly string diffStringLengthsFmt \r
51                         = "\tString lengths differ.  Expected length={0}, but was length={1}.";\r
52                 static private readonly string sameStringLengthsFmt\r
53                         = "\tString lengths are both {0}.";\r
54                 static private readonly string diffArrayLengthsFmt\r
55                         = "Array lengths differ.  Expected length={0}, but was length={1}.";\r
56                 static private readonly string sameArrayLengthsFmt\r
57                         = "Array lengths are both {0}.";\r
58                 static private readonly string stringsDifferAtIndexFmt\r
59                         = "\tStrings differ at index {0}.";\r
60                 static private readonly string arraysDifferAtIndexFmt\r
61                         = "Arrays differ at index {0}.";\r
62 \r
63                 #endregion\r
64 \r
65                 #region Constructors\r
66 \r
67                 /// <summary>\r
68                 /// Construct an AssertionFailureMessage with a message\r
69                 /// and optional arguments.\r
70                 /// </summary>\r
71                 /// <param name="message"></param>\r
72                 /// <param name="args"></param>\r
73                 public AssertionFailureMessage( string message, params object[] args )\r
74                 {\r
75                         if ( message != null && message != string.Empty )\r
76                                 if ( args != null )\r
77                                         WriteLine( message, args );\r
78                                 else\r
79                                         WriteLine( message );\r
80                 }\r
81 \r
82                 /// <summary>\r
83                 /// Construct an empty AssertionFailureMessage\r
84                 /// </summary>\r
85                 public AssertionFailureMessage() : this( null, null ) { }\r
86 \r
87                 #endregion\r
88 \r
89                 /// <summary>\r
90                 /// Add an expected value line to the message containing\r
91                 /// the text provided as an argument.\r
92                 /// </summary>\r
93                 /// <param name="text">Text describing what was expected.</param>\r
94                 public void WriteExpectedLine( string text )\r
95                 {\r
96                         WriteLine( string.Format( expectedAndActualFmt, expectedPrefix, text ) );\r
97                 }\r
98 \r
99                 /// <summary>\r
100                 /// Add an actual value line to the message containing\r
101                 /// the text provided as an argument.\r
102                 /// </summary>\r
103                 /// <param name="text">Text describing the actual value.</param>\r
104                 public void WriteActualLine( string text )\r
105                 {\r
106                         WriteLine( string.Format( expectedAndActualFmt, actualPrefix, text ) );\r
107                 }\r
108 \r
109                 /// <summary>\r
110                 /// Add an expected value line to the message containing\r
111                 /// a string representation of the object provided.\r
112                 /// </summary>\r
113                 /// <param name="expected">An object representing the expected value</param>\r
114                 public void DisplayExpectedValue( object expected )\r
115                 {\r
116                         WriteExpectedLine( FormatObjectForDisplay( expected ) );\r
117                 }\r
118 \r
119                 /// <summary>\r
120                 /// Add an expected value line to the message containing a double\r
121                 /// and the tolerance used in making the comparison.\r
122                 /// </summary>\r
123                 /// <param name="expected">The expected value</param>\r
124                 /// <param name="tolerance">The tolerance specified in the Assert</param>\r
125                 public void DisplayExpectedValue( double expected, double tolerance )\r
126                 {\r
127                         WriteExpectedLine( FormatObjectForDisplay( expected ) + " +/- " + tolerance.ToString() );\r
128                 }\r
129 \r
130                 /// <summary>\r
131                 /// Add an actual value line to the message containing\r
132                 /// a string representation of the object provided.\r
133                 /// </summary>\r
134                 /// <param name="actual">An object representing what was actually found</param>\r
135                 public void DisplayActualValue( object actual )\r
136                 {\r
137                         WriteActualLine( FormatObjectForDisplay( actual ) );\r
138                 }\r
139 \r
140                 /// <summary>\r
141                 /// Display two lines that communicate the expected value, and the actual value\r
142                 /// </summary>\r
143                 /// <param name="expected">The expected value</param>\r
144                 /// <param name="actual">The actual value found</param>\r
145                 public void DisplayExpectedAndActual( Object expected, Object actual )\r
146                 {\r
147                         DisplayExpectedValue( expected );\r
148                         DisplayActualValue( actual );\r
149                 }\r
150 \r
151                 /// <summary>\r
152                 /// Display two lines that communicate the expected value, the actual value and\r
153                 /// the tolerance used in comparing two doubles.\r
154                 /// </summary>\r
155                 /// <param name="expected">The expected value</param>\r
156                 /// <param name="actual">The actual value found</param>\r
157                 /// <param name="tolerance">The tolerance specified in the Assert</param>\r
158                 public void DisplayExpectedAndActual( double expected, double actual, double tolerance )\r
159                 {\r
160                         DisplayExpectedValue( expected, tolerance );\r
161                         DisplayActualValue( actual );\r
162                 }\r
163 \r
164                 /// <summary>\r
165                 /// Draws a marker under the expected/actual strings that highlights\r
166                 /// where in the string a mismatch occurred.\r
167                 /// </summary>\r
168                 /// <param name="iPosition">The position of the mismatch</param>\r
169                 public void DisplayPositionMarker( int iPosition )\r
170                 {\r
171                         WriteLine( "\t{0}^", new String( '-', expectedPrefix.Length + iPosition + 3 ) );\r
172                 }\r
173 \r
174                 /// <summary>\r
175                 /// Reports whether the string lengths are the same or different, and\r
176                 /// what the string lengths are.\r
177                 /// </summary>\r
178                 /// <param name="sExpected">The expected string</param>\r
179                 /// <param name="sActual">The actual string value</param>\r
180                 protected void BuildStringLengthReport( string sExpected, string sActual )\r
181                 {\r
182                         if( sExpected.Length != sActual.Length )\r
183                                 WriteLine( diffStringLengthsFmt, sExpected.Length, sActual.Length );\r
184                         else\r
185                                 WriteLine( sameStringLengthsFmt, sExpected.Length );\r
186                 }\r
187 \r
188                 /// <summary>\r
189                 /// Called to create additional message lines when two objects have been \r
190                 /// found to be unequal.  If the inputs are strings, a special message is\r
191                 /// rendered that can help track down where the strings are different,\r
192                 /// based on differences in length, or differences in content.\r
193                 /// \r
194                 /// If the inputs are not strings, the ToString method of the objects\r
195                 /// is used to show what is different about them.\r
196                 /// </summary>\r
197                 /// <param name="expected">The expected value</param>\r
198                 /// <param name="actual">The actual value</param>\r
199                 /// <param name="caseInsensitive">True if a case-insensitive comparison is being performed</param>\r
200                 public void DisplayDifferences( object expected, object actual, bool caseInsensitive )\r
201                 {\r
202                         if( InputsAreStrings( expected, actual ) )\r
203                         {\r
204                                 DisplayStringDifferences( \r
205                                         (string)expected, \r
206                                         (string)actual,\r
207                                         caseInsensitive );\r
208                         }\r
209                         else\r
210                         {\r
211                                 DisplayExpectedAndActual( expected, actual );\r
212                         }\r
213                 }\r
214 \r
215                 /// <summary>\r
216                 /// Called to create additional message lines when two doubles have been \r
217                 /// found to be unequal, within the specified tolerance.\r
218                 /// </summary>\r
219                 public void DisplayDifferencesWithTolerance( double expected, double actual, double tolerance )\r
220                 {\r
221                         DisplayExpectedAndActual( expected, actual, tolerance );\r
222                 }\r
223 \r
224                 /// <summary>\r
225                 /// Constructs a message that can be displayed when the content of two\r
226                 /// strings are different, but the string lengths are the same.  The\r
227                 /// message will clip the strings to a reasonable length, centered\r
228                 /// around the first position where they are mismatched, and draw \r
229                 /// a line marking the position of the difference to make comparison\r
230                 /// quicker.\r
231                 /// </summary>\r
232                 /// <param name="sExpected">The expected string value</param>\r
233                 /// <param name="sActual">The actual string value</param>\r
234                 /// <param name="caseInsensitive">True if a case-insensitive comparison is being performed</param>\r
235                 protected void DisplayStringDifferences( string sExpected, string sActual, bool caseInsensitive )\r
236                 {\r
237                         //\r
238                         // If they mismatch at a specified position, report the\r
239                         // difference.\r
240                         //\r
241                         int iPosition = caseInsensitive\r
242                                 ? FindMismatchPosition( sExpected.ToLower(), sActual.ToLower(), 0 )\r
243                                 : FindMismatchPosition( sExpected, sActual, 0 );\r
244                         //\r
245                         // If the lengths differ, but they match up to the length,\r
246                         // show the difference just past the length of the shorter\r
247                         // string\r
248                         //\r
249                         if( iPosition == -1 ) \r
250                                 iPosition = Math.Min( sExpected.Length, sActual.Length );\r
251                         \r
252                         BuildStringLengthReport( sExpected, sActual );\r
253 \r
254                         WriteLine( stringsDifferAtIndexFmt, iPosition );\r
255 \r
256                         //\r
257                         // Clips the strings, then turns any hidden whitespace into visible\r
258                         // characters\r
259                         //\r
260                         string sClippedExpected = ConvertWhitespace(ClipAroundPosition( sExpected, iPosition ));\r
261                         string sClippedActual   = ConvertWhitespace(ClipAroundPosition( sActual,   iPosition ));\r
262 \r
263                         DisplayExpectedAndActual( \r
264                                 sClippedExpected, \r
265                                 sClippedActual );\r
266 \r
267                         // Add a line showing where they differ.  If the string lengths are\r
268                         // different, they start differing just past the length of the \r
269                         // shorter string\r
270                         DisplayPositionMarker( caseInsensitive\r
271                                 ? FindMismatchPosition( sClippedExpected.ToLower(), sClippedActual.ToLower(), 0 )\r
272                                 : FindMismatchPosition( sClippedExpected, sClippedActual, 0 ) );\r
273                 }\r
274 \r
275                 /// <summary>\r
276                 /// Display a standard message showing the differences found between \r
277                 /// two arrays that were expected to be equal.\r
278                 /// </summary>\r
279                 /// <param name="expected">The expected array value</param>\r
280                 /// <param name="actual">The actual array value</param>\r
281                 /// <param name="index">The index at which a difference was found</param>\r
282                 public void DisplayArrayDifferences( Array expected, Array actual, int index )\r
283                 {\r
284                         if( expected.Length != actual.Length )\r
285                                 WriteLine( diffArrayLengthsFmt, expected.Length, actual.Length );\r
286                         else\r
287                                 WriteLine( sameArrayLengthsFmt, expected.Length );\r
288                         \r
289                         WriteLine( arraysDifferAtIndexFmt, index );\r
290                                 \r
291                         if ( index < expected.Length && index < actual.Length )\r
292                         {\r
293                                 DisplayDifferences( GetValueFromCollection(expected, index ), GetValueFromCollection(actual, index), false );\r
294                         }\r
295                         else if( expected.Length < actual.Length )\r
296                                 DisplayListElements( "   extra:", actual, index, 3 );\r
297                         else\r
298                                 DisplayListElements( " missing:", expected, index, 3 );\r
299                 }\r
300 \r
301                 /// <summary>\r
302                 /// Display a standard message showing the differences found between \r
303                 /// two collections that were expected to be equal.\r
304                 /// </summary>\r
305                 /// <param name="expected">The expected collection value</param>\r
306                 /// <param name="actual">The actual collection value</param>\r
307                 /// <param name="index">The index at which a difference was found</param>\r
308                 // NOTE: This is a temporary method for use until the code from NUnitLite\r
309                 // is integrated into NUnit.\r
310                 public void DisplayCollectionDifferences( ICollection expected, ICollection actual, int index )\r
311                 {\r
312                         if( expected.Count != actual.Count )\r
313                                 WriteLine( diffArrayLengthsFmt, expected.Count, actual.Count );\r
314                         else\r
315                                 WriteLine( sameArrayLengthsFmt, expected.Count );\r
316                         \r
317                         WriteLine( arraysDifferAtIndexFmt, index );\r
318                                 \r
319                         if ( index < expected.Count && index < actual.Count )\r
320                         {\r
321                                 DisplayDifferences( GetValueFromCollection(expected, index ), GetValueFromCollection(actual, index), false );\r
322                         }\r
323 //                      else if( expected.Count < actual.Count )\r
324 //                              DisplayListElements( "   extra:", actual, index, 3 );\r
325 //                      else\r
326 //                              DisplayListElements( " missing:", expected, index, 3 );\r
327                 }\r
328 \r
329                 private static object GetValueFromCollection(ICollection collection, int index)\r
330                 {\r
331                         Array array = collection as Array;\r
332 \r
333                         if (array != null && array.Rank > 1)\r
334                                 return array.GetValue(GetArrayIndicesFromCollectionIndex(array, index));\r
335 \r
336                         if (collection is IList)\r
337                                 return ((IList)collection)[index];\r
338 \r
339                         foreach (object obj in collection)\r
340                                 if (--index < 0)\r
341                                         return obj;\r
342 \r
343                         return null;\r
344                 }\r
345 \r
346                 /// <summary>\r
347                 /// Get an array of indices representing the point in a collection or\r
348                 /// array corresponding to a single int index into the collection.\r
349                 /// </summary>\r
350                 /// <param name="collection">The collection to which the indices apply</param>\r
351                 /// <param name="index">Index in the collection</param>\r
352                 /// <returns>Array of indices</returns>\r
353                 private static int[] GetArrayIndicesFromCollectionIndex(ICollection collection, int index)\r
354                 {\r
355                         Array array = collection as Array;\r
356                         int rank = array == null ? 1 : array.Rank;\r
357                         int[] result = new int[rank];\r
358 \r
359                         for (int r = array.Rank; --r > 0; )\r
360                         {\r
361                                 int l = array.GetLength(r);\r
362                                 result[r] = index % l;\r
363                                 index /= l;\r
364                         }\r
365 \r
366                         result[0] = index;\r
367                         return result;\r
368                 }\r
369 \r
370                 /// <summary>\r
371                 /// Displays elements from a list on a line\r
372                 /// </summary>\r
373                 /// <param name="label">Text to prefix the line with</param>\r
374                 /// <param name="list">The list of items to display</param>\r
375                 /// <param name="index">The index in the list of the first element to display</param>\r
376                 /// <param name="max">The maximum number of elements to display</param>\r
377                 public void DisplayListElements( string label, IList list, int index, int max )\r
378                 {\r
379                         Write( "{0}<", label );\r
380 \r
381                         if ( list == null )\r
382                                 Write( "null" );\r
383                         else if ( list.Count == 0 )\r
384                                 Write( "empty" );\r
385                         else\r
386                         {\r
387                                 for( int i = 0; i < max && index < list.Count; i++ )\r
388                                 {\r
389                                         Write( FormatObjectForDisplay( list[index++] ) );\r
390                                 \r
391                                         if ( index < list.Count )\r
392                                                 Write( "," );\r
393                                 }\r
394 \r
395                                 if ( index < list.Count )\r
396                                         Write( "..." );\r
397                         }\r
398 \r
399                         WriteLine( ">" );\r
400                 }\r
401 \r
402                 #region Static Methods\r
403 \r
404                 /// <summary>\r
405                 /// Formats an object for display in a message line\r
406                 /// </summary>\r
407                 /// <param name="obj">The object to be displayed</param>\r
408                 /// <returns></returns>\r
409                 static public string FormatObjectForDisplay( object  obj )\r
410                 {\r
411                         if ( obj == null ) \r
412                                 return "<(null)>";\r
413                         else if ( obj is string )\r
414                                 return string.Format( "<\"{0}\">", obj );\r
415                         else if ( obj is double )\r
416                                 return string.Format( "<{0}>", ((double)obj).ToString( "G17" ) );\r
417                         else if ( obj is float )\r
418                                 return string.Format( "<{0}>", ((float)obj).ToString( "G9" ) );\r
419                         else\r
420                                 return string.Format( "<{0}>", obj );\r
421                 }\r
422 \r
423                 /// <summary>\r
424                 /// Tests two objects to determine if they are strings.\r
425                 /// </summary>\r
426                 /// <param name="expected"></param>\r
427                 /// <param name="actual"></param>\r
428                 /// <returns></returns>\r
429                 static protected bool InputsAreStrings( Object expected, Object actual )\r
430                 {\r
431                         return expected != null && actual != null && \r
432                                 expected is string && actual is string;\r
433                 }\r
434 \r
435                 /// <summary>\r
436                 /// Renders up to M characters before, and up to N characters after\r
437                 /// the specified index position.  If leading or trailing text is\r
438                 /// clipped, and elipses "..." is added where the missing text would\r
439                 /// be.\r
440                 /// \r
441                 /// Clips strings to limit previous or post newline characters,\r
442                 /// since these mess up the comparison\r
443                 /// </summary>\r
444                 /// <param name="sString"></param>\r
445                 /// <param name="iPosition"></param>\r
446                 /// <returns></returns>\r
447                 static protected string ClipAroundPosition( string sString, int iPosition )\r
448                 {\r
449                         if( sString == null || sString.Length == 0 )\r
450                                 return "";\r
451 \r
452                         bool preClip = iPosition > PreClipLength;\r
453                         bool postClip = iPosition + PostClipLength < sString.Length;\r
454 \r
455                         int start = preClip \r
456                                 ? iPosition - PreClipLength : 0;\r
457                         int length = postClip \r
458                                 ? iPosition + PostClipLength - start : sString.Length - start;\r
459 \r
460                         if ( start + length > iPosition + PostClipLength )\r
461                                 length = iPosition + PostClipLength - start;\r
462 \r
463                         StringBuilder sb = new StringBuilder();\r
464                         if ( preClip ) sb.Append("...");\r
465                         sb.Append( sString.Substring( start, length ) );\r
466                         if ( postClip ) sb.Append("...");\r
467 \r
468                         return sb.ToString();\r
469                 }\r
470 \r
471                 /// <summary>\r
472                 /// Shows the position two strings start to differ.  Comparison \r
473                 /// starts at the start index.\r
474                 /// </summary>\r
475                 /// <param name="sExpected"></param>\r
476                 /// <param name="sActual"></param>\r
477                 /// <param name="iStart"></param>\r
478                 /// <returns>-1 if no mismatch found, or the index where mismatch found</returns>\r
479                 static private int FindMismatchPosition( string sExpected, string sActual, int iStart )\r
480                 {\r
481                         int iLength = Math.Min( sExpected.Length, sActual.Length );\r
482                         for( int i=iStart; i<iLength; i++ )\r
483                         {\r
484                                 //\r
485                                 // If they mismatch at a specified position, report the\r
486                                 // difference.\r
487                                 //\r
488                                 if( sExpected[i] != sActual[i] )\r
489                                 {\r
490                                         return i;\r
491                                 }\r
492                         }\r
493                         //\r
494                         // Strings have same content up to the length of the shorter string.\r
495                         // Mismatch occurs because string lengths are different, so show\r
496                         // that they start differing where the shortest string ends\r
497                         //\r
498                         if( sExpected.Length != sActual.Length )\r
499                         {\r
500                                 return iLength;\r
501                         }\r
502             \r
503                         //\r
504                         // Same strings\r
505                         //\r
506                         Assert.IsTrue( sExpected.Equals( sActual ) );\r
507                         return -1;\r
508                 }\r
509 \r
510                 /// <summary>\r
511                 /// Turns CR, LF, or TAB into visual indicator to preserve visual marker \r
512                 /// position.   This is done by replacing the '\r' into '\\' and 'r' \r
513                 /// characters, and the '\n' into '\\' and 'n' characters, and '\t' into\r
514                 /// '\\' and 't' characters.  \r
515                 /// \r
516                 /// Thus the single character becomes two characters for display.\r
517                 /// </summary>\r
518                 /// <param name="sInput"></param>\r
519                 /// <returns></returns>\r
520                 static protected string ConvertWhitespace( string sInput )\r
521                 {\r
522                         if( null != sInput )\r
523                         {\r
524                                 sInput = sInput.Replace( "\\", "\\\\" );\r
525                                 sInput = sInput.Replace( "\r", "\\r" );\r
526                                 sInput = sInput.Replace( "\n", "\\n" );\r
527                                 sInput = sInput.Replace( "\t", "\\t" );\r
528                         }\r
529                         return sInput;\r
530                 }\r
531                 #endregion\r
532         }\r
533 }\r