File: ValueFormattingTests.cs
Web Access
Project: ..\..\..\src\ExpressionEvaluator\CSharp\Test\ResultProvider\Microsoft.CodeAnalysis.CSharp.ResultProvider.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.ResultProvider.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.Globalization;
using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.VisualStudio.Debugger.Clr;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.UnitTests
{
    public class ValueFormattingTests : CSharpResultProviderTestBase
    {
        [Fact]
        public void IntegralPrimitives()
        {
            // only testing a couple simple cases here...more tests live in ObjectDisplayTests...
            unchecked
            {
                Assert.Equal("1", FormatValue((ushort)1));
                Assert.Equal("65535", FormatValue((ushort)-1));
                Assert.Equal("1", FormatValue((int)1));
                Assert.Equal("-1", FormatValue((int)-1));
 
                Assert.Equal("0x01", FormatValue((sbyte)1, useHexadecimal: true));
                Assert.Equal("0xffffffff", FormatValue((sbyte)-1, useHexadecimal: true)); // As in dev11.
                Assert.Equal("0x0001", FormatValue((short)1, useHexadecimal: true));
                Assert.Equal("0xffffffff", FormatValue((short)-1, useHexadecimal: true)); // As in dev11.
                Assert.Equal("0x0000000000000001", FormatValue((ulong)1, useHexadecimal: true));
                Assert.Equal("0xffffffffffffffff", FormatValue((ulong)-1, useHexadecimal: true));
            }
        }
 
        [Fact]
        public void Double()
        {
            Assert.Equal("-1.7976931348623157E+308", FormatValue(double.MinValue));
            Assert.Equal("-1.1", FormatValue((double)-1.1));
            Assert.Equal("0", FormatValue((double)0));
            Assert.Equal("1.1", FormatValue((double)1.1));
            Assert.Equal("1.7976931348623157E+308", FormatValue(double.MaxValue));
 
            Assert.Equal("-Infinity", FormatValue(double.NegativeInfinity));
            Assert.Equal("Infinity", FormatValue(double.PositiveInfinity));
            Assert.Equal("NaN", FormatValue(double.NaN));
            Assert.Equal("4.94065645841247E-324", FormatValue(double.Epsilon));
        }
 
        [Fact]
        public void Float()
        {
            Assert.Equal("-3.40282347E+38", FormatValue(float.MinValue));
            Assert.Equal("-1.1", FormatValue((float)-1.1));
            Assert.Equal("0", FormatValue((float)0));
            Assert.Equal("1.1", FormatValue((float)1.1));
            Assert.Equal("3.40282347E+38", FormatValue(float.MaxValue));
 
            Assert.Equal("-Infinity", FormatValue(float.NegativeInfinity));
            Assert.Equal("Infinity", FormatValue(float.PositiveInfinity));
            Assert.Equal("NaN", FormatValue(float.NaN));
            Assert.Equal("1.401298E-45", FormatValue(float.Epsilon));
        }
 
        [Fact]
        public void Decimal()
        {
            Assert.Equal("-79228162514264337593543950335", FormatValue(decimal.MinValue));
            Assert.Equal("-1.1", FormatValue((decimal)-1.1));
            Assert.Equal("0", FormatValue((decimal)0));
            Assert.Equal("1.1", FormatValue((decimal)1.1));
            Assert.Equal("79228162514264337593543950335", FormatValue(decimal.MaxValue));
        }
 
        [Fact]
        public void Bool()
        {
            Assert.Equal("true", FormatValue(true));
            Assert.Equal("false", FormatValue(false));
        }
 
        [Fact]
        public void Char()
        {
            // We'll exhaustively test the first 256 code points (single-byte characters) as well
            // as a few double-byte characters.  Testing all possible characters takes too long.
            const string format = "{0} '{1}'";
            const string formatUsingHex = "0x{0:x4} '{1}'";
            char ch;
            for (ch = (char)0; ch < 0xff; ch++)
            {
                string expected;
                switch (ch)
                {
                    case '\0':
                        expected = "\\0";
                        break;
                    case '\t':
                        expected = "\\t";
                        break;
                    case '\f':
                        expected = "\\f";
                        break;
                    case '\r':
                        expected = "\\r";
                        break;
                    case '\n':
                        expected = "\\n";
                        break;
                    case '\a':
                        expected = "\\a";
                        break;
                    case '\b':
                        expected = "\\b";
                        break;
                    case '\v':
                        expected = "\\v";
                        break;
                    case '\'':
                        expected = "\\'";
                        break;
                    case '\\':
                        expected = "\\\\";
                        break;
                    default:
                        expected = FormatStringChar(ch);
                        break;
                }
                Assert.Equal(string.Format(format, (int)ch, expected), FormatValue(ch));
                Assert.Equal(string.Format(formatUsingHex, (int)ch, expected), FormatValue(ch, useHexadecimal: true));
            }
 
            ch = (char)0xabcd;
            Assert.Equal(string.Format(format, (int)ch, ch), FormatValue(ch));
            Assert.Equal(string.Format(formatUsingHex, (int)ch, ch), FormatValue(ch, useHexadecimal: true));
 
            ch = (char)0xfeef;
            Assert.Equal(string.Format(format, (int)ch, ch), FormatValue(ch));
            Assert.Equal(string.Format(formatUsingHex, (int)ch, ch), FormatValue(ch, useHexadecimal: true));
 
            ch = (char)0xffef;
            Assert.Equal("65519 '\\uffef'", FormatValue(ch));
            Assert.Equal("0xffef '\\uffef'", FormatValue(ch, useHexadecimal: true));
 
            ch = char.MaxValue;
            Assert.Equal("65535 '\\uffff'", FormatValue(ch));
            Assert.Equal("0xffff '\\uffff'", FormatValue(ch, useHexadecimal: true));
        }
 
        [Fact]
        public void String()
        {
            Assert.Equal("null", FormatNull<string>());
            Assert.Equal("null", FormatNull<string>(useHexadecimal: true));
 
            // We'll exhaustively test the first 256 code points (single-byte characters) as well
            // as a few multi-byte characters.  Testing all possible characters takes too long.
            string format = "\"{0}\"";
            for (char ch = (char)0; ch < 0xff; ch++)
            {
                string expected;
                switch (ch)
                {
                    case '\0':
                        expected = "\\0";
                        break;
                    case '\t':
                        expected = "\\t";
                        break;
                    case '\f':
                        expected = "\\f";
                        break;
                    case '\r':
                        expected = "\\r";
                        break;
                    case '\n':
                        expected = "\\n";
                        break;
                    case '\a':
                        expected = "\\a";
                        break;
                    case '\b':
                        expected = "\\b";
                        break;
                    case '\v':
                        expected = "\\v";
                        break;
                    case '"':
                        expected = "\\\"";
                        break;
                    case '\\':
                        expected = "\\\\";
                        break;
                    default:
                        expected = FormatStringChar(ch);
                        break;
                }
                Assert.Equal(string.Format(format, expected), FormatValue(ch.ToString()));
                Assert.Equal(string.Format(format, expected), FormatValue(ch.ToString(), useHexadecimal: true));
            }
 
            var s = ((char)0xabcd).ToString();
            Assert.Equal(string.Format(format, s), FormatValue(s));
            Assert.Equal(string.Format(format, s), FormatValue(s, useHexadecimal: true));
 
            s = ((char)0xfeef).ToString();
            Assert.Equal(string.Format(format, s), FormatValue(s));
            Assert.Equal(string.Format(format, s), FormatValue(s, useHexadecimal: true));
 
            s = ((char)0xffef).ToString();
            Assert.Equal("\"\\uffef\"", FormatValue(s));
            Assert.Equal("\"\\uffef\"", FormatValue(s, useHexadecimal: true));
 
            s = char.MaxValue.ToString();
            Assert.Equal("\"\\uffff\"", FormatValue(s));
            Assert.Equal("\"\\uffff\"", FormatValue(s, useHexadecimal: true));
 
            string multiByte = "\ud83c\udfc8"; // unicode surrogates properly paired representing a printable Unicode codepoint
            Assert.Equal(string.Format(format, "🏈"), FormatValue(multiByte));
            Assert.Equal(string.Format(format, "🏈"), FormatValue(multiByte, useHexadecimal: true));
            Assert.Equal("🏈", multiByte);
 
            multiByte = "\udbff\udfff"; // unicode surrogates representing an unprintable Unicode codepoint
            Assert.Equal(string.Format(format, "\\U0010ffff"), FormatValue(multiByte));
            Assert.Equal(string.Format(format, "\\U0010ffff"), FormatValue(multiByte, useHexadecimal: true));
 
            multiByte = "\udfc8\ud83c"; // unicode surrogates not properly paired (in the wrong order in this case)
            Assert.Equal(string.Format(format, "\\udfc8\\ud83c"), FormatValue(multiByte));
            Assert.Equal(string.Format(format, "\\udfc8\\ud83c"), FormatValue(multiByte, useHexadecimal: true));
        }
 
        private static string FormatStringChar(char c)
        {
            return (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.Control)
                ? $"\\u{((int)c).ToString("x4")}"
                : c.ToString();
        }
 
        [Fact]
        public void Void()
        {
            // Something happens but, in practice, we expect the debugger to recognize
            // that the value is of type void and turn it into the error string 
            // "Expression has been evaluated and has no value".
            Assert.Equal("{void}", FormatValue(null, typeof(void)));
        }
 
        [Fact]
        public void InvalidValue_1()
        {
            const string errorMessage = "An error has occurred.";
            var clrValue = CreateDkmClrValue(errorMessage, typeof(string), evalFlags: DkmEvaluationResultFlags.None, valueFlags: DkmClrValueFlags.Error);
            Assert.Equal(errorMessage, ((DkmFailedEvaluationResult)FormatResult("invalidIdentifier", clrValue)).ErrorMessage);
        }
 
        [Fact]
        public void InvalidValue_2()
        {
            const string errorMessage = "An error has occurred.";
            var clrValue = CreateDkmClrValue(errorMessage, typeof(int), evalFlags: DkmEvaluationResultFlags.None, valueFlags: DkmClrValueFlags.Error);
            Assert.Equal(errorMessage, ((DkmFailedEvaluationResult)FormatResult("invalidIdentifier", clrValue)).ErrorMessage);
        }
 
        [Fact]
        public void NonFlagsEnum()
        {
            var source = @"
enum E
{
    A = 1,
    B = 2,
}
";
            var assembly = GetAssembly(source);
 
            var type = assembly.GetType("E");
 
            Assert.Equal("0", FormatValue(0, type));
            Assert.Equal("A", FormatValue(1, type));
            Assert.Equal("B", FormatValue(2, type));
            Assert.Equal("3", FormatValue(3, type));
        }
 
        [Fact]
        public void NonFlagsEnum_Negative()
        {
            var source = @"
enum E
{
    A = -1,
    B = -2,
}
";
            var assembly = GetAssembly(source);
 
            var type = assembly.GetType("E");
 
            Assert.Equal("0", FormatValue(0, type));
            Assert.Equal("A", FormatValue(-1, type));
            Assert.Equal("B", FormatValue(-2, type));
            Assert.Equal("-3", FormatValue(-3, type));
        }
 
        [Fact]
        public void NonFlagsEnum_Order()
        {
            var source = @"
enum E1 
{
    A = 1,
    B = 1,
}
 
enum E2
{
    B = 1,
    A = 1,
}
";
            var assembly = GetAssembly(source);
 
            var e1 = assembly.GetType("E1");
            var e2 = assembly.GetType("E2");
 
            Assert.Equal("A", FormatValue(1, e1));
            Assert.Equal("A", FormatValue(1, e2));
        }
 
        [Fact]
        public void FlagsEnum()
        {
            var source = @"
using System;
 
[Flags]
enum E
{
    A = 1,
    B = 2,
}
";
            var assembly = GetAssembly(source);
 
            var type = assembly.GetType("E");
 
            Assert.Equal("0", FormatValue(0, type));
            Assert.Equal("A", FormatValue(1, type));
            Assert.Equal("B", FormatValue(2, type));
            Assert.Equal("A | B", FormatValue(3, type));
            Assert.Equal("4", FormatValue(4, type));
        }
 
        [Fact]
        public void FlagsEnum_Zero()
        {
            var source = @"
using System;
 
[Flags]
enum E
{
    None = 0,
    A = 1,
    B = 2,
}
";
            var assembly = GetAssembly(source);
 
            var type = assembly.GetType("E");
 
            Assert.Equal("None", FormatValue(0, type));
            Assert.Equal("A", FormatValue(1, type));
            Assert.Equal("B", FormatValue(2, type));
            Assert.Equal("A | B", FormatValue(3, type));
            Assert.Equal("4", FormatValue(4, type));
        }
 
        [Fact]
        public void FlagsEnum_Combination()
        {
            var source = @"
using System;
 
[Flags]
enum E
{
    None = 0,
    A = 1,
    B = 2,
    C = A | B,
}
";
            var assembly = GetAssembly(source);
 
            var type = assembly.GetType("E");
 
            Assert.Equal("None", FormatValue(0, type));
            Assert.Equal("A", FormatValue(1, type));
            Assert.Equal("B", FormatValue(2, type));
            Assert.Equal("C", FormatValue(3, type));
            Assert.Equal("4", FormatValue(4, type));
        }
 
        [Fact]
        public void FlagsEnum_Negative()
        {
            var source = @"
using System;
 
[Flags]
enum E
{
    None = 0,
    A = -1,
    B = -2,
}
";
            var assembly = GetAssembly(source);
 
            var type = assembly.GetType("E");
 
            Assert.Equal("None", FormatValue(0, type));
            Assert.Equal("A", FormatValue(-1, type));
            Assert.Equal("B", FormatValue(-2, type));
            Assert.Equal("-3", FormatValue(-3, type));
            Assert.Equal("-4", FormatValue(-4, type));
        }
 
        [Fact]
        public void FlagsEnum_Order()
        {
            var source = @"
using System;
 
[Flags]
enum E1 
{
    A = 1,
    B = 1,
    C = 2,
    D = 2,
}
 
[Flags]
enum E2
{
    D = 2,
    C = 2,
    B = 1,
    A = 1,
}
";
            var assembly = GetAssembly(source);
 
            var e1 = assembly.GetType("E1");
            var e2 = assembly.GetType("E2");
 
            Assert.Equal("0", FormatValue(0, e1));
            Assert.Equal("A", FormatValue(1, e1));
            Assert.Equal("C", FormatValue(2, e1));
            Assert.Equal("A | C", FormatValue(3, e1));
 
            Assert.Equal("0", FormatValue(0, e2));
            Assert.Equal("A", FormatValue(1, e2));
            Assert.Equal("C", FormatValue(2, e2));
            Assert.Equal("A | C", FormatValue(3, e2));
        }
 
        [Fact]
        public void Arrays()
        {
            var source = @"
namespace N
{
    public class A<T>
    {
        public class B<U>
        {
        }
    }
}
";
            var assembly = GetAssembly(source);
            var typeA = assembly.GetType("N.A`1");
            var typeB = typeA.GetNestedType("B`1");
            var constructedType = typeB.MakeGenericType(typeof(bool), typeof(long));
 
            var vectorInstance = Array.CreateInstance(constructedType, 2);
            var matrixInstance = Array.CreateInstance(constructedType, 3, 4);
            var irregularInstance = Array.CreateInstance(constructedType, new[] { 1, 2 }, new[] { 3, 4 });
 
            Assert.Equal("{N.A<bool>.B<long>[2]}", FormatValue(vectorInstance));
            Assert.Equal("{N.A<bool>.B<long>[3, 4]}", FormatValue(matrixInstance));
            Assert.Equal("{N.A<bool>.B<long>[3..3, 4..5]}", FormatValue(irregularInstance));
 
            Assert.Equal("{N.A<bool>.B<long>[0x00000002]}", FormatValue(vectorInstance, useHexadecimal: true));
            Assert.Equal("{N.A<bool>.B<long>[0x00000003, 0x00000004]}", FormatValue(matrixInstance, useHexadecimal: true));
            Assert.Equal("{N.A<bool>.B<long>[0x00000003..0x00000003, 0x00000004..0x00000005]}", FormatValue(irregularInstance, useHexadecimal: true));
        }
 
        [Fact]
        public void Pointers()
        {
            var pointerType = typeof(int).MakePointerType();
            var doublePointerType = pointerType.MakePointerType();
 
            Assert.Equal("0x00000001", FormatValue(1, pointerType, useHexadecimal: false)); // In hex, regardless.
            Assert.Equal("0x00000001", FormatValue(1, pointerType, useHexadecimal: true));
 
            Assert.Equal("0xffffffff", FormatValue(-1, doublePointerType, useHexadecimal: false)); // In hex, regardless.
            Assert.Equal("0xffffffff", FormatValue(-1, doublePointerType, useHexadecimal: true));
        }
 
        [Fact]
        public void Nullable()
        {
            var source = @"
namespace N
{
    public struct A<T>
    {
        public struct B<U>
        {
            public override string ToString()
            {
                return ""ToString() called."";
            }
        }
    }
}
";
            var assembly = GetAssembly(source);
            var typeA = assembly.GetType("N.A`1");
            var typeB = typeA.GetNestedType("B`1");
            var constructedType = typeB.MakeGenericType(typeof(bool), typeof(long));
            var nullableType = typeof(Nullable<>);
            var nullableConstructedType = nullableType.MakeGenericType(constructedType);
            var nullableInt = nullableType.MakeGenericType(typeof(int));
 
            Assert.Equal("null", FormatValue(null, nullableConstructedType));
            Assert.Equal("{ToString() called.}", FormatValue(constructedType.Instantiate(), nullableConstructedType));
 
            Assert.Equal("null", FormatValue(null, nullableInt));
            Assert.Equal("1", FormatValue(1, nullableInt));
        }
 
        [Fact]
        public void ToStringOverrides()
        {
            var source = @"
public class A<T>
{
}
 
public class B : A<int>
{
    public override string ToString()
    {
        return ""B.ToString()"";
    }
}
 
public class C : B
{
}
";
            var assembly = GetAssembly(source);
            var typeC = assembly.GetType("C");
            var typeB = assembly.GetType("B");
            var typeA = typeB.BaseType;
 
            Assert.Equal("null", FormatValue(null, typeA));
            Assert.Equal("null", FormatValue(null, typeB));
            Assert.Equal("null", FormatValue(null, typeC));
 
            Assert.Equal("{A<int>}", FormatValue(typeA.Instantiate()));
            Assert.Equal("{B.ToString()}", FormatValue(typeB.Instantiate()));
            Assert.Equal("{B.ToString()}", FormatValue(typeC.Instantiate()));
        }
 
        [Fact]
        public void ValuesWithUnderlyingString()
        {
            Assert.True(HasUnderlyingString("Test"));
            Assert.False(HasUnderlyingString(null, typeof(string)));
            Assert.False(HasUnderlyingString(0));
            Assert.False(HasUnderlyingString(DkmEvaluationFlags.None)); // Enum
            Assert.False(HasUnderlyingString('a'));
            Assert.False(HasUnderlyingString(new int[] { 1, 2, 3 }));
            Assert.False(HasUnderlyingString(new object()));
            Assert.False(HasUnderlyingString(0, typeof(int*)));
        }
 
        [Fact]
        public void VisualizeString()
        {
            Assert.Equal("\r\n", GetUnderlyingString("\r\n"));
        }
 
        [Fact]
        public void VisualizeSqlString()
        {
            var source = @"
namespace System.Data.SqlTypes
{
    public struct SqlString
    {
        private string m_value;
 
        public void Set(string value)
        {
            m_value = value;
        }
    }
}
";
            var assembly = GetAssembly(source);
            var type = assembly.GetType("System.Data.SqlTypes.SqlString");
 
            object sqlString = type.Instantiate();
            var setMethod = type.GetMethod("Set");
            setMethod.Invoke(sqlString, new object[] { "Test" });
 
            Assert.Equal("Test", GetUnderlyingString(sqlString));
        }
 
        [Fact]
        public void VisualizeXNode()
        {
            var source = @"
namespace System.Xml.Linq
{
    public class XNode
    {
        public override string ToString()
        {
            return ""Test1"";
        }
    }
 
    public class XContainer : XNode
    {
    }
 
    public class XElement : XContainer
    {
        public override string ToString()
        {
            return ""Test2"";
        }
    }
}
";
            var assembly = GetAssembly(source);
            var xnType = assembly.GetType("System.Xml.Linq.XNode");
            var xeType = assembly.GetType("System.Xml.Linq.XElement");
 
            Assert.Equal("Test1", GetUnderlyingString(xnType.Instantiate()));
            Assert.Equal("Test2", GetUnderlyingString(xeType.Instantiate()));
        }
 
        [Fact]
        public void HostValueNotFound_int()
        {
            var clrValue = new DkmClrValue(
                value: null, hostObjectValue: null, new DkmClrType((TypeImpl)typeof(int)),
                alias: null, evalFlags: DkmEvaluationResultFlags.None, valueFlags: DkmClrValueFlags.None);
 
            Assert.Equal(Resources.HostValueNotFound, FormatValue(clrValue));
        }
 
        [Fact]
        public void HostValueNotFound_char()
        {
            var clrValue = new DkmClrValue(
                value: null, hostObjectValue: null, new DkmClrType((TypeImpl)typeof(char)),
                alias: null, evalFlags: DkmEvaluationResultFlags.None, valueFlags: DkmClrValueFlags.None);
 
            Assert.Equal(Resources.HostValueNotFound, FormatValue(clrValue));
        }
 
        [Fact]
        public void HostValueNotFound_IntPtr()
        {
            var clrValue = new DkmClrValue(
                value: null, hostObjectValue: null, new DkmClrType((TypeImpl)typeof(IntPtr)),
                alias: null, evalFlags: DkmEvaluationResultFlags.None, valueFlags: DkmClrValueFlags.None);
 
            Assert.Equal(Resources.HostValueNotFound, FormatValue(clrValue));
        }
 
        [Fact]
        public void HostValueNotFound_UIntPtr()
        {
            var clrValue = new DkmClrValue(
                value: null, hostObjectValue: null, new DkmClrType((TypeImpl)typeof(UIntPtr)),
                alias: null, evalFlags: DkmEvaluationResultFlags.None, valueFlags: DkmClrValueFlags.None);
 
            Assert.Equal(Resources.HostValueNotFound, FormatValue(clrValue));
        }
 
        [Fact]
        public void HostValueNotFound_enum()
        {
            var clrValue = new DkmClrValue(
                value: null, hostObjectValue: null, new DkmClrType((TypeImpl)typeof(TestEnum)),
                alias: null, evalFlags: DkmEvaluationResultFlags.None, valueFlags: DkmClrValueFlags.None);
 
            Assert.Equal(Resources.HostValueNotFound, FormatValue(clrValue));
        }
 
        private enum TestEnum
        {
            One
        }
    }
}