返回 DeepSeek-Reasonix
shell_selection_regression_test.go
根目录 / internal / sandbox / shell_selection_regression_test.go
1 package sandbox
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8 )
9
10 func TestConfiguredShellPathCompatibilityAndCacheIdentity(t *testing.T) {
11 exists := func(string) bool { return true }
12 noWSL := func(string) bool { return false }
13 for _, goos := range []string{"darwin", "linux"} {
14 if got := configuredShellPathForPreference(goos, "powershell", "/portable/pwsh", exists, noWSL); got != "/portable/pwsh" {
15 t.Fatalf("%s: legacy PowerShell preference lost its configured pwsh: %q", goos, got)
16 }
17 if shellInventoryKey(goos, "bash", "/portable/A/bash") == shellInventoryKey(goos, "bash", "/portable/a/bash") {
18 t.Fatalf("%s: different configured executables share a capability snapshot", goos)
19 }
20 }
21 if shellInventoryKey("windows", "PWSH", `C:\Portable\pwsh.exe`) != shellInventoryKey("windows", "pwsh", `c:\portable\PWSH.EXE`) {
22 t.Fatal("Windows snapshot identity must remain case-insensitive")
23 }
24 }
25
26 func TestConfiguredShellSnapshotUsesConfiguredExecutable(t *testing.T) {
27 for _, goos := range []string{"windows", "darwin", "linux"} {
28 t.Run(goos, func(t *testing.T) {
29 prefer, binary, id := "bash", "bash", ShellCapabilityBash
30 if goos == "windows" {
31 prefer, binary, id = "pwsh", "pwsh.exe", ShellCapabilityPwsh
32 }
33 configured := filepath.Join(t.TempDir(), binary)
34 if err := os.WriteFile(configured, []byte("#!/bin/sh\nexit 0\n"), 0o700); err != nil {
35 t.Fatal(err)
36 }
37 // A real snapshot must retain config provenance, not only manually
38 // constructed unit-test snapshots. Non-Windows Bash probes are no-ops.
39 snap := buildShellSnapshot(goos, prefer, configured)
40 // On Windows, POSIX fixtures cannot execute a shebang; the native
41 // configured PowerShell branch above needs no such probe.
42 if goos != "windows" {
43 snap.probeFunc = func(string) bool { return true }
44 snap.probeCache = map[string]bool{}
45 snap.caps = unixShellCapabilities(snap)
46 }
47 sh := resolveShell(prefer, configured, nil, goos, snap.lookPath, snap.exists, snap.bashCands, snap.psCands, snap.probe, snap.isWSL)
48 if sh.Path != configured {
49 t.Fatalf("runtime did not select config: %+v", sh)
50 }
51 for _, cap := range snap.caps {
52 if cap.ID == id && (!cap.Available || cap.Path != configured || cap.Source != ShellSourceConfig) {
53 t.Fatalf("config was lost during discovery: %+v", cap)
54 }
55 }
56 })
57 }
58 }
59
60 func TestWSLBashAliasesExcluded(t *testing.T) {
61 const gitBash = `D:\PortableGit\bin\bash.exe`
62 isWSL := func(p string) bool { return isWindowsWSLBashPath(p, `C:\Windows`) }
63 for _, alias := range []string{
64 `C:\Windows\System32\bash.exe`,
65 `C:\Users\tester\AppData\Local\Microsoft\WindowsApps\bash.exe`,
66 `C:/Users/tester/AppData/Local/MICROSOFT/WindowsApps/BASH.EXE`,
67 `\\?\C:\Users\tester\AppData\Local\Microsoft\WindowsApps\bash.exe`,
68 `C:\Users\tester\AppData\Local\Microsoft\WindowsApps\MicrosoftCorporationII.WindowsSubsystemForLinux_8wekyb3d8bbwe\bash.exe`,
69 } {
70 t.Run(alias, func(t *testing.T) {
71 if !isWSL(alias) {
72 t.Fatal("WSL launcher was accepted as native Bash")
73 }
74 lookup := fakeLookPath(map[string]string{"bash": alias})
75 for _, configured := range []string{"", alias} {
76 sh := resolveShell("bash", configured, nil, "windows", lookup, func(string) bool { return true }, []string{gitBash}, nil, func(string) bool { return true }, isWSL)
77 if sh.Path != gitBash {
78 t.Fatalf("configured=%q resolved=%+v; must skip even a healthy WSL alias", configured, sh)
79 }
80 }
81 snap := &shellSnapshot{lookPath: lookup, exists: func(string) bool { return true }, isWSL: isWSL,
82 bashCands: []string{gitBash}, probeFunc: func(string) bool { return true }, probeCache: map[string]bool{}}
83 if cap := windowsShellCapabilities(snap)[0]; !cap.Available || cap.Path != gitBash {
84 t.Fatalf("capability accepted WSL: %+v", cap)
85 }
86 })
87 }
88 if !isWindowsWSLBashPath(`C:\Users\tester\AppData\Local\Microsoft\WindowsApps\bash.exe`, "") {
89 t.Fatal("alias exclusion must not depend on SystemRoot")
90 }
91 for _, native := range []string{"", gitBash, `C:\WindowsPortable\Git\bin\bash.exe`, `C:\Users\tester\Microsoft\WindowsAppsOther\bash.exe`} {
92 if isWSL(native) {
93 t.Fatalf("native path incorrectly excluded: %q", native)
94 }
95 }
96 }
97
98 func TestPowerShellPreferenceAndInventoryUseCompatibleConfiguredPath(t *testing.T) {
99 const ps = `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`
100 const pwsh = `C:\Program Files\PowerShell\7\pwsh.exe`
101 const portablePS = `D:\PortablePowerShell\powershell.exe`
102 const portablePwsh = `D:\PortablePowerShell\pwsh.exe`
103 const wrapper = `D:\Tools\ps-wrapper.exe`
104 for _, tc := range []struct {
105 name, prefer, configured, want string
106 installed bool
107 }{
108 {"switch to Windows PowerShell", "powershell", portablePwsh, ps, true},
109 {"switch to PowerShell 7", "pwsh", portablePS, pwsh, true},
110 {"portable PS5", "powershell", portablePS, portablePS, false},
111 {"portable PS7", "pwsh", portablePwsh, portablePwsh, false},
112 {"config before standard", "pwsh", portablePwsh, portablePwsh, true},
113 {"auto portable PS7", "auto", portablePwsh, portablePwsh, false},
114 {"auto portable PS5", "auto", portablePS, portablePS, false},
115 {"case insensitive", " PWSH ", portablePwsh, portablePwsh, false},
116 {"intentional wrapper", "powershell", wrapper, wrapper, false},
117 } {
118 t.Run(tc.name, func(t *testing.T) {
119 paths := map[string]string{}
120 var candidates []string
121 if tc.installed {
122 paths["powershell"], paths["pwsh"] = ps, pwsh
123 candidates = []string{pwsh, ps}
124 }
125 lookup := fakeLookPath(paths)
126 exists := func(p string) bool { return p == tc.configured || tc.installed && (p == ps || p == pwsh) }
127 noWSL := func(string) bool { return false }
128 sh := resolveShell(tc.prefer, tc.configured, nil, "windows", lookup, exists, nil, candidates, func(string) bool { return true }, noWSL)
129 if sh.Path != tc.want {
130 t.Fatalf("resolved=%+v, want %q", sh, tc.want)
131 }
132 snap := &shellSnapshot{prefer: tc.prefer, configPath: tc.configured, lookPath: lookup, exists: exists, isWSL: noWSL, psCands: candidates, probeCache: map[string]bool{}}
133 id := ShellCapabilityPowerShell
134 if sh.SupportsChaining() {
135 id = ShellCapabilityPwsh
136 }
137 for _, cap := range windowsShellCapabilities(snap) {
138 if cap.ID != id {
139 continue
140 }
141 if !cap.Available || cap.Path != tc.want {
142 t.Fatalf("runtime=%+v, capability=%+v", sh, cap)
143 }
144 if tc.want == tc.configured && cap.Source != ShellSourceConfig {
145 t.Fatalf("custom path source=%q", cap.Source)
146 }
147 }
148 if strings.TrimSpace(tc.prefer) != "auto" && tc.want != tc.configured && configuredShellPathForPreference("windows", tc.prefer, tc.configured, exists, noWSL) != "" {
149 t.Fatal("shared path consumer retained the wrong PowerShell version")
150 }
151 })
152 }
153 }
154
154 lines GO