1
# Copyright (C) 2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tools for dealing with the Launchpad API without using launchpadlib.
23
from bzrlib import tests
24
from bzrlib.plugins import launchpad
25
from bzrlib.plugins.launchpad import lp_api_lite
27
from testtools.matchers import DocTestMatches
30
class _JSONParserFeature(tests.Feature):
33
return lp_api_lite.json is not None
35
def feature_name(self):
36
return 'simplejson or json'
38
JSONParserFeature = _JSONParserFeature()
40
_example_response = r"""
44
"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",
47
"package_creator_link": "https://api.launchpad.net/1.0/~maxb",
48
"package_signer_link": "https://api.launchpad.net/1.0/~jelmer",
49
"source_package_name": "bzr",
50
"removal_comment": null,
51
"display_name": "bzr 2.1.4-0ubuntu1 in lucid",
52
"date_made_pending": null,
53
"source_package_version": "2.1.4-0ubuntu1",
54
"date_superseded": null,
55
"http_etag": "\"9ba966152dec474dc0fe1629d0bbce2452efaf3b-5f4c3fbb3eaf26d502db4089777a9b6a0537ffab\"",
56
"self_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary/+sourcepub/1750327",
57
"distro_series_link": "https://api.launchpad.net/1.0/ubuntu/lucid",
58
"component_name": "main",
59
"status": "Published",
62
"date_published": "2011-05-30T06:09:58.653984+00:00",
63
"removed_by_link": null,
64
"section_name": "devel",
65
"resource_type_link": "https://api.launchpad.net/1.0/#source_package_publishing_history",
66
"archive_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary",
67
"package_maintainer_link": "https://api.launchpad.net/1.0/~ubuntu-devel-discuss-lists",
68
"date_created": "2011-05-30T05:19:12.233621+00:00",
69
"scheduled_deletion_date": null
74
_no_versions_response = '{"total_size": 0, "start": 0, "entries": []}'
77
class TestLatestPublication(tests.TestCase):
79
def make_latest_publication(self, archive='ubuntu', series='natty',
81
return lp_api_lite.LatestPublication(archive, series, project)
83
def assertPlace(self, place, archive, series, project):
84
lp = lp_api_lite.LatestPublication(archive, series, project)
85
self.assertEqual(place, lp.place())
88
latest_pub = self.make_latest_publication()
89
self.assertEqual('ubuntu', latest_pub._archive)
90
self.assertEqual('natty', latest_pub._series)
91
self.assertEqual('bzr', latest_pub._project)
92
self.assertEqual('Release', latest_pub._pocket)
94
def test__archive_URL(self):
95
latest_pub = self.make_latest_publication()
97
'https://api.launchpad.net/1.0/ubuntu/+archive/primary',
98
latest_pub._archive_URL())
100
def test__publication_status_for_ubuntu(self):
101
latest_pub = self.make_latest_publication()
102
self.assertEqual('Published', latest_pub._publication_status())
104
def test__publication_status_for_debian(self):
105
latest_pub = self.make_latest_publication(archive='debian')
106
self.assertEqual('Pending', latest_pub._publication_status())
108
def test_pocket(self):
109
latest_pub = self.make_latest_publication(series='natty-proposed')
110
self.assertEqual('natty', latest_pub._series)
111
self.assertEqual('Proposed', latest_pub._pocket)
113
def test_series_None(self):
114
latest_pub = self.make_latest_publication(series=None)
115
self.assertEqual('ubuntu', latest_pub._archive)
116
self.assertEqual(None, latest_pub._series)
117
self.assertEqual('bzr', latest_pub._project)
118
self.assertEqual('Release', latest_pub._pocket)
120
def test__query_params(self):
121
latest_pub = self.make_latest_publication()
122
self.assertEqual({'ws.op': 'getPublishedSources',
123
'exact_match': 'true',
124
'source_name': '"bzr"',
125
'status': 'Published',
127
'distro_series': '/ubuntu/natty',
129
}, latest_pub._query_params())
131
def test__query_params_no_series(self):
132
latest_pub = self.make_latest_publication(series=None)
133
self.assertEqual({'ws.op': 'getPublishedSources',
134
'exact_match': 'true',
135
'source_name': '"bzr"',
136
'status': 'Published',
139
}, latest_pub._query_params())
141
def test__query_params_pocket(self):
142
latest_pub = self.make_latest_publication(series='natty-proposed')
143
self.assertEqual({'ws.op': 'getPublishedSources',
144
'exact_match': 'true',
145
'source_name': '"bzr"',
146
'status': 'Published',
148
'distro_series': '/ubuntu/natty',
149
'pocket': 'Proposed',
150
}, latest_pub._query_params())
152
def test__query_URL(self):
153
latest_pub = self.make_latest_publication()
154
# we explicitly sort params, so we can be sure this URL matches exactly
156
'https://api.launchpad.net/1.0/ubuntu/+archive/primary'
157
'?distro_series=%2Fubuntu%2Fnatty&exact_match=true'
158
'&pocket=Release&source_name=%22bzr%22&status=Published'
159
'&ws.op=getPublishedSources&ws.size=1',
160
latest_pub._query_URL())
162
def DONT_test__gracefully_handle_failed_rpc_connection(self):
163
# TODO: This test kind of sucks. We intentionally create an arbitrary
164
# port and don't listen to it, because we want the request to fail.
165
# However, it seems to take 1s for it to timeout. Is there a way
166
# to make it fail faster?
167
latest_pub = self.make_latest_publication()
169
s.bind(('127.0.0.1', 0))
170
addr, port = s.getsockname()
171
latest_pub.LP_API_ROOT = 'http://%s:%s/' % (addr, port)
173
self.assertIs(None, latest_pub._get_lp_info())
175
def DONT_test__query_launchpad(self):
176
# TODO: This is a test that we are making a valid request against
177
# launchpad. This seems important, but it is slow, requires net
178
# access, and requires launchpad to be up and running. So for
179
# now, it is commented out for production tests.
180
latest_pub = self.make_latest_publication()
181
json_txt = latest_pub._get_lp_info()
182
self.assertIsNot(None, json_txt)
183
if lp_api_lite.json is None:
184
# We don't have a way to parse the text
186
# The content should be a valid json result
187
content = lp_api_lite.json.loads(json_txt)
188
entries = content['entries'] # It should have an 'entries' field.
189
# ws.size should mean we get 0 or 1, and there should be something
190
self.assertEqual(1, len(entries))
192
self.assertEqual('bzr', entry['source_package_name'])
193
version = entry['source_package_version']
194
self.assertIsNot(None, version)
196
def test__get_lp_info_no_json(self):
197
# If we can't parse the json, we don't make the query.
198
self.overrideAttr(lp_api_lite, 'json', None)
199
latest_pub = self.make_latest_publication()
200
self.assertIs(None, latest_pub._get_lp_info())
202
def test__parse_json_info_no_module(self):
203
# If a json parsing module isn't available, we just return None here.
204
self.overrideAttr(lp_api_lite, 'json', None)
205
latest_pub = self.make_latest_publication()
206
self.assertIs(None, latest_pub._parse_json_info(_example_response))
208
def test__parse_json_example_response(self):
209
self.requireFeature(JSONParserFeature)
210
latest_pub = self.make_latest_publication()
211
content = latest_pub._parse_json_info(_example_response)
212
self.assertIsNot(None, content)
213
self.assertEqual(2, content['total_size'])
214
entries = content['entries']
215
self.assertEqual(1, len(entries))
217
self.assertEqual('bzr', entry['source_package_name'])
218
self.assertEqual("2.1.4-0ubuntu1", entry["source_package_version"])
220
def test__parse_json_not_json(self):
221
self.requireFeature(JSONParserFeature)
222
latest_pub = self.make_latest_publication()
223
self.assertIs(None, latest_pub._parse_json_info('Not_valid_json'))
225
def test_get_latest_version_no_response(self):
226
latest_pub = self.make_latest_publication()
227
latest_pub._get_lp_info = lambda: None
228
self.assertEqual(None, latest_pub.get_latest_version())
230
def test_get_latest_version_no_json(self):
231
self.overrideAttr(lp_api_lite, 'json', None)
232
latest_pub = self.make_latest_publication()
233
self.assertEqual(None, latest_pub.get_latest_version())
235
def test_get_latest_version_invalid_json(self):
236
self.requireFeature(JSONParserFeature)
237
latest_pub = self.make_latest_publication()
238
latest_pub._get_lp_info = lambda: "not json"
239
self.assertEqual(None, latest_pub.get_latest_version())
241
def test_get_latest_version_no_versions(self):
242
self.requireFeature(JSONParserFeature)
243
latest_pub = self.make_latest_publication()
244
latest_pub._get_lp_info = lambda: _no_versions_response
245
self.assertEqual(None, latest_pub.get_latest_version())
247
def test_get_latest_version_missing_entries(self):
248
# Launchpad's no-entries response does have an empty entries value.
249
# However, lets test that we handle other failures without tracebacks
250
self.requireFeature(JSONParserFeature)
251
latest_pub = self.make_latest_publication()
252
latest_pub._get_lp_info = lambda: '{}'
253
self.assertEqual(None, latest_pub.get_latest_version())
255
def test_get_latest_version_invalid_entries(self):
256
# Make sure we sanely handle a json response we don't understand
257
self.requireFeature(JSONParserFeature)
258
latest_pub = self.make_latest_publication()
259
latest_pub._get_lp_info = lambda: '{"entries": {"a": 1}}'
260
self.assertEqual(None, latest_pub.get_latest_version())
262
def test_get_latest_version_example(self):
263
self.requireFeature(JSONParserFeature)
264
latest_pub = self.make_latest_publication()
265
latest_pub._get_lp_info = lambda: _example_response
266
self.assertEqual("2.1.4-0ubuntu1", latest_pub.get_latest_version())
268
def DONT_test_get_latest_version_from_launchpad(self):
269
self.requireFeature(JSONParserFeature)
270
latest_pub = self.make_latest_publication()
271
self.assertIsNot(None, latest_pub.get_latest_version())
273
def test_place(self):
274
self.assertPlace('Ubuntu', 'ubuntu', None, 'bzr')
275
self.assertPlace('Ubuntu Natty', 'ubuntu', 'natty', 'bzr')
276
self.assertPlace('Ubuntu Natty Proposed', 'ubuntu', 'natty-proposed',
278
self.assertPlace('Debian', 'debian', None, 'bzr')
279
self.assertPlace('Debian Sid', 'debian', 'sid', 'bzr')
282
class TestIsUpToDate(tests.TestCase):
284
def assertPackageBranchRe(self, url, user, archive, series, project):
285
m = launchpad._package_branch.search(url)
287
self.fail('package_branch regex did not match url: %s' % (url,))
289
(user, archive, series, project),
290
m.group('user', 'archive', 'series', 'project'))
292
def assertNotPackageBranch(self, url):
293
self.assertIs(None, launchpad._get_package_branch_info(url))
295
def assertBranchInfo(self, url, archive, series, project):
296
self.assertEqual((archive, series, project),
297
launchpad._get_package_branch_info(url))
299
def test_package_branch_regex(self):
300
self.assertPackageBranchRe(
301
'http://bazaar.launchpad.net/+branch/ubuntu/foo',
302
None, 'ubuntu', None, 'foo')
303
self.assertPackageBranchRe(
304
'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
305
None, 'ubuntu', 'natty/', 'foo')
306
self.assertPackageBranchRe(
307
'sftp://bazaar.launchpad.net/+branch/debian/foo',
308
None, 'debian', None, 'foo')
309
self.assertPackageBranchRe(
310
'http://bazaar.launchpad.net/+branch/debian/sid/foo',
311
None, 'debian', 'sid/', 'foo')
312
self.assertPackageBranchRe(
313
'http://bazaar.launchpad.net/+branch'
314
'/~ubuntu-branches/ubuntu/natty/foo/natty',
315
'~ubuntu-branches/', 'ubuntu', 'natty/', 'foo')
316
self.assertPackageBranchRe(
317
'http://bazaar.launchpad.net/+branch'
318
'/~user/ubuntu/natty/foo/test',
319
'~user/', 'ubuntu', 'natty/', 'foo')
321
def test_package_branch_doesnt_match(self):
322
self.assertNotPackageBranch('http://example.com/ubuntu/foo')
323
self.assertNotPackageBranch(
324
'http://bazaar.launchpad.net/+branch/bzr')
325
self.assertNotPackageBranch(
326
'http://bazaar.launchpad.net/+branch/~bzr-pqm/bzr/bzr.dev')
327
# Not a packaging branch because ~user isn't ~ubuntu-branches
328
self.assertNotPackageBranch(
329
'http://bazaar.launchpad.net/+branch'
330
'/~user/ubuntu/natty/foo/natty')
332
def test__get_package_branch_info(self):
333
self.assertBranchInfo(
334
'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
335
'ubuntu', 'natty', 'foo')
336
self.assertBranchInfo(
337
'bzr+ssh://bazaar.launchpad.net/+branch'
338
'/~ubuntu-branches/ubuntu/natty/foo/natty',
339
'ubuntu', 'natty', 'foo')
340
self.assertBranchInfo(
341
'http://bazaar.launchpad.net/+branch'
342
'/~ubuntu-branches/debian/sid/foo/sid',
343
'debian', 'sid', 'foo')
346
class TestGetMostRecentTag(tests.TestCaseWithMemoryTransport):
348
def make_simple_builder(self):
349
builder = self.make_branch_builder('tip')
350
builder.build_snapshot('A', [], [
351
('add', ('', 'root-id', 'directory', None))])
352
b = builder.get_branch()
353
b.tags.set_tag('tip-1.0', 'A')
354
return builder, b, b.tags.get_tag_dict()
356
def test_get_most_recent_tag_tip(self):
357
builder, b, tag_dict = self.make_simple_builder()
358
self.assertEqual('tip-1.0',
359
lp_api_lite.get_most_recent_tag(tag_dict, b))
361
def test_get_most_recent_tag_older(self):
362
builder, b, tag_dict = self.make_simple_builder()
363
builder.build_snapshot('B', ['A'], [])
364
self.assertEqual('B', b.last_revision())
365
self.assertEqual('tip-1.0',
366
lp_api_lite.get_most_recent_tag(tag_dict, b))
369
class StubLatestPublication(object):
371
def __init__(self, latest):
375
def get_latest_version(self):
380
return 'Ubuntu Natty'
383
class TestReportFreshness(tests.TestCaseWithMemoryTransport):
386
super(TestReportFreshness, self).setUp()
387
builder = self.make_branch_builder('tip')
388
builder.build_snapshot('A', [], [
389
('add', ('', 'root-id', 'directory', None))])
390
self.branch = builder.get_branch()
392
def assertFreshnessReports(self, verbosity, latest_version, content):
393
"""Assert that lp_api_lite.report_freshness reports the given content.
395
:param verbosity: The reporting level
396
:param latest_version: The version reported by StubLatestPublication
397
:param content: The expected content. This should be in DocTest form.
399
orig_log_len = len(self.get_log())
400
lp_api_lite.report_freshness(self.branch, verbosity,
401
StubLatestPublication(latest_version))
402
new_content = self.get_log()[orig_log_len:]
403
# Strip out lines that have LatestPublication.get_* because those are
404
# timing related lines. While interesting to log for now, they aren't
405
# something we want to be testing
406
new_content = new_content.split('\n')
408
if (len(new_content) > 0
409
and 'LatestPublication.get_' in new_content[0]):
410
new_content = new_content[1:]
411
new_content = '\n'.join(new_content)
412
self.assertThat(new_content,
413
DocTestMatches(content,
414
doctest.ELLIPSIS | doctest.REPORT_UDIFF))
416
def test_verbosity_off_skips_check(self):
417
# We force _get_package_branch_info so that we know it would otherwise
418
# try to connect to launcphad
419
self.overrideAttr(launchpad, '_get_package_branch_info',
420
lambda x: ('ubuntu', 'natty', 'bzr'))
421
self.overrideAttr(lp_api_lite, 'LatestPublication',
422
lambda *args: self.fail('Tried to query launchpad'))
423
c = self.branch.get_config()
424
c.set_user_option('launchpad.packaging_verbosity', 'off')
425
orig_log_len = len(self.get_log())
426
launchpad._check_is_up_to_date(self.branch)
427
new_content = self.get_log()[orig_log_len:]
428
self.assertContainsRe(new_content,
429
'not checking memory.*/tip/ because verbosity is turned off')
431
def test_verbosity_off(self):
432
latest_pub = StubLatestPublication('1.0-1ubuntu2')
433
lp_api_lite.report_freshness(self.branch, 'off', latest_pub)
434
self.assertFalse(latest_pub.called)
436
def test_verbosity_all_out_of_date_smoke(self):
437
self.branch.tags.set_tag('1.0-1ubuntu1', 'A')
438
self.assertFreshnessReports('all', '1.0-1ubuntu2',
439
' INFO Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
440
'Packaging branch version: 1.0-1ubuntu1\n'
441
'Packaging branch status: OUT-OF-DATE\n')
444
class Test_GetNewestVersions(tests.TestCaseWithMemoryTransport):
447
super(Test_GetNewestVersions, self).setUp()
448
builder = self.make_branch_builder('tip')
449
builder.build_snapshot('A', [], [
450
('add', ('', 'root-id', 'directory', None))])
451
self.branch = builder.get_branch()
453
def assertLatestVersions(self, latest_branch_version, pub_version):
454
if latest_branch_version is not None:
455
self.branch.tags.set_tag(latest_branch_version, 'A')
456
latest_pub = StubLatestPublication(pub_version)
457
self.assertEqual((pub_version, latest_branch_version),
458
lp_api_lite._get_newest_versions(self.branch, latest_pub))
460
def test_no_tags(self):
461
self.assertLatestVersions(None, '1.0-1ubuntu2')
463
def test_out_of_date(self):
464
self.assertLatestVersions('1.0-1ubuntu1', '1.0-1ubuntu2')
466
def test_up_to_date(self):
467
self.assertLatestVersions('1.0-1ubuntu2', '1.0-1ubuntu2')
469
def test_missing(self):
470
self.assertLatestVersions(None, None)
473
class Test_ReportFreshness(tests.TestCase):
475
def assertReportedFreshness(self, verbosity, latest_ver, branch_latest_ver,
476
content, place='Ubuntu Natty'):
477
"""Assert that lp_api_lite.report_freshness reports the given content.
480
def report_func(value):
481
reported.append(value)
482
lp_api_lite._report_freshness(latest_ver, branch_latest_ver, place,
483
verbosity, report_func)
484
new_content = '\n'.join(reported)
485
self.assertThat(new_content,
486
DocTestMatches(content,
487
doctest.ELLIPSIS | doctest.REPORT_UDIFF))
489
def test_verbosity_minimal_no_tags(self):
490
self.assertReportedFreshness('minimal', '1.0-1ubuntu2', None,
491
'Branch is OUT-OF-DATE, Ubuntu Natty has 1.0-1ubuntu2\n')
493
def test_verbosity_minimal_out_of_date(self):
494
self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu1',
495
'1.0-1ubuntu1 is OUT-OF-DATE,'
496
' Ubuntu Natty has 1.0-1ubuntu2\n')
498
def test_verbosity_minimal_up_to_date(self):
499
self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu2',
502
def test_verbosity_minimal_missing(self):
503
self.assertReportedFreshness('minimal', None, None,
506
def test_verbosity_short_out_of_date(self):
507
self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu1',
508
'1.0-1ubuntu1 is OUT-OF-DATE,'
509
' Ubuntu Natty has 1.0-1ubuntu2\n')
511
def test_verbosity_short_up_to_date(self):
512
self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu2',
513
'1.0-1ubuntu2 is CURRENT in Ubuntu Natty')
515
def test_verbosity_short_missing(self):
516
self.assertReportedFreshness('short', None, None,
517
'Ubuntu Natty is MISSING a version')
519
def test_verbosity_all_no_tags(self):
520
self.assertReportedFreshness('all', '1.0-1ubuntu2', None,
521
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
522
'Packaging branch version: None\n'
523
'Packaging branch status: OUT-OF-DATE\n')
525
def test_verbosity_all_out_of_date(self):
526
self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu1',
527
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
528
'Packaging branch version: 1.0-1ubuntu1\n'
529
'Packaging branch status: OUT-OF-DATE\n')
531
def test_verbosity_all_up_to_date(self):
532
self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu2',
533
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
534
'Packaging branch status: CURRENT\n')
536
def test_verbosity_all_missing(self):
537
self.assertReportedFreshness('all', None, None,
538
'Most recent Ubuntu Natty version: MISSING\n')
540
def test_verbosity_None_is_all(self):
541
self.assertReportedFreshness(None, '1.0-1ubuntu2', '1.0-1ubuntu2',
542
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
543
'Packaging branch status: CURRENT\n')