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