diff options
author | Mikhail Borisov <borisov.mikhail@gmail.com> | 2022-02-10 16:45:39 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:45:39 +0300 |
commit | a6a92afe03e02795227d2641b49819b687f088f8 (patch) | |
tree | f6984a1d27d5a7ec88a6fdd6e20cd5b7693b6ece /contrib/python/ipython/py2/IPython/testing | |
parent | c6dc8b8bd530985bc4cce0137e9a5de32f1087cb (diff) | |
download | ydb-a6a92afe03e02795227d2641b49819b687f088f8.tar.gz |
Restoring authorship annotation for Mikhail Borisov <borisov.mikhail@gmail.com>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/ipython/py2/IPython/testing')
23 files changed, 3502 insertions, 3502 deletions
diff --git a/contrib/python/ipython/py2/IPython/testing/__init__.py b/contrib/python/ipython/py2/IPython/testing/__init__.py index 165f503169..6011b39f77 100644 --- a/contrib/python/ipython/py2/IPython/testing/__init__.py +++ b/contrib/python/ipython/py2/IPython/testing/__init__.py @@ -1,38 +1,38 @@ -"""Testing support (tools to test IPython itself). -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2009-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Functions -#----------------------------------------------------------------------------- - -# User-level entry point for testing -def test(**kwargs): - """Run the entire IPython test suite. - - Any of the options for run_iptestall() may be passed as keyword arguments. - - For example:: - - IPython.test(testgroups=['lib', 'config', 'utils'], fast=2) - - will run those three sections of the test suite, using two processes. - """ - - # Do the import internally, so that this function doesn't increase total - # import time - from .iptestcontroller import run_iptestall, default_options - options = default_options() - for name, val in kwargs.items(): - setattr(options, name, val) - run_iptestall(options) - -# So nose doesn't try to run this as a test itself and we end up with an -# infinite test loop -test.__test__ = False +"""Testing support (tools to test IPython itself). +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2009-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Functions +#----------------------------------------------------------------------------- + +# User-level entry point for testing +def test(**kwargs): + """Run the entire IPython test suite. + + Any of the options for run_iptestall() may be passed as keyword arguments. + + For example:: + + IPython.test(testgroups=['lib', 'config', 'utils'], fast=2) + + will run those three sections of the test suite, using two processes. + """ + + # Do the import internally, so that this function doesn't increase total + # import time + from .iptestcontroller import run_iptestall, default_options + options = default_options() + for name, val in kwargs.items(): + setattr(options, name, val) + run_iptestall(options) + +# So nose doesn't try to run this as a test itself and we end up with an +# infinite test loop +test.__test__ = False diff --git a/contrib/python/ipython/py2/IPython/testing/__main__.py b/contrib/python/ipython/py2/IPython/testing/__main__.py index 4b0bb8ba9c..179ec6f699 100644 --- a/contrib/python/ipython/py2/IPython/testing/__main__.py +++ b/contrib/python/ipython/py2/IPython/testing/__main__.py @@ -1,3 +1,3 @@ -if __name__ == '__main__': - from IPython.testing import iptestcontroller - iptestcontroller.main() +if __name__ == '__main__': + from IPython.testing import iptestcontroller + iptestcontroller.main() diff --git a/contrib/python/ipython/py2/IPython/testing/decorators.py b/contrib/python/ipython/py2/IPython/testing/decorators.py index c9807ce70e..2fe72f6cae 100644 --- a/contrib/python/ipython/py2/IPython/testing/decorators.py +++ b/contrib/python/ipython/py2/IPython/testing/decorators.py @@ -1,384 +1,384 @@ -# -*- coding: utf-8 -*- -"""Decorators for labeling test objects. - -Decorators that merely return a modified version of the original function -object are straightforward. Decorators that return a new function object need -to use nose.tools.make_decorator(original_function)(decorator) in returning the -decorator, in order to preserve metadata such as function name, setup and -teardown functions and so on - see nose.tools for more information. - -This module provides a set of useful decorators meant to be ready to use in -your own tests. See the bottom of the file for the ready-made ones, and if you -find yourself writing a new one that may be of generic use, add it here. - -Included decorators: - - -Lightweight testing that remains unittest-compatible. - -- An @as_unittest decorator can be used to tag any normal parameter-less - function as a unittest TestCase. Then, both nose and normal unittest will - recognize it as such. This will make it easier to migrate away from Nose if - we ever need/want to while maintaining very lightweight tests. - -NOTE: This file contains IPython-specific decorators. Using the machinery in -IPython.external.decorators, we import either numpy.testing.decorators if numpy is -available, OR use equivalent code in IPython.external._decorators, which -we've copied verbatim from numpy. - -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -import os -import tempfile -import unittest -import warnings - -from decorator import decorator - -# Expose the unittest-driven decorators -from .ipunittest import ipdoctest, ipdocstring - -# Grab the numpy-specific decorators which we keep in a file that we -# occasionally update from upstream: decorators.py is a copy of -# numpy.testing.decorators, we expose all of it here. -from IPython.external.decorators import * - -# For onlyif_cmd_exists decorator +# -*- coding: utf-8 -*- +"""Decorators for labeling test objects. + +Decorators that merely return a modified version of the original function +object are straightforward. Decorators that return a new function object need +to use nose.tools.make_decorator(original_function)(decorator) in returning the +decorator, in order to preserve metadata such as function name, setup and +teardown functions and so on - see nose.tools for more information. + +This module provides a set of useful decorators meant to be ready to use in +your own tests. See the bottom of the file for the ready-made ones, and if you +find yourself writing a new one that may be of generic use, add it here. + +Included decorators: + + +Lightweight testing that remains unittest-compatible. + +- An @as_unittest decorator can be used to tag any normal parameter-less + function as a unittest TestCase. Then, both nose and normal unittest will + recognize it as such. This will make it easier to migrate away from Nose if + we ever need/want to while maintaining very lightweight tests. + +NOTE: This file contains IPython-specific decorators. Using the machinery in +IPython.external.decorators, we import either numpy.testing.decorators if numpy is +available, OR use equivalent code in IPython.external._decorators, which +we've copied verbatim from numpy. + +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import sys +import os +import tempfile +import unittest +import warnings + +from decorator import decorator + +# Expose the unittest-driven decorators +from .ipunittest import ipdoctest, ipdocstring + +# Grab the numpy-specific decorators which we keep in a file that we +# occasionally update from upstream: decorators.py is a copy of +# numpy.testing.decorators, we expose all of it here. +from IPython.external.decorators import * + +# For onlyif_cmd_exists decorator from IPython.utils.py3compat import string_types, which, PY2, PY3, PYPY - -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - -# Simple example of the basic idea -def as_unittest(func): - """Decorator to make a simple function into a normal test via unittest.""" - class Tester(unittest.TestCase): - def test(self): - func() - - Tester.__name__ = func.__name__ - - return Tester - -# Utility functions - + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +# Simple example of the basic idea +def as_unittest(func): + """Decorator to make a simple function into a normal test via unittest.""" + class Tester(unittest.TestCase): + def test(self): + func() + + Tester.__name__ = func.__name__ + + return Tester + +# Utility functions + def apply_wrapper(wrapper, func): - """Apply a wrapper to a function for decoration. - - This mixes Michele Simionato's decorator tool with nose's make_decorator, - to apply a wrapper in a decorator so that all nose attributes, as well as - function signature and other properties, survive the decoration cleanly. - This will ensure that wrapped functions can still be well introspected via - IPython, for example. - """ + """Apply a wrapper to a function for decoration. + + This mixes Michele Simionato's decorator tool with nose's make_decorator, + to apply a wrapper in a decorator so that all nose attributes, as well as + function signature and other properties, survive the decoration cleanly. + This will ensure that wrapped functions can still be well introspected via + IPython, for example. + """ warnings.warn("The function `apply_wrapper` is deprecated since IPython 4.0", DeprecationWarning, stacklevel=2) - import nose.tools - - return decorator(wrapper,nose.tools.make_decorator(func)(wrapper)) - - + import nose.tools + + return decorator(wrapper,nose.tools.make_decorator(func)(wrapper)) + + def make_label_dec(label, ds=None): - """Factory function to create a decorator that applies one or more labels. - - Parameters - ---------- - label : string or sequence - One or more labels that will be applied by the decorator to the functions - it decorates. Labels are attributes of the decorated function with their - value set to True. - - ds : string - An optional docstring for the resulting decorator. If not given, a - default docstring is auto-generated. - - Returns - ------- - A decorator. - - Examples - -------- - - A simple labeling decorator: - - >>> slow = make_label_dec('slow') - >>> slow.__doc__ - "Labels a test as 'slow'." - - And one that uses multiple labels and a custom docstring: - - >>> rare = make_label_dec(['slow','hard'], - ... "Mix labels 'slow' and 'hard' for rare tests.") - >>> rare.__doc__ - "Mix labels 'slow' and 'hard' for rare tests." - - Now, let's test using this one: - >>> @rare - ... def f(): pass - ... - >>> - >>> f.slow - True - >>> f.hard - True - """ - + """Factory function to create a decorator that applies one or more labels. + + Parameters + ---------- + label : string or sequence + One or more labels that will be applied by the decorator to the functions + it decorates. Labels are attributes of the decorated function with their + value set to True. + + ds : string + An optional docstring for the resulting decorator. If not given, a + default docstring is auto-generated. + + Returns + ------- + A decorator. + + Examples + -------- + + A simple labeling decorator: + + >>> slow = make_label_dec('slow') + >>> slow.__doc__ + "Labels a test as 'slow'." + + And one that uses multiple labels and a custom docstring: + + >>> rare = make_label_dec(['slow','hard'], + ... "Mix labels 'slow' and 'hard' for rare tests.") + >>> rare.__doc__ + "Mix labels 'slow' and 'hard' for rare tests." + + Now, let's test using this one: + >>> @rare + ... def f(): pass + ... + >>> + >>> f.slow + True + >>> f.hard + True + """ + warnings.warn("The function `make_label_dec` is deprecated since IPython 4.0", DeprecationWarning, stacklevel=2) - if isinstance(label, string_types): - labels = [label] - else: - labels = label - - # Validate that the given label(s) are OK for use in setattr() by doing a - # dry run on a dummy function. - tmp = lambda : None - for label in labels: - setattr(tmp,label,True) - - # This is the actual decorator we'll return - def decor(f): - for label in labels: - setattr(f,label,True) - return f - - # Apply the user's docstring, or autogenerate a basic one - if ds is None: - ds = "Labels a test as %r." % label - decor.__doc__ = ds - - return decor - - -# Inspired by numpy's skipif, but uses the full apply_wrapper utility to -# preserve function metadata better and allows the skip condition to be a -# callable. -def skipif(skip_condition, msg=None): - ''' Make function raise SkipTest exception if skip_condition is true - - Parameters - ---------- - - skip_condition : bool or callable - Flag to determine whether to skip test. If the condition is a - callable, it is used at runtime to dynamically make the decision. This - is useful for tests that may require costly imports, to delay the cost - until the test suite is actually executed. - msg : string - Message to give on raising a SkipTest exception. - - Returns - ------- - decorator : function - Decorator, which, when applied to a function, causes SkipTest - to be raised when the skip_condition was True, and the function - to be called normally otherwise. - - Notes - ----- - You will see from the code that we had to further decorate the - decorator with the nose.tools.make_decorator function in order to - transmit function name, and various other metadata. - ''' - - def skip_decorator(f): - # Local import to avoid a hard nose dependency and only incur the - # import time overhead at actual test-time. - import nose - - # Allow for both boolean or callable skip conditions. - if callable(skip_condition): - skip_val = skip_condition - else: - skip_val = lambda : skip_condition - - def get_msg(func,msg=None): - """Skip message with information about function being skipped.""" - if msg is None: out = 'Test skipped due to test condition.' - else: out = msg - return "Skipping test: %s. %s" % (func.__name__,out) - - # We need to define *two* skippers because Python doesn't allow both - # return with value and yield inside the same function. - def skipper_func(*args, **kwargs): - """Skipper for normal test functions.""" - if skip_val(): - raise nose.SkipTest(get_msg(f,msg)) - else: - return f(*args, **kwargs) - - def skipper_gen(*args, **kwargs): - """Skipper for test generators.""" - if skip_val(): - raise nose.SkipTest(get_msg(f,msg)) - else: - for x in f(*args, **kwargs): - yield x - - # Choose the right skipper to use when building the actual generator. - if nose.util.isgenerator(f): - skipper = skipper_gen - else: - skipper = skipper_func - - return nose.tools.make_decorator(f)(skipper) - - return skip_decorator - -# A version with the condition set to true, common case just to attach a message -# to a skip decorator -def skip(msg=None): - """Decorator factory - mark a test function for skipping from test suite. - - Parameters - ---------- - msg : string - Optional message to be added. - - Returns - ------- - decorator : function - Decorator, which, when applied to a function, causes SkipTest - to be raised, with the optional message added. - """ - - return skipif(True,msg) - - -def onlyif(condition, msg): - """The reverse from skipif, see skipif for details.""" - - if callable(condition): - skip_condition = lambda : not condition() - else: - skip_condition = lambda : not condition - - return skipif(skip_condition, msg) - -#----------------------------------------------------------------------------- -# Utility functions for decorators -def module_not_available(module): - """Can module be imported? Returns true if module does NOT import. - - This is used to make a decorator to skip tests that require module to be - available, but delay the 'import numpy' to test execution time. - """ - try: - mod = __import__(module) - mod_not_avail = False - except ImportError: - mod_not_avail = True - - return mod_not_avail - - -def decorated_dummy(dec, name): - """Return a dummy function decorated with dec, with the given name. - - Examples - -------- - import IPython.testing.decorators as dec - setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__) - """ + if isinstance(label, string_types): + labels = [label] + else: + labels = label + + # Validate that the given label(s) are OK for use in setattr() by doing a + # dry run on a dummy function. + tmp = lambda : None + for label in labels: + setattr(tmp,label,True) + + # This is the actual decorator we'll return + def decor(f): + for label in labels: + setattr(f,label,True) + return f + + # Apply the user's docstring, or autogenerate a basic one + if ds is None: + ds = "Labels a test as %r." % label + decor.__doc__ = ds + + return decor + + +# Inspired by numpy's skipif, but uses the full apply_wrapper utility to +# preserve function metadata better and allows the skip condition to be a +# callable. +def skipif(skip_condition, msg=None): + ''' Make function raise SkipTest exception if skip_condition is true + + Parameters + ---------- + + skip_condition : bool or callable + Flag to determine whether to skip test. If the condition is a + callable, it is used at runtime to dynamically make the decision. This + is useful for tests that may require costly imports, to delay the cost + until the test suite is actually executed. + msg : string + Message to give on raising a SkipTest exception. + + Returns + ------- + decorator : function + Decorator, which, when applied to a function, causes SkipTest + to be raised when the skip_condition was True, and the function + to be called normally otherwise. + + Notes + ----- + You will see from the code that we had to further decorate the + decorator with the nose.tools.make_decorator function in order to + transmit function name, and various other metadata. + ''' + + def skip_decorator(f): + # Local import to avoid a hard nose dependency and only incur the + # import time overhead at actual test-time. + import nose + + # Allow for both boolean or callable skip conditions. + if callable(skip_condition): + skip_val = skip_condition + else: + skip_val = lambda : skip_condition + + def get_msg(func,msg=None): + """Skip message with information about function being skipped.""" + if msg is None: out = 'Test skipped due to test condition.' + else: out = msg + return "Skipping test: %s. %s" % (func.__name__,out) + + # We need to define *two* skippers because Python doesn't allow both + # return with value and yield inside the same function. + def skipper_func(*args, **kwargs): + """Skipper for normal test functions.""" + if skip_val(): + raise nose.SkipTest(get_msg(f,msg)) + else: + return f(*args, **kwargs) + + def skipper_gen(*args, **kwargs): + """Skipper for test generators.""" + if skip_val(): + raise nose.SkipTest(get_msg(f,msg)) + else: + for x in f(*args, **kwargs): + yield x + + # Choose the right skipper to use when building the actual generator. + if nose.util.isgenerator(f): + skipper = skipper_gen + else: + skipper = skipper_func + + return nose.tools.make_decorator(f)(skipper) + + return skip_decorator + +# A version with the condition set to true, common case just to attach a message +# to a skip decorator +def skip(msg=None): + """Decorator factory - mark a test function for skipping from test suite. + + Parameters + ---------- + msg : string + Optional message to be added. + + Returns + ------- + decorator : function + Decorator, which, when applied to a function, causes SkipTest + to be raised, with the optional message added. + """ + + return skipif(True,msg) + + +def onlyif(condition, msg): + """The reverse from skipif, see skipif for details.""" + + if callable(condition): + skip_condition = lambda : not condition() + else: + skip_condition = lambda : not condition + + return skipif(skip_condition, msg) + +#----------------------------------------------------------------------------- +# Utility functions for decorators +def module_not_available(module): + """Can module be imported? Returns true if module does NOT import. + + This is used to make a decorator to skip tests that require module to be + available, but delay the 'import numpy' to test execution time. + """ + try: + mod = __import__(module) + mod_not_avail = False + except ImportError: + mod_not_avail = True + + return mod_not_avail + + +def decorated_dummy(dec, name): + """Return a dummy function decorated with dec, with the given name. + + Examples + -------- + import IPython.testing.decorators as dec + setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__) + """ warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0", DeprecationWarning, stacklevel=2) - dummy = lambda: None - dummy.__name__ = name - return dec(dummy) - -#----------------------------------------------------------------------------- -# Decorators for public use - -# Decorators to skip certain tests on specific platforms. -skip_win32 = skipif(sys.platform == 'win32', - "This test does not run under Windows") -skip_linux = skipif(sys.platform.startswith('linux'), - "This test does not run under Linux") -skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X") - - -# Decorators to skip tests if not on specific platforms. -skip_if_not_win32 = skipif(sys.platform != 'win32', - "This test only runs under Windows") -skip_if_not_linux = skipif(not sys.platform.startswith('linux'), - "This test only runs under Linux") -skip_if_not_osx = skipif(sys.platform != 'darwin', - "This test only runs under OSX") - - -_x11_skip_cond = (sys.platform not in ('darwin', 'win32') and - os.environ.get('DISPLAY', '') == '') -_x11_skip_msg = "Skipped under *nix when X11/XOrg not available" - -skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg) - -# not a decorator itself, returns a dummy function to be used as setup -def skip_file_no_x11(name): + dummy = lambda: None + dummy.__name__ = name + return dec(dummy) + +#----------------------------------------------------------------------------- +# Decorators for public use + +# Decorators to skip certain tests on specific platforms. +skip_win32 = skipif(sys.platform == 'win32', + "This test does not run under Windows") +skip_linux = skipif(sys.platform.startswith('linux'), + "This test does not run under Linux") +skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X") + + +# Decorators to skip tests if not on specific platforms. +skip_if_not_win32 = skipif(sys.platform != 'win32', + "This test only runs under Windows") +skip_if_not_linux = skipif(not sys.platform.startswith('linux'), + "This test only runs under Linux") +skip_if_not_osx = skipif(sys.platform != 'darwin', + "This test only runs under OSX") + + +_x11_skip_cond = (sys.platform not in ('darwin', 'win32') and + os.environ.get('DISPLAY', '') == '') +_x11_skip_msg = "Skipped under *nix when X11/XOrg not available" + +skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg) + +# not a decorator itself, returns a dummy function to be used as setup +def skip_file_no_x11(name): warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0", DeprecationWarning, stacklevel=2) - return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None - -# Other skip decorators - -# generic skip without module -skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod) - -skipif_not_numpy = skip_without('numpy') - -skipif_not_matplotlib = skip_without('matplotlib') - -skipif_not_sympy = skip_without('sympy') - -skip_known_failure = knownfailureif(True,'This test is known to fail') - -known_failure_py3 = knownfailureif(sys.version_info[0] >= 3, - 'This test is known to fail on Python 3.') - + return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None + +# Other skip decorators + +# generic skip without module +skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod) + +skipif_not_numpy = skip_without('numpy') + +skipif_not_matplotlib = skip_without('matplotlib') + +skipif_not_sympy = skip_without('sympy') + +skip_known_failure = knownfailureif(True,'This test is known to fail') + +known_failure_py3 = knownfailureif(sys.version_info[0] >= 3, + 'This test is known to fail on Python 3.') + cpython2_only = skipif(PY3 or PYPY, "This test only runs on CPython 2.") -py2_only = skipif(PY3, "This test only runs on Python 2.") -py3_only = skipif(PY2, "This test only runs on Python 3.") - -# A null 'decorator', useful to make more readable code that needs to pick -# between different decorators based on OS or other conditions -null_deco = lambda f: f - -# Some tests only run where we can use unicode paths. Note that we can't just -# check os.path.supports_unicode_filenames, which is always False on Linux. -try: - f = tempfile.NamedTemporaryFile(prefix=u"tmp€") -except UnicodeEncodeError: - unicode_paths = False -else: - unicode_paths = True - f.close() - -onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable " - "where we can use unicode in filenames.")) - - -def onlyif_cmds_exist(*commands): - """ - Decorator to skip test when at least one of `commands` is not found. - """ - for cmd in commands: - if not which(cmd): - return skip("This test runs only if command '{0}' " - "is installed".format(cmd)) - return null_deco - -def onlyif_any_cmd_exists(*commands): - """ - Decorator to skip test unless at least one of `commands` is found. - """ +py2_only = skipif(PY3, "This test only runs on Python 2.") +py3_only = skipif(PY2, "This test only runs on Python 3.") + +# A null 'decorator', useful to make more readable code that needs to pick +# between different decorators based on OS or other conditions +null_deco = lambda f: f + +# Some tests only run where we can use unicode paths. Note that we can't just +# check os.path.supports_unicode_filenames, which is always False on Linux. +try: + f = tempfile.NamedTemporaryFile(prefix=u"tmp€") +except UnicodeEncodeError: + unicode_paths = False +else: + unicode_paths = True + f.close() + +onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable " + "where we can use unicode in filenames.")) + + +def onlyif_cmds_exist(*commands): + """ + Decorator to skip test when at least one of `commands` is not found. + """ + for cmd in commands: + if not which(cmd): + return skip("This test runs only if command '{0}' " + "is installed".format(cmd)) + return null_deco + +def onlyif_any_cmd_exists(*commands): + """ + Decorator to skip test unless at least one of `commands` is found. + """ warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0", DeprecationWarning, stacklevel=2) - for cmd in commands: - if which(cmd): - return null_deco - return skip("This test runs only if one of the commands {0} " - "is installed".format(commands)) + for cmd in commands: + if which(cmd): + return null_deco + return skip("This test runs only if one of the commands {0} " + "is installed".format(commands)) diff --git a/contrib/python/ipython/py2/IPython/testing/globalipapp.py b/contrib/python/ipython/py2/IPython/testing/globalipapp.py index 3983393112..a40702cc67 100644 --- a/contrib/python/ipython/py2/IPython/testing/globalipapp.py +++ b/contrib/python/ipython/py2/IPython/testing/globalipapp.py @@ -1,138 +1,138 @@ -"""Global IPython app to support test running. - -We must start our own ipython object and heavily muck with it so that all the -modifications IPython makes to system behavior don't send the doctest machinery -into a fit. This code should be considered a gross hack, but it gets the job -done. -""" -from __future__ import absolute_import -from __future__ import print_function - +"""Global IPython app to support test running. + +We must start our own ipython object and heavily muck with it so that all the +modifications IPython makes to system behavior don't send the doctest machinery +into a fit. This code should be considered a gross hack, but it gets the job +done. +""" +from __future__ import absolute_import +from __future__ import print_function + # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - -import sys + +import sys import warnings - -from . import tools - -from IPython.core import page -from IPython.utils import io -from IPython.utils import py3compat -from IPython.utils.py3compat import builtin_mod -from IPython.terminal.interactiveshell import TerminalInteractiveShell - - -class StreamProxy(io.IOStream): - """Proxy for sys.stdout/err. This will request the stream *at call time* - allowing for nose's Capture plugin's redirection of sys.stdout/err. - - Parameters - ---------- - name : str - The name of the stream. This will be requested anew at every call - """ - - def __init__(self, name): + +from . import tools + +from IPython.core import page +from IPython.utils import io +from IPython.utils import py3compat +from IPython.utils.py3compat import builtin_mod +from IPython.terminal.interactiveshell import TerminalInteractiveShell + + +class StreamProxy(io.IOStream): + """Proxy for sys.stdout/err. This will request the stream *at call time* + allowing for nose's Capture plugin's redirection of sys.stdout/err. + + Parameters + ---------- + name : str + The name of the stream. This will be requested anew at every call + """ + + def __init__(self, name): warnings.warn("StreamProxy is deprecated and unused as of IPython 5", DeprecationWarning, stacklevel=2, ) - self.name=name - - @property - def stream(self): - return getattr(sys, self.name) - - def flush(self): - self.stream.flush() - - -def get_ipython(): - # This will get replaced by the real thing once we start IPython below - return start_ipython() - - -# A couple of methods to override those in the running IPython to interact -# better with doctest (doctest captures on raw stdout, so we need to direct -# various types of output there otherwise it will miss them). - -def xsys(self, cmd): - """Replace the default system call with a capturing one for doctest. - """ - # We use getoutput, but we need to strip it because pexpect captures - # the trailing newline differently from commands.getoutput - print(self.getoutput(cmd, split=False, depth=1).rstrip(), end='', file=sys.stdout) - sys.stdout.flush() - - -def _showtraceback(self, etype, evalue, stb): - """Print the traceback purely on stdout for doctest to capture it. - """ - print(self.InteractiveTB.stb2text(stb), file=sys.stdout) - - -def start_ipython(): - """Start a global IPython shell, which we need for IPython-specific syntax. - """ - global get_ipython - - # This function should only ever run once! - if hasattr(start_ipython, 'already_called'): - return - start_ipython.already_called = True - - # Store certain global objects that IPython modifies - _displayhook = sys.displayhook - _excepthook = sys.excepthook - _main = sys.modules.get('__main__') - - # Create custom argv and namespaces for our IPython to be test-friendly - config = tools.default_config() + self.name=name + + @property + def stream(self): + return getattr(sys, self.name) + + def flush(self): + self.stream.flush() + + +def get_ipython(): + # This will get replaced by the real thing once we start IPython below + return start_ipython() + + +# A couple of methods to override those in the running IPython to interact +# better with doctest (doctest captures on raw stdout, so we need to direct +# various types of output there otherwise it will miss them). + +def xsys(self, cmd): + """Replace the default system call with a capturing one for doctest. + """ + # We use getoutput, but we need to strip it because pexpect captures + # the trailing newline differently from commands.getoutput + print(self.getoutput(cmd, split=False, depth=1).rstrip(), end='', file=sys.stdout) + sys.stdout.flush() + + +def _showtraceback(self, etype, evalue, stb): + """Print the traceback purely on stdout for doctest to capture it. + """ + print(self.InteractiveTB.stb2text(stb), file=sys.stdout) + + +def start_ipython(): + """Start a global IPython shell, which we need for IPython-specific syntax. + """ + global get_ipython + + # This function should only ever run once! + if hasattr(start_ipython, 'already_called'): + return + start_ipython.already_called = True + + # Store certain global objects that IPython modifies + _displayhook = sys.displayhook + _excepthook = sys.excepthook + _main = sys.modules.get('__main__') + + # Create custom argv and namespaces for our IPython to be test-friendly + config = tools.default_config() config.TerminalInteractiveShell.simple_prompt = True - - # Create and initialize our test-friendly IPython instance. - shell = TerminalInteractiveShell.instance(config=config, - ) - - # A few more tweaks needed for playing nicely with doctests... - - # remove history file - shell.tempfiles.append(config.HistoryManager.hist_file) - - # These traps are normally only active for interactive use, set them - # permanently since we'll be mocking interactive sessions. - shell.builtin_trap.activate() - - # Modify the IPython system call with one that uses getoutput, so that we - # can capture subcommands and print them to Python's stdout, otherwise the - # doctest machinery would miss them. - shell.system = py3compat.MethodType(xsys, shell) - - shell._showtraceback = py3compat.MethodType(_showtraceback, shell) - - # IPython is ready, now clean up some global state... - - # Deactivate the various python system hooks added by ipython for - # interactive convenience so we don't confuse the doctest system - sys.modules['__main__'] = _main - sys.displayhook = _displayhook - sys.excepthook = _excepthook - - # So that ipython magics and aliases can be doctested (they work by making - # a call into a global _ip object). Also make the top-level get_ipython - # now return this without recursively calling here again. - _ip = shell - get_ipython = _ip.get_ipython - builtin_mod._ip = _ip - builtin_mod.get_ipython = get_ipython - - # Override paging, so we don't require user interaction during the tests. - def nopage(strng, start=0, screen_lines=0, pager_cmd=None): + + # Create and initialize our test-friendly IPython instance. + shell = TerminalInteractiveShell.instance(config=config, + ) + + # A few more tweaks needed for playing nicely with doctests... + + # remove history file + shell.tempfiles.append(config.HistoryManager.hist_file) + + # These traps are normally only active for interactive use, set them + # permanently since we'll be mocking interactive sessions. + shell.builtin_trap.activate() + + # Modify the IPython system call with one that uses getoutput, so that we + # can capture subcommands and print them to Python's stdout, otherwise the + # doctest machinery would miss them. + shell.system = py3compat.MethodType(xsys, shell) + + shell._showtraceback = py3compat.MethodType(_showtraceback, shell) + + # IPython is ready, now clean up some global state... + + # Deactivate the various python system hooks added by ipython for + # interactive convenience so we don't confuse the doctest system + sys.modules['__main__'] = _main + sys.displayhook = _displayhook + sys.excepthook = _excepthook + + # So that ipython magics and aliases can be doctested (they work by making + # a call into a global _ip object). Also make the top-level get_ipython + # now return this without recursively calling here again. + _ip = shell + get_ipython = _ip.get_ipython + builtin_mod._ip = _ip + builtin_mod.get_ipython = get_ipython + + # Override paging, so we don't require user interaction during the tests. + def nopage(strng, start=0, screen_lines=0, pager_cmd=None): if isinstance(strng, dict): strng = strng.get('text/plain', '') - print(strng) - - page.orig_page = page.pager_page - page.pager_page = nopage - - return _ip + print(strng) + + page.orig_page = page.pager_page + page.pager_page = nopage + + return _ip diff --git a/contrib/python/ipython/py2/IPython/testing/iptest.py b/contrib/python/ipython/py2/IPython/testing/iptest.py index 4018264125..9a5a3d6f58 100644 --- a/contrib/python/ipython/py2/IPython/testing/iptest.py +++ b/contrib/python/ipython/py2/IPython/testing/iptest.py @@ -1,55 +1,55 @@ -# -*- coding: utf-8 -*- -"""IPython Test Suite Runner. - -This module provides a main entry point to a user script to test IPython -itself from the command line. There are two ways of running this script: - -1. With the syntax `iptest all`. This runs our entire test suite by - calling this script (with different arguments) recursively. This - causes modules and package to be tested in different processes, using nose - or trial where appropriate. -2. With the regular nose syntax, like `iptest -vvs IPython`. In this form - the script simply calls nose, but with special command line flags and - plugins loaded. - -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import print_function - -import glob -from io import BytesIO -import os -import os.path as path -import sys -from threading import Thread, Lock, Event -import warnings - -import nose.plugins.builtin -from nose.plugins.xunit import Xunit -from nose import SkipTest -from nose.core import TestProgram -from nose.plugins import Plugin -from nose.util import safe_str - -from IPython import version_info -from IPython.utils.py3compat import bytes_to_str -from IPython.utils.importstring import import_item -from IPython.testing.plugin.ipdoctest import IPythonDoctest -from IPython.external.decorators import KnownFailure, knownfailureif - -pjoin = path.join - - -# Enable printing all warnings raise by IPython's modules +# -*- coding: utf-8 -*- +"""IPython Test Suite Runner. + +This module provides a main entry point to a user script to test IPython +itself from the command line. There are two ways of running this script: + +1. With the syntax `iptest all`. This runs our entire test suite by + calling this script (with different arguments) recursively. This + causes modules and package to be tested in different processes, using nose + or trial where appropriate. +2. With the regular nose syntax, like `iptest -vvs IPython`. In this form + the script simply calls nose, but with special command line flags and + plugins loaded. + +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import glob +from io import BytesIO +import os +import os.path as path +import sys +from threading import Thread, Lock, Event +import warnings + +import nose.plugins.builtin +from nose.plugins.xunit import Xunit +from nose import SkipTest +from nose.core import TestProgram +from nose.plugins import Plugin +from nose.util import safe_str + +from IPython import version_info +from IPython.utils.py3compat import bytes_to_str +from IPython.utils.importstring import import_item +from IPython.testing.plugin.ipdoctest import IPythonDoctest +from IPython.external.decorators import KnownFailure, knownfailureif + +pjoin = path.join + + +# Enable printing all warnings raise by IPython's modules warnings.filterwarnings('ignore', message='.*Matplotlib is building the font cache.*', category=UserWarning, module='.*') if sys.version_info > (3,0): warnings.filterwarnings('error', message='.*', category=ResourceWarning, module='.*') warnings.filterwarnings('error', message=".*{'config': True}.*", category=DeprecationWarning, module='IPy.*') -warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*') - +warnings.filterwarnings('default', message='.*', category=Warning, module='IPy.*') + warnings.filterwarnings('error', message='.*apply_wrapper.*', category=DeprecationWarning, module='.*') warnings.filterwarnings('error', message='.*make_label_dec', category=DeprecationWarning, module='.*') warnings.filterwarnings('error', message='.*decorated_dummy.*', category=DeprecationWarning, module='.*') @@ -60,384 +60,384 @@ warnings.filterwarnings('error', message='.*disable_gui.*', category=Deprecation warnings.filterwarnings('error', message='.*ExceptionColors global is deprecated.*', category=DeprecationWarning, module='.*') -if version_info < (6,): - # nose.tools renames all things from `camelCase` to `snake_case` which raise an - # warning with the runner they also import from standard import library. (as of Dec 2015) - # Ignore, let's revisit that in a couple of years for IPython 6. - warnings.filterwarnings('ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*') - - -# ------------------------------------------------------------------------------ -# Monkeypatch Xunit to count known failures as skipped. -# ------------------------------------------------------------------------------ -def monkeypatch_xunit(): - try: - knownfailureif(True)(lambda: None)() - except Exception as e: - KnownFailureTest = type(e) - - def addError(self, test, err, capt=None): - if issubclass(err[0], KnownFailureTest): - err = (SkipTest,) + err[1:] - return self.orig_addError(test, err, capt) - - Xunit.orig_addError = Xunit.addError - Xunit.addError = addError - -#----------------------------------------------------------------------------- -# Check which dependencies are installed and greater than minimum version. -#----------------------------------------------------------------------------- -def extract_version(mod): - return mod.__version__ - -def test_for(item, min_version=None, callback=extract_version): - """Test to see if item is importable, and optionally check against a minimum - version. - - If min_version is given, the default behavior is to check against the - `__version__` attribute of the item, but specifying `callback` allows you to - extract the value you are interested in. e.g:: - - In [1]: import sys - - In [2]: from IPython.testing.iptest import test_for - - In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info) - Out[3]: True - - """ - try: - check = import_item(item) - except (ImportError, RuntimeError): - # GTK reports Runtime error if it can't be initialized even if it's - # importable. - return False - else: - if min_version: - if callback: - # extra processing step to get version to compare - check = callback(check) - - return check >= min_version - else: - return True - -# Global dict where we can store information on what we have and what we don't -# have available at test run time -have = {'matplotlib': test_for('matplotlib'), - 'pygments': test_for('pygments'), - 'sqlite3': test_for('sqlite3')} - -#----------------------------------------------------------------------------- -# Test suite definitions -#----------------------------------------------------------------------------- - -test_group_names = ['core', - 'extensions', 'lib', 'terminal', 'testing', 'utils', - ] - -class TestSection(object): - def __init__(self, name, includes): - self.name = name - self.includes = includes - self.excludes = [] - self.dependencies = [] - self.enabled = True - - def exclude(self, module): - if not module.startswith('IPython'): - module = self.includes[0] + "." + module - self.excludes.append(module.replace('.', os.sep)) - - def requires(self, *packages): - self.dependencies.extend(packages) - - @property - def will_run(self): - return self.enabled and all(have[p] for p in self.dependencies) - -# Name -> (include, exclude, dependencies_met) -test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names} - - -# Exclusions and dependencies -# --------------------------- - -# core: -sec = test_sections['core'] -if not have['sqlite3']: - sec.exclude('tests.test_history') - sec.exclude('history') -if not have['matplotlib']: - sec.exclude('pylabtools'), - sec.exclude('tests.test_pylabtools') - -# lib: -sec = test_sections['lib'] -sec.exclude('kernel') -if not have['pygments']: - sec.exclude('tests.test_lexers') -# We do this unconditionally, so that the test suite doesn't import -# gtk, changing the default encoding and masking some unicode bugs. -sec.exclude('inputhookgtk') -# We also do this unconditionally, because wx can interfere with Unix signals. -# There are currently no tests for it anyway. -sec.exclude('inputhookwx') -# Testing inputhook will need a lot of thought, to figure out -# how to have tests that don't lock up with the gui event -# loops in the picture -sec.exclude('inputhook') - -# testing: -sec = test_sections['testing'] -# These have to be skipped on win32 because they use echo, rm, cd, etc. -# See ticket https://github.com/ipython/ipython/issues/87 -if sys.platform == 'win32': - sec.exclude('plugin.test_exampleip') - sec.exclude('plugin.dtexample') - -# don't run jupyter_console tests found via shim -test_sections['terminal'].exclude('console') - -# extensions: -sec = test_sections['extensions'] -# This is deprecated in favour of rpy2 -sec.exclude('rmagic') -# autoreload does some strange stuff, so move it to its own test section -sec.exclude('autoreload') -sec.exclude('tests.test_autoreload') -test_sections['autoreload'] = TestSection('autoreload', - ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload']) -test_group_names.append('autoreload') - - -#----------------------------------------------------------------------------- -# Functions and classes -#----------------------------------------------------------------------------- - -def check_exclusions_exist(): - from IPython.paths import get_ipython_package_dir +if version_info < (6,): + # nose.tools renames all things from `camelCase` to `snake_case` which raise an + # warning with the runner they also import from standard import library. (as of Dec 2015) + # Ignore, let's revisit that in a couple of years for IPython 6. + warnings.filterwarnings('ignore', message='.*Please use assertEqual instead', category=Warning, module='IPython.*') + + +# ------------------------------------------------------------------------------ +# Monkeypatch Xunit to count known failures as skipped. +# ------------------------------------------------------------------------------ +def monkeypatch_xunit(): + try: + knownfailureif(True)(lambda: None)() + except Exception as e: + KnownFailureTest = type(e) + + def addError(self, test, err, capt=None): + if issubclass(err[0], KnownFailureTest): + err = (SkipTest,) + err[1:] + return self.orig_addError(test, err, capt) + + Xunit.orig_addError = Xunit.addError + Xunit.addError = addError + +#----------------------------------------------------------------------------- +# Check which dependencies are installed and greater than minimum version. +#----------------------------------------------------------------------------- +def extract_version(mod): + return mod.__version__ + +def test_for(item, min_version=None, callback=extract_version): + """Test to see if item is importable, and optionally check against a minimum + version. + + If min_version is given, the default behavior is to check against the + `__version__` attribute of the item, but specifying `callback` allows you to + extract the value you are interested in. e.g:: + + In [1]: import sys + + In [2]: from IPython.testing.iptest import test_for + + In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info) + Out[3]: True + + """ + try: + check = import_item(item) + except (ImportError, RuntimeError): + # GTK reports Runtime error if it can't be initialized even if it's + # importable. + return False + else: + if min_version: + if callback: + # extra processing step to get version to compare + check = callback(check) + + return check >= min_version + else: + return True + +# Global dict where we can store information on what we have and what we don't +# have available at test run time +have = {'matplotlib': test_for('matplotlib'), + 'pygments': test_for('pygments'), + 'sqlite3': test_for('sqlite3')} + +#----------------------------------------------------------------------------- +# Test suite definitions +#----------------------------------------------------------------------------- + +test_group_names = ['core', + 'extensions', 'lib', 'terminal', 'testing', 'utils', + ] + +class TestSection(object): + def __init__(self, name, includes): + self.name = name + self.includes = includes + self.excludes = [] + self.dependencies = [] + self.enabled = True + + def exclude(self, module): + if not module.startswith('IPython'): + module = self.includes[0] + "." + module + self.excludes.append(module.replace('.', os.sep)) + + def requires(self, *packages): + self.dependencies.extend(packages) + + @property + def will_run(self): + return self.enabled and all(have[p] for p in self.dependencies) + +# Name -> (include, exclude, dependencies_met) +test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names} + + +# Exclusions and dependencies +# --------------------------- + +# core: +sec = test_sections['core'] +if not have['sqlite3']: + sec.exclude('tests.test_history') + sec.exclude('history') +if not have['matplotlib']: + sec.exclude('pylabtools'), + sec.exclude('tests.test_pylabtools') + +# lib: +sec = test_sections['lib'] +sec.exclude('kernel') +if not have['pygments']: + sec.exclude('tests.test_lexers') +# We do this unconditionally, so that the test suite doesn't import +# gtk, changing the default encoding and masking some unicode bugs. +sec.exclude('inputhookgtk') +# We also do this unconditionally, because wx can interfere with Unix signals. +# There are currently no tests for it anyway. +sec.exclude('inputhookwx') +# Testing inputhook will need a lot of thought, to figure out +# how to have tests that don't lock up with the gui event +# loops in the picture +sec.exclude('inputhook') + +# testing: +sec = test_sections['testing'] +# These have to be skipped on win32 because they use echo, rm, cd, etc. +# See ticket https://github.com/ipython/ipython/issues/87 +if sys.platform == 'win32': + sec.exclude('plugin.test_exampleip') + sec.exclude('plugin.dtexample') + +# don't run jupyter_console tests found via shim +test_sections['terminal'].exclude('console') + +# extensions: +sec = test_sections['extensions'] +# This is deprecated in favour of rpy2 +sec.exclude('rmagic') +# autoreload does some strange stuff, so move it to its own test section +sec.exclude('autoreload') +sec.exclude('tests.test_autoreload') +test_sections['autoreload'] = TestSection('autoreload', + ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload']) +test_group_names.append('autoreload') + + +#----------------------------------------------------------------------------- +# Functions and classes +#----------------------------------------------------------------------------- + +def check_exclusions_exist(): + from IPython.paths import get_ipython_package_dir from warnings import warn - parent = os.path.dirname(get_ipython_package_dir()) - for sec in test_sections: - for pattern in sec.exclusions: - fullpath = pjoin(parent, pattern) - if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'): - warn("Excluding nonexistent file: %r" % pattern) - - -class ExclusionPlugin(Plugin): - """A nose plugin to effect our exclusions of files and directories. - """ - name = 'exclusions' - score = 3000 # Should come before any other plugins - - def __init__(self, exclude_patterns=None): - """ - Parameters - ---------- - - exclude_patterns : sequence of strings, optional - Filenames containing these patterns (as raw strings, not as regular - expressions) are excluded from the tests. - """ - self.exclude_patterns = exclude_patterns or [] - super(ExclusionPlugin, self).__init__() - - def options(self, parser, env=os.environ): - Plugin.options(self, parser, env) - - def configure(self, options, config): - Plugin.configure(self, options, config) - # Override nose trying to disable plugin. - self.enabled = True - - def wantFile(self, filename): - """Return whether the given filename should be scanned for tests. - """ - if any(pat in filename for pat in self.exclude_patterns): - return False - return None - - def wantDirectory(self, directory): - """Return whether the given directory should be scanned for tests. - """ - if any(pat in directory for pat in self.exclude_patterns): - return False - return None - - -class StreamCapturer(Thread): - daemon = True # Don't hang if main thread crashes - started = False - def __init__(self, echo=False): - super(StreamCapturer, self).__init__() - self.echo = echo - self.streams = [] - self.buffer = BytesIO() - self.readfd, self.writefd = os.pipe() - self.buffer_lock = Lock() - self.stop = Event() - - def run(self): - self.started = True - - while not self.stop.is_set(): - chunk = os.read(self.readfd, 1024) - - with self.buffer_lock: - self.buffer.write(chunk) - if self.echo: - sys.stdout.write(bytes_to_str(chunk)) - - os.close(self.readfd) - os.close(self.writefd) - - def reset_buffer(self): - with self.buffer_lock: - self.buffer.truncate(0) - self.buffer.seek(0) - - def get_buffer(self): - with self.buffer_lock: - return self.buffer.getvalue() - - def ensure_started(self): - if not self.started: - self.start() - - def halt(self): - """Safely stop the thread.""" - if not self.started: - return - - self.stop.set() - os.write(self.writefd, b'\0') # Ensure we're not locked in a read() - self.join() - -class SubprocessStreamCapturePlugin(Plugin): - name='subprocstreams' - def __init__(self): - Plugin.__init__(self) - self.stream_capturer = StreamCapturer() - self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture') - # This is ugly, but distant parts of the test machinery need to be able - # to redirect streams, so we make the object globally accessible. - nose.iptest_stdstreams_fileno = self.get_write_fileno - - def get_write_fileno(self): - if self.destination == 'capture': - self.stream_capturer.ensure_started() - return self.stream_capturer.writefd - elif self.destination == 'discard': - return os.open(os.devnull, os.O_WRONLY) - else: - return sys.__stdout__.fileno() - - def configure(self, options, config): - Plugin.configure(self, options, config) - # Override nose trying to disable plugin. - if self.destination == 'capture': - self.enabled = True - - def startTest(self, test): - # Reset log capture - self.stream_capturer.reset_buffer() - - def formatFailure(self, test, err): - # Show output - ec, ev, tb = err - captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace') - if captured.strip(): - ev = safe_str(ev) - out = [ev, '>> begin captured subprocess output <<', - captured, - '>> end captured subprocess output <<'] - return ec, '\n'.join(out), tb - - return err - - formatError = formatFailure - - def finalize(self, result): - self.stream_capturer.halt() - - -def run_iptest(): - """Run the IPython test suite using nose. - - This function is called when this script is **not** called with the form - `iptest all`. It simply calls nose with appropriate command line flags - and accepts all of the standard nose arguments. - """ - # Apply our monkeypatch to Xunit - if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'): - monkeypatch_xunit() - - arg1 = sys.argv[1] - if arg1 in test_sections: - section = test_sections[arg1] - sys.argv[1:2] = section.includes - elif arg1.startswith('IPython.') and arg1[8:] in test_sections: - section = test_sections[arg1[8:]] - sys.argv[1:2] = section.includes - else: - section = TestSection(arg1, includes=[arg1]) - - - argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks - # We add --exe because of setuptools' imbecility (it - # blindly does chmod +x on ALL files). Nose does the - # right thing and it tries to avoid executables, - # setuptools unfortunately forces our hand here. This - # has been discussed on the distutils list and the - # setuptools devs refuse to fix this problem! - '--exe', - ] - if '-a' not in argv and '-A' not in argv: - argv = argv + ['-a', '!crash'] - - if nose.__version__ >= '0.11': - # I don't fully understand why we need this one, but depending on what - # directory the test suite is run from, if we don't give it, 0 tests - # get run. Specifically, if the test suite is run from the source dir - # with an argument (like 'iptest.py IPython.core', 0 tests are run, - # even if the same call done in this directory works fine). It appears - # that if the requested package is in the current dir, nose bails early - # by default. Since it's otherwise harmless, leave it in by default - # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it. - argv.append('--traverse-namespace') - - plugins = [ ExclusionPlugin(section.excludes), KnownFailure(), - SubprocessStreamCapturePlugin() ] - - # we still have some vestigial doctests in core - if (section.name.startswith(('core', 'IPython.core'))): - plugins.append(IPythonDoctest()) - argv.extend([ - '--with-ipdoctest', - '--ipdoctest-tests', - '--ipdoctest-extension=txt', - ]) - - - # Use working directory set by parent process (see iptestcontroller) - if 'IPTEST_WORKING_DIR' in os.environ: - os.chdir(os.environ['IPTEST_WORKING_DIR']) - - # We need a global ipython running in this process, but the special - # in-process group spawns its own IPython kernels, so for *that* group we - # must avoid also opening the global one (otherwise there's a conflict of - # singletons). Ultimately the solution to this problem is to refactor our - # assumptions about what needs to be a singleton and what doesn't (app - # objects should, individual shells shouldn't). But for now, this - # workaround allows the test suite for the inprocess module to complete. - if 'kernel.inprocess' not in section.name: - from IPython.testing import globalipapp - globalipapp.start_ipython() - - # Now nose can run - TestProgram(argv=argv, addplugins=plugins) - -if __name__ == '__main__': - run_iptest() + parent = os.path.dirname(get_ipython_package_dir()) + for sec in test_sections: + for pattern in sec.exclusions: + fullpath = pjoin(parent, pattern) + if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'): + warn("Excluding nonexistent file: %r" % pattern) + + +class ExclusionPlugin(Plugin): + """A nose plugin to effect our exclusions of files and directories. + """ + name = 'exclusions' + score = 3000 # Should come before any other plugins + + def __init__(self, exclude_patterns=None): + """ + Parameters + ---------- + + exclude_patterns : sequence of strings, optional + Filenames containing these patterns (as raw strings, not as regular + expressions) are excluded from the tests. + """ + self.exclude_patterns = exclude_patterns or [] + super(ExclusionPlugin, self).__init__() + + def options(self, parser, env=os.environ): + Plugin.options(self, parser, env) + + def configure(self, options, config): + Plugin.configure(self, options, config) + # Override nose trying to disable plugin. + self.enabled = True + + def wantFile(self, filename): + """Return whether the given filename should be scanned for tests. + """ + if any(pat in filename for pat in self.exclude_patterns): + return False + return None + + def wantDirectory(self, directory): + """Return whether the given directory should be scanned for tests. + """ + if any(pat in directory for pat in self.exclude_patterns): + return False + return None + + +class StreamCapturer(Thread): + daemon = True # Don't hang if main thread crashes + started = False + def __init__(self, echo=False): + super(StreamCapturer, self).__init__() + self.echo = echo + self.streams = [] + self.buffer = BytesIO() + self.readfd, self.writefd = os.pipe() + self.buffer_lock = Lock() + self.stop = Event() + + def run(self): + self.started = True + + while not self.stop.is_set(): + chunk = os.read(self.readfd, 1024) + + with self.buffer_lock: + self.buffer.write(chunk) + if self.echo: + sys.stdout.write(bytes_to_str(chunk)) + + os.close(self.readfd) + os.close(self.writefd) + + def reset_buffer(self): + with self.buffer_lock: + self.buffer.truncate(0) + self.buffer.seek(0) + + def get_buffer(self): + with self.buffer_lock: + return self.buffer.getvalue() + + def ensure_started(self): + if not self.started: + self.start() + + def halt(self): + """Safely stop the thread.""" + if not self.started: + return + + self.stop.set() + os.write(self.writefd, b'\0') # Ensure we're not locked in a read() + self.join() + +class SubprocessStreamCapturePlugin(Plugin): + name='subprocstreams' + def __init__(self): + Plugin.__init__(self) + self.stream_capturer = StreamCapturer() + self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture') + # This is ugly, but distant parts of the test machinery need to be able + # to redirect streams, so we make the object globally accessible. + nose.iptest_stdstreams_fileno = self.get_write_fileno + + def get_write_fileno(self): + if self.destination == 'capture': + self.stream_capturer.ensure_started() + return self.stream_capturer.writefd + elif self.destination == 'discard': + return os.open(os.devnull, os.O_WRONLY) + else: + return sys.__stdout__.fileno() + + def configure(self, options, config): + Plugin.configure(self, options, config) + # Override nose trying to disable plugin. + if self.destination == 'capture': + self.enabled = True + + def startTest(self, test): + # Reset log capture + self.stream_capturer.reset_buffer() + + def formatFailure(self, test, err): + # Show output + ec, ev, tb = err + captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace') + if captured.strip(): + ev = safe_str(ev) + out = [ev, '>> begin captured subprocess output <<', + captured, + '>> end captured subprocess output <<'] + return ec, '\n'.join(out), tb + + return err + + formatError = formatFailure + + def finalize(self, result): + self.stream_capturer.halt() + + +def run_iptest(): + """Run the IPython test suite using nose. + + This function is called when this script is **not** called with the form + `iptest all`. It simply calls nose with appropriate command line flags + and accepts all of the standard nose arguments. + """ + # Apply our monkeypatch to Xunit + if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'): + monkeypatch_xunit() + + arg1 = sys.argv[1] + if arg1 in test_sections: + section = test_sections[arg1] + sys.argv[1:2] = section.includes + elif arg1.startswith('IPython.') and arg1[8:] in test_sections: + section = test_sections[arg1[8:]] + sys.argv[1:2] = section.includes + else: + section = TestSection(arg1, includes=[arg1]) + + + argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks + # We add --exe because of setuptools' imbecility (it + # blindly does chmod +x on ALL files). Nose does the + # right thing and it tries to avoid executables, + # setuptools unfortunately forces our hand here. This + # has been discussed on the distutils list and the + # setuptools devs refuse to fix this problem! + '--exe', + ] + if '-a' not in argv and '-A' not in argv: + argv = argv + ['-a', '!crash'] + + if nose.__version__ >= '0.11': + # I don't fully understand why we need this one, but depending on what + # directory the test suite is run from, if we don't give it, 0 tests + # get run. Specifically, if the test suite is run from the source dir + # with an argument (like 'iptest.py IPython.core', 0 tests are run, + # even if the same call done in this directory works fine). It appears + # that if the requested package is in the current dir, nose bails early + # by default. Since it's otherwise harmless, leave it in by default + # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it. + argv.append('--traverse-namespace') + + plugins = [ ExclusionPlugin(section.excludes), KnownFailure(), + SubprocessStreamCapturePlugin() ] + + # we still have some vestigial doctests in core + if (section.name.startswith(('core', 'IPython.core'))): + plugins.append(IPythonDoctest()) + argv.extend([ + '--with-ipdoctest', + '--ipdoctest-tests', + '--ipdoctest-extension=txt', + ]) + + + # Use working directory set by parent process (see iptestcontroller) + if 'IPTEST_WORKING_DIR' in os.environ: + os.chdir(os.environ['IPTEST_WORKING_DIR']) + + # We need a global ipython running in this process, but the special + # in-process group spawns its own IPython kernels, so for *that* group we + # must avoid also opening the global one (otherwise there's a conflict of + # singletons). Ultimately the solution to this problem is to refactor our + # assumptions about what needs to be a singleton and what doesn't (app + # objects should, individual shells shouldn't). But for now, this + # workaround allows the test suite for the inprocess module to complete. + if 'kernel.inprocess' not in section.name: + from IPython.testing import globalipapp + globalipapp.start_ipython() + + # Now nose can run + TestProgram(argv=argv, addplugins=plugins) + +if __name__ == '__main__': + run_iptest() diff --git a/contrib/python/ipython/py2/IPython/testing/iptestcontroller.py b/contrib/python/ipython/py2/IPython/testing/iptestcontroller.py index 95aa06e4a4..6d8834f193 100644 --- a/contrib/python/ipython/py2/IPython/testing/iptestcontroller.py +++ b/contrib/python/ipython/py2/IPython/testing/iptestcontroller.py @@ -1,532 +1,532 @@ -# -*- coding: utf-8 -*- -"""IPython Test Process Controller - -This module runs one or more subprocesses which will actually run the IPython -test suite. - -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -from __future__ import print_function - -import argparse -import json -import multiprocessing.pool -import os -import stat -import re -import requests -import shutil -import signal -import sys -import subprocess -import time - -from .iptest import ( - have, test_group_names as py_test_group_names, test_sections, StreamCapturer, - test_for, -) -from IPython.utils.path import compress_user -from IPython.utils.py3compat import bytes_to_str -from IPython.utils.sysinfo import get_sys_info -from IPython.utils.tempdir import TemporaryDirectory -from IPython.utils.text import strip_ansi - -try: - # Python >= 3.3 - from subprocess import TimeoutExpired - def popen_wait(p, timeout): - return p.wait(timeout) -except ImportError: - class TimeoutExpired(Exception): - pass - def popen_wait(p, timeout): - """backport of Popen.wait from Python 3""" - for i in range(int(10 * timeout)): - if p.poll() is not None: - return - time.sleep(0.1) - if p.poll() is None: - raise TimeoutExpired - -NOTEBOOK_SHUTDOWN_TIMEOUT = 10 - -class TestController(object): - """Run tests in a subprocess - """ - #: str, IPython test suite to be executed. - section = None - #: list, command line arguments to be executed - cmd = None - #: dict, extra environment variables to set for the subprocess - env = None - #: list, TemporaryDirectory instances to clear up when the process finishes - dirs = None - #: subprocess.Popen instance - process = None - #: str, process stdout+stderr - stdout = None - - def __init__(self): - self.cmd = [] - self.env = {} - self.dirs = [] - - def setup(self): - """Create temporary directories etc. - - This is only called when we know the test group will be run. Things - created here may be cleaned up by self.cleanup(). - """ - pass - - def launch(self, buffer_output=False, capture_output=False): - # print('*** ENV:', self.env) # dbg - # print('*** CMD:', self.cmd) # dbg - env = os.environ.copy() - env.update(self.env) - if buffer_output: - capture_output = True - self.stdout_capturer = c = StreamCapturer(echo=not buffer_output) - c.start() - stdout = c.writefd if capture_output else None - stderr = subprocess.STDOUT if capture_output else None - self.process = subprocess.Popen(self.cmd, stdout=stdout, - stderr=stderr, env=env) - - def wait(self): - self.process.wait() - self.stdout_capturer.halt() - self.stdout = self.stdout_capturer.get_buffer() - return self.process.returncode - - def print_extra_info(self): - """Print extra information about this test run. - - If we're running in parallel and showing the concise view, this is only - called if the test group fails. Otherwise, it's called before the test - group is started. - - The base implementation does nothing, but it can be overridden by - subclasses. - """ - return - - def cleanup_process(self): - """Cleanup on exit by killing any leftover processes.""" - subp = self.process - if subp is None or (subp.poll() is not None): - return # Process doesn't exist, or is already dead. - - try: - print('Cleaning up stale PID: %d' % subp.pid) - subp.kill() - except: # (OSError, WindowsError) ? - # This is just a best effort, if we fail or the process was - # really gone, ignore it. - pass - else: - for i in range(10): - if subp.poll() is None: - time.sleep(0.1) - else: - break - - if subp.poll() is None: - # The process did not die... - print('... failed. Manual cleanup may be required.') - - def cleanup(self): - "Kill process if it's still alive, and clean up temporary directories" - self.cleanup_process() - for td in self.dirs: - td.cleanup() - - __del__ = cleanup - - -class PyTestController(TestController): - """Run Python tests using IPython.testing.iptest""" - #: str, Python command to execute in subprocess - pycmd = None - - def __init__(self, section, options): - """Create new test runner.""" - TestController.__init__(self) - self.section = section - # pycmd is put into cmd[2] in PyTestController.launch() - self.cmd = [sys.executable, '-c', None, section] - self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()" - self.options = options - - def setup(self): - ipydir = TemporaryDirectory() - self.dirs.append(ipydir) - self.env['IPYTHONDIR'] = ipydir.name - self.workingdir = workingdir = TemporaryDirectory() - self.dirs.append(workingdir) - self.env['IPTEST_WORKING_DIR'] = workingdir.name - # This means we won't get odd effects from our own matplotlib config - self.env['MPLCONFIGDIR'] = workingdir.name - # For security reasons (http://bugs.python.org/issue16202), use - # a temporary directory to which other users have no access. - self.env['TMPDIR'] = workingdir.name - - # Add a non-accessible directory to PATH (see gh-7053) - noaccess = os.path.join(self.workingdir.name, "_no_access_") - self.noaccess = noaccess - os.mkdir(noaccess, 0) - - PATH = os.environ.get('PATH', '') - if PATH: - PATH = noaccess + os.pathsep + PATH - else: - PATH = noaccess - self.env['PATH'] = PATH - - # From options: - if self.options.xunit: - self.add_xunit() - if self.options.coverage: - self.add_coverage() - self.env['IPTEST_SUBPROC_STREAMS'] = self.options.subproc_streams - self.cmd.extend(self.options.extra_args) - - def cleanup(self): - """ - Make the non-accessible directory created in setup() accessible - again, otherwise deleting the workingdir will fail. - """ - os.chmod(self.noaccess, stat.S_IRWXU) - TestController.cleanup(self) - - @property - def will_run(self): - try: - return test_sections[self.section].will_run - except KeyError: - return True - - def add_xunit(self): - xunit_file = os.path.abspath(self.section + '.xunit.xml') - self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file]) - - def add_coverage(self): - try: - sources = test_sections[self.section].includes - except KeyError: - sources = ['IPython'] - - coverage_rc = ("[run]\n" - "data_file = {data_file}\n" - "source =\n" - " {source}\n" - ).format(data_file=os.path.abspath('.coverage.'+self.section), - source="\n ".join(sources)) - config_file = os.path.join(self.workingdir.name, '.coveragerc') - with open(config_file, 'w') as f: - f.write(coverage_rc) - - self.env['COVERAGE_PROCESS_START'] = config_file - self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd - - def launch(self, buffer_output=False): - self.cmd[2] = self.pycmd - super(PyTestController, self).launch(buffer_output=buffer_output) - - -def prepare_controllers(options): - """Returns two lists of TestController instances, those to run, and those - not to run.""" - testgroups = options.testgroups - if not testgroups: - testgroups = py_test_group_names - - controllers = [PyTestController(name, options) for name in testgroups] - - to_run = [c for c in controllers if c.will_run] - not_run = [c for c in controllers if not c.will_run] - return to_run, not_run - -def do_run(controller, buffer_output=True): - """Setup and run a test controller. - - If buffer_output is True, no output is displayed, to avoid it appearing - interleaved. In this case, the caller is responsible for displaying test - output on failure. - - Returns - ------- - controller : TestController - The same controller as passed in, as a convenience for using map() type - APIs. - exitcode : int - The exit code of the test subprocess. Non-zero indicates failure. - """ - try: - try: - controller.setup() - if not buffer_output: - controller.print_extra_info() - controller.launch(buffer_output=buffer_output) - except Exception: - import traceback - traceback.print_exc() - return controller, 1 # signal failure - - exitcode = controller.wait() - return controller, exitcode - - except KeyboardInterrupt: - return controller, -signal.SIGINT - finally: - controller.cleanup() - -def report(): - """Return a string with a summary report of test-related variables.""" - inf = get_sys_info() - out = [] - def _add(name, value): - out.append((name, value)) - - _add('IPython version', inf['ipython_version']) - _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source'])) - _add('IPython package', compress_user(inf['ipython_path'])) - _add('Python version', inf['sys_version'].replace('\n','')) - _add('sys.executable', compress_user(inf['sys_executable'])) - _add('Platform', inf['platform']) - - width = max(len(n) for (n,v) in out) - out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out] - - avail = [] - not_avail = [] - - for k, is_avail in have.items(): - if is_avail: - avail.append(k) - else: - not_avail.append(k) - - if avail: - out.append('\nTools and libraries available at test time:\n') - avail.sort() - out.append(' ' + ' '.join(avail)+'\n') - - if not_avail: - out.append('\nTools and libraries NOT available at test time:\n') - not_avail.sort() - out.append(' ' + ' '.join(not_avail)+'\n') - - return ''.join(out) - -def run_iptestall(options): - """Run the entire IPython test suite by calling nose and trial. - - This function constructs :class:`IPTester` instances for all IPython - modules and package and then runs each of them. This causes the modules - and packages of IPython to be tested each in their own subprocess using - nose. - - Parameters - ---------- - - All parameters are passed as attributes of the options object. - - testgroups : list of str - Run only these sections of the test suite. If empty, run all the available - sections. - - fast : int or None - Run the test suite in parallel, using n simultaneous processes. If None - is passed, one process is used per CPU core. Default 1 (i.e. sequential) - - inc_slow : bool - Include slow tests. By default, these tests aren't run. - - url : unicode - Address:port to use when running the JS tests. - - xunit : bool - Produce Xunit XML output. This is written to multiple foo.xunit.xml files. - - coverage : bool or str - Measure code coverage from tests. True will store the raw coverage data, - or pass 'html' or 'xml' to get reports. - - extra_args : list - Extra arguments to pass to the test subprocesses, e.g. '-v' - """ - to_run, not_run = prepare_controllers(options) - - def justify(ltext, rtext, width=70, fill='-'): - ltext += ' ' - rtext = (' ' + rtext).rjust(width - len(ltext), fill) - return ltext + rtext - - # Run all test runners, tracking execution time - failed = [] - t_start = time.time() - - print() - if options.fast == 1: - # This actually means sequential, i.e. with 1 job - for controller in to_run: - print('Test group:', controller.section) - sys.stdout.flush() # Show in correct order when output is piped - controller, res = do_run(controller, buffer_output=False) - if res: - failed.append(controller) - if res == -signal.SIGINT: - print("Interrupted") - break - print() - - else: - # Run tests concurrently - try: - pool = multiprocessing.pool.ThreadPool(options.fast) - for (controller, res) in pool.imap_unordered(do_run, to_run): - res_string = 'OK' if res == 0 else 'FAILED' - print(justify('Test group: ' + controller.section, res_string)) - if res: - controller.print_extra_info() - print(bytes_to_str(controller.stdout)) - failed.append(controller) - if res == -signal.SIGINT: - print("Interrupted") - break - except KeyboardInterrupt: - return - - for controller in not_run: - print(justify('Test group: ' + controller.section, 'NOT RUN')) - - t_end = time.time() - t_tests = t_end - t_start - nrunners = len(to_run) - nfail = len(failed) - # summarize results - print('_'*70) - print('Test suite completed for system with the following information:') - print(report()) - took = "Took %.3fs." % t_tests - print('Status: ', end='') - if not failed: - print('OK (%d test groups).' % nrunners, took) - else: - # If anything went wrong, point out what command to rerun manually to - # see the actual errors and individual summary - failed_sections = [c.section for c in failed] - print('ERROR - {} out of {} test groups failed ({}).'.format(nfail, - nrunners, ', '.join(failed_sections)), took) - print() - print('You may wish to rerun these, with:') - print(' iptest', *failed_sections) - print() - - if options.coverage: - from coverage import coverage, CoverageException - cov = coverage(data_file='.coverage') - cov.combine() - cov.save() - - # Coverage HTML report - if options.coverage == 'html': - html_dir = 'ipy_htmlcov' - shutil.rmtree(html_dir, ignore_errors=True) - print("Writing HTML coverage report to %s/ ... " % html_dir, end="") - sys.stdout.flush() - - # Custom HTML reporter to clean up module names. - from coverage.html import HtmlReporter - class CustomHtmlReporter(HtmlReporter): - def find_code_units(self, morfs): - super(CustomHtmlReporter, self).find_code_units(morfs) - for cu in self.code_units: - nameparts = cu.name.split(os.sep) - if 'IPython' not in nameparts: - continue - ix = nameparts.index('IPython') - cu.name = '.'.join(nameparts[ix:]) - - # Reimplement the html_report method with our custom reporter - cov.get_data() - cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir, - html_title='IPython test coverage', - ) - reporter = CustomHtmlReporter(cov, cov.config) - reporter.report(None) - print('done.') - - # Coverage XML report - elif options.coverage == 'xml': - try: - cov.xml_report(outfile='ipy_coverage.xml') - except CoverageException as e: - print('Generating coverage report failed. Are you running javascript tests only?') - import traceback - traceback.print_exc() - - if failed: - # Ensure that our exit code indicates failure - sys.exit(1) - -argparser = argparse.ArgumentParser(description='Run IPython test suite') -argparser.add_argument('testgroups', nargs='*', - help='Run specified groups of tests. If omitted, run ' - 'all tests.') -argparser.add_argument('--all', action='store_true', - help='Include slow tests not run by default.') -argparser.add_argument('--url', help="URL to use for the JS tests.") -argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int, - help='Run test sections in parallel. This starts as many ' - 'processes as you have cores, or you can specify a number.') -argparser.add_argument('--xunit', action='store_true', - help='Produce Xunit XML results') -argparser.add_argument('--coverage', nargs='?', const=True, default=False, - help="Measure test coverage. Specify 'html' or " - "'xml' to get reports.") -argparser.add_argument('--subproc-streams', default='capture', - help="What to do with stdout/stderr from subprocesses. " - "'capture' (default), 'show' and 'discard' are the options.") - -def default_options(): - """Get an argparse Namespace object with the default arguments, to pass to - :func:`run_iptestall`. - """ - options = argparser.parse_args([]) - options.extra_args = [] - return options - -def main(): - # iptest doesn't work correctly if the working directory is the - # root of the IPython source tree. Tell the user to avoid - # frustration. - if os.path.exists(os.path.join(os.getcwd(), - 'IPython', 'testing', '__main__.py')): - print("Don't run iptest from the IPython source directory", - file=sys.stderr) - sys.exit(1) - # Arguments after -- should be passed through to nose. Argparse treats - # everything after -- as regular positional arguments, so we separate them - # first. - try: - ix = sys.argv.index('--') - except ValueError: - to_parse = sys.argv[1:] - extra_args = [] - else: - to_parse = sys.argv[1:ix] - extra_args = sys.argv[ix+1:] - - options = argparser.parse_args(to_parse) - options.extra_args = extra_args - - run_iptestall(options) - - -if __name__ == '__main__': - main() +# -*- coding: utf-8 -*- +"""IPython Test Process Controller + +This module runs one or more subprocesses which will actually run the IPython +test suite. + +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +import argparse +import json +import multiprocessing.pool +import os +import stat +import re +import requests +import shutil +import signal +import sys +import subprocess +import time + +from .iptest import ( + have, test_group_names as py_test_group_names, test_sections, StreamCapturer, + test_for, +) +from IPython.utils.path import compress_user +from IPython.utils.py3compat import bytes_to_str +from IPython.utils.sysinfo import get_sys_info +from IPython.utils.tempdir import TemporaryDirectory +from IPython.utils.text import strip_ansi + +try: + # Python >= 3.3 + from subprocess import TimeoutExpired + def popen_wait(p, timeout): + return p.wait(timeout) +except ImportError: + class TimeoutExpired(Exception): + pass + def popen_wait(p, timeout): + """backport of Popen.wait from Python 3""" + for i in range(int(10 * timeout)): + if p.poll() is not None: + return + time.sleep(0.1) + if p.poll() is None: + raise TimeoutExpired + +NOTEBOOK_SHUTDOWN_TIMEOUT = 10 + +class TestController(object): + """Run tests in a subprocess + """ + #: str, IPython test suite to be executed. + section = None + #: list, command line arguments to be executed + cmd = None + #: dict, extra environment variables to set for the subprocess + env = None + #: list, TemporaryDirectory instances to clear up when the process finishes + dirs = None + #: subprocess.Popen instance + process = None + #: str, process stdout+stderr + stdout = None + + def __init__(self): + self.cmd = [] + self.env = {} + self.dirs = [] + + def setup(self): + """Create temporary directories etc. + + This is only called when we know the test group will be run. Things + created here may be cleaned up by self.cleanup(). + """ + pass + + def launch(self, buffer_output=False, capture_output=False): + # print('*** ENV:', self.env) # dbg + # print('*** CMD:', self.cmd) # dbg + env = os.environ.copy() + env.update(self.env) + if buffer_output: + capture_output = True + self.stdout_capturer = c = StreamCapturer(echo=not buffer_output) + c.start() + stdout = c.writefd if capture_output else None + stderr = subprocess.STDOUT if capture_output else None + self.process = subprocess.Popen(self.cmd, stdout=stdout, + stderr=stderr, env=env) + + def wait(self): + self.process.wait() + self.stdout_capturer.halt() + self.stdout = self.stdout_capturer.get_buffer() + return self.process.returncode + + def print_extra_info(self): + """Print extra information about this test run. + + If we're running in parallel and showing the concise view, this is only + called if the test group fails. Otherwise, it's called before the test + group is started. + + The base implementation does nothing, but it can be overridden by + subclasses. + """ + return + + def cleanup_process(self): + """Cleanup on exit by killing any leftover processes.""" + subp = self.process + if subp is None or (subp.poll() is not None): + return # Process doesn't exist, or is already dead. + + try: + print('Cleaning up stale PID: %d' % subp.pid) + subp.kill() + except: # (OSError, WindowsError) ? + # This is just a best effort, if we fail or the process was + # really gone, ignore it. + pass + else: + for i in range(10): + if subp.poll() is None: + time.sleep(0.1) + else: + break + + if subp.poll() is None: + # The process did not die... + print('... failed. Manual cleanup may be required.') + + def cleanup(self): + "Kill process if it's still alive, and clean up temporary directories" + self.cleanup_process() + for td in self.dirs: + td.cleanup() + + __del__ = cleanup + + +class PyTestController(TestController): + """Run Python tests using IPython.testing.iptest""" + #: str, Python command to execute in subprocess + pycmd = None + + def __init__(self, section, options): + """Create new test runner.""" + TestController.__init__(self) + self.section = section + # pycmd is put into cmd[2] in PyTestController.launch() + self.cmd = [sys.executable, '-c', None, section] + self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()" + self.options = options + + def setup(self): + ipydir = TemporaryDirectory() + self.dirs.append(ipydir) + self.env['IPYTHONDIR'] = ipydir.name + self.workingdir = workingdir = TemporaryDirectory() + self.dirs.append(workingdir) + self.env['IPTEST_WORKING_DIR'] = workingdir.name + # This means we won't get odd effects from our own matplotlib config + self.env['MPLCONFIGDIR'] = workingdir.name + # For security reasons (http://bugs.python.org/issue16202), use + # a temporary directory to which other users have no access. + self.env['TMPDIR'] = workingdir.name + + # Add a non-accessible directory to PATH (see gh-7053) + noaccess = os.path.join(self.workingdir.name, "_no_access_") + self.noaccess = noaccess + os.mkdir(noaccess, 0) + + PATH = os.environ.get('PATH', '') + if PATH: + PATH = noaccess + os.pathsep + PATH + else: + PATH = noaccess + self.env['PATH'] = PATH + + # From options: + if self.options.xunit: + self.add_xunit() + if self.options.coverage: + self.add_coverage() + self.env['IPTEST_SUBPROC_STREAMS'] = self.options.subproc_streams + self.cmd.extend(self.options.extra_args) + + def cleanup(self): + """ + Make the non-accessible directory created in setup() accessible + again, otherwise deleting the workingdir will fail. + """ + os.chmod(self.noaccess, stat.S_IRWXU) + TestController.cleanup(self) + + @property + def will_run(self): + try: + return test_sections[self.section].will_run + except KeyError: + return True + + def add_xunit(self): + xunit_file = os.path.abspath(self.section + '.xunit.xml') + self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file]) + + def add_coverage(self): + try: + sources = test_sections[self.section].includes + except KeyError: + sources = ['IPython'] + + coverage_rc = ("[run]\n" + "data_file = {data_file}\n" + "source =\n" + " {source}\n" + ).format(data_file=os.path.abspath('.coverage.'+self.section), + source="\n ".join(sources)) + config_file = os.path.join(self.workingdir.name, '.coveragerc') + with open(config_file, 'w') as f: + f.write(coverage_rc) + + self.env['COVERAGE_PROCESS_START'] = config_file + self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd + + def launch(self, buffer_output=False): + self.cmd[2] = self.pycmd + super(PyTestController, self).launch(buffer_output=buffer_output) + + +def prepare_controllers(options): + """Returns two lists of TestController instances, those to run, and those + not to run.""" + testgroups = options.testgroups + if not testgroups: + testgroups = py_test_group_names + + controllers = [PyTestController(name, options) for name in testgroups] + + to_run = [c for c in controllers if c.will_run] + not_run = [c for c in controllers if not c.will_run] + return to_run, not_run + +def do_run(controller, buffer_output=True): + """Setup and run a test controller. + + If buffer_output is True, no output is displayed, to avoid it appearing + interleaved. In this case, the caller is responsible for displaying test + output on failure. + + Returns + ------- + controller : TestController + The same controller as passed in, as a convenience for using map() type + APIs. + exitcode : int + The exit code of the test subprocess. Non-zero indicates failure. + """ + try: + try: + controller.setup() + if not buffer_output: + controller.print_extra_info() + controller.launch(buffer_output=buffer_output) + except Exception: + import traceback + traceback.print_exc() + return controller, 1 # signal failure + + exitcode = controller.wait() + return controller, exitcode + + except KeyboardInterrupt: + return controller, -signal.SIGINT + finally: + controller.cleanup() + +def report(): + """Return a string with a summary report of test-related variables.""" + inf = get_sys_info() + out = [] + def _add(name, value): + out.append((name, value)) + + _add('IPython version', inf['ipython_version']) + _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source'])) + _add('IPython package', compress_user(inf['ipython_path'])) + _add('Python version', inf['sys_version'].replace('\n','')) + _add('sys.executable', compress_user(inf['sys_executable'])) + _add('Platform', inf['platform']) + + width = max(len(n) for (n,v) in out) + out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out] + + avail = [] + not_avail = [] + + for k, is_avail in have.items(): + if is_avail: + avail.append(k) + else: + not_avail.append(k) + + if avail: + out.append('\nTools and libraries available at test time:\n') + avail.sort() + out.append(' ' + ' '.join(avail)+'\n') + + if not_avail: + out.append('\nTools and libraries NOT available at test time:\n') + not_avail.sort() + out.append(' ' + ' '.join(not_avail)+'\n') + + return ''.join(out) + +def run_iptestall(options): + """Run the entire IPython test suite by calling nose and trial. + + This function constructs :class:`IPTester` instances for all IPython + modules and package and then runs each of them. This causes the modules + and packages of IPython to be tested each in their own subprocess using + nose. + + Parameters + ---------- + + All parameters are passed as attributes of the options object. + + testgroups : list of str + Run only these sections of the test suite. If empty, run all the available + sections. + + fast : int or None + Run the test suite in parallel, using n simultaneous processes. If None + is passed, one process is used per CPU core. Default 1 (i.e. sequential) + + inc_slow : bool + Include slow tests. By default, these tests aren't run. + + url : unicode + Address:port to use when running the JS tests. + + xunit : bool + Produce Xunit XML output. This is written to multiple foo.xunit.xml files. + + coverage : bool or str + Measure code coverage from tests. True will store the raw coverage data, + or pass 'html' or 'xml' to get reports. + + extra_args : list + Extra arguments to pass to the test subprocesses, e.g. '-v' + """ + to_run, not_run = prepare_controllers(options) + + def justify(ltext, rtext, width=70, fill='-'): + ltext += ' ' + rtext = (' ' + rtext).rjust(width - len(ltext), fill) + return ltext + rtext + + # Run all test runners, tracking execution time + failed = [] + t_start = time.time() + + print() + if options.fast == 1: + # This actually means sequential, i.e. with 1 job + for controller in to_run: + print('Test group:', controller.section) + sys.stdout.flush() # Show in correct order when output is piped + controller, res = do_run(controller, buffer_output=False) + if res: + failed.append(controller) + if res == -signal.SIGINT: + print("Interrupted") + break + print() + + else: + # Run tests concurrently + try: + pool = multiprocessing.pool.ThreadPool(options.fast) + for (controller, res) in pool.imap_unordered(do_run, to_run): + res_string = 'OK' if res == 0 else 'FAILED' + print(justify('Test group: ' + controller.section, res_string)) + if res: + controller.print_extra_info() + print(bytes_to_str(controller.stdout)) + failed.append(controller) + if res == -signal.SIGINT: + print("Interrupted") + break + except KeyboardInterrupt: + return + + for controller in not_run: + print(justify('Test group: ' + controller.section, 'NOT RUN')) + + t_end = time.time() + t_tests = t_end - t_start + nrunners = len(to_run) + nfail = len(failed) + # summarize results + print('_'*70) + print('Test suite completed for system with the following information:') + print(report()) + took = "Took %.3fs." % t_tests + print('Status: ', end='') + if not failed: + print('OK (%d test groups).' % nrunners, took) + else: + # If anything went wrong, point out what command to rerun manually to + # see the actual errors and individual summary + failed_sections = [c.section for c in failed] + print('ERROR - {} out of {} test groups failed ({}).'.format(nfail, + nrunners, ', '.join(failed_sections)), took) + print() + print('You may wish to rerun these, with:') + print(' iptest', *failed_sections) + print() + + if options.coverage: + from coverage import coverage, CoverageException + cov = coverage(data_file='.coverage') + cov.combine() + cov.save() + + # Coverage HTML report + if options.coverage == 'html': + html_dir = 'ipy_htmlcov' + shutil.rmtree(html_dir, ignore_errors=True) + print("Writing HTML coverage report to %s/ ... " % html_dir, end="") + sys.stdout.flush() + + # Custom HTML reporter to clean up module names. + from coverage.html import HtmlReporter + class CustomHtmlReporter(HtmlReporter): + def find_code_units(self, morfs): + super(CustomHtmlReporter, self).find_code_units(morfs) + for cu in self.code_units: + nameparts = cu.name.split(os.sep) + if 'IPython' not in nameparts: + continue + ix = nameparts.index('IPython') + cu.name = '.'.join(nameparts[ix:]) + + # Reimplement the html_report method with our custom reporter + cov.get_data() + cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir, + html_title='IPython test coverage', + ) + reporter = CustomHtmlReporter(cov, cov.config) + reporter.report(None) + print('done.') + + # Coverage XML report + elif options.coverage == 'xml': + try: + cov.xml_report(outfile='ipy_coverage.xml') + except CoverageException as e: + print('Generating coverage report failed. Are you running javascript tests only?') + import traceback + traceback.print_exc() + + if failed: + # Ensure that our exit code indicates failure + sys.exit(1) + +argparser = argparse.ArgumentParser(description='Run IPython test suite') +argparser.add_argument('testgroups', nargs='*', + help='Run specified groups of tests. If omitted, run ' + 'all tests.') +argparser.add_argument('--all', action='store_true', + help='Include slow tests not run by default.') +argparser.add_argument('--url', help="URL to use for the JS tests.") +argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int, + help='Run test sections in parallel. This starts as many ' + 'processes as you have cores, or you can specify a number.') +argparser.add_argument('--xunit', action='store_true', + help='Produce Xunit XML results') +argparser.add_argument('--coverage', nargs='?', const=True, default=False, + help="Measure test coverage. Specify 'html' or " + "'xml' to get reports.") +argparser.add_argument('--subproc-streams', default='capture', + help="What to do with stdout/stderr from subprocesses. " + "'capture' (default), 'show' and 'discard' are the options.") + +def default_options(): + """Get an argparse Namespace object with the default arguments, to pass to + :func:`run_iptestall`. + """ + options = argparser.parse_args([]) + options.extra_args = [] + return options + +def main(): + # iptest doesn't work correctly if the working directory is the + # root of the IPython source tree. Tell the user to avoid + # frustration. + if os.path.exists(os.path.join(os.getcwd(), + 'IPython', 'testing', '__main__.py')): + print("Don't run iptest from the IPython source directory", + file=sys.stderr) + sys.exit(1) + # Arguments after -- should be passed through to nose. Argparse treats + # everything after -- as regular positional arguments, so we separate them + # first. + try: + ix = sys.argv.index('--') + except ValueError: + to_parse = sys.argv[1:] + extra_args = [] + else: + to_parse = sys.argv[1:ix] + extra_args = sys.argv[ix+1:] + + options = argparser.parse_args(to_parse) + options.extra_args = extra_args + + run_iptestall(options) + + +if __name__ == '__main__': + main() diff --git a/contrib/python/ipython/py2/IPython/testing/ipunittest.py b/contrib/python/ipython/py2/IPython/testing/ipunittest.py index da059816e2..04ea8320fb 100644 --- a/contrib/python/ipython/py2/IPython/testing/ipunittest.py +++ b/contrib/python/ipython/py2/IPython/testing/ipunittest.py @@ -1,178 +1,178 @@ -"""Experimental code for cleaner support of IPython syntax with unittest. - -In IPython up until 0.10, we've used very hacked up nose machinery for running -tests with IPython special syntax, and this has proved to be extremely slow. -This module provides decorators to try a different approach, stemming from a -conversation Brian and I (FP) had about this problem Sept/09. - -The goal is to be able to easily write simple functions that can be seen by -unittest as tests, and ultimately for these to support doctests with full -IPython syntax. Nose already offers this based on naming conventions and our -hackish plugins, but we are seeking to move away from nose dependencies if -possible. - -This module follows a different approach, based on decorators. - -- A decorator called @ipdoctest can mark any function as having a docstring - that should be viewed as a doctest, but after syntax conversion. - -Authors -------- - -- Fernando Perez <Fernando.Perez@berkeley.edu> -""" - -from __future__ import absolute_import - -#----------------------------------------------------------------------------- -# Copyright (C) 2009-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# Stdlib -import re -import unittest -from doctest import DocTestFinder, DocTestRunner, TestResults - -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - -def count_failures(runner): - """Count number of failures in a doctest runner. - - Code modeled after the summarize() method in doctest. - """ - return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ] - - -class IPython2PythonConverter(object): - """Convert IPython 'syntax' to valid Python. - - Eventually this code may grow to be the full IPython syntax conversion - implementation, but for now it only does prompt convertion.""" - - def __init__(self): - self.rps1 = re.compile(r'In\ \[\d+\]: ') - self.rps2 = re.compile(r'\ \ \ \.\.\.+: ') - self.rout = re.compile(r'Out\[\d+\]: \s*?\n?') - self.pyps1 = '>>> ' - self.pyps2 = '... ' - self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1) - self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2) - - def __call__(self, ds): - """Convert IPython prompts to python ones in a string.""" - from . import globalipapp - - pyps1 = '>>> ' - pyps2 = '... ' - pyout = '' - - dnew = ds - dnew = self.rps1.sub(pyps1, dnew) - dnew = self.rps2.sub(pyps2, dnew) - dnew = self.rout.sub(pyout, dnew) - ip = globalipapp.get_ipython() - - # Convert input IPython source into valid Python. - out = [] - newline = out.append - for line in dnew.splitlines(): - - mps1 = self.rpyps1.match(line) - if mps1 is not None: - prompt, text = mps1.groups() - newline(prompt+ip.prefilter(text, False)) - continue - - mps2 = self.rpyps2.match(line) - if mps2 is not None: - prompt, text = mps2.groups() - newline(prompt+ip.prefilter(text, True)) - continue - - newline(line) - newline('') # ensure a closing newline, needed by doctest - #print "PYSRC:", '\n'.join(out) # dbg - return '\n'.join(out) - - #return dnew - - -class Doc2UnitTester(object): - """Class whose instances act as a decorator for docstring testing. - - In practice we're only likely to need one instance ever, made below (though - no attempt is made at turning it into a singleton, there is no need for - that). - """ - def __init__(self, verbose=False): - """New decorator. - - Parameters - ---------- - - verbose : boolean, optional (False) - Passed to the doctest finder and runner to control verbosity. - """ - self.verbose = verbose - # We can reuse the same finder for all instances - self.finder = DocTestFinder(verbose=verbose, recurse=False) - - def __call__(self, func): - """Use as a decorator: doctest a function's docstring as a unittest. - - This version runs normal doctests, but the idea is to make it later run - ipython syntax instead.""" - - # Capture the enclosing instance with a different name, so the new - # class below can see it without confusion regarding its own 'self' - # that will point to the test instance at runtime - d2u = self - - # Rewrite the function's docstring to have python syntax - if func.__doc__ is not None: - func.__doc__ = ip2py(func.__doc__) - - # Now, create a tester object that is a real unittest instance, so - # normal unittest machinery (or Nose, or Trial) can find it. - class Tester(unittest.TestCase): - def test(self): - # Make a new runner per function to be tested - runner = DocTestRunner(verbose=d2u.verbose) +"""Experimental code for cleaner support of IPython syntax with unittest. + +In IPython up until 0.10, we've used very hacked up nose machinery for running +tests with IPython special syntax, and this has proved to be extremely slow. +This module provides decorators to try a different approach, stemming from a +conversation Brian and I (FP) had about this problem Sept/09. + +The goal is to be able to easily write simple functions that can be seen by +unittest as tests, and ultimately for these to support doctests with full +IPython syntax. Nose already offers this based on naming conventions and our +hackish plugins, but we are seeking to move away from nose dependencies if +possible. + +This module follows a different approach, based on decorators. + +- A decorator called @ipdoctest can mark any function as having a docstring + that should be viewed as a doctest, but after syntax conversion. + +Authors +------- + +- Fernando Perez <Fernando.Perez@berkeley.edu> +""" + +from __future__ import absolute_import + +#----------------------------------------------------------------------------- +# Copyright (C) 2009-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib +import re +import unittest +from doctest import DocTestFinder, DocTestRunner, TestResults + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +def count_failures(runner): + """Count number of failures in a doctest runner. + + Code modeled after the summarize() method in doctest. + """ + return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ] + + +class IPython2PythonConverter(object): + """Convert IPython 'syntax' to valid Python. + + Eventually this code may grow to be the full IPython syntax conversion + implementation, but for now it only does prompt convertion.""" + + def __init__(self): + self.rps1 = re.compile(r'In\ \[\d+\]: ') + self.rps2 = re.compile(r'\ \ \ \.\.\.+: ') + self.rout = re.compile(r'Out\[\d+\]: \s*?\n?') + self.pyps1 = '>>> ' + self.pyps2 = '... ' + self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1) + self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2) + + def __call__(self, ds): + """Convert IPython prompts to python ones in a string.""" + from . import globalipapp + + pyps1 = '>>> ' + pyps2 = '... ' + pyout = '' + + dnew = ds + dnew = self.rps1.sub(pyps1, dnew) + dnew = self.rps2.sub(pyps2, dnew) + dnew = self.rout.sub(pyout, dnew) + ip = globalipapp.get_ipython() + + # Convert input IPython source into valid Python. + out = [] + newline = out.append + for line in dnew.splitlines(): + + mps1 = self.rpyps1.match(line) + if mps1 is not None: + prompt, text = mps1.groups() + newline(prompt+ip.prefilter(text, False)) + continue + + mps2 = self.rpyps2.match(line) + if mps2 is not None: + prompt, text = mps2.groups() + newline(prompt+ip.prefilter(text, True)) + continue + + newline(line) + newline('') # ensure a closing newline, needed by doctest + #print "PYSRC:", '\n'.join(out) # dbg + return '\n'.join(out) + + #return dnew + + +class Doc2UnitTester(object): + """Class whose instances act as a decorator for docstring testing. + + In practice we're only likely to need one instance ever, made below (though + no attempt is made at turning it into a singleton, there is no need for + that). + """ + def __init__(self, verbose=False): + """New decorator. + + Parameters + ---------- + + verbose : boolean, optional (False) + Passed to the doctest finder and runner to control verbosity. + """ + self.verbose = verbose + # We can reuse the same finder for all instances + self.finder = DocTestFinder(verbose=verbose, recurse=False) + + def __call__(self, func): + """Use as a decorator: doctest a function's docstring as a unittest. + + This version runs normal doctests, but the idea is to make it later run + ipython syntax instead.""" + + # Capture the enclosing instance with a different name, so the new + # class below can see it without confusion regarding its own 'self' + # that will point to the test instance at runtime + d2u = self + + # Rewrite the function's docstring to have python syntax + if func.__doc__ is not None: + func.__doc__ = ip2py(func.__doc__) + + # Now, create a tester object that is a real unittest instance, so + # normal unittest machinery (or Nose, or Trial) can find it. + class Tester(unittest.TestCase): + def test(self): + # Make a new runner per function to be tested + runner = DocTestRunner(verbose=d2u.verbose) for the_test in d2u.finder.find(func, func.__name__): runner.run(the_test) - failed = count_failures(runner) - if failed: - # Since we only looked at a single function's docstring, - # failed should contain at most one item. More than that - # is a case we can't handle and should error out on - if len(failed) > 1: - err = "Invalid number of test results:" % failed - raise ValueError(err) - # Report a normal failure. - self.fail('failed doctests: %s' % str(failed[0])) - - # Rename it so test reports have the original signature. - Tester.__name__ = func.__name__ - return Tester - - -def ipdocstring(func): - """Change the function docstring via ip2py. - """ - if func.__doc__ is not None: - func.__doc__ = ip2py(func.__doc__) - return func - - -# Make an instance of the classes for public use -ipdoctest = Doc2UnitTester() -ip2py = IPython2PythonConverter() + failed = count_failures(runner) + if failed: + # Since we only looked at a single function's docstring, + # failed should contain at most one item. More than that + # is a case we can't handle and should error out on + if len(failed) > 1: + err = "Invalid number of test results:" % failed + raise ValueError(err) + # Report a normal failure. + self.fail('failed doctests: %s' % str(failed[0])) + + # Rename it so test reports have the original signature. + Tester.__name__ = func.__name__ + return Tester + + +def ipdocstring(func): + """Change the function docstring via ip2py. + """ + if func.__doc__ is not None: + func.__doc__ = ip2py(func.__doc__) + return func + + +# Make an instance of the classes for public use +ipdoctest = Doc2UnitTester() +ip2py = IPython2PythonConverter() diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/Makefile b/contrib/python/ipython/py2/IPython/testing/plugin/Makefile index 6f999a38fd..d57d198f15 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/Makefile +++ b/contrib/python/ipython/py2/IPython/testing/plugin/Makefile @@ -1,74 +1,74 @@ -# Set this prefix to where you want to install the plugin -PREFIX=/usr/local - -NOSE0=nosetests -vs --with-doctest --doctest-tests --detailed-errors -NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt \ ---detailed-errors - -SRC=ipdoctest.py setup.py ../decorators.py - -# Default target for clean 'make' -default: interactiveshell - -# The actual plugin installation -plugin: IPython_doctest_plugin.egg-info - -# Simple targets that test one thing -simple: plugin simple.py - $(NOSE) simple.py - -dtest: plugin dtexample.py - $(NOSE) dtexample.py - -rtest: plugin test_refs.py - $(NOSE) test_refs.py - -test: plugin dtexample.py - $(NOSE) dtexample.py test*.py test*.txt - -deb: plugin dtexample.py - $(NOSE) test_combo.txt - -# IPython tests -deco: - $(NOSE0) IPython.testing.decorators - -magic: plugin - $(NOSE) IPython.core.magic - -excolors: plugin - $(NOSE) IPython.core.excolors - -interactiveshell: plugin - $(NOSE) IPython.core.interactiveshell - -strd: plugin - $(NOSE) IPython.core.strdispatch - -engine: plugin - $(NOSE) IPython.kernel - -tf: plugin - $(NOSE) IPython.config.traitlets - -# All of ipython itself -ipython: plugin - $(NOSE) IPython - - -# Combined targets -sr: rtest strd - -base: dtest rtest test strd deco - -quick: base interactiveshell ipipe - -all: base ipython - -# Main plugin and cleanup -IPython_doctest_plugin.egg-info: $(SRC) - python setup.py install --prefix=$(PREFIX) - touch $@ - -clean: - rm -rf IPython_doctest_plugin.egg-info *~ *pyc build/ dist/ +# Set this prefix to where you want to install the plugin +PREFIX=/usr/local + +NOSE0=nosetests -vs --with-doctest --doctest-tests --detailed-errors +NOSE=nosetests -vvs --with-ipdoctest --doctest-tests --doctest-extension=txt \ +--detailed-errors + +SRC=ipdoctest.py setup.py ../decorators.py + +# Default target for clean 'make' +default: interactiveshell + +# The actual plugin installation +plugin: IPython_doctest_plugin.egg-info + +# Simple targets that test one thing +simple: plugin simple.py + $(NOSE) simple.py + +dtest: plugin dtexample.py + $(NOSE) dtexample.py + +rtest: plugin test_refs.py + $(NOSE) test_refs.py + +test: plugin dtexample.py + $(NOSE) dtexample.py test*.py test*.txt + +deb: plugin dtexample.py + $(NOSE) test_combo.txt + +# IPython tests +deco: + $(NOSE0) IPython.testing.decorators + +magic: plugin + $(NOSE) IPython.core.magic + +excolors: plugin + $(NOSE) IPython.core.excolors + +interactiveshell: plugin + $(NOSE) IPython.core.interactiveshell + +strd: plugin + $(NOSE) IPython.core.strdispatch + +engine: plugin + $(NOSE) IPython.kernel + +tf: plugin + $(NOSE) IPython.config.traitlets + +# All of ipython itself +ipython: plugin + $(NOSE) IPython + + +# Combined targets +sr: rtest strd + +base: dtest rtest test strd deco + +quick: base interactiveshell ipipe + +all: base ipython + +# Main plugin and cleanup +IPython_doctest_plugin.egg-info: $(SRC) + python setup.py install --prefix=$(PREFIX) + touch $@ + +clean: + rm -rf IPython_doctest_plugin.egg-info *~ *pyc build/ dist/ diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/README.txt b/contrib/python/ipython/py2/IPython/testing/plugin/README.txt index 6b34f9e5e1..e08380d9de 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/README.txt +++ b/contrib/python/ipython/py2/IPython/testing/plugin/README.txt @@ -1,39 +1,39 @@ -======================================================= - Nose plugin with IPython and extension module support -======================================================= - -This directory provides the key functionality for test support that IPython -needs as a nose plugin, which can be installed for use in projects other than -IPython. - -The presence of a Makefile here is mostly for development and debugging -purposes as it only provides a few shorthand commands. You can manually -install the plugin by using standard Python procedures (``setup.py install`` -with appropriate arguments). - -To install the plugin using the Makefile, edit its first line to reflect where -you'd like the installation. If you want it system-wide, you may want to edit -the install line in the plugin target to use sudo and no prefix:: - - sudo python setup.py install - -instead of the code using `--prefix` that's in there. - -Once you've set the prefix, simply build/install the plugin with:: - - make - -and run the tests with:: - - make test - -You should see output similar to:: - - maqroll[plugin]> make test - nosetests -s --with-ipdoctest --doctest-tests dtexample.py - .. - ---------------------------------------------------------------------- - Ran 2 tests in 0.016s - - OK - +======================================================= + Nose plugin with IPython and extension module support +======================================================= + +This directory provides the key functionality for test support that IPython +needs as a nose plugin, which can be installed for use in projects other than +IPython. + +The presence of a Makefile here is mostly for development and debugging +purposes as it only provides a few shorthand commands. You can manually +install the plugin by using standard Python procedures (``setup.py install`` +with appropriate arguments). + +To install the plugin using the Makefile, edit its first line to reflect where +you'd like the installation. If you want it system-wide, you may want to edit +the install line in the plugin target to use sudo and no prefix:: + + sudo python setup.py install + +instead of the code using `--prefix` that's in there. + +Once you've set the prefix, simply build/install the plugin with:: + + make + +and run the tests with:: + + make test + +You should see output similar to:: + + maqroll[plugin]> make test + nosetests -s --with-ipdoctest --doctest-tests dtexample.py + .. + ---------------------------------------------------------------------- + Ran 2 tests in 0.016s + + OK + diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/dtexample.py b/contrib/python/ipython/py2/IPython/testing/plugin/dtexample.py index 5e02629bf7..081bf35571 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/dtexample.py +++ b/contrib/python/ipython/py2/IPython/testing/plugin/dtexample.py @@ -1,158 +1,158 @@ -"""Simple example using doctests. - -This file just contains doctests both using plain python and IPython prompts. -All tests should be loaded by nose. -""" -from __future__ import print_function - -def pyfunc(): - """Some pure python tests... - - >>> pyfunc() - 'pyfunc' - - >>> import os - - >>> 2+3 - 5 - - >>> for i in range(3): - ... print(i, end=' ') - ... print(i+1, end=' ') - ... - 0 1 1 2 2 3 - """ - return 'pyfunc' - -def ipfunc(): - """Some ipython tests... - - In [1]: import os - - In [3]: 2+3 - Out[3]: 5 - - In [26]: for i in range(3): - ....: print(i, end=' ') - ....: print(i+1, end=' ') - ....: - 0 1 1 2 2 3 - - - Examples that access the operating system work: - - In [1]: !echo hello - hello - - In [2]: !echo hello > /tmp/foo_iptest - - In [3]: !cat /tmp/foo_iptest - hello - - In [4]: rm -f /tmp/foo_iptest - - It's OK to use '_' for the last result, but do NOT try to use IPython's - numbered history of _NN outputs, since those won't exist under the - doctest environment: - - In [7]: 'hi' - Out[7]: 'hi' - - In [8]: print(repr(_)) - 'hi' - - In [7]: 3+4 - Out[7]: 7 - - In [8]: _+3 - Out[8]: 10 - - In [9]: ipfunc() - Out[9]: 'ipfunc' - """ - return 'ipfunc' - - -def ranfunc(): - """A function with some random output. - - Normal examples are verified as usual: - >>> 1+3 - 4 - - But if you put '# random' in the output, it is ignored: - >>> 1+3 - junk goes here... # random - - >>> 1+2 - again, anything goes #random - if multiline, the random mark is only needed once. - - >>> 1+2 - You can also put the random marker at the end: - # random - - >>> 1+2 - # random - .. or at the beginning. - - More correct input is properly verified: - >>> ranfunc() - 'ranfunc' - """ - return 'ranfunc' - - -def random_all(): - """A function where we ignore the output of ALL examples. - - Examples: - - # all-random - - This mark tells the testing machinery that all subsequent examples should - be treated as random (ignoring their output). They are still executed, - so if a they raise an error, it will be detected as such, but their - output is completely ignored. - - >>> 1+3 - junk goes here... - - >>> 1+3 - klasdfj; - - >>> 1+2 - again, anything goes - blah... - """ - pass - -def iprand(): - """Some ipython tests with random output. - - In [7]: 3+4 - Out[7]: 7 - - In [8]: print('hello') - world # random - - In [9]: iprand() - Out[9]: 'iprand' - """ - return 'iprand' - -def iprand_all(): - """Some ipython tests with fully random output. - - # all-random - - In [7]: 1 - Out[7]: 99 - - In [8]: print('hello') - world - - In [9]: iprand_all() - Out[9]: 'junk' - """ - return 'iprand_all' +"""Simple example using doctests. + +This file just contains doctests both using plain python and IPython prompts. +All tests should be loaded by nose. +""" +from __future__ import print_function + +def pyfunc(): + """Some pure python tests... + + >>> pyfunc() + 'pyfunc' + + >>> import os + + >>> 2+3 + 5 + + >>> for i in range(3): + ... print(i, end=' ') + ... print(i+1, end=' ') + ... + 0 1 1 2 2 3 + """ + return 'pyfunc' + +def ipfunc(): + """Some ipython tests... + + In [1]: import os + + In [3]: 2+3 + Out[3]: 5 + + In [26]: for i in range(3): + ....: print(i, end=' ') + ....: print(i+1, end=' ') + ....: + 0 1 1 2 2 3 + + + Examples that access the operating system work: + + In [1]: !echo hello + hello + + In [2]: !echo hello > /tmp/foo_iptest + + In [3]: !cat /tmp/foo_iptest + hello + + In [4]: rm -f /tmp/foo_iptest + + It's OK to use '_' for the last result, but do NOT try to use IPython's + numbered history of _NN outputs, since those won't exist under the + doctest environment: + + In [7]: 'hi' + Out[7]: 'hi' + + In [8]: print(repr(_)) + 'hi' + + In [7]: 3+4 + Out[7]: 7 + + In [8]: _+3 + Out[8]: 10 + + In [9]: ipfunc() + Out[9]: 'ipfunc' + """ + return 'ipfunc' + + +def ranfunc(): + """A function with some random output. + + Normal examples are verified as usual: + >>> 1+3 + 4 + + But if you put '# random' in the output, it is ignored: + >>> 1+3 + junk goes here... # random + + >>> 1+2 + again, anything goes #random + if multiline, the random mark is only needed once. + + >>> 1+2 + You can also put the random marker at the end: + # random + + >>> 1+2 + # random + .. or at the beginning. + + More correct input is properly verified: + >>> ranfunc() + 'ranfunc' + """ + return 'ranfunc' + + +def random_all(): + """A function where we ignore the output of ALL examples. + + Examples: + + # all-random + + This mark tells the testing machinery that all subsequent examples should + be treated as random (ignoring their output). They are still executed, + so if a they raise an error, it will be detected as such, but their + output is completely ignored. + + >>> 1+3 + junk goes here... + + >>> 1+3 + klasdfj; + + >>> 1+2 + again, anything goes + blah... + """ + pass + +def iprand(): + """Some ipython tests with random output. + + In [7]: 3+4 + Out[7]: 7 + + In [8]: print('hello') + world # random + + In [9]: iprand() + Out[9]: 'iprand' + """ + return 'iprand' + +def iprand_all(): + """Some ipython tests with fully random output. + + # all-random + + In [7]: 1 + Out[7]: 99 + + In [8]: print('hello') + world + + In [9]: iprand_all() + Out[9]: 'junk' + """ + return 'iprand_all' diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/ipdoctest.py b/contrib/python/ipython/py2/IPython/testing/plugin/ipdoctest.py index bc750e0efd..64e7e536f8 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/ipdoctest.py +++ b/contrib/python/ipython/py2/IPython/testing/plugin/ipdoctest.py @@ -1,769 +1,769 @@ -"""Nose Plugin that supports IPython doctests. - -Limitations: - -- When generating examples for use as doctests, make sure that you have - pretty-printing OFF. This can be done either by setting the - ``PlainTextFormatter.pprint`` option in your configuration file to False, or - by interactively disabling it with %Pprint. This is required so that IPython - output matches that of normal Python, which is used by doctest for internal - execution. - -- Do not rely on specific prompt numbers for results (such as using - '_34==True', for example). For IPython tests run via an external process the - prompt numbers may be different, and IPython tests run as normal python code - won't even have these special _NN variables set at all. -""" - -#----------------------------------------------------------------------------- -# Module imports - -# From the standard library -import doctest -import inspect -import logging -import os -import re -import sys - +"""Nose Plugin that supports IPython doctests. + +Limitations: + +- When generating examples for use as doctests, make sure that you have + pretty-printing OFF. This can be done either by setting the + ``PlainTextFormatter.pprint`` option in your configuration file to False, or + by interactively disabling it with %Pprint. This is required so that IPython + output matches that of normal Python, which is used by doctest for internal + execution. + +- Do not rely on specific prompt numbers for results (such as using + '_34==True', for example). For IPython tests run via an external process the + prompt numbers may be different, and IPython tests run as normal python code + won't even have these special _NN variables set at all. +""" + +#----------------------------------------------------------------------------- +# Module imports + +# From the standard library +import doctest +import inspect +import logging +import os +import re +import sys + from testpath import modified_env -from inspect import getmodule - -# We are overriding the default doctest runner, so we need to import a few -# things from doctest directly -from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE, - _unittest_reportflags, DocTestRunner, - _extract_future_flags, pdb, _OutputRedirectingPdb, - _exception_traceback, - linecache) - -# Third-party modules - -from nose.plugins import doctests, Plugin +from inspect import getmodule + +# We are overriding the default doctest runner, so we need to import a few +# things from doctest directly +from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE, + _unittest_reportflags, DocTestRunner, + _extract_future_flags, pdb, _OutputRedirectingPdb, + _exception_traceback, + linecache) + +# Third-party modules + +from nose.plugins import doctests, Plugin from nose.util import anyp, tolist - -# Our own imports -from IPython.utils.py3compat import builtin_mod, PY3, getcwd - -if PY3: - from io import StringIO -else: - from StringIO import StringIO - -#----------------------------------------------------------------------------- -# Module globals and other constants -#----------------------------------------------------------------------------- - -log = logging.getLogger(__name__) - - -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - -def is_extension_module(filename): - """Return whether the given filename is an extension module. - - This simply checks that the extension is either .so or .pyd. - """ - return os.path.splitext(filename)[1].lower() in ('.so','.pyd') - - -class DocTestSkip(object): - """Object wrapper for doctests to be skipped.""" - - ds_skip = """Doctest to skip. - >>> 1 #doctest: +SKIP - """ - - def __init__(self,obj): - self.obj = obj - - def __getattribute__(self,key): - if key == '__doc__': - return DocTestSkip.ds_skip - else: - return getattr(object.__getattribute__(self,'obj'),key) - -# Modified version of the one in the stdlib, that fixes a python bug (doctests -# not found in extension modules, http://bugs.python.org/issue3158) -class DocTestFinder(doctest.DocTestFinder): - - def _from_module(self, module, object): - """ - Return true if the given object is defined in the given - module. - """ - if module is None: - return True - elif inspect.isfunction(object): - return module.__dict__ is object.__globals__ - elif inspect.isbuiltin(object): - return module.__name__ == object.__module__ - elif inspect.isclass(object): - return module.__name__ == object.__module__ - elif inspect.ismethod(object): - # This one may be a bug in cython that fails to correctly set the - # __module__ attribute of methods, but since the same error is easy - # to make by extension code writers, having this safety in place - # isn't such a bad idea - return module.__name__ == object.__self__.__class__.__module__ - elif inspect.getmodule(object) is not None: - return module is inspect.getmodule(object) - elif hasattr(object, '__module__'): - return module.__name__ == object.__module__ - elif isinstance(object, property): - return True # [XX] no way not be sure. - elif inspect.ismethoddescriptor(object): - # Unbound PyQt signals reach this point in Python 3.4b3, and we want - # to avoid throwing an error. See also http://bugs.python.org/issue3158 - return False - else: - raise ValueError("object must be a class or function, got %r" % object) - - def _find(self, tests, obj, name, module, source_lines, globs, seen): - """ - Find tests for the given object and any contained objects, and - add them to `tests`. - """ - print('_find for:', obj, name, module) # dbg - if hasattr(obj,"skip_doctest"): - #print 'SKIPPING DOCTEST FOR:',obj # dbg - obj = DocTestSkip(obj) - - doctest.DocTestFinder._find(self,tests, obj, name, module, - source_lines, globs, seen) - - # Below we re-run pieces of the above method with manual modifications, - # because the original code is buggy and fails to correctly identify - # doctests in extension modules. - - # Local shorthands + +# Our own imports +from IPython.utils.py3compat import builtin_mod, PY3, getcwd + +if PY3: + from io import StringIO +else: + from StringIO import StringIO + +#----------------------------------------------------------------------------- +# Module globals and other constants +#----------------------------------------------------------------------------- + +log = logging.getLogger(__name__) + + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +def is_extension_module(filename): + """Return whether the given filename is an extension module. + + This simply checks that the extension is either .so or .pyd. + """ + return os.path.splitext(filename)[1].lower() in ('.so','.pyd') + + +class DocTestSkip(object): + """Object wrapper for doctests to be skipped.""" + + ds_skip = """Doctest to skip. + >>> 1 #doctest: +SKIP + """ + + def __init__(self,obj): + self.obj = obj + + def __getattribute__(self,key): + if key == '__doc__': + return DocTestSkip.ds_skip + else: + return getattr(object.__getattribute__(self,'obj'),key) + +# Modified version of the one in the stdlib, that fixes a python bug (doctests +# not found in extension modules, http://bugs.python.org/issue3158) +class DocTestFinder(doctest.DocTestFinder): + + def _from_module(self, module, object): + """ + Return true if the given object is defined in the given + module. + """ + if module is None: + return True + elif inspect.isfunction(object): + return module.__dict__ is object.__globals__ + elif inspect.isbuiltin(object): + return module.__name__ == object.__module__ + elif inspect.isclass(object): + return module.__name__ == object.__module__ + elif inspect.ismethod(object): + # This one may be a bug in cython that fails to correctly set the + # __module__ attribute of methods, but since the same error is easy + # to make by extension code writers, having this safety in place + # isn't such a bad idea + return module.__name__ == object.__self__.__class__.__module__ + elif inspect.getmodule(object) is not None: + return module is inspect.getmodule(object) + elif hasattr(object, '__module__'): + return module.__name__ == object.__module__ + elif isinstance(object, property): + return True # [XX] no way not be sure. + elif inspect.ismethoddescriptor(object): + # Unbound PyQt signals reach this point in Python 3.4b3, and we want + # to avoid throwing an error. See also http://bugs.python.org/issue3158 + return False + else: + raise ValueError("object must be a class or function, got %r" % object) + + def _find(self, tests, obj, name, module, source_lines, globs, seen): + """ + Find tests for the given object and any contained objects, and + add them to `tests`. + """ + print('_find for:', obj, name, module) # dbg + if hasattr(obj,"skip_doctest"): + #print 'SKIPPING DOCTEST FOR:',obj # dbg + obj = DocTestSkip(obj) + + doctest.DocTestFinder._find(self,tests, obj, name, module, + source_lines, globs, seen) + + # Below we re-run pieces of the above method with manual modifications, + # because the original code is buggy and fails to correctly identify + # doctests in extension modules. + + # Local shorthands from inspect import isroutine, isclass - - # Look for tests in a module's contained objects. - if inspect.ismodule(obj) and self._recurse: - for valname, val in obj.__dict__.items(): - valname1 = '%s.%s' % (name, valname) - if ( (isroutine(val) or isclass(val)) - and self._from_module(module, val) ): - - self._find(tests, val, valname1, module, source_lines, - globs, seen) - - # Look for tests in a class's contained objects. - if inspect.isclass(obj) and self._recurse: - #print 'RECURSE into class:',obj # dbg - for valname, val in obj.__dict__.items(): - # Special handling for staticmethod/classmethod. - if isinstance(val, staticmethod): - val = getattr(obj, valname) - if isinstance(val, classmethod): - val = getattr(obj, valname).__func__ - - # Recurse to methods, properties, and nested classes. - if ((inspect.isfunction(val) or inspect.isclass(val) or - inspect.ismethod(val) or - isinstance(val, property)) and - self._from_module(module, val)): - valname = '%s.%s' % (name, valname) - self._find(tests, val, valname, module, source_lines, - globs, seen) - - -class IPDoctestOutputChecker(doctest.OutputChecker): - """Second-chance checker with support for random tests. - - If the default comparison doesn't pass, this checker looks in the expected - output string for flags that tell us to ignore the output. - """ - - random_re = re.compile(r'#\s*random\s+') - - def check_output(self, want, got, optionflags): - """Check output, accepting special markers embedded in the output. - - If the output didn't pass the default validation but the special string - '#random' is included, we accept it.""" - - # Let the original tester verify first, in case people have valid tests - # that happen to have a comment saying '#random' embedded in. - ret = doctest.OutputChecker.check_output(self, want, got, - optionflags) - if not ret and self.random_re.search(want): - #print >> sys.stderr, 'RANDOM OK:',want # dbg - return True - - return ret - - -class DocTestCase(doctests.DocTestCase): - """Proxy for DocTestCase: provides an address() method that - returns the correct address for the doctest case. Otherwise - acts as a proxy to the test case. To provide hints for address(), - an obj may also be passed -- this will be used as the test object - for purposes of determining the test address, if it is provided. - """ - - # Note: this method was taken from numpy's nosetester module. - - # Subclass nose.plugins.doctests.DocTestCase to work around a bug in - # its constructor that blocks non-default arguments from being passed - # down into doctest.DocTestCase - - def __init__(self, test, optionflags=0, setUp=None, tearDown=None, - checker=None, obj=None, result_var='_'): - self._result_var = result_var - doctests.DocTestCase.__init__(self, test, - optionflags=optionflags, - setUp=setUp, tearDown=tearDown, - checker=checker) - # Now we must actually copy the original constructor from the stdlib - # doctest class, because we can't call it directly and a bug in nose - # means it never gets passed the right arguments. - - self._dt_optionflags = optionflags - self._dt_checker = checker - self._dt_test = test - self._dt_test_globs_ori = test.globs - self._dt_setUp = setUp - self._dt_tearDown = tearDown - - # XXX - store this runner once in the object! - runner = IPDocTestRunner(optionflags=optionflags, - checker=checker, verbose=False) - self._dt_runner = runner - - - # Each doctest should remember the directory it was loaded from, so - # things like %run work without too many contortions - self._ori_dir = os.path.dirname(test.filename) - - # Modified runTest from the default stdlib - def runTest(self): - test = self._dt_test - runner = self._dt_runner - - old = sys.stdout - new = StringIO() - optionflags = self._dt_optionflags - - if not (optionflags & REPORTING_FLAGS): - # The option flags don't include any reporting flags, - # so add the default reporting flags - optionflags |= _unittest_reportflags - - try: - # Save our current directory and switch out to the one where the - # test was originally created, in case another doctest did a - # directory change. We'll restore this in the finally clause. - curdir = getcwd() - #print 'runTest in dir:', self._ori_dir # dbg - os.chdir(self._ori_dir) - - runner.DIVIDER = "-"*70 - failures, tries = runner.run(test,out=new.write, - clear_globs=False) - finally: - sys.stdout = old - os.chdir(curdir) - - if failures: - raise self.failureException(self.format_failure(new.getvalue())) - - def setUp(self): - """Modified test setup that syncs with ipython namespace""" - #print "setUp test", self._dt_test.examples # dbg - if isinstance(self._dt_test.examples[0], IPExample): - # for IPython examples *only*, we swap the globals with the ipython - # namespace, after updating it with the globals (which doctest - # fills with the necessary info from the module being tested). - self.user_ns_orig = {} - self.user_ns_orig.update(_ip.user_ns) - _ip.user_ns.update(self._dt_test.globs) - # We must remove the _ key in the namespace, so that Python's - # doctest code sets it naturally - _ip.user_ns.pop('_', None) - _ip.user_ns['__builtins__'] = builtin_mod - self._dt_test.globs = _ip.user_ns - - super(DocTestCase, self).setUp() - - def tearDown(self): - - # Undo the test.globs reassignment we made, so that the parent class - # teardown doesn't destroy the ipython namespace - if isinstance(self._dt_test.examples[0], IPExample): - self._dt_test.globs = self._dt_test_globs_ori - _ip.user_ns.clear() - _ip.user_ns.update(self.user_ns_orig) - - # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but - # it does look like one to me: its tearDown method tries to run - # - # delattr(builtin_mod, self._result_var) - # - # without checking that the attribute really is there; it implicitly - # assumes it should have been set via displayhook. But if the - # displayhook was never called, this doesn't necessarily happen. I - # haven't been able to find a little self-contained example outside of - # ipython that would show the problem so I can report it to the nose - # team, but it does happen a lot in our code. - # - # So here, we just protect as narrowly as possible by trapping an - # attribute error whose message would be the name of self._result_var, - # and letting any other error propagate. - try: - super(DocTestCase, self).tearDown() - except AttributeError as exc: - if exc.args[0] != self._result_var: - raise - - -# A simple subclassing of the original with a different class name, so we can -# distinguish and treat differently IPython examples from pure python ones. -class IPExample(doctest.Example): pass - - -class IPExternalExample(doctest.Example): - """Doctest examples to be run in an external process.""" - - def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, - options=None): - # Parent constructor - doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options) - - # An EXTRA newline is needed to prevent pexpect hangs - self.source += '\n' - - -class IPDocTestParser(doctest.DocTestParser): - """ - A class used to parse strings containing doctest examples. - - Note: This is a version modified to properly recognize IPython input and - convert any IPython examples into valid Python ones. - """ - # This regular expression is used to find doctest examples in a - # string. It defines three groups: `source` is the source code - # (including leading indentation and prompts); `indent` is the - # indentation of the first (PS1) line of the source code; and - # `want` is the expected output (including leading indentation). - - # Classic Python prompts or default IPython ones - _PS1_PY = r'>>>' - _PS2_PY = r'\.\.\.' - - _PS1_IP = r'In\ \[\d+\]:' - _PS2_IP = r'\ \ \ \.\.\.+:' - - _RE_TPL = r''' - # Source consists of a PS1 line followed by zero or more PS2 lines. - (?P<source> - (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line - (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines - \n? # a newline - # Want consists of any non-blank lines that do not start with PS1. - (?P<want> (?:(?![ ]*$) # Not a blank line - (?![ ]*%s) # Not a line starting with PS1 - (?![ ]*%s) # Not a line starting with PS2 - .*$\n? # But any other line - )*) - ''' - - _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY), - re.MULTILINE | re.VERBOSE) - - _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP), - re.MULTILINE | re.VERBOSE) - - # Mark a test as being fully random. In this case, we simply append the - # random marker ('#random') to each individual example's output. This way - # we don't need to modify any other code. - _RANDOM_TEST = re.compile(r'#\s*all-random\s+') - - # Mark tests to be executed in an external process - currently unsupported. - _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL') - - def ip2py(self,source): - """Convert input IPython source into valid Python.""" - block = _ip.input_transformer_manager.transform_cell(source) - if len(block.splitlines()) == 1: - return _ip.prefilter(block) - else: - return block - - def parse(self, string, name='<string>'): - """ - Divide the given string into examples and intervening text, - and return them as a list of alternating Examples and strings. - Line numbers for the Examples are 0-based. The optional - argument `name` is a name identifying this string, and is only - used for error messages. - """ - - #print 'Parse string:\n',string # dbg - - string = string.expandtabs() - # If all lines begin with the same indentation, then strip it. - min_indent = self._min_indent(string) - if min_indent > 0: - string = '\n'.join([l[min_indent:] for l in string.split('\n')]) - - output = [] - charno, lineno = 0, 0 - - # We make 'all random' tests by adding the '# random' mark to every - # block of output in the test. - if self._RANDOM_TEST.search(string): - random_marker = '\n# random' - else: - random_marker = '' - - # Whether to convert the input from ipython to python syntax - ip2py = False - # Find all doctest examples in the string. First, try them as Python - # examples, then as IPython ones - terms = list(self._EXAMPLE_RE_PY.finditer(string)) - if terms: - # Normal Python example - #print '-'*70 # dbg - #print 'PyExample, Source:\n',string # dbg - #print '-'*70 # dbg - Example = doctest.Example - else: - # It's an ipython example. Note that IPExamples are run - # in-process, so their syntax must be turned into valid python. - # IPExternalExamples are run out-of-process (via pexpect) so they - # don't need any filtering (a real ipython will be executing them). - terms = list(self._EXAMPLE_RE_IP.finditer(string)) - if self._EXTERNAL_IP.search(string): - #print '-'*70 # dbg - #print 'IPExternalExample, Source:\n',string # dbg - #print '-'*70 # dbg - Example = IPExternalExample - else: - #print '-'*70 # dbg - #print 'IPExample, Source:\n',string # dbg - #print '-'*70 # dbg - Example = IPExample - ip2py = True - - for m in terms: - # Add the pre-example text to `output`. - output.append(string[charno:m.start()]) - # Update lineno (lines before this example) - lineno += string.count('\n', charno, m.start()) - # Extract info from the regexp match. - (source, options, want, exc_msg) = \ - self._parse_example(m, name, lineno,ip2py) - - # Append the random-output marker (it defaults to empty in most - # cases, it's only non-empty for 'all-random' tests): - want += random_marker - - if Example is IPExternalExample: - options[doctest.NORMALIZE_WHITESPACE] = True - want += '\n' - - # Create an Example, and add it to the list. - if not self._IS_BLANK_OR_COMMENT(source): - output.append(Example(source, want, exc_msg, - lineno=lineno, - indent=min_indent+len(m.group('indent')), - options=options)) - # Update lineno (lines inside this example) - lineno += string.count('\n', m.start(), m.end()) - # Update charno. - charno = m.end() - # Add any remaining post-example text to `output`. - output.append(string[charno:]) - return output - - def _parse_example(self, m, name, lineno,ip2py=False): - """ - Given a regular expression match from `_EXAMPLE_RE` (`m`), - return a pair `(source, want)`, where `source` is the matched - example's source code (with prompts and indentation stripped); - and `want` is the example's expected output (with indentation - stripped). - - `name` is the string's name, and `lineno` is the line number - where the example starts; both are used for error messages. - - Optional: - `ip2py`: if true, filter the input via IPython to convert the syntax - into valid python. - """ - - # Get the example's indentation level. - indent = len(m.group('indent')) - - # Divide source into lines; check that they're properly - # indented; and then strip their indentation & prompts. - source_lines = m.group('source').split('\n') - - # We're using variable-length input prompts - ps1 = m.group('ps1') - ps2 = m.group('ps2') - ps1_len = len(ps1) - - self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len) - if ps2: - self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno) - - source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines]) - - if ip2py: - # Convert source input from IPython into valid Python syntax - source = self.ip2py(source) - - # Divide want into lines; check that it's properly indented; and - # then strip the indentation. Spaces before the last newline should - # be preserved, so plain rstrip() isn't good enough. - want = m.group('want') - want_lines = want.split('\n') - if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): - del want_lines[-1] # forget final newline & spaces after it - self._check_prefix(want_lines, ' '*indent, name, - lineno + len(source_lines)) - - # Remove ipython output prompt that might be present in the first line - want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0]) - - want = '\n'.join([wl[indent:] for wl in want_lines]) - - # If `want` contains a traceback message, then extract it. - m = self._EXCEPTION_RE.match(want) - if m: - exc_msg = m.group('msg') - else: - exc_msg = None - - # Extract options from the source. - options = self._find_options(source, name, lineno) - - return source, options, want, exc_msg - - def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len): - """ - Given the lines of a source string (including prompts and - leading indentation), check to make sure that every prompt is - followed by a space character. If any line is not followed by - a space character, then raise ValueError. - - Note: IPython-modified version which takes the input prompt length as a - parameter, so that prompts of variable length can be dealt with. - """ - space_idx = indent+ps1_len - min_len = space_idx+1 - for i, line in enumerate(lines): - if len(line) >= min_len and line[space_idx] != ' ': - raise ValueError('line %r of the docstring for %s ' - 'lacks blank after %s: %r' % - (lineno+i+1, name, - line[indent:space_idx], line)) - - -SKIP = doctest.register_optionflag('SKIP') - - -class IPDocTestRunner(doctest.DocTestRunner,object): - """Test runner that synchronizes the IPython namespace with test globals. - """ - - def run(self, test, compileflags=None, out=None, clear_globs=True): - - # Hack: ipython needs access to the execution context of the example, - # so that it can propagate user variables loaded by %run into - # test.globs. We put them here into our modified %run as a function - # attribute. Our new %run will then only make the namespace update - # when called (rather than unconconditionally updating test.globs here - # for all examples, most of which won't be calling %run anyway). - #_ip._ipdoctest_test_globs = test.globs - #_ip._ipdoctest_test_filename = test.filename - - test.globs.update(_ip.user_ns) - + + # Look for tests in a module's contained objects. + if inspect.ismodule(obj) and self._recurse: + for valname, val in obj.__dict__.items(): + valname1 = '%s.%s' % (name, valname) + if ( (isroutine(val) or isclass(val)) + and self._from_module(module, val) ): + + self._find(tests, val, valname1, module, source_lines, + globs, seen) + + # Look for tests in a class's contained objects. + if inspect.isclass(obj) and self._recurse: + #print 'RECURSE into class:',obj # dbg + for valname, val in obj.__dict__.items(): + # Special handling for staticmethod/classmethod. + if isinstance(val, staticmethod): + val = getattr(obj, valname) + if isinstance(val, classmethod): + val = getattr(obj, valname).__func__ + + # Recurse to methods, properties, and nested classes. + if ((inspect.isfunction(val) or inspect.isclass(val) or + inspect.ismethod(val) or + isinstance(val, property)) and + self._from_module(module, val)): + valname = '%s.%s' % (name, valname) + self._find(tests, val, valname, module, source_lines, + globs, seen) + + +class IPDoctestOutputChecker(doctest.OutputChecker): + """Second-chance checker with support for random tests. + + If the default comparison doesn't pass, this checker looks in the expected + output string for flags that tell us to ignore the output. + """ + + random_re = re.compile(r'#\s*random\s+') + + def check_output(self, want, got, optionflags): + """Check output, accepting special markers embedded in the output. + + If the output didn't pass the default validation but the special string + '#random' is included, we accept it.""" + + # Let the original tester verify first, in case people have valid tests + # that happen to have a comment saying '#random' embedded in. + ret = doctest.OutputChecker.check_output(self, want, got, + optionflags) + if not ret and self.random_re.search(want): + #print >> sys.stderr, 'RANDOM OK:',want # dbg + return True + + return ret + + +class DocTestCase(doctests.DocTestCase): + """Proxy for DocTestCase: provides an address() method that + returns the correct address for the doctest case. Otherwise + acts as a proxy to the test case. To provide hints for address(), + an obj may also be passed -- this will be used as the test object + for purposes of determining the test address, if it is provided. + """ + + # Note: this method was taken from numpy's nosetester module. + + # Subclass nose.plugins.doctests.DocTestCase to work around a bug in + # its constructor that blocks non-default arguments from being passed + # down into doctest.DocTestCase + + def __init__(self, test, optionflags=0, setUp=None, tearDown=None, + checker=None, obj=None, result_var='_'): + self._result_var = result_var + doctests.DocTestCase.__init__(self, test, + optionflags=optionflags, + setUp=setUp, tearDown=tearDown, + checker=checker) + # Now we must actually copy the original constructor from the stdlib + # doctest class, because we can't call it directly and a bug in nose + # means it never gets passed the right arguments. + + self._dt_optionflags = optionflags + self._dt_checker = checker + self._dt_test = test + self._dt_test_globs_ori = test.globs + self._dt_setUp = setUp + self._dt_tearDown = tearDown + + # XXX - store this runner once in the object! + runner = IPDocTestRunner(optionflags=optionflags, + checker=checker, verbose=False) + self._dt_runner = runner + + + # Each doctest should remember the directory it was loaded from, so + # things like %run work without too many contortions + self._ori_dir = os.path.dirname(test.filename) + + # Modified runTest from the default stdlib + def runTest(self): + test = self._dt_test + runner = self._dt_runner + + old = sys.stdout + new = StringIO() + optionflags = self._dt_optionflags + + if not (optionflags & REPORTING_FLAGS): + # The option flags don't include any reporting flags, + # so add the default reporting flags + optionflags |= _unittest_reportflags + + try: + # Save our current directory and switch out to the one where the + # test was originally created, in case another doctest did a + # directory change. We'll restore this in the finally clause. + curdir = getcwd() + #print 'runTest in dir:', self._ori_dir # dbg + os.chdir(self._ori_dir) + + runner.DIVIDER = "-"*70 + failures, tries = runner.run(test,out=new.write, + clear_globs=False) + finally: + sys.stdout = old + os.chdir(curdir) + + if failures: + raise self.failureException(self.format_failure(new.getvalue())) + + def setUp(self): + """Modified test setup that syncs with ipython namespace""" + #print "setUp test", self._dt_test.examples # dbg + if isinstance(self._dt_test.examples[0], IPExample): + # for IPython examples *only*, we swap the globals with the ipython + # namespace, after updating it with the globals (which doctest + # fills with the necessary info from the module being tested). + self.user_ns_orig = {} + self.user_ns_orig.update(_ip.user_ns) + _ip.user_ns.update(self._dt_test.globs) + # We must remove the _ key in the namespace, so that Python's + # doctest code sets it naturally + _ip.user_ns.pop('_', None) + _ip.user_ns['__builtins__'] = builtin_mod + self._dt_test.globs = _ip.user_ns + + super(DocTestCase, self).setUp() + + def tearDown(self): + + # Undo the test.globs reassignment we made, so that the parent class + # teardown doesn't destroy the ipython namespace + if isinstance(self._dt_test.examples[0], IPExample): + self._dt_test.globs = self._dt_test_globs_ori + _ip.user_ns.clear() + _ip.user_ns.update(self.user_ns_orig) + + # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but + # it does look like one to me: its tearDown method tries to run + # + # delattr(builtin_mod, self._result_var) + # + # without checking that the attribute really is there; it implicitly + # assumes it should have been set via displayhook. But if the + # displayhook was never called, this doesn't necessarily happen. I + # haven't been able to find a little self-contained example outside of + # ipython that would show the problem so I can report it to the nose + # team, but it does happen a lot in our code. + # + # So here, we just protect as narrowly as possible by trapping an + # attribute error whose message would be the name of self._result_var, + # and letting any other error propagate. + try: + super(DocTestCase, self).tearDown() + except AttributeError as exc: + if exc.args[0] != self._result_var: + raise + + +# A simple subclassing of the original with a different class name, so we can +# distinguish and treat differently IPython examples from pure python ones. +class IPExample(doctest.Example): pass + + +class IPExternalExample(doctest.Example): + """Doctest examples to be run in an external process.""" + + def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, + options=None): + # Parent constructor + doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options) + + # An EXTRA newline is needed to prevent pexpect hangs + self.source += '\n' + + +class IPDocTestParser(doctest.DocTestParser): + """ + A class used to parse strings containing doctest examples. + + Note: This is a version modified to properly recognize IPython input and + convert any IPython examples into valid Python ones. + """ + # This regular expression is used to find doctest examples in a + # string. It defines three groups: `source` is the source code + # (including leading indentation and prompts); `indent` is the + # indentation of the first (PS1) line of the source code; and + # `want` is the expected output (including leading indentation). + + # Classic Python prompts or default IPython ones + _PS1_PY = r'>>>' + _PS2_PY = r'\.\.\.' + + _PS1_IP = r'In\ \[\d+\]:' + _PS2_IP = r'\ \ \ \.\.\.+:' + + _RE_TPL = r''' + # Source consists of a PS1 line followed by zero or more PS2 lines. + (?P<source> + (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line + (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines + \n? # a newline + # Want consists of any non-blank lines that do not start with PS1. + (?P<want> (?:(?![ ]*$) # Not a blank line + (?![ ]*%s) # Not a line starting with PS1 + (?![ ]*%s) # Not a line starting with PS2 + .*$\n? # But any other line + )*) + ''' + + _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY), + re.MULTILINE | re.VERBOSE) + + _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP), + re.MULTILINE | re.VERBOSE) + + # Mark a test as being fully random. In this case, we simply append the + # random marker ('#random') to each individual example's output. This way + # we don't need to modify any other code. + _RANDOM_TEST = re.compile(r'#\s*all-random\s+') + + # Mark tests to be executed in an external process - currently unsupported. + _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL') + + def ip2py(self,source): + """Convert input IPython source into valid Python.""" + block = _ip.input_transformer_manager.transform_cell(source) + if len(block.splitlines()) == 1: + return _ip.prefilter(block) + else: + return block + + def parse(self, string, name='<string>'): + """ + Divide the given string into examples and intervening text, + and return them as a list of alternating Examples and strings. + Line numbers for the Examples are 0-based. The optional + argument `name` is a name identifying this string, and is only + used for error messages. + """ + + #print 'Parse string:\n',string # dbg + + string = string.expandtabs() + # If all lines begin with the same indentation, then strip it. + min_indent = self._min_indent(string) + if min_indent > 0: + string = '\n'.join([l[min_indent:] for l in string.split('\n')]) + + output = [] + charno, lineno = 0, 0 + + # We make 'all random' tests by adding the '# random' mark to every + # block of output in the test. + if self._RANDOM_TEST.search(string): + random_marker = '\n# random' + else: + random_marker = '' + + # Whether to convert the input from ipython to python syntax + ip2py = False + # Find all doctest examples in the string. First, try them as Python + # examples, then as IPython ones + terms = list(self._EXAMPLE_RE_PY.finditer(string)) + if terms: + # Normal Python example + #print '-'*70 # dbg + #print 'PyExample, Source:\n',string # dbg + #print '-'*70 # dbg + Example = doctest.Example + else: + # It's an ipython example. Note that IPExamples are run + # in-process, so their syntax must be turned into valid python. + # IPExternalExamples are run out-of-process (via pexpect) so they + # don't need any filtering (a real ipython will be executing them). + terms = list(self._EXAMPLE_RE_IP.finditer(string)) + if self._EXTERNAL_IP.search(string): + #print '-'*70 # dbg + #print 'IPExternalExample, Source:\n',string # dbg + #print '-'*70 # dbg + Example = IPExternalExample + else: + #print '-'*70 # dbg + #print 'IPExample, Source:\n',string # dbg + #print '-'*70 # dbg + Example = IPExample + ip2py = True + + for m in terms: + # Add the pre-example text to `output`. + output.append(string[charno:m.start()]) + # Update lineno (lines before this example) + lineno += string.count('\n', charno, m.start()) + # Extract info from the regexp match. + (source, options, want, exc_msg) = \ + self._parse_example(m, name, lineno,ip2py) + + # Append the random-output marker (it defaults to empty in most + # cases, it's only non-empty for 'all-random' tests): + want += random_marker + + if Example is IPExternalExample: + options[doctest.NORMALIZE_WHITESPACE] = True + want += '\n' + + # Create an Example, and add it to the list. + if not self._IS_BLANK_OR_COMMENT(source): + output.append(Example(source, want, exc_msg, + lineno=lineno, + indent=min_indent+len(m.group('indent')), + options=options)) + # Update lineno (lines inside this example) + lineno += string.count('\n', m.start(), m.end()) + # Update charno. + charno = m.end() + # Add any remaining post-example text to `output`. + output.append(string[charno:]) + return output + + def _parse_example(self, m, name, lineno,ip2py=False): + """ + Given a regular expression match from `_EXAMPLE_RE` (`m`), + return a pair `(source, want)`, where `source` is the matched + example's source code (with prompts and indentation stripped); + and `want` is the example's expected output (with indentation + stripped). + + `name` is the string's name, and `lineno` is the line number + where the example starts; both are used for error messages. + + Optional: + `ip2py`: if true, filter the input via IPython to convert the syntax + into valid python. + """ + + # Get the example's indentation level. + indent = len(m.group('indent')) + + # Divide source into lines; check that they're properly + # indented; and then strip their indentation & prompts. + source_lines = m.group('source').split('\n') + + # We're using variable-length input prompts + ps1 = m.group('ps1') + ps2 = m.group('ps2') + ps1_len = len(ps1) + + self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len) + if ps2: + self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno) + + source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines]) + + if ip2py: + # Convert source input from IPython into valid Python syntax + source = self.ip2py(source) + + # Divide want into lines; check that it's properly indented; and + # then strip the indentation. Spaces before the last newline should + # be preserved, so plain rstrip() isn't good enough. + want = m.group('want') + want_lines = want.split('\n') + if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): + del want_lines[-1] # forget final newline & spaces after it + self._check_prefix(want_lines, ' '*indent, name, + lineno + len(source_lines)) + + # Remove ipython output prompt that might be present in the first line + want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0]) + + want = '\n'.join([wl[indent:] for wl in want_lines]) + + # If `want` contains a traceback message, then extract it. + m = self._EXCEPTION_RE.match(want) + if m: + exc_msg = m.group('msg') + else: + exc_msg = None + + # Extract options from the source. + options = self._find_options(source, name, lineno) + + return source, options, want, exc_msg + + def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len): + """ + Given the lines of a source string (including prompts and + leading indentation), check to make sure that every prompt is + followed by a space character. If any line is not followed by + a space character, then raise ValueError. + + Note: IPython-modified version which takes the input prompt length as a + parameter, so that prompts of variable length can be dealt with. + """ + space_idx = indent+ps1_len + min_len = space_idx+1 + for i, line in enumerate(lines): + if len(line) >= min_len and line[space_idx] != ' ': + raise ValueError('line %r of the docstring for %s ' + 'lacks blank after %s: %r' % + (lineno+i+1, name, + line[indent:space_idx], line)) + + +SKIP = doctest.register_optionflag('SKIP') + + +class IPDocTestRunner(doctest.DocTestRunner,object): + """Test runner that synchronizes the IPython namespace with test globals. + """ + + def run(self, test, compileflags=None, out=None, clear_globs=True): + + # Hack: ipython needs access to the execution context of the example, + # so that it can propagate user variables loaded by %run into + # test.globs. We put them here into our modified %run as a function + # attribute. Our new %run will then only make the namespace update + # when called (rather than unconconditionally updating test.globs here + # for all examples, most of which won't be calling %run anyway). + #_ip._ipdoctest_test_globs = test.globs + #_ip._ipdoctest_test_filename = test.filename + + test.globs.update(_ip.user_ns) + # Override terminal size to standardise traceback format with modified_env({'COLUMNS': '80', 'LINES': '24'}): return super(IPDocTestRunner,self).run(test, compileflags,out,clear_globs) - - -class DocFileCase(doctest.DocFileCase): - """Overrides to provide filename - """ - def address(self): - return (self._dt_test.filename, None, None) - - -class ExtensionDoctest(doctests.Doctest): - """Nose Plugin that supports doctests in extension modules. - """ - name = 'extdoctest' # call nosetests with --with-extdoctest - enabled = True - - def options(self, parser, env=os.environ): - Plugin.options(self, parser, env) - parser.add_option('--doctest-tests', action='store_true', - dest='doctest_tests', - default=env.get('NOSE_DOCTEST_TESTS',True), - help="Also look for doctests in test modules. " - "Note that classes, methods and functions should " - "have either doctests or non-doctest tests, " - "not both. [NOSE_DOCTEST_TESTS]") - parser.add_option('--doctest-extension', action="append", - dest="doctestExtension", - help="Also look for doctests in files with " - "this extension [NOSE_DOCTEST_EXTENSION]") - # Set the default as a list, if given in env; otherwise - # an additional value set on the command line will cause - # an error. - env_setting = env.get('NOSE_DOCTEST_EXTENSION') - if env_setting is not None: - parser.set_defaults(doctestExtension=tolist(env_setting)) - - - def configure(self, options, config): - Plugin.configure(self, options, config) - # Pull standard doctest plugin out of config; we will do doctesting - config.plugins.plugins = [p for p in config.plugins.plugins - if p.name != 'doctest'] - self.doctest_tests = options.doctest_tests - self.extension = tolist(options.doctestExtension) - - self.parser = doctest.DocTestParser() - self.finder = DocTestFinder() - self.checker = IPDoctestOutputChecker() - self.globs = None - self.extraglobs = None - - - def loadTestsFromExtensionModule(self,filename): - bpath,mod = os.path.split(filename) - modname = os.path.splitext(mod)[0] - try: - sys.path.append(bpath) - module = __import__(modname) - tests = list(self.loadTestsFromModule(module)) - finally: - sys.path.pop() - return tests - - # NOTE: the method below is almost a copy of the original one in nose, with - # a few modifications to control output checking. - - def loadTestsFromModule(self, module): - #print '*** ipdoctest - lTM',module # dbg - - if not self.matches(module.__name__): - log.debug("Doctest doesn't want module %s", module) - return - - tests = self.finder.find(module,globs=self.globs, - extraglobs=self.extraglobs) - if not tests: - return - - # always use whitespace and ellipsis options - optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS - - tests.sort() - module_file = module.__file__ - if module_file[-4:] in ('.pyc', '.pyo'): - module_file = module_file[:-1] - for test in tests: - if not test.examples: - continue - if not test.filename: - test.filename = module_file - - yield DocTestCase(test, - optionflags=optionflags, - checker=self.checker) - - - def loadTestsFromFile(self, filename): - #print "ipdoctest - from file", filename # dbg - if is_extension_module(filename): - for t in self.loadTestsFromExtensionModule(filename): - yield t - else: - if self.extension and anyp(filename.endswith, self.extension): - name = os.path.basename(filename) - dh = open(filename) - try: - doc = dh.read() - finally: - dh.close() - test = self.parser.get_doctest( - doc, globs={'__file__': filename}, name=name, - filename=filename, lineno=0) - if test.examples: - #print 'FileCase:',test.examples # dbg - yield DocFileCase(test) - else: - yield False # no tests to load - - -class IPythonDoctest(ExtensionDoctest): - """Nose Plugin that supports doctests in extension modules. - """ - name = 'ipdoctest' # call nosetests with --with-ipdoctest - enabled = True - - def makeTest(self, obj, parent): - """Look for doctests in the given object, which will be a - function, method or class. - """ - #print 'Plugin analyzing:', obj, parent # dbg - # always use whitespace and ellipsis options - optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS - - doctests = self.finder.find(obj, module=getmodule(parent)) - if doctests: - for test in doctests: - if len(test.examples) == 0: - continue - - yield DocTestCase(test, obj=obj, - optionflags=optionflags, - checker=self.checker) - - def options(self, parser, env=os.environ): - #print "Options for nose plugin:", self.name # dbg - Plugin.options(self, parser, env) - parser.add_option('--ipdoctest-tests', action='store_true', - dest='ipdoctest_tests', - default=env.get('NOSE_IPDOCTEST_TESTS',True), - help="Also look for doctests in test modules. " - "Note that classes, methods and functions should " - "have either doctests or non-doctest tests, " - "not both. [NOSE_IPDOCTEST_TESTS]") - parser.add_option('--ipdoctest-extension', action="append", - dest="ipdoctest_extension", - help="Also look for doctests in files with " - "this extension [NOSE_IPDOCTEST_EXTENSION]") - # Set the default as a list, if given in env; otherwise - # an additional value set on the command line will cause - # an error. - env_setting = env.get('NOSE_IPDOCTEST_EXTENSION') - if env_setting is not None: - parser.set_defaults(ipdoctest_extension=tolist(env_setting)) - - def configure(self, options, config): - #print "Configuring nose plugin:", self.name # dbg - Plugin.configure(self, options, config) - # Pull standard doctest plugin out of config; we will do doctesting - config.plugins.plugins = [p for p in config.plugins.plugins - if p.name != 'doctest'] - self.doctest_tests = options.ipdoctest_tests - self.extension = tolist(options.ipdoctest_extension) - - self.parser = IPDocTestParser() - self.finder = DocTestFinder(parser=self.parser) - self.checker = IPDoctestOutputChecker() - self.globs = None - self.extraglobs = None + + +class DocFileCase(doctest.DocFileCase): + """Overrides to provide filename + """ + def address(self): + return (self._dt_test.filename, None, None) + + +class ExtensionDoctest(doctests.Doctest): + """Nose Plugin that supports doctests in extension modules. + """ + name = 'extdoctest' # call nosetests with --with-extdoctest + enabled = True + + def options(self, parser, env=os.environ): + Plugin.options(self, parser, env) + parser.add_option('--doctest-tests', action='store_true', + dest='doctest_tests', + default=env.get('NOSE_DOCTEST_TESTS',True), + help="Also look for doctests in test modules. " + "Note that classes, methods and functions should " + "have either doctests or non-doctest tests, " + "not both. [NOSE_DOCTEST_TESTS]") + parser.add_option('--doctest-extension', action="append", + dest="doctestExtension", + help="Also look for doctests in files with " + "this extension [NOSE_DOCTEST_EXTENSION]") + # Set the default as a list, if given in env; otherwise + # an additional value set on the command line will cause + # an error. + env_setting = env.get('NOSE_DOCTEST_EXTENSION') + if env_setting is not None: + parser.set_defaults(doctestExtension=tolist(env_setting)) + + + def configure(self, options, config): + Plugin.configure(self, options, config) + # Pull standard doctest plugin out of config; we will do doctesting + config.plugins.plugins = [p for p in config.plugins.plugins + if p.name != 'doctest'] + self.doctest_tests = options.doctest_tests + self.extension = tolist(options.doctestExtension) + + self.parser = doctest.DocTestParser() + self.finder = DocTestFinder() + self.checker = IPDoctestOutputChecker() + self.globs = None + self.extraglobs = None + + + def loadTestsFromExtensionModule(self,filename): + bpath,mod = os.path.split(filename) + modname = os.path.splitext(mod)[0] + try: + sys.path.append(bpath) + module = __import__(modname) + tests = list(self.loadTestsFromModule(module)) + finally: + sys.path.pop() + return tests + + # NOTE: the method below is almost a copy of the original one in nose, with + # a few modifications to control output checking. + + def loadTestsFromModule(self, module): + #print '*** ipdoctest - lTM',module # dbg + + if not self.matches(module.__name__): + log.debug("Doctest doesn't want module %s", module) + return + + tests = self.finder.find(module,globs=self.globs, + extraglobs=self.extraglobs) + if not tests: + return + + # always use whitespace and ellipsis options + optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + + tests.sort() + module_file = module.__file__ + if module_file[-4:] in ('.pyc', '.pyo'): + module_file = module_file[:-1] + for test in tests: + if not test.examples: + continue + if not test.filename: + test.filename = module_file + + yield DocTestCase(test, + optionflags=optionflags, + checker=self.checker) + + + def loadTestsFromFile(self, filename): + #print "ipdoctest - from file", filename # dbg + if is_extension_module(filename): + for t in self.loadTestsFromExtensionModule(filename): + yield t + else: + if self.extension and anyp(filename.endswith, self.extension): + name = os.path.basename(filename) + dh = open(filename) + try: + doc = dh.read() + finally: + dh.close() + test = self.parser.get_doctest( + doc, globs={'__file__': filename}, name=name, + filename=filename, lineno=0) + if test.examples: + #print 'FileCase:',test.examples # dbg + yield DocFileCase(test) + else: + yield False # no tests to load + + +class IPythonDoctest(ExtensionDoctest): + """Nose Plugin that supports doctests in extension modules. + """ + name = 'ipdoctest' # call nosetests with --with-ipdoctest + enabled = True + + def makeTest(self, obj, parent): + """Look for doctests in the given object, which will be a + function, method or class. + """ + #print 'Plugin analyzing:', obj, parent # dbg + # always use whitespace and ellipsis options + optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + + doctests = self.finder.find(obj, module=getmodule(parent)) + if doctests: + for test in doctests: + if len(test.examples) == 0: + continue + + yield DocTestCase(test, obj=obj, + optionflags=optionflags, + checker=self.checker) + + def options(self, parser, env=os.environ): + #print "Options for nose plugin:", self.name # dbg + Plugin.options(self, parser, env) + parser.add_option('--ipdoctest-tests', action='store_true', + dest='ipdoctest_tests', + default=env.get('NOSE_IPDOCTEST_TESTS',True), + help="Also look for doctests in test modules. " + "Note that classes, methods and functions should " + "have either doctests or non-doctest tests, " + "not both. [NOSE_IPDOCTEST_TESTS]") + parser.add_option('--ipdoctest-extension', action="append", + dest="ipdoctest_extension", + help="Also look for doctests in files with " + "this extension [NOSE_IPDOCTEST_EXTENSION]") + # Set the default as a list, if given in env; otherwise + # an additional value set on the command line will cause + # an error. + env_setting = env.get('NOSE_IPDOCTEST_EXTENSION') + if env_setting is not None: + parser.set_defaults(ipdoctest_extension=tolist(env_setting)) + + def configure(self, options, config): + #print "Configuring nose plugin:", self.name # dbg + Plugin.configure(self, options, config) + # Pull standard doctest plugin out of config; we will do doctesting + config.plugins.plugins = [p for p in config.plugins.plugins + if p.name != 'doctest'] + self.doctest_tests = options.ipdoctest_tests + self.extension = tolist(options.ipdoctest_extension) + + self.parser = IPDocTestParser() + self.finder = DocTestFinder(parser=self.parser) + self.checker = IPDoctestOutputChecker() + self.globs = None + self.extraglobs = None diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/iptest.py b/contrib/python/ipython/py2/IPython/testing/plugin/iptest.py index a75cab993f..25b4634f2f 100755 --- a/contrib/python/ipython/py2/IPython/testing/plugin/iptest.py +++ b/contrib/python/ipython/py2/IPython/testing/plugin/iptest.py @@ -1,19 +1,19 @@ -#!/usr/bin/env python -"""Nose-based test runner. -""" -from __future__ import print_function - -from nose.core import main -from nose.plugins.builtin import plugins -from nose.plugins.doctests import Doctest - -from . import ipdoctest -from .ipdoctest import IPDocTestRunner - -if __name__ == '__main__': - print('WARNING: this code is incomplete!') - print() - - pp = [x() for x in plugins] # activate all builtin plugins first - main(testRunner=IPDocTestRunner(), - plugins=pp+[ipdoctest.IPythonDoctest(),Doctest()]) +#!/usr/bin/env python +"""Nose-based test runner. +""" +from __future__ import print_function + +from nose.core import main +from nose.plugins.builtin import plugins +from nose.plugins.doctests import Doctest + +from . import ipdoctest +from .ipdoctest import IPDocTestRunner + +if __name__ == '__main__': + print('WARNING: this code is incomplete!') + print() + + pp = [x() for x in plugins] # activate all builtin plugins first + main(testRunner=IPDocTestRunner(), + plugins=pp+[ipdoctest.IPythonDoctest(),Doctest()]) diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/setup.py b/contrib/python/ipython/py2/IPython/testing/plugin/setup.py index a3281d30c8..785704337b 100755 --- a/contrib/python/ipython/py2/IPython/testing/plugin/setup.py +++ b/contrib/python/ipython/py2/IPython/testing/plugin/setup.py @@ -1,18 +1,18 @@ -#!/usr/bin/env python -"""A Nose plugin to support IPython doctests. -""" - -from setuptools import setup - -setup(name='IPython doctest plugin', - version='0.1', - author='The IPython Team', - description = 'Nose plugin to load IPython-extended doctests', - license = 'LGPL', - py_modules = ['ipdoctest'], - entry_points = { - 'nose.plugins.0.10': ['ipdoctest = ipdoctest:IPythonDoctest', - 'extdoctest = ipdoctest:ExtensionDoctest', - ], - }, - ) +#!/usr/bin/env python +"""A Nose plugin to support IPython doctests. +""" + +from setuptools import setup + +setup(name='IPython doctest plugin', + version='0.1', + author='The IPython Team', + description = 'Nose plugin to load IPython-extended doctests', + license = 'LGPL', + py_modules = ['ipdoctest'], + entry_points = { + 'nose.plugins.0.10': ['ipdoctest = ipdoctest:IPythonDoctest', + 'extdoctest = ipdoctest:ExtensionDoctest', + ], + }, + ) diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/show_refs.py b/contrib/python/ipython/py2/IPython/testing/plugin/show_refs.py index ef7dd157ae..4c517da949 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/show_refs.py +++ b/contrib/python/ipython/py2/IPython/testing/plugin/show_refs.py @@ -1,20 +1,20 @@ -"""Simple script to show reference holding behavior. - -This is used by a companion test case. -""" -from __future__ import print_function - -import gc - -class C(object): - def __del__(self): - pass - #print 'deleting object...' # dbg - -if __name__ == '__main__': - c = C() - - c_refs = gc.get_referrers(c) - ref_ids = list(map(id,c_refs)) - - print('c referrers:',list(map(type,c_refs))) +"""Simple script to show reference holding behavior. + +This is used by a companion test case. +""" +from __future__ import print_function + +import gc + +class C(object): + def __del__(self): + pass + #print 'deleting object...' # dbg + +if __name__ == '__main__': + c = C() + + c_refs = gc.get_referrers(c) + ref_ids = list(map(id,c_refs)) + + print('c referrers:',list(map(type,c_refs))) diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/simple.py b/contrib/python/ipython/py2/IPython/testing/plugin/simple.py index a7d33d9a16..bcc43f55e8 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/simple.py +++ b/contrib/python/ipython/py2/IPython/testing/plugin/simple.py @@ -1,34 +1,34 @@ -"""Simple example using doctests. - -This file just contains doctests both using plain python and IPython prompts. -All tests should be loaded by nose. -""" -from __future__ import print_function - -def pyfunc(): - """Some pure python tests... - - >>> pyfunc() - 'pyfunc' - - >>> import os - - >>> 2+3 - 5 - - >>> for i in range(3): - ... print(i, end=' ') - ... print(i+1, end=' ') - ... - 0 1 1 2 2 3 - """ - return 'pyfunc' - - -def ipyfunc2(): - """Some pure python tests... - - >>> 1+1 - 2 - """ - return 'pyfunc2' +"""Simple example using doctests. + +This file just contains doctests both using plain python and IPython prompts. +All tests should be loaded by nose. +""" +from __future__ import print_function + +def pyfunc(): + """Some pure python tests... + + >>> pyfunc() + 'pyfunc' + + >>> import os + + >>> 2+3 + 5 + + >>> for i in range(3): + ... print(i, end=' ') + ... print(i+1, end=' ') + ... + 0 1 1 2 2 3 + """ + return 'pyfunc' + + +def ipyfunc2(): + """Some pure python tests... + + >>> 1+1 + 2 + """ + return 'pyfunc2' diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/simplevars.py b/contrib/python/ipython/py2/IPython/testing/plugin/simplevars.py index 5134c6e928..ee4039a59a 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/simplevars.py +++ b/contrib/python/ipython/py2/IPython/testing/plugin/simplevars.py @@ -1,3 +1,3 @@ -from __future__ import print_function -x = 1 -print('x is:',x) +from __future__ import print_function +x = 1 +print('x is:',x) diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/test_combo.txt b/contrib/python/ipython/py2/IPython/testing/plugin/test_combo.txt index 6c8759f3e7..0de694fe7e 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/test_combo.txt +++ b/contrib/python/ipython/py2/IPython/testing/plugin/test_combo.txt @@ -1,36 +1,36 @@ -======================= - Combo testing example -======================= - -This is a simple example that mixes ipython doctests:: - - In [1]: import code - - In [2]: 2**12 - Out[2]: 4096 - -with command-line example information that does *not* get executed:: - - $ mpirun -n 4 ipengine --controller-port=10000 --controller-ip=host0 - -and with literal examples of Python source code:: - - controller = dict(host='myhost', - engine_port=None, # default is 10105 - control_port=None, - ) - - # keys are hostnames, values are the number of engine on that host - engines = dict(node1=2, - node2=2, - node3=2, - node3=2, - ) - - # Force failure to detect that this test is being run. - 1/0 - -These source code examples are executed but no output is compared at all. An -error or failure is reported only if an exception is raised. - -NOTE: the execution of pure python blocks is not yet working! +======================= + Combo testing example +======================= + +This is a simple example that mixes ipython doctests:: + + In [1]: import code + + In [2]: 2**12 + Out[2]: 4096 + +with command-line example information that does *not* get executed:: + + $ mpirun -n 4 ipengine --controller-port=10000 --controller-ip=host0 + +and with literal examples of Python source code:: + + controller = dict(host='myhost', + engine_port=None, # default is 10105 + control_port=None, + ) + + # keys are hostnames, values are the number of engine on that host + engines = dict(node1=2, + node2=2, + node3=2, + node3=2, + ) + + # Force failure to detect that this test is being run. + 1/0 + +These source code examples are executed but no output is compared at all. An +error or failure is reported only if an exception is raised. + +NOTE: the execution of pure python blocks is not yet working! diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/test_example.txt b/contrib/python/ipython/py2/IPython/testing/plugin/test_example.txt index f8b681eb4f..f6258b0615 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/test_example.txt +++ b/contrib/python/ipython/py2/IPython/testing/plugin/test_example.txt @@ -1,24 +1,24 @@ -===================================== - Tests in example form - pure python -===================================== - -This file contains doctest examples embedded as code blocks, using normal -Python prompts. See the accompanying file for similar examples using IPython -prompts (you can't mix both types within one file). The following will be run -as a test:: - - >>> 1+1 - 2 - >>> print ("hello") - hello - -More than one example works:: - - >>> s="Hello World" - - >>> s.upper() - 'HELLO WORLD' - -but you should note that the *entire* test file is considered to be a single -test. Individual code blocks that fail are printed separately as ``example -failures``, but the whole file is still counted and reported as one test. +===================================== + Tests in example form - pure python +===================================== + +This file contains doctest examples embedded as code blocks, using normal +Python prompts. See the accompanying file for similar examples using IPython +prompts (you can't mix both types within one file). The following will be run +as a test:: + + >>> 1+1 + 2 + >>> print ("hello") + hello + +More than one example works:: + + >>> s="Hello World" + + >>> s.upper() + 'HELLO WORLD' + +but you should note that the *entire* test file is considered to be a single +test. Individual code blocks that fail are printed separately as ``example +failures``, but the whole file is still counted and reported as one test. diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/test_exampleip.txt b/contrib/python/ipython/py2/IPython/testing/plugin/test_exampleip.txt index 8afcbfdf7d..cbc00cc976 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/test_exampleip.txt +++ b/contrib/python/ipython/py2/IPython/testing/plugin/test_exampleip.txt @@ -1,30 +1,30 @@ -================================= - Tests in example form - IPython -================================= - -You can write text files with examples that use IPython prompts (as long as you -use the nose ipython doctest plugin), but you can not mix and match prompt -styles in a single file. That is, you either use all ``>>>`` prompts or all -IPython-style prompts. Your test suite *can* have both types, you just need to -put each type of example in a separate. Using IPython prompts, you can paste -directly from your session:: - - In [5]: s="Hello World" - - In [6]: s.upper() - Out[6]: 'HELLO WORLD' - -Another example:: - - In [8]: 1+3 - Out[8]: 4 - -Just like in IPython docstrings, you can use all IPython syntax and features:: - - In [9]: !echo "hello" - hello - - In [10]: a='hi' - - In [11]: !echo $a - hi +================================= + Tests in example form - IPython +================================= + +You can write text files with examples that use IPython prompts (as long as you +use the nose ipython doctest plugin), but you can not mix and match prompt +styles in a single file. That is, you either use all ``>>>`` prompts or all +IPython-style prompts. Your test suite *can* have both types, you just need to +put each type of example in a separate. Using IPython prompts, you can paste +directly from your session:: + + In [5]: s="Hello World" + + In [6]: s.upper() + Out[6]: 'HELLO WORLD' + +Another example:: + + In [8]: 1+3 + Out[8]: 4 + +Just like in IPython docstrings, you can use all IPython syntax and features:: + + In [9]: !echo "hello" + hello + + In [10]: a='hi' + + In [11]: !echo $a + hi diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/test_ipdoctest.py b/contrib/python/ipython/py2/IPython/testing/plugin/test_ipdoctest.py index a7add7da79..05dc387d9b 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/test_ipdoctest.py +++ b/contrib/python/ipython/py2/IPython/testing/plugin/test_ipdoctest.py @@ -1,80 +1,80 @@ -"""Tests for the ipdoctest machinery itself. - -Note: in a file named test_X, functions whose only test is their docstring (as -a doctest) and which have no test functionality of their own, should be called -'doctest_foo' instead of 'test_foo', otherwise they get double-counted (the -empty function call is counted as a test, which just inflates tests numbers -artificially). -""" -from IPython.utils.py3compat import doctest_refactor_print - -@doctest_refactor_print -def doctest_simple(): - """ipdoctest must handle simple inputs - - In [1]: 1 - Out[1]: 1 - - In [2]: print 1 - 1 - """ - -@doctest_refactor_print -def doctest_multiline1(): - """The ipdoctest machinery must handle multiline examples gracefully. - - In [2]: for i in range(4): - ...: print i - ...: - 0 - 1 - 2 - 3 - """ - -@doctest_refactor_print -def doctest_multiline2(): - """Multiline examples that define functions and print output. - - In [7]: def f(x): - ...: return x+1 - ...: - - In [8]: f(1) - Out[8]: 2 - - In [9]: def g(x): - ...: print 'x is:',x - ...: - - In [10]: g(1) - x is: 1 - - In [11]: g('hello') - x is: hello - """ - - -def doctest_multiline3(): - """Multiline examples with blank lines. - - In [12]: def h(x): - ....: if x>1: - ....: return x**2 - ....: # To leave a blank line in the input, you must mark it - ....: # with a comment character: - ....: # - ....: # otherwise the doctest parser gets confused. - ....: else: - ....: return -1 - ....: - - In [13]: h(5) - Out[13]: 25 - - In [14]: h(1) - Out[14]: -1 - - In [15]: h(0) - Out[15]: -1 - """ +"""Tests for the ipdoctest machinery itself. + +Note: in a file named test_X, functions whose only test is their docstring (as +a doctest) and which have no test functionality of their own, should be called +'doctest_foo' instead of 'test_foo', otherwise they get double-counted (the +empty function call is counted as a test, which just inflates tests numbers +artificially). +""" +from IPython.utils.py3compat import doctest_refactor_print + +@doctest_refactor_print +def doctest_simple(): + """ipdoctest must handle simple inputs + + In [1]: 1 + Out[1]: 1 + + In [2]: print 1 + 1 + """ + +@doctest_refactor_print +def doctest_multiline1(): + """The ipdoctest machinery must handle multiline examples gracefully. + + In [2]: for i in range(4): + ...: print i + ...: + 0 + 1 + 2 + 3 + """ + +@doctest_refactor_print +def doctest_multiline2(): + """Multiline examples that define functions and print output. + + In [7]: def f(x): + ...: return x+1 + ...: + + In [8]: f(1) + Out[8]: 2 + + In [9]: def g(x): + ...: print 'x is:',x + ...: + + In [10]: g(1) + x is: 1 + + In [11]: g('hello') + x is: hello + """ + + +def doctest_multiline3(): + """Multiline examples with blank lines. + + In [12]: def h(x): + ....: if x>1: + ....: return x**2 + ....: # To leave a blank line in the input, you must mark it + ....: # with a comment character: + ....: # + ....: # otherwise the doctest parser gets confused. + ....: else: + ....: return -1 + ....: + + In [13]: h(5) + Out[13]: 25 + + In [14]: h(1) + Out[14]: -1 + + In [15]: h(0) + Out[15]: -1 + """ diff --git a/contrib/python/ipython/py2/IPython/testing/plugin/test_refs.py b/contrib/python/ipython/py2/IPython/testing/plugin/test_refs.py index 50d0857134..8a2a78b75d 100644 --- a/contrib/python/ipython/py2/IPython/testing/plugin/test_refs.py +++ b/contrib/python/ipython/py2/IPython/testing/plugin/test_refs.py @@ -1,46 +1,46 @@ -"""Some simple tests for the plugin while running scripts. -""" -# Module imports -# Std lib -import inspect - -# Our own - -#----------------------------------------------------------------------------- -# Testing functions - -def test_trivial(): - """A trivial passing test.""" - pass - -def doctest_run(): - """Test running a trivial script. - - In [13]: run simplevars.py - x is: 1 - """ - -def doctest_runvars(): - """Test that variables defined in scripts get loaded correcly via %run. - - In [13]: run simplevars.py - x is: 1 - - In [14]: x - Out[14]: 1 - """ - -def doctest_ivars(): - """Test that variables defined interactively are picked up. - In [5]: zz=1 - - In [6]: zz - Out[6]: 1 - """ - -def doctest_refs(): - """DocTest reference holding issues when running scripts. - - In [32]: run show_refs.py - c referrers: [<... 'dict'>] - """ +"""Some simple tests for the plugin while running scripts. +""" +# Module imports +# Std lib +import inspect + +# Our own + +#----------------------------------------------------------------------------- +# Testing functions + +def test_trivial(): + """A trivial passing test.""" + pass + +def doctest_run(): + """Test running a trivial script. + + In [13]: run simplevars.py + x is: 1 + """ + +def doctest_runvars(): + """Test that variables defined in scripts get loaded correcly via %run. + + In [13]: run simplevars.py + x is: 1 + + In [14]: x + Out[14]: 1 + """ + +def doctest_ivars(): + """Test that variables defined interactively are picked up. + In [5]: zz=1 + + In [6]: zz + Out[6]: 1 + """ + +def doctest_refs(): + """DocTest reference holding issues when running scripts. + + In [32]: run show_refs.py + c referrers: [<... 'dict'>] + """ diff --git a/contrib/python/ipython/py2/IPython/testing/skipdoctest.py b/contrib/python/ipython/py2/IPython/testing/skipdoctest.py index 564ca54027..8357f609b0 100644 --- a/contrib/python/ipython/py2/IPython/testing/skipdoctest.py +++ b/contrib/python/ipython/py2/IPython/testing/skipdoctest.py @@ -1,41 +1,41 @@ -"""Decorators marks that a doctest should be skipped, for both python 2 and 3. - -The IPython.testing.decorators module triggers various extra imports, including -numpy and sympy if they're present. Since this decorator is used in core parts -of IPython, it's in a separate module so that running IPython doesn't trigger -those imports.""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2009-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import sys - -#----------------------------------------------------------------------------- -# Decorators -#----------------------------------------------------------------------------- - -def skip_doctest(f): - """Decorator - mark a function or method for skipping its doctest. - - This decorator allows you to mark a function whose docstring you wish to - omit from testing, while preserving the docstring for introspection, help, - etc.""" - f.skip_doctest = True - return f - - -def skip_doctest_py3(f): - """Decorator - skip the doctest under Python 3.""" - f.skip_doctest = (sys.version_info[0] >= 3) - return f +"""Decorators marks that a doctest should be skipped, for both python 2 and 3. + +The IPython.testing.decorators module triggers various extra imports, including +numpy and sympy if they're present. Since this decorator is used in core parts +of IPython, it's in a separate module so that running IPython doesn't trigger +those imports.""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2009-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys + +#----------------------------------------------------------------------------- +# Decorators +#----------------------------------------------------------------------------- + +def skip_doctest(f): + """Decorator - mark a function or method for skipping its doctest. + + This decorator allows you to mark a function whose docstring you wish to + omit from testing, while preserving the docstring for introspection, help, + etc.""" + f.skip_doctest = True + return f + + +def skip_doctest_py3(f): + """Decorator - skip the doctest under Python 3.""" + f.skip_doctest = (sys.version_info[0] >= 3) + return f def skip_doctest_py2(f): """Decorator - skip the doctest under Python 3.""" diff --git a/contrib/python/ipython/py2/IPython/testing/tools.py b/contrib/python/ipython/py2/IPython/testing/tools.py index 23bf6a68cb..98f796fe3c 100644 --- a/contrib/python/ipython/py2/IPython/testing/tools.py +++ b/contrib/python/ipython/py2/IPython/testing/tools.py @@ -1,296 +1,296 @@ -"""Generic testing tools. - -Authors -------- -- Fernando Perez <Fernando.Perez@berkeley.edu> -""" - -from __future__ import absolute_import - +"""Generic testing tools. + +Authors +------- +- Fernando Perez <Fernando.Perez@berkeley.edu> +""" + +from __future__ import absolute_import + # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - -import os -import re -import sys -import tempfile - -from contextlib import contextmanager -from io import StringIO -from subprocess import Popen, PIPE + +import os +import re +import sys +import tempfile + +from contextlib import contextmanager +from io import StringIO +from subprocess import Popen, PIPE try: from unittest.mock import patch except ImportError: # Python 2 compatibility from mock import patch - -try: - # These tools are used by parts of the runtime, so we make the nose - # dependency optional at this point. Nose is a hard dependency to run the - # test suite, but NOT to use ipython itself. - import nose.tools as nt - has_nose = True -except ImportError: - has_nose = False - -from traitlets.config.loader import Config -from IPython.utils.process import get_output_error_code -from IPython.utils.text import list_strings -from IPython.utils.io import temp_pyfile, Tee -from IPython.utils import py3compat -from IPython.utils.encoding import DEFAULT_ENCODING - -from . import decorators as dec -from . import skipdoctest - - -# The docstring for full_path doctests differently on win32 (different path -# separator) so just skip the doctest there. The example remains informative. -doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco - -@doctest_deco -def full_path(startPath,files): - """Make full paths for all the listed files, based on startPath. - - Only the base part of startPath is kept, since this routine is typically - used with a script's ``__file__`` variable as startPath. The base of startPath - is then prepended to all the listed files, forming the output list. - - Parameters - ---------- - startPath : string - Initial path to use as the base for the results. This path is split - using os.path.split() and only its first component is kept. - - files : string or list - One or more files. - - Examples - -------- - - >>> full_path('/foo/bar.py',['a.txt','b.txt']) - ['/foo/a.txt', '/foo/b.txt'] - - >>> full_path('/foo',['a.txt','b.txt']) - ['/a.txt', '/b.txt'] - - If a single file is given, the output is still a list:: - - >>> full_path('/foo','a.txt') - ['/a.txt'] - """ - - files = list_strings(files) - base = os.path.split(startPath)[0] - return [ os.path.join(base,f) for f in files ] - - -def parse_test_output(txt): - """Parse the output of a test run and return errors, failures. - - Parameters - ---------- - txt : str - Text output of a test run, assumed to contain a line of one of the - following forms:: - - 'FAILED (errors=1)' - 'FAILED (failures=1)' - 'FAILED (errors=1, failures=1)' - - Returns - ------- - nerr, nfail - number of errors and failures. - """ - - err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE) - if err_m: - nerr = int(err_m.group(1)) - nfail = 0 - return nerr, nfail - - fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE) - if fail_m: - nerr = 0 - nfail = int(fail_m.group(1)) - return nerr, nfail - - both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt, - re.MULTILINE) - if both_m: - nerr = int(both_m.group(1)) - nfail = int(both_m.group(2)) - return nerr, nfail - - # If the input didn't match any of these forms, assume no error/failures - return 0, 0 - - -# So nose doesn't think this is a test -parse_test_output.__test__ = False - - -def default_argv(): - """Return a valid default argv for creating testing instances of ipython""" - - return ['--quick', # so no config file is loaded - # Other defaults to minimize side effects on stdout - '--colors=NoColor', '--no-term-title','--no-banner', - '--autocall=0'] - - -def default_config(): - """Return a config object with good defaults for testing.""" - config = Config() - config.TerminalInteractiveShell.colors = 'NoColor' - config.TerminalTerminalInteractiveShell.term_title = False, - config.TerminalInteractiveShell.autocall = 0 - f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False) - config.HistoryManager.hist_file = f.name - f.close() - config.HistoryManager.db_cache_size = 10000 - return config - - -def get_ipython_cmd(as_string=False): - """ - Return appropriate IPython command line name. By default, this will return - a list that can be used with subprocess.Popen, for example, but passing - `as_string=True` allows for returning the IPython command as a string. - - Parameters - ---------- - as_string: bool - Flag to allow to return the command as a string. - """ - ipython_cmd = [sys.executable, "-m", "IPython"] - - if as_string: - ipython_cmd = " ".join(ipython_cmd) - - return ipython_cmd - -def ipexec(fname, options=None, commands=()): - """Utility to call 'ipython filename'. - - Starts IPython with a minimal and safe configuration to make startup as fast - as possible. - - Note that this starts IPython in a subprocess! - - Parameters - ---------- - fname : str - Name of file to be executed (should have .py or .ipy extension). - - options : optional, list - Extra command-line flags to be passed to IPython. - - commands : optional, list - Commands to send in on stdin - - Returns - ------- - (stdout, stderr) of ipython subprocess. - """ - if options is None: options = [] - + +try: + # These tools are used by parts of the runtime, so we make the nose + # dependency optional at this point. Nose is a hard dependency to run the + # test suite, but NOT to use ipython itself. + import nose.tools as nt + has_nose = True +except ImportError: + has_nose = False + +from traitlets.config.loader import Config +from IPython.utils.process import get_output_error_code +from IPython.utils.text import list_strings +from IPython.utils.io import temp_pyfile, Tee +from IPython.utils import py3compat +from IPython.utils.encoding import DEFAULT_ENCODING + +from . import decorators as dec +from . import skipdoctest + + +# The docstring for full_path doctests differently on win32 (different path +# separator) so just skip the doctest there. The example remains informative. +doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco + +@doctest_deco +def full_path(startPath,files): + """Make full paths for all the listed files, based on startPath. + + Only the base part of startPath is kept, since this routine is typically + used with a script's ``__file__`` variable as startPath. The base of startPath + is then prepended to all the listed files, forming the output list. + + Parameters + ---------- + startPath : string + Initial path to use as the base for the results. This path is split + using os.path.split() and only its first component is kept. + + files : string or list + One or more files. + + Examples + -------- + + >>> full_path('/foo/bar.py',['a.txt','b.txt']) + ['/foo/a.txt', '/foo/b.txt'] + + >>> full_path('/foo',['a.txt','b.txt']) + ['/a.txt', '/b.txt'] + + If a single file is given, the output is still a list:: + + >>> full_path('/foo','a.txt') + ['/a.txt'] + """ + + files = list_strings(files) + base = os.path.split(startPath)[0] + return [ os.path.join(base,f) for f in files ] + + +def parse_test_output(txt): + """Parse the output of a test run and return errors, failures. + + Parameters + ---------- + txt : str + Text output of a test run, assumed to contain a line of one of the + following forms:: + + 'FAILED (errors=1)' + 'FAILED (failures=1)' + 'FAILED (errors=1, failures=1)' + + Returns + ------- + nerr, nfail + number of errors and failures. + """ + + err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE) + if err_m: + nerr = int(err_m.group(1)) + nfail = 0 + return nerr, nfail + + fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE) + if fail_m: + nerr = 0 + nfail = int(fail_m.group(1)) + return nerr, nfail + + both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt, + re.MULTILINE) + if both_m: + nerr = int(both_m.group(1)) + nfail = int(both_m.group(2)) + return nerr, nfail + + # If the input didn't match any of these forms, assume no error/failures + return 0, 0 + + +# So nose doesn't think this is a test +parse_test_output.__test__ = False + + +def default_argv(): + """Return a valid default argv for creating testing instances of ipython""" + + return ['--quick', # so no config file is loaded + # Other defaults to minimize side effects on stdout + '--colors=NoColor', '--no-term-title','--no-banner', + '--autocall=0'] + + +def default_config(): + """Return a config object with good defaults for testing.""" + config = Config() + config.TerminalInteractiveShell.colors = 'NoColor' + config.TerminalTerminalInteractiveShell.term_title = False, + config.TerminalInteractiveShell.autocall = 0 + f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False) + config.HistoryManager.hist_file = f.name + f.close() + config.HistoryManager.db_cache_size = 10000 + return config + + +def get_ipython_cmd(as_string=False): + """ + Return appropriate IPython command line name. By default, this will return + a list that can be used with subprocess.Popen, for example, but passing + `as_string=True` allows for returning the IPython command as a string. + + Parameters + ---------- + as_string: bool + Flag to allow to return the command as a string. + """ + ipython_cmd = [sys.executable, "-m", "IPython"] + + if as_string: + ipython_cmd = " ".join(ipython_cmd) + + return ipython_cmd + +def ipexec(fname, options=None, commands=()): + """Utility to call 'ipython filename'. + + Starts IPython with a minimal and safe configuration to make startup as fast + as possible. + + Note that this starts IPython in a subprocess! + + Parameters + ---------- + fname : str + Name of file to be executed (should have .py or .ipy extension). + + options : optional, list + Extra command-line flags to be passed to IPython. + + commands : optional, list + Commands to send in on stdin + + Returns + ------- + (stdout, stderr) of ipython subprocess. + """ + if options is None: options = [] + cmdargs = default_argv() + options - - test_dir = os.path.dirname(__file__) - - ipython_cmd = get_ipython_cmd() - # Absolute path for filename - full_fname = os.path.join(test_dir, fname) - full_cmd = ipython_cmd + cmdargs + [full_fname] - env = os.environ.copy() - # FIXME: ignore all warnings in ipexec while we have shims - # should we keep suppressing warnings here, even after removing shims? - env['PYTHONWARNINGS'] = 'ignore' - # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr - for k, v in env.items(): - # Debug a bizarre failure we've seen on Windows: - # TypeError: environment can only contain strings - if not isinstance(v, str): - print(k, v) - p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env) - out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None) - out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err) - # `import readline` causes 'ESC[?1034h' to be output sometimes, - # so strip that out before doing comparisons - if out: - out = re.sub(r'\x1b\[[^h]+h', '', out) - return out, err - - -def ipexec_validate(fname, expected_out, expected_err='', - options=None, commands=()): - """Utility to call 'ipython filename' and validate output/error. - - This function raises an AssertionError if the validation fails. - - Note that this starts IPython in a subprocess! - - Parameters - ---------- - fname : str - Name of the file to be executed (should have .py or .ipy extension). - - expected_out : str - Expected stdout of the process. - - expected_err : optional, str - Expected stderr of the process. - - options : optional, list - Extra command-line flags to be passed to IPython. - - Returns - ------- - None - """ - - import nose.tools as nt - - out, err = ipexec(fname, options, commands) - #print 'OUT', out # dbg - #print 'ERR', err # dbg - # If there are any errors, we must check those befor stdout, as they may be - # more informative than simply having an empty stdout. - if err: - if expected_err: - nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines())) - else: - raise ValueError('Running file %r produced error: %r' % - (fname, err)) - # If no errors or output on stderr was expected, match stdout - nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines())) - - -class TempFileMixin(object): - """Utility class to create temporary Python/IPython files. - - Meant as a mixin class for test cases.""" - - def mktmp(self, src, ext='.py'): - """Make a valid python temp file.""" - fname, f = temp_pyfile(src, ext) - self.tmpfile = f - self.fname = fname - - def tearDown(self): - if hasattr(self, 'tmpfile'): - # If the tmpfile wasn't made because of skipped tests, like in - # win32, there's nothing to cleanup. - self.tmpfile.close() - try: - os.unlink(self.fname) - except: - # On Windows, even though we close the file, we still can't - # delete it. I have no clue why - pass - + + test_dir = os.path.dirname(__file__) + + ipython_cmd = get_ipython_cmd() + # Absolute path for filename + full_fname = os.path.join(test_dir, fname) + full_cmd = ipython_cmd + cmdargs + [full_fname] + env = os.environ.copy() + # FIXME: ignore all warnings in ipexec while we have shims + # should we keep suppressing warnings here, even after removing shims? + env['PYTHONWARNINGS'] = 'ignore' + # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr + for k, v in env.items(): + # Debug a bizarre failure we've seen on Windows: + # TypeError: environment can only contain strings + if not isinstance(v, str): + print(k, v) + p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env) + out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None) + out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err) + # `import readline` causes 'ESC[?1034h' to be output sometimes, + # so strip that out before doing comparisons + if out: + out = re.sub(r'\x1b\[[^h]+h', '', out) + return out, err + + +def ipexec_validate(fname, expected_out, expected_err='', + options=None, commands=()): + """Utility to call 'ipython filename' and validate output/error. + + This function raises an AssertionError if the validation fails. + + Note that this starts IPython in a subprocess! + + Parameters + ---------- + fname : str + Name of the file to be executed (should have .py or .ipy extension). + + expected_out : str + Expected stdout of the process. + + expected_err : optional, str + Expected stderr of the process. + + options : optional, list + Extra command-line flags to be passed to IPython. + + Returns + ------- + None + """ + + import nose.tools as nt + + out, err = ipexec(fname, options, commands) + #print 'OUT', out # dbg + #print 'ERR', err # dbg + # If there are any errors, we must check those befor stdout, as they may be + # more informative than simply having an empty stdout. + if err: + if expected_err: + nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines())) + else: + raise ValueError('Running file %r produced error: %r' % + (fname, err)) + # If no errors or output on stderr was expected, match stdout + nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines())) + + +class TempFileMixin(object): + """Utility class to create temporary Python/IPython files. + + Meant as a mixin class for test cases.""" + + def mktmp(self, src, ext='.py'): + """Make a valid python temp file.""" + fname, f = temp_pyfile(src, ext) + self.tmpfile = f + self.fname = fname + + def tearDown(self): + if hasattr(self, 'tmpfile'): + # If the tmpfile wasn't made because of skipped tests, like in + # win32, there's nothing to cleanup. + self.tmpfile.close() + try: + os.unlink(self.fname) + except: + # On Windows, even though we close the file, we still can't + # delete it. I have no clue why + pass + def __enter__(self): return self @@ -298,149 +298,149 @@ class TempFileMixin(object): self.tearDown() -pair_fail_msg = ("Testing {0}\n\n" - "In:\n" - " {1!r}\n" - "Expected:\n" - " {2!r}\n" - "Got:\n" - " {3!r}\n") -def check_pairs(func, pairs): - """Utility function for the common case of checking a function with a - sequence of input/output pairs. - - Parameters - ---------- - func : callable - The function to be tested. Should accept a single argument. - pairs : iterable - A list of (input, expected_output) tuples. - - Returns - ------- - None. Raises an AssertionError if any output does not match the expected - value. - """ - name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>")) - for inp, expected in pairs: - out = func(inp) - assert out == expected, pair_fail_msg.format(name, inp, expected, out) - - -if py3compat.PY3: - MyStringIO = StringIO -else: - # In Python 2, stdout/stderr can have either bytes or unicode written to them, - # so we need a class that can handle both. - class MyStringIO(StringIO): - def write(self, s): - s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING) - super(MyStringIO, self).write(s) - -_re_type = type(re.compile(r'')) - -notprinted_msg = """Did not find {0!r} in printed output (on {1}): -------- -{2!s} -------- -""" - -class AssertPrints(object): - """Context manager for testing that code prints certain text. - - Examples - -------- - >>> with AssertPrints("abc", suppress=False): - ... print("abcd") - ... print("def") - ... - abcd - def - """ - def __init__(self, s, channel='stdout', suppress=True): - self.s = s - if isinstance(self.s, (py3compat.string_types, _re_type)): - self.s = [self.s] - self.channel = channel - self.suppress = suppress - - def __enter__(self): - self.orig_stream = getattr(sys, self.channel) - self.buffer = MyStringIO() - self.tee = Tee(self.buffer, channel=self.channel) - setattr(sys, self.channel, self.buffer if self.suppress else self.tee) - - def __exit__(self, etype, value, traceback): - try: - if value is not None: - # If an error was raised, don't check anything else - return False - self.tee.flush() - setattr(sys, self.channel, self.orig_stream) - printed = self.buffer.getvalue() - for s in self.s: - if isinstance(s, _re_type): - assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed) - else: - assert s in printed, notprinted_msg.format(s, self.channel, printed) - return False - finally: - self.tee.close() - -printed_msg = """Found {0!r} in printed output (on {1}): -------- -{2!s} -------- -""" - -class AssertNotPrints(AssertPrints): - """Context manager for checking that certain output *isn't* produced. - - Counterpart of AssertPrints""" - def __exit__(self, etype, value, traceback): - try: - if value is not None: - # If an error was raised, don't check anything else - self.tee.close() - return False - self.tee.flush() - setattr(sys, self.channel, self.orig_stream) - printed = self.buffer.getvalue() - for s in self.s: - if isinstance(s, _re_type): - assert not s.search(printed),printed_msg.format( - s.pattern, self.channel, printed) - else: - assert s not in printed, printed_msg.format( - s, self.channel, printed) - return False - finally: - self.tee.close() - -@contextmanager -def mute_warn(): - from IPython.utils import warn - save_warn = warn.warn - warn.warn = lambda *a, **kw: None - try: - yield - finally: - warn.warn = save_warn - -@contextmanager -def make_tempfile(name): - """ Create an empty, named, temporary file for the duration of the context. - """ - f = open(name, 'w') - f.close() - try: - yield - finally: - os.unlink(name) - +pair_fail_msg = ("Testing {0}\n\n" + "In:\n" + " {1!r}\n" + "Expected:\n" + " {2!r}\n" + "Got:\n" + " {3!r}\n") +def check_pairs(func, pairs): + """Utility function for the common case of checking a function with a + sequence of input/output pairs. + + Parameters + ---------- + func : callable + The function to be tested. Should accept a single argument. + pairs : iterable + A list of (input, expected_output) tuples. + + Returns + ------- + None. Raises an AssertionError if any output does not match the expected + value. + """ + name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>")) + for inp, expected in pairs: + out = func(inp) + assert out == expected, pair_fail_msg.format(name, inp, expected, out) + + +if py3compat.PY3: + MyStringIO = StringIO +else: + # In Python 2, stdout/stderr can have either bytes or unicode written to them, + # so we need a class that can handle both. + class MyStringIO(StringIO): + def write(self, s): + s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING) + super(MyStringIO, self).write(s) + +_re_type = type(re.compile(r'')) + +notprinted_msg = """Did not find {0!r} in printed output (on {1}): +------- +{2!s} +------- +""" + +class AssertPrints(object): + """Context manager for testing that code prints certain text. + + Examples + -------- + >>> with AssertPrints("abc", suppress=False): + ... print("abcd") + ... print("def") + ... + abcd + def + """ + def __init__(self, s, channel='stdout', suppress=True): + self.s = s + if isinstance(self.s, (py3compat.string_types, _re_type)): + self.s = [self.s] + self.channel = channel + self.suppress = suppress + + def __enter__(self): + self.orig_stream = getattr(sys, self.channel) + self.buffer = MyStringIO() + self.tee = Tee(self.buffer, channel=self.channel) + setattr(sys, self.channel, self.buffer if self.suppress else self.tee) + + def __exit__(self, etype, value, traceback): + try: + if value is not None: + # If an error was raised, don't check anything else + return False + self.tee.flush() + setattr(sys, self.channel, self.orig_stream) + printed = self.buffer.getvalue() + for s in self.s: + if isinstance(s, _re_type): + assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed) + else: + assert s in printed, notprinted_msg.format(s, self.channel, printed) + return False + finally: + self.tee.close() + +printed_msg = """Found {0!r} in printed output (on {1}): +------- +{2!s} +------- +""" + +class AssertNotPrints(AssertPrints): + """Context manager for checking that certain output *isn't* produced. + + Counterpart of AssertPrints""" + def __exit__(self, etype, value, traceback): + try: + if value is not None: + # If an error was raised, don't check anything else + self.tee.close() + return False + self.tee.flush() + setattr(sys, self.channel, self.orig_stream) + printed = self.buffer.getvalue() + for s in self.s: + if isinstance(s, _re_type): + assert not s.search(printed),printed_msg.format( + s.pattern, self.channel, printed) + else: + assert s not in printed, printed_msg.format( + s, self.channel, printed) + return False + finally: + self.tee.close() + +@contextmanager +def mute_warn(): + from IPython.utils import warn + save_warn = warn.warn + warn.warn = lambda *a, **kw: None + try: + yield + finally: + warn.warn = save_warn + +@contextmanager +def make_tempfile(name): + """ Create an empty, named, temporary file for the duration of the context. + """ + f = open(name, 'w') + f.close() + try: + yield + finally: + os.unlink(name) + def fake_input(inputs): """Temporarily replace the input() function to return the given values - + Use as a context manager: with fake_input(['result1', 'result2']): @@ -460,24 +460,24 @@ def fake_input(inputs): 'input' if py3compat.PY3 else 'raw_input') return patch(input_name, mock_input) -def help_output_test(subcommand=''): - """test that `ipython [subcommand] -h` works""" - cmd = get_ipython_cmd() + [subcommand, '-h'] - out, err, rc = get_output_error_code(cmd) - nt.assert_equal(rc, 0, err) - nt.assert_not_in("Traceback", err) - nt.assert_in("Options", out) - nt.assert_in("--help-all", out) - return out, err - - -def help_all_output_test(subcommand=''): - """test that `ipython [subcommand] --help-all` works""" - cmd = get_ipython_cmd() + [subcommand, '--help-all'] - out, err, rc = get_output_error_code(cmd) - nt.assert_equal(rc, 0, err) - nt.assert_not_in("Traceback", err) - nt.assert_in("Options", out) +def help_output_test(subcommand=''): + """test that `ipython [subcommand] -h` works""" + cmd = get_ipython_cmd() + [subcommand, '-h'] + out, err, rc = get_output_error_code(cmd) + nt.assert_equal(rc, 0, err) + nt.assert_not_in("Traceback", err) + nt.assert_in("Options", out) + nt.assert_in("--help-all", out) + return out, err + + +def help_all_output_test(subcommand=''): + """test that `ipython [subcommand] --help-all` works""" + cmd = get_ipython_cmd() + [subcommand, '--help-all'] + out, err, rc = get_output_error_code(cmd) + nt.assert_equal(rc, 0, err) + nt.assert_not_in("Traceback", err) + nt.assert_in("Options", out) nt.assert_in("Class", out) - return out, err - + return out, err + |