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