1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
|
// ****************************************************************
// 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
}
}
|