| File: JavaPolyglotApphostDirectoryTests.cs | Web Access |
| Project: src\tests\Aspire.Cli.EndToEnd.Tests\Aspire.Cli.EndToEnd.Tests.csproj (Aspire.Cli.EndToEnd.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Cli.EndToEnd.Tests.Helpers; using Aspire.Cli.Resources; using Hex1b.Automation; using Xunit; namespace Aspire.Cli.EndToEnd.Tests; /// <summary> /// Regression coverage for https://github.com/microsoft/aspire/issues/16513. /// Polyglot (non-.NET) AppHosts run under a generic host process named "aspire-managed", /// so without the env-var-based AppHostPath wiring in <c>AtsJavaCodeGenerator</c> the AppHost /// reports its path as "<dir>/aspire-managed" and <c>aspire <cmd> --apphost /// <directory></c> cannot match the running guest AppHost via its backchannel socket. /// This test duplicates the JavaPolyglotTests setup but starts the AppHost in the background /// and then issues <c>aspire stop --non-interactive --apphost <directory></c> from an /// unrelated cwd so the stop command is forced to resolve the AppHost via the path the host /// reported over the backchannel. /// Each test class runs as a separate CI job for parallelization. /// </summary> public sealed class JavaPolyglotApphostDirectoryTests(ITestOutputHelper output) { [Fact] [CaptureWorkspaceOnFailure] public async Task StopJavaPolyglotAppHostUsingApphostDirectory() { var repoRoot = CliE2ETestHelpers.GetRepoRoot(); var strategy = CliInstallStrategy.Detect(output.WriteLine); var workspace = TemporaryWorkspace.Create(output); using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, strategy, output, variant: CliE2ETestHelpers.DockerfileVariant.PolyglotJava, mountDockerSocket: true, workspace: workspace); var counter = new SequenceCounter(); var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500)); await using var terminalRun = CliE2ETestHelpers.StartRun(terminal, workspace, auto, counter, output, TestContext.Current.CancellationToken); await auto.PrepareDockerEnvironmentAsync(counter, workspace); await auto.InstallAspireCliAsync(strategy, counter); await auto.EnableExperimentalJavaSupportAsync(counter); // Put the AppHost in a subdirectory so we can `cd ..` away from it and then resolve it // back via `--apphost javaapp`. That forces AppHost discovery through the path the // running host reported over the backchannel (the bug scenario from issue 16513), // rather than through the current working directory. await auto.TypeAsync("mkdir javaapp && cd javaapp"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); await auto.TypeAsync("aspire init"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("Which language would you like to use?", timeout: TimeSpan.FromSeconds(30)); await auto.DownAsync(); await auto.DownAsync(); await auto.WaitUntilTextAsync("> Java", timeout: TimeSpan.FromSeconds(5)); await auto.EnterAsync(); await auto.WaitUntilTextAsync("Created AppHost.java", timeout: TimeSpan.FromMinutes(2)); await auto.DeclineAgentInitPromptAsync(counter); await auto.TypeAsync("npm create -y vite@latest viteapp -- --template vanilla-ts --no-interactive"); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter, TimeSpan.FromMinutes(2)); await auto.TypeAsync("cd viteapp && npm install && cd .."); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter, TimeSpan.FromMinutes(2)); await auto.TypeAsync("aspire add Aspire.Hosting.JavaScript"); await auto.EnterAsync(); await auto.WaitUntilTextAsync("The package Aspire.Hosting.", timeout: TimeSpan.FromMinutes(2)); await auto.WaitForSuccessPromptAsync(counter); var appHostPath = Path.Combine(workspace.WorkspaceRoot.FullName, "javaapp", "AppHost.java"); var newContent = """ import aspire.*; void main(String[] args) throws Exception { var builder = DistributedApplication.CreateBuilder(args); ViteAppResource viteApp = builder.addViteApp("viteapp", "./viteapp"); builder.build().run(); } """; File.WriteAllText(appHostPath, newContent); // Start the AppHost in the background (returns to the prompt once the AppHost reports it // has started over the backchannel). await auto.TypeAsync("aspire start"); await auto.EnterAsync(); await auto.WaitUntilTextAsync(RunCommandStrings.AppHostStartedSuccessfully, timeout: TimeSpan.FromMinutes(3)); await auto.WaitForSuccessPromptAsync(counter); await auto.ClearScreenAsync(counter); // Leave the AppHost directory so the only way `aspire stop` can find the running guest // AppHost is via the AppHostPath it reported over the backchannel. Without the env-var // fix, the reported path ends in "aspire-managed" and this stop call would fail with // "AppHost not running at path". await auto.TypeAsync("cd .."); await auto.EnterAsync(); await auto.WaitForSuccessPromptAsync(counter); await auto.TypeAsync("aspire stop --non-interactive --apphost javaapp"); await auto.EnterAsync(); await auto.WaitUntilAppHostStoppedSuccessfullyAsync(timeout: TimeSpan.FromMinutes(1)); await auto.WaitForSuccessPromptAsync(counter); } }