comparison planemo/lib/python3.7/site-packages/future/tests/base.py @ 0:d30785e31577 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:18:57 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d30785e31577
1 from __future__ import print_function, absolute_import
2 import os
3 import tempfile
4 import unittest
5 import sys
6 import re
7 import warnings
8 import io
9 from textwrap import dedent
10
11 from future.utils import bind_method, PY26, PY3, PY2, PY27
12 from future.moves.subprocess import check_output, STDOUT, CalledProcessError
13
14 if PY26:
15 import unittest2 as unittest
16
17
18 def reformat_code(code):
19 """
20 Removes any leading \n and dedents.
21 """
22 if code.startswith('\n'):
23 code = code[1:]
24 return dedent(code)
25
26
27 def order_future_lines(code):
28 """
29 Returns the code block with any ``__future__`` import lines sorted, and
30 then any ``future`` import lines sorted, then any ``builtins`` import lines
31 sorted.
32
33 This only sorts the lines within the expected blocks.
34
35 See test_order_future_lines() for an example.
36 """
37
38 # We need .splitlines(keepends=True), which doesn't exist on Py2,
39 # so we use this instead:
40 lines = code.split('\n')
41
42 uufuture_line_numbers = [i for i, line in enumerate(lines)
43 if line.startswith('from __future__ import ')]
44
45 future_line_numbers = [i for i, line in enumerate(lines)
46 if line.startswith('from future')
47 or line.startswith('from past')]
48
49 builtins_line_numbers = [i for i, line in enumerate(lines)
50 if line.startswith('from builtins')]
51
52 assert code.lstrip() == code, ('internal usage error: '
53 'dedent the code before calling order_future_lines()')
54
55 def mymax(numbers):
56 return max(numbers) if len(numbers) > 0 else 0
57
58 def mymin(numbers):
59 return min(numbers) if len(numbers) > 0 else float('inf')
60
61 assert mymax(uufuture_line_numbers) <= mymin(future_line_numbers), \
62 'the __future__ and future imports are out of order'
63
64 # assert mymax(future_line_numbers) <= mymin(builtins_line_numbers), \
65 # 'the future and builtins imports are out of order'
66
67 uul = sorted([lines[i] for i in uufuture_line_numbers])
68 sorted_uufuture_lines = dict(zip(uufuture_line_numbers, uul))
69
70 fl = sorted([lines[i] for i in future_line_numbers])
71 sorted_future_lines = dict(zip(future_line_numbers, fl))
72
73 bl = sorted([lines[i] for i in builtins_line_numbers])
74 sorted_builtins_lines = dict(zip(builtins_line_numbers, bl))
75
76 # Replace the old unsorted "from __future__ import ..." lines with the
77 # new sorted ones:
78 new_lines = []
79 for i in range(len(lines)):
80 if i in uufuture_line_numbers:
81 new_lines.append(sorted_uufuture_lines[i])
82 elif i in future_line_numbers:
83 new_lines.append(sorted_future_lines[i])
84 elif i in builtins_line_numbers:
85 new_lines.append(sorted_builtins_lines[i])
86 else:
87 new_lines.append(lines[i])
88 return '\n'.join(new_lines)
89
90
91 class VerboseCalledProcessError(CalledProcessError):
92 """
93 Like CalledProcessError, but it displays more information (message and
94 script output) for diagnosing test failures etc.
95 """
96 def __init__(self, msg, returncode, cmd, output=None):
97 self.msg = msg
98 self.returncode = returncode
99 self.cmd = cmd
100 self.output = output
101
102 def __str__(self):
103 return ("Command '%s' failed with exit status %d\nMessage: %s\nOutput: %s"
104 % (self.cmd, self.returncode, self.msg, self.output))
105
106 class FuturizeError(VerboseCalledProcessError):
107 pass
108
109 class PasteurizeError(VerboseCalledProcessError):
110 pass
111
112
113 class CodeHandler(unittest.TestCase):
114 """
115 Handy mixin for test classes for writing / reading / futurizing /
116 running .py files in the test suite.
117 """
118 def setUp(self):
119 """
120 The outputs from the various futurize stages should have the
121 following headers:
122 """
123 # After stage1:
124 # TODO: use this form after implementing a fixer to consolidate
125 # __future__ imports into a single line:
126 # self.headers1 = """
127 # from __future__ import absolute_import, division, print_function
128 # """
129 self.headers1 = reformat_code("""
130 from __future__ import absolute_import
131 from __future__ import division
132 from __future__ import print_function
133 """)
134
135 # After stage2 --all-imports:
136 # TODO: use this form after implementing a fixer to consolidate
137 # __future__ imports into a single line:
138 # self.headers2 = """
139 # from __future__ import (absolute_import, division,
140 # print_function, unicode_literals)
141 # from future import standard_library
142 # from future.builtins import *
143 # """
144 self.headers2 = reformat_code("""
145 from __future__ import absolute_import
146 from __future__ import division
147 from __future__ import print_function
148 from __future__ import unicode_literals
149 from future import standard_library
150 standard_library.install_aliases()
151 from builtins import *
152 """)
153 self.interpreters = [sys.executable]
154 self.tempdir = tempfile.mkdtemp() + os.path.sep
155 pypath = os.getenv('PYTHONPATH')
156 if pypath:
157 self.env = {'PYTHONPATH': os.getcwd() + os.pathsep + pypath}
158 else:
159 self.env = {'PYTHONPATH': os.getcwd()}
160
161 def convert(self, code, stages=(1, 2), all_imports=False, from3=False,
162 reformat=True, run=True, conservative=False):
163 """
164 Converts the code block using ``futurize`` and returns the
165 resulting code.
166
167 Passing stages=[1] or stages=[2] passes the flag ``--stage1`` or
168 ``stage2`` to ``futurize``. Passing both stages runs ``futurize``
169 with both stages by default.
170
171 If from3 is False, runs ``futurize``, converting from Python 2 to
172 both 2 and 3. If from3 is True, runs ``pasteurize`` to convert
173 from Python 3 to both 2 and 3.
174
175 Optionally reformats the code block first using the reformat() function.
176
177 If run is True, runs the resulting code under all Python
178 interpreters in self.interpreters.
179 """
180 if reformat:
181 code = reformat_code(code)
182 self._write_test_script(code)
183 self._futurize_test_script(stages=stages, all_imports=all_imports,
184 from3=from3, conservative=conservative)
185 output = self._read_test_script()
186 if run:
187 for interpreter in self.interpreters:
188 _ = self._run_test_script(interpreter=interpreter)
189 return output
190
191 def compare(self, output, expected, ignore_imports=True):
192 """
193 Compares whether the code blocks are equal. If not, raises an
194 exception so the test fails. Ignores any trailing whitespace like
195 blank lines.
196
197 If ignore_imports is True, passes the code blocks into the
198 strip_future_imports method.
199
200 If one code block is a unicode string and the other a
201 byte-string, it assumes the byte-string is encoded as utf-8.
202 """
203 if ignore_imports:
204 output = self.strip_future_imports(output)
205 expected = self.strip_future_imports(expected)
206 if isinstance(output, bytes) and not isinstance(expected, bytes):
207 output = output.decode('utf-8')
208 if isinstance(expected, bytes) and not isinstance(output, bytes):
209 expected = expected.decode('utf-8')
210 self.assertEqual(order_future_lines(output.rstrip()),
211 expected.rstrip())
212
213 def strip_future_imports(self, code):
214 """
215 Strips any of these import lines:
216
217 from __future__ import <anything>
218 from future <anything>
219 from future.<anything>
220 from builtins <anything>
221
222 or any line containing:
223 install_hooks()
224 or:
225 install_aliases()
226
227 Limitation: doesn't handle imports split across multiple lines like
228 this:
229
230 from __future__ import (absolute_import, division, print_function,
231 unicode_literals)
232 """
233 output = []
234 # We need .splitlines(keepends=True), which doesn't exist on Py2,
235 # so we use this instead:
236 for line in code.split('\n'):
237 if not (line.startswith('from __future__ import ')
238 or line.startswith('from future ')
239 or line.startswith('from builtins ')
240 or 'install_hooks()' in line
241 or 'install_aliases()' in line
242 # but don't match "from future_builtins" :)
243 or line.startswith('from future.')):
244 output.append(line)
245 return '\n'.join(output)
246
247 def convert_check(self, before, expected, stages=(1, 2), all_imports=False,
248 ignore_imports=True, from3=False, run=True,
249 conservative=False):
250 """
251 Convenience method that calls convert() and compare().
252
253 Reformats the code blocks automatically using the reformat_code()
254 function.
255
256 If all_imports is passed, we add the appropriate import headers
257 for the stage(s) selected to the ``expected`` code-block, so they
258 needn't appear repeatedly in the test code.
259
260 If ignore_imports is True, ignores the presence of any lines
261 beginning:
262
263 from __future__ import ...
264 from future import ...
265
266 for the purpose of the comparison.
267 """
268 output = self.convert(before, stages=stages, all_imports=all_imports,
269 from3=from3, run=run, conservative=conservative)
270 if all_imports:
271 headers = self.headers2 if 2 in stages else self.headers1
272 else:
273 headers = ''
274
275 reformatted = reformat_code(expected)
276 if headers in reformatted:
277 headers = ''
278
279 self.compare(output, headers + reformatted,
280 ignore_imports=ignore_imports)
281
282 def unchanged(self, code, **kwargs):
283 """
284 Convenience method to ensure the code is unchanged by the
285 futurize process.
286 """
287 self.convert_check(code, code, **kwargs)
288
289 def _write_test_script(self, code, filename='mytestscript.py'):
290 """
291 Dedents the given code (a multiline string) and writes it out to
292 a file in a temporary folder like /tmp/tmpUDCn7x/mytestscript.py.
293 """
294 if isinstance(code, bytes):
295 code = code.decode('utf-8')
296 # Be explicit about encoding the temp file as UTF-8 (issue #63):
297 with io.open(self.tempdir + filename, 'wt', encoding='utf-8') as f:
298 f.write(dedent(code))
299
300 def _read_test_script(self, filename='mytestscript.py'):
301 with io.open(self.tempdir + filename, 'rt', encoding='utf-8') as f:
302 newsource = f.read()
303 return newsource
304
305 def _futurize_test_script(self, filename='mytestscript.py', stages=(1, 2),
306 all_imports=False, from3=False,
307 conservative=False):
308 params = []
309 stages = list(stages)
310 if all_imports:
311 params.append('--all-imports')
312 if from3:
313 script = 'pasteurize.py'
314 else:
315 script = 'futurize.py'
316 if stages == [1]:
317 params.append('--stage1')
318 elif stages == [2]:
319 params.append('--stage2')
320 else:
321 assert stages == [1, 2]
322 if conservative:
323 params.append('--conservative')
324 # No extra params needed
325
326 # Absolute file path:
327 fn = self.tempdir + filename
328 call_args = [sys.executable, script] + params + ['-w', fn]
329 try:
330 output = check_output(call_args, stderr=STDOUT, env=self.env)
331 except CalledProcessError as e:
332 with open(fn) as f:
333 msg = (
334 'Error running the command %s\n'
335 '%s\n'
336 'Contents of file %s:\n'
337 '\n'
338 '%s') % (
339 ' '.join(call_args),
340 'env=%s' % self.env,
341 fn,
342 '----\n%s\n----' % f.read(),
343 )
344 ErrorClass = (FuturizeError if 'futurize' in script else PasteurizeError)
345
346 if not hasattr(e, 'output'):
347 # The attribute CalledProcessError.output doesn't exist on Py2.6
348 e.output = None
349 raise ErrorClass(msg, e.returncode, e.cmd, output=e.output)
350 return output
351
352 def _run_test_script(self, filename='mytestscript.py',
353 interpreter=sys.executable):
354 # Absolute file path:
355 fn = self.tempdir + filename
356 try:
357 output = check_output([interpreter, fn],
358 env=self.env, stderr=STDOUT)
359 except CalledProcessError as e:
360 with open(fn) as f:
361 msg = (
362 'Error running the command %s\n'
363 '%s\n'
364 'Contents of file %s:\n'
365 '\n'
366 '%s') % (
367 ' '.join([interpreter, fn]),
368 'env=%s' % self.env,
369 fn,
370 '----\n%s\n----' % f.read(),
371 )
372 if not hasattr(e, 'output'):
373 # The attribute CalledProcessError.output doesn't exist on Py2.6
374 e.output = None
375 raise VerboseCalledProcessError(msg, e.returncode, e.cmd, output=e.output)
376 return output
377
378
379 # Decorator to skip some tests on Python 2.6 ...
380 skip26 = unittest.skipIf(PY26, "this test is known to fail on Py2.6")
381
382
383 def expectedFailurePY3(func):
384 if not PY3:
385 return func
386 return unittest.expectedFailure(func)
387
388 def expectedFailurePY26(func):
389 if not PY26:
390 return func
391 return unittest.expectedFailure(func)
392
393
394 def expectedFailurePY27(func):
395 if not PY27:
396 return func
397 return unittest.expectedFailure(func)
398
399
400 def expectedFailurePY2(func):
401 if not PY2:
402 return func
403 return unittest.expectedFailure(func)
404
405
406 # Renamed in Py3.3:
407 if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
408 unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
409
410 # From Py3.3:
411 def assertRegex(self, text, expected_regex, msg=None):
412 """Fail the test unless the text matches the regular expression."""
413 if isinstance(expected_regex, (str, unicode)):
414 assert expected_regex, "expected_regex must not be empty."
415 expected_regex = re.compile(expected_regex)
416 if not expected_regex.search(text):
417 msg = msg or "Regex didn't match"
418 msg = '%s: %r not found in %r' % (msg, expected_regex.pattern, text)
419 raise self.failureException(msg)
420
421 if not hasattr(unittest.TestCase, 'assertRegex'):
422 bind_method(unittest.TestCase, 'assertRegex', assertRegex)
423
424 class _AssertRaisesBaseContext(object):
425
426 def __init__(self, expected, test_case, callable_obj=None,
427 expected_regex=None):
428 self.expected = expected
429 self.test_case = test_case
430 if callable_obj is not None:
431 try:
432 self.obj_name = callable_obj.__name__
433 except AttributeError:
434 self.obj_name = str(callable_obj)
435 else:
436 self.obj_name = None
437 if isinstance(expected_regex, (bytes, str)):
438 expected_regex = re.compile(expected_regex)
439 self.expected_regex = expected_regex
440 self.msg = None
441
442 def _raiseFailure(self, standardMsg):
443 msg = self.test_case._formatMessage(self.msg, standardMsg)
444 raise self.test_case.failureException(msg)
445
446 def handle(self, name, callable_obj, args, kwargs):
447 """
448 If callable_obj is None, assertRaises/Warns is being used as a
449 context manager, so check for a 'msg' kwarg and return self.
450 If callable_obj is not None, call it passing args and kwargs.
451 """
452 if callable_obj is None:
453 self.msg = kwargs.pop('msg', None)
454 return self
455 with self:
456 callable_obj(*args, **kwargs)
457
458 class _AssertWarnsContext(_AssertRaisesBaseContext):
459 """A context manager used to implement TestCase.assertWarns* methods."""
460
461 def __enter__(self):
462 # The __warningregistry__'s need to be in a pristine state for tests
463 # to work properly.
464 for v in sys.modules.values():
465 if getattr(v, '__warningregistry__', None):
466 v.__warningregistry__ = {}
467 self.warnings_manager = warnings.catch_warnings(record=True)
468 self.warnings = self.warnings_manager.__enter__()
469 warnings.simplefilter("always", self.expected)
470 return self
471
472 def __exit__(self, exc_type, exc_value, tb):
473 self.warnings_manager.__exit__(exc_type, exc_value, tb)
474 if exc_type is not None:
475 # let unexpected exceptions pass through
476 return
477 try:
478 exc_name = self.expected.__name__
479 except AttributeError:
480 exc_name = str(self.expected)
481 first_matching = None
482 for m in self.warnings:
483 w = m.message
484 if not isinstance(w, self.expected):
485 continue
486 if first_matching is None:
487 first_matching = w
488 if (self.expected_regex is not None and
489 not self.expected_regex.search(str(w))):
490 continue
491 # store warning for later retrieval
492 self.warning = w
493 self.filename = m.filename
494 self.lineno = m.lineno
495 return
496 # Now we simply try to choose a helpful failure message
497 if first_matching is not None:
498 self._raiseFailure('"{}" does not match "{}"'.format(
499 self.expected_regex.pattern, str(first_matching)))
500 if self.obj_name:
501 self._raiseFailure("{} not triggered by {}".format(exc_name,
502 self.obj_name))
503 else:
504 self._raiseFailure("{} not triggered".format(exc_name))
505
506
507 def assertWarns(self, expected_warning, callable_obj=None, *args, **kwargs):
508 """Fail unless a warning of class warnClass is triggered
509 by callable_obj when invoked with arguments args and keyword
510 arguments kwargs. If a different type of warning is
511 triggered, it will not be handled: depending on the other
512 warning filtering rules in effect, it might be silenced, printed
513 out, or raised as an exception.
514
515 If called with callable_obj omitted or None, will return a
516 context object used like this::
517
518 with self.assertWarns(SomeWarning):
519 do_something()
520
521 An optional keyword argument 'msg' can be provided when assertWarns
522 is used as a context object.
523
524 The context manager keeps a reference to the first matching
525 warning as the 'warning' attribute; similarly, the 'filename'
526 and 'lineno' attributes give you information about the line
527 of Python code from which the warning was triggered.
528 This allows you to inspect the warning after the assertion::
529
530 with self.assertWarns(SomeWarning) as cm:
531 do_something()
532 the_warning = cm.warning
533 self.assertEqual(the_warning.some_attribute, 147)
534 """
535 context = _AssertWarnsContext(expected_warning, self, callable_obj)
536 return context.handle('assertWarns', callable_obj, args, kwargs)
537
538 if not hasattr(unittest.TestCase, 'assertWarns'):
539 bind_method(unittest.TestCase, 'assertWarns', assertWarns)