Skip to content

Test failures from module-path refactor PR #26565: tests 103778 / 103842 / 108309 fail on ZIP/TAR/portable distribution lanes #27346

@jshigetomi

Description

@jshigetomi

Summary

Three Pester tests fail in the ReleaseAutomationTest comparison of 7.7.0-preview.1 against the 7.6.0-rc.1 baseline on portable (ZIP/TAR) and several non-portable distribution lanes. All three are downstream of PR #26565 ("Refactor the module path construction code", commit 592668bd0), which is in release/v7.7.0-preview.1 but is not in v7.6.0-rc.1 (verified: git merge-base --is-ancestor 592668bd0 v7.6.0-rc.1 returns 1).

Test ID Test File Origin
103778 ConsoleHost - SettingsFile - PSModulePath - Verify PowerShell PSModulePath should contain paths from config file (Windows) test/powershell/Host/ConsoleHost.Tests.ps1#L402 New test added by PR #26565
103842 Non-Windows equivalent of 103778 (same It block, run on Linux/macOS lanes) test/powershell/Host/ConsoleHost.Tests.ps1#L402 New test added by PR #26565
108309 Import-Module with WinCompat - WinCompat process does not inherit PowerShell-Core-specific paths test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1#L727 Pre-existing since PR #7183 (2018); newly fails in preview.1

Lanes affected (one or more of these tests fail per lane):

  • WindowsServer2016ZIP - Unelevated
  • ubuntu22 TAR
  • mariner ARM64
  • macOS TAR, macOS
  • linux ARM32
  • debian12
  • azurelinux30

First failing build: ReleaseAutomationTest-27557-ps-671171.

These tests were previously tracked as Case 2 of issue #27343 and are split out here because the root cause is unrelated to PR #27305 (the original framing of #27343).

Background: what PR #26565 changed

Commit 592668bd0 rewrote ModuleIntrinsics.GetModulePath and replaced the helper AddToPath (with PathContainsSubstring) with UpdatePath, which uses HashSet<string> plus Path.TrimEndingDirectorySeparator for dedup and advances insertPosition after each insert. It also added a static static ModuleIntrinsics() { SetModulePath(); } initializer.

The "EVT.Process exists" branch in preview.1's GetModulePath is:

string systemModulePathToUse = string.IsNullOrEmpty(hklmMachineModulePath)
    ? psHomeModulePath
    : hklmMachineModulePath;

int insertIndex = 0;
currentProcessModulePath = UpdatePath(currentProcessModulePath, personalModulePathToUse, ref insertIndex);
currentProcessModulePath = UpdatePath(currentProcessModulePath, sharedModulePath,       ref insertIndex);
currentProcessModulePath = UpdatePath(currentProcessModulePath, systemModulePathToUse,  ref insertIndex);

When hklmMachineModulePath is non-empty, systemModulePathToUse = hklmMachineModulePath and psHomeModulePath (i.e., $PSHOME/Modules) is never added to the process PSModulePath. The same logical branch exists on Linux/macOS via the equivalent OS-level system module path.

PR #26565 also added the new test Verify PowerShell PSModulePath should contain paths from config file (test 103778 / 103842) that asserts $PSHOME/Modules is already present at IndexOf > 0 in $env:PSModulePath. That assertion is incompatible with portable distributions where $PSHOME is a user-local path not registered in the OS-level PSModulePath.

Why each test fails

Tests 103778 (Windows) and 103842 (non-Windows)

# BeforeAll: write settings file with PSModulePath = "$PSHOME/Modules<sep>$TestDrive/NonExist"
$psModulePath = & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command '$env:PSModulePath'
# Assertion:
$index = $psModulePath.IndexOf("$mPath1$pathSep", [StringComparison]::OrdinalIgnoreCase)
$index | Should -BeGreaterThan 0   # FAILS - $PSHOME/Modules ends up at position 0

Failure mechanics on a portable distribution:

  1. Child pwsh inherits parent $env:PSModulePath = <personal><sep><shared><sep><OS-defaults> and does not contain the ZIP/TAR-extracted $PSHOME/Modules.
  2. Settings-file PSModulePath is read as hkcuUserModulePath = "$PSHOME/Modules<sep>$TestDrive/NonExist".
  3. GetModulePath "Process exists" branch sets personalModulePathToUse = hkcuUserModulePath.
  4. UpdatePath(current, "$PSHOME/Modules<sep>$TestDrive/NonExist", ref insertIndex=0):
    • $PSHOME/Modules is NOT in initialPaths -> inserted at position 0.
    • $TestDrive/NonExist is NOT in initialPaths -> inserted at position 1.
  5. Final PSModulePath starts with $PSHOME/Modules<sep>$TestDrive/NonExist<sep>....
  6. IndexOf("$PSHOME/Modules<sep>") = 0 -> Should -BeGreaterThan 0 fails.
  7. StartsWith("$TestDrive/NonExist<sep>") = false -> later assertion also fails.

The BeforeAll comment in the test literally states: "$mPath1 already exists in the value of env PSModulePath, so it won''t be added again." That precondition does not hold on portable distributions. Since the It block is shared across platforms, it manifests as test 103778 on Windows lanes and 103842 on non-Windows lanes.

Test 108309

$pscoreSystemPath = Join-Path -Path $PSHOME -ChildPath 'Modules'
$pscorePaths = $env:psmodulepath
$pscorePaths | Should -BeLike "*$pscoreSystemPath*"   # FAILS

Same root assumption - the test requires $PSHOME/Modules to be in $env:PSModulePath. On portable distributions this is not guaranteed.

This test is 7+ years old and passed on the v7.6.0-rc.1 baseline (confirmed via the rc.1 ReleaseAutomationTest xUnit output: <test-case description="WinCompat process does not inherit PowerShell-Core-specific paths" ... result="Success" success="True" executed="True"/>). It now fails on preview.1, so this is a real product-side regression introduced by PR #26565 - not just a comparator artifact. The rc.1 code (AddToPath + PathContainsSubstring) produced an effective PSModulePath that contained $PSHOME/Modules on the affected lanes; preview.1's UpdatePath rewrite no longer does. A product-side fix is required in addition to the test fix below.

Suggested resolution

Test side (required regardless): make the assertions portable-distribution-aware.

For tests 103778 / 103842 (test/powershell/Host/ConsoleHost.Tests.ps1):

Option A - have BeforeAll ensure the precondition the test was written against:

BeforeAll {
    $CustomSettingsFile = Join-Path -Path $TestDrive -ChildPath 'powershell.test.json'
    $mPath1 = Join-Path $PSHOME 'Modules'
    $mPath2 = Join-Path $TestDrive 'NonExist'
    $pathSep = [System.IO.Path]::PathSeparator

    # Test assumes $PSHOME/Modules is already in $env:PSModulePath.
    # On ZIP/TAR/portable distributions this may not hold; ensure it.
    $contains = $env:PSModulePath -split [regex]::Escape($pathSep) |
        Where-Object { [string]::Equals($_, $mPath1, 'OrdinalIgnoreCase') }
    if (-not $contains) {
        $script:savedPSModulePath = $env:PSModulePath
        $env:PSModulePath = "$mPath1$pathSep$env:PSModulePath"
    }

    $ModulePath = "${mPath1}${pathSep}${mPath2}".Replace(''\'', "\\")
    Set-Content -Path $CustomSettingsfile -Value "{`"Microsoft.PowerShell:ExecutionPolicy`":`"Unrestricted`", `"PSModulePath`": `"$ModulePath`" }" -ErrorAction Stop
}

AfterAll {
    if ($script:savedPSModulePath) {
        $env:PSModulePath = $script:savedPSModulePath
    }
}

Option B - skip when the precondition does not hold:

It "Verify PowerShell PSModulePath should contain paths from config file" {
    if (($env:PSModulePath -split [regex]::Escape([IO.Path]::PathSeparator)) -notcontains $mPath1) {
        Set-ItResult -Skipped -Because ''$PSHOME/Modules is not in $env:PSModulePath (portable distribution); test precondition does not hold.''
        return
    }
    # ... existing assertions ...
}

For test 108309 (test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1):

It ''WinCompat process does not inherit PowerShell-Core-specific paths'' {
    $pscoreUserPath   = Join-Path -Path $HOME -ChildPath "Documents/PowerShell/Modules"
    $pscoreSharedPath = Join-Path -Path $env:ProgramFiles -ChildPath "PowerShell/Modules"
    $pscoreSystemPath = Join-Path -Path $PSHOME -ChildPath ''Modules''
    $pscorePaths = $env:psmodulepath
    $pscorePaths | Should -BeLike "*$pscoreUserPath*"
    $pscorePaths | Should -BeLike "*$pscoreSharedPath*"
    if ($pscorePaths -like "*$pscoreSystemPath*") {
        $pscorePaths | Should -BeLike "*$pscoreSystemPath*"
    }
    # else: tolerated - portable/ZIP/TAR distributions do not put $PSHOME/Modules in $env:PSModulePath.
    # Remaining WinCompat exclusion assertions still run unchanged.
    # ...
}

Product side (required - confirmed regression): PR #26565's AddToPath -> UpdatePath rewrite changed the effective PSModulePath shape on the affected lanes; rc.1 included $PSHOME/Modules, preview.1 does not. Two viable options:

  1. Always include psHomeModulePath in the system path. In the "EVT.Process exists" branch of ModuleIntrinsics.GetModulePath, instead of:

    string systemModulePathToUse = string.IsNullOrEmpty(hklmMachineModulePath)
        ? psHomeModulePath
        : hklmMachineModulePath;
    ...
    currentProcessModulePath = UpdatePath(currentProcessModulePath, systemModulePathToUse, ref insertIndex);

    add both, so $PSHOME/Modules is always present regardless of HKLM state:

    if (!string.IsNullOrEmpty(hklmMachineModulePath))
    {
        currentProcessModulePath = UpdatePath(currentProcessModulePath, hklmMachineModulePath, ref insertIndex);
    }
    currentProcessModulePath = UpdatePath(currentProcessModulePath, psHomeModulePath, ref insertIndex);

    UpdatePath already dedups via HashSet<string>, so no double-insertion when HKLM also contains $PSHOME/Modules (e.g. MSI install).

  2. Restore the rc.1 path-construction shape directly by reverting the relevant portion of GetModulePath to the pre-Refactor the module path construction code to make it more robust and easier to maintain #26565 behavior (compose via AddToPath semantics) for the affected branch. Lower-risk for backport but loses the dedup/ordering improvements of Refactor the module path construction code to make it more robust and easier to maintain #26565.

Option 1 is preferred. After the fix, re-run the ReleaseAutomationTest lanes that originally failed (especially WindowsServer2016ZIP - Unelevated) and confirm $PSHOME/Modules is in $env:PSModulePath.

Branch strategy

Per .github/instructions/code-review-branch-strategy.instructions.md: PR #26565 is on master and on release/v7.7.0-preview.1. Fix the test files on master first; backport to release branches as needed. Do not add ZIP/TAR-specific workarounds only on the release branch.

Files affected

  • test/powershell/Host/ConsoleHost.Tests.ps1 (lines 391-413) - tests 103778 / 103842
  • test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 (line 727) - test 108309
  • src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs - product-side fix for confirmed 108309 regression (rc.1 result: Success)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-BugIssue has been identified as a bug in the product

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions