shell.test.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import * as vscode from "vscode"
  2. import { userInfo } from "os"
  3. import { getShell } from "../shell"
  4. describe("Shell Detection Tests", () => {
  5. let originalPlatform: string
  6. let originalEnv: NodeJS.ProcessEnv
  7. let originalGetConfig: any
  8. let originalUserInfo: any
  9. // Helper to mock VS Code configuration
  10. function mockVsCodeConfig(platformKey: string, defaultProfileName: string | null, profiles: Record<string, any>) {
  11. vscode.workspace.getConfiguration = () =>
  12. ({
  13. get: (key: string) => {
  14. if (key === `defaultProfile.${platformKey}`) {
  15. return defaultProfileName
  16. }
  17. if (key === `profiles.${platformKey}`) {
  18. return profiles
  19. }
  20. return undefined
  21. },
  22. }) as any
  23. }
  24. beforeEach(() => {
  25. // Store original references
  26. originalPlatform = process.platform
  27. originalEnv = { ...process.env }
  28. originalGetConfig = vscode.workspace.getConfiguration
  29. originalUserInfo = userInfo
  30. // Clear environment variables for a clean test
  31. delete process.env.SHELL
  32. delete process.env.COMSPEC
  33. // Default userInfo() mock
  34. ;(userInfo as any) = () => ({ shell: null })
  35. })
  36. afterEach(() => {
  37. // Restore everything
  38. Object.defineProperty(process, "platform", { value: originalPlatform })
  39. process.env = originalEnv
  40. vscode.workspace.getConfiguration = originalGetConfig
  41. ;(userInfo as any) = originalUserInfo
  42. })
  43. // --------------------------------------------------------------------------
  44. // Windows Shell Detection
  45. // --------------------------------------------------------------------------
  46. describe("Windows Shell Detection", () => {
  47. beforeEach(() => {
  48. Object.defineProperty(process, "platform", { value: "win32" })
  49. })
  50. it("uses explicit PowerShell 7 path from VS Code config (profile path)", () => {
  51. mockVsCodeConfig("windows", "PowerShell", {
  52. PowerShell: { path: "C:\\Program Files\\PowerShell\\7\\pwsh.exe" },
  53. })
  54. expect(getShell()).toBe("C:\\Program Files\\PowerShell\\7\\pwsh.exe")
  55. })
  56. it("uses PowerShell 7 path if source is 'PowerShell' but no explicit path", () => {
  57. mockVsCodeConfig("windows", "PowerShell", {
  58. PowerShell: { source: "PowerShell" },
  59. })
  60. expect(getShell()).toBe("C:\\Program Files\\PowerShell\\7\\pwsh.exe")
  61. })
  62. it("falls back to legacy PowerShell if profile includes 'powershell' but no path/source", () => {
  63. mockVsCodeConfig("windows", "PowerShell", {
  64. PowerShell: {},
  65. })
  66. expect(getShell()).toBe("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe")
  67. })
  68. it("uses WSL bash when profile indicates WSL source", () => {
  69. mockVsCodeConfig("windows", "WSL", {
  70. WSL: { source: "WSL" },
  71. })
  72. expect(getShell()).toBe("/bin/bash")
  73. })
  74. it("uses WSL bash when profile name includes 'wsl'", () => {
  75. mockVsCodeConfig("windows", "Ubuntu WSL", {
  76. "Ubuntu WSL": {},
  77. })
  78. expect(getShell()).toBe("/bin/bash")
  79. })
  80. it("defaults to cmd.exe if no special profile is matched", () => {
  81. mockVsCodeConfig("windows", "CommandPrompt", {
  82. CommandPrompt: {},
  83. })
  84. expect(getShell()).toBe("C:\\Windows\\System32\\cmd.exe")
  85. })
  86. it("handles undefined profile gracefully", () => {
  87. // Mock a case where defaultProfileName exists but the profile doesn't
  88. mockVsCodeConfig("windows", "NonexistentProfile", {})
  89. expect(getShell()).toBe("C:\\Windows\\System32\\cmd.exe")
  90. })
  91. it("respects userInfo() if no VS Code config is available", () => {
  92. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  93. ;(userInfo as any) = () => ({ shell: "C:\\Custom\\PowerShell.exe" })
  94. expect(getShell()).toBe("C:\\Custom\\PowerShell.exe")
  95. })
  96. it("respects an odd COMSPEC if no userInfo shell is available", () => {
  97. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  98. process.env.COMSPEC = "D:\\CustomCmd\\cmd.exe"
  99. expect(getShell()).toBe("D:\\CustomCmd\\cmd.exe")
  100. })
  101. })
  102. // --------------------------------------------------------------------------
  103. // macOS Shell Detection
  104. // --------------------------------------------------------------------------
  105. describe("macOS Shell Detection", () => {
  106. beforeEach(() => {
  107. Object.defineProperty(process, "platform", { value: "darwin" })
  108. })
  109. it("uses VS Code profile path if available", () => {
  110. mockVsCodeConfig("osx", "MyCustomShell", {
  111. MyCustomShell: { path: "/usr/local/bin/fish" },
  112. })
  113. expect(getShell()).toBe("/usr/local/bin/fish")
  114. })
  115. it("falls back to userInfo().shell if no VS Code config is available", () => {
  116. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  117. ;(userInfo as any) = () => ({ shell: "/opt/homebrew/bin/zsh" })
  118. expect(getShell()).toBe("/opt/homebrew/bin/zsh")
  119. })
  120. it("falls back to SHELL env var if no userInfo shell is found", () => {
  121. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  122. process.env.SHELL = "/usr/local/bin/zsh"
  123. expect(getShell()).toBe("/usr/local/bin/zsh")
  124. })
  125. it("falls back to /bin/zsh if no config, userInfo, or env variable is set", () => {
  126. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  127. expect(getShell()).toBe("/bin/zsh")
  128. })
  129. })
  130. // --------------------------------------------------------------------------
  131. // Linux Shell Detection
  132. // --------------------------------------------------------------------------
  133. describe("Linux Shell Detection", () => {
  134. beforeEach(() => {
  135. Object.defineProperty(process, "platform", { value: "linux" })
  136. })
  137. it("uses VS Code profile path if available", () => {
  138. mockVsCodeConfig("linux", "CustomProfile", {
  139. CustomProfile: { path: "/usr/bin/fish" },
  140. })
  141. expect(getShell()).toBe("/usr/bin/fish")
  142. })
  143. it("falls back to userInfo().shell if no VS Code config is available", () => {
  144. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  145. ;(userInfo as any) = () => ({ shell: "/usr/bin/zsh" })
  146. expect(getShell()).toBe("/usr/bin/zsh")
  147. })
  148. it("falls back to SHELL env var if no userInfo shell is found", () => {
  149. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  150. process.env.SHELL = "/usr/bin/fish"
  151. expect(getShell()).toBe("/usr/bin/fish")
  152. })
  153. it("falls back to /bin/bash if nothing is set", () => {
  154. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  155. expect(getShell()).toBe("/bin/bash")
  156. })
  157. })
  158. // --------------------------------------------------------------------------
  159. // Unknown Platform & Error Handling
  160. // --------------------------------------------------------------------------
  161. describe("Unknown Platform / Error Handling", () => {
  162. it("falls back to /bin/sh for unknown platforms", () => {
  163. Object.defineProperty(process, "platform", { value: "sunos" })
  164. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  165. expect(getShell()).toBe("/bin/sh")
  166. })
  167. it("handles VS Code config errors gracefully, falling back to userInfo shell if present", () => {
  168. Object.defineProperty(process, "platform", { value: "linux" })
  169. vscode.workspace.getConfiguration = () => {
  170. throw new Error("Configuration error")
  171. }
  172. ;(userInfo as any) = () => ({ shell: "/bin/bash" })
  173. expect(getShell()).toBe("/bin/bash")
  174. })
  175. it("handles userInfo errors gracefully, falling back to environment variable if present", () => {
  176. Object.defineProperty(process, "platform", { value: "darwin" })
  177. vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
  178. ;(userInfo as any) = () => {
  179. throw new Error("userInfo error")
  180. }
  181. process.env.SHELL = "/bin/zsh"
  182. expect(getShell()).toBe("/bin/zsh")
  183. })
  184. it("falls back fully to default shell paths if everything fails", () => {
  185. Object.defineProperty(process, "platform", { value: "linux" })
  186. vscode.workspace.getConfiguration = () => {
  187. throw new Error("Configuration error")
  188. }
  189. ;(userInfo as any) = () => {
  190. throw new Error("userInfo error")
  191. }
  192. delete process.env.SHELL
  193. expect(getShell()).toBe("/bin/bash")
  194. })
  195. })
  196. })