// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Shared;
using Shouldly;
using Xunit;

#nullable disable

namespace Microsoft.Build.Engine.UnitTests
{
    public class BuildEnvironmentHelper_Tests
    {
#if USE_MSBUILD_DLL_EXTN
        private const string MSBuildExeName = "MSBuild.dll";
#else
        private const string MSBuildExeName = "MSBuild.exe";
#endif

        [Fact]
        public void GetExecutablePath()
        {
            var msbuildPath = Path.GetDirectoryName(FileUtilities.ExecutingAssemblyPath);
            string expectedMSBuildPath = Path.Combine(msbuildPath, MSBuildExeName).ToLowerInvariant();

            string configFilePath = BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile.ToLowerInvariant();
            string toolsDirectoryPath = BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory.ToLowerInvariant();
            string actualMSBuildPath = BuildEnvironmentHelper.Instance.CurrentMSBuildExePath.ToLowerInvariant();

            configFilePath.ShouldBe($"{actualMSBuildPath}.config");
            actualMSBuildPath.ShouldBe(expectedMSBuildPath);
            Path.GetDirectoryName(expectedMSBuildPath).ShouldBe(toolsDirectoryPath);
            BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.Standalone);
        }

        [Fact]
        public void FindBuildEnvironmentByEnvironmentVariable()
        {
            using (var env = new EmptyStandaloneEnviroment(MSBuildExeName))
            {
                var path = env.BuildDirectory;
                var msBuildPath = Path.Combine(path, MSBuildExeName);
                var msBuildConfig = Path.Combine(path, $"{MSBuildExeName}.config");

                env.WithEnvironment("MSBUILD_EXE_PATH", env.MSBuildExePath);
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory.ShouldBe(path);
                BuildEnvironmentHelper.Instance.CurrentMSBuildExePath.ShouldBe(msBuildPath);
                BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile.ShouldBe(msBuildConfig);
                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBeNull();
                BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.Standalone);
            }
        }

        /// <summary>
        /// If MSBUILD_EXE_PATH is explicitly set, we should detect it as a VisualStudio instance even in older scenarios
        /// (for example when the install path is under 15.0).
        /// </summary>
        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void FindVisualStudioEnvironmentByEnvironmentVariable()
        {
            using (var env = new EmptyVSEnviroment())
            {
                var msbuildBinDirectory = env.BuildDirectory;

                var msBuildPath = Path.Combine(msbuildBinDirectory, MSBuildExeName);
                var msBuildConfig = Path.Combine(msbuildBinDirectory, $"{MSBuildExeName}.config");
                var vsMSBuildDirectory = Path.Combine(env.TempFolderRoot, "MSBuild");

                env.WithEnvironment("MSBUILD_EXE_PATH", msBuildPath);
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
                BuildEnvironmentHelper.Instance.MSBuildExtensionsPath.ShouldBe(vsMSBuildDirectory);
                BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory.ShouldBe(msbuildBinDirectory);
                BuildEnvironmentHelper.Instance.CurrentMSBuildExePath.ShouldBe(msBuildPath);
                BuildEnvironmentHelper.Instance.CurrentMSBuildConfigurationFile.ShouldBe(msBuildConfig);
                // This code is not running inside the Visual Studio devenv.exe process
                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
            }
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void FindBuildEnvironmentFromCommandLineVisualStudio()
        {
            using (var env = new EmptyVSEnviroment())
            {
                // All we know about is path to msbuild.exe as the command-line arg[0]
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.MSBuildExePath, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory64);
                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [Fact]
        public void FindBuildEnvironmentFromCommandLineStandalone()
        {
            // Path will not be under a Visual Studio install like path.
            using (var env = new EmptyStandaloneEnviroment(MSBuildExeName))
            {
                // All we know about is path to msbuild.exe as the command-line arg[0]
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.MSBuildExePath, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.Standalone);
            }
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void FindBuildEnvironmentFromRunningProcessVisualStudio()
        {
            using (var env = new EmptyVSEnviroment())
            {
                // All we know about is path to msbuild.exe as the current process
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, () => env.MSBuildExePath, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory64);
                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [Fact]
        public void FindBuildEnvironmentFromRunningProcessStandalone()
        {
            // Path will not be under a Visual Studio install like path.
            using (var env = new EmptyStandaloneEnviroment(MSBuildExeName))
            {
                // All we know about is path to msbuild.exe as the current process
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, () => env.MSBuildExePath, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.Standalone);
            }
        }

        [Fact]
        public void FindBuildEnvironmentFromExecutingAssemblyAsDll()
        {
            // Ensure the correct file is found (.dll not .exe)
            using (var env = new EmptyStandaloneEnviroment("MSBuild.dll"))
            {
                // All we know about is path to msbuild.exe as the current process
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, () => env.MSBuildExePath, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.Standalone);
            }
        }

        [Fact]
        public void FindBuildEnvironmentFromAppContextDirectory()
        {
            using (var env = new EmptyStandaloneEnviroment(MSBuildExeName))
            {
                // Only the app base directory will be available
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, ReturnNull, () => env.BuildDirectory, env.VsInstanceMock, env.EnvironmentMock, () => false);

                // Make sure we get the right MSBuild entry point. On .NET Core this will be MSBuild.dll, otherwise MSBuild.exe
                Path.GetFileName(BuildEnvironmentHelper.Instance.CurrentMSBuildExePath).ShouldBe(MSBuildExeName);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.Standalone);
            }
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void FindBuildEnvironmentFromVisualStudioRoot()
        {
            using (var env = new EmptyVSEnviroment())
            {
                // All we know about is path to DevEnv.exe
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.DevEnvPath, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory64);
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeTrue();
                BuildEnvironmentHelper.Instance.RunningTests.ShouldBeFalse();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [WindowsFullFrameworkOnlyTheory(additionalMessage: "No Visual Studio installed for .NET.")]
        [InlineData(MSBuildConstants.CurrentVisualStudioVersion, true)]
        [InlineData("15.0", false)]
        public void BuildEnvironmentDetectsVisualStudioByEnvironment(string visualStudioVersion, bool shouldBeValid)
        {
            using (var env = new EmptyVSEnviroment())
            {
                env.WithEnvironment("VSINSTALLDIR", env.TempFolderRoot);
                env.WithEnvironment("VisualStudioVersion", visualStudioVersion);
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                if (shouldBeValid)
                {
                    BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                    BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
                }
                else
                {
                    // Ensure that no VS instance was found (older versions that shouldn't be discovered).
                    BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBeNull();
                    BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.None);
                }
            }
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void BuildEnvironmentDetectsVisualStudioByMSBuildProcess()
        {
            using (var env = new EmptyVSEnviroment())
            {
                // We only know we're in msbuild.exe, we should still be able to attempt to find Visual Studio
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.MSBuildExePath, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void BuildEnvironmentDetectsVisualStudioByMSBuildProcessAmd64()
        {
            using (var env = new EmptyVSEnviroment())
            {
                // We only know we're in amd64\msbuild.exe, we should still be able to attempt to find Visual Studio
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.MSBuildExePath64, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [WindowsFullFrameworkOnlyTheory(additionalMessage: "No Visual Studio installed for .NET.")]
        [InlineData("18.0", true)]
        [InlineData("17.0", false)]
        [InlineData("17.3", false)]
        [InlineData("16.0", false)]
        public void BuildEnvironmentDetectsVisualStudioFromSetupInstance(string visualStudioVersion, bool shouldBeValid)
        {
            using (var env = new EmptyVSEnviroment())
            {
                env.WithVsInstance(new VisualStudioInstance("Invalid path", @"c:\_doesnotexist", new Version(visualStudioVersion)));
                env.WithVsInstance(new VisualStudioInstance("VS", env.TempFolderRoot, new Version(visualStudioVersion)));

                // This test has no context to find MSBuild other than Visual Studio root.
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                if (shouldBeValid)
                {
                    BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                    BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
                }
                else
                {
                    // Ensure that no VS instance was found (older versions that shouldn't be discovered).
                    BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBeNull();
                    BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.None);
                }
            }
        }

        [Fact]
        public void BuildEnvironmentVisualStudioNotFoundWhenVersionMismatch()
        {
            using (var env = new EmptyVSEnviroment())
            {
                env.WithVsInstance(new VisualStudioInstance("Invalid path", @"c:\_doesnotexist", new Version("15.0")));
                env.WithVsInstance(new VisualStudioInstance("VS", env.TempFolderRoot, new Version("14.0")));

                // This test has no context to find MSBuild other than Visual Studio root.
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBeNull();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.None);
            }
        }

        [Fact]
        public void BuildEnvironmentDetectsRunningTests()
        {
            BuildEnvironmentHelper.Instance.RunningTests.ShouldBeTrue();
            BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeFalse();
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void BuildEnvironmentDetectsVisualStudioByProcessName()
        {
            using (var env = new EmptyVSEnviroment())
            {
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.DevEnvPath, () => env.MSBuildExePath, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeTrue();
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void BuildEnvironmentDetectsVisualStudioByBlendProcess()
        {
            using (var env = new EmptyVSEnviroment())
            {
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.BlendPath, () => env.MSBuildExePath, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.RunningInVisualStudio.ShouldBeTrue();
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void BuildEnvironmentFindsAmd64()
        {
            using (var env = new EmptyVSEnviroment())
            {
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.DevEnvPath, ReturnNull,
                    ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory64);
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [ActiveIssue("https://github.com/dotnet/msbuild/issues/7552", TargetFrameworkMonikers.Any)]
        [WindowsOnlyFact]
        public void BuildEnvironmentFindsAmd64RunningInAmd64NoVS()
        {
            using (var env = new EmptyStandaloneEnviroment(MSBuildExeName, writeFakeFiles: true, includeAmd64Folder: true))
            {
                var msBuild64Exe = Path.Combine(env.BuildDirectory, "amd64", MSBuildExeName);
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => msBuild64Exe, ReturnNull, ReturnNull,
                    env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(Path.Combine(env.BuildDirectory, "amd64"));
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBeNull();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.Standalone);
            }
        }

        [WindowsOnlyFact]
        [ActiveIssue("https://github.com/dotnet/msbuild/issues/7552", TargetFrameworkMonikers.Any)]
        public void BuildEnvironmentFindsAmd64NoVS()
        {
            using (var env = new EmptyStandaloneEnviroment(MSBuildExeName, writeFakeFiles: true, includeAmd64Folder: true))
            {
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.MSBuildExePath, ReturnNull,
                    ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(Path.Combine(env.BuildDirectory, "amd64"));
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.Standalone);
            }
        }

        [ActiveIssue("https://github.com/dotnet/msbuild/issues/7552", TargetFrameworkMonikers.Any)]
        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void BuildEnvironmentFindsAmd64RunningInAmd64()
        {
            using (var env = new EmptyVSEnviroment())
            {
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => env.MSBuildExePath64, ReturnNull, ReturnNull, env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory64);
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [Fact]
        public void BuildEnvironmentNoneWhenNotAvailable()
        {
            using (var env = new EmptyStandaloneEnviroment(MSBuildExeName))
            {
                var entryProcess = Path.Combine(Path.GetTempPath(), "foo.exe");
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(() => entryProcess, ReturnNull, ReturnNull,
                    env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.CurrentMSBuildExePath.ShouldBe(entryProcess);
                BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory.ShouldBe(Path.GetDirectoryName(entryProcess));
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBeNull();
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.None);
            }
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void BuildEnvironmentVSFromMSBuildAssembly()
        {
            using (var env = new EmptyVSEnviroment())
            {
                var msBuildAssembly = Path.Combine(env.BuildDirectory, "Microsoft.Build.dll");

                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, () => msBuildAssembly, ReturnNull,
                    env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory64);
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        [WindowsFullFrameworkOnlyFact(additionalMessage: "No Visual Studio installed for .NET.")]
        public void BuildEnvironmentVSFromMSBuildAssemblyAmd64()
        {
            using (var env = new EmptyVSEnviroment())
            {
                var msBuildAssembly = Path.Combine(env.BuildDirectory64, "Microsoft.Build.dll");

                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(ReturnNull, () => msBuildAssembly, ReturnNull,
                    env.VsInstanceMock, env.EnvironmentMock, () => false);

                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32.ShouldBe(env.BuildDirectory);
                BuildEnvironmentHelper.Instance.MSBuildToolsDirectory64.ShouldBe(env.BuildDirectory64);
                BuildEnvironmentHelper.Instance.VisualStudioInstallRootDirectory.ShouldBe(env.TempFolderRoot);
                BuildEnvironmentHelper.Instance.Mode.ShouldBe(BuildEnvironmentMode.VisualStudio);
            }
        }

        private static string ReturnNull()
        {
            return null;
        }

        private sealed class EmptyVSEnviroment : EmptyStandaloneEnviroment
        {
            public string DevEnvPath { get; }

            public string BlendPath { get; }

            public string BuildDirectory64 { get; }

            public string MSBuildExePath64 => Path.Combine(BuildDirectory64, MSBuildExeName);

            public EmptyVSEnviroment(string toolsVersion = MSBuildConstants.CurrentToolsVersion) : base("MSBuild.exe", false)
            {
                try
                {
                    var files = new[] { "msbuild.exe", "msbuild.exe.config" };
                    BuildDirectory = Path.Combine(TempFolderRoot, "MSBuild", toolsVersion, "Bin");
                    BuildDirectory64 = Path.Combine(BuildDirectory, "amd64");
                    DevEnvPath = Path.Combine(TempFolderRoot, "Common7", "IDE", "devenv.exe");
                    BlendPath = Path.Combine(TempFolderRoot, "Common7", "IDE", "blend.exe");

                    Directory.CreateDirectory(BuildDirectory);
                    foreach (var file in files)
                    {
                        File.WriteAllText(Path.Combine(BuildDirectory, file), string.Empty);
                    }

                    Directory.CreateDirectory(BuildDirectory64);
                    foreach (var file in files)
                    {
                        File.WriteAllText(Path.Combine(BuildDirectory64, file), string.Empty);
                    }

                    Directory.CreateDirectory(Path.Combine(TempFolderRoot, "Common7", "IDE"));
                    File.WriteAllText(DevEnvPath, string.Empty);
                }
                catch (Exception)
                {
                    FileUtilities.DeleteDirectoryNoThrow(BuildDirectory, true);
                    throw;
                }
            }
        }

        private class EmptyStandaloneEnviroment : IDisposable
        {
            public string TempFolderRoot { get; }

            public string BuildDirectory { get; protected set; }

            public string MSBuildExeName { get; }

            public string MSBuildExePath => Path.Combine(BuildDirectory, MSBuildExeName);

            private readonly Dictionary<string, string> _mockEnvironment = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            private readonly List<VisualStudioInstance> _mockInstances = new List<VisualStudioInstance>();

            public EmptyStandaloneEnviroment(string msBuildExeName, bool writeFakeFiles = true, bool includeAmd64Folder = false)
            {
                try
                {
                    MSBuildExeName = msBuildExeName;
                    TempFolderRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
                    BuildDirectory = Path.Combine(TempFolderRoot, "MSBuild");

                    Directory.CreateDirectory(BuildDirectory);
                    if (writeFakeFiles)
                    {
                        File.WriteAllText(MSBuildExePath, string.Empty);
                        File.WriteAllText($"{MSBuildExePath}.config", string.Empty);

                        if (includeAmd64Folder)
                        {
                            Directory.CreateDirectory(Path.Combine(BuildDirectory, "amd64"));
                            File.WriteAllText(Path.Combine(BuildDirectory, "amd64", msBuildExeName), string.Empty);
                            File.WriteAllText(Path.Combine(BuildDirectory, "amd64", $"{MSBuildExePath}.config"), string.Empty);
                        }
                    }
                }
                catch (Exception)
                {
                    FileUtilities.DeleteDirectoryNoThrow(BuildDirectory, true);
                    throw;
                }
            }

            public void WithEnvironment(string variable, string value)
            {
                _mockEnvironment.Add(variable, value);
            }

            public void WithVsInstance(VisualStudioInstance instance)
            {
                _mockInstances.Add(instance);
            }

            public string EnvironmentMock(string variable)
            {
                return _mockEnvironment.ContainsKey(variable) ? _mockEnvironment[variable] : null;
            }

            public IEnumerable<VisualStudioInstance> VsInstanceMock()
            {
                return _mockInstances;
            }

            public void Dispose()
            {
                FileUtilities.DeleteDirectoryNoThrow(TempFolderRoot, true);
                BuildEnvironmentHelper.ResetInstance_ForUnitTestsOnly(runningTests: () => true);
            }
        }
    }
}
