pytest-revealtype-injector 0.7.1__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/PKG-INFO +20 -5
  2. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/README.md +19 -4
  3. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/__init__.py +1 -1
  4. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/adapter/mypy_.py +6 -1
  5. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/adapter/pyrefly_.py +30 -11
  6. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/adapter/pyright_.py +10 -6
  7. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/adapter/ty_.py +6 -5
  8. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/hooks.py +28 -5
  9. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/main.py +6 -1
  10. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/models.py +6 -1
  11. pytest_revealtype_injector-0.9.0/tests/test_marker.py +217 -0
  12. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/tests/test_options.py +1 -1
  13. pytest_revealtype_injector-0.7.1/tests/test_marker.py +0 -109
  14. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/.gitignore +0 -0
  15. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/COPYING.mit +0 -0
  16. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/pyproject.toml +0 -0
  17. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/adapter/__init__.py +0 -0
  18. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/adapter/basedpyright_.py +0 -0
  19. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/log.py +0 -0
  20. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/plugin.py +0 -0
  21. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/src/pytest_revealtype_injector/py.typed +0 -0
  22. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/tests/conftest.py +0 -0
  23. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/tests/test_ast_mode.py +0 -0
  24. {pytest_revealtype_injector-0.7.1 → pytest_revealtype_injector-0.9.0}/tests/test_import.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-revealtype-injector
3
- Version: 0.7.1
3
+ Version: 0.9.0
4
4
  Summary: Pytest plugin for replacing reveal_type() calls inside test functions with static and runtime type checking result comparison, for confirming type annotation validity.
5
5
  Project-URL: homepage, https://github.com/abelcheung/pytest-revealtype-injector
6
6
  Author-email: Abel Cheung <abelcheung@gmail.com>
@@ -34,12 +34,17 @@ Description-Content-Type: text/markdown
34
34
 
35
35
  `pytest-revealtype-injector` is a `pytest` plugin for replacing [`reveal_type()`](https://docs.python.org/3/library/typing.html#typing.reveal_type) calls inside test functions as something more sophisticated. It does the following tasks in parallel:
36
36
 
37
- - Launch external static type checkers (`basesdpyright`, `pyright` and `mypy`) and store `reveal_type` results.
37
+ - Launch external static type checkers and store `reveal_type` results.
38
38
  - Use [`typeguard`](https://github.com/agronholm/typeguard) to verify the aforementioned static type checker result _really_ matches runtime code result.
39
39
 
40
40
  ## Usage
41
41
 
42
- In short: install this plugin, create test functions which calls `reveal_type()` with variable or function return result, done.
42
+ TL;DR:
43
+
44
+ 1. Install this plugin
45
+ 2. Install type checkers: `basedpyright`, `mypy`, `pyrefly`, `pyright`, `ty`
46
+ - Disable any unwanted with `--revealtype-disable-adapter=<ADAPTER>` pytest CLI option
47
+ 3. Create `pytest` functions which call `reveal_type()` with variable or function return result
43
48
 
44
49
  ### The longer story
45
50
 
@@ -78,9 +83,11 @@ import typing as typ # or...
78
83
  from typing import reveal_type as rt
79
84
  ```
80
85
 
86
+ To supply config file specific for certain type checker, use `--revealtype-<ADAPTER>-config=<FILE>` pytest CLI option. For example, `--revealtype-pyrefly-config=tests/pyrefly.toml` instructs pyrefly to use `pyrefly.toml` under `tests` folder to override project root config.
87
+
81
88
  ### Limitations
82
89
 
83
- But there are 3 caveats.
90
+ There are 3 caveats.
84
91
 
85
92
  1. This plugin only searches for global import in test files, so local import inside test function doesn't work. That means following code doesn't utilize this plugin at all:
86
93
 
@@ -125,7 +132,15 @@ Module level:
125
132
  pytestmark = pytest.mark.notypechecker("basedpyright", "pyright")
126
133
  ```
127
134
 
128
- Note that disabling all type checkers is disallowed, and such tests would be treated as `pytest.fail`. Disable the `reveal_type()` call instead.
135
+ Conversely, it is possible to only turn on usage of specific type checkers with `onlytypechecker` marker and exclude all others:
136
+
137
+ ```python
138
+ @pytest.mark.onlytypechecker("mypy")
139
+ def test_for_mypy() -> None:
140
+ ......
141
+ ```
142
+
143
+ Note that disabling all type checkers is disallowed, and such tests would be treated as failure. Disable the `reveal_type()` call instead.
129
144
 
130
145
  ## Logging
131
146
 
@@ -5,12 +5,17 @@
5
5
 
6
6
  `pytest-revealtype-injector` is a `pytest` plugin for replacing [`reveal_type()`](https://docs.python.org/3/library/typing.html#typing.reveal_type) calls inside test functions as something more sophisticated. It does the following tasks in parallel:
7
7
 
8
- - Launch external static type checkers (`basesdpyright`, `pyright` and `mypy`) and store `reveal_type` results.
8
+ - Launch external static type checkers and store `reveal_type` results.
9
9
  - Use [`typeguard`](https://github.com/agronholm/typeguard) to verify the aforementioned static type checker result _really_ matches runtime code result.
10
10
 
11
11
  ## Usage
12
12
 
13
- In short: install this plugin, create test functions which calls `reveal_type()` with variable or function return result, done.
13
+ TL;DR:
14
+
15
+ 1. Install this plugin
16
+ 2. Install type checkers: `basedpyright`, `mypy`, `pyrefly`, `pyright`, `ty`
17
+ - Disable any unwanted with `--revealtype-disable-adapter=<ADAPTER>` pytest CLI option
18
+ 3. Create `pytest` functions which call `reveal_type()` with variable or function return result
14
19
 
15
20
  ### The longer story
16
21
 
@@ -49,9 +54,11 @@ import typing as typ # or...
49
54
  from typing import reveal_type as rt
50
55
  ```
51
56
 
57
+ To supply config file specific for certain type checker, use `--revealtype-<ADAPTER>-config=<FILE>` pytest CLI option. For example, `--revealtype-pyrefly-config=tests/pyrefly.toml` instructs pyrefly to use `pyrefly.toml` under `tests` folder to override project root config.
58
+
52
59
  ### Limitations
53
60
 
54
- But there are 3 caveats.
61
+ There are 3 caveats.
55
62
 
56
63
  1. This plugin only searches for global import in test files, so local import inside test function doesn't work. That means following code doesn't utilize this plugin at all:
57
64
 
@@ -96,7 +103,15 @@ Module level:
96
103
  pytestmark = pytest.mark.notypechecker("basedpyright", "pyright")
97
104
  ```
98
105
 
99
- Note that disabling all type checkers is disallowed, and such tests would be treated as `pytest.fail`. Disable the `reveal_type()` call instead.
106
+ Conversely, it is possible to only turn on usage of specific type checkers with `onlytypechecker` marker and exclude all others:
107
+
108
+ ```python
109
+ @pytest.mark.onlytypechecker("mypy")
110
+ def test_for_mypy() -> None:
111
+ ......
112
+ ```
113
+
114
+ Note that disabling all type checkers is disallowed, and such tests would be treated as failure. Disable the `reveal_type()` call instead.
100
115
 
101
116
  ## Logging
102
117
 
@@ -1,3 +1,3 @@
1
1
  """Pytest plugin for replacing reveal_type() calls inside test functions with static and runtime type checking result comparison, for confirming type annotation validity.""" # noqa: E501
2
2
 
3
- __version__ = "0.7.1"
3
+ __version__ = "0.9.0"
@@ -5,11 +5,11 @@ import importlib
5
5
  import json
6
6
  import pathlib
7
7
  import re
8
+ import sys
8
9
  from collections.abc import (
9
10
  Iterable,
10
11
  )
11
12
  from typing import (
12
- ForwardRef,
13
13
  Literal,
14
14
  TypedDict,
15
15
  cast,
@@ -27,6 +27,11 @@ from ..models import (
27
27
  VarType,
28
28
  )
29
29
 
30
+ if sys.version_info >= (3, 14):
31
+ from annotationlib import ForwardRef
32
+ else:
33
+ from typing import ForwardRef
34
+
30
35
  _logger = get_logger()
31
36
 
32
37
 
@@ -8,12 +8,11 @@ import shutil
8
8
  import subprocess
9
9
  import sys
10
10
  from collections.abc import Iterable
11
- from typing import ForwardRef, Literal, TypedDict, cast
12
-
13
- if sys.version_info >= (3, 11):
14
- from typing import TypedDict
15
- else:
16
- from typing_extensions import TypedDict
11
+ from typing import (
12
+ Literal,
13
+ TypedDict,
14
+ cast,
15
+ )
17
16
 
18
17
  import schema as s
19
18
 
@@ -26,6 +25,16 @@ from ..models import (
26
25
  VarType,
27
26
  )
28
27
 
28
+ if sys.version_info >= (3, 11):
29
+ from typing import TypedDict
30
+ else:
31
+ from typing_extensions import TypedDict
32
+
33
+ if sys.version_info >= (3, 14):
34
+ from annotationlib import ForwardRef
35
+ else:
36
+ from typing import ForwardRef
37
+
29
38
  _logger = get_logger()
30
39
 
31
40
 
@@ -53,15 +62,16 @@ class NameCollector(BareNameCollector):
53
62
  # resolves to a pytest function or method
54
63
  return ast.Name(id=node.attr, ctx=node.ctx)
55
64
 
65
+
56
66
  class PyreflyAdapter(TypeCheckerAdapter):
57
67
  id = "pyrefly"
58
68
  _executable = "pyrefly"
59
- _type_mesg_re = re.compile(r'revealed type: (?P<type>.+)')
69
+ _type_mesg_re = re.compile(r"revealed type: (?P<type>.+)")
60
70
  _namecollector_class = NameCollector
61
71
  _schema = s.Schema({
62
72
  "line": int,
63
73
  "column": int,
64
- "stop_line": int,
74
+ "stop_line": int,
65
75
  "stop_column": int,
66
76
  "path": str,
67
77
  "code": int,
@@ -90,13 +100,22 @@ class PyreflyAdapter(TypeCheckerAdapter):
90
100
  # of exit status
91
101
  if proc.returncode > 0:
92
102
  raise TypeCheckerError(
93
- "{} error with exit code {}: {}".format(self.id, proc.returncode, proc.stderr.decode()), None, None)
103
+ "{} error with exit code {}: {}".format(
104
+ self.id, proc.returncode, proc.stderr.decode()
105
+ ),
106
+ None,
107
+ None,
108
+ )
94
109
 
95
110
  try:
96
111
  report = json.loads(proc.stdout)
97
112
  except Exception as e:
98
- raise TypeCheckerError(f"Failed to parse pyrefly JSON output: {e}", None, None) from e
99
-
113
+ # pyrefly (circa 0.47.0) appends github text formatted annotation at the end of json output
114
+ decoder = json.JSONDecoder()
115
+ report, _ = decoder.raw_decode(proc.stdout.decode())
116
+ _logger.warning(
117
+ f"({self.id}) failed to parse json output, extracted partial json: {e}"
118
+ )
100
119
  assert isinstance(report, dict) and "errors" in report
101
120
  items = cast(list[_PyreflyDiagItem], report["errors"])
102
121
 
@@ -10,16 +10,10 @@ from collections.abc import (
10
10
  Iterable,
11
11
  )
12
12
  from typing import (
13
- ForwardRef,
14
13
  Literal,
15
14
  cast,
16
15
  )
17
16
 
18
- if sys.version_info >= (3, 11):
19
- from typing import NotRequired, TypedDict
20
- else:
21
- from typing_extensions import NotRequired, TypedDict
22
-
23
17
  import schema as s
24
18
 
25
19
  from ..log import get_logger
@@ -31,6 +25,16 @@ from ..models import (
31
25
  VarType,
32
26
  )
33
27
 
28
+ if sys.version_info >= (3, 11):
29
+ from typing import NotRequired, TypedDict
30
+ else:
31
+ from typing_extensions import NotRequired, TypedDict
32
+
33
+ if sys.version_info >= (3, 14):
34
+ from annotationlib import ForwardRef
35
+ else:
36
+ from typing import ForwardRef
37
+
34
38
  _logger = get_logger()
35
39
 
36
40
 
@@ -63,7 +63,7 @@ class NameCollector(BareNameCollector):
63
63
  class TyAdapter(TypeCheckerAdapter):
64
64
  id = "ty"
65
65
  _executable = "ty"
66
- _type_mesg_re = re.compile(r'Revealed type: `(?P<type>.+?)`')
66
+ _type_mesg_re = re.compile(r"Revealed type: `(?P<type>.+?)`")
67
67
  _namecollector_class = BareNameCollector
68
68
  _schema = s.Schema({
69
69
  "check_name": str,
@@ -78,8 +78,8 @@ class TyAdapter(TypeCheckerAdapter):
78
78
  "path": str,
79
79
  "positions": {
80
80
  "begin": {"line": int, "column": int},
81
- "end" : {"line": int, "column": int},
82
- }
81
+ "end": {"line": int, "column": int},
82
+ },
83
83
  },
84
84
  })
85
85
 
@@ -152,11 +152,12 @@ class TyAdapter(TypeCheckerAdapter):
152
152
  filename, lineno = (
153
153
  pathlib.Path(diag["location"]["path"]).name,
154
154
  diag["location"]["positions"]["begin"]["line"],
155
- )
155
+ )
156
156
  if (m := self._type_mesg_re.search(diag["description"])) is None:
157
157
  continue
158
158
  pos = FilePos(filename, lineno)
159
159
  self.typechecker_result[pos] = VarType(None, ForwardRef(m["type"]))
160
160
 
161
+
161
162
  def generate_adapter() -> TypeCheckerAdapter:
162
- return TyAdapter()
163
+ return TyAdapter()
@@ -20,20 +20,36 @@ def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> Iterator[None]:
20
20
  assert pyfuncitem.module is not None
21
21
  adp_stash = pyfuncitem.config.stash[adapter_stash_key]
22
22
 
23
- # Disable type checker based on marker
24
- mark = pyfuncitem.get_closest_marker("notypechecker")
25
- if mark:
26
- disabled_adapters = {a.id for a in adp_stash if a.id in mark.args}
23
+ # Marker-based adapter filtering
24
+ notype_mark = pyfuncitem.get_closest_marker("notypechecker")
25
+ only_mark = pyfuncitem.get_closest_marker("onlytypechecker")
26
+
27
+ if notype_mark and only_mark:
28
+ pytest.fail(
29
+ "Cannot use both 'notypechecker' and 'onlytypechecker' markers on the same test."
30
+ )
31
+
32
+ if only_mark:
33
+ enabled_adapters = {a.id for a in adp_stash if a.id in only_mark.args}
34
+ for a in enabled_adapters:
35
+ _logger.info(
36
+ f"{a} adapter enabled by 'onlytypechecker' marker in {pyfuncitem.name} test"
37
+ )
38
+ adapters = {a for a in adp_stash if a.id in enabled_adapters}
39
+
40
+ elif notype_mark:
41
+ disabled_adapters = {a.id for a in adp_stash if a.id in notype_mark.args}
27
42
  for a in disabled_adapters:
28
43
  _logger.info(
29
44
  f"{a} adapter disabled by 'notypechecker' marker in {pyfuncitem.name} test"
30
45
  )
31
46
  adapters = {a for a in adp_stash if a.id not in disabled_adapters}
47
+
32
48
  else:
33
49
  adapters = {a for a in adp_stash}
34
50
 
35
51
  if not adapters:
36
- pytest.fail("All type checkers have been disabled.")
52
+ pytest.skip("No type checker is enabled for this test.")
37
53
 
38
54
  # Monkeypatch reveal_type() with our own function, to guarantee
39
55
  # each test func can receive different adapters
@@ -139,3 +155,10 @@ def pytest_configure(config: pytest.Config) -> None:
139
155
  "specified type checker(s). Following type checkers are supported: "
140
156
  + ", ".join(all_ids),
141
157
  )
158
+ # Marker to enable only specified adapters for a test
159
+ config.addinivalue_line(
160
+ "markers",
161
+ "onlytypechecker(name, ...): mark reveal_type() test to enable "
162
+ "only the specified type checker(s). Following type checkers are supported: "
163
+ + ", ".join(all_ids),
164
+ )
@@ -4,7 +4,6 @@ import pathlib
4
4
  import sys
5
5
  from typing import (
6
6
  Any,
7
- ForwardRef,
8
7
  TypeVar,
9
8
  )
10
9
 
@@ -22,6 +21,12 @@ from .models import (
22
21
  VarType,
23
22
  )
24
23
 
24
+ if sys.version_info >= (3, 14):
25
+ from annotationlib import ForwardRef
26
+ else:
27
+ from typing import ForwardRef
28
+
29
+
25
30
  _T = TypeVar("_T")
26
31
  _logger = log.get_logger()
27
32
 
@@ -5,11 +5,11 @@ import ast
5
5
  import importlib
6
6
  import pathlib
7
7
  import re
8
+ import sys
8
9
  from collections.abc import Iterable
9
10
  from typing import (
10
11
  Any,
11
12
  ClassVar,
12
- ForwardRef,
13
13
  NamedTuple,
14
14
  TypeVar,
15
15
  cast,
@@ -21,6 +21,11 @@ from schema import Schema
21
21
 
22
22
  from .log import get_logger
23
23
 
24
+ if sys.version_info >= (3, 14):
25
+ from annotationlib import ForwardRef
26
+ else:
27
+ from typing import ForwardRef
28
+
24
29
 
25
30
  class FilePos(NamedTuple):
26
31
  file: str
@@ -0,0 +1,217 @@
1
+ from __future__ import annotations
2
+
3
+ import textwrap
4
+
5
+ import pytest
6
+
7
+
8
+ class TestOnlyTypeCheckerMarker:
9
+ PYPROJECT_TOML = """
10
+ [tool.pyright]
11
+ reportUnreachable = false
12
+ """
13
+ TEST_CONTENT = """
14
+ import sys
15
+ import pytest
16
+ from typing import cast
17
+ if sys.version_info >= (3, 11):
18
+ from typing import reveal_type
19
+ else:
20
+ from typing_extensions import reveal_type
21
+
22
+ # GLOBAL MARK
23
+
24
+ def test_foo() -> None:
25
+ MYPY = False
26
+ if MYPY:
27
+ x = 1
28
+ else:
29
+ x = cast(str, 1) # pyright: ignore[reportInvalidCast]
30
+ reveal_type(x)
31
+
32
+ # CLASS MARK
33
+ class TestFoo:
34
+ # FUNC MARK
35
+ def test_foo(self) -> None:
36
+ MYPY = False
37
+ if MYPY:
38
+ x = 1
39
+ else:
40
+ x = cast(str, 1) # pyright: ignore[reportInvalidCast]
41
+ reveal_type(x)
42
+
43
+ def test_foo2(self) -> None:
44
+ MYPY = False
45
+ if MYPY:
46
+ x = 1
47
+ else:
48
+ x = cast(str, 1) # pyright: ignore[reportInvalidCast]
49
+ reveal_type(x)
50
+ """
51
+
52
+ def test_vanilla(self, pytester: pytest.Pytester) -> None:
53
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
54
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
55
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
56
+ self.TEST_CONTENT
57
+ )
58
+ result = pytester.runpytest("--tb=short", "-vv")
59
+ result.assert_outcomes(passed=0, failed=3)
60
+
61
+ def test_function_marker(self, pytester: pytest.Pytester) -> None:
62
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
63
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
64
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
65
+ self.TEST_CONTENT.replace(
66
+ "# FUNC MARK", "@pytest.mark.onlytypechecker('mypy')"
67
+ )
68
+ )
69
+ result = pytester.runpytest("--tb=short", "-vv")
70
+ result.assert_outcomes(passed=1, failed=2)
71
+
72
+ def test_class_marker(self, pytester: pytest.Pytester) -> None:
73
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
74
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
75
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
76
+ self.TEST_CONTENT.replace(
77
+ "# CLASS MARK", "@pytest.mark.onlytypechecker('mypy')"
78
+ )
79
+ )
80
+ result = pytester.runpytest("--tb=short", "-vv")
81
+ result.assert_outcomes(passed=2, failed=1)
82
+
83
+ def test_global_marker(self, pytester: pytest.Pytester) -> None:
84
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
85
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
86
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
87
+ self.TEST_CONTENT.replace(
88
+ "# GLOBAL MARK", "pytestmark = pytest.mark.onlytypechecker('mypy')"
89
+ )
90
+ )
91
+ result = pytester.runpytest("--tb=short", "-vv")
92
+ result.assert_outcomes(passed=3, failed=0)
93
+
94
+
95
+ class TestNoTypeCheckerMarker:
96
+ PYPROJECT_TOML = """
97
+ [tool.pyright]
98
+ reportUnreachable = false
99
+ """
100
+ TEST_CONTENT = """
101
+ import sys
102
+ import pytest
103
+ from typing import cast
104
+ if sys.version_info >= (3, 11):
105
+ from typing import reveal_type
106
+ else:
107
+ from typing_extensions import reveal_type
108
+
109
+ # GLOBAL MARK
110
+
111
+ def test_foo() -> None:
112
+ MYPY = False
113
+ if MYPY:
114
+ x = cast(str, 1) # pyright: ignore[reportInvalidCast]
115
+ else:
116
+ x = 1
117
+ reveal_type(x)
118
+
119
+ # CLASS MARK
120
+ class TestFoo:
121
+ # FUNC MARK
122
+ def test_foo(self) -> None:
123
+ MYPY = False
124
+ if MYPY:
125
+ x = cast(str, 1) # pyright: ignore[reportInvalidCast]
126
+ else:
127
+ x = 1
128
+ reveal_type(x)
129
+
130
+ def test_foo2(self) -> None:
131
+ MYPY = False
132
+ if MYPY:
133
+ x = cast(str, 1) # pyright: ignore[reportInvalidCast]
134
+ else:
135
+ x = 1
136
+ reveal_type(x)
137
+ """
138
+
139
+ def test_vanilla(self, pytester: pytest.Pytester) -> None:
140
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
141
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
142
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
143
+ self.TEST_CONTENT
144
+ )
145
+ result = pytester.runpytest("--tb=short", "-vv")
146
+ result.assert_outcomes(passed=0, failed=3)
147
+
148
+ def test_function_marker(self, pytester: pytest.Pytester) -> None:
149
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
150
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
151
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
152
+ self.TEST_CONTENT.replace(
153
+ "# FUNC MARK", "@pytest.mark.notypechecker('mypy')"
154
+ )
155
+ )
156
+ result = pytester.runpytest("--tb=short", "-vv")
157
+ result.assert_outcomes(passed=1, failed=2)
158
+
159
+ def test_class_marker(self, pytester: pytest.Pytester) -> None:
160
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
161
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
162
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
163
+ self.TEST_CONTENT.replace(
164
+ "# CLASS MARK", "@pytest.mark.notypechecker('mypy')"
165
+ )
166
+ )
167
+ result = pytester.runpytest("--tb=short", "-vv")
168
+ result.assert_outcomes(passed=2, failed=1)
169
+
170
+ def test_global_marker(self, pytester: pytest.Pytester) -> None:
171
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
172
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
173
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
174
+ self.TEST_CONTENT.replace(
175
+ "# GLOBAL MARK", "pytestmark = pytest.mark.notypechecker('mypy')"
176
+ )
177
+ )
178
+ result = pytester.runpytest("--tb=short", "-vv")
179
+ result.assert_outcomes(passed=3, failed=0)
180
+
181
+
182
+ class TestMarkerConflicts:
183
+ PYPROJECT_TOML = """
184
+ [tool.pyright]
185
+ reportUnreachable = false
186
+ """
187
+ TEST_CONTENT = """
188
+ import pytest
189
+
190
+ # PLACEHOLDER
191
+ def test_foo() -> None:
192
+ pass
193
+ """
194
+
195
+ def test_typechecker_exclusive(self, pytester: pytest.Pytester) -> None:
196
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
197
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
198
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
199
+ textwrap.dedent(self.TEST_CONTENT).replace(
200
+ "# PLACEHOLDER",
201
+ "@pytest.mark.notypechecker('mypy')\n"
202
+ "@pytest.mark.onlytypechecker('pyright')",
203
+ )
204
+ )
205
+ result = pytester.runpytest("--tb=short", "-vv")
206
+ assert result.ret == pytest.ExitCode.TESTS_FAILED
207
+ result.assert_outcomes(failed=1)
208
+
209
+ def test_no_typechecker_left(self, pytester: pytest.Pytester) -> None:
210
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
211
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
212
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
213
+ self.TEST_CONTENT.replace("# PLACEHOLDER", "@pytest.mark.onlytypechecker()")
214
+ )
215
+ result = pytester.runpytest("--tb=short", "-vv")
216
+ assert result.ret == pytest.ExitCode.OK
217
+ result.assert_outcomes(skipped=1)
@@ -81,7 +81,7 @@ class TestDisableTypeChecker:
81
81
  pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
82
82
  self.content_fail
83
83
  )
84
- opts = self._gen_pytest_opts(["basedpyright", "pyright"])
84
+ opts = self._gen_pytest_opts(["basedpyright", "pyright", "pyrefly", "ty"])
85
85
  result = pytester.runpytest(*opts)
86
86
  assert result.ret == pytest.ExitCode.INTERNAL_ERROR
87
87
  result.assert_outcomes(passed=0, failed=0)
@@ -1,109 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import pytest
4
-
5
-
6
- class TestFuncAndGlobalMarker:
7
- PYPROJECT_TOML = """
8
- [tool.pyright]
9
- reportUnreachable = false
10
- defineConstant = {"MYPY" = false}
11
-
12
- [tool.mypy]
13
- always_true = ["MYPY"]
14
- """
15
- TEST_CONTENT = """
16
- import sys
17
- import pytest
18
- from typing import cast
19
- if sys.version_info >= (3, 11):
20
- from typing import reveal_type
21
- else:
22
- from typing_extensions import reveal_type
23
-
24
- # pytestmark = pytest.mark.notypechecker('mypy')
25
-
26
- # @pytest.mark.notypechecker('mypy')
27
- def test_foo() -> None:
28
- MYPY = False
29
- if MYPY:
30
- x = cast(str, 1) # type: ignore[assignment]
31
- else:
32
- x = 1
33
- reveal_type(x)
34
- """
35
-
36
- def test_vanilla(self, pytester: pytest.Pytester) -> None:
37
- pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
38
- pytester.makepyprojecttoml(self.PYPROJECT_TOML)
39
- pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
40
- self.TEST_CONTENT
41
- )
42
- result = pytester.runpytest("--tb=short", "-vv")
43
- result.assert_outcomes(passed=0, failed=1)
44
-
45
- def test_function_marker(self, pytester: pytest.Pytester) -> None:
46
- pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
47
- pytester.makepyprojecttoml(self.PYPROJECT_TOML)
48
- pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
49
- self.TEST_CONTENT.replace("# @pytest.mark", "@pytest.mark")
50
- )
51
- result = pytester.runpytest("--tb=short", "-vv")
52
- result.assert_outcomes(passed=1, failed=0)
53
-
54
- def test_global_marker(self, pytester: pytest.Pytester) -> None:
55
- pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
56
- pytester.makepyprojecttoml(self.PYPROJECT_TOML)
57
- pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
58
- self.TEST_CONTENT.replace("# pytestmark", "pytestmark")
59
- )
60
- result = pytester.runpytest("--tb=short", "-vv")
61
- result.assert_outcomes(passed=1, failed=0)
62
-
63
-
64
- class TestClassMarker:
65
- PYPROJECT_TOML = """
66
- [tool.pyright]
67
- reportUnreachable = false
68
- defineConstant = {"MYPY" = false}
69
-
70
- [tool.mypy]
71
- always_true = ["MYPY"]
72
- """
73
- TEST_CONTENT = """
74
- import sys
75
- import pytest
76
- from typing import cast
77
- if sys.version_info >= (3, 11):
78
- from typing import reveal_type
79
- else:
80
- from typing_extensions import reveal_type
81
-
82
- # @pytest.mark.notypechecker('pyright', 'basedpyright', 'ty')
83
- class TestFoo:
84
- def test_foo(self) -> None:
85
- MYPY = False
86
- if MYPY:
87
- x = 1
88
- else:
89
- x = cast(str, 1) # pyright: ignore[reportInvalidCast]
90
- reveal_type(x)
91
- """
92
-
93
- def test_vanilla(self, pytester: pytest.Pytester) -> None:
94
- pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
95
- pytester.makepyprojecttoml(self.PYPROJECT_TOML)
96
- pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
97
- self.TEST_CONTENT
98
- )
99
- result = pytester.runpytest("--tb=short", "-vv")
100
- result.assert_outcomes(passed=0, failed=1)
101
-
102
- def test_marker_applied(self, pytester: pytest.Pytester) -> None:
103
- pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
104
- pytester.makepyprojecttoml(self.PYPROJECT_TOML)
105
- pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
106
- self.TEST_CONTENT.replace("# @pytest.mark", "@pytest.mark")
107
- )
108
- result = pytester.runpytest("--tb=short", "-vv")
109
- result.assert_outcomes(passed=1, failed=0)