From 4ff901dbf2cfeac10ad3e288cc77c46884cc54ab Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 12 Nov 2026 15:55:10 +1100 Subject: [PATCH 1/2] test: diagnose SQL polyglot restore failures Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../GoAppHostTests.cs | 4 +- .../SqlPolyglotRestoreDiagnostics.cs | 48 +++++++++++++++++++ .../TypeScriptAppHostTests.cs | 4 +- 3 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotRestoreDiagnostics.cs diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/GoAppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/GoAppHostTests.cs index 140772e41..d0fcf1ba3 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/GoAppHostTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/GoAppHostTests.cs @@ -7,11 +7,11 @@ public class GoAppHostTests [Fact] public async Task GoAppHostCompiles() { - await GoAppHostTest.Run( + await SqlPolyglotRestoreDiagnostics.Run("CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost.Go", () => GoAppHostTest.Run( appHostProject: "CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost.Go", exampleName: "sql-database-projects", // The prebuilt Windows AppHost server fails to load hostfxr for MSBuildLocator. validatePublish: !OperatingSystem.IsWindows(), - cancellationToken: TestContext.Current.CancellationToken); + cancellationToken: TestContext.Current.CancellationToken)); } } diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotRestoreDiagnostics.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotRestoreDiagnostics.cs new file mode 100644 index 000000000..95f33f743 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotRestoreDiagnostics.cs @@ -0,0 +1,48 @@ +using System.Text.Json; +using CommunityToolkit.Aspire.Testing; +using Xunit.Sdk; + +namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests; + +internal static class SqlPolyglotRestoreDiagnostics +{ + public static async Task Run(string appHostProject, Func test) + { + try + { + await test(); + } + catch (XunitException original) + { + string appHostDirectory = Path.GetFullPath(Path.Combine("..", "..", "..", "..", "..", "examples", "sql-database-projects", appHostProject)); + string integrationDirectory = Path.Combine(appHostDirectory, ".aspire", "integrations", "apphosts"); + if (!Directory.Exists(integrationDirectory)) + { + throw; + } + + string? project = Directory.EnumerateFiles(integrationDirectory, "IntegrationRestore.csproj", SearchOption.AllDirectories).FirstOrDefault(); + if (project is null) + { + throw; + } + + using JsonDocument config = JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(appHostDirectory, "aspire.config.json"))); + string? version = config.RootElement.GetProperty("sdk").GetProperty("version").GetString(); + try + { + await ProcessTestUtilities.RunProcessAsync( + "dotnet", + ["build", project, $"-p:AspireIntegrationHostingVersion={version}"], + appHostDirectory, + TestContext.Current.CancellationToken); + } + catch (XunitException diagnostic) + { + throw new XunitException($"Original test failure:{Environment.NewLine}{original.Message}{Environment.NewLine}Generated integration build diagnostics:{Environment.NewLine}{diagnostic.Message}"); + } + + throw; + } + } +} diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/TypeScriptAppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/TypeScriptAppHostTests.cs index 27163b8c9..0be7fd879 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/TypeScriptAppHostTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/TypeScriptAppHostTests.cs @@ -9,11 +9,11 @@ public class TypeScriptAppHostTests [Fact] public async Task TypeScriptAppHostCompilesAndStarts() { - await TypeScriptAppHostTest.Run( + await SqlPolyglotRestoreDiagnostics.Run("CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost.TypeScript", () => TypeScriptAppHostTest.Run( appHostProject: "CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost.TypeScript", packageName: "CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects", exampleName: "sql-database-projects", waitForResources: [], - cancellationToken: TestContext.Current.CancellationToken); + cancellationToken: TestContext.Current.CancellationToken)); } } \ No newline at end of file From 6c6b55997c163ecf06dc2b1baf7898f810b9ef0f Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 12 Nov 2026 16:05:08 +1100 Subject: [PATCH 2/2] fix: isolate SQL polyglot tests from MSBuild locator Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../GoAppHostTests.cs | 2 +- .../SqlPolyglotAppHostTest.cs | 35 ++++++++++++++ .../SqlPolyglotAppHostTestTests.cs | 40 ++++++++++++++++ .../SqlPolyglotRestoreDiagnostics.cs | 48 ------------------- .../TypeScriptAppHostTests.cs | 2 +- 5 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotAppHostTest.cs create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotAppHostTestTests.cs delete mode 100644 tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotRestoreDiagnostics.cs diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/GoAppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/GoAppHostTests.cs index d0fcf1ba3..1dd58489c 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/GoAppHostTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/GoAppHostTests.cs @@ -7,7 +7,7 @@ public class GoAppHostTests [Fact] public async Task GoAppHostCompiles() { - await SqlPolyglotRestoreDiagnostics.Run("CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost.Go", () => GoAppHostTest.Run( + await SqlPolyglotAppHostTest.Run(() => GoAppHostTest.Run( appHostProject: "CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost.Go", exampleName: "sql-database-projects", // The prebuilt Windows AppHost server fails to load hostfxr for MSBuildLocator. diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotAppHostTest.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotAppHostTest.cs new file mode 100644 index 000000000..e74659b20 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotAppHostTest.cs @@ -0,0 +1,35 @@ +namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests; + +internal static class SqlPolyglotAppHostTest +{ + private static readonly string[] s_msbuildEnvironmentVariables = + [ + "MSBUILD_EXE_PATH", + "MSBuildExtensionsPath", + "MSBuildSDKsPath" + ]; + + public static async Task Run(Func test) + { + // MSBuildLocator in other SQL tests configures in-process MSBuild; child dotnet builds + // must select their own SDK instead of inheriting those paths. + Dictionary originalValues = s_msbuildEnvironmentVariables.ToDictionary( + name => name, Environment.GetEnvironmentVariable); + try + { + foreach (string name in s_msbuildEnvironmentVariables) + { + Environment.SetEnvironmentVariable(name, null); + } + + await test(); + } + finally + { + foreach ((string name, string? value) in originalValues) + { + Environment.SetEnvironmentVariable(name, value); + } + } + } +} diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotAppHostTestTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotAppHostTestTests.cs new file mode 100644 index 000000000..5d0eb94af --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotAppHostTestTests.cs @@ -0,0 +1,40 @@ +namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests; + +public class SqlPolyglotAppHostTestTests +{ + [Fact] + public async Task RestoresMSBuildEnvironmentAfterFailure() + { + string[] variables = ["MSBUILD_EXE_PATH", "MSBuildExtensionsPath", "MSBuildSDKsPath"]; + Dictionary originalValues = variables.ToDictionary(name => name, Environment.GetEnvironmentVariable); + try + { + foreach (string variable in variables) + { + Environment.SetEnvironmentVariable(variable, "test-sdk-path"); + } + + await Assert.ThrowsAsync(() => SqlPolyglotAppHostTest.Run(() => + { + foreach (string variable in variables) + { + Assert.Null(Environment.GetEnvironmentVariable(variable)); + } + + throw new InvalidOperationException(); + })); + + foreach (string variable in variables) + { + Assert.Equal("test-sdk-path", Environment.GetEnvironmentVariable(variable)); + } + } + finally + { + foreach ((string variable, string? value) in originalValues) + { + Environment.SetEnvironmentVariable(variable, value); + } + } + } +} diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotRestoreDiagnostics.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotRestoreDiagnostics.cs deleted file mode 100644 index 95f33f743..000000000 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/SqlPolyglotRestoreDiagnostics.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Text.Json; -using CommunityToolkit.Aspire.Testing; -using Xunit.Sdk; - -namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests; - -internal static class SqlPolyglotRestoreDiagnostics -{ - public static async Task Run(string appHostProject, Func test) - { - try - { - await test(); - } - catch (XunitException original) - { - string appHostDirectory = Path.GetFullPath(Path.Combine("..", "..", "..", "..", "..", "examples", "sql-database-projects", appHostProject)); - string integrationDirectory = Path.Combine(appHostDirectory, ".aspire", "integrations", "apphosts"); - if (!Directory.Exists(integrationDirectory)) - { - throw; - } - - string? project = Directory.EnumerateFiles(integrationDirectory, "IntegrationRestore.csproj", SearchOption.AllDirectories).FirstOrDefault(); - if (project is null) - { - throw; - } - - using JsonDocument config = JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(appHostDirectory, "aspire.config.json"))); - string? version = config.RootElement.GetProperty("sdk").GetProperty("version").GetString(); - try - { - await ProcessTestUtilities.RunProcessAsync( - "dotnet", - ["build", project, $"-p:AspireIntegrationHostingVersion={version}"], - appHostDirectory, - TestContext.Current.CancellationToken); - } - catch (XunitException diagnostic) - { - throw new XunitException($"Original test failure:{Environment.NewLine}{original.Message}{Environment.NewLine}Generated integration build diagnostics:{Environment.NewLine}{diagnostic.Message}"); - } - - throw; - } - } -} diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/TypeScriptAppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/TypeScriptAppHostTests.cs index 0be7fd879..fb689b2fc 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/TypeScriptAppHostTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/TypeScriptAppHostTests.cs @@ -9,7 +9,7 @@ public class TypeScriptAppHostTests [Fact] public async Task TypeScriptAppHostCompilesAndStarts() { - await SqlPolyglotRestoreDiagnostics.Run("CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost.TypeScript", () => TypeScriptAppHostTest.Run( + await SqlPolyglotAppHostTest.Run(() => TypeScriptAppHostTest.Run( appHostProject: "CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.AppHost.TypeScript", packageName: "CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects", exampleName: "sql-database-projects",