File: EndToEndTests.cs
Web Access
Project: ..\..\..\src\Compilers\CSharp\Test\EndToEnd\Microsoft.CodeAnalysis.CSharp.EndToEnd.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.EndToEnd.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 Roslyn.Test.Utilities;
using System;
using System.Text;
using Xunit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.EndToEnd
{
    [TestCaseOrderer("XUnit.Project.Orderers.AlphabeticalOrderer", "XUnit.Project")]
    public class EndToEndTests : EmitMetadataTestBase
    {
        /// <summary>
        /// These tests are very sensitive to stack size hence we use a fresh thread to ensure there 
        /// is a consistent stack size for them to execute in. 
        /// </summary>
        /// <param name="action"></param>
        private static void RunInThread(Action action)
        {
            Exception exception = null;
            var thread = new System.Threading.Thread(() =>
            {
                try
                {
                    action();
                }
                catch (Exception ex)
                {
                    exception = ex;
                }
            }, 0);
 
            thread.Start();
            thread.Join();
 
            if (exception is object)
            {
                throw exception;
            }
        }
 
        private static void RunTest(int expectedDepth, Action<int> runTest)
        {
            if (runTestAndCatch(expectedDepth))
            {
                return;
            }
 
            int minDepth = 0;
            int maxDepth = expectedDepth;
            int actualDepth;
            while (true)
            {
                int depth = (maxDepth - minDepth) / 2 + minDepth;
                if (depth <= minDepth)
                {
                    actualDepth = minDepth;
                    break;
                }
                if (depth >= maxDepth)
                {
                    actualDepth = maxDepth;
                    break;
                }
                if (runTestAndCatch(depth))
                {
                    minDepth = depth;
                }
                else
                {
                    maxDepth = depth;
                }
            }
            Assert.Equal(expectedDepth, actualDepth);
 
            bool runTestAndCatch(int depth)
            {
                try
                {
                    runTest(depth);
                    return true;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }
 
        // This test is a canary attempting to make sure that we don't regress the # of fluent calls that 
        // the compiler can handle. 
        [WorkItem(16669, "https://github.com/dotnet/roslyn/issues/16669")]
        [ConditionalFact(typeof(WindowsOrLinuxOnly)), WorkItem(34880, "https://github.com/dotnet/roslyn/issues/34880")]
        public void OverflowOnFluentCall()
        {
            int numberFluentCalls = (IntPtr.Size, ExecutionConditionUtil.Configuration) switch
            {
                (4, ExecutionConfiguration.Debug) => 520, // 510
                (4, ExecutionConfiguration.Release) => 1400, // 1310
                (8, ExecutionConfiguration.Debug) => 250, // 225,
                (8, ExecutionConfiguration.Release) => 700, // 620
                _ => throw new Exception($"Unexpected configuration {IntPtr.Size * 8}-bit {ExecutionConditionUtil.Configuration}")
            };
 
            // <path>\xunit.console.exe "<path>\CSharpCompilerEmitTest\Roslyn.Compilers.CSharp.Emit.UnitTests.dll"  -noshadow -verbose -class "Microsoft.CodeAnalysis.CSharp.UnitTests.Emit.EndToEndTests"
            // <path>\xunit.console.x86.exe "<path>\CSharpCompilerEmitTest\Roslyn.Compilers.CSharp.Emit.UnitTests.dll"  -noshadow -verbose -class "Microsoft.CodeAnalysis.CSharp.UnitTests.Emit.EndToEndTests"
            // Un-comment loop below and use above commands to figure out the new limits
            //for (int i = 0; i < numberFluentCalls; i = i + 10)
            //{
            //    Console.WriteLine($"Depth: {i}");
            //    tryCompileDeepFluentCalls(i);
            //}
 
            tryCompileDeepFluentCalls(numberFluentCalls);
 
            void tryCompileDeepFluentCalls(int depth)
            {
                var builder = new StringBuilder();
                builder.AppendLine(
        @"class C {
    C M(string x) { return this; }
    void M2() {
        new C()
");
                for (int i = 0; i < depth; i++)
                {
                    builder.AppendLine(@"            .M(""test"")");
                }
                builder.AppendLine(
                   @"            .M(""test"");
    }
}");
 
                var source = builder.ToString();
                RunInThread(() =>
                {
                    var options = TestOptions.DebugDll.WithConcurrentBuild(false);
                    var compilation = CreateCompilation(source, options: options);
                    compilation.VerifyDiagnostics();
                    compilation.EmitToArray();
                });
            }
        }
 
        [ConditionalFact(typeof(WindowsOrLinuxOnly))]
        [WorkItem(33909, "https://github.com/dotnet/roslyn/issues/33909")]
        [WorkItem(34880, "https://github.com/dotnet/roslyn/issues/34880")]
        [WorkItem(53361, "https://github.com/dotnet/roslyn/issues/53361")]
        public void DeeplyNestedGeneric()
        {
            int nestingLevel = (IntPtr.Size, ExecutionConditionUtil.Configuration) switch
            {
                // Legacy baselines are indicated by comments
                (4, ExecutionConfiguration.Debug) => 370, // 270
                (4, ExecutionConfiguration.Release) => 1290, // 1290
                (8, ExecutionConfiguration.Debug) => 270, // 170
                (8, ExecutionConfiguration.Release) => 730, // 730
                _ => throw new Exception($"Unexpected configuration {IntPtr.Size * 8}-bit {ExecutionConditionUtil.Configuration}")
            };
 
            // Un-comment loop below and use above commands to figure out the new limits
            //Console.WriteLine($"Using architecture: {ExecutionConditionUtil.Architecture}, configuration: {ExecutionConditionUtil.Configuration}");
            //for (int i = nestingLevel; i < int.MaxValue; i = i + 10)
            //{
            //    var start = DateTime.UtcNow;
            //    Console.Write($"Depth: {i}");
            //    runDeeplyNestedGenericTest(i);
            //    Console.WriteLine($" - {DateTime.UtcNow - start}");
            //}
 
            runDeeplyNestedGenericTest(nestingLevel);
 
            void runDeeplyNestedGenericTest(int nestingLevel)
            {
                var builder = new StringBuilder();
                builder.AppendLine(@"
#pragma warning disable 168 // Unused local
using System;
 
public class Test
{
    public static void Main(string[] args)
    {
");
 
                for (var i = 0; i < nestingLevel; i++)
                {
                    if (i > 0)
                    {
                        builder.Append('.');
                    }
                    builder.Append($"MyStruct{i}<int>");
                }
 
                builder.AppendLine(" local;");
                builder.AppendLine(@"
        Console.WriteLine(""Pass"");
    }
}");
 
                for (int i = 0; i < nestingLevel; i++)
                {
                    builder.AppendLine($"public struct MyStruct{i}<T{i}> {{");
                }
                for (int i = 0; i < nestingLevel; i++)
                {
                    builder.AppendLine("}");
                }
 
                var source = builder.ToString();
                RunInThread(() =>
                {
                    var compilation = CreateCompilation(source, options: TestOptions.DebugExe.WithConcurrentBuild(false));
                    compilation.VerifyDiagnostics();
 
                    // PEVerify is skipped here as it doesn't scale to this level of nested generics. After 
                    // about 600 levels of nesting it will not return in any reasonable amount of time.
                    CompileAndVerify(compilation, expectedOutput: "Pass", verify: Verification.Skipped);
                });
            }
        }
 
        [ConditionalFact(typeof(WindowsOrLinuxOnly), typeof(NoIOperationValidation))]
        public void NestedIfStatements()
        {
            int nestingLevel = (IntPtr.Size, ExecutionConditionUtil.Configuration) switch
            {
                (4, ExecutionConfiguration.Debug) => 310,
                (4, ExecutionConfiguration.Release) => 1650,
                (8, ExecutionConfiguration.Debug) => 200,
                (8, ExecutionConfiguration.Release) => 780,
                _ => throw new Exception($"Unexpected configuration {IntPtr.Size * 8}-bit {ExecutionConditionUtil.Configuration}")
            };
 
            RunTest(nestingLevel, runTest);
 
            static void runTest(int nestingLevel)
            {
                var builder = new StringBuilder();
                builder.AppendLine(
@"class Program
{
    static bool F(int i) => true;
    static void Main()
    {");
                for (int i = 0; i < nestingLevel; i++)
                {
                    builder.AppendLine(
$@"        if (F({i}))
        {{");
                }
                for (int i = 0; i < nestingLevel; i++)
                {
                    builder.AppendLine("        }");
                }
                builder.AppendLine(
@"    }
}");
                var source = builder.ToString();
                RunInThread(() =>
                {
                    var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithConcurrentBuild(false));
                    comp.VerifyDiagnostics();
                });
            }
        }
 
        [WorkItem(42361, "https://github.com/dotnet/roslyn/issues/42361")]
        [ConditionalFact(typeof(WindowsOrLinuxOnly))]
        public void Constraints()
        {
            int n = (IntPtr.Size, ExecutionConditionUtil.Configuration) switch
            {
                (4, ExecutionConfiguration.Debug) => 420,
                (4, ExecutionConfiguration.Release) => 1100,
                (8, ExecutionConfiguration.Debug) => 180,
                (8, ExecutionConfiguration.Release) => 400,
                _ => throw new Exception($"Unexpected configuration {IntPtr.Size * 8}-bit {ExecutionConditionUtil.Configuration}")
            };
 
            RunTest(n, runTest);
 
            static void runTest(int n)
            {
                // class C0<T> where T : C1<T> { }
                // class C1<T> where T : C2<T> { }
                // ...
                // class CN<T> where T : C0<T> { }
                var sourceBuilder = new StringBuilder();
                var diagnosticsBuilder = ArrayBuilder<DiagnosticDescription>.GetInstance();
                for (int i = 0; i <= n; i++)
                {
                    int next = (i == n) ? 0 : i + 1;
                    sourceBuilder.AppendLine($"class C{i}<T> where T : C{next}<T> {{ }}");
                    diagnosticsBuilder.Add(Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "T").WithArguments($"C{i}<T>", $"C{next}<T>", "T", "T"));
                }
                var source = sourceBuilder.ToString();
                var diagnostics = diagnosticsBuilder.ToArrayAndFree();
 
                RunInThread(() =>
                {
                    var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithConcurrentBuild(false));
                    var type = comp.GetMember<NamedTypeSymbol>("C0");
                    var typeParameter = type.TypeParameters[0];
                    Assert.True(typeParameter.IsReferenceType);
                    comp.VerifyDiagnostics(diagnostics);
                });
            }
        }
    }
}