Spaces:
Sleeping
Sleeping
| """ | |
| This is our testing framework. | |
| Goals: | |
| * it should be compatible with py.test and operate very similarly | |
| (or identically) | |
| * does not 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 | |
| """ | |
| import os | |
| import sys | |
| import platform | |
| import inspect | |
| import traceback | |
| import pdb | |
| import re | |
| import linecache | |
| import time | |
| 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 shutil | |
| import signal | |
| import stat | |
| import tempfile | |
| import warnings | |
| from contextlib import contextmanager | |
| from inspect import unwrap | |
| from sympy.core.cache import clear_cache | |
| from sympy.external import import_module | |
| from sympy.external.gmpy import GROUND_TYPES | |
| IS_WINDOWS = (os.name == 'nt') | |
| ON_CI = os.getenv('CI', None) | |
| # empirically generated list of the proportion of time spent running | |
| # an even split of tests. This should periodically be regenerated. | |
| # A list of [.6, .1, .3] would mean that if the tests are evenly split | |
| # into '1/3', '2/3', '3/3', the first split would take 60% of the time, | |
| # the second 10% and the third 30%. These lists are normalized to sum | |
| # to 1, so [60, 10, 30] has the same behavior as [6, 1, 3] or [.6, .1, .3]. | |
| # | |
| # This list can be generated with the code: | |
| # from time import time | |
| # import sympy | |
| # import os | |
| # os.environ["CI"] = 'true' # Mock CI to get more correct densities | |
| # delays, num_splits = [], 30 | |
| # for i in range(1, num_splits + 1): | |
| # tic = time() | |
| # sympy.test(split='{}/{}'.format(i, num_splits), time_balance=False) # Add slow=True for slow tests | |
| # delays.append(time() - tic) | |
| # tot = sum(delays) | |
| # print([round(x / tot, 4) for x in delays]) | |
| SPLIT_DENSITY = [ | |
| 0.0059, 0.0027, 0.0068, 0.0011, 0.0006, | |
| 0.0058, 0.0047, 0.0046, 0.004, 0.0257, | |
| 0.0017, 0.0026, 0.004, 0.0032, 0.0016, | |
| 0.0015, 0.0004, 0.0011, 0.0016, 0.0014, | |
| 0.0077, 0.0137, 0.0217, 0.0074, 0.0043, | |
| 0.0067, 0.0236, 0.0004, 0.1189, 0.0142, | |
| 0.0234, 0.0003, 0.0003, 0.0047, 0.0006, | |
| 0.0013, 0.0004, 0.0008, 0.0007, 0.0006, | |
| 0.0139, 0.0013, 0.0007, 0.0051, 0.002, | |
| 0.0004, 0.0005, 0.0213, 0.0048, 0.0016, | |
| 0.0012, 0.0014, 0.0024, 0.0015, 0.0004, | |
| 0.0005, 0.0007, 0.011, 0.0062, 0.0015, | |
| 0.0021, 0.0049, 0.0006, 0.0006, 0.0011, | |
| 0.0006, 0.0019, 0.003, 0.0044, 0.0054, | |
| 0.0057, 0.0049, 0.0016, 0.0006, 0.0009, | |
| 0.0006, 0.0012, 0.0006, 0.0149, 0.0532, | |
| 0.0076, 0.0041, 0.0024, 0.0135, 0.0081, | |
| 0.2209, 0.0459, 0.0438, 0.0488, 0.0137, | |
| 0.002, 0.0003, 0.0008, 0.0039, 0.0024, | |
| 0.0005, 0.0004, 0.003, 0.056, 0.0026] | |
| SPLIT_DENSITY_SLOW = [0.0086, 0.0004, 0.0568, 0.0003, 0.0032, 0.0005, 0.0004, 0.0013, 0.0016, 0.0648, 0.0198, 0.1285, 0.098, 0.0005, 0.0064, 0.0003, 0.0004, 0.0026, 0.0007, 0.0051, 0.0089, 0.0024, 0.0033, 0.0057, 0.0005, 0.0003, 0.001, 0.0045, 0.0091, 0.0006, 0.0005, 0.0321, 0.0059, 0.1105, 0.216, 0.1489, 0.0004, 0.0003, 0.0006, 0.0483] | |
| class Skipped(Exception): | |
| pass | |
| class TimeOutError(Exception): | |
| pass | |
| class DependencyError(Exception): | |
| pass | |
| 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. | |
| """ | |
| # This regexp matches the start of non-blank lines: | |
| return re.sub('(?m)^(?!$)', indent*' ', s) | |
| pdoctest._indent = _indent # type: ignore | |
| # override 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 IS_WINDOWS: | |
| DocTestRunner.report_failure = _report_failure # type: ignore | |
| 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(os.path.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. | |
| """ | |
| this_file = os.path.abspath(__file__) | |
| sympy_dir = os.path.join(os.path.dirname(this_file), "..", "..") | |
| sympy_dir = os.path.normpath(sympy_dir) | |
| return os.path.normcase(sympy_dir) | |
| def setup_pprint(disable_line_wrap=True): | |
| from sympy.interactive.printing import init_printing | |
| from sympy.printing.pretty.pretty import pprint_use_unicode | |
| import sympy.interactive.printing as interactive_printing | |
| from sympy.printing.pretty import stringpict | |
| # Prevent init_printing() in doctests from affecting other doctests | |
| interactive_printing.NO_GLOBAL = True | |
| # force pprint to be in ascii mode in doctests | |
| use_unicode_prev = pprint_use_unicode(False) | |
| # disable line wrapping for pprint() outputs | |
| wrap_line_prev = stringpict._GLOBAL_WRAP_LINE | |
| if disable_line_wrap: | |
| stringpict._GLOBAL_WRAP_LINE = False | |
| # hook our nice, hash-stable strprinter | |
| init_printing(pretty_print=False) | |
| return use_unicode_prev, wrap_line_prev | |
| def raise_on_deprecated(): | |
| """Context manager to make DeprecationWarning raise an error | |
| This is to catch SymPyDeprecationWarning from library code while running | |
| tests and doctests. It is important to use this context manager around | |
| each individual test/doctest in case some tests modify the warning | |
| filters. | |
| """ | |
| with warnings.catch_warnings(): | |
| warnings.filterwarnings('error', '.*', DeprecationWarning, module='sympy.*') | |
| yield | |
| def run_in_subprocess_with_hash_randomization( | |
| function, function_args=(), | |
| function_kwargs=None, command=sys.executable, | |
| module='sympy.testing.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.testing.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.testing.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. | |
| """ | |
| cwd = get_sympy_dir() | |
| # 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 does not have this support, it won't recognize the -R flag | |
| p = subprocess.Popen([command, "-RV"], stdout=subprocess.PIPE, | |
| stderr=subprocess.STDOUT, cwd=cwd) | |
| 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 | |
| function_kwargs = function_kwargs or {} | |
| # 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], cwd=cwd) | |
| 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=None, | |
| doctest_args=(), doctest_kwargs=None, | |
| examples_args=(), examples_kwargs=None): | |
| """ | |
| Run all tests. | |
| Right now, this runs the regular tests (bin/test), the doctests | |
| (bin/doctest), and 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.testing.runtests import run_all_tests | |
| >>> run_all_tests(test_args=("solvers",), | |
| ... test_kwargs={"colors:False"}) # doctest: +SKIP | |
| """ | |
| tests_successful = True | |
| test_kwargs = test_kwargs or {} | |
| doctest_kwargs = doctest_kwargs or {} | |
| examples_kwargs = examples_kwargs or {'quiet': True} | |
| try: | |
| # Regular tests | |
| if not test(*test_args, **test_kwargs): | |
| # some regular test fails, so set the tests_successful | |
| # flag to false and continue running the doctests | |
| tests_successful = False | |
| # Doctests | |
| print() | |
| if not doctest(*doctest_args, **doctest_kwargs): | |
| tests_successful = False | |
| # Examples | |
| print() | |
| sys.path.append("examples") # examples/all.py | |
| from all import run_examples # type: ignore | |
| 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, subprocess=True, rerun=0, **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 | |
| Do not 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 | |
| The ``time_balance`` option can be passed in conjunction with ``split``. | |
| If ``time_balance=True`` (the default for ``sympy.test``), SymPy will attempt | |
| to split the tests such that each split takes equal time. This heuristic | |
| for balancing is based on pre-recorded test data. | |
| >>> sympy.test(split='1/2', time_balance=True) # 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). | |
| """ | |
| # 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, | |
| verbose=False, tb="short", kw=None, pdb=False, colors=True, | |
| force_colors=False, sort=True, seed=None, timeout=False, | |
| fail_on_timeout=False, slow=False, enhance_asserts=False, split=None, | |
| time_balance=True, blacklist=(), | |
| fast_threshold=None, slow_threshold=None): | |
| """ | |
| 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. | |
| """ | |
| kw = kw or () | |
| # ensure that kw is a tuple | |
| if isinstance(kw, str): | |
| kw = (kw,) | |
| post_mortem = pdb | |
| if seed is None: | |
| seed = random.randrange(100000000) | |
| if ON_CI and timeout is False: | |
| timeout = 595 | |
| fail_on_timeout = True | |
| if ON_CI: | |
| blacklist = list(blacklist) + ['sympy/plotting/pygletplot/tests'] | |
| blacklist = convert_to_native_paths(blacklist) | |
| r = PyTestReporter(verbose=verbose, tb=tb, colors=colors, | |
| force_colors=force_colors, split=split) | |
| # This won't strictly run the test for the corresponding file, but it is | |
| # good enough for copying and pasting the failing test. | |
| _paths = [] | |
| for path in paths: | |
| if '::' in path: | |
| path, _kw = path.split('::', 1) | |
| kw += (_kw,) | |
| _paths.append(path) | |
| paths = _paths | |
| t = SymPyTests(r, kw, post_mortem, seed, | |
| fast_threshold=fast_threshold, | |
| slow_threshold=slow_threshold) | |
| 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 | |
| density = None | |
| if time_balance: | |
| if slow: | |
| density = SPLIT_DENSITY_SLOW | |
| else: | |
| density = SPLIT_DENSITY | |
| if split: | |
| matched = split_list(matched, split, density=density) | |
| t._testfiles.extend(matched) | |
| return int(not t.test(sort=sort, timeout=timeout, slow=slow, | |
| enhance_asserts=enhance_asserts, fail_on_timeout=fail_on_timeout)) | |
| def doctest(*paths, subprocess=True, rerun=0, **kwargs): | |
| r""" | |
| 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/src/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) except | |
| that ``verbose`` may also be set equal to ``2`` in order to print | |
| individual doctest lines, as they are being tested. | |
| """ | |
| # 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 _get_doctest_blacklist(): | |
| '''Get the default blacklist for the doctests''' | |
| blacklist = [] | |
| blacklist.extend([ | |
| "doc/src/modules/plotting.rst", # generates live plots | |
| "doc/src/modules/physics/mechanics/autolev_parser.rst", | |
| "sympy/codegen/array_utils.py", # raises deprecation warning | |
| "sympy/core/compatibility.py", # backwards compatibility shim, importing it triggers a deprecation warning | |
| "sympy/core/trace.py", # backwards compatibility shim, importing it triggers a deprecation warning | |
| "sympy/galgebra.py", # no longer part of SymPy | |
| "sympy/parsing/autolev/_antlr/autolevlexer.py", # generated code | |
| "sympy/parsing/autolev/_antlr/autolevlistener.py", # generated code | |
| "sympy/parsing/autolev/_antlr/autolevparser.py", # generated code | |
| "sympy/parsing/latex/_antlr/latexlexer.py", # generated code | |
| "sympy/parsing/latex/_antlr/latexparser.py", # generated code | |
| "sympy/plotting/pygletplot/__init__.py", # crashes on some systems | |
| "sympy/plotting/pygletplot/plot.py", # crashes on some systems | |
| "sympy/printing/ccode.py", # backwards compatibility shim, importing it breaks the codegen doctests | |
| "sympy/printing/cxxcode.py", # backwards compatibility shim, importing it breaks the codegen doctests | |
| "sympy/printing/fcode.py", # backwards compatibility shim, importing it breaks the codegen doctests | |
| "sympy/testing/randtest.py", # backwards compatibility shim, importing it triggers a deprecation warning | |
| "sympy/this.py", # prints text | |
| ]) | |
| # autolev parser tests | |
| num = 12 | |
| for i in range (1, num+1): | |
| blacklist.append("sympy/parsing/autolev/test-examples/ruletest" + str(i) + ".py") | |
| blacklist.extend(["sympy/parsing/autolev/test-examples/pydy-example-repo/mass_spring_damper.py", | |
| "sympy/parsing/autolev/test-examples/pydy-example-repo/chaos_pendulum.py", | |
| "sympy/parsing/autolev/test-examples/pydy-example-repo/double_pendulum.py", | |
| "sympy/parsing/autolev/test-examples/pydy-example-repo/non_min_pendulum.py"]) | |
| 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/src/modules/numeric-computation.rst", | |
| "doc/src/explanation/best-practices.md", | |
| "doc/src/tutorials/physics/biomechanics/biomechanical-model-example.rst", | |
| "doc/src/tutorials/physics/biomechanics/biomechanics.rst", | |
| ]) | |
| else: | |
| if import_module('matplotlib') is None: | |
| blacklist.extend([ | |
| "examples/intermediate/mplot2d.py", | |
| "examples/intermediate/mplot3d.py" | |
| ]) | |
| else: | |
| # Use a non-windowed backend, so that the tests work on CI | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| if ON_CI or import_module('pyglet') is None: | |
| blacklist.extend(["sympy/plotting/pygletplot"]) | |
| if import_module('aesara') is None: | |
| blacklist.extend([ | |
| "sympy/printing/aesaracode.py", | |
| "doc/src/modules/numeric-computation.rst", | |
| ]) | |
| if import_module('cupy') is None: | |
| blacklist.extend([ | |
| "doc/src/modules/numeric-computation.rst", | |
| ]) | |
| if import_module('jax') is None: | |
| blacklist.extend([ | |
| "doc/src/modules/numeric-computation.rst", | |
| ]) | |
| if import_module('antlr4') is None: | |
| blacklist.extend([ | |
| "sympy/parsing/autolev/__init__.py", | |
| "sympy/parsing/latex/_parse_latex_antlr.py", | |
| ]) | |
| if import_module('lfortran') is None: | |
| #throws ImportError when lfortran not installed | |
| blacklist.extend([ | |
| "sympy/parsing/sym_expr.py", | |
| ]) | |
| if import_module("scipy") is None: | |
| # throws ModuleNotFoundError when scipy not installed | |
| blacklist.extend([ | |
| "doc/src/guides/solving/solve-numerically.md", | |
| "doc/src/guides/solving/solve-ode.md", | |
| ]) | |
| if import_module("numpy") is None: | |
| # throws ModuleNotFoundError when numpy not installed | |
| blacklist.extend([ | |
| "doc/src/guides/solving/solve-ode.md", | |
| "doc/src/guides/solving/solve-numerically.md", | |
| ]) | |
| # 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.extend([ | |
| "sympy/conftest.py", # Depends on pytest | |
| ]) | |
| # These are deprecated stubs to be removed: | |
| blacklist.extend([ | |
| "sympy/utilities/tmpfiles.py", | |
| "sympy/utilities/pytest.py", | |
| "sympy/utilities/runtests.py", | |
| "sympy/utilities/quality_unicode.py", | |
| "sympy/utilities/randtest.py", | |
| ]) | |
| blacklist = convert_to_native_paths(blacklist) | |
| return blacklist | |
| 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. | |
| """ | |
| from sympy.printing.pretty.pretty import pprint_use_unicode | |
| from sympy.printing.pretty import stringpict | |
| normal = kwargs.get("normal", False) | |
| verbose = kwargs.get("verbose", False) | |
| colors = kwargs.get("colors", True) | |
| force_colors = kwargs.get("force_colors", False) | |
| blacklist = kwargs.get("blacklist", []) | |
| split = kwargs.get('split', None) | |
| blacklist.extend(_get_doctest_blacklist()) | |
| # Use a non-windowed backend, so that the tests work on CI | |
| if import_module('matplotlib') is not None: | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| # Disable warnings for external modules | |
| import sympy.external | |
| sympy.external.importtools.WARN_OLD_VERSION = False | |
| sympy.external.importtools.WARN_NOT_INSTALLED = False | |
| # Disable showing up of plots | |
| from sympy.plotting.plot import unset_show | |
| unset_show() | |
| r = PyTestReporter(verbose, split=split, colors=colors,\ | |
| force_colors=force_colors) | |
| 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 | |
| matched.sort() | |
| 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 and *.md files at or below doc/src. 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_rst = t.get_test_files('doc/src', '*.rst', init_only=False) | |
| test_files_md = t.get_test_files('doc/src', '*.md', init_only=False) | |
| test_files = test_files_rst + test_files_md | |
| 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) | |
| first_report = True | |
| for rst_file in matched: | |
| if not os.path.isfile(rst_file): | |
| continue | |
| old_displayhook = sys.displayhook | |
| try: | |
| use_unicode_prev, wrap_line_prev = setup_pprint() | |
| 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 | |
| # The NO_GLOBAL flag overrides the no_global flag to init_printing | |
| # if True | |
| import sympy.interactive.printing as interactive_printing | |
| interactive_printing.NO_GLOBAL = False | |
| pprint_use_unicode(use_unicode_prev) | |
| stringpict._GLOBAL_WRAP_LINE = wrap_line_prev | |
| rstfailed, tested = out | |
| if tested: | |
| failed = rstfailed or failed | |
| if first_report: | |
| first_report = False | |
| msg = 'rst/md 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, density=None): | |
| """ | |
| 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. | |
| `density` may be specified as a list. If specified, | |
| tests will be balanced so that each split has as equal-as-possible | |
| amount of mass according to `density`. | |
| >>> from sympy.testing.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()) | |
| if not density: | |
| return l[(i - 1)*len(l)//t : i*len(l)//t] | |
| # normalize density | |
| tot = sum(density) | |
| density = [x / tot for x in density] | |
| def density_inv(x): | |
| """Interpolate the inverse to the cumulative | |
| distribution function given by density""" | |
| if x <= 0: | |
| return 0 | |
| if x >= sum(density): | |
| return 1 | |
| # find the first time the cumulative sum surpasses x | |
| # and linearly interpolate | |
| cumm = 0 | |
| for i, d in enumerate(density): | |
| cumm += d | |
| if cumm >= x: | |
| break | |
| frac = (d - (cumm - x)) / d | |
| return (i + frac) / len(density) | |
| lower_frac = density_inv((i - 1) / t) | |
| higher_frac = density_inv(i / t) | |
| return l[int(lower_frac*len(l)) : int(higher_frac*len(l))] | |
| from collections import namedtuple | |
| SymPyTestResults = namedtuple('SymPyTestResults', 'failed attempted') | |
| 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 | |
| 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) | |
| if report: | |
| runner.summarize() | |
| if pdoctest.master is None: | |
| pdoctest.master = runner | |
| else: | |
| pdoctest.master.merge(runner) | |
| return SymPyTestResults(runner.failures, runner.tries) | |
| class SymPyTests: | |
| def __init__(self, reporter, kw="", post_mortem=False, | |
| seed=None, fast_threshold=None, slow_threshold=None): | |
| self._post_mortem = post_mortem | |
| self._kw = kw | |
| self._count = 0 | |
| self._root_dir = get_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() | |
| # Defaults in seconds, from human / UX design limits | |
| # http://www.nngroup.com/articles/response-times-3-important-limits/ | |
| # | |
| # These defaults are *NOT* set in stone as we are measuring different | |
| # things, so others feel free to come up with a better yardstick :) | |
| if fast_threshold: | |
| self._fast_threshold = float(fast_threshold) | |
| else: | |
| self._fast_threshold = 8 | |
| if slow_threshold: | |
| self._slow_threshold = float(slow_threshold) | |
| else: | |
| self._slow_threshold = 10 | |
| def test(self, sort=False, timeout=False, slow=False, | |
| enhance_asserts=False, fail_on_timeout=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() | |
| elif slow: | |
| pass | |
| else: | |
| random.seed(self._seed) | |
| random.shuffle(self._testfiles) | |
| self._reporter.start(self._seed) | |
| for f in self._testfiles: | |
| try: | |
| self.test_file(f, sort, timeout, slow, | |
| enhance_asserts, fail_on_timeout) | |
| 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, fail_on_timeout=False): | |
| reporter = self._reporter | |
| funcs = [] | |
| try: | |
| gl = {'__file__': filename} | |
| try: | |
| open_file = lambda: open(filename, encoding="utf8") | |
| with open_file() as f: | |
| source = f.read() | |
| if self._kw: | |
| for l in source.splitlines(): | |
| if l.lstrip().startswith('def '): | |
| if any(l.lower().find(k.lower()) != -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", flags=0, dont_inherit=True) | |
| exec(code, gl) | |
| except (SystemExit, KeyboardInterrupt): | |
| raise | |
| except ImportError: | |
| reporter.import_error(filename, sys.exc_info()) | |
| return | |
| except Exception: | |
| reporter.test_exception(sys.exc_info()) | |
| clear_cache() | |
| self._count += 1 | |
| random.seed(self._seed) | |
| disabled = gl.get("disabled", False) | |
| if not disabled: | |
| # we need to filter only those functions that begin with 'test_' | |
| # We have to be careful about decorated functions. As long as | |
| # the decorator uses functools.wraps, we can detect it. | |
| funcs = [] | |
| for f in gl: | |
| if (f.startswith("test_") and (inspect.isfunction(gl[f]) | |
| or inspect.ismethod(gl[f]))): | |
| func = gl[f] | |
| # Handle multiple decorators | |
| while hasattr(func, '__wrapped__'): | |
| func = func.__wrapped__ | |
| if inspect.getsourcefile(func) == filename: | |
| funcs.append(gl[f]) | |
| 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 inspect.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: | |
| reporter.entering_filename(filename, len(funcs)) | |
| raise | |
| reporter.entering_filename(filename, len(funcs)) | |
| if not sort: | |
| random.shuffle(funcs) | |
| for f in funcs: | |
| start = time.time() | |
| reporter.entering_test(f) | |
| try: | |
| if getattr(f, '_slow', False) and not slow: | |
| raise Skipped("Slow") | |
| with raise_on_deprecated(): | |
| if timeout: | |
| self._timeout(f, timeout, fail_on_timeout) | |
| else: | |
| random.seed(self._seed) | |
| f() | |
| except KeyboardInterrupt: | |
| if getattr(f, '_slow', False): | |
| 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: | |
| reporter.test_fail((t, v, tr)) | |
| if self._post_mortem: | |
| pdb.post_mortem(tr) | |
| elif t.__name__ == "Skipped": | |
| reporter.test_skip(v) | |
| elif t.__name__ == "XFail": | |
| reporter.test_xfail() | |
| elif t.__name__ == "XPass": | |
| reporter.test_xpass(v) | |
| else: | |
| reporter.test_exception((t, v, tr)) | |
| if self._post_mortem: | |
| pdb.post_mortem(tr) | |
| else: | |
| reporter.test_pass() | |
| taken = time.time() - start | |
| if taken > self._slow_threshold: | |
| filename = os.path.relpath(filename, reporter._root_dir) | |
| reporter.slow_test_functions.append( | |
| (filename + "::" + f.__name__, taken)) | |
| if getattr(f, '_slow', False) and slow: | |
| if taken < self._fast_threshold: | |
| filename = os.path.relpath(filename, reporter._root_dir) | |
| reporter.fast_test_functions.append( | |
| (filename + "::" + f.__name__, taken)) | |
| reporter.leaving_filename() | |
| def _timeout(self, function, timeout, fail_on_timeout): | |
| def callback(x, y): | |
| signal.alarm(0) | |
| if fail_on_timeout: | |
| raise TimeOutError("Timed out after %d seconds" % timeout) | |
| else: | |
| 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__.lower().find(kw.lower()) != -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([os.path.normcase(gi) for gi in g]) | |
| class SymPyDocTests: | |
| def __init__(self, reporter, normal): | |
| self._count = 0 | |
| self._root_dir = get_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 io import StringIO | |
| import sympy.interactive.printing as interactive_printing | |
| from sympy.printing.pretty.pretty import pprint_use_unicode | |
| from sympy.printing.pretty import stringpict | |
| 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" | |
| 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 | |
| if self._reporter._verbose: | |
| self._reporter.write("\n{} ".format(test.name)) | |
| # check if there are external dependencies which need to be met | |
| if '_doctest_depends_on' in test.globs: | |
| try: | |
| self._check_dependencies(**test.globs['_doctest_depends_on']) | |
| except DependencyError as e: | |
| self._reporter.test_skip(v=str(e)) | |
| continue | |
| runner = SymPyDocTestRunner(verbose=self._reporter._verbose==2, | |
| optionflags=pdoctest.ELLIPSIS | | |
| pdoctest.NORMALIZE_WHITESPACE | | |
| pdoctest.IGNORE_EXCEPTION_DETAIL) | |
| runner._checker = SymPyOutputChecker() | |
| old = sys.stdout | |
| new = old if self._reporter._verbose==2 else 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 | |
| old_displayhook = sys.displayhook | |
| use_unicode_prev, wrap_line_prev = setup_pprint() | |
| try: | |
| f, t = runner.run(test, | |
| 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() | |
| sys.displayhook = old_displayhook | |
| interactive_printing.NO_GLOBAL = False | |
| pprint_use_unicode(use_unicode_prev) | |
| stringpict._GLOBAL_WRAP_LINE = wrap_line_prev | |
| self._reporter.leaving_filename() | |
| def get_test_files(self, dir, pat='*.py', init_only=True): | |
| r""" | |
| 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 [os.path.normcase(gi) for gi in g] | |
| def _check_dependencies(self, | |
| executables=(), | |
| modules=(), | |
| disable_viewers=(), | |
| python_version=(3, 5), | |
| ground_types=None): | |
| """ | |
| Checks if the dependencies for the test are installed. | |
| Raises ``DependencyError`` it at least one dependency is not installed. | |
| """ | |
| for executable in executables: | |
| if not shutil.which(executable): | |
| raise DependencyError("Could not find %s" % executable) | |
| for module in modules: | |
| if module == 'matplotlib': | |
| matplotlib = import_module( | |
| 'matplotlib', | |
| import_kwargs={'fromlist': | |
| ['pyplot', 'cm', 'collections']}, | |
| min_module_version='1.0.0', catch=(RuntimeError,)) | |
| if matplotlib is None: | |
| raise DependencyError("Could not import matplotlib") | |
| else: | |
| if not import_module(module): | |
| raise DependencyError("Could not import %s" % module) | |
| if disable_viewers: | |
| tempdir = tempfile.mkdtemp() | |
| os.environ['PATH'] = '%s:%s' % (tempdir, os.environ['PATH']) | |
| vw = ('#!/usr/bin/env python3\n' | |
| 'import sys\n' | |
| 'if len(sys.argv) <= 1:\n' | |
| ' exit("wrong number of args")\n') | |
| for viewer in disable_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) | |
| if python_version: | |
| if sys.version_info < python_version: | |
| raise DependencyError("Requires Python >= " + '.'.join(map(str, python_version))) | |
| if ground_types is not None: | |
| if GROUND_TYPES not in ground_types: | |
| raise DependencyError("Requires ground_types in " + str(ground_types)) | |
| if 'pyglet' in modules: | |
| # monkey-patch pyglet s.t. it does not open a window during | |
| # doctesting | |
| import pyglet | |
| class DummyWindow: | |
| def __init__(self, *args, **kwargs): | |
| self.has_exit = True | |
| self.width = 600 | |
| self.height = 400 | |
| def set_vsync(self, x): | |
| pass | |
| def switch_to(self): | |
| pass | |
| def push_handlers(self, x): | |
| pass | |
| def close(self): | |
| pass | |
| pyglet.window.Window = DummyWindow | |
| 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 to look harder for code that | |
| appears comes from a different module. For example, the @vectorize | |
| decorator makes it look like functions come from multidimensional.py | |
| even though their code exists elsewhere. | |
| """ | |
| 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, str): | |
| 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, str)): | |
| 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(unwrap(val)) or | |
| inspect.isclass(val) or | |
| isinstance(val, property)) and | |
| self._from_module(module, val)): | |
| # 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 does not have one, | |
| # then return None (no test for this object). | |
| if isinstance(obj, str): | |
| # 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(r"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: | |
| docstring = getattr(obj, '__doc__', '') | |
| if docstring is None: | |
| docstring = '' | |
| if not isinstance(docstring, str): | |
| docstring = str(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: | |
| obj = unwrap(obj) | |
| # 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] | |
| globs['_doctest_depends_on'] = getattr(obj, '_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 | |
| # Remove ``` from the end of example, which may appear in Markdown | |
| # files | |
| for example in test.examples: | |
| example.want = example.want.replace('```\n', '') | |
| example.exc_msg = example.exc_msg and example.exc_msg.replace('```\n', '') | |
| 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 | |
| # Fail for deprecation warnings | |
| with raise_on_deprecated(): | |
| try: | |
| 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. | |
| monkeypatched_methods = [ | |
| 'patched_linecache_getlines', | |
| 'run', | |
| 'record_outcome' | |
| ] | |
| for method in monkeypatched_methods: | |
| oldname = '_DocTestRunner__' + method | |
| newname = '_SymPyDocTestRunner__' + method | |
| setattr(SymPyDocTestRunner, newname, getattr(DocTestRunner, oldname)) | |
| class SymPyOutputChecker(pdoctest.OutputChecker): | |
| """ | |
| Compared to the OutputChecker from the stdlib our OutputChecker class | |
| supports numerical comparison of floats occurring 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_) | |
| # <BLANKLINE> 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 <BLANKLINE> in want with a blank line. | |
| want = re.sub(r'(?m)^%s\s*?$' % re.escape(pdoctest.BLANKLINE_MARKER), | |
| '', want) | |
| # If a line in got contains only spaces, then remove the | |
| # spaces. | |
| got = re.sub(r'(?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: | |
| """ | |
| Parent class for all reporters. | |
| """ | |
| pass | |
| class PyTestReporter(Reporter): | |
| """ | |
| Py.test like reporter. Should produce output identical to py.test. | |
| """ | |
| def __init__(self, verbose=False, tb="short", colors=True, | |
| force_colors=False, split=None): | |
| self._verbose = verbose | |
| self._tb_style = tb | |
| self._colors = colors | |
| self._force_colors = force_colors | |
| self._xfailed = 0 | |
| self._xpassed = [] | |
| self._failed = [] | |
| self._failed_doctest = [] | |
| self._passed = 0 | |
| self._skipped = 0 | |
| self._exceptions = [] | |
| self._terminal_width = None | |
| self._default_width = 80 | |
| self._split = split | |
| self._active_file = '' | |
| self._active_f = None | |
| # TODO: Should these be protected? | |
| self.slow_test_functions = [] | |
| self.fast_test_functions = [] | |
| # this tracks the x-position of the cursor (useful for positioning | |
| # things on the screen), without the need for any readline library: | |
| self._write_pos = 0 | |
| self._line_wrap = False | |
| def root_dir(self, dir): | |
| self._root_dir = dir | |
| def terminal_width(self): | |
| if self._terminal_width is not None: | |
| return self._terminal_width | |
| def findout_terminal_width(): | |
| if sys.platform == "win32": | |
| # Windows support is based on: | |
| # | |
| # http://code.activestate.com/recipes/ | |
| # 440694-determine-size-of-console-window-on-windows/ | |
| from ctypes import windll, create_string_buffer | |
| h = windll.kernel32.GetStdHandle(-12) | |
| csbi = create_string_buffer(22) | |
| res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) | |
| if res: | |
| import struct | |
| (_, _, _, _, _, left, _, right, _, _, _) = \ | |
| struct.unpack("hhhhHhhhhhh", csbi.raw) | |
| return right - left | |
| else: | |
| return self._default_width | |
| if hasattr(sys.stdout, 'isatty') and not sys.stdout.isatty(): | |
| return self._default_width # leave PIPEs alone | |
| try: | |
| process = subprocess.Popen(['stty', '-a'], | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE) | |
| stdout, stderr = process.communicate() | |
| stdout = stdout.decode("utf-8") | |
| except OSError: | |
| pass | |
| else: | |
| # We support the following output formats from stty: | |
| # | |
| # 1) Linux -> columns 80 | |
| # 2) OS X -> 80 columns | |
| # 3) Solaris -> columns = 80 | |
| re_linux = r"columns\s+(?P<columns>\d+);" | |
| re_osx = r"(?P<columns>\d+)\s*columns;" | |
| re_solaris = r"columns\s+=\s+(?P<columns>\d+);" | |
| for regex in (re_linux, re_osx, re_solaris): | |
| match = re.search(regex, stdout) | |
| if match is not None: | |
| columns = match.group('columns') | |
| try: | |
| width = int(columns) | |
| except ValueError: | |
| pass | |
| if width != 0: | |
| return width | |
| return self._default_width | |
| width = findout_terminal_width() | |
| self._terminal_width = width | |
| return width | |
| def write(self, text, color="", align="left", width=None, | |
| force_colors=False): | |
| """ | |
| Prints a text on the screen. | |
| It uses sys.stdout.write(), so no readline library is necessary. | |
| Parameters | |
| ========== | |
| color : choose from the colors below, "" means default color | |
| align : "left"/"right", "left" is a normal print, "right" is aligned on | |
| the right-hand side of the screen, filled with spaces if | |
| necessary | |
| width : the screen width | |
| """ | |
| color_templates = ( | |
| ("Black", "0;30"), | |
| ("Red", "0;31"), | |
| ("Green", "0;32"), | |
| ("Brown", "0;33"), | |
| ("Blue", "0;34"), | |
| ("Purple", "0;35"), | |
| ("Cyan", "0;36"), | |
| ("LightGray", "0;37"), | |
| ("DarkGray", "1;30"), | |
| ("LightRed", "1;31"), | |
| ("LightGreen", "1;32"), | |
| ("Yellow", "1;33"), | |
| ("LightBlue", "1;34"), | |
| ("LightPurple", "1;35"), | |
| ("LightCyan", "1;36"), | |
| ("White", "1;37"), | |
| ) | |
| colors = {} | |
| for name, value in color_templates: | |
| colors[name] = value | |
| c_normal = '\033[0m' | |
| c_color = '\033[%sm' | |
| if width is None: | |
| width = self.terminal_width | |
| if align == "right": | |
| if self._write_pos + len(text) > width: | |
| # we don't fit on the current line, create a new line | |
| self.write("\n") | |
| self.write(" "*(width - self._write_pos - len(text))) | |
| if not self._force_colors and hasattr(sys.stdout, 'isatty') and not \ | |
| sys.stdout.isatty(): | |
| # the stdout is not a terminal, this for example happens if the | |
| # output is piped to less, e.g. "bin/test | less". In this case, | |
| # the terminal control sequences would be printed verbatim, so | |
| # don't use any colors. | |
| color = "" | |
| elif sys.platform == "win32": | |
| # Windows consoles don't support ANSI escape sequences | |
| color = "" | |
| elif not self._colors: | |
| color = "" | |
| if self._line_wrap: | |
| if text[0] != "\n": | |
| sys.stdout.write("\n") | |
| # Avoid UnicodeEncodeError when printing out test failures | |
| if IS_WINDOWS: | |
| text = text.encode('raw_unicode_escape').decode('utf8', 'ignore') | |
| elif not sys.stdout.encoding.lower().startswith('utf'): | |
| text = text.encode(sys.stdout.encoding, 'backslashreplace' | |
| ).decode(sys.stdout.encoding) | |
| if color == "": | |
| sys.stdout.write(text) | |
| else: | |
| sys.stdout.write("%s%s%s" % | |
| (c_color % colors[color], text, c_normal)) | |
| sys.stdout.flush() | |
| l = text.rfind("\n") | |
| if l == -1: | |
| self._write_pos += len(text) | |
| else: | |
| self._write_pos = len(text) - l - 1 | |
| self._line_wrap = self._write_pos >= width | |
| self._write_pos %= width | |
| def write_center(self, text, delim="="): | |
| width = self.terminal_width | |
| if text != "": | |
| text = " %s " % text | |
| idx = (width - len(text)) // 2 | |
| t = delim*idx + text + delim*(width - idx - len(text)) | |
| self.write(t + "\n") | |
| def write_exception(self, e, val, tb): | |
| # remove the first item, as that is always runtests.py | |
| tb = tb.tb_next | |
| t = traceback.format_exception(e, val, tb) | |
| self.write("".join(t)) | |
| def start(self, seed=None, msg="test process starts"): | |
| self.write_center(msg) | |
| executable = sys.executable | |
| v = tuple(sys.version_info) | |
| python_version = "%s.%s.%s-%s-%s" % v | |
| implementation = platform.python_implementation() | |
| if implementation == 'PyPy': | |
| implementation += " %s.%s.%s-%s-%s" % sys.pypy_version_info | |
| self.write("executable: %s (%s) [%s]\n" % | |
| (executable, python_version, implementation)) | |
| from sympy.utilities.misc import ARCH | |
| self.write("architecture: %s\n" % ARCH) | |
| from sympy.core.cache import USE_CACHE | |
| self.write("cache: %s\n" % USE_CACHE) | |
| version = '' | |
| if GROUND_TYPES =='gmpy': | |
| import gmpy2 as gmpy | |
| version = gmpy.version() | |
| self.write("ground types: %s %s\n" % (GROUND_TYPES, version)) | |
| numpy = import_module('numpy') | |
| self.write("numpy: %s\n" % (None if not numpy else numpy.__version__)) | |
| if seed is not None: | |
| self.write("random seed: %d\n" % seed) | |
| from sympy.utilities.misc import HASH_RANDOMIZATION | |
| self.write("hash randomization: ") | |
| hash_seed = os.getenv("PYTHONHASHSEED") or '0' | |
| if HASH_RANDOMIZATION and (hash_seed == "random" or int(hash_seed)): | |
| self.write("on (PYTHONHASHSEED=%s)\n" % hash_seed) | |
| else: | |
| self.write("off\n") | |
| if self._split: | |
| self.write("split: %s\n" % self._split) | |
| self.write('\n') | |
| self._t_start = clock() | |
| def finish(self): | |
| self._t_end = clock() | |
| self.write("\n") | |
| global text, linelen | |
| text = "tests finished: %d passed, " % self._passed | |
| linelen = len(text) | |
| def add_text(mytext): | |
| global text, linelen | |
| """Break new text if too long.""" | |
| if linelen + len(mytext) > self.terminal_width: | |
| text += '\n' | |
| linelen = 0 | |
| text += mytext | |
| linelen += len(mytext) | |
| if len(self._failed) > 0: | |
| add_text("%d failed, " % len(self._failed)) | |
| if len(self._failed_doctest) > 0: | |
| add_text("%d failed, " % len(self._failed_doctest)) | |
| if self._skipped > 0: | |
| add_text("%d skipped, " % self._skipped) | |
| if self._xfailed > 0: | |
| add_text("%d expected to fail, " % self._xfailed) | |
| if len(self._xpassed) > 0: | |
| add_text("%d expected to fail but passed, " % len(self._xpassed)) | |
| if len(self._exceptions) > 0: | |
| add_text("%d exceptions, " % len(self._exceptions)) | |
| add_text("in %.2f seconds" % (self._t_end - self._t_start)) | |
| if self.slow_test_functions: | |
| self.write_center('slowest tests', '_') | |
| sorted_slow = sorted(self.slow_test_functions, key=lambda r: r[1]) | |
| for slow_func_name, taken in sorted_slow: | |
| print('%s - Took %.3f seconds' % (slow_func_name, taken)) | |
| if self.fast_test_functions: | |
| self.write_center('unexpectedly fast tests', '_') | |
| sorted_fast = sorted(self.fast_test_functions, | |
| key=lambda r: r[1]) | |
| for fast_func_name, taken in sorted_fast: | |
| print('%s - Took %.3f seconds' % (fast_func_name, taken)) | |
| if len(self._xpassed) > 0: | |
| self.write_center("xpassed tests", "_") | |
| for e in self._xpassed: | |
| self.write("%s: %s\n" % (e[0], e[1])) | |
| self.write("\n") | |
| if self._tb_style != "no" and len(self._exceptions) > 0: | |
| for e in self._exceptions: | |
| filename, f, (t, val, tb) = e | |
| self.write_center("", "_") | |
| if f is None: | |
| s = "%s" % filename | |
| else: | |
| s = "%s:%s" % (filename, f.__name__) | |
| self.write_center(s, "_") | |
| self.write_exception(t, val, tb) | |
| self.write("\n") | |
| if self._tb_style != "no" and len(self._failed) > 0: | |
| for e in self._failed: | |
| filename, f, (t, val, tb) = e | |
| self.write_center("", "_") | |
| self.write_center("%s::%s" % (filename, f.__name__), "_") | |
| self.write_exception(t, val, tb) | |
| self.write("\n") | |
| if self._tb_style != "no" and len(self._failed_doctest) > 0: | |
| for e in self._failed_doctest: | |
| filename, msg = e | |
| self.write_center("", "_") | |
| self.write_center("%s" % filename, "_") | |
| self.write(msg) | |
| self.write("\n") | |
| self.write_center(text) | |
| ok = len(self._failed) == 0 and len(self._exceptions) == 0 and \ | |
| len(self._failed_doctest) == 0 | |
| if not ok: | |
| self.write("DO *NOT* COMMIT!\n") | |
| return ok | |
| def entering_filename(self, filename, n): | |
| rel_name = filename[len(self._root_dir) + 1:] | |
| self._active_file = rel_name | |
| self._active_file_error = False | |
| self.write(rel_name) | |
| self.write("[%d] " % n) | |
| def leaving_filename(self): | |
| self.write(" ") | |
| if self._active_file_error: | |
| self.write("[FAIL]", "Red", align="right") | |
| else: | |
| self.write("[OK]", "Green", align="right") | |
| self.write("\n") | |
| if self._verbose: | |
| self.write("\n") | |
| def entering_test(self, f): | |
| self._active_f = f | |
| if self._verbose: | |
| self.write("\n" + f.__name__ + " ") | |
| def test_xfail(self): | |
| self._xfailed += 1 | |
| self.write("f", "Green") | |
| def test_xpass(self, v): | |
| message = str(v) | |
| self._xpassed.append((self._active_file, message)) | |
| self.write("X", "Green") | |
| def test_fail(self, exc_info): | |
| self._failed.append((self._active_file, self._active_f, exc_info)) | |
| self.write("F", "Red") | |
| self._active_file_error = True | |
| def doctest_fail(self, name, error_msg): | |
| # the first line contains "******", remove it: | |
| error_msg = "\n".join(error_msg.split("\n")[1:]) | |
| self._failed_doctest.append((name, error_msg)) | |
| self.write("F", "Red") | |
| self._active_file_error = True | |
| def test_pass(self, char="."): | |
| self._passed += 1 | |
| if self._verbose: | |
| self.write("ok", "Green") | |
| else: | |
| self.write(char, "Green") | |
| def test_skip(self, v=None): | |
| char = "s" | |
| self._skipped += 1 | |
| if v is not None: | |
| message = str(v) | |
| if message == "KeyboardInterrupt": | |
| char = "K" | |
| elif message == "Timeout": | |
| char = "T" | |
| elif message == "Slow": | |
| char = "w" | |
| if self._verbose: | |
| if v is not None: | |
| self.write(message + ' ', "Blue") | |
| else: | |
| self.write(" - ", "Blue") | |
| self.write(char, "Blue") | |
| def test_exception(self, exc_info): | |
| self._exceptions.append((self._active_file, self._active_f, exc_info)) | |
| if exc_info[0] is TimeOutError: | |
| self.write("T", "Red") | |
| else: | |
| self.write("E", "Red") | |
| self._active_file_error = True | |
| def import_error(self, filename, exc_info): | |
| self._exceptions.append((filename, None, exc_info)) | |
| rel_name = filename[len(self._root_dir) + 1:] | |
| self.write(rel_name) | |
| self.write("[?] Failed to import", "Red") | |
| self.write(" ") | |
| self.write("[FAIL]", "Red", align="right") | |
| self.write("\n") | |