|
13 | 13 | import warnings |
14 | 14 | from functools import lru_cache |
15 | 15 | from pathlib import Path |
| 16 | +from types import FunctionType |
16 | 17 | from types import TracebackType |
17 | 18 | from typing import Any |
18 | 19 | from typing import Callable |
|
35 | 36 | from pluggy import HookimplMarker |
36 | 37 | from pluggy import HookspecMarker |
37 | 38 | from pluggy import PluginManager |
| 39 | +from typing_extensions import Literal |
38 | 40 |
|
39 | 41 | import _pytest._code |
40 | 42 | import _pytest.deprecated |
@@ -330,6 +332,32 @@ def _prepareconfig( |
330 | 332 | raise |
331 | 333 |
|
332 | 334 |
|
| 335 | +def _get_legacy_hook_marks( |
| 336 | + method: FunctionType, |
| 337 | + hook_type: Literal["spec", "impl"], |
| 338 | + opt_names: Tuple[str, ...], |
| 339 | +) -> Dict[str, bool]: |
| 340 | + known_marks = {m.name for m in getattr(method, "pytestmark", [])} |
| 341 | + must_warn = False |
| 342 | + opts = {} |
| 343 | + for opt_name in opt_names: |
| 344 | + if hasattr(method, opt_name) or opt_name in known_marks: |
| 345 | + opts[opt_name] = True |
| 346 | + must_warn = True |
| 347 | + else: |
| 348 | + opts[opt_name] = False |
| 349 | + if must_warn: |
| 350 | + |
| 351 | + hook_opts = ", ".join(f"{name}=True" for name, val in opts.items() if val) |
| 352 | + message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( |
| 353 | + type=hook_type, |
| 354 | + fullname=method.__qualname__, |
| 355 | + hook_opts=hook_opts, |
| 356 | + ) |
| 357 | + warn_explicit_for(method, message) |
| 358 | + return opts |
| 359 | + |
| 360 | + |
333 | 361 | @final |
334 | 362 | class PytestPluginManager(PluginManager): |
335 | 363 | """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with |
@@ -402,39 +430,20 @@ def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str): |
402 | 430 | if not inspect.isroutine(method): |
403 | 431 | return |
404 | 432 | # Collect unmarked hooks as long as they have the `pytest_' prefix. |
405 | | - |
406 | | - known_marks = {m.name for m in getattr(method, "pytestmark", [])} |
407 | | - opts = { |
408 | | - name: hasattr(method, name) or name in known_marks |
409 | | - for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper") |
410 | | - } |
411 | | - if any(opts.values()): |
412 | | - message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( |
413 | | - type="spec", fullname=method.__qualname__ |
414 | | - ) |
415 | | - warn_explicit_for(method, message) |
416 | | - |
417 | | - return opts |
| 433 | + return _get_legacy_hook_marks( |
| 434 | + method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") |
| 435 | + ) |
418 | 436 |
|
419 | 437 | def parse_hookspec_opts(self, module_or_class, name: str): |
420 | 438 | opts = super().parse_hookspec_opts(module_or_class, name) |
421 | 439 | if opts is None: |
422 | 440 | method = getattr(module_or_class, name) |
423 | 441 | if name.startswith("pytest_"): |
424 | | - |
425 | | - known_marks = {m.name for m in getattr(method, "pytestmark", [])} |
426 | | - opts = { |
427 | | - "firstresult": hasattr(method, "firstresult") |
428 | | - or "firstresult" in known_marks, |
429 | | - "historic": hasattr(method, "historic") |
430 | | - or "historic" in known_marks, |
431 | | - } |
432 | | - # hook from xdist, fixing in ... |
433 | | - if any(opts.values()) and module_or_class.__name__ != "xdist.newhooks": |
434 | | - message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( |
435 | | - type="spec", fullname=method.__qualname__ |
436 | | - ) |
437 | | - warn_explicit_for(method, message) |
| 442 | + opts = _get_legacy_hook_marks( |
| 443 | + method, |
| 444 | + "spec", |
| 445 | + ("firstresult", "historic"), |
| 446 | + ) |
438 | 447 | return opts |
439 | 448 |
|
440 | 449 | def register( |
|
0 commit comments