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