|
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Xunit.Sdk;
public class ArgumentFormatterTests
{
public class SimpleValues
{
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void NullValue()
{
Assert.Equal("null", ArgumentFormatter.Format(null));
}
// NOTE: It's important that this stays as MemberData
// instead of InlineData. String constants in attributes
// are stored as UTF-8 as IL, so since "\uD800" cannot
// be converted to valid UTF-8, it ends up getting corrupted
// and being stored as two characters instead. Thus, this test
// will fail:
// [Theory]
// [InlineData("\uD800", 1)]
// public void HasLength(string s, int length)
// {
// Assert.Equal(length, s.Length);
// }
// While this one will pass:
// public static IEnumerable<object[]> HasLength_TestData()
// {
// yield return new object[] { "\uD800", 1 };
// }
// [Theory]
// [MemberData(nameof(HasLength_TestData))]
// public void HasLength(string s, int length)
// {
// Assert.Equal(length, s.Length);
// }
// For more information, see the following links:
// - http://stackoverflow.com/q/36104766/4077294
// - http://codeblog.jonskeet.uk/2014/11/07/when-is-a-string-not-a-string/
public static IEnumerable<object[]> StringValue_TestData()
{
yield return new object[] { "\uD800", @"""\xd800""" };
yield return new object[] { "\uDC00", @"""\xdc00""" };
yield return new object[] { "\uDC00\uD800", @"""\xdc00\xd800""" };
yield return new object[] { "\uFFFD", "\"\uFFFD\"" };
}
[Theory]
[InlineData("Hello, world!", "\"Hello, world!\"")]
[InlineData(@"""", @"""\""""")] // quotes should be escaped
[InlineData("\uD800\uDFFF", "\"\uD800\uDFFF\"")] // valid surrogates should print normally
[InlineData("\uFFFE", @"""\xfffe""")] // same for U+FFFE
[InlineData("\uFFFF", @"""\xffff""")] // and U+FFFF, which are non-characters
[InlineData("\u001F", @"""\x1f""")] // non-escaped C0 controls should be 2 digits
// Other escape sequences
[InlineData("\0", @"""\0""")] // null
[InlineData("\r", @"""\r""")] // carriage return
[InlineData("\n", @"""\n""")] // line feed
[InlineData("\a", @"""\a""")] // alert
[InlineData("\b", @"""\b""")] // backspace
[InlineData("\\", @"""\\""")] // backslash
[InlineData("\v", @"""\v""")] // vertical tab
[InlineData("\t", @"""\t""")] // tab
[InlineData("\f", @"""\f""")] // formfeed
[InlineData("----|----1----|----2----|----3----|----4----|----5-", "\"----|----1----|----2----|----3----|----4----|----5\"$$ELLIPSIS$$")] // truncation
[MemberData(nameof(StringValue_TestData), DisableDiscoveryEnumeration = true)]
public static void StringValue(string value, string expected)
{
Assert.Equal(expected.Replace("$$ELLIPSIS$$", ArgumentFormatter.Ellipsis), ArgumentFormatter.Format(value));
}
public static IEnumerable<object[]> CharValue_TestData()
{
yield return new object[] { '\uD800', "0xd800" };
yield return new object[] { '\uDC00', "0xdc00" };
yield return new object[] { '\uFFFD', "'\uFFFD'" };
yield return new object[] { '\uFFFE', "0xfffe" };
}
[Theory]
// Printable
[InlineData(' ', "' '")]
[InlineData('a', "'a'")]
[InlineData('1', "'1'")]
[InlineData('!', "'!'")]
// Escape sequences
[InlineData('\t', @"'\t'")] // tab
[InlineData('\n', @"'\n'")] // newline
[InlineData('\'', @"'\''")] // single quote
[InlineData('\v', @"'\v'")] // vertical tab
[InlineData('\a', @"'\a'")] // alert
[InlineData('\\', @"'\\'")] // backslash
[InlineData('\b', @"'\b'")] // backspace
[InlineData('\r', @"'\r'")] // carriage return
[InlineData('\f', @"'\f'")] // formfeed
// Non-ASCII
[InlineData('©', "'©'")]
[InlineData('╬', "'╬'")]
[InlineData('ئ', "'ئ'")]
// Unprintable
[InlineData(char.MinValue, @"'\0'")]
[InlineData(char.MaxValue, "0xffff")]
[MemberData(nameof(CharValue_TestData), DisableDiscoveryEnumeration = true)]
public static void CharacterValue(char value, string expected)
{
Assert.Equal(expected, ArgumentFormatter.Format(value));
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void FloatValue()
{
var floatPI = (float)Math.PI;
Assert.Equal(floatPI.ToString("G9"), ArgumentFormatter.Format(floatPI));
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void DoubleValue()
{
Assert.Equal(Math.PI.ToString("G17"), ArgumentFormatter.Format(Math.PI));
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void DecimalValue()
{
Assert.Equal(123.45M.ToString(), ArgumentFormatter.Format(123.45M));
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void DateTimeValue()
{
var now = DateTime.UtcNow;
Assert.Equal(now.ToString("o"), ArgumentFormatter.Format(now));
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void DateTimeOffsetValue()
{
var now = DateTimeOffset.UtcNow;
Assert.Equal(now.ToString("o"), ArgumentFormatter.Format(now));
}
[Fact]
public static async Task TaskValue()
{
var task = Task.Run(() => { });
await task;
Assert.Equal("Task { Status = RanToCompletion }", ArgumentFormatter.Format(task));
}
[Fact]
public static void TaskGenericValue()
{
var taskCompletionSource = new TaskCompletionSource<int>();
taskCompletionSource.SetException(new DivideByZeroException());
Assert.Equal("Task<int> { Status = Faulted }", ArgumentFormatter.Format(taskCompletionSource.Task));
}
public static TheoryData<Type, string> TypeValueData = new()
{
{ typeof(string), "typeof(string)" },
{ typeof(int[]), "typeof(int[])" },
{ typeof(int).MakeArrayType(1), "typeof(int[*])" },
{ typeof(int).MakeArrayType(2), "typeof(int[,])" },
{ typeof(int).MakeArrayType(3), "typeof(int[,,])" },
{ typeof(DateTime[,]), "typeof(System.DateTime[,])" },
{ typeof(decimal[][,]), "typeof(decimal[][,])" },
{ typeof(IEnumerable<>), "typeof(System.Collections.Generic.IEnumerable<>)" },
{ typeof(IEnumerable<int>), "typeof(System.Collections.Generic.IEnumerable<int>)" },
{ typeof(IDictionary<,>), "typeof(System.Collections.Generic.IDictionary<,>)" },
{ typeof(IDictionary<string, DateTime>), "typeof(System.Collections.Generic.IDictionary<string, DateTime>)" },
{ typeof(IDictionary<string[,], DateTime[,][]>), "typeof(System.Collections.Generic.IDictionary<string[,], DateTime[,][]>)" },
{ typeof(bool?), "typeof(bool?)" },
{ typeof(bool?[]), "typeof(bool?[])" },
{ typeof(nint), "typeof(nint)" },
{ typeof(IntPtr), "typeof(nint)" },
{ typeof(nuint), "typeof(nuint)" },
{ typeof(UIntPtr), "typeof(nuint)" },
};
[Theory]
[MemberData(nameof(TypeValueData), DisableDiscoveryEnumeration = true)]
public static void TypeValue(Type type, string expected)
{
Assert.Equal(expected, ArgumentFormatter.Format(type));
}
}
public class Enums
{
public enum NonFlagsEnum
{
Value0 = 0,
Value1 = 1
}
#if XUNIT_AOT
[Theory]
#else
[CulturedTheory]
#endif
#pragma warning disable xUnit1010 // The value is not convertible to the method parameter type
[InlineData(0, "Value0")]
[InlineData(1, "Value1")]
[InlineData(42, "42")]
#pragma warning restore xUnit1010 // The value is not convertible to the method parameter type
public static void NonFlags(NonFlagsEnum enumValue, string expected)
{
var actual = ArgumentFormatter.Format(enumValue);
Assert.Equal(expected, actual);
}
[Flags]
public enum FlagsEnum
{
Nothing = 0,
Value1 = 1,
Value2 = 2,
}
#if XUNIT_AOT
[Theory]
#else
[CulturedTheory]
#endif
#pragma warning disable xUnit1010 // The value is not convertible to the method parameter type
[InlineData(0, "Nothing")]
[InlineData(1, "Value1")]
[InlineData(3, "Value1 | Value2")]
// This is expected, not "Value1 | Value2 | 4"
[InlineData(7, "7")]
#pragma warning restore xUnit1010 // The value is not convertible to the method parameter type
public static void Flags(FlagsEnum enumValue, string expected)
{
var actual = ArgumentFormatter.Format(enumValue);
Assert.Equal(expected, actual);
}
}
public class KeyValuePair
{
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void KeyValuePairValue()
{
var kvp = new KeyValuePair<object, List<object>>(42, new() { 21.12M, "2600" });
var expected = $"[42] = [{21.12M}, \"2600\"]";
Assert.Equal(expected, ArgumentFormatter.Format(kvp));
}
}
public class Enumerables
{
// Both tracked and untracked should be the same
public static TheoryData<IEnumerable> Collections = new()
{
new object[] { 1, 2.3M, "Hello, world!" },
new object[] { 1, 2.3M, "Hello, world!" }.AsTracker(),
};
#if XUNIT_AOT
[Theory]
#else
[CulturedTheory]
#endif
[MemberData(nameof(Collections), DisableDiscoveryEnumeration = true)]
public static void EnumerableValue(IEnumerable collection)
{
var expected = $"[1, {2.3M}, \"Hello, world!\"]";
Assert.Equal(expected, ArgumentFormatter.Format(collection));
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void DictionaryValue()
{
var value = new Dictionary<object, List<object>>
{
{ 42, new() { 21.12M, "2600" } },
{ "123", new() { } },
};
var expected = $"[[42] = [{21.12M}, \"2600\"], [\"123\"] = []]";
Assert.Equal(expected, ArgumentFormatter.Format(value));
}
public static TheoryData<IEnumerable> LongCollections = new()
{
new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.AsTracker(),
};
#if XUNIT_AOT
[Theory]
#else
[CulturedTheory]
#endif
[MemberData(nameof(LongCollections), DisableDiscoveryEnumeration = true)]
public static void OnlyFirstFewValuesOfEnumerableAreRendered(IEnumerable collection)
{
Assert.Equal($"[0, 1, 2, 3, 4, {ArgumentFormatter.Ellipsis}]", ArgumentFormatter.Format(collection));
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void EnumerablesAreRenderedWithMaximumDepthToPreventInfiniteRecursion()
{
var looping = new object[2];
looping[0] = 42;
looping[1] = looping;
Assert.Equal($"[42, [42, [{ArgumentFormatter.Ellipsis}]]]", ArgumentFormatter.Format(looping));
}
}
public class ComplexTypes
{
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void ReturnsValuesInAlphabeticalOrder()
{
var expected = $"MyComplexType {{ MyPublicField = 42, MyPublicProperty = {21.12M} }}";
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;
}
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void ComplexTypeInsideComplexType()
{
var expected = $"MyComplexTypeWrapper {{ c = 'A', s = \"Hello, world!\", t = MyComplexType {{ MyPublicField = 42, MyPublicProperty = {21.12M} }} }}";
Assert.Equal(expected, ArgumentFormatter.Format(new MyComplexTypeWrapper()));
}
public class MyComplexTypeWrapper
{
public MyComplexType t = new();
public char c = 'A';
public string s = "Hello, world!";
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void Empty()
{
Assert.Equal("Object { }", ArgumentFormatter.Format(new object()));
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void WithThrowingPropertyGetter()
{
Assert.Equal("ThrowingGetter { MyThrowingProperty = (throws NotImplementedException) }", ArgumentFormatter.Format(new ThrowingGetter()));
}
public class ThrowingGetter
{
public string MyThrowingProperty { get { throw new NotImplementedException(); } }
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void LimitsOutputToFirstFewValues()
{
var expected = $@"Big {{ MyField1 = 42, MyField2 = ""Hello, world!"", MyProp1 = {21.12}, MyProp2 = typeof(ArgumentFormatterTests+ComplexTypes+Big), MyProp3 = 2014-04-17T07:45:23.0000000+00:00, {ArgumentFormatter.Ellipsis} }}";
Assert.Equal(expected, ArgumentFormatter.Format(new Big()));
}
public class Big
{
public string MyField2 = "Hello, world!";
public decimal MyProp1 { get; set; }
public object MyProp4 { get; set; }
public object MyProp3 { get; set; }
public int MyField1 = 42;
public Type MyProp2 { get; set; }
public Big()
{
MyProp1 = 21.12M;
MyProp2 = typeof(Big);
MyProp3 = new DateTimeOffset(2014, 04, 17, 07, 45, 23, TimeSpan.Zero);
MyProp4 = "Should not be shown";
}
}
#if XUNIT_AOT
[Fact]
#else
[CulturedFact]
#endif
public static void TypesAreRenderedWithMaximumDepthToPreventInfiniteRecursion()
{
Assert.Equal($"Looping {{ Me = Looping {{ Me = Looping {{ {ArgumentFormatter.Ellipsis} }} }} }}", ArgumentFormatter.Format(new Looping()));
}
public class Looping
{
public Looping Me;
public Looping() => Me = this;
}
[Fact]
public static void WhenCustomTypeImplementsToString_UsesToString()
{
Assert.Equal("This is what you should show", ArgumentFormatter.Format(new TypeWithToString()));
}
public class TypeWithToString
{
public override string ToString() => "This is what you should show";
}
}
public class TypeNames
{
public static TheoryData<Type, string> ArgumentFormatterFormatTypeNamesData = new()
{
{ typeof(int), "typeof(int)" },
{ typeof(long), "typeof(long)" },
{ typeof(string), "typeof(string)" },
{ typeof(List<int>), "typeof(System.Collections.Generic.List<int>)" },
{ typeof(Dictionary<int, string>), "typeof(System.Collections.Generic.Dictionary<int, string>)" },
{ typeof(List<>), "typeof(System.Collections.Generic.List<>)" },
{ typeof(Dictionary<,>), "typeof(System.Collections.Generic.Dictionary<,>)" }
};
[Theory]
[MemberData(nameof(ArgumentFormatterFormatTypeNamesData), DisableDiscoveryEnumeration = true)]
public void ArgumentFormatterFormatTypeNames(Type type, string expectedResult)
{
Assert.Equal(expectedResult, ArgumentFormatter.Format(type));
}
[Fact]
public void ArgumentFormatterFormatTypeNameGenericTypeParameter()
{
var genericTypeParameters = typeof(List<>).GetGenericArguments();
var parameterType = genericTypeParameters.First();
Assert.Equal("typeof(T)", ArgumentFormatter.Format(parameterType));
}
[Fact]
public void ArgumentFormatterFormatTypeNameGenericTypeParameters()
{
var genericTypeParameters = typeof(Dictionary<,>).GetGenericArguments();
var parameterTKey = genericTypeParameters.First();
Assert.Equal("typeof(TKey)", ArgumentFormatter.Format(parameterTKey));
var parameterTValue = genericTypeParameters.Last();
Assert.Equal("typeof(TValue)", ArgumentFormatter.Format(parameterTValue));
}
}
}
|