Repository: pypa/pip

Issue:
Missing PATH environment causes pip to put things into an inconsistent state when upgrading a package
**Environment**

* pip version: 10.0.1
* Python version: 2.7.12
* OS: Ubuntu 16.0.4

<!-- Feel free to add more information about your environment here -->

**Description**
<!-- A clear and concise description of what the bug is. -->
If the `PATH` environment variable is missing, pip fails to upgrade a package correctly. `pip list` will report the new version in installed, but the old version will be imported from python

**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
Pip does not need `PATH` to be set to function - it is only used as a check to display a warning message. If it is missing, the check should be skipped, or a warning that the check could not be performed should be emitted.

**How to Reproduce**
<!-- Describe the steps to reproduce this bug. -->
1. Create a virtual environment, and install some package that you can programatically determine the version of
2. unset PATH
3. Use pip (specifying the full path to the executable in the virtual environment) to install a different version. Observe the stack trace
4. run `pip list` observe that it reports the new version of the package is installed
5. start a python REPL, observe that the version of the package you can actually import is the old version

**Output**

Install django 1.9 and observe that we get the expected version when importing in the REPL
```
oj:~$ mkvirtualenv pip-path-error
Running virtualenv with interpreter /usr/bin/python2
New python executable in /home/oj/virtualenv/pip-path-error/bin/python2
Also creating executable in /home/oj/virtualenv/pip-path-error/bin/python
Please make sure you remove any previous custom paths from your /home/oj/.pydistutils.cfg file.
Installing setuptools, pkg_resources, pip, wheel...done.
(pip-path-error)oj:~$ pip install django==1.9
Looking in indexes: https://oliver.jeeves:<password>@<internal_pypi_server>/repository/pypi/simple
Collecting django==1.9
  Downloading https://<internal_pypi_server>/repository/pypi/packages/ea/9b/b5a6360b3dfcd88d4bad70f59da26cbde4bdec395a31bb26dc840e806a50/Django-1.9-py2.py3-none-any.whl (6.6MB)
    100% |████████████████████████████████| 6.6MB 5.2MB/s 
Installing collected packages: django
Successfully installed django-1.9
(pip-path-error)oj:~$ python
Python 2.7.12 (default, Dec  4 2017, 14:50:18) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.VERSION
(1, 9, 0, 'final', 0)
>>> 
```

Unset PATH and try upgrading django
```
(pip-path-error)oj:~$ unset PATH
(pip-path-error)oj:~$ /home/oj/virtualenv/pip-path-error/bin/pip install --upgrade django==1.11
Looking in indexes: https://oliver.jeeves:<password>@<internal_pypi_server>/repository/pypi/simple
Collecting django==1.11
  Using cached https://<internal_pypi_server>/repository/pypi/packages/47/a6/078ebcbd49b19e22fd560a2348cfc5cec9e5dcfe3c4fad8e64c9865135bb/Django-1.11-py2.py3-none-any.whl
Collecting pytz (from django==1.11)
  Downloading https://<internal_pypi_server>/repository/pypi/packages/30/4e/27c34b62430286c6d59177a0842ed90dc789ce5d1ed740887653b898779a/pytz-2018.5-py2.py3-none-any.whl (510kB)
    100% |████████████████████████████████| 512kB 18.6MB/s 
Installing collected packages: pytz, django
  Found existing installation: Django 1.9
    Uninstalling Django-1.9:
      Successfully uninstalled Django-1.9
  Rolling back uninstall of Django
Exception:
Traceback (most recent call last):
  File "/home/oj/virtualenv/pip-path-error/local/lib/python2.7/site-packages/pip/_internal/basecommand.py", line 228, in main
    status = self.run(options, args)
  File "/home/oj/virtualenv/pip-path-error/local/lib/python2.7/site-packages/pip/_internal/commands/install.py", line 335, in run
    use_user_site=options.use_user_site,
  File "/home/oj/virtualenv/pip-path-error/local/lib/python2.7/site-packages/pip/_internal/req/__init__.py", line 49, in install_given_reqs
    **kwargs
  File "/home/oj/virtualenv/pip-path-error/local/lib/python2.7/site-packages/pip/_internal/req/req_install.py", line 748, in install
    use_user_site=use_user_site, pycompile=pycompile,
  File "/home/oj/virtualenv/pip-path-error/local/lib/python2.7/site-packages/pip/_internal/req/req_install.py", line 961, in move_wheel_files
    warn_script_location=warn_script_location,
  File "/home/oj/virtualenv/pip-path-error/local/lib/python2.7/site-packages/pip/_internal/wheel.py", line 466, in move_wheel_files
    msg = message_about_scripts_not_on_PATH(generated_console_scripts)
  File "/home/oj/virtualenv/pip-path-error/local/lib/python2.7/site-packages/pip/_internal/wheel.py", line 167, in message_about_scripts_not_on_PATH
    os.path.normcase(i) for i in os.environ["PATH"].split(os.pathsep)
  File "/home/oj/virtualenv/pip-path-error/lib/python2.7/UserDict.py", line 40, in __getitem__
    raise KeyError(key)
KeyError: 'PATH'
```

Observe that now `pip list` reports the new version, although that's not the version you get when you actually try to import it
```
(pip-path-error)oj:~$ /home/oj/virtualenv/pip-path-error/bin/pip list
Package       Version
------------- -------
Django        1.11   
pip           10.0.1 
pkg-resources 0.0.0  
pytz          2018.5 
setuptools    39.2.0 
wheel         0.31.1 
(pip-path-error)oj:~$ /home/oj/virtualenv/pip-path-error/bin/python
Python 2.7.12 (default, Dec  4 2017, 14:50:18) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.VERSION
(1, 9, 0, 'final', 0)
>>> 

```



Discussion:
Thanks for filing this issue @SpoonMeiser! :)

The required fix would be the following; with tests to verify. :)

```diff
diff --git a/src/pip/_internal/wheel.py b/src/pip/_internal/wheel.py
index 66738597..509501be 100644
--- a/src/pip/_internal/wheel.py
+++ b/src/pip/_internal/wheel.py
@@ -163,7 +163,7 @@ def message_about_scripts_not_on_PATH(scripts):
     # We don't want to warn for directories that are on PATH.
     not_warn_dirs = [
         os.path.normcase(i).rstrip(os.sep) for i in
-        os.environ["PATH"].split(os.pathsep)
+        os.environ.get("PATH", "").split(os.pathsep)
     ]
     # If an executable sits with sys.executable, we don't warn for it.
     #     This covers the case of venv invocations without activating the venv.
```
That was pretty much what I was going to propose - just having a few issues getting the tests running locally
@SpoonMeiser Please do let me know what issues you're facing with running tests locally.

I'm personally trying to figure out how to make it easier for new contributors to get started with pip's development, so it would be helpful to know what issues you're facing. As a bonus, I might be able to help you resolve them. :)

The problems I was having are:

1. `pip install -e .` wasn't working - but I think this might have been a problem with our internal pypi server; reverting to the 'global' one solved that
2. I expected `python setup.py test` to install test dependencies, but it didn't, and I'm not sure how I'm supposed to find out what they are other than repeatedly running it and installing dependencies it complains about.
3. Eventually taking the time to read your development doc, and using tox, I found that running `tox -e py27 tests/unit/test_wheel.py` fails, but `tox -e py27 tests/unit` passes, which doesn't seem right, although I admit I'm not at all familiar with tox.

I do have change to submit now though

Resolving patch:
diff --git a/src/pip/_internal/wheel.py b/src/pip/_internal/wheel.py
--- a/src/pip/_internal/wheel.py
+++ b/src/pip/_internal/wheel.py
@@ -163,7 +163,7 @@ def message_about_scripts_not_on_PATH(scripts):
     # We don't want to warn for directories that are on PATH.
     not_warn_dirs = [
         os.path.normcase(i).rstrip(os.sep) for i in
-        os.environ["PATH"].split(os.pathsep)
+        os.environ.get("PATH", "").split(os.pathsep)
     ]
     # If an executable sits with sys.executable, we don't warn for it.
     #     This covers the case of venv invocations without activating the venv.


Test patch:
diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py
--- a/tests/unit/test_wheel.py
+++ b/tests/unit/test_wheel.py
@@ -470,3 +470,16 @@ def test_trailing_ossep_removal(self):
             scripts=[os.path.join('a', 'b', 'c')]
         )
         assert retval is None
+
+    def test_missing_PATH_env_treated_as_empty_PATH_env(self):
+        scripts = ['a/b/foo']
+
+        env = os.environ.copy()
+        del env['PATH']
+        with patch.dict('os.environ', env, clear=True):
+            retval_missing = wheel.message_about_scripts_not_on_PATH(scripts)
+
+        with patch.dict('os.environ', {'PATH': ''}):
+            retval_empty = wheel.message_about_scripts_not_on_PATH(scripts)
+
+        assert retval_missing == retval_empty