1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
|
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
These plugins modify the behavior of py.test and are meant to be imported
into conftest.py in the root directory.
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import __future__
from ..extern import six
from ..extern.six.moves import filter, range
import ast
import doctest
import datetime
import fnmatch
import imp
import io
import locale
import math
import os
import re
import sys
import types
import argparse
from collections import OrderedDict
from ..config.paths import set_temp_config, set_temp_cache
from .helper import pytest, treat_deprecations_as_exceptions, ignore_warnings
from .helper import enable_deprecations_as_exceptions # pylint: disable=W0611
from .disable_internet import turn_off_internet, turn_on_internet
from .output_checker import AstropyOutputChecker, FIX
from ..utils.argparse import writeable_directory
from ..utils.introspection import resolve_name
try:
import importlib.machinery as importlib_machinery
except ImportError: # Python 2.7
importlib_machinery = None
# these pytest hooks allow us to mark tests and run the marked tests with
# specific command line options.
def pytest_addoption(parser):
# The following means that if --remote-data is not specified, the default
# is 'none', but if it is specified without arguments (--remote-data), it
# defaults to '--remote-data=any'.
parser.addoption("--remote-data", nargs="?", const='any', default='none',
help="run tests with online data")
parser.addoption("--open-files", action="store_true",
help="fail if any test leaves files open")
parser.addoption("--doctest-plus", action="store_true",
help="enable running doctests with additional "
"features not found in the normal doctest "
"plugin")
parser.addoption("--doctest-rst", action="store_true",
help="enable running doctests in .rst documentation")
parser.addoption("--config-dir", nargs='?', type=writeable_directory,
help="specify directory for storing and retrieving the "
"Astropy configuration during tests (default is "
"to use a temporary directory created by the test "
"runner); be aware that using an Astropy config "
"file other than the default can cause some tests "
"to fail unexpectedly")
parser.addoption("--cache-dir", nargs='?', type=writeable_directory,
help="specify directory for storing and retrieving the "
"Astropy cache during tests (default is "
"to use a temporary directory created by the test "
"runner)")
parser.addini("doctest_plus", "enable running doctests with additional "
"features not found in the normal doctest plugin")
parser.addini("doctest_norecursedirs",
"like the norecursedirs option but applies only to doctest "
"collection", type="args", default=())
parser.addini("doctest_rst",
"Run the doctests in the rst documentation",
default=False)
parser.addini("config_dir",
"specify directory for storing and retrieving the "
"Astropy configuration during tests (default is "
"to use a temporary directory created by the test "
"runner); be aware that using an Astropy config "
"file other than the default can cause some tests "
"to fail unexpectedly", default=None)
parser.addini("cache_dir",
"specify directory for storing and retrieving the "
"Astropy cache during tests (default is "
"to use a temporary directory created by the test "
"runner)", default=None)
parser.addini("open_files_ignore",
"when used with the --open-files option, allows "
"specifying names of files that may be ignored when "
"left open between tests--files in this list are matched "
"may be specified by their base name (ignoring their full "
"path) or by absolute path", type="args", default=())
parser.addoption('--repeat', action='store',
help='Number of times to repeat each test')
def pytest_generate_tests(metafunc):
# If the repeat option is set, we add a fixture for the repeat count and
# parametrize the tests over the repeats. Solution adapted from:
# http://stackoverflow.com/q/21764473/180783
if metafunc.config.option.repeat is not None:
count = int(metafunc.config.option.repeat)
metafunc.fixturenames.append('tmp_ct')
metafunc.parametrize('tmp_ct', range(count))
# We monkey-patch in our replacement doctest OutputChecker. Not
# great, but there isn't really an API to replace the checker when
# using doctest.testfile, unfortunately.
doctest.OutputChecker = AstropyOutputChecker
REMOTE_DATA = doctest.register_optionflag('REMOTE_DATA')
def pytest_configure(config):
treat_deprecations_as_exceptions()
config.getini('markers').append(
'remote_data: Run tests that require data from remote servers')
# Monkeypatch to deny access to remote resources unless explicitly told
# otherwise
if config.getoption('remote_data') != 'any':
turn_off_internet(verbose=config.option.verbose,
allow_astropy_data=config.getoption('remote_data') == 'astropy')
doctest_plugin = config.pluginmanager.getplugin('doctest')
if (doctest_plugin is None or config.option.doctestmodules or not
(config.getini('doctest_plus') or config.option.doctest_plus)):
return
# These are the default doctest options we use for everything.
# There shouldn't be any need to manually put them in doctests
# themselves.
opts = (doctest.ELLIPSIS |
doctest.NORMALIZE_WHITESPACE |
FIX)
class DocTestModulePlus(doctest_plugin.DoctestModule):
# pytest 2.4.0 defines "collect". Prior to that, it defined
# "runtest". The "collect" approach is better, because we can
# skip modules altogether that have no doctests. However, we
# need to continue to override "runtest" so that the built-in
# behavior (which doesn't do whitespace normalization or
# handling __doctest_skip__) doesn't happen.
def collect(self):
if self.fspath.basename == "conftest.py":
try:
module = self.config._conftest.importconftest(self.fspath)
except AttributeError: # pytest >= 2.8.0
module = self.config.pluginmanager._importconftest(self.fspath)
else:
try:
module = self.fspath.pyimport()
# Just ignore searching modules that can't be imported when
# collecting doctests
except ImportError:
return
# uses internal doctest module parsing mechanism
finder = DocTestFinderPlus()
runner = doctest.DebugRunner(verbose=False, optionflags=opts,
checker=AstropyOutputChecker())
for test in finder.find(module):
if test.examples: # skip empty doctests
if config.getvalue("remote_data") != 'any':
for example in test.examples:
if example.options.get(REMOTE_DATA):
example.options[doctest.SKIP] = True
yield doctest_plugin.DoctestItem(
test.name, self, runner, test)
class DocTestTextfilePlus(doctest_plugin.DoctestItem, pytest.Module):
def runtest(self):
# satisfy `FixtureRequest` constructor...
self.funcargs = {}
fixture_request = doctest_plugin._setup_fixtures(self)
failed, tot = doctest.testfile(
str(self.fspath), module_relative=False,
optionflags=opts, parser=DocTestParserPlus(),
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
raise_on_error=True, verbose=False, encoding='utf-8')
class DocTestParserPlus(doctest.DocTestParser):
"""
An extension to the builtin DocTestParser that handles the
special directives for skipping tests.
The directives are:
- ``.. doctest-skip::``: Skip the next doctest chunk.
- ``.. doctest-requires:: module1, module2``: Skip the next
doctest chunk if the given modules/packages are not
installed.
- ``.. doctest-skip-all``: Skip all subsequent doctests.
"""
def parse(self, s, name=None):
result = doctest.DocTestParser.parse(self, s, name=name)
# result is a sequence of alternating text chunks and
# doctest.Example objects. We need to look in the text
# chunks for the special directives that help us determine
# whether the following examples should be skipped.
required = []
skip_next = False
skip_all = False
for entry in result:
if isinstance(entry, six.string_types) and entry:
required = []
skip_next = False
lines = entry.strip().splitlines()
if '.. doctest-skip-all' in (x.strip() for x in lines):
skip_all = True
continue
if not len(lines):
continue
last_line = lines[-1]
match = re.match(
r'\.\.\s+doctest-skip\s*::(\s+.*)?', last_line)
if match:
marker = match.group(1)
if (marker is None or
(marker.strip() == 'win32' and
sys.platform == 'win32')):
skip_next = True
continue
match = re.match(
r'\.\.\s+doctest-requires\s*::\s+(.*)',
last_line)
if match:
required = re.split(r'\s*,?\s*', match.group(1))
elif isinstance(entry, doctest.Example):
if (skip_all or skip_next or
not DocTestFinderPlus.check_required_modules(required)):
entry.options[doctest.SKIP] = True
if (config.getvalue('remote_data') != 'any' and
entry.options.get(REMOTE_DATA)):
entry.options[doctest.SKIP] = True
return result
config.pluginmanager.register(
DoctestPlus(DocTestModulePlus, DocTestTextfilePlus,
config.getini('doctest_rst') or config.option.doctest_rst),
'doctestplus')
# Remove the doctest_plugin, or we'll end up testing the .rst
# files twice.
config.pluginmanager.unregister(doctest_plugin)
class DoctestPlus(object):
def __init__(self, doctest_module_item_cls, doctest_textfile_item_cls,
run_rst_doctests):
"""
doctest_module_item_cls should be a class inheriting
`pytest.doctest.DoctestItem` and `pytest.File`. This class handles
running of a single doctest found in a Python module. This is passed
in as an argument because the actual class to be used may not be
available at import time, depending on whether or not the doctest
plugin for py.test is available.
"""
self._doctest_module_item_cls = doctest_module_item_cls
self._doctest_textfile_item_cls = doctest_textfile_item_cls
self._run_rst_doctests = run_rst_doctests
# Directories to ignore when adding doctests
self._ignore_paths = []
def pytest_ignore_collect(self, path, config):
"""Skip paths that match any of the doctest_norecursedirs patterns."""
for pattern in config.getini("doctest_norecursedirs"):
if path.check(fnmatch=pattern):
# Apparently pytest_ignore_collect causes files not to be
# collected by any test runner; for DoctestPlus we only want to
# avoid creating doctest nodes for them
self._ignore_paths.append(path)
break
return False
def pytest_collect_file(self, path, parent):
"""Implements an enhanced version of the doctest module from py.test
(specifically, as enabled by the --doctest-modules option) which
supports skipping all doctests in a specific docstring by way of a
special ``__doctest_skip__`` module-level variable. It can also skip
tests that have special requirements by way of
``__doctest_requires__``.
``__doctest_skip__`` should be a list of functions, classes, or class
methods whose docstrings should be ignored when collecting doctests.
This also supports wildcard patterns. For example, to run doctests in
a class's docstring, but skip all doctests in its modules use, at the
module level::
__doctest_skip__ = ['ClassName.*']
You may also use the string ``'.'`` in ``__doctest_skip__`` to refer
to the module itself, in case its module-level docstring contains
doctests.
``__doctest_requires__`` should be a dictionary mapping wildcard
patterns (in the same format as ``__doctest_skip__``) to a list of one
or more modules that should be *importable* in order for the tests to
run. For example, if some tests require the scipy module to work they
will be skipped unless ``import scipy`` is possible. It is also
possible to use a tuple of wildcard patterns as a key in this dict::
__doctest_requires__ = {('func1', 'func2'): ['scipy']}
"""
for ignore_path in self._ignore_paths:
if ignore_path.common(path) == ignore_path:
return None
if path.ext == '.py':
if path.basename == 'conf.py':
return None
# Don't override the built-in doctest plugin
return self._doctest_module_item_cls(path, parent)
elif self._run_rst_doctests and path.ext == '.rst':
# Ignore generated .rst files
parts = str(path).split(os.path.sep)
if (path.basename.startswith('_') or
any(x.startswith('_') for x in parts) or
any(x == 'api' for x in parts)):
return None
# TODO: Get better names on these items when they are
# displayed in py.test output
return self._doctest_textfile_item_cls(path, parent)
class DocTestFinderPlus(doctest.DocTestFinder):
"""Extension to the default `doctest.DoctestFinder` that supports
``__doctest_skip__`` magic. See `pytest_collect_file` for more details.
"""
# Caches the results of import attempts
_import_cache = {}
@classmethod
def check_required_modules(cls, mods):
for mod in mods:
if mod in cls._import_cache:
if not cls._import_cache[mod]:
return False
try:
imp.find_module(mod)
except ImportError:
cls._import_cache[mod] = False
return False
else:
cls._import_cache[mod] = True
return True
def find(self, obj, name=None, module=None, globs=None,
extraglobs=None):
tests = doctest.DocTestFinder.find(self, obj, name, module, globs,
extraglobs)
if (hasattr(obj, '__doctest_skip__') or
hasattr(obj, '__doctest_requires__')):
if name is None and hasattr(obj, '__name__'):
name = obj.__name__
else:
raise ValueError("DocTestFinder.find: name must be given "
"when obj.__name__ doesn't exist: {!r}".format(
(type(obj),)))
def test_filter(test):
for pat in getattr(obj, '__doctest_skip__', []):
if pat == '*':
return False
elif pat == '.' and test.name == name:
return False
elif fnmatch.fnmatch(test.name, '.'.join((name, pat))):
return False
reqs = getattr(obj, '__doctest_requires__', {})
for pats, mods in six.iteritems(reqs):
if not isinstance(pats, tuple):
pats = (pats,)
for pat in pats:
if not fnmatch.fnmatch(test.name,
'.'.join((name, pat))):
continue
if not self.check_required_modules(mods):
return False
return True
tests = list(filter(test_filter, tests))
return tests
# Open file detection.
#
# This works by calling out to psutil to get the list of open files
# held by the process both before and after the test. If something is
# still open after the test that wasn't open before the test, an
# AssertionError is raised.
#
# This is not thread-safe. We're not currently running our tests
# multi-threaded, but that is worth noting.
def _get_open_file_list():
import psutil
files = []
p = psutil.Process()
if importlib_machinery is not None:
suffixes = tuple(importlib_machinery.all_suffixes())
else:
suffixes = tuple(info[0] for info in imp.get_suffixes())
files = [x.path for x in p.open_files() if not x.path.endswith(suffixes)]
return set(files)
def pytest_runtest_setup(item):
config_dir = item.config.getini('config_dir')
cache_dir = item.config.getini('cache_dir')
# Command-line options can override, however
config_dir = item.config.getoption('config_dir') or config_dir
cache_dir = item.config.getoption('cache_dir') or cache_dir
# We can't really use context managers directly in py.test (although
# py.test 2.7 adds the capability), so this may look a bit hacky
if config_dir:
item.set_temp_config = set_temp_config(config_dir)
item.set_temp_config.__enter__()
if cache_dir:
item.set_temp_cache = set_temp_cache(cache_dir)
item.set_temp_cache.__enter__()
# Store a list of the currently opened files so we can compare
# against them when the test is done.
if item.config.getvalue('open_files'):
item.open_files = _get_open_file_list()
remote_data = item.keywords.get('remote_data')
remote_data_config = item.config.getvalue("remote_data")
if remote_data is not None:
source = remote_data.kwargs.get('source', 'any')
if source not in ('astropy', 'any'):
raise ValueError("source should be 'astropy' or 'any'")
if remote_data_config == 'none':
pytest.skip("need --remote-data option to run")
elif remote_data_config == 'astropy':
if source == 'any':
pytest.skip("need --remote-data option to run")
def pytest_runtest_teardown(item, nextitem):
if hasattr(item, 'set_temp_cache'):
item.set_temp_cache.__exit__()
if hasattr(item, 'set_temp_config'):
item.set_temp_config.__exit__()
# a "skipped" test will not have been called with
# pytest_runtest_setup, so therefore won't have an
# "open_files" member
if (not item.config.getvalue('open_files') or
not hasattr(item, 'open_files')):
return
start_open_files = item.open_files
del item.open_files
open_files = _get_open_file_list()
# This works in tandem with the test_open_file_detection test to
# ensure that it creates one extra open file.
if item.name == 'test_open_file_detection':
assert len(start_open_files) + 1 == len(open_files)
return
not_closed = set()
open_files_ignore = item.config.getini('open_files_ignore')
for filename in open_files:
ignore = False
for ignored in open_files_ignore:
if not os.path.isabs(ignored):
if os.path.basename(filename) == ignored:
ignore = True
break
else:
if filename == ignored:
ignore = True
break
if ignore:
continue
if filename not in start_open_files:
not_closed.add(filename)
if len(not_closed):
msg = ['File(s) not closed:']
for name in not_closed:
msg.append(' {0}'.format(name))
raise AssertionError('\n'.join(msg))
PYTEST_HEADER_MODULES = OrderedDict([('Numpy', 'numpy'),
('Scipy', 'scipy'),
('Matplotlib', 'matplotlib'),
('h5py', 'h5py'),
('Pandas', 'pandas')])
# This always returns with Astropy's version
from .. import __version__
TESTED_VERSIONS = OrderedDict([('Astropy', __version__)])
def pytest_report_header(config):
try:
stdoutencoding = sys.stdout.encoding or 'ascii'
except AttributeError:
stdoutencoding = 'ascii'
if six.PY2:
args = [x.decode('utf-8') for x in config.args]
else:
args = config.args
# TESTED_VERSIONS can contain the affiliated package version, too
if len(TESTED_VERSIONS) > 1:
for pkg, version in TESTED_VERSIONS.items():
if pkg != 'Astropy':
s = "\nRunning tests with {0} version {1}.\n".format(
pkg, version)
else:
s = "\nRunning tests with Astropy version {0}.\n".format(
TESTED_VERSIONS['Astropy'])
# Per https://github.com/astropy/astropy/pull/4204, strip the rootdir from
# each directory argument
if hasattr(config, 'rootdir'):
rootdir = str(config.rootdir)
if not rootdir.endswith(os.sep):
rootdir += os.sep
dirs = [arg[len(rootdir):] if arg.startswith(rootdir) else arg
for arg in args]
else:
dirs = args
s += "Running tests in {0}.\n\n".format(" ".join(dirs))
s += "Date: {0}\n\n".format(datetime.datetime.now().isoformat()[:19])
from platform import platform
plat = platform()
if isinstance(plat, bytes):
plat = plat.decode(stdoutencoding, 'replace')
s += "Platform: {0}\n\n".format(plat)
s += "Executable: {0}\n\n".format(sys.executable)
s += "Full Python Version: \n{0}\n\n".format(sys.version)
s += "encodings: sys: {0}, locale: {1}, filesystem: {2}".format(
sys.getdefaultencoding(),
locale.getpreferredencoding(),
sys.getfilesystemencoding())
if sys.version_info < (3, 3, 0):
s += ", unicode bits: {0}".format(
int(math.log(sys.maxunicode, 2)))
s += '\n'
s += "byteorder: {0}\n".format(sys.byteorder)
s += "float info: dig: {0.dig}, mant_dig: {0.dig}\n\n".format(
sys.float_info)
for module_display, module_name in six.iteritems(PYTEST_HEADER_MODULES):
try:
with ignore_warnings(DeprecationWarning):
module = resolve_name(module_name)
except ImportError:
s += "{0}: not available\n".format(module_display)
else:
try:
version = module.__version__
except AttributeError:
version = 'unknown (no __version__ attribute)'
s += "{0}: {1}\n".format(module_display, version)
special_opts = ["remote_data", "pep8"]
opts = []
for op in special_opts:
if getattr(config.option, op, None):
opts.append(op)
if opts:
s += "Using Astropy options: {0}.\n".format(" ".join(opts))
if six.PY2:
s = s.encode(stdoutencoding, 'replace')
return s
def pytest_pycollect_makemodule(path, parent):
# This is where we set up testing both with and without
# from __future__ import unicode_literals
# On Python 3, just do the regular thing that py.test does
if six.PY2:
return Pair(path, parent)
else:
return pytest.Module(path, parent)
class Pair(pytest.File):
"""
This class treats a given test .py file as a pair of .py files
where one has __future__ unicode_literals and the other does not.
"""
def collect(self):
# First, just do the regular import of the module to make
# sure it's sane and valid. This block is copied directly
# from py.test
try:
mod = self.fspath.pyimport(ensuresyspath=True)
except SyntaxError:
import py
excinfo = py.code.ExceptionInfo()
raise self.CollectError(excinfo.getrepr(style="short"))
except self.fspath.ImportMismatchError:
e = sys.exc_info()[1]
raise self.CollectError(
"import file mismatch:\n"
"imported module {!r} has this __file__ attribute:\n"
" {}\n"
"which is not the same as the test file we want to collect:\n"
" {}\n"
"HINT: remove __pycache__ / .pyc files and/or use a "
"unique basename for your test file modules".format(e.args))
# Now get the file's content.
with io.open(six.text_type(self.fspath), 'rb') as fd:
content = fd.read()
# If the file contains the special marker, only test it both ways.
if b'TEST_UNICODE_LITERALS' in content:
# Return the file in both unicode_literal-enabled and disabled forms
return [
UnicodeLiteralsModule(mod.__name__, content, self.fspath, self),
NoUnicodeLiteralsModule(mod.__name__, content, self.fspath, self)
]
else:
return [pytest.Module(self.fspath, self)]
_RE_FUTURE_IMPORTS = re.compile(br'from __future__ import ((\(.*?\))|([^\n]+))',
flags=re.DOTALL)
class ModifiedModule(pytest.Module):
def __init__(self, mod_name, content, path, parent):
self.mod_name = mod_name
self.content = content
super(ModifiedModule, self).__init__(path, parent)
def _importtestmodule(self):
# We have to remove the __future__ statements *before* parsing
# with compile, otherwise the flags are ignored.
content = re.sub(_RE_FUTURE_IMPORTS, b'\n', self.content)
new_mod = types.ModuleType(self.mod_name)
new_mod.__file__ = six.text_type(self.fspath)
if hasattr(self, '_transform_ast'):
# ast.parse doesn't let us hand-select the __future__
# statements, but built-in compile, with the PyCF_ONLY_AST
# flag does.
tree = compile(
content, six.text_type(self.fspath), 'exec',
self.flags | ast.PyCF_ONLY_AST, True)
tree = self._transform_ast(tree)
# Now that we've transformed the tree, recompile it
code = compile(
tree, six.text_type(self.fspath), 'exec')
else:
# If we don't need to transform the AST, we can skip
# parsing/compiling in two steps
code = compile(
content, six.text_type(self.fspath), 'exec',
self.flags, True)
pwd = os.getcwd()
try:
os.chdir(os.path.dirname(six.text_type(self.fspath)))
six.exec_(code, new_mod.__dict__)
finally:
os.chdir(pwd)
self.config.pluginmanager.consider_module(new_mod)
return new_mod
class UnicodeLiteralsModule(ModifiedModule):
flags = (
__future__.absolute_import.compiler_flag |
__future__.division.compiler_flag |
__future__.print_function.compiler_flag |
__future__.unicode_literals.compiler_flag
)
class NoUnicodeLiteralsModule(ModifiedModule):
flags = (
__future__.absolute_import.compiler_flag |
__future__.division.compiler_flag |
__future__.print_function.compiler_flag
)
def _transform_ast(self, tree):
# When unicode_literals is disabled, we still need to convert any
# byte string containing non-ascii characters into a Unicode string.
# If it doesn't decode as utf-8, we assume it's some other kind
# of byte string and just ultimately leave it alone.
# Note that once we drop support for Python 3.2, we should be
# able to remove this transformation and just put explicit u''
# prefixes in the test source code.
class NonAsciiLiteral(ast.NodeTransformer):
def visit_Str(self, node):
s = node.s
if isinstance(s, bytes):
try:
s.decode('ascii')
except UnicodeDecodeError:
try:
s = s.decode('utf-8')
except UnicodeDecodeError:
pass
else:
return ast.copy_location(ast.Str(s=s), node)
return node
return NonAsciiLiteral().visit(tree)
def pytest_unconfigure():
"""
Cleanup post-testing
"""
# restore internet connectivity (only lost if remote_data=False and
# turn_off_internet previously called)
# this is harmless / does nothing if socket connections were never disabled
turn_on_internet()
def pytest_terminal_summary(terminalreporter):
"""Output a warning to IPython users in case any tests failed."""
try:
get_ipython()
except NameError:
return
if not terminalreporter.stats.get('failed'):
# Only issue the warning when there are actually failures
return
terminalreporter.ensure_newline()
terminalreporter.write_line(
'Some tests are known to fail when run from the IPython prompt; '
'especially, but not limited to tests involving logging and warning '
'handling. Unless you are certain as to the cause of the failure, '
'please check that the failure occurs outside IPython as well. See '
'http://docs.astropy.org/en/stable/known_issues.html#failing-logging-'
'tests-when-running-the-tests-in-ipython for more information.',
yellow=True, bold=True)
|