123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- # coding=utf-8
- from os import walk, sep, pardir
- from os.path import split, join, abspath, exists, isfile
- from glob import glob
- import re
- import random
- import ast
- from sympy.testing.pytest import raises
- from sympy.testing.quality_unicode import _test_this_file_encoding
- # System path separator (usually slash or backslash) to be
- # used with excluded files, e.g.
- # exclude = set([
- # "%(sep)smpmath%(sep)s" % sepd,
- # ])
- sepd = {"sep": sep}
- # path and sympy_path
- SYMPY_PATH = abspath(join(split(__file__)[0], pardir, pardir)) # go to sympy/
- assert exists(SYMPY_PATH)
- TOP_PATH = abspath(join(SYMPY_PATH, pardir))
- BIN_PATH = join(TOP_PATH, "bin")
- EXAMPLES_PATH = join(TOP_PATH, "examples")
- # Error messages
- message_space = "File contains trailing whitespace: %s, line %s."
- message_implicit = "File contains an implicit import: %s, line %s."
- message_tabs = "File contains tabs instead of spaces: %s, line %s."
- message_carriage = "File contains carriage returns at end of line: %s, line %s"
- message_str_raise = "File contains string exception: %s, line %s"
- message_gen_raise = "File contains generic exception: %s, line %s"
- message_old_raise = "File contains old-style raise statement: %s, line %s, \"%s\""
- message_eof = "File does not end with a newline: %s, line %s"
- message_multi_eof = "File ends with more than 1 newline: %s, line %s"
- message_test_suite_def = "Function should start with 'test_' or '_': %s, line %s"
- message_duplicate_test = "This is a duplicate test function: %s, line %s"
- message_self_assignments = "File contains assignments to self/cls: %s, line %s."
- message_func_is = "File contains '.func is': %s, line %s."
- message_bare_expr = "File contains bare expression: %s, line %s."
- implicit_test_re = re.compile(r'^\s*(>>> )?(\.\.\. )?from .* import .*\*')
- str_raise_re = re.compile(
- r'^\s*(>>> )?(\.\.\. )?raise(\s+(\'|\")|\s*(\(\s*)+(\'|\"))')
- gen_raise_re = re.compile(
- r'^\s*(>>> )?(\.\.\. )?raise(\s+Exception|\s*(\(\s*)+Exception)')
- old_raise_re = re.compile(r'^\s*(>>> )?(\.\.\. )?raise((\s*\(\s*)|\s+)\w+\s*,')
- test_suite_def_re = re.compile(r'^def\s+(?!(_|test))[^(]*\(\s*\)\s*:$')
- test_ok_def_re = re.compile(r'^def\s+test_.*:$')
- test_file_re = re.compile(r'.*[/\\]test_.*\.py$')
- func_is_re = re.compile(r'\.\s*func\s+is')
- def tab_in_leading(s):
- """Returns True if there are tabs in the leading whitespace of a line,
- including the whitespace of docstring code samples."""
- n = len(s) - len(s.lstrip())
- if not s[n:n + 3] in ['...', '>>>']:
- check = s[:n]
- else:
- smore = s[n + 3:]
- check = s[:n] + smore[:len(smore) - len(smore.lstrip())]
- return not (check.expandtabs() == check)
- def find_self_assignments(s):
- """Returns a list of "bad" assignments: if there are instances
- of assigning to the first argument of the class method (except
- for staticmethod's).
- """
- t = [n for n in ast.parse(s).body if isinstance(n, ast.ClassDef)]
- bad = []
- for c in t:
- for n in c.body:
- if not isinstance(n, ast.FunctionDef):
- continue
- if any(d.id == 'staticmethod'
- for d in n.decorator_list if isinstance(d, ast.Name)):
- continue
- if n.name == '__new__':
- continue
- if not n.args.args:
- continue
- first_arg = n.args.args[0].arg
- for m in ast.walk(n):
- if isinstance(m, ast.Assign):
- for a in m.targets:
- if isinstance(a, ast.Name) and a.id == first_arg:
- bad.append(m)
- elif (isinstance(a, ast.Tuple) and
- any(q.id == first_arg for q in a.elts
- if isinstance(q, ast.Name))):
- bad.append(m)
- return bad
- def check_directory_tree(base_path, file_check, exclusions=set(), pattern="*.py"):
- """
- Checks all files in the directory tree (with base_path as starting point)
- with the file_check function provided, skipping files that contain
- any of the strings in the set provided by exclusions.
- """
- if not base_path:
- return
- for root, dirs, files in walk(base_path):
- check_files(glob(join(root, pattern)), file_check, exclusions)
- def check_files(files, file_check, exclusions=set(), pattern=None):
- """
- Checks all files with the file_check function provided, skipping files
- that contain any of the strings in the set provided by exclusions.
- """
- if not files:
- return
- for fname in files:
- if not exists(fname) or not isfile(fname):
- continue
- if any(ex in fname for ex in exclusions):
- continue
- if pattern is None or re.match(pattern, fname):
- file_check(fname)
- class _Visit(ast.NodeVisitor):
- """return the line number corresponding to the
- line on which a bare expression appears if it is a binary op
- or a comparison that is not in a with block.
- EXAMPLES
- ========
- >>> import ast
- >>> class _Visit(ast.NodeVisitor):
- ... def visit_Expr(self, node):
- ... if isinstance(node.value, (ast.BinOp, ast.Compare)):
- ... print(node.lineno)
- ... def visit_With(self, node):
- ... pass # no checking there
- ...
- >>> code='''x = 1 # line 1
- ... for i in range(3):
- ... x == 2 # <-- 3
- ... if x == 2:
- ... x == 3 # <-- 5
- ... x + 1 # <-- 6
- ... x = 1
- ... if x == 1:
- ... print(1)
- ... while x != 1:
- ... x == 1 # <-- 11
- ... with raises(TypeError):
- ... c == 1
- ... raise TypeError
- ... assert x == 1
- ... '''
- >>> _Visit().visit(ast.parse(code))
- 3
- 5
- 6
- 11
- """
- def visit_Expr(self, node):
- if isinstance(node.value, (ast.BinOp, ast.Compare)):
- assert None, message_bare_expr % ('', node.lineno)
- def visit_With(self, node):
- pass
- BareExpr = _Visit()
- def line_with_bare_expr(code):
- """return None or else 0-based line number of code on which
- a bare expression appeared.
- """
- tree = ast.parse(code)
- try:
- BareExpr.visit(tree)
- except AssertionError as msg:
- assert msg.args
- msg = msg.args[0]
- assert msg.startswith(message_bare_expr.split(':', 1)[0])
- return int(msg.rsplit(' ', 1)[1].rstrip('.')) # the line number
- def test_files():
- """
- This test tests all files in SymPy and checks that:
- o no lines contains a trailing whitespace
- o no lines end with \r\n
- o no line uses tabs instead of spaces
- o that the file ends with a single newline
- o there are no general or string exceptions
- o there are no old style raise statements
- o name of arg-less test suite functions start with _ or test_
- o no duplicate function names that start with test_
- o no assignments to self variable in class methods
- o no lines contain ".func is" except in the test suite
- o there is no do-nothing expression like `a == b` or `x + 1`
- """
- def test(fname):
- with open(fname, encoding="utf8") as test_file:
- test_this_file(fname, test_file)
- with open(fname, encoding='utf8') as test_file:
- _test_this_file_encoding(fname, test_file)
- def test_this_file(fname, test_file):
- idx = None
- code = test_file.read()
- test_file.seek(0) # restore reader to head
- py = fname if sep not in fname else fname.rsplit(sep, 1)[-1]
- if py.startswith('test_'):
- idx = line_with_bare_expr(code)
- if idx is not None:
- assert False, message_bare_expr % (fname, idx + 1)
- line = None # to flag the case where there were no lines in file
- tests = 0
- test_set = set()
- for idx, line in enumerate(test_file):
- if test_file_re.match(fname):
- if test_suite_def_re.match(line):
- assert False, message_test_suite_def % (fname, idx + 1)
- if test_ok_def_re.match(line):
- tests += 1
- test_set.add(line[3:].split('(')[0].strip())
- if len(test_set) != tests:
- assert False, message_duplicate_test % (fname, idx + 1)
- if line.endswith(" \n") or line.endswith("\t\n"):
- assert False, message_space % (fname, idx + 1)
- if line.endswith("\r\n"):
- assert False, message_carriage % (fname, idx + 1)
- if tab_in_leading(line):
- assert False, message_tabs % (fname, idx + 1)
- if str_raise_re.search(line):
- assert False, message_str_raise % (fname, idx + 1)
- if gen_raise_re.search(line):
- assert False, message_gen_raise % (fname, idx + 1)
- if (implicit_test_re.search(line) and
- not list(filter(lambda ex: ex in fname, import_exclude))):
- assert False, message_implicit % (fname, idx + 1)
- if func_is_re.search(line) and not test_file_re.search(fname):
- assert False, message_func_is % (fname, idx + 1)
- result = old_raise_re.search(line)
- if result is not None:
- assert False, message_old_raise % (
- fname, idx + 1, result.group(2))
- if line is not None:
- if line == '\n' and idx > 0:
- assert False, message_multi_eof % (fname, idx + 1)
- elif not line.endswith('\n'):
- # eof newline check
- assert False, message_eof % (fname, idx + 1)
- # Files to test at top level
- top_level_files = [join(TOP_PATH, file) for file in [
- "isympy.py",
- "build.py",
- "setup.py",
- ]]
- # Files to exclude from all tests
- exclude = {
- "%(sep)ssympy%(sep)sparsing%(sep)sautolev%(sep)s_antlr%(sep)sautolevparser.py" % sepd,
- "%(sep)ssympy%(sep)sparsing%(sep)sautolev%(sep)s_antlr%(sep)sautolevlexer.py" % sepd,
- "%(sep)ssympy%(sep)sparsing%(sep)sautolev%(sep)s_antlr%(sep)sautolevlistener.py" % sepd,
- "%(sep)ssympy%(sep)sparsing%(sep)slatex%(sep)s_antlr%(sep)slatexparser.py" % sepd,
- "%(sep)ssympy%(sep)sparsing%(sep)slatex%(sep)s_antlr%(sep)slatexlexer.py" % sepd,
- }
- # Files to exclude from the implicit import test
- import_exclude = {
- # glob imports are allowed in top-level __init__.py:
- "%(sep)ssympy%(sep)s__init__.py" % sepd,
- # these __init__.py should be fixed:
- # XXX: not really, they use useful import pattern (DRY)
- "%(sep)svector%(sep)s__init__.py" % sepd,
- "%(sep)smechanics%(sep)s__init__.py" % sepd,
- "%(sep)squantum%(sep)s__init__.py" % sepd,
- "%(sep)spolys%(sep)s__init__.py" % sepd,
- "%(sep)spolys%(sep)sdomains%(sep)s__init__.py" % sepd,
- # interactive SymPy executes ``from sympy import *``:
- "%(sep)sinteractive%(sep)ssession.py" % sepd,
- # isympy.py executes ``from sympy import *``:
- "%(sep)sisympy.py" % sepd,
- # these two are import timing tests:
- "%(sep)sbin%(sep)ssympy_time.py" % sepd,
- "%(sep)sbin%(sep)ssympy_time_cache.py" % sepd,
- # Taken from Python stdlib:
- "%(sep)sparsing%(sep)ssympy_tokenize.py" % sepd,
- # this one should be fixed:
- "%(sep)splotting%(sep)spygletplot%(sep)s" % sepd,
- # False positive in the docstring
- "%(sep)sbin%(sep)stest_external_imports.py" % sepd,
- "%(sep)sbin%(sep)stest_submodule_imports.py" % sepd,
- # These are deprecated stubs that can be removed at some point:
- "%(sep)sutilities%(sep)sruntests.py" % sepd,
- "%(sep)sutilities%(sep)spytest.py" % sepd,
- "%(sep)sutilities%(sep)srandtest.py" % sepd,
- "%(sep)sutilities%(sep)stmpfiles.py" % sepd,
- "%(sep)sutilities%(sep)squality_unicode.py" % sepd,
- }
- check_files(top_level_files, test)
- check_directory_tree(BIN_PATH, test, {"~", ".pyc", ".sh", ".mjs"}, "*")
- check_directory_tree(SYMPY_PATH, test, exclude)
- check_directory_tree(EXAMPLES_PATH, test, exclude)
- def _with_space(c):
- # return c with a random amount of leading space
- return random.randint(0, 10)*' ' + c
- def test_raise_statement_regular_expression():
- candidates_ok = [
- "some text # raise Exception, 'text'",
- "raise ValueError('text') # raise Exception, 'text'",
- "raise ValueError('text')",
- "raise ValueError",
- "raise ValueError('text')",
- "raise ValueError('text') #,",
- # Talking about an exception in a docstring
- ''''"""This function will raise ValueError, except when it doesn't"""''',
- "raise (ValueError('text')",
- ]
- str_candidates_fail = [
- "raise 'exception'",
- "raise 'Exception'",
- 'raise "exception"',
- 'raise "Exception"',
- "raise 'ValueError'",
- ]
- gen_candidates_fail = [
- "raise Exception('text') # raise Exception, 'text'",
- "raise Exception('text')",
- "raise Exception",
- "raise Exception('text')",
- "raise Exception('text') #,",
- "raise Exception, 'text'",
- "raise Exception, 'text' # raise Exception('text')",
- "raise Exception, 'text' # raise Exception, 'text'",
- ">>> raise Exception, 'text'",
- ">>> raise Exception, 'text' # raise Exception('text')",
- ">>> raise Exception, 'text' # raise Exception, 'text'",
- ]
- old_candidates_fail = [
- "raise Exception, 'text'",
- "raise Exception, 'text' # raise Exception('text')",
- "raise Exception, 'text' # raise Exception, 'text'",
- ">>> raise Exception, 'text'",
- ">>> raise Exception, 'text' # raise Exception('text')",
- ">>> raise Exception, 'text' # raise Exception, 'text'",
- "raise ValueError, 'text'",
- "raise ValueError, 'text' # raise Exception('text')",
- "raise ValueError, 'text' # raise Exception, 'text'",
- ">>> raise ValueError, 'text'",
- ">>> raise ValueError, 'text' # raise Exception('text')",
- ">>> raise ValueError, 'text' # raise Exception, 'text'",
- "raise(ValueError,",
- "raise (ValueError,",
- "raise( ValueError,",
- "raise ( ValueError,",
- "raise(ValueError ,",
- "raise (ValueError ,",
- "raise( ValueError ,",
- "raise ( ValueError ,",
- ]
- for c in candidates_ok:
- assert str_raise_re.search(_with_space(c)) is None, c
- assert gen_raise_re.search(_with_space(c)) is None, c
- assert old_raise_re.search(_with_space(c)) is None, c
- for c in str_candidates_fail:
- assert str_raise_re.search(_with_space(c)) is not None, c
- for c in gen_candidates_fail:
- assert gen_raise_re.search(_with_space(c)) is not None, c
- for c in old_candidates_fail:
- assert old_raise_re.search(_with_space(c)) is not None, c
- def test_implicit_imports_regular_expression():
- candidates_ok = [
- "from sympy import something",
- ">>> from sympy import something",
- "from sympy.somewhere import something",
- ">>> from sympy.somewhere import something",
- "import sympy",
- ">>> import sympy",
- "import sympy.something.something",
- "... import sympy",
- "... import sympy.something.something",
- "... from sympy import something",
- "... from sympy.somewhere import something",
- ">> from sympy import *", # To allow 'fake' docstrings
- "# from sympy import *",
- "some text # from sympy import *",
- ]
- candidates_fail = [
- "from sympy import *",
- ">>> from sympy import *",
- "from sympy.somewhere import *",
- ">>> from sympy.somewhere import *",
- "... from sympy import *",
- "... from sympy.somewhere import *",
- ]
- for c in candidates_ok:
- assert implicit_test_re.search(_with_space(c)) is None, c
- for c in candidates_fail:
- assert implicit_test_re.search(_with_space(c)) is not None, c
- def test_test_suite_defs():
- candidates_ok = [
- " def foo():\n",
- "def foo(arg):\n",
- "def _foo():\n",
- "def test_foo():\n",
- ]
- candidates_fail = [
- "def foo():\n",
- "def foo() :\n",
- "def foo( ):\n",
- "def foo():\n",
- ]
- for c in candidates_ok:
- assert test_suite_def_re.search(c) is None, c
- for c in candidates_fail:
- assert test_suite_def_re.search(c) is not None, c
- def test_test_duplicate_defs():
- candidates_ok = [
- "def foo():\ndef foo():\n",
- "def test():\ndef test_():\n",
- "def test_():\ndef test__():\n",
- ]
- candidates_fail = [
- "def test_():\ndef test_ ():\n",
- "def test_1():\ndef test_1():\n",
- ]
- ok = (None, 'check')
- def check(file):
- tests = 0
- test_set = set()
- for idx, line in enumerate(file.splitlines()):
- if test_ok_def_re.match(line):
- tests += 1
- test_set.add(line[3:].split('(')[0].strip())
- if len(test_set) != tests:
- return False, message_duplicate_test % ('check', idx + 1)
- return None, 'check'
- for c in candidates_ok:
- assert check(c) == ok
- for c in candidates_fail:
- assert check(c) != ok
- def test_find_self_assignments():
- candidates_ok = [
- "class A(object):\n def foo(self, arg): arg = self\n",
- "class A(object):\n def foo(self, arg): self.prop = arg\n",
- "class A(object):\n def foo(self, arg): obj, obj2 = arg, self\n",
- "class A(object):\n @classmethod\n def bar(cls, arg): arg = cls\n",
- "class A(object):\n def foo(var, arg): arg = var\n",
- ]
- candidates_fail = [
- "class A(object):\n def foo(self, arg): self = arg\n",
- "class A(object):\n def foo(self, arg): obj, self = arg, arg\n",
- "class A(object):\n def foo(self, arg):\n if arg: self = arg",
- "class A(object):\n @classmethod\n def foo(cls, arg): cls = arg\n",
- "class A(object):\n def foo(var, arg): var = arg\n",
- ]
- for c in candidates_ok:
- assert find_self_assignments(c) == []
- for c in candidates_fail:
- assert find_self_assignments(c) != []
- def test_test_unicode_encoding():
- unicode_whitelist = ['foo']
- unicode_strict_whitelist = ['bar']
- fname = 'abc'
- test_file = ['α']
- raises(AssertionError, lambda: _test_this_file_encoding(
- fname, test_file, unicode_whitelist, unicode_strict_whitelist))
- fname = 'abc'
- test_file = ['abc']
- _test_this_file_encoding(
- fname, test_file, unicode_whitelist, unicode_strict_whitelist)
- fname = 'foo'
- test_file = ['abc']
- raises(AssertionError, lambda: _test_this_file_encoding(
- fname, test_file, unicode_whitelist, unicode_strict_whitelist))
- fname = 'bar'
- test_file = ['abc']
- _test_this_file_encoding(
- fname, test_file, unicode_whitelist, unicode_strict_whitelist)
|