// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Dynamic; using System.Linq; using System.Web.TestUtil; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Helpers.Test { public class ObjectInfoTest { [Fact] public void PrintWithNegativeDepthThrows() { // Act & Assert Assert.ThrowsArgumentGreaterThanOrEqualTo(() => ObjectInfo.Print(null, depth: -1), "depth", "0"); } [Fact] public void PrintWithInvalidEnumerationLength() { // Act & Assert Assert.ThrowsArgumentGreaterThan(() => ObjectInfo.Print(null, enumerationLength: -1), "enumerationLength", "0"); } [Fact] public void PrintWithNull() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); // Act visitor.Print(null); // Assert Assert.Equal(1, visitor.Values.Count); Assert.Equal("null", visitor.Values[0]); } [Fact] public void PrintWithEmptyString() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); // Act visitor.Print(String.Empty); // Assert Assert.Equal(1, visitor.Values.Count); Assert.Equal(String.Empty, visitor.Values[0]); } [Fact] public void PrintWithInt() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); // Act visitor.Print(404); // Assert Assert.Equal(1, visitor.Values.Count); Assert.Equal("404", visitor.Values[0]); } [Fact] public void PrintWithIDictionary() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); IDictionary dict = new OrderedDictionary(); dict.Add("foo", "bar"); dict.Add("abc", 500); // Act visitor.Print(dict); // Assert Assert.Equal("foo = bar", visitor.KeyValuePairs[0]); Assert.Equal("abc = 500", visitor.KeyValuePairs[1]); } [Fact] public void PrintWithIEnumerable() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); var values = Enumerable.Range(0, 10); // Act visitor.Print(values); // Assert foreach (var num in values) { Assert.True(visitor.Values.Contains(num.ToString())); } } [Fact] public void PrintWithGenericIListPrintsIndex() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); var values = Enumerable.Range(0, 10).ToList(); // Act visitor.Print(values); // Assert for (int i = 0; i < values.Count; i++) { Assert.True(visitor.Values.Contains(values[i].ToString())); Assert.True(visitor.Indexes.Contains(i)); } } [Fact] public void PrintWithArrayPrintsIndex() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); var values = Enumerable.Range(0, 10).ToArray(); // Act visitor.Print(values); // Assert for (int i = 0; i < values.Length; i++) { Assert.True(visitor.Values.Contains(values[i].ToString())); Assert.True(visitor.Indexes.Contains(i)); } } [Fact] public void PrintNameValueCollectionPrintsKeysAndValues() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); var values = new NameValueCollection(); values["a"] = "1"; values["b"] = null; // Act visitor.Print(values); // Assert Assert.Equal("a = 1", visitor.KeyValuePairs[0]); Assert.Equal("b = null", visitor.KeyValuePairs[1]); } [Fact] public void PrintDateTime() { using (new CultureReplacer()) { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); var dt = new DateTime(2001, 11, 20, 10, 30, 1); // Act visitor.Print(dt); // Assert Assert.Equal("11/20/2001 10:30:01 AM", visitor.Values[0]); } } [Fact] public void PrintCustomObjectPrintsMembers() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); var person = new Person { Name = "David", Age = 23.3, Dob = new DateTime(1986, 11, 19), LongType = 1000000000, Type = 1 }; using (new CultureReplacer()) { // Act visitor.Print(person); // Assert Assert.Equal(9, visitor.Members.Count); Assert.True(visitor.Members.Contains("double Age = 23.3")); Assert.True(visitor.Members.Contains("string Name = David")); Assert.True(visitor.Members.Contains("DateTime Dob = 11/19/1986 12:00:00 AM")); Assert.True(visitor.Members.Contains("short Type = 1")); Assert.True(visitor.Members.Contains("float Float = 0")); Assert.True(visitor.Members.Contains("byte Byte = 0")); Assert.True(visitor.Members.Contains("decimal Decimal = 0")); Assert.True(visitor.Members.Contains("bool Bool = False")); } } [Fact] public void PrintShowsVisitedWhenCircularReferenceInObjectGraph() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); PersonNode node = new PersonNode { Person = new Person { Name = "David", Age = 23.3 } }; node.Next = node; // Act visitor.Print(node); // Assert Assert.True(visitor.Members.Contains("string Name = David")); Assert.True(visitor.Members.Contains("double Age = 23.3")); Assert.True(visitor.Members.Contains("PersonNode Next = Visited")); } [Fact] public void PrintShowsVisitedWhenCircularReferenceIsIEnumerable() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); List values = new List(); values.Add(values); // Act visitor.Print(values); // Assert Assert.Equal("Visited", visitor.Values[0]); Assert.Equal("Visited " + values.GetHashCode(), visitor.Visited[0]); } [Fact] public void PrintShowsVisitedWhenCircularReferenceIsIDictionary() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); OrderedDictionary values = new OrderedDictionary(); values[values] = values; // Act visitor.Print(values); // Assert Assert.Equal("Visited", visitor.Values[0]); Assert.Equal("Visited " + values.GetHashCode(), visitor.Visited[0]); } [Fact] public void PrintShowsVisitedWhenCircularReferenceIsNameValueCollection() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); NameValueCollection nameValues = new NameValueCollection(); nameValues["id"] = "1"; List values = new List(); values.Add(nameValues); values.Add(nameValues); // Act visitor.Print(values); // Assert Assert.True(visitor.Values.Contains("Visited")); Assert.True(visitor.Visited.Contains("Visited " + nameValues.GetHashCode())); } [Fact] public void PrintExcludesWriteOnlyProperties() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); ClassWithWriteOnlyProperty cls = new ClassWithWriteOnlyProperty(); // Act visitor.Print(cls); // Assert Assert.Equal(0, visitor.Members.Count); } [Fact] public void PrintWritesEnumeratedElementsUntilLimitIsHit() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); var enumeration = Enumerable.Range(0, 2000); // Act visitor.Print(enumeration); // Assert for (int i = 0; i <= 2000; i++) { if (i < 1000) { Assert.True(visitor.Values.Contains(i.ToString())); } else { Assert.False(visitor.Values.Contains(i.ToString())); } } Assert.True(visitor.Values.Contains("Limit Exceeded")); } [Fact] public void PrintWithAnonymousType() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); var value = new { Name = "John", X = 1 }; // Act visitor.Print(value); // Assert Assert.True(visitor.Members.Contains("string Name = John")); Assert.True(visitor.Members.Contains("int X = 1")); } [Fact] public void PrintClassWithPublicFields() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); ClassWithFields value = new ClassWithFields(); value.Foo = "John"; value.Bar = 1; // Actt visitor.Print(value); // Assert Assert.True(visitor.Members.Contains("string Foo = John")); Assert.True(visitor.Members.Contains("int Bar = 1")); } [Fact] public void PrintClassWithDynamicMembersPrintsMembersIfGetDynamicMemberNamesIsImplemented() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); dynamic d = new DynamicDictionary(); d.Cycle = d; d.Name = "Foo"; d.Value = null; // Act visitor.Print(d); // Assert Assert.True(visitor.Members.Contains("DynamicDictionary Cycle = Visited")); Assert.True(visitor.Members.Contains("string Name = Foo")); Assert.True(visitor.Members.Contains("Value = null")); } [Fact] public void PrintClassWithDynamicMembersReturningNullPrintsNoMembers() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); dynamic d = new ClassWithDynamicAnNullMemberNames(); d.Cycle = d; d.Name = "Foo"; d.Value = null; // Act visitor.Print(d); // Assert Assert.False(visitor.Members.Any()); } [Fact] public void PrintUsesToStringOfIConvertibleObjects() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); ConvertibleClass cls = new ConvertibleClass(); // Act visitor.Print(cls); // Assert Assert.Equal("Test", visitor.Values[0]); } [Fact] public void PrintConvertsTypeToString() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); // Act visitor.Print(typeof(string)); // Assert Assert.Equal("typeof(string)", visitor.Values[0]); } [Fact] public void PrintClassWithPropertyThatThrowsExceptionPrintsException() { // Arrange MockObjectVisitor visitor = CreateObjectVisitor(); ClassWithPropertyThatThrowsException value = new ClassWithPropertyThatThrowsException(); // Act visitor.Print(value); // Assert Assert.Equal("int MyProperty = Property accessor 'MyProperty' on object 'System.Web.Helpers.Test.ObjectInfoTest+ClassWithPropertyThatThrowsException' threw the following exception:'Property that shows an exception'", visitor.Members[0]); } [Fact] public void ConvertEscapeSequencesPrintsStringEscapeSequencesAsLiterals() { // Act string value = HtmlObjectPrinter.ConvertEscapseSequences("\\\'\"\0\a\b\f\n\r\t\v"); // Assert Assert.Equal("\\\\'\\\"\\0\\a\\b\\f\\n\\r\\t\\v", value); } [Fact] public void ConvertEscapeSequencesDoesNotEscapeUnicodeSequences() { // Act string value = HtmlObjectPrinter.ConvertEscapseSequences("\u1023\x2045"); // Assert Assert.Equal("\u1023\x2045", value); } [Fact] public void PrintCharPrintsQuotedString() { // Arrange HtmlObjectPrinter printer = new HtmlObjectPrinter(100, 100); HtmlElement element = new HtmlElement("span"); printer.PushElement(element); // Act printer.VisitConvertedValue('x', "x"); // Assert Assert.Equal(1, element.Children.Count); HtmlElement child = element.Children[0]; Assert.Equal("'x'", child.InnerText); Assert.Equal("quote", child["class"]); } [Fact] public void PrintEscapeCharPrintsEscapedCharAsLiteral() { // Arrange HtmlObjectPrinter printer = new HtmlObjectPrinter(100, 100); HtmlElement element = new HtmlElement("span"); printer.PushElement(element); // Act printer.VisitConvertedValue('\t', "\t"); // Assert Assert.Equal(1, element.Children.Count); HtmlElement child = element.Children[0]; Assert.Equal("'\\t'", child.InnerText); Assert.Equal("quote", child["class"]); } [Fact] public void GetTypeNameConvertsGenericTypesToCsharpSyntax() { // Act string value = ObjectVisitor.GetTypeName(typeof(Func, Action>>)); // Assert Assert.Equal("Func, Action>>", value); } private class ConvertibleClass : IConvertible { public TypeCode GetTypeCode() { throw new NotImplementedException(); } public bool ToBoolean(IFormatProvider provider) { throw new NotImplementedException(); } public byte ToByte(IFormatProvider provider) { throw new NotImplementedException(); } public char ToChar(IFormatProvider provider) { throw new NotImplementedException(); } public DateTime ToDateTime(IFormatProvider provider) { throw new NotImplementedException(); } public decimal ToDecimal(IFormatProvider provider) { throw new NotImplementedException(); } public double ToDouble(IFormatProvider provider) { throw new NotImplementedException(); } public short ToInt16(IFormatProvider provider) { throw new NotImplementedException(); } public int ToInt32(IFormatProvider provider) { throw new NotImplementedException(); } public long ToInt64(IFormatProvider provider) { throw new NotImplementedException(); } public sbyte ToSByte(IFormatProvider provider) { throw new NotImplementedException(); } public float ToSingle(IFormatProvider provider) { throw new NotImplementedException(); } public string ToString(IFormatProvider provider) { return "Test"; } public object ToType(Type conversionType, IFormatProvider provider) { throw new NotImplementedException(); } public ushort ToUInt16(IFormatProvider provider) { throw new NotImplementedException(); } public uint ToUInt32(IFormatProvider provider) { throw new NotImplementedException(); } public ulong ToUInt64(IFormatProvider provider) { throw new NotImplementedException(); } } private class ClassWithPropertyThatThrowsException { public int MyProperty { get { throw new InvalidOperationException("Property that shows an exception"); } } } private class ClassWithDynamicAnNullMemberNames : DynamicObject { public override IEnumerable GetDynamicMemberNames() { return null; } public override bool TryGetMember(GetMemberBinder binder, out object result) { result = null; return true; } public override bool TrySetMember(SetMemberBinder binder, object value) { return true; } } private class Person { public string Name { get; set; } public double Age { get; set; } public DateTime Dob { get; set; } public short Type { get; set; } public long LongType { get; set; } public float Float { get; set; } public byte Byte { get; set; } public decimal Decimal { get; set; } public bool Bool { get; set; } } private class ClassWithFields { public string Foo; public int Bar = 13; } private class ClassWithWriteOnlyProperty { public int Value { set { } } } private class PersonNode { public Person Person { get; set; } public PersonNode Next { get; set; } } private MockObjectVisitor CreateObjectVisitor(int recursionLimit = 10, int enumerationLimit = 1000) { return new MockObjectVisitor(recursionLimit, enumerationLimit); } private class MockObjectVisitor : ObjectVisitor { public MockObjectVisitor(int recursionLimit, int enumerationLimit) : base(recursionLimit, enumerationLimit) { Values = new List(); KeyValuePairs = new List(); Members = new List(); Indexes = new List(); Visited = new List(); } public List Values { get; set; } public List KeyValuePairs { get; set; } public List Members { get; set; } public List Indexes { get; set; } public List Visited { get; set; } public void Print(object value) { Visit(value, 0); } public override void VisitObjectVisitorException(ObjectVisitorException exception) { Values.Add(exception.InnerException.Message); } public override void VisitStringValue(string stringValue) { Values.Add(stringValue); base.VisitStringValue(stringValue); } public override void VisitVisitedObject(string id, object value) { Visited.Add(String.Format("Visited {0}", id)); Values.Add("Visited"); base.VisitVisitedObject(id, value); } public override void VisitIndexedEnumeratedValue(int index, object item, int depth) { Indexes.Add(index); base.VisitIndexedEnumeratedValue(index, item, depth); } public override void VisitEnumeratonLimitExceeded() { Values.Add("Limit Exceeded"); base.VisitEnumeratonLimitExceeded(); } public override void VisitMember(string name, Type type, object value, int depth) { base.VisitMember(name, type, value, depth); type = type ?? (value != null ? value.GetType() : null); if (type == null) { Members.Add(String.Format("{0} = null", name)); } else { Members.Add(String.Format("{0} {1} = {2}", GetTypeName(type), name, Values.Last())); } } public override void VisitNull() { Values.Add("null"); base.VisitNull(); } public override void VisitKeyValue(object key, object value, int depth) { base.VisitKeyValue(key, value, depth); KeyValuePairs.Add(String.Format("{0} = {1}", Values[Values.Count - 2], Values[Values.Count - 1])); } } } }