/** @babel */ import path from 'path'; import childProcess from 'child_process'; import { updateProcessEnv, shouldGetEnvFromShell } from '../src/update-process-env'; import mockSpawn from 'mock-spawn'; const temp = require('temp').track(); describe('updateProcessEnv(launchEnv)', function() { let originalProcessEnv, originalProcessPlatform, originalSpawn, spawn; beforeEach(function() { originalSpawn = childProcess.spawn; spawn = mockSpawn(); childProcess.spawn = spawn; originalProcessEnv = process.env; originalProcessPlatform = process.platform; process.env = {}; }); afterEach(function() { if (originalSpawn) { childProcess.spawn = originalSpawn; } process.env = originalProcessEnv; process.platform = originalProcessPlatform; try { temp.cleanupSync(); } catch (e) { // Do nothing } }); describe('when the launch environment appears to come from a shell', function() { it('updates process.env to match the launch environment because PWD is set', async function() { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }; const initialProcessEnv = process.env; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', TERM: 'xterm-something', KEY1: 'value1', KEY2: 'value2' }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', TERM: 'xterm-something', KEY1: 'value1', KEY2: 'value2', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); // See #11302. On Windows, `process.env` is a magic object that offers // case-insensitive environment variable matching, so we cannot replace it // with another object. expect(process.env).toBe(initialProcessEnv); }); it('updates process.env to match the launch environment because PROMPT is set', async function() { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }; const initialProcessEnv = process.env; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PROMPT: '$P$G', KEY1: 'value1', KEY2: 'value2' }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PROMPT: '$P$G', KEY1: 'value1', KEY2: 'value2', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); // See #11302. On Windows, `process.env` is a magic object that offers // case-insensitive environment variable matching, so we cannot replace it // with another object. expect(process.env).toBe(initialProcessEnv); }); it('updates process.env to match the launch environment because PSModulePath is set', async function() { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }; const initialProcessEnv = process.env; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PSModulePath: 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules\\', KEY1: 'value1', KEY2: 'value2' }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PSModulePath: 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules\\', KEY1: 'value1', KEY2: 'value2', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); // See #11302. On Windows, `process.env` is a magic object that offers // case-insensitive environment variable matching, so we cannot replace it // with another object. expect(process.env).toBe(initialProcessEnv); }); it('allows ATOM_HOME to be overwritten only if the new value is a valid path', async function() { let newAtomHomePath = temp.mkdirSync('atom-home'); process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir' }); expect(process.env).toEqual({ PWD: '/the/dir', ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', ATOM_HOME: path.join(newAtomHomePath, 'non-existent') }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', ATOM_HOME: newAtomHomePath }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: newAtomHomePath }); }); it('allows ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT to be preserved if set', async function() { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }; await updateProcessEnv({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); await updateProcessEnv({ PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); expect(process.env).toEqual({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', PWD: '/the/dir', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); }); it('allows an existing env variable to be updated', async function() { process.env = { WILL_BE_UPDATED: 'old-value', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }; await updateProcessEnv(process.env); expect(process.env).toEqual(process.env); let updatedEnv = { ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', WILL_BE_UPDATED: 'new-value', NODE_ENV: 'the-node-env', NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home', PWD: '/the/dir' }; await updateProcessEnv(updatedEnv); expect(process.env).toEqual(updatedEnv); }); }); describe('when the launch environment does not come from a shell', function() { describe('on macOS', function() { it("updates process.env to match the environment in the user's login shell", async function() { if (process.platform === 'win32') return; // TestsThatFailOnWin32 process.platform = 'darwin'; process.env.SHELL = '/my/custom/bash'; spawn.setDefault( spawn.simple( 0, 'FOO=BAR=BAZ=QUUX\0MULTILINE\nNAME=multiline\nvalue\0TERM=xterm-something\0PATH=/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path' ) ); await updateProcessEnv(process.env); expect(spawn.calls.length).toBe(1); expect(spawn.calls[0].command).toBe('/my/custom/bash'); expect(spawn.calls[0].args).toEqual([ '-ilc', 'command awk \'BEGIN{for(v in ENVIRON) printf("%s=%s%c", v, ENVIRON[v], 0)}\'' ]); expect(process.env).toEqual({ FOO: 'BAR=BAZ=QUUX', 'MULTILINE\nNAME': 'multiline\nvalue', TERM: 'xterm-something', PATH: '/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path' }); // Doesn't error await updateProcessEnv(null); }); }); describe('on linux', function() { it("updates process.env to match the environment in the user's login shell", async function() { if (process.platform === 'win32') return; // TestsThatFailOnWin32 process.platform = 'linux'; process.env.SHELL = '/my/custom/bash'; spawn.setDefault( spawn.simple( 0, 'FOO=BAR=BAZ=QUUX\0MULTILINE\nNAME=multiline\nvalue\0TERM=xterm-something\0PATH=/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path' ) ); await updateProcessEnv(process.env); expect(spawn.calls.length).toBe(1); expect(spawn.calls[0].command).toBe('/my/custom/bash'); expect(spawn.calls[0].args).toEqual([ '-ilc', 'command awk \'BEGIN{for(v in ENVIRON) printf("%s=%s%c", v, ENVIRON[v], 0)}\'' ]); expect(process.env).toEqual({ FOO: 'BAR=BAZ=QUUX', 'MULTILINE\nNAME': 'multiline\nvalue', TERM: 'xterm-something', PATH: '/usr/bin:/bin:/usr/sbin:/sbin:/crazy/path' }); // Doesn't error await updateProcessEnv(null); }); }); describe('on windows', function() { it('does not update process.env', async function() { process.platform = 'win32'; spyOn(childProcess, 'spawn'); process.env = { FOO: 'bar' }; await updateProcessEnv(process.env); expect(childProcess.spawn).not.toHaveBeenCalled(); expect(process.env).toEqual({ FOO: 'bar' }); }); }); describe('shouldGetEnvFromShell()', function() { it('indicates when the environment should be fetched from the shell', function() { if (process.platform === 'win32') return; // TestsThatFailOnWin32 process.platform = 'darwin'; expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/sh' })).toBe( true ); expect(shouldGetEnvFromShell({ SHELL: '/bin/bash' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/bash' })).toBe( true ); expect(shouldGetEnvFromShell({ SHELL: '/bin/zsh' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/zsh' })).toBe( true ); expect(shouldGetEnvFromShell({ SHELL: '/bin/fish' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/fish' })).toBe( true ); process.platform = 'linux'; expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/sh' })).toBe( true ); expect(shouldGetEnvFromShell({ SHELL: '/bin/bash' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/bash' })).toBe( true ); expect(shouldGetEnvFromShell({ SHELL: '/bin/zsh' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/zsh' })).toBe( true ); expect(shouldGetEnvFromShell({ SHELL: '/bin/fish' })).toBe(true); expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/fish' })).toBe( true ); }); it('returns false when the environment indicates that Atom was launched from a shell', function() { process.platform = 'darwin'; expect( shouldGetEnvFromShell({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', SHELL: '/bin/sh' }) ).toBe(false); process.platform = 'linux'; expect( shouldGetEnvFromShell({ ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: 'true', SHELL: '/bin/sh' }) ).toBe(false); }); it('returns false when the shell is undefined or empty', function() { process.platform = 'darwin'; expect(shouldGetEnvFromShell(undefined)).toBe(false); expect(shouldGetEnvFromShell({})).toBe(false); process.platform = 'linux'; expect(shouldGetEnvFromShell(undefined)).toBe(false); expect(shouldGetEnvFromShell({})).toBe(false); }); }); }); });