Skip to content

Commit

Permalink
Port ARM snippets to bicep - part 2 (#2207)
Browse files Browse the repository at this point in the history
  • Loading branch information
bhsubra authored Apr 14, 2021
1 parent eded2cf commit 10a7ae5
Show file tree
Hide file tree
Showing 124 changed files with 1,217 additions and 198 deletions.
354 changes: 330 additions & 24 deletions src/Bicep.Core.Samples/Files/Completions/declarations.json

Large diffs are not rendered by default.

125 changes: 54 additions & 71 deletions src/Bicep.LangServer.IntegrationTests/CompletionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
using Bicep.Core.FileSystem;
using Bicep.Core.Parsing;
using Bicep.Core.Samples;
using Bicep.Core.Semantics;
using Bicep.Core.Syntax;
using Bicep.Core.Text;
using Bicep.Core.TypeSystem;
using Bicep.Core.TypeSystem.Az;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Utils;
using Bicep.Core.Workspaces;
using Bicep.LangServer.IntegrationTests.Completions;
using Bicep.LangServer.IntegrationTests.Helpers;
using Bicep.LanguageServer.Extensions;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -69,89 +71,70 @@ public async Task EmptyFileShouldProduceDeclarationCompletions()
[DynamicData(nameof(GetSnippetCompletionData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(CompletionData), DynamicDataDisplayName = nameof(CompletionData.GetDisplayName))]
public async Task ValidateSnippetCompletionAfterPlaceholderReplacements(CompletionData completionData)
{
string pathPrefix = "Completions/SnippetTemplates/";
string bicepManifestResourceStreamName = pathPrefix + completionData.Prefix + "/main.bicep";
string jsonManifestResourceStreamName = pathPrefix + completionData.Prefix + "/diagnostics.json";
string pathPrefix = $"Completions/SnippetTemplates/{completionData.Prefix}";

// Save all the files in the containing directory to disk
SaveFilesToDisk(bicepManifestResourceStreamName, jsonManifestResourceStreamName, out string bicepFile, out string diagnosticsFile);
var outputDirectory = FileHelper.SaveEmbeddedResourcesWithPathPrefix(TestContext, typeof(CompletionTests).Assembly, pathPrefix);

// Verify snippet placeholder and expected diagnostics files exist
VerifyPlaceholderAndDiagnosticsInformationFilesExist(completionData.Prefix, bicepFile, diagnosticsFile);
var bicepFileName = Path.Combine(outputDirectory, "main.bicep");
var bicepSourceFileName = Path.Combine("src", "Bicep.LangServer.IntegrationTests", pathPrefix, Path.GetRelativePath(outputDirectory, bicepFileName));
File.Exists(bicepFileName).Should().BeTrue($"Snippet placeholder file \"{bicepSourceFileName}\" should be checked in");
var bicepContents = await File.ReadAllTextAsync(bicepFileName);

// Start language server, copy snippet text, replace placeholders and return diagnostics information
Container<Diagnostic> diagnostics = await StartServerAndGetDiagnosticsAsync(bicepManifestResourceStreamName, completionData.SnippetText);
// Request the expected completion from the server, and ensure it is unique + valid
var completionText = await RequestSnippetCompletion(bicepFileName, completionData, bicepContents);

Stream? jsonStream = typeof(CompletionTests).Assembly.GetManifestResourceStream(jsonManifestResourceStreamName);
StreamReader streamReader = new StreamReader(jsonStream ?? throw new ArgumentNullException("Stream is null"), Encoding.Default);
string expected = await streamReader.ReadToEndAsync();
// Replace all the placeholders with values from the placeholder file
var replacementContents = SnippetCompletionTestHelper.GetSnippetTextAfterPlaceholderReplacements(completionText, bicepContents);

var actual = JToken.FromObject(diagnostics);

var actualLocation = FileHelper.SaveResultFile(this.TestContext, $"{completionData.Prefix}_Actual.json", actual.ToString(Formatting.Indented));
var expectedLocation = Path.Combine("src", "Bicep.LangServer.Integrationtests", "Completions", "SnippetTemplates", completionData.Prefix, "main.json");

actual.Should().EqualWithJsonDiffOutput(TestContext, JToken.Parse(expected), expectedLocation, actualLocation, "because ");
}

private void SaveFilesToDisk(string bicepManifestResourceStreamName,
string jsonManifestResourceStreamName,
out string bicepFile,
out string diagnosticsFile)
{
var parentStream = GetParentStreamName(bicepManifestResourceStreamName);
var outputDirectory = FileHelper.SaveEmbeddedResourcesWithPathPrefix(TestContext, typeof(CompletionTests).Assembly, parentStream);

bicepFile = Path.Combine(outputDirectory, Path.GetFileName(bicepManifestResourceStreamName));
diagnosticsFile = Path.Combine(outputDirectory, Path.GetFileName(jsonManifestResourceStreamName));
using (new AssertionScope())
{
var combinedFileName = Path.Combine(outputDirectory, "main.combined.bicep");
var combinedSourceFileName = Path.Combine("src", "Bicep.LangServer.IntegrationTests", pathPrefix, Path.GetRelativePath(outputDirectory, combinedFileName));
File.Exists(combinedFileName).Should().BeTrue($"Combined snippet file \"{combinedSourceFileName}\" should be checked in");

var syntaxTreeGrouping = SyntaxTreeGroupingBuilder.Build(new FileResolver(), new Workspace(), PathHelper.FilePathToFileUrl(combinedFileName));
var compilation = new Compilation(TypeProvider, syntaxTreeGrouping);
var diagnostics = compilation.GetEntrypointSemanticModel().GetAllDiagnostics();

var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(replacementContents, Environment.NewLine, diagnostics, diag => OutputHelper.GetDiagLoggingString(replacementContents, outputDirectory, diag));
File.WriteAllText(combinedFileName + ".actual", sourceTextWithDiags);

sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput(
TestContext,
File.Exists(combinedFileName) ? (await File.ReadAllTextAsync(combinedFileName)) : string.Empty,
expectedLocation: combinedSourceFileName,
actualLocation: combinedFileName + ".actual");
}
}

private static string GetParentStreamName(string streamName) => Path.GetDirectoryName(streamName)!.Replace('\\', '/');

private async Task<Container<Diagnostic>> StartServerAndGetDiagnosticsAsync(string bicepFileName, string snippetText)
private async Task<string> RequestSnippetCompletion(string bicepFileName, CompletionData completionData, string placeholderFile)
{
Dictionary<Uri, string> fileSystemDict = new Dictionary<Uri, string>();
MultipleMessageListener<PublishDiagnosticsParams> diagnosticsListener = new MultipleMessageListener<PublishDiagnosticsParams>();

ILanguageClient client = await IntegrationTestHelper.StartServerWithClientConnectionAsync(
options =>
{
options.OnPublishDiagnostics(diags => diagnosticsListener.AddMessage(diags));
},
fileResolver: new InMemoryFileResolver(fileSystemDict));
var documentUri = DocumentUri.FromFileSystemPath(bicepFileName);
var syntaxTree = SyntaxTree.Create(documentUri.ToUri(), placeholderFile);

DocumentUri documentUri = DocumentUri.FromFileSystemPath(bicepFileName);
Stream? bicepStream = typeof(CompletionTests).Assembly.GetManifestResourceStream(bicepFileName);
StreamReader streamReader = new StreamReader(bicepStream ?? throw new ArgumentNullException("Stream is null"), Encoding.Default);
var client = await IntegrationTestHelper.StartServerWithTextAsync(
placeholderFile,
documentUri,
null,
TypeProvider);

string bicepFileWithPlaceholderReplacements = await streamReader.ReadToEndAsync();

string snippetTextAfterReplacements = SnippetCompletionTestHelper.GetSnippetTextAfterPlaceholderReplacements(snippetText, bicepFileWithPlaceholderReplacements);
fileSystemDict[documentUri.ToUri()] = bicepFileWithPlaceholderReplacements.Replace("// Insert snippet here", snippetTextAfterReplacements);

// This is required to verify module snippet completion
DocumentUri testDocumentUri = DocumentUri.FromFileSystemPath("Completions/SnippetTemplates/module/test.bicep");
fileSystemDict[testDocumentUri.ToUri()] = string.Empty;

client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(documentUri, fileSystemDict[documentUri.ToUri()], 1));
var cursor = placeholderFile.IndexOf("// Insert snippet here");
var completions = await client.RequestCompletion(new CompletionParams
{
TextDocument = documentUri,
Position = TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, cursor),
});

var diagsParams = await diagnosticsListener.WaitNext();
diagsParams.Uri.Should().Be(documentUri);
var matchingSnippets = completions.Where(x => x.Kind == CompletionItemKind.Snippet && x.Label == completionData.Prefix);

return diagsParams.Diagnostics;
}
matchingSnippets.Should().HaveCount(1);
var completion = matchingSnippets.First();

private void VerifyPlaceholderAndDiagnosticsInformationFilesExist(string prefix, string bicepFileName, string jsonFileName)
{
// Group assertion failures using AssertionScope, rather than reporting the first failure
using (new AssertionScope())
{
bool snippetPlaceholderFileExists = File.Exists(bicepFileName);
snippetPlaceholderFileExists.Should().BeTrue($"Snippet placeholder file for snippet with label- \"{prefix}\" should be checked in");
completion.TextEdit.Should().NotBeNull();
completion.TextEdit!.Range.Should().Be(new TextSpan(cursor, 0).ToRange(syntaxTree.LineStarts));
completion.TextEdit.NewText.Should().NotBeNullOrWhiteSpace();

bool diagnosticsFileExists = File.Exists(jsonFileName);
diagnosticsFileExists.Should().BeTrue($"Diagnostics information file- diagnostics.json for snippet with label- \"{prefix}\" should be checked in");
}
return completion.TextEdit.NewText;
}

private static IEnumerable<object[]> GetSnippetCompletionData()
Expand Down Expand Up @@ -296,7 +279,7 @@ public async Task Property_completions_include_descriptions()
var syntaxTree = SyntaxTree.Create(new Uri("file:///path/to/main.bicep"), file);
var client = await IntegrationTestHelper.StartServerWithTextAsync(file, syntaxTree.FileUri, resourceTypeProvider: BuiltInTestTypes.Create());
var completions = await RequestCompletions(client, syntaxTree, cursors);

completions.Should().SatisfyRespectively(
x => x!.OrderBy(d => d.SortText).Should().SatisfyRespectively(
d => d.Documentation!.MarkupContent!.Value.Should().Contain("This is a property which supports reading AND writing!"),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// $1 = testModule
// $2 = test.bicep
// $2 = 'main.bicep'
// $3 = 'myModule'

// Insert snippet here
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module testModule 'main.bicep' = {
name: 'myModule'

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
output testOutput int = 1

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@allowed([
1
])
param testParam int = 2

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
param testParam int = 1

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@secure()
param testParam string

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
param testParam int

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// $1 = aksCluster
// $2 = 1.19.7
// $3 = testDnsPrefix
// $1 = 'aksCluster'
// $2 = '1.19.7'
// $3 = 'testDnsPrefix'
// $4 = 3
// $5 = Standard_DS2_v2
// $6 = testUser
// $7 = testKeyData
// $5 = 'Standard_DS2_v2'
// $6 = 'testUser'
// $7 = 'testKeyData'

// Insert snippet here
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
resource aksCluster 'Microsoft.ContainerService/managedClusters@2021-03-01' = {
name: 'aksCluster'
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
properties: {
kubernetesVersion: '1.19.7'
dnsPrefix: 'testDnsPrefix'
enableRBAC: true
agentPoolProfiles: [
{
name: 'agentpool'
count: 3
vmSize: 'Standard_DS2_v2'
osType: 'Linux'
mode: 'System'
}
]
linuxProfile: {
adminUsername: 'testUser'
ssh: {
publicKeys: [
{
keyData: 'testKeyData'
}
]
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// $1 = testApplicationSecurityGroup
// $1 = 'testApplicationSecurityGroup'

// Insert snippet here
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource applicationSecurityGroup 'Microsoft.Network/applicationSecurityGroups@2019-11-01' = {
name: 'testApplicationSecurityGroup'
location: resourceGroup().location
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// $1 = testAutomationAccount
// $2 = Basic
// $1 = 'testAutomationAccount'
// $2 = 'Basic'

// Insert snippet here
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resource automationAccount 'Microsoft.Automation/automationAccounts@2015-10-31' = {
name: 'testAutomationAccount'
location: resourceGroup().location
properties: {
sku: {
name: 'Basic'
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// $1 = testAvailabilitySet
// $1 = 'testAvailabilitySet'

// Insert snippet here
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource availabilitySet 'Microsoft.Compute/availabilitySets@2019-07-01' = {
name: 'testAvailabilitySet'
location: resourceGroup().location
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// $1 = testContainerGroup
// $2 = testContainerName
// $3 = testContainerImage
// $1 = 'testContainerGroup'
// $2 = 'testContainerName'
// $3 = 'testContainerImage'
// $4 = 80
// $5 = 1
// $6 = 4
// $7 = Linux
// $8 = TCP
// $7 = 'Linux'
// $8 = 'TCP'
// $9 = 80

// Insert snippet here
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
resource containerGroup 'Microsoft.ContainerInstance/containerGroups@2019-12-01' = {
name: 'testContainerGroup'
location: resourceGroup().location
properties: {
containers: [
{
name: 'testContainerName'
properties: {
image: 'testContainerImage'
ports: [
{
port: 80
}
]
resources: {
requests: {
cpu: 1
memoryInGB: 4
}
}
}
}
]
osType: 'Linux'
ipAddress: {
type: 'Public'
ports: [
{
protocol: 'TCP'
port: 80
}
]
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// $1 = testContainerRegistry
// $2 = Classic
// $1 = 'testContainerRegistry'
// $2 = 'Classic'
// $3 = true

// Insert snippet here
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
resource containerRegistry 'Microsoft.ContainerRegistry/registries@2019-05-01' = {
name: 'testContainerRegistry'
location: resourceGroup().location
sku: {
name: 'Classic'
}
properties: {
adminUserEnabled: true
}
}

This file was deleted.

Loading

0 comments on commit 10a7ae5

Please sign in to comment.