Skip to content

Commit dc30b00

Browse files
Add scoped install smoke workflow
- add cross-platform GitHub Actions coverage for user and machine installs - validate PowerShell 7.4, 7.5, and 7.6 alias resolution in both scopes - check list output and ambiguous uninstall behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4b95eb5 commit dc30b00

2 files changed

Lines changed: 323 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Scoped install smoke
2+
3+
on:
4+
push:
5+
branches:
6+
- "**"
7+
tags-ignore:
8+
- "v*"
9+
pull_request:
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
scoped-install-smoke:
17+
name: Scoped install smoke (${{ matrix.name }})
18+
runs-on: ${{ matrix.os }}
19+
timeout-minutes: 90
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
include:
24+
- name: windows
25+
os: windows-latest
26+
- name: macos
27+
os: macos-14
28+
- name: linux
29+
os: ubuntu-latest
30+
31+
env:
32+
GITHUB_TOKEN: ${{ github.token }}
33+
MULTI_PWSH_CACHE_DIR: ${{ runner.temp }}/multi-pwsh-cache
34+
MULTI_PWSH_CACHE_KEEP: "1"
35+
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v4
39+
40+
- name: Install .NET SDK
41+
uses: actions/setup-dotnet@v4
42+
with:
43+
global-json-file: global.json
44+
45+
- name: Install Rust toolchain
46+
shell: pwsh
47+
run: |
48+
rustup toolchain install stable --profile minimal
49+
rustup default stable
50+
51+
- name: Cache cargo artifacts
52+
uses: Swatinem/rust-cache@v2.8.2
53+
54+
- name: Cache multi-pwsh downloads
55+
uses: actions/cache@v4
56+
with:
57+
path: ${{ env.MULTI_PWSH_CACHE_DIR }}
58+
key: multi-pwsh-scope-smoke-${{ runner.os }}-${{ hashFiles('crates/multi-pwsh/Cargo.toml', '.github/workflows/scoped-install-smoke.yml', 'scripts/Invoke-ScopedInstallSmokeTest.ps1') }}
59+
restore-keys: |
60+
multi-pwsh-scope-smoke-${{ runner.os }}-
61+
62+
- name: Install multi-pwsh from source
63+
shell: pwsh
64+
run: |
65+
cargo install --locked --path crates/multi-pwsh --force
66+
67+
- name: Add cargo bin to PATH
68+
shell: pwsh
69+
run: |
70+
$cargoHome = if ($env:CARGO_HOME) { $env:CARGO_HOME } else { Join-Path $HOME '.cargo' }
71+
$cargoBin = Join-Path $cargoHome 'bin'
72+
$cargoBin | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
73+
74+
- name: Run scoped install smoke test
75+
shell: pwsh
76+
run: ./scripts/Invoke-ScopedInstallSmokeTest.ps1
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
[CmdletBinding()]
2+
param()
3+
4+
Set-StrictMode -Version Latest
5+
$ErrorActionPreference = "Stop"
6+
7+
function Assert-True {
8+
param(
9+
[Parameter(Mandatory = $true)]
10+
[bool]$Condition,
11+
12+
[Parameter(Mandatory = $true)]
13+
[string]$Message
14+
)
15+
16+
if (-not $Condition) {
17+
throw $Message
18+
}
19+
}
20+
21+
function Assert-Contains {
22+
param(
23+
[Parameter(Mandatory = $true)]
24+
[string]$Text,
25+
26+
[Parameter(Mandatory = $true)]
27+
[string]$Expected,
28+
29+
[Parameter(Mandatory = $true)]
30+
[string]$Context
31+
)
32+
33+
if (-not $Text.Contains($Expected)) {
34+
throw "Expected $Context to contain '$Expected'.`nActual output:`n$Text"
35+
}
36+
}
37+
38+
function Invoke-MultiPwsh {
39+
param(
40+
[Parameter(Mandatory = $true)]
41+
[string[]]$Arguments,
42+
43+
[switch]$UseMachinePrivileges,
44+
45+
[int[]]$AllowedExitCodes = @(0)
46+
)
47+
48+
$description = "multi-pwsh $($Arguments -join ' ')"
49+
50+
if ($UseMachinePrivileges -and -not $IsWindows) {
51+
$output = & sudo env `
52+
"PATH=${env:PATH}" `
53+
"GITHUB_TOKEN=${env:GITHUB_TOKEN}" `
54+
"MULTI_PWSH_CACHE_DIR=${env:MULTI_PWSH_CACHE_DIR}" `
55+
"MULTI_PWSH_CACHE_KEEP=${env:MULTI_PWSH_CACHE_KEEP}" `
56+
$script:MultiPwshExe @Arguments 2>&1 | Out-String
57+
}
58+
else {
59+
$output = & $script:MultiPwshExe @Arguments 2>&1 | Out-String
60+
}
61+
62+
$exitCode = $LASTEXITCODE
63+
$output = $output.Trim()
64+
65+
if ($AllowedExitCodes -notcontains $exitCode) {
66+
throw "Command failed with exit code ${exitCode}: $description`n$output"
67+
}
68+
69+
[pscustomobject]@{
70+
ExitCode = $exitCode
71+
Output = $output
72+
}
73+
}
74+
75+
function Invoke-PwshAlias {
76+
param(
77+
[Parameter(Mandatory = $true)]
78+
[string]$AliasPath
79+
)
80+
81+
$output = & $AliasPath -NoLogo -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' 2>&1 | Out-String
82+
$exitCode = $LASTEXITCODE
83+
$output = $output.Trim()
84+
85+
if ($exitCode -ne 0) {
86+
throw "Alias invocation failed with exit code ${exitCode}: $AliasPath`n$output"
87+
}
88+
89+
$output
90+
}
91+
92+
function Get-UserScopeRoot {
93+
Join-Path $HOME ".pwsh"
94+
}
95+
96+
function Get-UserScopeBin {
97+
Join-Path (Get-UserScopeRoot) "bin"
98+
}
99+
100+
function Get-MachineScopeRoot {
101+
if ($IsWindows) {
102+
Join-Path $env:ProgramFiles "PowerShell"
103+
}
104+
elseif ($IsMacOS) {
105+
"/usr/local/microsoft/powershell"
106+
}
107+
else {
108+
"/opt/microsoft/powershell"
109+
}
110+
}
111+
112+
function Get-MachineScopeBin {
113+
if ($IsWindows) {
114+
Join-Path (Get-MachineScopeRoot) "bin"
115+
}
116+
else {
117+
"/usr/local/bin"
118+
}
119+
}
120+
121+
function Get-AliasPath {
122+
param(
123+
[Parameter(Mandatory = $true)]
124+
[ValidateSet("user", "machine")]
125+
[string]$Scope,
126+
127+
[Parameter(Mandatory = $true)]
128+
[string]$AliasName
129+
)
130+
131+
$binDir = if ($Scope -eq "user") { Get-UserScopeBin } else { Get-MachineScopeBin }
132+
$fileName = if ($IsWindows) { "$AliasName.exe" } else { $AliasName }
133+
Join-Path $binDir $fileName
134+
}
135+
136+
function Assert-ListContainsVersions {
137+
param(
138+
[Parameter(Mandatory = $true)]
139+
[string]$Output,
140+
141+
[Parameter(Mandatory = $true)]
142+
[string]$Context
143+
)
144+
145+
foreach ($expected in @("7.4", "7.5", "7.6")) {
146+
Assert-Contains -Text $Output -Expected $expected -Context $Context
147+
}
148+
}
149+
150+
$script:MultiPwshExe = (Get-Command multi-pwsh -CommandType Application).Source
151+
Assert-True -Condition ([string]::IsNullOrWhiteSpace($script:MultiPwshExe) -eq $false) -Message "multi-pwsh is not installed on PATH"
152+
153+
$userRoot = Get-UserScopeRoot
154+
$userBin = Get-UserScopeBin
155+
$machineRoot = Get-MachineScopeRoot
156+
$machineBin = Get-MachineScopeBin
157+
158+
Write-Host "multi-pwsh executable: ${script:MultiPwshExe}"
159+
Write-Host "User root: $userRoot"
160+
Write-Host "User bin: $userBin"
161+
Write-Host "Machine root: $machineRoot"
162+
Write-Host "Machine bin: $machineBin"
163+
164+
$installMatrix = @(
165+
@{ Selector = "7.4"; InstallArgs = @(); ExpectedPrefix = "7.4." },
166+
@{ Selector = "7.5"; InstallArgs = @(); ExpectedPrefix = "7.5." },
167+
@{ Selector = "7.6"; InstallArgs = @("--include-prerelease"); ExpectedPrefix = "7.6." }
168+
)
169+
170+
foreach ($scope in @("user", "machine")) {
171+
$useMachinePrivileges = ($scope -eq "machine" -and -not $IsWindows)
172+
173+
foreach ($install in $installMatrix) {
174+
$args = @("install", $install.Selector, "--scope", $scope) + $install.InstallArgs
175+
if ($scope -eq "machine" -and $IsWindows) {
176+
$args += @("--no-register-manifest", "--no-use-mu", "--no-enable-mu")
177+
}
178+
$result = Invoke-MultiPwsh -Arguments $args -UseMachinePrivileges:$useMachinePrivileges
179+
Write-Host $result.Output
180+
}
181+
}
182+
183+
$userList = Invoke-MultiPwsh -Arguments @("list", "--scope", "user")
184+
$machineList = Invoke-MultiPwsh -Arguments @("list", "--scope", "machine") -UseMachinePrivileges:(-not $IsWindows)
185+
$allList = Invoke-MultiPwsh -Arguments @("list", "--scope", "all")
186+
187+
Assert-Contains -Text $userList.Output -Expected $userRoot -Context "user scope list"
188+
Assert-Contains -Text $userList.Output -Expected $userBin -Context "user scope list"
189+
Assert-ListContainsVersions -Output $userList.Output -Context "user scope list"
190+
191+
Assert-Contains -Text $machineList.Output -Expected $machineRoot -Context "machine scope list"
192+
Assert-Contains -Text $machineList.Output -Expected $machineBin -Context "machine scope list"
193+
Assert-ListContainsVersions -Output $machineList.Output -Context "machine scope list"
194+
195+
Assert-Contains -Text $allList.Output -Expected $userRoot -Context "all scope list"
196+
Assert-Contains -Text $allList.Output -Expected $machineRoot -Context "all scope list"
197+
Assert-ListContainsVersions -Output $allList.Output -Context "all scope list"
198+
199+
$resolvedVersions = @{}
200+
201+
foreach ($scope in @("user", "machine")) {
202+
$sevenSixVersion = $null
203+
204+
foreach ($install in $installMatrix) {
205+
$lineAliasName = "pwsh-$($install.Selector)"
206+
$lineAliasPath = Get-AliasPath -Scope $scope -AliasName $lineAliasName
207+
208+
Assert-True -Condition (Test-Path -Path $lineAliasPath) -Message "Expected alias to exist: $lineAliasPath"
209+
210+
$resolvedVersion = Invoke-PwshAlias -AliasPath $lineAliasPath
211+
Assert-True `
212+
-Condition ($resolvedVersion.StartsWith($install.ExpectedPrefix)) `
213+
-Message "Expected $lineAliasPath to resolve to a version starting with $($install.ExpectedPrefix), but got $resolvedVersion"
214+
215+
$patchAliasName = "pwsh-$resolvedVersion"
216+
$patchAliasPath = Get-AliasPath -Scope $scope -AliasName $patchAliasName
217+
Assert-True -Condition (Test-Path -Path $patchAliasPath) -Message "Expected patch alias to exist: $patchAliasPath"
218+
219+
$patchVersion = Invoke-PwshAlias -AliasPath $patchAliasPath
220+
Assert-True `
221+
-Condition ($patchVersion -eq $resolvedVersion) `
222+
-Message "Expected $patchAliasPath to resolve to $resolvedVersion, but got $patchVersion"
223+
224+
if ($install.Selector -eq "7.6") {
225+
$sevenSixVersion = $resolvedVersion
226+
}
227+
228+
$resolvedVersions["$scope-$($install.Selector)"] = $resolvedVersion
229+
}
230+
231+
$majorAliasPath = Get-AliasPath -Scope $scope -AliasName "pwsh-7"
232+
Assert-True -Condition (Test-Path -Path $majorAliasPath) -Message "Expected major alias to exist: $majorAliasPath"
233+
234+
$majorVersion = Invoke-PwshAlias -AliasPath $majorAliasPath
235+
Assert-True `
236+
-Condition ($majorVersion -eq $sevenSixVersion) `
237+
-Message "Expected $majorAliasPath to resolve to $sevenSixVersion, but got $majorVersion"
238+
}
239+
240+
$ambiguousVersion = $resolvedVersions["user-7.4"]
241+
$ambiguousUninstall = Invoke-MultiPwsh -Arguments @("uninstall", $ambiguousVersion) -AllowedExitCodes @(1)
242+
Assert-Contains `
243+
-Text $ambiguousUninstall.Output `
244+
-Expected "installed in both user and machine scopes" `
245+
-Context "ambiguous uninstall output"
246+
247+
Write-Host "Scoped install smoke test completed successfully."

0 commit comments

Comments
 (0)