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

New linter rule: use-recent-module-versions #14309

Merged
merged 18 commits into from
Jun 29, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
<PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="21.0.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Bicep.Cli.UnitTests\Bicep.Cli.UnitTests.csproj" />
<ProjectReference Include="..\Bicep.Cli\Bicep.Cli.csproj" />
<ProjectReference Include="..\Bicep.Core.Samples\Bicep.Core.Samples.csproj" />
<ProjectReference Include="..\Bicep.Core.UnitTests\Bicep.Core.UnitTests.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 2 additions & 4 deletions src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,7 @@ public async Task Build_Invalid_SingleFile_ShouldFail_WithExpectedErrorMessage(D
{
var outputDirectory = dataSet.SaveFilesToTestDirectory(TestContext);
var bicepFilePath = Path.Combine(outputDirectory, DataSet.TestFileMain);
var defaultSettings = CreateDefaultSettings();
var diagnostics = await GetAllDiagnostics(bicepFilePath, defaultSettings.ClientFactory, defaultSettings.TemplateSpecRepositoryFactory);
var diagnostics = await GetAllDiagnostics(bicepFilePath, InvocationSettings.Default.ClientFactory, InvocationSettings.Default.TemplateSpecRepositoryFactory);

var (output, error, result) = await Bicep("build", bicepFilePath);

Expand All @@ -367,8 +366,7 @@ public async Task Build_Invalid_SingleFile_ToStdOut_ShouldFail_WithExpectedError
result.Should().Be(1);
output.Should().BeEmpty();

var defaultSettings = CreateDefaultSettings();
var diagnostics = await GetAllDiagnostics(bicepFilePath, defaultSettings.ClientFactory, defaultSettings.TemplateSpecRepositoryFactory);
var diagnostics = await GetAllDiagnostics(bicepFilePath, InvocationSettings.Default.ClientFactory, InvocationSettings.Default.TemplateSpecRepositoryFactory);
error.Should().ContainAll(diagnostics);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace Bicep.Cli.IntegrationTests
public class BuildParamsCommandTests : TestBase
{
private InvocationSettings Settings
=> CreateDefaultSettings() with
=> new()
{
Environment = TestEnvironment.Create(
("stringEnvVariableName", "test"),
Expand Down
14 changes: 10 additions & 4 deletions src/Bicep.Cli.IntegrationTests/LintCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
using Microsoft.CodeAnalysis.Sarif;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.WindowsAzure.ResourceStack.Common.Json;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Bicep.Core.Registry.PublicRegistry;
using System.Collections.Immutable;
using System.Diagnostics;
using Bicep.Cli.UnitTests;
using FileSystem = System.IO.Abstractions.FileSystem;

namespace Bicep.Cli.IntegrationTests;

Expand Down Expand Up @@ -154,8 +160,7 @@ public async Task Lint_Invalid_SingleFile_ShouldFail_WithExpectedErrorMessage(Da
{
var outputDirectory = dataSet.SaveFilesToTestDirectory(TestContext);
var bicepFilePath = Path.Combine(outputDirectory, DataSet.TestFileMain);
var defaultSettings = CreateDefaultSettings();
var diagnostics = await GetAllDiagnostics(bicepFilePath, defaultSettings.ClientFactory, defaultSettings.TemplateSpecRepositoryFactory);
var diagnostics = await GetAllDiagnostics(bicepFilePath, InvocationSettings.Default.ClientFactory, InvocationSettings.Default.TemplateSpecRepositoryFactory, InvocationSettings.Default.ModuleMetadataClient);

var (output, error, result) = await Bicep("lint", bicepFilePath);

Expand All @@ -173,8 +178,10 @@ public async Task Lint_WithEmptyBicepConfig_ShouldProduceConfigurationError()
string testOutputPath = FileHelper.GetUniqueTestOutputPath(TestContext);
var inputFile = FileHelper.SaveResultFile(TestContext, "main.bicep", DataSets.Empty.Bicep, testOutputPath);
var configurationPath = FileHelper.SaveResultFile(TestContext, "bicepconfig.json", string.Empty, testOutputPath);
var settings = new InvocationSettings() { ModuleMetadataClient = PublicRegistryModuleMetadataClientMock.CreateToThrow(new Exception("unit test failed: shouldn't call this")).Object };

var (output, error, result) = await Bicep(settings, "lint", inputFile);

var (output, error, result) = await Bicep("lint", inputFile);

result.Should().Be(1);
output.Should().BeEmpty();
Expand Down Expand Up @@ -254,7 +261,6 @@ public async Task Lint_with_sarif_diagnostics_format_should_output_valid_sarif()
sarifLog.Runs[0].Results[0].RuleId.Should().Be("no-unused-params");
sarifLog.Runs[0].Results[0].Message.Text.Should().Contain("is declared but never used");
}

private static IEnumerable<object[]> GetValidDataSetsWithoutWarnings() => DataSets
.AllDataSets
.Where(ds => ds.IsValid)
Expand Down
12 changes: 6 additions & 6 deletions src/Bicep.Cli.IntegrationTests/PublishProviderCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public async Task Publish_provider_should_fail_for_malformed_target()
var outputDirectory = FileHelper.GetUniqueTestOutputPath(TestContext);
var indexPath = Path.Combine(outputDirectory, "index.json");

var result = await Bicep(CreateDefaultSettings(), "publish-provider", indexPath, "--target", $"asdf:123");
var result = await Bicep(InvocationSettings.Default, "publish-provider", indexPath, "--target", $"asdf:123");
result.Should().Fail().And.HaveStderrMatch("*The specified module reference scheme \"asdf\" is not recognized.*");
}

Expand All @@ -132,7 +132,7 @@ public async Task Publish_provider_should_fail_for_missing_index_path()
var outputDirectory = FileHelper.GetUniqueTestOutputPath(TestContext);
var indexPath = Path.Combine(outputDirectory, "index.json");

var result = await Bicep(CreateDefaultSettings(), "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
var result = await Bicep(InvocationSettings.Default, "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
result.Should().Fail().And.HaveStderrMatch("*Provider package creation failed: Could not find a part of the path '*'.*");
}

Expand All @@ -142,7 +142,7 @@ public async Task Publish_provider_should_fail_for_malformed_index()
var outputDirectory = FileHelper.GetUniqueTestOutputPath(TestContext);
var indexPath = FileHelper.SaveResultFile(TestContext, "index.json", "malformed", outputDirectory);

var result = await Bicep(CreateDefaultSettings(), "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
var result = await Bicep(InvocationSettings.Default, "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
result.Should().Fail().And.HaveStderrMatch("*Provider package creation failed: 'm' is an invalid start of a value.*");
}

Expand All @@ -161,7 +161,7 @@ public async Task Publish_provider_should_fail_for_missing_referenced_types_json
}
""", outputDirectory);

var result = await Bicep(CreateDefaultSettings(), "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
var result = await Bicep(InvocationSettings.Default, "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
result.Should().Fail().And.HaveStderrMatch("*Provider package creation failed: Could not find file '*types.json'.*");
}

Expand All @@ -181,7 +181,7 @@ public async Task Publish_provider_should_fail_for_malformed_types_json()
""", outputDirectory);
FileHelper.SaveResultFile(TestContext, "v1/types.json", "malformed", outputDirectory);

var result = await Bicep(CreateDefaultSettings(), "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
var result = await Bicep(InvocationSettings.Default, "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
result.Should().Fail().And.HaveStderrMatch("*Provider package creation failed: 'm' is an invalid start of a value.*");
}

Expand Down Expand Up @@ -213,7 +213,7 @@ public async Task Publish_provider_should_fail_for_bad_type_location()
]
""", outputDirectory);

var result = await Bicep(CreateDefaultSettings(), "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
var result = await Bicep(InvocationSettings.Default, "publish-provider", indexPath, "--target", $"br:example.com/test/provider:0.0.1");
result.Should().Fail().And.HaveStderrMatch("*Provider package creation failed: Index was outside the bounds of the array.*");
}
}
4 changes: 2 additions & 2 deletions src/Bicep.Cli.IntegrationTests/RootCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public async Task BicepVersionShouldPrintVersionInformation()
[TestMethod]
public async Task BicepHelpShouldPrintHelp()
{
var settings = CreateDefaultSettings() with { FeatureOverrides = new(RegistryEnabled: true) };
var settings = new InvocationSettings() { FeatureOverrides = new(RegistryEnabled: true) };

var (output, error, result) = await Bicep(settings, "--help");

Expand Down Expand Up @@ -127,7 +127,7 @@ public async Task BicepHelpShouldAlwaysIncludePublish()
{
// disable registry to ensure `bicep --help` is not consulting the feature provider before
// preparing the help text (as features can only be determined when an input file is specified)
var settings = CreateDefaultSettings() with { FeatureOverrides = new(RegistryEnabled: false) };
var settings = new InvocationSettings() { FeatureOverrides = new(RegistryEnabled: false) };

var (output, error, result) = await Bicep(settings, "--help");

Expand Down
100 changes: 69 additions & 31 deletions src/Bicep.Cli.IntegrationTests/TestBase.cs
Original file line number Diff line number Diff line change
@@ -1,67 +1,105 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Immutable;
using Bicep.Cli.UnitTests;
using Bicep.Core;
using Bicep.Core.Extensions;
using Bicep.Core.FileSystem;
using Bicep.Core.Registry;
using Bicep.Core.Registry.PublicRegistry;
using Bicep.Core.Text;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Features;
using Bicep.Core.UnitTests.Mock;
using Bicep.Core.UnitTests.Utils;
using Bicep.Core.Utils;
using FluentAssertions;
using FluentAssertions.Common;
using Microsoft.Extensions.DependencyInjection;
using Moq;

namespace Bicep.Cli.IntegrationTests
{
public abstract class TestBase : Bicep.Core.UnitTests.TestBase
{
private static BicepCompiler CreateCompiler(IContainerRegistryClientFactory clientFactory, ITemplateSpecRepositoryFactory templateSpecRepositoryFactory)
private static BicepCompiler CreateCompiler(IContainerRegistryClientFactory clientFactory, ITemplateSpecRepositoryFactory templateSpecRepositoryFactory, IPublicRegistryModuleMetadataClient? moduleMetadataClient)
=> ServiceBuilder.Create(
x => x.AddSingleton(clientFactory).AddSingleton(templateSpecRepositoryFactory)).GetCompiler();
services =>
{
services
.AddSingleton(clientFactory)
.AddSingleton(templateSpecRepositoryFactory)
.AddSingleton<IPublicRegistryModuleMetadataProvider, PublicRegistryModuleMetadataProvider>();

IServiceCollectionExtensions.AddMockHttpClientIfNotNull(services, moduleMetadataClient);
}
).GetCompiler();

protected const string BuildSummaryFailedRegex = @"Build failed: (\d*) Warning\(s\), ([1-9][0-9]*) Error\(s\)";
protected const string BuildSummarySucceededRegex = @"Build succeeded: (\d*) Warning\(s\), 0 Error\(s\)";

protected static readonly MockRepository Repository = new(MockBehavior.Strict);

protected record InvocationSettings(
FeatureProviderOverrides? FeatureOverrides,
IContainerRegistryClientFactory ClientFactory,
ITemplateSpecRepositoryFactory TemplateSpecRepositoryFactory,
IEnvironment? Environment = null);

protected static Task<CliResult> Bicep(params string[] args) => Bicep(CreateDefaultSettings(), args);
protected record InvocationSettings
{
public static readonly InvocationSettings Default = new();

public FeatureProviderOverrides? FeatureOverrides { get; init; }
public IContainerRegistryClientFactory ClientFactory { get; init; }
public ITemplateSpecRepositoryFactory TemplateSpecRepositoryFactory { get; init; }
public IEnvironment? Environment { get; init; }
public IPublicRegistryModuleMetadataClient ModuleMetadataClient { get; init; }

public InvocationSettings(
FeatureProviderOverrides? FeatureOverrides = null,
IContainerRegistryClientFactory? ClientFactory = null,
ITemplateSpecRepositoryFactory? TemplateSpecRepositoryFactory = null,
IEnvironment? Environment = null,
IPublicRegistryModuleMetadataClient? ModuleMetadataClient = null)
{
this.FeatureOverrides = FeatureOverrides;
this.ClientFactory = ClientFactory ?? Repository.Create<IContainerRegistryClientFactory>().Object;
this.TemplateSpecRepositoryFactory = TemplateSpecRepositoryFactory ?? Repository.Create<ITemplateSpecRepositoryFactory>().Object;
this.Environment = Environment;

protected static InvocationSettings CreateDefaultSettings() => new(
FeatureOverrides: null,
ClientFactory: Repository.Create<IContainerRegistryClientFactory>().Object,
TemplateSpecRepositoryFactory: Repository.Create<ITemplateSpecRepositoryFactory>().Object);
this.ModuleMetadataClient = ModuleMetadataClient ?? StrictMock.Of<IPublicRegistryModuleMetadataClient>().Object;
}
}

protected static Task<CliResult> Bicep(Action<IServiceCollection> registerAction, CancellationToken cancellationToken, params string[] args)
protected static Task<CliResult> Bicep(InvocationSettings settings, Action<IServiceCollection>? registerAction, CancellationToken cancellationToken, params string?[] args /*null args are ignored*/)
=> TextWriterHelper.InvokeWriterAction((@out, err)
=> new Program(new(Output: @out, Error: err), registerAction)
.RunAsync(args, cancellationToken));
=> new Program(
new(Output: @out, Error: err),
services =>
{
if (settings.FeatureOverrides is { })
{
services.WithFeatureOverrides(settings.FeatureOverrides);
}

protected static Task<CliResult> Bicep(Action<IServiceCollection> registerAction, params string[] args)
=> Bicep(registerAction, CancellationToken.None, args);
IServiceCollectionExtensions.AddMockHttpClientIfNotNull(services, settings.ModuleMetadataClient);

protected static Task<CliResult> Bicep(InvocationSettings settings, params string?[] args /*null args are ignored*/)
=> Bicep(services =>
{
if (settings.FeatureOverrides is { })
{
services.WithFeatureOverrides(settings.FeatureOverrides);
services
.AddSingletonIfNotNull(settings.Environment ?? BicepTestConstants.EmptyEnvironment)
.AddSingletonIfNotNull(settings.ClientFactory)
.AddSingletonIfNotNull(settings.TemplateSpecRepositoryFactory);

registerAction?.Invoke(services);
}
)
.RunAsync(args.ToArrayExcludingNull(), cancellationToken));

services
.AddSingleton(settings.Environment ?? BicepTestConstants.EmptyEnvironment)
.AddSingleton(settings.ClientFactory)
.AddSingleton(settings.TemplateSpecRepositoryFactory);
}, CancellationToken.None, args.ToArrayExcludingNull());
protected static Task<CliResult> Bicep(params string[] args) => Bicep(InvocationSettings.Default, args);

protected static Task<CliResult> Bicep(Action<IServiceCollection> registerAction, params string[] args)
=> Bicep(InvocationSettings.Default, registerAction, CancellationToken.None, args);

protected static Task<CliResult> Bicep(Action<IServiceCollection> registerAction, CancellationToken cancellationToken, params string[] args)
=> Bicep(InvocationSettings.Default, registerAction, cancellationToken, args);

protected static Task<CliResult> Bicep(InvocationSettings settings, params string?[] args /*null args are ignored*/)
=> Bicep(settings, null, CancellationToken.None, args);

protected static void AssertNoErrors(string error)
{
Expand All @@ -71,9 +109,9 @@ protected static void AssertNoErrors(string error)
}
}

protected static async Task<IEnumerable<string>> GetAllDiagnostics(string bicepFilePath, IContainerRegistryClientFactory clientFactory, ITemplateSpecRepositoryFactory templateSpecRepositoryFactory)
protected static async Task<IEnumerable<string>> GetAllDiagnostics(string bicepFilePath, IContainerRegistryClientFactory clientFactory, ITemplateSpecRepositoryFactory templateSpecRepositoryFactory, IPublicRegistryModuleMetadataClient? moduleMetadataClient = null)
{
var compilation = await CreateCompiler(clientFactory, templateSpecRepositoryFactory).CreateCompilation(PathHelper.FilePathToFileUrl(bicepFilePath));
var compilation = await CreateCompiler(clientFactory, templateSpecRepositoryFactory, moduleMetadataClient).CreateCompilation(PathHelper.FilePathToFileUrl(bicepFilePath));

var output = new List<string>();
foreach (var (bicepFile, diagnostics) in compilation.GetAllDiagnosticsByBicepFile())
Expand Down
Loading
Loading