|
| 1 | +// Licensed to the .NET Foundation under one or more agreements. |
| 2 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 3 | + |
| 4 | +using Aspire.Cli.EndToEnd.Tests.Helpers; |
| 5 | +using Aspire.Cli.Tests.Utils; |
| 6 | +using Hex1b.Automation; |
| 7 | +using Xunit; |
| 8 | + |
| 9 | +namespace Aspire.Cli.EndToEnd.Tests; |
| 10 | + |
| 11 | +/// <summary> |
| 12 | +/// End-to-end tests for the aspire otel logs command with structured logs. |
| 13 | +/// Each test class runs as a separate CI job for parallelization. |
| 14 | +/// </summary> |
| 15 | +public sealed class OtelLogsTests(ITestOutputHelper output) |
| 16 | +{ |
| 17 | + [Fact] |
| 18 | + [CaptureWorkspaceOnFailure] |
| 19 | + public async Task OtelLogsReturnsStructuredLogsFromStarterApp() |
| 20 | + { |
| 21 | + var repoRoot = CliE2ETestHelpers.GetRepoRoot(); |
| 22 | + var installMode = CliE2ETestHelpers.DetectDockerInstallMode(repoRoot); |
| 23 | + |
| 24 | + using var workspace = TemporaryWorkspace.Create(output); |
| 25 | + |
| 26 | + using var terminal = CliE2ETestHelpers.CreateDockerTestTerminal(repoRoot, installMode, output, mountDockerSocket: true, workspace: workspace); |
| 27 | + |
| 28 | + var pendingRun = terminal.RunAsync(TestContext.Current.CancellationToken); |
| 29 | + |
| 30 | + var counter = new SequenceCounter(); |
| 31 | + var auto = new Hex1bTerminalAutomator(terminal, defaultTimeout: TimeSpan.FromSeconds(500)); |
| 32 | + |
| 33 | + await auto.PrepareDockerEnvironmentAsync(counter, workspace); |
| 34 | + await auto.InstallAspireCliInDockerAsync(installMode, counter); |
| 35 | + |
| 36 | + // Create a new Starter project (includes an ASP.NET Core apiservice) |
| 37 | + await auto.AspireNewAsync("AspireOtelLogsApp", counter); |
| 38 | + |
| 39 | + // Navigate to the AppHost directory |
| 40 | + await auto.TypeAsync("cd AspireOtelLogsApp/AspireOtelLogsApp.AppHost"); |
| 41 | + await auto.EnterAsync(); |
| 42 | + await auto.WaitForSuccessPromptAsync(counter); |
| 43 | + |
| 44 | + // Start the AppHost in the background |
| 45 | + await auto.AspireStartAsync(counter); |
| 46 | + |
| 47 | + // Wait for the apiservice resource to be running before querying logs |
| 48 | + await auto.TypeAsync("aspire wait apiservice --status up --timeout 300"); |
| 49 | + await auto.EnterAsync(); |
| 50 | + await auto.WaitUntilTextAsync("is up (running).", timeout: TimeSpan.FromMinutes(6)); |
| 51 | + await auto.WaitForSuccessPromptAsync(counter); |
| 52 | + |
| 53 | + // Run aspire otel logs and capture output to a file |
| 54 | + await auto.TypeAsync("aspire otel logs > otel_logs.txt 2>&1"); |
| 55 | + await auto.EnterAsync(); |
| 56 | + await auto.WaitForSuccessPromptAsync(counter); |
| 57 | + |
| 58 | + // Verify the output contains structured log entries with apiservice content |
| 59 | + await auto.TypeAsync("cat otel_logs.txt | head -20"); |
| 60 | + await auto.EnterAsync(); |
| 61 | + await auto.WaitForSuccessPromptAsync(counter); |
| 62 | + |
| 63 | + // Assert structured logs are present and contain apiservice entries |
| 64 | + await auto.TypeAsync("if [ ! -r otel_logs.txt ]; then echo 'OTEL_LOGS_FILE_UNREADABLE'; elif grep -q 'apiservice' otel_logs.txt; then echo 'STRUCTURED_LOGS_PRESENT'; else echo 'STRUCTURED_LOGS_MISSING'; fi"); |
| 65 | + await auto.EnterAsync(); |
| 66 | + await auto.WaitUntilTextAsync("STRUCTURED_LOGS_PRESENT", timeout: TimeSpan.FromSeconds(10)); |
| 67 | + await auto.WaitForAnyPromptAsync(counter); |
| 68 | + |
| 69 | + // Also verify JSON format works and contains structured data |
| 70 | + await auto.TypeAsync("aspire otel logs --format json > otel_logs_json.txt 2>&1"); |
| 71 | + await auto.EnterAsync(); |
| 72 | + await auto.WaitForSuccessPromptAsync(counter); |
| 73 | + |
| 74 | + // Verify JSON output contains resourceLogs key |
| 75 | + await auto.TypeAsync("grep -q 'resourceLogs' otel_logs_json.txt && echo 'JSON_STRUCTURED_LOGS_PRESENT' || echo 'JSON_STRUCTURED_LOGS_MISSING'"); |
| 76 | + await auto.EnterAsync(); |
| 77 | + await auto.WaitUntilTextAsync("JSON_STRUCTURED_LOGS_PRESENT", timeout: TimeSpan.FromSeconds(10)); |
| 78 | + await auto.WaitForAnyPromptAsync(counter); |
| 79 | + |
| 80 | + // Stop the AppHost |
| 81 | + await auto.AspireStopAsync(counter); |
| 82 | + |
| 83 | + // Exit the shell |
| 84 | + await auto.TypeAsync("exit"); |
| 85 | + await auto.EnterAsync(); |
| 86 | + |
| 87 | + await pendingRun; |
| 88 | + } |
| 89 | +} |
0 commit comments