Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refresh compilation before build and deploy to fix 8239 #8721

Merged
merged 4 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Bicep.Core.UnitTests/Assertions/StringAssertionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ public static AndConstraint<StringAssertions> EqualIgnoringNewlines(this StringA
return new AndConstraint<StringAssertions>(instance);
}

public static AndConstraint<StringAssertions> ContainIgnoringNewlines(this StringAssertions instance, string expected)
{
var normalizedActual = StringUtils.ReplaceNewlines(instance.Subject, "\n");
var normalizedExpected = StringUtils.ReplaceNewlines(expected, "\n");

normalizedActual.Should().Contain(normalizedExpected);

return new AndConstraint<StringAssertions>(instance);
}

public static AndConstraint<StringAssertions> HaveLengthLessThanOrEqualTo(this StringAssertions instance, int maxLength, string because = "", params object[] becauseArgs)
{
int length = instance.Subject.Length;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,5 +243,50 @@ public void TemplateContainsBicepGeneratorMetadata_WithoutBicepGeneratorMetadata

Assert.IsFalse(actual);
}

[TestMethod]
public async Task Handle_ShouldPickUp_LoadTextContent_Updates()
{
string testOutputPath = FileHelper.GetUniqueTestOutputPath(TestContext);

string sqlFileContents = @"CREATE TABLE regions1 (
region_id INT IDENTITY(1,1) PRIMARY KEY
);";
FileHelper.SaveResultFile(TestContext, "test.sql", sqlFileContents, testOutputPath);

string bicepFileContents = @"var textFromFile = loadTextContent('test.sql')";
string bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents, testOutputPath);

Uri bicepFileUri = new Uri(bicepFilePath);
DocumentUri documentUri = DocumentUri.From(bicepFileUri);
BicepCompilationManager bicepCompilationManager = BicepCompilationManagerHelper.CreateCompilationManager(documentUri, bicepFileContents, true);
BicepBuildCommandHandler bicepBuildCommandHandler = new BicepBuildCommandHandler(bicepCompilationManager, Repository.Create<ISerializer>().Object, BicepTestConstants.FeatureProviderFactory, BicepTestConstants.NamespaceProvider, FileResolver, ModuleDispatcher, BicepTestConstants.ApiVersionProviderFactory, configurationManager, BicepTestConstants.LinterAnalyzer);

string buildOutputMessage = await bicepBuildCommandHandler.Handle(bicepFilePath, CancellationToken.None);

string buildOutputFilePath = Path.Combine(testOutputPath, "input.json");

VerifyBuildOutputMessageAndContents(buildOutputMessage, File.ReadAllText(buildOutputFilePath), @"""variables"": {
""textFromFile"": ""CREATE TABLE regions1 (\n region_id INT IDENTITY(1,1) PRIMARY KEY\n);""
}");

// Update test.sql and execute build command
sqlFileContents = @"CREATE TABLE regions2 (
region_id INT IDENTITY(1,1) PRIMARY KEY
);";
FileHelper.SaveResultFile(TestContext, "test.sql", sqlFileContents, testOutputPath);

buildOutputMessage = await bicepBuildCommandHandler.Handle(bicepFilePath, CancellationToken.None);

VerifyBuildOutputMessageAndContents(buildOutputMessage, File.ReadAllText(buildOutputFilePath), @"""variables"": {
""textFromFile"": ""CREATE TABLE regions2 (\n region_id INT IDENTITY(1,1) PRIMARY KEY\n);""
}");
}

private void VerifyBuildOutputMessageAndContents(string actualBuildOutputMessage, string buildOutputContents, string expectedText)
{
actualBuildOutputMessage.Should().Be(@"Bicep build succeeded. Created ARM template file: input.json");
buildOutputContents.Should().ContainIgnoringNewlines(expectedText);
}
}
}
92 changes: 92 additions & 0 deletions src/Bicep.LangServer.UnitTests/Helpers/CompilationHelperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Bicep.Core.Configuration;
using Bicep.Core.FileSystem;
using Bicep.Core.Registry;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Utils;
using Bicep.LanguageServer;
using Bicep.LanguageServer.Handlers;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Protocol;
using IOFileSystem = System.IO.Abstractions.FileSystem;
using CompilationHelper = Bicep.LanguageServer.Utils.CompilationHelper;

namespace Bicep.LangServer.UnitTests.Helpers
{
[TestClass]
public class CompilationHelperTests
{
[NotNull]
public TestContext? TestContext { get; set; }

private static readonly FileResolver FileResolver = BicepTestConstants.FileResolver;
private static readonly IConfigurationManager configurationManager = new ConfigurationManager(new IOFileSystem());
private readonly ModuleDispatcher ModuleDispatcher = new ModuleDispatcher(BicepTestConstants.RegistryProvider, configurationManager);

[TestMethod]
public void GetCompilation_WithNullCompilationContext_ShouldCreateCompilation()
{
string bicepFileContents = @"resource dnsZone 'Microsoft.Network/dnsZones@2018-05-01' = {
name: 'dnsZone'
location: 'global'
}";
string bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents);
DocumentUri documentUri = DocumentUri.FromFileSystemPath(bicepFilePath);
// Do not upsert compilation. This will cause CompilationContext to be null
BicepCompilationManager bicepCompilationManager = BicepCompilationManagerHelper.CreateCompilationManager(documentUri, bicepFileContents, upsertCompilation: false);
var compilationContext = bicepCompilationManager.GetCompilation(documentUri);

compilationContext.Should().BeNull();

var compilation = CompilationHelper.GetCompilation(
documentUri,
documentUri.ToUri(),
BicepTestConstants.ApiVersionProviderFactory,
BicepTestConstants.LinterAnalyzer,
bicepCompilationManager,
configurationManager,
BicepTestConstants.FeatureProviderFactory,
FileResolver,
ModuleDispatcher,
BicepTestConstants.NamespaceProvider);

compilation.Should().NotBeNull();
}

[TestMethod]
public void GetCompilation_WithNonNullCompilationContext_ShouldReuseCompilation()
{
string bicepFileContents = @"resource dnsZone 'Microsoft.Network/dnsZones@2018-05-01' = {
name: 'dnsZone'
location: 'global'
}";
string bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents);
DocumentUri documentUri = DocumentUri.FromFileSystemPath(bicepFilePath);
// Upsert compilation. This will cause CompilationContext to be non null
BicepCompilationManager bicepCompilationManager = BicepCompilationManagerHelper.CreateCompilationManager(documentUri, bicepFileContents, upsertCompilation: true);

var compilation = CompilationHelper.GetCompilation(
documentUri,
documentUri.ToUri(),
BicepTestConstants.ApiVersionProviderFactory,
BicepTestConstants.LinterAnalyzer,
bicepCompilationManager,
configurationManager,
BicepTestConstants.FeatureProviderFactory,
FileResolver,
ModuleDispatcher,
BicepTestConstants.NamespaceProvider);

compilation.Should().NotBeNull();
compilation.Should().BeSameAs(bicepCompilationManager.GetCompilation(documentUri)!.Compilation);
}
}
}
23 changes: 11 additions & 12 deletions src/Bicep.LangServer/Handlers/BicepBuildCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,17 @@ private string GenerateCompiledFileAndReturnBuildOutputMessage(string bicepFileP

var fileUri = documentUri.ToUri();

CompilationContext? context = compilationManager.GetCompilation(fileUri);
Compilation compilation;

if (context is null)
{
SourceFileGrouping sourceFileGrouping = SourceFileGroupingBuilder.Build(this.fileResolver, this.moduleDispatcher, new Workspace(), fileUri);
compilation = new Compilation(featureProviderFactory, namespaceProvider, sourceFileGrouping, configurationManager, apiVersionProviderFactory, bicepAnalyzer);
}
else
{
compilation = context.Compilation;
}
var compilation = CompilationHelper.GetCompilation(
documentUri,
fileUri,
apiVersionProviderFactory,
bicepAnalyzer,
compilationManager,
configurationManager,
featureProviderFactory,
fileResolver,
moduleDispatcher,
namespaceProvider);

var diagnosticsByFile = compilation.GetAllDiagnosticsByBicepFile()
.FirstOrDefault(x => x.Key.FileUri == fileUri);
Expand Down
28 changes: 11 additions & 17 deletions src/Bicep.LangServer/Handlers/BicepDeploymentScopeRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,17 @@ public override Task<BicepDeploymentScopeResponse> Handle(BicepDeploymentScopePa

try
{
compilation = GetCompilation(documentUri);
compilation = CompilationHelper.GetCompilation(
documentUri,
documentUri.ToUri(),
apiVersionProviderFactory,
bicepAnalyzer,
compilationManager,
configurationManager,
featureProviderFactory,
fileResolver,
moduleDispatcher,
namespaceProvider);

// Cache the compilation so that it can be reused by BicepDeploymentParametersHandler
deploymentFileCompilationCache.CacheCompilation(documentUri, compilation);
Expand Down Expand Up @@ -129,21 +139,5 @@ private string GetCompiledFile(Compilation compilation, DocumentUri documentUri)

return stringBuilder.ToString();
}

private Compilation GetCompilation(DocumentUri documentUri)
{
var fileUri = documentUri.ToUri();

CompilationContext? context = compilationManager.GetCompilation(documentUri);
if (context is null)
{
SourceFileGrouping sourceFileGrouping = SourceFileGroupingBuilder.Build(this.fileResolver, this.moduleDispatcher, new Workspace(), fileUri);
return new Compilation(featureProviderFactory, namespaceProvider, sourceFileGrouping, configurationManager, apiVersionProviderFactory, bicepAnalyzer);
}
else
{
return context.Compilation;
}
}
}
}
49 changes: 49 additions & 0 deletions src/Bicep.LangServer/Utils/CompilationHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using Bicep.Core.Analyzers.Interfaces;
using Bicep.Core.Analyzers.Linter.ApiVersions;
using Bicep.Core.Configuration;
using Bicep.Core.Features;
using Bicep.Core.FileSystem;
using Bicep.Core.Registry;
using Bicep.Core.Semantics;
using Bicep.Core.Semantics.Namespaces;
using Bicep.Core.Workspaces;
using Bicep.LanguageServer.CompilationManager;
using OmniSharp.Extensions.LanguageServer.Protocol;

namespace Bicep.LanguageServer.Utils
{
public static class CompilationHelper
{
public static Compilation GetCompilation(
DocumentUri documentUri,
Uri fileUri,
IApiVersionProviderFactory apiVersionProviderFactory,
IBicepAnalyzer bicepAnalyzer,
ICompilationManager compilationManager,
IConfigurationManager configurationManager,
IFeatureProviderFactory featureProviderFactory,
IFileResolver fileResolver,
IModuleDispatcher moduleDispatcher,
INamespaceProvider namespaceProvider)
{
// Bicep file could contain load functions like loadTextContent(..). We'll refresh compilation to detect changes in files referenced in load functions.
compilationManager.RefreshCompilation(fileUri);
CompilationContext? context = compilationManager.GetCompilation(documentUri);
// CompilationContext will be null if the file is not open in the editor.
// E.g. When user right clicks on a file from the explorer context menu without opening the file and invokes build/deploy
if (context is null)
{
SourceFileGrouping sourceFileGrouping = SourceFileGroupingBuilder.Build(fileResolver, moduleDispatcher, new Workspace(), fileUri);
return new Compilation(featureProviderFactory, namespaceProvider, sourceFileGrouping, configurationManager, apiVersionProviderFactory, bicepAnalyzer);
}
else
{
return context.Compilation;
}
}
}
}