File: TestInit.cs
Web Access
Project: src\src\dotnet-svcutil\lib\tests\src\dotnet-svcutil-lib.Tests.csproj (dotnet-svcutil-lib.Tests)
// 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.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Microsoft.Tools.ServiceModel.Svcutil;
using Xunit;
 
namespace SvcutilTest
{
    public class ClassFixture : IDisposable
    {
        #region fields and properties
 
        // global variables common to all tests.
        protected static string g_TestCasesDir;
        protected static string g_TestResultsDir;
        protected static string g_TestBootstrapDir;
        protected static string g_TestOutputDir;
        protected static string g_BaselinesDir;
        protected static string g_ServiceUrl;
        protected static string g_ServiceId;
        protected static string g_SvcutilNugetFeed;
        protected static string g_SvcutilPkgVersion;
        protected static string g_RepositoryRoot;
        protected static string g_SdkVersion;
 
        protected static bool containerAlreadyRunning;
 
        // test group-specific variables.
        protected string this_TestGroupBaselinesDir;
        protected string this_TestGroupOutputDir;
        protected string this_TestGroupBootstrapDir;
        protected string this_TestGroupProjDir;
 
        // test case-specific variables
        internal MSBuildProj this_TestCaseProject;
        protected string this_TestCaseName;
        protected string this_TestCaseBaselinesDir;
        protected string this_TestCaseOutputDir;
        protected string this_TestCaseBootstrapDir;
        protected string this_TestCaseSdkVersion;
        protected string this_TestCaseLogFile;
        internal ILogger this_TestCaseLogger;
        internal FixupUtil this_FixupUtil;
 
        internal static MSBuildProj __g_StarterProject;
        internal static MSBuildProj g_StarterProject
        {
            get
            {
                if (__g_StarterProject == null)
                {
                    File.WriteAllText(Path.Combine(g_TestOutputDir, "Directory.Build.props"), "<Project xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\" />");
                    File.WriteAllText(Path.Combine(g_TestOutputDir, "Directory.Build.targets"), "<Project></Project>");
                    var projectPath = Path.Combine(g_TestOutputDir, "TestStarterProject", "TestStarterProject.csproj");
                    __g_StarterProject = ProjectUtils.GetProject(projectPath, targetFramework: null, forceNew: true, build: false, logger: null);
                }
                return __g_StarterProject;
            }
        }
 
        internal static string buildType
        {
            get
            {
#if DEBUG
                return "Debug";
#else
                return "Release";
#endif
            }
        }
 
        // special-name TestGroup projects to identify them when updating baselines as they are not not tracked.
        // because they are not specific to any test.
        protected string this_TestGroupProjectName { get { return $"{this_TestCaseName}_TestGroup.csproj"; } }
 
        internal MSBuildProj __this_TestGroupProject;
        internal MSBuildProj this_TestGroupProject
        {
            get
            {
                if (__this_TestGroupProject == null)
                {
                    var fileName = Path.GetFileNameWithoutExtension(this_TestGroupProjectName);
                    var filePath = Path.Combine(this_TestGroupProjDir, this_TestGroupProjectName);
 
                    if (!File.Exists(filePath))
                    {
                        // copy project to avoid 'dotnet new + restore' and save a few seconds.
                        CopyProject(g_StarterProject, this_TestGroupProjDir, fileName);
                    }
 
                    __this_TestGroupProject = ProjectUtils.GetProject(filePath, targetFramework: null, forceNew: false, build: false, logger: this_TestCaseLogger);
                }
 
                return __this_TestGroupProject as MSBuildProj;
            }
        }
 
        internal static string CopyProject(MSBuildProj project, string outputDir, string projectName)
        {
            // no dotnet new/no build/no restore
            var projectPath = Path.Combine(outputDir, $"{projectName}.csproj");
            var programPath = Path.Combine(outputDir, $"{projectName}.cs");
            var srcProgramPath = Directory.GetFiles(project.DirectoryPath, "*.cs", SearchOption.TopDirectoryOnly).First();
 
            File.Copy(project.FullPath, projectPath);
            File.Copy(srcProgramPath, programPath);
 
            File.WriteAllText(programPath, File.ReadAllText(programPath)
                .Replace(Path.GetFileNameWithoutExtension(project.FileName), projectName));
 
            return projectPath;
        }
        #endregion
 
        #region Initialization
        public ClassFixture()
        {
            AppInsightsTelemetryClient.IsUserOptedIn = false;
 
            g_RepositoryRoot = FindRepositoryRoot();
 
            var vsTestsRoot = GetSTestRootDir();
 
            g_SdkVersion = "5.0.100";
            ProcessRunner.ProcessResult procResult = ProcessRunner.TryRunAsync("dotnet", "--version", Directory.GetCurrentDirectory(), null, new CancellationToken()).ConfigureAwait(false).GetAwaiter().GetResult();
 
            if (procResult.ExitCode == 0)
            {
                g_SdkVersion = procResult.OutputText.Trim();
            }
 
            g_ServiceUrl = "http://wcfcoresrv53.westus3.cloudapp.azure.com/wcftestservice1";
            g_TestOutputDir = Path.Combine(g_RepositoryRoot, "artifacts", "TestOutput");
            g_TestResultsDir = Path.Combine(g_TestOutputDir, "TestResults");
            g_TestBootstrapDir = Path.Combine(g_TestOutputDir, "TestBootstrap");
            g_TestCasesDir = Path.Combine(vsTestsRoot, "TestCases");
            g_BaselinesDir = Path.Combine(vsTestsRoot, "Baselines");
 
            Directory.CreateDirectory(g_TestOutputDir);
            Directory.CreateDirectory(g_TestResultsDir);
            Directory.CreateDirectory(g_TestBootstrapDir);
            Directory.CreateDirectory(g_TestCasesDir);
            Directory.CreateDirectory(g_BaselinesDir);
 
            Assert.True(Directory.Exists(g_RepositoryRoot), $"{nameof(g_RepositoryRoot)} is not initialized!");
            Assert.True(Directory.Exists(g_TestCasesDir), $"{nameof(g_TestCasesDir)} is not initialized!");
            Assert.True(Directory.Exists(g_TestResultsDir), $"{nameof(g_TestResultsDir)} is not initialized!");
            Assert.True(Directory.Exists(g_TestBootstrapDir), $"{nameof(g_TestBootstrapDir)} is not initialized!");
            Assert.True(Directory.Exists(g_TestOutputDir), $"{nameof(g_TestOutputDir)} is not initialized!");
            Assert.True(Directory.Exists(g_BaselinesDir), $"{nameof(g_BaselinesDir)} is not initialized!");
 
            GetSvcutilPkgVersionAndFeed();
            CreateNuGetConfig();
            CreateGlobalJson(g_TestOutputDir, g_SdkVersion);
 
            // force creation of common project.
            Assert.True(File.Exists(g_StarterProject.FullPath), $"{nameof(g_StarterProject)} is not initialized!");
 
            Assert.False(string.IsNullOrEmpty(g_SvcutilNugetFeed), $"{nameof(g_SvcutilNugetFeed)} is not initialized!");
            Assert.False(string.IsNullOrEmpty(g_SvcutilPkgVersion), $"{nameof(g_SvcutilPkgVersion)} is not initialized!");
 
            // copy MSBuild global props/targets file to test folder to disable build customizations for test projects.
            File.WriteAllText(Path.Combine(g_TestOutputDir, "Directory.Build.props"), "<Project xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\" />");
            File.WriteAllText(Path.Combine(g_TestOutputDir, "Directory.Build.targets"), "<Project></Project>");
 
            // Uninstall the global tool and install the current version.
            var currentDirectory = Directory.GetCurrentDirectory();
            ProcessRunner.ProcessResult ret = ProcessRunner.TryRunAsync("dotnet", "tool uninstall -g dotnet-svcutil", currentDirectory, null, CancellationToken.None).Result;
            string pkgPath = Path.Combine(g_RepositoryRoot, "artifacts", "packages", buildType, "Shipping");
            if (!Directory.Exists(pkgPath) || !Directory.GetFiles(pkgPath, "dotnet-svcutil-lib*.nupkg").Any())
            {
                pkgPath = Path.Combine(g_RepositoryRoot, "artifacts", "packages", buildType, "NonShipping");
            }
            Assert.True(Directory.GetFiles(pkgPath, "dotnet-svcutil-lib.*.nupkg").Any(), $"dotnet-svcutil-lib*.nupkg not found under {pkgPath}!");
            ret = ProcessRunner.TryRunAsync("dotnet", $"tool install --global --add-source {pkgPath} dotnet-svcutil --version {g_SvcutilPkgVersion}", currentDirectory, null, CancellationToken.None).Result;
            Assert.True(ret.ExitCode == 0, "Could not install the global tool." + Environment.NewLine + ret.OutputText);
        }
 
        public void Dispose()
        {
        }
 
        protected void InitializeE2E(string testCaseName, bool createUniqueProject = false, string targetFramework = null, string sdkVersion = null)
        {
            InitializeUnitTest(testCaseName, createProject: false);
            this_TestCaseLogger = new TestLogger() { Verbose = true };
 
            Directory.CreateDirectory(this_TestCaseBootstrapDir);
 
            this_TestCaseSdkVersion = sdkVersion ?? g_SdkVersion;
 
            if (sdkVersion != null)
            {
                CreateGlobalJson(createUniqueProject ? this_TestCaseOutputDir : this_TestGroupOutputDir, sdkVersion);
            }
 
            if (createUniqueProject)
            {
                // create a project specific to the test case.
                var projectPath = Path.Combine(this_TestCaseOutputDir, $"{testCaseName}.csproj");
                this_TestCaseProject = ProjectUtils.GetProject(projectPath, targetFramework, forceNew: true, build: true, logger: this_TestCaseLogger);
            }
            else
            {
                this_TestCaseProject = this_TestGroupProject;
            }
        }
 
        public void InitializeGlobal(string testCaseName, string targetFramework = null, string sdkVersion = null)
        {
            InitializeUnitTest(testCaseName, createProject: false);
            this_TestCaseLogger = new TestLogger() { Verbose = true };
 
            Directory.CreateDirectory(this_TestCaseBootstrapDir);
 
            this_TestCaseSdkVersion = sdkVersion ?? g_SdkVersion;
 
            if (sdkVersion != null)
            {
                CreateGlobalJson(this_TestCaseOutputDir, sdkVersion);
            }
            
            // create a project specific to the test case.
            var projectPath = Path.Combine(this_TestCaseOutputDir, $"{testCaseName}.csproj");
            this_TestCaseProject = ProjectUtils.GetProject(projectPath, targetFramework, forceNew: true, build: true, logger: this_TestCaseLogger, globalTool: true);
        }
 
        public void InitializeUnitTest(string testCaseName, bool createProject = true, string sdkVersion = null)
        {
            Assert.False(string.IsNullOrWhiteSpace(testCaseName), $"{nameof(testCaseName)} is not initialized!");
 
            this_TestCaseOutputDir = Path.Combine(this_TestGroupOutputDir, testCaseName);
            FileUtil.TryDeleteDirectory(this_TestCaseOutputDir);
            Directory.CreateDirectory(this_TestCaseOutputDir);
 
            // do not create directory as it is not expected to be used by a unit-test.
            this_TestCaseBootstrapDir = Path.Combine(this_TestGroupBootstrapDir, testCaseName);
 
            this_TestCaseLogger = new DebugLogger();
            this_TestCaseLogFile = Path.Combine(this_TestGroupOutputDir, $"{testCaseName}.log");
 
            this_TestCaseSdkVersion = sdkVersion;
 
            if (createProject == true)
            {
                var project = (sdkVersion == null || g_StarterProject.SdkVersion == sdkVersion) ? g_StarterProject : this_TestGroupProject;
                var projectPath = CopyProject(g_StarterProject, this_TestCaseOutputDir, testCaseName);
 
                this_TestCaseProject = MSBuildProj.FromPathAsync(projectPath, null, CancellationToken.None).Result;
            }
            else
            {
                this_TestCaseProject = g_StarterProject;
            }
 
            this_TestCaseBaselinesDir = Path.Combine(this_TestGroupBaselinesDir, testCaseName);
            var linuxBaselinePath = Path.Combine(this_TestCaseBaselinesDir, "Linux");
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && File.Exists(Path.Combine(linuxBaselinePath, Path.GetFileName(this_TestCaseLogFile))))
            {
                this_TestCaseBaselinesDir = linuxBaselinePath;
            }
            Directory.CreateDirectory(this_TestCaseBaselinesDir);
 
            this_FixupUtil = new FixupUtil();
            this_FixupUtil.Init(g_TestResultsDir, g_TestCasesDir, this_TestCaseOutputDir, g_ServiceUrl, g_ServiceId, g_RepositoryRoot);
 
            Assert.True(Directory.Exists(this_TestCaseOutputDir), $"{nameof(this_TestGroupOutputDir)} is not initialized!");
            Assert.True(Directory.Exists(this_TestCaseBaselinesDir), $"{nameof(this_TestCaseBaselinesDir)} is not initialized!");
            Assert.True(this_TestCaseBootstrapDir != null, $"{nameof(this_TestCaseBootstrapDir)} is not initialized!");
            Assert.True(this_TestCaseLogger != null, $"{nameof(this_TestGroupOutputDir)} is not initialized!");
            Assert.True(this_FixupUtil != null, $"{nameof(this_FixupUtil)} is not initialized!");
        }
        #endregion
 
        protected string AppendCommonOptions(string options)
        {
            // add the namespace option to avoid type clashing in the generated code and be able to compile the project when more than one service ref is added.
            options = $"{options} -v Minimal -d ../{this_TestCaseName} -n \"\"*,{this_TestCaseName}_NS\"\" -ntr";
            return options;
        }
 
        #region validation methods
        static readonly string g_DiffBaselineErrorFormat =
                Environment.NewLine + Environment.NewLine + "Error: The generated file doesn't match the baseline! Different lines:" +
                Environment.NewLine + "< \"{0}\"" +
                Environment.NewLine + "> \"{1}\"" + Environment.NewLine +
                Environment.NewLine + "  windiff \"{2}\" \"{3}\"" + Environment.NewLine +
                Environment.NewLine + "To update the baseline run the following command:" +
                Environment.NewLine + "  copy /y \"{3}\" \"{2}\"";
 
        static readonly string g_NoBaselineErrorFormat =
                Environment.NewLine + Environment.NewLine + "Error: A file was generated but no baseline exists!" +
                Environment.NewLine + "The \"{0}\" file was created and need to be committed into the source repo. Rerunning the test should succeed if no other error occurred!";
 
        static readonly string g_UnvalidatedBaselineMessageFormat =
                 Environment.NewLine + Environment.NewLine + "No generated file was found matching the following baselines:" + Environment.NewLine + "{0}" +
                 Environment.NewLine + "If no longer valid, run the following command script to delete them:" + Environment.NewLine + "  {1}";
 
        public static readonly string g_ToReproduceProblemMessageFormat =
                Environment.NewLine + Environment.NewLine + "To reproduce the problem run the following commands:" + Environment.NewLine +
                Environment.NewLine + "  pushd  {0}" + Environment.NewLine +
                Environment.NewLine + "  dotnet svcutil {1}";
 
        public static readonly string g_CommandAndWorkdirMessageFormat =
                Environment.NewLine + "Command: dotnet svcutil {0}" +
                Environment.NewLine + "Workdir: {1}";
 
        public static readonly string g_GeneralErrMsg = $"{Environment.NewLine}{Environment.NewLine}Click the Output link for a full report.";
 
        // get test case generated files. NOTE: common test project is not considered nor fixed up.
        public static readonly string[] g_GeneratedExtensions = new string[] { ".cs", ".log", ".csproj", ".json", ".config", ".wsdl" };
 
        protected void ValidateTest(string options, string workingDir, int exitCode, string outputText, bool expectSuccess = true)
        {
            var isSuccess = exitCode == 0 || exitCode == 6;
            var actualText = isSuccess ? "Success" : "Failure";
            var expectedText = expectSuccess ? "Success" : "Failure";
            var resultsMsg = Environment.NewLine + $"Svcutil Result: {actualText}. Expected: {expectedText}";
            var isTestSucess = !(isSuccess ^ expectSuccess);
 
            var commandMsg = string.Format(g_CommandAndWorkdirMessageFormat, options, workingDir);
            var logText = $"{Environment.NewLine}{outputText}{Environment.NewLine}{Environment.NewLine}{commandMsg}{resultsMsg}";
 
            WriteLog(logText, options);
            File.WriteAllText(this.this_TestCaseLogFile, logText);
 
            Assert.True(isTestSucess, $"{Environment.NewLine}Test failed:{Environment.NewLine}{outputText}{g_GeneralErrMsg}");
 
            // validate test outputs only if test succeeded not to fix up files and be able to reproduce the problem.
            var validationSuccess = ValidateOutputs(options, out var failureMessage);
            WriteLog(failureMessage);
 
            Assert.True(validationSuccess, $"{Environment.NewLine}Test failed validation!{failureMessage}{g_GeneralErrMsg}");
        }
 
        protected bool ValidateOutputs(string options, out string failureMessage)
        {
            return ValidateOutputs(options, true, out failureMessage);
        }
 
        protected bool ValidateOutputs(string options, bool isSuccess, out string failureMessage)
        {
            var linuxBaselinePath = Path.Combine(this_TestCaseBaselinesDir, "Linux");
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && File.Exists(Path.Combine(linuxBaselinePath, Path.GetFileName(this_TestCaseLogFile))))
            {
                this_TestCaseBaselinesDir = linuxBaselinePath;
            }
 
            var excludeDirs = new string[] { "bin", "obj", "lib" };
 
            // get test case baselines.
            var baselineFiles = Directory.GetFiles(this_TestCaseBaselinesDir, "*", SearchOption.AllDirectories)
                                    .Where(f => !PathHelper.PathHasFolder(f, excludeDirs, this_TestCaseBaselinesDir)).Where(f => !f.ToLower().EndsWith("nuget.config")).ToList();
 
            var nonRefbaselineFiles = baselineFiles.Where(f => f.EndsWith(".cs") && !f.EndsWith(Path.DirectorySeparatorChar + "reference.cs", StringComparison.OrdinalIgnoreCase)).ToList();
 
            baselineFiles = baselineFiles.Except(nonRefbaselineFiles).ToList();
            
            // we don't check the bootstrapping directory, for those tests where it is relevant the test should 
            // place the bootstrapping dir under the test case's output dir.
            var generatedFiles = Directory.GetFiles(this_TestCaseOutputDir, "*.*", SearchOption.AllDirectories)
                                    .Where(f => !PathHelper.PathHasFolder(f, excludeDirs, this_TestCaseOutputDir))
                                    .Where(f => !f.ToLower().EndsWith("nuget.config"))
                                    .Where(f => g_GeneratedExtensions
                                    .Any(e => e.Equals(Path.GetExtension(f), RuntimeEnvironmentHelper.FileStringComparison))).ToList();
            
            var nonRefGeneratedFiles = generatedFiles.Where(f => f.EndsWith(".cs") && !f.EndsWith(Path.DirectorySeparatorChar + "reference.cs", StringComparison.OrdinalIgnoreCase)).ToList();
            generatedFiles = generatedFiles.Except(nonRefGeneratedFiles).ToList();
 
            generatedFiles = generatedFiles.Distinct().Select(g => this_FixupUtil.FixupFile(g)).ToList();
 
            ValidateFiles(this_TestGroupOutputDir, this_TestGroupBaselinesDir, this_TestGroupBootstrapDir, generatedFiles, baselineFiles, out failureMessage);
 
            return string.IsNullOrEmpty(failureMessage);
        }
 
        protected bool ValidateFiles(string outputDir, string baselineDir, string bootstrapDir, List<string> generatedFiles, List<string> baselineFiles, out string failureMessage)
        {
            failureMessage = string.Empty;
 
            // populate tuple of generated files and baselines (if exist)
            var testFileTable = generatedFiles.Select(g => new Tuple<string, string>(g, baselineFiles.FirstOrDefault(b =>
            {
                var bRel = PathHelper.GetRelativePath(b, new DirectoryInfo(baselineDir));
                var gRel = PathHelper.GetRelativePath(g, new DirectoryInfo(outputDir));
                return bRel.Equals(gRel, RuntimeEnvironmentHelper.FileStringComparison);
            })));
 
            var generatedWithBaseline = testFileTable.Where(t => t.Item2 != null);
            var generatedWithNoBaseline = testFileTable.Where(t => t.Item2 == null).Select(t => t.Item1);
 
            var baselineWithNoGenerated = baselineFiles.Where(b => !generatedFiles.Any(g =>
            {
                var bRel = PathHelper.GetRelativePath(b, new DirectoryInfo(baselineDir));
                var gRel = PathHelper.GetRelativePath(g, new DirectoryInfo(outputDir));
                return bRel.Equals(gRel, RuntimeEnvironmentHelper.FileStringComparison);
            }));
 
            // Compare with baselines.
            foreach (var tuple in generatedWithBaseline)
            {
                var generated = tuple.Item1;
                var baseline = tuple.Item2;
                if (!CompareFiles(baseline, generated, out var diffLines))
                {
                    failureMessage += string.Format(g_DiffBaselineErrorFormat, diffLines.Value.Key, diffLines.Value.Value, baseline, generated);
                }
            }
 
            // copy generated files with no baseline.
            foreach (var generated in generatedWithNoBaseline)
            {
                PathHelper.GetRelativePath(generated, new DirectoryInfo(outputDir), out var relativePath);
                if (string.IsNullOrEmpty(relativePath))
                {
                    PathHelper.GetRelativePath(generated, new DirectoryInfo(bootstrapDir), out relativePath);
                }
                var expected = Path.Combine(baselineDir, relativePath);
                Directory.CreateDirectory(Path.GetDirectoryName(expected));
                File.Copy(generated, expected);
                failureMessage += string.Format(g_NoBaselineErrorFormat, expected);
            }
 
            // generate delete script for unvalidated baselines in case they are no longer valid.
            if (baselineWithNoGenerated.Count() > 0)
            {
                failureMessage += GenerateBaselineDeleteScript(baselineWithNoGenerated);
            }
 
            return string.IsNullOrEmpty(failureMessage);
        }
 
        protected string GenerateBaselineDeleteScript(IEnumerable<string> unvalidatedBaselines)
        {
            var scriptPath = Path.Combine(g_TestOutputDir, @"TestScripts", $"{this_TestCaseName}.cmd");
            var globalScriptPath = Path.Combine(g_TestOutputDir, @"TestScripts\deleteAll.cmd");
 
            var errMsg = new StringBuilder();
            var cmdStr = new StringBuilder();
 
            foreach (var baseline in unvalidatedBaselines)
            {
                errMsg.AppendLine($"+ {baseline}");
                cmdStr.AppendLine($"del /s {baseline}");
            }
 
            Directory.CreateDirectory(Path.GetDirectoryName(scriptPath));
            File.WriteAllText(scriptPath, cmdStr.ToString());
 
            return string.Format(g_UnvalidatedBaselineMessageFormat, unvalidatedBaselines
                .Aggregate((msg, b) => $"{msg}{Environment.NewLine}{b}"), scriptPath);
        }
 
        protected bool CompareFiles(string baselineFile, string generatedFile, out KeyValuePair<string, string>? diffLines)
        {
            diffLines = null;
 
            var fileLines1 = new List<string>();
            var fileLines2 = new List<string>();
            var exceptLines = new List<string>() { "<ImplicitUsings>enable</ImplicitUsings>", "<Nullable>enable</Nullable>", "<Content CopyToOutputDirectory" };
 
            fileLines1.AddRange(File.ReadAllLines(baselineFile));
            fileLines2.AddRange(File.ReadAllLines(generatedFile));
 
            fileLines1.RemoveAll(l => exceptLines.Any(ex => l.Contains(ex)));
            fileLines2.RemoveAll(l => exceptLines.Any(ex => l.Contains(ex)));
 
            // to reduce noise, let's ignore empty lines in log files (only).
            var isLogFile = Path.GetExtension(baselineFile).Equals(".log", StringComparison.OrdinalIgnoreCase);
            if (isLogFile && fileLines1.Count != fileLines2.Count)
            {
                fileLines1 = fileLines1.Where(f => !string.IsNullOrWhiteSpace(f)).ToList();
                fileLines2 = fileLines2.Where(f => !string.IsNullOrWhiteSpace(f)).ToList();
            }
 
            if (fileLines1.Count != fileLines2.Count)
            {
                diffLines = new KeyValuePair<string, string>($"Line count file1: {fileLines1.Count}", $"Line count file2: {fileLines2.Count}");
                return false;
            }
 
            // some source lines get generated in different order (mostly cs files), to reduce noise let's compare source lines.
            fileLines1.Sort();
            fileLines2.Sort();
 
            for (int i = 0; i < fileLines1.Count; i++)
            {
                if (fileLines1[i] != fileLines2[i])
                {
                    diffLines = new KeyValuePair<string, string>(fileLines1[i], fileLines2[i]);
                    return false;
                }
            }
 
            return true;
        }
        #endregion
 
        #region helper methods
        protected void WriteLog(string msg, string options = null)
        {
            if (options != null)
            {
                WriteParams(options);
            }
            
        }
 
        protected void WriteParams(string options)
        {
            WriteLog("Test name: " + this_TestCaseName);
            WriteLog("Parameters: " + options);
            WriteLog("Root path: " + g_TestCasesDir);
            WriteLog("Working dir: " + this_TestCaseProject?.DirectoryPath);
            WriteLog("Results dir: " + this_TestCaseOutputDir);
            WriteLog("Baslines dir: " + this_TestCaseBaselinesDir);
            WriteLog("Bootstrap dir: " + this_TestCaseBootstrapDir);
            WriteLog("Test project: " + this_TestCaseProject?.FullPath);
            WriteLog("Log file: " + this_TestCaseLogFile);
        }
 
        protected static string GetSTestRootDir()
        {
            // Root path is current working directory if TestCases exists in it. Otherwise set it 
            // based on the repository root.
            var vstestDir = Directory.GetCurrentDirectory();
            if (!Directory.Exists(Path.Combine(vstestDir, "TestCases")))
            {
                vstestDir = new DirectoryInfo(Path.Combine(g_RepositoryRoot, "src", "dotnet-svcutil", "lib", "tests")).FullName;
                Assert.True(Directory.Exists(vstestDir), $"{nameof(vstestDir)} is not initialized!");
            }
            return vstestDir;
        }
 
        internal static string GetSvcutilPkgVersionAndFeed()
        {
            if (g_SvcutilPkgVersion == null)
            {
                string nugetId;
                string[] nugetFiles = Directory.GetFiles(g_TestCasesDir, "*.nupkg", SearchOption.TopDirectoryOnly);
                Assert.True(nugetFiles.Length <= 1, "There should be one and only one nuget package for testing.");
 
                if (nugetFiles.Length == 0)
                {
                    var binDir = Path.Combine(g_RepositoryRoot, "artifacts", "packages", buildType, "Shipping");
                    if (!Directory.Exists(binDir) || !Directory.GetFiles(binDir, "dotnet-svcutil-lib.*.nupkg").Any())
                    {
                        binDir = Path.Combine(g_RepositoryRoot, "artifacts", "packages", buildType, "NonShipping");
                    }
 
                    DirectoryInfo binDirectory = new DirectoryInfo(binDir);
 
                    FileInfo nugetFile = binDirectory.GetFiles("dotnet-svcutil-lib.*.nupkg", SearchOption.AllDirectories).Where(f => !f.Name.Contains("symbols")).OrderByDescending(f => f.LastWriteTime).FirstOrDefault();
 
                    Assert.NotNull(nugetFile);
 
                    g_SvcutilNugetFeed = nugetFile.DirectoryName;
                    nugetId = Path.GetFileNameWithoutExtension(nugetFile.Name);
                }
                else
                {
                    g_SvcutilNugetFeed = g_TestCasesDir;
                    nugetId = Path.GetFileNameWithoutExtension(nugetFiles[0]);
                }
 
                g_SvcutilPkgVersion = nugetId.Substring("dotnet-svcutil-lib.".Length);
            }
 
            return g_SvcutilPkgVersion;
        }
 
        internal static string FindRepositoryRoot()
        {
            if (g_RepositoryRoot == null)
            {
                if (string.IsNullOrEmpty(g_RepositoryRoot))
                {
                    var parentDir = Directory.GetCurrentDirectory();
                    var rootSolutionFolder = PathHelper.TryFindFileAsync("dotnet-svcutil.sln", parentDir, null, CancellationToken.None).Result;
                    Assert.True(rootSolutionFolder != null && File.Exists(rootSolutionFolder), $"Unable to find dotnet-svcutil.sln file, current dir: {parentDir}");
 
                    g_RepositoryRoot = Path.GetDirectoryName(rootSolutionFolder);
                }
            }
 
            return g_RepositoryRoot;
        }
 
        // Need to fix the dotnet SDK version as newer versions report different messages, making it hard to keep up with the log baselines 
        protected const string globalConfigFormat = "{{ \"sdk\": {{ \"version\": \"{0}\" }} }}";
 
        internal static void CreateGlobalJson(string directory, string sdkVersion)
        {
            var globalConfig = string.Format(globalConfigFormat, sdkVersion);
            Directory.CreateDirectory(directory);
            File.WriteAllText(Path.Combine(directory, "global.json"), globalConfig);
        }
 
        // The build system require access to VSTS feeds which cannot be provided by the tests, need to remove that feed from nuget.config.
        protected static string nugetConfigText =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" + Environment.NewLine +
"<configuration>" + Environment.NewLine +
"  <packageSources>" + Environment.NewLine +
"    <clear />" + Environment.NewLine +
"    <add key = \"svcutilTestFeed\" value=\"$svcutilTestFeed$\" />" + Environment.NewLine +
"    <add key = \"dotnet-public\" value=\"https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json\" />" + Environment.NewLine +
"  </packageSources>" + Environment.NewLine +
"</configuration>" + Environment.NewLine;
 
        internal static void CreateNuGetConfig()
        {
            GetSvcutilPkgVersionAndFeed();
            Directory.CreateDirectory(g_TestOutputDir);
            File.WriteAllText(Path.Combine(g_TestOutputDir, "nuget.config"), nugetConfigText.Replace("$svcutilTestFeed$", g_SvcutilNugetFeed));
        }
        #endregion
 
        protected class CurrentDirectorySetter : IDisposable
        {
            protected string _cashedWorkingDirectory;
 
            public CurrentDirectorySetter(string currentDirectory)
            {
                this._cashedWorkingDirectory = Directory.GetCurrentDirectory();
                Directory.SetCurrentDirectory(currentDirectory);
            }
 
            public void Dispose()
            {
                Directory.SetCurrentDirectory(this._cashedWorkingDirectory);
            }
        }
    }
}