File: TouchedFileLoggingTests.cs
Web Access
Project: src\src\Compilers\Server\VBCSCompilerTests\VBCSCompiler.UnitTests.csproj (VBCSCompiler.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.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
using static Roslyn.Test.Utilities.SharedResourceHelpers;
using System.Reflection;
using Microsoft.CodeAnalysis.CompilerServer;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.VisualBasic;
 
namespace Microsoft.CodeAnalysis.CompilerServer.UnitTests
{
    public class TouchedFileLoggingTests : TestBase
    {
        private static readonly string s_libDirectory = Environment.GetEnvironmentVariable("LIB");
        private readonly string _baseDirectory = TempRoot.Root;
        private const string HelloWorldCS = @"using System;
 
class C
{
    public static void Main(string[] args)
    {
        Console.WriteLine(""Hello, world"");
    }
}";
 
        private const string HelloWorldVB = @"Imports System
Class C
    Shared Sub Main(args As String())
        Console.WriteLine(""Hello, world"")
    End Sub
End Class
";
 
        [ConditionalFact(typeof(DesktopOnly))]
        public void CSharpTrivialMetadataCaching()
        {
            var loader = DefaultAnalyzerAssemblyLoader.CreateNonLockingLoader(Temp.CreateDirectory().Path);
            var filelist = new List<string>();
 
            // Do the following compilation twice.
            // The compiler server API should hold on to the mscorlib bits
            // in memory, but the file tracker should still map that it was
            // touched.
            for (int i = 0; i < 2; i++)
            {
                var source1 = Temp.CreateFile().WriteAllText(HelloWorldCS).Path;
                var touchedDir = Temp.CreateDirectory();
                var touchedBase = Path.Combine(touchedDir.Path, "touched");
                var clientDirectory = AppContext.BaseDirectory;
 
                filelist.Add(source1);
                var outWriter = new StringWriter();
                var cmd = new CSharpCompilerServer(
                    CompilerServerHost.SharedAssemblyReferenceProvider,
                    responseFile: null,
                    new[] { "/nologo", "/touchedfiles:" + touchedBase, source1 },
                    new BuildPaths(clientDirectory, _baseDirectory, RuntimeEnvironment.GetRuntimeDirectory(), Path.GetTempPath()),
                    s_libDirectory,
                    loader,
                    driverCache: null);
 
                List<string> expectedReads;
                List<string> expectedWrites;
                BuildTouchedFiles(cmd,
                                  Path.ChangeExtension(source1, "exe"),
                                  out expectedReads,
                                  out expectedWrites);
 
                var exitCode = cmd.Run(outWriter);
 
                Assert.Equal(string.Empty, outWriter.ToString().Trim());
                Assert.Equal(0, exitCode);
 
                AssertTouchedFilesEqual(expectedReads,
                                        expectedWrites,
                                        touchedBase);
            }
 
            foreach (String f in filelist)
            {
                CleanupAllGeneratedFiles(f);
            }
        }
 
        [ConditionalFact(typeof(DesktopOnly))]
        public void VisualBasicTrivialMetadataCaching()
        {
            var loader = DefaultAnalyzerAssemblyLoader.CreateNonLockingLoader(Temp.CreateDirectory().Path);
            var filelist = new List<string>();
 
            // Do the following compilation twice.
            // The compiler server API should hold on to the mscorlib bits
            // in memory, but the file tracker should still map that it was
            // touched.
            for (int i = 0; i < 2; i++)
            {
                var source1 = Temp.CreateFile().WriteAllText(HelloWorldVB).Path;
                var touchedDir = Temp.CreateDirectory();
                var touchedBase = Path.Combine(touchedDir.Path, "touched");
                var clientDirectory = AppContext.BaseDirectory;
 
                filelist.Add(source1);
                var outWriter = new StringWriter();
                var cmd = new VisualBasicCompilerServer(
                    CompilerServerHost.SharedAssemblyReferenceProvider,
                    responseFile: null,
                    new[] { "/nologo", "/touchedfiles:" + touchedBase, source1 },
                    new BuildPaths(clientDirectory, _baseDirectory, RuntimeEnvironment.GetRuntimeDirectory(), Path.GetTempPath()),
                    s_libDirectory,
                    loader,
                    driverCache: null);
 
                List<string> expectedReads;
                List<string> expectedWrites;
                BuildTouchedFiles(cmd,
                                  Path.ChangeExtension(source1, "exe"),
                                  out expectedReads,
                                  out expectedWrites);
 
                var exitCode = cmd.Run(outWriter);
 
                Assert.Equal(string.Empty, outWriter.ToString().Trim());
                Assert.Equal(0, exitCode);
 
                AssertTouchedFilesEqual(expectedReads,
                                        expectedWrites,
                                        touchedBase);
            }
 
            foreach (string f in filelist)
            {
                CleanupAllGeneratedFiles(f);
            }
        }
 
        /// <summary>
        /// Builds the expected base of touched files.
        /// Adds a hook for temporary file creation as well,
        /// so this method must be called before the execution of
        /// Csc.Run.
        /// </summary>
        private static void BuildTouchedFiles(CommonCompiler cmd,
                                              string outputPath,
                                              out List<string> expectedReads,
                                              out List<string> expectedWrites)
        {
            expectedReads = new List<string>();
            expectedReads.AddRange(cmd.Arguments.MetadataReferences.Select(r => r.Reference));
 
            if (cmd.Arguments is VisualBasicCommandLineArguments { DefaultCoreLibraryReference: { } reference })
            {
                expectedReads.Add(reference.Reference);
            }
 
            foreach (var file in cmd.Arguments.SourceFiles)
            {
                expectedReads.Add(file.Path);
            }
 
            var writes = new List<string>();
            writes.Add(outputPath);
 
            expectedWrites = writes;
        }
 
        private static void AssertTouchedFilesEqual(
            List<string> expectedReads,
            List<string> expectedWrites,
            string touchedFilesBase)
        {
            var touchedReadPath = touchedFilesBase + ".read";
            var touchedWritesPath = touchedFilesBase + ".write";
 
            var expected = expectedReads.Select(s => s.ToUpperInvariant()).OrderBy(s => s);
            Assert.Equal(string.Join("\r\n", expected),
                         File.ReadAllText(touchedReadPath).Trim());
 
            expected = expectedWrites.Select(s => s.ToUpperInvariant()).OrderBy(s => s);
            Assert.Equal(string.Join("\r\n", expected),
                         File.ReadAllText(touchedWritesPath).Trim());
        }
    }
}