From 96ea867fec556a8d0e2b60392927572da38c88df Mon Sep 17 00:00:00 2001
From: Ran Benita <ran@unusedvar.com>
Date: Sat, 26 Dec 2020 21:23:23 +0200
Subject: [PATCH] runner: export pytest.CallInfo for typing purposes

The type cannot be constructed directly, but is exported for use in type
annotations, since it is reachable through existing public API.

This also documents `from_call` as public, because at least
pytest-forked uses it, so we must treat it as public already anyway.
---
 changelog/7469.deprecation.rst |  1 +
 changelog/7469.feature.rst     |  1 +
 doc/en/reference.rst           |  2 +-
 src/_pytest/runner.py          | 69 +++++++++++++++++++++++-----------
 src/pytest/__init__.py         |  2 +
 5 files changed, 52 insertions(+), 23 deletions(-)

diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst
index bcf4266d8..0d7908ef8 100644
--- a/changelog/7469.deprecation.rst
+++ b/changelog/7469.deprecation.rst
@@ -4,5 +4,6 @@ Directly constructing the following classes is now deprecated:
 - ``_pytest.mark.structures.MarkDecorator``
 - ``_pytest.mark.structures.MarkGenerator``
 - ``_pytest.python.Metafunc``
+- ``_pytest.runner.CallInfo``
 
 These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0.
diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst
index 0ab2b48c4..f9948d686 100644
--- a/changelog/7469.feature.rst
+++ b/changelog/7469.feature.rst
@@ -6,6 +6,7 @@ The newly-exported types are:
 - ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`.
 - ``pytest.MarkGenerator`` for the :class:`pytest.mark <pytest.MarkGenerator>` singleton.
 - ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the `pytest_generate_tests <pytest.hookspec.pytest_generate_tests>` hook.
+- ``pytest.runner.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks.
 
 Constructing them directly is not supported; they are only meant for use in type annotations.
 Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0.
diff --git a/doc/en/reference.rst b/doc/en/reference.rst
index 7f2ae0105..bc6c5670a 100644
--- a/doc/en/reference.rst
+++ b/doc/en/reference.rst
@@ -758,7 +758,7 @@ Full reference to objects accessible from :ref:`fixtures <fixture>` or :ref:`hoo
 CallInfo
 ~~~~~~~~
 
-.. autoclass:: _pytest.runner.CallInfo()
+.. autoclass:: pytest.CallInfo()
     :members:
 
 
diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py
index 794690ddb..df046a78a 100644
--- a/src/_pytest/runner.py
+++ b/src/_pytest/runner.py
@@ -26,6 +26,7 @@ from _pytest._code.code import ExceptionInfo
 from _pytest._code.code import TerminalRepr
 from _pytest.compat import final
 from _pytest.config.argparsing import Parser
+from _pytest.deprecated import check_ispytest
 from _pytest.nodes import Collector
 from _pytest.nodes import Item
 from _pytest.nodes import Node
@@ -260,34 +261,47 @@ TResult = TypeVar("TResult", covariant=True)
 
 
 @final
-@attr.s(repr=False)
+@attr.s(repr=False, init=False, auto_attribs=True)
 class CallInfo(Generic[TResult]):
-    """Result/Exception info a function invocation.
+    """Result/Exception info of a function invocation."""
 
-    :param T result:
-        The return value of the call, if it didn't raise. Can only be
-        accessed if excinfo is None.
-    :param Optional[ExceptionInfo] excinfo:
-        The captured exception of the call, if it raised.
-    :param float start:
-        The system time when the call started, in seconds since the epoch.
-    :param float stop:
-        The system time when the call ended, in seconds since the epoch.
-    :param float duration:
-        The call duration, in seconds.
-    :param str when:
-        The context of invocation: "setup", "call", "teardown", ...
-    """
+    _result: Optional[TResult]
+    #: The captured exception of the call, if it raised.
+    excinfo: Optional[ExceptionInfo[BaseException]]
+    #: The system time when the call started, in seconds since the epoch.
+    start: float
+    #: The system time when the call ended, in seconds since the epoch.
+    stop: float
+    #: The call duration, in seconds.
+    duration: float
+    #: The context of invocation: "collect", "setup", "call" or "teardown".
+    when: "Literal['collect', 'setup', 'call', 'teardown']"
 
-    _result = attr.ib(type="Optional[TResult]")
-    excinfo = attr.ib(type=Optional[ExceptionInfo[BaseException]])
-    start = attr.ib(type=float)
-    stop = attr.ib(type=float)
-    duration = attr.ib(type=float)
-    when = attr.ib(type="Literal['collect', 'setup', 'call', 'teardown']")
+    def __init__(
+        self,
+        result: Optional[TResult],
+        excinfo: Optional[ExceptionInfo[BaseException]],
+        start: float,
+        stop: float,
+        duration: float,
+        when: "Literal['collect', 'setup', 'call', 'teardown']",
+        *,
+        _ispytest: bool = False,
+    ) -> None:
+        check_ispytest(_ispytest)
+        self._result = result
+        self.excinfo = excinfo
+        self.start = start
+        self.stop = stop
+        self.duration = duration
+        self.when = when
 
     @property
     def result(self) -> TResult:
+        """The return value of the call, if it didn't raise.
+
+        Can only be accessed if excinfo is None.
+        """
         if self.excinfo is not None:
             raise AttributeError(f"{self!r} has no valid result")
         # The cast is safe because an exception wasn't raised, hence
@@ -304,6 +318,16 @@ class CallInfo(Generic[TResult]):
             Union[Type[BaseException], Tuple[Type[BaseException], ...]]
         ] = None,
     ) -> "CallInfo[TResult]":
+        """Call func, wrapping the result in a CallInfo.
+
+        :param func:
+            The function to call. Called without arguments.
+        :param when:
+            The phase in which the function is called.
+        :param reraise:
+            Exception or exceptions that shall propagate if raised by the
+            function, instead of being wrapped in the CallInfo.
+        """
         excinfo = None
         start = timing.time()
         precise_start = timing.perf_counter()
@@ -325,6 +349,7 @@ class CallInfo(Generic[TResult]):
             when=when,
             result=result,
             excinfo=excinfo,
+            _ispytest=True,
         )
 
     def __repr__(self) -> str:
diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py
index f97b0ac2e..53917340f 100644
--- a/src/pytest/__init__.py
+++ b/src/pytest/__init__.py
@@ -48,6 +48,7 @@ from _pytest.python_api import raises
 from _pytest.recwarn import deprecated_call
 from _pytest.recwarn import WarningsRecorder
 from _pytest.recwarn import warns
+from _pytest.runner import CallInfo
 from _pytest.tmpdir import TempdirFactory
 from _pytest.tmpdir import TempPathFactory
 from _pytest.warning_types import PytestAssertRewriteWarning
@@ -69,6 +70,7 @@ __all__ = [
     "_fillfuncargs",
     "approx",
     "Cache",
+    "CallInfo",
     "CaptureFixture",
     "Class",
     "cmdline",