/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/Server/VBCSCompilerTests/CompilationCacheBehaviorTests.cs
519 строк
21 KB
Copilot
Add compilation caching mode to VBCSCompiler server (#82881)
04 апр 2026, 01:17
Не верифицирован
04 апр 2026, 01:17
ae96a65
Код
Авторство
О чём код?
// 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. #if NET #nullable disable using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Basic.Reference.Assemblies; using Microsoft.CodeAnalysis.CommandLine; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Utilities; using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.CompilerServer.UnitTests; public sealed class CompilationCacheBehaviorTests(ITestOutputHelper testOutputHelper) : TestBase { private readonly ICompilerServerLogger _logger = new XunitCompilerServerLogger(testOutputHelper); [Theory] [InlineData(false)] [InlineData(true)] public async Task CacheHit_OmitsWarningDiagnosticsFromOutput(bool visualBasic) { var workingDirectory = Temp.CreateDirectory(); var cacheDirectory = Temp.CreateDirectory(); var supportAssembly = CreateWarningAnalyzerAssembly(workingDirectory); var sourceFileName = visualBasic ? "test.vb" : "test.cs"; var outputFileName = "test.dll"; var source = visualBasic ? """ Public Class TestType End Class """ : """ public class TestType { } """; using var serverData = await ServerUtil.CreateServer(_logger); var arguments = BuildCompilationArguments(visualBasic, serverData.PipeName, sourceFileName, outputFileName, additionalArguments: $"/a:{supportAssembly}", cachePath: cacheDirectory.Path); var (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), arguments, workingDirectory, [new(sourceFileName, source)]); Assert.Equal(0, exitCode); Assert.Contains("warning CACHWARN001", output, StringComparison.Ordinal); Assert.DoesNotContain("Compilation result restored from cache.", output, StringComparison.Ordinal); File.Delete(Path.Combine(workingDirectory.Path, outputFileName)); (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), arguments, workingDirectory); Assert.Equal(0, exitCode); Assert.Contains("Compilation result restored from cache.", output, StringComparison.Ordinal); Assert.DoesNotContain("warning CACHWARN001", output, StringComparison.Ordinal); Assert.True(File.Exists(Path.Combine(workingDirectory.Path, outputFileName))); } [Theory] [InlineData(false)] [InlineData(true)] public async Task CacheHit_DoesNotRecreateTouchedFiles(bool visualBasic) { var workingDirectory = Temp.CreateDirectory(); var cacheDirectory = Temp.CreateDirectory(); var sourceFileName = visualBasic ? "test.vb" : "test.cs"; var outputFileName = "test.dll"; var touchedFilesBase = Path.Combine(workingDirectory.Path, "touched"); var source = visualBasic ? """ Public Class TestType End Class """ : """ public class TestType { } """; using var serverData = await ServerUtil.CreateServer(_logger); var arguments = BuildCompilationArguments(visualBasic, serverData.PipeName, sourceFileName, outputFileName, additionalArguments: $"/touchedfiles:{touchedFilesBase}", cachePath: cacheDirectory.Path); var (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), arguments, workingDirectory, [new(sourceFileName, source)]); Assert.Equal(0, exitCode); Assert.True(File.Exists(touchedFilesBase + ".read")); Assert.True(File.Exists(touchedFilesBase + ".write")); File.Delete(Path.Combine(workingDirectory.Path, outputFileName)); File.Delete(touchedFilesBase + ".read"); File.Delete(touchedFilesBase + ".write"); (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), arguments, workingDirectory); Assert.Equal(0, exitCode); Assert.Contains("Compilation result restored from cache.", output, StringComparison.Ordinal); Assert.False(File.Exists(touchedFilesBase + ".read")); Assert.False(File.Exists(touchedFilesBase + ".write")); } [Theory] [InlineData(false)] [InlineData(true)] public async Task CacheHit_OmitsReportAnalyzerOutput(bool visualBasic) { var workingDirectory = Temp.CreateDirectory(); var cacheDirectory = Temp.CreateDirectory(); var supportAssembly = CreateReportAnalyzerAssembly(workingDirectory); var sourceFileName = visualBasic ? "test.vb" : "test.cs"; var outputFileName = "test.dll"; var source = visualBasic ? """ Public Class TestType End Class """ : """ public class TestType { } """; using var serverData = await ServerUtil.CreateServer(_logger); var arguments = BuildCompilationArguments(visualBasic, serverData.PipeName, sourceFileName, outputFileName, additionalArguments: $"/reportanalyzer /a:{supportAssembly}", cachePath: cacheDirectory.Path); var (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), arguments, workingDirectory, [new(sourceFileName, source)]); Assert.Equal(0, exitCode); Assert.Contains("Total analyzer execution time:", output, StringComparison.Ordinal); Assert.Contains("Total generator execution time:", output, StringComparison.Ordinal); Assert.Contains("CacheWarningAnalyzer", output, StringComparison.Ordinal); Assert.Contains("CacheReportGenerator", output, StringComparison.Ordinal); File.Delete(Path.Combine(workingDirectory.Path, outputFileName)); (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), arguments, workingDirectory); Assert.Equal(0, exitCode); Assert.Contains("Compilation result restored from cache.", output, StringComparison.Ordinal); Assert.DoesNotContain("Total analyzer execution time:", output, StringComparison.Ordinal); Assert.DoesNotContain("Total generator execution time:", output, StringComparison.Ordinal); Assert.DoesNotContain("CacheWarningAnalyzer", output, StringComparison.Ordinal); Assert.DoesNotContain("CacheReportGenerator", output, StringComparison.Ordinal); } [Theory] [InlineData(false)] [InlineData(true)] public async Task CacheHit_OmitsReportIvtsOutput(bool visualBasic) { var workingDirectory = Temp.CreateDirectory(); var cacheDirectory = Temp.CreateDirectory(); var referenceSourceFileName = visualBasic ? "FriendLib.vb" : "FriendLib.cs"; var consumerSourceFileName = visualBasic ? "Consumer.vb" : "Consumer.cs"; var referenceOutputFileName = "FriendLib.dll"; var consumerOutputFileName = "Consumer.dll"; var referenceSource = visualBasic ? """ Imports System.Runtime.CompilerServices <Assembly: InternalsVisibleTo("Consumer")> Friend Class BaseType End Class """ : """ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Consumer")] internal class BaseType { } """; var consumerSource = visualBasic ? """ Friend Class Consumer Inherits BaseType End Class """ : """ internal class Consumer : BaseType { } """; using var serverData = await ServerUtil.CreateServer(_logger); var referenceArguments = BuildCompilationArguments(visualBasic, serverData.PipeName, referenceSourceFileName, referenceOutputFileName, cachePath: cacheDirectory.Path); var (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), referenceArguments, workingDirectory, [new(referenceSourceFileName, referenceSource)]); Assert.Equal(0, exitCode); var consumerArguments = BuildCompilationArguments( visualBasic, serverData.PipeName, consumerSourceFileName, consumerOutputFileName, additionalArguments: $"/reportivts /r:{referenceOutputFileName}", cachePath: cacheDirectory.Path); (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), consumerArguments, workingDirectory, [new(consumerSourceFileName, consumerSource)]); Assert.Equal(0, exitCode); Assert.Contains("Printing 'InternalsVisibleToAttribute' information", output, StringComparison.Ordinal); Assert.Contains("Grants IVT to current assembly: True", output, StringComparison.Ordinal); File.Delete(Path.Combine(workingDirectory.Path, consumerOutputFileName)); (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), consumerArguments, workingDirectory); Assert.Equal(0, exitCode); Assert.Contains("Compilation result restored from cache.", output, StringComparison.Ordinal); Assert.DoesNotContain("Printing 'InternalsVisibleToAttribute' information", output, StringComparison.Ordinal); } [Fact] public async Task CacheHit_DoesNotRecreateGeneratedFiles() { var workingDirectory = Temp.CreateDirectory(); var cacheDirectory = Temp.CreateDirectory(); var sourceFileName = "test.cs"; var outputFileName = "test.dll"; var generatedFilesDirectory = Path.Combine(workingDirectory.Path, "generated"); var generatorAssembly = CreateGeneratedFilesGeneratorAssembly(workingDirectory); var source = """ public class TestType { } """; Directory.CreateDirectory(generatedFilesDirectory); using var serverData = await ServerUtil.CreateServer(_logger); var arguments = BuildCompilationArguments( visualBasic: false, serverData.PipeName, sourceFileName, outputFileName, additionalArguments: $"/langversion:preview /generatedfilesout:{generatedFilesDirectory} /a:{generatorAssembly}", cachePath: cacheDirectory.Path); var (exitCode, output) = RunCommandLineCompiler(RequestLanguage.CSharpCompile, arguments, workingDirectory, [new(sourceFileName, source)]); Assert.Equal(0, exitCode); Assert.Single(Directory.GetFiles(generatedFilesDirectory, "generatedSource.cs", SearchOption.AllDirectories)); File.Delete(Path.Combine(workingDirectory.Path, outputFileName)); Directory.Delete(generatedFilesDirectory, recursive: true); (exitCode, output) = RunCommandLineCompiler(RequestLanguage.CSharpCompile, arguments, workingDirectory); Assert.Equal(0, exitCode); Assert.Contains("Compilation result restored from cache.", output, StringComparison.Ordinal); Assert.False(Directory.Exists(generatedFilesDirectory)); } [Theory] [InlineData(false)] [InlineData(true)] public async Task CacheHit_DoesNotRecreateDebugDeterminismKey(bool visualBasic) { var workingDirectory = Temp.CreateDirectory(); var cacheDirectory = Temp.CreateDirectory(); var sourceFileName = visualBasic ? "test.vb" : "test.cs"; var outputFileName = "test.dll"; var pdbFileName = "test.pdb"; var keyFileName = "test.dll.key"; var source = visualBasic ? """ Public Class TestType End Class """ : """ public class TestType { } """; using var serverData = await ServerUtil.CreateServer(_logger); var arguments = BuildCompilationArguments( visualBasic, serverData.PipeName, sourceFileName, outputFileName, additionalArguments: "/debug:portable /features:debug-determinism", cachePath: cacheDirectory.Path); var (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), arguments, workingDirectory, [new(sourceFileName, source)]); Assert.Equal(0, exitCode); Assert.True(File.Exists(Path.Combine(workingDirectory.Path, keyFileName))); File.Delete(Path.Combine(workingDirectory.Path, outputFileName)); File.Delete(Path.Combine(workingDirectory.Path, pdbFileName)); File.Delete(Path.Combine(workingDirectory.Path, keyFileName)); (exitCode, output) = RunCommandLineCompiler(GetLanguage(visualBasic), arguments, workingDirectory); Assert.Equal(0, exitCode); Assert.Contains("Compilation result restored from cache.", output, StringComparison.Ordinal); Assert.True(File.Exists(Path.Combine(workingDirectory.Path, outputFileName))); Assert.True(File.Exists(Path.Combine(workingDirectory.Path, pdbFileName))); Assert.False(File.Exists(Path.Combine(workingDirectory.Path, keyFileName))); } private static RequestLanguage GetLanguage(bool visualBasic) => visualBasic ? RequestLanguage.VisualBasicCompile : RequestLanguage.CSharpCompile; private static string BuildCompilationArguments(bool visualBasic, string pipeName, string sourceFileName, string outputFileName, string additionalArguments = "", string cachePath = null) { var languageArguments = visualBasic ? "/vbruntime*" : ""; var segments = new[] { sourceFileName, $"/shared:{pipeName}", "/deterministic+", "/nologo", "/t:library", $"/out:{outputFileName}", cachePath is null ? "" : $"/features:{CompilerOptionParseUtilities.UseGlobalCacheFeatureFlag}={cachePath}", languageArguments, additionalArguments, }; return string.Join(" ", segments.Where(static segment => !string.IsNullOrWhiteSpace(segment))); } private static string CreateWarningAnalyzerAssembly(TempDirectory directory) => EmitSupportAssembly( directory, "CacheWarningAnalyzer", """ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class CacheWarningAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor s_rule = new( "CACHWARN001", "Cache warning", "Cache warning", "Testing", DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule); public override void Initialize(AnalysisContext context) => context.RegisterSyntaxTreeAction(static context => context.ReportDiagnostic(Diagnostic.Create(s_rule, context.Tree.GetRoot().GetLocation()))); } """); private static string CreateReportAnalyzerAssembly(TempDirectory directory) => EmitSupportAssembly( directory, "CacheReportAnalyzer", """ using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class CacheWarningAnalyzer : DiagnosticAnalyzer { private static readonly DiagnosticDescriptor s_rule = new( "CACHWARN001", "Cache warning", "Cache warning", "Testing", DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule); public override void Initialize(AnalysisContext context) => context.RegisterSyntaxTreeAction(static context => context.ReportDiagnostic(Diagnostic.Create(s_rule, context.Tree.GetRoot().GetLocation()))); } [Generator(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class CacheReportGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { } public void Execute(GeneratorExecutionContext context) { } } """); private static string CreateGeneratedFilesGeneratorAssembly(TempDirectory directory) => EmitSupportAssembly( directory, "CacheGeneratedFilesGenerator", """ using Microsoft.CodeAnalysis; [Generator(LanguageNames.CSharp)] public sealed class CacheGeneratedFileGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { } public void Execute(GeneratorExecutionContext context) { context.AddSource("generatedSource.cs", "public class GeneratedFromCache { }"); } } """); private static string EmitSupportAssembly(TempDirectory directory, string assemblyName, string source) { var path = Path.Combine(directory.Path, assemblyName + ".dll"); var compilation = CSharpCompilation.Create( assemblyName, syntaxTrees: [CSharpSyntaxTree.ParseText(source)], references: GetSupportAssemblyReferences(), options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var emitResult = compilation.Emit(path); Assert.True(emitResult.Success, string.Join(Environment.NewLine, emitResult.Diagnostics)); return path; } private static IEnumerable<MetadataReference> GetSupportAssemblyReferences() { var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")) .Split(Path.PathSeparator) .Select(static path => MetadataReference.CreateFromFile(path)) .ToList(); var codeAnalysisAssemblyPath = typeof(ISourceGenerator).Assembly.Location; if (!references.Any(reference => string.Equals(reference.Display, codeAnalysisAssemblyPath, StringComparison.OrdinalIgnoreCase))) { references.Add(MetadataReference.CreateFromFile(codeAnalysisAssemblyPath)); } return references; } private static void ReferenceNetstandardDllIfCoreClr(TempDirectory currentDirectory, List<string> arguments) { var filePath = Path.Combine(currentDirectory.Path, "netstandard.dll"); File.WriteAllBytes(filePath, NetStandard20.Resources.netstandard); arguments.Add("/nostdlib"); arguments.Add("/r:netstandard.dll"); } private static void CreateFiles(TempDirectory currentDirectory, IEnumerable<KeyValuePair<string, string>> files) { if (files == null) { return; } foreach (var pair in files) { currentDirectory.CreateFile(pair.Key).WriteAllText(pair.Value); } } private static (T Result, string Output) UseTextWriter<T>(Encoding encoding, Func<TextWriter, T> func) { MemoryStream memoryStream; TextWriter writer; if (encoding == null) { memoryStream = null; writer = new StringWriter(); } else { memoryStream = new MemoryStream(); writer = new StreamWriter(memoryStream, encoding); } var result = func(writer); writer.Flush(); if (memoryStream != null) { return (result, encoding.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length)); } return (result, ((StringWriter)writer).ToString()); } private (int ExitCode, string Output) RunCommandLineCompiler( RequestLanguage language, string argumentsSingle, TempDirectory currentDirectory, IEnumerable<KeyValuePair<string, string>> filesInDirectory = null, IEnumerable<KeyValuePair<string, string>> additionalEnvironmentVars = null, Encoding redirectEncoding = null) { var arguments = new List<string>(argumentsSingle.Split(' ')) { "/preferreduilang:en" }; ReferenceNetstandardDllIfCoreClr(currentDirectory, arguments); CreateFiles(currentDirectory, filesInDirectory); var client = ServerUtil.CreateBuildClient(language, _logger); var buildPaths = new BuildPaths( clientDir: Path.GetDirectoryName(typeof(CommonCompiler).Assembly.Location), workingDir: currentDirectory.Path, sdkDir: ServerUtil.DefaultSdkDirectory, tempDir: Path.GetTempPath()); var (result, output) = UseTextWriter( redirectEncoding, writer => ApplyEnvironmentVariables(additionalEnvironmentVars, () => client.RunCompilation(arguments, buildPaths, writer))); Assert.True(result.RanOnServer); return (result.ExitCode, output); } } #endif