0

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 ~/.bashrc 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 mock_pip to be used instead of pip in a bash script started from my Python process?

18
  • Your posted code contains syntax errors. Maybe you could get it to work when setting shell=True, but I guess it will not carry over for different popens. Why not make a python function that calls the appropriate pip? Commented Jun 21, 2016 at 19:45
  • 1
    subprocess does not erase the environment, the operating system does. Add the alias to .bashrc, but aliases are not normally expanded in scripts, they are a support nightmare and generally considered to be bad practice (functions are preferred), you have to shopt -s expand_aliases (again, in .bashrc). Commented Jun 21, 2016 at 19:49
  • @syntonym Sorry, I forgot to proof read my code, but I did have shell=True. What do you mean create a function to call the appropriate pip though? Commented Jun 21, 2016 at 19:50
  • 2
    You'd put the updated environment with the PATH change in your subprocess.Popen() argument list itself. subprocess.Popen(..., env={'PATH': '/directory/with/new/stub:' + os.environ['PATH']}) Commented Jun 21, 2016 at 19:55
  • 1
    BTW, I'd suggest modifying your question such that it asks what you're really interested in knowing -- ie. how to mock a shell command when called from Python's subprocess.Popen, rather than about alias persistence. Commented Jun 21, 2016 at 20:01

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

6 Comments

I set the path to the directory where my mock pip is located and it uses the mock now instead of the real pip. But there's still a problem (and I probably should've mentioned this earlier but didn't think it would affect anything), but the pip I'm trying to mock is inside of bash script.. (Sorry again) So basically i have the script mpp' which contains pip` commands, and those are the commands I want to mock. I tried subprocess.Popen('mpp', env={'PATH': '/path/to/mock/dir:' + os.environ['PATH']}) but it uses the real pip (located in bin) instead of my mock pip. Sorry if this is confusing..
Hmm. That should work, barring interference (such as something else being executed that modifies the inherited PATH). If you have your script run declare -p PATH, does it print the value with the mock directory first? If that script runs type pip >&2, what's the result?
The declare -p PATH does in fact give me the path to the mock's directory, so exactly as set by the env key in subprocess. As for the type pip >&2, it points directly to my mock pip script I created so /path/to/mock/dir/pip. Even though, when I run my mpp script, it uses the real pip and I can confirm since my mock pip has a counter which doesn't get updated. But the mock does get called when I run a simple pip command through subprocess since the counter actually updates.
...those declare -p PATH and type pip commands being directly before where mpp calls the real pip? Next step is probably to run mpp with set -x and inspect the actual commands.
Hahaha I figured out what the problem was, the mpp command I gave skipped the pip command because it wasn't getting the proper arguments so it just never called pip at all. I tried a few other commands and it does actually call the mock. Thank you so much for helping me through this!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.