|
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
|
|
58 | 59 | from _pytest.pathlib import resolve_package_path
|
59 | 60 | from _pytest.stash import Stash
|
60 | 61 | from _pytest.warning_types import PytestConfigWarning
|
| 62 | +from _pytest.warning_types import warn_explicit_for |
61 | 63 |
|
62 | 64 | if TYPE_CHECKING:
|
63 | 65 |
|
@@ -329,6 +331,32 @@ def _prepareconfig(
|
329 | 331 | raise
|
330 | 332 |
|
331 | 333 |
|
| 334 | +def _get_legacy_hook_marks( |
| 335 | + method: FunctionType, |
| 336 | + hook_type: str, |
| 337 | + opt_names: Tuple[str, ...], |
| 338 | +) -> Dict[str, bool]: |
| 339 | + known_marks = {m.name for m in getattr(method, "pytestmark", [])} |
| 340 | + must_warn = False |
| 341 | + opts = {} |
| 342 | + for opt_name in opt_names: |
| 343 | + if hasattr(method, opt_name) or opt_name in known_marks: |
| 344 | + opts[opt_name] = True |
| 345 | + must_warn = True |
| 346 | + else: |
| 347 | + opts[opt_name] = False |
| 348 | + if must_warn: |
| 349 | + |
| 350 | + hook_opts = ", ".join(f"{name}=True" for name, val in opts.items() if val) |
| 351 | + message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( |
| 352 | + type=hook_type, |
| 353 | + fullname=method.__qualname__, |
| 354 | + hook_opts=hook_opts, |
| 355 | + ) |
| 356 | + warn_explicit_for(method, message) |
| 357 | + return opts |
| 358 | + |
| 359 | + |
332 | 360 | @final
|
333 | 361 | class PytestPluginManager(PluginManager):
|
334 | 362 | """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
|
@@ -392,40 +420,29 @@ def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str):
|
392 | 420 | if name == "pytest_plugins":
|
393 | 421 | return
|
394 | 422 |
|
395 |
| - method = getattr(plugin, name) |
396 | 423 | opts = super().parse_hookimpl_opts(plugin, name)
|
| 424 | + if opts is not None: |
| 425 | + return opts |
397 | 426 |
|
| 427 | + method = getattr(plugin, name) |
398 | 428 | # Consider only actual functions for hooks (#3775).
|
399 | 429 | if not inspect.isroutine(method):
|
400 | 430 | return
|
401 |
| - |
402 | 431 | # Collect unmarked hooks as long as they have the `pytest_' prefix.
|
403 |
| - if opts is None and name.startswith("pytest_"): |
404 |
| - opts = {} |
405 |
| - if opts is not None: |
406 |
| - # TODO: DeprecationWarning, people should use hookimpl |
407 |
| - # https://github.com/pytest-dev/pytest/issues/4562 |
408 |
| - known_marks = {m.name for m in getattr(method, "pytestmark", [])} |
409 |
| - |
410 |
| - for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): |
411 |
| - opts.setdefault(name, hasattr(method, name) or name in known_marks) |
412 |
| - return opts |
| 432 | + return _get_legacy_hook_marks( |
| 433 | + method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") |
| 434 | + ) |
413 | 435 |
|
414 | 436 | def parse_hookspec_opts(self, module_or_class, name: str):
|
415 | 437 | opts = super().parse_hookspec_opts(module_or_class, name)
|
416 | 438 | if opts is None:
|
417 | 439 | method = getattr(module_or_class, name)
|
418 |
| - |
419 | 440 | if name.startswith("pytest_"):
|
420 |
| - # todo: deprecate hookspec hacks |
421 |
| - # https://github.com/pytest-dev/pytest/issues/4562 |
422 |
| - known_marks = {m.name for m in getattr(method, "pytestmark", [])} |
423 |
| - opts = { |
424 |
| - "firstresult": hasattr(method, "firstresult") |
425 |
| - or "firstresult" in known_marks, |
426 |
| - "historic": hasattr(method, "historic") |
427 |
| - or "historic" in known_marks, |
428 |
| - } |
| 441 | + opts = _get_legacy_hook_marks( |
| 442 | + method, |
| 443 | + "spec", |
| 444 | + ("firstresult", "historic"), |
| 445 | + ) |
429 | 446 | return opts
|
430 | 447 |
|
431 | 448 | def register(
|
|
0 commit comments