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 disableJSON(self):
197
orig = lp_api_lite.json
199
lp_api_lite.json = orig
200
self.addCleanup(cleanup)
201
lp_api_lite.json = None
203
def test__get_lp_info_no_json(self):
204
# If we can't parse the json, we don't make the query.
206
latest_pub = self.make_latest_publication()
207
self.assertIs(None, latest_pub._get_lp_info())
209
def test__parse_json_info_no_module(self):
210
# If a json parsing module isn't available, we just return None here.
212
latest_pub = self.make_latest_publication()
213
self.assertIs(None, latest_pub._parse_json_info(_example_response))
215
def test__parse_json_example_response(self):
216
self.requireFeature(JSONParserFeature)
217
latest_pub = self.make_latest_publication()
218
content = latest_pub._parse_json_info(_example_response)
219
self.assertIsNot(None, content)
220
self.assertEqual(2, content['total_size'])
221
entries = content['entries']
222
self.assertEqual(1, len(entries))
224
self.assertEqual('bzr', entry['source_package_name'])
225
self.assertEqual("2.1.4-0ubuntu1", entry["source_package_version"])
227
def test__parse_json_not_json(self):
228
self.requireFeature(JSONParserFeature)
229
latest_pub = self.make_latest_publication()
230
self.assertIs(None, latest_pub._parse_json_info('Not_valid_json'))
232
def test_get_latest_version_no_response(self):
233
latest_pub = self.make_latest_publication()
234
latest_pub._get_lp_info = lambda: None
235
self.assertEqual(None, latest_pub.get_latest_version())
237
def test_get_latest_version_no_json(self):
239
latest_pub = self.make_latest_publication()
240
self.assertEqual(None, latest_pub.get_latest_version())
242
def test_get_latest_version_invalid_json(self):
243
self.requireFeature(JSONParserFeature)
244
latest_pub = self.make_latest_publication()
245
latest_pub._get_lp_info = lambda: "not json"
246
self.assertEqual(None, latest_pub.get_latest_version())
248
def test_get_latest_version_no_versions(self):
249
self.requireFeature(JSONParserFeature)
250
latest_pub = self.make_latest_publication()
251
latest_pub._get_lp_info = lambda: _no_versions_response
252
self.assertEqual(None, latest_pub.get_latest_version())
254
def test_get_latest_version_missing_entries(self):
255
# Launchpad's no-entries response does have an empty entries value.
256
# However, lets test that we handle other failures without tracebacks
257
self.requireFeature(JSONParserFeature)
258
latest_pub = self.make_latest_publication()
259
latest_pub._get_lp_info = lambda: '{}'
260
self.assertEqual(None, latest_pub.get_latest_version())
262
def test_get_latest_version_invalid_entries(self):
263
# Make sure we sanely handle a json response we don't understand
264
self.requireFeature(JSONParserFeature)
265
latest_pub = self.make_latest_publication()
266
latest_pub._get_lp_info = lambda: '{"entries": {"a": 1}}'
267
self.assertEqual(None, latest_pub.get_latest_version())
269
def test_get_latest_version_example(self):
270
self.requireFeature(JSONParserFeature)
271
latest_pub = self.make_latest_publication()
272
latest_pub._get_lp_info = lambda: _example_response
273
self.assertEqual("2.1.4-0ubuntu1", latest_pub.get_latest_version())
275
def DONT_test_get_latest_version_from_launchpad(self):
276
self.requireFeature(JSONParserFeature)
277
latest_pub = self.make_latest_publication()
278
self.assertIsNot(None, latest_pub.get_latest_version())
280
def test_place(self):
281
self.assertPlace('Ubuntu', 'ubuntu', None, 'bzr')
282
self.assertPlace('Ubuntu Natty', 'ubuntu', 'natty', 'bzr')
283
self.assertPlace('Ubuntu Natty Proposed', 'ubuntu', 'natty-proposed',
285
self.assertPlace('Debian', 'debian', None, 'bzr')
286
self.assertPlace('Debian Sid', 'debian', 'sid', 'bzr')
289
class TestIsUpToDate(tests.TestCase):
291
def assertPackageBranchRe(self, url, user, archive, series, project):
292
m = launchpad._package_branch.search(url)
294
self.fail('package_branch regex did not match url: %s' % (url,))
296
(user, archive, series, project),
297
m.group('user', 'archive', 'series', 'project'))
299
def assertNotPackageBranch(self, url):
300
self.assertIs(None, launchpad._get_package_branch_info(url))
302
def assertBranchInfo(self, url, archive, series, project):
303
self.assertEqual((archive, series, project),
304
launchpad._get_package_branch_info(url))
306
def test_package_branch_regex(self):
307
self.assertPackageBranchRe(
308
'http://bazaar.launchpad.net/+branch/ubuntu/foo',
309
None, 'ubuntu', None, 'foo')
310
self.assertPackageBranchRe(
311
'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
312
None, 'ubuntu', 'natty/', 'foo')
313
self.assertPackageBranchRe(
314
'sftp://bazaar.launchpad.net/+branch/debian/foo',
315
None, 'debian', None, 'foo')
316
self.assertPackageBranchRe(
317
'http://bazaar.launchpad.net/+branch/debian/sid/foo',
318
None, 'debian', 'sid/', 'foo')
319
self.assertPackageBranchRe(
320
'http://bazaar.launchpad.net/+branch'
321
'/~ubuntu-branches/ubuntu/natty/foo/natty',
322
'~ubuntu-branches/', 'ubuntu', 'natty/', 'foo')
323
self.assertPackageBranchRe(
324
'http://bazaar.launchpad.net/+branch'
325
'/~user/ubuntu/natty/foo/test',
326
'~user/', 'ubuntu', 'natty/', 'foo')
328
def test_package_branch_doesnt_match(self):
329
self.assertNotPackageBranch('http://example.com/ubuntu/foo')
330
self.assertNotPackageBranch(
331
'http://bazaar.launchpad.net/+branch/bzr')
332
self.assertNotPackageBranch(
333
'http://bazaar.launchpad.net/+branch/~bzr-pqm/bzr/bzr.dev')
334
# Not a packaging branch because ~user isn't ~ubuntu-branches
335
self.assertNotPackageBranch(
336
'http://bazaar.launchpad.net/+branch'
337
'/~user/ubuntu/natty/foo/natty')
338
# Older versions of bzr-svn/hg/git did not set Branch.base until after
339
# they called Branch.__init__().
340
self.assertNotPackageBranch(None)
342
def test__get_package_branch_info(self):
343
self.assertBranchInfo(
344
'bzr+ssh://bazaar.launchpad.net/+branch/ubuntu/natty/foo',
345
'ubuntu', 'natty', 'foo')
346
self.assertBranchInfo(
347
'bzr+ssh://bazaar.launchpad.net/+branch'
348
'/~ubuntu-branches/ubuntu/natty/foo/natty',
349
'ubuntu', 'natty', 'foo')
350
self.assertBranchInfo(
351
'http://bazaar.launchpad.net/+branch'
352
'/~ubuntu-branches/debian/sid/foo/sid',
353
'debian', 'sid', 'foo')
356
class TestGetMostRecentTag(tests.TestCaseWithMemoryTransport):
358
def make_simple_builder(self):
359
builder = self.make_branch_builder('tip')
360
builder.build_snapshot('A', None, [
361
('add', ('', 'root-id', 'directory', None))])
362
b = builder.get_branch()
363
b.tags.set_tag('tip-1.0', 'A')
364
return builder, b, b.tags.get_tag_dict()
366
def test_get_most_recent_tag_tip(self):
367
builder, b, tag_dict = self.make_simple_builder()
368
self.assertEqual('tip-1.0',
369
lp_api_lite.get_most_recent_tag(tag_dict, b))
371
def test_get_most_recent_tag_older(self):
372
builder, b, tag_dict = self.make_simple_builder()
373
builder.build_snapshot('B', ['A'], [])
374
self.assertEqual('B', b.last_revision())
375
self.assertEqual('tip-1.0',
376
lp_api_lite.get_most_recent_tag(tag_dict, b))
379
class StubLatestPublication(object):
381
def __init__(self, latest):
385
def get_latest_version(self):
390
return 'Ubuntu Natty'
393
class TestReportFreshness(tests.TestCaseWithMemoryTransport):
396
super(TestReportFreshness, self).setUp()
397
builder = self.make_branch_builder('tip')
398
builder.build_snapshot('A', None, [
399
('add', ('', 'root-id', 'directory', None))])
400
self.branch = builder.get_branch()
402
def assertFreshnessReports(self, verbosity, latest_version, content):
403
"""Assert that lp_api_lite.report_freshness reports the given content.
405
:param verbosity: The reporting level
406
:param latest_version: The version reported by StubLatestPublication
407
:param content: The expected content. This should be in DocTest form.
409
orig_log_len = len(self.get_log())
410
lp_api_lite.report_freshness(self.branch, verbosity,
411
StubLatestPublication(latest_version))
412
new_content = self.get_log()[orig_log_len:]
413
# Strip out lines that have LatestPublication.get_* because those are
414
# timing related lines. While interesting to log for now, they aren't
415
# something we want to be testing
416
new_content = new_content.split('\n')
418
if (len(new_content) > 0
419
and 'LatestPublication.get_' in new_content[0]):
420
new_content = new_content[1:]
421
new_content = '\n'.join(new_content)
422
self.assertThat(new_content,
423
DocTestMatches(content,
424
doctest.ELLIPSIS | doctest.REPORT_UDIFF))
426
def test_verbosity_off_skips_check(self):
427
# We force _get_package_branch_info so that we know it would otherwise
428
# try to connect to launcphad
429
orig_gpbi = launchpad._get_package_branch_info
430
orig_lp = lp_api_lite.LatestPublication
432
launchpad._get_package_branch_info = orig_gpbi
433
lp_api_lite.LatestPublication = orig_lp
434
self.addCleanup(cleanup)
435
launchpad._get_package_branch_info = lambda x: ('ubuntu', 'natty', 'bzr')
436
lp_api_lite.LatestPublication = lambda *args: self.fail('Tried to query launchpad')
437
c = self.branch.get_config()
438
c.set_user_option('launchpad.packaging_verbosity', 'off')
439
orig_log_len = len(self.get_log())
440
launchpad._check_is_up_to_date(self.branch)
441
new_content = self.get_log()[orig_log_len:]
442
self.assertContainsRe(new_content,
443
'not checking memory.*/tip/ because verbosity is turned off')
445
def test_verbosity_off(self):
446
latest_pub = StubLatestPublication('1.0-1ubuntu2')
447
lp_api_lite.report_freshness(self.branch, 'off', latest_pub)
448
self.assertFalse(latest_pub.called)
450
def test_verbosity_all_out_of_date_smoke(self):
451
self.branch.tags.set_tag('1.0-1ubuntu1', 'A')
452
self.assertFreshnessReports('all', '1.0-1ubuntu2',
453
' INFO Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
454
'Packaging branch version: 1.0-1ubuntu1\n'
455
'Packaging branch status: OUT-OF-DATE\n')
458
class Test_GetNewestVersions(tests.TestCaseWithMemoryTransport):
461
super(Test_GetNewestVersions, self).setUp()
462
builder = self.make_branch_builder('tip')
463
builder.build_snapshot('A', None, [
464
('add', ('', 'root-id', 'directory', None))])
465
self.branch = builder.get_branch()
467
def assertLatestVersions(self, latest_branch_version, pub_version):
468
if latest_branch_version is not None:
469
self.branch.tags.set_tag(latest_branch_version, 'A')
470
latest_pub = StubLatestPublication(pub_version)
471
self.assertEqual((pub_version, latest_branch_version),
472
lp_api_lite._get_newest_versions(self.branch, latest_pub))
474
def test_no_tags(self):
475
self.assertLatestVersions(None, '1.0-1ubuntu2')
477
def test_out_of_date(self):
478
self.assertLatestVersions('1.0-1ubuntu1', '1.0-1ubuntu2')
480
def test_up_to_date(self):
481
self.assertLatestVersions('1.0-1ubuntu2', '1.0-1ubuntu2')
483
def test_missing(self):
484
self.assertLatestVersions(None, None)
487
class Test_ReportFreshness(tests.TestCase):
489
def assertReportedFreshness(self, verbosity, latest_ver, branch_latest_ver,
490
content, place='Ubuntu Natty'):
491
"""Assert that lp_api_lite.report_freshness reports the given content.
494
def report_func(value):
495
reported.append(value)
496
lp_api_lite._report_freshness(latest_ver, branch_latest_ver, place,
497
verbosity, report_func)
498
new_content = '\n'.join(reported)
499
self.assertThat(new_content,
500
DocTestMatches(content,
501
doctest.ELLIPSIS | doctest.REPORT_UDIFF))
503
def test_verbosity_minimal_no_tags(self):
504
self.assertReportedFreshness('minimal', '1.0-1ubuntu2', None,
505
'Branch is OUT-OF-DATE, Ubuntu Natty has 1.0-1ubuntu2\n')
507
def test_verbosity_minimal_out_of_date(self):
508
self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu1',
509
'1.0-1ubuntu1 is OUT-OF-DATE,'
510
' Ubuntu Natty has 1.0-1ubuntu2\n')
512
def test_verbosity_minimal_up_to_date(self):
513
self.assertReportedFreshness('minimal', '1.0-1ubuntu2', '1.0-1ubuntu2',
516
def test_verbosity_minimal_missing(self):
517
self.assertReportedFreshness('minimal', None, None,
520
def test_verbosity_short_out_of_date(self):
521
self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu1',
522
'1.0-1ubuntu1 is OUT-OF-DATE,'
523
' Ubuntu Natty has 1.0-1ubuntu2\n')
525
def test_verbosity_short_up_to_date(self):
526
self.assertReportedFreshness('short', '1.0-1ubuntu2', '1.0-1ubuntu2',
527
'1.0-1ubuntu2 is CURRENT in Ubuntu Natty')
529
def test_verbosity_short_missing(self):
530
self.assertReportedFreshness('short', None, None,
531
'Ubuntu Natty is MISSING a version')
533
def test_verbosity_all_no_tags(self):
534
self.assertReportedFreshness('all', '1.0-1ubuntu2', None,
535
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
536
'Packaging branch version: None\n'
537
'Packaging branch status: OUT-OF-DATE\n')
539
def test_verbosity_all_out_of_date(self):
540
self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu1',
541
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
542
'Packaging branch version: 1.0-1ubuntu1\n'
543
'Packaging branch status: OUT-OF-DATE\n')
545
def test_verbosity_all_up_to_date(self):
546
self.assertReportedFreshness('all', '1.0-1ubuntu2', '1.0-1ubuntu2',
547
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
548
'Packaging branch status: CURRENT\n')
550
def test_verbosity_all_missing(self):
551
self.assertReportedFreshness('all', None, None,
552
'Most recent Ubuntu Natty version: MISSING\n')
554
def test_verbosity_None_is_all(self):
555
self.assertReportedFreshness(None, '1.0-1ubuntu2', '1.0-1ubuntu2',
556
'Most recent Ubuntu Natty version: 1.0-1ubuntu2\n'
557
'Packaging branch status: CURRENT\n')