diff --git a/docs/uri-cleanup-plan.md b/docs/uri-cleanup-plan.md index 4ed09b9cbc4..1599794561a 100644 --- a/docs/uri-cleanup-plan.md +++ b/docs/uri-cleanup-plan.md @@ -129,12 +129,13 @@ Leave these categories alone unless a PR explicitly targets their owning behavio - `[x]` Initial Core unit-test slices use `TestCompiler`, including params emission and linter/extension scenarios. - `[x]` All linter rule tests use `TestCompiler`, `TestFileData`, and test-owned configuration APIs. - `[x]` `TestConfigurations` and `TestConfigurationBuilder` own shared test configuration presets and mutations. -- `[x]` Public `Test*` toolkit types live in the `Bicep.Testing` root namespace; `FakeEnvironment` lives under `Bicep.Testing.Fakes` and the `Utils` namespace is removed. +- `[x]` Compiler and service toolkit types live in the `Bicep.Testing` root namespace; virtual-file types live under `Bicep.Testing.IO`, `FakeEnvironment` lives under `Bicep.Testing.Fakes`, and the `Utils` namespace is removed. - `[x]` Simple Core unit-test callers use `TestCompiler` directly. - `[x]` No synchronous `CompilationHelper.Compile(...)` call sites remain in `Bicep.Core.UnitTests`. - `[x]` `Bicep.Decompiler.UnitTests` references `Bicep.Testing` instead of `Bicep.Core.UnitTests`. - `[x]` `Bicep.Local.Extension.UnitTests` and `Bicep.RegistryModuleTool.TestFixtures` no longer reference `Bicep.Core.UnitTests`. -- `[x]` Reusable JToken assertions have one canonical implementation in `Bicep.Testing.Assertions.Json`; Core retains only baseline-update behavior. +- `[x]` `Bicep.RpcClient.Tests` references `Bicep.Testing` instead of `Bicep.Core.UnitTests`. +- `[x]` Embedded baseline data, materialization, updates, and fluent text/JSON assertions have one canonical implementation in `Bicep.Testing.Baselines`. - `[ ]` Replace all `CompilationHelper` call sites with `TestCompiler` and delete `CompilationHelper` and its result types. - `[ ]` Replace `ServiceBuilder`, `ServiceBuilderExtensions`, and related Core-unit-test DI extensions with `TestCompiler`, `TestServices`, or focused fixtures, then delete them. - `[ ]` Consolidate reusable assertions, feature fixtures, mocks, and data builders in `Bicep.Testing`; rewrite or remove obsolete helpers instead of moving them wholesale. @@ -239,7 +240,7 @@ Deletion targets include: Do not move the whole assertions directory unchanged. Classify each assertion by its subject and consumers: - Merge duplicate implementations already present in `Bicep.Testing`, such as diagnostic assertion infrastructure. -- Keep focused reusable assertions in `Bicep.Testing.Assertions`; do not move baseline-management or Core-specific assertion APIs with them. +- Keep focused reusable assertions in `Bicep.Testing.Assertions`; keep embedded baseline management and baseline-specific assertions together in `Bicep.Testing.Baselines`. - Use `TestPrinter.Print(...)` only for printing and `BeValidBicepText()` for string syntax validation; keep source annotation rendering separate. - Expand `TestCompilationResultAssertions` to replace legacy compilation-result assertions, including template/parameters emission and diagnostic filtering where still useful. - Move broadly reusable assertions such as diagnostics, syntax, JSON tokens, strings, code fixes, and configuration assertions when non-Core projects consume them. @@ -251,7 +252,7 @@ Do not move the whole assertions directory unchanged. Classify each assertion by Treat each family separately: -- Keep the public `Test*` toolkit types in the `Bicep.Testing` root namespace. Keep `Fake*`, `Mock*`, and `Dummy*` implementations in their corresponding domain namespaces; for example, use `Bicep.Testing.Fakes.FakeEnvironment` rather than `TestEnvironment`. +- Keep compiler and service toolkit types in the `Bicep.Testing` root namespace. Keep virtual-file types under `Bicep.Testing.IO`, and `Fake*`, `Mock*`, and `Dummy*` implementations in their corresponding domain namespaces; for example, use `Bicep.Testing.Fakes.FakeEnvironment` rather than `TestEnvironment`. - Do not recreate a catch-all `Bicep.Testing.Utils` namespace. - Move feature overrides and their provider factory to `Bicep.Testing` if they remain the shared way to configure compiler features. This should allow `TestCompiler.WithFeatureOverrides(...)` to become non-generic. - Delete the Core `StrictMock` duplicate and use `Bicep.Testing.Mocks.StrictMock`. @@ -303,6 +304,7 @@ Completed: - `[x]` `Bicep.Decompiler.UnitTests`: migrated to `TestCompiler`, `TestPrinter`, and focused string/JSON assertions; all 52 tests passed. - `[x]` `Bicep.Local.Extension.UnitTests`: migrated to `Bicep.Testing.Mocks.StrictMock` and focused JSON assertions; all 99 tests passed. - `[x]` `Bicep.RegistryModuleTool.TestFixtures`: replaced default Core feature overrides with a focused assembly-version decorator; all 18 consuming integration tests passed. +- `[x]` `Bicep.RpcClient.Tests`: migrated result-file handling and its public API baseline to focused `Bicep.Testing` APIs; the full 91-test suite completed with 90 passed and 1 skipped. Validation: the full solution builds after all three project-reference removals and assertion consolidation. @@ -316,7 +318,6 @@ Current project-reference cleanup candidates: - `src/Bicep.LangServer.IntegrationTests/Bicep.LangServer.IntegrationTests.csproj` - `src/Bicep.Local.Deploy.IntegrationTests/Bicep.Local.Deploy.IntegrationTests.csproj` - `src/Bicep.McpServer.UnitTests/Bicep.McpServer.UnitTests.csproj` -- `src/Bicep.RpcClient.Tests/Bicep.RpcClient.Tests.csproj` - `src/Bicep.Wasm.UnitTests/Bicep.Wasm.UnitTests.csproj` Likely import cleanup areas: diff --git a/src/Bicep.Cli.IntegrationTests/Bicep.Cli.IntegrationTests.csproj b/src/Bicep.Cli.IntegrationTests/Bicep.Cli.IntegrationTests.csproj index 4f0a9a71858..94777c5dbdd 100644 --- a/src/Bicep.Cli.IntegrationTests/Bicep.Cli.IntegrationTests.csproj +++ b/src/Bicep.Cli.IntegrationTests/Bicep.Cli.IntegrationTests.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs b/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs index 39bd0bd1d2a..bcbbccc0d28 100644 --- a/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/BuildCommandTests.cs @@ -94,7 +94,7 @@ public async Task Build_Valid_SingleFile_WithTemplateSpecReference_ShouldSucceed var actual = JToken.Parse(compiledFileContent); - actual.Should().EqualWithJsonDiffOutput( + actual.Should().MatchJsonBaseline( TestContext, JToken.Parse(dataSet.Compiled!), expectedLocation: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainCompiled), @@ -137,7 +137,7 @@ public async Task Build_Valid_SingleFile_WithTemplateSpecReference_ToStdOut_Shou var actual = JToken.Parse(output); - actual.Should().EqualWithJsonDiffOutput( + actual.Should().MatchJsonBaseline( TestContext, JToken.Parse(dataSet.Compiled!), expectedLocation: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainCompiled), @@ -183,7 +183,7 @@ public async Task Build_Valid_SingleFile_After_Restore_Should_Succeed(DataSet da File.Exists(compiledFilePath).Should().BeTrue(); var actual = JToken.Parse(output); - actual.Should().EqualWithJsonDiffOutput( + actual.Should().MatchJsonBaseline( TestContext, JToken.Parse(dataSet.Compiled!), expectedLocation: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainCompiled), @@ -444,7 +444,7 @@ public async Task Build_WithOutDir_ShouldSucceed(string[] args) ] } """); - errorJToken.Should().EqualWithJsonDiffOutput( + errorJToken.Should().MatchJsonBaseline( TestContext, expectedErrorJToken, "", @@ -650,7 +650,7 @@ public async Task Build_WithValidBicepConfig_ShouldProduceOutputFileAndExpectedE selectedPath?.Value().Should().Contain("file://"); selectedPath?.Value().Should().Contain("main.bicep"); selectedPath?.Replace("main.bicep"); - errorJToken.Should().EqualWithJsonDiffOutput( + errorJToken.Should().MatchJsonBaseline( TestContext, expectedErrorJToken, "", diff --git a/src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs b/src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs index 4963a4085b8..de5b8ead260 100644 --- a/src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/BuildParamsCommandTests.cs @@ -12,8 +12,9 @@ using Bicep.Core.Samples; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Utils; +using Bicep.Testing.IO; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.CodeAnalysis.Sarif; @@ -34,10 +35,8 @@ public class BuildParamsCommandTests : TestBase [TestMethod] public async Task Build_params_with_extends_and_base_merging_succeeds_without_bicepconfig() { - var baseParamsFile = FileHelper.SaveResultFile( - TestContext, - "base.bicepparam", - """ + var files = MockFileSystemTestFileSet.Create( + ("base.bicepparam", """ using none param objParam = { @@ -51,12 +50,8 @@ public async Task Build_params_with_extends_and_base_merging_succeeds_without_bi param strParam = 'strParamFromBase' param intParam = 10 - """); - - var mainParamsFile = FileHelper.SaveResultFile( - TestContext, - "main.bicepparam", - """ + """), + ("main.bicepparam", """ using './main.bicep' extends './base.bicepparam' @@ -72,22 +67,16 @@ public async Task Build_params_with_extends_and_base_merging_succeeds_without_bi param strParam = base.strParam param intParam = base.intParam + 5 - """, - Path.GetDirectoryName(baseParamsFile)); - - FileHelper.SaveResultFile( - TestContext, - "main.bicep", - """ + """), + ("main.bicep", """ param objParam object param strParam string param intParam int - """, - Path.GetDirectoryName(baseParamsFile)); + """)); var settings = CreateDefaultSettings(); - var result = await Bicep(settings, "build-params", mainParamsFile, "--stdout"); + var result = await Bicep(settings, files, "build-params", files.GetUri("main.bicepparam").GetFilePath(), "--stdout"); result.Should().Succeed(); result.Stderr.Should().NotContain("experimental Bicep features"); @@ -1356,7 +1345,7 @@ param objParam object [DataTestMethod] [BaselineData_Bicepparam.TestData(Filter = BaselineData_Bicepparam.TestDataFilterType.ValidOnly)] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task Build_Valid_Params_File_Should_Succeed(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); @@ -1371,12 +1360,12 @@ public async Task Build_Valid_Params_File_Should_Succeed(BaselineData_Bicepparam } data.Compiled.Should().NotBeNull(); - data.Compiled!.ShouldHaveExpectedJsonValue(); + data.Compiled!.Read().Should().MatchJsonBaseline(data.Compiled); } [DataTestMethod] [BaselineData_Bicepparam.TestData(Filter = BaselineData_Bicepparam.TestDataFilterType.ValidOnly)] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task Build_Valid_Params_File_To_Outdir_Should_Succeed(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); @@ -1391,13 +1380,13 @@ public async Task Build_Valid_Params_File_To_Outdir_Should_Succeed(BaselineData_ } data.Compiled.Should().NotBeNull(); - data.Compiled!.ReadFromOutputFolder().Should().OnlyContainLFNewline(); - data.Compiled!.ShouldHaveExpectedJsonValue(); + data.Compiled!.Read().Should().OnlyContainLFNewline(); + data.Compiled.Read().Should().MatchJsonBaseline(data.Compiled); } [DataTestMethod] [BaselineData_Bicepparam.TestData(Filter = BaselineData_Bicepparam.TestDataFilterType.ValidOnly)] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task Build_Valid_Params_File_ToStdOut_Should_Succeed(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); @@ -1415,13 +1404,11 @@ public async Task Build_Valid_Params_File_ToStdOut_Should_Succeed(BaselineData_B parametersStdout.parametersJson.Should().OnlyContainLFNewline(); data.Compiled.Should().NotBeNull(); - data.Compiled!.WriteToOutputFolder(parametersStdout.parametersJson); - data.Compiled.ShouldHaveExpectedJsonValue(); + parametersStdout.parametersJson.Should().MatchJsonBaseline(data.Compiled!); } [DataTestMethod] [BaselineData_Bicepparam.TestData(Filter = BaselineData_Bicepparam.TestDataFilterType.InvalidOnly)] - [TestCategory(BaselineHelper.BaselineTestCategory)] public async Task Build_Invalid_Single_Params_File_ShouldFail_WithExpectedErrorMessage(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); @@ -1447,53 +1434,43 @@ public async Task Build_Invalid_Single_Params_File_ShouldFail_WithExpectedErrorM } [TestMethod] - [EmbeddedFilesTestData(@"Files/BuildParamsCommandTests/.*/main\.bicepparam")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/BuildParamsCommandTests/.*/main\.bicepparam")] + [TestCategory(TestCategories.Baseline)] public async Task Build_params_to_stdout_with_non_bicep_references_should_succeed(EmbeddedFile paramFile) { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); - var outputFile = baselineFolder.GetFileOrEnsureCheckedIn("output.json"); + var baselineFiles = TestContext.MaterializeBaseline(paramFile); + var outputFile = baselineFiles.GetFile("output.json"); - var result = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", baselineFolder.EntryFile.OutputFilePath, "--stdout"); + var result = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", baselineFiles.EntryFile.OutputFilePath, "--stdout"); result.Should().Succeed(); var parametersStdout = result.Stdout.FromJson(); // Force consistency for escaped newlines. parametersStdout = parametersStdout with { templateJson = parametersStdout?.templateJson?.ReplaceLineEndings("\n") }; - outputFile.WriteJsonToOutputFolder(parametersStdout); - outputFile.ShouldHaveExpectedJsonValue(); + JsonConvert.SerializeObject(parametersStdout, Formatting.Indented).Should().MatchJsonBaseline(outputFile); } [TestMethod] - [TestCategory(BaselineHelper.BaselineTestCategory)] public async Task Build_params_to_stdout_with_empty_bicepconfig_should_succeed() { - var mainBicepParamPath = FileHelper.SaveResultFile( - TestContext, - "main.bicepparam", - """ + var files = MockFileSystemTestFileSet.Create( + ("main.bicepparam", """ using 'br:mockregistry.io/parameters/basic:v1' extends 'shared.bicepparam' param intParam = 123 param boolParam = false param arrayParam = [] param objectParam = {} - """); - - _ = FileHelper.SaveResultFile( - TestContext, - "shared.bicepparam", """ + """), + ("shared.bicepparam", """ using none param stringParam = 'foo' - """, - Path.GetDirectoryName(mainBicepParamPath)); - - _ = FileHelper.SaveResultFile( - TestContext, - "bicepconfig.json", "{}", - Path.GetDirectoryName(mainBicepParamPath)); + """), + ("bicepconfig.json", "{}")); + var cacheRoot = files.FileExplorer.GetDirectory(files.GetUri("cache")).EnsureExists(); + var settings = await CreateDefaultSettingsWithDefaultMockRegistry(cacheRoot); - var result = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", mainBicepParamPath, "--stdout"); + var result = await Bicep(settings, files, "build-params", files.GetUri("main.bicepparam").GetFilePath(), "--stdout"); result.Should().Succeed(); result.Stderr.Should().NotContain("experimental Bicep features"); @@ -1504,73 +1481,72 @@ public async Task Build_params_to_stdout_with_empty_bicepconfig_should_succeed() } [TestMethod] - [EmbeddedFilesTestData(@"Files/BuildParamsCommandTests/.*/main\.bicepparam")] - [TestCategory(BaselineHelper.BaselineTestCategory)] - public async Task Build_params_returns_intuitive_error_if_invoked_with_bicep_file_param(EmbeddedFile paramFile) + public async Task Build_params_returns_intuitive_error_if_invoked_with_bicep_file_param() { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); - var bicepFile = Path.Combine(baselineFolder.OutputFolderPath, "main.bicep"); - File.WriteAllText(bicepFile, ""); + var files = MockFileSystemTestFileSet.Create( + ("main.bicepparam", """ + using 'br:mockregistry.io/parameters/basic:v1' - var result = await Bicep(await CreateDefaultSettingsWithDefaultMockRegistry(), "build-params", baselineFolder.EntryFile.OutputFilePath, "--bicep-file", bicepFile, "--stdout"); + param stringParam = 'foo' + param intParam = 123 + param boolParam = false + param objectParam = { abc: 'def' } + param arrayParam = ['abc', 'def'] + """), + ("main.bicep", "")); + var cacheRoot = files.FileExplorer.GetDirectory(files.GetUri("cache")).EnsureExists(); + var settings = await CreateDefaultSettingsWithDefaultMockRegistry(cacheRoot); + + var result = await Bicep(settings, files, "build-params", files.GetUri("main.bicepparam").GetFilePath(), "--bicep-file", files.GetUri("main.bicep").GetFilePath(), "--stdout"); result.Should().Fail().And.HaveStderrMatch($"Bicep file * provided with --bicep-file can only be used if the Bicep parameters \"using\" declaration refers to a Bicep file on disk.*"); } [TestMethod] - [TestCategory(BaselineHelper.BaselineTestCategory)] public async Task Build_params_works_with_using_none() { - var outputPath = FileHelper.GetUniqueTestOutputPath(TestContext); - - var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", @" + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", @" param unusedParam int - ", outputPath); - - var inputFile = FileHelper.SaveResultFile(TestContext, "main.bicepparam", @" + "), + ("main.bicepparam", @" using none param unusedParam = 3 - ", outputPath); + ")); - var (output, error, result) = await Bicep(["build-params", inputFile, "--bicep-file", bicepFile]); + var result = await Bicep(files, "build-params", files.GetUri("main.bicepparam").GetFilePath(), "--bicep-file", files.GetUri("main.bicep").GetFilePath()); - var expectedOutputFile = FileHelper.GetResultFilePath(TestContext, "main.json", outputPath); - - File.Exists(expectedOutputFile).Should().BeTrue(); - output.Should().BeEmpty(); - error.Should().BeEmpty(); - result.Should().Be(0); + result.Should().Succeed().And.NotHaveStdout().And.NotHaveStderr(); + files.FileExplorer.GetFile(files.GetUri("main.json")).Exists().Should().BeTrue(); } [TestMethod] - [EmbeddedFilesTestData(@"Files/BuildParamsCommandTests/Registry/main\.bicepparam")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/BuildParamsCommandTests/Registry/main\.bicepparam")] + [TestCategory(TestCategories.Baseline)] public async Task Build_params_to_stdout_with_registry_should_succeed_after_restore(EmbeddedFile paramFile) { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); - var outputFile = baselineFolder.GetFileOrEnsureCheckedIn("output.json"); + var baselineFiles = TestContext.MaterializeBaseline(paramFile); + var outputFile = baselineFiles.GetFile("output.json"); var settings = await CreateDefaultSettingsWithDefaultMockRegistry(); - var result = await Bicep(settings, "restore", baselineFolder.EntryFile.OutputFilePath); + var result = await Bicep(settings, "restore", baselineFiles.EntryFile.OutputFilePath); result.Should().Succeed().And.NotHaveStdout().And.NotHaveStderr(); - result = await Bicep(settings, "build-params", baselineFolder.EntryFile.OutputFilePath, "--no-restore", "--stdout"); + result = await Bicep(settings, "build-params", baselineFiles.EntryFile.OutputFilePath, "--no-restore", "--stdout"); result.Should().Succeed().And.NotHaveStderr(); var parametersStdout = result.Stdout.FromJson(); // Force consistency for escaped newlines. parametersStdout = parametersStdout with { templateJson = parametersStdout?.templateJson?.ReplaceLineEndings("\n") }; - outputFile.WriteJsonToOutputFolder(parametersStdout); - outputFile.ShouldHaveExpectedJsonValue(); + JsonConvert.SerializeObject(parametersStdout, Formatting.Indented).Should().MatchJsonBaseline(outputFile); } [TestMethod] - [EmbeddedFilesTestData(@"Files/BuildParamsCommandTests/Registry/main\.bicepparam")] - [TestCategory(BaselineHelper.BaselineTestCategory)] - public async Task Build_bicepparam_should_fail_with_error_diagnostics_for_registry_failure(EmbeddedFile paramFile) + public async Task Build_bicepparam_should_fail_with_error_diagnostics_for_registry_failure() { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); + var files = MockFileSystemTestFileSet.Create(("main.bicepparam", "using 'br:mockregistry.io/parameters/basic:v1'")); + var cacheRoot = files.FileExplorer.GetDirectory(files.GetUri("cache")).EnsureExists(); var client = StrictMock.Of(); client @@ -1584,8 +1560,8 @@ public async Task Build_bicepparam_should_fail_with_error_diagnostics_for_regist var templateSpecRepositoryFactory = StrictMock.Of(); - var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clientFactory.Object, templateSpecRepositoryFactory.Object); - var result = await Bicep(settings, "build-params", baselineFolder.EntryFile.OutputFilePath, "--stdout"); + var settings = new InvocationSettings(new(CacheRootDirectory: cacheRoot, RegistryEnabled: true), clientFactory.Object, templateSpecRepositoryFactory.Object); + var result = await Bicep(settings, files, "build-params", files.GetUri("main.bicepparam").GetFilePath(), "--stdout"); result.Should().Fail().And.NotHaveStdout(); result.Stderr.Should().Contain("main.bicepparam(1,7) : Error BCP192: Unable to restore the artifact with reference \"br:mockregistry.io/parameters/basic:v1\": Mock registry request failure."); @@ -1731,18 +1707,16 @@ param intParam int [TestMethod] public async Task Build_params_with_multiple_object_spreads_succeeds() { - var rootDir = FileHelper.GetUniqueTestOutputPath(TestContext); - - var basePath = FileHelper.SaveResultFile(TestContext, "base.bicepparam", """ + var files = MockFileSystemTestFileSet.Create( + ("base.bicepparam", """ using none param obj = { a: 1 arr: [1,2] } - """, rootDir); - - var mainPath = FileHelper.SaveResultFile(TestContext, "main.bicepparam", """ + """), + ("main.bicepparam", """ using './main.bicep' extends './base.bicepparam' @@ -1753,13 +1727,12 @@ public async Task Build_params_with_multiple_object_spreads_succeeds() ...base.obj post: 'post' } - """, rootDir); - - FileHelper.SaveResultFile(TestContext, "main.bicep", """ + """), + ("main.bicep", """ param obj object - """, rootDir); + """)); - var result = await Bicep(CreateDefaultSettings(), "build-params", mainPath, "--stdout"); + var result = await Bicep(CreateDefaultSettings(), files, "build-params", files.GetUri("main.bicepparam").GetFilePath(), "--stdout"); result.Should().Succeed(); var json = result.Stdout.FromJson().parametersJson.FromJson(); json.Should().HaveValueAtPath("parameters.obj.value.a", 1); @@ -1772,30 +1745,28 @@ param obj object [TestMethod] public async Task Build_params_with_array_spread_positions_succeeds() { - var rootDir = FileHelper.GetUniqueTestOutputPath(TestContext); - var basePath = FileHelper.SaveResultFile(TestContext, "base.bicepparam", """ + var files = MockFileSystemTestFileSet.Create( + ("base.bicepparam", """ using none param arr = [1,2,3] - """, rootDir); - - var mainPath = FileHelper.SaveResultFile(TestContext, "main.bicepparam", """ + """), + ("main.bicepparam", """ using './main.bicep' extends './base.bicepparam' param arrStart = [0, ...base.arr] param arrMiddle = [0, ...base.arr, 4] param arrEnd = [...base.arr, 4] - """, rootDir); - - FileHelper.SaveResultFile(TestContext, "main.bicep", """ + """), + ("main.bicep", """ param arrStart array param arrMiddle array param arrEnd array param arr array - """, rootDir); + """)); - var result = await Bicep(CreateDefaultSettings(), "build-params", mainPath, "--stdout"); + var result = await Bicep(CreateDefaultSettings(), files, "build-params", files.GetUri("main.bicepparam").GetFilePath(), "--stdout"); result.Should().Succeed(); var json = result.Stdout.FromJson().parametersJson.FromJson(); json.Should().HaveValueAtPath("parameters.arrStart.value", JToken.Parse("[0,1,2,3]")); @@ -1806,27 +1777,25 @@ param arr array [TestMethod] public async Task Build_params_child_variable_referencing_base_param_succeeds() { - var rootDir = FileHelper.GetUniqueTestOutputPath(TestContext); - var basePath = FileHelper.SaveResultFile(TestContext, "base.bicepparam", """ + var files = MockFileSystemTestFileSet.Create( + ("base.bicepparam", """ using none param greeting = 'hello' - """, rootDir); - - var mainPath = FileHelper.SaveResultFile(TestContext, "main.bicepparam", """ + """), + ("main.bicepparam", """ using './main.bicep' extends './base.bicepparam' var full = '${base.greeting}-world' param final = full - """, rootDir); - - FileHelper.SaveResultFile(TestContext, "main.bicep", """ + """), + ("main.bicep", """ param greeting string param final string - """, rootDir); + """)); - var result = await Bicep(CreateDefaultSettings(), "build-params", mainPath, "--stdout"); + var result = await Bicep(CreateDefaultSettings(), files, "build-params", files.GetUri("main.bicepparam").GetFilePath(), "--stdout"); result.Should().Succeed(); var json = result.Stdout.FromJson().parametersJson.FromJson(); json.Should().HaveValueAtPath("parameters.final.value", "hello-world"); @@ -1835,26 +1804,24 @@ param final string [TestMethod] public async Task Build_params_spread_non_object_should_fail() { - var rootDir = FileHelper.GetUniqueTestOutputPath(TestContext); - FileHelper.SaveResultFile(TestContext, "base.bicepparam", """ + var files = MockFileSystemTestFileSet.Create( + ("base.bicepparam", """ using none param strParam = 'text' - """, rootDir); - - var childPath = FileHelper.SaveResultFile(TestContext, "child.bicepparam", """ + """), + ("child.bicepparam", """ using './main.bicep' extends './base.bicepparam' param objParam = { ...base.strParam } - """, rootDir); - - FileHelper.SaveResultFile(TestContext, "main.bicep", """ + """), + ("main.bicep", """ param strParam string param objParam object - """, rootDir); + """)); - var result = await Bicep(CreateDefaultSettings(), "build-params", childPath, "--stdout"); + var result = await Bicep(CreateDefaultSettings(), files, "build-params", files.GetUri("child.bicepparam").GetFilePath(), "--stdout"); result.Should().Fail(); result.Stderr.Should().Contain("Error BCP402: The spread operator \"...\" can only be used in this context for an expression assignable to type \"object\"."); } @@ -1862,19 +1829,18 @@ param objParam object [TestMethod] public async Task Build_params_self_extends_should_fail() { - var rootDir = FileHelper.GetUniqueTestOutputPath(TestContext); - var path = FileHelper.SaveResultFile(TestContext, "self.bicepparam", """ + var files = MockFileSystemTestFileSet.Create( + ("self.bicepparam", """ using './main.bicep' extends './self.bicepparam' param p = 1 - """, rootDir); - - FileHelper.SaveResultFile(TestContext, "main.bicep", """ + """), + ("main.bicep", """ param p int - """, rootDir); + """)); - var result = await Bicep(CreateDefaultSettings(), "build-params", path, "--stdout"); + var result = await Bicep(CreateDefaultSettings(), files, "build-params", files.GetUri("self.bicepparam").GetFilePath(), "--stdout"); result.Should().Fail(); result.Stderr.Should().Contain("Error BCP278: This parameters file references itself, which is not allowed."); } @@ -1950,29 +1916,28 @@ param tag string [TestMethod] public async Task BuildParams_Extends_Multiple_InvalidType_ThrowsMultipleErrors() { - var outputPath = FileHelper.GetUniqueTestOutputPath(TestContext); - FileHelper.SaveResultFile(TestContext, "main.bicep", @" - param myString string - param myInt int - param myBool bool - ", outputPath); - FileHelper.SaveResultFile(TestContext, "base.bicepparam", @" - using none - param myInt = '42' - param myString = {} - param myBool = [] - ", outputPath); - var inputFile = FileHelper.SaveResultFile(TestContext, "main.bicepparam", @" - using './main.bicep' - extends 'base.bicepparam' - ", outputPath); + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", """ + param myString string + param myInt int + param myBool bool + """), + ("base.bicepparam", """ + using none + param myInt = '42' + param myString = {} + param myBool = [] + """), + ("main.bicepparam", """ + using './main.bicep' + extends 'base.bicepparam' + """)); - var expectedOutputFile = FileHelper.GetResultFilePath(TestContext, "main.json", outputPath); - File.Exists(expectedOutputFile).Should().BeFalse(); + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeFalse(); - var (output, error, result) = await Bicep(["build-params", inputFile]); + var (output, error, result) = await Bicep(files, "build-params", files.GetUri("main.bicepparam").GetFilePath()); - File.Exists(expectedOutputFile).Should().BeFalse(); + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeFalse(); output.Should().BeEmpty(); error.Should().Contain("Error BCP033: Expected a value of type \"int\" but the provided value is of type \"'42'\"."); @@ -1984,41 +1949,38 @@ param myBool bool [TestMethod] public async Task BuildParams_ResourceInputType_WithValidObject_Succeeds() { - var outputPath = FileHelper.GetUniqueTestOutputPath(TestContext); - FileHelper.SaveResultFile(TestContext, "main.bicep", """ - @description('Parameter with resourceInput type') - param storageConfig resourceInput<'Microsoft.Storage/storageAccounts@2022-09-01'>.properties.encryption - - output test string = 'success' - """, outputPath); - - var inputFile = FileHelper.SaveResultFile(TestContext, "main.bicepparam", """ - using './main.bicep' - - param storageConfig = { - services: { - blob: { - enabled: true - } - file: { - enabled: true - } - } - keySource: 'Microsoft.Storage' - } - """, outputPath); - - var expectedOutputFile = FileHelper.GetResultFilePath(TestContext, "main.json", outputPath); - File.Exists(expectedOutputFile).Should().BeFalse(); - - var (output, error, result) = await Bicep(["build-params", inputFile]); + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", """ + @description('Parameter with resourceInput type') + param storageConfig resourceInput<'Microsoft.Storage/storageAccounts@2022-09-01'>.properties.encryption + + output test string = 'success' + """), + ("main.bicepparam", """ + using './main.bicep' + + param storageConfig = { + services: { + blob: { + enabled: true + } + file: { + enabled: true + } + } + keySource: 'Microsoft.Storage' + } + """)); + + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeFalse(); + + var (output, error, result) = await Bicep(files, "build-params", files.GetUri("main.bicepparam").GetFilePath()); result.Should().Be(0); error.Should().NotContain("Error"); - File.Exists(expectedOutputFile).Should().BeTrue(); + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeTrue(); - var parametersFile = File.ReadAllText(expectedOutputFile); - var parametersObject = JObject.Parse(parametersFile); + var parametersObject = JObject.Parse(files.GetFileText("main.json")); ((JToken)parametersObject).Should().NotBeNull(); var storageConfigValue = parametersObject["parameters"]?["storageConfig"]?["value"]; storageConfigValue.Should().NotBeNull(); @@ -2027,75 +1989,70 @@ param storageConfig resourceInput<'Microsoft.Storage/storageAccounts@2022-09-01' [TestMethod] public async Task BuildParams_ResourceInputType_NestedProperty_Succeeds() { - var outputPath = FileHelper.GetUniqueTestOutputPath(TestContext); - FileHelper.SaveResultFile(TestContext, "main.bicep", """ - @description('Parameter with nested resourceInput type') - param encryptionServices resourceInput<'Microsoft.Storage/storageAccounts@2022-09-01'>.properties.encryption.services - - output test string = 'success' - """, outputPath); - - var inputFile = FileHelper.SaveResultFile(TestContext, "main.bicepparam", """ - using './main.bicep' - - param encryptionServices = { - blob: { - enabled: true - keyType: 'Account' - } - file: { - enabled: false - } - } - """, outputPath); - - var expectedOutputFile = FileHelper.GetResultFilePath(TestContext, "main.json", outputPath); - File.Exists(expectedOutputFile).Should().BeFalse(); - - var (output, error, result) = await Bicep(["build-params", inputFile]); + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", """ + @description('Parameter with nested resourceInput type') + param encryptionServices resourceInput<'Microsoft.Storage/storageAccounts@2022-09-01'>.properties.encryption.services + + output test string = 'success' + """), + ("main.bicepparam", """ + using './main.bicep' + + param encryptionServices = { + blob: { + enabled: true + keyType: 'Account' + } + file: { + enabled: false + } + } + """)); + + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeFalse(); + + var (output, error, result) = await Bicep(files, "build-params", files.GetUri("main.bicepparam").GetFilePath()); result.Should().Be(0); error.Should().NotContain("Error"); - File.Exists(expectedOutputFile).Should().BeTrue(); + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeTrue(); } [TestMethod] public async Task BuildParams_ResourceInputType_ArrayOfResources_Succeeds() { - var outputPath = FileHelper.GetUniqueTestOutputPath(TestContext); - FileHelper.SaveResultFile(TestContext, "main.bicep", """ - @description('Parameter with array of resourceInput type') - param subnets resourceInput<'Microsoft.Network/virtualNetworks/subnets@2023-09-01'>.properties[] - - output test string = 'success' - """, outputPath); - - var inputFile = FileHelper.SaveResultFile(TestContext, "main.bicepparam", """ - using './main.bicep' - - param subnets = [ - { - addressPrefix: '10.0.1.0/24' - privateEndpointNetworkPolicies: 'Disabled' - } - { - addressPrefix: '10.0.2.0/24' - delegations: [] - } - ] - """, outputPath); - - var expectedOutputFile = FileHelper.GetResultFilePath(TestContext, "main.json", outputPath); - File.Exists(expectedOutputFile).Should().BeFalse(); - - var (output, error, result) = await Bicep(["build-params", inputFile]); + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", """ + @description('Parameter with array of resourceInput type') + param subnets resourceInput<'Microsoft.Network/virtualNetworks/subnets@2023-09-01'>.properties[] + + output test string = 'success' + """), + ("main.bicepparam", """ + using './main.bicep' + + param subnets = [ + { + addressPrefix: '10.0.1.0/24' + privateEndpointNetworkPolicies: 'Disabled' + } + { + addressPrefix: '10.0.2.0/24' + delegations: [] + } + ] + """)); + + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeFalse(); + + var (output, error, result) = await Bicep(files, "build-params", files.GetUri("main.bicepparam").GetFilePath()); result.Should().Be(0); error.Should().NotContain("Error"); - File.Exists(expectedOutputFile).Should().BeTrue(); + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeTrue(); - var parametersFile = File.ReadAllText(expectedOutputFile); - var parametersObject = JObject.Parse(parametersFile); + var parametersObject = JObject.Parse(files.GetFileText("main.json")); var subnetsArray = parametersObject["parameters"]?["subnets"]?["value"] as JArray; subnetsArray.Should().NotBeNull(); subnetsArray!.Count.Should().Be(2); @@ -2104,43 +2061,40 @@ param subnets resourceInput<'Microsoft.Network/virtualNetworks/subnets@2023-09-0 [TestMethod] public async Task BuildParams_ResourceInputType_ComplexNestedObject_Succeeds() { - var outputPath = FileHelper.GetUniqueTestOutputPath(TestContext); - FileHelper.SaveResultFile(TestContext, "main.bicep", """ - @description('Parameter with complex resourceInput type') - param organizationProfile resourceInput<'Microsoft.DevOpsInfrastructure/pools@2024-10-19'>.properties.organizationProfile - - output test string = 'success' - """, outputPath); - - var inputFile = FileHelper.SaveResultFile(TestContext, "main.bicepparam", """ - using './main.bicep' - - param organizationProfile = { - kind: 'AzureDevOps' - organizations: [ - { - url: 'https://dev.azure.com/my-org' - projects: [] - parallelism: 1 - } - ] - permissionProfile: { - kind: 'CreatorOnly' - } - } - """, outputPath); - - var expectedOutputFile = FileHelper.GetResultFilePath(TestContext, "main.json", outputPath); - File.Exists(expectedOutputFile).Should().BeFalse(); - - var (output, error, result) = await Bicep(["build-params", inputFile]); + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", """ + @description('Parameter with complex resourceInput type') + param organizationProfile resourceInput<'Microsoft.DevOpsInfrastructure/pools@2024-10-19'>.properties.organizationProfile + + output test string = 'success' + """), + ("main.bicepparam", """ + using './main.bicep' + + param organizationProfile = { + kind: 'AzureDevOps' + organizations: [ + { + url: 'https://dev.azure.com/my-org' + projects: [] + parallelism: 1 + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + } + """)); + + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeFalse(); + + var (output, error, result) = await Bicep(files, "build-params", files.GetUri("main.bicepparam").GetFilePath()); result.Should().Be(0); error.Should().NotContain("Error"); - File.Exists(expectedOutputFile).Should().BeTrue(); + files.FileSystem.File.Exists(files.GetUri("main.json").GetFilePath()).Should().BeTrue(); - var parametersFile = File.ReadAllText(expectedOutputFile); - var parametersObject = JObject.Parse(parametersFile); + var parametersObject = JObject.Parse(files.GetFileText("main.json")); var kindValue = parametersObject["parameters"]?["organizationProfile"]?["value"]?["kind"]?.ToString(); kindValue.Should().Be("AzureDevOps"); } diff --git a/src/Bicep.Cli.IntegrationTests/Commands/LocalDeployCommandTests.cs b/src/Bicep.Cli.IntegrationTests/Commands/LocalDeployCommandTests.cs index 35b8c58d85b..2d853ccad9c 100644 --- a/src/Bicep.Cli.IntegrationTests/Commands/LocalDeployCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/Commands/LocalDeployCommandTests.cs @@ -17,7 +17,6 @@ using Bicep.Core.Registry.Oci; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; using Bicep.Core.UnitTests.Features; using Bicep.Core.UnitTests.Mock; using Bicep.Core.UnitTests.Utils; @@ -25,6 +24,7 @@ using Bicep.Local.Deploy; using Bicep.Local.Deploy.Azure; using Bicep.Local.Deploy.Extensibility; +using Bicep.Testing.IO; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -38,6 +38,17 @@ namespace Bicep.Cli.IntegrationTests.Commands; [TestClass] public class LocalDeployCommandTests : TestBase { + private async Task<(MockFileSystemTestFileSet Files, InvocationSettings Settings)> CreateTestSettings(EmbeddedFile paramFile) + { + var files = new MockFileSystemTestFileSet().AddEmbeddedFiles(paramFile); + var cacheDirectory = files.FileExplorer.GetDirectory(files.GetUri("cache")).EnsureExists(); + var features = new FeatureProviderOverrides(CacheRootDirectory: cacheDirectory, LocalDeployEnabled: true); + var services = await ExtensionTestHelper.GetServiceBuilderWithPublishedExtension(GetMockLocalDeployPackage(), features, files.FileSystem); + var clientFactory = services.Build().Construct(); + + return (files, new InvocationSettings(ClientFactory: clientFactory, FeatureOverrides: features)); + } + private static ExtensionPackage GetMockLocalDeployPackage(BinaryData? tgzData = null) { tgzData ??= ExtensionResourceTypeHelper.GetHttpExtensionTypesTgz(); @@ -140,18 +151,14 @@ private ILocalExtension GetFailingExtensionMock() public async Task Local_deploy_should_succeed() { var paramFile = new EmbeddedFile(typeof(LocalDeployCommandTests).Assembly, "Files/LocalDeployCommandTests/weather/main.bicepparam"); - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); - - var services = await ExtensionTestHelper.GetServiceBuilderWithPublishedExtension(GetMockLocalDeployPackage(), new(LocalDeployEnabled: true)); - var clientFactory = services.Build().Construct(); - - var cacheDirectory = FileHelper.GetCacheRootDirectory(TestContext).EnsureExists(); + var (files, settings) = await CreateTestSettings(paramFile); var result = await Bicep( - new InvocationSettings(ClientFactory: clientFactory, FeatureOverrides: new(CacheRootDirectory: cacheDirectory)), + settings, + files, services => RegisterExtensionMocks(services, GetExtensionMock()), - TestContext.CancellationTokenSource.Token, - ["local-deploy", baselineFolder.EntryFile.OutputFilePath]); + "local-deploy", + files.GetUri(paramFile.FileName).GetFilePath()); result.Should().NotHaveStderr().And.Succeed(); @@ -189,18 +196,14 @@ public async Task Local_deploy_should_succeed() public async Task Local_deploy_should_report_failures() { var paramFile = new EmbeddedFile(typeof(LocalDeployCommandTests).Assembly, "Files/LocalDeployCommandTests/weather/main.bicepparam"); - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); - - var services = await ExtensionTestHelper.GetServiceBuilderWithPublishedExtension(GetMockLocalDeployPackage(), new(LocalDeployEnabled: true)); - var clientFactory = services.Build().Construct(); - - var cacheDirectory = FileHelper.GetCacheRootDirectory(TestContext).EnsureExists(); + var (files, settings) = await CreateTestSettings(paramFile); var result = await Bicep( - new InvocationSettings(ClientFactory: clientFactory, FeatureOverrides: new(CacheRootDirectory: cacheDirectory)), + settings, + files, services => RegisterExtensionMocks(services, GetFailingExtensionMock()), - TestContext.CancellationTokenSource.Token, - ["local-deploy", baselineFolder.EntryFile.OutputFilePath]); + "local-deploy", + files.GetUri(paramFile.FileName).GetFilePath()); result.Should().NotHaveStderr().And.Fail(); @@ -220,18 +223,16 @@ public async Task Local_deploy_should_report_failures() public async Task Local_deploy_should_succeed_with_json_output() { var paramFile = new EmbeddedFile(typeof(LocalDeployCommandTests).Assembly, "Files/LocalDeployCommandTests/weather/main.bicepparam"); - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); - - var services = await ExtensionTestHelper.GetServiceBuilderWithPublishedExtension(GetMockLocalDeployPackage(), new(LocalDeployEnabled: true)); - var clientFactory = services.Build().Construct(); - - var cacheDirectory = FileHelper.GetCacheRootDirectory(TestContext).EnsureExists(); + var (files, settings) = await CreateTestSettings(paramFile); var result = await Bicep( - new InvocationSettings(ClientFactory: clientFactory, FeatureOverrides: new(CacheRootDirectory: cacheDirectory)), + settings, + files, services => RegisterExtensionMocks(services, GetExtensionMock()), - TestContext.CancellationTokenSource.Token, - ["local-deploy", baselineFolder.EntryFile.OutputFilePath, "--format", "json"]); + "local-deploy", + files.GetUri(paramFile.FileName).GetFilePath(), + "--format", + "json"); result.Should().NotHaveStderr().And.Succeed(); @@ -264,7 +265,7 @@ public async Task Local_deploy_should_succeed_with_json_output() public async Task Local_deploy_with_azure_should_succeed(bool async) { var paramFile = new EmbeddedFile(typeof(LocalDeployCommandTests).Assembly, "Files/LocalDeployCommandTests/azure/main.bicepparam"); - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); + var (files, settings) = await CreateTestSettings(paramFile); var extensionMock = StrictMock.Of(); extensionMock.Setup(x => x.CreateOrUpdate(It.IsAny(), It.IsAny())) @@ -321,16 +322,12 @@ public async Task Local_deploy_with_azure_should_succeed(bool async) [], []); }); - var services = await ExtensionTestHelper.GetServiceBuilderWithPublishedExtension(GetMockLocalDeployPackage(), new(LocalDeployEnabled: true)); - var clientFactory = services.Build().Construct(); - - var cacheDirectory = FileHelper.GetCacheRootDirectory(TestContext).EnsureExists(); - var result = await Bicep( - new InvocationSettings(ClientFactory: clientFactory, FeatureOverrides: new(CacheRootDirectory: cacheDirectory)), + settings, + files, services => RegisterExtensionMocks(services, extensionMock.Object, deploymentProviderMock.Object), - TestContext.CancellationTokenSource.Token, - ["local-deploy", baselineFolder.EntryFile.OutputFilePath]); + "local-deploy", + files.GetUri(paramFile.FileName).GetFilePath()); result.Should().NotHaveStderr().And.Succeed(); @@ -354,18 +351,14 @@ public async Task Local_deploy_with_azure_should_succeed(bool async) public async Task Local_deploy_should_report_nested_operations() { var paramFile = new EmbeddedFile(typeof(LocalDeployCommandTests).Assembly, "Files/LocalDeployCommandTests/weather/nested.bicepparam"); - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); - - var services = await ExtensionTestHelper.GetServiceBuilderWithPublishedExtension(GetMockLocalDeployPackage(), new(LocalDeployEnabled: true)); - var clientFactory = services.Build().Construct(); - - var cacheDirectory = FileHelper.GetCacheRootDirectory(TestContext).EnsureExists(); + var (files, settings) = await CreateTestSettings(paramFile); var result = await Bicep( - new InvocationSettings(ClientFactory: clientFactory, FeatureOverrides: new(CacheRootDirectory: cacheDirectory)), + settings, + files, services => RegisterExtensionMocks(services, GetExtensionMock()), - TestContext.CancellationTokenSource.Token, - ["local-deploy", baselineFolder.EntryFile.OutputFilePath]); + "local-deploy", + files.GetUri(paramFile.FileName).GetFilePath()); result.Should().NotHaveStderr().And.Succeed(); @@ -385,18 +378,14 @@ public async Task Local_deploy_should_report_nested_operations() public async Task Local_deploy_should_report_nested_operation_failures() { var paramFile = new EmbeddedFile(typeof(LocalDeployCommandTests).Assembly, "Files/LocalDeployCommandTests/weather/nested.bicepparam"); - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); - - var services = await ExtensionTestHelper.GetServiceBuilderWithPublishedExtension(GetMockLocalDeployPackage(), new(LocalDeployEnabled: true)); - var clientFactory = services.Build().Construct(); - - var cacheDirectory = FileHelper.GetCacheRootDirectory(TestContext).EnsureExists(); + var (files, settings) = await CreateTestSettings(paramFile); var result = await Bicep( - new InvocationSettings(ClientFactory: clientFactory, FeatureOverrides: new(CacheRootDirectory: cacheDirectory)), + settings, + files, services => RegisterExtensionMocks(services, GetFailingExtensionMock()), - TestContext.CancellationTokenSource.Token, - ["local-deploy", baselineFolder.EntryFile.OutputFilePath]); + "local-deploy", + files.GetUri(paramFile.FileName).GetFilePath()); result.Should().NotHaveStderr().And.Fail(); diff --git a/src/Bicep.Cli.IntegrationTests/FormatCommandTests.cs b/src/Bicep.Cli.IntegrationTests/FormatCommandTests.cs index 41d71d28c30..e0d178d5765 100644 --- a/src/Bicep.Cli.IntegrationTests/FormatCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/FormatCommandTests.cs @@ -127,12 +127,12 @@ public async Task Format_SampleBicepParam_MatchesFormattedSample(BaselineData_Bi { var data = baselineData.GetData(TestContext); - data.Formatted.WriteToOutputFolder(data.Parameters.EmbeddedFile.Contents); + data.Formatted.Write(data.Parameters.EmbeddedFile.Contents); var result = await Bicep("format", data.Formatted.OutputFilePath); AssertSuccess(result); - data.Formatted.ShouldHaveExpectedValue(); + data.Formatted.Read().Should().MatchTextBaseline(data.Formatted); } [DataTestMethod] diff --git a/src/Bicep.Cli.IntegrationTests/GlobalUsings.cs b/src/Bicep.Cli.IntegrationTests/GlobalUsings.cs index f09b85cb868..7bbb4d099a7 100644 --- a/src/Bicep.Cli.IntegrationTests/GlobalUsings.cs +++ b/src/Bicep.Cli.IntegrationTests/GlobalUsings.cs @@ -1,4 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +global using Bicep.Testing; global using Bicep.Testing.Assertions.Json; +global using Bicep.Testing.Baselines; +global using Bicep.Testing.IO; diff --git a/src/Bicep.Cli.IntegrationTests/RestoreCommandTests.cs b/src/Bicep.Cli.IntegrationTests/RestoreCommandTests.cs index 678b61afaa2..610985693be 100644 --- a/src/Bicep.Cli.IntegrationTests/RestoreCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/RestoreCommandTests.cs @@ -13,7 +13,7 @@ using Bicep.Core.Samples; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Features; using Bicep.Core.UnitTests.Mock; using Bicep.Core.UnitTests.Registry; @@ -21,6 +21,7 @@ using Bicep.IO.Abstraction; using Bicep.IO.FileSystem; using Bicep.Testing; +using Bicep.Testing.IO; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -143,19 +144,17 @@ await RegistryHelper.PublishModuleToRegistryAsync( } [TestMethod] - [EmbeddedFilesTestData(@"Files/BuildParamsCommandTests/Registry/main\.bicepparam")] - [TestCategory(BaselineHelper.BaselineTestCategory)] - public async Task Restore_should_succeed_for_bicepparam_file_with_registry_reference(EmbeddedFile paramFile) + public async Task Restore_should_succeed_for_bicepparam_file_with_registry_reference() { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); + var files = MockFileSystemTestFileSet.Create(("main.bicepparam", "using 'br:mockregistry.io/parameters/basic:v1'")); + var cacheRoot = files.FileExplorer.GetDirectory(files.GetUri("cache")).EnsureExists(); + var settings = await CreateDefaultSettingsWithDefaultMockRegistry(cacheRoot); - var settings = await CreateDefaultSettingsWithDefaultMockRegistry(); - - var result = await Bicep(settings, "restore", baselineFolder.EntryFile.OutputFilePath); + var result = await Bicep(settings, files, "restore", files.GetUri("main.bicepparam").GetFilePath()); result.Should().Succeed().And.NotHaveStdout().And.NotHaveStderr(); // ensure something got restored - CachedModules.GetCachedModules(BicepTestConstants.FileSystem, settings.FeatureOverrides!.CacheRootDirectory!).Should().HaveCountGreaterThan(0) + CachedModules.GetCachedModules(files.FileSystem, cacheRoot).Should().HaveCountGreaterThan(0) .And.AllSatisfy(m => m.Should().NotHaveSource()); } @@ -630,11 +629,10 @@ public async Task Restore_RequestFailedException_ShouldFail() } [TestMethod] - [EmbeddedFilesTestData(@"Files/BuildParamsCommandTests/Registry/main\.bicepparam")] - [TestCategory(BaselineHelper.BaselineTestCategory)] - public async Task Restore_bicepparam_should_fail_with_error_diagnostics_for_registry_failure(EmbeddedFile paramFile) + public async Task Restore_bicepparam_should_fail_with_error_diagnostics_for_registry_failure() { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); + var files = MockFileSystemTestFileSet.Create(("main.bicepparam", "using 'br:mockregistry.io/parameters/basic:v1'")); + var cacheRoot = files.FileExplorer.GetDirectory(files.GetUri("cache")).EnsureExists(); var client = StrictMock.Of(); client @@ -648,8 +646,8 @@ public async Task Restore_bicepparam_should_fail_with_error_diagnostics_for_regi var templateSpecRepositoryFactory = StrictMock.Of(); - var settings = new InvocationSettings(new(TestContext, RegistryEnabled: true), clientFactory.Object, templateSpecRepositoryFactory.Object); - var result = await Bicep(settings, "restore", baselineFolder.EntryFile.OutputFilePath); + var settings = new InvocationSettings(new(CacheRootDirectory: cacheRoot, RegistryEnabled: true), clientFactory.Object, templateSpecRepositoryFactory.Object); + var result = await Bicep(settings, files, "restore", files.GetUri("main.bicepparam").GetFilePath()); result.Should().Fail().And.NotHaveStdout(); result.Stderr.Should().Contain("main.bicepparam(1,7) : Error BCP192: Unable to restore the artifact with reference \"br:mockregistry.io/parameters/basic:v1\": Mock registry request failure."); diff --git a/src/Bicep.Cli.IntegrationTests/SnapshotCommandTests.cs b/src/Bicep.Cli.IntegrationTests/SnapshotCommandTests.cs index 74b4920e767..a1fc6bc1104 100644 --- a/src/Bicep.Cli.IntegrationTests/SnapshotCommandTests.cs +++ b/src/Bicep.Cli.IntegrationTests/SnapshotCommandTests.cs @@ -15,7 +15,7 @@ using Bicep.Core.Samples; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Mock; using Bicep.Core.UnitTests.Utils; using Bicep.IO.FileSystem; @@ -123,8 +123,8 @@ public async Task Snapshot_with_overwrite_should_generate_a_valid_snapshot_file( } [TestMethod] - [EmbeddedFilesTestData(@"Files/SnapshotCommandTests/.*/main\.bicepparam")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/SnapshotCommandTests/.*/main\.bicepparam")] + [TestCategory(TestCategories.Baseline)] public async Task Snapshot_generates_correct_format(EmbeddedFile paramFile) { var services = await ExtensionTestHelper.GetServiceBuilderWithPublishedExtension(ExtensionResourceTypeHelper.GetHttpExtensionTypesTgz(), new(), artifactTarget: "example.azurecr.io/extensions/snapshot:1.2.3"); @@ -133,15 +133,15 @@ public async Task Snapshot_generates_correct_format(EmbeddedFile paramFile) var subscriptionId = new Guid().ToString(); var resourceGroupName = "myRg"; - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, paramFile); - var snapshotFile = baselineFolder.GetFileOrEnsureCheckedIn("main.snapshot.json"); + var baselineFiles = TestContext.MaterializeBaseline(paramFile); + var snapshotFile = baselineFiles.GetFile("main.snapshot.json"); var result = await Bicep( services => services.WithContainerRegistryClientFactory(clientFactory), - "snapshot", baselineFolder.EntryFile.OutputFilePath, "--mode", "overwrite", "--subscription-id", subscriptionId, "--resource-group", resourceGroupName); + "snapshot", baselineFiles.EntryFile.OutputFilePath, "--mode", "overwrite", "--subscription-id", subscriptionId, "--resource-group", resourceGroupName); result.Should().Succeed(); - snapshotFile.ShouldHaveExpectedJsonValue(); + snapshotFile.Read().Should().MatchJsonBaseline(snapshotFile); } [TestMethod] @@ -251,7 +251,7 @@ public async Task Snapshot_speculatively_evaluates_references() "main.bicep", """ module mod 'mod.bicep' = {} - + module mod2 'mod2.bicep' = { params: { vnetName: mod.outputs.static @@ -271,7 +271,7 @@ public async Task Snapshot_speculatively_evaluates_references() "mod2.bicep", """ param vnetName string - + resource vnet 'Microsoft.Network/virtualNetworks@2024-07-01' = { name: vnetName } diff --git a/src/Bicep.Cli.IntegrationTests/TestBase.cs b/src/Bicep.Cli.IntegrationTests/TestBase.cs index 4bf093fa0ce..6478b00b733 100644 --- a/src/Bicep.Cli.IntegrationTests/TestBase.cs +++ b/src/Bicep.Cli.IntegrationTests/TestBase.cs @@ -15,7 +15,9 @@ using Bicep.Core.UnitTests.Mock; using Bicep.Core.UnitTests.Utils; using Bicep.Core.Utils; +using Bicep.IO.Abstraction; using Bicep.Testing; +using Bicep.Testing.IO; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -83,6 +85,25 @@ this with protected static Task Bicep(InvocationSettings settings, Action? registerAction, CancellationToken cancellationToken, params string?[] args /*null args are ignored*/) => BicepInternal(settings, registerAction, null, cancellationToken, args); + protected Task Bicep(InvocationSettings settings, MockFileSystemTestFileSet files, params string[] args) + => Bicep(settings, files, _ => { }, args); + + protected Task Bicep(InvocationSettings settings, MockFileSystemTestFileSet files, Action registerAction, params string[] args) + => Bicep( + settings, + services => + { + services + .WithFileSystem(files.FileSystem) + .WithFileExplorer(files.FileExplorer); + registerAction(services); + }, + TestContext.CancellationTokenSource.Token, + args); + + protected Task Bicep(MockFileSystemTestFileSet files, params string[] args) + => Bicep(InvocationSettings.Default, files, args); + protected static Task Bicep(params string[] args) => Bicep(InvocationSettings.Default, args); protected static Task Bicep(Action registerAction, params string[] args) @@ -146,6 +167,14 @@ protected static async Task> GetAllParamDiagnostics(ServiceB protected async Task CreateDefaultSettingsWithDefaultMockRegistry() => CreateDefaultSettings().WithArtifactManager(await CreateDefaultExternalArtifactManager(), TestContext); + protected async Task CreateDefaultSettingsWithDefaultMockRegistry(IDirectoryHandle cacheRootDirectory) + { + var featureOverrides = new FeatureProviderOverrides(CacheRootDirectory: cacheRootDirectory); + + return new InvocationSettings(FeatureOverrides: featureOverrides) + .WithArtifactManager(await MockRegistry.CreateDefaultExternalArtifactManager(featureOverrides), TestContext); + } + protected InvocationSettings CreateDefaultSettings(Func? featureOverrides = null) => new() { diff --git a/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs b/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs index 88d03aac984..c01e6bd2243 100644 --- a/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs +++ b/src/Bicep.Cli.IntegrationTests/UseRecentModuleVersionsIntegrationTests.cs @@ -19,7 +19,7 @@ using Bicep.Core.Samples; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Mock.Registry; using Bicep.Core.UnitTests.Mock.Registry.Catalog; using Bicep.Core.UnitTests.Registry; diff --git a/src/Bicep.Core.IntegrationTests/Emit/TemplateEmitterTests.cs b/src/Bicep.Core.IntegrationTests/Emit/TemplateEmitterTests.cs index 99e59b404de..56f4d27e9f5 100644 --- a/src/Bicep.Core.IntegrationTests/Emit/TemplateEmitterTests.cs +++ b/src/Bicep.Core.IntegrationTests/Emit/TemplateEmitterTests.cs @@ -11,9 +11,10 @@ using Bicep.Core.Semantics; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Features; using Bicep.Core.UnitTests.Utils; +using Bicep.IO.Abstraction; using Bicep.Testing; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -72,12 +73,12 @@ private async Task GetCompilation(BaselineData_Bicepparam baseline, .Build() .GetCompiler(); - return await compiler.CreateCompilation(baseline.GetData(TestContext).Parameters.OutputFileUri.ToIOUri()); + return await compiler.CreateCompilation(IOUri.FromFilePath(baseline.GetData(TestContext).Parameters.OutputFilePath)); } [DataTestMethod] [DynamicData(nameof(GetValidDataSets), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task ValidBicep_TemplateEmiterShouldProduceExpectedTemplate(DataSet dataSet) { var compiledFilePath = FileHelper.GetResultFilePath(this.TestContext, Path.Combine(dataSet.Name, DataSet.TestFileMainCompiled)); @@ -91,7 +92,7 @@ public async Task ValidBicep_TemplateEmiterShouldProduceExpectedTemplate(DataSet var outputFile = File.ReadAllText(compiledFilePath); var actual = JToken.Parse(outputFile); - actual.Should().EqualWithJsonDiffOutput( + actual.Should().MatchJsonBaseline( TestContext, JToken.Parse(dataSet.Compiled!), expectedLocation: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainCompiled), @@ -103,7 +104,7 @@ public async Task ValidBicep_TemplateEmiterShouldProduceExpectedTemplate(DataSet [DataTestMethod] [DynamicData(nameof(GetValidDataSets), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task ValidBicep_EmitTemplate_should_produce_expected_symbolicname_template(DataSet dataSet) { var compiledFilePath = FileHelper.GetResultFilePath(this.TestContext, Path.Combine(dataSet.Name, DataSet.TestFileMainCompiledWithSymbolicNames)); @@ -117,7 +118,7 @@ public async Task ValidBicep_EmitTemplate_should_produce_expected_symbolicname_t var outputFile = File.ReadAllText(compiledFilePath); var actual = JToken.Parse(outputFile); - actual.Should().EqualWithJsonDiffOutput( + actual.Should().MatchJsonBaseline( TestContext, JToken.Parse(dataSet.CompiledWithSymbolicNames!), expectedLocation: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainCompiledWithSymbolicNames), @@ -128,13 +129,13 @@ public async Task ValidBicep_EmitTemplate_should_produce_expected_symbolicname_t } [DataTestMethod] - [EmbeddedFilesTestData(@"Files/SourceMapping/.*/main.bicep")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/SourceMapping/.*/main.bicep")] + [TestCategory(TestCategories.Baseline)] public async Task Source_map_generation_should_work(EmbeddedFile file) { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, file); - var bicepFile = baselineFolder.EntryFile; - var sourceMapFile = baselineFolder.GetFileOrEnsureCheckedIn("sourcemap.json"); + var baselineFiles = TestContext.MaterializeBaseline(file); + var bicepFile = baselineFiles.EntryFile; + var sourceMapFile = baselineFiles.GetFile("sourcemap.json"); var features = new FeatureProviderOverrides(TestContext, SourceMappingEnabled: true); var compiler = ServiceBuilder.Create(s => s.WithFeatureOverrides(features)).GetCompiler(); @@ -150,13 +151,12 @@ public async Task Source_map_generation_should_work(EmbeddedFile file) // Here we simply verify that the format of the baseline file looks correct. var sourceMapJson = JToken.FromObject(emitResult.SourceMap!); - sourceMapFile.WriteToOutputFolder(sourceMapJson.ToString()); - sourceMapFile.ShouldHaveExpectedJsonValue(); + sourceMapJson.Should().MatchJsonBaseline(sourceMapFile); } [DataTestMethod] [DynamicData(nameof(GetValidDataSets), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task SourceMap_maps_json_to_bicep_lines(DataSet dataSet) { var features = new FeatureProviderOverrides(TestContext, SourceMappingEnabled: true); @@ -178,7 +178,7 @@ public async Task SourceMap_maps_json_to_bicep_lines(DataSet dataSet) File.WriteAllText(sourceTextWithSourceMapFileName, sourceTextWithSourceMap.ToString()); // Here we validate visually that the in-memory source map can be used to map JSON -> Bicep lines - sourceTextWithSourceMap.Should().EqualWithLineByLineDiffOutput( + sourceTextWithSourceMap.Should().MatchTextBaseline( TestContext, dataSet.SourceMap!, expectedPath: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainSourceMap), @@ -205,7 +205,7 @@ public void TemplateEmitter_output_should_not_include_UTF8_BOM() [DataTestMethod] [DynamicData(nameof(GetValidDataSets), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task ValidBicepTextWriter_TemplateEmiterShouldProduceExpectedTemplate(DataSet dataSet) { var compilation = await GetCompilation(dataSet, new(TestContext)); @@ -220,7 +220,7 @@ public async Task ValidBicepTextWriter_TemplateEmiterShouldProduceExpectedTempla var actual = JToken.ReadFrom(new JsonTextReader(new StreamReader(new MemoryStream(memoryStream.ToArray())))); var compiledFilePath = FileHelper.SaveResultFile(this.TestContext, Path.Combine(dataSet.Name, DataSet.TestFileMainCompiled), actual.ToString(Formatting.Indented)); - actual.Should().EqualWithJsonDiffOutput( + actual.Should().MatchJsonBaseline( TestContext, JToken.Parse(dataSet.Compiled!), expectedLocation: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainCompiled), @@ -242,7 +242,7 @@ public async Task InvalidBicep_TemplateEmiterShouldNotProduceAnyTemplate(DataSet [DataTestMethod] [BaselineData_Bicepparam.TestData(Filter = BaselineData_Bicepparam.TestDataFilterType.ValidOnly)] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task Valid_bicepparam_TemplateEmiter_should_produce_expected_template(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); @@ -255,12 +255,12 @@ public async Task Valid_bicepparam_TemplateEmiter_should_produce_expected_templa result.Diagnostics.Should().NotHaveErrors(); result.Status.Should().Be(EmitStatus.Succeeded); - data.Compiled.ShouldHaveExpectedJsonValue(); + data.Compiled.Read().Should().MatchJsonBaseline(data.Compiled); } [DataTestMethod] [BaselineData_Bicepparam.TestData(Filter = BaselineData_Bicepparam.TestDataFilterType.InvalidOnly)] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task Invalid_bicepparam_TemplateEmiter_should_not_produce_a_template(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); diff --git a/src/Bicep.Core.IntegrationTests/ExamplesTests.cs b/src/Bicep.Core.IntegrationTests/ExamplesTests.cs index 49822d71d35..9c9c6b36eab 100644 --- a/src/Bicep.Core.IntegrationTests/ExamplesTests.cs +++ b/src/Bicep.Core.IntegrationTests/ExamplesTests.cs @@ -8,9 +8,10 @@ using Bicep.Core.PrettyPrintV2; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Features; using Bicep.Core.UnitTests.Utils; +using Bicep.IO.Abstraction; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -29,12 +30,12 @@ public static async Task RunExampleTest(TestContext testContext, EmbeddedFile em { features ??= new(testContext); FileHelper.GetCacheRootDirectory(testContext).EnsureExists(); - var baselineFolder = BaselineFolder.BuildOutputFolder(testContext, embeddedBicep); - var bicepFile = baselineFolder.EntryFile; - var jsonFile = baselineFolder.GetFileOrEnsureCheckedIn(Path.ChangeExtension(embeddedBicep.FileName, jsonFileExtension)); + var baselineFiles = testContext.MaterializeBaseline(embeddedBicep); + var bicepFile = baselineFiles.EntryFile; + var jsonFile = baselineFiles.GetFile(Path.ChangeExtension(embeddedBicep.FileName, jsonFileExtension)); var compiler = Services.WithFeatureOverrides(features).Build().GetCompiler(); - var compilation = await compiler.CreateCompilation(bicepFile.OutputFileUri.ToIOUri()); + var compilation = await compiler.CreateCompilation(IOUri.FromFilePath(bicepFile.OutputFilePath)); var model = compilation.GetEntrypointSemanticModel(); var emitter = new TemplateEmitter(model); @@ -60,8 +61,7 @@ public static async Task RunExampleTest(TestContext testContext, EmbeddedFile em if (result.Status == EmitStatus.Succeeded) { - jsonFile.WriteToOutputFolder(stringWriter.ToString()); - jsonFile.ShouldHaveExpectedJsonValue(); + stringWriter.ToString().Should().MatchJsonBaseline(jsonFile); // validate that the template is parseable by the deployment engine UnitTests.Utils.TemplateHelper.TemplateShouldBeValid(stringWriter.ToString(), model.Features); @@ -71,31 +71,30 @@ public static async Task RunExampleTest(TestContext testContext, EmbeddedFile em [DataTestMethod] [DynamicData(nameof(GetAllExampleData), DynamicDataSourceType.Method)] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public Task ExampleIsValid(EmbeddedFile embeddedBicep) => RunExampleTest(TestContext, embeddedBicep, new(TestContext), ".json"); [DataTestMethod] [DynamicData(nameof(GetAllExampleData), DynamicDataSourceType.Method)] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public Task ExampleIsValid_using_experimental_symbolic_names(EmbeddedFile embeddedBicep) => RunExampleTest(TestContext, embeddedBicep, new(TestContext, SymbolicNameCodegenEnabled: true), ".symbolicnames.json"); [DataTestMethod] [DynamicData(nameof(GetAllExampleData), DynamicDataSourceType.Method)] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void Example_uses_consistent_formatting(EmbeddedFile embeddedBicep) { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, embeddedBicep); - var bicepFile = baselineFolder.EntryFile; + var baselineFiles = TestContext.MaterializeBaseline(embeddedBicep); + var bicepFile = baselineFiles.EntryFile; var program = ParserHelper.Parse(embeddedBicep.Contents, out var lexingErrorLookup, out var parsingErrorLookup); var context = PrettyPrinterV2Context.Create(PrettyPrinterV2Options.Default, lexingErrorLookup, parsingErrorLookup); var formattedContents = PrettyPrinterV2.Print(program, context); formattedContents.Should().NotBeNull(); - bicepFile.WriteToOutputFolder(formattedContents); - bicepFile.ShouldHaveExpectedValue(); + formattedContents.Should().MatchTextBaseline(bicepFile); } [TestMethod] diff --git a/src/Bicep.Core.IntegrationTests/ExtensionRegistryTests.cs b/src/Bicep.Core.IntegrationTests/ExtensionRegistryTests.cs index ff6eeecba55..92b5aefd19b 100644 --- a/src/Bicep.Core.IntegrationTests/ExtensionRegistryTests.cs +++ b/src/Bicep.Core.IntegrationTests/ExtensionRegistryTests.cs @@ -8,7 +8,8 @@ using Bicep.Core.Registry.Extensions; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; +using FluentAssertions; using Bicep.Core.UnitTests.Extensions; using Bicep.Core.UnitTests.Features; using Bicep.Core.UnitTests.Utils; @@ -30,21 +31,20 @@ public class ExtensionRegistryTests : TestBase private readonly TestCompiler compiler = TestCompiler.ForMockFileSystemCompilation(); [TestMethod] - [TestCategory(BaselineHelper.BaselineTestCategory)] - [EmbeddedFilesTestData(@"Files/ExtensionRegistryTests/http/types/index.json")] + [TestCategory(TestCategories.Baseline)] + [TestEmbeddedFileData(@"Files/ExtensionRegistryTests/http/types/index.json")] public void Http_extension_can_be_generated(EmbeddedFile indexJson) { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, indexJson); + var baselineFiles = TestContext.MaterializeBaseline(indexJson); var httpTypes = ExtensionResourceTypeHelper.GetHttpExtensionTypes(); using (new AssertionScope()) { foreach (var (relativePath, contents) in httpTypes) { - var jsonFile = baselineFolder.GetFileOrEnsureCheckedIn(PathHelper.ResolvePath(relativePath, Path.GetDirectoryName(indexJson.FileName))); + var jsonFile = baselineFiles.GetFile(PathHelper.ResolvePath(relativePath, Path.GetDirectoryName(indexJson.FileName))); - jsonFile.WriteToOutputFolder(contents); - jsonFile.ShouldHaveExpectedJsonValue(); + contents.Should().MatchJsonBaseline(jsonFile); } } } diff --git a/src/Bicep.Core.IntegrationTests/GlobalUsings.cs b/src/Bicep.Core.IntegrationTests/GlobalUsings.cs index f09b85cb868..7bbb4d099a7 100644 --- a/src/Bicep.Core.IntegrationTests/GlobalUsings.cs +++ b/src/Bicep.Core.IntegrationTests/GlobalUsings.cs @@ -1,4 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +global using Bicep.Testing; global using Bicep.Testing.Assertions.Json; +global using Bicep.Testing.Baselines; +global using Bicep.Testing.IO; diff --git a/src/Bicep.Core.IntegrationTests/LexerTests.cs b/src/Bicep.Core.IntegrationTests/LexerTests.cs index 314dc6d99df..ae1c88d3699 100644 --- a/src/Bicep.Core.IntegrationTests/LexerTests.cs +++ b/src/Bicep.Core.IntegrationTests/LexerTests.cs @@ -9,6 +9,7 @@ using Bicep.Core.Syntax; using Bicep.Core.Text; using Bicep.Core.UnitTests.Assertions; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Utils; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -92,7 +93,7 @@ void VisitTrivia(IEnumerable trivia) [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void LexerShouldProduceExpectedTokens(DataSet dataSet) { var lexer = new Lexer(new SlidingTextWindow(dataSet.Bicep), ToListDiagnosticWriter.Create()); @@ -106,7 +107,7 @@ string getLoggingString(Token token) var sourceTextWithDiags = DataSet.AddDiagsToSourceText(dataSet, lexer.GetTokens(), getLoggingString); var resultsFile = FileHelper.SaveResultFile(this.TestContext, Path.Combine(dataSet.Name, DataSet.TestFileMainTokens), sourceTextWithDiags); - sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( + sourceTextWithDiags.Should().MatchTextBaseline( TestContext, dataSet.Tokens, expectedPath: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainTokens), @@ -118,7 +119,7 @@ string getLoggingString(Token token) [DataTestMethod] [BaselineData_Bicepparam.TestData()] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void ParamsFile_LexerShouldProduceExpectedTokens(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); @@ -133,8 +134,7 @@ string getLoggingString(Token token) var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(data.Parameters.EmbeddedFile.Contents, "\n", lexer.GetTokens(), getLoggingString); - data.Tokens.WriteToOutputFolder(sourceTextWithDiags); - data.Tokens.ShouldHaveExpectedValue(); + sourceTextWithDiags.Should().MatchTextBaseline(data.Tokens); lexer.GetTokens().Count(token => token.Type == TokenType.EndOfFile).Should().Be(1, "because there should only be 1 EOF token"); lexer.GetTokens().Last().Type.Should().Be(TokenType.EndOfFile, "because the last token should always be EOF."); diff --git a/src/Bicep.Core.IntegrationTests/ParserTests.cs b/src/Bicep.Core.IntegrationTests/ParserTests.cs index ac76876502d..3ce24503c7d 100644 --- a/src/Bicep.Core.IntegrationTests/ParserTests.cs +++ b/src/Bicep.Core.IntegrationTests/ParserTests.cs @@ -8,6 +8,7 @@ using Bicep.Core.Syntax; using Bicep.Core.Text; using Bicep.Core.UnitTests.Assertions; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Syntax; using Bicep.Core.UnitTests.Utils; using FluentAssertions; @@ -52,7 +53,7 @@ public void Oneliners_ShouldRoundTripSuccessfully(string contents) [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void Parser_should_produce_expected_syntax(DataSet dataSet) { var program = ParserHelper.Parse(dataSet.Bicep); @@ -64,7 +65,7 @@ public void Parser_should_produce_expected_syntax(DataSet dataSet) var sourceTextWithDiags = DataSet.AddDiagsToSourceText(dataSet, syntaxList, getSpan, syntax => GetSyntaxLoggingString(syntaxByParent, syntax)); var resultsFile = FileHelper.SaveResultFile(this.TestContext, Path.Combine(dataSet.Name, DataSet.TestFileMainSyntax), sourceTextWithDiags); - sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( + sourceTextWithDiags.Should().MatchTextBaseline( TestContext, dataSet.Syntax, expectedPath: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainSyntax), @@ -73,7 +74,7 @@ public void Parser_should_produce_expected_syntax(DataSet dataSet) [DataTestMethod] [BaselineData_Bicepparam.TestData()] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void Params_Parser_should_produce_expected_syntax(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); @@ -85,8 +86,7 @@ public void Params_Parser_should_produce_expected_syntax(BaselineData_Bicepparam var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(data.Parameters.EmbeddedFile.Contents, "\n", syntaxList, getSpan, syntax => GetSyntaxLoggingString(syntaxByParent, syntax)); - data.Syntax.WriteToOutputFolder(sourceTextWithDiags); - data.Syntax.ShouldHaveExpectedValue(); + sourceTextWithDiags.Should().MatchTextBaseline(data.Syntax); } private static IEnumerable GetData() diff --git a/src/Bicep.Core.IntegrationTests/PrettyPrint/PrettyPrinterV2Tests.cs b/src/Bicep.Core.IntegrationTests/PrettyPrint/PrettyPrinterV2Tests.cs index 9e008a2a36a..f29d528a868 100644 --- a/src/Bicep.Core.IntegrationTests/PrettyPrint/PrettyPrinterV2Tests.cs +++ b/src/Bicep.Core.IntegrationTests/PrettyPrint/PrettyPrinterV2Tests.cs @@ -5,6 +5,7 @@ using Bicep.Core.PrettyPrintV2; using Bicep.Core.Samples; using Bicep.Core.UnitTests.Assertions; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Utils; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -19,7 +20,7 @@ public partial class PrettyPrinterV2Tests [DataTestMethod] [DataRow(40)] [DataRow(80)] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void Print_VariousWidths_OptimizesLayoutAccordingly(int width) { var dataSet = DataSets.PrettyPrint_LF; @@ -31,7 +32,7 @@ public void Print_VariousWidths_OptimizesLayoutAccordingly(int width) var outputFile = FileHelper.SaveResultFile(this.TestContext, Path.Combine(dataSet.Name, outputFileName), output); var expected = dataSet.ReadDataSetFile(outputFileName); - output.Should().EqualWithLineByLineDiffOutput( + output.Should().MatchTextBaseline( TestContext, expected, expectedPath: DataSet.GetBaselineUpdatePath(dataSet, outputFileName), @@ -42,14 +43,14 @@ public void Print_VariousWidths_OptimizesLayoutAccordingly(int width) [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void Print_DataSet_ProducesExpectedOutput(DataSet dataSet) { var output = Print(dataSet.Bicep, PrettyPrinterV2Options.Default); var outputFileName = DataSet.TestFileMainFormatted; var outputFile = FileHelper.SaveResultFile(this.TestContext, Path.Combine(dataSet.Name, outputFileName), output); - output.Should().EqualWithLineByLineDiffOutput( + output.Should().MatchTextBaseline( TestContext, dataSet.Formatted, expectedPath: DataSet.GetBaselineUpdatePath(dataSet, outputFileName), @@ -60,7 +61,7 @@ public void Print_DataSet_ProducesExpectedOutput(DataSet dataSet) [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void Print_DataSet_ProducesConsistentNewlines(DataSet dataSet) { var output = Print(dataSet.Bicep, PrettyPrinterV2Options.Default); @@ -74,21 +75,20 @@ public void Print_DataSet_ProducesConsistentNewlines(DataSet dataSet) [DataTestMethod] [BaselineData_Bicepparam.TestData()] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void Print_ParamDataSet_ProducesExpectedOutput(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); var output = Print(data.Parameters.EmbeddedFile.Contents, PrettyPrinterV2Options.Default, isParamFile: true); - data.Formatted.WriteToOutputFolder(output); - data.Formatted.ShouldHaveExpectedValue(); + output.Should().MatchTextBaseline(data.Formatted); AssertConsistentParamsOutput(output, PrettyPrinterV2Options.Default); } [DataTestMethod] [BaselineData_Bicepparam.TestData()] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void Print_ParamDataSet_ProducesConsistentNewlines(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); diff --git a/src/Bicep.Core.IntegrationTests/Semantics/NamespaceTests.cs b/src/Bicep.Core.IntegrationTests/Semantics/NamespaceTests.cs index 2010a7bf570..e2b77a96b1c 100644 --- a/src/Bicep.Core.IntegrationTests/Semantics/NamespaceTests.cs +++ b/src/Bicep.Core.IntegrationTests/Semantics/NamespaceTests.cs @@ -9,6 +9,7 @@ using Bicep.Core.Semantics; using Bicep.Core.TypeSystem; using Bicep.Core.UnitTests.Assertions; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Utils; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -25,7 +26,7 @@ public class NamespaceTests [DataTestMethod] [DynamicData(nameof(GetNamespaces), DynamicDataSourceType.Method, DynamicDataDisplayName = nameof(GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void FunctionsShouldHaveExpectedSignatures(INamespaceSymbol @namespace) { var knownOverloads = @namespace.TryGetNamespaceType()!.MethodResolver.GetKnownFunctions().Values @@ -46,7 +47,7 @@ public void FunctionsShouldHaveExpectedSignatures(INamespaceSymbol @namespace) var expected = JToken.Parse(expectedStr); var expectedPath = DataSet.GetBaselineUpdatePath(DataSet.TestFunctionsDirectory, fileName); - actual.Should().EqualWithJsonDiffOutput(TestContext, expected, expectedPath, actualLocation); + actual.Should().MatchJsonBaseline(TestContext, expected, expectedPath, actualLocation); } private static IEnumerable GetNamespaces() diff --git a/src/Bicep.Core.IntegrationTests/Semantics/ParamsSemanticModelTests.cs b/src/Bicep.Core.IntegrationTests/Semantics/ParamsSemanticModelTests.cs index 0fc6766ac44..cb89d783016 100644 --- a/src/Bicep.Core.IntegrationTests/Semantics/ParamsSemanticModelTests.cs +++ b/src/Bicep.Core.IntegrationTests/Semantics/ParamsSemanticModelTests.cs @@ -10,6 +10,9 @@ using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; using Bicep.Core.UnitTests.Utils; +using Bicep.IO.Abstraction; +using Bicep.Testing.Baselines; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Bicep.Core.IntegrationTests.Semantics @@ -30,7 +33,7 @@ private async Task CreateSemanticModel(ServiceBuilder services, s [DataTestMethod] [BaselineData_Bicepparam.TestData()] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task ProgramsShouldProduceExpectedDiagnostic(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); @@ -45,15 +48,14 @@ public async Task ProgramsShouldProduceExpectedDiagnostic(BaselineData_Biceppara .ThenBy(x => x.Message, StringComparer.Ordinal); var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(data.Parameters.EmbeddedFile.Contents, "\n", diagnostics, - diag => OutputHelper.GetDiagLoggingString(data.Parameters.EmbeddedFile.Contents, data.OutputFolder.OutputFolderPath, diag)); + diag => OutputHelper.GetDiagLoggingString(data.Parameters.EmbeddedFile.Contents, data.FileSet.OutputDirectoryPath, diag)); - data.Diagnostics.WriteToOutputFolder(sourceTextWithDiags); - data.Diagnostics.ShouldHaveExpectedValue(); + sourceTextWithDiags.Should().MatchTextBaseline(data.Diagnostics); } [DataTestMethod] [BaselineData_Bicepparam.TestData()] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task ProgramsShouldProduceExpectedUserDeclaredSymbols(BaselineData_Bicepparam baselineData) { var data = baselineData.GetData(TestContext); @@ -74,8 +76,7 @@ string getLoggingString(DeclaredSymbol symbol) var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(data.Parameters.EmbeddedFile.Contents, "\n", symbols, symb => symb.NameSource.Span, getLoggingString); - data.Symbols.WriteToOutputFolder(sourceTextWithDiags); - data.Symbols.ShouldHaveExpectedValue(); + sourceTextWithDiags.Should().MatchTextBaseline(data.Symbols); } [TestMethod] @@ -102,14 +103,17 @@ param routes resourceInput<'Microsoft.Network/routeTables@2024-07-01'>.propertie var artifactManager = await MockRegistry.CreateDefaultExternalArtifactManager(TestContext); await artifactManager.PublishRegistryModule(moduleRef, moduleContent); - var paramsFilePath = FileHelper.SaveResultFile(TestContext, "main.bicepparam", paramsContent); - var fileUri = PathHelper.FilePathToFileUrl(paramsFilePath); + var files = MockFileSystemTestFileSet.Create(("main.bicepparam", paramsContent)); + var cacheRoot = files.FileExplorer.GetDirectory(files.GetUri("cache")).EnsureExists(); - var services = await CreateServicesAsync(); + var services = await CreateServicesAsync(cacheRoot); services = services.WithTestArtifactManager(artifactManager); + services = services + .WithFileSystem(files.FileSystem) + .WithFileExplorer(files.FileExplorer); var compiler = services.Build().GetCompiler(); - var compilation = await compiler.CreateCompilation(fileUri.ToIOUri()); + var compilation = await compiler.CreateCompilation(files.GetUri("main.bicepparam")); var diagnostics = compilation.GetEntrypointSemanticModel().GetAllDiagnostics().ExcludingLinterDiagnostics(); @@ -117,8 +121,11 @@ param routes resourceInput<'Microsoft.Network/routeTables@2024-07-01'>.propertie } private async Task CreateServicesAsync() + => await CreateServicesAsync(FileHelper.GetCacheRootDirectory(TestContext)); + + private async Task CreateServicesAsync(IDirectoryHandle cacheRootDirectory) => new ServiceBuilder() - .WithFeatureOverrides(new(TestContext)) + .WithFeatureOverrides(new(CacheRootDirectory: cacheRootDirectory)) .WithEnvironmentVariables( ("stringEnvVariableName", "test"), ("intEnvVariableName", "100"), diff --git a/src/Bicep.Core.IntegrationTests/Semantics/SemanticModelTests.cs b/src/Bicep.Core.IntegrationTests/Semantics/SemanticModelTests.cs index cf60e160376..4c2a2c4b0ec 100644 --- a/src/Bicep.Core.IntegrationTests/Semantics/SemanticModelTests.cs +++ b/src/Bicep.Core.IntegrationTests/Semantics/SemanticModelTests.cs @@ -11,6 +11,7 @@ using Bicep.Core.Text; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Syntax; using Bicep.Core.UnitTests.Utils; using FluentAssertions; @@ -36,7 +37,7 @@ public class SemanticModelTests // Problematic ones that should be disabled in this and most other tests by default can be added to BicepTestConstants.AnalyzerRulesToDisableInTests [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task ProgramsShouldProduceExpectedDiagnostics(DataSet dataSet) { var (compilation, outputDirectory, _) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext); @@ -52,7 +53,7 @@ public async Task ProgramsShouldProduceExpectedDiagnostics(DataSet dataSet) var resultsFile = Path.Combine(outputDirectory, DataSet.TestFileMainDiagnostics); File.WriteAllText(resultsFile, sourceTextWithDiags); - sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( + sourceTextWithDiags.Should().MatchTextBaseline( TestContext, dataSet.Diagnostics, expectedPath: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainDiagnostics), @@ -69,7 +70,7 @@ public void EndOfFileFollowingSpaceAfterParameterKeyWordShouldNotThrow() [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task ProgramsShouldProduceExpectedUserDeclaredSymbols(DataSet dataSet) { var (compilation, outputDirectory, _) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext); @@ -91,7 +92,7 @@ string getLoggingString(DeclaredSymbol symbol) var resultsFile = Path.Combine(outputDirectory, DataSet.TestFileMainDiagnostics); File.WriteAllText(resultsFile, sourceTextWithDiags); - sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( + sourceTextWithDiags.Should().MatchTextBaseline( TestContext, dataSet.Symbols, expectedPath: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainSymbols), @@ -203,8 +204,7 @@ public void GetAllDiagnostics_VerifyDisableNextLineDiagnosticsDirectiveDoesNotSu { var bicepFileContents = @"#disable-next-line BCP029 BCP068 resource test"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", bicepFileContents); - var documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); + var documentUri = DocumentUri.FromFileSystemPath(TestFileUri.FromInMemoryPath("main.bicep").GetFilePath()); var uri = documentUri.ToUriEncoded(); var files = new Dictionary @@ -244,8 +244,7 @@ public void GetAllDiagnostics_VerifyDisableNextLineDiagnosticsDirectiveSupportsC #disable-next-line BCP036 BCP037 properties: vmProperties }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", bicepFileContents); - var documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); + var documentUri = DocumentUri.FromFileSystemPath(TestFileUri.FromInMemoryPath("main.bicep").GetFilePath()); var uri = documentUri.ToUriEncoded(); var files = new Dictionary @@ -263,8 +262,7 @@ public void GetAllDiagnostics_VerifyDisableNextLineDiagnosticsDirectiveSupportsL { var bicepFileContents = @"#disable-next-line no-unused-params param storageAccount string = 'testStorageAccount'"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", bicepFileContents); - var documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); + var documentUri = DocumentUri.FromFileSystemPath(TestFileUri.FromInMemoryPath("main.bicep").GetFilePath()); var uri = documentUri.ToUriEncoded(); var files = new Dictionary @@ -283,8 +281,7 @@ public void GetAllDiagnostics_WithNoDisableNextLineDiagnosticsDirectiveInPreviou var bicepFileContents = @"#disable-next-line no-unused-params param storageAccount string = 'testStorageAccount'"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", bicepFileContents); - var documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); + var documentUri = DocumentUri.FromFileSystemPath(TestFileUri.FromInMemoryPath("main.bicep").GetFilePath()); var uri = documentUri.ToUriEncoded(); var files = new Dictionary @@ -320,7 +317,7 @@ public async Task All_nodes_should_be_parented(DataSet dataSet) [DataTestMethod] [DynamicData(nameof(GetValidDataSets), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(DataSet), DynamicDataDisplayName = nameof(DataSet.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task ProgramsShouldProduceExpectedIrTree(DataSet dataSet) { var (compilation, outputDirectory, _) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext); @@ -337,7 +334,7 @@ public async Task ProgramsShouldProduceExpectedIrTree(DataSet dataSet) var sourceTextWithDiags = DataSet.AddDiagsToSourceText(dataSet, expressionList, getSpan, expression => ExpressionCollectorVisitor.GetExpressionLoggingString(expressionByParent, expression)); var resultsFile = FileHelper.SaveResultFile(this.TestContext, Path.Combine(dataSet.Name, DataSet.TestFileMainIr), sourceTextWithDiags); - sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( + sourceTextWithDiags.Should().MatchTextBaseline( TestContext, dataSet.Ir ?? "", expectedPath: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainIr), diff --git a/src/Bicep.Core.IntegrationTests/SymbolicNameGenerationTests.cs b/src/Bicep.Core.IntegrationTests/SymbolicNameGenerationTests.cs index 3d92bf6aaac..6e8122eac46 100644 --- a/src/Bicep.Core.IntegrationTests/SymbolicNameGenerationTests.cs +++ b/src/Bicep.Core.IntegrationTests/SymbolicNameGenerationTests.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Utils; using Bicep.Core.Utils; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -19,8 +19,8 @@ public class SymbolicNameTests public TestContext? TestContext { get; set; } [TestMethod] - [TestCategory(BaselineHelper.BaselineTestCategory)] - [EmbeddedFilesTestData(@"Files/SymbolicNameTests/ResourceInfo/.*/main\.bicep")] + [TestCategory(TestCategories.Baseline)] + [TestEmbeddedFileData(@"Files/SymbolicNameTests/ResourceInfo/.*/main\.bicep")] public async Task ResourceInfoCodegenEnabled_output_is_valid(EmbeddedFile bicepFile) => await ExamplesTests.RunExampleTest(TestContext, bicepFile, new(TestContext, ResourceInfoCodegenEnabled: true)); diff --git a/src/Bicep.Core.Samples/BaselineData_Bicepparam.cs b/src/Bicep.Core.Samples/BaselineData_Bicepparam.cs index 8fb7d24e468..74ff378cbf5 100644 --- a/src/Bicep.Core.Samples/BaselineData_Bicepparam.cs +++ b/src/Bicep.Core.Samples/BaselineData_Bicepparam.cs @@ -2,7 +2,9 @@ // Licensed under the MIT License. using System.Reflection; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing; +using Bicep.Testing.Baselines; +using Bicep.Testing.IO; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -51,7 +53,7 @@ public IEnumerable GetData(MethodInfo methodInfo) } public record BaselineData( - BaselineFolder OutputFolder, + BaselineDirectory FileSet, BaselineFile Parameters, BaselineFile? Compiled, BaselineFile Bicep, @@ -72,20 +74,20 @@ public BaselineData_Bicepparam(EmbeddedFile paramsFile) public BaselineData GetData(TestContext testContext) { - var outputFolder = BaselineFolder.BuildOutputFolder(testContext, paramsFile); + var outputFolder = testContext.MaterializeBaseline(paramsFile); using (new AssertionScope()) { return new( - OutputFolder: outputFolder, - Parameters: outputFolder.GetFileOrEnsureCheckedIn("parameters.bicepparam"), + FileSet: outputFolder, + Parameters: outputFolder.GetFile("parameters.bicepparam"), Compiled: outputFolder.TryGetFile("parameters.json"), - Bicep: outputFolder.GetFileOrEnsureCheckedIn("main.bicep"), - Tokens: outputFolder.GetFileOrEnsureCheckedIn("parameters.tokens.bicepparam"), - Diagnostics: outputFolder.GetFileOrEnsureCheckedIn("parameters.diagnostics.bicepparam"), - Symbols: outputFolder.GetFileOrEnsureCheckedIn("parameters.symbols.bicepparam"), - Syntax: outputFolder.GetFileOrEnsureCheckedIn("parameters.syntax.bicepparam"), - Formatted: outputFolder.GetFileOrEnsureCheckedIn("parameters.formatted.bicepparam")); + Bicep: outputFolder.GetFile("main.bicep"), + Tokens: outputFolder.GetFile("parameters.tokens.bicepparam"), + Diagnostics: outputFolder.GetFile("parameters.diagnostics.bicepparam"), + Symbols: outputFolder.GetFile("parameters.symbols.bicepparam"), + Syntax: outputFolder.GetFile("parameters.syntax.bicepparam"), + Formatted: outputFolder.GetFile("parameters.formatted.bicepparam")); } } diff --git a/src/Bicep.Core.Samples/Bicep.Core.Samples.csproj b/src/Bicep.Core.Samples/Bicep.Core.Samples.csproj index ad6073f7469..2ba0c03066b 100644 --- a/src/Bicep.Core.Samples/Bicep.Core.Samples.csproj +++ b/src/Bicep.Core.Samples/Bicep.Core.Samples.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Bicep.Core.Samples/MockRegistry.cs b/src/Bicep.Core.Samples/MockRegistry.cs index 3020562bf31..b70dcca917f 100644 --- a/src/Bicep.Core.Samples/MockRegistry.cs +++ b/src/Bicep.Core.Samples/MockRegistry.cs @@ -4,10 +4,11 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Bicep.Core.Diagnostics; +using Bicep.Testing.IO; using Bicep.Core.Modules; using Bicep.Core.Registry.Oci; using Bicep.Core.UnitTests; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Features; using Bicep.Core.UnitTests.Utils; using Bicep.Testing.Mocks; @@ -128,6 +129,6 @@ private static async Task CreateDefaultExternalArti return manager; } - private static async Task CreateDefaultExternalArtifactManager(FeatureProviderOverrides overrides) + public static async Task CreateDefaultExternalArtifactManager(FeatureProviderOverrides overrides) => await CreateDefaultExternalArtifactManager(TestCompiler.ForMockFileSystemCompilation().WithFeatureOverrides(overrides)); } diff --git a/src/Bicep.Core.UnitTests/Assertions/BaselineHelper.cs b/src/Bicep.Core.UnitTests/Assertions/BaselineHelper.cs deleted file mode 100644 index e310fa2fb91..00000000000 --- a/src/Bicep.Core.UnitTests/Assertions/BaselineHelper.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Runtime.InteropServices; -using System.Text; -using Bicep.Core.FileSystem; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Bicep.Core.UnitTests.Assertions -{ - public static class BaselineHelper - { - private static readonly string RepoRoot = GetRepoRoot(); - - private const string SetBaseLineSettingName = "SetBaseLine"; - public const string BaselineTestCategory = "Baseline"; - - public static bool ShouldSetBaseline(TestContext testContext) => - testContext.Properties.Contains(SetBaseLineSettingName) && string.Equals(testContext.Properties[SetBaseLineSettingName] as string, bool.TrueString, StringComparison.OrdinalIgnoreCase); - - public static void SetBaseline(string actualPath, string expectedPath) - { - actualPath = GetAbsolutePathRelativeToRepoRoot(actualPath); - expectedPath = GetAbsolutePathRelativeToRepoRoot(expectedPath); - - if (Path.GetDirectoryName(expectedPath) is { } parentDir && - !Directory.Exists(parentDir)) - { - Directory.CreateDirectory(parentDir); - } - - File.Copy(actualPath, expectedPath, overwrite: true); - } - - public static string GetAbsolutePathRelativeToRepoRoot(string path) - => PathHelper.ResolveAndNormalizePath(path, RepoRoot); - - private static string GetRepoRoot() - { - var currentDir = new DirectoryInfo(Environment.CurrentDirectory); - - while (currentDir.Parent is { } parentDir) - { - // search upwards for the .git directory. This should only exist at the repository root. - if (Directory.Exists(Path.Join(currentDir.FullName, ".git"))) - { - // If TF_BUILD is not null, the code is running in the official build pipeline in ADO, - // and bicep is a Git submodule in the BicepMirror repo. - return Environment.GetEnvironmentVariable("TF_BUILD") is not null - ? Path.Join(currentDir.FullName, "bicep") - : currentDir.FullName; - } - - currentDir = parentDir; - } - - throw new InvalidOperationException($"Unable to determine the repo root path from directory {Environment.CurrentDirectory}"); - } - - public static string GetAssertionFormatString(bool isBaselineUpdate) - { - var output = new StringBuilder(); - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - - output.Append(@" -Found diffs between actual and expected: -{0} -"); - - if (isBaselineUpdate) - { - output.Append(@" -Baseline {2} has been updated. -"); - } - else - { - output.Append(@" -View this diff with: - git diff --color-words --no-index {2} {1} -"); - - if (isWindows) - { - output.Append(@" -Overwrite the single baseline: - xcopy /yq {1} {2} - -Overwrite all baselines: - dotnet test -- --filter ""TestCategory=Baseline"" --test-parameter SetBaseLine=true - -See https://github.com/Azure/bicep/blob/main/CONTRIBUTING.md#updating-test-baselines for more information on how to fix this error. -"); - } - else - { - output.Append(@" -Overwrite the single baseline: - cp {1} {2} - -Overwrite all baselines: - dotnet test -- --filter ""TestCategory=Baseline"" --test-parameter SetBaseLine=true - -See https://github.com/Azure/bicep/blob/main/CONTRIBUTING.md#updating-test-baselines for more information on how to fix this error. -"); - } - } - - return output.ToString(); - } - } -} diff --git a/src/Bicep.Core.UnitTests/Assertions/CachedModuleExtensions.cs b/src/Bicep.Core.UnitTests/Assertions/CachedModuleExtensions.cs index 742950185ee..17b35ce4d11 100644 --- a/src/Bicep.Core.UnitTests/Assertions/CachedModuleExtensions.cs +++ b/src/Bicep.Core.UnitTests/Assertions/CachedModuleExtensions.cs @@ -61,7 +61,7 @@ public AndConstraint BeValid() expectedFiles.Add("source.tgz"); } - var files = new DirectoryInfo(Subject.ModuleCacheFolder).EnumerateFiles().Select(file => file.Name).ToImmutableArray(); + var files = Subject.FileSystem.DirectoryInfo.New(Subject.ModuleCacheFolder).EnumerateFiles().Select(file => file.Name).ToImmutableArray(); files.Should().BeEquivalentTo(expectedFiles); return new(this); diff --git a/src/Bicep.Core.UnitTests/Assertions/JTokenAssertionsExtensions.cs b/src/Bicep.Core.UnitTests/Assertions/JTokenAssertionsExtensions.cs deleted file mode 100644 index 3be62f2baa5..00000000000 --- a/src/Bicep.Core.UnitTests/Assertions/JTokenAssertionsExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Bicep.Testing.Assertions.Json; -using FluentAssertions; -using FluentAssertions.Execution; -using JsonDiffPatchDotNet; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json.Linq; - -namespace Bicep.Core.UnitTests.Assertions; - -public static class JTokenAssertionsExtensions -{ - public static AndConstraint EqualWithJsonDiffOutput(this JTokenAssertions instance, TestContext testContext, JToken expected, string expectedLocation, string actualLocation, string because = "", bool validateLocation = true, params object[] becauseArgs) - { - var diff = new JsonDiffPatch(new Options { TextDiff = TextDiffMode.Simple }).Diff(instance.Subject, expected); - var jsonDiff = diff?.ToString(); - var testPassed = jsonDiff is null; - - if (validateLocation) - { - var isBaselineUpdate = !testPassed && BaselineHelper.ShouldSetBaseline(testContext); - if (isBaselineUpdate) - { - BaselineHelper.SetBaseline(actualLocation, expectedLocation); - } - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(testPassed) - .FailWith( - BaselineHelper.GetAssertionFormatString(isBaselineUpdate), - jsonDiff, - BaselineHelper.GetAbsolutePathRelativeToRepoRoot(actualLocation), - BaselineHelper.GetAbsolutePathRelativeToRepoRoot(expectedLocation)); - } - else - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(testPassed) - .FailWith(jsonDiff); - } - - return new(instance); - } -} diff --git a/src/Bicep.Core.UnitTests/Assertions/StringAssertionsExtensions.cs b/src/Bicep.Core.UnitTests/Assertions/StringAssertionsExtensions.cs index 3174ffcffe4..4962c90831b 100644 --- a/src/Bicep.Core.UnitTests/Assertions/StringAssertionsExtensions.cs +++ b/src/Bicep.Core.UnitTests/Assertions/StringAssertionsExtensions.cs @@ -9,6 +9,7 @@ using Bicep.Core.Semantics; using Bicep.Core.UnitTests.Utils; using Bicep.Testing; +using Bicep.Testing.Baselines; using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder.Model; using FluentAssertions; @@ -94,30 +95,6 @@ public static AndConstraint EqualWithLineByLineDiff(this Strin return new AndConstraint(instance); } - public static AndConstraint EqualWithLineByLineDiffOutput(this StringAssertions instance, TestContext testContext, string expected, string expectedPath, string actualPath, string because = "", params object[] becauseArgs) - { - var lineDiff = CalculateDiff(expected, instance.Subject); - var hasNewlineDiffsOnly = lineDiff is null && !expected.Equals(instance.Subject, System.StringComparison.Ordinal); - var testPassed = lineDiff is null && !hasNewlineDiffsOnly; - - var isBaselineUpdate = !testPassed && BaselineHelper.ShouldSetBaseline(testContext); - if (isBaselineUpdate) - { - BaselineHelper.SetBaseline(actualPath, expectedPath); - } - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(testPassed) - .FailWith( - BaselineHelper.GetAssertionFormatString(isBaselineUpdate), - lineDiff ?? "differences in newlines only", - BaselineHelper.GetAbsolutePathRelativeToRepoRoot(actualPath), - BaselineHelper.GetAbsolutePathRelativeToRepoRoot(expectedPath)); - - return new AndConstraint(instance); - } - public static AndConstraint BeEquivalentToIgnoringNewlines(this StringAssertions instance, string expected, string because = "", params object[] becauseArgs) { var normalizedActual = StringUtils.ReplaceNewlines(instance.Subject, "\n"); diff --git a/src/Bicep.Core.UnitTests/Baselines/BaselineFile.cs b/src/Bicep.Core.UnitTests/Baselines/BaselineFile.cs deleted file mode 100644 index 7106f0930d6..00000000000 --- a/src/Bicep.Core.UnitTests/Baselines/BaselineFile.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Encodings.Web; -using System.Text.Json; -using System.Text.Json.Serialization; -using Bicep.Core.FileSystem; -using Bicep.Core.UnitTests.Assertions; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.WindowsAzure.ResourceStack.Common.Json; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Bicep.Core.UnitTests.Baselines -{ - public record BaselineFile( - TestContext TestContext, - EmbeddedFile EmbeddedFile, - string OutputFilePath) - { - - private readonly static JsonSerializerOptions StjSerializerOptions = new() - { - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - }; - - public string ReadFromOutputFolder() => File.ReadAllText(OutputFilePath); - - public void WriteToOutputFolder(string contents) => File.WriteAllText(OutputFilePath, contents); - - public void WriteJsonToOutputFolder(T contents) => WriteToOutputFolder(JsonConvert.SerializeObject(contents, Formatting.Indented)); - - public void WriteStjJsonToOutputFolder(T contents) => WriteToOutputFolder(System.Text.Json.JsonSerializer.Serialize(contents, StjSerializerOptions)); - - public Uri OutputFileUri => PathHelper.FilePathToFileUrl(OutputFilePath); - - public void ShouldHaveExpectedValue() - { - this.ReadFromOutputFolder().Should().EqualWithLineByLineDiffOutput( - TestContext, - EmbeddedFile.Contents, - expectedPath: EmbeddedFile.RelativeSourcePath, - actualPath: OutputFilePath); - } - - public void ShouldHaveExpectedJsonValue() - { - this.ReadFromOutputFolder().FromJson().Should().EqualWithJsonDiffOutput( - TestContext, - EmbeddedFile.Contents.TryFromJson() ?? JValue.CreateNull(), - expectedLocation: EmbeddedFile.RelativeSourcePath, - actualLocation: OutputFilePath); - } - } -} diff --git a/src/Bicep.Core.UnitTests/Baselines/BaselineFolder.cs b/src/Bicep.Core.UnitTests/Baselines/BaselineFolder.cs deleted file mode 100644 index 411f94fcc0e..00000000000 --- a/src/Bicep.Core.UnitTests/Baselines/BaselineFolder.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Immutable; -using Bicep.Core.Extensions; -using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Utils; -using Bicep.IO.Abstraction; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Bicep.Core.UnitTests.Baselines -{ - public record BaselineFolder( - string OutputFolderPath, - string StreamFolderPath, - ImmutableDictionary Files, - BaselineFile EntryFile) - { - public static BaselineFolder BuildOutputFolder(TestContext testContext, EmbeddedFile embeddedFile) - { - var outputDirectory = FileHelper.GetUniqueTestOutputPath(testContext); - var parentStream = Path.GetDirectoryName(embeddedFile.StreamPath)!.Replace('\\', '/'); - var entryFileRelativePath = embeddedFile.StreamPath.Substring(parentStream.Length).TrimStart('/'); - - var baselineFiles = new Dictionary(); - foreach (var streamPath in embeddedFile.Assembly.GetManifestResourceNames() - .Where(file => file.StartsWith(parentStream, StringComparison.Ordinal))) - { - var relativePath = streamPath.Substring(parentStream.Length).TrimStart('/'); - var filePath = Path.Combine(outputDirectory, relativePath); - - baselineFiles[relativePath] = new( - testContext, - new EmbeddedFile(embeddedFile.Assembly, streamPath), - filePath); - } - - foreach (var baselineFile in baselineFiles.Values) - { - var directoryPath = Path.GetDirectoryName(baselineFile.OutputFilePath)!; - Directory.CreateDirectory(directoryPath); - - File.WriteAllText(baselineFile.OutputFilePath, baselineFile.EmbeddedFile.Contents); - testContext.AddResultFile(baselineFile.OutputFilePath); - } - - return new( - outputDirectory, - parentStream, - baselineFiles.ToImmutableDictionary(), - baselineFiles[entryFileRelativePath]); - } - - public BaselineFile? TryGetFile(string relativePath) - => Files.TryGetValue(relativePath); - - private string GetBaselineStreamRelativePath(string filePath) - => filePath.StartsWith(OutputFolderPath) ? - filePath.Substring(OutputFolderPath.Length).Replace('\\', '/').TrimStart('/') : - throw new InvalidOperationException($"FilePath {filePath} is not a sub-path of {OutputFolderPath}"); - - public BaselineFile GetFileOrEnsureCheckedIn(IOUri fileUri) => GetFileOrEnsureCheckedIn(GetBaselineStreamRelativePath(fileUri.GetFilePath())); - - public BaselineFile GetFileOrEnsureCheckedIn(string relativePath) - { - if (TryGetFile(relativePath) is { } baselineFile) - { - return baselineFile; - } - - var embeddedFile = new EmbeddedFile( - this.EntryFile.EmbeddedFile.Assembly, - $"{this.StreamFolderPath}/{relativePath}"); - - var outputFile = Path.Combine(this.OutputFolderPath, relativePath); - File.WriteAllText(outputFile, ""); - - "".Should().EqualWithLineByLineDiffOutput( - this.EntryFile.TestContext, - "", - expectedPath: embeddedFile.RelativeSourcePath, - actualPath: outputFile); - throw new NotImplementedException("Code cannot reach this point as the previous line will always throw"); - } - } -} diff --git a/src/Bicep.Core.UnitTests/Baselines/EmbeddedFile.cs b/src/Bicep.Core.UnitTests/Baselines/EmbeddedFile.cs deleted file mode 100644 index 84fc008b929..00000000000 --- a/src/Bicep.Core.UnitTests/Baselines/EmbeddedFile.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Reflection; -using System.Text.RegularExpressions; -using FluentAssertions; - -namespace Bicep.Core.UnitTests.Baselines -{ - public record EmbeddedFile( - Assembly Assembly, - string StreamPath) - { - private readonly Lazy binaryDataLazy = new(() => BinaryData.FromStream(Assembly.GetManifestResourceStream(StreamPath)!)); - private readonly Lazy contentsLazy = new(() => new StreamReader(Assembly.GetManifestResourceStream(StreamPath)!).ReadToEnd()); - - public string Contents => contentsLazy.Value; - - public BinaryData BinaryData => binaryDataLazy.Value; - - public string FileName => Path.GetFileName(StreamPath); - - public string RelativeSourcePath => Path.Combine("src", Assembly.GetName().Name!, StreamPath); - - public static IEnumerable LoadAll(Assembly assembly, string streamPathPrefix, Func shouldLoad) - { - // Set the convention that all embedded resource files are in a folder named "Files" - var combinedPathPrefix = $"Files/{streamPathPrefix}/"; - - return LoadAll(assembly, name => name.StartsWith(combinedPathPrefix, StringComparison.Ordinal) && shouldLoad(name)); - } - - public static IEnumerable LoadAll(Assembly assembly, Regex regex) - => LoadAll(assembly, regex.IsMatch); - - public static IEnumerable LoadAll(Assembly assembly, Func shouldLoad) - { - foreach (var streamName in assembly.GetManifestResourceNames().Where(shouldLoad)) - { - if (shouldLoad(streamName)) - { - yield return new(assembly, streamName); - } - } - } - - public override string ToString() => StreamPath; - } -} diff --git a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/NoModuleNameRuleTests.cs b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/NoModuleNameRuleTests.cs index f4442f4890f..3100c287310 100644 --- a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/NoModuleNameRuleTests.cs +++ b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/NoModuleNameRuleTests.cs @@ -3,6 +3,7 @@ using Bicep.Core.Analyzers.Linter.Rules; using Bicep.Testing; +using Bicep.Testing.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Bicep.Core.UnitTests.Diagnostics.LinterRuleTests; diff --git a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/NoUnusedImportsRuleTests.cs b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/NoUnusedImportsRuleTests.cs index c4722d8eadb..53b5de51b31 100644 --- a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/NoUnusedImportsRuleTests.cs +++ b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/NoUnusedImportsRuleTests.cs @@ -5,6 +5,7 @@ using Bicep.Core.UnitTests.Assertions; using Bicep.Core.UnitTests.Utils; using Bicep.Testing; +using Bicep.Testing.IO; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseSafeAccessRuleTests.cs b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseSafeAccessRuleTests.cs index 13c6bdd0379..4b3a5670725 100644 --- a/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseSafeAccessRuleTests.cs +++ b/src/Bicep.Core.UnitTests/Diagnostics/LinterRuleTests/UseSafeAccessRuleTests.cs @@ -3,6 +3,7 @@ using Bicep.Core.Analyzers.Linter.Rules; using Bicep.Testing; +using Bicep.Testing.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Bicep.Core.UnitTests.Diagnostics.LinterRuleTests; diff --git a/src/Bicep.Core.UnitTests/Registry/CachedModules.cs b/src/Bicep.Core.UnitTests/Registry/CachedModules.cs index a22e2790620..91bedc6dfd8 100644 --- a/src/Bicep.Core.UnitTests/Registry/CachedModules.cs +++ b/src/Bicep.Core.UnitTests/Registry/CachedModules.cs @@ -68,10 +68,10 @@ public record CachedModule( string Repository, string Tag) { - public string ManifestContents => File.ReadAllText(Path.Combine(ModuleCacheFolder, "manifest")); + public string ManifestContents => FileSystem.File.ReadAllText(FileSystem.Path.Combine(ModuleCacheFolder, "manifest")); public JsonObject ManifestJson => (JsonObject)JsonNode.Parse(ManifestContents)!; - public string MetadataContents => File.ReadAllText(Path.Combine(ModuleCacheFolder, "metadata")); + public string MetadataContents => FileSystem.File.ReadAllText(FileSystem.Path.Combine(ModuleCacheFolder, "metadata")); public JsonObject MetadataJson => (JsonObject)JsonNode.Parse(MetadataContents)!; public string[] LayerMediaTypes diff --git a/src/Bicep.Core.UnitTests/Semantics/Namespaces/ThisNamespaceTests.cs b/src/Bicep.Core.UnitTests/Semantics/Namespaces/ThisNamespaceTests.cs index 9e243f49a4d..3e3ecdbadee 100644 --- a/src/Bicep.Core.UnitTests/Semantics/Namespaces/ThisNamespaceTests.cs +++ b/src/Bicep.Core.UnitTests/Semantics/Namespaces/ThisNamespaceTests.cs @@ -8,6 +8,7 @@ using Bicep.Core.UnitTests.Features; using Bicep.Core.UnitTests.Utils; using Bicep.Testing; +using Bicep.Testing.IO; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/Bicep.Core.UnitTests/SourceFiles/ResXFileTests.cs b/src/Bicep.Core.UnitTests/SourceFiles/ResXFileTests.cs index f21496bd8f9..2f95c659602 100644 --- a/src/Bicep.Core.UnitTests/SourceFiles/ResXFileTests.cs +++ b/src/Bicep.Core.UnitTests/SourceFiles/ResXFileTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using Bicep.Core.UnitTests.Assertions; +using Bicep.Testing; +using Bicep.Testing.Baselines; using FluentAssertions; using FluentAssertions.Execution; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -18,7 +20,7 @@ public class ResXFileTests const string Info = "If this test fails, it may indicate that the file was formatted directly in the editor instead of the Resource Editor in Visual Studio. (In VSCode or VS Mac, there is no Resource Editor, so be sure you haven't allowed the editor to format the file after editing.)"; private string GetRelativeFileContents(string relativePath) { - var path = BaselineHelper.GetAbsolutePathRelativeToRepoRoot(relativePath); + var path = TestRepository.GetAbsolutePath(relativePath); return File.ReadAllText(path); } @@ -33,7 +35,7 @@ private void AssertDoesNotContainTabs(string relativePath, string text) [TestMethod] public void ResXAndDesignerFilesShouldBeConsistentAndNotCauseUnnecessaryMergeConflicts() { - string[] resxFiles = System.IO.Directory.GetFiles(BaselineHelper.GetAbsolutePathRelativeToRepoRoot("src"), "*.resx", SearchOption.AllDirectories) + string[] resxFiles = System.IO.Directory.GetFiles(TestRepository.GetAbsolutePath("src"), "*.resx", SearchOption.AllDirectories) .Where(path => !path.ContainsOrdinally("packages")) .ToArray(); resxFiles.Should().HaveCountGreaterThan(2, "There should be at least 3 ResX files found in the project"); diff --git a/src/Bicep.Decompiler.IntegrationTests/Bicep.Decompiler.IntegrationTests.csproj b/src/Bicep.Decompiler.IntegrationTests/Bicep.Decompiler.IntegrationTests.csproj index 60e47f2618c..c928a7753cd 100644 --- a/src/Bicep.Decompiler.IntegrationTests/Bicep.Decompiler.IntegrationTests.csproj +++ b/src/Bicep.Decompiler.IntegrationTests/Bicep.Decompiler.IntegrationTests.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs b/src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs index d0ae37bd905..02b4d3b9d07 100644 --- a/src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs +++ b/src/Bicep.Decompiler.IntegrationTests/DecompilationTests.cs @@ -7,7 +7,8 @@ using Bicep.Core.FileSystem; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; +using Bicep.Testing.IO; using Bicep.Core.UnitTests.Utils; using Bicep.Decompiler; using Bicep.Decompiler.Exceptions; @@ -28,12 +29,12 @@ public class DecompilationTests private TestDecompiler CreateDecompilerWithEmptyAzResourceTypes() => new TestDecompiler().ConfigureServices(services => services.AddAzureResourceTypes([])); [DataTestMethod] - [EmbeddedFilesTestData(@"Files/Working/.*\.json")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/Working/.*\.json")] + [TestCategory(TestCategories.Baseline)] public async Task Decompiler_generates_expected_bicep_files_with_diagnostics(EmbeddedFile embeddedJson) { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, embeddedJson); - var jsonFile = baselineFolder.EntryFile; + var baselineFiles = TestContext.MaterializeBaseline(embeddedJson); + var jsonFile = baselineFiles.EntryFile; var jsonUri = IOUri.FromFilePath(jsonFile.OutputFilePath); var (bicepUri, filesToSave) = await new TestDecompiler().Decompile(jsonUri.WithExtension(LanguageConstants.LanguageFileExtension), jsonFile.EmbeddedFile.Contents); @@ -45,33 +46,31 @@ public async Task Decompiler_generates_expected_bicep_files_with_diagnostics(Emb { foreach (var (bicepFile, diagnostics) in diagnosticsByBicepFile) { - var baselineFile = baselineFolder.GetFileOrEnsureCheckedIn(bicepFile.FileHandle.Uri); + var baselineFile = baselineFiles.GetFileForPath(bicepFile.FileHandle.Uri.GetFilePath()); var bicepOutput = filesToSave[bicepFile.FileHandle.Uri]; - var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(bicepOutput, "\n", diagnostics, diag => OutputHelper.GetDiagLoggingString(bicepOutput, baselineFolder.OutputFolderPath, diag)); + var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(bicepOutput, "\n", diagnostics, diag => OutputHelper.GetDiagLoggingString(bicepOutput, baselineFiles.OutputDirectoryPath, diag)); var decompiler = new TestDecompiler(); - baselineFile.WriteToOutputFolder(sourceTextWithDiags); - baselineFile.ShouldHaveExpectedValue(); + sourceTextWithDiags.Should().MatchTextBaseline(baselineFile); } } } [DataTestMethod] - [EmbeddedFilesTestData(@"Files/Parameters/.*\.json")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/Parameters/.*\.json")] + [TestCategory(TestCategories.Baseline)] public void Decompiler_generates_expected_bicepparam_files_with_diagnostics(EmbeddedFile embeddedJson) { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, embeddedJson); - var jsonFile = baselineFolder.EntryFile; + var baselineFiles = TestContext.MaterializeBaseline(embeddedJson); + var jsonFile = baselineFiles.EntryFile; var jsonUri = IOUri.FromFilePath(jsonFile.OutputFilePath); var (entryPointUri, filesToSave) = new TestDecompiler().DecompileParameters(jsonFile.EmbeddedFile.Contents, jsonUri.WithExtension(LanguageConstants.ParamsFileExtension), null); - var baselineFile = baselineFolder.GetFileOrEnsureCheckedIn(entryPointUri); - baselineFile.WriteToOutputFolder(filesToSave[entryPointUri]); - baselineFile.ShouldHaveExpectedValue(); + var baselineFile = baselineFiles.GetFileForPath(entryPointUri.GetFilePath()); + filesToSave[entryPointUri].Should().MatchTextBaseline(baselineFile); } private static string ReadResourceFile(string resourcePath) diff --git a/src/Bicep.LangServer.IntegrationTests/Bicep.LangServer.IntegrationTests.csproj b/src/Bicep.LangServer.IntegrationTests/Bicep.LangServer.IntegrationTests.csproj index bc0b3b97eae..5a6676e9a5f 100644 --- a/src/Bicep.LangServer.IntegrationTests/Bicep.LangServer.IntegrationTests.csproj +++ b/src/Bicep.LangServer.IntegrationTests/Bicep.LangServer.IntegrationTests.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Bicep.LangServer.IntegrationTests/CodeActionTests.cs b/src/Bicep.LangServer.IntegrationTests/CodeActionTests.cs index af7e8118275..b658bb6a670 100644 --- a/src/Bicep.LangServer.IntegrationTests/CodeActionTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/CodeActionTests.cs @@ -231,8 +231,7 @@ public async Task VerifyCodeActionIsNotAvailableToSuppressCoreCompilerError() { var bicepFileContents = @"#disable-next-line BCP029 BCP068 resource test"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", bicepFileContents); - var documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); + var documentUri = DocumentUri.From(InMemoryFileResolver.GetFileUri("/path/to/main.bicep")); var uri = documentUri.ToUriEncoded(); var files = new Dictionary @@ -288,8 +287,7 @@ public async Task VerifyCodeActionIsAvailableToSuppressCoreCompilerWarning() location: 'West US' properties: vmProperties }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", bicepFileContents); - var documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); + var documentUri = DocumentUri.From(InMemoryFileResolver.GetFileUri("/path/to/main.bicep")); var uri = documentUri.ToUriEncoded(); var files = new Dictionary diff --git a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs index d2bbc212bf5..d3937c407bd 100644 --- a/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/CompletionTests.cs @@ -20,6 +20,7 @@ using Bicep.Core.TypeSystem.Types; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.FileSystem; using Bicep.Core.UnitTests.Mock; using Bicep.Core.UnitTests.Mock.Registry; @@ -135,12 +136,12 @@ public async Task EmptyFileShouldProduceDeclarationCompletions() var expected = JToken.Parse(expectedStr); - actual.Should().EqualWithJsonDiffOutput(this.TestContext, expected, GetGlobalCompletionSetPath(expectedSetName), actualLocation); + actual.Should().MatchJsonBaseline(this.TestContext, expected, GetGlobalCompletionSetPath(expectedSetName), actualLocation); } [DataTestMethod] [DynamicData(nameof(GetSnippetCompletionData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(CompletionData), DynamicDataDisplayName = nameof(CompletionData.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task ValidateSnippetCompletionAfterPlaceholderReplacements(CompletionData completionData) { string pathPrefix = $"Files/SnippetTemplates/{completionData.Prefix}"; @@ -190,7 +191,7 @@ public async Task ValidateSnippetCompletionAfterPlaceholderReplacements(Completi sourceTextWithDiags); } - sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( + sourceTextWithDiags.Should().MatchTextBaseline( TestContext, File.Exists(combinedFileName) ? (await File.ReadAllTextAsync(combinedFileName)) : string.Empty, expectedPath: combinedSourceFileName, @@ -228,7 +229,7 @@ private async Task RequestSnippetCompletion(string bicepFileName, Comple [DataTestMethod] [DynamicData(nameof(GetData), DynamicDataSourceType.Method, DynamicDataDisplayName = nameof(GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task CompletionRequestShouldProduceExpectedCompletions(DataSet dataSet, string setName, IList positions) { // ensure all files are present locally @@ -4082,7 +4083,7 @@ private void ValidateCompletions(DataSet dataSet, string setName, List<(Position _ => GetGlobalCompletionSetPath(setName) }; - actual.Should().EqualWithJsonDiffOutput(this.TestContext, expected, expectedLocation, actualLocation, "because "); + actual.Should().MatchJsonBaseline(this.TestContext, expected, expectedLocation, actualLocation, "because "); } private static string GetGlobalCompletionSetPath(string setName) => DataSet.GetBaselineUpdatePath(DataSet.TestCompletionsDirectory, GetFullSetName(setName)); diff --git a/src/Bicep.LangServer.IntegrationTests/DeployBicepFileActionTest.cs b/src/Bicep.LangServer.IntegrationTests/DeployBicepFileActionTest.cs index 9197bb4245c..db83c86e4df 100644 --- a/src/Bicep.LangServer.IntegrationTests/DeployBicepFileActionTest.cs +++ b/src/Bicep.LangServer.IntegrationTests/DeployBicepFileActionTest.cs @@ -322,7 +322,6 @@ public async Task StartDeploymentAsync_WithInvalidParameterFileContents_ReturnsD public async Task StartDeploymentAsync_WithNoParameterFile_Succeeds() { var bicepFileUri = InMemoryFileResolver.GetFileUri("/path/to/main.bicep"); - var parametersFilePath = FileHelper.SaveResultFile(TestContext, "parameters.json", "invalid_parameters_file"); var fileTextsByUri = new Dictionary() { diff --git a/src/Bicep.LangServer.IntegrationTests/GlobalUsings.cs b/src/Bicep.LangServer.IntegrationTests/GlobalUsings.cs index f09b85cb868..7bbb4d099a7 100644 --- a/src/Bicep.LangServer.IntegrationTests/GlobalUsings.cs +++ b/src/Bicep.LangServer.IntegrationTests/GlobalUsings.cs @@ -1,4 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +global using Bicep.Testing; global using Bicep.Testing.Assertions.Json; +global using Bicep.Testing.Baselines; +global using Bicep.Testing.IO; diff --git a/src/Bicep.LangServer.IntegrationTests/ImportKubernetesManifestTests.cs b/src/Bicep.LangServer.IntegrationTests/ImportKubernetesManifestTests.cs index 27493bb09d1..4e82c1b9583 100644 --- a/src/Bicep.LangServer.IntegrationTests/ImportKubernetesManifestTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/ImportKubernetesManifestTests.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Utils; using Bicep.LangServer.IntegrationTests.Assertions; using Bicep.LanguageServer.Handlers; @@ -23,14 +23,14 @@ public class ImportKubernetesManifestTests public TestContext? TestContext { get; set; } [DataTestMethod] - [EmbeddedFilesTestData(@"Files/ImportKubernetesManifest/.*/.*\.yml")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/ImportKubernetesManifest/.*/.*\.yml")] + [TestCategory(TestCategories.Baseline)] public async Task ImportKubernetesManifest_generates_valid_bicep_files_from_kubernetes_manifests(EmbeddedFile embeddedYml) { var telemetryEventsListener = new MultipleMessageListener(); - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, embeddedYml); - var yamlFile = baselineFolder.EntryFile; - var bicepFile = baselineFolder.GetFileOrEnsureCheckedIn(Path.ChangeExtension(embeddedYml.FileName, ".bicep")); + var baselineFiles = TestContext.MaterializeBaseline(embeddedYml); + var yamlFile = baselineFiles.EntryFile; + var bicepFile = baselineFiles.GetFile(Path.ChangeExtension(embeddedYml.FileName, ".bicep")); using var helper = await LanguageServerHelper.StartServer( this.TestContext, @@ -46,9 +46,9 @@ public async Task ImportKubernetesManifest_generates_valid_bicep_files_from_kube ["success"] = "true", }); - bicepFile.ShouldHaveExpectedValue(); + bicepFile.Read().Should().MatchTextBaseline(bicepFile); - CompilationHelper.Compile(bicepFile.ReadFromOutputFolder()).Should().GenerateATemplate(); + CompilationHelper.Compile(bicepFile.Read()).Should().GenerateATemplate(); } [TestMethod] diff --git a/src/Bicep.LangServer.IntegrationTests/SnippetTemplatesTests.cs b/src/Bicep.LangServer.IntegrationTests/SnippetTemplatesTests.cs index 86ad3139bcc..4cf8669898b 100644 --- a/src/Bicep.LangServer.IntegrationTests/SnippetTemplatesTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/SnippetTemplatesTests.cs @@ -8,6 +8,7 @@ using Bicep.Core.Syntax; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Utils; using Bicep.LangServer.IntegrationTests.Completions; using FluentAssertions; @@ -25,7 +26,7 @@ public class SnippetTemplatesTests [DataTestMethod] [DynamicData(nameof(GetSnippetCompletionData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(CompletionData), DynamicDataDisplayName = nameof(CompletionData.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void VerifySnippetTemplatesAreErrorFree(CompletionData completionData) { string pathPrefix = $"Files/SnippetTemplates/{completionData.Prefix}"; @@ -69,7 +70,7 @@ public void VerifySnippetTemplatesAreErrorFree(CompletionData completionData) [DataTestMethod] [DynamicData(nameof(GetSnippetCompletionData), DynamicDataSourceType.Method, DynamicDataDisplayNameDeclaringType = typeof(CompletionData), DynamicDataDisplayName = nameof(CompletionData.GetDisplayName))] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public void VerifySnippetTemplatesDoNotContainTargetScope(CompletionData completionData) { var parser = new Parser(completionData.SnippetText); diff --git a/src/Bicep.LangServer.IntegrationTests/TelemetryTests.cs b/src/Bicep.LangServer.IntegrationTests/TelemetryTests.cs index ee49c9eda93..ec313395e5d 100644 --- a/src/Bicep.LangServer.IntegrationTests/TelemetryTests.cs +++ b/src/Bicep.LangServer.IntegrationTests/TelemetryTests.cs @@ -146,8 +146,7 @@ public async Task VerifyModuleBodySnippetInsertionFiresTelemetryEvent() public async Task VerifyDisableNextLineCodeActionInvocationFiresTelemetryEvent() { var bicepFileContents = @"param storageAccount string = 'testStorageAccount'"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", bicepFileContents); - var documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); + var documentUri = DocumentUri.From(InMemoryFileResolver.GetFileUri("/path/to/main.bicep")); var uri = documentUri.ToUriEncoded(); var files = new Dictionary diff --git a/src/Bicep.LangServer.UnitTests/Bicep.LangServer.UnitTests.csproj b/src/Bicep.LangServer.UnitTests/Bicep.LangServer.UnitTests.csproj index 93a44c38b3d..e4a0442a1f1 100644 --- a/src/Bicep.LangServer.UnitTests/Bicep.LangServer.UnitTests.csproj +++ b/src/Bicep.LangServer.UnitTests/Bicep.LangServer.UnitTests.csproj @@ -23,6 +23,7 @@ + diff --git a/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileForPasteBicepParamsCommandHandlerTests.cs b/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileForPasteBicepParamsCommandHandlerTests.cs index 835caf92315..0e77a39b5ce 100644 --- a/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileForPasteBicepParamsCommandHandlerTests.cs +++ b/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileForPasteBicepParamsCommandHandlerTests.cs @@ -67,7 +67,6 @@ private async Task TestDecompileForPaste(Options options) : (string.Empty, 0); var editorContentsWithPastedJson = string.Concat(editorContents.AsSpan(0, cursorOffset), options.pastedJson, editorContents.AsSpan(cursorOffset)); - _ = FileHelper.SaveResultFile(TestContext, "main.bicep", editorContentsWithPastedJson); LanguageServerMock server = new(); var handler = CreateHandler(server); diff --git a/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileForPasteCommandHandlerTests.cs b/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileForPasteCommandHandlerTests.cs index 4a0e248db1f..c1009ffca8a 100644 --- a/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileForPasteCommandHandlerTests.cs +++ b/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileForPasteCommandHandlerTests.cs @@ -67,7 +67,6 @@ private async Task TestDecompileForPaste(Options options) : (string.Empty, 0); var editorContentsWithPastedJson = string.Concat(editorContents.AsSpan(0, cursorOffset), options.pastedJson, editorContents.AsSpan(cursorOffset)); - _ = FileHelper.SaveResultFile(TestContext, "main.bicep", editorContentsWithPastedJson); LanguageServerMock server = new(); var handler = CreateHandler(server); diff --git a/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileParamsCommandHandlerTests.cs b/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileParamsCommandHandlerTests.cs index 1760841ef03..b7844a60658 100644 --- a/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileParamsCommandHandlerTests.cs +++ b/src/Bicep.LangServer.UnitTests/Handlers/BicepDecompileParamsCommandHandlerTests.cs @@ -6,8 +6,9 @@ using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; using Bicep.Core.UnitTests.Mock; -using Bicep.Core.UnitTests.Utils; +using Bicep.LanguageServer.Extensions; using Bicep.LanguageServer.Handlers; +using Bicep.Testing.IO; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -22,9 +23,10 @@ public class BicepDecompileParamsCommandHandlerTests [NotNull] public TestContext? TestContext { get; set; } - private static BicepDecompileParamsCommandHandler CreateHandler() + private static BicepDecompileParamsCommandHandler CreateHandler(TestFileSet files) { var helper = ServiceBuilder.Create(services => services + .WithFileExplorer(files.FileExplorer) .AddSingleton(StrictMock.Of().Object) .AddSingleton()); @@ -54,12 +56,13 @@ public async Task HandleDecompileParams_WithValidParamsFile_ShouldSucceed() """; - var paramFilePath = FileHelper.SaveResultFile(TestContext, "param.json", paramFile); - var bicepPath = PathHelper.ResolvePath("./main.bicep", Path.GetDirectoryName(paramFilePath)); + var files = new InMemoryTestFileSet().AddFile("param.json", paramFile); + var paramFileUri = files.GetUri("param.json"); + var bicepFileUri = files.GetUri("main.bicep"); - var requestParams = new BicepDecompileParamsCommandParams(DocumentUri.File(paramFilePath), DocumentUri.File(bicepPath)); + var requestParams = new BicepDecompileParamsCommandParams(paramFileUri.ToDocumentUri(), bicepFileUri.ToDocumentUri()); - var decompileParamsCommandHandler = CreateHandler(); + var decompileParamsCommandHandler = CreateHandler(files); var result = await decompileParamsCommandHandler.Handle( requestParams, @@ -84,11 +87,11 @@ public async Task HandleDecompileParams_WithInValidParamsFile_ShouldFailWithErro }"; var expectedErrorMsg = "Decompilation failed. Please fix the following problems and try again: [5:10]: No value found parameter foo"; - var paramFilePath = FileHelper.SaveResultFile(TestContext, "param.json", paramFile); + var files = new InMemoryTestFileSet().AddFile("param.json", paramFile); - var requestParams = new BicepDecompileParamsCommandParams(DocumentUri.File(paramFilePath), "/main.bicep"); + var requestParams = new BicepDecompileParamsCommandParams(files.GetUri("param.json").ToDocumentUri(), "/main.bicep"); - var decompileParamsCommandHandler = CreateHandler(); + var decompileParamsCommandHandler = CreateHandler(files); var result = await decompileParamsCommandHandler.Handle( requestParams, diff --git a/src/Bicep.LangServer.UnitTests/Handlers/BicepDeploymentParametersHandlerTests.cs b/src/Bicep.LangServer.UnitTests/Handlers/BicepDeploymentParametersHandlerTests.cs index 9616a250d2c..6c24037e49b 100644 --- a/src/Bicep.LangServer.UnitTests/Handlers/BicepDeploymentParametersHandlerTests.cs +++ b/src/Bicep.LangServer.UnitTests/Handlers/BicepDeploymentParametersHandlerTests.cs @@ -11,6 +11,7 @@ using Bicep.LanguageServer; using Bicep.LanguageServer.Deploy; using Bicep.LanguageServer.Handlers; +using Bicep.Testing.IO; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; @@ -47,8 +48,8 @@ public async Task Handle_WithNoParamsInSourceFile_ShouldReturnEmptyListOfUpdated }, ""resources"": [] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); - var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, string.Empty); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); + var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, string.Empty, template, CancellationToken.None); @@ -78,7 +79,7 @@ public async Task Handle_WithUnusedParamInSourceFile_ShouldReturnUpdatedDeployme }, ""resources"": [] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, string.Empty, template, CancellationToken.None); @@ -133,7 +134,7 @@ public async Task Handle_WithOnlyDefaultValues_ShouldReturnUpdatedDeploymentPara } ] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, string.Empty, template, CancellationToken.None); @@ -197,7 +198,7 @@ param location string ""value"": ""westus"" } }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var parametersFilePath = FileHelper.SaveResultFile(TestContext, "parameters.json", parametersFileContents); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); @@ -249,7 +250,7 @@ param location string } ] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, string.Empty, template, CancellationToken.None); @@ -313,7 +314,7 @@ public async Task Handle_ParameterWithDefaultValueAndEntryInParametersFile_Shoul ""value"": ""westus"" } }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var parametersFilePath = FileHelper.SaveResultFile(TestContext, "parameters.json", parametersFileContents); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); @@ -372,8 +373,8 @@ public async Task Handle_WithParameterOfTypeObjectAndDefaultValue_ShouldReturnEm } ] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); - var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, string.Empty); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); + var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, string.Empty, template, CancellationToken.None); @@ -422,8 +423,8 @@ public async Task Handle_WithParameterOfTypeArrayAndDefaultValue_ShouldReturnEmp } ] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); - var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, string.Empty); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); + var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, string.Empty, template, CancellationToken.None); @@ -469,7 +470,7 @@ public async Task Handle_WithParameterOfTypeObjectAndNoDefaultValue_ShouldReturn } ] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, string.Empty, template, CancellationToken.None); @@ -534,7 +535,7 @@ public async Task Handle_ParameterWithDefaultValuesOfTypeExpression_ShouldReturn } ] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, string.Empty, template, CancellationToken.None); @@ -613,9 +614,9 @@ public async Task Handle_WithInvalidParametersFileContents_ShouldReturnBicepDepl ""location"": { ""value"": ""westus"" }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var parametersFilePath = FileHelper.SaveResultFile(TestContext, "parameters.json", parametersFileContents); - var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, string.Empty); + var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, parametersFilePath, template, CancellationToken.None); @@ -673,7 +674,7 @@ param location string } ] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, bicepFileContents); var result = await bicepDeploymentParametersHandler.Handle(bicepFilePath, string.Empty, template, CancellationToken.None); @@ -744,7 +745,7 @@ public async Task Handle_WithValidInput_VerifyNoEntryInDeploymentFileCompilation } ] }"; - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); var bicepCompilationManager = BicepCompilationManagerHelper.CreateCompilationManager(documentUri, bicepFileContents, true); var compilation = bicepCompilationManager.GetCompilation(documentUri)!.Compilation; @@ -776,13 +777,13 @@ public async Task Handle_WithValidInput_VerifyNoEntryInDeploymentFileCompilation [DataRow("param test ", null)] public async Task VerifyParameterType(string bicepFileContents, ParameterType? expected) { - var outputPath = FileHelper.GetUniqueTestOutputPath(TestContext); - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents, outputPath); - var compiler = new ServiceBuilder().Build().GetCompiler(); - var compilation = await compiler.CreateCompilation(PathHelper.FilePathToFileUrl(bicepFilePath).ToIOUri()); + var files = InMemoryTestFileSet.Create(("input.bicep", bicepFileContents)); + var bicepFileUri = files.GetUri("input.bicep"); + var compiler = new ServiceBuilder().WithFileExplorer(files.FileExplorer).Build().GetCompiler(); + var compilation = await compiler.CreateCompilation(bicepFileUri); var parameterSymbol = compilation.GetEntrypointSemanticModel().Binder.FileSymbol.ParameterDeclarations.Single(); - var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, string.Empty); + var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFileUri.GetFilePath(), string.Empty); var result = bicepDeploymentParametersHandler.GetParameterType(parameterSymbol); @@ -796,7 +797,7 @@ public async Task VerifyParameterType(string bicepFileContents, ParameterType? e [DataRow("some_path")] public void GetParametersInfoFromProvidedFile_WithInvalidInput_ShouldReturnNull(string parametersFilePath) { - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", string.Empty); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, string.Empty); var result = bicepDeploymentParametersHandler.GetParametersInfoFromProvidedFile(parametersFilePath); @@ -816,7 +817,7 @@ public void GetParametersInfoFromProvidedFile_WithNonArmTemplateFormatParameters } }"; var parametersFilePath = FileHelper.SaveResultFile(TestContext, "parameters.json", parametersFileContents); - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", string.Empty); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, string.Empty); var result = bicepDeploymentParametersHandler.GetParametersInfoFromProvidedFile(parametersFilePath); @@ -868,7 +869,7 @@ public void GetParametersInfoFromProvidedFile_WithArmTemplateFormatParametersFil } }"; var parametersFilePath = FileHelper.SaveResultFile(TestContext, "parameters.json", parametersFileContents); - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", string.Empty); + var bicepFilePath = TestFileUri.FromInMemoryPath("input.bicep").GetFilePath(); var bicepDeploymentParametersHandler = GetBicepDeploymentParametersHandler(bicepFilePath, string.Empty); var result = bicepDeploymentParametersHandler.GetParametersInfoFromProvidedFile(parametersFilePath); diff --git a/src/Bicep.LangServer.UnitTests/Helpers/CompilationHelperTests.cs b/src/Bicep.LangServer.UnitTests/Helpers/CompilationHelperTests.cs index e383e887745..85b55324d9e 100644 --- a/src/Bicep.LangServer.UnitTests/Helpers/CompilationHelperTests.cs +++ b/src/Bicep.LangServer.UnitTests/Helpers/CompilationHelperTests.cs @@ -6,6 +6,8 @@ using Bicep.Core.UnitTests.Assertions; using Bicep.Core.UnitTests.Utils; using Bicep.LanguageServer; +using Bicep.LanguageServer.Extensions; +using Bicep.Testing.IO; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using OmniSharp.Extensions.LanguageServer.Protocol; @@ -26,9 +28,9 @@ public async Task GetCompilation_WithNullCompilationContext_ShouldCreateCompilat name: 'dnsZone' location: 'global' }"; - string bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); - DocumentUri documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); - var bicepCompiler = ServiceBuilder.Create().GetCompiler(); + var files = InMemoryTestFileSet.Create(("input.bicep", bicepFileContents)); + DocumentUri documentUri = files.GetUri("input.bicep").ToDocumentUri(); + var bicepCompiler = new ServiceBuilder().WithFileExplorer(files.FileExplorer).Build().GetCompiler(); // Do not upsert compilation. This will cause CompilationContext to be null BicepCompilationManager bicepCompilationManager = BicepCompilationManagerHelper.CreateCompilationManager(documentUri, bicepFileContents, upsertCompilation: false); @@ -48,9 +50,9 @@ public async Task GetCompilation_WithNonNullCompilationContext_ShouldReuseCompil name: 'dnsZone' location: 'global' }"; - string bicepFilePath = FileHelper.SaveResultFile(TestContext, "input.bicep", bicepFileContents); - DocumentUri documentUri = DocumentUri.FromFileSystemPath(bicepFilePath); - var bicepCompiler = ServiceBuilder.Create().GetCompiler(); + var files = InMemoryTestFileSet.Create(("input.bicep", bicepFileContents)); + DocumentUri documentUri = files.GetUri("input.bicep").ToDocumentUri(); + var bicepCompiler = new ServiceBuilder().WithFileExplorer(files.FileExplorer).Build().GetCompiler(); // Upsert compilation. This will cause CompilationContext to be non null BicepCompilationManager bicepCompilationManager = BicepCompilationManagerHelper.CreateCompilationManager(documentUri, bicepFileContents, upsertCompilation: true); diff --git a/src/Bicep.LangServer.UnitTests/Snippets/SnippetCacheTests.cs b/src/Bicep.LangServer.UnitTests/Snippets/SnippetCacheTests.cs index 885ff210004..48f4e003d5f 100644 --- a/src/Bicep.LangServer.UnitTests/Snippets/SnippetCacheTests.cs +++ b/src/Bicep.LangServer.UnitTests/Snippets/SnippetCacheTests.cs @@ -4,7 +4,9 @@ using System.Diagnostics.CodeAnalysis; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing; +using Bicep.Testing.Baselines; +using Bicep.Testing.IO; using Bicep.LanguageServer.Snippets; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -22,16 +24,15 @@ private SnippetCacheBuilder CreateSnippetCacheBuilder() => ServiceBuilder.Create(s => s.AddSingleton()).Construct(); [TestMethod] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestCategory(TestCategories.Baseline)] public async Task Verify_snippet_cache() { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, new EmbeddedFile(typeof(SnippetCache).Assembly, "Files/SnippetCache.json")); - var baselineFile = baselineFolder.EntryFile; + var baselineFiles = TestContext.MaterializeBaseline(new EmbeddedFile(typeof(SnippetCache).Assembly, "Files/SnippetCache.json")); + var baselineFile = baselineFiles.EntryFile; var snippetCache = await CreateSnippetCacheBuilder().Build(); - baselineFile.WriteToOutputFolder(SnippetCache.Serialize(snippetCache)); - baselineFile.ShouldHaveExpectedJsonValue(); + SnippetCache.Serialize(snippetCache).Should().MatchJsonBaseline(baselineFile); // If the baseline has been updated, then verify that the FromManifest method gives us the same result. var fromManifest = SnippetCache.FromManifest(); diff --git a/src/Bicep.McpServer.UnitTests/Bicep.McpServer.UnitTests.csproj b/src/Bicep.McpServer.UnitTests/Bicep.McpServer.UnitTests.csproj index 45538d92ade..0dac7f0ff8b 100644 --- a/src/Bicep.McpServer.UnitTests/Bicep.McpServer.UnitTests.csproj +++ b/src/Bicep.McpServer.UnitTests/Bicep.McpServer.UnitTests.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs b/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs index a185d10618e..9c927ea9386 100644 --- a/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs +++ b/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Diagnostics.CodeAnalysis; +using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Utils; using Bicep.McpServer.Core; using FluentAssertions; @@ -15,25 +16,25 @@ public class BicepCompilerToolsTests [NotNull] public TestContext? TestContext { get; set; } - private static IServiceProvider GetServiceProvider() + private static BicepCompilerTools CreateTools(MockFileSystemTestFileSet files) { var services = new ServiceCollection(); + services.AddBicepMcpServer(); services - .AddBicepMcpServer(); + .WithFileSystem(files.FileSystem) + .WithFileExplorer(files.FileExplorer); - return services.BuildServiceProvider(); + return services.BuildServiceProvider().GetRequiredService(); } - private readonly BicepCompilerTools tools = GetServiceProvider().GetRequiredService(); - [TestMethod] public async Task FormatBicepFile_returns_formatted_bicep_content() { - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", """ + var files = MockFileSystemTestFileSet.Create(("main.bicep", """ param foo string - """); + """)); - var response = await tools.FormatBicepFile(bicepFilePath); + var response = await CreateTools(files).FormatBicepFile(files.GetUri("main.bicep").GetFilePath()); response.Content.Should().Contain("param foo string"); } @@ -41,23 +42,22 @@ param foo string [TestMethod] public async Task GetFileReferences_returns_referenced_files() { - var outputFolder = FileHelper.SaveResultFiles(TestContext, [ - new("main.bicep", """ + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", """ param location string """), - new("main.bicepparam", """ + ("main.bicepparam", """ using 'main.bicep' param location = loadTextContent('location.txt') """), - new("location.txt", "westus"), - new("bicepconfig.json", """ + ("location.txt", "westus"), + ("bicepconfig.json", """ { } - """), - ]); + """)); - var response = await tools.GetFileReferences(Path.Combine(outputFolder, "main.bicepparam")); + var response = await CreateTools(files).GetFileReferences(files.GetUri("main.bicepparam").GetFilePath()); response.FileUris.Select(u => u.AbsoluteUri.Split('/').Last()).Should().BeEquivalentTo([ "main.bicep", "main.bicepparam", @@ -69,12 +69,12 @@ param location string [TestMethod] public async Task BuildBicep_returns_compiled_template() { - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", """ + var files = MockFileSystemTestFileSet.Create(("main.bicep", """ param location string = 'westus' output loc string = location - """); + """)); - var response = await tools.BuildBicep(bicepFilePath); + var response = await CreateTools(files).BuildBicep(files.GetUri("main.bicep").GetFilePath()); response.Success.Should().BeTrue(); response.Template.Should().NotBeNullOrEmpty(); @@ -85,11 +85,11 @@ public async Task BuildBicep_returns_compiled_template() [TestMethod] public async Task BuildBicep_returns_diagnostics_on_error() { - var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", """ + var files = MockFileSystemTestFileSet.Create(("main.bicep", """ var foo string = 123 - """); + """)); - var response = await tools.BuildBicep(bicepFilePath); + var response = await CreateTools(files).BuildBicep(files.GetUri("main.bicep").GetFilePath()); response.Success.Should().BeFalse(); response.Template.Should().BeNull(); @@ -101,19 +101,18 @@ public async Task BuildBicep_returns_diagnostics_on_error() [TestMethod] public async Task BuildBicepparam_returns_compiled_parameters() { - var outputFolder = FileHelper.SaveResultFiles(TestContext, [ - new("main.bicep", """ + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", """ param location string output loc string = location """), - new("main.bicepparam", """ + ("main.bicepparam", """ using 'main.bicep' param location = 'westus' - """), - ]); + """)); - var response = await tools.BuildBicepparam(Path.Combine(outputFolder, "main.bicepparam")); + var response = await CreateTools(files).BuildBicepparam(files.GetUri("main.bicepparam").GetFilePath()); response.Success.Should().BeTrue(); response.Parameters.Should().NotBeNullOrEmpty(); @@ -126,18 +125,17 @@ param location string [TestMethod] public async Task BuildBicepparam_returns_diagnostics_on_error() { - var outputFolder = FileHelper.SaveResultFiles(TestContext, [ - new("main.bicep", """ + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", """ param location string """), - new("main.bicepparam", """ + ("main.bicepparam", """ using 'main.bicep' param location = 123 - """), - ]); + """)); - var response = await tools.BuildBicepparam(Path.Combine(outputFolder, "main.bicepparam")); + var response = await CreateTools(files).BuildBicepparam(files.GetUri("main.bicepparam").GetFilePath()); response.Success.Should().BeFalse(); response.Parameters.Should().BeNull(); diff --git a/src/Bicep.McpServer.UnitTests/BicepDecompilerToolsTests.cs b/src/Bicep.McpServer.UnitTests/BicepDecompilerToolsTests.cs index 8063d0469e7..ecf730cf50d 100644 --- a/src/Bicep.McpServer.UnitTests/BicepDecompilerToolsTests.cs +++ b/src/Bicep.McpServer.UnitTests/BicepDecompilerToolsTests.cs @@ -3,8 +3,9 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.Core.UnitTests.Utils; using Bicep.IO.Abstraction; using Bicep.McpServer.Core; @@ -19,21 +20,21 @@ public class BicepDecompilerToolsTests [NotNull] public TestContext? TestContext { get; set; } - private static IServiceProvider GetServiceProvider() + private static BicepDecompilerTools CreateTools(MockFileSystemTestFileSet files) { var services = new ServiceCollection(); + services.AddBicepMcpServer(); services - .AddBicepMcpServer(); + .WithFileSystem(files.FileSystem) + .WithFileExplorer(files.FileExplorer); - return services.BuildServiceProvider(); + return services.BuildServiceProvider().GetRequiredService(); } - private readonly BicepDecompilerTools tools = GetServiceProvider().GetRequiredService(); - [TestMethod] public async Task DecompileArmParametersFile_returns_bicep_parameters() { - var paramsFilePath = FileHelper.SaveResultFile(TestContext, "parameters.json", """ + var files = MockFileSystemTestFileSet.Create(("parameters.json", """ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", @@ -46,9 +47,9 @@ public async Task DecompileArmParametersFile_returns_bicep_parameters() } } } - """); + """)); - var response = await tools.DecompileArmParametersFile(paramsFilePath); + var response = await CreateTools(files).DecompileArmParametersFile(files.GetUri("parameters.json").GetFilePath()); response.FilesToSave[response.EntrypointUri].Should().Contain("param adminUsername = 'tim'"); response.FilesToSave[response.EntrypointUri].Should().Contain("param dnsLabelPrefix = 'newvm79347a'"); } @@ -56,7 +57,7 @@ public async Task DecompileArmParametersFile_returns_bicep_parameters() [TestMethod] public async Task DecompileArmTemplateFile_returns_bicep() { - var templateFilePath = FileHelper.SaveResultFile(TestContext, "template.json", """ + var files = MockFileSystemTestFileSet.Create(("template.json", """ { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", @@ -73,9 +74,9 @@ public async Task DecompileArmTemplateFile_returns_bicep() } } } - """); + """)); - var response = await tools.DecompileArmTemplateFile(templateFilePath); + var response = await CreateTools(files).DecompileArmTemplateFile(files.GetUri("template.json").GetFilePath()); response.FilesToSave[response.EntrypointUri].Should().Contain("param inputObject object"); response.FilesToSave[response.EntrypointUri].Should().Contain("output outputObject object = inputObject"); } diff --git a/src/Bicep.McpServer.UnitTests/BicepDeploymentToolsTests.cs b/src/Bicep.McpServer.UnitTests/BicepDeploymentToolsTests.cs index f9770692830..092187d9b10 100644 --- a/src/Bicep.McpServer.UnitTests/BicepDeploymentToolsTests.cs +++ b/src/Bicep.McpServer.UnitTests/BicepDeploymentToolsTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; +using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; using Bicep.Core.UnitTests.Utils; using Bicep.McpServer.Core; @@ -17,22 +18,22 @@ public class BicepDeploymentToolsTests [NotNull] public TestContext? TestContext { get; set; } - private static IServiceProvider GetServiceProvider() + private static BicepDeploymentTools CreateTools(MockFileSystemTestFileSet files) { var services = new ServiceCollection(); + services.AddBicepMcpServer(); services - .AddBicepMcpServer(); + .WithFileSystem(files.FileSystem) + .WithFileExplorer(files.FileExplorer); - return services.BuildServiceProvider(); + return services.BuildServiceProvider().GetRequiredService(); } - private readonly BicepDeploymentTools tools = GetServiceProvider().GetRequiredService(); - [TestMethod] public async Task GetDeploymentSnapshot_returns_a_valid_snapshot() { - var outputFolder = FileHelper.SaveResultFiles(TestContext, [ - new("main.bicep", """ + var files = MockFileSystemTestFileSet.Create( + ("main.bicep", """ @description('Storage Account type') param storageAccountType string = 'Standard_LRS' @@ -52,15 +53,14 @@ public async Task GetDeploymentSnapshot_returns_a_valid_snapshot() properties: {} } """), - new("main.bicepparam", """ + ("main.bicepparam", """ using './main.bicep' param location = 'eastus' - """), - ]); + """)); - var response = await tools.GetDeploymentSnapshot( - filePath: Path.Combine(outputFolder, "main.bicepparam"), + var response = await CreateTools(files).GetDeploymentSnapshot( + filePath: files.GetUri("main.bicepparam").GetFilePath(), tenantId: null, subscriptionId: "1ec1dd71-d88e-465d-95e2-4996c828833a", resourceGroup: "myRg", diff --git a/src/Bicep.McpServer.UnitTests/BicepToolsTests.cs b/src/Bicep.McpServer.UnitTests/BicepToolsTests.cs index a950dc1c106..8a87225add0 100644 --- a/src/Bicep.McpServer.UnitTests/BicepToolsTests.cs +++ b/src/Bicep.McpServer.UnitTests/BicepToolsTests.cs @@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis; using Bicep.Core; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.McpServer.Core; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -48,35 +48,33 @@ public void ListAzureResourceTypes_returns_empty_array_for_invalid_provider() } [TestMethod] - [EmbeddedFilesTestData(@"Files/GetAzResourceSchema/.*\.json")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/GetAzResourceSchema/.*\.json")] + [TestCategory(TestCategories.Baseline)] public void GetAzureResourceTypeSchema_returns_resource_schema(EmbeddedFile jsonFile) { - var baselineFile = BaselineFolder.BuildOutputFolder(TestContext, jsonFile).EntryFile; + var baselineFile = TestContext.MaterializeBaseline(jsonFile).EntryFile; var split = Path.GetFileNameWithoutExtension(jsonFile.FileName).Split("@"); var resourceType = split[0].Replace("-", "/"); var apiVersion = split[1]; var response = tools.GetAzureResourceTypeSchema(resourceType, apiVersion); - baselineFile.WriteToOutputFolder(response.Schema); - baselineFile.ShouldHaveExpectedJsonValue(); + response.Schema.Should().MatchJsonBaseline(baselineFile); } [TestMethod] - [EmbeddedFilesTestData(@"Files/GetAzResourceSchemaTrimmed/.*\.json")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/GetAzResourceSchemaTrimmed/.*\.json")] + [TestCategory(TestCategories.Baseline)] public void GetAzureResourceTypeSchema_returns_trimmed_resource_schema(EmbeddedFile jsonFile) { - var baselineFile = BaselineFolder.BuildOutputFolder(TestContext, jsonFile).EntryFile; + var baselineFile = TestContext.MaterializeBaseline(jsonFile).EntryFile; var split = Path.GetFileNameWithoutExtension(jsonFile.FileName).Split("@"); var resourceType = split[0].Replace("-", "/"); var apiVersion = split[1]; var response = tools.GetAzureResourceTypeSchema(resourceType, apiVersion, excludeDescriptions: true, excludeReadOnlyProperties: true); - baselineFile.WriteToOutputFolder(response.Schema); - baselineFile.ShouldHaveExpectedJsonValue(); + response.Schema.Should().MatchJsonBaseline(baselineFile); } [TestMethod] @@ -97,11 +95,11 @@ public async Task ListExtensionResourceTypes_returns_graph_resource_types() } [TestMethod] - [EmbeddedFilesTestData(@"Files/GetExtensionResourceSchema/.*\.json")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/GetExtensionResourceSchema/.*\.json")] + [TestCategory(TestCategories.Baseline)] public async Task GetExtensionResourceTypeSchema_returns_resource_schema(EmbeddedFile jsonFile) { - var baselineFile = BaselineFolder.BuildOutputFolder(TestContext, jsonFile).EntryFile; + var baselineFile = TestContext.MaterializeBaseline(jsonFile).EntryFile; var fileName = Path.GetFileNameWithoutExtension(jsonFile.FileName); // File name format: {repoPath}#{tag}#{resourceType}@{apiVersion} @@ -119,16 +117,15 @@ public async Task GetExtensionResourceTypeSchema_returns_resource_schema(Embedde var response = await tools.GetExtensionResourceTypeSchema(extensionReference, resourceType, apiVersion); - baselineFile.WriteToOutputFolder(response.Schema); - baselineFile.ShouldHaveExpectedJsonValue(); + response.Schema.Should().MatchJsonBaseline(baselineFile); } [TestMethod] - [EmbeddedFilesTestData(@"Files/GetExtensionResourceSchemaTrimmed/.*\.json")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/GetExtensionResourceSchemaTrimmed/.*\.json")] + [TestCategory(TestCategories.Baseline)] public async Task GetExtensionResourceTypeSchema_returns_trimmed_resource_schema(EmbeddedFile jsonFile) { - var baselineFile = BaselineFolder.BuildOutputFolder(TestContext, jsonFile).EntryFile; + var baselineFile = TestContext.MaterializeBaseline(jsonFile).EntryFile; var fileName = Path.GetFileNameWithoutExtension(jsonFile.FileName); // File name format: {repoPath}#{tag}#{resourceType}@{apiVersion} @@ -146,8 +143,7 @@ public async Task GetExtensionResourceTypeSchema_returns_trimmed_resource_schema var response = await tools.GetExtensionResourceTypeSchema(extensionReference, resourceType, apiVersion, excludeDescriptions: true, excludeReadOnlyProperties: true); - baselineFile.WriteToOutputFolder(response.Schema); - baselineFile.ShouldHaveExpectedJsonValue(); + response.Schema.Should().MatchJsonBaseline(baselineFile); } [TestMethod] diff --git a/src/Bicep.McpServer.UnitTests/GlobalUsings.cs b/src/Bicep.McpServer.UnitTests/GlobalUsings.cs index f09b85cb868..7bbb4d099a7 100644 --- a/src/Bicep.McpServer.UnitTests/GlobalUsings.cs +++ b/src/Bicep.McpServer.UnitTests/GlobalUsings.cs @@ -1,4 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +global using Bicep.Testing; global using Bicep.Testing.Assertions.Json; +global using Bicep.Testing.Baselines; +global using Bicep.Testing.IO; diff --git a/src/Bicep.McpServer.UnitTests/ServerTests.cs b/src/Bicep.McpServer.UnitTests/ServerTests.cs index 868585a082f..a7e5cc22188 100644 --- a/src/Bicep.McpServer.UnitTests/ServerTests.cs +++ b/src/Bicep.McpServer.UnitTests/ServerTests.cs @@ -7,7 +7,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing.Baselines; using Bicep.McpServer.Core; using Bicep.McpServer.UnitTests.Helpers; using FluentAssertions; @@ -25,12 +25,12 @@ public class ServerTests public TestContext? TestContext { get; set; } [TestMethod] - [EmbeddedFilesTestData(@"Files/ServerTests/tools.json")] - [TestCategory(BaselineHelper.BaselineTestCategory)] + [TestEmbeddedFileData(@"Files/ServerTests/tools.json")] + [TestCategory(TestCategories.Baseline)] public async Task List_tools_returns_full_list_of_tools(EmbeddedFile toolsJson) { - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, toolsJson); - var toolsJsonFile = baselineFolder.EntryFile; + var baselineFiles = TestContext.MaterializeBaseline(toolsJson); + var toolsJsonFile = baselineFiles.EntryFile; await using var helper = await McpServerHelper.StartServer(TestContext); var tools = await helper.Client.ListToolsAsync(); @@ -45,8 +45,7 @@ public async Task List_tools_returns_full_list_of_tools(EmbeddedFile toolsJson) .OrderByAscending(x => x.Name) .ToImmutableArray(); - toolsJsonFile.WriteStjJsonToOutputFolder(toolDefinitions); - toolsJsonFile.ShouldHaveExpectedJsonValue(); + TestJsonSerializer.Serialize(toolDefinitions).Should().MatchJsonBaseline(toolsJsonFile); } [TestMethod] diff --git a/src/Bicep.RpcClient.Tests/Bicep.RpcClient.Tests.csproj b/src/Bicep.RpcClient.Tests/Bicep.RpcClient.Tests.csproj index c2a03e6e019..b4dac9c698f 100644 --- a/src/Bicep.RpcClient.Tests/Bicep.RpcClient.Tests.csproj +++ b/src/Bicep.RpcClient.Tests/Bicep.RpcClient.Tests.csproj @@ -15,12 +15,13 @@ + - + diff --git a/src/Bicep.RpcClient.Tests/BicepClientTests.cs b/src/Bicep.RpcClient.Tests/BicepClientTests.cs index 69090fb8d56..82a0412ca31 100644 --- a/src/Bicep.RpcClient.Tests/BicepClientTests.cs +++ b/src/Bicep.RpcClient.Tests/BicepClientTests.cs @@ -4,10 +4,8 @@ using System.IO.Pipes; using System.Runtime.InteropServices; using System.Threading.Tasks; -using Bicep.Core.FileSystem; -using Bicep.Core.Registry.Oci; -using Bicep.Core.UnitTests.Utils; using Bicep.RpcClient.Helpers; +using Bicep.Testing; using FluentAssertions; using RichardSzalay.MockHttp; @@ -59,7 +57,7 @@ public async Task Download_fetches_and_installs_bicep_cli(string name, Architect { var osPlatform = OSPlatform.Create(osPlatformString); var exeSuffix = osPlatform == OSPlatform.Windows ? ".exe" : string.Empty; - var outputDir = FileHelper.GetUniqueTestOutputPath(TestContext); + var outputDir = TestContext.GetUniqueOutputPath(); MockHttpMessageHandler mockHandler = new(); mockHandler.When(HttpMethod.Get, "https://downloads.bicep.azure.com/releases/latest") @@ -96,7 +94,7 @@ public async Task Download_fetches_and_installs_bicep_cli(string name, Architect [TestMethod] public async Task Download_uses_specified_BicepVersion_without_querying_latest() { - var outputDir = FileHelper.GetUniqueTestOutputPath(TestContext); + var outputDir = TestContext.GetUniqueOutputPath(); MockHttpMessageHandler mockHandler = new(); // Any request other than the pinned-version artifact (e.g. releases/latest) should fail the test. @@ -123,7 +121,7 @@ public async Task Download_uses_specified_BicepVersion_without_querying_latest() [TestMethod] public async Task Download_skips_download_when_cli_is_already_installed() { - var outputDir = FileHelper.GetUniqueTestOutputPath(TestContext); + var outputDir = TestContext.GetUniqueOutputPath(); var existingPath = Path.Combine(outputDir, "v9.8.7", "bicep"); Directory.CreateDirectory(Path.GetDirectoryName(existingPath)!); await File.WriteAllTextAsync(existingPath, "already-installed"); @@ -149,7 +147,7 @@ public async Task Download_skips_download_when_cli_is_already_installed() [TestMethod] public async Task Download_falls_back_to_obsolete_InstallPath_when_InstallBasePath_is_not_set() { - var outputDir = FileHelper.GetUniqueTestOutputPath(TestContext); + var outputDir = TestContext.GetUniqueOutputPath(); MockHttpMessageHandler mockHandler = new(); var randomBytes = Guid.NewGuid().ToByteArray(); @@ -256,7 +254,7 @@ public void Validate_accepts_Stdio_with_ExistingCliPath() [TestMethod] public async Task Initialize_validates_path_existence() { - var nonExistentPath = FileHelper.GetUniqueTestOutputPath(TestContext); + var nonExistentPath = TestContext.GetUniqueOutputPath(); var clientFactory = new BicepClientFactory(); await FluentActions.Invoking(() => clientFactory.Initialize(new() { ExistingCliPath = nonExistentPath }, default)) .Should().ThrowAsync().WithMessage($"The specified Bicep CLI path does not exist: '{nonExistentPath}'."); @@ -308,7 +306,7 @@ public async Task GetVersion_runs_successfully() [TestMethod] public async Task Compile_runs_successfully() { - var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", """ + var bicepFile = TestContext.SaveResultFile("main.bicep", """ param location string """); @@ -330,7 +328,7 @@ public async Task Compile_runs_successfully_with_stdio() new() { ExistingCliPath = cliPath, ConnectionMode = BicepConnectionMode.Stdio }, TestContext.CancellationTokenSource.Token); - var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", """ + var bicepFile = TestContext.SaveResultFile("main.bicep", """ param location string """); @@ -344,7 +342,7 @@ param location string [TestMethod] public async Task CompileParams_runs_successfully() { - var outputPath = FileHelper.SaveResultFiles(TestContext, [ + var outputPath = TestContext.SaveResultFiles([ new("main.bicep", """ param location string """), @@ -366,7 +364,7 @@ param location string [TestMethod] public async Task Format_runs_successfully() { - var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", """ + var bicepFile = TestContext.SaveResultFile("main.bicep", """ param location string """); @@ -381,10 +379,10 @@ param location string [TestMethod] public async Task GetSnapshot_runs_successfully() { - var outputPath = FileHelper.SaveResultFiles(TestContext, [ + var outputPath = TestContext.SaveResultFiles([ new("main.bicep", """ param sku string - + resource storageaccount 'Microsoft.Storage/storageAccounts@2021-02-01' = { name: 'myStgAct' location: resourceGroup().location @@ -396,7 +394,7 @@ param sku string """), new("main.bicepparam", """ using 'main.bicep' - + param sku = 'Premium_LRS' """), ]); @@ -414,7 +412,7 @@ param sku string [TestMethod] public async Task GetMetadataResponse_runs_successfully() { - var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", """ + var bicepFile = TestContext.SaveResultFile("main.bicep", """ @export() @description('A foo object') type foo = { @@ -430,7 +428,7 @@ public async Task GetMetadataResponse_runs_successfully() [TestMethod] public async Task GetDeploymentGraph_runs_successfully() { - var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", """ + var bicepFile = TestContext.SaveResultFile("main.bicep", """ resource storageAccount 'Microsoft.Storage/storageAccounts@2021-02-01' = { name: 'myStgAct' location: 'westus' @@ -456,7 +454,7 @@ public async Task GetDeploymentGraph_runs_successfully() [TestMethod] public async Task GetFileReferences_runs_successfully() { - var outputPath = FileHelper.SaveResultFiles(TestContext, [ + var outputPath = TestContext.SaveResultFiles([ new("main.bicep", """ module mod 'mod.bicep' = { name: 'mod' diff --git a/src/Bicep.RpcClient.Tests/PooledBicepClientFactoryTests.cs b/src/Bicep.RpcClient.Tests/PooledBicepClientFactoryTests.cs index 68f5677888f..16af7523410 100644 --- a/src/Bicep.RpcClient.Tests/PooledBicepClientFactoryTests.cs +++ b/src/Bicep.RpcClient.Tests/PooledBicepClientFactoryTests.cs @@ -5,8 +5,8 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using Bicep.Core.UnitTests.Utils; using Bicep.RpcClient.Models; +using Bicep.Testing; using FluentAssertions; namespace Bicep.RpcClient.Tests; @@ -65,7 +65,7 @@ public async Task Concurrent_requests_succeed_with_multiple_wrappers() try { - var bicepFile = FileHelper.SaveResultFile(TestContext, "main.bicep", "param location string"); + var bicepFile = TestContext.SaveResultFile("main.bicep", "param location string"); var results = await Task.WhenAll(wrappers.Select(wrapper => wrapper.Compile(new CompileRequest(bicepFile), TestContext.CancellationTokenSource.Token))); diff --git a/src/Bicep.RpcClient.Tests/PublicApiTests.cs b/src/Bicep.RpcClient.Tests/PublicApiTests.cs index f78e6832d55..011d0b5ad53 100644 --- a/src/Bicep.RpcClient.Tests/PublicApiTests.cs +++ b/src/Bicep.RpcClient.Tests/PublicApiTests.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Bicep.Core.UnitTests.Assertions; -using Bicep.Core.UnitTests.Baselines; +using Bicep.Testing; +using Bicep.Testing.Baselines; +using Bicep.Testing.IO; using FluentAssertions; using PublicApiGenerator; @@ -14,22 +15,18 @@ public class PublicApiTests public TestContext TestContext { get; set; } = null!; [TestMethod] - [TestCategory(BaselineHelper.BaselineTestCategory)] - [EmbeddedFilesTestData(@"^Files\/PublicApis\/Azure.Bicep.RpcClient.txt$")] + [TestCategory(TestCategories.Baseline)] + [TestEmbeddedFileData(@"^Files\/PublicApis\/Azure.Bicep.RpcClient.txt$")] public void PublicApi_should_be_up_to_date(EmbeddedFile publicApiFile) { - // This test just asserts that the public API surface of the assembly as defined in Azure.Bicep.RpcClient.txt is up to date. - // This ensures that any changes to the public API are reviewed. - var baselineFolder = BaselineFolder.BuildOutputFolder(TestContext, publicApiFile); - var result = baselineFolder.GetFileOrEnsureCheckedIn(publicApiFile.FileName); + var baselineFiles = TestContext.MaterializeBaseline(publicApiFile); + var result = baselineFiles.GetFile(publicApiFile.FileName); var publicApi = typeof(BicepClientConfiguration).Assembly.GeneratePublicApi(); - // Normalize line endings so the baseline is consistent across Windows and Linux CI agents. publicApi = publicApi.Replace("\r\n", "\n"); - result.WriteToOutputFolder(publicApi); - result.ShouldHaveExpectedValue(); + publicApi.Should().MatchTextBaseline(result); } [TestMethod] diff --git a/src/Bicep.Testing/Baselines/BaselineAssertionsExtensions.cs b/src/Bicep.Testing/Baselines/BaselineAssertionsExtensions.cs new file mode 100644 index 00000000000..33da38338bb --- /dev/null +++ b/src/Bicep.Testing/Baselines/BaselineAssertionsExtensions.cs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Bicep.Testing.Assertions.Json; +using DiffPlex.DiffBuilder; +using DiffPlex.DiffBuilder.Model; +using FluentAssertions; +using FluentAssertions.Execution; +using FluentAssertions.Primitives; +using JsonDiffPatchDotNet; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; + +namespace Bicep.Testing.Baselines; + +public static class BaselineAssertionsExtensions +{ + public static AndConstraint MatchTextBaseline(this StringAssertions instance, BaselineFile baselineFile, string because = "", params object[] becauseArgs) + { + baselineFile.Write(instance.Subject); + + return instance.MatchTextBaseline( + baselineFile.TestContext, + baselineFile.EmbeddedFile.Contents, + baselineFile.EmbeddedFile.RelativeSourcePath, + baselineFile.OutputFilePath, + because, + becauseArgs); + } + + public static AndConstraint MatchJsonBaseline(this StringAssertions instance, BaselineFile baselineFile, string because = "", params object[] becauseArgs) + { + baselineFile.Write(instance.Subject); + + JToken.Parse(instance.Subject).Should().MatchJsonBaseline( + baselineFile.TestContext, + JToken.Parse(baselineFile.EmbeddedFile.Contents), + baselineFile.EmbeddedFile.RelativeSourcePath, + baselineFile.OutputFilePath, + because, + validateLocation: true, + becauseArgs); + + return new(instance); + } + + public static AndConstraint MatchTextBaseline(this StringAssertions instance, TestContext testContext, string expected, string expectedPath, string actualPath, string because = "", params object[] becauseArgs) + { + var lineDiff = CalculateDiff(expected, instance.Subject); + var hasNewlineDiffsOnly = lineDiff is null && !expected.Equals(instance.Subject, StringComparison.Ordinal); + var testPassed = lineDiff is null && !hasNewlineDiffsOnly; + + var isBaselineUpdate = !testPassed && BaselineUpdate.IsEnabled(testContext); + if (isBaselineUpdate) + { + BaselineUpdate.Apply(actualPath, expectedPath); + } + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .ForCondition(testPassed) + .FailWith( + BaselineUpdate.GetFailureMessage(isBaselineUpdate), + lineDiff ?? "differences in newlines only", + TestRepository.GetAbsolutePath(actualPath), + TestRepository.GetAbsolutePath(expectedPath)); + + return new(instance); + } + + public static AndConstraint MatchJsonBaseline(this JTokenAssertions instance, BaselineFile baselineFile, string because = "", params object[] becauseArgs) + { + baselineFile.Write(instance.Subject?.ToString() ?? "null"); + + return instance.MatchJsonBaseline( + baselineFile.TestContext, + JToken.Parse(baselineFile.EmbeddedFile.Contents), + baselineFile.EmbeddedFile.RelativeSourcePath, + baselineFile.OutputFilePath, + because, + validateLocation: true, + becauseArgs); + } + + public static AndConstraint MatchJsonBaseline(this JTokenAssertions instance, TestContext testContext, JToken expected, string expectedLocation, string actualLocation, string because = "", bool validateLocation = true, params object[] becauseArgs) + { + var diff = new JsonDiffPatch(new Options { TextDiff = TextDiffMode.Simple }).Diff(instance.Subject, expected); + var jsonDiff = diff?.ToString(); + var testPassed = jsonDiff is null; + + if (validateLocation) + { + var isBaselineUpdate = !testPassed && BaselineUpdate.IsEnabled(testContext); + if (isBaselineUpdate) + { + BaselineUpdate.Apply(actualLocation, expectedLocation); + } + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .ForCondition(testPassed) + .FailWith( + BaselineUpdate.GetFailureMessage(isBaselineUpdate), + jsonDiff, + TestRepository.GetAbsolutePath(actualLocation), + TestRepository.GetAbsolutePath(expectedLocation)); + } + else + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .ForCondition(testPassed) + .FailWith(jsonDiff); + } + + return new(instance); + } + + private static string? CalculateDiff(string expected, string actual, int truncate = 100) + { + var diff = InlineDiffBuilder.Diff(expected, actual); + var lineLogs = diff.Lines + .Where(line => line.Type != ChangeType.Unchanged) + .Select(line => $"[{line.Position}] {GetDiffMarker(line.Type)} {EscapeWhitespace(line.Text)}") + .Take(truncate); + + if (lineLogs.Count() >= truncate) + { + lineLogs = lineLogs.Concat(["...truncated..."]); + } + + return diff.HasDifferences ? string.Join('\n', lineLogs) : null; + } + + private static string EscapeWhitespace(string input) + => input.Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t"); + + private static string GetDiffMarker(ChangeType type) + => type switch + { + ChangeType.Inserted => "++", + ChangeType.Modified => "//", + ChangeType.Deleted => "--", + _ => " ", + }; +} diff --git a/src/Bicep.Testing/Baselines/BaselineDirectory.cs b/src/Bicep.Testing/Baselines/BaselineDirectory.cs new file mode 100644 index 00000000000..b9689733427 --- /dev/null +++ b/src/Bicep.Testing/Baselines/BaselineDirectory.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Immutable; +using Bicep.Core.Extensions; +using Bicep.Testing.Assertions; +using Bicep.Testing.IO; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Bicep.Testing.Baselines; + +public record BaselineDirectory( + string OutputDirectoryPath, + string StreamDirectoryPath, + ImmutableDictionary Files, + BaselineFile EntryFile) +{ + internal static BaselineDirectory Materialize(TestContext testContext, EmbeddedFile embeddedFile) + { + var outputDirectory = testContext.GetUniqueOutputPath(); + var baselines = embeddedFile.GetDirectoryFiles().ToImmutableDictionary( + file => file.GetPathRelativeToDirectory(embeddedFile.StreamDirectoryPath), + file => new BaselineFile( + testContext, + file, + testContext.SaveResultFile(file.GetPathRelativeToDirectory(embeddedFile.StreamDirectoryPath), file.Contents, outputDirectory))); + + return new( + outputDirectory, + embeddedFile.StreamDirectoryPath, + baselines, + baselines[embeddedFile.GetPathRelativeToDirectory(embeddedFile.StreamDirectoryPath)]); + } + + public BaselineFile? TryGetFile(string relativePath) + => Files.TryGetValue(relativePath); + + public BaselineFile GetFileForPath(string filePath) => GetFile(GetBaselineStreamRelativePath(filePath)); + + public BaselineFile GetFile(string relativePath) + { + if (TryGetFile(relativePath) is { } baseline) + { + return baseline; + } + + var embeddedFile = new EmbeddedFile( + EntryFile.EmbeddedFile.Assembly, + $"{StreamDirectoryPath}/{relativePath}"); + + var outputFile = Path.Combine(OutputDirectoryPath, relativePath); + File.WriteAllText(outputFile, ""); + + "".Should().MatchTextBaseline( + EntryFile.TestContext, + "", + expectedPath: embeddedFile.RelativeSourcePath, + actualPath: outputFile); + throw new NotImplementedException("Code cannot reach this point as the previous line will always throw"); + } + + private string GetBaselineStreamRelativePath(string filePath) + => filePath.StartsWith(OutputDirectoryPath) ? + filePath.Substring(OutputDirectoryPath.Length).Replace('\\', '/').TrimStart('/') : + throw new InvalidOperationException($"FilePath {filePath} is not a sub-path of {OutputDirectoryPath}"); +} \ No newline at end of file diff --git a/src/Bicep.Testing/Baselines/BaselineFile.cs b/src/Bicep.Testing/Baselines/BaselineFile.cs new file mode 100644 index 00000000000..bc96b04d128 --- /dev/null +++ b/src/Bicep.Testing/Baselines/BaselineFile.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Bicep.Testing.IO; + +namespace Bicep.Testing.Baselines; + +public record BaselineFile(TestContext TestContext, EmbeddedFile EmbeddedFile, string OutputFilePath) +{ + public string Read() => File.ReadAllText(OutputFilePath); + + public void Write(string contents) => File.WriteAllText(OutputFilePath, contents); +} \ No newline at end of file diff --git a/src/Bicep.Testing/Baselines/BaselineUpdate.cs b/src/Bicep.Testing/Baselines/BaselineUpdate.cs new file mode 100644 index 00000000000..963e22d443b --- /dev/null +++ b/src/Bicep.Testing/Baselines/BaselineUpdate.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Bicep.Testing.Baselines; + +internal static class BaselineUpdate +{ + private const string SetBaselineSettingName = "SetBaseLine"; + + public static bool IsEnabled(TestContext testContext) => + testContext.Properties.Contains(SetBaselineSettingName) && + string.Equals(testContext.Properties[SetBaselineSettingName] as string, bool.TrueString, StringComparison.OrdinalIgnoreCase); + + public static void Apply(string actualPath, string expectedPath) + { + actualPath = TestRepository.GetAbsolutePath(actualPath); + expectedPath = TestRepository.GetAbsolutePath(expectedPath); + + if (Path.GetDirectoryName(expectedPath) is { } parentDirectory) + { + Directory.CreateDirectory(parentDirectory); + } + + File.Copy(actualPath, expectedPath, overwrite: true); + } + + public static string GetFailureMessage(bool wasApplied) + { + var output = new StringBuilder(); + + output.Append(@" +Found diffs between actual and expected: +{0} +"); + + if (wasApplied) + { + output.Append(@" +Baseline {2} has been updated. +"); + } + else + { + output.Append(@" +View this diff with: + git diff --color-words --no-index {2} {1} +"); + + output.Append(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @" +Overwrite the single baseline: + xcopy /yq {1} {2} +" : @" +Overwrite the single baseline: + cp {1} {2} +"); + + output.Append(@" +Overwrite all baselines: + dotnet test -- --filter ""TestCategory=Baseline"" --test-parameter SetBaseLine=true + +See https://github.com/Azure/bicep/blob/main/CONTRIBUTING.md#updating-test-baselines for more information on how to fix this error. +"); + } + + return output.ToString(); + } +} diff --git a/src/Bicep.Core.UnitTests/Baselines/EmbeddedFilesTestDataAttribute.cs b/src/Bicep.Testing/Baselines/TestEmbeddedFileDataAttribute.cs similarity index 58% rename from src/Bicep.Core.UnitTests/Baselines/EmbeddedFilesTestDataAttribute.cs rename to src/Bicep.Testing/Baselines/TestEmbeddedFileDataAttribute.cs index 5770a282dda..0230a274eab 100644 --- a/src/Bicep.Core.UnitTests/Baselines/EmbeddedFilesTestDataAttribute.cs +++ b/src/Bicep.Testing/Baselines/TestEmbeddedFileDataAttribute.cs @@ -3,30 +3,25 @@ using System.Reflection; using System.Text.RegularExpressions; -using Bicep.Core.UnitTests.Assertions; +using Bicep.Testing.IO; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Bicep.Core.UnitTests.Baselines; +namespace Bicep.Testing.Baselines; [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -public sealed class EmbeddedFilesTestDataAttribute : Attribute, ITestDataSource +public sealed class TestEmbeddedFileDataAttribute(string regexFilter) : Attribute, ITestDataSource { - public EmbeddedFilesTestDataAttribute(string regexFilter) - { - RegexFilter = regexFilter; - } - - public string RegexFilter { get; } + public string RegexFilter { get; } = regexFilter; public IEnumerable GetData(MethodInfo methodInfo) { var files = EmbeddedFile.LoadAll(methodInfo.DeclaringType!.Assembly, new Regex(RegexFilter)); - var testCategories = methodInfo.GetCustomAttributes().OfType() + methodInfo.GetCustomAttributes().OfType() .Should().Contain( - x => x.TestCategories.Contains(BaselineHelper.BaselineTestCategory), - $"Expected test method to have the {BaselineHelper.BaselineTestCategory} category"); + x => x.TestCategories.Contains(TestCategories.Baseline), + $"Expected test method to have the {TestCategories.Baseline} category"); files.Should().NotBeEmpty($"Expected filter {RegexFilter} to match at least 1 file"); return files.Select(x => new object[] { x }); diff --git a/src/Bicep.Testing/Bicep.Testing.csproj b/src/Bicep.Testing/Bicep.Testing.csproj index 01890fb5389..56d565530fd 100644 --- a/src/Bicep.Testing/Bicep.Testing.csproj +++ b/src/Bicep.Testing/Bicep.Testing.csproj @@ -2,14 +2,17 @@ false + false false + + diff --git a/src/Bicep.Testing/IO/EmbeddedFile.cs b/src/Bicep.Testing/IO/EmbeddedFile.cs new file mode 100644 index 00000000000..fbe34a1b3f3 --- /dev/null +++ b/src/Bicep.Testing/IO/EmbeddedFile.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Bicep.Testing.IO; + +public record EmbeddedFile(Assembly Assembly, string StreamPath) +{ + private readonly Lazy binaryDataLazy = new(() => BinaryData.FromStream(Assembly.GetManifestResourceStream(StreamPath)!)); + private readonly Lazy contentsLazy = new(() => new StreamReader(Assembly.GetManifestResourceStream(StreamPath)!).ReadToEnd()); + + public string Contents => contentsLazy.Value; + + public BinaryData BinaryData => binaryDataLazy.Value; + + public string FileName => Path.GetFileName(StreamPath); + + public string RelativeSourcePath => Path.Combine("src", Assembly.GetName().Name!, StreamPath); + + public string StreamDirectoryPath => Path.GetDirectoryName(StreamPath)!.Replace('\\', '/'); + + public string GetPathRelativeToDirectory(string streamDirectoryPath) + => StreamPath[streamDirectoryPath.Length..].TrimStart('/'); + + public IEnumerable GetDirectoryFiles() + => LoadAll(Assembly, streamPath => streamPath.StartsWith($"{StreamDirectoryPath}/", StringComparison.Ordinal)); + + public static IEnumerable LoadAll(Assembly assembly, string streamPathPrefix, Func shouldLoad) + { + var combinedPathPrefix = $"Files/{streamPathPrefix}/"; + + return LoadAll(assembly, name => name.StartsWith(combinedPathPrefix, StringComparison.Ordinal) && shouldLoad(name)); + } + + public static IEnumerable LoadAll(Assembly assembly, Regex regex) + => LoadAll(assembly, regex.IsMatch); + + public static IEnumerable LoadAll(Assembly assembly, Func shouldLoad) + { + foreach (var streamName in assembly.GetManifestResourceNames().Where(shouldLoad)) + { + yield return new(assembly, streamName); + } + } + + public override string ToString() => StreamPath; +} \ No newline at end of file diff --git a/src/Bicep.Testing/IO/TestFileData.cs b/src/Bicep.Testing/IO/TestFileData.cs new file mode 100644 index 00000000000..ae96a20e5a3 --- /dev/null +++ b/src/Bicep.Testing/IO/TestFileData.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text; + +namespace Bicep.Testing.IO; + +public record TestFileData +{ + public static readonly TestFileData Directory = new(true); + + private readonly BinaryData? data; + + public TestFileData(BinaryData data) => this.data = data; + + public TestFileData(string text) => this.data = BinaryData.FromString(text); + + public TestFileData(string text, Encoding encoding) => this.data = BinaryData.FromBytes([.. encoding.GetPreamble(), .. encoding.GetBytes(text)]); + + private TestFileData(bool _) => this.data = null; + + public static implicit operator TestFileData(string text) => new(text); + + public static implicit operator TestFileData(BinaryData data) => new(data); + + public bool IsDirectory => this.data is null; + + public BinaryData AsBinaryData() => this.data ?? throw new InvalidOperationException("This TestFileData represents a directory, not a file."); +} \ No newline at end of file diff --git a/src/Bicep.Testing/IO/TestFileSet.cs b/src/Bicep.Testing/IO/TestFileSet.cs new file mode 100644 index 00000000000..caf47f2c249 --- /dev/null +++ b/src/Bicep.Testing/IO/TestFileSet.cs @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Bicep.Core.Extensions; +using Bicep.IO.Abstraction; + +namespace Bicep.Testing.IO; + +public abstract class TestFileSet +{ + private readonly HashSet fileUris; + + protected TestFileSet(IFileExplorer fileExplorer) + { + this.fileUris = []; + this.FileExplorer = fileExplorer; + this.FileExplorer.GetDirectory(this.GetUri("")).EnsureExists(); + } + + public IFileExplorer FileExplorer { get; } + + public TestFileSet AddDirectory(IOUri uri) + { + this.FileExplorer.GetDirectory(uri).EnsureExists(); + + return this; + } + + public TestFileSet AddDirectory(string path) => this.AddDirectory(this.GetUri(path)); + + public TestFileSet AddFile(IOUri uri, TestFileData data) + { + if (data.IsDirectory) + { + return this.AddDirectory(uri); + } + + this.FileExplorer.GetFile(uri).EnsureExists().Write(data.AsBinaryData()); + this.fileUris.Add(uri); + + return this; + } + + public TestFileSet AddFile(string path, TestFileData data) => this.AddFile(this.GetUri(path), data); + + public TestFileSet AddFiles(params (IOUri, TestFileData)[] files) + { + foreach (var (uri, data) in files) + { + this.AddFile(uri, data); + } + + return this; + } + + public TestFileSet AddFiles(params (string, TestFileData)[] files) => this.AddFiles(files.Select(x => (this.GetUri(x.Item1), x.Item2)).ToArray()); + + public TestFileSet RemoveFile(IOUri uri) + { + if (this.fileUris.Contains(uri)) + { + this.FileExplorer.GetFile(uri).Delete(); + this.fileUris.Remove(uri); + } + + return this; + } + + public TestFileSet RemoveFile(string path) => this.RemoveFile(this.GetUri(path)); + + public TestFileSet RemoveFiles(params IOUri[] uris) + { + foreach (var uri in uris) + { + this.RemoveFile(uri); + } + + return this; + } + + public TestFileSet RemoveFiles(params string[] paths) => this.RemoveFiles(paths.Select(this.GetUri).ToArray()); + + public BinaryData GetFileData(IOUri uri) => this.FileExplorer.GetFile(uri).TryReadBinaryData().Unwrap(); + + public BinaryData GetFileData(string path) => this.GetFileData(this.GetUri(path)); + + public string GetFileText(IOUri uri) => this.GetFileData(uri).ToString(); + + public string GetFileText(string path) => this.GetFileText(this.GetUri(path)); + + public TestFileSet Clear() + { + foreach (var uri in this.fileUris) + { + this.FileExplorer.GetFile(uri).Delete(); + } + + this.fileUris.Clear(); + + return this; + } + + public abstract IOUri GetUri(string path); +} \ No newline at end of file diff --git a/src/Bicep.Testing/IO/TestFileSetExtensions.cs b/src/Bicep.Testing/IO/TestFileSetExtensions.cs new file mode 100644 index 00000000000..b1bcb8e1660 --- /dev/null +++ b/src/Bicep.Testing/IO/TestFileSetExtensions.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Bicep.Testing.IO; + +public static class TestFileSetExtensions +{ + public static T AddEmbeddedFiles(this T fileSet, EmbeddedFile entryFile) + where T : TestFileSet + { + fileSet.AddFiles(entryFile.GetDirectoryFiles() + .Select(file => (file.GetPathRelativeToDirectory(entryFile.StreamDirectoryPath), (TestFileData)file.BinaryData)) + .ToArray()); + + return fileSet; + } +} \ No newline at end of file diff --git a/src/Bicep.Testing/IO/TestFileUri.cs b/src/Bicep.Testing/IO/TestFileUri.cs new file mode 100644 index 00000000000..9d86702317e --- /dev/null +++ b/src/Bicep.Testing/IO/TestFileUri.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO.Abstractions.TestingHelpers; +using Bicep.IO.Abstraction; + +namespace Bicep.Testing.IO; + +public static class TestFileUri +{ + private static readonly MockFileSystem MockFileSystem = new(); + + public static IOUri FromInMemoryPath(string path) => IOUri.FromFilePath(NormalizePath(path)); + + public static IOUri FromMockFileSystemPath(string path) => IOUri.FromFilePath(MockFileSystem.Path.GetFullPath(NormalizePath(path))); + + private static string NormalizePath(string path) => "/path/to/" + path.TrimStart('/'); +} \ No newline at end of file diff --git a/src/Bicep.Testing/README.md b/src/Bicep.Testing/README.md index bdfd3104607..e95044f5921 100644 --- a/src/Bicep.Testing/README.md +++ b/src/Bicep.Testing/README.md @@ -7,6 +7,8 @@ Typical imports are: ```csharp using Bicep.Testing; using Bicep.Testing.Assertions; +using Bicep.Testing.Baselines; +using Bicep.Testing.IO; using Bicep.Testing.Mocks; ``` @@ -35,9 +37,32 @@ using Bicep.Testing.Assertions.Json; | Validate and compare printed Bicep text | `BeValidBicepText(...)` | | Assert JSON tokens | `Bicep.Testing.Assertions.Json` | | Create strict Moq mocks | `Bicep.Testing.Mocks.StrictMock` | +| Create and attach real test output files | `TestContext.SaveResultFile(...)` | +| Populate a virtual file set from embedded resources | `fileSet.AddEmbeddedFiles(...)` | +| Materialize and assert embedded baselines | `TestContext.MaterializeBaseline(...)` | | Override the reported compiler assembly version | `TestFeatureProviderFactory.WithAssemblyVersion(...)` | | Decompile templates or parameters | `TestDecompiler` | +## Baselines + +Use `TestEmbeddedFileData` for embedded baseline test data and materialize its file set through the test context: + +```csharp +[TestMethod] +[TestCategory(TestCategories.Baseline)] +[TestEmbeddedFileData(@"Files/Scenarios/.*/main\.bicep")] +public void Produces_expected_output(EmbeddedFile inputFile) +{ + var files = TestContext.MaterializeBaseline(inputFile); + var outputFile = files.GetFile("main.json"); + + GenerateOutput(files.EntryFile.OutputFilePath) + .Should().MatchJsonBaseline(outputFile); +} +``` + +Use `MatchTextBaseline(...)` for text and `MatchJsonBaseline(...)` for JSON. Both write the actual result to `OutputFilePath`, support baseline updates, and report a diff against the checked-in embedded file. `BaselineDirectory` exposes `EntryFile`, `OutputDirectoryPath`, `GetFile(...)`, and `GetFileForPath(...)` without introducing URI conversions. + ## Compiler Recipes ### Single file without restore @@ -187,10 +212,10 @@ services ## Namespace And Naming Conventions -- Public `Test*` toolkit types live in the `Bicep.Testing` root namespace. +- Public compiler and service test toolkit types live in the `Bicep.Testing` root namespace. - `Fake*`, `Mock*`, and `Dummy*` implementations live under `Fakes`, `Mocks`, and `Dummies`. - Assertion infrastructure that is not itself a public `Test*` type lives under `Assertions`. -- In-memory and mock-file-system implementations that back `TestFileSet` live under `IO`. +- `EmbeddedFile`, `TestFileData`, `TestFileSet`, `TestFileUri`, and virtual file-set implementations live under `IO`. - Do not create a catch-all `Bicep.Testing.Utils` namespace. ## Migration Rules diff --git a/src/Bicep.Testing/TestCategories.cs b/src/Bicep.Testing/TestCategories.cs new file mode 100644 index 00000000000..3935010dd92 --- /dev/null +++ b/src/Bicep.Testing/TestCategories.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Bicep.Testing; + +public static class TestCategories +{ + public const string Baseline = "Baseline"; +} diff --git a/src/Bicep.Testing/TestContextExtensions.cs b/src/Bicep.Testing/TestContextExtensions.cs new file mode 100644 index 00000000000..3d863d03bfa --- /dev/null +++ b/src/Bicep.Testing/TestContextExtensions.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text; +using Bicep.Testing.Baselines; +using Bicep.Testing.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Bicep.Testing; + +public static class TestContextExtensions +{ + public static BaselineDirectory MaterializeBaseline(this TestContext testContext, EmbeddedFile embeddedFile) + => BaselineDirectory.Materialize(testContext, embeddedFile); + + public static string GetUniqueOutputPath(this TestContext testContext) + => Path.Combine(testContext.ResultsDirectory!, Guid.NewGuid().ToString()); + + public static string GetResultFilePath(this TestContext testContext, string fileName, string? outputPath = null) + { + var filePath = Path.Combine(outputPath ?? testContext.GetUniqueOutputPath(), fileName); + + Directory.CreateDirectory(Path.GetDirectoryName(filePath) ?? throw new AssertFailedException($"There is no directory path for file '{filePath}'.")); + testContext.AddResultFile(filePath); + + return filePath; + } + + public static string SaveResultFile(this TestContext testContext, string fileName, string contents, string? outputPath = null, Encoding? encoding = null) + { + var resultPath = testContext.SaveResultFiles([new(fileName, contents, encoding)], outputPath); + + return Path.Combine(resultPath, fileName); + } + + public static string SaveResultFiles(this TestContext testContext, TestResultFile[] files, string? outputPath = null) + { + outputPath ??= testContext.GetUniqueOutputPath(); + + foreach (var (fileName, contents, encoding) in files) + { + var filePath = testContext.GetResultFilePath(fileName, outputPath); + if (encoding is null) + { + File.WriteAllText(filePath, contents); + } + else + { + File.WriteAllText(filePath, contents, encoding); + } + } + + return outputPath; + } +} diff --git a/src/Bicep.Testing/TestFileData.cs b/src/Bicep.Testing/TestFileData.cs deleted file mode 100644 index 1b4dd9fcc92..00000000000 --- a/src/Bicep.Testing/TestFileData.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text; - -namespace Bicep.Testing -{ - public record TestFileData - { - public static readonly TestFileData Directory = new(true); - - private readonly BinaryData? data; - - public TestFileData(BinaryData data) => this.data = data; - - public TestFileData(string text) => this.data = BinaryData.FromString(text); - - public TestFileData(string text, Encoding encoding) => this.data = BinaryData.FromBytes([.. encoding.GetPreamble(), .. encoding.GetBytes(text)]); - - private TestFileData(bool _) => this.data = null; - - public static implicit operator TestFileData(string text) => new(text); - - public static implicit operator TestFileData(BinaryData data) => new(data); - - public bool IsDirectory => this.data is null; - - public BinaryData AsBinaryData() => this.data ?? throw new InvalidOperationException("This TestFileData represents a directory, not a file."); - } -} diff --git a/src/Bicep.Testing/TestFileSet.cs b/src/Bicep.Testing/TestFileSet.cs deleted file mode 100644 index 1614dc044e1..00000000000 --- a/src/Bicep.Testing/TestFileSet.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Bicep.Core.Extensions; -using Bicep.IO.Abstraction; - -namespace Bicep.Testing -{ - public abstract class TestFileSet - { - private readonly HashSet fileUris; - - protected TestFileSet(IFileExplorer fileExplorer) - { - this.fileUris = []; - this.FileExplorer = fileExplorer; - this.FileExplorer.GetDirectory(this.GetUri("")).EnsureExists(); - } - - public IFileExplorer FileExplorer { get; } - - public TestFileSet AddDirectory(IOUri uri) - { - this.FileExplorer.GetDirectory(uri).EnsureExists(); - - return this; - } - - public TestFileSet AddDirectory(string path) => this.AddDirectory(this.GetUri(path)); - - public TestFileSet AddFile(IOUri uri, TestFileData data) - { - if (data.IsDirectory) - { - return this.AddDirectory(uri); - } - - this.FileExplorer.GetFile(uri).EnsureExists().Write(data.AsBinaryData()); - this.fileUris.Add(uri); - - return this; - } - - public TestFileSet AddFile(string path, TestFileData data) => this.AddFile(this.GetUri(path), data); - - public TestFileSet AddFiles(params (IOUri, TestFileData)[] files) - { - foreach (var (uri, data) in files) - { - this.AddFile(uri, data); - } - - return this; - } - - public TestFileSet AddFiles(params (string, TestFileData)[] files) => this.AddFiles(files.Select(x => (this.GetUri(x.Item1), x.Item2)).ToArray()); - - public TestFileSet RemoveFile(IOUri uri) - { - if (this.fileUris.Contains(uri)) - { - this.FileExplorer.GetFile(uri).Delete(); - this.fileUris.Remove(uri); - } - - return this; - } - - public TestFileSet RemoveFile(string path) => this.RemoveFile(this.GetUri(path)); - - public TestFileSet RemoveFiles(params IOUri[] uris) - { - foreach (var uri in uris) - { - this.RemoveFile(uri); - } - - return this; - } - - public TestFileSet RemoveFiles(params string[] paths) => this.RemoveFiles(paths.Select(this.GetUri).ToArray()); - - public BinaryData GetFileData(IOUri uri) => this.FileExplorer.GetFile(uri).TryReadBinaryData().Unwrap(); - - public BinaryData GetFileData(string path) => this.GetFileData(this.GetUri(path)); - - public string GetFileText(IOUri uri) => this.GetFileData(uri).ToString(); - - public string GetFileText(string path) => this.GetFileText(this.GetUri(path)); - - public TestFileSet Clear() - { - foreach (var uri in this.fileUris) - { - this.FileExplorer.GetFile(uri).Delete(); - } - - this.fileUris.Clear(); - - return this; - } - - public abstract IOUri GetUri(string path); - } -} diff --git a/src/Bicep.Testing/TestFileUri.cs b/src/Bicep.Testing/TestFileUri.cs deleted file mode 100644 index a3a9f33180b..00000000000 --- a/src/Bicep.Testing/TestFileUri.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.IO.Abstractions.TestingHelpers; -using Bicep.IO.Abstraction; - -namespace Bicep.Testing -{ - public static class TestFileUri - { - private static readonly MockFileSystem MockFileSystem = new(); - - public static IOUri FromInMemoryPath(string path) => IOUri.FromFilePath(NormalizePath(path)); - - public static IOUri FromMockFileSystemPath(string path) => IOUri.FromFilePath(MockFileSystem.Path.GetFullPath(NormalizePath(path))); - - // Prepend "/path/to" to enable use of ".." in tests for convenience. - private static string NormalizePath(string path) => "/path/to/" + path.TrimStart('/'); - } -} diff --git a/src/Bicep.Testing/TestJsonSerializer.cs b/src/Bicep.Testing/TestJsonSerializer.cs new file mode 100644 index 00000000000..b5cfefcb66e --- /dev/null +++ b/src/Bicep.Testing/TestJsonSerializer.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Bicep.Testing; + +public static class TestJsonSerializer +{ + private static readonly JsonSerializerOptions Options = new() + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + + public static string Serialize(T value) => JsonSerializer.Serialize(value, Options); +} diff --git a/src/Bicep.Testing/TestRepository.cs b/src/Bicep.Testing/TestRepository.cs new file mode 100644 index 00000000000..f7c80a79e74 --- /dev/null +++ b/src/Bicep.Testing/TestRepository.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Bicep.Testing; + +public static class TestRepository +{ + private static readonly string RootPath = FindRootPath(); + + public static string GetAbsolutePath(string path) => Path.GetFullPath(path, RootPath); + + private static string FindRootPath() + { + var currentDirectory = new DirectoryInfo(Environment.CurrentDirectory); + + while (currentDirectory.Parent is { } parentDirectory) + { + if (Directory.Exists(Path.Join(currentDirectory.FullName, ".git"))) + { + return Environment.GetEnvironmentVariable("TF_BUILD") is not null + ? Path.Join(currentDirectory.FullName, "bicep") + : currentDirectory.FullName; + } + + currentDirectory = parentDirectory; + } + + throw new InvalidOperationException($"Unable to determine the repository root path from directory {Environment.CurrentDirectory}"); + } +} diff --git a/src/Bicep.Testing/TestResultFile.cs b/src/Bicep.Testing/TestResultFile.cs new file mode 100644 index 00000000000..ff1fba33365 --- /dev/null +++ b/src/Bicep.Testing/TestResultFile.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text; + +namespace Bicep.Testing; + +public record TestResultFile(string FileName, string Contents, Encoding? Encoding = null);