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.tests import features
25
from bzrlib.plugins import launchpad
26
from bzrlib.plugins.launchpad import lp_api_lite
27
from testtools.matchers import DocTestMatches
30
class _JSONParserFeature(features.Feature):
33
return lp_api_lite.json is not None
35
def feature_name(self):
36
return 'simplejson or json'
39
JSONParserFeature = _JSONParserFeature()
42
_example_response = r"""
46
"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",
49
"package_creator_link": "https://api.launchpad.net/1.0/~maxb",
50
"package_signer_link": "https://api.launchpad.net/1.0/~jelmer",
51
"source_package_name": "bzr",
52
"removal_comment": null,
53
"display_name": "bzr 2.1.4-0ubuntu1 in lucid",
54
"date_made_pending": null,
55
"source_package_version": "2.1.4-0ubuntu1",
56
"date_superseded": null,
57
"http_etag": "\"9ba966152dec474dc0fe1629d0bbce2452efaf3b-5f4c3fbb3eaf26d502db4089777a9b6a0537ffab\"",
58
"self_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary/+sourcepub/1750327",
59
"distro_series_link": "https://api.launchpad.net/1.0/ubuntu/lucid",
60
"component_name": "main",
61
"status": "Published",
64
"date_published": "2011-05-30T06:09:58.653984+00:00",
65
"removed_by_link": null,
66
"section_name": "devel",
67
"resource_type_link": "https://api.launchpad.net/1.0/#source_package_publishing_history",
68
"archive_link": "https://api.launchpad.net/1.0/ubuntu/+archive/primary",
69
"package_maintainer_link": "https://api.launchpad.net/1.0/~ubuntu-devel-discuss-lists",
70
"date_created": "2011-05-30T05:19:12.233621+00:00",
71
"scheduled_deletion_date": null
76
_no_versions_response = '{"total_size": 0, "start": 0, "entries": []}'
79
class TestLatestPublication(tests.TestCase):
81
def make_latest_publication(self, archive='ubuntu', series='natty',
83
return lp_api_lite.LatestPublication(archive, series, project)
85
def assertPlace(self, place, archive, series, project):
86
lp = lp_api_lite.LatestPublication(archive, series, project)
87
self.assertEqual(place, lp.place())
90
latest_pub = self.make_latest_publication()
91
self.assertEqual('ubuntu', latest_pub._archive)
92
self.assertEqual('natty', latest_pub._series)
93
self.assertEqual('bzr', latest_pub._project)
94
self.assertEqual('Release', latest_pub._pocket)
96
def test__archive_URL(self):
97
latest_pub = self.make_latest_publication()
99
'https://api.launchpad.net/1.0/ubuntu/+archive/primary',
100
latest_pub._archive_URL())
102
def test__publication_status_for_ubuntu(self):
103
latest_pub = self.make_latest_publication()
104
self.assertEqual('Published', latest_pub._publication_status())
106
def test__publication_status_for_debian(self):
107
latest_pub = self.make_latest_publication(archive='debian')
108
self.assertEqual('Pending', latest_pub._publication_status())
110
def test_pocket(self):
111
latest_pub = self.make_latest_publication(series='natty-proposed')
112
self.assertEqual('natty', latest_pub._series)
113
self.assertEqual('Proposed', latest_pub._pocket)
115
def test_series_None(self):
116
latest_pub = self.make_latest_publication(series=None)
117
self.assertEqual('ubuntu', latest_pub._archive)
118
self.assertEqual(None, latest_pub._series)
119
self.assertEqual('bzr', latest_pub._project)
120
self.assertEqual('Release', latest_pub._pocket)
122
def test__query_params(self):
123
latest_pub = self.make_latest_publication()
124
self.assertEqual({'ws.op': 'getPublishedSources',
125
'exact_match': 'true',
126
'source_name': '"bzr"',
127
'status': 'Published',
129
'distro_series': '/ubuntu/natty',
131
}, latest_pub._query_params())
133
def test__query_params_no_series(self):
134
latest_pub = self.make_latest_publication(series=None)
135
self.assertEqual({'ws.op': 'getPublishedSources',
136
'exact_match': 'true',
137
'source_name': '"bzr"',
138
'status': 'Published',
141
}, latest_pub._query_params())
143
def test__query_params_pocket(self):
144
latest_pub = self.make_latest_publication(series='natty-proposed')
145
self.assertEqual({'ws.op': 'getPublishedSources',
146
'exact_match': 'true',
147
'source_name': '"bzr"',
148
'status': 'Published',
150
'distro_series': '/ubuntu/natty',
151
'pocket': 'Proposed',
152
}, latest_pub._query_params())
154
def test__query_URL(self):
155
latest_pub = self.make_latest_publication()
156
# we explicitly sort params, so we can be sure this URL matches exactly
158
'https://api.launchpad.net/1.0/ubuntu/+archive/primary'
159
'?distro_series=%2Fubuntu%2Fnatty&exact_match=true'
160
'&pocket=Release&source_name=%22bzr%22&status=Published'
161
'&ws.op=getPublishedSources&ws.size=1',
162
latest_pub._query_URL())
164
def DONT_test__gracefully_handle_failed_rpc_connection(self):
165
# TODO: This test kind of sucks. We intentionally create an arbitrary
166
# port and don't listen to it, because we want the request to fail.
167
# However, it seems to take 1s for it to timeout. Is there a way
168
# to make it fail faster?
169
latest_pub = self.make_latest_publication()
171
s.bind(('127.0.0.1', 0))
172
addr, port = s.getsockname()
173
latest_pub.LP_API_ROOT = 'http://%s:%s/' % (addr, port)
175
self.assertIs(None, latest_pub._get_lp_info())
177
def DONT_test__query_launchpad(self):
178
# TODO: This is a test that we are making a valid request against
179
# launchpad. This seems important, but it is slow, requires net
180
# access, and requires launchpad to be up and running. So for
181
# now, it is commented out for production tests.
182
latest_pub = self.make_latest_publication()
183
json_txt = latest_pub._get_lp_info()
184
self.assertIsNot(None, json_txt)
185
if lp_api_lite.json is None:
186
# We don't have a way to parse the text
188
# The content should be a valid json result
189
content = lp_api_lite.json.loads(json_txt)
190
entries = content['entries'] # It should have an 'entries' field.
191
# ws.size should mean we get 0 or 1, and there should be something
192
self.assertEqual(1, len(entries))
194
self.assertEqual('bzr', entry['source_package_name'])
195
version = entry['source_package_version']
196
self.assertIsNot(None, version)
198
def test__get_lp_info_no_json(self):
199
# If we can't parse the json, we don't make the query.
200
self.overrideAttr(lp_api_lite, 'json', None)
201
latest_pub = self.make_latest_publication()
202
self.assertIs(None, latest_pub._get_lp_info())
204
def test__parse_json_info_no_module(self):
205
# If a json parsing module isn't available, we just return None here.
206
self.overrideAttr(lp_api_lite, 'json', None)
207
latest_pub = self.make_latest_publication()
208
self.assertIs(None, latest_pub._parse_json_info(_example_response))
210
def test__parse_json_example_response(self):
211
self.requireFeature(JSONParserFeature)
212
latest_pub = self.make_latest_publication()
213
content = latest_pub._parse_json_info(_example_response)
214
self.assertIsNot(None, content)
215
self.assertEqual(2, content['total_size'])
216
entries = content['entries']
217
self.assertEqual(1, len(entries))
219
self.assertEqual('bzr', entry['source_package_name'])
220
self.assertEqual("2.1.4-0ubuntu1", entry["source_package_version"])
222
def test__parse_json_not_json(self):
223
self.requireFeature(JSONParserFeature)
224
latest_pub = self.make_latest_publication()
225
self.assertIs(None, latest_pub._parse_json_info('Not_valid_json'))
227
def test_get_latest_version_no_response(self):
228
latest_pub = self.make_latest_publication()
229
latest_pub._get_lp_info = lambda: None
230
self.assertEqual(None, latest_pub.get_latest_version())
232
def test_get_latest_version_no_json(self):
233
self.overrideAttr(lp_api_lite, 'json', None)
234
latest_pub = self.make_latest_publication()
235
self.assertEqual(None, latest_pub.get_latest_version())
237
def test_get_latest_version_invalid_json(self):
238
self.requireFeature(JSONParserFeature)
239
latest_pub = self.make_latest_publication()
240
latest_pub._get_lp_info = lambda: "not json"
241
self.assertEqual(None, latest_pub.get_latest_version())
243
def test_get_latest_version_no_versions(self):
244
self.requireFeature(JSONParserFeature)
245
latest_pub = self.make_latest_publication()
246
latest_pub._get_lp_info = lambda: _no_versions_response
247
self.assertEqual(None, latest_pub.get_latest_version())
249
def test_get_latest_version_missing_entries(self):
250
# Launchpad's no-entries response does have an empty entries value.
251
# However, lets test that we handle other failures without tracebacks
252
self.requireFeature(JSONParserFeature)
253
latest_pub = self.make_latest_publication()
254
latest_pub._get_lp_info = lambda: '{}'
255
self.assertEqual(None, latest_pub.get_latest_version())
257
def test_get_latest_version_invalid_entries(self):
258
# Make sure we sanely handle a json response we don't understand
259
self.requireFeature(JSONParserFeature)
260
latest_pub = self.make_latest_publication()
261
latest_pub._get_lp_info = lambda: '{"entries": {"a": 1}}'
262
self.assertEqual(None, latest_pub.get_latest_version())
264
def test_get_latest_version_example(self):
265
self.requireFeature(JSONParserFeature)
266
latest_pub = self.make_latest_publication()
267
latest_pub._get_lp_info = lambda: _example_response
268
self.assertEqual("2.1.4-0ubuntu1", latest_pub.get_latest_version())
270
def DONT_test_get_latest_version_from_launchpad(self):
271
self.requireFeature(JSONParserFeature)
272
latest_pub = self.make_latest_publication()
273
self.assertIsNot(None, latest_pub.get_latest_version())
275
def test_place(self):
276
self.assertPlace('Ubuntu', 'ubuntu', None, 'bzr')
277
self.assertPlace('Ubuntu Natty', 'ubuntu', 'natty', 'bzr')
278
self.assertPlace('Ubuntu Natty Proposed', 'ubuntu', 'natty-proposed',
280
self.assertPlace('Debian', 'debian', None, 'bzr')
281
self.assertPlace('Debian Sid', 'debian', 'sid', 'bzr')
284
class TestIsUpToDate(tests.TestCase):
286
def assertPackageBranchRe(self, url, user, archive, series, project):
287
m = launchpad._package_branch.search(url)
289
self.fail('package_branch regex did not match url: %s' % (url,))
291
(user, archive, series, project),
292
m.group('user', 'archive', 'series', 'project'))
294
def assertNotPackageBranch(self, url):
295
self.assertIs(None, launchpad._get_package_branch_info(url))
297
def assertBranchInfo(self, url, archive, series, project):
298
self.assertEqual((archive, series, project),
299
launchpad._get_package_branch_info(url))
301
def test_package_branch_regex(self):
302
self.assertPackageBranchRe(
303
'http://bazaar.launchpad.net/+branch/ubuntu/foo',
304
None, 'ubuntu', None, 'foo')
305
self.assertPackageBranchRe(
306
'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
307
None, 'ubuntu', 'natty/', 'foo')
308
self.assertPackageBranchRe(
309
'sftp://bazaar.launchpad.net/+branch/debian/foo',
310
None, 'debian', None, 'foo')
311
self.assertPackageBranchRe(
312
'http://bazaar.launchpad.net/+branch/debian/sid/foo',
313
None, 'debian', 'sid/', 'foo')
314
self.assertPackageBranchRe(
315
'http://bazaar.launchpad.net/+branch'
316
'/~ubuntu-branches/ubuntu/natty/foo/natty',
317
'~ubuntu-branches/', 'ubuntu', 'natty/', 'foo')
318
self.assertPackageBranchRe(
319
'http://bazaar.launchpad.net/+branch'
320
'/~user/ubuntu/natty/foo/test',
321
'~user/', 'ubuntu', 'natty/', 'foo')
323
def test_package_branch_doesnt_match(self):
324
self.assertNotPackageBranch('http://example.com/ubuntu/foo')
325
self.assertNotPackageBranch(
326
'http://bazaar.launchpad.net/+branch/bzr')
327
self.assertNotPackageBranch(
328
'http://bazaar.launchpad.net/+branch/~bzr-pqm/bzr/bzr.dev')
329
# Not a packaging branch because ~user isn't ~ubuntu-branches
330
self.assertNotPackageBranch(
331
'http://bazaar.launchpad.net/+branch'
332
'/~user/ubuntu/natty/foo/natty')
333
# Older versions of bzr-svn/hg/git did not set Branch.base until after
334
# they called Branch.__init__().
335
self.assertNotPackageBranch(None)
337
def test__get_package_branch_info(self):
338
self.assertBranchInfo(
339
'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
340
'ubuntu', 'natty', 'foo')
341
self.assertBranchInfo(
342
'bzr+ssh://bazaar.launchpad.net/+branch'
343
'/~ubuntu-branches/ubuntu/natty/foo/natty',
344
'ubuntu', 'natty', 'foo')
345
self.assertBranchInfo(
346
'http://bazaar.launchpad.net/+branch'
347
'/~ubuntu-branches/debian/sid/foo/sid',
348
'debian', 'sid', 'foo')
351
class TestGetMostRecentTag(tests.TestCaseWithMemoryTransport):
353
def make_simple_builder(self):
354
builder = self.make_branch_builder('tip')
355
builder.build_snapshot('A', [], [
356
('add', ('', 'root-id', 'directory', None))])
357
b = builder.get_branch()
358
b.tags.set_tag('tip-1.0', 'A')
359
return builder, b, b.tags.get_tag_dict()
361
def test_get_most_recent_tag_tip(self):
362
builder, b, tag_dict = self.make_simple_builder()
363
self.assertEqual('tip-1.0',
364
lp_api_lite.get_most_recent_tag(tag_dict, b))
366
def test_get_most_recent_tag_older(self):
367
builder, b, tag_dict = self.make_simple_builder()
368
builder.build_snapshot('B', ['A'], [])
369
self.assertEqual('B', b.last_revision())
370
self.assertEqual('tip-1.0',
371
lp_api_lite.get_most_recent_tag(tag_dict, b))
374
class StubLatestPublication(object):
376
def __init__(self, latest):
380
def get_latest_version(self):
385
return 'Ubuntu Natty'
388
class TestReportFreshness(tests.TestCaseWithMemoryTransport):
391
super(TestReportFreshness, self).setUp()
392
builder = self.make_branch_builder('tip')
393
builder.build_snapshot('A', [], [
394
('add', ('', 'root-id', 'directory', None))])
395
self.branch = builder.get_branch()
397
def assertFreshnessReports(self, verbosity, latest_version, content):
398
"""Assert that lp_api_lite.report_freshness reports the given content.
400
:param verbosity: The reporting level
401
:param latest_version: The version reported by StubLatestPublication
402
:param content: The expected content. This should be in DocTest form.
404
orig_log_len = len(self.get_log())
405
lp_api_lite.report_freshness(self.branch, verbosity,
406
StubLatestPublication(latest_version))
407
new_content = self.get_log()[orig_log_len:]
408
# Strip out lines that have LatestPublication.get_* because those are
409
# timing related lines. While interesting to log for now, they aren't
410
# something we want to be testing
411
new_content = new_content.split('\n')
413
if (len(new_content) > 0
414
and 'LatestPublication.get_' in new_content[0]):
415
new_content = new_content[1:]
416
new_content = '\n'.join(new_content)
417
self.assertThat(new_content,
418
DocTestMatches(content,
419
doctest.ELLIPSIS | doctest.REPORT_UDIFF))
421
def test_verbosity_off_skips_check(self):
422
# We force _get_package_branch_info so that we know it would otherwise
423
# try to connect to launcphad
424
self.overrideAttr(launchpad, '_get_package_branch_info',
425
lambda x: ('ubuntu', 'natty', 'bzr'))
426
self.overrideAttr(lp_api_lite, 'LatestPublication',
427
lambda *args: self.fail('Tried to query launchpad'))
428
c = self.branch.get_config_stack()
429
c.set('launchpad.packaging_verbosity', 'off')
430
orig_log_len = len(self.get_log())
431
launchpad._check_is_up_to_date(self.branch)
432
new_content = self.get_log()[orig_log_len:]
433
self.assertContainsRe(new_content,
434
'not checking memory.*/tip/ because verbosity is turned off')
436
def test_verbosity_off(self):
437
latest_pub = StubLatestPublication('1.0-1ubuntu2')
438
lp_api_lite.report_freshness(self.branch, 'off', latest_pub)
439
self.assertFalse(latest_pub.called)
441
def test_verbosity_all_out_of_date_smoke(self):
442
self.branch.tags.set_tag('1.0-1ubuntu1', 'A')
443
self.assertFreshnessReports('all', '1.0-1ubuntu2',
444
' INFO Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
445
'Packaging branch version: 1.0-1ubuntu1\n'
446
'Packaging branch status: OUT-OF-DATE\n')
449
class Test_GetNewestVersions(tests.TestCaseWithMemoryTransport):
452
super(Test_GetNewestVersions, self).setUp()
453
builder = self.make_branch_builder('tip')
454
builder.build_snapshot('A', [], [
455
('add', ('', 'root-id', 'directory', None))])
456
self.branch = builder.get_branch()
458
def assertLatestVersions(self, latest_branch_version, pub_version):
459
if latest_branch_version is not None:
460
self.branch.tags.set_tag(latest_branch_version, 'A')
461
latest_pub = StubLatestPublication(pub_version)
462
self.assertEqual((pub_version, latest_branch_version),
463
lp_api_lite._get_newest_versions(self.branch, latest_pub))
465
def test_no_tags(self):
466
self.assertLatestVersions(None, '1.0-1ubuntu2')
468
def test_out_of_date(self):
469
self.assertLatestVersions('1.0-1ubuntu1', '1.0-1ubuntu2')
471
def test_up_to_date(self):
472
self.assertLatestVersions('1.0-1ubuntu2', '1.0-1ubuntu2')
474
def test_missing(self):
475
self.assertLatestVersions(None, None)
478
class Test_ReportFreshness(tests.TestCase):
480
def assertReportedFreshness(self, verbosity, latest_ver, branch_latest_ver,
481
content, place='Ubuntu Natty'):
482
"""Assert that lp_api_lite.report_freshness reports the given content.
485
def report_func(value):
486
reported.append(value)
488
lp_api_lite._report_freshness(latest_ver, branch_latest_ver, place,
489
verbosity, report_func)
490
new_content = '\n'.join(reported)
491
self.assertThat(new_content,
492
DocTestMatches(content,
493
doctest.ELLIPSIS | doctest.REPORT_UDIFF))
495
def test_verbosity_minimal_no_tags(self):
496
self.assertReportedFreshness('minimal', '1.0-1ubuntu2', None,
497
'Branch is OUT-OF-DATE, Ubuntu Natty has 1.0-1ubuntu2\n')
499
def test_verbosity_minimal_out_of_date(self):
500
self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu1',
501
'1.0-1ubuntu1 is OUT-OF-DATE,'
502
' Ubuntu Natty has 1.0-1ubuntu2\n')
504
def test_verbosity_minimal_up_to_date(self):
505
self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu2',
508
def test_verbosity_minimal_missing(self):
509
self.assertReportedFreshness('minimal', None, None,
512
def test_verbosity_short_out_of_date(self):
513
self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu1',
514
'1.0-1ubuntu1 is OUT-OF-DATE,'
515
' Ubuntu Natty has 1.0-1ubuntu2\n')
517
def test_verbosity_short_up_to_date(self):
518
self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu2',
519
'1.0-1ubuntu2 is CURRENT in Ubuntu Natty')
521
def test_verbosity_short_missing(self):
522
self.assertReportedFreshness('short', None, None,
523
'Ubuntu Natty is MISSING a version')
525
def test_verbosity_all_no_tags(self):
526
self.assertReportedFreshness('all', '1.0-1ubuntu2', None,
527
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
528
'Packaging branch version: None\n'
529
'Packaging branch status: OUT-OF-DATE\n')
531
def test_verbosity_all_out_of_date(self):
532
self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu1',
533
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
534
'Packaging branch version: 1.0-1ubuntu1\n'
535
'Packaging branch status: OUT-OF-DATE\n')
537
def test_verbosity_all_up_to_date(self):
538
self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu2',
539
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
540
'Packaging branch status: CURRENT\n')
542
def test_verbosity_all_missing(self):
543
self.assertReportedFreshness('all', None, None,
544
'Most recent Ubuntu Natty version: MISSING\n')
546
def test_verbosity_None_is_all(self):
547
self.assertReportedFreshness(None, '1.0-1ubuntu2', '1.0-1ubuntu2',
548
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
549
'Packaging branch status: CURRENT\n')