TheGirrafish TheGirrafish - 6 months ago 90
Bash Question

Mock a shell command in a script called from Python subprocess.Popen()

I have a situation where I need to mock an executable (pip) with a shell script I wrote (mock_pip) for a unit test, so I use the subprocess module to get access to the shell. I've tried a lot of things like:

subprocess.Popen("alias pip='/some/dir/mock_pip'", shell=True) # Create alias
subprocess.Popen("pip", shell=True) # Try to use new alias, but still points towards real pip instead

subprocess.Popen("alias pip='/some/dir/mock_pip'"; "pip", shell=True)
# Once again it uses the real pip instead of mock

I even tried this method by changing the
file in my home directory (also during the unit test using subprocess) but that doesn't work either.

I'm imagining the problem is that subprocess erases the environment after every command that is called, meaning my alias doesn't exist when I try calling it.

How can I cause
to be used instead of
in a bash script started from my Python process?


If your goal here is to provide a mock implementation of pip, you can do that in a few ways:

  • Generate it as an exported function
  • Set a PATH value pointing to a directory containing wrapper as an executable script

The former is shell-version-specific, so we'll cover the latter first:

subprocess.Popen('pip', env={'PATH': '/path/to/mock/dir:' + os.environ['PATH']})

...where your /path/to/mock/dir is a location with a pip executable that performs your desired operations.

The latter, for post-shellshock upstream bash releases (and prior versions of bash with shellshock fixes compatible with the protocol for exported functions that upstream arrived on):

env = dict(os.environ)
env['BASH_FUNC_pip%%'] = '() { mock_pip "$@"; }'
subprocess.Popen(['bash', '-c', 'pip'], shell=False, env=env)

Note the shell=False here, with the explicit ['bash', '-c', ...] -- that ensures that it's bash (which will honor the exported function) rather than the default shell sh invoked.