aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/ipython/py3/IPython/testing/plugin
diff options
context:
space:
mode:
authorrobot-contrib <robot-contrib@yandex-team.ru>2022-05-18 00:43:36 +0300
committerrobot-contrib <robot-contrib@yandex-team.ru>2022-05-18 00:43:36 +0300
commit9e5f436a8b2a27bcc7802e443ea3ef3e41a82a75 (patch)
tree78b522cab9f76336e62064d4d8ff7c897659b20e /contrib/python/ipython/py3/IPython/testing/plugin
parent8113a823ffca6451bb5ff8f0334560885a939a24 (diff)
downloadydb-9e5f436a8b2a27bcc7802e443ea3ef3e41a82a75.tar.gz
Update contrib/python/ipython/py3 to 8.3.0
ref:e84342d4d30476f9148137f37fd0c6405fd36f55
Diffstat (limited to 'contrib/python/ipython/py3/IPython/testing/plugin')
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/dtexample.py40
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/ipdoctest.py479
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/iptest.py18
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/pytest_ipdoctest.py860
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/show_refs.py19
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/simple.py23
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/test_exampleip.txt2
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/test_ipdoctest.py16
-rw-r--r--contrib/python/ipython/py3/IPython/testing/plugin/test_refs.py7
9 files changed, 928 insertions, 536 deletions
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/dtexample.py b/contrib/python/ipython/py3/IPython/testing/plugin/dtexample.py
index d73cd246fd..68f7016e34 100644
--- a/contrib/python/ipython/py3/IPython/testing/plugin/dtexample.py
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/dtexample.py
@@ -4,6 +4,9 @@ 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...
@@ -35,20 +38,8 @@ def ipfunc():
....: 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
+ 0 1 1 2 2 3
- 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
@@ -59,7 +50,7 @@ def ipfunc():
In [8]: print(repr(_))
'hi'
-
+
In [7]: 3+4
Out[7]: 7
@@ -69,7 +60,26 @@ def ipfunc():
In [9]: ipfunc()
Out[9]: 'ipfunc'
"""
- return '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():
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/ipdoctest.py b/contrib/python/ipython/py3/IPython/testing/plugin/ipdoctest.py
index 3b8667e72f..52cd8fd3b8 100644
--- a/contrib/python/ipython/py3/IPython/testing/plugin/ipdoctest.py
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/ipdoctest.py
@@ -19,33 +19,13 @@ Limitations:
# Module imports
# From the standard library
-import builtins as builtin_mod
import doctest
-import inspect
import logging
import os
import re
-import sys
-from importlib import import_module
-from io import StringIO
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 nose.util import anyp, tolist
-
#-----------------------------------------------------------------------------
# Module globals and other constants
#-----------------------------------------------------------------------------
@@ -57,114 +37,16 @@ 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 _get_test(self, obj, name, module, globs, source_lines):
+ test = super()._get_test(obj, name, module, globs, source_lines)
- 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)
+ if bool(getattr(obj, "__skip_doctest__", False)) and test is not None:
+ for example in test.examples:
+ example.options[doctest.SKIP] = True
- 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)
+ return test
class IPDoctestOutputChecker(doctest.OutputChecker):
@@ -193,146 +75,11 @@ class IPDoctestOutputChecker(doctest.OutputChecker):
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 = os.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.
@@ -378,9 +125,6 @@ class IPDocTestParser(doctest.DocTestParser):
# 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)
@@ -423,27 +167,12 @@ class IPDocTestParser(doctest.DocTestParser):
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).
+ # It's an ipython example.
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
+ Example = IPExample
+ ip2py = True
for m in terms:
# Add the pre-example text to `output`.
@@ -458,10 +187,6 @@ class IPDocTestParser(doctest.DocTestParser):
# 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,
@@ -569,193 +294,7 @@ class IPDocTestRunner(doctest.DocTestRunner,object):
"""
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 unconditionally 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_module(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)
- with open(filename) as dh:
- doc = dh.read()
- 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/py3/IPython/testing/plugin/iptest.py b/contrib/python/ipython/py3/IPython/testing/plugin/iptest.py
deleted file mode 100644
index e24e22a830..0000000000
--- a/contrib/python/ipython/py3/IPython/testing/plugin/iptest.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env python
-"""Nose-based test runner.
-"""
-
-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/py3/IPython/testing/plugin/pytest_ipdoctest.py b/contrib/python/ipython/py3/IPython/testing/plugin/pytest_ipdoctest.py
new file mode 100644
index 0000000000..809713d7c8
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/pytest_ipdoctest.py
@@ -0,0 +1,860 @@
+# 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/show_refs.py b/contrib/python/ipython/py3/IPython/testing/plugin/show_refs.py
deleted file mode 100644
index b2c70adfc1..0000000000
--- a/contrib/python/ipython/py3/IPython/testing/plugin/show_refs.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""Simple script to show reference holding behavior.
-
-This is used by a companion test case.
-"""
-
-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/py3/IPython/testing/plugin/simple.py b/contrib/python/ipython/py3/IPython/testing/plugin/simple.py
index 3861977cab..35fbfd2fbd 100644
--- a/contrib/python/ipython/py3/IPython/testing/plugin/simple.py
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/simple.py
@@ -1,7 +1,7 @@
"""Simple example using doctests.
This file just contains doctests both using plain python and IPython prompts.
-All tests should be loaded by nose.
+All tests should be loaded by Pytest.
"""
def pyfunc():
@@ -24,10 +24,21 @@ def pyfunc():
return 'pyfunc'
-def ipyfunc2():
- """Some pure python tests...
+def ipyfunc():
+ """Some IPython tests...
+
+ In [1]: ipyfunc()
+ Out[1]: 'ipyfunc'
+
+ In [2]: import os
+
+ In [3]: 2+3
+ Out[3]: 5
- >>> 1+1
- 2
+ In [4]: for i in range(3):
+ ...: print(i, end=' ')
+ ...: print(i+1, end=' ')
+ ...:
+ Out[4]: 0 1 1 2 2 3
"""
- return 'pyfunc2'
+ return "ipyfunc"
diff --git a/contrib/python/ipython/py3/IPython/testing/plugin/test_exampleip.txt b/contrib/python/ipython/py3/IPython/testing/plugin/test_exampleip.txt
index 8afcbfdf7d..96b1eae19f 100644
--- a/contrib/python/ipython/py3/IPython/testing/plugin/test_exampleip.txt
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/test_exampleip.txt
@@ -21,7 +21,7 @@ Another example::
Just like in IPython docstrings, you can use all IPython syntax and features::
- In [9]: !echo "hello"
+ In [9]: !echo hello
hello
In [10]: 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
index d8f5991636..2686172bb2 100644
--- a/contrib/python/ipython/py3/IPython/testing/plugin/test_ipdoctest.py
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/test_ipdoctest.py
@@ -74,3 +74,19 @@ def doctest_multiline3():
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
index bd7ad8fb3e..b92448be07 100644
--- a/contrib/python/ipython/py3/IPython/testing/plugin/test_refs.py
+++ b/contrib/python/ipython/py3/IPython/testing/plugin/test_refs.py
@@ -37,10 +37,3 @@ def doctest_ivars():
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'>]
- """