summaryrefslogtreecommitdiff
path: root/test/ext/mypy/test_mypy_plugin_py3k.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/ext/mypy/test_mypy_plugin_py3k.py')
-rw-r--r--test/ext/mypy/test_mypy_plugin_py3k.py211
1 files changed, 153 insertions, 58 deletions
diff --git a/test/ext/mypy/test_mypy_plugin_py3k.py b/test/ext/mypy/test_mypy_plugin_py3k.py
index cc8d8955f..6df21e46c 100644
--- a/test/ext/mypy/test_mypy_plugin_py3k.py
+++ b/test/ext/mypy/test_mypy_plugin_py3k.py
@@ -3,16 +3,54 @@ import re
import shutil
import sys
import tempfile
+from typing import Any
+from typing import cast
+from typing import List
+from typing import Tuple
+import sqlalchemy
from sqlalchemy import testing
from sqlalchemy.testing import config
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
+def _file_combinations(dirname):
+ path = os.path.join(os.path.dirname(__file__), dirname)
+ files = []
+ for f in os.listdir(path):
+ if f.endswith(".py"):
+ files.append(os.path.join(os.path.dirname(__file__), dirname, f))
+
+ for extra_dir in testing.config.options.mypy_extra_test_paths:
+ if extra_dir and os.path.isdir(extra_dir):
+ for f in os.listdir(os.path.join(extra_dir, dirname)):
+ if f.endswith(".py"):
+ files.append(os.path.join(extra_dir, dirname, f))
+ return files
+
+
+def _incremental_dirs():
+ path = os.path.join(os.path.dirname(__file__), "incremental")
+ files = []
+ for d in os.listdir(path):
+ if os.path.isdir(os.path.join(path, d)):
+ files.append(
+ os.path.join(os.path.dirname(__file__), "incremental", d)
+ )
+
+ for extra_dir in testing.config.options.mypy_extra_test_paths:
+ if extra_dir and os.path.isdir(extra_dir):
+ for d in os.listdir(os.path.join(extra_dir, "incremental")):
+ if os.path.isdir(os.path.join(path, d)):
+ files.append(os.path.join(extra_dir, "incremental", d))
+ return files
+
+
@testing.add_to_marker.mypy
class MypyPluginTest(fixtures.TestBase):
- __requires__ = ("sqlalchemy2_stubs",)
+ __tags__ = ("mypy",)
+ __requires__ = ("no_sqlalchemy2_stubs",)
@testing.fixture(scope="function")
def per_func_cachedir(self):
@@ -25,22 +63,50 @@ class MypyPluginTest(fixtures.TestBase):
yield item
def _cachedir(self):
+ sqlalchemy_path = os.path.dirname(os.path.dirname(sqlalchemy.__file__))
+
+ # for a pytest from my local ./lib/ , i need mypy_path.
+ # for a tox run where sqlalchemy is in site_packages, mypy complains
+ # "../python3.10/site-packages is in the MYPYPATH. Please remove it."
+ # previously when we used sqlalchemy2-stubs, it would just be
+ # installed as a dependency, which is why mypy_path wasn't needed
+ # then, but I like to be able to run the test suite from the local
+ # ./lib/ as well.
+
+ if "site-packages" not in sqlalchemy_path:
+ mypy_path = f"mypy_path={sqlalchemy_path}"
+ else:
+ mypy_path = ""
+
with tempfile.TemporaryDirectory() as cachedir:
with open(
os.path.join(cachedir, "sqla_mypy_config.cfg"), "w"
) as config_file:
config_file.write(
- """
+ f"""
[mypy]\n
plugins = sqlalchemy.ext.mypy.plugin\n
+ show_error_codes = True\n
+ {mypy_path}
+ disable_error_code = no-untyped-call
+
+ [mypy-sqlalchemy.*]
+ ignore_errors = True
+
"""
)
with open(
os.path.join(cachedir, "plain_mypy_config.cfg"), "w"
) as config_file:
config_file.write(
- """
+ f"""
[mypy]\n
+ show_error_codes = True\n
+ {mypy_path}
+ disable_error_code = var-annotated,no-untyped-call
+ [mypy-sqlalchemy.*]
+ ignore_errors = True
+
"""
)
yield cachedir
@@ -70,24 +136,12 @@ class MypyPluginTest(fixtures.TestBase):
return run
- def _incremental_dirs():
- path = os.path.join(os.path.dirname(__file__), "incremental")
- files = []
- for d in os.listdir(path):
- if os.path.isdir(os.path.join(path, d)):
- files.append(
- os.path.join(os.path.dirname(__file__), "incremental", d)
- )
-
- for extra_dir in testing.config.options.mypy_extra_test_paths:
- if extra_dir and os.path.isdir(extra_dir):
- for d in os.listdir(os.path.join(extra_dir, "incremental")):
- if os.path.isdir(os.path.join(path, d)):
- files.append(os.path.join(extra_dir, "incremental", d))
- return files
-
@testing.combinations(
- *[(pathname,) for pathname in _incremental_dirs()], argnames="pathname"
+ *[
+ (pathname, testing.exclusions.closed())
+ for pathname in _incremental_dirs()
+ ],
+ argnames="pathname",
)
@testing.requires.patch_library
def test_incremental(self, mypy_runner, per_func_cachedir, pathname):
@@ -131,33 +185,33 @@ class MypyPluginTest(fixtures.TestBase):
% (patchfile, result[0]),
)
- def _file_combinations():
- path = os.path.join(os.path.dirname(__file__), "files")
- files = []
- for f in os.listdir(path):
- if f.endswith(".py"):
- files.append(
- os.path.join(os.path.dirname(__file__), "files", f)
- )
-
- for extra_dir in testing.config.options.mypy_extra_test_paths:
- if extra_dir and os.path.isdir(extra_dir):
- for f in os.listdir(os.path.join(extra_dir, "files")):
- if f.endswith(".py"):
- files.append(os.path.join(extra_dir, "files", f))
- return files
-
@testing.combinations(
- *[(filename,) for filename in _file_combinations()], argnames="path"
+ *(
+ cast(
+ List[Tuple[Any, ...]],
+ [
+ ("w_plugin", os.path.basename(path), path, True)
+ for path in _file_combinations("plugin_files")
+ ],
+ )
+ + cast(
+ List[Tuple[Any, ...]],
+ [
+ ("plain", os.path.basename(path), path, False)
+ for path in _file_combinations("plain_files")
+ ],
+ )
+ ),
+ argnames="filename,path,use_plugin",
+ id_="isaa",
)
- def test_mypy(self, mypy_runner, path):
- filename = os.path.basename(path)
- use_plugin = True
+ def test_files(self, mypy_runner, filename, path, use_plugin):
- expected_errors = []
- expected_re = re.compile(r"\s*# EXPECTED(_MYPY)?: (.+)")
+ expected_messages = []
+ expected_re = re.compile(r"\s*# EXPECTED(_MYPY)?(_RE)?(_TYPE)?: (.+)")
py_ver_re = re.compile(r"^#\s*PYTHON_VERSION\s?>=\s?(\d+\.\d+)")
with open(path) as file_:
+ current_assert_messages = []
for num, line in enumerate(file_, 1):
m = py_ver_re.match(line)
if m:
@@ -174,38 +228,79 @@ class MypyPluginTest(fixtures.TestBase):
m = expected_re.match(line)
if m:
is_mypy = bool(m.group(1))
- expected_msg = m.group(2)
- expected_msg = re.sub(r"# noqa ?.*", "", m.group(2))
- expected_errors.append(
- (num, is_mypy, expected_msg.strip())
+ is_re = bool(m.group(2))
+ is_type = bool(m.group(3))
+
+ expected_msg = re.sub(r"# noqa ?.*", "", m.group(4))
+ if is_type:
+ is_mypy = is_re = True
+ expected_msg = f'Revealed type is "{expected_msg}"'
+ current_assert_messages.append(
+ (is_mypy, is_re, expected_msg.strip())
+ )
+ elif current_assert_messages:
+ expected_messages.extend(
+ (num, is_mypy, is_re, expected_msg)
+ for (
+ is_mypy,
+ is_re,
+ expected_msg,
+ ) in current_assert_messages
)
+ current_assert_messages[:] = []
result = mypy_runner(path, use_plugin=use_plugin)
- if expected_errors:
+ if expected_messages:
eq_(result[2], 1, msg=result)
- print(result[0])
+ output = []
- errors = []
- for e in result[0].split("\n"):
+ raw_lines = result[0].split("\n")
+ while raw_lines:
+ e = raw_lines.pop(0)
if re.match(r".+\.py:\d+: error: .*", e):
- errors.append(e)
-
- for num, is_mypy, msg in expected_errors:
+ output.append(("error", e))
+ elif re.match(
+ r".+\.py:\d+: note: +(?:Possible overload|def ).*", e
+ ):
+ while raw_lines:
+ ol = raw_lines.pop(0)
+ if not re.match(r".+\.py:\d+: note: +def \[.*", ol):
+ break
+ elif re.match(
+ r".+\.py:\d+: note: .*(?:perhaps|suggestion)", e, re.I
+ ):
+ pass
+ elif re.match(r".+\.py:\d+: note: .*", e):
+ output.append(("note", e))
+
+ for num, is_mypy, is_re, msg in expected_messages:
msg = msg.replace("'", '"')
prefix = "[SQLAlchemy Mypy plugin] " if not is_mypy else ""
- for idx, errmsg in enumerate(errors):
- if (
- f"{filename}:{num + 1}: error: {prefix}{msg}"
+ for idx, (typ, errmsg) in enumerate(output):
+ if is_re:
+ if re.match(
+ fr".*{filename}\:{num}\: {typ}\: {prefix}{msg}", # noqa E501
+ errmsg,
+ ):
+ break
+ elif (
+ f"{filename}:{num}: {typ}: {prefix}{msg}"
in errmsg.replace("'", '"')
):
break
else:
continue
- del errors[idx]
+ del output[idx]
- assert not errors, "errors remain: %s" % "\n".join(errors)
+ if output:
+ print("messages from mypy that were not consumed:")
+ print("\n".join(msg for _, msg in output))
+ assert False, "errors and/or notes remain, see stdout"
else:
+ if result[2] != 0:
+ print(result[0])
+
eq_(result[2], 0, msg=result)