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.Text; 9 using System.Collections; 10 11 namespace NUnit.Framework 12 { 13 /// <summary> 14 /// Static methods used in creating messages 15 /// </summary> 16 public class MsgUtils 17 { 18 /// <summary> 19 /// Static string used when strings are clipped 20 /// </summary> 21 public static readonly string ELLIPSIS = "..."; 22 23 /// <summary> 24 /// Returns the representation of a type as used in NUnitLite. 25 /// This is the same as Type.ToString() except for arrays, 26 /// which are displayed with their declared sizes. 27 /// </summary> 28 /// <param name="obj"></param> 29 /// <returns></returns> GetTypeRepresentation(object obj)30 public static string GetTypeRepresentation(object obj) 31 { 32 Array array = obj as Array; 33 if ( array == null ) 34 return string.Format( "<{0}>", obj.GetType() ); 35 36 StringBuilder sb = new StringBuilder(); 37 Type elementType = array.GetType(); 38 int nest = 0; 39 while (elementType.IsArray) 40 { 41 elementType = elementType.GetElementType(); 42 ++nest; 43 } 44 sb.Append(elementType.ToString()); 45 sb.Append('['); 46 for (int r = 0; r < array.Rank; r++) 47 { 48 if (r > 0) sb.Append(','); 49 sb.Append(array.GetLength(r)); 50 } 51 sb.Append(']'); 52 53 while (--nest > 0) 54 sb.Append("[]"); 55 56 return string.Format( "<{0}>", sb.ToString() ); 57 } 58 /// <summary> 59 /// Converts any control characters in a string 60 /// to their escaped representation. 61 /// </summary> 62 /// <param name="s">The string to be converted</param> 63 /// <returns>The converted string</returns> ConvertWhitespace(string s)64 public static string ConvertWhitespace(string s) 65 { 66 if( s != null ) 67 { 68 s = s.Replace( "\\", "\\\\" ); 69 s = s.Replace( "\r", "\\r" ); 70 s = s.Replace( "\n", "\\n" ); 71 s = s.Replace( "\t", "\\t" ); 72 } 73 return s; 74 } 75 76 /// <summary> 77 /// Return the a string representation for a set of indices into an array 78 /// </summary> 79 /// <param name="indices">Array of indices for which a string is needed</param> GetArrayIndicesAsString(int[] indices)80 public static string GetArrayIndicesAsString(int[] indices) 81 { 82 StringBuilder sb = new StringBuilder(); 83 sb.Append('['); 84 for (int r = 0; r < indices.Length; r++) 85 { 86 if (r > 0) sb.Append(','); 87 sb.Append(indices[r].ToString()); 88 } 89 sb.Append(']'); 90 return sb.ToString(); 91 } 92 93 /// <summary> 94 /// Get an array of indices representing the point in a collection or 95 /// array corresponding to a single int index into the collection. 96 /// </summary> 97 /// <param name="collection">The collection to which the indices apply</param> 98 /// <param name="index">Index in the collection</param> 99 /// <returns>Array of indices</returns> GetArrayIndicesFromCollectionIndex(ICollection collection, int index)100 public static int[] GetArrayIndicesFromCollectionIndex(ICollection collection, int index) 101 { 102 Array array = collection as Array; 103 104 if ( array == null || array.Rank == 1) 105 return new int[] { index }; 106 107 int[] result = new int[array.Rank]; 108 109 for (int r = array.Rank; --r > 0; ) 110 { 111 int l = array.GetLength(r); 112 result[r] = index % l; 113 index /= l; 114 } 115 116 result[0] = index; 117 return result; 118 } 119 120 /// <summary> 121 /// Clip a string to a given length, starting at a particular offset, returning the clipped 122 /// string with ellipses representing the removed parts 123 /// </summary> 124 /// <param name="s">The string to be clipped</param> 125 /// <param name="maxStringLength">The maximum permitted length of the result string</param> 126 /// <param name="clipStart">The point at which to start clipping</param> 127 /// <returns>The clipped string</returns> ClipString(string s, int maxStringLength, int clipStart)128 public static string ClipString(string s, int maxStringLength, int clipStart) 129 { 130 int clipLength = maxStringLength; 131 StringBuilder sb = new StringBuilder(); 132 133 if (clipStart > 0) 134 { 135 clipLength -= ELLIPSIS.Length; 136 sb.Append( ELLIPSIS ); 137 } 138 139 if (s.Length - clipStart > clipLength) 140 { 141 clipLength -= ELLIPSIS.Length; 142 sb.Append( s.Substring( clipStart, clipLength )); 143 sb.Append(ELLIPSIS); 144 } 145 else if (clipStart > 0) 146 sb.Append( s.Substring(clipStart)); 147 else 148 sb.Append( s ); 149 150 return sb.ToString(); 151 } 152 153 /// <summary> 154 /// Clip the expected and actual strings in a coordinated fashion, 155 /// so that they may be displayed together. 156 /// </summary> 157 /// <param name="expected"></param> 158 /// <param name="actual"></param> 159 /// <param name="maxDisplayLength"></param> 160 /// <param name="mismatch"></param> ClipExpectedAndActual(ref string expected, ref string actual, int maxDisplayLength, int mismatch)161 public static void ClipExpectedAndActual(ref string expected, ref string actual, int maxDisplayLength, int mismatch) 162 { 163 // Case 1: Both strings fit on line 164 int maxStringLength = Math.Max(expected.Length, actual.Length); 165 if (maxStringLength <= maxDisplayLength) 166 return; 167 168 // Case 2: Assume that the tail of each string fits on line 169 int clipLength = maxDisplayLength - ELLIPSIS.Length; 170 int tailLength = clipLength - mismatch; 171 int clipStart = maxStringLength - clipLength; 172 173 // Case 3: If it doesn't, center the mismatch position 174 if ( clipStart > mismatch ) 175 clipStart = Math.Max( 0, mismatch - clipLength / 2 ); 176 177 expected = ClipString(expected, maxDisplayLength, clipStart); 178 actual = ClipString(actual, maxDisplayLength, clipStart); 179 } 180 181 /// <summary> 182 /// Shows the position two strings start to differ. Comparison 183 /// starts at the start index. 184 /// </summary> 185 /// <param name="expected">The expected string</param> 186 /// <param name="actual">The actual string</param> 187 /// <param name="istart">The index in the strings at which comparison should start</param> 188 /// <param name="ignoreCase">Boolean indicating whether case should be ignored</param> 189 /// <returns>-1 if no mismatch found, or the index where mismatch found</returns> FindMismatchPosition(string expected, string actual, int istart, bool ignoreCase)190 static public int FindMismatchPosition(string expected, string actual, int istart, bool ignoreCase) 191 { 192 int length = Math.Min(expected.Length, actual.Length); 193 194 string s1 = ignoreCase ? expected.ToLower() : expected; 195 string s2 = ignoreCase ? actual.ToLower() : actual; 196 197 for (int i = istart; i < length; i++) 198 { 199 if (s1[i] != s2[i]) 200 return i; 201 } 202 203 // 204 // Strings have same content up to the length of the shorter string. 205 // Mismatch occurs because string lengths are different, so show 206 // that they start differing where the shortest string ends 207 // 208 if (expected.Length != actual.Length) 209 return length; 210 211 // 212 // Same strings : We shouldn't get here 213 // 214 return -1; 215 } 216 } 217 } 218