File: MetadataShadowCopyProviderTests.cs
Web Access
Project: src\src\Scripting\CoreTest.Desktop\Microsoft.CodeAnalysis.Scripting.Desktop.UnitTests.csproj (Microsoft.CodeAnalysis.Scripting.Desktop.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.Linq;
using System.IO;
using Roslyn.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using Roslyn.Utilities;
using System.Runtime.InteropServices;
using System.Globalization;
 
using static Roslyn.Utilities.PlatformInformation;
 
namespace Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests
{
    // TODO: clean up and move to portable tests
 
    public class MetadataShadowCopyProviderTests : TestBase
    {
        private readonly MetadataShadowCopyProvider _provider;
 
        private static readonly ImmutableArray<string> s_systemNoShadowCopyDirectories = IsRunningOnMono
            ? ImmutableArray<string>.Empty
            : ImmutableArray.Create(
                FileUtilities.NormalizeDirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.Windows)),
                FileUtilities.NormalizeDirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)),
                FileUtilities.NormalizeDirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)),
                FileUtilities.NormalizeDirectoryPath(RuntimeEnvironment.GetRuntimeDirectory()));
 
        public MetadataShadowCopyProviderTests()
        {
            _provider = CreateProvider(CultureInfo.InvariantCulture);
        }
 
        private static MetadataShadowCopyProvider CreateProvider(CultureInfo culture)
        {
            return new MetadataShadowCopyProvider(TempRoot.Root, s_systemNoShadowCopyDirectories, culture);
        }
 
        public override void Dispose()
        {
            _provider.Dispose();
            Assert.False(Directory.Exists(_provider.ShadowCopyDirectory), "Shadow copy directory should have been deleted");
            base.Dispose();
        }
 
        [Fact]
        public void Errors()
        {
            Assert.Throws<ArgumentNullException>(() => _provider.NeedsShadowCopy(null));
            Assert.Throws<ArgumentException>(() => _provider.NeedsShadowCopy("c:goo.dll"));
            Assert.Throws<ArgumentException>(() => _provider.NeedsShadowCopy("bar.dll"));
            Assert.Throws<ArgumentException>(() => _provider.NeedsShadowCopy(@"\bar.dll"));
            Assert.Throws<ArgumentException>(() => _provider.NeedsShadowCopy(@"../bar.dll"));
 
            Assert.Throws<ArgumentNullException>(() => _provider.SuppressShadowCopy(null));
            Assert.Throws<ArgumentException>(() => _provider.SuppressShadowCopy("c:goo.dll"));
            Assert.Throws<ArgumentException>(() => _provider.SuppressShadowCopy("bar.dll"));
            Assert.Throws<ArgumentException>(() => _provider.SuppressShadowCopy(@"\bar.dll"));
            Assert.Throws<ArgumentException>(() => _provider.SuppressShadowCopy(@"../bar.dll"));
 
            Assert.Throws<ArgumentOutOfRangeException>(() => _provider.GetMetadataShadowCopy(IsRunningOnMono ? "/goo.dll" : @"c:\goo.dll", (MetadataImageKind)Byte.MaxValue));
            Assert.Throws<ArgumentNullException>(() => _provider.GetMetadataShadowCopy(null, MetadataImageKind.Assembly));
            Assert.Throws<ArgumentException>(() => _provider.GetMetadataShadowCopy("c:goo.dll", MetadataImageKind.Assembly));
            Assert.Throws<ArgumentException>(() => _provider.GetMetadataShadowCopy("bar.dll", MetadataImageKind.Assembly));
            Assert.Throws<ArgumentException>(() => _provider.GetMetadataShadowCopy(@"\bar.dll", MetadataImageKind.Assembly));
            Assert.Throws<ArgumentException>(() => _provider.GetMetadataShadowCopy(@"../bar.dll", MetadataImageKind.Assembly));
 
            Assert.Throws<ArgumentOutOfRangeException>(() => _provider.GetMetadata(IsRunningOnMono ? "/goo.dll" : @"c:\goo.dll", (MetadataImageKind)Byte.MaxValue));
            Assert.Throws<ArgumentNullException>(() => _provider.GetMetadata(null, MetadataImageKind.Assembly));
            Assert.Throws<ArgumentException>(() => _provider.GetMetadata("c:goo.dll", MetadataImageKind.Assembly));
        }
 
        [Fact]
        public void Copy()
        {
            var dir = Temp.CreateDirectory();
            var dll = dir.CreateFile("a.dll").WriteAllBytes(TestResources.MetadataTests.InterfaceAndClass.CSClasses01);
            var doc = dir.CreateFile("a.xml").WriteAllText("<hello>");
 
            var sc1 = _provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
            var sc2 = _provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
            Assert.Equal(sc2, sc1);
            Assert.Equal(dll.Path, sc1.PrimaryModule.OriginalPath);
            Assert.NotEqual(dll.Path, sc1.PrimaryModule.FullPath);
 
            Assert.False(sc1.Metadata.IsImageOwner, "Copy expected");
 
            Assert.Equal(File.ReadAllBytes(dll.Path), File.ReadAllBytes(sc1.PrimaryModule.FullPath));
            Assert.Equal(File.ReadAllBytes(doc.Path), File.ReadAllBytes(sc1.DocumentationFile.FullPath));
        }
 
        [Fact]
        public void SuppressCopy1()
        {
            var dll = Temp.CreateFile().WriteAllText("blah");
 
            _provider.SuppressShadowCopy(dll.Path);
 
            var sc1 = _provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
            Assert.Null(sc1);
        }
 
        [ClrOnlyFact(ClrOnlyReason.Fusion)]
        public void SuppressCopy_Framework()
        {
            // framework assemblies not copied:
            string mscorlib = typeof(object).Assembly.Location;
            var sc2 = _provider.GetMetadataShadowCopy(mscorlib, MetadataImageKind.Assembly);
            Assert.Null(sc2);
        }
 
        [Fact]
        public void SuppressCopy_ShadowCopyDirectory()
        {
            // shadow copies not copied:
            var dll = Temp.CreateFile("a.dll").WriteAllBytes(TestResources.MetadataTests.InterfaceAndClass.CSClasses01);
 
            // copy:
            var sc1 = _provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
            Assert.NotEqual(dll.Path, sc1.PrimaryModule.FullPath);
 
            // file not copied:
            var sc2 = _provider.GetMetadataShadowCopy(sc1.PrimaryModule.FullPath, MetadataImageKind.Assembly);
            Assert.Null(sc2);
        }
 
        [Fact]
        public void Modules()
        {
            // modules: { MultiModule.dll, mod2.netmodule, mod3.netmodule }
            var dir = Temp.CreateDirectory();
            string path0 = dir.CreateFile("MultiModule.dll").WriteAllBytes(TestResources.SymbolsTests.MultiModule.MultiModuleDll).Path;
            string path1 = dir.CreateFile("mod2.netmodule").WriteAllBytes(TestResources.SymbolsTests.MultiModule.mod2).Path;
            string path2 = dir.CreateFile("mod3.netmodule").WriteAllBytes(TestResources.SymbolsTests.MultiModule.mod3).Path;
 
            var metadata1 = _provider.GetMetadata(path0, MetadataImageKind.Assembly) as AssemblyMetadata;
            Assert.NotNull(metadata1);
            Assert.Equal(3, metadata1.GetModules().Length);
 
            var scDir = Directory.GetFileSystemEntries(_provider.ShadowCopyDirectory).Single();
            Assert.True(Directory.Exists(scDir));
 
            var scFiles = Directory.GetFileSystemEntries(scDir);
            AssertEx.SetEqual(new[] { "MultiModule.dll", "mod2.netmodule", "mod3.netmodule" }, scFiles.Select(p => Path.GetFileName(p)));
 
            foreach (var sc in scFiles)
            {
                Assert.True(_provider.IsShadowCopy(sc));
 
                if (!IsRunningOnMono)
                {
                    // files should be locked:
                    Assert.Throws<IOException>(() => File.Delete(sc));
                }
            }
 
            // should get the same metadata:
            var metadata2 = _provider.GetMetadata(path0, MetadataImageKind.Assembly) as AssemblyMetadata;
            Assert.Same(metadata1, metadata2);
 
            // modify the file:
            File.SetLastWriteTimeUtc(path0, DateTime.Now + TimeSpan.FromHours(1));
 
            // we get an updated image if we ask again:
            var modifiedMetadata3 = _provider.GetMetadata(path0, MetadataImageKind.Assembly) as AssemblyMetadata;
            Assert.NotSame(modifiedMetadata3, metadata2);
 
            // the file has been modified - we get new metadata:
            for (int i = 0; i < metadata2.GetModules().Length; i++)
            {
                Assert.NotSame(metadata2.GetModules()[i], modifiedMetadata3.GetModules()[i]);
            }
        }
 
        [Fact]
        public unsafe void DisposalOnFailure()
        {
            var f0 = Temp.CreateFile().WriteAllText("bogus").Path;
            Assert.Throws<BadImageFormatException>(() => _provider.GetMetadata(f0, MetadataImageKind.Assembly));
 
            string f1 = Temp.CreateFile().WriteAllBytes(TestResources.SymbolsTests.MultiModule.MultiModuleDll).Path;
            Assert.Throws<FileNotFoundException>(() => _provider.GetMetadata(f1, MetadataImageKind.Assembly));
        }
 
        [Fact]
        public void GetMetadata()
        {
            var dir = Temp.CreateDirectory();
            var dll = dir.CreateFile("a.dll").WriteAllBytes(TestResources.MetadataTests.InterfaceAndClass.CSClasses01);
            var doc = dir.CreateFile("a.xml").WriteAllText("<hello>");
 
            var sc1 = _provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
            var sc2 = _provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
 
            var md1 = _provider.GetMetadata(dll.Path, MetadataImageKind.Assembly);
            Assert.NotNull(md1);
            Assert.Equal(MetadataImageKind.Assembly, md1.Kind);
 
            // This needs to be in different folder from referencesdir to cause the other code path 
            // to be triggered for NeedsShadowCopy method
            var dir2 = Temp.CreateDirectory();
            var dll2 = dir2.CreateFile("a2.dll").WriteAllBytes(TestResources.MetadataTests.InterfaceAndClass.CSClasses01);
 
            Assert.Equal(1, _provider.CacheSize);
            var sc3a = _provider.GetMetadataShadowCopy(dll2.Path, MetadataImageKind.Module);
            Assert.Equal(2, _provider.CacheSize);
        }
 
        [Fact]
        public void XmlDocComments_SpecificCulture()
        {
            var elGR = CultureInfo.GetCultureInfo("el-GR");
            var arMA = CultureInfo.GetCultureInfo("ar-MA");
 
            var dir = Temp.CreateDirectory();
            var dll = dir.CreateFile("a.dll").WriteAllBytes(TestResources.MetadataTests.InterfaceAndClass.CSClasses01);
            var docInvariant = dir.CreateFile("a.xml").WriteAllText("Invariant");
            var docGreek = dir.CreateDirectory(elGR.Name).CreateFile("a.xml").WriteAllText("Greek");
 
            // invariant culture
            var provider = CreateProvider(CultureInfo.InvariantCulture);
            var sc = provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
            Assert.Equal(Path.Combine(Path.GetDirectoryName(sc.PrimaryModule.FullPath), @"a.xml"), sc.DocumentationFile.FullPath);
            Assert.Equal("Invariant", File.ReadAllText(sc.DocumentationFile.FullPath));
 
            // Greek culture
            provider = CreateProvider(elGR);
            sc = provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
            Assert.Equal(Path.Combine(Path.GetDirectoryName(sc.PrimaryModule.FullPath), @"el-GR", "a.xml"), sc.DocumentationFile.FullPath);
            Assert.Equal("Greek", File.ReadAllText(sc.DocumentationFile.FullPath));
 
            // Arabic culture (culture specific docs not found, use invariant)
            provider = CreateProvider(arMA);
            sc = provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
            Assert.Equal(Path.Combine(Path.GetDirectoryName(sc.PrimaryModule.FullPath), @"a.xml"), sc.DocumentationFile.FullPath);
            Assert.Equal("Invariant", File.ReadAllText(sc.DocumentationFile.FullPath));
 
            // no culture:
            provider = CreateProvider(null);
            sc = provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
            Assert.Null(sc.DocumentationFile);
        }
    }
}