1 // ***********************************************************************
2 // Copyright (c) 2015 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
30 using System.Collections.Generic;
31 using System.Reflection;
34 namespace NUnit.Framework.Internal
37 /// TestNameGenerator is able to create test names according to
40 public class TestNameGenerator
42 // TODO: Using a static here is not good it's the easiest
43 // way to get a temporary implementation without passing the
44 // pattern all the way down the test builder hierarchy
47 /// Default pattern used to generate names
49 public static string DefaultTestNamePattern = "{m}{a}";
51 // The name pattern used by this TestNameGenerator
52 private string _pattern;
54 // The list of NameFragments used to generate names
55 private List<NameFragment> _fragments;
58 /// Construct a TestNameGenerator
60 public TestNameGenerator()
62 _pattern = DefaultTestNamePattern;
66 /// Construct a TestNameGenerator
68 /// <param name="pattern">The pattern used by this generator.</param>
69 public TestNameGenerator(string pattern)
75 /// Get the display name for a TestMethod and it's arguments
77 /// <param name="testMethod">A TestMethod</param>
78 /// <returns>The display name</returns>
79 public string GetDisplayName(TestMethod testMethod)
81 return GetDisplayName(testMethod, null);
85 /// Get the display name for a TestMethod and it's arguments
87 /// <param name="testMethod">A TestMethod</param>
88 /// <param name="args">Arguments to be used</param>
89 /// <returns>The display name</returns>
90 public string GetDisplayName(TestMethod testMethod, object[] args)
92 if (_fragments == null)
93 _fragments = BuildFragmentList(_pattern);
95 var result = new StringBuilder();
97 foreach (var fragment in _fragments)
98 result.Append(fragment.GetText(testMethod, args));
100 return result.ToString();
103 #region Helper Methods
105 private static List<NameFragment> BuildFragmentList(string pattern)
107 var fragments = new List<NameFragment>();
109 // Build a list of actions so this generator can be applied to
110 // multiple types and methods.
113 while (start < pattern.Length)
115 int lcurly = pattern.IndexOf('{', start);
116 if (lcurly < 0) // No more substitutions in pattern
119 int rcurly = pattern.IndexOf('}', lcurly);
123 if (lcurly > start) // Handle fixedixed text before curly brace
124 fragments.Add(new FixedTextFragment(pattern.Substring(start, lcurly - start)));
126 string token = pattern.Substring(lcurly, rcurly - lcurly + 1);
131 fragments.Add(new MethodNameFragment());
134 fragments.Add(new TestIDFragment());
137 fragments.Add(new NamespaceFragment());
140 fragments.Add(new ClassNameFragment());
143 fragments.Add(new ClassFullNameFragment());
146 fragments.Add(new MethodFullNameFragment());
149 fragments.Add(new ArgListFragment(0));
161 int index = token[1] - '0';
162 fragments.Add(new ArgumentFragment(index, 40));
166 if (token.Length >= 5 && token[2] == ':' && (c == 'a' || char.IsDigit(c)))
170 // NOTE: The code would be much simpler using TryParse. However,
171 // that method doesn't exist in the Compact Framework.
174 length = int.Parse(token.Substring(3, token.Length - 4));
183 fragments.Add(new ArgListFragment(length));
185 fragments.Add(new ArgumentFragment(c - '0', length));
190 // Output the erroneous token to aid user in debugging
191 fragments.Add(new FixedTextFragment(token));
199 // Output any trailing plain text
200 if (start < pattern.Length)
201 fragments.Add(new FixedTextFragment(pattern.Substring(start)));
208 #region Nested Classes Representing Name Fragments
210 private abstract class NameFragment
212 private const string THREE_DOTS = "...";
214 public virtual string GetText(TestMethod testMethod, object[] args)
216 return GetText(testMethod.Method.MethodInfo, args);
219 public abstract string GetText(MethodInfo method, object[] args);
221 protected static void AppendGenericTypeNames(StringBuilder sb, MethodInfo method)
225 foreach (Type t in method.GetGenericArguments())
227 if (cnt++ > 0) sb.Append(",");
233 protected static string GetDisplayString(object arg, int stringMax)
235 string display = arg == null
237 : Convert.ToString(arg, System.Globalization.CultureInfo.InvariantCulture);
241 double d = (double)arg;
244 display = "double.NaN";
245 else if (double.IsPositiveInfinity(d))
246 display = "double.PositiveInfinity";
247 else if (double.IsNegativeInfinity(d))
248 display = "double.NegativeInfinity";
249 else if (d == double.MaxValue)
250 display = "double.MaxValue";
251 else if (d == double.MinValue)
252 display = "double.MinValue";
255 if (display.IndexOf('.') == -1)
260 else if (arg is float)
262 float f = (float)arg;
265 display = "float.NaN";
266 else if (float.IsPositiveInfinity(f))
267 display = "float.PositiveInfinity";
268 else if (float.IsNegativeInfinity(f))
269 display = "float.NegativeInfinity";
270 else if (f == float.MaxValue)
271 display = "float.MaxValue";
272 else if (f == float.MinValue)
273 display = "float.MinValue";
276 if (display.IndexOf('.') == -1)
281 else if (arg is decimal)
283 decimal d = (decimal)arg;
284 if (d == decimal.MinValue)
285 display = "decimal.MinValue";
286 else if (d == decimal.MaxValue)
287 display = "decimal.MaxValue";
291 else if (arg is long)
293 if (arg.Equals(long.MinValue))
294 display = "long.MinValue";
295 else if (arg.Equals(long.MaxValue))
296 display = "long.MaxValue";
300 else if (arg is ulong)
302 ulong ul = (ulong)arg;
303 if (ul == ulong.MinValue)
304 display = "ulong.MinValue";
305 else if (ul == ulong.MaxValue)
306 display = "ulong.MaxValue";
310 else if (arg is string)
312 var str = (string)arg;
313 bool tooLong = stringMax > 0 && str.Length > stringMax;
314 int limit = tooLong ? stringMax - THREE_DOTS.Length : 0;
316 StringBuilder sb = new StringBuilder();
318 foreach (char c in str)
320 sb.Append(EscapeCharInString(c));
321 if (tooLong && sb.Length > limit)
323 sb.Append(THREE_DOTS);
328 display = sb.ToString();
330 else if (arg is char)
332 display = "\'" + EscapeSingleChar((char)arg) + "\'";
336 if (arg.Equals(int.MaxValue))
337 display = "int.MaxValue";
338 else if (arg.Equals(int.MinValue))
339 display = "int.MinValue";
341 else if (arg is uint)
343 if (arg.Equals(uint.MaxValue))
344 display = "uint.MaxValue";
345 else if (arg.Equals(uint.MinValue))
346 display = "uint.MinValue";
348 else if (arg is short)
350 if (arg.Equals(short.MaxValue))
351 display = "short.MaxValue";
352 else if (arg.Equals(short.MinValue))
353 display = "short.MinValue";
355 else if (arg is ushort)
357 if (arg.Equals(ushort.MaxValue))
358 display = "ushort.MaxValue";
359 else if (arg.Equals(ushort.MinValue))
360 display = "ushort.MinValue";
362 else if (arg is byte)
364 if (arg.Equals(byte.MaxValue))
365 display = "byte.MaxValue";
366 else if (arg.Equals(byte.MinValue))
367 display = "byte.MinValue";
369 else if (arg is sbyte)
371 if (arg.Equals(sbyte.MaxValue))
372 display = "sbyte.MaxValue";
373 else if (arg.Equals(sbyte.MinValue))
374 display = "sbyte.MinValue";
380 private static string EscapeSingleChar(char c)
385 return EscapeControlChar(c);
388 private static string EscapeCharInString(char c)
393 return EscapeControlChar(c);
396 private static string EscapeControlChar(char c)
422 return string.Format("\\x{0:X4}", (int)c);
430 private class TestIDFragment : NameFragment
432 public override string GetText(MethodInfo method, object[] args)
434 return "{i}"; // No id available using MethodInfo
437 public override string GetText(TestMethod testMethod, object[] args)
439 return testMethod.Id;
443 private class FixedTextFragment : NameFragment
445 private string _text;
447 public FixedTextFragment(string text)
452 public override string GetText(MethodInfo method, object[] args)
458 private class MethodNameFragment : NameFragment
460 public override string GetText(MethodInfo method, object[] args)
462 var sb = new StringBuilder();
464 sb.Append(method.Name);
466 if (method.IsGenericMethod)
467 AppendGenericTypeNames(sb, method);
469 return sb.ToString();
473 private class NamespaceFragment : NameFragment
475 public override string GetText(MethodInfo method, object[] args)
477 return method.DeclaringType.Namespace;
481 private class MethodFullNameFragment : NameFragment
483 public override string GetText(MethodInfo method, object[] args)
485 var sb = new StringBuilder();
487 sb.Append(method.DeclaringType.FullName);
489 sb.Append(method.Name);
491 if (method.IsGenericMethod)
492 AppendGenericTypeNames(sb, method);
494 return sb.ToString();
498 private class ClassNameFragment : NameFragment
500 public override string GetText(MethodInfo method, object[] args)
502 return method.DeclaringType.Name;
506 private class ClassFullNameFragment : NameFragment
508 public override string GetText(MethodInfo method, object[] args)
510 return method.DeclaringType.FullName;
514 private class ArgListFragment : NameFragment
516 private int _maxStringLength;
518 public ArgListFragment(int maxStringLength)
520 _maxStringLength = maxStringLength;
523 public override string GetText(MethodInfo method, object[] arglist)
525 var sb = new StringBuilder();
531 for (int i = 0; i < arglist.Length; i++)
533 if (i > 0) sb.Append(",");
534 sb.Append(GetDisplayString(arglist[i], _maxStringLength));
540 return sb.ToString();
544 private class ArgumentFragment : NameFragment
547 private int _maxStringLength;
549 public ArgumentFragment(int index, int maxStringLength)
552 _maxStringLength = maxStringLength;
555 public override string GetText(MethodInfo method, object[] args)
557 return _index < args.Length
558 ? GetDisplayString(args[_index], _maxStringLength)