blob: 18a03a92207d282b578231a123994498f864f80c [file] [log] [blame] [edit]
-- End-to-end test cases for the daemon (dmypy).
-- These are special because they run multiple shell commands.
[case testDaemonStartStop]
$ dmypy start -- --follow-imports=error
Daemon started
$ dmypy stop
Daemon stopped
[case testDaemonBasic]
$ dmypy start -- --follow-imports=error
Daemon started
$ dmypy check -- foo.py
Success: no issues found in 1 source file
$ dmypy recheck
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f(): pass
[case testDaemonRun]
$ dmypy run -- foo.py --follow-imports=error
Daemon started
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f(): pass
[case testDaemonRunIgnoreMissingImports]
$ dmypy run -- foo.py --follow-imports=error --ignore-missing-imports
Daemon started
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f(): pass
[case testDaemonRunErrorCodes]
$ dmypy run -- foo.py --follow-imports=error --disable-error-code=type-abstract
Daemon started
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f(): pass
[case testDaemonRunCombinedOptions]
$ dmypy run -- foo.py --follow-imports=error --ignore-missing-imports --disable-error-code=type-abstract
Daemon started
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f(): pass
[case testDaemonIgnoreConfigFiles]
$ dmypy start -- --follow-imports=error
Daemon started
[file mypy.ini]
\[mypy]
files = ./foo.py
[case testDaemonRunMultipleStrict]
$ dmypy run -- foo.py --strict --follow-imports=error
Daemon started
foo.py:1: error: Function is missing a return type annotation
foo.py:1: note: Use "-> None" if function does not return a value
Found 1 error in 1 file (checked 1 source file)
== Return code: 1
$ dmypy run -- bar.py --strict --follow-imports=error
bar.py:1: error: Function is missing a return type annotation
bar.py:1: note: Use "-> None" if function does not return a value
Found 1 error in 1 file (checked 1 source file)
== Return code: 1
$ dmypy run -- foo.py --strict --follow-imports=error
foo.py:1: error: Function is missing a return type annotation
foo.py:1: note: Use "-> None" if function does not return a value
Found 1 error in 1 file (checked 1 source file)
== Return code: 1
[file foo.py]
def f(): pass
[file bar.py]
def f(): pass
[case testDaemonRunRestart]
$ dmypy run -- foo.py --follow-imports=error
Daemon started
Success: no issues found in 1 source file
$ dmypy run -- foo.py --follow-imports=error
Success: no issues found in 1 source file
$ {python} -c "print('[mypy]')" >mypy.ini
$ {python} -c "print('disallow_untyped_defs = True')" >>mypy.ini
$ dmypy run -- foo.py --follow-imports=error
Restarting: configuration changed
Daemon stopped
Daemon started
foo.py:1: error: Function is missing a return type annotation
foo.py:1: note: Use "-> None" if function does not return a value
Found 1 error in 1 file (checked 1 source file)
== Return code: 1
$ {python} -c "print('def f() -> None: pass')" >foo.py
$ dmypy run -- foo.py --follow-imports=error
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f(): pass
[case testDaemonRunRestartPretty]
$ dmypy run -- foo.py --follow-imports=error --pretty
Daemon started
Success: no issues found in 1 source file
$ dmypy run -- foo.py --follow-imports=error --pretty
Success: no issues found in 1 source file
$ {python} -c "print('[mypy]')" >mypy.ini
$ {python} -c "print('disallow_untyped_defs = True')" >>mypy.ini
$ dmypy run -- foo.py --follow-imports=error --pretty
Restarting: configuration changed
Daemon stopped
Daemon started
foo.py:1: error: Function is missing a return type annotation
def f():
^
foo.py:1: note: Use "-> None" if function does not return a value
Found 1 error in 1 file (checked 1 source file)
== Return code: 1
$ {python} -c "print('def f() -> None: pass')" >foo.py
$ dmypy run -- foo.py --follow-imports=error --pretty
Success: no issues found in 1 source file
$ dmypy stop
Daemon stopped
[file foo.py]
def f():
pass
[case testDaemonRunRestartPluginVersion]
$ dmypy run -- foo.py --no-error-summary
Daemon started
$ {python} -c "print(' ')" >> plug.py
$ dmypy run -- foo.py --no-error-summary
Restarting: plugins changed
Daemon stopped
Daemon started
$ dmypy stop
Daemon stopped
[file mypy.ini]
\[mypy]
follow_imports = error
plugins = plug.py
[file foo.py]
pass
[file plug.py]
from mypy.plugin import Plugin
class Dummy(Plugin): pass
def plugin(version): return Dummy
[case testDaemonRunRestartGlobs]
-- Ensure dmypy is not restarted if the configuration doesn't change and it contains globs
-- Note: Backslash path separator in output is replaced with forward slash so the same test succeeds on Windows as well
$ dmypy run -- foo --follow-imports=error
Daemon started
foo/lol.py:1: error: Name "fail" is not defined
Found 1 error in 1 file (checked 3 source files)
== Return code: 1
$ dmypy run -- foo --follow-imports=error
foo/lol.py:1: error: Name "fail" is not defined
Found 1 error in 1 file (checked 3 source files)
== Return code: 1
$ {python} -c "print('[mypy]')" >mypy.ini
$ {python} -c "print('ignore_errors=True')" >>mypy.ini
$ dmypy run -- foo --follow-imports=error
Restarting: configuration changed
Daemon stopped
Daemon started
Success: no issues found in 3 source files
$ dmypy stop
Daemon stopped
[file mypy.ini]
\[mypy]
ignore_errors = True
\[mypy-*.lol]
ignore_errors = False
[file foo/__init__.py]
[file foo/lol.py]
fail
[file foo/ok.py]
a: int = 1
[case testDaemonStatusKillRestartRecheck]
$ dmypy status
No status file found
== Return code: 2
$ dmypy stop
No status file found
== Return code: 2
$ dmypy kill
No status file found
== Return code: 2
$ dmypy recheck
No status file found
== Return code: 2
$ dmypy start -- --follow-imports=error --no-error-summary
Daemon started
$ dmypy status
Daemon is up and running
$ dmypy start
Daemon is still alive
== Return code: 2
$ dmypy restart -- --follow-imports=error --no-error-summary
Daemon stopped
Daemon started
$ dmypy stop
Daemon stopped
$ dmypy status
No status file found
== Return code: 2
$ dmypy restart -- --follow-imports=error --no-error-summary
Daemon started
$ dmypy recheck
Command 'recheck' is only valid after a 'check' command
== Return code: 2
$ dmypy kill
Daemon killed
$ dmypy status
Daemon has died
== Return code: 2
[case testDaemonRecheck]
$ dmypy start -- --follow-imports=error --no-error-summary
Daemon started
$ dmypy check foo.py bar.py
$ dmypy recheck
$ dmypy recheck --update foo.py --remove bar.py sir_not_appearing_in_this_film.py
foo.py:1: error: Import of "bar" ignored [misc]
foo.py:1: note: (Using --follow-imports=error, module not passed on command line)
== Return code: 1
$ dmypy recheck --update bar.py
$ dmypy recheck --update sir_not_appearing_in_this_film.py
$ dmypy recheck --update --remove
$ dmypy stop
Daemon stopped
[file foo.py]
import bar
[file bar.py]
pass
[case testDaemonTimeout]
$ dmypy start --timeout 1 -- --follow-imports=error
Daemon started
$ {python} -c "import time;time.sleep(1)"
$ dmypy status
No status file found
== Return code: 2
[case testDaemonRunNoTarget]
$ dmypy run -- --follow-imports=error
Daemon started
mypy-daemon: error: Missing target module, package, files, or command.
== Return code: 2
$ dmypy stop
Daemon stopped
[case testDaemonWarningSuccessExitCode-posix]
$ dmypy run -- foo.py --follow-imports=error --python-version=3.11
Daemon started
foo.py:2: note: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs
Success: no issues found in 1 source file
$ echo $?
0
$ dmypy stop
Daemon stopped
[file foo.py]
def foo():
a: int = 1
print(a + "2")
-- this is carefully constructed to be able to break if the quickstart system lets
-- something through incorrectly. in particular, the files need to have the same size
[case testDaemonQuickstart]
$ {python} -c "print('x=1')" >foo.py
$ {python} -c "print('x=1')" >bar.py
$ mypy --local-partial-types --cache-fine-grained --follow-imports=error --no-sqlite-cache --python-version=3.11 -- foo.py bar.py
Success: no issues found in 2 source files
$ {python} -c "import shutil; shutil.copy('.mypy_cache/3.11/bar.meta.json', 'asdf.json')"
-- update bar's timestamp but don't change the file
$ {python} -c "import time;time.sleep(1)"
$ {python} -c "print('x=1')" >bar.py
$ dmypy run -- foo.py bar.py --follow-imports=error --use-fine-grained-cache --no-sqlite-cache --python-version=3.11
Daemon started
Success: no issues found in 2 source files
$ dmypy status --fswatcher-dump-file test.json
Daemon is up and running
$ dmypy stop
Daemon stopped
-- copy the original bar cache file back so that the mtime mismatches
$ {python} -c "import shutil; shutil.copy('asdf.json', '.mypy_cache/3.11/bar.meta.json')"
-- sleep guarantees timestamp changes
$ {python} -c "import time;time.sleep(1)"
$ {python} -c "print('lol')" >foo.py
$ dmypy run --log-file=log -- foo.py bar.py --follow-imports=error --use-fine-grained-cache --no-sqlite-cache --python-version=3.11 --quickstart-file test.json
Daemon started
foo.py:1: error: Name "lol" is not defined
Found 1 error in 1 file (checked 2 source files)
== Return code: 1
-- make sure no errors made it to the log file
$ {python} -c "import sys; sys.stdout.write(open('log').read())"
-- make sure the meta file didn't get updated. we use this as an imperfect proxy for
-- whether the source file got rehashed, which we don't want it to have been.
$ {python} -c "x = open('.mypy_cache/3.11/bar.meta.json').read(); y = open('asdf.json').read(); assert x == y"
[case testDaemonSuggest]
$ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary
Daemon started
$ dmypy suggest foo:foo
Command 'suggest' is only valid after a 'check' command (that produces no parse errors)
== Return code: 2
$ dmypy check foo.py bar.py
$ dmypy suggest foo.bar
Unknown function foo.bar
== Return code: 2
$ dmypy suggest foo.var
Object foo.var is not a function
== Return code: 2
$ dmypy suggest foo.Foo.var
Unknown class foo.Foo
== Return code: 2
$ dmypy suggest foo.Bar.baz
Unknown method foo.Bar.baz
== Return code: 2
$ dmypy suggest foo.foo.baz
Object foo.foo is not a class
== Return code: 2
$ dmypy suggest --callsites foo.foo
bar.py:3: (str)
bar.py:4: (arg=str)
$ dmypy suggest foo.foo
(str) -> int
$ {python} -c "import shutil; shutil.copy('foo2.py', 'foo.py')"
$ dmypy check foo.py bar.py
bar.py:3: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]
== Return code: 1
[file foo.py]
def foo(arg):
return 12
class Bar:
def bar(self): pass
var = 0
[file foo2.py]
def foo(arg: str) -> int:
return 12
class Bar:
def bar(self) -> None: pass
var = 0
[file bar.py]
from foo import foo
def bar() -> None:
x = foo('abc') # type: str
foo(arg='xyz')
[case testDaemonGetType]
$ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary --python-version 3.8
Daemon started
$ dmypy inspect foo:1:2:3:4
Command "inspect" is only valid after a "check" command (that produces no parse errors)
== Return code: 2
$ dmypy check foo.py --export-types
foo.py:3: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
== Return code: 1
$ dmypy inspect foo:1
Format should be file:line:column[:end_line:end_column]
== Return code: 2
$ dmypy inspect foo.py:1:2:a:b
invalid literal for int() with base 10: 'a'
== Return code: 2
$ dmypy inspect foo.pyc:1:1:2:2
Source file is not a Python file
== Return code: 2
$ dmypy inspect bar/baz.py:1:1:2:2
Unknown module: baz
== Return code: 1
$ dmypy inspect foo.py:3:1:1:1
"end_line" must not be before "line"
== Return code: 2
$ dmypy inspect foo.py:3:3:3:1
"end_column" must be after "column"
== Return code: 2
$ dmypy inspect foo.py:3:10:3:17
"str"
$ dmypy inspect foo.py:3:10:3:17 -vv
"builtins.str"
$ dmypy inspect foo.py:9:9:9:11
"int"
$ dmypy inspect foo.py:11:1:11:3
"Callable[[Optional[int]], None]"
$ dmypy inspect foo.py:11:1:13:1
"None"
$ dmypy inspect foo.py:1:2:3:4
Can't find expression at span 1:2:3:4
== Return code: 1
$ dmypy inspect foo.py:17:5:17:5
No known type available for "NameExpr" (maybe unreachable or try --force-reload)
== Return code: 1
[file foo.py]
from typing import Optional
x: int = "no way" # line 3
def foo(arg: Optional[int] = None) -> None:
if arg is None:
arg
else:
arg # line 9
foo(
# multiline
)
def unreachable(x: int) -> None:
return
x # line 17
[case testDaemonGetTypeInexact]
$ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary
Daemon started
$ dmypy check foo.py --export-types
$ dmypy inspect foo.py:1:a
invalid literal for int() with base 10: 'a'
== Return code: 2
$ dmypy inspect foo.pyc:1:2
Source file is not a Python file
== Return code: 2
$ dmypy inspect bar/baz.py:1:2
Unknown module: baz
== Return code: 1
$ dmypy inspect foo.py:7:5 --include-span
7:5:7:5 -> "int"
7:5:7:11 -> "int"
7:1:7:12 -> "None"
$ dmypy inspect foo.py:7:5 --include-kind
NameExpr -> "int"
OpExpr -> "int"
CallExpr -> "None"
$ dmypy inspect foo.py:7:5 --include-span --include-kind
NameExpr:7:5:7:5 -> "int"
OpExpr:7:5:7:11 -> "int"
CallExpr:7:1:7:12 -> "None"
$ dmypy inspect foo.py:7:5 -vv
"builtins.int"
"builtins.int"
"None"
$ dmypy inspect foo.py:7:5 -vv --limit=1
"builtins.int"
$ dmypy inspect foo.py:7:3
"Callable[[int], None]"
"None"
$ dmypy inspect foo.py:1:2
Can't find any expressions at position 1:2
== Return code: 1
$ dmypy inspect foo.py:11:5 --force-reload
No known type available for "NameExpr" (maybe unreachable)
No known type available for "OpExpr" (maybe unreachable)
== Return code: 1
[file foo.py]
from typing import Optional
def foo(x: int) -> None: ...
a: int
b: int
foo(a and b) # line 7
def unreachable(x: int, y: int) -> None:
return
x and y # line 11
[case testDaemonGetAttrs]
$ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary
Daemon started
$ dmypy check foo.py bar.py --export-types
$ dmypy inspect foo.py:9:1 --show attrs --include-span --include-kind -vv
NameExpr:9:1:9:1 -> {"foo.C": ["a", "x", "y"], "foo.B": ["a", "b"]}
$ dmypy inspect foo.py:11:10 --show attrs
No known type available for "StrExpr" (maybe unreachable or try --force-reload)
== Return code: 1
$ dmypy inspect foo.py:1:1 --show attrs
Can't find any expressions at position 1:1
== Return code: 1
$ dmypy inspect --show attrs bar.py:10:1
{"A": ["z"], "B": ["z"]}
$ dmypy inspect --show attrs bar.py:10:1 --union-attrs
{"A": ["x", "z"], "B": ["y", "z"]}
[file foo.py]
class B:
def b(self) -> int: return 0
a: int
class C(B):
a: int
y: int
def x(self) -> int: return 0
v: C # line 9
if False:
"unreachable"
[file bar.py]
from typing import Union
class A:
x: int
z: int
class B:
y: int
z: int
var: Union[A, B]
var # line 10
[case testDaemonGetDefinition]
$ dmypy start --log-file log.txt -- --follow-imports=error --no-error-summary
Daemon started
$ dmypy check foo.py bar/baz.py bar/__init__.py --export-types
$ dmypy inspect foo.py:5:1 --show definition
foo.py:4:1:y
$ dmypy inspect foo.py:2:3 --show definition --include-span --include-kind -vv
MemberExpr:2:1:2:7 -> bar/baz.py:3:5:Alias
$ dmypy inspect foo.py:3:1 --show definition
Cannot find definition for "NameExpr" at 3:1:3:1
== Return code: 1
$ dmypy inspect foo.py:4:6 --show definition
No name or member expressions at 4:6
== Return code: 1
$ dmypy inspect foo.py:7:1:7:6 --show definition
bar/baz.py:4:5:attr
$ dmypy inspect foo.py:10:10 --show definition --include-span
10:1:10:12 -> bar/baz.py:6:1:test
$ dmypy inspect foo.py:14:6 --show definition --include-span --include-kind
NameExpr:14:5:14:7 -> foo.py:13:9:arg
MemberExpr:14:5:14:9 -> bar/baz.py:9:5:x, bar/baz.py:11:5:x
[file foo.py]
from bar.baz import A, B, C
C.Alias
x # type: ignore
y = 42
y # line 5
z = C()
z.attr
import bar
bar.baz.test() # line 10
from typing import Union
def foo(arg: Union[A, B]) -> None:
arg.x
[file bar/__init__.py]
[file bar/baz.py]
from typing import Union
class C:
Alias = Union[int, str]
attr = 42
def test() -> None: ... # line 6
class A:
x: int
class B:
x: int