Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/tests-daily-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ jobs:
--filter-not-trait "outerloop=true" \
--hangdump --hangdump-type none --hangdump-timeout 15m

- name: Verify Aspire CLI versions match
shell: pwsh
run: ./eng/scripts/verify-daily-smoke-cli-versions.ps1 -VersionsDir (Join-Path $env:GITHUB_WORKSPACE 'testresults/cli-versions')

- name: Upload logs and test results
id: upload-logs
if: always()
Expand Down
124 changes: 124 additions & 0 deletions eng/scripts/verify-daily-smoke-cli-versions.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Licensed to the .NET Foundation under one or more agreements.
# The .NET Foundation licenses this file to you under the MIT license.

[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$VersionsDir,

[string]$StepSummaryPath = $env:GITHUB_STEP_SUMMARY
)

$ErrorActionPreference = 'Stop'

if (-not (Test-Path -LiteralPath $VersionsDir -PathType Container)) {
Write-Host "::error::Aspire CLI version records directory was not created: $VersionsDir"
exit 1
}

$versionFiles = @(Get-ChildItem -LiteralPath $VersionsDir -Filter '*.env' -File)

if ($versionFiles.Count -eq 0) {
Write-Host '::error::No Aspire CLI version records were produced.'
exit 1
}

function Read-CliVersionRecord {
param([System.IO.FileInfo]$File)

$values = @{}
foreach ($line in Get-Content -LiteralPath $File.FullName) {
$separatorIndex = $line.IndexOf('=')
if ($separatorIndex -lt 0) {
continue
}

$key = $line.Substring(0, $separatorIndex)
if ([string]::IsNullOrWhiteSpace($key)) {
continue
}

$values[$key] = $line.Substring($separatorIndex + 1)
}

$testName = $values['test']
$mode = $values['mode']
$strategy = $values['strategy']
$version = $values['version']

$route = if (-not [string]::IsNullOrWhiteSpace($strategy)) {
$strategy
}
elseif (-not [string]::IsNullOrWhiteSpace($mode)) {
$mode
}
else {
'unknown install route'
}

if ([string]::IsNullOrWhiteSpace($testName)) {
$testName = $File.Name
}

$versionLabel = if ([string]::IsNullOrWhiteSpace($version)) {
'(missing version)'
}
else {
$version
}

[pscustomobject]@{
Test = $testName
Mode = $mode
Strategy = $strategy
Version = $version
Route = $route
Description = "$route | $testName | $versionLabel"
}
}

function Write-FailureSummary {
param([object[]]$Records)

if ([string]::IsNullOrWhiteSpace($StepSummaryPath)) {
return
}

$summaryLines = @(
'## Aspire CLI version consistency check failed'
''
'All daily smoke install routes should test the same Aspire CLI version.'
''
'### Version records'
''
)

foreach ($record in $Records) {
$summaryLines += "- ``$($record.Description)``"
}

Add-Content -LiteralPath $StepSummaryPath -Value ($summaryLines -join [Environment]::NewLine)
}

$records = @($versionFiles | ForEach-Object { Read-CliVersionRecord $_ })
$missingVersionRecords = @($records | Where-Object { [string]::IsNullOrWhiteSpace($_.Version) })

if ($missingVersionRecords.Count -gt 0) {
Write-Host '::error::Some Aspire CLI version records did not include a version.'
$missingVersionRecords | ForEach-Object { Write-Host "Missing version: $($_.Description)" }
Write-FailureSummary $records
exit 1
}

$uniqueVersions = @($records | ForEach-Object { $_.Version } | Sort-Object -Unique)
if ($uniqueVersions.Count -ne 1) {
Write-Host "::error::Daily smoke tests installed $($uniqueVersions.Count) different Aspire CLI versions."
Write-Host 'Installed versions:'
$uniqueVersions | ForEach-Object { Write-Host " $_" }
Write-Host 'Version records:'
$records | ForEach-Object { Write-Host " $($_.Description)" }
Write-FailureSummary $records
exit 1
}

Write-Host "All daily smoke tests installed Aspire CLI version: $($uniqueVersions[0])"
24 changes: 21 additions & 3 deletions tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliInstallStrategyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ public void Detect_DotnetTool_TakesPriorityOverQuality()
}

[Fact]
public void Detect_DotnetTool_IncludesPrereleaseForStagingQuality()
public void Detect_DotnetTool_UsesStagingFeedForStagingQuality()
{
using var environment = new EnvironmentVariableScope(
("ASPIRE_E2E_ARCHIVE", null),
Expand All @@ -308,7 +308,8 @@ public void Detect_DotnetTool_IncludesPrereleaseForStagingQuality()
var strategy = CliInstallStrategy.Detect();

Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
Assert.True(strategy.IncludePrerelease);
Assert.False(strategy.IncludePrerelease);
Assert.True(strategy.UsesStagingDotnetToolFeed);
}

[Fact]
Expand Down Expand Up @@ -380,8 +381,9 @@ public void DotnetToolSmokeTests_UsesPublishedFeedWhenQualityIsSet()
var strategy = DotnetToolSmokeTests.GetDotnetToolStrategy();

Assert.Equal(CliInstallMode.DotnetTool, strategy.Mode);
Assert.True(strategy.IncludePrerelease);
Assert.False(strategy.IncludePrerelease);
Assert.Null(strategy.Version);
Assert.True(strategy.UsesStagingDotnetToolFeed);
}

[Fact]
Expand Down Expand Up @@ -485,6 +487,22 @@ public void GetDotnetToolInstallCommandInDocker_WithPrerelease()
Assert.Equal("dotnet tool install --global Aspire.Cli --prerelease --configfile '/opt/aspire-scripts/NuGet.config'", command);
}

[Fact]
public void GetDotnetToolInstallCommandInDocker_WithStagingQuality()
{
var strategy = CliInstallStrategy.FromPublishedDotnetToolFeed(version: null, CliInstallQuality.Staging);
var command = AspireCliShellCommandHelpers.GetDotnetToolInstallCommandInDocker(strategy);

Assert.Contains("/opt/aspire-scripts/get-aspire-cli.sh --quality staging --install-path \"$STAGING_INSTALL_DIR\" --skip-path", command);
Assert.Contains("darc-pub-microsoft-aspire-${STAGING_SHA8}/nuget/v3/index.json", command);
Assert.Contains("/opt/aspire-scripts/NuGet.config", command);
Assert.Contains("\"$STAGING_NUGET_CONFIG\"", command);
Assert.Contains("<packageSource key=\\\"aspire-staging\\\">", command);
Assert.Contains("dotnet tool install --global Aspire.Cli --version \"$STAGING_BASE_VERSION\" --configfile \"$STAGING_NUGET_CONFIG\"", command);
Assert.Contains("aspire config set channel staging -g", command);
Assert.DoesNotContain("--prerelease", command);
}

[Fact]
public void Detect_ReturnsLocalArchive_WhenArchiveDirIsSetInCIWithoutPrMetadata()
{
Expand Down
10 changes: 5 additions & 5 deletions tests/Aspire.Cli.EndToEnd.Tests/SmokeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ public async Task LatestCliCanStartStableChannelTypeScriptAppHost()
throw new FileNotFoundException($"Expected TypeScript AppHost file to exist: {appHostPath}", appHostPath);
}

AssertStableTypeScriptAppHostConfig(Path.Combine(projectPath, "aspire.config.json"));
output.WriteLine("Stable TypeScript AppHost config verified.");
AssertStableTypeScriptAppHostConfig(Path.Combine(projectPath, "aspire.config.json"), "stable");
output.WriteLine("Stable TypeScript AppHost config verified with channel 'stable'.");

await auto.RunCommandFailFastAsync($"cd {projectName}", counter);
await auto.AspireStartAsync(counter);
Expand All @@ -169,17 +169,17 @@ private static string GetAppHostSdkVersion(string appHostPath)
: throw new InvalidOperationException($"Could not find Aspire.AppHost.Sdk directive in {appHostPath}.");
}

private static void AssertStableTypeScriptAppHostConfig(string configPath)
private static void AssertStableTypeScriptAppHostConfig(string configPath, string expectedChannel)
{
if (!File.Exists(configPath))
{
throw new FileNotFoundException($"Expected Aspire config file to exist: {configPath}", configPath);
}

// Expected shape: { "appHost": { "path": "apphost.ts", "language": "typescript/nodejs" }, "sdk": { "version": "13.2.0" }, "channel": "stable" }
// Expected shape: { "appHost": { "path": "apphost.ts", "language": "typescript/nodejs" }, "sdk": { "version": "13.2.0" }, "channel": "<expected>" }
using var config = JsonDocument.Parse(File.ReadAllText(configPath));
var root = config.RootElement;
AssertJsonStringProperty(root, "channel", "stable", configPath);
AssertJsonStringProperty(root, "channel", expectedChannel, configPath);
var sdk = GetRequiredJsonObjectProperty(root, "sdk", configPath);
var sdkVersion = GetRequiredJsonStringProperty(sdk, "version", configPath);
if (sdkVersion.Contains('-', StringComparison.Ordinal) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ namespace Infrastructure.Tests;
/// <summary>
/// End-to-end tests for the CreateFailingTestIssue tool.
/// </summary>
// These tests build and run repository tools in child dotnet processes. Keep them in the same
// non-parallel collection as the other tool tests so concurrent child builds don't race in artifacts/obj.
[Collection(ToolBuildCollection.Name)]
public sealed class CreateFailingTestIssueToolTests : IClassFixture<CreateFailingTestIssueFixture>, IDisposable
{
private readonly TestTempDirectory _tempDirectory = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ namespace Infrastructure.Tests;
/// <summary>
/// End-to-end tests for the DownloadFailingJobLogs script.
/// </summary>
// These tests build and run repository tools in child dotnet processes. Keep them in the same
// non-parallel collection as the other tool tests so concurrent child builds don't race in artifacts/obj.
[Collection(ToolBuildCollection.Name)]
public sealed class DownloadFailingJobLogsToolTests : IClassFixture<DownloadFailingJobLogsFixture>, IDisposable
{
private const long RunId = 123;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ namespace Infrastructure.Tests;
/// <summary>
/// End-to-end tests for the GenerateTestSummary tool.
/// </summary>
// These tests build and run repository tools in child dotnet processes. Keep them in the same
// non-parallel collection as the other tool tests so concurrent child builds don't race in artifacts/obj.
[Collection(ToolBuildCollection.Name)]
public sealed class GenerateTestSummaryToolTests : IClassFixture<GenerateTestSummaryFixture>, IDisposable
{
private readonly TestTempDirectory _tempDirectory = new();
Expand Down
1 change: 1 addition & 0 deletions tests/Infrastructure.Tests/Infrastructure.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

<ItemGroup>
<Compile Include="$(TestsSharedDir)TempDirectory.cs" Link="shared/TempDirectory.cs" />
<Compile Include="..\Aspire.Hosting.Tests\Utils\MSBuildUtils.cs" Link="shared/MSBuildUtils.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.Tests;
using Aspire.TestUtilities;
using Xunit;

namespace Infrastructure.Tests;

/// <summary>
/// Tests for eng/scripts/verify-daily-smoke-cli-versions.ps1
/// </summary>
public class VerifyDailySmokeCliVersionsTests : IDisposable
{
private readonly TestTempDirectory _tempDir = new();
private readonly string _scriptPath;
private readonly string _stepSummaryPath;
private readonly ITestOutputHelper _output;

public VerifyDailySmokeCliVersionsTests(ITestOutputHelper output)
{
_output = output;
_scriptPath = Path.Combine(MSBuildUtils.GetRepoRoot(), "eng", "scripts", "verify-daily-smoke-cli-versions.ps1");
_stepSummaryPath = Path.Combine(_tempDir.Path, "step-summary.md");
}

public void Dispose() => _tempDir.Dispose();

[Fact]
[RequiresTools(["pwsh"])]
public async Task SucceedsWhenAllVersionRecordsMatch()
{
var versionsDir = CreateVersionsDir();
WriteVersionRecord(versionsDir, "install-script.env", "SmokeTests.CreateAndRun", "InstallScript", "InstallScript (--quality staging)", "13.3.0+abc123");
WriteVersionRecord(versionsDir, "dotnet-tool.env", "DotnetToolSmokeTests.CreateAndRun", "DotnetTool", "DotnetTool (staging)", "13.3.0+abc123");

var result = await RunScript(versionsDir);

result.EnsureSuccessful("verify-daily-smoke-cli-versions.ps1 failed");
Assert.Contains("All daily smoke tests installed Aspire CLI version: 13.3.0+abc123", result.Output);
Assert.False(File.Exists(_stepSummaryPath));
}

[Fact]
[RequiresTools(["pwsh"])]
public async Task FailsWhenVersionRecordsDoNotMatch()
{
var versionsDir = CreateVersionsDir();
WriteVersionRecord(versionsDir, "install-script.env", "SmokeTests.CreateAndRun", "InstallScript", "InstallScript (--quality staging)", "13.3.0+abc123");
WriteVersionRecord(versionsDir, "dotnet-tool.env", "DotnetToolSmokeTests.CreateAndRun", "DotnetTool", "DotnetTool (staging)", "13.4.0-preview.1+def456");

var result = await RunScript(versionsDir);

Assert.NotEqual(0, result.ExitCode);
Assert.Contains("different Aspire CLI versions", result.Output);
Assert.Contains("InstallScript (--quality staging) | SmokeTests.CreateAndRun | 13.3.0+abc123", result.Output);
Assert.Contains("DotnetTool (staging) | DotnetToolSmokeTests.CreateAndRun | 13.4.0-preview.1+def456", result.Output);
var summary = File.ReadAllText(_stepSummaryPath);
Assert.Contains("Aspire CLI version consistency check failed", summary);
Assert.Contains("DotnetTool (staging) | DotnetToolSmokeTests.CreateAndRun | 13.4.0-preview.1+def456", summary);
}

[Fact]
[RequiresTools(["pwsh"])]
public async Task FailsWhenVersionRecordDoesNotIncludeVersion()
{
var versionsDir = CreateVersionsDir();
WriteVersionRecord(versionsDir, "missing-version.env", "SmokeTests.CreateAndRun", "InstallScript", "InstallScript (--quality staging)", version: null);

var result = await RunScript(versionsDir);

Assert.NotEqual(0, result.ExitCode);
Assert.Contains("did not include a version", result.Output);
Assert.Contains("InstallScript (--quality staging) | SmokeTests.CreateAndRun | (missing version)", result.Output);
Assert.Contains("Aspire CLI version consistency check failed", File.ReadAllText(_stepSummaryPath));
}

[Fact]
[RequiresTools(["pwsh"])]
public async Task FailsWhenVersionRecordsDirectoryDoesNotExist()
{
var versionsDir = Path.Combine(_tempDir.Path, "missing");

var result = await RunScript(versionsDir);

Assert.NotEqual(0, result.ExitCode);
Assert.Contains("version records directory was not created", result.Output);
Assert.False(File.Exists(_stepSummaryPath));
}

[Fact]
[RequiresTools(["pwsh"])]
public async Task FailsWhenVersionRecordsDirectoryIsEmpty()
{
var versionsDir = CreateVersionsDir();

var result = await RunScript(versionsDir);

Assert.NotEqual(0, result.ExitCode);
Assert.Contains("No Aspire CLI version records were produced", result.Output);
Assert.False(File.Exists(_stepSummaryPath));
}

private async Task<CommandResult> RunScript(string versionsDir)
{
using var command = new PowerShellCommand(_scriptPath, _output)
.WithTimeout(TimeSpan.FromMinutes(1))
.WithEnvironmentVariable("GITHUB_STEP_SUMMARY", _stepSummaryPath);

return await command.ExecuteAsync("-VersionsDir", $"\"{versionsDir}\"");
}

private string CreateVersionsDir()
{
var versionsDir = Path.Combine(_tempDir.Path, Guid.NewGuid().ToString("N"), "testresults", "cli-versions");
Directory.CreateDirectory(versionsDir);
return versionsDir;
}

private static void WriteVersionRecord(string versionsDir, string fileName, string test, string mode, string strategy, string? version)
{
var lines = new List<string>
{
$"test={test}",
$"mode={mode}",
$"strategy={strategy}"
};

if (version is not null)
{
lines.Add($"version={version}");
lines.Add($"baseVersion={version.Split('+')[0]}");
}

File.WriteAllLines(Path.Combine(versionsDir, fileName), lines);
}

}
Loading
Loading