Skip to content

Commit 693faaa

Browse files
bolenscursoragent
andcommitted
fix(test): harden Python Test-ValidPath probe; fix choco/nodejs mocks
- Python.psm1: probe Test-ValidPath via core-qualified Get-Command and invoke through a guarded helper so a mocked Get-Command or an unloaded Validation module (in-shard) can no longer surface 'Test-ValidPath is not recognized'. - library-chocolatey-detection: mock Get-Command in the CheckCommand test to hide both choco (present on Windows runners) and Test-ValidPath. - library-nodejs: mock Get-NodeModuleSearchPaths so the restore test is deterministic regardless of ambient shard search paths. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent abcdd7c commit 693faaa

3 files changed

Lines changed: 60 additions & 53 deletions

File tree

scripts/lib/runtime/Python.psm1

Lines changed: 41 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,25 @@ function Get-PythonPath {
2222
[string]$RepoRoot
2323
)
2424

25-
# Use Validation module if available
26-
$useValidation = Get-Command Test-ValidPath -ErrorAction SilentlyContinue
25+
# Use Validation module if available. Probe with the core-qualified Get-Command so unit
26+
# tests that Mock Get-Command (-ModuleName Python) cannot flip this to a false positive,
27+
# and invoke Test-ValidPath through a guarded helper that falls back to Test-Path when the
28+
# Validation module was unloaded by an earlier test in the same shard (prevents
29+
# "Test-ValidPath is not recognized").
30+
$useValidation = $null -ne (Microsoft.PowerShell.Core\Get-Command -Name 'Test-ValidPath' -ErrorAction SilentlyContinue)
31+
$testPathValidated = {
32+
param([string]$Path, [string]$PathType = 'Any')
33+
if (-not $Path -or [string]::IsNullOrWhiteSpace($Path)) { return $false }
34+
if ($useValidation) {
35+
try {
36+
return [bool](Test-ValidPath -Path $Path -PathType $PathType)
37+
}
38+
catch {
39+
# Validation helper vanished mid-shard; fall through to Test-Path.
40+
}
41+
}
42+
return [bool](Test-Path -LiteralPath $Path)
43+
}
2744

2845
# First, check common Python-related environment variables (highest priority)
2946
$pythonEnvVars = @('PYTHON_HOME', 'PYTHON_ROOT', 'PYTHON', 'VIRTUAL_ENV', 'CONDA_PREFIX')
@@ -45,12 +62,7 @@ function Get-PythonPath {
4562
else {
4663
Join-Path $envValue 'bin' 'python'
4764
}
48-
$pythonExists = if ($useValidation) {
49-
Test-ValidPath -Path $pythonExe -PathType File
50-
}
51-
else {
52-
$pythonExe -and -not [string]::IsNullOrWhiteSpace($pythonExe) -and (Test-Path -LiteralPath $pythonExe)
53-
}
65+
$pythonExists = & $testPathValidated -Path $pythonExe -PathType File
5466
if ($pythonExists) {
5567
$debugLevel = 0
5668
if ($env:PS_PROFILE_DEBUG -and [int]::TryParse($env:PS_PROFILE_DEBUG, [ref]$debugLevel) -and $debugLevel -ge 3) {
@@ -62,12 +74,7 @@ function Get-PythonPath {
6274
# For PYTHON_HOME, PYTHON_ROOT, PYTHON - these might point directly to Python or its directory
6375
elseif ($envVar -eq 'PYTHON') {
6476
# PYTHON might be a direct path to Python executable
65-
$pythonExists = if ($useValidation) {
66-
Test-ValidPath -Path $envValue -PathType File
67-
}
68-
else {
69-
$envValue -and -not [string]::IsNullOrWhiteSpace($envValue) -and (Test-Path -LiteralPath $envValue)
70-
}
77+
$pythonExists = & $testPathValidated -Path $envValue -PathType File
7178
if ($pythonExists) {
7279
$debugLevel = 0
7380
if ($env:PS_PROFILE_DEBUG -and [int]::TryParse($env:PS_PROFILE_DEBUG, [ref]$debugLevel) -and $debugLevel -ge 3) {
@@ -84,12 +91,7 @@ function Get-PythonPath {
8491
else {
8592
Join-Path $envValue 'python'
8693
}
87-
$pythonExists = if ($useValidation) {
88-
Test-ValidPath -Path $pythonExe -PathType File
89-
}
90-
else {
91-
$pythonExe -and -not [string]::IsNullOrWhiteSpace($pythonExe) -and (Test-Path -LiteralPath $pythonExe)
92-
}
94+
$pythonExists = & $testPathValidated -Path $pythonExe -PathType File
9395
if ($pythonExists) {
9496
return $pythonExe
9597
}
@@ -100,12 +102,7 @@ function Get-PythonPath {
100102
else {
101103
Join-Path $envValue 'bin' 'python'
102104
}
103-
$pythonExists = if ($useValidation) {
104-
Test-ValidPath -Path $pythonExe -PathType File
105-
}
106-
else {
107-
$pythonExe -and -not [string]::IsNullOrWhiteSpace($pythonExe) -and (Test-Path -LiteralPath $pythonExe)
108-
}
105+
$pythonExists = & $testPathValidated -Path $pythonExe -PathType File
109106
if ($pythonExists) {
110107
$debugLevel = 0
111108
if ($env:PS_PROFILE_DEBUG -and [int]::TryParse($env:PS_PROFILE_DEBUG, [ref]$debugLevel) -and $debugLevel -ge 3) {
@@ -147,22 +144,12 @@ function Get-PythonPath {
147144

148145
if ($RepoRoot) {
149146
$venvPath = Join-Path $RepoRoot '.venv'
150-
$venvExists = if ($useValidation) {
151-
Test-ValidPath -Path $venvPath -PathType Directory
152-
}
153-
else {
154-
$venvPath -and -not [string]::IsNullOrWhiteSpace($venvPath) -and (Test-Path -LiteralPath $venvPath)
155-
}
147+
$venvExists = & $testPathValidated -Path $venvPath -PathType Directory
156148

157149
if ($venvExists) {
158150
# Try Windows path first
159151
$venvPythonPath = Join-Path $venvPath 'Scripts' 'python.exe'
160-
$pythonExists = if ($useValidation) {
161-
Test-ValidPath -Path $venvPythonPath -PathType File
162-
}
163-
else {
164-
$venvPythonPath -and -not [string]::IsNullOrWhiteSpace($venvPythonPath) -and (Test-Path -LiteralPath $venvPythonPath)
165-
}
152+
$pythonExists = & $testPathValidated -Path $venvPythonPath -PathType File
166153
if ($pythonExists) {
167154
$debugLevel = 0
168155
if ($env:PS_PROFILE_DEBUG -and [int]::TryParse($env:PS_PROFILE_DEBUG, [ref]$debugLevel) -and $debugLevel -ge 2) {
@@ -172,12 +159,7 @@ function Get-PythonPath {
172159
}
173160
# Try Unix-style path
174161
$venvPythonPath = Join-Path $venvPath 'bin' 'python'
175-
$pythonExists = if ($useValidation) {
176-
Test-ValidPath -Path $venvPythonPath -PathType File
177-
}
178-
else {
179-
$venvPythonPath -and -not [string]::IsNullOrWhiteSpace($venvPythonPath) -and (Test-Path -LiteralPath $venvPythonPath)
180-
}
162+
$pythonExists = & $testPathValidated -Path $venvPythonPath -PathType File
181163
if ($pythonExists) {
182164
$debugLevel = 0
183165
if ($env:PS_PROFILE_DEBUG -and [int]::TryParse($env:PS_PROFILE_DEBUG, [ref]$debugLevel) -and $debugLevel -ge 2) {
@@ -295,18 +277,24 @@ function Invoke-PythonScript {
295277
[string]$RepoRoot
296278
)
297279

298-
# Validate script path exists
299-
# Use Validation module if available
300-
if (Get-Command Test-ValidPath -ErrorAction SilentlyContinue) {
301-
if (-not (Test-ValidPath -Path $ScriptPath -PathType File)) {
302-
throw "Python script not found: $ScriptPath"
280+
# Validate script path exists. Probe with the core-qualified Get-Command so a mocked
281+
# Get-Command cannot claim Test-ValidPath is present when the Validation module was
282+
# unloaded by an earlier test in the shard, and guard the call so it never throws
283+
# "Test-ValidPath is not recognized".
284+
$scriptPathValid = $false
285+
if ($null -ne (Microsoft.PowerShell.Core\Get-Command -Name 'Test-ValidPath' -ErrorAction SilentlyContinue)) {
286+
try {
287+
$scriptPathValid = [bool](Test-ValidPath -Path $ScriptPath -PathType File)
288+
}
289+
catch {
290+
$scriptPathValid = ($ScriptPath -and -not [string]::IsNullOrWhiteSpace($ScriptPath) -and (Test-Path -LiteralPath $ScriptPath -ErrorAction SilentlyContinue))
303291
}
304292
}
305293
else {
306-
# Fallback to manual validation
307-
if (-not ($ScriptPath -and -not [string]::IsNullOrWhiteSpace($ScriptPath) -and (Test-Path -LiteralPath $ScriptPath -ErrorAction SilentlyContinue))) {
308-
throw "Python script not found: $ScriptPath"
309-
}
294+
$scriptPathValid = ($ScriptPath -and -not [string]::IsNullOrWhiteSpace($ScriptPath) -and (Test-Path -LiteralPath $ScriptPath -ErrorAction SilentlyContinue))
295+
}
296+
if (-not $scriptPathValid) {
297+
throw "Python script not found: $ScriptPath"
310298
}
311299

312300
# Validate script is readable

tests/unit/library/chocolatey/library-chocolatey-detection.tests.ps1

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,21 @@ Describe 'ChocolateyDetection Module' {
149149
It 'Requires choco command when CheckCommand is specified' {
150150
Mock-EnvironmentVariable -Name 'ChocolateyInstall' -Value $script:FakeChocoRoot
151151

152+
# Windows CI runners usually have Chocolatey installed; hide the real binary so
153+
# -CheckCommand exercises the "root found but command missing" branch. Also hide
154+
# Test-ValidPath so Get-ChocolateyRoot stays on its Test-Path fallback (the
155+
# Validation module is not imported in this test's isolated scope).
156+
Mock Get-Command {
157+
$cmdName = $Name
158+
if ([string]::IsNullOrWhiteSpace($cmdName) -and $args.Count -gt 0) {
159+
$cmdName = [string]$args[0]
160+
}
161+
if ($cmdName -in @('choco', 'Test-ValidPath')) {
162+
return $null
163+
}
164+
return Microsoft.PowerShell.Core\Get-Command @PSBoundParameters
165+
} -ModuleName ChocolateyDetection
166+
152167
Test-ChocolateyInstalled -CheckCommand | Should -Be $false
153168
}
154169
}

tests/unit/library/nodejs/library-nodejs.tests.ps1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ Describe 'NodeJs Module Functions' {
181181
}
182182

183183
It 'Restore scriptblock removes NODE_PATH when it was originally unset' {
184+
# The restore only clears NODE_PATH when Set-NodePathForPnpm actually set it,
185+
# i.e. when search paths exist. Ambient search paths vary per shard/runner, so
186+
# force a deterministic non-empty result.
187+
Mock Get-NodeModuleSearchPaths { @((Join-Path $script:TestDir 'fake-node-modules')) } -ModuleName NodeJs
184188
Remove-Item Env:\NODE_PATH -ErrorAction SilentlyContinue
185189
$restore = Set-NodePathForPnpm
186190
$env:NODE_PATH = '/simulated-change'

0 commit comments

Comments
 (0)