Merge ~cjwatson/launchpad:testing-print-function into launchpad:master
- Git
- lp:~cjwatson/launchpad
- testing-print-function
- Merge into master
Status: | Merged |
---|---|
Approved by: | Colin Watson |
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) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Kristian Glass (community) | Approve | ||
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.
Description of the change
Kristian Glass (doismellburning) wrote : | # |
Colin Watson (cjwatson) wrote : | # |
I think the best context for the build_test_suite change is probably to look at the end of https:/
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
Colin Watson (cjwatson) wrote : | # |
Sure - I've amended the commit message.
Otto Co-Pilot (otto-copilot) wrote : | # |
Voting criteria not met
https:/
Colin Watson (cjwatson) wrote : | # |
Self-approving to work around incorrect reviewer on this MP (since fixed).
Preview Diff
1 | diff --git a/lib/lp/testing/__init__.py b/lib/lp/testing/__init__.py |
2 | index 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 | |
43 | diff --git a/lib/lp/testing/doc/pagetest-helpers.txt b/lib/lp/testing/doc/pagetest-helpers.txt |
44 | index 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   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 | |
269 | diff --git a/lib/lp/testing/doc/sample-data-assertions.txt b/lib/lp/testing/doc/sample-data-assertions.txt |
270 | index 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 |
280 | diff --git a/lib/lp/testing/faketransaction.py b/lib/lp/testing/faketransaction.py |
281 | index 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.""" |
302 | diff --git a/lib/lp/testing/karma.py b/lib/lp/testing/karma.py |
303 | index 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) |
321 | diff --git a/lib/lp/testing/layers.py b/lib/lp/testing/layers.py |
322 | index 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)) |
346 | diff --git a/lib/lp/testing/mail_helpers.py b/lib/lp/testing/mail_helpers.py |
347 | index 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, |
397 | diff --git a/lib/lp/testing/menu.py b/lib/lp/testing/menu.py |
398 | index 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): |
428 | diff --git a/lib/lp/testing/pages.py b/lib/lp/testing/pages.py |
429 | index 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): |
665 | diff --git a/lib/lp/testing/publication.py b/lib/lp/testing/publication.py |
666 | index 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): |
699 | diff --git a/lib/lp/testing/systemdocs.py b/lib/lp/testing/systemdocs.py |
700 | index 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): |
723 | diff --git a/lib/lp/testing/tests/test_doc.py b/lib/lp/testing/tests/test_doc.py |
724 | index 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)) |
740 | diff --git a/lib/lp/testing/yuixhr.py b/lib/lp/testing/yuixhr.py |
741 | index 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' |
Looks fab, thanks - generally approve but I'd appreciate context on the build_test_suite change please