ยปCore Development>Code coverage>Lib/packaging/tests/test_install.py

Python code coverage for Lib/packaging/tests/test_install.py

#countcontent
1n/a"""Tests for the packaging.install module."""
2n/aimport os
3n/aimport logging
4n/afrom tempfile import mkstemp
5n/afrom sysconfig import is_python_build
6n/a
7n/afrom packaging import install
8n/afrom packaging.pypi.xmlrpc import Client
9n/afrom packaging.metadata import Metadata
10n/afrom packaging.tests.support import (LoggingCatcher, TempdirManager, unittest,
11n/a fake_dec)
12n/atry:
13n/a import threading
14n/a from packaging.tests.pypi_server import use_xmlrpc_server
15n/aexcept ImportError:
16n/a threading = None
17n/a use_xmlrpc_server = fake_dec
18n/a
19n/a
20n/aclass InstalledDist:
21n/a """Distribution object, represent distributions currently installed on the
22n/a system"""
23n/a def __init__(self, name, version, deps):
24n/a self.metadata = Metadata()
25n/a self.name = name
26n/a self.version = version
27n/a self.metadata['Name'] = name
28n/a self.metadata['Version'] = version
29n/a self.metadata['Requires-Dist'] = deps
30n/a
31n/a def __repr__(self):
32n/a return '<InstalledDist %r>' % self.metadata['Name']
33n/a
34n/a
35n/aclass ToInstallDist:
36n/a """Distribution that will be installed"""
37n/a
38n/a def __init__(self, files=False):
39n/a self._files = files
40n/a self.install_called = False
41n/a self.install_called_with = {}
42n/a self.uninstall_called = False
43n/a self._real_files = []
44n/a self.name = "fake"
45n/a self.version = "fake"
46n/a if files:
47n/a for f in range(0, 3):
48n/a fp, fn = mkstemp()
49n/a os.close(fp)
50n/a self._real_files.append(fn)
51n/a
52n/a def _unlink_installed_files(self):
53n/a if self._files:
54n/a for fn in self._real_files:
55n/a os.unlink(fn)
56n/a
57n/a def list_installed_files(self, **args):
58n/a if self._files:
59n/a return self._real_files
60n/a
61n/a def get_install(self, **args):
62n/a return self.list_installed_files()
63n/a
64n/a
65n/aclass MagicMock:
66n/a def __init__(self, return_value=None, raise_exception=False):
67n/a self.called = False
68n/a self._times_called = 0
69n/a self._called_with = []
70n/a self._return_value = return_value
71n/a self._raise = raise_exception
72n/a
73n/a def __call__(self, *args, **kwargs):
74n/a self.called = True
75n/a self._times_called = self._times_called + 1
76n/a self._called_with.append((args, kwargs))
77n/a iterable = hasattr(self._raise, '__iter__')
78n/a if self._raise:
79n/a if ((not iterable and self._raise)
80n/a or self._raise[self._times_called - 1]):
81n/a raise Exception
82n/a return self._return_value
83n/a
84n/a def called_with(self, *args, **kwargs):
85n/a return (args, kwargs) in self._called_with
86n/a
87n/a
88n/adef get_installed_dists(dists):
89n/a """Return a list of fake installed dists.
90n/a The list is name, version, deps"""
91n/a objects = []
92n/a for name, version, deps in dists:
93n/a objects.append(InstalledDist(name, version, deps))
94n/a return objects
95n/a
96n/a
97n/aclass TestInstall(LoggingCatcher, TempdirManager, unittest.TestCase):
98n/a def _get_client(self, server, *args, **kwargs):
99n/a return Client(server.full_address, *args, **kwargs)
100n/a
101n/a def _get_results(self, output):
102n/a """return a list of results"""
103n/a installed = [(o.name, str(o.version)) for o in output['install']]
104n/a remove = [(o.name, str(o.version)) for o in output['remove']]
105n/a conflict = [(o.name, str(o.version)) for o in output['conflict']]
106n/a return installed, remove, conflict
107n/a
108n/a @unittest.skipIf(threading is None, 'needs threading')
109n/a @use_xmlrpc_server()
110n/a def test_existing_deps(self, server):
111n/a # Test that the installer get the dependencies from the metadatas
112n/a # and ask the index for this dependencies.
113n/a # In this test case, we have choxie that is dependent from towel-stuff
114n/a # 0.1, which is in-turn dependent on bacon <= 0.2:
115n/a # choxie -> towel-stuff -> bacon.
116n/a # Each release metadata is not provided in metadata 1.2.
117n/a client = self._get_client(server)
118n/a archive_path = '%s/distribution.tar.gz' % server.full_address
119n/a server.xmlrpc.set_distributions([
120n/a {'name': 'choxie',
121n/a 'version': '2.0.0.9',
122n/a 'requires_dist': ['towel-stuff (0.1)'],
123n/a 'url': archive_path},
124n/a {'name': 'towel-stuff',
125n/a 'version': '0.1',
126n/a 'requires_dist': ['bacon (<= 0.2)'],
127n/a 'url': archive_path},
128n/a {'name': 'bacon',
129n/a 'version': '0.1',
130n/a 'requires_dist': [],
131n/a 'url': archive_path},
132n/a ])
133n/a installed = get_installed_dists([('bacon', '0.1', [])])
134n/a output = install.get_infos("choxie", index=client,
135n/a installed=installed)
136n/a
137n/a # we don't have installed bacon as it's already installed system-wide
138n/a self.assertEqual(0, len(output['remove']))
139n/a self.assertEqual(2, len(output['install']))
140n/a readable_output = [(o.name, str(o.version))
141n/a for o in output['install']]
142n/a self.assertIn(('towel-stuff', '0.1'), readable_output)
143n/a self.assertIn(('choxie', '2.0.0.9'), readable_output)
144n/a
145n/a @unittest.skipIf(threading is None, 'needs threading')
146n/a @use_xmlrpc_server()
147n/a def test_upgrade_existing_deps(self, server):
148n/a client = self._get_client(server)
149n/a archive_path = '%s/distribution.tar.gz' % server.full_address
150n/a server.xmlrpc.set_distributions([
151n/a {'name': 'choxie',
152n/a 'version': '2.0.0.9',
153n/a 'requires_dist': ['towel-stuff (0.1)'],
154n/a 'url': archive_path},
155n/a {'name': 'towel-stuff',
156n/a 'version': '0.1',
157n/a 'requires_dist': ['bacon (>= 0.2)'],
158n/a 'url': archive_path},
159n/a {'name': 'bacon',
160n/a 'version': '0.2',
161n/a 'requires_dist': [],
162n/a 'url': archive_path},
163n/a ])
164n/a
165n/a output = install.get_infos("choxie", index=client,
166n/a installed=get_installed_dists([('bacon', '0.1', [])]))
167n/a installed = [(o.name, str(o.version)) for o in output['install']]
168n/a
169n/a # we need bacon 0.2, but 0.1 is installed.
170n/a # So we expect to remove 0.1 and to install 0.2 instead.
171n/a remove = [(o.name, str(o.version)) for o in output['remove']]
172n/a self.assertIn(('choxie', '2.0.0.9'), installed)
173n/a self.assertIn(('towel-stuff', '0.1'), installed)
174n/a self.assertIn(('bacon', '0.2'), installed)
175n/a self.assertIn(('bacon', '0.1'), remove)
176n/a self.assertEqual(0, len(output['conflict']))
177n/a
178n/a @unittest.skipIf(threading is None, 'needs threading')
179n/a @use_xmlrpc_server()
180n/a def test_conflicts(self, server):
181n/a # Tests that conflicts are detected
182n/a client = self._get_client(server)
183n/a archive_path = '%s/distribution.tar.gz' % server.full_address
184n/a
185n/a # choxie depends on towel-stuff, which depends on bacon.
186n/a server.xmlrpc.set_distributions([
187n/a {'name': 'choxie',
188n/a 'version': '2.0.0.9',
189n/a 'requires_dist': ['towel-stuff (0.1)'],
190n/a 'url': archive_path},
191n/a {'name': 'towel-stuff',
192n/a 'version': '0.1',
193n/a 'requires_dist': ['bacon (>= 0.2)'],
194n/a 'url': archive_path},
195n/a {'name': 'bacon',
196n/a 'version': '0.2',
197n/a 'requires_dist': [],
198n/a 'url': archive_path},
199n/a ])
200n/a
201n/a # name, version, deps.
202n/a already_installed = [('bacon', '0.1', []),
203n/a ('chicken', '1.1', ['bacon (0.1)'])]
204n/a output = install.get_infos(
205n/a 'choxie', index=client,
206n/a installed=get_installed_dists(already_installed))
207n/a
208n/a # we need bacon 0.2, but 0.1 is installed.
209n/a # So we expect to remove 0.1 and to install 0.2 instead.
210n/a installed, remove, conflict = self._get_results(output)
211n/a self.assertIn(('choxie', '2.0.0.9'), installed)
212n/a self.assertIn(('towel-stuff', '0.1'), installed)
213n/a self.assertIn(('bacon', '0.2'), installed)
214n/a self.assertIn(('bacon', '0.1'), remove)
215n/a self.assertIn(('chicken', '1.1'), conflict)
216n/a
217n/a @unittest.skipIf(threading is None, 'needs threading')
218n/a @use_xmlrpc_server()
219n/a def test_installation_unexisting_project(self, server):
220n/a # Test that the isntalled raises an exception if the project does not
221n/a # exists.
222n/a client = self._get_client(server)
223n/a self.assertRaises(install.InstallationException,
224n/a install.get_infos,
225n/a 'unexisting project', index=client)
226n/a
227n/a def test_move_files(self):
228n/a # test that the files are really moved, and that the new path is
229n/a # returned.
230n/a path = self.mkdtemp()
231n/a newpath = self.mkdtemp()
232n/a files = [os.path.join(path, str(x)) for x in range(1, 20)]
233n/a for f in files:
234n/a open(f, 'ab+').close()
235n/a output = [o for o in install._move_files(files, newpath)]
236n/a
237n/a # check that output return the list of old/new places
238n/a for file_ in files:
239n/a name = os.path.split(file_)[-1]
240n/a newloc = os.path.join(newpath, name)
241n/a self.assertIn((file_, newloc), output)
242n/a
243n/a # remove the files
244n/a for f in [o[1] for o in output]: # o[1] is the new place
245n/a os.remove(f)
246n/a
247n/a def test_update_infos(self):
248n/a tests = [[
249n/a {'foo': ['foobar', 'foo', 'baz'], 'baz': ['foo', 'foo']},
250n/a {'foo': ['additional_content', 'yeah'], 'baz': ['test', 'foo']},
251n/a {'foo': ['foobar', 'foo', 'baz', 'additional_content', 'yeah'],
252n/a 'baz': ['foo', 'foo', 'test', 'foo']},
253n/a ]]
254n/a
255n/a for dict1, dict2, expect in tests:
256n/a install._update_infos(dict1, dict2)
257n/a for key in expect:
258n/a self.assertEqual(expect[key], dict1[key])
259n/a
260n/a def test_install_dists_rollback(self):
261n/a # if one of the distribution installation fails, call uninstall on all
262n/a # installed distributions.
263n/a
264n/a old_install_dist = install._install_dist
265n/a old_uninstall = getattr(install, 'uninstall', None)
266n/a
267n/a install._install_dist = MagicMock(return_value=[],
268n/a raise_exception=(False, True))
269n/a install.remove = MagicMock()
270n/a try:
271n/a d1 = ToInstallDist()
272n/a d2 = ToInstallDist()
273n/a path = self.mkdtemp()
274n/a self.assertRaises(Exception, install.install_dists, [d1, d2], path)
275n/a self.assertTrue(install._install_dist.called_with(d1, path))
276n/a self.assertTrue(install.remove.called)
277n/a finally:
278n/a install._install_dist = old_install_dist
279n/a install.remove = old_uninstall
280n/a
281n/a def test_install_dists_success(self):
282n/a old_install_dist = install._install_dist
283n/a install._install_dist = MagicMock(return_value=[])
284n/a try:
285n/a # test that the install method is called on each distributions
286n/a d1 = ToInstallDist()
287n/a d2 = ToInstallDist()
288n/a
289n/a # should call install
290n/a path = self.mkdtemp()
291n/a install.install_dists([d1, d2], path)
292n/a for dist in (d1, d2):
293n/a self.assertTrue(install._install_dist.called_with(dist, path))
294n/a finally:
295n/a install._install_dist = old_install_dist
296n/a
297n/a def test_install_from_infos_conflict(self):
298n/a # assert conflicts raise an exception
299n/a self.assertRaises(install.InstallationConflict,
300n/a install.install_from_infos,
301n/a conflicts=[ToInstallDist()])
302n/a
303n/a def test_install_from_infos_remove_success(self):
304n/a old_install_dists = install.install_dists
305n/a install.install_dists = lambda x, y=None: None
306n/a try:
307n/a dists = []
308n/a for i in range(2):
309n/a dists.append(ToInstallDist(files=True))
310n/a install.install_from_infos(remove=dists)
311n/a
312n/a # assert that the files have been removed
313n/a for dist in dists:
314n/a for f in dist.list_installed_files():
315n/a self.assertFalse(os.path.exists(f))
316n/a finally:
317n/a install.install_dists = old_install_dists
318n/a
319n/a def test_install_from_infos_remove_rollback(self):
320n/a old_install_dist = install._install_dist
321n/a old_uninstall = getattr(install, 'uninstall', None)
322n/a
323n/a install._install_dist = MagicMock(return_value=[],
324n/a raise_exception=(False, True))
325n/a install.uninstall = MagicMock()
326n/a try:
327n/a # assert that if an error occurs, the removed files are restored.
328n/a remove = []
329n/a for i in range(2):
330n/a remove.append(ToInstallDist(files=True))
331n/a to_install = [ToInstallDist(), ToInstallDist()]
332n/a temp_dir = self.mkdtemp()
333n/a
334n/a self.assertRaises(Exception, install.install_from_infos,
335n/a install_path=temp_dir, install=to_install,
336n/a remove=remove)
337n/a # assert that the files are in the same place
338n/a # assert that the files have been removed
339n/a for dist in remove:
340n/a for f in dist.list_installed_files():
341n/a self.assertTrue(os.path.exists(f))
342n/a dist._unlink_installed_files()
343n/a finally:
344n/a install._install_dist = old_install_dist
345n/a install.uninstall = old_uninstall
346n/a
347n/a def test_install_from_infos_install_succes(self):
348n/a old_install_dist = install._install_dist
349n/a install._install_dist = MagicMock([])
350n/a try:
351n/a # assert that the distribution can be installed
352n/a install_path = "my_install_path"
353n/a to_install = [ToInstallDist(), ToInstallDist()]
354n/a
355n/a install.install_from_infos(install_path, install=to_install)
356n/a for dist in to_install:
357n/a install._install_dist.called_with(install_path)
358n/a finally:
359n/a install._install_dist = old_install_dist
360n/a
361n/a def test_install_permission_denied(self):
362n/a # if we don't have access to the installation path, we should abort
363n/a # immediately
364n/a project = os.path.join(os.path.dirname(__file__), 'package.tgz')
365n/a
366n/a # when running from an uninstalled build, a warning is emitted and the
367n/a # installation is not attempted
368n/a if is_python_build():
369n/a self.assertFalse(install.install(project))
370n/a self.assertEqual(1, len(self.get_logs(logging.ERROR)))
371n/a return
372n/a
373n/a install_path = self.mkdtemp()
374n/a old_get_path = install.get_path
375n/a install.get_path = lambda path: install_path
376n/a old_mod = os.stat(install_path).st_mode
377n/a os.chmod(install_path, 0)
378n/a try:
379n/a self.assertFalse(install.install(project))
380n/a finally:
381n/a os.chmod(install_path, old_mod)
382n/a install.get_path = old_get_path
383n/a
384n/a
385n/adef test_suite():
386n/a suite = unittest.TestSuite()
387n/a suite.addTest(unittest.makeSuite(TestInstall))
388n/a return suite
389n/a
390n/aif __name__ == '__main__':
391n/a unittest.main(defaultTest='test_suite')