| """End-to-end test cases for the daemon (dmypy). |
| |
| These are special because they run multiple shell commands. |
| |
| This also includes some unit tests. |
| """ |
| |
| from __future__ import annotations |
| |
| import os |
| import subprocess |
| import sys |
| import tempfile |
| import unittest |
| |
| from mypy.dmypy_server import filter_out_missing_top_level_packages |
| from mypy.fscache import FileSystemCache |
| from mypy.modulefinder import SearchPaths |
| from mypy.test.config import PREFIX, test_temp_dir |
| from mypy.test.data import DataDrivenTestCase, DataSuite |
| from mypy.test.helpers import assert_string_arrays_equal, normalize_error_messages |
| |
| # Files containing test cases descriptions. |
| daemon_files = ["daemon.test"] |
| |
| |
| class DaemonSuite(DataSuite): |
| files = daemon_files |
| |
| def run_case(self, testcase: DataDrivenTestCase) -> None: |
| try: |
| test_daemon(testcase) |
| finally: |
| # Kill the daemon if it's still running. |
| run_cmd("dmypy kill") |
| |
| |
| def test_daemon(testcase: DataDrivenTestCase) -> None: |
| assert testcase.old_cwd is not None, "test was not properly set up" |
| for i, step in enumerate(parse_script(testcase.input)): |
| cmd = step[0] |
| expected_lines = step[1:] |
| assert cmd.startswith("$") |
| cmd = cmd[1:].strip() |
| cmd = cmd.replace("{python}", sys.executable) |
| sts, output = run_cmd(cmd) |
| output_lines = output.splitlines() |
| output_lines = normalize_error_messages(output_lines) |
| if sts: |
| output_lines.append("== Return code: %d" % sts) |
| assert_string_arrays_equal( |
| expected_lines, |
| output_lines, |
| "Command %d (%s) did not give expected output" % (i + 1, cmd), |
| ) |
| |
| |
| def parse_script(input: list[str]) -> list[list[str]]: |
| """Parse testcase.input into steps. |
| |
| Each command starts with a line starting with '$'. |
| The first line (less '$') is sent to the shell. |
| The remaining lines are expected output. |
| """ |
| steps = [] |
| step: list[str] = [] |
| for line in input: |
| if line.startswith("$"): |
| if step: |
| assert step[0].startswith("$") |
| steps.append(step) |
| step = [] |
| step.append(line) |
| if step: |
| steps.append(step) |
| return steps |
| |
| |
| def run_cmd(input: str) -> tuple[int, str]: |
| if input[1:].startswith("mypy run --") and "--show-error-codes" not in input: |
| input += " --hide-error-codes" |
| if input.startswith("dmypy "): |
| input = sys.executable + " -m mypy." + input |
| if input.startswith("mypy "): |
| input = sys.executable + " -m" + input |
| env = os.environ.copy() |
| env["PYTHONPATH"] = PREFIX |
| try: |
| output = subprocess.check_output( |
| input, shell=True, stderr=subprocess.STDOUT, text=True, cwd=test_temp_dir, env=env |
| ) |
| return 0, output |
| except subprocess.CalledProcessError as err: |
| return err.returncode, err.output |
| |
| |
| class DaemonUtilitySuite(unittest.TestCase): |
| """Unit tests for helpers""" |
| |
| def test_filter_out_missing_top_level_packages(self) -> None: |
| with tempfile.TemporaryDirectory() as td: |
| self.make_file(td, "base/a/") |
| self.make_file(td, "base/b.py") |
| self.make_file(td, "base/c.pyi") |
| self.make_file(td, "base/missing.txt") |
| self.make_file(td, "typeshed/d.pyi") |
| self.make_file(td, "typeshed/@python2/e") # outdated |
| self.make_file(td, "pkg1/f-stubs") |
| self.make_file(td, "pkg2/g-python2-stubs") # outdated |
| self.make_file(td, "mpath/sub/long_name/") |
| |
| def makepath(p: str) -> str: |
| return os.path.join(td, p) |
| |
| search = SearchPaths( |
| python_path=(makepath("base"),), |
| mypy_path=(makepath("mpath/sub"),), |
| package_path=(makepath("pkg1"), makepath("pkg2")), |
| typeshed_path=(makepath("typeshed"),), |
| ) |
| fscache = FileSystemCache() |
| res = filter_out_missing_top_level_packages( |
| {"a", "b", "c", "d", "e", "f", "g", "long_name", "ff", "missing"}, search, fscache |
| ) |
| assert res == {"a", "b", "c", "d", "f", "long_name"} |
| |
| def make_file(self, base: str, path: str) -> None: |
| fullpath = os.path.join(base, path) |
| os.makedirs(os.path.dirname(fullpath), exist_ok=True) |
| if not path.endswith("/"): |
| with open(fullpath, "w") as f: |
| f.write("# test file") |