Skip to content

Commit da71cde

Browse files
authored
Make testfinegrained use dmypy_server (#4699)
1 parent 12863c7 commit da71cde

File tree

8 files changed

+150
-127
lines changed

8 files changed

+150
-127
lines changed

mypy/dmypy.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ def do_restart(args: argparse.Namespace) -> None:
147147
def start_server(args: argparse.Namespace) -> None:
148148
"""Start the server from command arguments and wait for it."""
149149
# Lazy import so this import doesn't slow down other commands.
150-
from mypy.dmypy_server import daemonize, Server
151-
if daemonize(Server(args.flags).serve, args.log_file) != 0:
150+
from mypy.dmypy_server import daemonize, Server, process_start_options
151+
if daemonize(Server(process_start_options(args.flags)).serve, args.log_file) != 0:
152152
sys.exit(1)
153153
wait_for_server()
154154

@@ -283,8 +283,8 @@ def do_hang(args: argparse.Namespace) -> None:
283283
def do_daemon(args: argparse.Namespace) -> None:
284284
"""Serve requests in the foreground."""
285285
# Lazy import so this import doesn't slow down other commands.
286-
from mypy.dmypy_server import Server
287-
Server(args.flags).serve()
286+
from mypy.dmypy_server import Server, process_start_options
287+
Server(process_start_options(args.flags)).serve()
288288

289289

290290
@action(help_parser)

mypy/dmypy_server.py

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
import mypy.build
2121
import mypy.errors
2222
import mypy.main
23-
import mypy.server.update
23+
from mypy.server.update import FineGrainedBuildManager
2424
from mypy.dmypy_util import STATUS_FILE, receive
2525
from mypy.gclogger import GcLogger
2626
from mypy.fscache import FileSystemCache
2727
from mypy.fswatcher import FileSystemWatcher, FileData
28+
from mypy.options import Options
2829

2930

3031
def daemonize(func: Callable[[], None], log_file: Optional[str] = None) -> int:
@@ -78,33 +79,44 @@ def daemonize(func: Callable[[], None], log_file: Optional[str] = None) -> int:
7879
SOCKET_NAME = 'dmypy.sock' # In current directory.
7980

8081

82+
def process_start_options(flags: List[str]) -> Options:
83+
import mypy.main
84+
sources, options = mypy.main.process_options(['-i'] + flags,
85+
require_targets=False,
86+
server_options=True)
87+
if sources:
88+
sys.exit("dmypy: start/restart does not accept sources")
89+
if options.report_dirs:
90+
sys.exit("dmypy: start/restart cannot generate reports")
91+
if options.junit_xml:
92+
sys.exit("dmypy: start/restart does not support --junit-xml; "
93+
"pass it to check/recheck instead")
94+
if not options.incremental:
95+
sys.exit("dmypy: start/restart should not disable incremental mode")
96+
if options.quick_and_dirty:
97+
sys.exit("dmypy: start/restart should not specify quick_and_dirty mode")
98+
if options.use_fine_grained_cache and not options.fine_grained_incremental:
99+
sys.exit("dmypy: fine-grained cache can only be used in experimental mode")
100+
# Our file change tracking can't yet handle changes to files that aren't
101+
# specified in the sources list.
102+
if options.follow_imports not in ('skip', 'error'):
103+
sys.exit("dmypy: follow-imports must be 'skip' or 'error'")
104+
return options
105+
106+
81107
class Server:
82108

83109
# NOTE: the instance is constructed in the parent process but
84110
# serve() is called in the grandchild (by daemonize()).
85111

86-
def __init__(self, flags: List[str]) -> None:
112+
def __init__(self, options: Options, alt_lib_path: Optional[str] = None) -> None:
87113
"""Initialize the server with the desired mypy flags."""
88114
self.saved_cache = {} # type: mypy.build.SavedCache
89-
self.fine_grained_initialized = False
90-
sources, options = mypy.main.process_options(['-i'] + flags,
91-
require_targets=False,
92-
server_options=True)
93115
self.fine_grained = options.fine_grained_incremental
94-
if sources:
95-
sys.exit("dmypy: start/restart does not accept sources")
96-
if options.report_dirs:
97-
sys.exit("dmypy: start/restart cannot generate reports")
98-
if options.junit_xml:
99-
sys.exit("dmypy: start/restart does not support --junit-xml; "
100-
"pass it to check/recheck instead")
101-
if not options.incremental:
102-
sys.exit("dmypy: start/restart should not disable incremental mode")
103-
if options.quick_and_dirty:
104-
sys.exit("dmypy: start/restart should not specify quick_and_dirty mode")
105-
if options.use_fine_grained_cache and not options.fine_grained_incremental:
106-
sys.exit("dmypy: fine-grained cache can only be used in experimental mode")
107116
self.options = options
117+
self.alt_lib_path = alt_lib_path
118+
self.fine_grained_manager = None # type: Optional[FineGrainedBuildManager]
119+
108120
if os.path.isfile(STATUS_FILE):
109121
os.unlink(STATUS_FILE)
110122
if self.fine_grained:
@@ -214,15 +226,13 @@ def cmd_recheck(self) -> Dict[str, object]:
214226
# Needed by tests.
215227
last_manager = None # type: Optional[mypy.build.BuildManager]
216228

217-
def check(self, sources: List[mypy.build.BuildSource],
218-
alt_lib_path: Optional[str] = None) -> Dict[str, Any]:
229+
def check(self, sources: List[mypy.build.BuildSource]) -> Dict[str, Any]:
219230
if self.fine_grained:
220231
return self.check_fine_grained(sources)
221232
else:
222-
return self.check_default(sources, alt_lib_path)
233+
return self.check_default(sources)
223234

224-
def check_default(self, sources: List[mypy.build.BuildSource],
225-
alt_lib_path: Optional[str] = None) -> Dict[str, Any]:
235+
def check_default(self, sources: List[mypy.build.BuildSource]) -> Dict[str, Any]:
226236
"""Check using the default (per-file) incremental mode."""
227237
self.last_manager = None
228238
blockers = False
@@ -231,7 +241,7 @@ def check_default(self, sources: List[mypy.build.BuildSource],
231241
# saved_cache is mutated in place.
232242
res = mypy.build.build(sources, self.options,
233243
saved_cache=self.saved_cache,
234-
alt_lib_path=alt_lib_path)
244+
alt_lib_path=self.alt_lib_path)
235245
msgs = res.errors
236246
self.last_manager = res.manager # type: Optional[mypy.build.BuildManager]
237247
except mypy.errors.CompileError as err:
@@ -254,7 +264,7 @@ def check_default(self, sources: List[mypy.build.BuildSource],
254264

255265
def check_fine_grained(self, sources: List[mypy.build.BuildSource]) -> Dict[str, Any]:
256266
"""Check using fine-grained incremental mode."""
257-
if not self.fine_grained_initialized:
267+
if not self.fine_grained_manager:
258268
return self.initialize_fine_grained(sources)
259269
else:
260270
return self.fine_grained_increment(sources)
@@ -267,9 +277,9 @@ def initialize_fine_grained(self, sources: List[mypy.build.BuildSource]) -> Dict
267277
# Stores the initial state of sources as a side effect.
268278
self.fswatcher.find_changed()
269279
try:
270-
# TODO: alt_lib_path
271280
result = mypy.build.build(sources=sources,
272-
options=self.options)
281+
options=self.options,
282+
alt_lib_path=self.alt_lib_path)
273283
except mypy.errors.CompileError as e:
274284
output = ''.join(s + '\n' for s in e.messages)
275285
if e.use_stdout:
@@ -280,8 +290,7 @@ def initialize_fine_grained(self, sources: List[mypy.build.BuildSource]) -> Dict
280290
messages = result.errors
281291
manager = result.manager
282292
graph = result.graph
283-
self.fine_grained_manager = mypy.server.update.FineGrainedBuildManager(manager, graph)
284-
self.fine_grained_initialized = True
293+
self.fine_grained_manager = FineGrainedBuildManager(manager, graph)
285294
self.previous_sources = sources
286295
self.fscache.flush()
287296

@@ -310,6 +319,8 @@ def initialize_fine_grained(self, sources: List[mypy.build.BuildSource]) -> Dict
310319
return {'out': ''.join(s + '\n' for s in messages), 'err': '', 'status': status}
311320

312321
def fine_grained_increment(self, sources: List[mypy.build.BuildSource]) -> Dict[str, Any]:
322+
assert self.fine_grained_manager is not None
323+
313324
t0 = time.time()
314325
self.update_sources(sources)
315326
changed = self.find_changed(sources)

mypy/test/helpers.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import subprocess
44
import sys
55
import time
6+
import shutil
67

78
from typing import List, Dict, Tuple, Callable, Any, Optional
89

@@ -356,3 +357,22 @@ def run_command(cmdline: List[str], *, env: Optional[Dict[str, str]] = None,
356357
out = err = b''
357358
process.kill()
358359
return process.returncode, split_lines(out, err)
360+
361+
362+
def copy_and_fudge_mtime(source_path: str, target_path: str) -> None:
363+
# In some systems, mtime has a resolution of 1 second which can
364+
# cause annoying-to-debug issues when a file has the same size
365+
# after a change. We manually set the mtime to circumvent this.
366+
# Note that we increment the old file's mtime, which guarentees a
367+
# different value, rather than incrementing the mtime after the
368+
# copy, which could leave the mtime unchanged if the old file had
369+
# a similarly fudged mtime.
370+
new_time = None
371+
if os.path.isfile(target_path):
372+
new_time = os.stat(target_path).st_mtime + 1
373+
374+
# Use retries to work around potential flakiness on Windows (AppVeyor).
375+
retry_on_error(lambda: shutil.copy(source_path, target_path))
376+
377+
if new_time:
378+
os.utime(target_path, times=(new_time, new_time))

mypy/test/testcheck.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from mypy.test.data import DataDrivenTestCase, DataSuite
1313
from mypy.test.helpers import (
1414
assert_string_arrays_equal, normalize_error_messages,
15-
retry_on_error, update_testcase_output, parse_options
15+
retry_on_error, update_testcase_output, parse_options,
16+
copy_and_fudge_mtime
1617
)
1718
from mypy.errors import CompileError
1819
from mypy.options import Options
@@ -132,14 +133,7 @@ def run_case_once(self, testcase: DataDrivenTestCase, incremental_step: int = 0)
132133
if file.endswith('.' + str(incremental_step)):
133134
full = os.path.join(dn, file)
134135
target = full[:-2]
135-
# Use retries to work around potential flakiness on Windows (AppVeyor).
136-
retry_on_error(lambda: shutil.copy(full, target))
137-
138-
# In some systems, mtime has a resolution of 1 second which can cause
139-
# annoying-to-debug issues when a file has the same size after a
140-
# change. We manually set the mtime to circumvent this.
141-
new_time = os.stat(target).st_mtime + 1
142-
os.utime(target, times=(new_time, new_time))
136+
copy_and_fudge_mtime(full, target)
143137
# Delete files scheduled to be deleted in [delete <path>.num] sections.
144138
for path in testcase.deleted_paths.get(incremental_step, set()):
145139
# Use retries to work around potential flakiness on Windows (AppVeyor).

mypy/test/testdmypy.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from mypy.test.helpers import (
1616
assert_string_arrays_equal, normalize_error_messages,
1717
retry_on_error, testcase_pyversion, update_testcase_output,
18+
copy_and_fudge_mtime,
1819
)
1920
from mypy.options import Options
2021

@@ -91,14 +92,7 @@ def run_case_once(self, testcase: DataDrivenTestCase, incremental_step: int) ->
9192
if file.endswith('.' + str(incremental_step)):
9293
full = os.path.join(dn, file)
9394
target = full[:-2]
94-
# Use retries to work around potential flakiness on Windows (AppVeyor).
95-
retry_on_error(lambda: shutil.copy(full, target))
96-
97-
# In some systems, mtime has a resolution of 1 second which can cause
98-
# annoying-to-debug issues when a file has the same size after a
99-
# change. We manually set the mtime to circumvent this.
100-
new_time = os.stat(target).st_mtime + 1
101-
os.utime(target, times=(new_time, new_time))
95+
copy_and_fudge_mtime(full, target)
10296
# Delete files scheduled to be deleted in [delete <path>.num] sections.
10397
for path in testcase.deleted_paths.get(incremental_step, set()):
10498
# Use retries to work around potential flakiness on Windows (AppVeyor).
@@ -117,20 +111,16 @@ def run_case_once(self, testcase: DataDrivenTestCase, incremental_step: int) ->
117111
# Parse options after moving files (in case mypy.ini is being moved).
118112
options = self.parse_options(original_program_text, testcase, incremental_step)
119113
if incremental_step == 1:
120-
server_options = [] # type: List[str]
121114
if 'fine-grained' in testcase.file:
122-
server_options.append('--experimental')
123115
options.fine_grained_incremental = True
124-
options.local_partial_types = True
125-
self.server = dmypy_server.Server(server_options) # TODO: Fix ugly API
126-
self.server.options = options
116+
self.server = dmypy_server.Server(options, alt_lib_path=test_temp_dir)
127117

128118
assert self.server is not None # Set in step 1 and survives into next steps
129119
sources = []
130120
for module_name, program_path, program_text in module_data:
131121
# Always set to none so we're forced to reread the module in incremental mode
132122
sources.append(build.BuildSource(program_path, module_name, None))
133-
response = self.server.check(sources, alt_lib_path=test_temp_dir)
123+
response = self.server.check(sources)
134124
a = (response['out'] or response['err']).splitlines()
135125
a = normalize_error_messages(a)
136126

0 commit comments

Comments
 (0)