123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361 |
- """
- 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
- """
- 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, HAS_GMPY
- IS_WINDOWS = (os.name == 'nt')
- ON_TRAVIS = os.getenv('TRAVIS_BUILD_NUMBER', None)
- # emperically 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["TRAVIS_BUILD_NUMBER"] = '2' # Mock travis 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():
- from sympy.interactive.printing import init_printing
- from sympy.printing.pretty.pretty import pprint_use_unicode
- import sympy.interactive.printing as interactive_printing
- # force pprint to be in ascii mode in doctests
- use_unicode_prev = pprint_use_unicode(False)
- # hook our nice, hash-stable strprinter
- init_printing(pretty_print=False)
- # Prevent init_printing() in doctests from affecting other doctests
- interactive_printing.NO_GLOBAL = True
- return use_unicode_prev
- @contextmanager
- 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 doesn't 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
- 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
- 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=('sympy/integrals/rubi',),
- 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_TRAVIS and timeout is False:
- # Travis times out if no activity is seen for 10 minutes.
- timeout = 595
- fail_on_timeout = True
- if ON_TRAVIS:
- # pyglet does not work on Travis
- 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)
- 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.
- """
- # 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/integrals/rubi/rubi.py",
- "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"
- ])
- 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 Travis
- import matplotlib
- matplotlib.use('Agg')
- if ON_TRAVIS 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('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",
- ])
- # 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", # Depends on pytest
- "sympy/testing/benchmarking.py",
- ])
- # These are deprecated stubs to be removed:
- blacklist.extend([
- "sympy/utilities/benchmarking.py",
- "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
- 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 Travis
- 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
- 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/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 = t.get_test_files('doc/src', '*.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)
- first_report = True
- for rst_file in matched:
- if not os.path.isfile(rst_file):
- continue
- old_displayhook = sys.displayhook
- try:
- use_unicode_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)
- 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, 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.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", 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__.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([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
- 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(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
- old_displayhook = sys.displayhook
- use_unicode_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)
- 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)):
- """
- 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 '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 doesn't 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:
- try:
- if obj.__doc__ is None:
- docstring = ''
- else:
- docstring = obj.__doc__
- if not isinstance(docstring, str):
- 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:
- 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
- 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
- @property
- 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 = process.stdout.read()
- 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':
- if HAS_GMPY == 1:
- import gmpy
- elif HAS_GMPY == 2:
- 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")
|