Merge lp:~bjornt/python-apt/package-broken-dependency-info into lp:~mvo/python-apt/debian-sid-mirrored

Proposed by Björn Tillenius
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
Reviewer Review Type Date Requested Status
Michael Vogt Pending
Review via email: mp+93286@code.launchpad.net

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.

To post a comment you must log in.

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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()

Subscribers

People subscribed via source and target branches

to all changes: