Repository: pypa/pip

Issue:
pip freeze reports doubles in requirements files as "not installed"
* Pip version: 9.0.1
* Python version: all
* Operating system: all

### Description:
If a package is listed twice in a single requirements file, or once in two or more requirements files, then 'pip freeze -r requirements.txt' erroneously reports that the package isn't installed.


### What I've run:

```
pip install simplejson

cat <<EOF > requirements.txt
simplejson
simplejson
EOF

pip freeze -r requirements.txt
simplejson==3.10.0
Requirement file [requirements.txt] contains simplejson, but that package is not installed
## The following requirements were added by pip freeze:
```

Similarly, pip freeze now supports multiple '-r' options, and if the same package appears in more than one of the requirements files, then the same message about the package not being installed is displayed.


Discussion:

Resolving patch:
diff --git a/src/pip/_internal/operations/freeze.py b/src/pip/_internal/operations/freeze.py
--- a/src/pip/_internal/operations/freeze.py
+++ b/src/pip/_internal/operations/freeze.py
@@ -1,11 +1,12 @@
 from __future__ import absolute_import
 
+import collections
 import logging
 import os
 import re
 import warnings
 
-from pip._vendor import pkg_resources
+from pip._vendor import pkg_resources, six
 from pip._vendor.packaging.utils import canonicalize_name
 from pip._vendor.pkg_resources import RequirementParseError
 
@@ -70,6 +71,9 @@ def freeze(
         # requirements files, so we need to keep track of what has been emitted
         # so that we don't emit it again if it's seen again
         emitted_options = set()
+        # keep track of which files a requirement is in so that we can
+        # give an accurate warning if a requirement appears multiple times.
+        req_files = collections.defaultdict(list)
         for req_file_path in requirement:
             with open(req_file_path) as req_file:
                 for line in req_file:
@@ -119,14 +123,28 @@ def freeze(
                             " this warning)"
                         )
                     elif line_req.name not in installations:
-                        logger.warning(
-                            "Requirement file [%s] contains %s, but that "
-                            "package is not installed",
-                            req_file_path, COMMENT_RE.sub('', line).strip(),
-                        )
+                        # either it's not installed, or it is installed
+                        # but has been processed already
+                        if not req_files[line_req.name]:
+                            logger.warning(
+                                "Requirement file [%s] contains %s, but that "
+                                "package is not installed",
+                                req_file_path,
+                                COMMENT_RE.sub('', line).strip(),
+                            )
+                        else:
+                            req_files[line_req.name].append(req_file_path)
                     else:
                         yield str(installations[line_req.name]).rstrip()
                         del installations[line_req.name]
+                        req_files[line_req.name].append(req_file_path)
+
+        # Warn about requirements that were included multiple times (in a
+        # single requirements file or in different requirements files).
+        for name, files in six.iteritems(req_files):
+            if len(files) > 1:
+                logger.warning("Requirement %s included multiple times [%s]",
+                               name, ', '.join(sorted(set(files))))
 
         yield(
             '## The following requirements were added by '


Test patch:
diff --git a/tests/functional/test_freeze.py b/tests/functional/test_freeze.py
--- a/tests/functional/test_freeze.py
+++ b/tests/functional/test_freeze.py
@@ -495,6 +495,78 @@ def test_freeze_with_requirement_option_multiple(script):
     assert result.stdout.count("--index-url http://ignore") == 1
 
 
+def test_freeze_with_requirement_option_package_repeated_one_file(script):
+    """
+    Test freezing with single requirements file that contains a package
+    multiple times
+    """
+    script.scratch_path.join('hint1.txt').write(textwrap.dedent("""\
+        simple2
+        simple2
+        NoExist
+    """) + _freeze_req_opts)
+    result = script.pip_install_local('simple2==1.0')
+    result = script.pip_install_local('meta')
+    result = script.pip(
+        'freeze', '--requirement', 'hint1.txt',
+        expect_stderr=True,
+    )
+    expected_out = textwrap.dedent("""\
+        simple2==1.0
+    """)
+    expected_out += _freeze_req_opts
+    expected_out += "## The following requirements were added by pip freeze:"
+    expected_out += os.linesep + textwrap.dedent("""\
+        ...meta==1.0...
+    """)
+    _check_output(result.stdout, expected_out)
+    err1 = ("Requirement file [hint1.txt] contains NoExist, "
+            "but that package is not installed\n")
+    err2 = "Requirement simple2 included multiple times [hint1.txt]\n"
+    assert err1 in result.stderr
+    assert err2 in result.stderr
+    # there shouldn't be any other 'is not installed' warnings
+    assert result.stderr.count('is not installed') == 1
+
+
+def test_freeze_with_requirement_option_package_repeated_multi_file(script):
+    """
+    Test freezing with multiple requirements file that contain a package
+    """
+    script.scratch_path.join('hint1.txt').write(textwrap.dedent("""\
+        simple
+    """) + _freeze_req_opts)
+    script.scratch_path.join('hint2.txt').write(textwrap.dedent("""\
+        simple
+        NoExist
+    """) + _freeze_req_opts)
+    result = script.pip_install_local('simple==1.0')
+    result = script.pip_install_local('meta')
+    result = script.pip(
+        'freeze', '--requirement', 'hint1.txt',
+        '--requirement', 'hint2.txt',
+        expect_stderr=True,
+    )
+    expected_out = textwrap.dedent("""\
+        simple==1.0
+    """)
+    expected_out += _freeze_req_opts
+    expected_out += "## The following requirements were added by pip freeze:"
+    expected_out += os.linesep + textwrap.dedent("""\
+        ...meta==1.0...
+    """)
+    _check_output(result.stdout, expected_out)
+
+    err1 = ("Requirement file [hint2.txt] contains NoExist, but that "
+            "package is not installed\n")
+    err2 = ("Requirement simple included multiple times "
+            "[hint1.txt, hint2.txt]\n")
+    assert err1 in result.stderr
+    assert err2 in result.stderr
+    # there shouldn't be any other 'is not installed' warnings
+    assert result.stderr.count('is not installed') == 1
+
+
 @pytest.mark.network
 def test_freeze_user(script, virtualenv, data):
     """