File: CommonTestBase.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.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Test.Utilities
{
    [Flags]
    public enum VerificationStatus
    {
        /// <summary>
        /// default(<see cref="Verification"/>) should be passing.
        /// </summary>
        Passes = 0,
 
        Skipped = 1 << 1,
 
        FailsPEVerify = 1 << 2,
        FailsILVerify = 1 << 3,
        Fails = FailsPEVerify | FailsILVerify,
 
        PassesOrFailFast = 1 << 4,
    }
 
    public readonly struct Verification
    {
        public VerificationStatus Status { get; init; }
        public string? ILVerifyMessage { get; init; }
        public string? PEVerifyMessage { get; init; }
 
        /// <summary>
        /// True if the expected messages include member tokens and MVIDs.
        /// </summary>
        public bool IncludeTokensAndModuleIds { get; init; }
 
        public static readonly Verification Skipped = new() { Status = VerificationStatus.Skipped };
        public static readonly Verification Passes = new() { Status = VerificationStatus.Passes };
        public static readonly Verification FailsPEVerify = new() { Status = VerificationStatus.FailsPEVerify };
        public static readonly Verification FailsILVerify = new() { Status = VerificationStatus.FailsILVerify };
        public static readonly Verification Fails = new() { Status = VerificationStatus.Fails };
        public static readonly Verification PassesOrFailFast = new() { Status = VerificationStatus.PassesOrFailFast };
 
        public Verification WithILVerifyMessage(string message)
            => this with { ILVerifyMessage = message };
    }
 
#nullable disable
 
    /// <summary>
    /// Base class for all language specific tests.
    /// </summary>
    public abstract partial class CommonTestBase : TestBase
    {
        #region Emit
 
        internal CompilationVerifier CompileAndVerifyCommon(
            Compilation compilation,
            IEnumerable<ResourceDescription> manifestResources = null,
            IEnumerable<ModuleData> dependencies = null,
            Action<IModuleSymbol> sourceSymbolValidator = null,
            Action<PEAssembly> assemblyValidator = null,
            Action<IModuleSymbol> symbolValidator = null,
            SignatureDescription[] expectedSignatures = null,
            string expectedOutput = null,
            bool trimOutput = true,
            int? expectedReturnCode = null,
            string[] args = null,
            EmitOptions emitOptions = null,
            Verification verify = default)
        {
            Assert.NotNull(compilation);
 
            Assert.True(expectedOutput == null ||
                (compilation.Options.OutputKind == OutputKind.ConsoleApplication || compilation.Options.OutputKind == OutputKind.WindowsApplication),
                "Compilation must be executable if output is expected.");
 
            if (sourceSymbolValidator != null)
            {
                var module = compilation.Assembly.Modules.First();
                sourceSymbolValidator(module);
            }
 
            CompilationVerifier result = null;
 
            var verifier = Emit(compilation,
                                dependencies,
                                manifestResources,
                                expectedSignatures,
                                expectedOutput,
                                trimOutput,
                                expectedReturnCode,
                                args ?? Array.Empty<string>(),
                                assemblyValidator,
                                symbolValidator,
                                emitOptions,
                                verify);
 
            if (result == null)
            {
                result = verifier;
            }
            else
            {
                // only one emitter should return a verifier
                Assert.Null(verifier);
            }
 
            // If this fails, it means that more that all emitters failed to return a validator
            // (i.e. none thought that they were applicable for the given input parameters).
            Assert.NotNull(result);
 
            return result;
        }
 
        internal CompilationVerifier CompileAndVerifyFieldMarshalCommon(Compilation compilation, Dictionary<string, byte[]> expectedBlobs, bool isField = true)
        {
            return CompileAndVerifyFieldMarshalCommon(
                compilation,
                (s, _omitted1) =>
                {
                    Assert.True(expectedBlobs.ContainsKey(s), "Expecting marshalling blob for " + (isField ? "field " : "parameter ") + s);
                    return expectedBlobs[s];
                },
                isField);
        }
 
        internal CompilationVerifier CompileAndVerifyFieldMarshalCommon(Compilation compilation, Func<string, PEAssembly, byte[]> getExpectedBlob, bool isField = true)
        {
            return CompileAndVerifyCommon(compilation, assemblyValidator: (assembly) => MetadataValidation.MarshalAsMetadataValidator(assembly, getExpectedBlob, isField));
        }
 
        internal static void RunValidators(CompilationVerifier verifier, Action<PEAssembly> assemblyValidator, Action<IModuleSymbol> symbolValidator)
        {
            Assert.True(assemblyValidator != null || symbolValidator != null);
 
            var emittedMetadata = verifier.GetMetadata();
 
            if (assemblyValidator != null)
            {
                Assert.Equal(MetadataImageKind.Assembly, emittedMetadata.Kind);
 
                var assembly = ((AssemblyMetadata)emittedMetadata).GetAssembly();
                assemblyValidator(assembly);
            }
 
            if (symbolValidator != null)
            {
                var reference = emittedMetadata.Kind == MetadataImageKind.Assembly
                    ? ((AssemblyMetadata)emittedMetadata).GetReference()
                    : ((ModuleMetadata)emittedMetadata).GetReference();
 
                var moduleSymbol = verifier.GetSymbolFromMetadata(reference, verifier.Compilation.Options.MetadataImportOptions);
                symbolValidator(moduleSymbol);
            }
        }
 
        internal CompilationVerifier Emit(
            Compilation compilation,
            IEnumerable<ModuleData> dependencies,
            IEnumerable<ResourceDescription> manifestResources,
            SignatureDescription[] expectedSignatures,
            string expectedOutput,
            bool trimOutput,
            int? expectedReturnCode,
            string[] args,
            Action<PEAssembly> assemblyValidator,
            Action<IModuleSymbol> symbolValidator,
            EmitOptions emitOptions,
            Verification verify)
        {
            var verifier = new CompilationVerifier(compilation, VisualizeRealIL, dependencies);
 
            verifier.Emit(expectedOutput, trimOutput, expectedReturnCode, args, manifestResources, emitOptions, verify, expectedSignatures);
 
            if (assemblyValidator != null || symbolValidator != null)
            {
                // We're dual-purposing emitters here.  In this context, it
                // tells the validator the version of Emit that is calling it.
                RunValidators(verifier, assemblyValidator, symbolValidator);
            }
 
            return verifier;
        }
 
        /// <summary>
        /// Reads content of the specified file.
        /// </summary>
        /// <param name="path">The path to the file.</param>
        /// <returns>Read-only binary data read from the file.</returns>
        public static ImmutableArray<byte> ReadFromFile(string path)
        {
            return ImmutableArray.Create<byte>(File.ReadAllBytes(path));
        }
 
        internal static void EmitILToArray(
            string ilSource,
            bool appendDefaultHeader,
            bool includePdb,
            out ImmutableArray<byte> assemblyBytes,
            out ImmutableArray<byte> pdbBytes,
            bool autoInherit = true)
        {
            IlasmUtilities.IlasmTempAssembly(ilSource, appendDefaultHeader, includePdb, autoInherit, out var assemblyPath, out var pdbPath);
 
            Assert.NotNull(assemblyPath);
            Assert.Equal(pdbPath != null, includePdb);
 
            using (new DisposableFile(assemblyPath))
            {
                assemblyBytes = ReadFromFile(assemblyPath);
            }
 
            if (pdbPath != null)
            {
                using (new DisposableFile(pdbPath))
                {
                    pdbBytes = ReadFromFile(pdbPath);
                }
            }
            else
            {
                pdbBytes = default(ImmutableArray<byte>);
            }
        }
 
        internal static MetadataReference CompileIL(string ilSource, bool prependDefaultHeader = true, bool embedInteropTypes = false, bool autoInherit = true)
        {
            EmitILToArray(ilSource, prependDefaultHeader, includePdb: false, assemblyBytes: out var assemblyBytes, pdbBytes: out var pdbBytes, autoInherit: autoInherit);
            return AssemblyMetadata.CreateFromImage(assemblyBytes).GetReference(embedInteropTypes: embedInteropTypes);
        }
 
        internal static MetadataReference GetILModuleReference(string ilSource, bool prependDefaultHeader = true)
        {
            EmitILToArray(ilSource, prependDefaultHeader, includePdb: false, assemblyBytes: out var assemblyBytes, pdbBytes: out var pdbBytes);
            return ModuleMetadata.CreateFromImage(assemblyBytes).GetReference();
        }
 
        #endregion
 
        #region Compilation Creation Helpers
 
        protected CSharp.CSharpCompilation CreateCSharpCompilation(
            XCData code,
            CSharp.CSharpParseOptions parseOptions = null,
            CSharp.CSharpCompilationOptions compilationOptions = null,
            string assemblyName = null,
            IEnumerable<MetadataReference> referencedAssemblies = null)
        {
            return CreateCSharpCompilation(assemblyName, code, parseOptions, compilationOptions, referencedAssemblies, referencedCompilations: null);
        }
 
        protected CSharp.CSharpCompilation CreateCSharpCompilation(
            string assemblyName,
            XCData code,
            CSharp.CSharpParseOptions parseOptions = null,
            CSharp.CSharpCompilationOptions compilationOptions = null,
            IEnumerable<MetadataReference> referencedAssemblies = null,
            IEnumerable<Compilation> referencedCompilations = null)
        {
            return CreateCSharpCompilation(
                assemblyName,
                code.Value,
                parseOptions,
                compilationOptions,
                referencedAssemblies,
                referencedCompilations);
        }
 
        protected VisualBasic.VisualBasicCompilation CreateVisualBasicCompilation(
            XCData code,
            VisualBasic.VisualBasicParseOptions parseOptions = null,
            VisualBasic.VisualBasicCompilationOptions compilationOptions = null,
            string assemblyName = null,
            IEnumerable<MetadataReference> referencedAssemblies = null)
        {
            return CreateVisualBasicCompilation(assemblyName, code, parseOptions, compilationOptions, referencedAssemblies, referencedCompilations: null);
        }
 
        protected VisualBasic.VisualBasicCompilation CreateVisualBasicCompilation(
            string assemblyName,
            XCData code,
            VisualBasic.VisualBasicParseOptions parseOptions = null,
            VisualBasic.VisualBasicCompilationOptions compilationOptions = null,
            IEnumerable<MetadataReference> referencedAssemblies = null,
            IEnumerable<Compilation> referencedCompilations = null)
        {
            return CreateVisualBasicCompilation(
                assemblyName,
                code.Value,
                parseOptions,
                compilationOptions,
                referencedAssemblies,
                referencedCompilations);
        }
 
        protected CSharp.CSharpCompilation CreateCSharpCompilation(
            string code,
            CSharp.CSharpParseOptions parseOptions = null,
            CSharp.CSharpCompilationOptions compilationOptions = null,
            string assemblyName = null,
            IEnumerable<MetadataReference> referencedAssemblies = null)
        {
            return CreateCSharpCompilation(assemblyName, code, parseOptions, compilationOptions, referencedAssemblies, referencedCompilations: null);
        }
 
        protected CSharp.CSharpCompilation CreateCSharpCompilation(
            string assemblyName,
            string code,
            CSharp.CSharpParseOptions parseOptions = null,
            CSharp.CSharpCompilationOptions compilationOptions = null,
            IEnumerable<MetadataReference> referencedAssemblies = null,
            IEnumerable<Compilation> referencedCompilations = null)
        {
            return CreateCSharpCompilation(assemblyName, assemblyIdentity: null, code, parseOptions, compilationOptions, referencedAssemblies, referencedCompilations);
        }
 
        protected CSharp.CSharpCompilation CreateCSharpCompilation(
            AssemblyIdentity assemblyIdentity,
            string code,
            CSharp.CSharpParseOptions parseOptions = null,
            CSharp.CSharpCompilationOptions compilationOptions = null,
            IEnumerable<MetadataReference> referencedAssemblies = null,
            IEnumerable<Compilation> referencedCompilations = null)
        {
            return CreateCSharpCompilation(assemblyName: null, assemblyIdentity, code, parseOptions, compilationOptions, referencedAssemblies, referencedCompilations);
        }
 
        private CSharp.CSharpCompilation CreateCSharpCompilation(
            string assemblyName,
            AssemblyIdentity assemblyIdentity,
            string code,
            CSharp.CSharpParseOptions parseOptions = null,
            CSharp.CSharpCompilationOptions compilationOptions = null,
            IEnumerable<MetadataReference> referencedAssemblies = null,
            IEnumerable<Compilation> referencedCompilations = null)
        {
            Debug.Assert(assemblyName == null || assemblyIdentity == null || assemblyIdentity.Name == assemblyName);
            if (assemblyName == null)
            {
                assemblyName = assemblyIdentity?.Name ?? GetUniqueName();
            }
 
            if (parseOptions == null)
            {
                parseOptions = CSharp.CSharpParseOptions.Default.WithLanguageVersion(CSharp.LanguageVersion.Default).WithDocumentationMode(DocumentationMode.None);
            }
 
            if (compilationOptions == null)
            {
                compilationOptions = new CSharp.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
            }
 
            var references = new List<MetadataReference>();
            if (referencedAssemblies == null)
            {
                references.Add(MscorlibRef);
                references.Add(SystemRef);
                references.Add(SystemCoreRef);
                //TODO: references.Add(MsCSRef);
                references.Add(SystemXmlRef);
                references.Add(SystemXmlLinqRef);
            }
            else
            {
                references.AddRange(referencedAssemblies);
            }
 
            AddReferencedCompilations(referencedCompilations, references);
 
            var tree = CSharp.SyntaxFactory.ParseSyntaxTree(SourceText.From(code, encoding: null, SourceHashAlgorithms.Default), options: parseOptions);
 
            var compilation = CSharp.CSharpCompilation.Create(assemblyName, new[] { tree }, references, compilationOptions);
 
            if (assemblyIdentity != null)
            {
                ((SourceAssemblySymbol)compilation.Assembly).lazyAssemblyIdentity = assemblyIdentity;
            }
 
            return compilation;
        }
 
        protected VisualBasic.VisualBasicCompilation CreateVisualBasicCompilation(
            string code,
            VisualBasic.VisualBasicParseOptions parseOptions = null,
            VisualBasic.VisualBasicCompilationOptions compilationOptions = null,
            string assemblyName = null,
            IEnumerable<MetadataReference> referencedAssemblies = null)
        {
            return CreateVisualBasicCompilation(assemblyName, code, parseOptions, compilationOptions, referencedAssemblies, referencedCompilations: null);
        }
 
        protected VisualBasic.VisualBasicCompilation CreateVisualBasicCompilation(
            string[] files,
            VisualBasic.VisualBasicParseOptions parseOptions = null,
            VisualBasic.VisualBasicCompilationOptions compilationOptions = null,
            string assemblyName = null,
            IEnumerable<MetadataReference> referencedAssemblies = null)
        {
            return CreateVisualBasicCompilation(assemblyName, files, parseOptions, compilationOptions, referencedAssemblies, referencedCompilations: null);
        }
 
        protected VisualBasic.VisualBasicCompilation CreateVisualBasicCompilation(
            string assemblyName,
            string code,
            VisualBasic.VisualBasicParseOptions parseOptions = null,
            VisualBasic.VisualBasicCompilationOptions compilationOptions = null,
            IEnumerable<MetadataReference> referencedAssemblies = null,
            IEnumerable<Compilation> referencedCompilations = null,
            Encoding encoding = null,
            string sourceFileName = null)
            => CreateVisualBasicCompilation(assemblyName, new[] { code }, parseOptions, compilationOptions, referencedAssemblies, referencedCompilations, encoding, new[] { sourceFileName });
 
        protected VisualBasic.VisualBasicCompilation CreateVisualBasicCompilation(
            string assemblyName,
            string[] files,
            VisualBasic.VisualBasicParseOptions parseOptions = null,
            VisualBasic.VisualBasicCompilationOptions compilationOptions = null,
            IEnumerable<MetadataReference> referencedAssemblies = null,
            IEnumerable<Compilation> referencedCompilations = null,
            Encoding encoding = null,
            string[] sourceFileNames = null)
        {
            Debug.Assert(sourceFileNames == null || sourceFileNames.Length == files.Length);
            if (assemblyName == null)
            {
                assemblyName = GetUniqueName();
            }
 
            if (parseOptions == null)
            {
                parseOptions = VisualBasic.VisualBasicParseOptions.Default;
            }
 
            if (compilationOptions == null)
            {
                compilationOptions = new VisualBasic.VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
            }
 
            compilationOptions = compilationOptions.WithParseOptions(parseOptions);
            var references = new List<MetadataReference>();
            if (referencedAssemblies == null)
            {
                references.Add(MscorlibRef);
                references.Add(SystemRef);
                references.Add(SystemCoreRef);
                references.Add(MsvbRef);
                references.Add(SystemXmlRef);
                references.Add(SystemXmlLinqRef);
            }
            else
            {
                references.AddRange(referencedAssemblies);
            }
 
            AddReferencedCompilations(referencedCompilations, references);
 
            var trees = new SyntaxTree[files.Length];
            for (int i = 0; i < files.Length; i++)
            {
                trees[i] = VisualBasic.VisualBasicSyntaxTree.ParseText(SourceText.From(files[i], encoding, SourceHashAlgorithms.Default), options: parseOptions, path: sourceFileNames?[i]);
            }
 
            return VisualBasic.VisualBasicCompilation.Create(assemblyName, trees, references, compilationOptions);
        }
 
        private void AddReferencedCompilations(IEnumerable<Compilation> referencedCompilations, List<MetadataReference> references)
        {
            if (referencedCompilations != null)
            {
                foreach (var referencedCompilation in referencedCompilations)
                {
                    references.Add(referencedCompilation.EmitToImageReference());
                }
            }
        }
 
        internal static MetadataReference AsReference(Compilation comp, bool useCompilationReference)
        {
            return useCompilationReference ? comp.ToMetadataReference() : comp.EmitToImageReference();
        }
 
        public static string WithWindowsLineBreaks(string source)
            => source.Replace(Environment.NewLine, "\r\n");
 
        #endregion
 
        #region IL Verification
 
        internal abstract string VisualizeRealIL(IModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary<int, string> markers, bool areLocalsZeroed);
 
        #endregion
 
        #region Other Helpers
 
        internal static Cci.ModulePropertiesForSerialization GetDefaultModulePropertiesForSerialization()
        {
            return new Cci.ModulePropertiesForSerialization(
                persistentIdentifier: default(Guid),
                corFlags: CorFlags.ILOnly,
                fileAlignment: Cci.ModulePropertiesForSerialization.DefaultFileAlignment32Bit,
                sectionAlignment: Cci.ModulePropertiesForSerialization.DefaultSectionAlignment,
                targetRuntimeVersion: "v4.0.30319",
                machine: 0,
                baseAddress: Cci.ModulePropertiesForSerialization.DefaultExeBaseAddress32Bit,
                sizeOfHeapReserve: Cci.ModulePropertiesForSerialization.DefaultSizeOfHeapReserve32Bit,
                sizeOfHeapCommit: Cci.ModulePropertiesForSerialization.DefaultSizeOfHeapCommit32Bit,
                sizeOfStackReserve: Cci.ModulePropertiesForSerialization.DefaultSizeOfStackReserve32Bit,
                sizeOfStackCommit: Cci.ModulePropertiesForSerialization.DefaultSizeOfStackCommit32Bit,
                dllCharacteristics: Compilation.GetDllCharacteristics(enableHighEntropyVA: true, configureToExecuteInAppContainer: false),
                subsystem: Subsystem.WindowsCui,
                imageCharacteristics: Characteristics.Dll,
                majorSubsystemVersion: 0,
                minorSubsystemVersion: 0,
                linkerMajorVersion: 0,
                linkerMinorVersion: 0);
        }
 
        internal void AssertDeclaresType(PEModuleSymbol peModule, WellKnownType type, Accessibility expectedAccessibility)
        {
            var name = MetadataTypeName.FromFullName(type.GetMetadataName());
            Assert.Equal(expectedAccessibility, peModule.LookupTopLevelMetadataType(ref name).DeclaredAccessibility);
        }
 
        #endregion
 
        #region Metadata Validation
 
        /// <summary>
        /// Creates instance of SignatureDescription for a specified member
        /// </summary>
        /// <param name="fullyQualifiedTypeName">
        /// Fully qualified type name for member
        /// Names must be in format recognized by reflection
        /// e.g. MyType{T}.MyNestedType{T, U} => MyType`1+MyNestedType`2
        /// </param>
        /// <param name="memberName">
        /// Name of member on specified type whose signature needs to be verified
        /// Names must be in format recognized by reflection
        /// e.g. For explicitly implemented member - I1{string}.Method => I1{System.String}.Method
        /// </param>
        /// <param name="expectedSignature">
        /// Baseline string for signature of specified member
        /// Skip this argument to get an error message that shows all available signatures for specified member
        /// </param>
        /// <returns>Instance of SignatureDescription for specified member</returns>
        internal static SignatureDescription Signature(string fullyQualifiedTypeName, string memberName, string expectedSignature = "")
        {
            return new SignatureDescription()
            {
                FullyQualifiedTypeName = fullyQualifiedTypeName,
                MemberName = memberName,
                ExpectedSignature = expectedSignature
            };
        }
 
        #endregion
 
        #region Operation Test Helpers
        internal static void VerifyParentOperations(SemanticModel model)
        {
            var parentMap = GetParentOperationsMap(model);
 
            // check parent for each child
            foreach (var (child, parent) in parentMap)
            {
                // check parent property returns same parent we gathered by walking down operation tree
                Assert.Equal(child.Parent, parent);
 
                if (parent == null)
                {
                    // this is root of operation tree
                    VerifyOperationTreeContracts(child);
                }
            }
        }
 
        private static Dictionary<IOperation, IOperation> GetParentOperationsMap(SemanticModel model)
        {
            // get top operations first
            var topOperations = new HashSet<IOperation>();
            var root = model.SyntaxTree.GetRoot();
 
            CollectTopOperations(model, root, topOperations);
 
            // dig down the each operation tree to create the parent operation map
            var map = new Dictionary<IOperation, IOperation>();
            foreach (var topOperation in topOperations)
            {
                // this is top of the operation tree
                map.Add(topOperation, null);
 
                CollectParentOperations(topOperation, map);
            }
 
            return map;
        }
 
        private static void CollectParentOperations(IOperation operation, Dictionary<IOperation, IOperation> map)
        {
            // walk down to collect all parent operation map for this tree
            foreach (var child in operation.ChildOperations)
            {
                map.Add(child, operation);
 
                CollectParentOperations(child, map);
            }
        }
 
        private static void CollectTopOperations(SemanticModel model, SyntaxNode node, HashSet<IOperation> topOperations)
        {
            foreach (var child in node.ChildNodes())
            {
                var operation = model.GetOperation(child);
                if (operation != null)
                {
                    // found top operation
                    topOperations.Add(operation);
 
                    // don't dig down anymore
                    continue;
                }
 
                // sub tree might have the top operation
                CollectTopOperations(model, child, topOperations);
            }
        }
 
        internal static void VerifyClone(SemanticModel model)
        {
            foreach (var node in model.SyntaxTree.GetRoot().DescendantNodes())
            {
                var operation = model.GetOperation(node);
                if (operation == null)
                {
                    continue;
                }
 
                var clonedOperation = OperationCloner.CloneOperation(operation);
 
                Assert.Same(model, operation.SemanticModel);
                Assert.Same(model, clonedOperation.SemanticModel);
                Assert.NotSame(model, ((Operation)operation).OwningSemanticModel);
                Assert.Same(((Operation)operation).OwningSemanticModel, ((Operation)clonedOperation).OwningSemanticModel);
 
                // check whether cloned IOperation is same as original one
                var original = OperationTreeVerifier.GetOperationTree(model.Compilation, operation);
                var cloned = OperationTreeVerifier.GetOperationTree(model.Compilation, clonedOperation);
 
                Assert.Equal(original, cloned);
 
                // make sure cloned operation is value equal but doesn't share any IOperations
                var originalSet = new HashSet<IOperation>(operation.DescendantsAndSelf());
                var clonedSet = new HashSet<IOperation>(clonedOperation.DescendantsAndSelf());
 
                Assert.Equal(originalSet.Count, clonedSet.Count);
                Assert.Equal(0, originalSet.Intersect(clonedSet).Count());
            }
        }
 
        private static void VerifyOperationTreeContracts(IOperation root)
        {
            var semanticModel = ((Operation)root).OwningSemanticModel;
            var set = new HashSet<IOperation>(root.DescendantsAndSelf());
 
            foreach (var child in root.DescendantsAndSelf())
            {
                // all operations from spine should belong to the operation tree set
                VerifyOperationTreeSpine(semanticModel, set, child.Syntax);
 
                // operation tree's node must be part of root of semantic model which is
                // owner of operation's lifetime
                Assert.True(semanticModel.Root.FullSpan.Contains(child.Syntax.FullSpan));
            }
        }
 
        private static void VerifyOperationTreeSpine(
            SemanticModel semanticModel, HashSet<IOperation> set, SyntaxNode node)
        {
            while (node != semanticModel.Root)
            {
                var operation = semanticModel.GetOperation(node);
                if (operation != null)
                {
                    Assert.True(set.Contains(operation));
                }
 
                node = node.Parent;
            }
        }
 
        #endregion
 
        #region Theory Helpers
 
        public static IEnumerable<object[]> ExternalPdbFormats
        {
            get
            {
                if (ExecutionConditionUtil.IsWindows)
                {
                    return new List<object[]>()
                    {
                        new object[] { DebugInformationFormat.Pdb },
                        new object[] { DebugInformationFormat.PortablePdb }
                    };
                }
                else
                {
                    return new List<object[]>()
                    {
                        new object[] { DebugInformationFormat.PortablePdb }
                    };
                }
            }
        }
 
        public static IEnumerable<object[]> PdbFormats =>
            new List<object[]>(ExternalPdbFormats)
            {
                new object[] { DebugInformationFormat.Embedded }
            };
 
        #endregion
    }
}