| -- 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 |