testdaemon.py 4.4 KB

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