From c056dd2ce68d692e7c49a2826b79c0f8cc9b19a4 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 24 May 2015 15:13:44 +0300 Subject: [PATCH 1/4] Adopt some sphinx tests for py.test --- doc/guide.rst | 5 +++ doc/modules/core.rst | 8 ++--- doc/modules/evalf.rst | 4 +-- doc/modules/logic.rst | 8 ++--- doc/modules/numeric-computation.rst | 4 +-- doc/modules/printing.rst | 12 ++++---- doc/modules/rewriting.rst | 4 +-- doc/modules/solvers/diophantine.rst | 47 +++++++++++++++-------------- doc/tutorial/basic_operations.rst | 2 +- doc/tutorial/gotchas.rst | 2 +- doc/tutorial/intro.rst | 2 +- 11 files changed, 52 insertions(+), 46 deletions(-) diff --git a/doc/guide.rst b/doc/guide.rst index fb2adf021e..0da80a75a1 100644 --- a/doc/guide.rst +++ b/doc/guide.rst @@ -347,6 +347,9 @@ This is how to create a function with two variables:: .. note:: the first argument of a @classmethod should be ``cls`` (i.e. not ``self``). + >>> from sympy.interactive import init_printing + >>> init_printing(use_unicode=False) + Here it's how to define a derivative of the function:: >>> from sympy import Function, sympify, cos @@ -430,6 +433,8 @@ Please use the same way as is shown below all across SymPy. >>> from sympy import sign, sin >>> from sympy.abc import x, y, z + >>> from sympy.interactive import init_printing + >>> init_printing(pretty_print=False) >>> e = sign(x**2) >>> e.args diff --git a/doc/modules/core.rst b/doc/modules/core.rst index 3c184cb5a4..de774f6acb 100644 --- a/doc/modules/core.rst +++ b/doc/modules/core.rst @@ -396,15 +396,15 @@ Function the atoms method: >>> e = (f(x) + cos(x) + 2) - >>> e.atoms(Function) - set([f(x), cos(x)]) + >>> e.atoms(Function) == {f(x), cos(x)} + True If you just want the function you defined, not SymPy functions, the thing to search for is AppliedUndef: >>> from sympy.core.function import AppliedUndef - >>> e.atoms(AppliedUndef) - set([f(x)]) + >>> e.atoms(AppliedUndef) == {f(x)} + True Subs ^^^^ diff --git a/doc/modules/evalf.rst b/doc/modules/evalf.rst index 2fa5e830f3..62b107b54b 100644 --- a/doc/modules/evalf.rst +++ b/doc/modules/evalf.rst @@ -138,9 +138,9 @@ cancellation: >>> a, b = GoldenRatio**1000/sqrt(5), fibonacci(1000) >>> float(a) - 4.34665576869e+208 + 4.3466557686937455e+208 >>> float(b) - 4.34665576869e+208 + 4.3466557686937455e+208 >>> float(a) - float(b) 0.0 diff --git a/doc/modules/logic.rst b/doc/modules/logic.rst index e3af051ae3..3bef93ea84 100644 --- a/doc/modules/logic.rst +++ b/doc/modules/logic.rst @@ -37,8 +37,8 @@ Like most types in SymPy, Boolean expressions inherit from >>> (y & x).subs({x: True, y: True}) True - >>> (x | y).atoms() - set([x, y]) + >>> (x | y).atoms() == {x, y} + True The logic module also includes the following functions to derive boolean expressions from their truth tables- @@ -114,8 +114,8 @@ values for ``x`` that make this sentence ``True``. On the other hand, ``(x >>> y = Symbol('y') >>> satisfiable(x & ~x) False - >>> satisfiable((x | y) & (x | ~y) & (~x | y)) - {x: True, y: True} + >>> satisfiable((x | y) & (x | ~y) & (~x | y)) == {x: True, y: True} + True As you see, when a sentence is satisfiable, it returns a model that makes that sentence ``True``. If it is not satisfiable it will return ``False``. diff --git a/doc/modules/numeric-computation.rst b/doc/modules/numeric-computation.rst index bc6cecf984..4a816c11de 100644 --- a/doc/modules/numeric-computation.rst +++ b/doc/modules/numeric-computation.rst @@ -44,7 +44,7 @@ leveraging a variety of numerical libraries. It is used as follows: >>> expr = sin(x)/x >>> f = lambdify(x, expr) >>> f(3.14) - 0.000507214304614 + 0.0005072143046136395 Here lambdify makes a function that computes ``f(x) = sin(x)/x``. By default lambdify relies on implementations in the ``math`` standard library. This @@ -64,7 +64,7 @@ powerful vectorized ufuncs that are backed by compiled C code. >>> import numpy >>> data = numpy.linspace(1, 10, 10000) - >>> f(data) + >>> pprint(f(data)) [ 0.84147098 0.84119981 0.84092844 ..., -0.05426074 -0.05433146 -0.05440211] diff --git a/doc/modules/printing.rst b/doc/modules/printing.rst index b3be5d5b00..bf0fc95462 100644 --- a/doc/modules/printing.rst +++ b/doc/modules/printing.rst @@ -202,12 +202,12 @@ return value is a three-tuple containing: (i) a set of number symbols that must be defined as 'Fortran parameters', (ii) a list functions that can not be translated in pure Fortran and (iii) a string of Fortran code. A few examples: - >>> fcode(1 - gamma(x)**2, human=False) - (set(), set([gamma(x)]), ' -gamma(x)**2 + 1') - >>> fcode(1 - sin(x)**2, human=False) - (set(), set(), ' -sin(x)**2 + 1') - >>> fcode(x - pi**2, human=False) - (set([(pi, '3.14159265358979d0')]), set(), ' x - pi**2') + >>> fcode(1 - gamma(x)**2, human=False) == (set(), {gamma(x)}, ' -gamma(x)**2 + 1') + True + >>> fcode(1 - sin(x)**2, human=False) == (set(), set(), ' -sin(x)**2 + 1') + True + >>> fcode(x - pi**2, human=False) == ({(pi, '3.14159265358979d0')}, set(), ' x - pi**2') + True Mathematica code printing ------------------------- diff --git a/doc/modules/rewriting.rst b/doc/modules/rewriting.rst index 3318b0b08c..d439acf8a7 100644 --- a/doc/modules/rewriting.rst +++ b/doc/modules/rewriting.rst @@ -48,8 +48,8 @@ process by using ``collect`` function: >>> (x + I*y).as_real_imag() (re(x) - im(y), re(y) + im(x)) - >>> collect((x + I*y).expand(complex=True), I, evaluate=False) - {1: re(x) - im(y), I: re(y) + im(x)} + >>> collect((x + I*y).expand(complex=True), I, evaluate=False) == {1: re(x) - im(y), I: re(y) + im(x)} + True There is also possibility for expanding expressions in terms of expressions of different kind. This is very general type of expanding and usually you would diff --git a/doc/modules/solvers/diophantine.rst b/doc/modules/solvers/diophantine.rst index 704b76ef9d..82bcc845ef 100644 --- a/doc/modules/solvers/diophantine.rst +++ b/doc/modules/solvers/diophantine.rst @@ -84,15 +84,16 @@ First, let's import the highest API of the Diophantine module. Before we start solving the equations, we need to define the variables. >>> from sympy import symbols ->>> x, y, z = symbols("x, y, z", integer=True) +>>> x, y, z, t, p, q = symbols("x, y, z, t, p, q", integer=True) +>>> t1, t2, t3, t4, t5 = symbols("t1:6", integer=True) Let's start by solving the easiest type of Diophantine equations, i.e. linear Diophantine equations. Let's solve `2x + 3y = 5`. Note that although we write the equation in the above form, when we input the equation to any of the functions in Diophantine module, it needs to be in the form `eq = 0`. ->>> diophantine(2*x + 3*y - 5) -set([(3*t - 5, -2*t + 5)]) +>>> diophantine(2*x + 3*y - 5) == {(3*t - 5, -2*t + 5)} +True Note that stepping one more level below the highest API, we can solve the very same equation by calling :py:meth:`~sympy.solvers.diophantine.diop_solve`. @@ -115,8 +116,8 @@ which is what :py:meth:`~sympy.solvers.diophantine.diop_solve` calls internally. If the given equation has no solutions then the outputs will look like below. ->>> diophantine(2*x + 4*y - 3) -set() +>>> diophantine(2*x + 4*y - 3) == set() +True >>> diop_solve(2*x + 4*y - 3) (None, None) >>> diop_linear(2*x + 4*y - 3) @@ -143,27 +144,27 @@ of the solutions. Let us define `\Delta = b^2 - 4ac` w.r.t. the binary quadratic When `\Delta < 0`, there are either no solutions or only a finite number of solutions. ->>> diophantine(x**2 - 4*x*y + 8*y**2 - 3*x + 7*y - 5) -set([(2, 1), (5, 1)]) +>>> diophantine(x**2 - 4*x*y + 8*y**2 - 3*x + 7*y - 5) == {(2, 1), (5, 1)} +True In the above equation `\Delta = (-4)^2 - 4*1*8 = -16` and hence only a finite number of solutions exist. When `\Delta = 0` we might have either no solutions or parameterized solutions. ->>> diophantine(3*x**2 - 6*x*y + 3*y**2 - 3*x + 7*y - 5) -set() ->>> diophantine(x**2 - 4*x*y + 4*y**2 - 3*x + 7*y - 5) -set([(-2*t**2 - 7*t + 10, -t**2 - 3*t + 5)]) ->>> diophantine(x**2 + 2*x*y + y**2 - 3*x - 3*y) -set([(t, -t), (t, -t + 3)]) +>>> diophantine(3*x**2 - 6*x*y + 3*y**2 - 3*x + 7*y - 5) == set() +True +>>> diophantine(x**2 - 4*x*y + 4*y**2 - 3*x + 7*y - 5) == {(-2*t**2 - 7*t + 10, -t**2 - 3*t + 5)} +True +>>> diophantine(x**2 + 2*x*y + y**2 - 3*x - 3*y) == {(t, -t), (t, -t + 3)} +True The most interesting case is when `\Delta > 0` and it is not a perfect square. In this case, the equation has either no solutions or an infinte number of solutions. Consider the below cases where `\Delta = 8`. ->>> diophantine(x**2 - 4*x*y + 2*y**2 - 3*x + 7*y - 5) -set() +>>> diophantine(x**2 - 4*x*y + 2*y**2 - 3*x + 7*y - 5) == set() +True >>> from sympy import expand >>> n = symbols("n", integer=True) >>> s = diophantine(x**2 - 2*y**2 - 2*x - 4*y, n) @@ -224,10 +225,10 @@ of the form `ax^2 + by^2 + cz^2 + dxy + eyz + fzx = 0`. These type of equations either have infinitely many solutions or no solutions (except the obvious solution (0, 0, 0)) ->>> diophantine(3*x**2 + 4*y**2 - 5*z**2 + 4*x*y + 6*y*z + 7*z*x) -set() ->>> diophantine(3*x**2 + 4*y**2 - 5*z**2 + 4*x*y - 7*y*z + 7*z*x) -set([(-16*p**2 + 28*p*q + 20*q**2, 3*p**2 + 38*p*q - 25*q**2, 4*p**2 - 24*p*q + 68*q**2)]) +>>> diophantine(3*x**2 + 4*y**2 - 5*z**2 + 4*x*y + 6*y*z + 7*z*x) == set() +True +>>> diophantine(3*x**2 + 4*y**2 - 5*z**2 + 4*x*y - 7*y*z + 7*z*x) == {(-16*p**2 + 28*p*q + 20*q**2, 3*p**2 + 38*p*q - 25*q**2, 4*p**2 - 24*p*q + 68*q**2)} +True If you are only interested about a base solution rather than the parameterized general solution (to be more precise, one of the general solutions), you can @@ -257,8 +258,8 @@ general sum of squares equation, `x_{1}^2 + x_{2}^2 + \ldots + x_{n}^2 = k` can also be solved using the Diophantine module. >>> from sympy.abc import a, b, c, d, e, f ->>> diophantine(9*a**2 + 16*b**2 + c**2 + 49*d**2 + 4*e**2 - 25*f**2) -set([(70*t1**2 + 70*t2**2 + 70*t3**2 + 70*t4**2 - 70*t5**2, 105*t1*t5, 420*t2*t5, 60*t3*t5, 210*t4*t5, 42*t1**2 + 42*t2**2 + 42*t3**2 + 42*t4**2 + 42*t5**2)]) +>>> diophantine(9*a**2 + 16*b**2 + c**2 + 49*d**2 + 4*e**2 - 25*f**2) == {(70*t1**2 + 70*t2**2 + 70*t3**2 + 70*t4**2 - 70*t5**2, 105*t1*t5, 420*t2*t5, 60*t3*t5, 210*t4*t5, 42*t1**2 + 42*t2**2 + 42*t3**2 + 42*t4**2 + 42*t5**2)} +True function :py:meth:`~sympy.solvers.diophantine.diop_general_pythagorean` can also be called directly to solve the same equation. This is true about the @@ -266,8 +267,8 @@ general sum of squares too. Either you can call :py:meth:`~sympy.solvers.diophantine.diop_general_pythagorean` or use the high level API. ->>> diophantine(a**2 + b**2 + c**2 + d**2 + e**2 + f**2 - 112) -set([(8, 4, 4, 4, 0, 0)]) +>>> diophantine(a**2 + b**2 + c**2 + d**2 + e**2 + f**2 - 112) == {(8, 4, 4, 4, 0, 0)} +True If you want to get a more thorough idea about the the Diophantine module please refer to the following blog. diff --git a/doc/tutorial/basic_operations.rst b/doc/tutorial/basic_operations.rst index d55680ba8a..179961f4d5 100644 --- a/doc/tutorial/basic_operations.rst +++ b/doc/tutorial/basic_operations.rst @@ -183,7 +183,7 @@ library math module, use ``"math"``. >>> f = lambdify(x, expr, "math") >>> f(0.1) - 0.0998334166468 + 0.09983341664682815 To use lambdify with numerical libraries that it does not know about, pass a dictionary of ``sympy_name:numerical_function`` pairs. For example diff --git a/doc/tutorial/gotchas.rst b/doc/tutorial/gotchas.rst index c35e429aef..038e1a89ce 100644 --- a/doc/tutorial/gotchas.rst +++ b/doc/tutorial/gotchas.rst @@ -300,7 +300,7 @@ to a Python expression. Use the :func:`sympy.core.sympify.sympify` function, or :func:`S `, to ensure that something is a SymPy expression. >>> 6.2 # Python float. Notice the floating point accuracy problems. - 6.2000000000000002 + 6.2 >>> type(6.2) # in Python 2.x, in Py3k <... 'float'> >>> S(6.2) # SymPy Float has no such problems because of arbitrary precision. diff --git a/doc/tutorial/intro.rst b/doc/tutorial/intro.rst index 0d0137fca7..e95783f495 100644 --- a/doc/tutorial/intro.rst +++ b/doc/tutorial/intro.rst @@ -21,7 +21,7 @@ compute square roots. We might do something like this the square root of a number that isn't a perfect square >>> math.sqrt(8) - 2.82842712475 + 2.8284271247461903 Here we got an approximate result. 2.82842712475 is not the exact square root of 8 (indeed, the actual square root of 8 cannot be represented by a finite From 23b29db068ad13a0f277c75d8fc4f9cea1fe4b21 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 24 May 2015 15:59:41 +0300 Subject: [PATCH 2/4] Change default encoding for py.test in sphinx directory --- doc/conftest.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 doc/conftest.py diff --git a/doc/conftest.py b/doc/conftest.py new file mode 100644 index 0000000000..133eaf4b4b --- /dev/null +++ b/doc/conftest.py @@ -0,0 +1,6 @@ +import sys + + +if sys.version_info[0] == 2: + reload(sys) + sys.setdefaultencoding('utf-8') From b35245623a5b0b0c833b103540ec59a9dbab8c15 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 25 May 2015 01:49:05 +0300 Subject: [PATCH 3/4] Test sphinx files with py.test, bin/doctest removed --- bin/doctest | 75 ---------------------------------------------- bin/test_travis.sh | 6 ++-- setup.cfg | 1 + 3 files changed, 4 insertions(+), 78 deletions(-) delete mode 100755 bin/doctest diff --git a/bin/doctest b/bin/doctest deleted file mode 100755 index 92c2626c50..0000000000 --- a/bin/doctest +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python - -""" -Program to execute doctests using the py.test like interface. - -The advantage over py.test is that it only depends on sympy and should just -work in any circumstances. See "sympy.dotest?" for documentation. -""" - -from __future__ import print_function - -# files listed here can be in unix forward slash format with paths -# listed relative to sympy (which contains bin, etc...) -blacklist = [] - -import sys -import os -from optparse import OptionParser -import re - -parser = OptionParser() -parser.add_option("-v", "--verbose", action="store_true", dest="verbose", - default=False) - -# if you don't see a -n `default=False`; -# if you do see a -n `store_true` means to store a True value for it; -# dest is where in options to put it, options.normal will hold the bool; -# when the user enters -h or --help, print the `help` text -parser.add_option("-n", "--normal", action="store_true", dest="normal", - help="run normal doctests; do not require explicit imports", default=False) -parser.add_option('-t', '--types', dest='types', action='store', - default=None, choices=['gmpy', 'gmpy1', 'python'], - help='setup ground types: gmpy | gmpy1 | python') -parser.add_option('-C', '--no-cache', dest='cache', action='store_false', - default=True, help='disable caching mechanism') -parser.add_option("--no-subprocess", action="store_false", dest="subprocess", - default=True, help="Don't run the tests in a separate " - "subprocess. This may prevent hash randomization from being enabled.") -parser.add_option('--split', action="store", type='str', default=None, - help="Only run part of the doctests. Should be of the form a/b, e.g., 1/2") -parser.add_option('--rerun', action="store", dest="rerun", - default=0, help="Number of times to rerun the specified tests", - type='int') -parser.set_usage("test [options ...] [files ...]") -parser.epilog = """\ -"options" are any of the options above. "files" are 0 or more glob strings of \ -files to run doctests on. If no file arguments are given, all doctests will be \ -run. This program runs both doctests in the source and doctests in the Sphinx \ -documentation (doc/ directory).\ -""" - -options, args = parser.parse_args() - -# Check this again here to give a better error message -if options.split: - sp = re.compile(r'([0-9]+)/([1-9][0-9]*)') - if not sp.match(options.split): - parser.error("option --split: must be of the form a/b where a and b " - "are integers, not %r" % options.split) - -if not options.cache: - os.environ['SYMPY_USE_CACHE'] = 'no' -if options.types: - os.environ['SYMPY_GROUND_TYPES'] = options.types - -import sympy - -ok = sympy.doctest(*args, verbose=options.verbose, blacklist=blacklist, - normal=options.normal, subprocess=options.subprocess, split=options.split, - rerun=options.rerun) - -if ok: - sys.exit(0) -else: - sys.exit(1) diff --git a/bin/test_travis.sh b/bin/test_travis.sh index 6ee1ea4165..f80f005c1c 100755 --- a/bin/test_travis.sh +++ b/bin/test_travis.sh @@ -3,7 +3,6 @@ set -e -x # exit on error and echo each command if [[ "${TEST_SPHINX}" == "true" ]]; then - bin/doctest `find doc/ -name '*.rst'` make -C doc html-errors man latex LATEXOPTIONS="-interaction=nonstopmode" make -C doc/_build/latex else @@ -19,12 +18,13 @@ else py.test --duration=100 --cov sympy --doctest-modules \ sympy/printing/tests/test_theanocode.py \ sympy/external/tests/test_autowrap.py \ - sympy/polys/ sympy/plotting/ + sympy/polys/ sympy/plotting/ doc \ + --ignore doc/tutorial/gotchas.rst # XXX: workaround __future__ imports! else py.test --duration=100 --doctest-modules \ sympy/printing/tests/test_theanocode.py \ sympy/external/tests/test_autowrap.py \ - sympy/polys/ sympy/plotting/ + sympy/polys/ sympy/plotting/ doc fi elif [[ "${TRAVIS_PYTHON_VERSION}" == "2.7" ]]; then py.test -m 'not slow' --duration=100 --cov sympy --split="${SPLIT}" \ diff --git a/setup.cfg b/setup.cfg index 7b90b25611..6e528ffbeb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,3 +12,4 @@ select = E101,W191,W291,W293,E111,E112,E113,W292,W391 [pytest] minversion = 2.7.0 doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL +addopts = --ignore=doc/conf.py --ignore=setup.py --doctest-glob='*.rst' From 3c5a78244c8e161da6c955158c9b16a75387f85b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 25 May 2015 01:50:30 +0300 Subject: [PATCH 4/4] Removed (mostly) py.test fork in runtests.py --- bin/coverage_doctest.py | 2 +- doc/modules/utilities/index.rst | 1 - doc/modules/utilities/runtests.rst | 6 - sympy/plotting/plot.py | 4 - sympy/utilities/__init__.py | 2 - sympy/utilities/runtests.py | 1691 +--------------------------- 6 files changed, 4 insertions(+), 1702 deletions(-) delete mode 100644 doc/modules/utilities/runtests.rst diff --git a/bin/coverage_doctest.py b/bin/coverage_doctest.py index dfe02413e7..1b588b7dbe 100755 --- a/bin/coverage_doctest.py +++ b/bin/coverage_doctest.py @@ -30,7 +30,7 @@ # It's html.parser in Python 3 from html.parser import HTMLParser -# Load color templates, used from sympy/utilities/runtests.py +# Load color templates color_templates = ( ("Black", "0;30"), ("Red", "0;31"), diff --git a/doc/modules/utilities/index.rst b/doc/modules/utilities/index.rst index e3041878c1..b8cc73f4b4 100644 --- a/doc/modules/utilities/index.rst +++ b/doc/modules/utilities/index.rst @@ -21,6 +21,5 @@ Contents: misc.rst pkgdata.rst randtest.rst - runtests.rst source.rst timeutils.rst diff --git a/doc/modules/utilities/runtests.rst b/doc/modules/utilities/runtests.rst deleted file mode 100644 index baa16deab3..0000000000 --- a/doc/modules/utilities/runtests.rst +++ /dev/null @@ -1,6 +0,0 @@ -========= -Run Tests -========= - -.. automodule:: sympy.utilities.runtests - :members: diff --git a/sympy/plotting/plot.py b/sympy/plotting/plot.py index 60b374c5a3..2c7d4e1514 100644 --- a/sympy/plotting/plot.py +++ b/sympy/plotting/plot.py @@ -35,10 +35,6 @@ from sympy.utilities.iterables import is_sequence from .experimental_lambdify import (vectorized_lambdify, lambdify) -# N.B. -# When changing the minimum module version for matplotlib, please change -# the same in the `SymPyDocTestFinder`` in `sympy/utilities/runtests.py` - # Global variable # Set to False when running tests / doctests so that the plots don't show. _show = True diff --git a/sympy/utilities/__init__.py b/sympy/utilities/__init__.py index 24d1cef32f..1a357f6fb1 100644 --- a/sympy/utilities/__init__.py +++ b/sympy/utilities/__init__.py @@ -15,6 +15,4 @@ from .decorator import threaded, xthreaded, public -from .runtests import test, doctest - from .timeutils import timed diff --git a/sympy/utilities/runtests.py b/sympy/utilities/runtests.py index c019e7b25a..4ac4ac9822 100644 --- a/sympy/utilities/runtests.py +++ b/sympy/utilities/runtests.py @@ -1,1702 +1,19 @@ """ -This is our testing framework. - -Goals: - -* it should be compatible with py.test and operate very similarly - (or identically) -* doesn't require any external dependencies -* preferably all the functionality should be in this file only -* no magic, just import the test file and execute the test functions, that's it -* portable - +Utilities for our testing framework. """ from __future__ import print_function, division import os import sys -import platform -import inspect -import traceback -import pdb -import re -import linecache -from fnmatch import fnmatch -from timeit import default_timer as clock -import doctest as pdoctest # avoid clashing with our doctest() function -from doctest import DocTestFinder, DocTestRunner -import random import subprocess -import signal -import stat -from inspect import isgeneratorfunction +import re +from sympy.core.compatibility import PY3 -from sympy.core.cache import clear_cache -from sympy.core.compatibility import exec_, PY3, string_types, range -from sympy.utilities.misc import find_executable -from sympy.external import import_module -from sympy.utilities.exceptions import SymPyDeprecationWarning IS_WINDOWS = (os.name == 'nt') -class Skipped(Exception): - pass - -import __future__ -# add more flags ?? -future_flags = __future__.division.compiler_flag - -def _indent(s, indent=4): - """ - Add the given number of space characters to the beginning of - every non-blank line in ``s``, and return the result. - If the string ``s`` is Unicode, it is encoded using the stdout - encoding and the ``backslashreplace`` error handler. - """ - # After a 2to3 run the below code is bogus, so wrap it with a version check - if not PY3: - if isinstance(s, unicode): - s = s.encode(pdoctest._encoding, 'backslashreplace') - # This regexp matches the start of non-blank lines: - return re.sub('(?m)^(?!$)', indent*' ', s) - -pdoctest._indent = _indent - -# ovverride reporter to maintain windows and python3 - - -def _report_failure(self, out, test, example, got): - """ - Report that the given example failed. - """ - s = self._checker.output_difference(example, got, self.optionflags) - s = s.encode('raw_unicode_escape').decode('utf8', 'ignore') - out(self._failure_header(test, example) + s) - -if PY3 and IS_WINDOWS: - DocTestRunner.report_failure = _report_failure - - -def convert_to_native_paths(lst): - """ - Converts a list of '/' separated paths into a list of - native (os.sep separated) paths and converts to lowercase - if the system is case insensitive. - """ - newlst = [] - for i, rv in enumerate(lst): - rv = os.path.join(*rv.split("/")) - # on windows the slash after the colon is dropped - if sys.platform == "win32": - pos = rv.find(':') - if pos != -1: - if rv[pos + 1] != '\\': - rv = rv[:pos + 1] + '\\' + rv[pos + 1:] - newlst.append(sys_normcase(rv)) - return newlst - - -def get_sympy_dir(): - """ - Returns the root sympy directory and set the global value - indicating whether the system is case sensitive or not. - """ - global sys_case_insensitive - - this_file = os.path.abspath(__file__) - sympy_dir = os.path.join(os.path.dirname(this_file), "..", "..") - sympy_dir = os.path.normpath(sympy_dir) - sys_case_insensitive = (os.path.isdir(sympy_dir) and - os.path.isdir(sympy_dir.lower()) and - os.path.isdir(sympy_dir.upper())) - return sys_normcase(sympy_dir) - - -def sys_normcase(f): - if sys_case_insensitive: # global defined after call to get_sympy_dir() - return f.lower() - return f - - -def setup_pprint(): - from sympy import pprint_use_unicode, init_printing - - # force pprint to be in ascii mode in doctests - pprint_use_unicode(False) - - # hook our nice, hash-stable strprinter - init_printing(pretty_print=False) - - -def run_in_subprocess_with_hash_randomization(function, function_args=(), - function_kwargs={}, command=sys.executable, - module='sympy.utilities.runtests', force=False): - """ - Run a function in a Python subprocess with hash randomization enabled. - - If hash randomization is not supported by the version of Python given, it - returns False. Otherwise, it returns the exit value of the command. The - function is passed to sys.exit(), so the return value of the function will - be the return value. - - The environment variable PYTHONHASHSEED is used to seed Python's hash - randomization. If it is set, this function will return False, because - starting a new subprocess is unnecessary in that case. If it is not set, - one is set at random, and the tests are run. Note that if this - environment variable is set when Python starts, hash randomization is - automatically enabled. To force a subprocess to be created even if - PYTHONHASHSEED is set, pass ``force=True``. This flag will not force a - subprocess in Python versions that do not support hash randomization (see - below), because those versions of Python do not support the ``-R`` flag. - - ``function`` should be a string name of a function that is importable from - the module ``module``, like "_test". The default for ``module`` is - "sympy.utilities.runtests". ``function_args`` and ``function_kwargs`` - should be a repr-able tuple and dict, respectively. The default Python - command is sys.executable, which is the currently running Python command. - - This function is necessary because the seed for hash randomization must be - set by the environment variable before Python starts. Hence, in order to - use a predetermined seed for tests, we must start Python in a separate - subprocess. - - Hash randomization was added in the minor Python versions 2.6.8, 2.7.3, - 3.1.5, and 3.2.3, and is enabled by default in all Python versions after - and including 3.3.0. - - Examples - ======== - - >>> from sympy.utilities.runtests import ( - ... run_in_subprocess_with_hash_randomization) - >>> # run the core tests in verbose mode - >>> run_in_subprocess_with_hash_randomization("_test", - ... function_args=("core",), - ... function_kwargs={'verbose': True}) # doctest: +SKIP - # Will return 0 if sys.executable supports hash randomization and tests - # pass, 1 if they fail, and False if it does not support hash - # randomization. - - """ - # Note, we must return False everywhere, not None, as subprocess.call will - # sometimes return None. - - # First check if the Python version supports hash randomization - # If it doesn't have this support, it won't reconize the -R flag - p = subprocess.Popen([command, "-RV"], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - p.communicate() - if p.returncode != 0: - return False - - hash_seed = os.getenv("PYTHONHASHSEED") - if not hash_seed: - os.environ["PYTHONHASHSEED"] = str(random.randrange(2**32)) - else: - if not force: - return False - # Now run the command - commandstring = ("import sys; from %s import %s;sys.exit(%s(*%s, **%s))" % - (module, function, function, repr(function_args), - repr(function_kwargs))) - - try: - p = subprocess.Popen([command, "-R", "-c", commandstring]) - p.communicate() - except KeyboardInterrupt: - p.wait() - finally: - # Put the environment variable back, so that it reads correctly for - # the current Python process. - if hash_seed is None: - del os.environ["PYTHONHASHSEED"] - else: - os.environ["PYTHONHASHSEED"] = hash_seed - return p.returncode - - -def run_all_tests(test_args=(), test_kwargs={}, doctest_args=(), - doctest_kwargs={}, examples_args=(), examples_kwargs={'quiet': True}): - """ - Run all tests. - - Right now, this runs the regular tests (py.test), the doctests - (bin/doctest), the examples (examples/all.py). - - This is what ``setup.py test`` uses. - - You can pass arguments and keyword arguments to the test functions that - support them (for now, test, doctest, and the examples). See the - docstrings of those functions for a description of the available options. - - For example, to run the solvers tests with colors turned off: - - >>> from sympy.utilities.runtests import run_all_tests - >>> run_all_tests(test_args=("solvers",), - ... test_kwargs={"colors:False"}) # doctest: +SKIP - - """ - tests_successful = True - - try: - # Regular tests - if subprocess.call("py.test --duration=100 sympy", shell=True) != 0: - tests_successful = False - - # Doctests - print() - if not doctest(*doctest_args, **doctest_kwargs): - tests_successful = False - - # Examples - print() - sys.path.append("examples") - from all import run_examples # examples/all.py - if not run_examples(*examples_args, **examples_kwargs): - tests_successful = False - - if tests_successful: - return - else: - # Return nonzero exit code - sys.exit(1) - except KeyboardInterrupt: - print() - print("DO *NOT* COMMIT!") - sys.exit(1) - - -def test(*paths, **kwargs): - """ - Run tests in the specified test_*.py files. - - Tests in a particular test_*.py file are run if any of the given strings - in ``paths`` matches a part of the test file's path. If ``paths=[]``, - tests in all test_*.py files are run. - - Notes: - - - If sort=False, tests are run in random order (not default). - - Paths can be entered in native system format or in unix, - forward-slash format. - - Files that are on the blacklist can be tested by providing - their path; they are only excluded if no paths are given. - - **Explanation of test results** - - ====== =============================================================== - Output Meaning - ====== =============================================================== - . passed - F failed - X XPassed (expected to fail but passed) - f XFAILed (expected to fail and indeed failed) - s skipped - w slow - T timeout (e.g., when ``--timeout`` is used) - K KeyboardInterrupt (when running the slow tests with ``--slow``, - you can interrupt one of them without killing the test runner) - ====== =============================================================== - - - Colors have no additional meaning and are used just to facilitate - interpreting the output. - - Examples - ======== - - >>> import sympy - - Run all tests: - - >>> sympy.test() # doctest: +SKIP - - Run one file: - - >>> sympy.test("sympy/core/tests/test_basic.py") # doctest: +SKIP - >>> sympy.test("_basic") # doctest: +SKIP - - Run all tests in sympy/functions/ and some particular file: - - >>> sympy.test("sympy/core/tests/test_basic.py", - ... "sympy/functions") # doctest: +SKIP - - Run all tests in sympy/core and sympy/utilities: - - >>> sympy.test("/core", "/util") # doctest: +SKIP - - Run specific test from a file: - - >>> sympy.test("sympy/core/tests/test_basic.py", - ... kw="test_equality") # doctest: +SKIP - - Run specific test from any file: - - >>> sympy.test(kw="subs") # doctest: +SKIP - - Run the tests with verbose mode on: - - >>> sympy.test(verbose=True) # doctest: +SKIP - - Don't sort the test output: - - >>> sympy.test(sort=False) # doctest: +SKIP - - Turn on post-mortem pdb: - - >>> sympy.test(pdb=True) # doctest: +SKIP - - Turn off colors: - - >>> sympy.test(colors=False) # doctest: +SKIP - - Force colors, even when the output is not to a terminal (this is useful, - e.g., if you are piping to ``less -r`` and you still want colors) - - >>> sympy.test(force_colors=False) # doctest: +SKIP - - The traceback verboseness can be set to "short" or "no" (default is - "short") - - >>> sympy.test(tb='no') # doctest: +SKIP - - The ``split`` option can be passed to split the test run into parts. The - split currently only splits the test files, though this may change in the - future. ``split`` should be a string of the form 'a/b', which will run - part ``a`` of ``b``. For instance, to run the first half of the test suite: - - >>> sympy.test(split='1/2') # doctest: +SKIP - - You can disable running the tests in a separate subprocess using - ``subprocess=False``. This is done to support seeding hash randomization, - which is enabled by default in the Python versions where it is supported. - If subprocess=False, hash randomization is enabled/disabled according to - whether it has been enabled or not in the calling Python process. - However, even if it is enabled, the seed cannot be printed unless it is - called from a new Python process. - - Hash randomization was added in the minor Python versions 2.6.8, 2.7.3, - 3.1.5, and 3.2.3, and is enabled by default in all Python versions after - and including 3.3.0. - - If hash randomization is not supported ``subprocess=False`` is used - automatically. - - >>> sympy.test(subprocess=False) # doctest: +SKIP - - To set the hash randomization seed, set the environment variable - ``PYTHONHASHSEED`` before running the tests. This can be done from within - Python using - - >>> import os - >>> os.environ['PYTHONHASHSEED'] = '42' # doctest: +SKIP - - Or from the command line using - - $ PYTHONHASHSEED=42 ./bin/test - - If the seed is not set, a random seed will be chosen. - - Note that to reproduce the same hash values, you must use both the same seed - as well as the same architecture (32-bit vs. 64-bit). - - """ - subprocess = kwargs.pop("subprocess", True) - rerun = kwargs.pop("rerun", 0) - # count up from 0, do not print 0 - print_counter = lambda i : (print("rerun %d" % (rerun-i)) - if rerun-i else None) - - if subprocess: - # loop backwards so last i is 0 - for i in range(rerun, -1, -1): - print_counter(i) - ret = run_in_subprocess_with_hash_randomization("_test", - function_args=paths, function_kwargs=kwargs) - if ret is False: - break - val = not bool(ret) - # exit on the first failure or if done - if not val or i == 0: - return val - - # rerun even if hash randomization is not supported - for i in range(rerun, -1, -1): - print_counter(i) - val = not bool(_test(*paths, **kwargs)) - if not val or i == 0: - return val - - -def _test(*paths, **kwargs): - """ - Internal function that actually runs the tests. - - All keyword arguments from ``test()`` are passed to this function except for - ``subprocess``. - - Returns 0 if tests passed and 1 if they failed. See the docstring of - ``test()`` for more information. - """ - verbose = kwargs.get("verbose", False) - tb = kwargs.get("tb", "short") - kw = kwargs.get("kw", None) or () - # ensure that kw is a tuple - if isinstance(kw, str): - kw = (kw, ) - post_mortem = kwargs.get("pdb", False) - colors = kwargs.get("colors", True) - force_colors = kwargs.get("force_colors", False) - sort = kwargs.get("sort", True) - seed = kwargs.get("seed", None) - if seed is None: - seed = random.randrange(100000000) - timeout = kwargs.get("timeout", False) - slow = kwargs.get("slow", False) - enhance_asserts = kwargs.get("enhance_asserts", False) - split = kwargs.get('split', None) - blacklist = kwargs.get('blacklist', []) - blacklist = convert_to_native_paths(blacklist) - r = PyTestReporter(verbose=verbose, tb=tb, colors=colors, - force_colors=force_colors, split=split) - t = SymPyTests(r, kw, post_mortem, seed) - - - # Disable warnings for external modules - import sympy.external - sympy.external.importtools.WARN_OLD_VERSION = False - sympy.external.importtools.WARN_NOT_INSTALLED = False - - # Show deprecation warnings - import warnings - warnings.simplefilter("error", SymPyDeprecationWarning) - - test_files = t.get_test_files('sympy') - - not_blacklisted = [f for f in test_files - if not any(b in f for b in blacklist)] - - if len(paths) == 0: - matched = not_blacklisted - else: - paths = convert_to_native_paths(paths) - matched = [] - for f in not_blacklisted: - basename = os.path.basename(f) - for p in paths: - if p in f or fnmatch(basename, p): - matched.append(f) - break - - if split: - matched = split_list(matched, split) - - t._testfiles.extend(matched) - - return int(not t.test(sort=sort, timeout=timeout, - slow=slow, enhance_asserts=enhance_asserts)) - - -def doctest(*paths, **kwargs): - """ - Runs doctests in all \*.py files in the sympy directory which match - any of the given strings in ``paths`` or all tests if paths=[]. - - Notes: - - - Paths can be entered in native system format or in unix, - forward-slash format. - - Files that are on the blacklist can be tested by providing - their path; they are only excluded if no paths are given. - - Examples - ======== - - >>> import sympy - - Run all tests: - - >>> sympy.doctest() # doctest: +SKIP - - Run one file: - - >>> sympy.doctest("sympy/core/basic.py") # doctest: +SKIP - >>> sympy.doctest("polynomial.rst") # doctest: +SKIP - - Run all tests in sympy/functions/ and some particular file: - - >>> sympy.doctest("/functions", "basic.py") # doctest: +SKIP - - Run any file having polynomial in its name, doc/modules/polynomial.rst, - sympy/functions/special/polynomials.py, and sympy/polys/polynomial.py: - - >>> sympy.doctest("polynomial") # doctest: +SKIP - - The ``split`` option can be passed to split the test run into parts. The - split currently only splits the test files, though this may change in the - future. ``split`` should be a string of the form 'a/b', which will run - part ``a`` of ``b``. Note that the regular doctests and the Sphinx - doctests are split independently. For instance, to run the first half of - the test suite: - - >>> sympy.doctest(split='1/2') # doctest: +SKIP - - The ``subprocess`` and ``verbose`` options are the same as with the function - ``test()``. See the docstring of that function for more information. - - """ - subprocess = kwargs.pop("subprocess", True) - rerun = kwargs.pop("rerun", 0) - # count up from 0, do not print 0 - print_counter = lambda i : (print("rerun %d" % (rerun-i)) - if rerun-i else None) - - if subprocess: - # loop backwards so last i is 0 - for i in range(rerun, -1, -1): - print_counter(i) - ret = run_in_subprocess_with_hash_randomization("_doctest", - function_args=paths, function_kwargs=kwargs) - if ret is False: - break - val = not bool(ret) - # exit on the first failure or if done - if not val or i == 0: - return val - - # rerun even if hash randomization is not supported - for i in range(rerun, -1, -1): - print_counter(i) - val = not bool(_doctest(*paths, **kwargs)) - if not val or i == 0: - return val - - -def _doctest(*paths, **kwargs): - """ - Internal function that actually runs the doctests. - - All keyword arguments from ``doctest()`` are passed to this function - except for ``subprocess``. - - Returns 0 if tests passed and 1 if they failed. See the docstrings of - ``doctest()`` and ``test()`` for more information. - """ - normal = kwargs.get("normal", False) - verbose = kwargs.get("verbose", False) - blacklist = kwargs.get("blacklist", []) - split = kwargs.get('split', None) - blacklist.extend([ - "doc/modules/plotting.rst", # generates live plots - ]) - - if import_module('numpy') is None: - blacklist.extend([ - "sympy/plotting/experimental_lambdify.py", - "sympy/plotting/plot_implicit.py", - "examples/advanced/autowrap_integrators.py", - "examples/advanced/autowrap_ufuncify.py", - "examples/intermediate/sample.py", - "examples/intermediate/mplot2d.py", - "examples/intermediate/mplot3d.py", - "doc/modules/numeric-computation.rst" - ]) - else: - if import_module('matplotlib') is None: - blacklist.extend([ - "examples/intermediate/mplot2d.py", - "examples/intermediate/mplot3d.py" - ]) - else: - # don't display matplotlib windows - from sympy.plotting.plot import unset_show - unset_show() - - if import_module('theano') is None: - blacklist.extend(["doc/modules/numeric-computation.rst"]) - - # disabled because of doctest failures in asmeurer's bot - blacklist.extend([ - "sympy/utilities/autowrap.py", - "examples/advanced/autowrap_integrators.py", - "examples/advanced/autowrap_ufuncify.py" - ]) - - # blacklist these modules until issue 4840 is resolved - blacklist.extend([ - "sympy/conftest.py", - ]) - - blacklist = convert_to_native_paths(blacklist) - - # Disable warnings for external modules - import sympy.external - sympy.external.importtools.WARN_OLD_VERSION = False - sympy.external.importtools.WARN_NOT_INSTALLED = False - - # Show deprecation warnings - import warnings - warnings.simplefilter("error", SymPyDeprecationWarning) - - r = PyTestReporter(verbose, split=split) - t = SymPyDocTests(r, normal) - - test_files = t.get_test_files('sympy') - test_files.extend(t.get_test_files('examples', init_only=False)) - - not_blacklisted = [f for f in test_files - if not any(b in f for b in blacklist)] - if len(paths) == 0: - matched = not_blacklisted - else: - # take only what was requested...but not blacklisted items - # and allow for partial match anywhere or fnmatch of name - paths = convert_to_native_paths(paths) - matched = [] - for f in not_blacklisted: - basename = os.path.basename(f) - for p in paths: - if p in f or fnmatch(basename, p): - matched.append(f) - break - - if split: - matched = split_list(matched, split) - - t._testfiles.extend(matched) - - # run the tests and record the result for this *py portion of the tests - if t._testfiles: - failed = not t.test() - else: - failed = False - - # N.B. - # -------------------------------------------------------------------- - # Here we test *.rst files at or below doc/. Code from these must - # be self supporting in terms of imports since there is no importing - # of necessary modules by doctest.testfile. If you try to pass *.py - # files through this they might fail because they will lack the needed - # imports and smarter parsing that can be done with source code. - # - test_files = t.get_test_files('doc/', '*.rst', init_only=False) - test_files.sort() - - not_blacklisted = [f for f in test_files - if not any(b in f for b in blacklist)] - - if len(paths) == 0: - matched = not_blacklisted - else: - # Take only what was requested as long as it's not on the blacklist. - # Paths were already made native in *py tests so don't repeat here. - # There's no chance of having a *py file slip through since we - # only have *rst files in test_files. - matched = [] - for f in not_blacklisted: - basename = os.path.basename(f) - for p in paths: - if p in f or fnmatch(basename, p): - matched.append(f) - break - - if split: - matched = split_list(matched, split) - - setup_pprint() - first_report = True - for rst_file in matched: - if not os.path.isfile(rst_file): - continue - old_displayhook = sys.displayhook - try: - out = sympytestfile( - rst_file, module_relative=False, encoding='utf-8', - optionflags=pdoctest.ELLIPSIS | pdoctest.NORMALIZE_WHITESPACE | - pdoctest.IGNORE_EXCEPTION_DETAIL) - finally: - # make sure we return to the original displayhook in case some - # doctest has changed that - sys.displayhook = old_displayhook - - rstfailed, tested = out - if tested: - failed = rstfailed or failed - if first_report: - first_report = False - msg = 'rst doctests start' - if not t._testfiles: - r.start(msg=msg) - else: - r.write_center(msg) - print() - # use as the id, everything past the first 'sympy' - file_id = rst_file[rst_file.find('sympy') + len('sympy') + 1:] - print(file_id, end=" ") - # get at least the name out so it is know who is being tested - wid = r.terminal_width - len(file_id) - 1 # update width - test_file = '[%s]' % (tested) - report = '[%s]' % (rstfailed or 'OK') - print(''.join( - [test_file, ' '*(wid - len(test_file) - len(report)), report]) - ) - - # the doctests for *py will have printed this message already if there was - # a failure, so now only print it if there was intervening reporting by - # testing the *rst as evidenced by first_report no longer being True. - if not first_report and failed: - print() - print("DO *NOT* COMMIT!") - - return int(failed) - -sp = re.compile(r'([0-9]+)/([1-9][0-9]*)') - -def split_list(l, split): - """ - Splits a list into part a of b - - split should be a string of the form 'a/b'. For instance, '1/3' would give - the split one of three. - - If the length of the list is not divisible by the number of splits, the - last split will have more items. - - >>> from sympy.utilities.runtests import split_list - >>> a = list(range(10)) - >>> split_list(a, '1/3') - [0, 1, 2] - >>> split_list(a, '2/3') - [3, 4, 5] - >>> split_list(a, '3/3') - [6, 7, 8, 9] - """ - m = sp.match(split) - if not m: - raise ValueError("split must be a string of the form a/b where a and b are ints") - i, t = map(int, m.groups()) - return l[(i - 1)*len(l)//t:i*len(l)//t] - - -def sympytestfile(filename, module_relative=True, name=None, package=None, - globs=None, verbose=None, report=True, optionflags=0, - extraglobs=None, raise_on_error=False, - parser=pdoctest.DocTestParser(), encoding=None): - - """ - Test examples in the given file. Return (#failures, #tests). - - Optional keyword arg ``module_relative`` specifies how filenames - should be interpreted: - - - If ``module_relative`` is True (the default), then ``filename`` - specifies a module-relative path. By default, this path is - relative to the calling module's directory; but if the - ``package`` argument is specified, then it is relative to that - package. To ensure os-independence, ``filename`` should use - "/" characters to separate path segments, and should not - be an absolute path (i.e., it may not begin with "/"). - - - If ``module_relative`` is False, then ``filename`` specifies an - os-specific path. The path may be absolute or relative (to - the current working directory). - - Optional keyword arg ``name`` gives the name of the test; by default - use the file's basename. - - Optional keyword argument ``package`` is a Python package or the - name of a Python package whose directory should be used as the - base directory for a module relative filename. If no package is - specified, then the calling module's directory is used as the base - directory for module relative filenames. It is an error to - specify ``package`` if ``module_relative`` is False. - - Optional keyword arg ``globs`` gives a dict to be used as the globals - when executing examples; by default, use {}. A copy of this dict - is actually used for each docstring, so that each docstring's - examples start with a clean slate. - - Optional keyword arg ``extraglobs`` gives a dictionary that should be - merged into the globals that are used to execute examples. By - default, no extra globals are used. - - Optional keyword arg ``verbose`` prints lots of stuff if true, prints - only failures if false; by default, it's true iff "-v" is in sys.argv. - - Optional keyword arg ``report`` prints a summary at the end when true, - else prints nothing at the end. In verbose mode, the summary is - detailed, else very brief (in fact, empty if all tests passed). - - Optional keyword arg ``optionflags`` or's together module constants, - and defaults to 0. Possible values (see the docs for details): - - - DONT_ACCEPT_TRUE_FOR_1 - - DONT_ACCEPT_BLANKLINE - - NORMALIZE_WHITESPACE - - ELLIPSIS - - SKIP - - IGNORE_EXCEPTION_DETAIL - - REPORT_UDIFF - - REPORT_CDIFF - - REPORT_NDIFF - - REPORT_ONLY_FIRST_FAILURE - - Optional keyword arg ``raise_on_error`` raises an exception on the - first unexpected exception or failure. This allows failures to be - post-mortem debugged. - - Optional keyword arg ``parser`` specifies a DocTestParser (or - subclass) that should be used to extract tests from the files. - - Optional keyword arg ``encoding`` specifies an encoding that should - be used to convert the file to unicode. - - Advanced tomfoolery: testmod runs methods of a local instance of - class doctest.Tester, then merges the results into (or creates) - global Tester instance doctest.master. Methods of doctest.master - can be called directly too, if you want to do something unusual. - Passing report=0 to testmod is especially useful then, to delay - displaying a summary. Invoke doctest.master.summarize(verbose) - when you're done fiddling. - """ - if package and not module_relative: - raise ValueError("Package may only be specified for module-" - "relative paths.") - - # Relativize the path - if not PY3: - text, filename = pdoctest._load_testfile( - filename, package, module_relative) - if encoding is not None: - text = text.decode(encoding) - else: - text, filename = pdoctest._load_testfile( - filename, package, module_relative, encoding) - - # If no name was given, then use the file's name. - if name is None: - name = os.path.basename(filename) - - # Assemble the globals. - if globs is None: - globs = {} - else: - globs = globs.copy() - if extraglobs is not None: - globs.update(extraglobs) - if '__name__' not in globs: - globs['__name__'] = '__main__' - - if raise_on_error: - runner = pdoctest.DebugRunner(verbose=verbose, optionflags=optionflags) - else: - runner = SymPyDocTestRunner(verbose=verbose, optionflags=optionflags) - runner._checker = SymPyOutputChecker() - - # Read the file, convert it to a test, and run it. - test = parser.get_doctest(text, globs, name, filename, 0) - runner.run(test, compileflags=future_flags) - - if report: - runner.summarize() - - if pdoctest.master is None: - pdoctest.master = runner - else: - pdoctest.master.merge(runner) - - from collections import namedtuple - SymPyTestResults = namedtuple('TestResults', 'failed attempted') - - return SymPyTestResults(runner.failures, runner.tries) - - -class SymPyTests(object): - - def __init__(self, reporter, kw="", post_mortem=False, - seed=None): - self._post_mortem = post_mortem - self._kw = kw - self._count = 0 - self._root_dir = sympy_dir - self._reporter = reporter - self._reporter.root_dir(self._root_dir) - self._testfiles = [] - self._seed = seed if seed is not None else random.random() - - def test(self, sort=False, timeout=False, slow=False, enhance_asserts=False): - """ - Runs the tests returning True if all tests pass, otherwise False. - - If sort=False run tests in random order. - """ - if sort: - self._testfiles.sort() - else: - from random import shuffle - random.seed(self._seed) - shuffle(self._testfiles) - self._reporter.start(self._seed) - for f in self._testfiles: - try: - self.test_file(f, sort, timeout, slow, enhance_asserts) - except KeyboardInterrupt: - print(" interrupted by user") - self._reporter.finish() - raise - return self._reporter.finish() - - def _enhance_asserts(self, source): - from ast import (NodeTransformer, Compare, Name, Store, Load, Tuple, - Assign, BinOp, Str, Mod, Assert, parse, fix_missing_locations) - - ops = {"Eq": '==', "NotEq": '!=', "Lt": '<', "LtE": '<=', - "Gt": '>', "GtE": '>=', "Is": 'is', "IsNot": 'is not', - "In": 'in', "NotIn": 'not in'} - - class Transform(NodeTransformer): - def visit_Assert(self, stmt): - if isinstance(stmt.test, Compare): - compare = stmt.test - values = [compare.left] + compare.comparators - names = [ "_%s" % i for i, _ in enumerate(values) ] - names_store = [ Name(n, Store()) for n in names ] - names_load = [ Name(n, Load()) for n in names ] - target = Tuple(names_store, Store()) - value = Tuple(values, Load()) - assign = Assign([target], value) - new_compare = Compare(names_load[0], compare.ops, names_load[1:]) - msg_format = "\n%s " + "\n%s ".join([ ops[op.__class__.__name__] for op in compare.ops ]) + "\n%s" - msg = BinOp(Str(msg_format), Mod(), Tuple(names_load, Load())) - test = Assert(new_compare, msg, lineno=stmt.lineno, col_offset=stmt.col_offset) - return [assign, test] - else: - return stmt - - tree = parse(source) - new_tree = Transform().visit(tree) - return fix_missing_locations(new_tree) - - def test_file(self, filename, sort=True, timeout=False, slow=False, enhance_asserts=False): - funcs = [] - try: - gl = {'__file__': filename} - try: - if PY3: - open_file = lambda: open(filename, encoding="utf8") - else: - open_file = lambda: open(filename) - - with open_file() as f: - source = f.read() - if self._kw: - for l in source.splitlines(): - if l.lstrip().startswith('def '): - if any(l.find(k) != -1 for k in self._kw): - break - else: - return - - if enhance_asserts: - try: - source = self._enhance_asserts(source) - except ImportError: - pass - - code = compile(source, filename, "exec") - exec_(code, gl) - except (SystemExit, KeyboardInterrupt): - raise - except ImportError: - self._reporter.import_error(filename, sys.exc_info()) - return - clear_cache() - self._count += 1 - random.seed(self._seed) - pytestfile = "" - if "XFAIL" in gl: - pytestfile = inspect.getsourcefile(gl["XFAIL"]) - pytestfile2 = "" - if "slow" in gl: - pytestfile2 = inspect.getsourcefile(gl["slow"]) - disabled = gl.get("disabled", False) - if not disabled: - # we need to filter only those functions that begin with 'test_' - # that are defined in the testing file or in the file where - # is defined the XFAIL decorator - funcs = [gl[f] for f in gl.keys() if f.startswith("test_") and - (inspect.isfunction(gl[f]) or inspect.ismethod(gl[f])) and - (inspect.getsourcefile(gl[f]) == filename or - inspect.getsourcefile(gl[f]) == pytestfile or - inspect.getsourcefile(gl[f]) == pytestfile2)] - if slow: - funcs = [f for f in funcs if getattr(f, '_slow', False)] - # Sorting of XFAILed functions isn't fixed yet :-( - funcs.sort(key=lambda x: inspect.getsourcelines(x)[1]) - i = 0 - while i < len(funcs): - if isgeneratorfunction(funcs[i]): - # some tests can be generators, that return the actual - # test functions. We unpack it below: - f = funcs.pop(i) - for fg in f(): - func = fg[0] - args = fg[1:] - fgw = lambda: func(*args) - funcs.insert(i, fgw) - i += 1 - else: - i += 1 - # drop functions that are not selected with the keyword expression: - funcs = [x for x in funcs if self.matches(x)] - - if not funcs: - return - except Exception: - self._reporter.entering_filename(filename, len(funcs)) - raise - - self._reporter.entering_filename(filename, len(funcs)) - if not sort: - random.shuffle(funcs) - for f in funcs: - self._reporter.entering_test(f) - try: - if getattr(f, '_slow', False) and not slow: - raise Skipped("Slow") - if timeout: - self._timeout(f, timeout) - else: - random.seed(self._seed) - f() - except KeyboardInterrupt: - if getattr(f, '_slow', False): - self._reporter.test_skip("KeyboardInterrupt") - else: - raise - except Exception: - if timeout: - signal.alarm(0) # Disable the alarm. It could not be handled before. - t, v, tr = sys.exc_info() - if t is AssertionError: - self._reporter.test_fail((t, v, tr)) - if self._post_mortem: - pdb.post_mortem(tr) - elif t.__name__ == "Skipped": - self._reporter.test_skip(v) - elif t.__name__ == "XFail": - self._reporter.test_xfail() - elif t.__name__ == "XPass": - self._reporter.test_xpass(v) - else: - self._reporter.test_exception((t, v, tr)) - if self._post_mortem: - pdb.post_mortem(tr) - else: - self._reporter.test_pass() - self._reporter.leaving_filename() - - def _timeout(self, function, timeout): - def callback(x, y): - signal.alarm(0) - raise Skipped("Timeout") - signal.signal(signal.SIGALRM, callback) - signal.alarm(timeout) # Set an alarm with a given timeout - function() - signal.alarm(0) # Disable the alarm - - def matches(self, x): - """ - Does the keyword expression self._kw match "x"? Returns True/False. - - Always returns True if self._kw is "". - """ - if not self._kw: - return True - for kw in self._kw: - if x.__name__.find(kw) != -1: - return True - return False - - def get_test_files(self, dir, pat='test_*.py'): - """ - Returns the list of test_*.py (default) files at or below directory - ``dir`` relative to the sympy home directory. - """ - dir = os.path.join(self._root_dir, convert_to_native_paths([dir])[0]) - - g = [] - for path, folders, files in os.walk(dir): - g.extend([os.path.join(path, f) for f in files if fnmatch(f, pat)]) - - return sorted([sys_normcase(gi) for gi in g]) - - -class SymPyDocTests(object): - - def __init__(self, reporter, normal): - self._count = 0 - self._root_dir = sympy_dir - self._reporter = reporter - self._reporter.root_dir(self._root_dir) - self._normal = normal - - self._testfiles = [] - - def test(self): - """ - Runs the tests and returns True if all tests pass, otherwise False. - """ - self._reporter.start() - for f in self._testfiles: - try: - self.test_file(f) - except KeyboardInterrupt: - print(" interrupted by user") - self._reporter.finish() - raise - return self._reporter.finish() - - def test_file(self, filename): - clear_cache() - - from sympy.core.compatibility import StringIO - - rel_name = filename[len(self._root_dir) + 1:] - dirname, file = os.path.split(filename) - module = rel_name.replace(os.sep, '.')[:-3] - - if rel_name.startswith("examples"): - # Examples files do not have __init__.py files, - # So we have to temporarily extend sys.path to import them - sys.path.insert(0, dirname) - module = file[:-3] # remove ".py" - setup_pprint() - try: - module = pdoctest._normalize_module(module) - tests = SymPyDocTestFinder().find(module) - except (SystemExit, KeyboardInterrupt): - raise - except ImportError: - self._reporter.import_error(filename, sys.exc_info()) - return - finally: - if rel_name.startswith("examples"): - del sys.path[0] - - tests = [test for test in tests if len(test.examples) > 0] - # By default tests are sorted by alphabetical order by function name. - # We sort by line number so one can edit the file sequentially from - # bottom to top. However, if there are decorated functions, their line - # numbers will be too large and for now one must just search for these - # by text and function name. - tests.sort(key=lambda x: -x.lineno) - - if not tests: - return - self._reporter.entering_filename(filename, len(tests)) - for test in tests: - assert len(test.examples) != 0 - - # check if there are external dependencies which need to be met - if '_doctest_depends_on' in test.globs: - if not self._process_dependencies(test.globs['_doctest_depends_on']): - self._reporter.test_skip() - continue - - runner = SymPyDocTestRunner(optionflags=pdoctest.ELLIPSIS | - pdoctest.NORMALIZE_WHITESPACE | - pdoctest.IGNORE_EXCEPTION_DETAIL) - runner._checker = SymPyOutputChecker() - old = sys.stdout - new = StringIO() - sys.stdout = new - # If the testing is normal, the doctests get importing magic to - # provide the global namespace. If not normal (the default) then - # then must run on their own; all imports must be explicit within - # a function's docstring. Once imported that import will be - # available to the rest of the tests in a given function's - # docstring (unless clear_globs=True below). - if not self._normal: - test.globs = {} - # if this is uncommented then all the test would get is what - # comes by default with a "from sympy import *" - #exec('from sympy import *') in test.globs - test.globs['print_function'] = print_function - try: - f, t = runner.run(test, compileflags=future_flags, - out=new.write, clear_globs=False) - except KeyboardInterrupt: - raise - finally: - sys.stdout = old - if f > 0: - self._reporter.doctest_fail(test.name, new.getvalue()) - else: - self._reporter.test_pass() - self._reporter.leaving_filename() - - def get_test_files(self, dir, pat='*.py', init_only=True): - """ - Returns the list of \*.py files (default) from which docstrings - will be tested which are at or below directory ``dir``. By default, - only those that have an __init__.py in their parent directory - and do not start with ``test_`` will be included. - """ - def importable(x): - """ - Checks if given pathname x is an importable module by checking for - __init__.py file. - - Returns True/False. - - Currently we only test if the __init__.py file exists in the - directory with the file "x" (in theory we should also test all the - parent dirs). - """ - init_py = os.path.join(os.path.dirname(x), "__init__.py") - return os.path.exists(init_py) - - dir = os.path.join(self._root_dir, convert_to_native_paths([dir])[0]) - - g = [] - for path, folders, files in os.walk(dir): - g.extend([os.path.join(path, f) for f in files - if not f.startswith('test_') and fnmatch(f, pat)]) - if init_only: - # skip files that are not importable (i.e. missing __init__.py) - g = [x for x in g if importable(x)] - - return [sys_normcase(gi) for gi in g] - - def _process_dependencies(self, deps): - """ - Returns ``False`` if some dependencies are not met and the test should be - skipped otherwise returns ``True``. - """ - executables = deps.get('exe', None) - moduledeps = deps.get('modules', None) - viewers = deps.get('disable_viewers', None) - - # print deps - - if executables is not None: - for ex in executables: - found = find_executable(ex) - if found is None: - return False - if moduledeps is not None: - for extmod in moduledeps: - if extmod == 'matplotlib': - matplotlib = import_module( - 'matplotlib', - __import__kwargs={'fromlist': - ['pyplot', 'cm', 'collections']}, - min_module_version='1.0.0', catch=(RuntimeError,)) - if matplotlib is not None: - pass - else: - return False - else: - # TODO min version support - mod = import_module(extmod) - if mod is not None: - version = "unknown" - if hasattr(mod, '__version__'): - version = mod.__version__ - else: - return False - if viewers is not None: - import tempfile - tempdir = tempfile.mkdtemp() - os.environ['PATH'] = '%s:%s' % (tempdir, os.environ['PATH']) - - if PY3: - vw = '#!/usr/bin/env python3\n' \ - 'import sys\n' \ - 'if len(sys.argv) <= 1:\n' \ - ' exit("wrong number of args")\n' - else: - vw = '#!/usr/bin/env python\n' \ - 'import sys\n' \ - 'if len(sys.argv) <= 1:\n' \ - ' exit("wrong number of args")\n' - - for viewer in viewers: - with open(os.path.join(tempdir, viewer), 'w') as fh: - fh.write(vw) - - # make the file executable - os.chmod(os.path.join(tempdir, viewer), - stat.S_IREAD | stat.S_IWRITE | stat.S_IXUSR) - - return True - -class SymPyDocTestFinder(DocTestFinder): - """ - A class used to extract the DocTests that are relevant to a given - object, from its docstring and the docstrings of its contained - objects. Doctests can currently be extracted from the following - object types: modules, functions, classes, methods, staticmethods, - classmethods, and properties. - - Modified from doctest's version by looking harder for code in the - case that it looks like the the code comes from a different module. - In the case of decorated functions (e.g. @vectorize) they appear - to come from a different module (e.g. multidemensional) even though - their code is not there. - """ - - 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``. - """ - if self._verbose: - print('Finding tests in %s' % name) - - # If we've already processed this object, then ignore it. - if id(obj) in seen: - return - seen[id(obj)] = 1 - - # Make sure we don't run doctests for classes outside of sympy, such - # as in numpy or scipy. - if inspect.isclass(obj): - if obj.__module__.split('.')[0] != 'sympy': - return - - # Find a test for this object, and add it to the list of tests. - test = self._get_test(obj, name, module, globs, source_lines) - if test is not None: - tests.append(test) - - if not self._recurse: - return - - # Look for tests in a module's contained objects. - if inspect.ismodule(obj): - for rawname, val in obj.__dict__.items(): - # Recurse to functions & classes. - if inspect.isfunction(val) or inspect.isclass(val): - # Make sure we don't run doctests functions or classes - # from different modules - if val.__module__ != module.__name__: - continue - - assert self._from_module(module, val), \ - "%s is not in module %s (rawname %s)" % (val, module, rawname) - - try: - valname = '%s.%s' % (name, rawname) - self._find(tests, val, valname, module, - source_lines, globs, seen) - except KeyboardInterrupt: - raise - - # Look for tests in a module's __test__ dictionary. - for valname, val in getattr(obj, '__test__', {}).items(): - if not isinstance(valname, string_types): - raise ValueError("SymPyDocTestFinder.find: __test__ keys " - "must be strings: %r" % - (type(valname),)) - if not (inspect.isfunction(val) or inspect.isclass(val) or - inspect.ismethod(val) or inspect.ismodule(val) or - isinstance(val, string_types)): - raise ValueError("SymPyDocTestFinder.find: __test__ values " - "must be strings, functions, methods, " - "classes, or modules: %r" % - (type(val),)) - valname = '%s.__test__.%s' % (name, valname) - self._find(tests, val, valname, module, source_lines, - globs, seen) - - # Look for tests in a class's contained objects. - if inspect.isclass(obj): - 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 - isinstance(val, property)): - # Make sure we don't run doctests functions or classes - # from different modules - if isinstance(val, property): - if hasattr(val.fget, '__module__'): - if val.fget.__module__ != module.__name__: - continue - else: - if val.__module__ != module.__name__: - continue - - assert self._from_module(module, val), \ - "%s is not in module %s (valname %s)" % ( - val, module, valname) - - valname = '%s.%s' % (name, valname) - self._find(tests, val, valname, module, source_lines, - globs, seen) - - def _get_test(self, obj, name, module, globs, source_lines): - """ - Return a DocTest for the given object, if it defines a docstring; - otherwise, return None. - """ - - lineno = None - - # Extract the object's docstring. If it doesn't have one, - # then return None (no test for this object). - if isinstance(obj, string_types): - # obj is a string in the case for objects in the polys package. - # Note that source_lines is a binary string (compiled polys - # modules), which can't be handled by _find_lineno so determine - # the line number here. - - docstring = obj - - matches = re.findall("line \d+", name) - assert len(matches) == 1, \ - "string '%s' does not contain lineno " % name - - # NOTE: this is not the exact linenumber but its better than no - # lineno ;) - lineno = int(matches[0][5:]) - - else: - try: - if obj.__doc__ is None: - docstring = '' - else: - docstring = obj.__doc__ - if not isinstance(docstring, string_types): - docstring = str(docstring) - except (TypeError, AttributeError): - docstring = '' - - # Don't bother if the docstring is empty. - if self._exclude_empty and not docstring: - return None - - # check that properties have a docstring because _find_lineno - # assumes it - if isinstance(obj, property): - if obj.fget.__doc__ is None: - return None - - # Find the docstring's location in the file. - if lineno is None: - # handling of properties is not implemented in _find_lineno so do - # it here - if hasattr(obj, 'func_closure') and obj.func_closure is not None: - tobj = obj.func_closure[0].cell_contents - elif isinstance(obj, property): - tobj = obj.fget - else: - tobj = obj - lineno = self._find_lineno(tobj, source_lines) - - if lineno is None: - return None - - # Return a DocTest for this object. - if module is None: - filename = None - else: - filename = getattr(module, '__file__', module.__name__) - if filename[-4:] in (".pyc", ".pyo"): - filename = filename[:-1] - - if hasattr(obj, '_doctest_depends_on'): - globs['_doctest_depends_on'] = obj._doctest_depends_on - else: - globs['_doctest_depends_on'] = {} - - return self._parser.get_doctest(docstring, globs, name, - filename, lineno) - - -class SymPyDocTestRunner(DocTestRunner): - """ - A class used to run DocTest test cases, and accumulate statistics. - The ``run`` method is used to process a single DocTest case. It - returns a tuple ``(f, t)``, where ``t`` is the number of test cases - tried, and ``f`` is the number of test cases that failed. - - Modified from the doctest version to not reset the sys.displayhook (see - issue 5140). - - See the docstring of the original DocTestRunner for more information. - """ - - def run(self, test, compileflags=None, out=None, clear_globs=True): - """ - Run the examples in ``test``, and display the results using the - writer function ``out``. - - The examples are run in the namespace ``test.globs``. If - ``clear_globs`` is true (the default), then this namespace will - be cleared after the test runs, to help with garbage - collection. If you would like to examine the namespace after - the test completes, then use ``clear_globs=False``. - - ``compileflags`` gives the set of flags that should be used by - the Python compiler when running the examples. If not - specified, then it will default to the set of future-import - flags that apply to ``globs``. - - The output of each example is checked using - ``SymPyDocTestRunner.check_output``, and the results are - formatted by the ``SymPyDocTestRunner.report_*`` methods. - """ - self.test = test - - if compileflags is None: - compileflags = pdoctest._extract_future_flags(test.globs) - - save_stdout = sys.stdout - if out is None: - out = save_stdout.write - sys.stdout = self._fakeout - - # Patch pdb.set_trace to restore sys.stdout during interactive - # debugging (so it's not still redirected to self._fakeout). - # Note that the interactive output will go to *our* - # save_stdout, even if that's not the real sys.stdout; this - # allows us to write test cases for the set_trace behavior. - save_set_trace = pdb.set_trace - self.debugger = pdoctest._OutputRedirectingPdb(save_stdout) - self.debugger.reset() - pdb.set_trace = self.debugger.set_trace - - # Patch linecache.getlines, so we can see the example's source - # when we're inside the debugger. - self.save_linecache_getlines = pdoctest.linecache.getlines - linecache.getlines = self.__patched_linecache_getlines - - try: - test.globs['print_function'] = print_function - return self.__run(test, compileflags, out) - finally: - sys.stdout = save_stdout - pdb.set_trace = save_set_trace - linecache.getlines = self.save_linecache_getlines - if clear_globs: - test.globs.clear() - -# We have to override the name mangled methods. -SymPyDocTestRunner._SymPyDocTestRunner__patched_linecache_getlines = \ - DocTestRunner._DocTestRunner__patched_linecache_getlines -SymPyDocTestRunner._SymPyDocTestRunner__run = DocTestRunner._DocTestRunner__run -SymPyDocTestRunner._SymPyDocTestRunner__record_outcome = \ - DocTestRunner._DocTestRunner__record_outcome - - -class SymPyOutputChecker(pdoctest.OutputChecker): - """ - Compared to the OutputChecker from the stdlib our OutputChecker class - supports numerical comparison of floats occuring in the output of the - doctest examples - """ - - def __init__(self): - # NOTE OutputChecker is an old-style class with no __init__ method, - # so we can't call the base class version of __init__ here - - got_floats = r'(\d+\.\d*|\.\d+)' - - # floats in the 'want' string may contain ellipses - want_floats = got_floats + r'(\.{3})?' - - front_sep = r'\s|\+|\-|\*|,' - back_sep = front_sep + r'|j|e' - - fbeg = r'^%s(?=%s|$)' % (got_floats, back_sep) - fmidend = r'(?<=%s)%s(?=%s|$)' % (front_sep, got_floats, back_sep) - self.num_got_rgx = re.compile(r'(%s|%s)' %(fbeg, fmidend)) - - fbeg = r'^%s(?=%s|$)' % (want_floats, back_sep) - fmidend = r'(?<=%s)%s(?=%s|$)' % (front_sep, want_floats, back_sep) - self.num_want_rgx = re.compile(r'(%s|%s)' %(fbeg, fmidend)) - - def check_output(self, want, got, optionflags): - """ - Return True iff the actual output from an example (`got`) - matches the expected output (`want`). These strings are - always considered to match if they are identical; but - depending on what option flags the test runner is using, - several non-exact match types are also possible. See the - documentation for `TestRunner` for more information about - option flags. - """ - # Handle the common case first, for efficiency: - # if they're string-identical, always return true. - if got == want: - return True - - # TODO parse integers as well ? - # Parse floats and compare them. If some of the parsed floats contain - # ellipses, skip the comparison. - matches = self.num_got_rgx.finditer(got) - numbers_got = [match.group(1) for match in matches] # list of strs - matches = self.num_want_rgx.finditer(want) - numbers_want = [match.group(1) for match in matches] # list of strs - if len(numbers_got) != len(numbers_want): - return False - - if len(numbers_got) > 0: - nw_ = [] - for ng, nw in zip(numbers_got, numbers_want): - if '...' in nw: - nw_.append(ng) - continue - else: - nw_.append(nw) - - if abs(float(ng)-float(nw)) > 1e-5: - return False - - got = self.num_got_rgx.sub(r'%s', got) - got = got % tuple(nw_) - - # can be used as a special sequence to signify a - # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used. - if not (optionflags & pdoctest.DONT_ACCEPT_BLANKLINE): - # Replace in want with a blank line. - want = re.sub('(?m)^%s\s*?$' % re.escape(pdoctest.BLANKLINE_MARKER), - '', want) - # If a line in got contains only spaces, then remove the - # spaces. - got = re.sub('(?m)^\s*?$', '', got) - if got == want: - return True - - # This flag causes doctest to ignore any differences in the - # contents of whitespace strings. Note that this can be used - # in conjunction with the ELLIPSIS flag. - if optionflags & pdoctest.NORMALIZE_WHITESPACE: - got = ' '.join(got.split()) - want = ' '.join(want.split()) - if got == want: - return True - - # The ELLIPSIS flag says to let the sequence "..." in `want` - # match any substring in `got`. - if optionflags & pdoctest.ELLIPSIS: - if pdoctest._ellipsis_match(want, got): - return True - - # We didn't find any match; return false. - return False - - class Reporter(object): """ Parent class for all reporters. @@ -2098,5 +415,3 @@ def import_error(self, filename, exc_info): self.write(" ") self.write("[FAIL]", "Red", align="right") self.write("\n") - -sympy_dir = get_sympy_dir()