﻿// 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.IO;
using System.Runtime.InteropServices;
using System.Xml;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.UnitTests
{
    public class AssemblyPortabilityPolicyTests : TestBase
    {
        private const string correctAppConfigText = @"
<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""false""/> <!-- platform -->
    </assemblyBinding>
  </runtime>
</configuration>
";
        private static void AssertIsEnabled(string appConfigPath, bool platform, bool nonPlatform, bool fusionOnly = false)
        {
            using (var policy = FusionAssemblyPortabilityPolicy.LoadFromFile(appConfigPath))
            {
                // portability is suppressed if the identities are not equivalent

                Assert.Equal(platform, IsEquivalent(policy,
                    "System, Version=5.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e",
                    "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"));

                Assert.Equal(nonPlatform, IsEquivalent(policy,
                    "System.ComponentModel.Composition, Version=5.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
                    "System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"));
            }

            if (!fusionOnly)
            {
                using (var stream = new FileStream(appConfigPath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    var actual = AssemblyPortabilityPolicy.LoadFromXml(stream);
                    Assert.Equal(platform, !actual.SuppressSilverlightPlatformAssembliesPortability);
                    Assert.Equal(nonPlatform, !actual.SuppressSilverlightLibraryAssembliesPortability);
                }
            }
        }

        private static bool IsEquivalent(FusionAssemblyPortabilityPolicy policy, string reference, string ported)
        {
            bool equivalent;
            FusionAssemblyIdentityComparer.AssemblyComparisonResult result;

            int hr = FusionAssemblyIdentityComparer.DefaultModelCompareAssemblyIdentity(
                reference,
                false,
                ported,
                false,
                out equivalent,
                out result,
                policy.ConfigCookie);

            return result == FusionAssemblyIdentityComparer.AssemblyComparisonResult.EquivalentFullMatch;
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_Errors()
        {
            var appConfig = Temp.CreateFile();
            var stream = new FileStream(appConfig.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

            // not XML:
            appConfig.WriteAllText("garbage");
            stream.Position = 0;
            Assert.Throws<COMException>(() => FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig.Path));
            Assert.Throws<XmlException>(() => AssemblyPortabilityPolicy.LoadFromXml(stream));

            // missing root element:
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
");
            stream.Position = 0;

            Assert.Throws<COMException>(() => FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig.Path));
            Assert.Throws<XmlException>(() => AssemblyPortabilityPolicy.LoadFromXml(stream));

            // duplicate attribute:
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""7cec85d7bea7798e"" PKT=""31bf3856ad364e35"" enable=""false""/>
    </assemblyBinding>
  </runtime>
</configuration>
");
            stream.Position = 0;

            Assert.Throws<COMException>(() => FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig.Path));
            Assert.Throws<XmlException>(() => AssemblyPortabilityPolicy.LoadFromXml(stream));
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_LeadingWhitespace()
        {
            var appConfig = Temp.CreateFile();

            // whitespace in front of header:
            appConfig.WriteAllText(
@"   <?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""false""/> <!-- platform -->
    </assemblyBinding>
  </runtime>
</configuration>
");
            var stream = new FileStream(appConfig.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

            AssertIsEnabled(appConfig.Path, platform: false, nonPlatform: true, fusionOnly: true);
            Assert.Throws<XmlException>(() => AssemblyPortabilityPolicy.LoadFromXml(stream));
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_LinkedConfiguration()
        {
            var appConfig1 = Temp.CreateFile();
            var appConfig2 = Temp.CreateFile();

            appConfig1.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
        <linkedConfiguration href=""file://" + appConfig2.Path + @"""/>       
        <supportPortability PKT=""7cec85d7bea7798e"" enable=""false""/> <!-- platform -->
    </assemblyBinding>
  </runtime>
</configuration>
");

            appConfig2.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""31bf3856ad364e35"" enable=""false""/> <!-- non-platform -->
    </assemblyBinding>
  </runtime>
</configuration>
");

            // Linked configuration isn't supported by fusion when reading portability elements (see CreateAssemblyConfigCookie)
            AssertIsEnabled(appConfig1.Path, platform: false, nonPlatform: true);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_NoValues()
        {
            var appConfig = Temp.CreateFile();

            // ok, but no configuration:
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<goo>
</goo>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: true);

            // ok, but no runtime
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: true);

            // ok, but no assembly binding
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>    
  </runtime>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: true);

            // no namespace
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <goo/>
  <runtime>
    <goo/>
    <assemblyBinding>
    </assemblyBinding>    
    <goo/>
  </runtime>
  <goo/>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: true);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_Values_MissingNamespace()
        {
            var appConfig = Temp.CreateFile();
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding>
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""false""/> <!-- platform -->
       <supportPortability PKT=""31bf3856ad364e35"" enable=""false""/> <!-- non-platform -->
    </assemblyBinding>
  </runtime>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: true);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_Values1()
        {
            var appConfig = Temp.CreateFile();

            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""false""/>   <!-- platform -->
    </assemblyBinding>
  </runtime>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: false, nonPlatform: true);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_Values2()
        {
            var appConfig = Temp.CreateFile();

            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""31bf3856ad364e35"" enable=""false""/>  <!-- non-platform -->
    </assemblyBinding>
  </runtime>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: false);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_Values4()
        {
            var appConfig = Temp.CreateFile();
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""true""/> <!-- platform -->
       <supportPortability PKT=""31bf3856ad364e35"" enable=""false""/> <!-- nonplatform -->
    </assemblyBinding>
  </runtime>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: false);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_DuplicateSupportPortability1()
        {
            var appConfig = Temp.CreateFile();
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""true""/>   <!-- platform -->
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""false""/>  <!-- platform -->
       <supportPortability PKT=""31bf3856ad364e35"" enable=""false""/> <!-- nonplatform -->
    </assemblyBinding>
  </runtime>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: false, nonPlatform: false);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_DuplicateSupportPortability2()
        {
            var appConfig = Temp.CreateFile();
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""false""/> <!-- platform -->
       <supportPortability PKT=""31bf3856ad364e35"" enable=""false""/> <!-- nonplatform -->
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""true""/>  <!-- platform -->
       <supportPortability PKT=""31bf3856ad364e35"" enable=""false""/> <!-- nonplatform -->
    </assemblyBinding>
  </runtime>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: false);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_UnknownAttributes2()
        {
            var appConfig = Temp.CreateFile();
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""7cec85d7bea7798e"" goo=""false""/> <!-- platform -->
       <supportPortability pkt=""7cec85d7bea7798e"" enable=""false""/> <!-- platform -->
       <supportPortability PKT=""31bf3856ad364e35"" Enable=""false""/> <!-- nonplatform -->
       <supportPortability enable=""false""/> <!-- platform -->
       <supportPortability PKT=""31Zf3856ad364e35"" enable=""false""/> <!-- garbage -->
       <supportPortability PKT=""9999999999999999"" enable=""false""/> <!-- garbage -->
       <supportPortability PKT="""" enable=""false""/> <!-- garbage -->
       <supportPortability PKT=""7cec85d7bea7798e"" enable=""""/> <!-- garbage -->
    </assemblyBinding>
  </runtime>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: true);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_InterleavingElements()
        {
            var appConfig = Temp.CreateFile();
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <goo>
    <runtime>
      <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
         <supportPortability PKT=""7cec85d7bea7798e"" enable=""true""/> <!-- platform -->
         <supportPortability PKT=""31bf3856ad364e35"" enable=""false""/> <!-- nonplatform -->
      </assemblyBinding>
    </runtime>
  </goo>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: true);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void LoadFromFile_EmptyElement()
        {
            var appConfig = Temp.CreateFile();
            appConfig.WriteAllText(
@"<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <goo>
    <runtime>
      <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"" />
    </runtime>
  </goo>
</configuration>
");
            AssertIsEnabled(appConfig.Path, platform: true, nonPlatform: true);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void Fusion_Dispose()
        {
            var appConfig = Temp.CreateFile().WriteAllText(correctAppConfigText);

            var policy = FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig.Path);
            Assert.NotEqual(IntPtr.Zero, policy.ConfigCookie);
            policy.Dispose();
            Assert.Equal(IntPtr.Zero, policy.ConfigCookie);
            policy.Dispose();
            Assert.Equal(IntPtr.Zero, policy.ConfigCookie);
        }

        [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsFusion)]
        public void Fusion_TestEquals()
        {
            var appConfig = Temp.CreateFile().WriteAllText(correctAppConfigText);

            var policy1 = FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig.Path);
            var policy2 = FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig.Path);
            Assert.Equal(policy1, policy2);
            Assert.Equal(policy1.GetHashCode(), policy2.GetHashCode());

            appConfig.WriteAllText(correctAppConfigText);
            policy2 = FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig.Path);
            Assert.Equal(policy1, policy2);
            Assert.Equal(policy1.GetHashCode(), policy2.GetHashCode());

            appConfig = Temp.CreateFile().WriteAllText(correctAppConfigText);
            policy2 = FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig.Path);
            Assert.Equal(policy1, policy2);
            Assert.Equal(policy1.GetHashCode(), policy2.GetHashCode());

            appConfig.WriteAllText(@"
<?xml version=""1.0"" encoding=""utf-8"" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
       <supportPortability PKT=""01234567890abcdef"" enable=""false""/>
    </assemblyBinding>
  </runtime>
</configuration>");

            policy2 = FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig.Path);
            Assert.NotEqual(policy1, policy2);

            var appConfig2 = Temp.CreateFile().WriteAllText(correctAppConfigText);

            policy2 = FusionAssemblyPortabilityPolicy.LoadFromFile(appConfig2.Path);
            Assert.Equal(policy1, policy2);
            Assert.Equal(policy1.GetHashCode(), policy2.GetHashCode());
        }
    }
}

