| 862 | |
| 863 | @contextmanager |
| 864 | def warns_here( |
| 865 | expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, |
| 866 | *, |
| 867 | match: str | re.Pattern[str] | None = None, |
| 868 | ) -> Generator[pytest.WarningsRecorder, None, None]: |
| 869 | frame = sys._getframe(2) |
| 870 | code = frame.f_code |
| 871 | first_line = frame.f_lineno |
| 872 | del frame |
| 873 | file_name = code.co_filename |
| 874 | function_lines = { |
| 875 | line for (_start, _end, line) in code.co_lines() if line is not None |
| 876 | } |
| 877 | del code |
| 878 | |
| 879 | with pytest.warns(expected_warning, match=match) as context: |
| 880 | yield context |
| 881 | |
| 882 | def matches(warning) -> bool: |
| 883 | if not isinstance(warning.message, expected_warning): |
| 884 | return False |
| 885 | if match is not None and not re.search(match, str(warning.message)): |
| 886 | return False |
| 887 | if warning.filename != file_name: |
| 888 | return False |
| 889 | if warning.lineno < first_line: |
| 890 | return False |
| 891 | if warning.lineno not in function_lines: |
| 892 | return False |
| 893 | return True |
| 894 | |
| 895 | if not any(matches(warning) for warning in context): |
| 896 | raise AssertionError( |
| 897 | "No matched warning caused by expected source line.\n" |
| 898 | "All warnings:\n" |
| 899 | + "\n".join(f" {warning}" for warning in context) |
| 900 | + f"\nExpected: {file_name!r}, line in " |
| 901 | f"{list(line for line in function_lines if line >= first_line)}" |
| 902 | ) |
| 903 | |
| 904 | |
| 905 | def deprecated_call_here( |