More performance/memory improvements and protections in ArgumentFormatter

This commit is contained in:
Brad Wilson 2014-04-15 12:45:26 -07:00
Родитель cb0224191d
Коммит d002a821ad
2 изменённых файлов: 157 добавлений и 112 удалений

Просмотреть файл

@ -3,6 +3,7 @@ using System.Collections;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
namespace Xunit.Sdk
{
@ -11,6 +12,10 @@ namespace Xunit.Sdk
/// </summary>
public static class ArgumentFormatter
{
const int MAX_ENUMERABLE_LENGTH = 5;
const int MAX_OBJECT_DEPTH = 3;
const int MAX_STRING_LENGTH = 50;
/// <summary>
/// Format the value for presentation.
/// </summary>
@ -36,21 +41,21 @@ namespace Xunit.Sdk
string stringParameter = value as string;
if (stringParameter != null)
{
if (stringParameter.Length > 50)
return String.Format("\"{0}\"...", stringParameter.Substring(0, 50));
if (stringParameter.Length > MAX_STRING_LENGTH)
return String.Format("\"{0}\"...", stringParameter.Substring(0, MAX_STRING_LENGTH));
return String.Format("\"{0}\"", stringParameter);
}
var enumerable = value as IEnumerable;
if (enumerable != null)
return String.Format("[{0}]", String.Join(", ", enumerable.Cast<object>().Select(x => Format(x, depth + 1))));
return FormatEnumerable(enumerable.Cast<object>(), depth);
var type = value.GetType();
if (type.GetTypeInfo().IsValueType)
return Convert.ToString(value, CultureInfo.CurrentCulture);
if (depth == 3)
if (depth == MAX_OBJECT_DEPTH)
return String.Format("{0} {{ ... }}", type.Name);
var fields = type.GetRuntimeFields()
@ -70,6 +75,20 @@ namespace Xunit.Sdk
return String.Format("{0} {1}", type.Name, parameterValues);
}
private static string FormatEnumerable(IEnumerable<object> enumerableValues, int depth)
{
if (depth == MAX_OBJECT_DEPTH)
return "[...]";
var values = enumerableValues.Take(MAX_ENUMERABLE_LENGTH + 1).ToList();
var printedValues = String.Join(", ", values.Take(MAX_ENUMERABLE_LENGTH).Select(x => Format(x, depth + 1)));
if (values.Count > MAX_ENUMERABLE_LENGTH)
printedValues += ", ...";
return String.Format("[{0}]", printedValues);
}
private static string WrapAndGetFormattedValue(Func<object> getter, int depth)
{
try

Просмотреть файл

@ -1,125 +1,151 @@
using System;
using System.Globalization;
using System.Linq;
using Xunit;
using Xunit.Sdk;
public class ArgumentFormatterTests
{
[Fact]
public static void NullValue()
public class SimpleValues
{
Assert.Equal("null", ArgumentFormatter.Format(null));
}
[Fact]
public static void StringValue()
{
Assert.Equal("\"Hello, world!\"", ArgumentFormatter.Format("Hello, world!"));
}
[Fact]
public static void StringValueTruncatedPast50Characters()
{
Assert.Equal("\"----|----1----|----2----|----3----|----4----|----5\"...", ArgumentFormatter.Format("----|----1----|----2----|----3----|----4----|----5-"));
}
[Fact]
public static void CharacterValue()
{
Assert.Equal("'a'", ArgumentFormatter.Format('a'));
}
[Fact]
public static void DecimalValue()
{
Assert.Equal(123.45M.ToString(CultureInfo.CurrentCulture), ArgumentFormatter.Format(123.45M));
}
[Fact]
public static void EnumerableValue()
{
var expected = String.Format("[1, {0}, \"Hello, world!\"]", 2.3M.ToString(CultureInfo.CurrentCulture));
Assert.Equal(expected, ArgumentFormatter.Format(new object[] { 1, 2.3M, "Hello, world!" }));
}
[Fact]
public static void TypeValue()
{
Assert.Equal("typeof(System.String)", ArgumentFormatter.Format(typeof(string)));
}
[Fact]
public static void ComplexTypeReturnsValuesInAlphabeticalOrder()
{
var expected = String.Format("MyComplexType {{ MyPublicField = 42, MyPublicProperty = {0} }}", 21.12M.ToString(CultureInfo.CurrentCulture));
Assert.Equal(expected, ArgumentFormatter.Format(new MyComplexType()));
}
public class MyComplexType
{
#pragma warning disable 414
private string MyPrivateField = "Hello, world";
#pragma warning restore 414
public static int MyPublicStaticField = 2112;
public decimal MyPublicProperty { get; private set; }
public int MyPublicField = 42;
public MyComplexType()
[Fact]
public static void NullValue()
{
MyPublicProperty = 21.12M;
Assert.Equal("null", ArgumentFormatter.Format(null));
}
[Fact]
public static void StringValue()
{
Assert.Equal("\"Hello, world!\"", ArgumentFormatter.Format("Hello, world!"));
}
[Fact]
public static void StringValueTruncated()
{
Assert.Equal("\"----|----1----|----2----|----3----|----4----|----5\"...", ArgumentFormatter.Format("----|----1----|----2----|----3----|----4----|----5-"));
}
[Fact]
public static void CharacterValue()
{
Assert.Equal("'a'", ArgumentFormatter.Format('a'));
}
[Fact]
public static void DecimalValue()
{
Assert.Equal(123.45M.ToString(CultureInfo.CurrentCulture), ArgumentFormatter.Format(123.45M));
}
[Fact]
public static void TypeValue()
{
Assert.Equal("typeof(System.String)", ArgumentFormatter.Format(typeof(string)));
}
}
[Fact]
public static void ComplexTypeInsideComplexType()
public class Enumerables
{
var expected = String.Format("MyComplexTypeWrapper {{ c = 'A', s = \"Hello, world!\", t = MyComplexType {{ MyPublicField = 42, MyPublicProperty = {0} }} }}", 21.12M.ToString(CultureInfo.CurrentCulture));
Assert.Equal(expected, ArgumentFormatter.Format(new MyComplexTypeWrapper()));
}
public class MyComplexTypeWrapper
{
public MyComplexType t = new MyComplexType();
public char c = 'A';
public string s = "Hello, world!";
}
[Fact]
public static void EmptyComplexType()
{
Assert.Equal("Object { }", ArgumentFormatter.Format(new object()));
}
[Fact]
public static void ComplexTypeWithThrowingPropertyGetter()
{
Assert.Equal("ThrowingGetter { MyThrowingProperty = (throws NotImplementedException) }", ArgumentFormatter.Format(new ThrowingGetter()));
}
public class ThrowingGetter
{
public string MyThrowingProperty { get { throw new NotImplementedException(); } }
}
[Fact]
public static void TypesAreRenderedWithMaximumDepthToPreventInfiniteRecursion()
{
Assert.Equal("Looping { Me = Looping { Me = Looping { ... } } }", ArgumentFormatter.Format(new Looping()));
}
public class Looping
{
public Looping Me;
public Looping()
[Fact]
public static void EnumerableValue()
{
Me = this;
var expected = String.Format("[1, {0}, \"Hello, world!\"]", 2.3M.ToString(CultureInfo.CurrentCulture));
Assert.Equal(expected, ArgumentFormatter.Format(new object[] { 1, 2.3M, "Hello, world!" }));
}
[Fact]
public static void OnlyFirstFewValuesOfEnumerableAreRendered()
{
Assert.Equal("[0, 1, 2, 3, 4, ...]", ArgumentFormatter.Format(Enumerable.Range(0, Int32.MaxValue)));
}
[Fact]
public static void EnumerablesAreRenderedWithMaximumDepthToPreventInfiniteRecursion()
{
object[] looping = new object[2];
looping[0] = 42;
looping[1] = looping;
Assert.Equal("[42, [42, [...]]]", ArgumentFormatter.Format(looping));
}
}
public class ComplexTypes
{
[Fact]
public static void ComplexTypeReturnsValuesInAlphabeticalOrder()
{
var expected = String.Format("MyComplexType {{ MyPublicField = 42, MyPublicProperty = {0} }}", 21.12M.ToString(CultureInfo.CurrentCulture));
Assert.Equal(expected, ArgumentFormatter.Format(new MyComplexType()));
}
public class MyComplexType
{
#pragma warning disable 414
private string MyPrivateField = "Hello, world";
#pragma warning restore 414
public static int MyPublicStaticField = 2112;
public decimal MyPublicProperty { get; private set; }
public int MyPublicField = 42;
public MyComplexType()
{
MyPublicProperty = 21.12M;
}
}
[Fact]
public static void ComplexTypeInsideComplexType()
{
var expected = String.Format("MyComplexTypeWrapper {{ c = 'A', s = \"Hello, world!\", t = MyComplexType {{ MyPublicField = 42, MyPublicProperty = {0} }} }}", 21.12M.ToString(CultureInfo.CurrentCulture));
Assert.Equal(expected, ArgumentFormatter.Format(new MyComplexTypeWrapper()));
}
public class MyComplexTypeWrapper
{
public MyComplexType t = new MyComplexType();
public char c = 'A';
public string s = "Hello, world!";
}
[Fact]
public static void EmptyComplexType()
{
Assert.Equal("Object { }", ArgumentFormatter.Format(new object()));
}
[Fact]
public static void ComplexTypeWithThrowingPropertyGetter()
{
Assert.Equal("ThrowingGetter { MyThrowingProperty = (throws NotImplementedException) }", ArgumentFormatter.Format(new ThrowingGetter()));
}
public class ThrowingGetter
{
public string MyThrowingProperty { get { throw new NotImplementedException(); } }
}
[Fact]
public static void TypesAreRenderedWithMaximumDepthToPreventInfiniteRecursion()
{
Assert.Equal("Looping { Me = Looping { Me = Looping { ... } } }", ArgumentFormatter.Format(new Looping()));
}
public class Looping
{
public Looping Me;
public Looping()
{
Me = this;
}
}
}
}