Merge lp:~jameinel/bzr/2.1-launchpad-package-freshness-609187 into lp:bzr/2.1
- 2.1-launchpad-package-freshness-609187
- Merge into 2.1
Status: | Merged |
---|---|
Approved by: | Jelmer Vernooij |
Approved revision: | no longer in the source branch. |
Merged at revision: | 4881 |
Proposed branch: | lp:~jameinel/bzr/2.1-launchpad-package-freshness-609187 |
Merge into: | lp:bzr/2.1 |
Diff against target: |
987 lines (+931/-4) 4 files modified
NEWS (+25/-0) bzrlib/plugins/launchpad/__init__.py (+64/-4) bzrlib/plugins/launchpad/lp_api_lite.py (+285/-0) bzrlib/plugins/launchpad/test_lp_api_lite.py (+557/-0) |
To merge this branch: | bzr merge lp:~jameinel/bzr/2.1-launchpad-package-freshness-609187 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij (community) | Approve | ||
Review via email: mp+71542@code.launchpad.net |
Commit message
Bug #609187, whenever accessing a Launchpad packaging branch, check that it has the most recent package version.
Description of the change
This is a backport of my Launchpad plugin changes for bug #609187. This makes bzr-2.1 check for the most recent package versions available from Launchpad's database whenever we access a debian or ubuntu branch on Launchpad.
This should cause conflicts when merging up to bzr-2.4 because there were some bzrlib incompatibilities. Namely:
1) TestCase.
2) graph.iter_
When we land this, I'll take care to merge up in the most compatible fashion I can. (So as soon as overrideAttr exists in a bzr release, I'll switch back to using it, similarly for iter_reverse_
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On 8/16/2011 4:27 PM, Jelmer Vernooij wrote:
> Review: Approve I have only briefly glanced over this. I've
> specifically checked the overriding of attributes and
> iter_reverse_
>
> It would be useful to test this with 2.4, in case this uses anything
> that relies on 2.5 or 2.6. I can't find anything when looking
> manually.
I'm not sure what you mean by 2.4. But we already have this landed into
the 2.4 series, using the newer apis.
When I merge this up, I plan on using whatever apis are available at
each stage.
And I did make sure that:
a) The test suite passes
b) Activating the "DONT_*" tests that actually connect to launchpad and
make a query and parse the result also still pass.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAk5
/p8AnRZpY9lxZZy
=RlmU
-----END PGP SIGNATURE-----
Jelmer Vernooij (jelmer) wrote : | # |
On 16/08/11 16:30, John Arbash Meinel wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> On 8/16/2011 4:27 PM, Jelmer Vernooij wrote:
>> Review: Approve I have only briefly glanced over this. I've
>> specifically checked the overriding of attributes and
>> iter_reverse_
>>
>> It would be useful to test this with 2.4, in case this uses anything
>> that relies on 2.5 or 2.6. I can't find anything when looking
>> manually.
> I'm not sure what you mean by 2.4. But we already have this landed into
> the 2.4 series, using the newer apis.
Sorry, that was ambiguous. I meant compatible with python 2.4,
considering we still try to support it for bzr 2.1. I'm also not sure
what PQM uses to land things on lp:bzr/2.1 these days (if it's python2.6
we need to be careful).
Cheers,
Jelmer
John A Meinel (jameinel) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On 8/16/2011 4:42 PM, Jelmer Vernooij wrote:
> On 16/08/11 16:30, John Arbash Meinel wrote:
>> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
>>
>> On 8/16/2011 4:27 PM, Jelmer Vernooij wrote:
>>> Review: Approve I have only briefly glanced over this. I've
>>> specifically checked the overriding of attributes and
>>> iter_reverse_
>>>
>>> It would be useful to test this with 2.4, in case this uses
>>> anything that relies on 2.5 or 2.6. I can't find anything when
>>> looking manually.
>> I'm not sure what you mean by 2.4. But we already have this landed
>> into the 2.4 series, using the newer apis.
> Sorry, that was ambiguous. I meant compatible with python 2.4,
> considering we still try to support it for bzr 2.1. I'm also not
> sure what PQM uses to land things on lp:bzr/2.1 these days (if it's
> python2.6 we need to be careful).
>
> Cheers,
>
> Jelmer
Ah, good point. No we don't test with python 2.4 anywhere, and I don't
have it installed on my system anymore.
lp_api_lite is fairly large, but I don't think I have any 2.4-isms in
there. Your point is well made, though, I probably should try to dig
python-2.4 out.
John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAk5
LG4AnicZX0dS3ko
=EtOu
-----END PGP SIGNATURE-----
John A Meinel (jameinel) wrote : | # |
sent to pqm by email
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2011-05-17 08:19:08 +0000 |
3 | +++ NEWS 2011-08-15 10:49:24 +0000 |
4 | @@ -19,6 +19,31 @@ |
5 | Bug Fixes |
6 | ********* |
7 | |
8 | +* Accessing a packaging branch on Launchpad (eg, ``lp:ubuntu/bzr``) now |
9 | + checks to see if the most recent published source package version for |
10 | + that project is present in the branch tags. This should help developers |
11 | + trust whether the packaging branch is up-to-date and can be used for new |
12 | + changes. The level of verbosity is controlled by the config item |
13 | + ``launchpad.packaging_verbosity``. It can be set to one of |
14 | + |
15 | + off |
16 | + disable all checks |
17 | + |
18 | + |
19 | + minimal |
20 | + only display if the branch is out-of-date |
21 | + |
22 | + short |
23 | + also display single-line up-to-date and missing, |
24 | + |
25 | + |
26 | + all |
27 | + (default) display multi-line content for all states |
28 | + |
29 | + |
30 | + (John Arbash Meinel, #609187, #812928) |
31 | + |
32 | + |
33 | Improvements |
34 | ************ |
35 | |
36 | |
37 | === modified file 'bzrlib/plugins/launchpad/__init__.py' |
38 | --- bzrlib/plugins/launchpad/__init__.py 2010-11-30 20:42:42 +0000 |
39 | +++ bzrlib/plugins/launchpad/__init__.py 2011-08-15 10:49:24 +0000 |
40 | @@ -21,18 +21,20 @@ |
41 | |
42 | # see http://bazaar-vcs.org/Specs/BranchRegistrationTool |
43 | |
44 | -# Since we are a built-in plugin we share the bzrlib version |
45 | -from bzrlib import version_info |
46 | - |
47 | from bzrlib.lazy_import import lazy_import |
48 | lazy_import(globals(), """ |
49 | from bzrlib import ( |
50 | - branch as _mod_branch, |
51 | trace, |
52 | ) |
53 | """) |
54 | |
55 | from bzrlib.commands import Command, Option, register_command |
56 | +from bzrlib import ( |
57 | + branch as _mod_branch, |
58 | + lazy_regex, |
59 | + # Since we are a built-in plugin we share the bzrlib version |
60 | + version_info, |
61 | + ) |
62 | from bzrlib.directory_service import directories |
63 | from bzrlib.errors import ( |
64 | BzrCommandError, |
65 | @@ -283,12 +285,70 @@ |
66 | 'Launchpad-based directory service',) |
67 | _register_directory() |
68 | |
69 | +# This is kept in __init__ so that we don't load lp_api_lite unless the branch |
70 | +# actually matches. That way we can avoid importing extra dependencies like |
71 | +# json. |
72 | +_package_branch = lazy_regex.lazy_compile( |
73 | + r'bazaar.launchpad.net.*?/' |
74 | + r'(?P<user>~[^/]+/)?(?P<archive>ubuntu|debian)/(?P<series>[^/]+/)?' |
75 | + r'(?P<project>[^/]+)(?P<branch>/[^/]+)?' |
76 | + ) |
77 | + |
78 | +def _get_package_branch_info(url): |
79 | + """Determine the packaging information for this URL. |
80 | + |
81 | + :return: If this isn't a packaging branch, return None. If it is, return |
82 | + (archive, series, project) |
83 | + """ |
84 | + if url is None: |
85 | + return None |
86 | + m = _package_branch.search(url) |
87 | + if m is None: |
88 | + return None |
89 | + archive, series, project, user = m.group('archive', 'series', |
90 | + 'project', 'user') |
91 | + if series is not None: |
92 | + # series is optional, so the regex includes the extra '/', we don't |
93 | + # want to send that on (it causes Internal Server Errors.) |
94 | + series = series.strip('/') |
95 | + if user is not None: |
96 | + user = user.strip('~/') |
97 | + if user != 'ubuntu-branches': |
98 | + return None |
99 | + return archive, series, project |
100 | + |
101 | + |
102 | +def _check_is_up_to_date(the_branch): |
103 | + info = _get_package_branch_info(the_branch.base) |
104 | + if info is None: |
105 | + return |
106 | + c = the_branch.get_config() |
107 | + verbosity = c.get_user_option('launchpad.packaging_verbosity') |
108 | + if verbosity is not None: |
109 | + verbosity = verbosity.lower() |
110 | + if verbosity == 'off': |
111 | + trace.mutter('not checking %s because verbosity is turned off' |
112 | + % (the_branch.base,)) |
113 | + return |
114 | + archive, series, project = info |
115 | + from bzrlib.plugins.launchpad import lp_api_lite |
116 | + latest_pub = lp_api_lite.LatestPublication(archive, series, project) |
117 | + lp_api_lite.report_freshness(the_branch, verbosity, latest_pub) |
118 | + |
119 | + |
120 | +def _register_hooks(): |
121 | + _mod_branch.Branch.hooks.install_named_hook('open', |
122 | + _check_is_up_to_date, 'package-branch-up-to-date') |
123 | + |
124 | + |
125 | +_register_hooks() |
126 | |
127 | def load_tests(basic_tests, module, loader): |
128 | testmod_names = [ |
129 | 'test_account', |
130 | 'test_register', |
131 | 'test_lp_api', |
132 | + 'test_lp_api_lite', |
133 | 'test_lp_directory', |
134 | 'test_lp_login', |
135 | 'test_lp_open', |
136 | |
137 | === added file 'bzrlib/plugins/launchpad/lp_api_lite.py' |
138 | --- bzrlib/plugins/launchpad/lp_api_lite.py 1970-01-01 00:00:00 +0000 |
139 | +++ bzrlib/plugins/launchpad/lp_api_lite.py 2011-08-15 10:49:24 +0000 |
140 | @@ -0,0 +1,285 @@ |
141 | +# Copyright (C) 2011 Canonical Ltd |
142 | +# |
143 | +# This program is free software; you can redistribute it and/or modify |
144 | +# it under the terms of the GNU General Public License as published by |
145 | +# the Free Software Foundation; either version 2 of the License, or |
146 | +# (at your option) any later version. |
147 | +# |
148 | +# This program is distributed in the hope that it will be useful, |
149 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
150 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
151 | +# GNU General Public License for more details. |
152 | +# |
153 | +# You should have received a copy of the GNU General Public License |
154 | +# along with this program; if not, write to the Free Software |
155 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
156 | + |
157 | +"""Tools for dealing with the Launchpad API without using launchpadlib. |
158 | + |
159 | +The api itself is a RESTful interface, so we can make HTTP queries directly. |
160 | +loading launchpadlib itself has a fairly high overhead (just calling |
161 | +Launchpad.login_anonymously() takes a 500ms once the WADL is cached, and 5+s to |
162 | +get the WADL. |
163 | +""" |
164 | + |
165 | +try: |
166 | + # Use simplejson if available, much faster, and can be easily installed in |
167 | + # older versions of python |
168 | + import simplejson as json |
169 | +except ImportError: |
170 | + # Is present since python 2.6 |
171 | + try: |
172 | + import json |
173 | + except ImportError: |
174 | + json = None |
175 | + |
176 | +import time |
177 | +import urllib |
178 | +import urllib2 |
179 | + |
180 | +from bzrlib import ( |
181 | + revision, |
182 | + trace, |
183 | + ) |
184 | + |
185 | + |
186 | +class LatestPublication(object): |
187 | + """Encapsulate how to find the latest publication for a given project.""" |
188 | + |
189 | + LP_API_ROOT = 'https://api.launchpad.net/1.0' |
190 | + |
191 | + def __init__(self, archive, series, project): |
192 | + self._archive = archive |
193 | + self._project = project |
194 | + self._setup_series_and_pocket(series) |
195 | + |
196 | + def _setup_series_and_pocket(self, series): |
197 | + """Parse the 'series' info into a series and a pocket. |
198 | + |
199 | + eg:: |
200 | + _setup_series_and_pocket('natty-proposed') |
201 | + => _series == 'natty' |
202 | + _pocket == 'Proposed' |
203 | + """ |
204 | + self._series = series |
205 | + self._pocket = None |
206 | + if self._series is not None and '-' in self._series: |
207 | + self._series, self._pocket = self._series.split('-', 1) |
208 | + self._pocket = self._pocket.title() |
209 | + else: |
210 | + self._pocket = 'Release' |
211 | + |
212 | + def _archive_URL(self): |
213 | + """Return the Launchpad 'Archive' URL that we will query. |
214 | + This is everything in the URL except the query parameters. |
215 | + """ |
216 | + return '%s/%s/+archive/primary' % (self.LP_API_ROOT, self._archive) |
217 | + |
218 | + def _publication_status(self): |
219 | + """Handle the 'status' field. |
220 | + It seems that Launchpad tracks all 'debian' packages as 'Pending', while |
221 | + for 'ubuntu' we care about the 'Published' packages. |
222 | + """ |
223 | + if self._archive == 'debian': |
224 | + # Launchpad only tracks debian packages as "Pending", it doesn't mark |
225 | + # them Published |
226 | + return 'Pending' |
227 | + return 'Published' |
228 | + |
229 | + def _query_params(self): |
230 | + """Get the parameters defining our query. |
231 | + This defines the actions we are making against the archive. |
232 | + :return: A dict of query parameters. |
233 | + """ |
234 | + params = {'ws.op': 'getPublishedSources', |
235 | + 'exact_match': 'true', |
236 | + # If we need to use "" shouldn't we quote the project somehow? |
237 | + 'source_name': '"%s"' % (self._project,), |
238 | + 'status': self._publication_status(), |
239 | + # We only need the latest one, the results seem to be properly |
240 | + # most-recent-debian-version sorted |
241 | + 'ws.size': '1', |
242 | + } |
243 | + if self._series is not None: |
244 | + params['distro_series'] = '/%s/%s' % (self._archive, self._series) |
245 | + if self._pocket is not None: |
246 | + params['pocket'] = self._pocket |
247 | + return params |
248 | + |
249 | + def _query_URL(self): |
250 | + """Create the full URL that we need to query, including parameters.""" |
251 | + params = self._query_params() |
252 | + # We sort to give deterministic results for testing |
253 | + encoded = urllib.urlencode(sorted(params.items())) |
254 | + return '%s?%s' % (self._archive_URL(), encoded) |
255 | + |
256 | + def _get_lp_info(self): |
257 | + """Place an actual HTTP query against the Launchpad service.""" |
258 | + if json is None: |
259 | + return None |
260 | + query_URL = self._query_URL() |
261 | + try: |
262 | + req = urllib2.Request(query_URL) |
263 | + response = urllib2.urlopen(req) |
264 | + json_info = response.read() |
265 | + # TODO: We haven't tested the HTTPError |
266 | + except (urllib2.URLError, urllib2.HTTPError), e: |
267 | + trace.mutter('failed to place query to %r' % (query_URL,)) |
268 | + trace.log_exception_quietly() |
269 | + return None |
270 | + return json_info |
271 | + |
272 | + def _parse_json_info(self, json_info): |
273 | + """Parse the json response from Launchpad into objects.""" |
274 | + if json is None: |
275 | + return None |
276 | + try: |
277 | + return json.loads(json_info) |
278 | + except Exception: |
279 | + trace.mutter('Failed to parse json info: %r' % (json_info,)) |
280 | + trace.log_exception_quietly() |
281 | + return None |
282 | + |
283 | + def get_latest_version(self): |
284 | + """Get the latest published version for the given package.""" |
285 | + json_info = self._get_lp_info() |
286 | + if json_info is None: |
287 | + return None |
288 | + info = self._parse_json_info(json_info) |
289 | + if info is None: |
290 | + return None |
291 | + try: |
292 | + entries = info['entries'] |
293 | + if len(entries) == 0: |
294 | + return None |
295 | + return entries[0]['source_package_version'] |
296 | + except KeyError: |
297 | + trace.log_exception_quietly() |
298 | + return None |
299 | + |
300 | + def place(self): |
301 | + """Text-form for what location this represents. |
302 | + |
303 | + Example:: |
304 | + ubuntu, natty => Ubuntu Natty |
305 | + ubuntu, natty-proposed => Ubuntu Natty Proposed |
306 | + :return: A string representing the location we are checking. |
307 | + """ |
308 | + place = self._archive |
309 | + if self._series is not None: |
310 | + place = '%s %s' % (place, self._series) |
311 | + if self._pocket is not None and self._pocket != 'Release': |
312 | + place = '%s %s' % (place, self._pocket) |
313 | + return place.title() |
314 | + |
315 | + |
316 | +def get_latest_publication(archive, series, project): |
317 | + """Get the most recent publication for a given project. |
318 | + |
319 | + :param archive: Either 'ubuntu' or 'debian' |
320 | + :param series: Something like 'natty', 'sid', etc. Can be set as None. Can |
321 | + also include a pocket such as 'natty-proposed'. |
322 | + :param project: Something like 'bzr' |
323 | + :return: A version string indicating the most-recent version published in |
324 | + Launchpad. Might return None if there is an error. |
325 | + """ |
326 | + lp = LatestPublication(archive, series, project) |
327 | + return lp.get_latest_version() |
328 | + |
329 | + |
330 | +def get_most_recent_tag(tag_dict, the_branch): |
331 | + """Get the most recent revision that has been tagged.""" |
332 | + # Note: this assumes that a given rev won't get tagged multiple times. But |
333 | + # it should be valid for the package importer branches that we care |
334 | + # about |
335 | + reverse_dict = dict((rev, tag) for tag, rev in tag_dict.iteritems()) |
336 | + the_branch.lock_read() |
337 | + try: |
338 | + history = the_branch.repository.iter_reverse_revision_history( |
339 | + the_branch.last_revision()) |
340 | + for rev_id in history: |
341 | + if rev_id in reverse_dict: |
342 | + return reverse_dict[rev_id] |
343 | + finally: |
344 | + the_branch.unlock() |
345 | + |
346 | + |
347 | +def _get_newest_versions(the_branch, latest_pub): |
348 | + """Get information about how 'fresh' this packaging branch is. |
349 | + |
350 | + :param the_branch: The Branch to check |
351 | + :param latest_pub: The LatestPublication used to check most recent |
352 | + published version. |
353 | + :return: (latest_ver, branch_latest_ver) |
354 | + """ |
355 | + t = time.time() |
356 | + latest_ver = latest_pub.get_latest_version() |
357 | + t_latest_ver = time.time() - t |
358 | + trace.mutter('LatestPublication.get_latest_version took: %.3fs' |
359 | + % (t_latest_ver,)) |
360 | + if latest_ver is None: |
361 | + return None, None |
362 | + t = time.time() |
363 | + tags = the_branch.tags.get_tag_dict() |
364 | + t_tag_dict = time.time() - t |
365 | + trace.mutter('LatestPublication.get_tag_dict took: %.3fs' % (t_tag_dict,)) |
366 | + if latest_ver in tags: |
367 | + # branch might have a newer tag, but we don't really care |
368 | + return latest_ver, latest_ver |
369 | + else: |
370 | + best_tag = get_most_recent_tag(tags, the_branch) |
371 | + return latest_ver, best_tag |
372 | + |
373 | + |
374 | +def _report_freshness(latest_ver, branch_latest_ver, place, verbosity, |
375 | + report_func): |
376 | + """Report if the branch is up-to-date.""" |
377 | + if latest_ver is None: |
378 | + if verbosity == 'all': |
379 | + report_func('Most recent %s version: MISSING' % (place,)) |
380 | + elif verbosity == 'short': |
381 | + report_func('%s is MISSING a version' % (place,)) |
382 | + return |
383 | + elif latest_ver == branch_latest_ver: |
384 | + if verbosity == 'minimal': |
385 | + return |
386 | + elif verbosity == 'short': |
387 | + report_func('%s is CURRENT in %s' % (latest_ver, place)) |
388 | + else: |
389 | + report_func('Most recent %s version: %s\n' |
390 | + 'Packaging branch status: CURRENT' |
391 | + % (place, latest_ver)) |
392 | + else: |
393 | + if verbosity in ('minimal', 'short'): |
394 | + if branch_latest_ver is None: |
395 | + branch_latest_ver = 'Branch' |
396 | + report_func('%s is OUT-OF-DATE, %s has %s' |
397 | + % (branch_latest_ver, place, latest_ver)) |
398 | + else: |
399 | + report_func('Most recent %s version: %s\n' |
400 | + 'Packaging branch version: %s\n' |
401 | + 'Packaging branch status: OUT-OF-DATE' |
402 | + % (place, latest_ver, branch_latest_ver)) |
403 | + |
404 | + |
405 | +def report_freshness(the_branch, verbosity, latest_pub): |
406 | + """Report to the user how up-to-date the packaging branch is. |
407 | + |
408 | + :param the_branch: A Branch object |
409 | + :param verbosity: Can be one of: |
410 | + off: Do not print anything, and skip all checks. |
411 | + all: Print all information that we have in a verbose manner, this |
412 | + includes misses, etc. |
413 | + short: Print information, but only one-line summaries |
414 | + minimal: Only print a one-line summary when the package branch is |
415 | + out-of-date |
416 | + :param latest_pub: A LatestPublication instance |
417 | + """ |
418 | + if verbosity == 'off': |
419 | + return |
420 | + if verbosity is None: |
421 | + verbosity = 'all' |
422 | + latest_ver, branch_ver = _get_newest_versions(the_branch, latest_pub) |
423 | + place = latest_pub.place() |
424 | + _report_freshness(latest_ver, branch_ver, place, verbosity, |
425 | + trace.note) |
426 | |
427 | === added file 'bzrlib/plugins/launchpad/test_lp_api_lite.py' |
428 | --- bzrlib/plugins/launchpad/test_lp_api_lite.py 1970-01-01 00:00:00 +0000 |
429 | +++ bzrlib/plugins/launchpad/test_lp_api_lite.py 2011-08-15 10:49:24 +0000 |
430 | @@ -0,0 +1,557 @@ |
431 | +# Copyright (C) 2011 Canonical Ltd |
432 | +# |
433 | +# This program is free software; you can redistribute it and/or modify |
434 | +# it under the terms of the GNU General Public License as published by |
435 | +# the Free Software Foundation; either version 2 of the License, or |
436 | +# (at your option) any later version. |
437 | +# |
438 | +# This program is distributed in the hope that it will be useful, |
439 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
440 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
441 | +# GNU General Public License for more details. |
442 | +# |
443 | +# You should have received a copy of the GNU General Public License |
444 | +# along with this program; if not, write to the Free Software |
445 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
446 | + |
447 | +"""Tools for dealing with the Launchpad API without using launchpadlib. |
448 | +""" |
449 | + |
450 | +import doctest |
451 | +import socket |
452 | + |
453 | +from bzrlib import tests |
454 | +from bzrlib.plugins import launchpad |
455 | +from bzrlib.plugins.launchpad import lp_api_lite |
456 | + |
457 | +from testtools.matchers import DocTestMatches |
458 | + |
459 | + |
460 | +class _JSONParserFeature(tests.Feature): |
461 | + |
462 | + def _probe(self): |
463 | + return lp_api_lite.json is not None |
464 | + |
465 | + def feature_name(self): |
466 | + return 'simplejson or json' |
467 | + |
468 | +JSONParserFeature = _JSONParserFeature() |
469 | + |
470 | +_example_response = r""" |
471 | +{ |
472 | + "total_size": 2, |
473 | + "start": 0, |
474 | + "next_collection_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary?distro_series=%2Fubuntu%2Flucid&exact_match=true&source_name=%22bzr%22&status=Published&ws.op=getPublishedSources&ws.start=1&ws.size=1", |
475 | + "entries": [ |
476 | + { |
477 | + "package_creator_link": "https://api.launchpad.net/1.0/~maxb", |
478 | + "package_signer_link": "https://api.launchpad.net/1.0/~jelmer", |
479 | + "source_package_name": "bzr", |
480 | + "removal_comment": null, |
481 | + "display_name": "bzr 2.1.4-0ubuntu1 in lucid", |
482 | + "date_made_pending": null, |
483 | + "source_package_version": "2.1.4-0ubuntu1", |
484 | + "date_superseded": null, |
485 | + "http_etag": "\"9ba966152dec474dc0fe1629d0bbce2452efaf3b-5f4c3fbb3eaf26d502db4089777a9b6a0537ffab\"", |
486 | + "self_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary/+sourcepub/1750327", |
487 | + "distro_series_link": "https://api.launchpad.net/1.0/ubuntu/lucid", |
488 | + "component_name": "main", |
489 | + "status": "Published", |
490 | + "date_removed": null, |
491 | + "pocket": "Updates", |
492 | + "date_published": "2011-05-30T06:09:58.653984+00:00", |
493 | + "removed_by_link": null, |
494 | + "section_name": "devel", |
495 | + "resource_type_link": "https://api.launchpad.net/1.0/#source_package_publishing_history", |
496 | + "archive_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary", |
497 | + "package_maintainer_link": "https://api.launchpad.net/1.0/~ubuntu-devel-discuss-lists", |
498 | + "date_created": "2011-05-30T05:19:12.233621+00:00", |
499 | + "scheduled_deletion_date": null |
500 | + } |
501 | + ] |
502 | +}""" |
503 | + |
504 | +_no_versions_response = '{"total_size": 0, "start": 0, "entries": []}' |
505 | + |
506 | + |
507 | +class TestLatestPublication(tests.TestCase): |
508 | + |
509 | + def make_latest_publication(self, archive='ubuntu', series='natty', |
510 | + project='bzr'): |
511 | + return lp_api_lite.LatestPublication(archive, series, project) |
512 | + |
513 | + def assertPlace(self, place, archive, series, project): |
514 | + lp = lp_api_lite.LatestPublication(archive, series, project) |
515 | + self.assertEqual(place, lp.place()) |
516 | + |
517 | + def test_init(self): |
518 | + latest_pub = self.make_latest_publication() |
519 | + self.assertEqual('ubuntu', latest_pub._archive) |
520 | + self.assertEqual('natty', latest_pub._series) |
521 | + self.assertEqual('bzr', latest_pub._project) |
522 | + self.assertEqual('Release', latest_pub._pocket) |
523 | + |
524 | + def test__archive_URL(self): |
525 | + latest_pub = self.make_latest_publication() |
526 | + self.assertEqual( |
527 | + 'https://api.launchpad.net/1.0/ubuntu/+archive/primary', |
528 | + latest_pub._archive_URL()) |
529 | + |
530 | + def test__publication_status_for_ubuntu(self): |
531 | + latest_pub = self.make_latest_publication() |
532 | + self.assertEqual('Published', latest_pub._publication_status()) |
533 | + |
534 | + def test__publication_status_for_debian(self): |
535 | + latest_pub = self.make_latest_publication(archive='debian') |
536 | + self.assertEqual('Pending', latest_pub._publication_status()) |
537 | + |
538 | + def test_pocket(self): |
539 | + latest_pub = self.make_latest_publication(series='natty-proposed') |
540 | + self.assertEqual('natty', latest_pub._series) |
541 | + self.assertEqual('Proposed', latest_pub._pocket) |
542 | + |
543 | + def test_series_None(self): |
544 | + latest_pub = self.make_latest_publication(series=None) |
545 | + self.assertEqual('ubuntu', latest_pub._archive) |
546 | + self.assertEqual(None, latest_pub._series) |
547 | + self.assertEqual('bzr', latest_pub._project) |
548 | + self.assertEqual('Release', latest_pub._pocket) |
549 | + |
550 | + def test__query_params(self): |
551 | + latest_pub = self.make_latest_publication() |
552 | + self.assertEqual({'ws.op': 'getPublishedSources', |
553 | + 'exact_match': 'true', |
554 | + 'source_name': '"bzr"', |
555 | + 'status': 'Published', |
556 | + 'ws.size': '1', |
557 | + 'distro_series': '/ubuntu/natty', |
558 | + 'pocket': 'Release', |
559 | + }, latest_pub._query_params()) |
560 | + |
561 | + def test__query_params_no_series(self): |
562 | + latest_pub = self.make_latest_publication(series=None) |
563 | + self.assertEqual({'ws.op': 'getPublishedSources', |
564 | + 'exact_match': 'true', |
565 | + 'source_name': '"bzr"', |
566 | + 'status': 'Published', |
567 | + 'ws.size': '1', |
568 | + 'pocket': 'Release', |
569 | + }, latest_pub._query_params()) |
570 | + |
571 | + def test__query_params_pocket(self): |
572 | + latest_pub = self.make_latest_publication(series='natty-proposed') |
573 | + self.assertEqual({'ws.op': 'getPublishedSources', |
574 | + 'exact_match': 'true', |
575 | + 'source_name': '"bzr"', |
576 | + 'status': 'Published', |
577 | + 'ws.size': '1', |
578 | + 'distro_series': '/ubuntu/natty', |
579 | + 'pocket': 'Proposed', |
580 | + }, latest_pub._query_params()) |
581 | + |
582 | + def test__query_URL(self): |
583 | + latest_pub = self.make_latest_publication() |
584 | + # we explicitly sort params, so we can be sure this URL matches exactly |
585 | + self.assertEqual( |
586 | + 'https://api.launchpad.net/1.0/ubuntu/+archive/primary' |
587 | + '?distro_series=%2Fubuntu%2Fnatty&exact_match=true' |
588 | + '&pocket=Release&source_name=%22bzr%22&status=Published' |
589 | + '&ws.op=getPublishedSources&ws.size=1', |
590 | + latest_pub._query_URL()) |
591 | + |
592 | + def DONT_test__gracefully_handle_failed_rpc_connection(self): |
593 | + # TODO: This test kind of sucks. We intentionally create an arbitrary |
594 | + # port and don't listen to it, because we want the request to fail. |
595 | + # However, it seems to take 1s for it to timeout. Is there a way |
596 | + # to make it fail faster? |
597 | + latest_pub = self.make_latest_publication() |
598 | + s = socket.socket() |
599 | + s.bind(('127.0.0.1', 0)) |
600 | + addr, port = s.getsockname() |
601 | + latest_pub.LP_API_ROOT = 'http://%s:%s/' % (addr, port) |
602 | + s.close() |
603 | + self.assertIs(None, latest_pub._get_lp_info()) |
604 | + |
605 | + def DONT_test__query_launchpad(self): |
606 | + # TODO: This is a test that we are making a valid request against |
607 | + # launchpad. This seems important, but it is slow, requires net |
608 | + # access, and requires launchpad to be up and running. So for |
609 | + # now, it is commented out for production tests. |
610 | + latest_pub = self.make_latest_publication() |
611 | + json_txt = latest_pub._get_lp_info() |
612 | + self.assertIsNot(None, json_txt) |
613 | + if lp_api_lite.json is None: |
614 | + # We don't have a way to parse the text |
615 | + return |
616 | + # The content should be a valid json result |
617 | + content = lp_api_lite.json.loads(json_txt) |
618 | + entries = content['entries'] # It should have an 'entries' field. |
619 | + # ws.size should mean we get 0 or 1, and there should be something |
620 | + self.assertEqual(1, len(entries)) |
621 | + entry = entries[0] |
622 | + self.assertEqual('bzr', entry['source_package_name']) |
623 | + version = entry['source_package_version'] |
624 | + self.assertIsNot(None, version) |
625 | + |
626 | + def disableJSON(self): |
627 | + orig = lp_api_lite.json |
628 | + def cleanup(): |
629 | + lp_api_lite.json = orig |
630 | + self.addCleanup(cleanup) |
631 | + lp_api_lite.json = None |
632 | + |
633 | + def test__get_lp_info_no_json(self): |
634 | + # If we can't parse the json, we don't make the query. |
635 | + self.disableJSON() |
636 | + latest_pub = self.make_latest_publication() |
637 | + self.assertIs(None, latest_pub._get_lp_info()) |
638 | + |
639 | + def test__parse_json_info_no_module(self): |
640 | + # If a json parsing module isn't available, we just return None here. |
641 | + self.disableJSON() |
642 | + latest_pub = self.make_latest_publication() |
643 | + self.assertIs(None, latest_pub._parse_json_info(_example_response)) |
644 | + |
645 | + def test__parse_json_example_response(self): |
646 | + self.requireFeature(JSONParserFeature) |
647 | + latest_pub = self.make_latest_publication() |
648 | + content = latest_pub._parse_json_info(_example_response) |
649 | + self.assertIsNot(None, content) |
650 | + self.assertEqual(2, content['total_size']) |
651 | + entries = content['entries'] |
652 | + self.assertEqual(1, len(entries)) |
653 | + entry = entries[0] |
654 | + self.assertEqual('bzr', entry['source_package_name']) |
655 | + self.assertEqual("2.1.4-0ubuntu1", entry["source_package_version"]) |
656 | + |
657 | + def test__parse_json_not_json(self): |
658 | + self.requireFeature(JSONParserFeature) |
659 | + latest_pub = self.make_latest_publication() |
660 | + self.assertIs(None, latest_pub._parse_json_info('Not_valid_json')) |
661 | + |
662 | + def test_get_latest_version_no_response(self): |
663 | + latest_pub = self.make_latest_publication() |
664 | + latest_pub._get_lp_info = lambda: None |
665 | + self.assertEqual(None, latest_pub.get_latest_version()) |
666 | + |
667 | + def test_get_latest_version_no_json(self): |
668 | + self.disableJSON() |
669 | + latest_pub = self.make_latest_publication() |
670 | + self.assertEqual(None, latest_pub.get_latest_version()) |
671 | + |
672 | + def test_get_latest_version_invalid_json(self): |
673 | + self.requireFeature(JSONParserFeature) |
674 | + latest_pub = self.make_latest_publication() |
675 | + latest_pub._get_lp_info = lambda: "not json" |
676 | + self.assertEqual(None, latest_pub.get_latest_version()) |
677 | + |
678 | + def test_get_latest_version_no_versions(self): |
679 | + self.requireFeature(JSONParserFeature) |
680 | + latest_pub = self.make_latest_publication() |
681 | + latest_pub._get_lp_info = lambda: _no_versions_response |
682 | + self.assertEqual(None, latest_pub.get_latest_version()) |
683 | + |
684 | + def test_get_latest_version_missing_entries(self): |
685 | + # Launchpad's no-entries response does have an empty entries value. |
686 | + # However, lets test that we handle other failures without tracebacks |
687 | + self.requireFeature(JSONParserFeature) |
688 | + latest_pub = self.make_latest_publication() |
689 | + latest_pub._get_lp_info = lambda: '{}' |
690 | + self.assertEqual(None, latest_pub.get_latest_version()) |
691 | + |
692 | + def test_get_latest_version_invalid_entries(self): |
693 | + # Make sure we sanely handle a json response we don't understand |
694 | + self.requireFeature(JSONParserFeature) |
695 | + latest_pub = self.make_latest_publication() |
696 | + latest_pub._get_lp_info = lambda: '{"entries": {"a": 1}}' |
697 | + self.assertEqual(None, latest_pub.get_latest_version()) |
698 | + |
699 | + def test_get_latest_version_example(self): |
700 | + self.requireFeature(JSONParserFeature) |
701 | + latest_pub = self.make_latest_publication() |
702 | + latest_pub._get_lp_info = lambda: _example_response |
703 | + self.assertEqual("2.1.4-0ubuntu1", latest_pub.get_latest_version()) |
704 | + |
705 | + def DONT_test_get_latest_version_from_launchpad(self): |
706 | + self.requireFeature(JSONParserFeature) |
707 | + latest_pub = self.make_latest_publication() |
708 | + self.assertIsNot(None, latest_pub.get_latest_version()) |
709 | + |
710 | + def test_place(self): |
711 | + self.assertPlace('Ubuntu', 'ubuntu', None, 'bzr') |
712 | + self.assertPlace('Ubuntu Natty', 'ubuntu', 'natty', 'bzr') |
713 | + self.assertPlace('Ubuntu Natty Proposed', 'ubuntu', 'natty-proposed', |
714 | + 'bzr') |
715 | + self.assertPlace('Debian', 'debian', None, 'bzr') |
716 | + self.assertPlace('Debian Sid', 'debian', 'sid', 'bzr') |
717 | + |
718 | + |
719 | +class TestIsUpToDate(tests.TestCase): |
720 | + |
721 | + def assertPackageBranchRe(self, url, user, archive, series, project): |
722 | + m = launchpad._package_branch.search(url) |
723 | + if m is None: |
724 | + self.fail('package_branch regex did not match url: %s' % (url,)) |
725 | + self.assertEqual( |
726 | + (user, archive, series, project), |
727 | + m.group('user', 'archive', 'series', 'project')) |
728 | + |
729 | + def assertNotPackageBranch(self, url): |
730 | + self.assertIs(None, launchpad._get_package_branch_info(url)) |
731 | + |
732 | + def assertBranchInfo(self, url, archive, series, project): |
733 | + self.assertEqual((archive, series, project), |
734 | + launchpad._get_package_branch_info(url)) |
735 | + |
736 | + def test_package_branch_regex(self): |
737 | + self.assertPackageBranchRe( |
738 | + 'http://bazaar.launchpad.net/+branch/ubuntu/foo', |
739 | + None, 'ubuntu', None, 'foo') |
740 | + self.assertPackageBranchRe( |
741 | + 'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo', |
742 | + None, 'ubuntu', 'natty/', 'foo') |
743 | + self.assertPackageBranchRe( |
744 | + 'sftp://bazaar.launchpad.net/+branch/debian/foo', |
745 | + None, 'debian', None, 'foo') |
746 | + self.assertPackageBranchRe( |
747 | + 'http://bazaar.launchpad.net/+branch/debian/sid/foo', |
748 | + None, 'debian', 'sid/', 'foo') |
749 | + self.assertPackageBranchRe( |
750 | + 'http://bazaar.launchpad.net/+branch' |
751 | + '/~ubuntu-branches/ubuntu/natty/foo/natty', |
752 | + '~ubuntu-branches/', 'ubuntu', 'natty/', 'foo') |
753 | + self.assertPackageBranchRe( |
754 | + 'http://bazaar.launchpad.net/+branch' |
755 | + '/~user/ubuntu/natty/foo/test', |
756 | + '~user/', 'ubuntu', 'natty/', 'foo') |
757 | + |
758 | + def test_package_branch_doesnt_match(self): |
759 | + self.assertNotPackageBranch('http://example.com/ubuntu/foo') |
760 | + self.assertNotPackageBranch( |
761 | + 'http://bazaar.launchpad.net/+branch/bzr') |
762 | + self.assertNotPackageBranch( |
763 | + 'http://bazaar.launchpad.net/+branch/~bzr-pqm/bzr/bzr.dev') |
764 | + # Not a packaging branch because ~user isn't ~ubuntu-branches |
765 | + self.assertNotPackageBranch( |
766 | + 'http://bazaar.launchpad.net/+branch' |
767 | + '/~user/ubuntu/natty/foo/natty') |
768 | + # Older versions of bzr-svn/hg/git did not set Branch.base until after |
769 | + # they called Branch.__init__(). |
770 | + self.assertNotPackageBranch(None) |
771 | + |
772 | + def test__get_package_branch_info(self): |
773 | + self.assertBranchInfo( |
774 | + 'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo', |
775 | + 'ubuntu', 'natty', 'foo') |
776 | + self.assertBranchInfo( |
777 | + 'bzr+ssh://bazaar.launchpad.net/+branch' |
778 | + '/~ubuntu-branches/ubuntu/natty/foo/natty', |
779 | + 'ubuntu', 'natty', 'foo') |
780 | + self.assertBranchInfo( |
781 | + 'http://bazaar.launchpad.net/+branch' |
782 | + '/~ubuntu-branches/debian/sid/foo/sid', |
783 | + 'debian', 'sid', 'foo') |
784 | + |
785 | + |
786 | +class TestGetMostRecentTag(tests.TestCaseWithMemoryTransport): |
787 | + |
788 | + def make_simple_builder(self): |
789 | + builder = self.make_branch_builder('tip') |
790 | + builder.build_snapshot('A', None, [ |
791 | + ('add', ('', 'root-id', 'directory', None))]) |
792 | + b = builder.get_branch() |
793 | + b.tags.set_tag('tip-1.0', 'A') |
794 | + return builder, b, b.tags.get_tag_dict() |
795 | + |
796 | + def test_get_most_recent_tag_tip(self): |
797 | + builder, b, tag_dict = self.make_simple_builder() |
798 | + self.assertEqual('tip-1.0', |
799 | + lp_api_lite.get_most_recent_tag(tag_dict, b)) |
800 | + |
801 | + def test_get_most_recent_tag_older(self): |
802 | + builder, b, tag_dict = self.make_simple_builder() |
803 | + builder.build_snapshot('B', ['A'], []) |
804 | + self.assertEqual('B', b.last_revision()) |
805 | + self.assertEqual('tip-1.0', |
806 | + lp_api_lite.get_most_recent_tag(tag_dict, b)) |
807 | + |
808 | + |
809 | +class StubLatestPublication(object): |
810 | + |
811 | + def __init__(self, latest): |
812 | + self.called = False |
813 | + self.latest = latest |
814 | + |
815 | + def get_latest_version(self): |
816 | + self.called = True |
817 | + return self.latest |
818 | + |
819 | + def place(self): |
820 | + return 'Ubuntu Natty' |
821 | + |
822 | + |
823 | +class TestReportFreshness(tests.TestCaseWithMemoryTransport): |
824 | + |
825 | + def setUp(self): |
826 | + super(TestReportFreshness, self).setUp() |
827 | + builder = self.make_branch_builder('tip') |
828 | + builder.build_snapshot('A', None, [ |
829 | + ('add', ('', 'root-id', 'directory', None))]) |
830 | + self.branch = builder.get_branch() |
831 | + |
832 | + def assertFreshnessReports(self, verbosity, latest_version, content): |
833 | + """Assert that lp_api_lite.report_freshness reports the given content. |
834 | + |
835 | + :param verbosity: The reporting level |
836 | + :param latest_version: The version reported by StubLatestPublication |
837 | + :param content: The expected content. This should be in DocTest form. |
838 | + """ |
839 | + orig_log_len = len(self.get_log()) |
840 | + lp_api_lite.report_freshness(self.branch, verbosity, |
841 | + StubLatestPublication(latest_version)) |
842 | + new_content = self.get_log()[orig_log_len:] |
843 | + # Strip out lines that have LatestPublication.get_* because those are |
844 | + # timing related lines. While interesting to log for now, they aren't |
845 | + # something we want to be testing |
846 | + new_content = new_content.split('\n') |
847 | + for i in range(2): |
848 | + if (len(new_content) > 0 |
849 | + and 'LatestPublication.get_' in new_content[0]): |
850 | + new_content = new_content[1:] |
851 | + new_content = '\n'.join(new_content) |
852 | + self.assertThat(new_content, |
853 | + DocTestMatches(content, |
854 | + doctest.ELLIPSIS | doctest.REPORT_UDIFF)) |
855 | + |
856 | + def test_verbosity_off_skips_check(self): |
857 | + # We force _get_package_branch_info so that we know it would otherwise |
858 | + # try to connect to launcphad |
859 | + orig_gpbi = launchpad._get_package_branch_info |
860 | + orig_lp = lp_api_lite.LatestPublication |
861 | + def cleanup(): |
862 | + launchpad._get_package_branch_info = orig_gpbi |
863 | + lp_api_lite.LatestPublication = orig_lp |
864 | + self.addCleanup(cleanup) |
865 | + launchpad._get_package_branch_info = lambda x: ('ubuntu', 'natty', 'bzr') |
866 | + lp_api_lite.LatestPublication = lambda *args: self.fail('Tried to query launchpad') |
867 | + c = self.branch.get_config() |
868 | + c.set_user_option('launchpad.packaging_verbosity', 'off') |
869 | + orig_log_len = len(self.get_log()) |
870 | + launchpad._check_is_up_to_date(self.branch) |
871 | + new_content = self.get_log()[orig_log_len:] |
872 | + self.assertContainsRe(new_content, |
873 | + 'not checking memory.*/tip/ because verbosity is turned off') |
874 | + |
875 | + def test_verbosity_off(self): |
876 | + latest_pub = StubLatestPublication('1.0-1ubuntu2') |
877 | + lp_api_lite.report_freshness(self.branch, 'off', latest_pub) |
878 | + self.assertFalse(latest_pub.called) |
879 | + |
880 | + def test_verbosity_all_out_of_date_smoke(self): |
881 | + self.branch.tags.set_tag('1.0-1ubuntu1', 'A') |
882 | + self.assertFreshnessReports('all', '1.0-1ubuntu2', |
883 | + ' INFO Most recent Ubuntu Natty version: 1.0-1ubuntu2\n' |
884 | + 'Packaging branch version: 1.0-1ubuntu1\n' |
885 | + 'Packaging branch status: OUT-OF-DATE\n') |
886 | + |
887 | + |
888 | +class Test_GetNewestVersions(tests.TestCaseWithMemoryTransport): |
889 | + |
890 | + def setUp(self): |
891 | + super(Test_GetNewestVersions, self).setUp() |
892 | + builder = self.make_branch_builder('tip') |
893 | + builder.build_snapshot('A', None, [ |
894 | + ('add', ('', 'root-id', 'directory', None))]) |
895 | + self.branch = builder.get_branch() |
896 | + |
897 | + def assertLatestVersions(self, latest_branch_version, pub_version): |
898 | + if latest_branch_version is not None: |
899 | + self.branch.tags.set_tag(latest_branch_version, 'A') |
900 | + latest_pub = StubLatestPublication(pub_version) |
901 | + self.assertEqual((pub_version, latest_branch_version), |
902 | + lp_api_lite._get_newest_versions(self.branch, latest_pub)) |
903 | + |
904 | + def test_no_tags(self): |
905 | + self.assertLatestVersions(None, '1.0-1ubuntu2') |
906 | + |
907 | + def test_out_of_date(self): |
908 | + self.assertLatestVersions('1.0-1ubuntu1', '1.0-1ubuntu2') |
909 | + |
910 | + def test_up_to_date(self): |
911 | + self.assertLatestVersions('1.0-1ubuntu2', '1.0-1ubuntu2') |
912 | + |
913 | + def test_missing(self): |
914 | + self.assertLatestVersions(None, None) |
915 | + |
916 | + |
917 | +class Test_ReportFreshness(tests.TestCase): |
918 | + |
919 | + def assertReportedFreshness(self, verbosity, latest_ver, branch_latest_ver, |
920 | + content, place='Ubuntu Natty'): |
921 | + """Assert that lp_api_lite.report_freshness reports the given content. |
922 | + """ |
923 | + reported = [] |
924 | + def report_func(value): |
925 | + reported.append(value) |
926 | + lp_api_lite._report_freshness(latest_ver, branch_latest_ver, place, |
927 | + verbosity, report_func) |
928 | + new_content = '\n'.join(reported) |
929 | + self.assertThat(new_content, |
930 | + DocTestMatches(content, |
931 | + doctest.ELLIPSIS | doctest.REPORT_UDIFF)) |
932 | + |
933 | + def test_verbosity_minimal_no_tags(self): |
934 | + self.assertReportedFreshness('minimal', '1.0-1ubuntu2', None, |
935 | + 'Branch is OUT-OF-DATE, Ubuntu Natty has 1.0-1ubuntu2\n') |
936 | + |
937 | + def test_verbosity_minimal_out_of_date(self): |
938 | + self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu1', |
939 | + '1.0-1ubuntu1 is OUT-OF-DATE,' |
940 | + ' Ubuntu Natty has 1.0-1ubuntu2\n') |
941 | + |
942 | + def test_verbosity_minimal_up_to_date(self): |
943 | + self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu2', |
944 | + '') |
945 | + |
946 | + def test_verbosity_minimal_missing(self): |
947 | + self.assertReportedFreshness('minimal', None, None, |
948 | + '') |
949 | + |
950 | + def test_verbosity_short_out_of_date(self): |
951 | + self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu1', |
952 | + '1.0-1ubuntu1 is OUT-OF-DATE,' |
953 | + ' Ubuntu Natty has 1.0-1ubuntu2\n') |
954 | + |
955 | + def test_verbosity_short_up_to_date(self): |
956 | + self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu2', |
957 | + '1.0-1ubuntu2 is CURRENT in Ubuntu Natty') |
958 | + |
959 | + def test_verbosity_short_missing(self): |
960 | + self.assertReportedFreshness('short', None, None, |
961 | + 'Ubuntu Natty is MISSING a version') |
962 | + |
963 | + def test_verbosity_all_no_tags(self): |
964 | + self.assertReportedFreshness('all', '1.0-1ubuntu2', None, |
965 | + 'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n' |
966 | + 'Packaging branch version: None\n' |
967 | + 'Packaging branch status: OUT-OF-DATE\n') |
968 | + |
969 | + def test_verbosity_all_out_of_date(self): |
970 | + self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu1', |
971 | + 'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n' |
972 | + 'Packaging branch version: 1.0-1ubuntu1\n' |
973 | + 'Packaging branch status: OUT-OF-DATE\n') |
974 | + |
975 | + def test_verbosity_all_up_to_date(self): |
976 | + self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu2', |
977 | + 'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n' |
978 | + 'Packaging branch status: CURRENT\n') |
979 | + |
980 | + def test_verbosity_all_missing(self): |
981 | + self.assertReportedFreshness('all', None, None, |
982 | + 'Most recent Ubuntu Natty version: MISSING\n') |
983 | + |
984 | + def test_verbosity_None_is_all(self): |
985 | + self.assertReportedFreshness(None, '1.0-1ubuntu2', '1.0-1ubuntu2', |
986 | + 'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n' |
987 | + 'Packaging branch status: CURRENT\n') |
I have only briefly glanced over this. I've specifically checked the overriding of attributes and iter_reverse_ revision_ history. That all looks good to me.
It would be useful to test this with 2.4, in case this uses anything that relies on 2.5 or 2.6. I can't find anything when looking manually.