File: ObjectFormatterTests.cs
Web Access
Project: src\src\Scripting\CSharpTest\Microsoft.CodeAnalysis.CSharp.Scripting.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Scripting.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Scripting.Hosting;
using Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests;
using Microsoft.CodeAnalysis.Test.Utilities;
using ObjectFormatterFixtures;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests
{
    public class ObjectFormatterTests : ObjectFormatterTestBase
    {
        private static readonly TestCSharpObjectFormatter s_formatter = new TestCSharpObjectFormatter();
 
        [Fact]
        public void Objects()
        {
            string str;
            object nested = new Outer.Nested<int>();
 
            str = s_formatter.FormatObject(nested, SingleLineOptions);
            Assert.Equal(@"Outer.Nested<int> { A=1, B=2 }", str);
 
            str = s_formatter.FormatObject(nested, HiddenOptions);
            Assert.Equal(@"Outer.Nested<int>", str);
 
            str = s_formatter.FormatObject(A<int>.X, HiddenOptions);
            Assert.Equal(@"A<int>.B<int>", str);
 
            object obj = new A<int>.B<bool>.C.D<string, double>.E();
            str = s_formatter.FormatObject(obj, HiddenOptions);
            Assert.Equal(@"A<int>.B<bool>.C.D<string, double>.E", str);
 
            var sort = new Sort();
            str = new TestCSharpObjectFormatter(maximumLineLength: 51).FormatObject(sort, SingleLineOptions);
            Assert.Equal(@"Sort { aB=-1, ab=1, Ac=-1, Ad=1, ad=-1, aE=1, aF=-1...", str);
            Assert.Equal(51 + 3, str.Length);
 
            str = new TestCSharpObjectFormatter(maximumLineLength: 5).FormatObject(sort, SingleLineOptions);
            Assert.Equal(@"Sort ...", str);
            Assert.Equal(5 + 3, str.Length);
 
            str = new TestCSharpObjectFormatter(maximumLineLength: 4).FormatObject(sort, SingleLineOptions);
            Assert.Equal(@"Sort...", str);
 
            str = new TestCSharpObjectFormatter(maximumLineLength: 3).FormatObject(sort, SingleLineOptions);
            Assert.Equal(@"Sor...", str);
 
            str = new TestCSharpObjectFormatter(maximumLineLength: 2).FormatObject(sort, SingleLineOptions);
            Assert.Equal(@"So...", str);
 
            str = new TestCSharpObjectFormatter(maximumLineLength: 1).FormatObject(sort, SingleLineOptions);
            Assert.Equal(@"S...", str);
 
            str = new TestCSharpObjectFormatter(maximumLineLength: 80).FormatObject(sort, SingleLineOptions);
            Assert.Equal(@"Sort { aB=-1, ab=1, Ac=-1, Ad=1, ad=-1, aE=1, aF=-1, AG=1 }", str);
        }
 
        [Fact]
        public void TupleType()
        {
            var tup = new Tuple<int, int>(1, 2);
            Assert.Equal("(1, 2)", s_formatter.FormatObject(tup));
        }
 
        [Fact]
        public void ValueTupleType()
        {
            (int, int) tup = (1, 2);
            Assert.Equal("(1, 2)", s_formatter.FormatObject(tup));
        }
 
        [Fact]
        public void ArrayMethodParameters()
        {
            var result = s_formatter.FormatMethodSignature(Signatures.Arrays);
            Assert.Equal("ObjectFormatterFixtures.Signatures.ArrayParameters(int[], int[,], int[,,])", result);
        }
 
        [Fact]
        public void ArrayOfInt32_NoMembers()
        {
            object o = new int[4] { 3, 4, 5, 6 };
            var str = s_formatter.FormatObject(o, HiddenOptions);
            Assert.Equal("int[4] { 3, 4, 5, 6 }", str);
        }
 
        #region DANGER: Debugging this method under VS2010 might freeze your machine.
 
        [Fact]
        public void RecursiveRootHidden()
        {
            var DO_NOT_ADD_TO_WATCH_WINDOW = new RecursiveRootHidden();
            DO_NOT_ADD_TO_WATCH_WINDOW.C = DO_NOT_ADD_TO_WATCH_WINDOW;
 
            string str = s_formatter.FormatObject(DO_NOT_ADD_TO_WATCH_WINDOW, SingleLineOptions);
            Assert.Equal(@"RecursiveRootHidden { A=0, B=0 }", str);
        }
 
        #endregion
 
        [Fact]
        public void DebuggerDisplay_ParseSimpleMemberName()
        {
            Test_ParseSimpleMemberName("goo", name: "goo", callable: false, nq: false);
            Test_ParseSimpleMemberName("goo  ", name: "goo", callable: false, nq: false);
            Test_ParseSimpleMemberName("   goo", name: "goo", callable: false, nq: false);
            Test_ParseSimpleMemberName("   goo   ", name: "goo", callable: false, nq: false);
 
            Test_ParseSimpleMemberName("goo()", name: "goo", callable: true, nq: false);
            Test_ParseSimpleMemberName("\ngoo (\r\n)", name: "goo", callable: true, nq: false);
            Test_ParseSimpleMemberName(" goo ( \t) ", name: "goo", callable: true, nq: false);
 
            Test_ParseSimpleMemberName("goo,nq", name: "goo", callable: false, nq: true);
            Test_ParseSimpleMemberName("goo  ,nq", name: "goo", callable: false, nq: true);
            Test_ParseSimpleMemberName("goo(),nq", name: "goo", callable: true, nq: true);
            Test_ParseSimpleMemberName("  goo \t( )   ,nq", name: "goo", callable: true, nq: true);
            Test_ParseSimpleMemberName("  goo \t( )   , nq", name: "goo", callable: true, nq: true);
 
            Test_ParseSimpleMemberName("goo,  nq", name: "goo", callable: false, nq: true);
            Test_ParseSimpleMemberName("goo(,nq", name: "goo(", callable: false, nq: true);
            Test_ParseSimpleMemberName("goo),nq", name: "goo)", callable: false, nq: true);
            Test_ParseSimpleMemberName("goo ( ,nq", name: "goo (", callable: false, nq: true);
            Test_ParseSimpleMemberName("goo ) ,nq", name: "goo )", callable: false, nq: true);
 
            Test_ParseSimpleMemberName(",nq", name: "", callable: false, nq: true);
            Test_ParseSimpleMemberName("  ,nq", name: "", callable: false, nq: true);
        }
 
        private void Test_ParseSimpleMemberName(string value, string name, bool callable, bool nq)
        {
            bool actualNoQuotes, actualIsCallable;
            string actualName = ObjectFormatterHelpers.ParseSimpleMemberName(value, 0, value.Length, out actualNoQuotes, out actualIsCallable);
            Assert.Equal(name, actualName);
            Assert.Equal(nq, actualNoQuotes);
            Assert.Equal(callable, actualIsCallable);
 
            actualName = ObjectFormatterHelpers.ParseSimpleMemberName("---" + value + "-", 3, 3 + value.Length, out actualNoQuotes, out actualIsCallable);
            Assert.Equal(name, actualName);
            Assert.Equal(nq, actualNoQuotes);
            Assert.Equal(callable, actualIsCallable);
        }
 
        [Fact]
        public void DebuggerDisplay()
        {
            string str;
            var a = new ComplexProxy();
 
            str = s_formatter.FormatObject(a, SeparateLinesOptions);
 
            AssertMembers(str, @"[AStr]",
                @"_02_public_property_dd: *1",
                @"_03_private_property_dd: *2",
                @"_04_protected_property_dd: *3",
                @"_05_internal_property_dd: *4",
                @"_07_private_field_dd: +2",
                @"_08_protected_field_dd: +3",
                @"_09_internal_field_dd: +4",
                @"_10_private_collapsed: 0",
                @"_12_public: 0",
                @"_13_private: 0",
                @"_14_protected: 0",
                @"_15_internal: 0",
                "_16_eolns: ==\r\n=\r\n=",
                @"_17_braces_0: =={==",
                @"_17_braces_1: =={{==",
                @"_17_braces_2: ==!<Member ''{'' not found>==",
                @"_17_braces_3: ==!<Member ''\{'' not found>==",
                @"_17_braces_4: ==!<Member '1/*{*/' not found>==",
                @"_17_braces_5: ==!<Member ''{'/*\' not found>*/}==",
                @"_17_braces_6: ==!<Member ''{'/*' not found>*/}==",
                @"_19_escapes: ==\{\x\t==",
                @"_21: !<Member '1+1' not found>",
                @"_22: !<Member '""xxx""' not found>",
                @"_23: !<Member '""xxx""' not found>",
                @"_24: !<Member ''x'' not found>",
                @"_25: !<Member ''x'' not found>",
                @"_26_0: !<Method 'new B' not found>",
                @"_26_1: !<Method 'new D' not found>",
                @"_26_2: !<Method 'new E' not found>",
                @"_26_3: ",
                @"_26_4: !<Member 'F1(1)' not found>",
                @"_26_5: 1",
                @"_26_6: 2",
                @"A: 1",
                @"B: 2",
                @"_28: [CStr]",
                @"_29_collapsed: [CStr]",
                @"_31: 0",
                @"_32: 0",
                @"_33: 0",
                @"_34_Exception: !<Exception>",
                @"_35_Exception: -!-",
                @"_36: !<MyException>",
                @"_38_private_get_public_set: 1",
                @"_39_public_get_private_set: 1",
                @"_40_private_get_private_set: 1"
            );
 
            var b = new TypeWithComplexProxy();
            str = s_formatter.FormatObject(b, SeparateLinesOptions);
 
            AssertMembers(str, @"[BStr]",
                @"_02_public_property_dd: *1",
                @"_04_protected_property_dd: *3",
                @"_08_protected_field_dd: +3",
                @"_10_private_collapsed: 0",
                @"_12_public: 0",
                @"_14_protected: 0",
                "_16_eolns: ==\r\n=\r\n=",
                @"_17_braces_0: =={==",
                @"_17_braces_1: =={{==",
                @"_17_braces_2: ==!<Member ''{'' not found>==",
                @"_17_braces_3: ==!<Member ''\{'' not found>==",
                @"_17_braces_4: ==!<Member '1/*{*/' not found>==",
                @"_17_braces_5: ==!<Member ''{'/*\' not found>*/}==",
                @"_17_braces_6: ==!<Member ''{'/*' not found>*/}==",
                @"_19_escapes: ==\{\x\t==",
                @"_21: !<Member '1+1' not found>",
                @"_22: !<Member '""xxx""' not found>",
                @"_23: !<Member '""xxx""' not found>",
                @"_24: !<Member ''x'' not found>",
                @"_25: !<Member ''x'' not found>",
                @"_26_0: !<Method 'new B' not found>",
                @"_26_1: !<Method 'new D' not found>",
                @"_26_2: !<Method 'new E' not found>",
                @"_26_3: ",
                @"_26_4: !<Member 'F1(1)' not found>",
                @"_26_5: 1",
                @"_26_6: 2",
                @"A: 1",
                @"B: 2",
                @"_28: [CStr]",
                @"_29_collapsed: [CStr]",
                @"_31: 0",
                @"_32: 0",
                @"_34_Exception: !<Exception>",
                @"_35_Exception: -!-",
                @"_36: !<MyException>",
                @"_38_private_get_public_set: 1",
                @"_39_public_get_private_set: 1"
            );
        }
 
        [Fact]
        public void DebuggerDisplay_Inherited()
        {
            var obj = new InheritedDebuggerDisplay();
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("InheritedDebuggerDisplay(DebuggerDisplayValue)", str);
        }
 
        [Fact]
        public void DebuggerProxy_DebuggerDisplayAndProxy()
        {
            var obj = new TypeWithDebuggerDisplayAndProxy();
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("TypeWithDebuggerDisplayAndProxy(DD) { A=0, B=0 }", str);
 
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
            AssertMembers(str, "TypeWithDebuggerDisplayAndProxy(DD)",
                "A: 0",
                "B: 0"
            );
        }
 
        [ConditionalFact(typeof(ClrOnly), Reason = "https://github.com/mono/mono/issues/10816")]
        public void DebuggerProxy_Recursive()
        {
            string str;
 
            object obj = new RecursiveProxy.Node(0);
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
 
            AssertMembers(str, "RecursiveProxy.Node",
                "x: 0",
                "y: RecursiveProxy.Node { x=1, y=RecursiveProxy.Node { x=2, y=RecursiveProxy.Node { x=3, y=RecursiveProxy.Node { x=4, y=RecursiveProxy.Node { x=5, y=null } } } } }"
            );
 
            obj = new InvalidRecursiveProxy.Node();
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
 
            // TODO: better overflow handling
            Assert.Equal(ScriptingResources.StackOverflowWhileEvaluating, str);
        }
 
        [Fact]
        public void Array_Recursive()
        {
            string str;
 
            ListNode n2;
            ListNode n1 = new ListNode();
            object[] obj = new object[5];
            obj[0] = 1;
            obj[1] = obj;
            obj[2] = n2 = new ListNode() { data = obj, next = n1 };
            obj[3] = new object[] { 4, 5, obj, 6, new ListNode() };
            obj[4] = 3;
            n1.next = n2;
            n1.data = new object[] { 7, n2, 8, obj };
 
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
 
            AssertMembers(str, "object[5]",
                "1",
                "{ ... }",
                "ListNode { data={ ... }, next=ListNode { data=object[4] { 7, ListNode { ... }, 8, { ... } }, next=ListNode { ... } } }",
                "object[5] { 4, 5, { ... }, 6, ListNode { data=null, next=null } }",
                "3"
            );
 
            str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("object[5] { 1, { ... }, ListNode { data={ ... }, next=ListNode { data=object[4] { 7, ListNode { ... }, 8, { ... } }, next=ListNode { ... } } }, object[5] { 4, 5, { ... }, 6, ListNode { data=null, next=null } }, 3 }", str);
        }
 
        [Fact]
        public void LargeGraph()
        {
            var list = new LinkedList<object>();
            object obj = list;
            for (int i = 0; i < 10000; i++)
            {
                var node = list.AddFirst(i);
                var newList = new LinkedList<object>();
                list.AddAfter(node, newList);
                list = newList;
            }
 
            string output = "LinkedList<object>(2) { 0, LinkedList<object>(2) { 1, LinkedList<object>(2) { 2, LinkedList<object>(2) {";
 
            for (int i = 100; i > 4; i--)
            {
                var printOptions = new PrintOptions
                {
                    MaximumOutputLength = i,
                    MemberDisplayFormat = MemberDisplayFormat.SingleLine,
                };
 
                var actual = s_formatter.FormatObject(obj, printOptions);
                var expected = output[..i] + "...";
                Assert.Equal(expected, actual);
            }
        }
 
        [Fact]
        public void LongMembers()
        {
            object obj = new LongMembers();
 
            var str = new TestCSharpObjectFormatter(maximumLineLength: 20).FormatObject(obj, SingleLineOptions);
            Assert.Equal("LongMembers { LongNa...", str);
 
            str = new TestCSharpObjectFormatter(maximumLineLength: 20).FormatObject(obj, SeparateLinesOptions);
            Assert.Equal($"LongMembers {{{Environment.NewLine}  LongName0123456789...{Environment.NewLine}  LongValue: \"012345...{Environment.NewLine}}}{Environment.NewLine}", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_Array()
        {
            var obj = new Object[] { new C(), 1, "str", 'c', true, null, new bool[] { true, false, true, false } };
            var str = s_formatter.FormatObject(obj, SeparateLinesOptions);
 
            AssertMembers(str, "object[7]",
                "[CStr]",
                "1",
                "\"str\"",
                "'c'",
                "true",
                "null",
                "bool[4] { true, false, true, false }"
            );
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_MdArray()
        {
            string str;
 
            int[,,] a = new int[2, 3, 4]
            {
                {
                    { 000, 001, 002, 003 },
                    { 010, 011, 012, 013 },
                    { 020, 021, 022, 023 },
                },
                {
                    { 100, 101, 102, 103 },
                    { 110, 111, 112, 113 },
                    { 120, 121, 122, 123 },
                }
            };
 
            str = s_formatter.FormatObject(a, SingleLineOptions);
            Assert.Equal("int[2, 3, 4] { { { 0, 1, 2, 3 }, { 10, 11, 12, 13 }, { 20, 21, 22, 23 } }, { { 100, 101, 102, 103 }, { 110, 111, 112, 113 }, { 120, 121, 122, 123 } } }", str);
 
            str = s_formatter.FormatObject(a, SeparateLinesOptions);
            AssertMembers(str, "int[2, 3, 4]",
                "{ { 0, 1, 2, 3 }, { 10, 11, 12, 13 }, { 20, 21, 22, 23 } }",
                "{ { 100, 101, 102, 103 }, { 110, 111, 112, 113 }, { 120, 121, 122, 123 } }"
            );
 
            int[][,][,,,] obj = new int[2][,][,,,];
            obj[0] = new int[1, 2][,,,];
            obj[0][0, 0] = new int[1, 2, 3, 4];
 
            str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("int[2][,][,,,] { int[1, 2][,,,] { { int[1, 2, 3, 4] { { { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } } }, null } }, null }", str);
 
            Array x = Array.CreateInstance(typeof(Object), lengths: new int[] { 2, 3 }, lowerBounds: new int[] { 2, 9 });
            str = s_formatter.FormatObject(x, SingleLineOptions);
            Assert.Equal("object[2..4, 9..12] { { null, null, null }, { null, null, null } }", str);
 
            Array y = Array.CreateInstance(typeof(Object), lengths: new int[] { 1, 1 }, lowerBounds: new int[] { 0, 0 });
            str = s_formatter.FormatObject(y, SingleLineOptions);
            Assert.Equal("object[1, 1] { { null } }", str);
 
            Array z = Array.CreateInstance(typeof(Object), lengths: new int[] { 0, 0 }, lowerBounds: new int[] { 0, 0 });
            str = s_formatter.FormatObject(z, SingleLineOptions);
            Assert.Equal("object[0, 0] { }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_IEnumerable_Core()
        {
            string str;
            object obj;
 
            obj = Range_Core(0, 10);
            str = s_formatter.FormatObject(obj, SingleLineOptions);
 
            Assert.Equal("ObjectFormatterTests.CoreRangeIterator(Count = 10)", str);
        }
 
        // This method and the class below emulate the behaviour of Enumerable.Range
        // in .NET Core. We use a custom type since not all runtime implementations
        // (e.g. Mono) apply precisely the same attributes, but we want to test behavior
        // under a specific set of attributes.
        private static IEnumerable<int> Range_Core(int start, int count)
            => new CoreRangeIterator(start, count);
 
        [DebuggerDisplay("Count = {CountForDebugger}")]
        private class CoreRangeIterator : IEnumerable<int>
        {
            private readonly int _start;
            private readonly int _end;
 
            private int CountForDebugger => _end - _start;
 
            public CoreRangeIterator(int start, int count)
                => (_start, _end) = (start, start + count);
 
            public IEnumerator<int> GetEnumerator() => null;
            IEnumerator IEnumerable.GetEnumerator() => null;
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_IEnumerable_Framework()
        {
            string str;
            object obj;
 
            obj = Range_Framework(0, 10);
            str = s_formatter.FormatObject(obj, SingleLineOptions);
 
            Assert.Equal("RangeIterator { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }", str);
        }
 
        // These methods emulate the .NET Framework Enumerable.Range method
        private static IEnumerable<int> Range_Framework(int start, int count)
            => RangeIterator(start, count);
 
        private static IEnumerable<int> RangeIterator(int start, int count)
        {
            for (var i = 0; i < count; i++)
                yield return start + i;
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_IEnumerable_Exception()
        {
            string str;
            object obj;
 
            obj = Enumerable.Range(0, 10).Where(i =>
            {
                if (i == 5)
                    throw new Exception("xxx");
                return i < 7;
            });
            str = s_formatter.FormatObject(obj, SingleLineOptions);
            var iteratorType = RuntimeUtilities.IsCoreClr9OrHigherRuntime
                ? "IEnumerableWhereIterator"
                : "WhereEnumerableIterator";
            Assert.Equal($"Enumerable.{iteratorType}<int> {{ 0, 1, 2, 3, 4, !<Exception> ... }}", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_IDictionary()
        {
            string str;
            object obj;
 
            obj = new ThrowingDictionary(throwAt: -1);
            str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("ThrowingDictionary(10) { { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } }", str);
 
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
            AssertMembers(str, "ThrowingDictionary(10)",
                "{ 1, 1 }",
                "{ 2, 2 }",
                "{ 3, 3 }",
                "{ 4, 4 }"
            );
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_IDictionary_Exception()
        {
            string str;
            object obj;
 
            obj = new ThrowingDictionary(throwAt: 3);
            str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("ThrowingDictionary(10) { { 1, 1 }, { 2, 2 }, !<Exception> ... }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_BitArray()
        {
            // BitArray doesn't have debugger proxy/display
            var obj = new System.Collections.BitArray(new int[] { 1 });
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("BitArray(32) { true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_Queue()
        {
            var obj = new Queue<int>();
            obj.Enqueue(1);
            obj.Enqueue(2);
            obj.Enqueue(3);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("Queue<int>(3) { 1, 2, 3 }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_Stack()
        {
            var obj = new Stack<int>();
            obj.Push(1);
            obj.Push(2);
            obj.Push(3);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("Stack<int>(3) { 3, 2, 1 }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_Dictionary()
        {
            var obj = new Dictionary<string, int>
            {
                { "x", 1 },
            };
 
            var str = s_formatter.FormatObject(obj, SeparateLinesOptions);
 
            AssertMembers(str, "Dictionary<string, int>(1)",
                "{ \"x\", 1 }"
            );
 
            str = s_formatter.FormatObject(obj, SingleLineOptions);
 
            Assert.Equal("Dictionary<string, int>(1) { { \"x\", 1 } }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_KeyValuePair()
        {
            var obj = new KeyValuePair<int, string>(1, "x");
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("KeyValuePair<int, string> { 1, \"x\" }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_List()
        {
            var obj = new List<object> { 1, 2, 'c' };
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("List<object>(3) { 1, 2, 'c' }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_LinkedList()
        {
            var obj = new LinkedList<int>();
            obj.AddLast(1);
            obj.AddLast(2);
            obj.AddLast(3);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("LinkedList<int>(3) { 1, 2, 3 }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_SortedList()
        {
            var obj = new SortedList<int, int>();
            obj.Add(3, 4);
            obj.Add(1, 5);
            obj.Add(2, 6);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("SortedList<int, int>(3) { { 1, 5 }, { 2, 6 }, { 3, 4 } }", str);
 
            var obj2 = new SortedList<int[], int[]>();
            obj2.Add(new[] { 3 }, new int[] { 4 });
 
            str = s_formatter.FormatObject(obj2, SingleLineOptions);
            Assert.Equal("SortedList<int[], int[]>(1) { { int[1] { 3 }, int[1] { 4 } } }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_SortedDictionary()
        {
            var obj = new SortedDictionary<int, int>();
            obj.Add(1, 0x1a);
            obj.Add(3, 0x3c);
            obj.Add(2, 0x2b);
 
            var str = s_formatter.
                FormatObject(obj, new PrintOptions { NumberRadix = ObjectFormatterHelpers.NumberRadixHexadecimal });
 
            Assert.Equal("SortedDictionary<int, int>(3) { { 0x00000001, 0x0000001a }, { 0x00000002, 0x0000002b }, { 0x00000003, 0x0000003c } }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_HashSet()
        {
            var obj = new HashSet<int>();
            obj.Add(1);
            obj.Add(2);
 
            // HashSet doesn't implement ICollection (it only implements ICollection<T>) so we don't call Count, 
            // instead a DebuggerDisplay.Value is used.
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("HashSet<int>(Count = 2) { 1, 2 }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_SortedSet()
        {
            var obj = new SortedSet<int>();
            obj.Add(1);
            obj.Add(2);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("SortedSet<int>(2) { 1, 2 }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_ConcurrentDictionary()
        {
            var obj = new ConcurrentDictionary<string, int>();
            obj.AddOrUpdate("x", 1, (k, v) => v);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
 
            Assert.Equal("ConcurrentDictionary<string, int>(1) { { \"x\", 1 } }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_ConcurrentQueue()
        {
            var obj = new ConcurrentQueue<object>();
            obj.Enqueue(1);
            obj.Enqueue(2);
            obj.Enqueue(3);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("ConcurrentQueue<object>(3) { 1, 2, 3 }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_ConcurrentStack()
        {
            var obj = new ConcurrentStack<object>();
            obj.Push(1);
            obj.Push(2);
            obj.Push(3);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("ConcurrentStack<object>(3) { 3, 2, 1 }", str);
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_BlockingCollection()
        {
            var obj = new BlockingCollection<int>();
            obj.Add(1);
            obj.Add(2, new CancellationToken());
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("BlockingCollection<int>(2) { 1, 2 }", str);
        }
 
        // TODO(tomat): this only works with System.dll file version 30319.16644 (Win8 build)
        //[Fact]
        //public void DebuggerProxy_FrameworkTypes_ConcurrentBag()
        //{
        //    var obj = new ConcurrentBag<int>();
        //    obj.Add(1);
 
        //    var str = ObjectFormatter.Instance.FormatObject(obj, quoteStrings: true, memberFormat: MemberDisplayFormat.Inline);
        //    Assert.Equal("ConcurrentBag<int>(1) { 1 }", str);
        //}
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_ReadOnlyCollection()
        {
            var obj = new System.Collections.ObjectModel.ReadOnlyCollection<int>(new[] { 1, 2, 3 });
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("ReadOnlyCollection<int>(3) { 1, 2, 3 }", str);
        }
 
        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsWindowsTypes)]
        public void DebuggerProxy_FrameworkTypes_Lazy()
        {
            var obj = new Lazy<int[]>(() => new int[] { 1, 2 }, LazyThreadSafetyMode.None);
 
            // Lazy<T> has both DebuggerDisplay and DebuggerProxy attributes and both display the same information.
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("Lazy<int[]>(ThreadSafetyMode=None, IsValueCreated=false, IsValueFaulted=false, Value=null) { IsValueCreated=false, IsValueFaulted=false, Mode=None, Value=null }", str);
 
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
            AssertMembers(str, "Lazy<int[]>(ThreadSafetyMode=None, IsValueCreated=false, IsValueFaulted=false, Value=null)",
                "IsValueCreated: false",
                "IsValueFaulted: false",
                "Mode: None",
                "Value: null"
            );
 
            Assert.NotNull(obj.Value);
 
            str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("Lazy<int[]>(ThreadSafetyMode=None, IsValueCreated=true, IsValueFaulted=false, Value=int[2] { 1, 2 }) { IsValueCreated=true, IsValueFaulted=false, Mode=None, Value=int[2] { 1, 2 } }", str);
 
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
            AssertMembers(str, "Lazy<int[]>(ThreadSafetyMode=None, IsValueCreated=true, IsValueFaulted=false, Value=int[2] { 1, 2 })",
                "IsValueCreated: true",
                "IsValueFaulted: false",
                "Mode: None",
                "Value: int[2] { 1, 2 }"
            );
        }
 
        private void TaskMethod()
        {
        }
 
        [Fact, WorkItem("https://github.com/mono/mono/issues/10838")]
        public void DebuggerProxy_FrameworkTypes_Task()
        {
            var obj = new MockDesktopTask(TaskMethod);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal(
                "MockDesktopTask(Id = 1234, Status = Created, Method = \"Void TaskMethod()\") " +
                "{ AsyncState=null, CancellationPending=false, CreationOptions=None, Exception=null, Id=1234, Status=Created }",
                str);
 
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
            AssertMembers(str, "MockDesktopTask(Id = 1234, Status = Created, Method = \"Void TaskMethod()\")",
                "AsyncState: null",
                "CancellationPending: false",
                "CreationOptions: None",
                "Exception: null",
                "Id: 1234",
                "Status: Created"
            );
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_SpinLock1()
        {
            var obj = new MockDesktopSpinLock(false);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("MockDesktopSpinLock(IsHeld = false) { IsHeld=false, IsHeldByCurrentThread=!<InvalidOperationException>, OwnerThreadID=null }", str);
 
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
            AssertMembers(str, "MockDesktopSpinLock(IsHeld = false)",
                "IsHeld: false",
                "IsHeldByCurrentThread: !<InvalidOperationException>",
                "OwnerThreadID: null"
            );
        }
 
        [Fact]
        public void DebuggerProxy_FrameworkTypes_SpinLock2()
        {
            var obj = new MockDesktopSpinLock(true);
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("MockDesktopSpinLock(IsHeld = false) { IsHeld=false, IsHeldByCurrentThread=true, OwnerThreadID=0 }", str);
 
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
            AssertMembers(str, "MockDesktopSpinLock(IsHeld = false)",
                "IsHeld: false",
                "IsHeldByCurrentThread: true",
                "OwnerThreadID: 0"
            );
        }
 
        [Fact]
        public void DebuggerProxy_DiagnosticBag()
        {
            var obj = new DiagnosticBag();
            obj.Add(new DiagnosticInfo(MessageProvider.Instance, (int)ErrorCode.ERR_AbstractAndExtern, "bar"), NoLocation.Singleton);
            obj.Add(new DiagnosticInfo(MessageProvider.Instance, (int)ErrorCode.ERR_BadExternIdentifier, "goo"), NoLocation.Singleton);
 
            using (new EnsureEnglishUICulture())
            {
                var str = s_formatter.FormatObject(obj, SingleLineOptions);
                Assert.Equal("DiagnosticBag(Count = 2) { =error CS0180: 'bar' cannot be both extern and abstract, =error CS1679: Invalid extern alias for '/reference'; 'goo' is not a valid identifier }", str);
 
                str = s_formatter.FormatObject(obj, SeparateLinesOptions);
                AssertMembers(str, "DiagnosticBag(Count = 2)",
                     ": error CS0180: 'bar' cannot be both extern and abstract",
                     ": error CS1679: Invalid extern alias for '/reference'; 'goo' is not a valid identifier"
                );
            }
        }
 
        [Fact]
        public void DebuggerProxy_ArrayBuilder()
        {
            var obj = ArrayBuilder<int>.GetInstance();
            obj.AddRange(new[] { 1, 2, 3, 4, 5 });
 
            var str = s_formatter.FormatObject(obj, SingleLineOptions);
            Assert.Equal("ArrayBuilder<int>(Count = 5) { 1, 2, 3, 4, 5 }", str);
 
            str = s_formatter.FormatObject(obj, SeparateLinesOptions);
            AssertMembers(str, "ArrayBuilder<int>(Count = 5)",
                 "1",
                 "2",
                 "3",
                 "4",
                 "5"
            );
 
            obj.Free();
        }
 
        [Fact, WorkItem(8542, "https://github.com/dotnet/roslyn/issues/8452")]
        public void FormatConstructorSignature()
        {
            var constructor = typeof(object).GetTypeInfo().DeclaredConstructors.Single();
            var signature = ((CommonObjectFormatter)s_formatter).FormatMethodSignature(constructor);
            Assert.Equal("object..ctor()", signature); // Checking for exceptions, more than particular output.
        }
 
        // The stack trace contains line numbers.  We use a #line directive
        // so that the baseline doesn't need to be updated every time this
        // file changes.
        //
        // When adding a new test to this region, ADD IT ADD THE END, so you
        // don't have to update all the other baselines.
#line 10000 "z:\Fixture.cs"
 
        private static class Fixture
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static void Method()
            {
                throw new Exception();
            }
 
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static void Method<U>()
            {
                throw new Exception();
            }
        }
 
        private static class Fixture<T>
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static void Method()
            {
                throw new Exception();
            }
 
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static void Method<U>()
            {
                throw new Exception();
            }
        }
 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19027")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/15860")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/19027")]
        public void StackTrace_NonGeneric()
        {
            try
            {
                Fixture.Method();
            }
            catch (Exception e)
            {
                const string filePath = @"z:\Fixture.cs";
 
                var expected =
$@"{new Exception().Message}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.Fixture.Method(){string.Format(ScriptingResources.AtFileLine, filePath, 10006)}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.StackTrace_NonGeneric(){string.Format(ScriptingResources.AtFileLine, filePath, 10036)}
";
                var actual = s_formatter.FormatException(e);
                Assert.Equal(expected, actual);
            }
        }
 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19027")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/15860")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/19027")]
        public void StackTrace_GenericMethod()
        {
            try
            {
                Fixture.Method<char>();
            }
            catch (Exception e)
            {
                const string filePath = @"z:\Fixture.cs";
 
                // TODO (DevDiv #173210): Should show Fixture.Method<char>
                var expected =
$@"{new Exception().Message}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.Fixture.Method<U>(){string.Format(ScriptingResources.AtFileLine, filePath, 10012)}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.StackTrace_GenericMethod(){string.Format(ScriptingResources.AtFileLine, filePath, 10057)}
";
                var actual = s_formatter.FormatException(e);
                Assert.Equal(expected, actual);
            }
        }
 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19027")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/15860")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/19027")]
        public void StackTrace_GenericType()
        {
            try
            {
                Fixture<int>.Method();
            }
            catch (Exception e)
            {
                const string filePath = @"z:\Fixture.cs";
 
                // TODO (DevDiv #173210): Should show Fixture<int>.Method
                var expected =
$@"{new Exception().Message}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.Fixture<T>.Method(){string.Format(ScriptingResources.AtFileLine, filePath, 10021)}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.StackTrace_GenericType(){string.Format(ScriptingResources.AtFileLine, filePath, 10079)}
";
                var actual = s_formatter.FormatException(e);
                Assert.Equal(expected, actual);
            }
        }
 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19027")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/15860")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/19027")]
        public void StackTrace_GenericMethodInGenericType()
        {
            try
            {
                Fixture<int>.Method<char>();
            }
            catch (Exception e)
            {
                const string filePath = @"z:\Fixture.cs";
 
                // TODO (DevDiv #173210): Should show Fixture<int>.Method<char>
                var expected =
$@"{new Exception().Message}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.Fixture<T>.Method<U>(){string.Format(ScriptingResources.AtFileLine, filePath, 10027)}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.StackTrace_GenericMethodInGenericType(){string.Format(ScriptingResources.AtFileLine, filePath, 10101)}
";
                var actual = s_formatter.FormatException(e);
                Assert.Equal(expected, actual);
            }
        }
 
        private static class Fixture2
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static void MethodDynamic()
            {
                ((dynamic)new object()).x();
            }
        }
 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/9221"), WorkItem("https://github.com/dotnet/roslyn/issues/9221")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/19027")]
        public void StackTrace_Dynamic()
        {
            try
            {
                Fixture2.MethodDynamic();
                Assert.False(true);
            }
            catch (Exception e)
            {
                const string filePath = @"z:\Fixture.cs";
 
                var expected =
$@"'object' does not contain a definition for 'x'
  + System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1<T0>(System.Runtime.CompilerServices.CallSite, T0)
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.Fixture2.MethodDynamic(){string.Format(ScriptingResources.AtFileLine, filePath, 10123)}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.StackTrace_Dynamic(){string.Format(ScriptingResources.AtFileLine, filePath, 10132)}
";
                var actual = s_formatter.FormatException(e);
                Assert.Equal(expected, actual);
            }
        }
 
        private static class ParametersFixture
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static void Method(ref char c, out DateTime d)
            {
                throw new Exception();
            }
 
            [MethodImpl(MethodImplOptions.NoInlining)]
            public static void Method<U>(ref U u)
            {
                throw new Exception();
            }
        }
 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19027")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/15860")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/19027")]
        public void StackTrace_RefOutParameters()
        {
            try
            {
                char c = ' ';
                DateTime date;
                ParametersFixture.Method(ref c, out date);
            }
            catch (Exception e)
            {
                const string filePath = @"z:\Fixture.cs";
 
                var expected =
$@"{new Exception().Message}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.ParametersFixture.Method(ref char, out System.DateTime){string.Format(ScriptingResources.AtFileLine, filePath, 10155)}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.StackTrace_RefOutParameters(){string.Format(ScriptingResources.AtFileLine, filePath, 10172)}
";
                var actual = s_formatter.FormatException(e);
                Assert.Equal(expected, actual);
            }
        }
 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/19027")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/15860")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/19027")]
        public void StackTrace_GenericRefParameter()
        {
            try
            {
                char c = ' ';
                ParametersFixture.Method<char>(ref c);
            }
            catch (Exception e)
            {
                const string filePath = @"z:\Fixture.cs";
 
                // TODO (DevDiv #173210): Should show ParametersFixture.Method<char>(ref char)
                var expected =
$@"{new Exception().Message}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.ParametersFixture.Method<U>(ref U){string.Format(ScriptingResources.AtFileLine, filePath, 10161)}
  + Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests.ObjectFormatterTests.StackTrace_GenericRefParameter(){string.Format(ScriptingResources.AtFileLine, filePath, 10194)}
";
                var actual = s_formatter.FormatException(e);
                Assert.Equal(expected, actual);
            }
        }
    }
}