Merge ~cjwatson/launchpad:testing-print-function into launchpad:master

Proposed by Colin Watson on 2019-10-04
Status: Merged
Approved by: Colin Watson on 2019-10-10
Approved revision: 1724d19e4228a63c404e32d4df2bd1d8e926a01f
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:testing-print-function
Merge into: launchpad:master
Diff against target: 761 lines (+126/-107)
13 files modified
lib/lp/testing/__init__.py (+8/-8)
lib/lp/testing/doc/pagetest-helpers.txt (+35/-35)
lib/lp/testing/doc/sample-data-assertions.txt (+1/-1)
lib/lp/testing/faketransaction.py (+3/-1)
lib/lp/testing/karma.py (+3/-1)
lib/lp/testing/layers.py (+4/-2)
lib/lp/testing/mail_helpers.py (+14/-12)
lib/lp/testing/menu.py (+5/-3)
lib/lp/testing/pages.py (+37/-35)
lib/lp/testing/publication.py (+7/-5)
lib/lp/testing/systemdocs.py (+4/-2)
lib/lp/testing/tests/test_doc.py (+2/-1)
lib/lp/testing/yuixhr.py (+3/-1)
Reviewer Review Type Date Requested Status
Colin Watson Approve on 2019-10-10
Kristian Glass Approve on 2019-10-08
Review via email: mp+373699@code.launchpad.net

Commit message

Convert lp.testing to print_function

I also added unicode_literals in a few places where it could be done
with minimal disruption.

The build_test_suite change is because for doctests we can't just do
"from __future__ import print_function" etc. at the top of the file, and
instead have to insert print_function etc. into their globals when
constructing the corresponding test suite; this is a way of spelling
that without having to incur too much verbiage at every site.

To post a comment you must log in.
Kristian Glass (doismellburning) wrote :

Looks fab, thanks - generally approve but I'd appreciate context on the build_test_suite change please

Colin Watson (cjwatson) wrote :

I think the best context for the build_test_suite change is probably to look at the end of https://code.launchpad.net/~cjwatson/launchpad/code-doctests-future-imports/+merge/345470. Briefly, for doctests we can't just do "from __future__ import print_function" etc. at the top of the file; instead, we have to do a much more awkward construction of inserting print_function etc. into their globals when constructing the corresponding test suite. lp.testing.systemdocs.setUp(future=True) is a way of spelling that without having to incur too much verbiage at every site.

Kristian Glass (doismellburning) wrote :

Great, thank you

What do you think about adding that context to the commit message? I approve the change either way, but I know if I found myself hitting that line in `git blame`, I'd appreciate the additional explanation

review: Approve
Colin Watson (cjwatson) wrote :

Sure - I've amended the commit message.

Colin Watson (cjwatson) wrote :

Self-approving to work around incorrect reviewer on this MP (since fixed).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/testing/__init__.py b/lib/lp/testing/__init__.py
2index 81e0f1f..5f91c86 100644
3--- a/lib/lp/testing/__init__.py
4+++ b/lib/lp/testing/__init__.py
5@@ -1,7 +1,7 @@
6 # Copyright 2009-2018 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9-from __future__ import absolute_import
10+from __future__ import absolute_import, print_function
11
12
13 __metaclass__ = type
14@@ -258,21 +258,21 @@ class FakeTime:
15 calls of advance() or next_now().
16
17 >>> faketime = FakeTime(1000)
18- >>> print faketime.now()
19+ >>> print(faketime.now())
20 1000
21- >>> print faketime.now()
22+ >>> print(faketime.now())
23 1000
24 >>> faketime.advance(10)
25- >>> print faketime.now()
26+ >>> print(faketime.now())
27 1010
28- >>> print faketime.next_now()
29+ >>> print(faketime.next_now())
30 1011
31- >>> print faketime.next_now(100)
32+ >>> print(faketime.next_now(100))
33 1111
34 >>> faketime = FakeTime(1000, 5)
35- >>> print faketime.next_now()
36+ >>> print(faketime.next_now())
37 1005
38- >>> print faketime.next_now()
39+ >>> print(faketime.next_now())
40 1010
41 """
42
43diff --git a/lib/lp/testing/doc/pagetest-helpers.txt b/lib/lp/testing/doc/pagetest-helpers.txt
44index e0bb718..21b6e8f 100644
45--- a/lib/lp/testing/doc/pagetest-helpers.txt
46+++ b/lib/lp/testing/doc/pagetest-helpers.txt
47@@ -30,32 +30,32 @@ one should be use for all anonymous browsing tests.
48 ... return dict(browser.mech_browser.addheaders).get('Authorization','')
49
50 >>> anon_browser = test.globs['anon_browser']
51- >>> getAuthorizationHeader(anon_browser)
52- ''
53+ >>> print(getAuthorizationHeader(anon_browser))
54+ <BLANKLINE>
55
56 A browser with a logged in user without any privileges is available
57 under 'user_browser'. This one should be use all workflows involving
58 logged in users, when it shouldn't require any special privileges.
59
60 >>> user_browser = test.globs['user_browser']
61- >>> getAuthorizationHeader(user_browser)
62- 'Basic no-priv@canonical.com:test'
63+ >>> print(getAuthorizationHeader(user_browser))
64+ Basic no-priv@canonical.com:test
65
66 A browser with a logged in user with administrative privileges is
67 available under 'admin_browser'. This one should be used for testing
68 administrative workflows.
69
70 >>> admin_browser = test.globs['admin_browser']
71- >>> getAuthorizationHeader(admin_browser)
72- 'Basic foo.bar@canonical.com:test'
73+ >>> print(getAuthorizationHeader(admin_browser))
74+ Basic foo.bar@canonical.com:test
75
76 Finally, here is a 'browser' instance that simply contain a pre-
77 initialized Browser instance. It doesn't have any authentication
78 configured. It can be used when you need to configure another user.
79
80 >>> browser = test.globs['browser']
81- >>> getAuthorizationHeader(browser)
82- ''
83+ >>> print(getAuthorizationHeader(browser))
84+ <BLANKLINE>
85
86 All these browser instances are configured with handleErrors set to
87 False. This means that exception are raised instead of returning the
88@@ -111,15 +111,15 @@ This routine will return the tag with the given id:
89 ... </html>
90 ... '''
91
92- >>> print find_tag_by_id(content, 'para-1')
93+ >>> print(find_tag_by_id(content, 'para-1'))
94 <p id="para-1">Paragraph 1</p>
95
96- >>> print find_tag_by_id(content, 'para-2')
97+ >>> print(find_tag_by_id(content, 'para-2'))
98 <p id="para-2">Paragraph <b>2</b></p>
99
100 If an unknown ID is used, None is returned:
101
102- >>> print find_tag_by_id(content, 'para-3')
103+ >>> print(find_tag_by_id(content, 'para-3'))
104 None
105
106 If more than one element has the requested id, raise a DuplicateIdError
107@@ -140,10 +140,10 @@ A BeautifulSoup PageElement can be passed instead of a string so that
108 content can be retrieved without reparsing the entire page.
109
110 >>> parsed_content = find_tag_by_id(content, 'root')
111- >>> print parsed_content.name
112+ >>> print(parsed_content.name)
113 html
114
115- >>> print find_tag_by_id(parsed_content, 'para-1')
116+ >>> print(find_tag_by_id(parsed_content, 'para-1'))
117 <p id="para-1">Paragraph 1</p>
118
119
120@@ -172,18 +172,18 @@ class:
121 ... '''
122
123 >>> for tag in find_tags_by_class(content, 'message'):
124- ... print tag
125+ ... print(tag)
126 <p class="message">Message</p>
127 <p class="error message">Error message</p>
128 <p class="warning message">Warning message</p>
129
130 >>> for tag in find_tags_by_class(content, 'error'):
131- ... print tag
132+ ... print(tag)
133 <p class="error message">Error message</p>
134 <p class="error">Error</p>
135
136 >>> for tag in find_tags_by_class(content, 'warning'):
137- ... print tag
138+ ... print(tag)
139 <p class="warning message">Warning message</p>
140 <p class="warning">
141 Warning (outer)
142@@ -218,7 +218,7 @@ matching Tag object, if one exists:
143 ... </html>
144 ... '''
145
146- >>> print first_tag_by_class(content, 'light')
147+ >>> print(first_tag_by_class(content, 'light'))
148 <p class="light">Error message</p>
149
150 If no tags have the given class, then "None" is returned.
151@@ -235,7 +235,7 @@ If no tags have the given class, then "None" is returned.
152 ... </html>
153 ... '''
154
155- >>> print first_tag_by_class(content, 'light')
156+ >>> print(first_tag_by_class(content, 'light'))
157 None
158
159
160@@ -276,11 +276,11 @@ find a portlet by its title and return it:
161 ... </html>
162 ... '''
163
164- >>> print find_portlet(content, 'Portlet 1')
165+ >>> print(find_portlet(content, 'Portlet 1'))
166 <div...
167 ...Contents of portlet 1...
168
169- >>> print find_portlet(content, 'Portlet 2')
170+ >>> print(find_portlet(content, 'Portlet 2'))
171 <div class="portlet">
172 <h2>Portlet 2</h2>
173 Contents of portlet 2
174@@ -290,14 +290,14 @@ When looking for a portlet to match, any two sequences of whitespace are
175 considered equivalent. Whitespace at the beginning or end of the title
176 is also ignored.
177
178- >>> print find_portlet(
179- ... content, 'Portlet with title broken on multiple lines ')
180+ >>> print(find_portlet(
181+ ... content, 'Portlet with title broken on multiple lines '))
182 <div class="portlet">
183 <h2> Portlet with title...
184
185 If the portlet doesn't exist, then None is returned:
186
187- >>> print find_portlet(content, 'No such portlet')
188+ >>> print(find_portlet(content, 'No such portlet'))
189 None
190
191
192@@ -309,7 +309,7 @@ the main content of the page. The find_main_content() method can be
193 used to do this:
194
195 >>> find_main_content = test.globs['find_main_content']
196- >>> print find_main_content(content)
197+ >>> print(find_main_content(content))
198 <...
199 Main content area
200 ...
201@@ -323,16 +323,16 @@ to the end user, and we don't want necessarily to check how the text is
202 displayed (ie. bold, italics, coloured et al).
203
204 >>> extract_text = test.globs['extract_text']
205- >>> print extract_text(
206- ... '<p>A paragraph with <b>inline</b> <i>style</i>.</p>')
207+ >>> print(extract_text(
208+ ... '<p>A paragraph with <b>inline</b> <i>style</i>.</p>'))
209 A paragraph with inline style.
210
211 The function also takes care of inserting proper white space for block
212 level and other elements introducing a visual separation:
213
214- >>> print extract_text( # doctest: -NORMALIZE_WHITESPACE
215+ >>> print(extract_text( # doctest: -NORMALIZE_WHITESPACE
216 ... '<p>Para 1</p><p>Para 2<br>Line 2</p><ul><li>Item 1</li>'
217- ... '<li>Item 2</li></ul><div>Div 1</div><h1>A heading</h1>')
218+ ... '<li>Item 2</li></ul><div>Div 1</div><h1>A heading</h1>'))
219 Para 1
220 Para 2
221 Line 2
222@@ -344,9 +344,9 @@ level and other elements introducing a visual separation:
223 Of course, the function ignores processing instructions, declaration,
224 comments and render CDATA section has plain text.
225
226- >>> print extract_text(
227+ >>> print(extract_text(
228 ... '<?php echo("Hello world!")?><!-- A comment -->'
229- ... '<?A declaration.><![CDATA[Some << characters >>]]>')
230+ ... '<?A declaration.><![CDATA[Some << characters >>]]>'))
231 Some << characters >>
232
233 The function also does some white space normalization, since formatted
234@@ -358,10 +358,10 @@ single space. Runs of newlines is replaced by one newline. (Note also
235 that non-breaking space entities are also transformed into regular
236 space.)
237
238- >>> print extract_text( # doctest: -NORMALIZE_WHITESPACE
239+ >>> print(extract_text( # doctest: -NORMALIZE_WHITESPACE
240 ... ' <p>Some \t white space <br /></p> '
241 ... '<p>Another&nbsp; &#160; paragraph.</p><p><p>'
242- ... '<p>A final one</p> ')
243+ ... '<p>A final one</p> '))
244 Some white space
245 Another paragraph.
246 A final one
247@@ -369,10 +369,10 @@ space.)
248 The function also knows about the sortkey class used in many tables. The
249 sortkey is not displayed but is used for the javascript table sorting.
250
251- >>> print extract_text(
252+ >>> print(extract_text(
253 ... '<table><tr><td><span class="sortkey">1</span>First</td></tr>'
254 ... '<tr><td><span class="sortkey">2</span>Second</td></tr>'
255- ... '<tr><td><span class="sortkey">3</span>Third</td></tr></table>')
256+ ... '<tr><td><span class="sortkey">3</span>Third</td></tr></table>'))
257 First Second Third
258
259 The extract_text method is often used in conjunction with the other
260@@ -380,7 +380,7 @@ find_xxx helper methods to identify the text to display. Because of
261 this the function also accepts BeautifulSoup instance as a parameter
262 rather than a plain string.
263
264- >>> print extract_text(find_portlet(content, 'Portlet 2'))
265+ >>> print(extract_text(find_portlet(content, 'Portlet 2')))
266 Portlet 2
267 Contents of portlet 2
268
269diff --git a/lib/lp/testing/doc/sample-data-assertions.txt b/lib/lp/testing/doc/sample-data-assertions.txt
270index 7f9548e..d50015c 100644
271--- a/lib/lp/testing/doc/sample-data-assertions.txt
272+++ b/lib/lp/testing/doc/sample-data-assertions.txt
273@@ -29,5 +29,5 @@ specifically referenced in Launchpad tests.
274 This user is supposed to be a member of only one team, the "Simple Team".
275
276 >>> one_membership = personset.getByName('one-membership')
277- >>> for t in one_membership.team_memberships: print t.team.displayname
278+ >>> for t in one_membership.team_memberships: print(t.team.displayname)
279 Simple Team
280diff --git a/lib/lp/testing/faketransaction.py b/lib/lp/testing/faketransaction.py
281index 8bf2386..1c41916 100644
282--- a/lib/lp/testing/faketransaction.py
283+++ b/lib/lp/testing/faketransaction.py
284@@ -3,6 +3,8 @@
285
286 """Fake transaction manager."""
287
288+from __future__ import absolute_import, print_function, unicode_literals
289+
290 __metaclass__ = type
291 __all__ = ['FakeTransaction']
292
293@@ -23,7 +25,7 @@ class FakeTransaction:
294 def _log(self, call):
295 """Print calls that are being made, if desired."""
296 if self.log_calls:
297- print call
298+ print(call)
299
300 def begin(self):
301 """Pretend to begin a transaction. Does not log."""
302diff --git a/lib/lp/testing/karma.py b/lib/lp/testing/karma.py
303index d8b2ea3..c0eaf8a 100644
304--- a/lib/lp/testing/karma.py
305+++ b/lib/lp/testing/karma.py
306@@ -3,6 +3,8 @@
307
308 """Helper functions/classes to be used when testing the karma framework."""
309
310+from __future__ import absolute_import, print_function, unicode_literals
311+
312 __metaclass__ = type
313 __all__ = [
314 'KarmaAssignedEventListener',
315@@ -123,4 +125,4 @@ class KarmaAssignedEventListener(KarmaRecorder):
316 text += " distribution=%s" % karma.distribution.name
317 if self.show_person:
318 text += ", person=%s" % karma.person.name
319- print text
320+ print(text)
321diff --git a/lib/lp/testing/layers.py b/lib/lp/testing/layers.py
322index ffac753..647fbb1 100644
323--- a/lib/lp/testing/layers.py
324+++ b/lib/lp/testing/layers.py
325@@ -18,6 +18,8 @@ of one, forcing us to attempt to make some sort of layer tree.
326 -- StuartBishop 20060619
327 """
328
329+from __future__ import absolute_import, print_function
330+
331 __metaclass__ = type
332 __all__ = [
333 'AppServerLayer',
334@@ -447,9 +449,9 @@ class BaseLayer:
335 # tests that leave threads behind from failing. Its use
336 # should only ever be temporary.
337 if BaseLayer.disable_thread_check:
338- print (
339+ print((
340 "ERROR DISABLED: "
341- "Test left new live threads: %s") % repr(new_threads)
342+ "Test left new live threads: %s") % repr(new_threads))
343 else:
344 BaseLayer.flagTestIsolationFailure(
345 "Test left new live threads: %s" % repr(new_threads))
346diff --git a/lib/lp/testing/mail_helpers.py b/lib/lp/testing/mail_helpers.py
347index e5a27a9..3ac740a 100644
348--- a/lib/lp/testing/mail_helpers.py
349+++ b/lib/lp/testing/mail_helpers.py
350@@ -1,8 +1,10 @@
351 # Copyright 2009-2019 Canonical Ltd. This software is licensed under the
352 # GNU Affero General Public License version 3 (see the file LICENSE).
353
354-"""Helper functions dealing with emails in tests.
355-"""
356+"""Helper functions dealing with emails in tests."""
357+
358+from __future__ import absolute_import, print_function
359+
360 __metaclass__ = type
361
362 import email
363@@ -102,23 +104,23 @@ def print_emails(include_reply_to=False, group_similar=False,
364 distinct_bodies[body] = (message, recipients)
365 for body in sorted(distinct_bodies):
366 message, recipients = distinct_bodies[body]
367- print 'From:', message['From']
368- print 'To:', ", ".join(sorted(recipients))
369+ print('From:', message['From'])
370+ print('To:', ", ".join(sorted(recipients)))
371 if include_reply_to:
372- print 'Reply-To:', message['Reply-To']
373+ print('Reply-To:', message['Reply-To'])
374 rationale_header = 'X-Launchpad-Message-Rationale'
375 if include_rationale and rationale_header in message:
376- print '%s: %s' % (rationale_header, message[rationale_header])
377+ print('%s: %s' % (rationale_header, message[rationale_header]))
378 for_header = 'X-Launchpad-Message-For'
379 if include_for and for_header in message:
380- print '%s: %s' % (for_header, message[for_header])
381+ print('%s: %s' % (for_header, message[for_header]))
382 notification_type_header = 'X-Launchpad-Notification-Type'
383 if include_notification_type and notification_type_header in message:
384- print '%s: %s' % (
385- notification_type_header, message[notification_type_header])
386- print 'Subject:', message['Subject']
387- print body
388- print "-" * 40
389+ print('%s: %s' % (
390+ notification_type_header, message[notification_type_header]))
391+ print('Subject:', message['Subject'])
392+ print(body)
393+ print("-" * 40)
394
395
396 def print_distinct_emails(include_reply_to=False, include_rationale=True,
397diff --git a/lib/lp/testing/menu.py b/lib/lp/testing/menu.py
398index 43ed314..f0f3676 100644
399--- a/lib/lp/testing/menu.py
400+++ b/lib/lp/testing/menu.py
401@@ -3,6 +3,8 @@
402
403 """Helpers for testing menus."""
404
405+from __future__ import absolute_import, print_function
406+
407 __metaclass__ = type
408 __all__ = [
409 'summarise_tal_links',
410@@ -62,14 +64,14 @@ def summarise_tal_links(links):
411 else:
412 link = key
413 if ILink.providedBy(link):
414- print 'link %s' % link.name
415+ print('link %s' % link.name)
416 attributes = ('url', 'enabled', 'menu', 'selected', 'linked')
417 for attrname in attributes:
418 if not safe_hasattr(link, attrname):
419 continue
420- print ' %s:' % attrname, getattr(link, attrname)
421+ print(' %s:' % attrname, getattr(link, attrname))
422 else:
423- print 'attribute %s: %s' % (key, link)
424+ print('attribute %s: %s' % (key, link))
425
426
427 def make_fake_request(url, traversed_objects=None):
428diff --git a/lib/lp/testing/pages.py b/lib/lp/testing/pages.py
429index b73d004..9e1b0c3 100644
430--- a/lib/lp/testing/pages.py
431+++ b/lib/lp/testing/pages.py
432@@ -3,6 +3,8 @@
433
434 """Testing infrastructure for page tests."""
435
436+from __future__ import absolute_import, print_function
437+
438 __metaclass__ = type
439
440 from contextlib import contextmanager
441@@ -216,7 +218,7 @@ def find_tag_by_id(content, id):
442 return elements_with_id[0]
443 else:
444 raise DuplicateIdError(
445- 'Found %d elements with id %r' % (len(elements_with_id), id))
446+ "Found %d elements with id '%s'" % (len(elements_with_id), id))
447
448
449 def first_tag_by_class(content, class_):
450@@ -286,7 +288,7 @@ def get_feedback_messages(content):
451 def print_feedback_messages(content, formatter='minimal'):
452 """Print out the feedback messages."""
453 for message in get_feedback_messages(content):
454- print extract_text(message, formatter=formatter)
455+ print(extract_text(message, formatter=formatter))
456
457
458 def print_table(content, columns=None, skip_rows=None, sep="\t"):
459@@ -307,7 +309,7 @@ def print_table(content, columns=None, skip_rows=None, sep="\t"):
460 if columns is None or col_num in columns:
461 row_content.append(extract_text(item))
462 if len(row_content) > 0:
463- print sep.join(row_content)
464+ print(sep.join(row_content))
465
466
467 def get_radio_button_text_for_field(soup, name):
468@@ -340,7 +342,7 @@ def print_radio_button_field(content, name):
469 """
470 main = BeautifulSoup(content)
471 for field in get_radio_button_text_for_field(main, name):
472- print field
473+ print(field)
474
475
476 def strip_label(label):
477@@ -474,48 +476,48 @@ def parse_relationship_section(content):
478 section = soup.find('ul')
479 whitespace_re = re.compile('\s+')
480 if section is None:
481- print 'EMPTY SECTION'
482+ print('EMPTY SECTION')
483 return
484 for li in section.findAll('li'):
485 if li.a:
486 link = li.a
487 content = whitespace_re.sub(' ', link.string.strip())
488 url = link['href']
489- print 'LINK: "%s" -> %s' % (content, url)
490+ print('LINK: "%s" -> %s' % (content, url))
491 else:
492 content = whitespace_re.sub(' ', li.string.strip())
493- print 'TEXT: "%s"' % content
494+ print('TEXT: "%s"' % content)
495
496
497 def print_action_links(content):
498 """Print action menu urls."""
499 actions = find_tag_by_id(content, 'actions')
500 if actions is None:
501- print "No actions portlet"
502+ print("No actions portlet")
503 return
504 entries = actions.findAll('li')
505 for entry in entries:
506 if entry.a:
507- print '%s: %s' % (entry.a.string, entry.a['href'])
508+ print('%s: %s' % (entry.a.string, entry.a['href']))
509 elif entry.strong:
510- print entry.strong.string
511+ print(entry.strong.string)
512
513
514 def print_navigation_links(content):
515 """Print navigation menu urls."""
516 navigation_links = find_tag_by_id(content, 'navigation-tabs')
517 if navigation_links is None:
518- print "No navigation links"
519+ print("No navigation links")
520 return
521 title = navigation_links.find('label')
522 if title is not None:
523- print '= %s =' % title.string
524+ print('= %s =' % title.string)
525 entries = navigation_links.findAll(['strong', 'a'])
526 for entry in entries:
527 try:
528- print '%s: %s' % (entry.span.string, entry['href'])
529+ print('%s: %s' % (entry.span.string, entry['href']))
530 except KeyError:
531- print entry.span.string
532+ print(entry.span.string)
533
534
535 def print_portlet_links(content, name, base=None):
536@@ -536,15 +538,15 @@ def print_portlet_links(content, name, base=None):
537
538 portlet_contents = find_portlet(content, name)
539 if portlet_contents is None:
540- print "No portlet found with name:", name
541+ print("No portlet found with name:", name)
542 return
543 portlet_links = portlet_contents.findAll('a')
544 if len(portlet_links) == 0:
545- print "No links were found in the portlet."
546+ print("No links were found in the portlet.")
547 return
548 for portlet_link in portlet_links:
549- print '%s: %s' % (portlet_link.string,
550- extract_link_from_tag(portlet_link, base))
551+ print('%s: %s' % (portlet_link.string,
552+ extract_link_from_tag(portlet_link, base)))
553
554
555 def print_submit_buttons(content):
556@@ -555,10 +557,10 @@ def print_submit_buttons(content):
557 buttons = find_main_content(content).findAll(
558 'input', attrs={'class': 'button', 'type': 'submit'})
559 if buttons is None:
560- print "No buttons found"
561+ print("No buttons found")
562 else:
563 for button in buttons:
564- print button['value']
565+ print(button['value'])
566
567
568 def print_comments(page):
569@@ -566,31 +568,31 @@ def print_comments(page):
570 main_content = find_main_content(page)
571 for comment in main_content('div', 'boardCommentBody'):
572 for li_tag in comment('li'):
573- print "Attachment: %s" % li_tag.a.renderContents()
574- print comment.div.renderContents()
575- print "-" * 40
576+ print("Attachment: %s" % li_tag.a.renderContents())
577+ print(comment.div.renderContents())
578+ print("-" * 40)
579
580
581 def print_batch_header(soup):
582 """Print the batch navigator header."""
583 navigation = soup.find('td', {'class': 'batch-navigation-index'})
584- print extract_text(navigation).encode('ASCII', 'backslashreplace')
585+ print(extract_text(navigation).encode('ASCII', 'backslashreplace'))
586
587
588 def print_self_link_of_entries(json_body):
589 """Print the self_link attribute of each entry in the given JSON body."""
590 links = sorted(entry['self_link'] for entry in json_body['entries'])
591 for link in links:
592- print link
593+ print(link)
594
595
596 def print_ppa_packages(contents):
597 packages = find_tags_by_class(contents, 'archive_package_row')
598 for pkg in packages:
599- print extract_text(pkg)
600+ print(extract_text(pkg))
601 empty_section = find_tag_by_id(contents, 'empty-result')
602 if empty_section is not None:
603- print extract_text(empty_section)
604+ print(extract_text(empty_section))
605
606
607 def print_location(contents):
608@@ -614,8 +616,8 @@ def print_location(contents):
609 else:
610 breadcrumbs = ' > '.join(segments)
611
612- print 'Hierarchy:', breadcrumbs
613- print 'Tabs:'
614+ print('Hierarchy:', breadcrumbs)
615+ print('Tabs:')
616 print_location_apps(contents)
617 main_heading = doc.h1
618 if main_heading:
619@@ -623,7 +625,7 @@ def print_location(contents):
620 'us-ascii', 'replace')
621 else:
622 main_heading = '(No main heading)'
623- print "Main heading: %s" % main_heading
624+ print("Main heading: %s" % main_heading)
625
626
627 def print_location_apps(contents):
628@@ -636,9 +638,9 @@ def print_location_apps(contents):
629 else:
630 location_apps = location_apps.findAll('span')
631 if location_apps is None:
632- print "(Application tabs omitted)"
633+ print("(Application tabs omitted)")
634 elif len(location_apps) == 0:
635- print "(No application tabs)"
636+ print("(No application tabs)")
637 else:
638 for tab in location_apps:
639 tab_text = extract_text(tab)
640@@ -652,13 +654,13 @@ def print_location_apps(contents):
641 link = tab.a['href']
642 else:
643 link = 'not linked'
644- print "* %s - %s" % (tab_text, link)
645+ print("* %s - %s" % (tab_text, link))
646
647
648 def print_tag_with_id(contents, id):
649 """A simple helper to print the extracted text of the tag."""
650 tag = find_tag_by_id(contents, id)
651- print extract_text(tag)
652+ print(extract_text(tag))
653
654
655 def print_errors(contents):
656@@ -666,7 +668,7 @@ def print_errors(contents):
657 errors = find_tags_by_class(contents, 'error')
658 error_texts = [extract_text(error) for error in errors]
659 for error in error_texts:
660- print error
661+ print(error)
662
663
664 def setupBrowser(auth=None):
665diff --git a/lib/lp/testing/publication.py b/lib/lp/testing/publication.py
666index 929c643..7f8ee3a 100644
667--- a/lib/lp/testing/publication.py
668+++ b/lib/lp/testing/publication.py
669@@ -3,6 +3,8 @@
670
671 """Helpers for testing out publication related code."""
672
673+from __future__ import absolute_import, print_function
674+
675 __metaclass__ = type
676 __all__ = [
677 'get_request_and_publication',
678@@ -67,15 +69,15 @@ def print_request_and_publication(host='localhost', port=None,
679 request, publication = get_request_and_publication(
680 host, port, method, mime_type,
681 extra_environment=extra_environment)
682- print type(request).__name__.split('.')[-1]
683+ print(type(request).__name__.split('.')[-1])
684 publication_classname = type(publication).__name__.split('.')[-1]
685 if isinstance(publication, ProtocolErrorPublication):
686- print "%s: status=%d" % (
687- publication_classname, publication.status)
688+ print("%s: status=%d" % (
689+ publication_classname, publication.status))
690 for name, value in publication.headers.items():
691- print " %s: %s" % (name, value)
692+ print(" %s: %s" % (name, value))
693 else:
694- print publication_classname
695+ print(publication_classname)
696
697
698 def test_traverse(url):
699diff --git a/lib/lp/testing/systemdocs.py b/lib/lp/testing/systemdocs.py
700index d72d7e7..5a34004 100644
701--- a/lib/lp/testing/systemdocs.py
702+++ b/lib/lp/testing/systemdocs.py
703@@ -3,6 +3,8 @@
704
705 """Infrastructure for setting up doctests."""
706
707+from __future__ import absolute_import, print_function
708+
709 __metaclass__ = type
710 __all__ = [
711 'default_optionflags',
712@@ -85,8 +87,8 @@ class StdoutHandler(Handler):
713 """
714 def emit(self, record):
715 Handler.emit(self, record)
716- print >> sys.stdout, '%s:%s:%s' % (
717- record.levelname, record.name, self.format(record))
718+ print('%s:%s:%s' % (
719+ record.levelname, record.name, self.format(record)))
720
721
722 def LayeredDocFileSuite(paths, id_extensions=None, **kw):
723diff --git a/lib/lp/testing/tests/test_doc.py b/lib/lp/testing/tests/test_doc.py
724index 7ce594c..66783e9 100644
725--- a/lib/lp/testing/tests/test_doc.py
726+++ b/lib/lp/testing/tests/test_doc.py
727@@ -8,10 +8,11 @@ Run the doctests and pagetests.
728 import os
729
730 from lp.services.testing import build_test_suite
731+from lp.testing.systemdocs import setUp
732
733
734 here = os.path.dirname(os.path.realpath(__file__))
735
736
737 def test_suite():
738- return build_test_suite(here)
739+ return build_test_suite(here, setUp=lambda test: setUp(test, future=True))
740diff --git a/lib/lp/testing/yuixhr.py b/lib/lp/testing/yuixhr.py
741index 13a9cd6..8a601e8 100644
742--- a/lib/lp/testing/yuixhr.py
743+++ b/lib/lp/testing/yuixhr.py
744@@ -3,6 +3,8 @@
745
746 """Fixture code for YUITest + XHR integration testing."""
747
748+from __future__ import absolute_import, print_function
749+
750 __metaclass__ = type
751 __all__ = [
752 'login_as_person',
753@@ -146,7 +148,7 @@ class CloseDbResult:
754 yield ''
755 LibrarianLayer.testSetUp()
756 except Exception:
757- print "Hm, serious error when trying to clean up the test."
758+ print("Hm, serious error when trying to clean up the test.")
759 traceback.print_exc()
760 # We're done, so we can yield the body.
761 yield '\n'

Subscribers

People subscribed via source and target branches