Merge lp:~bjornt/python-apt/package-broken-dependency-info into lp:~mvo/python-apt/debian-sid-mirrored
- package-broken-dependency-info
- Merge into debian-sid-mirrored
Status: | Needs review |
---|---|
Proposed branch: | lp:~bjornt/python-apt/package-broken-dependency-info |
Merge into: | lp:~mvo/python-apt/debian-sid-mirrored |
Diff against target: |
621 lines (+565/-0) 5 files modified
apt/package.py (+63/-0) apt/testing.py (+167/-0) tests/test_aptsources.py (+8/-0) tests/test_aptsources_ports.py (+8/-0) tests/test_package.py (+319/-0) |
To merge this branch: | bzr merge lp:~bjornt/python-apt/package-broken-dependency-info |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Vogt | Pending | ||
Review via email: mp+93286@code.launchpad.net |
Commit message
Description of the change
Add a method to get information about broken dependency in a format like
apt-get and aptitude displays, e.g.:
package-name: Depends: missing-name but is not installable
I made the method return a list of such strings so that callsites can
construct their own error message from it. I was considering returning
objects having the necessary information, providing even more freedom to
construct your own formatting, but didn't have time to do it.
I also added an AptTestHelper object to make it easier to test the
things I wanted to test. I brought over functionality I had added to
landscape-client already, so I put it in apt.testing.py rather than in
the tests directory so that it's possible to import it in other
projects.
I also had to save/restore the apt config in the aptsources tests, since
otherwise my added tests would fail when running test_all.py.
Unmerged revisions
- 597. By Björn Tillenius
-
Save/restore apt config in tests, not to break other tests.
- 596. By Björn Tillenius
-
Document AptTestHelper.
- 595. By Björn Tillenius
-
Correct docstring.
- 594. By Björn Tillenius
-
Document tests.
- 593. By Björn Tillenius
-
Improve docstrings.
- 592. By Björn Tillenius
-
Rename method, add docstring.
- 591. By Björn Tillenius
-
Rename method.
- 590. By Björn Tillenius
-
Handle broken installed packages.
- 589. By Björn Tillenius
-
Don't include non-broken dependencies.
- 588. By Björn Tillenius
-
Add a test for multiple dependencies.
Preview Diff
1 | === modified file 'apt/package.py' |
2 | --- apt/package.py 2012-01-30 14:46:34 +0000 |
3 | +++ apt/package.py 2012-02-15 19:31:18 +0000 |
4 | @@ -1319,6 +1319,69 @@ |
5 | """ |
6 | self._pcache._depcache.commit(fprogress, iprogress) |
7 | |
8 | + def _is_dependency_broken(self, dependency, dep_type): |
9 | + """Return whether the dependency is broken. |
10 | + |
11 | + The dependency parameter is a list of apt_pkg.Dependency objects |
12 | + of which one needs to be satisified to be considered not broken. |
13 | + """ |
14 | + is_negative = dep_type in ["Breaks", "Conflicts"] |
15 | + depcache = self._pcache._depcache |
16 | + for or_dep in dependency: |
17 | + for target in or_dep.all_targets(): |
18 | + package = target.parent_pkg |
19 | + if ((package.current_state == apt_pkg.CURSTATE_INSTALLED |
20 | + or depcache.marked_install(package)) |
21 | + and not depcache.marked_delete(package)): |
22 | + return is_negative |
23 | + return not is_negative |
24 | + |
25 | + def _get_broken_dependency_relation_info(self, dep_relation): |
26 | + """Return a string representation of a specific dependency relation. |
27 | + |
28 | + The dep_relation parameter is an apt_pkg.Dependency object. |
29 | + """ |
30 | + info = dep_relation.target_pkg.name |
31 | + if dep_relation.target_ver: |
32 | + info += " (%s %s)" % ( |
33 | + dep_relation.comp_type, dep_relation.target_ver) |
34 | + reason = " but is not installable" |
35 | + if (dep_relation.target_pkg and |
36 | + dep_relation.target_pkg.name in self._pcache): |
37 | + dep_package = self._pcache[dep_relation.target_pkg.name] |
38 | + if dep_package.installed or dep_package.marked_install: |
39 | + version = dep_package.candidate.version |
40 | + if dep_package not in self._pcache.get_changes(): |
41 | + version = dep_package.installed.version |
42 | + reason = " but %s is to be installed" % version |
43 | + info += reason |
44 | + return info |
45 | + |
46 | + def get_broken_dependency_info(self): |
47 | + """Get information about the broken dependencies for this package |
48 | + |
49 | + It returns a list of strings similar to the ones apt-get and |
50 | + aptitude display (e.g. "package-name: Depends: missing-package |
51 | + but is not installable") for each dependency that isn't satisfied. |
52 | + |
53 | + Pre-Depends, Depends, Conflicts and Breaks dependencies are checked. |
54 | + """ |
55 | + broken_info = [] |
56 | + for dep_type in ["PreDepends", "Depends", "Conflicts", "Breaks"]: |
57 | + target = self.installed or self.candidate |
58 | + dependencies = target._cand.depends_list.get(dep_type, []) |
59 | + for dependency in dependencies: |
60 | + if not self._is_dependency_broken(dependency, dep_type): |
61 | + continue |
62 | + relation_infos = [] |
63 | + for relation in dependency: |
64 | + relation_infos.append( |
65 | + self._get_broken_dependency_relation_info(relation)) |
66 | + info = "%s: %s: " % (self.name, dep_type) |
67 | + or_divider = " or\n" + " " * len(info) |
68 | + broken_info.append(info + or_divider.join(relation_infos)) |
69 | + return broken_info |
70 | + |
71 | |
72 | if not apt_pkg._COMPAT_0_7: |
73 | del installedVersion |
74 | |
75 | === added file 'apt/testing.py' |
76 | --- apt/testing.py 1970-01-01 00:00:00 +0000 |
77 | +++ apt/testing.py 2012-02-15 19:31:18 +0000 |
78 | @@ -0,0 +1,167 @@ |
79 | +# Copyright (C) 2012 Canonical Ltd. |
80 | +# |
81 | +# Authors: |
82 | +# Bjorn Tillenius <bjorn@canonical.com> |
83 | +# |
84 | +# This program is free software; you can redistribute it and/or |
85 | +# modify it under the terms of the GNU General Public License as |
86 | +# published by the Free Software Foundation; either version 2 of the |
87 | +# License, or (at your option) any later version. |
88 | +# |
89 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
90 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
91 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
92 | +# details. |
93 | +# |
94 | +# You should have received a copy of the GNU General Public License along with |
95 | +# this program; if not, write to the Free Software Foundation, Inc., |
96 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
97 | +"""Classes and methods useful for testing.""" |
98 | + |
99 | +import os |
100 | +import shutil |
101 | +import tempfile |
102 | +import textwrap |
103 | + |
104 | +import apt |
105 | +import apt_pkg |
106 | + |
107 | + |
108 | +class AptTestHelper(object): |
109 | + """A helper object to test apt functionality. |
110 | + |
111 | + An apt.Cache with a tempdir as the root is available in the cache |
112 | + attribute after set_up() has been called. Call tear_down() to clean |
113 | + up when testing is done. |
114 | + """ |
115 | + |
116 | + def set_up(self): |
117 | + """Set up the helper object. |
118 | + |
119 | + An apt.Cache is created with a tempdir as the root. |
120 | + |
121 | + The apt config is also stored so that it can be restored later. |
122 | + """ |
123 | + # reset any config manipulations done in the individual tests |
124 | + apt_pkg.init_config() |
125 | + # save/restore the apt config |
126 | + self._cnf = {} |
127 | + for item in apt_pkg.config.keys(): |
128 | + self._cnf[item] = apt_pkg.config.find(item) |
129 | + self.apt_root = tempfile.mkdtemp() |
130 | + self._ensure_dir_structure() |
131 | + self.cache = apt.Cache(rootdir=self.apt_root) |
132 | + self.repo = None |
133 | + |
134 | + def tear_down(self): |
135 | + """Clean up what the helper object created. |
136 | + |
137 | + All created tempdirs are removed and the apt config is restored. |
138 | + """ |
139 | + shutil.rmtree(self.apt_root) |
140 | + if self.repo is not None: |
141 | + shutil.rmtree(self.repo) |
142 | + for item in self._cnf: |
143 | + apt_pkg.config.set(item, self._cnf[item]) |
144 | + |
145 | + def _ensure_dir_structure(self): |
146 | + """Ensure that all directores and files apt.Cache needs are there.""" |
147 | + self._ensure_sub_dir("etc/apt") |
148 | + self._ensure_sub_dir("etc/apt/sources.list.d") |
149 | + self._ensure_sub_dir("var/cache/apt/archives/partial") |
150 | + self._ensure_sub_dir("var/lib/apt/lists/partial") |
151 | + dpkg_dir = self._ensure_sub_dir("var/lib/dpkg") |
152 | + self._ensure_sub_dir("var/lib/dpkg/info") |
153 | + self._ensure_sub_dir("var/lib/dpkg/updates") |
154 | + self._ensure_sub_dir("var/lib/dpkg/triggers") |
155 | + self.dpkg_status = os.path.join(dpkg_dir, "status") |
156 | + self._ensure_file(os.path.join(dpkg_dir, "available")) |
157 | + self._ensure_file(self.dpkg_status) |
158 | + |
159 | + def _ensure_sub_dir(self, sub_dir): |
160 | + """Ensure that a dir in the Apt root exists.""" |
161 | + full_path = os.path.join(self.apt_root, sub_dir) |
162 | + if not os.path.exists(full_path): |
163 | + os.makedirs(full_path) |
164 | + return full_path |
165 | + |
166 | + def _ensure_file(self, file_path): |
167 | + """Ensure that a file in the Apt root exists.""" |
168 | + if os.path.exists(file_path): |
169 | + return |
170 | + with open(file_path, "w") as empty_file: |
171 | + empty_file.write("") |
172 | + |
173 | + def _ensure_repo(self): |
174 | + """Ensure that the internal deb-dir repository exists.""" |
175 | + if self.repo is not None: |
176 | + return |
177 | + self.repo = tempfile.mkdtemp() |
178 | + sources_dir = apt_pkg.config.find_dir("Dir::Etc::sourceparts") |
179 | + self.sources_file_path = os.path.join( |
180 | + sources_dir, "apt-test-helper.list") |
181 | + sources_line = "deb file://%s ./" % self.repo |
182 | + with open(self.sources_file_path, "w") as sources_file: |
183 | + sources_file.write(sources_line + "\n") |
184 | + |
185 | + |
186 | + def add_package(self, packages_filepath, name, architecture="all", |
187 | + version="1.0", control_fields=None): |
188 | + """Add package information to a Packages file. |
189 | + |
190 | + A package stanza is added with all required values needed for |
191 | + Apt to parse it. Individual fields can be overrided by passing a |
192 | + dict as the control_fields parameter. Only package information |
193 | + will be added to the Packages file, there won't be an actual |
194 | + package in the dir. |
195 | + """ |
196 | + if control_fields is None: |
197 | + control_fields = {} |
198 | + package_stanza = textwrap.dedent(""" |
199 | + Package: %(name)s |
200 | + Priority: optional |
201 | + Section: misc |
202 | + Installed-Size: 1234 |
203 | + Maintainer: Someone |
204 | + Architecture: %(architecture)s |
205 | + Source: source |
206 | + Version: %(version)s |
207 | + Description: description |
208 | + """ % {"name": name, "version": version, |
209 | + "architecture": architecture}) |
210 | + package_stanza = apt_pkg.rewrite_section( |
211 | + apt_pkg.TagSection(package_stanza), apt_pkg.REWRITE_PACKAGE_ORDER, |
212 | + control_fields.items()) |
213 | + with open(packages_filepath, "a") as packages_file: |
214 | + packages_file.write("\n" + package_stanza + "\n") |
215 | + |
216 | + def add_installed_package(self, name, architecture="all", version="1.0", |
217 | + control_fields=None): |
218 | + """Add a package to the dpkg status file. |
219 | + |
220 | + See add_package() for more information. |
221 | + """ |
222 | + system_control_fields = {"Status": "install ok installed"} |
223 | + if control_fields is not None: |
224 | + system_control_fields.update(control_fields) |
225 | + self.add_package( |
226 | + self.dpkg_status, name, architecture=architecture, version=version, |
227 | + control_fields=system_control_fields) |
228 | + |
229 | + def add_available_package(self, name, architecture="all", |
230 | + version="1.0", control_fields=None): |
231 | + """Add a package to the internal deb-dir repo. |
232 | + |
233 | + See add_package() for more information. |
234 | + """ |
235 | + self._ensure_repo() |
236 | + self.add_package( |
237 | + os.path.join(self.repo, "Packages"), name, |
238 | + architecture=architecture, |
239 | + version=version, control_fields=control_fields) |
240 | + |
241 | + def update(self): |
242 | + """Update the cache and make it ready to use.""" |
243 | + self.cache.open(None) |
244 | + self.cache.update() |
245 | + self.cache.open(None) |
246 | |
247 | === modified file 'tests/test_aptsources.py' |
248 | --- tests/test_aptsources.py 2011-10-19 15:40:03 +0000 |
249 | +++ tests/test_aptsources.py 2012-02-15 19:31:18 +0000 |
250 | @@ -14,6 +14,10 @@ |
251 | |
252 | def setUp(self): |
253 | apt_pkg.init_config() |
254 | + # save/restore the apt config |
255 | + self._cnf = {} |
256 | + for item in apt_pkg.config.keys(): |
257 | + self._cnf[item] = apt_pkg.config.find(item) |
258 | apt_pkg.init_system() |
259 | if apt_pkg.config["APT::Architecture"] not in ('i386', 'amd64'): |
260 | apt_pkg.config.set("APT::Architecture", "i386") |
261 | @@ -26,6 +30,10 @@ |
262 | else: |
263 | self.templates = "/usr/share/python-apt/templates/" |
264 | |
265 | + def tearDown(self): |
266 | + for item in self._cnf: |
267 | + apt_pkg.config.set(item, self._cnf[item]) |
268 | + |
269 | def testIsMirror(self): |
270 | """aptsources: Test mirror detection.""" |
271 | yes = aptsources.sourceslist.is_mirror("http://archive.ubuntu.com", |
272 | |
273 | === modified file 'tests/test_aptsources_ports.py' |
274 | --- tests/test_aptsources_ports.py 2011-07-29 17:17:06 +0000 |
275 | +++ tests/test_aptsources_ports.py 2012-02-15 19:31:18 +0000 |
276 | @@ -13,6 +13,10 @@ |
277 | |
278 | def setUp(self): |
279 | apt_pkg.init_config() |
280 | + # save/restore the apt config |
281 | + self._cnf = {} |
282 | + for item in apt_pkg.config.keys(): |
283 | + self._cnf[item] = apt_pkg.config.find(item) |
284 | apt_pkg.init_system() |
285 | apt_pkg.config.set("APT::Architecture", "powerpc") |
286 | apt_pkg.config.set("Dir::Etc", |
287 | @@ -23,6 +27,10 @@ |
288 | else: |
289 | self.templates = "/usr/share/python-apt/templates/" |
290 | |
291 | + def tearDown(self): |
292 | + for item in self._cnf: |
293 | + apt_pkg.config.set(item, self._cnf[item]) |
294 | + |
295 | def testMatcher(self): |
296 | """aptsources_ports: Test matcher.""" |
297 | apt_pkg.config.set("Dir::Etc::sourcelist", "sources.list") |
298 | |
299 | === added file 'tests/test_package.py' |
300 | --- tests/test_package.py 1970-01-01 00:00:00 +0000 |
301 | +++ tests/test_package.py 2012-02-15 19:31:18 +0000 |
302 | @@ -0,0 +1,319 @@ |
303 | +#!/usr/bin/python |
304 | +# |
305 | +# Copyright (C) 2012 Canonical Ltd. |
306 | +# |
307 | +# Copying and distribution of this file, with or without modification, |
308 | +# are permitted in any medium without royalty provided the copyright |
309 | +# notice and this notice are preserved. |
310 | +"""Unit tests for the apt.package.Package class.""" |
311 | + |
312 | +import unittest |
313 | + |
314 | +from test_all import get_library_dir |
315 | +import sys |
316 | +sys.path.insert(0, get_library_dir()) |
317 | + |
318 | +from apt.testing import AptTestHelper |
319 | + |
320 | + |
321 | +class TestPackageBrokenDependenyInfo(unittest.TestCase): |
322 | + """Tests for getting info about broken dependencies for a package.""" |
323 | + |
324 | + def setUp(self): |
325 | + self.helper = AptTestHelper() |
326 | + self.helper.set_up() |
327 | + self.cache = self.helper.cache |
328 | + |
329 | + def tearDown(self): |
330 | + self.helper.tear_down() |
331 | + |
332 | + def test_not_broken(self): |
333 | + """An empty list is returned for packages that aren't broken.""" |
334 | + self.helper.add_installed_package("not-broken") |
335 | + self.helper.update() |
336 | + not_broken = self.cache["not-broken"] |
337 | + self.assertFalse(not_broken.is_inst_broken) |
338 | + self.assertEqual([], not_broken.get_broken_dependency_info()) |
339 | + |
340 | + def test_broken_depends(self): |
341 | + """Broken Depends relations are reported.""" |
342 | + self.helper.add_installed_package( |
343 | + "broken-depends", control_fields={"Depends": "missing"}) |
344 | + self.helper.update() |
345 | + broken_depends = self.cache["broken-depends"] |
346 | + self.assertTrue(broken_depends.is_inst_broken) |
347 | + self.assertEqual( |
348 | + ["broken-depends: Depends: missing but is not installable"], |
349 | + broken_depends.get_broken_dependency_info()) |
350 | + |
351 | + def test_broken_predepends(self): |
352 | + """Broken Pre-Depends relations are reported.""" |
353 | + self.helper.add_installed_package( |
354 | + "broken-predepends", control_fields={"Pre-Depends": "missing"}) |
355 | + self.helper.update() |
356 | + broken_predepends = self.cache["broken-predepends"] |
357 | + self.assertTrue(broken_predepends.is_inst_broken) |
358 | + self.assertEqual( |
359 | + ["broken-predepends: PreDepends: missing but is not installable"], |
360 | + broken_predepends.get_broken_dependency_info()) |
361 | + |
362 | + def test_broken_conflicts(self): |
363 | + """Broken Conflicts relations are reported.""" |
364 | + self.helper.add_installed_package("conflict") |
365 | + self.helper.add_available_package( |
366 | + "broken-conflicts", control_fields={"Conflicts": "conflict"}) |
367 | + self.helper.update() |
368 | + broken_conflicts = self.cache["broken-conflicts"] |
369 | + conflict = self.cache["conflict"] |
370 | + broken_conflicts.mark_install(auto_fix=False) |
371 | + conflict.mark_keep() |
372 | + self.assertTrue(broken_conflicts.is_inst_broken) |
373 | + self.assertEqual( |
374 | + ["broken-conflicts: Conflicts: conflict but 1.0 is to be installed"], |
375 | + broken_conflicts.get_broken_dependency_info()) |
376 | + |
377 | + def test_broken_breaks(self): |
378 | + """Broken Breaks relations are reported.""" |
379 | + self.helper.add_installed_package("breakage") |
380 | + self.helper.add_available_package( |
381 | + "broken-breaks", control_fields={"Breaks": "breakage"}) |
382 | + self.helper.update() |
383 | + broken_breaks = self.cache["broken-breaks"] |
384 | + breakage = self.cache["breakage"] |
385 | + broken_breaks.mark_install(auto_fix=False) |
386 | + breakage.mark_keep() |
387 | + self.assertTrue(broken_breaks.is_inst_broken) |
388 | + self.assertEqual( |
389 | + ["broken-breaks: Breaks: breakage but 1.0 is to be installed"], |
390 | + broken_breaks.get_broken_dependency_info()) |
391 | + |
392 | + def test_broken_with_version(self): |
393 | + """ |
394 | + If a broken dependency includes a version relation, it's |
395 | + included in the error information about the broken dependencies. |
396 | + """ |
397 | + self.helper.add_installed_package( |
398 | + "broken-ver", control_fields={"Depends": "missing (>= 1.0)"}) |
399 | + self.helper.update() |
400 | + broken_ver = self.cache["broken-ver"] |
401 | + self.assertTrue(broken_ver.is_inst_broken) |
402 | + self.assertEqual( |
403 | + ["broken-ver: Depends: missing (>= 1.0) but is not installable"], |
404 | + broken_ver.get_broken_dependency_info()) |
405 | + |
406 | + def test_broken_marked_install(self): |
407 | + """ |
408 | + If a broken package is being installed the dependencies of the |
409 | + candidate version is used when looking for broken dependencies. |
410 | + """ |
411 | + self.helper.add_available_package( |
412 | + "broken", version="1.0", control_fields={"Depends": "missing1"}) |
413 | + self.helper.add_available_package( |
414 | + "broken", version="2.0", control_fields={"Depends": "missing2"}) |
415 | + self.helper.add_available_package( |
416 | + "broken", version="3.0", control_fields={"Depends": "missing3"}) |
417 | + self.helper.update() |
418 | + broken = self.cache["broken"] |
419 | + broken.candidate = sorted(broken.versions)[1] |
420 | + broken.mark_install(auto_fix=False) |
421 | + self.assertTrue(broken.is_inst_broken) |
422 | + self.assertEqual( |
423 | + ["broken: Depends: missing2 but is not installable"], |
424 | + broken.get_broken_dependency_info()) |
425 | + |
426 | + def test_broken_installed(self): |
427 | + """ |
428 | + If a broken package is installed the dependencies of the |
429 | + installed version is used when looking for broken dependencies. |
430 | + """ |
431 | + self.helper.add_installed_package( |
432 | + "broken", version="1.0", control_fields={"Depends": "missing1"}) |
433 | + self.helper.add_available_package( |
434 | + "broken", version="2.0", control_fields={"Depends": "missing2"}) |
435 | + self.helper.update() |
436 | + broken = self.cache["broken"] |
437 | + self.assertTrue(broken.is_inst_broken) |
438 | + self.assertEqual( |
439 | + ["broken: Depends: missing1 but is not installable"], |
440 | + broken.get_broken_dependency_info()) |
441 | + |
442 | + def test_broken_with_dep_marked_install(self): |
443 | + """ |
444 | + If a broken dependency is being installed (but still doesn't |
445 | + meet the version requirements), the version being installed is |
446 | + included in the error information about the broken dependency. |
447 | + """ |
448 | + self.helper.add_installed_package( |
449 | + "broken", control_fields={"Depends": "missing (>= 2.0)"}) |
450 | + self.helper.add_available_package("missing", version="1.0") |
451 | + self.helper.update() |
452 | + broken = self.cache["broken"] |
453 | + missing = self.cache["missing"] |
454 | + missing.mark_install(auto_fix=False) |
455 | + self.assertTrue(broken.is_inst_broken) |
456 | + self.assertEqual( |
457 | + ["broken: Depends: missing (>= 2.0) but 1.0 is to be installed"], |
458 | + broken.get_broken_dependency_info()) |
459 | + |
460 | + def test_broken_with_dep_installed(self): |
461 | + """ |
462 | + If an unmet dependency is already installed (but still doesn't |
463 | + meet the version requirements), the version that is installed is |
464 | + included in the error information about the broken dependency. |
465 | + """ |
466 | + self.helper.add_available_package( |
467 | + "broken", control_fields={"Depends": "missing (>= 2.0)"}) |
468 | + self.helper.add_installed_package("missing", version="1.0") |
469 | + self.helper.update() |
470 | + broken = self.cache["broken"] |
471 | + broken.mark_install(auto_fix=False) |
472 | + self.assertTrue(broken.is_inst_broken) |
473 | + self.assertEqual( |
474 | + ["broken: Depends: missing (>= 2.0) but 1.0 is to be installed"], |
475 | + broken.get_broken_dependency_info()) |
476 | + |
477 | + def test_broken_with_dep_upgraded(self): |
478 | + """ |
479 | + If a broken dependency is being upgraded (but still doesn't meet |
480 | + the version requirements), the version that it is upgraded to is |
481 | + included in the error information about the broken dependeny. |
482 | + """ |
483 | + self.helper.add_available_package( |
484 | + "broken", control_fields={"Depends": "missing (>= 3.0)"}) |
485 | + self.helper.add_installed_package("missing", version="1.0") |
486 | + self.helper.add_available_package("missing", version="2.0") |
487 | + self.helper.update() |
488 | + broken = self.cache["broken"] |
489 | + broken.mark_install(auto_fix=False) |
490 | + missing = self.cache["missing"] |
491 | + missing.mark_install(auto_fix=False) |
492 | + self.assertTrue(broken.is_inst_broken) |
493 | + self.assertEqual( |
494 | + ["broken: Depends: missing (>= 3.0) but 2.0 is to be installed"], |
495 | + broken.get_broken_dependency_info()) |
496 | + |
497 | + def test_broken_with_dep_downgraded(self): |
498 | + """ |
499 | + If a broken dependency is being downgraded (but still doesn't meet |
500 | + the version requirements), the version that it is downgraded to is |
501 | + included in the error information about the broken dependency. |
502 | + """ |
503 | + self.helper.add_installed_package( |
504 | + "broken", control_fields={"Depends": "missing (>= 3.0)"}) |
505 | + self.helper.add_installed_package("missing", version="2.0") |
506 | + self.helper.add_available_package("missing", version="1.0") |
507 | + self.helper.update() |
508 | + broken = self.cache["broken"] |
509 | + missing = self.cache["missing"] |
510 | + missing.candidate = sorted(missing.versions)[0] |
511 | + missing.mark_install(auto_fix=False) |
512 | + self.assertTrue(broken.is_inst_broken) |
513 | + self.assertEqual( |
514 | + ["broken: Depends: missing (>= 3.0) but 1.0 is to be installed"], |
515 | + broken.get_broken_dependency_info()) |
516 | + |
517 | + def test_broken_with_or_deps(self): |
518 | + """ |
519 | + If a broken dependency includes an or relation, all of the |
520 | + possible options are included in the error information about the |
521 | + broken dependency. |
522 | + """ |
523 | + self.helper.add_installed_package( |
524 | + "broken", |
525 | + control_fields={"Depends": "missing1 | missing2 (>= 1.0)"}) |
526 | + self.helper.update() |
527 | + broken = self.cache["broken"] |
528 | + self.assertTrue(broken.is_inst_broken) |
529 | + self.assertEqual( |
530 | + ["broken: Depends: missing1 but is not installable or\n" + |
531 | + " missing2 (>= 1.0) but is not installable"], |
532 | + broken.get_broken_dependency_info()) |
533 | + |
534 | + def test_broken_with_multiple(self): |
535 | + """ |
536 | + If a package has multiple broken dependencies, all of the |
537 | + broken ones are included in the error information about the |
538 | + broken dependency. |
539 | + """ |
540 | + self.helper.add_installed_package( |
541 | + "broken", |
542 | + control_fields={"Depends": "missing1, missing2"}) |
543 | + self.helper.update() |
544 | + broken = self.cache["broken"] |
545 | + self.assertTrue(broken.is_inst_broken) |
546 | + self.assertEqual( |
547 | + ["broken: Depends: missing1 but is not installable", |
548 | + "broken: Depends: missing2 but is not installable"], |
549 | + broken.get_broken_dependency_info()) |
550 | + |
551 | + def test_broken_with_conflicts_not_installed(self): |
552 | + """ |
553 | + If a broken package conflicts or breaks a package that isn't |
554 | + installed or marked for installation, information about that |
555 | + conflict isn't included in the error information about broken |
556 | + dependency. |
557 | + """ |
558 | + self.helper.add_installed_package("needed") |
559 | + self.helper.add_available_package( |
560 | + "broken", |
561 | + control_fields={"Conflicts": "needed, not-installed", |
562 | + "Breaks": "needed, not-installed"}) |
563 | + self.helper.add_available_package("not-installed") |
564 | + self.helper.update() |
565 | + broken = self.cache["broken"] |
566 | + broken.mark_install(auto_fix=False) |
567 | + needed = self.cache["needed"] |
568 | + needed.mark_keep() |
569 | + self.assertTrue(broken.is_inst_broken) |
570 | + self.assertEqual( |
571 | + ["broken: Conflicts: needed but 1.0 is to be installed", |
572 | + "broken: Breaks: needed but 1.0 is to be installed"], |
573 | + broken.get_broken_dependency_info()) |
574 | + |
575 | + def test_get_unmet_dependency_info_with_conflicts_marked_delete(self): |
576 | + """ |
577 | + If a broken package conflicts or breaks an installed package |
578 | + that is marekd for removal, information about that conflict |
579 | + isn't included in the error information about broken dependency. |
580 | + """ |
581 | + self.helper.add_installed_package("needed") |
582 | + self.helper.add_available_package( |
583 | + "broken", |
584 | + control_fields={"Conflicts": "needed, not-installed", |
585 | + "Breaks": "needed, not-installed"}) |
586 | + self.helper.add_installed_package("removed") |
587 | + self.helper.update() |
588 | + removed = self.cache["removed"] |
589 | + removed.mark_delete(auto_fix=False) |
590 | + broken = self.cache["broken"] |
591 | + broken.mark_install(auto_fix=False) |
592 | + needed = self.cache["needed"] |
593 | + needed.mark_keep() |
594 | + self.assertTrue(broken.is_inst_broken) |
595 | + self.assertEqual( |
596 | + ["broken: Conflicts: needed but 1.0 is to be installed", |
597 | + "broken: Breaks: needed but 1.0 is to be installed"], |
598 | + broken.get_broken_dependency_info()) |
599 | + |
600 | + def test_get_unmet_dependency_info_only_unmet(self): |
601 | + """ |
602 | + If a broken package has some dependencies that are being |
603 | + fulfilled, those aren't included in the error information from |
604 | + C{_get_unmet_dependency_info}. |
605 | + """ |
606 | + self.helper.add_installed_package("there1") |
607 | + self.helper.add_installed_package("there2") |
608 | + self.helper.add_available_package( |
609 | + "broken", |
610 | + control_fields={"Depends": "there1, missing1, there2 | missing2"}) |
611 | + self.helper.update() |
612 | + broken = self.cache["broken"] |
613 | + broken.mark_install(auto_fix=False) |
614 | + self.assertTrue(broken.is_inst_broken) |
615 | + self.assertEqual( |
616 | + ["broken: Depends: missing1 but is not installable"], |
617 | + broken.get_broken_dependency_info()) |
618 | + |
619 | + |
620 | +if __name__ == "__main__": |
621 | + unittest.main() |