update-process-env-spec.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /** @babel */
  2. 'not strict';
  3. import path from 'path';
  4. import childProcess from 'child_process';
  5. import {
  6. updateProcessEnv,
  7. shouldGetEnvFromShell
  8. } from '../src/update-process-env';
  9. import mockSpawn from 'mock-spawn';
  10. const temp = require('temp').track();
  11. describe('updateProcessEnv(launchEnv)', function() {
  12. let originalProcessEnv, originalProcessPlatform, originalSpawn, spawn;
  13. beforeEach(function() {
  14. originalSpawn = childProcess.spawn;
  15. spawn = mockSpawn();
  16. childProcess.spawn = spawn;
  17. originalProcessEnv = process.env;
  18. originalProcessPlatform = process.platform;
  19. process.env = {};
  20. });
  21. afterEach(function() {
  22. if (originalSpawn) {
  23. childProcess.spawn = originalSpawn;
  24. }
  25. process.env = originalProcessEnv;
  26. process.platform = originalProcessPlatform;
  27. try {
  28. temp.cleanupSync();
  29. } catch (e) {
  30. // Do nothing
  31. }
  32. });
  33. describe('when the launch environment appears to come from a shell', function() {
  34. it('updates process.env to match the launch environment because PWD is set', async function() {
  35. process.env = {
  36. WILL_BE_DELETED: 'hi',
  37. NODE_ENV: 'the-node-env',
  38. NODE_PATH: '/the/node/path',
  39. ATOM_HOME: '/the/atom/home'
  40. };
  41. const initialProcessEnv = process.env;
  42. await updateProcessEnv({
  43. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  44. PWD: '/the/dir',
  45. TERM: 'xterm-something',
  46. KEY1: 'value1',
  47. KEY2: 'value2'
  48. });
  49. expect(process.env).toEqual({
  50. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  51. PWD: '/the/dir',
  52. TERM: 'xterm-something',
  53. KEY1: 'value1',
  54. KEY2: 'value2',
  55. NODE_ENV: 'the-node-env',
  56. NODE_PATH: '/the/node/path',
  57. ATOM_HOME: '/the/atom/home'
  58. });
  59. // See #11302. On Windows, `process.env` is a magic object that offers
  60. // case-insensitive environment variable matching, so we cannot replace it
  61. // with another object.
  62. expect(process.env).toBe(initialProcessEnv);
  63. });
  64. it('updates process.env to match the launch environment because PROMPT is set', async function() {
  65. process.env = {
  66. WILL_BE_DELETED: 'hi',
  67. NODE_ENV: 'the-node-env',
  68. NODE_PATH: '/the/node/path',
  69. ATOM_HOME: '/the/atom/home'
  70. };
  71. const initialProcessEnv = process.env;
  72. await updateProcessEnv({
  73. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  74. PROMPT: '$P$G',
  75. KEY1: 'value1',
  76. KEY2: 'value2'
  77. });
  78. expect(process.env).toEqual({
  79. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  80. PROMPT: '$P$G',
  81. KEY1: 'value1',
  82. KEY2: 'value2',
  83. NODE_ENV: 'the-node-env',
  84. NODE_PATH: '/the/node/path',
  85. ATOM_HOME: '/the/atom/home'
  86. });
  87. // See #11302. On Windows, `process.env` is a magic object that offers
  88. // case-insensitive environment variable matching, so we cannot replace it
  89. // with another object.
  90. expect(process.env).toBe(initialProcessEnv);
  91. });
  92. it('updates process.env to match the launch environment because PSModulePath is set', async function() {
  93. process.env = {
  94. WILL_BE_DELETED: 'hi',
  95. NODE_ENV: 'the-node-env',
  96. NODE_PATH: '/the/node/path',
  97. ATOM_HOME: '/the/atom/home'
  98. };
  99. const initialProcessEnv = process.env;
  100. await updateProcessEnv({
  101. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  102. PSModulePath:
  103. 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules\\',
  104. KEY1: 'value1',
  105. KEY2: 'value2'
  106. });
  107. expect(process.env).toEqual({
  108. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  109. PSModulePath:
  110. 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules\\',
  111. KEY1: 'value1',
  112. KEY2: 'value2',
  113. NODE_ENV: 'the-node-env',
  114. NODE_PATH: '/the/node/path',
  115. ATOM_HOME: '/the/atom/home'
  116. });
  117. // See #11302. On Windows, `process.env` is a magic object that offers
  118. // case-insensitive environment variable matching, so we cannot replace it
  119. // with another object.
  120. expect(process.env).toBe(initialProcessEnv);
  121. });
  122. it('allows ATOM_HOME to be overwritten only if the new value is a valid path', async function() {
  123. let newAtomHomePath = temp.mkdirSync('atom-home');
  124. process.env = {
  125. WILL_BE_DELETED: 'hi',
  126. NODE_ENV: 'the-node-env',
  127. NODE_PATH: '/the/node/path',
  128. ATOM_HOME: '/the/atom/home'
  129. };
  130. await updateProcessEnv({
  131. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  132. PWD: '/the/dir'
  133. });
  134. expect(process.env).toEqual({
  135. PWD: '/the/dir',
  136. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  137. NODE_ENV: 'the-node-env',
  138. NODE_PATH: '/the/node/path',
  139. ATOM_HOME: '/the/atom/home'
  140. });
  141. await updateProcessEnv({
  142. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  143. PWD: '/the/dir',
  144. ATOM_HOME: path.join(newAtomHomePath, 'non-existent')
  145. });
  146. expect(process.env).toEqual({
  147. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  148. PWD: '/the/dir',
  149. NODE_ENV: 'the-node-env',
  150. NODE_PATH: '/the/node/path',
  151. ATOM_HOME: '/the/atom/home'
  152. });
  153. await updateProcessEnv({
  154. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  155. PWD: '/the/dir',
  156. ATOM_HOME: newAtomHomePath
  157. });
  158. expect(process.env).toEqual({
  159. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  160. PWD: '/the/dir',
  161. NODE_ENV: 'the-node-env',
  162. NODE_PATH: '/the/node/path',
  163. ATOM_HOME: newAtomHomePath
  164. });
  165. });
  166. it('allows ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT to be preserved if set', async function() {
  167. process.env = {
  168. WILL_BE_DELETED: 'hi',
  169. NODE_ENV: 'the-node-env',
  170. NODE_PATH: '/the/node/path',
  171. ATOM_HOME: '/the/atom/home'
  172. };
  173. await updateProcessEnv({
  174. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  175. PWD: '/the/dir',
  176. NODE_ENV: 'the-node-env',
  177. NODE_PATH: '/the/node/path',
  178. ATOM_HOME: '/the/atom/home'
  179. });
  180. expect(process.env).toEqual({
  181. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  182. PWD: '/the/dir',
  183. NODE_ENV: 'the-node-env',
  184. NODE_PATH: '/the/node/path',
  185. ATOM_HOME: '/the/atom/home'
  186. });
  187. await updateProcessEnv({
  188. PWD: '/the/dir',
  189. NODE_ENV: 'the-node-env',
  190. NODE_PATH: '/the/node/path',
  191. ATOM_HOME: '/the/atom/home'
  192. });
  193. expect(process.env).toEqual({
  194. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  195. PWD: '/the/dir',
  196. NODE_ENV: 'the-node-env',
  197. NODE_PATH: '/the/node/path',
  198. ATOM_HOME: '/the/atom/home'
  199. });
  200. });
  201. it('allows an existing env variable to be updated', async function() {
  202. process.env = {
  203. WILL_BE_UPDATED: 'old-value',
  204. NODE_ENV: 'the-node-env',
  205. NODE_PATH: '/the/node/path',
  206. ATOM_HOME: '/the/atom/home'
  207. };
  208. await updateProcessEnv(process.env);
  209. expect(process.env).toEqual(process.env);
  210. let updatedEnv = {
  211. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  212. WILL_BE_UPDATED: 'new-value',
  213. NODE_ENV: 'the-node-env',
  214. NODE_PATH: '/the/node/path',
  215. ATOM_HOME: '/the/atom/home',
  216. PWD: '/the/dir'
  217. };
  218. await updateProcessEnv(updatedEnv);
  219. expect(process.env).toEqual(updatedEnv);
  220. });
  221. });
  222. describe('when the launch environment does not come from a shell', function() {
  223. describe('on macOS', function() {
  224. it("updates process.env to match the environment in the user's login shell", async function() {
  225. if (process.platform === 'win32') return; // TestsThatFailOnWin32
  226. process.platform = 'darwin';
  227. process.env.SHELL = '/my/custom/bash';
  228. spawn.setDefault(
  229. spawn.simple(
  230. 0,
  231. 'FOO=BAR=BAZ=QUUX\0MULTILINE\nNAME=multiline\nvalue\0TERM=xterm-something\0PATH=/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path'
  232. )
  233. );
  234. await updateProcessEnv(process.env);
  235. expect(spawn.calls.length).toBe(1);
  236. expect(spawn.calls[0].command).toBe('/my/custom/bash');
  237. expect(spawn.calls[0].args).toEqual([
  238. '-ilc',
  239. 'command awk \'BEGIN{for(v in ENVIRON) printf("%s=%s%c", v, ENVIRON[v], 0)}\''
  240. ]);
  241. expect(process.env).toEqual({
  242. FOO: 'BAR=BAZ=QUUX',
  243. 'MULTILINE\nNAME': 'multiline\nvalue',
  244. TERM: 'xterm-something',
  245. PATH: '/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path'
  246. });
  247. // Doesn't error
  248. await updateProcessEnv(null);
  249. });
  250. });
  251. describe('on linux', function() {
  252. it("updates process.env to match the environment in the user's login shell", async function() {
  253. if (process.platform === 'win32') return; // TestsThatFailOnWin32
  254. process.platform = 'linux';
  255. process.env.SHELL = '/my/custom/bash';
  256. spawn.setDefault(
  257. spawn.simple(
  258. 0,
  259. 'FOO=BAR=BAZ=QUUX\0MULTILINE\nNAME=multiline\nvalue\0TERM=xterm-something\0PATH=/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path'
  260. )
  261. );
  262. await updateProcessEnv(process.env);
  263. expect(spawn.calls.length).toBe(1);
  264. expect(spawn.calls[0].command).toBe('/my/custom/bash');
  265. expect(spawn.calls[0].args).toEqual([
  266. '-ilc',
  267. 'command awk \'BEGIN{for(v in ENVIRON) printf("%s=%s%c", v, ENVIRON[v], 0)}\''
  268. ]);
  269. expect(process.env).toEqual({
  270. FOO: 'BAR=BAZ=QUUX',
  271. 'MULTILINE\nNAME': 'multiline\nvalue',
  272. TERM: 'xterm-something',
  273. PATH: '/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path'
  274. });
  275. // Doesn't error
  276. await updateProcessEnv(null);
  277. });
  278. });
  279. describe('on windows', function() {
  280. it('does not update process.env', async function() {
  281. process.platform = 'win32';
  282. spyOn(childProcess, 'spawn');
  283. process.env = { FOO: 'bar' };
  284. await updateProcessEnv(process.env);
  285. expect(childProcess.spawn).not.toHaveBeenCalled();
  286. expect(process.env).toEqual({ FOO: 'bar' });
  287. });
  288. });
  289. describe('shouldGetEnvFromShell()', function() {
  290. it('indicates when the environment should be fetched from the shell', function() {
  291. if (process.platform === 'win32') return; // TestsThatFailOnWin32
  292. process.platform = 'darwin';
  293. expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true);
  294. expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/sh' })).toBe(
  295. true
  296. );
  297. expect(shouldGetEnvFromShell({ SHELL: '/bin/bash' })).toBe(true);
  298. expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/bash' })).toBe(
  299. true
  300. );
  301. expect(shouldGetEnvFromShell({ SHELL: '/bin/zsh' })).toBe(true);
  302. expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/zsh' })).toBe(
  303. true
  304. );
  305. expect(shouldGetEnvFromShell({ SHELL: '/bin/fish' })).toBe(true);
  306. expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/fish' })).toBe(
  307. true
  308. );
  309. process.platform = 'linux';
  310. expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true);
  311. expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/sh' })).toBe(
  312. true
  313. );
  314. expect(shouldGetEnvFromShell({ SHELL: '/bin/bash' })).toBe(true);
  315. expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/bash' })).toBe(
  316. true
  317. );
  318. expect(shouldGetEnvFromShell({ SHELL: '/bin/zsh' })).toBe(true);
  319. expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/zsh' })).toBe(
  320. true
  321. );
  322. expect(shouldGetEnvFromShell({ SHELL: '/bin/fish' })).toBe(true);
  323. expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/fish' })).toBe(
  324. true
  325. );
  326. });
  327. it('returns false when the environment indicates that Atom was launched from a shell', function() {
  328. process.platform = 'darwin';
  329. expect(
  330. shouldGetEnvFromShell({
  331. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  332. SHELL: '/bin/sh'
  333. })
  334. ).toBe(false);
  335. process.platform = 'linux';
  336. expect(
  337. shouldGetEnvFromShell({
  338. ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true',
  339. SHELL: '/bin/sh'
  340. })
  341. ).toBe(false);
  342. });
  343. it('returns false when the shell is undefined or empty', function() {
  344. process.platform = 'darwin';
  345. expect(shouldGetEnvFromShell(undefined)).toBe(false);
  346. expect(shouldGetEnvFromShell({})).toBe(false);
  347. process.platform = 'linux';
  348. expect(shouldGetEnvFromShell(undefined)).toBe(false);
  349. expect(shouldGetEnvFromShell({})).toBe(false);
  350. });
  351. });
  352. });
  353. });