1import unittest
2import unittest.mock
3import test.support
4import os
5import os.path
6import contextlib
7import sys
8
9import ensurepip
10import ensurepip._uninstall
11
12
13class TestEnsurePipVersion(unittest.TestCase):
14
15    def test_returns_version(self):
16        self.assertEqual(ensurepip._PIP_VERSION, ensurepip.version())
17
18class EnsurepipMixin:
19
20    def setUp(self):
21        run_pip_patch = unittest.mock.patch("ensurepip._run_pip")
22        self.run_pip = run_pip_patch.start()
23        self.addCleanup(run_pip_patch.stop)
24
25        # Avoid side effects on the actual os module
26        real_devnull = os.devnull
27        os_patch = unittest.mock.patch("ensurepip.os")
28        patched_os = os_patch.start()
29        self.addCleanup(os_patch.stop)
30        patched_os.devnull = real_devnull
31        patched_os.path = os.path
32        self.os_environ = patched_os.environ = os.environ.copy()
33
34
35class TestBootstrap(EnsurepipMixin, unittest.TestCase):
36
37    def test_basic_bootstrapping(self):
38        ensurepip.bootstrap()
39
40        self.run_pip.assert_called_once_with(
41            [
42                "install", "--no-index", "--find-links",
43                unittest.mock.ANY, "setuptools", "pip",
44            ],
45            unittest.mock.ANY,
46        )
47
48        additional_paths = self.run_pip.call_args[0][1]
49        self.assertEqual(len(additional_paths), 2)
50
51    def test_bootstrapping_with_root(self):
52        ensurepip.bootstrap(root="/foo/bar/")
53
54        self.run_pip.assert_called_once_with(
55            [
56                "install", "--no-index", "--find-links",
57                unittest.mock.ANY, "--root", "/foo/bar/",
58                "setuptools", "pip",
59            ],
60            unittest.mock.ANY,
61        )
62
63    def test_bootstrapping_with_user(self):
64        ensurepip.bootstrap(user=True)
65
66        self.run_pip.assert_called_once_with(
67            [
68                "install", "--no-index", "--find-links",
69                unittest.mock.ANY, "--user", "setuptools", "pip",
70            ],
71            unittest.mock.ANY,
72        )
73
74    def test_bootstrapping_with_upgrade(self):
75        ensurepip.bootstrap(upgrade=True)
76
77        self.run_pip.assert_called_once_with(
78            [
79                "install", "--no-index", "--find-links",
80                unittest.mock.ANY, "--upgrade", "setuptools", "pip",
81            ],
82            unittest.mock.ANY,
83        )
84
85    def test_bootstrapping_with_verbosity_1(self):
86        ensurepip.bootstrap(verbosity=1)
87
88        self.run_pip.assert_called_once_with(
89            [
90                "install", "--no-index", "--find-links",
91                unittest.mock.ANY, "-v", "setuptools", "pip",
92            ],
93            unittest.mock.ANY,
94        )
95
96    def test_bootstrapping_with_verbosity_2(self):
97        ensurepip.bootstrap(verbosity=2)
98
99        self.run_pip.assert_called_once_with(
100            [
101                "install", "--no-index", "--find-links",
102                unittest.mock.ANY, "-vv", "setuptools", "pip",
103            ],
104            unittest.mock.ANY,
105        )
106
107    def test_bootstrapping_with_verbosity_3(self):
108        ensurepip.bootstrap(verbosity=3)
109
110        self.run_pip.assert_called_once_with(
111            [
112                "install", "--no-index", "--find-links",
113                unittest.mock.ANY, "-vvv", "setuptools", "pip",
114            ],
115            unittest.mock.ANY,
116        )
117
118    def test_bootstrapping_with_regular_install(self):
119        ensurepip.bootstrap()
120        self.assertEqual(self.os_environ["ENSUREPIP_OPTIONS"], "install")
121
122    def test_bootstrapping_with_alt_install(self):
123        ensurepip.bootstrap(altinstall=True)
124        self.assertEqual(self.os_environ["ENSUREPIP_OPTIONS"], "altinstall")
125
126    def test_bootstrapping_with_default_pip(self):
127        ensurepip.bootstrap(default_pip=True)
128        self.assertNotIn("ENSUREPIP_OPTIONS", self.os_environ)
129
130    def test_altinstall_default_pip_conflict(self):
131        with self.assertRaises(ValueError):
132            ensurepip.bootstrap(altinstall=True, default_pip=True)
133        self.assertFalse(self.run_pip.called)
134
135    def test_pip_environment_variables_removed(self):
136        # ensurepip deliberately ignores all pip environment variables
137        # See http://bugs.python.org/issue19734 for details
138        self.os_environ["PIP_THIS_SHOULD_GO_AWAY"] = "test fodder"
139        ensurepip.bootstrap()
140        self.assertNotIn("PIP_THIS_SHOULD_GO_AWAY", self.os_environ)
141
142    def test_pip_config_file_disabled(self):
143        # ensurepip deliberately ignores the pip config file
144        # See http://bugs.python.org/issue20053 for details
145        ensurepip.bootstrap()
146        self.assertEqual(self.os_environ["PIP_CONFIG_FILE"], os.devnull)
147
148@contextlib.contextmanager
149def fake_pip(version=ensurepip._PIP_VERSION):
150    if version is None:
151        pip = None
152    else:
153        class FakePip():
154            __version__ = version
155        pip = FakePip()
156    sentinel = object()
157    orig_pip = sys.modules.get("pip", sentinel)
158    sys.modules["pip"] = pip
159    try:
160        yield pip
161    finally:
162        if orig_pip is sentinel:
163            del sys.modules["pip"]
164        else:
165            sys.modules["pip"] = orig_pip
166
167class TestUninstall(EnsurepipMixin, unittest.TestCase):
168
169    def test_uninstall_skipped_when_not_installed(self):
170        with fake_pip(None):
171            ensurepip._uninstall_helper()
172        self.assertFalse(self.run_pip.called)
173
174    def test_uninstall_skipped_with_warning_for_wrong_version(self):
175        with fake_pip("not a valid version"):
176            with test.support.captured_stderr() as stderr:
177                ensurepip._uninstall_helper()
178        warning = stderr.getvalue().strip()
179        self.assertIn("only uninstall a matching version", warning)
180        self.assertFalse(self.run_pip.called)
181
182
183    def test_uninstall(self):
184        with fake_pip():
185            ensurepip._uninstall_helper()
186
187        self.run_pip.assert_called_once_with(
188            [
189                "uninstall", "-y", "--disable-pip-version-check", "pip",
190                "setuptools",
191            ]
192        )
193
194    def test_uninstall_with_verbosity_1(self):
195        with fake_pip():
196            ensurepip._uninstall_helper(verbosity=1)
197
198        self.run_pip.assert_called_once_with(
199            [
200                "uninstall", "-y", "--disable-pip-version-check", "-v", "pip",
201                "setuptools",
202            ]
203        )
204
205    def test_uninstall_with_verbosity_2(self):
206        with fake_pip():
207            ensurepip._uninstall_helper(verbosity=2)
208
209        self.run_pip.assert_called_once_with(
210            [
211                "uninstall", "-y", "--disable-pip-version-check", "-vv", "pip",
212                "setuptools",
213            ]
214        )
215
216    def test_uninstall_with_verbosity_3(self):
217        with fake_pip():
218            ensurepip._uninstall_helper(verbosity=3)
219
220        self.run_pip.assert_called_once_with(
221            [
222                "uninstall", "-y", "--disable-pip-version-check", "-vvv",
223                "pip", "setuptools",
224            ]
225        )
226
227    def test_pip_environment_variables_removed(self):
228        # ensurepip deliberately ignores all pip environment variables
229        # See http://bugs.python.org/issue19734 for details
230        self.os_environ["PIP_THIS_SHOULD_GO_AWAY"] = "test fodder"
231        with fake_pip():
232            ensurepip._uninstall_helper()
233        self.assertNotIn("PIP_THIS_SHOULD_GO_AWAY", self.os_environ)
234
235    def test_pip_config_file_disabled(self):
236        # ensurepip deliberately ignores the pip config file
237        # See http://bugs.python.org/issue20053 for details
238        with fake_pip():
239            ensurepip._uninstall_helper()
240        self.assertEqual(self.os_environ["PIP_CONFIG_FILE"], os.devnull)
241
242
243# Basic testing of the main functions and their argument parsing
244
245EXPECTED_VERSION_OUTPUT = "pip " + ensurepip._PIP_VERSION
246
247class TestBootstrappingMainFunction(EnsurepipMixin, unittest.TestCase):
248
249    def test_bootstrap_version(self):
250        with test.support.captured_stdout() as stdout:
251            with self.assertRaises(SystemExit):
252                ensurepip._main(["--version"])
253        result = stdout.getvalue().strip()
254        self.assertEqual(result, EXPECTED_VERSION_OUTPUT)
255        self.assertFalse(self.run_pip.called)
256
257    def test_basic_bootstrapping(self):
258        ensurepip._main([])
259
260        self.run_pip.assert_called_once_with(
261            [
262                "install", "--no-index", "--find-links",
263                unittest.mock.ANY, "setuptools", "pip",
264            ],
265            unittest.mock.ANY,
266        )
267
268        additional_paths = self.run_pip.call_args[0][1]
269        self.assertEqual(len(additional_paths), 2)
270
271class TestUninstallationMainFunction(EnsurepipMixin, unittest.TestCase):
272
273    def test_uninstall_version(self):
274        with test.support.captured_stdout() as stdout:
275            with self.assertRaises(SystemExit):
276                ensurepip._uninstall._main(["--version"])
277        result = stdout.getvalue().strip()
278        self.assertEqual(result, EXPECTED_VERSION_OUTPUT)
279        self.assertFalse(self.run_pip.called)
280
281    def test_basic_uninstall(self):
282        with fake_pip():
283            ensurepip._uninstall._main([])
284
285        self.run_pip.assert_called_once_with(
286            [
287                "uninstall", "-y", "--disable-pip-version-check", "pip",
288                "setuptools",
289            ]
290        )
291
292
293
294if __name__ == "__main__":
295    unittest.main()
296