File: Platform\CoreClr\CoreCLRRuntimeEnvironment.cs
Web Access
Project: src\src\Compilers\Test\Core\Microsoft.CodeAnalysis.Test.Utilities.csproj (Microsoft.CodeAnalysis.Test.Utilities)
// 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
 
#if NET
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using static Roslyn.Test.Utilities.RuntimeEnvironmentUtilities;
 
namespace Roslyn.Test.Utilities.CoreClr
{
    public class CoreCLRRuntimeEnvironment : IRuntimeEnvironment, IInternalRuntimeEnvironment
    {
        static CoreCLRRuntimeEnvironment()
        {
            SharedConsole.OverrideConsole();
        }
 
        private readonly IEnumerable<ModuleData> _additionalDependencies;
        private EmitData _emitData;
        private readonly CompilationTestData _testData = new CompilationTestData();
 
        public CoreCLRRuntimeEnvironment(IEnumerable<ModuleData> additionalDependencies = null)
        {
            _additionalDependencies = additionalDependencies;
        }
 
        public void Emit(
            Compilation mainCompilation,
            IEnumerable<ResourceDescription> manifestResources,
            EmitOptions emitOptions,
            bool usePdbForDebugging = false)
        {
            _testData.Methods.Clear();
 
            var diagnostics = DiagnosticBag.GetInstance();
            var dependencies = new List<ModuleData>();
            var mainOutput = EmitCompilation(mainCompilation, manifestResources, dependencies, diagnostics, _testData, emitOptions);
 
            _emitData = new EmitData();
            _emitData.Diagnostics = diagnostics.ToReadOnlyAndFree();
 
            if (mainOutput.HasValue)
            {
                var mainImage = mainOutput.Value.Assembly;
                var mainPdb = mainOutput.Value.Pdb;
                var corLibIdentity = mainCompilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly.Identity;
                var identity = mainCompilation.Assembly.Identity;
                _emitData.MainModule = new ModuleData(
                    identity,
                    mainCompilation.Options.OutputKind,
                    mainImage,
                    pdb: usePdbForDebugging ? mainPdb : default(ImmutableArray<byte>),
                    inMemoryModule: true,
                    isCorLib: corLibIdentity == identity);
                _emitData.MainModulePdb = mainPdb;
                _emitData.AllModuleData = dependencies;
 
                // We need to add the main module so that it gets checked against already loaded assembly names.
                // If an assembly is loaded directly via PEVerify(image) another assembly of the same full name
                // can't be loaded as a dependency (via Assembly.ReflectionOnlyLoad) in the same domain.
                _emitData.AllModuleData.Insert(0, _emitData.MainModule);
                _emitData.RuntimeData = new RuntimeData(dependencies);
            }
            else
            {
                DumpAssemblyData(dependencies, out var dumpDir);
 
                // This method MUST throw if compilation did not succeed.  If compilation succeeded and there were errors, that is bad.
                // Please see KevinH if you intend to change this behavior as many tests expect the Exception to indicate failure.
                throw new EmitException(_emitData.Diagnostics, dumpDir);
            }
        }
 
        public int Execute(string moduleName, string[] args, string expectedOutput, bool trimOutput = true)
        {
            var emitData = GetEmitData();
            emitData.RuntimeData.ExecuteRequested = true;
            var (exitCode, output) = emitData.LoadContext.Execute(GetMainImage(), args, expectedOutput?.Length);
 
            if (expectedOutput != null)
            {
                if (trimOutput ? (expectedOutput.Trim() != output.Trim()) : (expectedOutput != output))
                    throw new ExecutionException(expectedOutput, output, moduleName);
            }
 
            return exitCode;
        }
 
        private EmitData GetEmitData() => _emitData ?? throw new InvalidOperationException("Must call Emit before calling this method");
 
        public IList<ModuleData> GetAllModuleData() => GetEmitData().AllModuleData;
 
        public ImmutableArray<Diagnostic> GetDiagnostics() => GetEmitData().Diagnostics;
 
        public ImmutableArray<byte> GetMainImage() => GetEmitData().MainModule.Image;
 
        public ImmutableArray<byte> GetMainPdb() => GetEmitData().MainModulePdb;
 
        public SortedSet<string> GetMemberSignaturesFromMetadata(string fullyQualifiedTypeName, string memberName) =>
            GetEmitData().GetMemberSignaturesFromMetadata(fullyQualifiedTypeName, memberName);
 
        public void Verify(Verification verification)
        {
        }
 
        public string[] VerifyModules(string[] modulesToVerify)
        {
            // TODO(https://github.com/dotnet/coreclr/issues/295): Implement peverify
            return null;
        }
 
        CompilationTestData IInternalRuntimeEnvironment.GetCompilationTestData()
        {
            return _testData;
        }
 
        public void Dispose()
        {
            // We need Dispose to satisfy the IRuntimeEnvironment interface, but 
            // we don't really need it.
        }
 
        public void CaptureOutput(Action action, int expectedLength, out string output, out string errorOutput)
            => SharedConsole.CaptureOutput(action, expectedLength, out output, out errorOutput);
 
        private sealed class RuntimeData
        {
            internal TestExecutionLoadContext LoadContext { get; }
            internal bool PeverifyRequested { get; set; }
            internal bool ExecuteRequested { get; set; }
            internal bool Disposed { get; set; }
            internal int ConflictCount { get; set; }
 
            public RuntimeData(IList<ModuleData> dependencies)
            {
                LoadContext = new TestExecutionLoadContext(dependencies);
            }
        }
 
        private sealed class EmitData
        {
            internal RuntimeData RuntimeData;
 
            internal TestExecutionLoadContext LoadContext => RuntimeData?.LoadContext;
 
            // All of the <see cref="ModuleData"/> created for this Emit
            internal List<ModuleData> AllModuleData;
 
            // Main module for this emit
            internal ModuleData MainModule;
            internal ImmutableArray<byte> MainModulePdb;
 
            internal ImmutableArray<Diagnostic> Diagnostics;
 
            public SortedSet<string> GetMemberSignaturesFromMetadata(string fullyQualifiedTypeName, string memberName)
            {
                return LoadContext.GetMemberSignaturesFromMetadata(fullyQualifiedTypeName, memberName, AllModuleData.Select(x => x.Id));
            }
        }
    }
}
#endif