add read me
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Import diagnostics. Run bin/diagnose_imports.py --help for details.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
import sys
|
||||
import inspect
|
||||
import builtins
|
||||
|
||||
import optparse
|
||||
|
||||
from os.path import abspath, dirname, join, normpath
|
||||
this_file = abspath(__file__)
|
||||
sympy_dir = join(dirname(this_file), '..', '..', '..')
|
||||
sympy_dir = normpath(sympy_dir)
|
||||
sys.path.insert(0, sympy_dir)
|
||||
|
||||
option_parser = optparse.OptionParser(
|
||||
usage=
|
||||
"Usage: %prog option [options]\n"
|
||||
"\n"
|
||||
"Import analysis for imports between SymPy modules.")
|
||||
option_group = optparse.OptionGroup(
|
||||
option_parser,
|
||||
'Analysis options',
|
||||
'Options that define what to do. Exactly one of these must be given.')
|
||||
option_group.add_option(
|
||||
'--problems',
|
||||
help=
|
||||
'Print all import problems, that is: '
|
||||
'If an import pulls in a package instead of a module '
|
||||
'(e.g. sympy.core instead of sympy.core.add); ' # see ##PACKAGE##
|
||||
'if it imports a symbol that is already present; ' # see ##DUPLICATE##
|
||||
'if it imports a symbol '
|
||||
'from somewhere other than the defining module.', # see ##ORIGIN##
|
||||
action='count')
|
||||
option_group.add_option(
|
||||
'--origins',
|
||||
help=
|
||||
'For each imported symbol in each module, '
|
||||
'print the module that defined it. '
|
||||
'(This is useful for import refactoring.)',
|
||||
action='count')
|
||||
option_parser.add_option_group(option_group)
|
||||
option_group = optparse.OptionGroup(
|
||||
option_parser,
|
||||
'Sort options',
|
||||
'These options define the sort order for output lines. '
|
||||
'At most one of these options is allowed. '
|
||||
'Unsorted output will reflect the order in which imports happened.')
|
||||
option_group.add_option(
|
||||
'--by-importer',
|
||||
help='Sort output lines by name of importing module.',
|
||||
action='count')
|
||||
option_group.add_option(
|
||||
'--by-origin',
|
||||
help='Sort output lines by name of imported module.',
|
||||
action='count')
|
||||
option_parser.add_option_group(option_group)
|
||||
(options, args) = option_parser.parse_args()
|
||||
if args:
|
||||
option_parser.error(
|
||||
'Unexpected arguments %s (try %s --help)' % (args, sys.argv[0]))
|
||||
if options.problems > 1:
|
||||
option_parser.error('--problems must not be given more than once.')
|
||||
if options.origins > 1:
|
||||
option_parser.error('--origins must not be given more than once.')
|
||||
if options.by_importer > 1:
|
||||
option_parser.error('--by-importer must not be given more than once.')
|
||||
if options.by_origin > 1:
|
||||
option_parser.error('--by-origin must not be given more than once.')
|
||||
options.problems = options.problems == 1
|
||||
options.origins = options.origins == 1
|
||||
options.by_importer = options.by_importer == 1
|
||||
options.by_origin = options.by_origin == 1
|
||||
if not options.problems and not options.origins:
|
||||
option_parser.error(
|
||||
'At least one of --problems and --origins is required')
|
||||
if options.problems and options.origins:
|
||||
option_parser.error(
|
||||
'At most one of --problems and --origins is allowed')
|
||||
if options.by_importer and options.by_origin:
|
||||
option_parser.error(
|
||||
'At most one of --by-importer and --by-origin is allowed')
|
||||
options.by_process = not options.by_importer and not options.by_origin
|
||||
|
||||
builtin_import = builtins.__import__
|
||||
|
||||
class Definition:
|
||||
"""Information about a symbol's definition."""
|
||||
def __init__(self, name, value, definer):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.definer = definer
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name and self.value == other.value
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
def __repr__(self):
|
||||
return 'Definition(%s, ..., %s)' % (
|
||||
repr(self.name), repr(self.definer))
|
||||
|
||||
# Maps each function/variable to name of module to define it
|
||||
symbol_definers: dict[Definition, str] = {}
|
||||
|
||||
def in_module(a, b):
|
||||
"""Is a the same module as or a submodule of b?"""
|
||||
return a == b or a != None and b != None and a.startswith(b + '.')
|
||||
|
||||
def relevant(module):
|
||||
"""Is module relevant for import checking?
|
||||
|
||||
Only imports between relevant modules will be checked."""
|
||||
return in_module(module, 'sympy')
|
||||
|
||||
sorted_messages = []
|
||||
|
||||
def msg(msg, *args):
|
||||
if options.by_process:
|
||||
print(msg % args)
|
||||
else:
|
||||
sorted_messages.append(msg % args)
|
||||
|
||||
def tracking_import(module, globals=globals(), locals=[], fromlist=None, level=-1):
|
||||
"""__import__ wrapper - does not change imports at all, but tracks them.
|
||||
|
||||
Default order is implemented by doing output directly.
|
||||
All other orders are implemented by collecting output information into
|
||||
a sorted list that will be emitted after all imports are processed.
|
||||
|
||||
Indirect imports can only occur after the requested symbol has been
|
||||
imported directly (because the indirect import would not have a module
|
||||
to pick the symbol up from).
|
||||
So this code detects indirect imports by checking whether the symbol in
|
||||
question was already imported.
|
||||
|
||||
Keeps the semantics of __import__ unchanged."""
|
||||
caller_frame = inspect.getframeinfo(sys._getframe(1))
|
||||
importer_filename = caller_frame.filename
|
||||
importer_module = globals['__name__']
|
||||
if importer_filename == caller_frame.filename:
|
||||
importer_reference = '%s line %s' % (
|
||||
importer_filename, str(caller_frame.lineno))
|
||||
else:
|
||||
importer_reference = importer_filename
|
||||
result = builtin_import(module, globals, locals, fromlist, level)
|
||||
importee_module = result.__name__
|
||||
# We're only interested if importer and importee are in SymPy
|
||||
if relevant(importer_module) and relevant(importee_module):
|
||||
for symbol in result.__dict__.iterkeys():
|
||||
definition = Definition(
|
||||
symbol, result.__dict__[symbol], importer_module)
|
||||
if definition not in symbol_definers:
|
||||
symbol_definers[definition] = importee_module
|
||||
if hasattr(result, '__path__'):
|
||||
##PACKAGE##
|
||||
# The existence of __path__ is documented in the tutorial on modules.
|
||||
# Python 3.3 documents this in http://docs.python.org/3.3/reference/import.html
|
||||
if options.by_origin:
|
||||
msg('Error: %s (a package) is imported by %s',
|
||||
module, importer_reference)
|
||||
else:
|
||||
msg('Error: %s contains package import %s',
|
||||
importer_reference, module)
|
||||
if fromlist != None:
|
||||
symbol_list = fromlist
|
||||
if '*' in symbol_list:
|
||||
if (importer_filename.endswith(("__init__.py", "__init__.pyc", "__init__.pyo"))):
|
||||
# We do not check starred imports inside __init__
|
||||
# That's the normal "please copy over its imports to my namespace"
|
||||
symbol_list = []
|
||||
else:
|
||||
symbol_list = result.__dict__.iterkeys()
|
||||
for symbol in symbol_list:
|
||||
if symbol not in result.__dict__:
|
||||
if options.by_origin:
|
||||
msg('Error: %s.%s is not defined (yet), but %s tries to import it',
|
||||
importee_module, symbol, importer_reference)
|
||||
else:
|
||||
msg('Error: %s tries to import %s.%s, which did not define it (yet)',
|
||||
importer_reference, importee_module, symbol)
|
||||
else:
|
||||
definition = Definition(
|
||||
symbol, result.__dict__[symbol], importer_module)
|
||||
symbol_definer = symbol_definers[definition]
|
||||
if symbol_definer == importee_module:
|
||||
##DUPLICATE##
|
||||
if options.by_origin:
|
||||
msg('Error: %s.%s is imported again into %s',
|
||||
importee_module, symbol, importer_reference)
|
||||
else:
|
||||
msg('Error: %s imports %s.%s again',
|
||||
importer_reference, importee_module, symbol)
|
||||
else:
|
||||
##ORIGIN##
|
||||
if options.by_origin:
|
||||
msg('Error: %s.%s is imported by %s, which should import %s.%s instead',
|
||||
importee_module, symbol, importer_reference, symbol_definer, symbol)
|
||||
else:
|
||||
msg('Error: %s imports %s.%s but should import %s.%s instead',
|
||||
importer_reference, importee_module, symbol, symbol_definer, symbol)
|
||||
return result
|
||||
|
||||
builtins.__import__ = tracking_import
|
||||
__import__('sympy')
|
||||
|
||||
sorted_messages.sort()
|
||||
for message in sorted_messages:
|
||||
print(message)
|
||||
@@ -0,0 +1,510 @@
|
||||
# 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", "\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"}, "*")
|
||||
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)
|
||||
@@ -0,0 +1,5 @@
|
||||
from sympy.testing.pytest import warns_deprecated_sympy
|
||||
|
||||
def test_deprecated_testing_randtest():
|
||||
with warns_deprecated_sympy():
|
||||
import sympy.testing.randtest # noqa:F401
|
||||
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Checks that SymPy does not contain indirect imports.
|
||||
|
||||
An indirect import is importing a symbol from a module that itself imported the
|
||||
symbol from elsewhere. Such a constellation makes it harder to diagnose
|
||||
inter-module dependencies and import order problems, and is therefore strongly
|
||||
discouraged.
|
||||
|
||||
(Indirect imports from end-user code is fine and in fact a best practice.)
|
||||
|
||||
Implementation note: Forcing Python into actually unloading already-imported
|
||||
submodules is a tricky and partly undocumented process. To avoid these issues,
|
||||
the actual diagnostic code is in bin/diagnose_imports, which is run as a
|
||||
separate, pristine Python process.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from os.path import abspath, dirname, join, normpath
|
||||
import inspect
|
||||
|
||||
from sympy.testing.pytest import XFAIL
|
||||
|
||||
@XFAIL
|
||||
def test_module_imports_are_direct():
|
||||
my_filename = abspath(inspect.getfile(inspect.currentframe()))
|
||||
my_dirname = dirname(my_filename)
|
||||
diagnose_imports_filename = join(my_dirname, 'diagnose_imports.py')
|
||||
diagnose_imports_filename = normpath(diagnose_imports_filename)
|
||||
|
||||
process = subprocess.Popen(
|
||||
[
|
||||
sys.executable,
|
||||
normpath(diagnose_imports_filename),
|
||||
'--problems',
|
||||
'--by-importer'
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
bufsize=-1)
|
||||
output, _ = process.communicate()
|
||||
assert output == '', "There are import problems:\n" + output.decode()
|
||||
@@ -0,0 +1,211 @@
|
||||
import warnings
|
||||
|
||||
from sympy.testing.pytest import (raises, warns, ignore_warnings,
|
||||
warns_deprecated_sympy, Failed)
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
|
||||
|
||||
|
||||
# Test callables
|
||||
|
||||
|
||||
def test_expected_exception_is_silent_callable():
|
||||
def f():
|
||||
raise ValueError()
|
||||
raises(ValueError, f)
|
||||
|
||||
|
||||
# Under pytest raises will raise Failed rather than AssertionError
|
||||
def test_lack_of_exception_triggers_AssertionError_callable():
|
||||
try:
|
||||
raises(Exception, lambda: 1 + 1)
|
||||
assert False
|
||||
except Failed as e:
|
||||
assert "DID NOT RAISE" in str(e)
|
||||
|
||||
|
||||
def test_unexpected_exception_is_passed_through_callable():
|
||||
def f():
|
||||
raise ValueError("some error message")
|
||||
try:
|
||||
raises(TypeError, f)
|
||||
assert False
|
||||
except ValueError as e:
|
||||
assert str(e) == "some error message"
|
||||
|
||||
# Test with statement
|
||||
|
||||
def test_expected_exception_is_silent_with():
|
||||
with raises(ValueError):
|
||||
raise ValueError()
|
||||
|
||||
|
||||
def test_lack_of_exception_triggers_AssertionError_with():
|
||||
try:
|
||||
with raises(Exception):
|
||||
1 + 1
|
||||
assert False
|
||||
except Failed as e:
|
||||
assert "DID NOT RAISE" in str(e)
|
||||
|
||||
|
||||
def test_unexpected_exception_is_passed_through_with():
|
||||
try:
|
||||
with raises(TypeError):
|
||||
raise ValueError("some error message")
|
||||
assert False
|
||||
except ValueError as e:
|
||||
assert str(e) == "some error message"
|
||||
|
||||
# Now we can use raises() instead of try/catch
|
||||
# to test that a specific exception class is raised
|
||||
|
||||
|
||||
def test_second_argument_should_be_callable_or_string():
|
||||
raises(TypeError, lambda: raises("irrelevant", 42))
|
||||
|
||||
|
||||
def test_warns_catches_warning():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
with warns(UserWarning):
|
||||
warnings.warn('this is the warning message')
|
||||
assert len(w) == 0
|
||||
|
||||
|
||||
def test_warns_raises_without_warning():
|
||||
with raises(Failed):
|
||||
with warns(UserWarning):
|
||||
pass
|
||||
|
||||
|
||||
def test_warns_hides_other_warnings():
|
||||
with raises(RuntimeWarning):
|
||||
with warns(UserWarning):
|
||||
warnings.warn('this is the warning message', UserWarning)
|
||||
warnings.warn('this is the other message', RuntimeWarning)
|
||||
|
||||
|
||||
def test_warns_continues_after_warning():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
finished = False
|
||||
with warns(UserWarning):
|
||||
warnings.warn('this is the warning message')
|
||||
finished = True
|
||||
assert finished
|
||||
assert len(w) == 0
|
||||
|
||||
|
||||
def test_warns_many_warnings():
|
||||
with warns(UserWarning):
|
||||
warnings.warn('this is the warning message', UserWarning)
|
||||
warnings.warn('this is the other warning message', UserWarning)
|
||||
|
||||
|
||||
def test_warns_match_matching():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
with warns(UserWarning, match='this is the warning message'):
|
||||
warnings.warn('this is the warning message', UserWarning)
|
||||
assert len(w) == 0
|
||||
|
||||
|
||||
def test_warns_match_non_matching():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
with raises(Failed):
|
||||
with warns(UserWarning, match='this is the warning message'):
|
||||
warnings.warn('this is not the expected warning message', UserWarning)
|
||||
assert len(w) == 0
|
||||
|
||||
def _warn_sympy_deprecation(stacklevel=3):
|
||||
sympy_deprecation_warning(
|
||||
"feature",
|
||||
active_deprecations_target="active-deprecations",
|
||||
deprecated_since_version="0.0.0",
|
||||
stacklevel=stacklevel,
|
||||
)
|
||||
|
||||
def test_warns_deprecated_sympy_catches_warning():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
with warns_deprecated_sympy():
|
||||
_warn_sympy_deprecation()
|
||||
assert len(w) == 0
|
||||
|
||||
|
||||
def test_warns_deprecated_sympy_raises_without_warning():
|
||||
with raises(Failed):
|
||||
with warns_deprecated_sympy():
|
||||
pass
|
||||
|
||||
def test_warns_deprecated_sympy_wrong_stacklevel():
|
||||
with raises(Failed):
|
||||
with warns_deprecated_sympy():
|
||||
_warn_sympy_deprecation(stacklevel=1)
|
||||
|
||||
def test_warns_deprecated_sympy_doesnt_hide_other_warnings():
|
||||
# Unlike pytest's deprecated_call, we should not hide other warnings.
|
||||
with raises(RuntimeWarning):
|
||||
with warns_deprecated_sympy():
|
||||
_warn_sympy_deprecation()
|
||||
warnings.warn('this is the other message', RuntimeWarning)
|
||||
|
||||
|
||||
def test_warns_deprecated_sympy_continues_after_warning():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
finished = False
|
||||
with warns_deprecated_sympy():
|
||||
_warn_sympy_deprecation()
|
||||
finished = True
|
||||
assert finished
|
||||
assert len(w) == 0
|
||||
|
||||
def test_ignore_ignores_warning():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
with ignore_warnings(UserWarning):
|
||||
warnings.warn('this is the warning message')
|
||||
assert len(w) == 0
|
||||
|
||||
|
||||
def test_ignore_does_not_raise_without_warning():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
with ignore_warnings(UserWarning):
|
||||
pass
|
||||
assert len(w) == 0
|
||||
|
||||
|
||||
def test_ignore_allows_other_warnings():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
# This is needed when pytest is run as -Werror
|
||||
# the setting is reverted at the end of the catch_Warnings block.
|
||||
warnings.simplefilter("always")
|
||||
with ignore_warnings(UserWarning):
|
||||
warnings.warn('this is the warning message', UserWarning)
|
||||
warnings.warn('this is the other message', RuntimeWarning)
|
||||
assert len(w) == 1
|
||||
assert isinstance(w[0].message, RuntimeWarning)
|
||||
assert str(w[0].message) == 'this is the other message'
|
||||
|
||||
|
||||
def test_ignore_continues_after_warning():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
finished = False
|
||||
with ignore_warnings(UserWarning):
|
||||
warnings.warn('this is the warning message')
|
||||
finished = True
|
||||
assert finished
|
||||
assert len(w) == 0
|
||||
|
||||
|
||||
def test_ignore_many_warnings():
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
# This is needed when pytest is run as -Werror
|
||||
# the setting is reverted at the end of the catch_Warnings block.
|
||||
warnings.simplefilter("always")
|
||||
with ignore_warnings(UserWarning):
|
||||
warnings.warn('this is the warning message', UserWarning)
|
||||
warnings.warn('this is the other message', RuntimeWarning)
|
||||
warnings.warn('this is the warning message', UserWarning)
|
||||
warnings.warn('this is the other message', RuntimeWarning)
|
||||
warnings.warn('this is the other message', RuntimeWarning)
|
||||
assert len(w) == 3
|
||||
for wi in w:
|
||||
assert isinstance(wi.message, RuntimeWarning)
|
||||
assert str(wi.message) == 'this is the other message'
|
||||
@@ -0,0 +1,171 @@
|
||||
import pathlib
|
||||
from typing import List
|
||||
|
||||
import pytest
|
||||
|
||||
from sympy.testing.runtests_pytest import (
|
||||
make_absolute_path,
|
||||
sympy_dir,
|
||||
update_args_with_paths,
|
||||
)
|
||||
|
||||
|
||||
class TestMakeAbsolutePath:
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'partial_path', ['sympy', 'sympy/core', 'sympy/nonexistant_directory'],
|
||||
)
|
||||
def test_valid_partial_path(partial_path: str):
|
||||
"""Paths that start with `sympy` are valid."""
|
||||
_ = make_absolute_path(partial_path)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'partial_path', ['not_sympy', 'also/not/sympy'],
|
||||
)
|
||||
def test_invalid_partial_path_raises_value_error(partial_path: str):
|
||||
"""A `ValueError` is raises on paths that don't start with `sympy`."""
|
||||
with pytest.raises(ValueError):
|
||||
_ = make_absolute_path(partial_path)
|
||||
|
||||
|
||||
class TestUpdateArgsWithPaths:
|
||||
|
||||
@staticmethod
|
||||
def test_no_paths():
|
||||
"""If no paths are passed, only `sympy` and `doc/src` are appended.
|
||||
|
||||
`sympy` and `doc/src` are the `testpaths` stated in `pytest.ini`. They
|
||||
need to be manually added as if any path-related arguments are passed
|
||||
to `pytest.main` then the settings in `pytest.ini` may be ignored.
|
||||
|
||||
"""
|
||||
paths = []
|
||||
args = update_args_with_paths(paths=paths, keywords=None, args=[])
|
||||
expected = [
|
||||
str(pathlib.Path(sympy_dir(), 'sympy')),
|
||||
str(pathlib.Path(sympy_dir(), 'doc/src')),
|
||||
]
|
||||
assert args == expected
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'path',
|
||||
['sympy/core/tests/test_basic.py', '_basic']
|
||||
)
|
||||
def test_one_file(path: str):
|
||||
"""Single files/paths, full or partial, are matched correctly."""
|
||||
args = update_args_with_paths(paths=[path], keywords=None, args=[])
|
||||
expected = [
|
||||
str(pathlib.Path(sympy_dir(), 'sympy/core/tests/test_basic.py')),
|
||||
]
|
||||
assert args == expected
|
||||
|
||||
@staticmethod
|
||||
def test_partial_path_from_root():
|
||||
"""Partial paths from the root directly are matched correctly."""
|
||||
args = update_args_with_paths(paths=['sympy/functions'], keywords=None, args=[])
|
||||
expected = [str(pathlib.Path(sympy_dir(), 'sympy/functions'))]
|
||||
assert args == expected
|
||||
|
||||
@staticmethod
|
||||
def test_multiple_paths_from_root():
|
||||
"""Multiple paths, partial or full, are matched correctly."""
|
||||
paths = ['sympy/core/tests/test_basic.py', 'sympy/functions']
|
||||
args = update_args_with_paths(paths=paths, keywords=None, args=[])
|
||||
expected = [
|
||||
str(pathlib.Path(sympy_dir(), 'sympy/core/tests/test_basic.py')),
|
||||
str(pathlib.Path(sympy_dir(), 'sympy/functions')),
|
||||
]
|
||||
assert args == expected
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'paths, expected_paths',
|
||||
[
|
||||
(
|
||||
['/core', '/util'],
|
||||
[
|
||||
'doc/src/modules/utilities',
|
||||
'doc/src/reference/public/utilities',
|
||||
'sympy/core',
|
||||
'sympy/logic/utilities',
|
||||
'sympy/utilities',
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
def test_multiple_paths_from_non_root(paths: List[str], expected_paths: List[str]):
|
||||
"""Multiple partial paths are matched correctly."""
|
||||
args = update_args_with_paths(paths=paths, keywords=None, args=[])
|
||||
assert len(args) == len(expected_paths)
|
||||
for arg, expected in zip(sorted(args), expected_paths):
|
||||
assert expected in arg
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'paths',
|
||||
[
|
||||
|
||||
[],
|
||||
['sympy/physics'],
|
||||
['sympy/physics/mechanics'],
|
||||
['sympy/physics/mechanics/tests'],
|
||||
['sympy/physics/mechanics/tests/test_kane3.py'],
|
||||
]
|
||||
)
|
||||
def test_string_as_keyword(paths: List[str]):
|
||||
"""String keywords are matched correctly."""
|
||||
keywords = ('bicycle', )
|
||||
args = update_args_with_paths(paths=paths, keywords=keywords, args=[])
|
||||
expected_args = ['sympy/physics/mechanics/tests/test_kane3.py::test_bicycle']
|
||||
assert len(args) == len(expected_args)
|
||||
for arg, expected in zip(sorted(args), expected_args):
|
||||
assert expected in arg
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'paths',
|
||||
[
|
||||
|
||||
[],
|
||||
['sympy/core'],
|
||||
['sympy/core/tests'],
|
||||
['sympy/core/tests/test_sympify.py'],
|
||||
]
|
||||
)
|
||||
def test_integer_as_keyword(paths: List[str]):
|
||||
"""Integer keywords are matched correctly."""
|
||||
keywords = ('3538', )
|
||||
args = update_args_with_paths(paths=paths, keywords=keywords, args=[])
|
||||
expected_args = ['sympy/core/tests/test_sympify.py::test_issue_3538']
|
||||
assert len(args) == len(expected_args)
|
||||
for arg, expected in zip(sorted(args), expected_args):
|
||||
assert expected in arg
|
||||
|
||||
@staticmethod
|
||||
def test_multiple_keywords():
|
||||
"""Multiple keywords are matched correctly."""
|
||||
keywords = ('bicycle', '3538')
|
||||
args = update_args_with_paths(paths=[], keywords=keywords, args=[])
|
||||
expected_args = [
|
||||
'sympy/core/tests/test_sympify.py::test_issue_3538',
|
||||
'sympy/physics/mechanics/tests/test_kane3.py::test_bicycle',
|
||||
]
|
||||
assert len(args) == len(expected_args)
|
||||
for arg, expected in zip(sorted(args), expected_args):
|
||||
assert expected in arg
|
||||
|
||||
@staticmethod
|
||||
def test_keyword_match_in_multiple_files():
|
||||
"""Keywords are matched across multiple files."""
|
||||
keywords = ('1130', )
|
||||
args = update_args_with_paths(paths=[], keywords=keywords, args=[])
|
||||
expected_args = [
|
||||
'sympy/integrals/tests/test_heurisch.py::test_heurisch_symbolic_coeffs_1130',
|
||||
'sympy/utilities/tests/test_lambdify.py::test_python_div_zero_issue_11306',
|
||||
]
|
||||
assert len(args) == len(expected_args)
|
||||
for arg, expected in zip(sorted(args), expected_args):
|
||||
assert expected in arg
|
||||
Reference in New Issue
Block a user