testdaemon.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. """End-to-end test cases for the daemon (dmypy).
  2. These are special because they run multiple shell commands.
  3. This also includes some unit tests.
  4. """
  5. from __future__ import annotations
  6. import os
  7. import subprocess
  8. import sys
  9. import tempfile
  10. import unittest
  11. import pytest
  12. from mypy.dmypy_server import filter_out_missing_top_level_packages
  13. from mypy.fscache import FileSystemCache
  14. from mypy.modulefinder import SearchPaths
  15. from mypy.test.config import PREFIX, test_temp_dir
  16. from mypy.test.data import DataDrivenTestCase, DataSuite
  17. from mypy.test.helpers import assert_string_arrays_equal, normalize_error_messages
  18. # Files containing test cases descriptions.
  19. daemon_files = ["daemon.test"]
  20. class DaemonSuite(DataSuite):
  21. files = daemon_files
  22. def run_case(self, testcase: DataDrivenTestCase) -> None:
  23. if testcase.name.endswith("_python38") and sys.version_info < (3, 8):
  24. pytest.skip("Not supported on this version of Python")
  25. try:
  26. test_daemon(testcase)
  27. finally:
  28. # Kill the daemon if it's still running.
  29. run_cmd("dmypy kill")
  30. def test_daemon(testcase: DataDrivenTestCase) -> None:
  31. assert testcase.old_cwd is not None, "test was not properly set up"
  32. for i, step in enumerate(parse_script(testcase.input)):
  33. cmd = step[0]
  34. expected_lines = step[1:]
  35. assert cmd.startswith("$")
  36. cmd = cmd[1:].strip()
  37. cmd = cmd.replace("{python}", sys.executable)
  38. sts, output = run_cmd(cmd)
  39. output_lines = output.splitlines()
  40. output_lines = normalize_error_messages(output_lines)
  41. if sts:
  42. output_lines.append("== Return code: %d" % sts)
  43. assert_string_arrays_equal(
  44. expected_lines,
  45. output_lines,
  46. "Command %d (%s) did not give expected output" % (i + 1, cmd),
  47. )
  48. def parse_script(input: list[str]) -> list[list[str]]:
  49. """Parse testcase.input into steps.
  50. Each command starts with a line starting with '$'.
  51. The first line (less '$') is sent to the shell.
  52. The remaining lines are expected output.
  53. """
  54. steps = []
  55. step: list[str] = []
  56. for line in input:
  57. if line.startswith("$"):
  58. if step:
  59. assert step[0].startswith("$")
  60. steps.append(step)
  61. step = []
  62. step.append(line)
  63. if step:
  64. steps.append(step)
  65. return steps
  66. def run_cmd(input: str) -> tuple[int, str]:
  67. if input[1:].startswith("mypy run --") and "--show-error-codes" not in input:
  68. input += " --hide-error-codes"
  69. if input.startswith("dmypy "):
  70. input = sys.executable + " -m mypy." + input
  71. if input.startswith("mypy "):
  72. input = sys.executable + " -m" + input
  73. env = os.environ.copy()
  74. env["PYTHONPATH"] = PREFIX
  75. try:
  76. output = subprocess.check_output(
  77. input, shell=True, stderr=subprocess.STDOUT, text=True, cwd=test_temp_dir, env=env
  78. )
  79. return 0, output
  80. except subprocess.CalledProcessError as err:
  81. return err.returncode, err.output
  82. class DaemonUtilitySuite(unittest.TestCase):
  83. """Unit tests for helpers"""
  84. def test_filter_out_missing_top_level_packages(self) -> None:
  85. with tempfile.TemporaryDirectory() as td:
  86. self.make_file(td, "base/a/")
  87. self.make_file(td, "base/b.py")
  88. self.make_file(td, "base/c.pyi")
  89. self.make_file(td, "base/missing.txt")
  90. self.make_file(td, "typeshed/d.pyi")
  91. self.make_file(td, "typeshed/@python2/e") # outdated
  92. self.make_file(td, "pkg1/f-stubs")
  93. self.make_file(td, "pkg2/g-python2-stubs") # outdated
  94. self.make_file(td, "mpath/sub/long_name/")
  95. def makepath(p: str) -> str:
  96. return os.path.join(td, p)
  97. search = SearchPaths(
  98. python_path=(makepath("base"),),
  99. mypy_path=(makepath("mpath/sub"),),
  100. package_path=(makepath("pkg1"), makepath("pkg2")),
  101. typeshed_path=(makepath("typeshed"),),
  102. )
  103. fscache = FileSystemCache()
  104. res = filter_out_missing_top_level_packages(
  105. {"a", "b", "c", "d", "e", "f", "g", "long_name", "ff", "missing"}, search, fscache
  106. )
  107. assert res == {"a", "b", "c", "d", "f", "long_name"}
  108. def make_file(self, base: str, path: str) -> None:
  109. fullpath = os.path.join(base, path)
  110. os.makedirs(os.path.dirname(fullpath), exist_ok=True)
  111. if not path.endswith("/"):
  112. with open(fullpath, "w") as f:
  113. f.write("# test file")