Skip to content

Add comprehensive test coverage for CLI acquisition scripts#15995

Draft
radical wants to merge 25 commits intomainfrom
copilot/implement-cli-testing-scripts
Draft

Add comprehensive test coverage for CLI acquisition scripts#15995
radical wants to merge 25 commits intomainfrom
copilot/implement-cli-testing-scripts

Conversation

@radical
Copy link
Copy Markdown
Member

@radical radical commented Apr 9, 2026

Summary

Adds a new test project (Aspire.Acquisition.Scripts.Tests) with 160 tests covering the two CLI acquisition scripts (get-aspire-cli.sh and get-aspire-cli-pr.sh). The goal is confidence to refactor these scripts — any incorrect change should break at least one test.

What's tested

Release script (get-aspire-cli.sh)

  • Input validation: version/quality mutual exclusion, extension+release/staging gating
  • URL construction: quality-specific download URLs, extension URL building
  • Platform detection: detect_os, detect_architecture, arch normalization
  • Installation paths: default install dir, GITHUB_PATH integration
  • Shell profile: add_to_shell_profile for bash/zsh, duplicate detection, dry-run
  • Content-type validation: HTML error page detection vs binary responses
  • Archive handling: zip extraction, version output

PR script (get-aspire-cli-pr.sh)

  • PR validation: non-numeric PR numbers, zero, negative, missing values
  • Artifact selection: zero/single/multiple matching archives
  • Platform detection: detect_os, detect_architecture
  • Temp directory cleanup: remove_temp_dir with keep-archive and dry-run modes
  • Configuration: ASPIRE_REPO override, artifact name prefixes, hive-only mode, unknown flags

PowerShell variants

  • Parameter validation for both scripts
  • Default install path behavior

Test infrastructure

  • ScriptFunctionCommand — sources a bash script and calls individual functions in isolation, enabling unit-level testing of internal helpers
  • ScriptToolCommand — runs the full script with arguments for end-to-end behavior tests
  • TestEnvironment — creates per-test temp directories with mock HOME, mock curl/gh scripts, and ASPIRE_TEST_MODE=true
  • FakeArchiveHelper — generates fake .tar.gz/.zip archives with .sha512 sidecar files
  • Mock gh CLI handles --version, pr list, run list, run view, api, and run download commands

Files changed

Area Files
Test project 18 new files in tests/Aspire.Acquisition.Scripts.Tests/ (~3,400 lines)
Solution Added project to Aspire.slnx
CI trigger Added test project path to eng/testing/github-ci-trigger-patterns.txt

No changes to the scripts themselves.

radical and others added 11 commits April 8, 2026 02:15
Add Aspire.Cli.Scripts.Tests project with functional tests for the 4
CLI acquisition scripts in eng/scripts/:
- get-aspire-cli.sh (release bash script)
- get-aspire-cli.ps1 (release PowerShell script)
- get-aspire-cli-pr.sh (PR bash script)
- get-aspire-cli-pr.ps1 (PR PowerShell script)

Tests validate parameter parsing, help output, flag recognition, and
error handling using --dry-run / -WhatIf modes. PR script tests use a
mock gh CLI returning canned JSON responses.

Integration tests query real GitHub PRs but are disabled in CI via
ActiveIssue attribute.

Test infrastructure:
- ScriptToolCommand: extends ToolCommand for bash/pwsh script execution
- TestEnvironment: provides isolated temp directories and mock gh CLI
- RequiresGHCliAttribute: skips tests when gh CLI unavailable
- RealGitHubPRFixture: discovers real PRs for integration tests

60 unit tests, all passing on macOS/Linux. PowerShell PR tests skipped
on Windows due to WhatIf + mock gh interaction issues. Bash tests
skipped on Windows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use Assert.Skip() instead of silent return in integration tests
  so test reports show proper skip status when GH_TOKEN is unavailable
- Capture stdout separately in RealGitHubPRFixture to prevent gh CLI
  stderr warnings (upgrade notices, rate limits) from breaking JSON parsing
- Make mock gh script fail (exit 1) for unhandled commands instead of
  silently succeeding, and add handlers for --version and api subcommands

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implement unit-level testing of individual bash/PowerShell functions in the
CLI acquisition scripts, enabling fast, isolated verification of script logic
without requiring full end-to-end execution.

New infrastructure:
- ScriptFunctionCommand: Sources a script and calls individual functions via
  temp wrapper scripts. For bash, uses BASH_SOURCE guard. For PowerShell,
  strips main execution block and Help check to load only function definitions.
- FakeArchiveHelper: Creates fake tar.gz/zip archives with SHA-512 checksums
  for testing validate_checksum and install_archive functions.
- EnhancedMockGhHelper: URL-pattern-aware mock gh CLI with error injection
  support via environment variables.

New test files:
- SourceabilityTests: 6 tests verifying scripts can be sourced without
  executing their main flow.
- ReleaseScriptFunctionTests: 17 tests for bash release script functions
  (construct_aspire_cli_url, map_quality_to_channel, validate_checksum, etc.)
- PRScriptFunctionTests: 14 tests for bash PR script functions
  (get_runtime_identifier, get_cli_architecture_from_architecture, etc.)

Script changes:
- Wrap main execution in bash scripts with main() function and BASH_SOURCE
  guard to enable sourcing without side effects.

Other changes:
- Add PowerShell function-level parity tests in ReleaseScriptPowerShellTests
  for ConvertTo-ChannelName and Get-AspireCliUrl.
- Replace ActiveIssue with OuterloopTest on PRScriptIntegrationTests since
  they require real GitHub API access.
- Add using to Process in TestEnvironment.cs to prevent resource leak.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Make bash wrapper idempotent: save/restore shell options and guard
  readonly redeclarations so sourced scripts don't leak state
- Fix PowerShell path escaping for paths containing apostrophes
- Fix RealGitHubPRFixture: query microsoft/aspire (matching script default)
  instead of dotnet/aspire
- Remove dead code: EnhancedMockGhHelper.cs (238 lines, never referenced)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add output validation assertions to existing tests that previously only
checked exit codes. Tests now verify:
- Dry-run output contains [DRY RUN] markers (bash) or What if (PowerShell)
- Error messages contain relevant keywords (quality, PR number, etc.)
- Flag-specific output (--skip-path, --hive-only, --skip-extension, etc.)
- Custom paths and parameter values appear in output
- Verbose mode produces substantial output

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cripts

New tests cover:
- Version/quality mutual exclusion (bash + PowerShell)
- Default install path verification ($HOME/.aspire/bin)
- GitHub Actions GITHUB_PATH integration
- Dev quality URL correctness (distinguishes from staging/release)
- Version-specific URL output
- install-extension gating for staging quality (bash + PowerShell parity)
- PR number validation: non-numeric, zero, negative, option-as-first-arg
- Unknown flags for PR script
- ASPIRE_REPO env var override
- Artifact name patterns (cli-native-archives)
- NuGet hive path construction
- --hive-only skipping CLI download (asserts specific message)
- Platform detection (detect_os, detect_architecture) for both scripts
- Architecture normalization with case sensitivity
- Zip archive extraction (complements existing tar.gz test)
- Extension URL construction (quality-based and version-based)
- Default install path for PR script (PowerShell)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…p-archive, and PR artifact selection

Add 12 new test methods across two test files:

ReleaseScriptFunctionTests.cs:
- add_to_shell_profile: Tests PATH modification for bash/zsh profiles,
  duplicate detection, and dry-run mode
- validate_content_type: Tests HTML error page detection and valid
  binary content-type acceptance

PRScriptFunctionTests.cs:
- remove_temp_dir: Tests --keep-archive flag preserves temp directory,
  cleanup when disabled, and dry-run behavior
- archive file discovery: Tests correct archive selection by OS/arch,
  missing archive error handling, and multiple archive disambiguation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15995

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15995"

radical and others added 14 commits April 9, 2026 00:54
Move ProcessExtensions to the shared Aspire.TestUtilities project,
removing duplicate copies from Aspire.Acquisition.Tests and
Infrastructure.Tests (PowerShellCommand.cs).
Move SkipOnPlatform(Windows) from class level to individual tests that
use mock gh CLI with -WhatIf. Pure help and parameter validation tests
now run on Windows too.

Also enhance the Windows mock gh.cmd to handle 'run download' with -D
argument parsing, matching the Unix mock behavior.
Restructure Windows batch mock gh.cmd to use top-level goto dispatch
instead of exit /b inside nested if() blocks. CMD's exit /b is
unreliable inside parenthesized blocks and can fall through to the
error handler, producing stderr output that breaks PowerShell's output
capture when combined with WhatIf/ShouldProcess.

Changes:
- Rewrite gh.cmd to use goto :ver/:api/:pr/:run labels at top level
- Use %~1 to strip surrounding quotes from arguments
- Add setlocal for clean variable scope
- Remove all 16 [SkipOnPlatform(Windows)] attributes from tests
- Remove unused using Microsoft.DotNet.XUnitExtensions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When PowerShell invokes a .cmd file with arguments containing '&'
(e.g. API URLs like '?event=pull_request&head_sha=abc'), CMD
interprets '&' as a command separator and executes the text after it
as a separate command, producing spurious stderr output.

Using 'exit' (without /b) terminates the entire CMD process spawned
by PowerShell, preventing the phantom second command from running.

Also restructure :pr handler to use goto :pr_list instead of
exit /b inside if() block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add RunOnGithubActionsMacOS=true to run tests on macOS CI
- Simplify System.Environment.GetEnvironmentVariable to
  Environment.GetEnvironmentVariable (using directive already present)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

🎬 CLI E2E Test Recordings — 55 recordings uploaded (commit 108d508)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AllPublishMethodsBuildDockerImages ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
Banner_NotDisplayedWithNoLogoFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJavaEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateJavaAppHostWithViteApp ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DashboardRunWithOtelTracesReturnsNoTraces ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View Recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording

📹 Recordings uploaded automatically from CI run #24176816448

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant