Merge lp:~bjornt/landscape-client/add-remove-dpkg-holds into lp:~landscape/landscape-client/trunk
- add-remove-dpkg-holds
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Geoff Teale | ||||
Approved revision: | 447 | ||||
Merged at revision: | 435 | ||||
Proposed branch: | lp:~bjornt/landscape-client/add-remove-dpkg-holds | ||||
Merge into: | lp:~landscape/landscape-client/trunk | ||||
Diff against target: |
438 lines (+314/-3) 6 files modified
landscape/message_schemas.py (+6/-0) landscape/package/changer.py (+55/-1) landscape/package/facade.py (+41/-0) landscape/package/tests/helpers.py (+0/-1) landscape/package/tests/test_changer.py (+123/-1) landscape/package/tests/test_facade.py (+89/-0) |
||||
To merge this branch: | bzr merge lp:~bjornt/landscape-client/add-remove-dpkg-holds | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Geoff Teale (community) | Approve | ||
Alberto Donato (community) | Approve | ||
Review via email: mp+89253@code.launchpad.net |
Commit message
Description of the change
Add a message for adding and removing package holds, handling the
message in the package changer.
Geoff Teale (tealeg) wrote : | # |
+1 Looks good. Please fix:
landscape/
- 448. By Björn Tillenius
-
Typo.
- 449. By Björn Tillenius
-
Lint.
Björn Tillenius (bjornt) wrote : | # |
On Thu, Jan 19, 2012 at 04:06:23PM -0000, Alberto Donato wrote:
> Review: Approve
>
> Looks good! +1
>
> #1:
> + """Log that a package holds result is sent and sent the response."""
>
> typo, "and send"
Thanks, fixed.
--
Björn Tillenius | https:/
Björn Tillenius (bjornt) wrote : | # |
On Fri, Jan 20, 2012 at 10:43:24AM -0000, Geoff Teale wrote:
> Review: Approve
>
> +1 Looks good. Please fix:
>
> landscape/
Oops, missed that. Fixed.
--
Björn Tillenius | https:/
Preview Diff
1 | === modified file 'landscape/message_schemas.py' | |||
2 | --- landscape/message_schemas.py 2012-01-11 13:37:17 +0000 | |||
3 | +++ landscape/message_schemas.py 2012-01-20 12:58:25 +0000 | |||
4 | @@ -294,6 +294,12 @@ | |||
5 | 294 | "deleted": package_locks}, | 294 | "deleted": package_locks}, |
6 | 295 | optional=["created", "deleted"]) | 295 | optional=["created", "deleted"]) |
7 | 296 | 296 | ||
8 | 297 | CHANGE_PACKAGE_HOLDS = Message( | ||
9 | 298 | "change-package-holds", | ||
10 | 299 | {"created": List(utf8), | ||
11 | 300 | "deleted": List(utf8)}, | ||
12 | 301 | optional=["created", "deleted"]) | ||
13 | 302 | |||
14 | 297 | CHANGE_PACKAGES_RESULT = Message( | 303 | CHANGE_PACKAGES_RESULT = Message( |
15 | 298 | "change-packages-result", | 304 | "change-packages-result", |
16 | 299 | {"operation-id": Int(), | 305 | {"operation-id": Int(), |
17 | 300 | 306 | ||
18 | === modified file 'landscape/package/changer.py' | |||
19 | --- landscape/package/changer.py 2012-01-13 14:01:43 +0000 | |||
20 | +++ landscape/package/changer.py 2012-01-20 12:58:25 +0000 | |||
21 | @@ -18,7 +18,7 @@ | |||
22 | 18 | from landscape.package.taskhandler import ( | 18 | from landscape.package.taskhandler import ( |
23 | 19 | PackageTaskHandler, PackageTaskHandlerConfiguration, PackageTaskError, | 19 | PackageTaskHandler, PackageTaskHandlerConfiguration, PackageTaskError, |
24 | 20 | run_task_handler) | 20 | run_task_handler) |
26 | 21 | from landscape.manager.manager import SUCCEEDED | 21 | from landscape.manager.manager import FAILED, SUCCEEDED |
27 | 22 | 22 | ||
28 | 23 | 23 | ||
29 | 24 | class UnknownPackageData(Exception): | 24 | class UnknownPackageData(Exception): |
30 | @@ -103,6 +103,8 @@ | |||
31 | 103 | return result.addErrback(self.unknown_package_data_error, task) | 103 | return result.addErrback(self.unknown_package_data_error, task) |
32 | 104 | if message["type"] == "change-package-locks": | 104 | if message["type"] == "change-package-locks": |
33 | 105 | return self.handle_change_package_locks(message) | 105 | return self.handle_change_package_locks(message) |
34 | 106 | if message["type"] == "change-package-holds": | ||
35 | 107 | return self.handle_change_package_holds(message) | ||
36 | 106 | 108 | ||
37 | 107 | def unknown_package_data_error(self, failure, task): | 109 | def unknown_package_data_error(self, failure, task): |
38 | 108 | """Handle L{UnknownPackageData} data errors. | 110 | """Handle L{UnknownPackageData} data errors. |
39 | @@ -296,6 +298,58 @@ | |||
40 | 296 | "exchange urgently.") | 298 | "exchange urgently.") |
41 | 297 | return self._broker.send_message(response, True) | 299 | return self._broker.send_message(response, True) |
42 | 298 | 300 | ||
43 | 301 | def _send_change_package_holds_response(self, response): | ||
44 | 302 | """Log that a package holds result is sent and send the response.""" | ||
45 | 303 | logging.info("Queuing message with change package holds results to " | ||
46 | 304 | "exchange urgently.") | ||
47 | 305 | return self._broker.send_message(response, True) | ||
48 | 306 | |||
49 | 307 | def handle_change_package_holds(self, message): | ||
50 | 308 | """Handle a C{change-package-holds} message. | ||
51 | 309 | |||
52 | 310 | Create and delete package holds as requested by the given C{message}. | ||
53 | 311 | """ | ||
54 | 312 | if not self._facade.supports_package_holds: | ||
55 | 313 | response = { | ||
56 | 314 | "type": "operation-result", | ||
57 | 315 | "operation-id": message.get("operation-id"), | ||
58 | 316 | "status": FAILED, | ||
59 | 317 | "result-text": "This client doesn't support package holds.", | ||
60 | 318 | "result-code": 1} | ||
61 | 319 | return self._send_change_package_holds_response(response) | ||
62 | 320 | |||
63 | 321 | not_installed = set() | ||
64 | 322 | holds_to_create = message.get("create", []) | ||
65 | 323 | for name in holds_to_create: | ||
66 | 324 | versions = self._facade.get_packages_by_name(name) | ||
67 | 325 | if not versions or not versions[0].package.installed: | ||
68 | 326 | not_installed.add(name) | ||
69 | 327 | if not_installed: | ||
70 | 328 | response = { | ||
71 | 329 | "type": "operation-result", | ||
72 | 330 | "operation-id": message.get("operation-id"), | ||
73 | 331 | "status": FAILED, | ||
74 | 332 | "result-text": "Package holds not added, since the following" + | ||
75 | 333 | " packages are not installed: %s" % ( | ||
76 | 334 | ", ".join(sorted(not_installed))), | ||
77 | 335 | "result-code": 1} | ||
78 | 336 | return self._send_change_package_holds_response(response) | ||
79 | 337 | |||
80 | 338 | for hold in holds_to_create: | ||
81 | 339 | self._facade.set_package_hold(hold) | ||
82 | 340 | self._facade.reload_channels() | ||
83 | 341 | for hold in message.get("delete", []): | ||
84 | 342 | self._facade.remove_package_hold(hold) | ||
85 | 343 | self._facade.reload_channels() | ||
86 | 344 | |||
87 | 345 | response = {"type": "operation-result", | ||
88 | 346 | "operation-id": message.get("operation-id"), | ||
89 | 347 | "status": SUCCEEDED, | ||
90 | 348 | "result-text": "Package holds successfully changed.", | ||
91 | 349 | "result-code": 0} | ||
92 | 350 | |||
93 | 351 | return self._send_change_package_holds_response(response) | ||
94 | 352 | |||
95 | 299 | @staticmethod | 353 | @staticmethod |
96 | 300 | def find_command(): | 354 | def find_command(): |
97 | 301 | return find_changer_command() | 355 | return find_changer_command() |
98 | 302 | 356 | ||
99 | === modified file 'landscape/package/facade.py' | |||
100 | --- landscape/package/facade.py 2012-01-16 15:08:15 +0000 | |||
101 | +++ landscape/package/facade.py 2012-01-20 12:58:25 +0000 | |||
102 | @@ -1,5 +1,6 @@ | |||
103 | 1 | import hashlib | 1 | import hashlib |
104 | 2 | import os | 2 | import os |
105 | 3 | import subprocess | ||
106 | 3 | import tempfile | 4 | import tempfile |
107 | 4 | from cStringIO import StringIO | 5 | from cStringIO import StringIO |
108 | 5 | 6 | ||
109 | @@ -79,10 +80,14 @@ | |||
110 | 79 | database. | 80 | database. |
111 | 80 | """ | 81 | """ |
112 | 81 | 82 | ||
113 | 83 | supports_package_holds = True | ||
114 | 84 | |||
115 | 82 | def __init__(self, root=None): | 85 | def __init__(self, root=None): |
116 | 83 | self._root = root | 86 | self._root = root |
117 | 87 | self._dpkg_args = [] | ||
118 | 84 | if self._root is not None: | 88 | if self._root is not None: |
119 | 85 | self._ensure_dir_structure() | 89 | self._ensure_dir_structure() |
120 | 90 | self._dpkg_args.extend(["--root", self._root]) | ||
121 | 86 | # don't use memonly=True here because of a python-apt bug on Natty when | 91 | # don't use memonly=True here because of a python-apt bug on Natty when |
122 | 87 | # sources.list contains invalid lines (LP: #886208) | 92 | # sources.list contains invalid lines (LP: #886208) |
123 | 88 | self._cache = apt.cache.Cache(rootdir=root) | 93 | self._cache = apt.cache.Cache(rootdir=root) |
124 | @@ -100,6 +105,10 @@ | |||
125 | 100 | self._ensure_sub_dir("var/cache/apt/archives/partial") | 105 | self._ensure_sub_dir("var/cache/apt/archives/partial") |
126 | 101 | self._ensure_sub_dir("var/lib/apt/lists/partial") | 106 | self._ensure_sub_dir("var/lib/apt/lists/partial") |
127 | 102 | dpkg_dir = self._ensure_sub_dir("var/lib/dpkg") | 107 | dpkg_dir = self._ensure_sub_dir("var/lib/dpkg") |
128 | 108 | self._ensure_sub_dir("var/lib/dpkg/info") | ||
129 | 109 | self._ensure_sub_dir("var/lib/dpkg/updates") | ||
130 | 110 | self._ensure_sub_dir("var/lib/dpkg/triggers") | ||
131 | 111 | create_file(os.path.join(dpkg_dir, "available"), "") | ||
132 | 103 | self._dpkg_status = os.path.join(dpkg_dir, "status") | 112 | self._dpkg_status = os.path.join(dpkg_dir, "status") |
133 | 104 | if not os.path.exists(self._dpkg_status): | 113 | if not os.path.exists(self._dpkg_status): |
134 | 105 | create_file(self._dpkg_status, "") | 114 | create_file(self._dpkg_status, "") |
135 | @@ -157,6 +166,37 @@ | |||
136 | 157 | """ | 166 | """ |
137 | 158 | return [] | 167 | return [] |
138 | 159 | 168 | ||
139 | 169 | def get_package_holds(self): | ||
140 | 170 | """Return the name of all the packages that are on hold.""" | ||
141 | 171 | return [version.package.name for version in self.get_locked_packages()] | ||
142 | 172 | |||
143 | 173 | def _set_dpkg_selections(self, selection): | ||
144 | 174 | """Set the dpkg selection. | ||
145 | 175 | |||
146 | 176 | It basically does "echo $selection | dpkg --set-selections". | ||
147 | 177 | """ | ||
148 | 178 | process = subprocess.Popen( | ||
149 | 179 | ["dpkg", "--set-selections"] + self._dpkg_args, | ||
150 | 180 | stdin=subprocess.PIPE) | ||
151 | 181 | process.communicate(selection) | ||
152 | 182 | |||
153 | 183 | def set_package_hold(self, name): | ||
154 | 184 | """Add a dpkg hold for a package. | ||
155 | 185 | |||
156 | 186 | @param name: The name of the package to hold. | ||
157 | 187 | """ | ||
158 | 188 | self._set_dpkg_selections(name + " hold") | ||
159 | 189 | |||
160 | 190 | def remove_package_hold(self, name): | ||
161 | 191 | """Removes a dpkg hold for a package. | ||
162 | 192 | |||
163 | 193 | @param name: The name of the package to unhold. | ||
164 | 194 | """ | ||
165 | 195 | versions = self.get_packages_by_name(name) | ||
166 | 196 | if not versions or not self._is_package_held(versions[0].package): | ||
167 | 197 | return | ||
168 | 198 | self._set_dpkg_selections(name + " install") | ||
169 | 199 | |||
170 | 160 | def reload_channels(self): | 200 | def reload_channels(self): |
171 | 161 | """Reload the channels and update the cache.""" | 201 | """Reload the channels and update the cache.""" |
172 | 162 | self._cache.open(None) | 202 | self._cache.open(None) |
173 | @@ -469,6 +509,7 @@ | |||
174 | 469 | """ | 509 | """ |
175 | 470 | 510 | ||
176 | 471 | _deb_package_type = None | 511 | _deb_package_type = None |
177 | 512 | supports_package_holds = False | ||
178 | 472 | 513 | ||
179 | 473 | def __init__(self, smart_init_kwargs={}, sysconf_args=None): | 514 | def __init__(self, smart_init_kwargs={}, sysconf_args=None): |
180 | 474 | self._smart_init_kwargs = smart_init_kwargs.copy() | 515 | self._smart_init_kwargs = smart_init_kwargs.copy() |
181 | 475 | 516 | ||
182 | === modified file 'landscape/package/tests/helpers.py' | |||
183 | --- landscape/package/tests/helpers.py 2011-12-14 10:41:04 +0000 | |||
184 | +++ landscape/package/tests/helpers.py 2012-01-20 12:58:25 +0000 | |||
185 | @@ -40,7 +40,6 @@ | |||
186 | 40 | Architecture: %(architecture)s | 40 | Architecture: %(architecture)s |
187 | 41 | Source: source | 41 | Source: source |
188 | 42 | Version: %(version)s | 42 | Version: %(version)s |
189 | 43 | Config-Version: 1.0 | ||
190 | 44 | Description: description | 43 | Description: description |
191 | 45 | """ % {"name": name, "version": version, | 44 | """ % {"name": name, "version": version, |
192 | 46 | "architecture": architecture}) | 45 | "architecture": architecture}) |
193 | 47 | 46 | ||
194 | === modified file 'landscape/package/tests/test_changer.py' | |||
195 | --- landscape/package/tests/test_changer.py 2011-12-13 10:29:04 +0000 | |||
196 | +++ landscape/package/tests/test_changer.py 2012-01-20 12:58:25 +0000 | |||
197 | @@ -24,7 +24,7 @@ | |||
198 | 24 | from landscape.package.tests.helpers import ( | 24 | from landscape.package.tests.helpers import ( |
199 | 25 | SmartFacadeHelper, HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2, PKGNAME2, | 25 | SmartFacadeHelper, HASH1, HASH2, HASH3, PKGDEB1, PKGDEB2, PKGNAME2, |
200 | 26 | AptFacadeHelper, SimpleRepositoryHelper) | 26 | AptFacadeHelper, SimpleRepositoryHelper) |
202 | 27 | from landscape.manager.manager import SUCCEEDED | 27 | from landscape.manager.manager import FAILED, SUCCEEDED |
203 | 28 | 28 | ||
204 | 29 | 29 | ||
205 | 30 | class PackageChangerTestMixin(object): | 30 | class PackageChangerTestMixin(object): |
206 | @@ -1037,6 +1037,31 @@ | |||
207 | 1037 | self.assertIn("(2)", text) | 1037 | self.assertIn("(2)", text) |
208 | 1038 | return result.addCallback(got_result) | 1038 | return result.addCallback(got_result) |
209 | 1039 | 1039 | ||
210 | 1040 | def test_change_package_holds(self): | ||
211 | 1041 | """ | ||
212 | 1042 | If C{SmartFacade} is used, the L{PackageChanger.handle_tasks} | ||
213 | 1043 | method fails the activity, since it can't add or remove dpkg holds. | ||
214 | 1044 | """ | ||
215 | 1045 | self.facade.reload_channels() | ||
216 | 1046 | self.store.add_task("changer", {"type": "change-package-holds", | ||
217 | 1047 | "create": ["name1"], | ||
218 | 1048 | "delete": ["name2"], | ||
219 | 1049 | "operation-id": 123}) | ||
220 | 1050 | |||
221 | 1051 | def assert_result(result): | ||
222 | 1052 | self.assertIn("Queuing message with change package holds results " | ||
223 | 1053 | "to exchange urgently.", self.logfile.getvalue()) | ||
224 | 1054 | self.assertMessages( | ||
225 | 1055 | self.get_pending_messages(), | ||
226 | 1056 | [{"type": "operation-result", | ||
227 | 1057 | "operation-id": 123, | ||
228 | 1058 | "status": FAILED, | ||
229 | 1059 | "result-text": "This client doesn't support package holds.", | ||
230 | 1060 | "result-code": 1}]) | ||
231 | 1061 | |||
232 | 1062 | result = self.changer.handle_tasks() | ||
233 | 1063 | return result.addCallback(assert_result) | ||
234 | 1064 | |||
235 | 1040 | 1065 | ||
236 | 1041 | class AptPackageChangerTest(LandscapeTest, PackageChangerTestMixin): | 1066 | class AptPackageChangerTest(LandscapeTest, PackageChangerTestMixin): |
237 | 1042 | 1067 | ||
238 | @@ -1129,3 +1154,100 @@ | |||
239 | 1129 | def get_package_name(self, version): | 1154 | def get_package_name(self, version): |
240 | 1130 | """Return the name of the package.""" | 1155 | """Return the name of the package.""" |
241 | 1131 | return version.package.name | 1156 | return version.package.name |
242 | 1157 | |||
243 | 1158 | def test_change_package_holds(self): | ||
244 | 1159 | """ | ||
245 | 1160 | The L{PackageChanger.handle_tasks} method appropriately creates and | ||
246 | 1161 | deletes package holds as requested by the C{change-package-holds} | ||
247 | 1162 | message. | ||
248 | 1163 | """ | ||
249 | 1164 | self._add_system_package("foo") | ||
250 | 1165 | self._add_system_package("bar") | ||
251 | 1166 | self.facade.set_package_hold("bar") | ||
252 | 1167 | self.facade.reload_channels() | ||
253 | 1168 | self.store.add_task("changer", {"type": "change-package-holds", | ||
254 | 1169 | "create": ["foo"], | ||
255 | 1170 | "delete": ["bar"], | ||
256 | 1171 | "operation-id": 123}) | ||
257 | 1172 | |||
258 | 1173 | def assert_result(result): | ||
259 | 1174 | self.assertEqual(["foo"], self.facade.get_package_holds()) | ||
260 | 1175 | self.assertIn("Queuing message with change package holds results " | ||
261 | 1176 | "to exchange urgently.", self.logfile.getvalue()) | ||
262 | 1177 | self.assertMessages( | ||
263 | 1178 | self.get_pending_messages(), | ||
264 | 1179 | [{"type": "operation-result", | ||
265 | 1180 | "operation-id": 123, | ||
266 | 1181 | "status": SUCCEEDED, | ||
267 | 1182 | "result-text": "Package holds successfully changed.", | ||
268 | 1183 | "result-code": 0}]) | ||
269 | 1184 | |||
270 | 1185 | result = self.changer.handle_tasks() | ||
271 | 1186 | return result.addCallback(assert_result) | ||
272 | 1187 | |||
273 | 1188 | def test_change_package_holds_create_not_installed(self): | ||
274 | 1189 | """ | ||
275 | 1190 | If the C{change-package-holds} message requests to add holds for | ||
276 | 1191 | packages that aren't installed, the whole activity is failed. If | ||
277 | 1192 | multiple holds are specified, those won't be added. There's no | ||
278 | 1193 | difference between a package that is available in some | ||
279 | 1194 | repository and a package that the facade doesn't know about at | ||
280 | 1195 | all. | ||
281 | 1196 | """ | ||
282 | 1197 | self._add_system_package("foo") | ||
283 | 1198 | self._add_package_to_deb_dir(self.repository_dir, "bar") | ||
284 | 1199 | self.facade.reload_channels() | ||
285 | 1200 | self.store.add_task("changer", {"type": "change-package-holds", | ||
286 | 1201 | "create": ["foo", "bar", "baz"], | ||
287 | 1202 | "operation-id": 123}) | ||
288 | 1203 | |||
289 | 1204 | def assert_result(result): | ||
290 | 1205 | self.facade.reload_channels() | ||
291 | 1206 | self.assertEqual([], self.facade.get_package_holds()) | ||
292 | 1207 | self.assertIn("Queuing message with change package holds results " | ||
293 | 1208 | "to exchange urgently.", self.logfile.getvalue()) | ||
294 | 1209 | self.assertMessages( | ||
295 | 1210 | self.get_pending_messages(), | ||
296 | 1211 | [{"type": "operation-result", | ||
297 | 1212 | "operation-id": 123, | ||
298 | 1213 | "status": FAILED, | ||
299 | 1214 | "result-text": "Package holds not added, since the " | ||
300 | 1215 | "following packages are not installed: " | ||
301 | 1216 | "bar, baz", | ||
302 | 1217 | "result-code": 1}]) | ||
303 | 1218 | |||
304 | 1219 | result = self.changer.handle_tasks() | ||
305 | 1220 | return result.addCallback(assert_result) | ||
306 | 1221 | |||
307 | 1222 | def test_change_package_holds_delete_not_held(self): | ||
308 | 1223 | """ | ||
309 | 1224 | If the C{change-package-holds} message requests to remove holds | ||
310 | 1225 | for packages that aren't held, the activity still succeeds. If | ||
311 | 1226 | other valid holds are specified, those will be removed. | ||
312 | 1227 | There's no difference between a package that is installed | ||
313 | 1228 | and a package that isn't installed. In either case, the | ||
314 | 1229 | result is that the package isn't held. | ||
315 | 1230 | """ | ||
316 | 1231 | self._add_system_package("foo") | ||
317 | 1232 | self._add_system_package("bar") | ||
318 | 1233 | self.facade.set_package_hold("bar") | ||
319 | 1234 | self.facade.reload_channels() | ||
320 | 1235 | self.store.add_task("changer", {"type": "change-package-holds", | ||
321 | 1236 | "delete": ["foo", "bar", "baz"], | ||
322 | 1237 | "operation-id": 123}) | ||
323 | 1238 | |||
324 | 1239 | def assert_result(result): | ||
325 | 1240 | self.facade.reload_channels() | ||
326 | 1241 | self.assertEqual([], self.facade.get_package_holds()) | ||
327 | 1242 | self.assertIn("Queuing message with change package holds results " | ||
328 | 1243 | "to exchange urgently.", self.logfile.getvalue()) | ||
329 | 1244 | self.assertMessages( | ||
330 | 1245 | self.get_pending_messages(), | ||
331 | 1246 | [{"type": "operation-result", | ||
332 | 1247 | "operation-id": 123, | ||
333 | 1248 | "status": SUCCEEDED, | ||
334 | 1249 | "result-text": "Package holds successfully changed.", | ||
335 | 1250 | "result-code": 0}]) | ||
336 | 1251 | |||
337 | 1252 | result = self.changer.handle_tasks() | ||
338 | 1253 | return result.addCallback(assert_result) | ||
339 | 1132 | 1254 | ||
340 | === modified file 'landscape/package/tests/test_facade.py' | |||
341 | --- landscape/package/tests/test_facade.py 2012-01-16 15:08:15 +0000 | |||
342 | +++ landscape/package/tests/test_facade.py 2012-01-20 12:58:25 +0000 | |||
343 | @@ -1385,6 +1385,95 @@ | |||
344 | 1385 | sorted(error.packages, key=self.version_sortkey), | 1385 | sorted(error.packages, key=self.version_sortkey), |
345 | 1386 | sorted([bar, baz], key=self.version_sortkey)) | 1386 | sorted([bar, baz], key=self.version_sortkey)) |
346 | 1387 | 1387 | ||
347 | 1388 | def test_get_package_holds_with_no_hold(self): | ||
348 | 1389 | """ | ||
349 | 1390 | If no package holds are set, C{get_package_holds} returns | ||
350 | 1391 | an empty C{list}. | ||
351 | 1392 | """ | ||
352 | 1393 | self._add_system_package("foo") | ||
353 | 1394 | self.facade.reload_channels() | ||
354 | 1395 | self.assertEqual([], self.facade.get_package_holds()) | ||
355 | 1396 | |||
356 | 1397 | def test_get_package_holds_with_holds(self): | ||
357 | 1398 | """ | ||
358 | 1399 | If package holds are set, C{get_package_holds} returns | ||
359 | 1400 | the name of the packages that are held. | ||
360 | 1401 | """ | ||
361 | 1402 | self._add_system_package( | ||
362 | 1403 | "foo", control_fields={"Status": "hold ok installed"}) | ||
363 | 1404 | self._add_system_package("bar") | ||
364 | 1405 | self._add_system_package( | ||
365 | 1406 | "baz", control_fields={"Status": "hold ok installed"}) | ||
366 | 1407 | self.facade.reload_channels() | ||
367 | 1408 | |||
368 | 1409 | self.assertEqual( | ||
369 | 1410 | ["baz", "foo"], sorted(self.facade.get_package_holds())) | ||
370 | 1411 | |||
371 | 1412 | def test_set_package_hold(self): | ||
372 | 1413 | """ | ||
373 | 1414 | C{set_package_hold} marks a package to be on hold. | ||
374 | 1415 | """ | ||
375 | 1416 | self._add_system_package("foo") | ||
376 | 1417 | self.facade.reload_channels() | ||
377 | 1418 | self.facade.set_package_hold("foo") | ||
378 | 1419 | self.facade.reload_channels() | ||
379 | 1420 | |||
380 | 1421 | self.assertEqual(["foo"], self.facade.get_package_holds()) | ||
381 | 1422 | |||
382 | 1423 | def test_set_package_hold_existing_hold(self): | ||
383 | 1424 | """ | ||
384 | 1425 | If a package is already hel, C{set_package_hold} doesn't return | ||
385 | 1426 | an error. | ||
386 | 1427 | """ | ||
387 | 1428 | self._add_system_package( | ||
388 | 1429 | "foo", control_fields={"Status": "hold ok installed"}) | ||
389 | 1430 | self.facade.reload_channels() | ||
390 | 1431 | self.facade.set_package_hold("foo") | ||
391 | 1432 | self.facade.reload_channels() | ||
392 | 1433 | |||
393 | 1434 | self.assertEqual(["foo"], self.facade.get_package_holds()) | ||
394 | 1435 | |||
395 | 1436 | def test_remove_package_hold(self): | ||
396 | 1437 | """ | ||
397 | 1438 | C{remove_package_hold} marks a package not to be on hold. | ||
398 | 1439 | """ | ||
399 | 1440 | self._add_system_package( | ||
400 | 1441 | "foo", control_fields={"Status": "hold ok installed"}) | ||
401 | 1442 | self.facade.reload_channels() | ||
402 | 1443 | self.facade.remove_package_hold("foo") | ||
403 | 1444 | self.facade.reload_channels() | ||
404 | 1445 | |||
405 | 1446 | self.assertEqual([], self.facade.get_package_holds()) | ||
406 | 1447 | |||
407 | 1448 | def test_remove_package_hold_no_package(self): | ||
408 | 1449 | """ | ||
409 | 1450 | If a package doesn't exist, C{remove_package_hold} doesn't | ||
410 | 1451 | return an error. It's up to the caller to make sure that the | ||
411 | 1452 | package exist, if it's important. | ||
412 | 1453 | """ | ||
413 | 1454 | self._add_system_package("foo") | ||
414 | 1455 | self.facade.reload_channels() | ||
415 | 1456 | self.facade.remove_package_hold("bar") | ||
416 | 1457 | self.facade.reload_channels() | ||
417 | 1458 | |||
418 | 1459 | self.assertEqual([], self.facade.get_package_holds()) | ||
419 | 1460 | |||
420 | 1461 | def test_remove_package_hold_no_hold(self): | ||
421 | 1462 | """ | ||
422 | 1463 | If a package isn't held, the existing selection is retained when | ||
423 | 1464 | C{remove_package_hold} is called. | ||
424 | 1465 | """ | ||
425 | 1466 | self._add_system_package( | ||
426 | 1467 | "foo", control_fields={"Status": "deinstall ok installed"}) | ||
427 | 1468 | self.facade.reload_channels() | ||
428 | 1469 | self.facade.remove_package_hold("foo") | ||
429 | 1470 | self.facade.reload_channels() | ||
430 | 1471 | |||
431 | 1472 | self.assertEqual([], self.facade.get_package_holds()) | ||
432 | 1473 | [foo] = self.facade.get_packages_by_name("foo") | ||
433 | 1474 | self.assertEqual( | ||
434 | 1475 | apt_pkg.SELSTATE_DEINSTALL, foo.package._pkg.selected_state) | ||
435 | 1476 | |||
436 | 1388 | if not hasattr(Package, "shortname"): | 1477 | if not hasattr(Package, "shortname"): |
437 | 1389 | # The 'shortname' attribute was added when multi-arch support | 1478 | # The 'shortname' attribute was added when multi-arch support |
438 | 1390 | # was added to python-apt. So if it's not there, it means that | 1479 | # was added to python-apt. So if it's not there, it means that |
Looks good! +1
#1:
+ """Log that a package holds result is sent and sent the response."""
typo, "and send"