Skip to content

Commit

Permalink
robotframework 7.0rc2
Browse files Browse the repository at this point in the history
  • Loading branch information
DetachHead committed Jan 8, 2024
1 parent 0c91615 commit 2a1b2b7
Show file tree
Hide file tree
Showing 22 changed files with 106 additions and 124 deletions.
10 changes: 5 additions & 5 deletions pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ fixture-parentheses = false

[tool.ruff.per-file-ignores]
"*.pyi" = ["A001", "A002", "N"] # we don't control names in 3rd party modules
"pytest_robotframework/**/*.py" = [
#robot needs to parse type annotations when importing libraries which fails for types that are only imported in TYPE_CHECKING blocks
"TCH001", # typing-only-first-party-import
"TCH002", # typing-only-third-party-import
"TCH003", # typing-only-standard-library-import
]
"tests/**/*.py" = [
"S101", # Use of assert detected (pytest uses assert statements)
]
Expand Down
68 changes: 38 additions & 30 deletions pytest_robotframework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from robot.api import SuiteVisitor, deco
from robot.api.interfaces import ListenerV2, ListenerV3
from robot.libraries.BuiltIn import BuiltIn
from robot.running.librarykeyword import StaticKeyword, StaticKeywordCreator
from robot.running.librarykeywordrunner import LibraryKeywordRunner
from robot.running.statusreporter import StatusReporter
from robot.utils import getshortdoc, printable_name
Expand All @@ -44,17 +45,11 @@
escape_robot_str,
execution_context,
)
from pytest_robotframework._internal.utils import (
ClassOrInstance,
ContextManager,
patch_method,
)
from pytest_robotframework._internal.utils import ClassOrInstance, ContextManager

if TYPE_CHECKING:
from types import TracebackType

from robot.running.context import _ExecutionContext


RobotVariables = Dict[str, object]

Expand Down Expand Up @@ -87,27 +82,33 @@ def import_resource(path: Path | str):
_resources.append(Path(path))


@patch_method(LibraryKeywordRunner, "_runner_for")
def _( # noqa: PLR0917
old_method: Callable[
[
LibraryKeywordRunner,
_ExecutionContext,
Function,
list[object],
dict[str, object],
],
Function,
],
self: LibraryKeywordRunner,
context: _ExecutionContext,
handler: Function,
positional: list[object],
named: dict[str, object],
) -> Function:
"""use the original function instead of the `@keyword` wrapped one"""
handler = cast(Function, getattr(handler, "_keyword_original_function", handler))
return old_method(self, context, handler, positional, named)
class _StaticKeyword(StaticKeyword): # pylint:disable=abstract-method
"""prevents keywords decorated with `pytest_robotframework.keyword` from being wrapped in two
status reporters when called from `.robot` tests"""

@override
@property
def method(self) -> Function:
method = cast(Function, super().method)
return cast(Function, getattr(method, "_keyword_original_function", method))

@override
def copy(self, **attributes: object) -> _StaticKeyword:
return _StaticKeyword( # type:ignore[no-untyped-call]
self.method_name,
self.owner,
self.name,
self.args,
self._doc,
self.tags,
self._resolve_args_until,
self.parent,
self.error,
).config(**attributes)


# patch StaticKeywordCreator to use our one instead
StaticKeywordCreator.keyword_class = _StaticKeyword


class _KeywordDecorator:
Expand Down Expand Up @@ -166,11 +167,12 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
*(f"{key}={value!s}" for key, value in kwargs.items()),
)
context = execution_context()
data = running.Keyword(name=keyword_name, args=log_args)
context_manager: ContextManager[object] = (
# needed to work around mypy/pyright bug, see ContextManager documentation
StatusReporter( # type:ignore[assignment]
running.Keyword(name=keyword_name, args=log_args),
result.Keyword(
data=data,
result=result.Keyword(
name=keyword_name,
owner=self.module,
doc=(
Expand All @@ -191,6 +193,12 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
# afterwards, so that context managers like `pytest.raises` can see the actual
# exception instead of `robot.errors.HandlerExecutionFailed`
suppress=True,
implementation=cast(
LibraryKeywordRunner,
context.get_runner( # type:ignore[no-untyped-call]
keyword_name
),
).keyword.bind(data),
)
if context
else nullcontext()
Expand Down
28 changes: 19 additions & 9 deletions pytest_robotframework/_internal/pytest_robot_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@
from typing import TYPE_CHECKING, cast

from pytest import Config, File, Item, MarkDecorator, Session, mark, skip
from robot import model
from robot.errors import ExecutionFailed
from robot.libraries.BuiltIn import BuiltIn
from robot.running.bodyrunner import BodyRunner
from typing_extensions import override

from pytest_robotframework._internal.errors import InternalError
from pytest_robotframework._internal.robot_classes import (
collected_robot_suite_key,
original_body_key,
original_setup_key,
original_teardown_key,
)
from pytest_robotframework._internal.robot_utils import (
ModelTestSuite,
ModelTestCase,
execution_context,
running_test_case_key,
)
Expand All @@ -28,14 +30,14 @@
from collections.abc import Iterable
from os import PathLike

from robot import model, running
from robot import running


class RobotFile(File):
@override
def collect(self) -> Iterable[Item]:
for test in cast(
Iterator[ModelTestSuite],
Iterator[ModelTestCase],
self.session.stash[collected_robot_suite_key].all_tests,
):
if self.path == test.source:
Expand Down Expand Up @@ -108,12 +110,20 @@ def setup(self):
def runtest(self):
test = self.stash[running_test_case_key]
context = execution_context()
with self._check_skipped():
BodyRunner(
context=context, templated=bool(test.template)
).run( # type:ignore[no-untyped-call]
self.stash[original_body_key]
)
if not context:
raise InternalError("failed to runtest because no execution context")
wrapped_body = test.body
# TODO: what is mypy's problem
test.body = self.stash[original_body_key] # type:ignore[index]
try:
with self._check_skipped():
BodyRunner(
context=context, templated=bool(test.template)
).run( # type:ignore[no-untyped-call]
data=test, result=context.test
)
finally:
test.body = wrapped_body

@override
def teardown(self):
Expand Down
21 changes: 6 additions & 15 deletions pytest_robotframework/_internal/robot_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,7 @@ def _create_running_keyword(
f"kwargs not supported: {kwargs}" # type:ignore[helpful-string]
)
return running.Keyword(
name=f"{fn.__module__}.{fn.__name__}",
# robot says this can only be a str but keywords can take any object when called from
# python
args=args, # type:ignore[arg-type]
type=keyword_type,
name=f"{fn.__module__}.{fn.__name__}", args=args, type=keyword_type
)


Expand Down Expand Up @@ -134,7 +130,7 @@ def visit_suite(self, suite: ModelTestSuite):
if not isinstance(suite, running.TestSuite):
raise _NotRunningTestSuiteError
# only do this once, on the top level suite
if not suite.parent: # type:ignore[no-any-expr]
if not suite.parent:
self.session.stash[collected_robot_suite_key] = suite
try:
self.session.perform_collect()
Expand Down Expand Up @@ -170,8 +166,7 @@ def visit_suite(self, suite: ModelTestSuite):
else:
# remove any .robot tests that were filtered out by pytest (and the fake test
# from `PythonParser`):
# messed up types, fixed in robot 7
for test in suite.tests[:]: # type:ignore[var-annotated]
for test in suite.tests[:]:
if not get_item_from_robot_test(self.session, test):
suite.tests.remove(test)

Expand All @@ -182,17 +177,14 @@ def visit_suite(self, suite: ModelTestSuite):
if module.__doc__ and not suite.doc:
suite.doc = module.__doc__
if item.path == suite.source:
suite.tests.append(
# messed up types, fixed in robot 7
item.stash[running_test_case_key] # type:ignore[index]
)
suite.tests.append(item.stash[running_test_case_key])
super().visit_suite(suite)

@override
def end_suite(self, suite: ModelTestSuite):
"""Remove suites that are empty after removing tests."""
suite.suites = [s for s in suite.suites if s.test_count > 0]
if not suite.parent and self.collection_error: # type:ignore[no-any-expr]
if not suite.parent and self.collection_error:
raise self.collection_error


Expand Down Expand Up @@ -229,8 +221,7 @@ def start_suite(self, suite: ModelTestSuite):
suite.resource.imports.library(
robot_library.__name__, alias=robot_library.__name__
)
# fixed in robot 7
for test in suite.tests: # type:ignore[var-annotated]
for test in suite.tests:
item = get_item_from_robot_test(self.session, test)
if not item:
raise InternalError(
Expand Down
11 changes: 6 additions & 5 deletions pytest_robotframework/_internal/robot_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
from _pytest._code.code import TerminalRepr
from _pytest.runner import call_and_report, show_test_item
from pytest import Item, StashKey, TestReport
from robot.api.deco import keyword
from robot.libraries.BuiltIn import BuiltIn

from pytest_robotframework import keyword
from pytest_robotframework._internal import cringe_globals
from pytest_robotframework._internal.errors import InternalError
from pytest_robotframework._internal.pytest_exception_getter import exception_key
Expand Down Expand Up @@ -61,7 +61,8 @@ def _call_and_report_robot_edition(
)


@keyword
# not using pytest_robotframework.keyword because they are already nested
@keyword # type:ignore[no-any-expr]
def setup(arg: Cloaked[Item]):
item = arg.value
cringe_globals._current_item = item # noqa: SLF001
Expand All @@ -76,7 +77,7 @@ def setup(arg: Cloaked[Item]):
_call_and_report_robot_edition(item, "setup")


@keyword
@keyword # type:ignore[no-any-expr]
def run_test(arg: Cloaked[Item]):
item = arg.value
# mostly copied from the middle of `_pytest.runner.runtestprotocol`:
Expand All @@ -92,7 +93,7 @@ def run_test(arg: Cloaked[Item]):
_call_and_report_robot_edition(item, "call")


@keyword
@keyword # type:ignore[no-any-expr]
def teardown(arg: Cloaked[Item]):
item = arg.value
# mostly copied from the end of `_pytest.runner.runtestprotocol`:
Expand All @@ -102,6 +103,6 @@ def teardown(arg: Cloaked[Item]):
cringe_globals._current_item = None # noqa: SLF001


@keyword
@keyword # type:ignore[no-any-expr]
def internal_error(msg: Cloaked[str]):
raise InternalError(msg.value)
6 changes: 5 additions & 1 deletion pytest_robotframework/_internal/robot_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
from robot.running.context import _ExecutionContext
from typing_extensions import override

ModelTestSuite = model.TestSuite[model.Keyword, model.TestCase[model.Keyword]]
ModelTestCase = model.TestCase[model.Keyword]
"""robot `model.TestSuite` with the default generic value"""


ModelTestSuite = model.TestSuite[model.Keyword, ModelTestCase]
"""robot `model.TestSuite` with the default generic values"""


Expand Down
Loading

0 comments on commit 2a1b2b7

Please sign in to comment.