aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/ipython/py3/IPython/testing
diff options
context:
space:
mode:
authornkozlovskiy <nmk@ydb.tech>2023-09-29 12:24:06 +0300
committernkozlovskiy <nmk@ydb.tech>2023-09-29 12:41:34 +0300
commite0e3e1717e3d33762ce61950504f9637a6e669ed (patch)
treebca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/ipython/py3/IPython/testing
parent38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff)
downloadydb-e0e3e1717e3d33762ce61950504f9637a6e669ed.tar.gz
add ydb deps
Diffstat (limited to 'contrib/python/ipython/py3/IPython/testing')
-rw-r--r--contrib/python/ipython/py3/IPython/testing/__init__.py20
-rw-r--r--contrib/python/ipython/py3/IPython/testing/decorators.py201
-rw-r--r--contrib/python/ipython/py3/IPython/testing/globalipapp.py114
-rw-r--r--contrib/python/ipython/py3/IPython/testing/ipunittest.py178
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/README.txt34
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/__init__.py0
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/dtexample.py167
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/ipdoctest.py299
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/pytest_ipdoctest.py859
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/setup.py18
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/simple.py44
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/simplevars.py2
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/test_combo.txt36
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/test_example.txt24
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/test_exampleip.txt30
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/test_ipdoctest.py92
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/test_refs.py39
-rw-r--r--contrib/python/ipython/py3/IPython/testing/skipdoctest.py19
-rw-r--r--contrib/python/ipython/py3/IPython/testing/tools.py476
19 files changed, 2652 insertions, 0 deletions
diff --git a/contrib/python/ipython/py3/IPython/testing/__init__.py b/contrib/python/ipython/py3/IPython/testing/__init__.py
new file mode 100644
index 0000000000..8fcd65ea41
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/__init__.py
@@ -0,0 +1,20 @@
+"""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.
+#-----------------------------------------------------------------------------
+
+
+import os
+
+#-----------------------------------------------------------------------------
+# Constants
+#-----------------------------------------------------------------------------
+
+# We scale all timeouts via this factor, slow machines can increase it
+IPYTHON_TESTING_TIMEOUT_SCALE = float(os.getenv(
+ 'IPYTHON_TESTING_TIMEOUT_SCALE', 1))
diff --git a/contrib/python/ipython/py3/IPython/testing/decorators.py b/contrib/python/ipython/py3/IPython/testing/decorators.py
new file mode 100644
index 0000000000..af42f349d5
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/decorators.py
@@ -0,0 +1,201 @@
+# -*- 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 os
+import shutil
+import sys
+import tempfile
+import unittest
+from importlib import import_module
+
+from decorator import decorator
+
+# Expose the unittest-driven decorators
+from .ipunittest import ipdoctest, ipdocstring
+
+#-----------------------------------------------------------------------------
+# 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 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.
+ """
+ if msg is None:
+ msg = "Test skipped due to test condition."
+
+ import pytest
+
+ assert isinstance(skip_condition, bool)
+ return pytest.mark.skipif(skip_condition, reason=msg)
+
+
+# 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.
+ """
+ if msg and not isinstance(msg, str):
+ raise ValueError('invalid object passed to `@skip` decorator, did you '
+ 'meant `@skip()` with brackets ?')
+ return skipif(True, msg)
+
+
+def onlyif(condition, msg):
+ """The reverse from skipif, see skipif for details."""
+
+ return skipif(not 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(module)
+ mod_not_avail = False
+ except ImportError:
+ mod_not_avail = True
+
+ return mod_not_avail
+
+
+#-----------------------------------------------------------------------------
+# 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")
+
+_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)
+
+# 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')
+
+# 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.
+ """
+ assert (
+ os.environ.get("IPTEST_WORKING_DIR", None) is None
+ ), "iptest deprecated since IPython 8.0"
+ for cmd in commands:
+ reason = f"This test runs only if command '{cmd}' is installed"
+ if not shutil.which(cmd):
+ import pytest
+
+ return pytest.mark.skip(reason=reason)
+ return null_deco
diff --git a/contrib/python/ipython/py3/IPython/testing/globalipapp.py b/contrib/python/ipython/py3/IPython/testing/globalipapp.py
new file mode 100644
index 0000000000..3a699e07d6
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/globalipapp.py
@@ -0,0 +1,114 @@
+"""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.
+"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import builtins as builtin_mod
+import sys
+import types
+
+from pathlib import Path
+
+from . import tools
+
+from IPython.core import page
+from IPython.utils import io
+from IPython.terminal.interactiveshell import TerminalInteractiveShell
+
+
+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(Path(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 = types.MethodType(xsys, shell)
+
+ shell._showtraceback = types.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.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
diff --git a/contrib/python/ipython/py3/IPython/testing/ipunittest.py b/contrib/python/ipython/py3/IPython/testing/ipunittest.py
new file mode 100644
index 0000000000..5a940a5fe9
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/ipunittest.py
@@ -0,0 +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>
+"""
+
+
+#-----------------------------------------------------------------------------
+# 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
+from IPython.terminal.interactiveshell import InteractiveShell
+
+#-----------------------------------------------------------------------------
+# 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 conversion."""
+
+ 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 (r'(\s*%s)(.*)$' % self.pyps1)
+ self.rpyps2 = re.compile (r'(\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 = InteractiveShell.instance()
+
+ # 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: %s" % 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/py3/IPython/testing/plugin/README.txt b/contrib/python/ipython/py3/IPython/testing/plugin/README.txt
new file mode 100644
index 0000000000..a85e5a12a1
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/README.txt
@@ -0,0 +1,34 @@
+=======================================================
+ 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.
+
+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/py3/IPython/testing/plugin/__init__.py b/contrib/python/ipython/py3/IPython/testing/plugin/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/__init__.py
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/dtexample.py b/contrib/python/ipython/py3/IPython/testing/plugin/dtexample.py
new file mode 100644
index 0000000000..68f7016e34
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/dtexample.py
@@ -0,0 +1,167 @@
+"""Simple example using doctests.
+
+This file just contains doctests both using plain python and IPython prompts.
+All tests should be loaded by nose.
+"""
+
+import os
+
+
+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
+
+
+ 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 ipos():
+ """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
+ """
+ pass
+
+
+ipos.__skip_doctest__ = os.name == "nt"
+
+
+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/py3/IPython/testing/plugin/ipdoctest.py b/contrib/python/ipython/py3/IPython/testing/plugin/ipdoctest.py
new file mode 100644
index 0000000000..e7edf9837f
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/ipdoctest.py
@@ -0,0 +1,299 @@
+"""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 logging
+import re
+
+from testpath import modified_env
+
+#-----------------------------------------------------------------------------
+# Module globals and other constants
+#-----------------------------------------------------------------------------
+
+log = logging.getLogger(__name__)
+
+
+#-----------------------------------------------------------------------------
+# Classes and functions
+#-----------------------------------------------------------------------------
+
+
+class DocTestFinder(doctest.DocTestFinder):
+ def _get_test(self, obj, name, module, globs, source_lines):
+ test = super()._get_test(obj, name, module, globs, source_lines)
+
+ if bool(getattr(obj, "__skip_doctest__", False)) and test is not None:
+ for example in test.examples:
+ example.options[doctest.SKIP] = True
+
+ return test
+
+
+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
+
+
+# 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 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+')
+
+ 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
+ Example = doctest.Example
+ else:
+ # It's an ipython example.
+ terms = list(self._EXAMPLE_RE_IP.finditer(string))
+ 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
+
+ # 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):
+ # Override terminal size to standardise traceback format
+ with modified_env({'COLUMNS': '80', 'LINES': '24'}):
+ return super(IPDocTestRunner,self).run(test,
+ compileflags,out,clear_globs)
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/pytest_ipdoctest.py b/contrib/python/ipython/py3/IPython/testing/plugin/pytest_ipdoctest.py
new file mode 100644
index 0000000000..fd19ba4966
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/pytest_ipdoctest.py
@@ -0,0 +1,859 @@
+# Based on Pytest doctest.py
+# Original license:
+# The MIT License (MIT)
+#
+# Copyright (c) 2004-2021 Holger Krekel and others
+"""Discover and run ipdoctests in modules and test files."""
+import builtins
+import bdb
+import inspect
+import os
+import platform
+import sys
+import traceback
+import types
+import warnings
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import Generator
+from typing import Iterable
+from typing import List
+from typing import Optional
+from typing import Pattern
+from typing import Sequence
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import Union
+
+import pytest
+from _pytest import outcomes
+from _pytest._code.code import ExceptionInfo
+from _pytest._code.code import ReprFileLocation
+from _pytest._code.code import TerminalRepr
+from _pytest._io import TerminalWriter
+from _pytest.compat import safe_getattr
+from _pytest.config import Config
+from _pytest.config.argparsing import Parser
+from _pytest.fixtures import FixtureRequest
+from _pytest.nodes import Collector
+from _pytest.outcomes import OutcomeException
+from _pytest.pathlib import fnmatch_ex
+from _pytest.pathlib import import_path
+from _pytest.python_api import approx
+from _pytest.warning_types import PytestWarning
+
+if TYPE_CHECKING:
+ import doctest
+
+DOCTEST_REPORT_CHOICE_NONE = "none"
+DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
+DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
+DOCTEST_REPORT_CHOICE_UDIFF = "udiff"
+DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure"
+
+DOCTEST_REPORT_CHOICES = (
+ DOCTEST_REPORT_CHOICE_NONE,
+ DOCTEST_REPORT_CHOICE_CDIFF,
+ DOCTEST_REPORT_CHOICE_NDIFF,
+ DOCTEST_REPORT_CHOICE_UDIFF,
+ DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
+)
+
+# Lazy definition of runner class
+RUNNER_CLASS = None
+# Lazy definition of output checker class
+CHECKER_CLASS: Optional[Type["IPDoctestOutputChecker"]] = None
+
+
+def pytest_addoption(parser: Parser) -> None:
+ parser.addini(
+ "ipdoctest_optionflags",
+ "option flags for ipdoctests",
+ type="args",
+ default=["ELLIPSIS"],
+ )
+ parser.addini(
+ "ipdoctest_encoding", "encoding used for ipdoctest files", default="utf-8"
+ )
+ group = parser.getgroup("collect")
+ group.addoption(
+ "--ipdoctest-modules",
+ action="store_true",
+ default=False,
+ help="run ipdoctests in all .py modules",
+ dest="ipdoctestmodules",
+ )
+ group.addoption(
+ "--ipdoctest-report",
+ type=str.lower,
+ default="udiff",
+ help="choose another output format for diffs on ipdoctest failure",
+ choices=DOCTEST_REPORT_CHOICES,
+ dest="ipdoctestreport",
+ )
+ group.addoption(
+ "--ipdoctest-glob",
+ action="append",
+ default=[],
+ metavar="pat",
+ help="ipdoctests file matching pattern, default: test*.txt",
+ dest="ipdoctestglob",
+ )
+ group.addoption(
+ "--ipdoctest-ignore-import-errors",
+ action="store_true",
+ default=False,
+ help="ignore ipdoctest ImportErrors",
+ dest="ipdoctest_ignore_import_errors",
+ )
+ group.addoption(
+ "--ipdoctest-continue-on-failure",
+ action="store_true",
+ default=False,
+ help="for a given ipdoctest, continue to run after the first failure",
+ dest="ipdoctest_continue_on_failure",
+ )
+
+
+def pytest_unconfigure() -> None:
+ global RUNNER_CLASS
+
+ RUNNER_CLASS = None
+
+
+def pytest_collect_file(
+ file_path: Path,
+ parent: Collector,
+) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]:
+ config = parent.config
+ if file_path.suffix == ".py":
+ if config.option.ipdoctestmodules and not any(
+ (_is_setup_py(file_path), _is_main_py(file_path))
+ ):
+ mod: IPDoctestModule = IPDoctestModule.from_parent(parent, path=file_path)
+ return mod
+ elif _is_ipdoctest(config, file_path, parent):
+ txt: IPDoctestTextfile = IPDoctestTextfile.from_parent(parent, path=file_path)
+ return txt
+ return None
+
+
+if int(pytest.__version__.split(".")[0]) < 7:
+ _collect_file = pytest_collect_file
+
+ def pytest_collect_file(
+ path,
+ parent: Collector,
+ ) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]:
+ return _collect_file(Path(path), parent)
+
+ _import_path = import_path
+
+ def import_path(path, root):
+ import py.path
+
+ return _import_path(py.path.local(path))
+
+
+def _is_setup_py(path: Path) -> bool:
+ if path.name != "setup.py":
+ return False
+ contents = path.read_bytes()
+ return b"setuptools" in contents or b"distutils" in contents
+
+
+def _is_ipdoctest(config: Config, path: Path, parent: Collector) -> bool:
+ if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path):
+ return True
+ globs = config.getoption("ipdoctestglob") or ["test*.txt"]
+ return any(fnmatch_ex(glob, path) for glob in globs)
+
+
+def _is_main_py(path: Path) -> bool:
+ return path.name == "__main__.py"
+
+
+class ReprFailDoctest(TerminalRepr):
+ def __init__(
+ self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]]
+ ) -> None:
+ self.reprlocation_lines = reprlocation_lines
+
+ def toterminal(self, tw: TerminalWriter) -> None:
+ for reprlocation, lines in self.reprlocation_lines:
+ for line in lines:
+ tw.line(line)
+ reprlocation.toterminal(tw)
+
+
+class MultipleDoctestFailures(Exception):
+ def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None:
+ super().__init__()
+ self.failures = failures
+
+
+def _init_runner_class() -> Type["IPDocTestRunner"]:
+ import doctest
+ from .ipdoctest import IPDocTestRunner
+
+ class PytestDoctestRunner(IPDocTestRunner):
+ """Runner to collect failures.
+
+ Note that the out variable in this case is a list instead of a
+ stdout-like object.
+ """
+
+ def __init__(
+ self,
+ checker: Optional["IPDoctestOutputChecker"] = None,
+ verbose: Optional[bool] = None,
+ optionflags: int = 0,
+ continue_on_failure: bool = True,
+ ) -> None:
+ super().__init__(checker=checker, verbose=verbose, optionflags=optionflags)
+ self.continue_on_failure = continue_on_failure
+
+ def report_failure(
+ self,
+ out,
+ test: "doctest.DocTest",
+ example: "doctest.Example",
+ got: str,
+ ) -> None:
+ failure = doctest.DocTestFailure(test, example, got)
+ if self.continue_on_failure:
+ out.append(failure)
+ else:
+ raise failure
+
+ def report_unexpected_exception(
+ self,
+ out,
+ test: "doctest.DocTest",
+ example: "doctest.Example",
+ exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType],
+ ) -> None:
+ if isinstance(exc_info[1], OutcomeException):
+ raise exc_info[1]
+ if isinstance(exc_info[1], bdb.BdbQuit):
+ outcomes.exit("Quitting debugger")
+ failure = doctest.UnexpectedException(test, example, exc_info)
+ if self.continue_on_failure:
+ out.append(failure)
+ else:
+ raise failure
+
+ return PytestDoctestRunner
+
+
+def _get_runner(
+ checker: Optional["IPDoctestOutputChecker"] = None,
+ verbose: Optional[bool] = None,
+ optionflags: int = 0,
+ continue_on_failure: bool = True,
+) -> "IPDocTestRunner":
+ # We need this in order to do a lazy import on doctest
+ global RUNNER_CLASS
+ if RUNNER_CLASS is None:
+ RUNNER_CLASS = _init_runner_class()
+ # Type ignored because the continue_on_failure argument is only defined on
+ # PytestDoctestRunner, which is lazily defined so can't be used as a type.
+ return RUNNER_CLASS( # type: ignore
+ checker=checker,
+ verbose=verbose,
+ optionflags=optionflags,
+ continue_on_failure=continue_on_failure,
+ )
+
+
+class IPDoctestItem(pytest.Item):
+ def __init__(
+ self,
+ name: str,
+ parent: "Union[IPDoctestTextfile, IPDoctestModule]",
+ runner: Optional["IPDocTestRunner"] = None,
+ dtest: Optional["doctest.DocTest"] = None,
+ ) -> None:
+ super().__init__(name, parent)
+ self.runner = runner
+ self.dtest = dtest
+ self.obj = None
+ self.fixture_request: Optional[FixtureRequest] = None
+
+ @classmethod
+ def from_parent( # type: ignore
+ cls,
+ parent: "Union[IPDoctestTextfile, IPDoctestModule]",
+ *,
+ name: str,
+ runner: "IPDocTestRunner",
+ dtest: "doctest.DocTest",
+ ):
+ # incompatible signature due to imposed limits on subclass
+ """The public named constructor."""
+ return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
+
+ def setup(self) -> None:
+ if self.dtest is not None:
+ self.fixture_request = _setup_fixtures(self)
+ globs = dict(getfixture=self.fixture_request.getfixturevalue)
+ for name, value in self.fixture_request.getfixturevalue(
+ "ipdoctest_namespace"
+ ).items():
+ globs[name] = value
+ self.dtest.globs.update(globs)
+
+ from .ipdoctest import IPExample
+
+ if isinstance(self.dtest.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.dtest.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__"] = builtins
+ self.dtest.globs = _ip.user_ns
+
+ def teardown(self) -> None:
+ from .ipdoctest import IPExample
+
+ # Undo the test.globs reassignment we made
+ if isinstance(self.dtest.examples[0], IPExample):
+ self.dtest.globs = {}
+ _ip.user_ns.clear()
+ _ip.user_ns.update(self._user_ns_orig)
+ del self._user_ns_orig
+
+ self.dtest.globs.clear()
+
+ def runtest(self) -> None:
+ assert self.dtest is not None
+ assert self.runner is not None
+ _check_all_skipped(self.dtest)
+ self._disable_output_capturing_for_darwin()
+ failures: List["doctest.DocTestFailure"] = []
+
+ # exec(compile(..., "single", ...), ...) puts result in builtins._
+ had_underscore_value = hasattr(builtins, "_")
+ underscore_original_value = getattr(builtins, "_", None)
+
+ # 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 = os.getcwd()
+ os.chdir(self.fspath.dirname)
+ try:
+ # Type ignored because we change the type of `out` from what
+ # ipdoctest expects.
+ self.runner.run(self.dtest, out=failures, clear_globs=False) # type: ignore[arg-type]
+ finally:
+ os.chdir(curdir)
+ if had_underscore_value:
+ setattr(builtins, "_", underscore_original_value)
+ elif hasattr(builtins, "_"):
+ delattr(builtins, "_")
+
+ if failures:
+ raise MultipleDoctestFailures(failures)
+
+ def _disable_output_capturing_for_darwin(self) -> None:
+ """Disable output capturing. Otherwise, stdout is lost to ipdoctest (pytest#985)."""
+ if platform.system() != "Darwin":
+ return
+ capman = self.config.pluginmanager.getplugin("capturemanager")
+ if capman:
+ capman.suspend_global_capture(in_=True)
+ out, err = capman.read_global_capture()
+ sys.stdout.write(out)
+ sys.stderr.write(err)
+
+ # TODO: Type ignored -- breaks Liskov Substitution.
+ def repr_failure( # type: ignore[override]
+ self,
+ excinfo: ExceptionInfo[BaseException],
+ ) -> Union[str, TerminalRepr]:
+ import doctest
+
+ failures: Optional[
+ Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]
+ ] = None
+ if isinstance(
+ excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
+ ):
+ failures = [excinfo.value]
+ elif isinstance(excinfo.value, MultipleDoctestFailures):
+ failures = excinfo.value.failures
+
+ if failures is None:
+ return super().repr_failure(excinfo)
+
+ reprlocation_lines = []
+ for failure in failures:
+ example = failure.example
+ test = failure.test
+ filename = test.filename
+ if test.lineno is None:
+ lineno = None
+ else:
+ lineno = test.lineno + example.lineno + 1
+ message = type(failure).__name__
+ # TODO: ReprFileLocation doesn't expect a None lineno.
+ reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type]
+ checker = _get_checker()
+ report_choice = _get_report_choice(self.config.getoption("ipdoctestreport"))
+ if lineno is not None:
+ assert failure.test.docstring is not None
+ lines = failure.test.docstring.splitlines(False)
+ # add line numbers to the left of the error message
+ assert test.lineno is not None
+ lines = [
+ "%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines)
+ ]
+ # trim docstring error lines to 10
+ lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
+ else:
+ lines = [
+ "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example"
+ ]
+ indent = ">>>"
+ for line in example.source.splitlines():
+ lines.append(f"??? {indent} {line}")
+ indent = "..."
+ if isinstance(failure, doctest.DocTestFailure):
+ lines += checker.output_difference(
+ example, failure.got, report_choice
+ ).split("\n")
+ else:
+ inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info)
+ lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
+ lines += [
+ x.strip("\n") for x in traceback.format_exception(*failure.exc_info)
+ ]
+ reprlocation_lines.append((reprlocation, lines))
+ return ReprFailDoctest(reprlocation_lines)
+
+ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
+ assert self.dtest is not None
+ return self.path, self.dtest.lineno, "[ipdoctest] %s" % self.name
+
+ if int(pytest.__version__.split(".")[0]) < 7:
+
+ @property
+ def path(self) -> Path:
+ return Path(self.fspath)
+
+
+def _get_flag_lookup() -> Dict[str, int]:
+ import doctest
+
+ return dict(
+ DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
+ DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE,
+ NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
+ ELLIPSIS=doctest.ELLIPSIS,
+ IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
+ COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
+ ALLOW_UNICODE=_get_allow_unicode_flag(),
+ ALLOW_BYTES=_get_allow_bytes_flag(),
+ NUMBER=_get_number_flag(),
+ )
+
+
+def get_optionflags(parent):
+ optionflags_str = parent.config.getini("ipdoctest_optionflags")
+ flag_lookup_table = _get_flag_lookup()
+ flag_acc = 0
+ for flag in optionflags_str:
+ flag_acc |= flag_lookup_table[flag]
+ return flag_acc
+
+
+def _get_continue_on_failure(config):
+ continue_on_failure = config.getvalue("ipdoctest_continue_on_failure")
+ if continue_on_failure:
+ # We need to turn off this if we use pdb since we should stop at
+ # the first failure.
+ if config.getvalue("usepdb"):
+ continue_on_failure = False
+ return continue_on_failure
+
+
+class IPDoctestTextfile(pytest.Module):
+ obj = None
+
+ def collect(self) -> Iterable[IPDoctestItem]:
+ import doctest
+ from .ipdoctest import IPDocTestParser
+
+ # Inspired by doctest.testfile; ideally we would use it directly,
+ # but it doesn't support passing a custom checker.
+ encoding = self.config.getini("ipdoctest_encoding")
+ text = self.path.read_text(encoding)
+ filename = str(self.path)
+ name = self.path.name
+ globs = {"__name__": "__main__"}
+
+ optionflags = get_optionflags(self)
+
+ runner = _get_runner(
+ verbose=False,
+ optionflags=optionflags,
+ checker=_get_checker(),
+ continue_on_failure=_get_continue_on_failure(self.config),
+ )
+
+ parser = IPDocTestParser()
+ test = parser.get_doctest(text, globs, name, filename, 0)
+ if test.examples:
+ yield IPDoctestItem.from_parent(
+ self, name=test.name, runner=runner, dtest=test
+ )
+
+ if int(pytest.__version__.split(".")[0]) < 7:
+
+ @property
+ def path(self) -> Path:
+ return Path(self.fspath)
+
+ @classmethod
+ def from_parent(
+ cls,
+ parent,
+ *,
+ fspath=None,
+ path: Optional[Path] = None,
+ **kw,
+ ):
+ if path is not None:
+ import py.path
+
+ fspath = py.path.local(path)
+ return super().from_parent(parent=parent, fspath=fspath, **kw)
+
+
+def _check_all_skipped(test: "doctest.DocTest") -> None:
+ """Raise pytest.skip() if all examples in the given DocTest have the SKIP
+ option set."""
+ import doctest
+
+ all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
+ if all_skipped:
+ pytest.skip("all docstests skipped by +SKIP option")
+
+
+def _is_mocked(obj: object) -> bool:
+ """Return if an object is possibly a mock object by checking the
+ existence of a highly improbable attribute."""
+ return (
+ safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None)
+ is not None
+ )
+
+
+@contextmanager
+def _patch_unwrap_mock_aware() -> Generator[None, None, None]:
+ """Context manager which replaces ``inspect.unwrap`` with a version
+ that's aware of mock objects and doesn't recurse into them."""
+ real_unwrap = inspect.unwrap
+
+ def _mock_aware_unwrap(
+ func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None
+ ) -> Any:
+ try:
+ if stop is None or stop is _is_mocked:
+ return real_unwrap(func, stop=_is_mocked)
+ _stop = stop
+ return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func))
+ except Exception as e:
+ warnings.warn(
+ "Got %r when unwrapping %r. This is usually caused "
+ "by a violation of Python's object protocol; see e.g. "
+ "https://github.com/pytest-dev/pytest/issues/5080" % (e, func),
+ PytestWarning,
+ )
+ raise
+
+ inspect.unwrap = _mock_aware_unwrap
+ try:
+ yield
+ finally:
+ inspect.unwrap = real_unwrap
+
+
+class IPDoctestModule(pytest.Module):
+ def collect(self) -> Iterable[IPDoctestItem]:
+ import doctest
+ from .ipdoctest import DocTestFinder, IPDocTestParser
+
+ class MockAwareDocTestFinder(DocTestFinder):
+ """A hackish ipdoctest finder that overrides stdlib internals to fix a stdlib bug.
+
+ https://github.com/pytest-dev/pytest/issues/3456
+ https://bugs.python.org/issue25532
+ """
+
+ def _find_lineno(self, obj, source_lines):
+ """Doctest code does not take into account `@property`, this
+ is a hackish way to fix it. https://bugs.python.org/issue17446
+
+ Wrapped Doctests will need to be unwrapped so the correct
+ line number is returned. This will be reported upstream. #8796
+ """
+ if isinstance(obj, property):
+ obj = getattr(obj, "fget", obj)
+
+ if hasattr(obj, "__wrapped__"):
+ # Get the main obj in case of it being wrapped
+ obj = inspect.unwrap(obj)
+
+ # Type ignored because this is a private function.
+ return super()._find_lineno( # type:ignore[misc]
+ obj,
+ source_lines,
+ )
+
+ def _find(
+ self, tests, obj, name, module, source_lines, globs, seen
+ ) -> None:
+ if _is_mocked(obj):
+ return
+ with _patch_unwrap_mock_aware():
+ # Type ignored because this is a private function.
+ super()._find( # type:ignore[misc]
+ tests, obj, name, module, source_lines, globs, seen
+ )
+
+ if self.path.name == "conftest.py":
+ if int(pytest.__version__.split(".")[0]) < 7:
+ module = self.config.pluginmanager._importconftest(
+ self.path,
+ self.config.getoption("importmode"),
+ )
+ else:
+ module = self.config.pluginmanager._importconftest(
+ self.path,
+ self.config.getoption("importmode"),
+ rootpath=self.config.rootpath,
+ )
+ else:
+ try:
+ module = import_path(self.path, root=self.config.rootpath)
+ except ImportError:
+ if self.config.getvalue("ipdoctest_ignore_import_errors"):
+ pytest.skip("unable to import module %r" % self.path)
+ else:
+ raise
+ # Uses internal doctest module parsing mechanism.
+ finder = MockAwareDocTestFinder(parser=IPDocTestParser())
+ optionflags = get_optionflags(self)
+ runner = _get_runner(
+ verbose=False,
+ optionflags=optionflags,
+ checker=_get_checker(),
+ continue_on_failure=_get_continue_on_failure(self.config),
+ )
+
+ for test in finder.find(module, module.__name__):
+ if test.examples: # skip empty ipdoctests
+ yield IPDoctestItem.from_parent(
+ self, name=test.name, runner=runner, dtest=test
+ )
+
+ if int(pytest.__version__.split(".")[0]) < 7:
+
+ @property
+ def path(self) -> Path:
+ return Path(self.fspath)
+
+ @classmethod
+ def from_parent(
+ cls,
+ parent,
+ *,
+ fspath=None,
+ path: Optional[Path] = None,
+ **kw,
+ ):
+ if path is not None:
+ import py.path
+
+ fspath = py.path.local(path)
+ return super().from_parent(parent=parent, fspath=fspath, **kw)
+
+
+def _setup_fixtures(doctest_item: IPDoctestItem) -> FixtureRequest:
+ """Used by IPDoctestTextfile and IPDoctestItem to setup fixture information."""
+
+ def func() -> None:
+ pass
+
+ doctest_item.funcargs = {} # type: ignore[attr-defined]
+ fm = doctest_item.session._fixturemanager
+ doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined]
+ node=doctest_item, func=func, cls=None, funcargs=False
+ )
+ fixture_request = FixtureRequest(doctest_item, _ispytest=True)
+ fixture_request._fillfixtures()
+ return fixture_request
+
+
+def _init_checker_class() -> Type["IPDoctestOutputChecker"]:
+ import doctest
+ import re
+ from .ipdoctest import IPDoctestOutputChecker
+
+ class LiteralsOutputChecker(IPDoctestOutputChecker):
+ # Based on doctest_nose_plugin.py from the nltk project
+ # (https://github.com/nltk/nltk) and on the "numtest" doctest extension
+ # by Sebastien Boisgerault (https://github.com/boisgera/numtest).
+
+ _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
+ _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
+ _number_re = re.compile(
+ r"""
+ (?P<number>
+ (?P<mantissa>
+ (?P<integer1> [+-]?\d*)\.(?P<fraction>\d+)
+ |
+ (?P<integer2> [+-]?\d+)\.
+ )
+ (?:
+ [Ee]
+ (?P<exponent1> [+-]?\d+)
+ )?
+ |
+ (?P<integer3> [+-]?\d+)
+ (?:
+ [Ee]
+ (?P<exponent2> [+-]?\d+)
+ )
+ )
+ """,
+ re.VERBOSE,
+ )
+
+ def check_output(self, want: str, got: str, optionflags: int) -> bool:
+ if super().check_output(want, got, optionflags):
+ return True
+
+ allow_unicode = optionflags & _get_allow_unicode_flag()
+ allow_bytes = optionflags & _get_allow_bytes_flag()
+ allow_number = optionflags & _get_number_flag()
+
+ if not allow_unicode and not allow_bytes and not allow_number:
+ return False
+
+ def remove_prefixes(regex: Pattern[str], txt: str) -> str:
+ return re.sub(regex, r"\1\2", txt)
+
+ if allow_unicode:
+ want = remove_prefixes(self._unicode_literal_re, want)
+ got = remove_prefixes(self._unicode_literal_re, got)
+
+ if allow_bytes:
+ want = remove_prefixes(self._bytes_literal_re, want)
+ got = remove_prefixes(self._bytes_literal_re, got)
+
+ if allow_number:
+ got = self._remove_unwanted_precision(want, got)
+
+ return super().check_output(want, got, optionflags)
+
+ def _remove_unwanted_precision(self, want: str, got: str) -> str:
+ wants = list(self._number_re.finditer(want))
+ gots = list(self._number_re.finditer(got))
+ if len(wants) != len(gots):
+ return got
+ offset = 0
+ for w, g in zip(wants, gots):
+ fraction: Optional[str] = w.group("fraction")
+ exponent: Optional[str] = w.group("exponent1")
+ if exponent is None:
+ exponent = w.group("exponent2")
+ precision = 0 if fraction is None else len(fraction)
+ if exponent is not None:
+ precision -= int(exponent)
+ if float(w.group()) == approx(float(g.group()), abs=10**-precision):
+ # They're close enough. Replace the text we actually
+ # got with the text we want, so that it will match when we
+ # check the string literally.
+ got = (
+ got[: g.start() + offset] + w.group() + got[g.end() + offset :]
+ )
+ offset += w.end() - w.start() - (g.end() - g.start())
+ return got
+
+ return LiteralsOutputChecker
+
+
+def _get_checker() -> "IPDoctestOutputChecker":
+ """Return a IPDoctestOutputChecker subclass that supports some
+ additional options:
+
+ * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
+ prefixes (respectively) in string literals. Useful when the same
+ ipdoctest should run in Python 2 and Python 3.
+
+ * NUMBER to ignore floating-point differences smaller than the
+ precision of the literal number in the ipdoctest.
+
+ An inner class is used to avoid importing "ipdoctest" at the module
+ level.
+ """
+ global CHECKER_CLASS
+ if CHECKER_CLASS is None:
+ CHECKER_CLASS = _init_checker_class()
+ return CHECKER_CLASS()
+
+
+def _get_allow_unicode_flag() -> int:
+ """Register and return the ALLOW_UNICODE flag."""
+ import doctest
+
+ return doctest.register_optionflag("ALLOW_UNICODE")
+
+
+def _get_allow_bytes_flag() -> int:
+ """Register and return the ALLOW_BYTES flag."""
+ import doctest
+
+ return doctest.register_optionflag("ALLOW_BYTES")
+
+
+def _get_number_flag() -> int:
+ """Register and return the NUMBER flag."""
+ import doctest
+
+ return doctest.register_optionflag("NUMBER")
+
+
+def _get_report_choice(key: str) -> int:
+ """Return the actual `ipdoctest` module flag value.
+
+ We want to do it as late as possible to avoid importing `ipdoctest` and all
+ its dependencies when parsing options, as it adds overhead and breaks tests.
+ """
+ import doctest
+
+ return {
+ DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF,
+ DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF,
+ DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF,
+ DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE,
+ DOCTEST_REPORT_CHOICE_NONE: 0,
+ }[key]
+
+
+@pytest.fixture(scope="session")
+def ipdoctest_namespace() -> Dict[str, Any]:
+ """Fixture that returns a :py:class:`dict` that will be injected into the
+ namespace of ipdoctests."""
+ return dict()
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/setup.py b/contrib/python/ipython/py3/IPython/testing/plugin/setup.py
new file mode 100644
index 0000000000..a3281d30c8
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/setup.py
@@ -0,0 +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',
+ ],
+ },
+ )
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/simple.py b/contrib/python/ipython/py3/IPython/testing/plugin/simple.py
new file mode 100644
index 0000000000..35fbfd2fbd
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/simple.py
@@ -0,0 +1,44 @@
+"""Simple example using doctests.
+
+This file just contains doctests both using plain python and IPython prompts.
+All tests should be loaded by Pytest.
+"""
+
+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 ipyfunc():
+ """Some IPython tests...
+
+ In [1]: ipyfunc()
+ Out[1]: 'ipyfunc'
+
+ In [2]: import os
+
+ In [3]: 2+3
+ Out[3]: 5
+
+ In [4]: for i in range(3):
+ ...: print(i, end=' ')
+ ...: print(i+1, end=' ')
+ ...:
+ Out[4]: 0 1 1 2 2 3
+ """
+ return "ipyfunc"
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/simplevars.py b/contrib/python/ipython/py3/IPython/testing/plugin/simplevars.py
new file mode 100644
index 0000000000..82a5edb028
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/simplevars.py
@@ -0,0 +1,2 @@
+x = 1
+print("x is:", x)
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/test_combo.txt b/contrib/python/ipython/py3/IPython/testing/plugin/test_combo.txt
new file mode 100644
index 0000000000..6c8759f3e7
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/test_combo.txt
@@ -0,0 +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!
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/test_example.txt b/contrib/python/ipython/py3/IPython/testing/plugin/test_example.txt
new file mode 100644
index 0000000000..f8b681eb4f
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/test_example.txt
@@ -0,0 +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.
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/test_exampleip.txt b/contrib/python/ipython/py3/IPython/testing/plugin/test_exampleip.txt
new file mode 100644
index 0000000000..96b1eae19f
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/test_exampleip.txt
@@ -0,0 +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
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/test_ipdoctest.py b/contrib/python/ipython/py3/IPython/testing/plugin/test_ipdoctest.py
new file mode 100644
index 0000000000..2686172bb2
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/test_ipdoctest.py
@@ -0,0 +1,92 @@
+"""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).
+"""
+
+def doctest_simple():
+ """ipdoctest must handle simple inputs
+
+ In [1]: 1
+ Out[1]: 1
+
+ In [2]: print(1)
+ 1
+ """
+
+def doctest_multiline1():
+ """The ipdoctest machinery must handle multiline examples gracefully.
+
+ In [2]: for i in range(4):
+ ...: print(i)
+ ...:
+ 0
+ 1
+ 2
+ 3
+ """
+
+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
+ """
+
+
+def doctest_builtin_underscore():
+ """Defining builtins._ should not break anything outside the doctest
+ while also should be working as expected inside the doctest.
+
+ In [1]: import builtins
+
+ In [2]: builtins._ = 42
+
+ In [3]: builtins._
+ Out[3]: 42
+
+ In [4]: _
+ Out[4]: 42
+ """
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/test_refs.py b/contrib/python/ipython/py3/IPython/testing/plugin/test_refs.py
new file mode 100644
index 0000000000..b92448be07
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/test_refs.py
@@ -0,0 +1,39 @@
+"""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 correctly 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
+ """
diff --git a/contrib/python/ipython/py3/IPython/testing/skipdoctest.py b/contrib/python/ipython/py3/IPython/testing/skipdoctest.py
new file mode 100644
index 0000000000..f440ea14b2
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/skipdoctest.py
@@ -0,0 +1,19 @@
+"""Decorators marks that a doctest should be skipped.
+
+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) IPython Development Team
+# Distributed under the terms of the Modified BSD License.
+
+
+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
diff --git a/contrib/python/ipython/py3/IPython/testing/tools.py b/contrib/python/ipython/py3/IPython/testing/tools.py
new file mode 100644
index 0000000000..2ff63a6d4a
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/tools.py
@@ -0,0 +1,476 @@
+"""Generic testing tools.
+
+Authors
+-------
+- Fernando Perez <Fernando.Perez@berkeley.edu>
+"""
+
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import os
+from pathlib import Path
+import re
+import sys
+import tempfile
+import unittest
+
+from contextlib import contextmanager
+from io import StringIO
+from subprocess import Popen, PIPE
+from unittest.mock import patch
+
+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 . 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 = Path(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, Path
+ 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.
+ """
+ __tracebackhide__ = True
+
+ 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
+ # Prevent coloring under PyCharm ("\x1b[0m" at the end of the stdout)
+ env.pop("PYCHARM_HOSTED", None)
+ 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.encode('\n'.join(commands)) or None)
+ out, err = py3compat.decode(out), py3compat.decode(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, Path
+ 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
+ """
+ __tracebackhide__ = True
+
+ out, err = ipexec(fname, options, commands)
+ #print 'OUT', out # dbg
+ #print 'ERR', err # dbg
+ # If there are any errors, we must check those before stdout, as they may be
+ # more informative than simply having an empty stdout.
+ if err:
+ if expected_err:
+ assert "\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
+ assert "\n".join(out.strip().splitlines()) == "\n".join(
+ expected_out.strip().splitlines()
+ )
+
+
+class TempFileMixin(unittest.TestCase):
+ """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 = temp_pyfile(src, ext)
+ if not hasattr(self, 'tmps'):
+ self.tmps=[]
+ self.tmps.append(fname)
+ self.fname = fname
+
+ def tearDown(self):
+ # If the tmpfile wasn't made because of skipped tests, like in
+ # win32, there's nothing to cleanup.
+ if hasattr(self, 'tmps'):
+ for fname in self.tmps:
+ # If the tmpfile wasn't made because of skipped tests, like in
+ # win32, there's nothing to cleanup.
+ try:
+ os.unlink(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
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ 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.
+ """
+ __tracebackhide__ = True
+
+ 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)
+
+
+MyStringIO = StringIO
+
+_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, (str, _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):
+ __tracebackhide__ = True
+
+ 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):
+ __tracebackhide__ = True
+
+ 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."""
+ open(name, "w", encoding="utf-8").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']):
+ ...
+
+ Values are returned in order. If input() is called again after the last value
+ was used, EOFError is raised.
+ """
+ it = iter(inputs)
+ def mock_input(prompt=''):
+ try:
+ return next(it)
+ except StopIteration as e:
+ raise EOFError('No more inputs given') from e
+
+ return patch('builtins.input', 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)
+ assert rc == 0, err
+ assert "Traceback" not in err
+ assert "Options" in out
+ assert "--help-all" in 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)
+ assert rc == 0, err
+ assert "Traceback" not in err
+ assert "Options" in out
+ assert "Class" in out
+ return out, err
+