Skip to content

Add comprehensive test coverage for CLI acquisition scripts#15995

Open
radical wants to merge 7 commits intomainfrom
copilot/implement-cli-testing-scripts
Open

Add comprehensive test coverage for CLI acquisition scripts#15995
radical wants to merge 7 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.Tests) with ~160 tests covering the four CLI acquisition scripts (get-aspire-cli.sh, get-aspire-cli.ps1, get-aspire-cli-pr.sh, get-aspire-cli-pr.ps1). The goal is confidence to refactor these scripts — any incorrect change should break at least one test.

Also includes minor script changes required for testability (wrapping in main() with BASH_SOURCE guard, initializing config_file variable under set -u).

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

Piped install patterns

  • curl | bash and cat | bash patterns for bash scripts
  • irm | iex pattern for PowerShell scripts
  • Argument forwarding with spaces in paths

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
  • ScriptHostFixture — HTTP server for piped install tests (serves scripts over curl/irm)
  • 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
Scripts eng/scripts/get-aspire-cli.sh, get-aspire-cli-pr.shmain() wrapper with BASH_SOURCE guard, config_file init
Test project 24 new files in tests/Aspire.Acquisition.Tests/ (~3,800 lines)
Shared infra Consolidated ProcessExtensions into tests/Aspire.TestUtilities/, simplified CommandResult/ToolCommandException
CI workflow Extended GH_TOKEN/PR metadata exports in run-tests.yml for Acquisition tests
Solution Added project to Aspire.slnx

@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"

@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.

@radical radical force-pushed the copilot/implement-cli-testing-scripts branch from c27293c to 8556d0d Compare April 11, 2026 04:31
@radical radical marked this pull request as ready for review April 11, 2026 05:11
@radical radical requested a review from eerhardt as a code owner April 11, 2026 05:11
Copilot AI review requested due to automatic review settings April 11, 2026 05:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Aspire.Acquisition.Tests test project to validate the CLI acquisition scripts in eng/scripts/, plus a small refactor to make the bash scripts safely “sourceable” for function-level testing. The PR also factors shared Process extension helpers into Aspire.TestUtilities and modernizes a couple of shared test helpers.

Changes:

  • Introduce tests/Aspire.Acquisition.Tests with unit-style function tests and tool-style script invocation tests for the acquisition scripts (bash + PowerShell).
  • Update get-aspire-cli.sh / get-aspire-cli-pr.sh to wrap execution in a main() guarded by BASH_SOURCE for safe sourcing.
  • Move Process lifecycle extension methods into tests/Aspire.TestUtilities and update existing test helpers to consume them.

Reviewed changes

Copilot reviewed 30 out of 31 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
tests/Shared/TemplatesTesting/ToolCommandException.cs Switch to primary-constructor style exception with inline property init.
tests/Shared/TemplatesTesting/CommandResult.cs Switch CommandResult to primary-constructor style struct with inline property init.
tests/Infrastructure.Tests/WorkflowScripts/NodeCommand.cs Import Aspire.TestUtilities for shared Process extensions.
tests/Infrastructure.Tests/PowerShellScripts/PowerShellCommand.cs Remove local ProcessExtensions; now uses Aspire.TestUtilities.
tests/Aspire.TestUtilities/ProcessExtensions.cs New shared Process extension helpers for safe termination/exit checks.
tests/Aspire.Acquisition.Tests/xunit.runner.json Enables xUnit parallelization for the new test project.
tests/Aspire.Acquisition.Tests/Scripts/SourceabilityTests.cs Verifies bash scripts can be sourced without running main.
tests/Aspire.Acquisition.Tests/Scripts/ReleaseScriptShellTests.cs Bash release-script end-to-end-ish argument/dry-run tests.
tests/Aspire.Acquisition.Tests/Scripts/ReleaseScriptPSFunctionTests.cs PowerShell release-script function-level tests (URLs, checksum, extraction, RID detection).
tests/Aspire.Acquisition.Tests/Scripts/ReleaseScriptPowerShellTests.cs PowerShell release-script parameter/help/WhatIf behavior tests.
tests/Aspire.Acquisition.Tests/Scripts/ReleaseScriptFunctionTests.cs Bash release-script function-level tests (URLs, checksum, extraction, profile edits, content-type).
tests/Aspire.Acquisition.Tests/Scripts/PSSourceabilityTests.cs Verifies PS scripts can load functions without executing main (AST extraction).
tests/Aspire.Acquisition.Tests/Scripts/PRScriptShellTests.cs Bash PR-script end-to-end-ish argument validation using a mock gh.
tests/Aspire.Acquisition.Tests/Scripts/PRScriptPSFunctionTests.cs PowerShell PR-script function tests (RID, suffix extraction, temp cleanup, archive selection).
tests/Aspire.Acquisition.Tests/Scripts/PRScriptPowerShellTests.cs PowerShell PR-script parameter/help/WhatIf behavior tests using a mock gh.
tests/Aspire.Acquisition.Tests/Scripts/PRScriptIntegrationTests.cs Outerloop integration tests hitting real GitHub PR artifacts via gh.
tests/Aspire.Acquisition.Tests/Scripts/PRScriptFunctionTests.cs Bash PR-script function tests (RID/suffix/temp cleanup/archive selection).
tests/Aspire.Acquisition.Tests/Scripts/Common/TestEnvironment.cs Isolated temp HOME + mock tooling environment for script tests.
tests/Aspire.Acquisition.Tests/Scripts/Common/ScriptToolCommand.cs Runs scripts via bash/pwsh under ToolCommand with repo-root resolution.
tests/Aspire.Acquisition.Tests/Scripts/Common/ScriptPaths.cs Centralized relative paths to the acquisition scripts under eng/scripts.
tests/Aspire.Acquisition.Tests/Scripts/Common/ScriptFunctionCommand.cs Wrapper for calling individual bash/PS functions in isolation (sourcing/AST extraction).
tests/Aspire.Acquisition.Tests/Scripts/Common/RealGitHubPRFixture.cs Discovers a suitable merged PR/run with expected artifacts for integration tests.
tests/Aspire.Acquisition.Tests/Scripts/Common/FileHelper.cs Unix executable-bit helper for mock tools and extracted binaries.
tests/Aspire.Acquisition.Tests/Scripts/Common/FakeArchiveHelper.cs Generates fake archives + checksums and fake .nupkg files for tests.
tests/Aspire.Acquisition.Tests/README.md Documents safety model and how to run unit/integration tests.
tests/Aspire.Acquisition.Tests/Aspire.Acquisition.Tests.csproj New test project definition + links to shared ToolCommand infrastructure.
tags.lock Adds a root-level lock-like file with a numeric value.
eng/scripts/get-aspire-cli.sh Adds main() + BASH_SOURCE guard for sourceability; minor local var init.
eng/scripts/get-aspire-cli-pr.sh Adds main() + BASH_SOURCE guard for sourceability; minor local var init.
Aspire.slnx Adds the new test project to the solution.
.github/workflows/run-tests.yml Exposes PR metadata + GH_TOKEN for the Acquisition test suite.

@radical radical marked this pull request as draft April 11, 2026 05:50
radical and others added 4 commits April 11, 2026 02:07
Enable sourcing get-aspire-cli.sh and get-aspire-cli-pr.sh to load
function definitions without executing the main flow. This is required
for unit-testing individual script functions in isolation.

Also initialize `local config_file=""` to prevent unbound-variable
errors under `set -u` when no shell config file is found.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move ProcessExtensions from Infrastructure.Tests (internal) to
Aspire.TestUtilities (public) so it can be shared by the new
acquisition test project. Update Infrastructure.Tests to reference
the shared version. Simplify CommandResult and ToolCommandException
to use primary constructors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extend the existing requiresCliArchive token/metadata exports to also
trigger when testShortName == 'Acquisition', so integration tests can
access GitHub API when run as outerloop.

Remove unrelated create-failing-test-issue.* entry from CI trigger
skip-list.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New test project covering get-aspire-cli.sh, get-aspire-cli-pr.sh,
and their PowerShell equivalents. Tests are organized in tiers:

- Function-level tests (ScriptFunctionCommand): source a script and
  call individual functions in isolation — URL construction, platform
  detection, input validation, shell profile handling, archive ops.

- Script-level tests (ScriptToolCommand): run full scripts with mock
  gh CLI and --dry-run to validate end-to-end parameter handling.

- Piped install tests (ScriptHostFixture): serve scripts over HTTP
  and test curl|bash and irm|iex patterns against a real pipe.

- Integration tests (RealGitHubPRFixture): query real GitHub PRs
  with --dry-run, gated behind OuterloopTest + Category=integration.

Test infrastructure:
- TestEnvironment: isolated temp dirs with mock HOME
- FakeArchiveHelper: generates tar.gz/zip with .sha512 sidecars
- Mock gh CLI: canned JSON for pr/run/api commands

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@radical radical force-pushed the copilot/implement-cli-testing-scripts branch from e982f47 to 0368dd0 Compare April 11, 2026 06:08
Fix Windows test failures where double quotes in ProcessStartInfo.Arguments
were consumed as argument delimiters by MSVCRT command-line parser. Wrap the
entire iex expression in outer quotes with escaped inner quotes so they
survive as literal characters for pwsh -Command.

Add 60-second timeouts to all piped install tests to prevent CI hang dumps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@radical radical marked this pull request as ready for review April 11, 2026 06:42
@radical radical requested review from davidfowl and joperezr April 11, 2026 06:43
radical and others added 2 commits April 11, 2026 02:59
Fix macOS test failures where .NET's ProcessStartInfo.Arguments parser
splits compound bash -c commands on spaces. Wrap the entire command
string in quotes so it stays as a single argv entry.

Add missing -Help switch parameter to get-aspire-cli-pr.ps1 (the release
script has it but the PR script was missing it, causing iex test to fail
with 'parameter not found').

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

🎬 CLI E2E Test Recordings — 58 recordings uploaded (commit e59c671)

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
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ 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
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #24277193743

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.

2 participants