@@ -144,9 +144,9 @@ def __invert__(self: _T) -> _T: pass
144144"""
145145
146146
147- def run_stubtest (
147+ def run_stubtest_with_stderr (
148148 stub : str , runtime : str , options : list [str ], config_file : str | None = None
149- ) -> str :
149+ ) -> tuple [ str , str ] :
150150 with use_tmp_dir (TEST_MODULE_NAME ) as tmp_dir :
151151 with open ("builtins.pyi" , "w" ) as f :
152152 f .write (stubtest_builtins_stub )
@@ -163,13 +163,26 @@ def run_stubtest(
163163 f .write (config_file )
164164 options = options + ["--mypy-config-file" , f"{ TEST_MODULE_NAME } _config.ini" ]
165165 output = io .StringIO ()
166- with contextlib .redirect_stdout (output ):
166+ outerr = io .StringIO ()
167+ with contextlib .redirect_stdout (output ), contextlib .redirect_stderr (outerr ):
167168 test_stubs (parse_options ([TEST_MODULE_NAME ] + options ), use_builtins_fixtures = True )
168- return remove_color_code (
169- output .getvalue ()
170- # remove cwd as it's not available from outside
171- .replace (os .path .realpath (tmp_dir ) + os .sep , "" ).replace (tmp_dir + os .sep , "" )
172- )
169+ filtered_output = remove_color_code (
170+ output .getvalue ()
171+ # remove cwd as it's not available from outside
172+ .replace (os .path .realpath (tmp_dir ) + os .sep , "" ).replace (tmp_dir + os .sep , "" )
173+ )
174+ filtered_outerr = remove_color_code (
175+ outerr .getvalue ()
176+ # remove cwd as it's not available from outside
177+ .replace (os .path .realpath (tmp_dir ) + os .sep , "" ).replace (tmp_dir + os .sep , "" )
178+ )
179+ return filtered_output , filtered_outerr
180+
181+
182+ def run_stubtest (
183+ stub : str , runtime : str , options : list [str ], config_file : str | None = None
184+ ) -> str :
185+ return run_stubtest_with_stderr (stub , runtime , options , config_file )[0 ]
173186
174187
175188class Case :
@@ -893,6 +906,106 @@ class FineAndDandy:
893906 error = None ,
894907 )
895908
909+ @collect_cases
910+ def test_cached_property (self ) -> Iterator [Case ]:
911+ yield Case (
912+ stub = """
913+ from functools import cached_property
914+ class Good:
915+ @cached_property
916+ def read_only_attr(self) -> int: ...
917+ @cached_property
918+ def read_only_attr2(self) -> int: ...
919+ """ ,
920+ runtime = """
921+ import functools as ft
922+ from functools import cached_property
923+ class Good:
924+ @cached_property
925+ def read_only_attr(self): return 1
926+ @ft.cached_property
927+ def read_only_attr2(self): return 1
928+ """ ,
929+ error = None ,
930+ )
931+ yield Case (
932+ stub = """
933+ from functools import cached_property
934+ class Bad:
935+ @cached_property
936+ def f(self) -> int: ...
937+ """ ,
938+ runtime = """
939+ class Bad:
940+ def f(self) -> int: return 1
941+ """ ,
942+ error = "Bad.f" ,
943+ )
944+ yield Case (
945+ stub = """
946+ from functools import cached_property
947+ class GoodCachedAttr:
948+ @cached_property
949+ def f(self) -> int: ...
950+ """ ,
951+ runtime = """
952+ class GoodCachedAttr:
953+ f = 1
954+ """ ,
955+ error = None ,
956+ )
957+ yield Case (
958+ stub = """
959+ from functools import cached_property
960+ class BadCachedAttr:
961+ @cached_property
962+ def f(self) -> str: ...
963+ """ ,
964+ runtime = """
965+ class BadCachedAttr:
966+ f = 1
967+ """ ,
968+ error = "BadCachedAttr.f" ,
969+ )
970+ yield Case (
971+ stub = """
972+ from functools import cached_property
973+ from typing import final
974+ class FinalGood:
975+ @cached_property
976+ @final
977+ def attr(self) -> int: ...
978+ """ ,
979+ runtime = """
980+ from functools import cached_property
981+ from typing import final
982+ class FinalGood:
983+ @cached_property
984+ @final
985+ def attr(self):
986+ return 1
987+ """ ,
988+ error = None ,
989+ )
990+ yield Case (
991+ stub = """
992+ from functools import cached_property
993+ class FinalBad:
994+ @cached_property
995+ def attr(self) -> int: ...
996+ """ ,
997+ runtime = """
998+ from functools import cached_property
999+ from typing_extensions import final
1000+ class FinalBad:
1001+ @cached_property
1002+ @final
1003+ def attr(self):
1004+ return 1
1005+ """ ,
1006+ error = "FinalBad.attr" ,
1007+ )
1008+
8961009 @collect_cases
8971010 def test_var (self ) -> Iterator [Case ]:
8981011 yield Case (stub = "x1: int" , runtime = "x1 = 5" , error = None )
@@ -2490,6 +2603,33 @@ def test_config_file_error_codes(self) -> None:
24902603 output = run_stubtest (stub = stub , runtime = runtime , options = [], config_file = config_file )
24912604 assert output == "Success: no issues found in 1 module\n "
24922605
2606+ def test_config_file_error_codes_invalid (self ) -> None :
2607+ runtime = "temp = 5\n "
2608+ stub = "temp: int\n "
2609+ config_file = "[mypy]\n disable_error_code = not-a-valid-name\n "
2610+ output , outerr = run_stubtest_with_stderr (
2611+ stub = stub , runtime = runtime , options = [], config_file = config_file
2612+ )
2613+ assert output == "Success: no issues found in 1 module\n "
2614+ assert outerr == (
2615+ "test_module_config.ini: [mypy]: disable_error_code: "
2616+ "Invalid error code(s): not-a-valid-name\n "
2617+ )
2618+
2619+ def test_config_file_wrong_incomplete_feature (self ) -> None :
2620+ runtime = "x = 1\n "
2621+ stub = "x: int\n "
2622+ config_file = "[mypy]\n enable_incomplete_feature = Unpack\n "
2623+ output = run_stubtest (stub = stub , runtime = runtime , options = [], config_file = config_file )
2624+ assert output == (
2625+ "warning: Warning: Unpack is already enabled by default\n "
2626+ "Success: no issues found in 1 module\n "
2627+ )
2628+
2629+ config_file = "[mypy]\n enable_incomplete_feature = not-a-valid-name\n "
2630+ with self .assertRaises (SystemExit ):
2631+ run_stubtest (stub = stub , runtime = runtime , options = [], config_file = config_file )
2632+
24932633 def test_no_modules (self ) -> None :
24942634 output = io .StringIO ()
24952635 with contextlib .redirect_stdout (output ):
0 commit comments