From 9eb78850599f7a1714182d1c050efba3f831d8b2 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Tue, 15 Oct 2024 15:33:34 +0300 Subject: [PATCH 01/43] Add tests and hook skeletons --- examples/__init__.py | 15 --------- .../fixtures/class_fixture_return/conftest.py | 23 +++++++++++++ .../test_fixture_session_setup.py | 29 ++++++++++++++++ .../module_fixture_return/conftest.py | 23 +++++++++++++ .../test_fixture_module_setup.py | 32 ++++++++++++++++++ .../package_fixture_return}/__init__.py | 7 ++-- .../package_fixture_return/conftest.py | 23 +++++++++++++ .../test_fixture_package_setup.py | 32 ++++++++++++++++++ .../session_fixture_return/conftest.py | 23 +++++++++++++ .../test_fixture_session_setup.py | 33 +++++++++++++++++++ .../fixtures/test_fixture_return/conftest.py | 23 +++++++++++++ .../test_fixture_return/test_fixture_setup.py | 29 ++++++++++++++++ .../fixtures/test_fixture_yield/conftest.py | 23 +++++++++++++ .../test_fixture_teardown.py | 29 ++++++++++++++++ examples/test_case_id/__init__.py | 15 --------- examples/threads/__init__.py | 0 pytest_reportportal/plugin.py | 21 ++++++++++++ tests/integration/test_fixtures.py | 24 ++++++++++++++ 18 files changed, 371 insertions(+), 33 deletions(-) delete mode 100644 examples/__init__.py create mode 100644 examples/fixtures/class_fixture_return/conftest.py create mode 100644 examples/fixtures/class_fixture_return/test_fixture_session_setup.py create mode 100644 examples/fixtures/module_fixture_return/conftest.py create mode 100644 examples/fixtures/module_fixture_return/test_fixture_module_setup.py rename examples/{skip => fixtures/package_fixture_return}/__init__.py (78%) create mode 100644 examples/fixtures/package_fixture_return/conftest.py create mode 100644 examples/fixtures/package_fixture_return/test_fixture_package_setup.py create mode 100644 examples/fixtures/session_fixture_return/conftest.py create mode 100644 examples/fixtures/session_fixture_return/test_fixture_session_setup.py create mode 100644 examples/fixtures/test_fixture_return/conftest.py create mode 100644 examples/fixtures/test_fixture_return/test_fixture_setup.py create mode 100644 examples/fixtures/test_fixture_yield/conftest.py create mode 100644 examples/fixtures/test_fixture_yield/test_fixture_teardown.py delete mode 100644 examples/test_case_id/__init__.py delete mode 100644 examples/threads/__init__.py create mode 100644 tests/integration/test_fixtures.py diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index a9905a1c..00000000 --- a/examples/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -"""This package contains integration test examples for the project. - -Copyright (c) 2022 https://reportportal.io . -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License -""" diff --git a/examples/fixtures/class_fixture_return/conftest.py b/examples/fixtures/class_fixture_return/conftest.py new file mode 100644 index 00000000..3028b4ca --- /dev/null +++ b/examples/fixtures/class_fixture_return/conftest.py @@ -0,0 +1,23 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +import pytest + + +@pytest.fixture(scope='class') +def mocked_config(): + print('setup') + return mock.Mock() diff --git a/examples/fixtures/class_fixture_return/test_fixture_session_setup.py b/examples/fixtures/class_fixture_return/test_fixture_session_setup.py new file mode 100644 index 00000000..79f2c7e4 --- /dev/null +++ b/examples/fixtures/class_fixture_return/test_fixture_session_setup.py @@ -0,0 +1,29 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class TestClass: + def test_fixture_class_setup(self, mocked_config): + assert mocked_config is not None diff --git a/examples/fixtures/module_fixture_return/conftest.py b/examples/fixtures/module_fixture_return/conftest.py new file mode 100644 index 00000000..48d49daa --- /dev/null +++ b/examples/fixtures/module_fixture_return/conftest.py @@ -0,0 +1,23 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +import pytest + + +@pytest.fixture(scope='module') +def mocked_config(): + print('setup') + return mock.Mock() diff --git a/examples/fixtures/module_fixture_return/test_fixture_module_setup.py b/examples/fixtures/module_fixture_return/test_fixture_module_setup.py new file mode 100644 index 00000000..3792099d --- /dev/null +++ b/examples/fixtures/module_fixture_return/test_fixture_module_setup.py @@ -0,0 +1,32 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def test_fixture_module_setup_first(mocked_config): + assert mocked_config is not None + + +def test_fixture_module_setup_second(mocked_config): + assert mocked_config is not None diff --git a/examples/skip/__init__.py b/examples/fixtures/package_fixture_return/__init__.py similarity index 78% rename from examples/skip/__init__.py rename to examples/fixtures/package_fixture_return/__init__.py index 067eea4d..2c4530c5 100644 --- a/examples/skip/__init__.py +++ b/examples/fixtures/package_fixture_return/__init__.py @@ -1,12 +1,13 @@ -# Copyright (c) 2022 https://reportportal.io . +# Copyright 2024 EPAM Systems +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# https://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and -# limitations under the License +# limitations under the License. diff --git a/examples/fixtures/package_fixture_return/conftest.py b/examples/fixtures/package_fixture_return/conftest.py new file mode 100644 index 00000000..1ae95a23 --- /dev/null +++ b/examples/fixtures/package_fixture_return/conftest.py @@ -0,0 +1,23 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +import pytest + + +@pytest.fixture(scope='package') +def mocked_config(): + print('setup') + return mock.Mock() diff --git a/examples/fixtures/package_fixture_return/test_fixture_package_setup.py b/examples/fixtures/package_fixture_return/test_fixture_package_setup.py new file mode 100644 index 00000000..7d5601e0 --- /dev/null +++ b/examples/fixtures/package_fixture_return/test_fixture_package_setup.py @@ -0,0 +1,32 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def test_fixture_package_setup_first(mocked_config): + assert mocked_config is not None + + +def test_fixture_package_setup_second(mocked_config): + assert mocked_config is not None diff --git a/examples/fixtures/session_fixture_return/conftest.py b/examples/fixtures/session_fixture_return/conftest.py new file mode 100644 index 00000000..0e4dc5c0 --- /dev/null +++ b/examples/fixtures/session_fixture_return/conftest.py @@ -0,0 +1,23 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +import pytest + + +@pytest.fixture(scope='session') +def mocked_config(): + print('setup') + return mock.Mock() diff --git a/examples/fixtures/session_fixture_return/test_fixture_session_setup.py b/examples/fixtures/session_fixture_return/test_fixture_session_setup.py new file mode 100644 index 00000000..d56dec06 --- /dev/null +++ b/examples/fixtures/session_fixture_return/test_fixture_session_setup.py @@ -0,0 +1,33 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_fixture_session_setup_first(mocked_config): + assert mocked_config is not None + + +def test_fixture_session_setup_second(mocked_config): + assert mocked_config is not None diff --git a/examples/fixtures/test_fixture_return/conftest.py b/examples/fixtures/test_fixture_return/conftest.py new file mode 100644 index 00000000..56d66de1 --- /dev/null +++ b/examples/fixtures/test_fixture_return/conftest.py @@ -0,0 +1,23 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +import pytest + + +@pytest.fixture +def mocked_config(): + print('setup') + return mock.Mock() diff --git a/examples/fixtures/test_fixture_return/test_fixture_setup.py b/examples/fixtures/test_fixture_return/test_fixture_setup.py new file mode 100644 index 00000000..04270723 --- /dev/null +++ b/examples/fixtures/test_fixture_return/test_fixture_setup.py @@ -0,0 +1,29 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_fixture_setup(mocked_config): + assert mocked_config is not None diff --git a/examples/fixtures/test_fixture_yield/conftest.py b/examples/fixtures/test_fixture_yield/conftest.py new file mode 100644 index 00000000..0f5fa125 --- /dev/null +++ b/examples/fixtures/test_fixture_yield/conftest.py @@ -0,0 +1,23 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import mock + +import pytest + + +@pytest.fixture +def mocked_config(): + yield mock.Mock() + print('teardown') diff --git a/examples/fixtures/test_fixture_yield/test_fixture_teardown.py b/examples/fixtures/test_fixture_yield/test_fixture_teardown.py new file mode 100644 index 00000000..46472f88 --- /dev/null +++ b/examples/fixtures/test_fixture_yield/test_fixture_teardown.py @@ -0,0 +1,29 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_fixture_teardown(mocked_config): + assert mocked_config is not None diff --git a/examples/test_case_id/__init__.py b/examples/test_case_id/__init__.py deleted file mode 100644 index fdf50e77..00000000 --- a/examples/test_case_id/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -"""This package contains Test Case ID integration test examples. - -Copyright (c) 2022 https://reportportal.io . -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License -""" diff --git a/examples/threads/__init__.py b/examples/threads/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index a4b27872..ae05a4bb 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -295,6 +295,27 @@ def pytest_runtest_makereport(item): service.process_results(item, report) +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_post_finalizer(fixturedef, request): + config = request.config + if not config._rp_enabled: + yield + return + + outcome = yield + result = str(fixturedef.cached_result) if hasattr(fixturedef, 'cached_result') else None + + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_setup(fixturedef, request): + config = request.config + if not config._rp_enabled: + yield + return + outcome = yield + result = str(fixturedef.cached_result) if hasattr(fixturedef, 'cached_result') else None + + def pytest_addoption(parser): """Add support for the RP-related options. diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py new file mode 100644 index 00000000..19410d1f --- /dev/null +++ b/tests/integration/test_fixtures.py @@ -0,0 +1,24 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from tests.helpers import utils + + +# TODO: finish these tests + + +def test_fixture_simple(): + variables = utils.DEFAULT_VARIABLES + result = utils.run_pytest_tests(tests=['examples/fixtures/before_test/test_fixture_setup.py'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' From 83a390db894996f8b01cd3b0b6573de6f101e0cc Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 16 Oct 2024 13:16:28 +0300 Subject: [PATCH 02/43] Update typing --- pytest_reportportal/plugin.py | 58 +++++++++++++++++++--------------- pytest_reportportal/plugin.pyi | 39 ----------------------- 2 files changed, 32 insertions(+), 65 deletions(-) delete mode 100644 pytest_reportportal/plugin.pyi diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index ae05a4bb..47742b6e 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -16,35 +16,38 @@ import logging import os.path import time +from logging import Logger +from typing import Any import _pytest.logging import dill as pickle import pytest import requests -from reportportal_client import RPLogHandler +from pytest import Config, FixtureDef, FixtureRequest, Parser, Session, Item +from reportportal_client import RPLogHandler, RP from reportportal_client.errors import ResponseError from reportportal_client.logs import MAX_LOG_BATCH_PAYLOAD_SIZE from pytest_reportportal import LAUNCH_WAIT_TIMEOUT -from .config import AgentConfig -from .rp_logging import patching_logger_class, patching_thread_class -from .service import PyTestServiceClass +from pytest_reportportal.config import AgentConfig +from pytest_reportportal.rp_logging import patching_logger_class, patching_thread_class +from pytest_reportportal.service import PyTestServiceClass -log = logging.getLogger(__name__) +log: Logger = logging.getLogger(__name__) -MANDATORY_PARAMETER_MISSED_PATTERN = \ +MANDATORY_PARAMETER_MISSED_PATTERN: str = \ 'One of the following mandatory parameters is unset: ' + \ 'rp_project: {}, ' + \ 'rp_endpoint: {}, ' + \ 'rp_api_key: {}' -FAILED_LAUNCH_WAIT = 'Failed to initialize reportportal-client service. ' \ - + 'Waiting for Launch start timed out. ' \ - + 'Reporting is disabled.' +FAILED_LAUNCH_WAIT: str = 'Failed to initialize reportportal-client service. ' \ + + 'Waiting for Launch start timed out. ' \ + + 'Reporting is disabled.' @pytest.hookimpl(optionalhook=True) -def pytest_configure_node(node): +def pytest_configure_node(node: Any) -> None: """Configure xdist node controller. :param node: Object of the xdist WorkerController class @@ -56,7 +59,7 @@ def pytest_configure_node(node): node.workerinput['py_test_service'] = pickle.dumps(node.config.py_test_service) -def is_control(config): +def is_control(config: Config) -> bool: """Validate workerinput attribute of the Config object. True if the code, running the given pytest.config object, @@ -65,7 +68,7 @@ def is_control(config): return not hasattr(config, 'workerinput') -def wait_launch(rp_client): +def wait_launch(rp_client: RP) -> bool: """Wait for the launch startup. :param rp_client: Instance of the ReportPortalService class @@ -79,7 +82,7 @@ def wait_launch(rp_client): # noinspection PyProtectedMember -def pytest_sessionstart(session): +def pytest_sessionstart(session: Session) -> None: """Start Report Portal launch. This method is called every time on control or worker process start, it @@ -111,7 +114,7 @@ def pytest_sessionstart(session): config._rp_enabled = False -def pytest_collection_finish(session): +def pytest_collection_finish(session: Session) -> None: """Collect tests if session is configured. :param session: Object of the pytest Session class @@ -125,7 +128,7 @@ def pytest_collection_finish(session): # noinspection PyProtectedMember -def pytest_sessionfinish(session): +def pytest_sessionfinish(session: Session) -> None: """Finish current test session. :param session: Object of the pytest Session class @@ -142,7 +145,7 @@ def pytest_sessionfinish(session): config.py_test_service.stop() -def register_markers(config): +def register_markers(config: Config) -> None: """Register plugin's markers, to avoid declaring them in `pytest.ini`. :param config: Object of the pytest Config class @@ -161,7 +164,7 @@ def register_markers(config): ) -def check_connection(agent_config): +def check_connection(agent_config: AgentConfig): """Check connection to RP using provided options. If connection is successful returns True either False. @@ -184,7 +187,7 @@ def check_connection(agent_config): # noinspection PyProtectedMember -def pytest_configure(config): +def pytest_configure(config: Config) -> None: """Update Config object with attributes required for reporting to RP. :param config: Object of the pytest Config class @@ -227,7 +230,7 @@ def pytest_configure(config): # noinspection PyProtectedMember @pytest.hookimpl(hookwrapper=True) -def pytest_runtestloop(session): +def pytest_runtestloop(session: Session) -> None: """ Control start and finish of all test items in the session. @@ -246,7 +249,7 @@ def pytest_runtestloop(session): # noinspection PyProtectedMember @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_protocol(item): +def pytest_runtest_protocol(item: Item) -> None: """ Control start and finish of pytest items. @@ -278,7 +281,7 @@ def pytest_runtest_protocol(item): # noinspection PyProtectedMember @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item): +def pytest_runtest_makereport(item: Item) -> None: """ Change runtest_makereport function. @@ -296,8 +299,9 @@ def pytest_runtest_makereport(item): @pytest.hookimpl(hookwrapper=True) -def pytest_fixture_post_finalizer(fixturedef, request): +def pytest_fixture_setup(fixturedef: FixtureDef, request: FixtureRequest) -> None: config = request.config + # noinspection PyUnresolvedReferences, PyProtectedMember if not config._rp_enabled: yield return @@ -307,16 +311,18 @@ def pytest_fixture_post_finalizer(fixturedef, request): @pytest.hookimpl(hookwrapper=True) -def pytest_fixture_setup(fixturedef, request): +def pytest_fixture_post_finalizer(fixturedef: FixtureDef, request: FixtureRequest) -> None: config = request.config + # noinspection PyUnresolvedReferences, PyProtectedMember if not config._rp_enabled: yield return + outcome = yield result = str(fixturedef.cached_result) if hasattr(fixturedef, 'cached_result') else None -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: """Add support for the RP-related options. :param parser: Object of the Parser class @@ -364,7 +370,7 @@ def add_shared_option(name, help_str, default=None, action='store'): add_shared_option( name='rp_launch_id', help_str='Use already existing launch-id. The plugin won\'t control ' - 'the Launch status', + 'the Launch status', ) add_shared_option( name='rp_launch_description', @@ -398,7 +404,7 @@ def add_shared_option(name, help_str, default=None, action='store'): 'existing) item.', ) add_shared_option(name='rp_uuid', help_str='Deprecated: use `rp_api_key` ' - 'instead.') + 'instead.') add_shared_option( name='rp_api_key', help_str='API key of Report Portal. Usually located on UI profile ' diff --git a/pytest_reportportal/plugin.pyi b/pytest_reportportal/plugin.pyi deleted file mode 100644 index 71356385..00000000 --- a/pytest_reportportal/plugin.pyi +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2023 https://reportportal.io . -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License - -from logging import Logger -from typing import Text, Any - -import pytest - -from .config import AgentConfig -from _pytest.config import Config -from _pytest.config.argparsing import Parser -from _pytest.main import Session -from reportportal_client import RPClient - -log: Logger -MANDATORY_PARAMETER_MISSED_PATTERN: Text -FAILED_LAUNCH_WAIT: Text - -def check_connection(agent_config: AgentConfig) -> bool: ... -def is_control(config: Config) -> bool: ... -def wait_launch(rp_client: RPClient) -> bool: ... -def pytest_configure_node(node: Any) -> None: ... -def pytest_sessionstart(session: Session) -> None: ... -def pytest_collection_finish(session: Session) -> None: ... -def pytest_sessionfinish(session: Session) -> None: ... -def pytest_configure(config: Config) -> None: ... -def pytest_runtest_protocol(item: pytest.Item) -> None: ... -def pytest_runtest_makereport(item: pytest.Item) -> None: ... -def pytest_addoption(parser: Parser) -> None: ... From 2bf08db3a7bbd54746d74e33adabf5e5b38550b4 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 16 Oct 2024 15:40:33 +0300 Subject: [PATCH 03/43] Update typing and docs --- pytest_reportportal/service.py | 62 +++++++++++++++------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 5eb5a5d4..1c086189 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -24,10 +24,10 @@ from _pytest.doctest import DoctestItem from aenum import auto, Enum, unique -from pytest import Class, Function, Module, Package, Item, Session, \ - PytestWarning -from reportportal_client.core.rp_issues import Issue, ExternalIssue +from pytest import Class, Function, Module, Package, Item, Session, PytestWarning from reportportal_client.aio import Task +from reportportal_client.core.rp_issues import Issue, ExternalIssue +from typing_extensions import TypeVar from .config import AgentConfig @@ -109,6 +109,9 @@ class LeafType(Enum): ROOT = auto() +Leaf = TypeVar('Leaf', bound=LeafType) + + @unique class ExecStatus(Enum): """This class stores test item path types.""" @@ -215,7 +218,7 @@ def start_launch(self) -> Optional[str]: log.debug('ReportPortal - Launch started: id=%s', self._launch_id) return self._launch_id - def _get_item_dirs(self, item): + def _get_item_dirs(self, item: Item) -> List[str]: """ Get directory of item. @@ -228,9 +231,8 @@ def _get_item_dirs(self, item): drive="") return [d for d in rel_dir.parts(reverse=False) if d.basename] - def _get_tree_path(self, item): - """ - Get item of parents. + def _get_tree_path(self, item: Item) -> List[Item]: + """Get item of parents. :param item: pytest.Item :return list of parents @@ -245,7 +247,8 @@ def _get_tree_path(self, item): path.reverse() return path - def _get_leaf(self, leaf_type, parent_item, item, item_id=None): + def _get_leaf(self, leaf_type: Leaf, parent_item: Optional[Dict[str, Any]], item: Optional[Item], + item_id: Optional[str] = None) -> Dict[str, Any]: """Construct a leaf for the itest tree. :param leaf_type: the leaf type @@ -259,14 +262,13 @@ def _get_leaf(self, leaf_type, parent_item, item, item_id=None): 'exec': ExecStatus.CREATED, 'item_id': item_id } - def _build_test_tree(self, session): + def _build_test_tree(self, session: Session) -> Dict[str, Any]: """Construct a tree of tests and their suites. :param session: pytest.Session object of the current execution :return: a tree of all tests and their suites """ - test_tree = self._get_leaf(LeafType.ROOT, None, None, - item_id=self.parent_item_id) + test_tree = self._get_leaf(LeafType.ROOT, None, None, item_id=self.parent_item_id) for item in session.items: dir_path = self._get_item_dirs(item) @@ -281,13 +283,11 @@ def _build_test_tree(self, session): leaf_type = LeafType.CODE if leaf not in children_leafs: - children_leafs[leaf] = self._get_leaf(leaf_type, - current_leaf, - leaf) + children_leafs[leaf] = self._get_leaf(leaf_type, current_leaf, leaf) current_leaf = children_leafs[leaf] return test_tree - def _remove_root_dirs(self, test_tree, max_dir_level, dir_level=0): + def _remove_root_dirs(self, test_tree: Dict[str, Any], max_dir_level, dir_level=0) -> None: if test_tree['type'] == LeafType.ROOT: for item, child_leaf in test_tree['children'].items(): self._remove_root_dirs(child_leaf, max_dir_level, 1) @@ -300,10 +300,9 @@ def _remove_root_dirs(self, test_tree, max_dir_level, dir_level=0): for item, child_leaf in test_tree['children'].items(): parent_leaf['children'][item] = child_leaf child_leaf['parent'] = parent_leaf - self._remove_root_dirs(child_leaf, max_dir_level, - new_level) + self._remove_root_dirs(child_leaf, max_dir_level, new_level) - def _generate_names(self, test_tree): + def _generate_names(self, test_tree: Dict[str, Any]) -> None: if test_tree['type'] == LeafType.ROOT: test_tree['name'] = 'root' @@ -337,14 +336,13 @@ def _merge_leaf_type(self, test_tree, leaf_type, separator): current_name + separator + child_leaf['name'] self._merge_leaf_type(child_leaf, leaf_type, separator) - def _merge_dirs(self, test_tree): - self._merge_leaf_type(test_tree, LeafType.DIR, - self._config.rp_dir_path_separator) + def _merge_dirs(self, test_tree: Dict[str, Any]) -> None: + self._merge_leaf_type(test_tree, LeafType.DIR, self._config.rp_dir_path_separator) - def _merge_code(self, test_tree): + def _merge_code(self, test_tree: Dict[str, Any]) -> None: self._merge_leaf_type(test_tree, LeafType.CODE, '::') - def _build_item_paths(self, leaf, path): + def _build_item_paths(self, leaf: Dict[str, Any], path: List[Dict[str, Any]]) -> None: if 'children' in leaf and len(leaf['children']) > 0: path.append(leaf) for name, child_leaf in leaf['children'].items(): @@ -354,9 +352,8 @@ def _build_item_paths(self, leaf, path): self._tree_path[leaf['item']] = path + [leaf] @check_rp_enabled - def collect_tests(self, session): - """ - Collect all tests. + def collect_tests(self, session: Session) -> None: + """Collect all tests. :param session: pytest.Session """ @@ -371,8 +368,7 @@ def collect_tests(self, session): self._build_item_paths(test_tree, []) def _get_item_name(self, name): - """ - Get name of item. + """Get name of item. :param name: Item name :return: name @@ -389,8 +385,7 @@ def _get_item_name(self, name): return name def _get_item_description(self, test_item): - """ - Get description of item. + """Get description of item. :param test_item: pytest.Item :return string description @@ -443,7 +438,7 @@ def _create_suite(self, leaf): leaf['exec'] = ExecStatus.IN_PROGRESS @check_rp_enabled - def _create_suite_path(self, item): + def _create_suite_path(self, item: Item): path = self._tree_path[item] for leaf in path[1:-1]: if leaf['exec'] != ExecStatus.CREATED: @@ -455,8 +450,7 @@ def _get_code_ref(self, item): # same path on different systems and do not affect Test Case ID on # different systems path = os.path.relpath(str(item.fspath), ROOT_DIR).replace('\\', '/') - method_name = item.originalname if hasattr(item, 'originalname') \ - and item.originalname is not None \ + method_name = item.originalname if hasattr(item, 'originalname') and item.originalname is not None \ else item.name parent = item.parent classes = [method_name] @@ -686,7 +680,7 @@ def __started(self): return self.__unique_id() in self._start_tracker @check_rp_enabled - def start_pytest_item(self, test_item=None): + def start_pytest_item(self, test_item: Optional[Item] = None): """ Start pytest_item. From 20f85b72ca0be0d4425423ef7d0e2043d1c7aa37 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 16 Oct 2024 17:32:31 +0300 Subject: [PATCH 04/43] Implement fixture handling --- pytest_reportportal/plugin.py | 45 ++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 47742b6e..bda47131 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -23,10 +23,12 @@ import dill as pickle import pytest import requests +from pluggy import Result from pytest import Config, FixtureDef, FixtureRequest, Parser, Session, Item from reportportal_client import RPLogHandler, RP from reportportal_client.errors import ResponseError from reportportal_client.logs import MAX_LOG_BATCH_PAYLOAD_SIZE +from reportportal_client.steps import Step from pytest_reportportal import LAUNCH_WAIT_TIMEOUT from pytest_reportportal.config import AgentConfig @@ -250,8 +252,7 @@ def pytest_runtestloop(session: Session) -> None: # noinspection PyProtectedMember @pytest.hookimpl(hookwrapper=True) def pytest_runtest_protocol(item: Item) -> None: - """ - Control start and finish of pytest items. + """Control start and finish of pytest items. :param item: Pytest.Item :return: generator object @@ -282,8 +283,7 @@ def pytest_runtest_protocol(item: Item) -> None: # noinspection PyProtectedMember @pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item: Item) -> None: - """ - Change runtest_makereport function. + """Change runtest_makereport function. :param item: pytest.Item :return: None @@ -298,6 +298,23 @@ def pytest_runtest_makereport(item: Item) -> None: service.process_results(item, report) +def report_fixture(fixturedef: FixtureDef, request: FixtureRequest, name: str, error_msg: str) -> None: + config = request.config + # noinspection PyUnresolvedReferences, PyProtectedMember + if not config._rp_enabled: + yield + return + + name = fixturedef.argname + scope = fixturedef.scope + with Step(name, {}, 'PASSED', None): + outcome: Result = yield + if outcome.exception: + log.error(error_msg) + log.exception(outcome.exception) + raise outcome.exception + + @pytest.hookimpl(hookwrapper=True) def pytest_fixture_setup(fixturedef: FixtureDef, request: FixtureRequest) -> None: config = request.config @@ -306,8 +323,14 @@ def pytest_fixture_setup(fixturedef: FixtureDef, request: FixtureRequest) -> Non yield return - outcome = yield - result = str(fixturedef.cached_result) if hasattr(fixturedef, 'cached_result') else None + name = fixturedef.argname + scope = fixturedef.scope + with Step(f'{scope} fixture setup: {name}', {}, 'PASSED', None): + outcome: Result = yield + if outcome.exception: + log.error(f'{scope} fixture setup failed: {name}') + log.exception(outcome.exception) + raise outcome.exception @pytest.hookimpl(hookwrapper=True) @@ -318,8 +341,14 @@ def pytest_fixture_post_finalizer(fixturedef: FixtureDef, request: FixtureReques yield return - outcome = yield - result = str(fixturedef.cached_result) if hasattr(fixturedef, 'cached_result') else None + name = fixturedef.argname + scope = fixturedef.scope + with Step(f'{scope} fixture teardown: {name}', {}, 'PASSED', None): + outcome: Result = yield + if outcome.exception: + log.error(f'{scope} fixture teardown failed: {name}') + log.exception(outcome.exception) + raise outcome.exception def pytest_addoption(parser: Parser) -> None: From f5d5c841f59c2ac00ef29137022b0303c0e5d4ca Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 16 Oct 2024 20:33:39 +0300 Subject: [PATCH 05/43] Dependencies update --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index dda5974a..897791c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ dill>=0.3.6 pytest>=3.8.0 -reportportal-client~=5.5.7 +reportportal-client~=5.5.9 aenum>=3.1.0 -requests>=2.28.0 From 2d4568b4aba1ef75179191cc00edf4c8fd7669b0 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 16 Oct 2024 20:35:26 +0300 Subject: [PATCH 06/43] Remove redundant code --- pytest_reportportal/plugin.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index bda47131..5be6915c 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -298,23 +298,6 @@ def pytest_runtest_makereport(item: Item) -> None: service.process_results(item, report) -def report_fixture(fixturedef: FixtureDef, request: FixtureRequest, name: str, error_msg: str) -> None: - config = request.config - # noinspection PyUnresolvedReferences, PyProtectedMember - if not config._rp_enabled: - yield - return - - name = fixturedef.argname - scope = fixturedef.scope - with Step(name, {}, 'PASSED', None): - outcome: Result = yield - if outcome.exception: - log.error(error_msg) - log.exception(outcome.exception) - raise outcome.exception - - @pytest.hookimpl(hookwrapper=True) def pytest_fixture_setup(fixturedef: FixtureDef, request: FixtureRequest) -> None: config = request.config From ef718517643cab4e3b0394f2b133390128f502d4 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 16 Oct 2024 21:44:29 +0300 Subject: [PATCH 07/43] Refactoring, add fixture report parameter --- pytest_reportportal/config.py | 2 + pytest_reportportal/plugin.py | 73 +++++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/pytest_reportportal/config.py b/pytest_reportportal/config.py index 6467db0e..8663af51 100644 --- a/pytest_reportportal/config.py +++ b/pytest_reportportal/config.py @@ -67,6 +67,7 @@ class AgentConfig: rp_launch_uuid_print: bool rp_launch_uuid_print_output: Optional[OutputType] rp_http_timeout: Optional[Union[Tuple[float, float], float]] + rp_report_fixtures: bool def __init__(self, pytest_config: Config) -> None: """Initialize required attributes.""" @@ -197,6 +198,7 @@ def __init__(self, pytest_config: Config) -> None: self.rp_http_timeout = (connect_timeout, read_timeout) else: self.rp_http_timeout = connect_timeout or read_timeout + self.rp_report_fixtures = to_bool(self.find_option(pytest_config, 'rp_report_fixtures', False)) # noinspection PyMethodMayBeStatic def find_option(self, pytest_config: Config, option_name: str, default: Any = None) -> Any: diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 5be6915c..934e1e9d 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -25,10 +25,11 @@ import requests from pluggy import Result from pytest import Config, FixtureDef, FixtureRequest, Parser, Session, Item -from reportportal_client import RPLogHandler, RP +from reportportal_client import RPLogHandler, RP, current from reportportal_client.errors import ResponseError +from reportportal_client.helpers import timestamp from reportportal_client.logs import MAX_LOG_BATCH_PAYLOAD_SIZE -from reportportal_client.steps import Step +from reportportal_client.steps import StepReporter from pytest_reportportal import LAUNCH_WAIT_TIMEOUT from pytest_reportportal.config import AgentConfig @@ -266,11 +267,9 @@ def pytest_runtest_protocol(item: Item) -> None: agent_config = config._reporter_config service.start_pytest_item(item) log_level = agent_config.rp_log_level or logging.NOTSET - log_handler = RPLogHandler(level=log_level, - filter_client_logs=True, - endpoint=agent_config.rp_endpoint, - ignored_record_names=('reportportal_client', - 'pytest_reportportal')) + log_handler = RPLogHandler( + level=log_level, filter_client_logs=True, endpoint=agent_config.rp_endpoint, + ignored_record_names=('reportportal_client', 'pytest_reportportal')) log_format = agent_config.rp_log_format if log_format: log_handler.setFormatter(logging.Formatter(log_format)) @@ -298,40 +297,50 @@ def pytest_runtest_makereport(item: Item) -> None: service.process_results(item, report) -@pytest.hookimpl(hookwrapper=True) -def pytest_fixture_setup(fixturedef: FixtureDef, request: FixtureRequest) -> None: +def report_fixture(request: FixtureRequest, name: str, error_msg: str) -> None: + """Report fixture setup and teardown. + + :param request: Object of the FixtureRequest class + :param name: Name of the fixture + :param error_msg: Error message + """ config = request.config # noinspection PyUnresolvedReferences, PyProtectedMember - if not config._rp_enabled: + agent_config = config._reporter_config + # noinspection PyUnresolvedReferences, PyProtectedMember + if not config._rp_enabled or not agent_config.rp_report_fixtures: yield return - name = fixturedef.argname - scope = fixturedef.scope - with Step(f'{scope} fixture setup: {name}', {}, 'PASSED', None): + reporter = StepReporter(current()) + item_id = reporter.start_nested_step(name, timestamp()) + + try: outcome: Result = yield if outcome.exception: - log.error(f'{scope} fixture setup failed: {name}') + log.error(error_msg) log.exception(outcome.exception) - raise outcome.exception + reporter.finish_nested_step(item_id, timestamp(), 'FAILED') + else: + reporter.finish_nested_step(item_id, timestamp(), 'PASSED') + except Exception as e: + log.error('Failed to report fixture: %s', name) + log.exception(e) + reporter.finish_nested_step(item_id, timestamp(), 'FAILED') @pytest.hookimpl(hookwrapper=True) -def pytest_fixture_post_finalizer(fixturedef: FixtureDef, request: FixtureRequest) -> None: - config = request.config - # noinspection PyUnresolvedReferences, PyProtectedMember - if not config._rp_enabled: - yield - return +def pytest_fixture_setup(fixturedef: FixtureDef, request: FixtureRequest) -> None: + yield from report_fixture( + request, f'{fixturedef.scope} fixture setup: {fixturedef.argname}', + f'{fixturedef.scope} fixture setup failed: {fixturedef.argname}') - name = fixturedef.argname - scope = fixturedef.scope - with Step(f'{scope} fixture teardown: {name}', {}, 'PASSED', None): - outcome: Result = yield - if outcome.exception: - log.error(f'{scope} fixture teardown failed: {name}') - log.exception(outcome.exception) - raise outcome.exception + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_post_finalizer(fixturedef: FixtureDef, request: FixtureRequest) -> None: + yield from report_fixture( + request, f'{fixturedef.scope} fixture teardown: {fixturedef.argname}', + f'{fixturedef.scope} fixture teardown failed: {fixturedef.argname}') def pytest_addoption(parser: Parser) -> None: @@ -552,3 +561,9 @@ def add_shared_option(name, help_str, default=None, action='store'): 'rp_read_timeout', help='Response read timeout for ReportPortal connection' ) + parser.addini( + 'rp_report_fixtures', + default=False, + type='bool', + help='Enable reporting fixtures as test items. Possible values: [True, False]' + ) From d929237ef3bf99523998f4c40a61002b48758f2c Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 13:52:15 +0300 Subject: [PATCH 08/43] Test examples update --- .../test_fixture_session_setup.py | 15 +++++++-- .../test_fixture_package_setup_first.py | 28 ++++++++++++++++ ...y => test_fixture_package_setup_second.py} | 4 --- .../test_fixture_session_setup_first.py | 29 ++++++++++++++++ ...y => test_fixture_session_setup_second.py} | 4 --- .../conftest.py | 12 +++++-- .../test_fixture_return_none.py} | 0 .../conftest.py | 10 +++++- .../test_fixture_setup/test_fixture_setup.py | 29 ++++++++++++++++ .../test_fixture_teardown/conftest.py | 33 +++++++++++++++++++ .../test_fixture_teardown.py | 3 ++ 11 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 examples/fixtures/package_fixture_return/test_fixture_package_setup_first.py rename examples/fixtures/package_fixture_return/{test_fixture_package_setup.py => test_fixture_package_setup_second.py} (93%) create mode 100644 examples/fixtures/session_fixture_return/test_fixture_session_setup_first.py rename examples/fixtures/session_fixture_return/{test_fixture_session_setup.py => test_fixture_session_setup_second.py} (93%) rename examples/fixtures/{test_fixture_yield => test_fixture_return_none}/conftest.py (71%) rename examples/fixtures/{test_fixture_return/test_fixture_setup.py => test_fixture_return_none/test_fixture_return_none.py} (100%) rename examples/fixtures/{test_fixture_return => test_fixture_setup}/conftest.py (74%) create mode 100644 examples/fixtures/test_fixture_setup/test_fixture_setup.py create mode 100644 examples/fixtures/test_fixture_teardown/conftest.py rename examples/fixtures/{test_fixture_yield => test_fixture_teardown}/test_fixture_teardown.py (96%) diff --git a/examples/fixtures/class_fixture_return/test_fixture_session_setup.py b/examples/fixtures/class_fixture_return/test_fixture_session_setup.py index 79f2c7e4..a2eecb76 100644 --- a/examples/fixtures/class_fixture_return/test_fixture_session_setup.py +++ b/examples/fixtures/class_fixture_return/test_fixture_session_setup.py @@ -24,6 +24,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -class TestClass: - def test_fixture_class_setup(self, mocked_config): +class TestClassOne: + def test_fixture_class_setup_first(self, mocked_config): + assert mocked_config is not None + + def test_fixture_class_setup_second(self, mocked_config): + assert mocked_config is not None + + +class TestClassTwo: + def test_fixture_class_setup_forth(self, mocked_config): + assert mocked_config is not None + + def test_fixture_class_setup_fifth(self, mocked_config): assert mocked_config is not None diff --git a/examples/fixtures/package_fixture_return/test_fixture_package_setup_first.py b/examples/fixtures/package_fixture_return/test_fixture_package_setup_first.py new file mode 100644 index 00000000..955dad3c --- /dev/null +++ b/examples/fixtures/package_fixture_return/test_fixture_package_setup_first.py @@ -0,0 +1,28 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def test_fixture_package_setup_first(mocked_config): + assert mocked_config is not None diff --git a/examples/fixtures/package_fixture_return/test_fixture_package_setup.py b/examples/fixtures/package_fixture_return/test_fixture_package_setup_second.py similarity index 93% rename from examples/fixtures/package_fixture_return/test_fixture_package_setup.py rename to examples/fixtures/package_fixture_return/test_fixture_package_setup_second.py index 7d5601e0..138f4b91 100644 --- a/examples/fixtures/package_fixture_return/test_fixture_package_setup.py +++ b/examples/fixtures/package_fixture_return/test_fixture_package_setup_second.py @@ -24,9 +24,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -def test_fixture_package_setup_first(mocked_config): - assert mocked_config is not None - - def test_fixture_package_setup_second(mocked_config): assert mocked_config is not None diff --git a/examples/fixtures/session_fixture_return/test_fixture_session_setup_first.py b/examples/fixtures/session_fixture_return/test_fixture_session_setup_first.py new file mode 100644 index 00000000..83d4f1e1 --- /dev/null +++ b/examples/fixtures/session_fixture_return/test_fixture_session_setup_first.py @@ -0,0 +1,29 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_fixture_session_setup_first(mocked_config): + assert mocked_config is not None diff --git a/examples/fixtures/session_fixture_return/test_fixture_session_setup.py b/examples/fixtures/session_fixture_return/test_fixture_session_setup_second.py similarity index 93% rename from examples/fixtures/session_fixture_return/test_fixture_session_setup.py rename to examples/fixtures/session_fixture_return/test_fixture_session_setup_second.py index d56dec06..2bd544d8 100644 --- a/examples/fixtures/session_fixture_return/test_fixture_session_setup.py +++ b/examples/fixtures/session_fixture_return/test_fixture_session_setup_second.py @@ -25,9 +25,5 @@ # limitations under the License. -def test_fixture_session_setup_first(mocked_config): - assert mocked_config is not None - - def test_fixture_session_setup_second(mocked_config): assert mocked_config is not None diff --git a/examples/fixtures/test_fixture_yield/conftest.py b/examples/fixtures/test_fixture_return_none/conftest.py similarity index 71% rename from examples/fixtures/test_fixture_yield/conftest.py rename to examples/fixtures/test_fixture_return_none/conftest.py index 0f5fa125..8496549f 100644 --- a/examples/fixtures/test_fixture_yield/conftest.py +++ b/examples/fixtures/test_fixture_return_none/conftest.py @@ -12,12 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from unittest import mock +import logging import pytest +from reportportal_client import RPLogger + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logging.setLoggerClass(RPLogger) + +LOG_MESSAGE_SETUP = 'Log message for setup and return None' @pytest.fixture def mocked_config(): - yield mock.Mock() - print('teardown') + LOGGER.warn(LOG_MESSAGE_SETUP) diff --git a/examples/fixtures/test_fixture_return/test_fixture_setup.py b/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py similarity index 100% rename from examples/fixtures/test_fixture_return/test_fixture_setup.py rename to examples/fixtures/test_fixture_return_none/test_fixture_return_none.py diff --git a/examples/fixtures/test_fixture_return/conftest.py b/examples/fixtures/test_fixture_setup/conftest.py similarity index 74% rename from examples/fixtures/test_fixture_return/conftest.py rename to examples/fixtures/test_fixture_setup/conftest.py index 56d66de1..eeecc1bb 100644 --- a/examples/fixtures/test_fixture_return/conftest.py +++ b/examples/fixtures/test_fixture_setup/conftest.py @@ -12,12 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging from unittest import mock import pytest +from reportportal_client import RPLogger + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logging.setLoggerClass(RPLogger) + +LOG_MESSAGE_SETUP = 'Log message for setup' @pytest.fixture def mocked_config(): - print('setup') + logging.error(LOG_MESSAGE_SETUP) return mock.Mock() diff --git a/examples/fixtures/test_fixture_setup/test_fixture_setup.py b/examples/fixtures/test_fixture_setup/test_fixture_setup.py new file mode 100644 index 00000000..04270723 --- /dev/null +++ b/examples/fixtures/test_fixture_setup/test_fixture_setup.py @@ -0,0 +1,29 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_fixture_setup(mocked_config): + assert mocked_config is not None diff --git a/examples/fixtures/test_fixture_teardown/conftest.py b/examples/fixtures/test_fixture_teardown/conftest.py new file mode 100644 index 00000000..7e53f49f --- /dev/null +++ b/examples/fixtures/test_fixture_teardown/conftest.py @@ -0,0 +1,33 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from unittest import mock + +import pytest +from reportportal_client import RPLogger + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logging.setLoggerClass(RPLogger) + +LOG_MESSAGE_BEFORE_YIELD = 'Log message before yield' +LOG_MESSAGE_TEARDOWN = 'Log message for teardown' + + +@pytest.fixture +def mocked_config(): + logging.error(LOG_MESSAGE_BEFORE_YIELD) + yield mock.Mock() + logging.error(LOG_MESSAGE_TEARDOWN) diff --git a/examples/fixtures/test_fixture_yield/test_fixture_teardown.py b/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py similarity index 96% rename from examples/fixtures/test_fixture_yield/test_fixture_teardown.py rename to examples/fixtures/test_fixture_teardown/test_fixture_teardown.py index 46472f88..52be323c 100644 --- a/examples/fixtures/test_fixture_yield/test_fixture_teardown.py +++ b/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py @@ -24,6 +24,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from time import sleep + def test_fixture_teardown(mocked_config): + sleep(0.001) assert mocked_config is not None From 54aba5ecb13086548384ea6b5830df03310c9dc1 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 13:57:18 +0300 Subject: [PATCH 09/43] Test formatting update --- tests/integration/__init__.py | 4 +- tests/integration/test_attributes.py | 53 +++++-------------- tests/integration/test_case_id_report.py | 26 +++------ tests/integration/test_code_reference.py | 16 ++---- tests/integration/test_connection_close.py | 6 +-- tests/integration/test_debug_mode.py | 14 ++--- tests/integration/test_empty_run.py | 12 ++--- tests/integration/test_fixtures.py | 11 ++-- tests/integration/test_issue_report.py | 24 ++++----- tests/integration/test_parameters_report.py | 10 ++-- tests/integration/test_pass_failed_skipped.py | 28 +++++----- tests/integration/test_pytest_parallel.py | 15 ++---- tests/integration/test_suite_hierarchy.py | 3 +- tests/integration/test_threads_logs.py | 3 +- 14 files changed, 81 insertions(+), 144 deletions(-) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index 21bd2b2e..ae8d3520 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -240,7 +240,5 @@ HIERARCHY_TEST_PARAMETERS = [ (test, HIERARCHY_TEST_VARIABLES[idx], HIERARCHY_TEST_EXPECTED_ITEMS[idx]) - for - idx, test in - enumerate(HIERARCHY_TESTS) + for idx, test in enumerate(HIERARCHY_TESTS) ] diff --git a/tests/integration/test_attributes.py b/tests/integration/test_attributes.py index 5bd7fa86..2c6c4a52 100644 --- a/tests/integration/test_attributes.py +++ b/tests/integration/test_attributes.py @@ -25,19 +25,13 @@ def test_custom_attribute_report(mock_client_init): :param mock_client_init: Pytest fixture """ - variables = { - 'markers': 'scope: to which test scope a test relates' - } + variables = {'markers': 'scope: to which test scope a test relates'} variables.update(utils.DEFAULT_VARIABLES.items()) - result = utils.run_pytest_tests( - tests=['examples/attributes/test_one_attribute.py'], - variables=variables - ) + result = utils.run_pytest_tests(tests=['examples/attributes/test_one_attribute.py'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' mock_client = mock_client_init.return_value - assert mock_client.start_test_item.call_count > 0, \ - '"start_test_item" called incorrect number of times' + assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times' call_args = mock_client.start_test_item.call_args_list step_call_args = call_args[-1][1] @@ -55,15 +49,11 @@ def test_custom_attribute_not_reported_if_skip_configured(mock_client_init): 'rp_ignore_attributes': 'scope' } variables.update(utils.DEFAULT_VARIABLES.items()) - result = utils.run_pytest_tests( - tests=['examples/attributes/test_one_attribute.py'], - variables=variables - ) + result = utils.run_pytest_tests(tests=['examples/attributes/test_one_attribute.py'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' mock_client = mock_client_init.return_value - assert mock_client.start_test_item.call_count > 0, \ - '"start_test_item" called incorrect number of times' + assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times' call_args = mock_client.start_test_item.call_args_list step_call_args = call_args[-1][1] @@ -76,19 +66,14 @@ def test_two_attributes_different_values_report(mock_client_init): :param mock_client_init: Pytest fixture """ - variables = { - 'markers': 'scope: to which test scope a test relates' - } + variables = {'markers': 'scope: to which test scope a test relates'} variables.update(utils.DEFAULT_VARIABLES.items()) result = utils.run_pytest_tests( - tests=['examples/attributes/test_two_attributes_with_same_key.py'], - variables=variables - ) + tests=['examples/attributes/test_two_attributes_with_same_key.py'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' mock_client = mock_client_init.return_value - assert mock_client.start_test_item.call_count > 0, \ - '"start_test_item" called incorrect number of times' + assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times' call_args = mock_client.start_test_item.call_args_list step_call_args = call_args[-1][1] @@ -106,13 +91,11 @@ def test_skip_attribute(mock_client_init): :param mock_client_init: Pytest fixture """ - result = utils.run_pytest_tests( - tests=['examples/skip/test_simple_skip.py']) + result = utils.run_pytest_tests(tests=['examples/skip/test_simple_skip.py']) assert int(result) == 0, 'Exit code should be 0 (no errors)' mock_client = mock_client_init.return_value - assert mock_client.start_test_item.call_count > 0, \ - '"start_test_item" called incorrect number of times' + assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times' call_args = mock_client.start_test_item.call_args_list step_call_args = call_args[-1][1] @@ -129,22 +112,14 @@ def test_custom_runtime_attribute_report(mock_client_init): :param mock_client_init: Pytest fixture """ - variables = { - 'markers': 'scope: to which test scope a test relates\n' - 'runtime: runtime attribute mark' - } + variables = {'markers': 'scope: to which test scope a test relates\nruntime: runtime attribute mark'} variables.update(utils.DEFAULT_VARIABLES.items()) - result = utils.run_pytest_tests( - tests=['examples/attributes/test_runtime_attribute.py'], - variables=variables - ) + result = utils.run_pytest_tests(tests=['examples/attributes/test_runtime_attribute.py'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' mock_client = mock_client_init.return_value - assert mock_client.start_test_item.call_count > 0, \ - '"start_test_item" called incorrect number of times' - assert mock_client.finish_test_item.call_count > 0, \ - '"finish_test_item" called incorrect number of times' + assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times' + assert mock_client.finish_test_item.call_count > 0, '"finish_test_item" called incorrect number of times' start_call_args = mock_client.start_test_item.call_args_list start_step_call_args = start_call_args[-1][1] diff --git a/tests/integration/test_case_id_report.py b/tests/integration/test_case_id_report.py index 2704b506..345294e4 100644 --- a/tests/integration/test_case_id_report.py +++ b/tests/integration/test_case_id_report.py @@ -27,25 +27,18 @@ @pytest.mark.parametrize(['test', 'expected_id'], [ ('examples/test_simple.py', 'examples/test_simple.py:test_simple'), ('examples/params/test_in_class_parameterized.py', - 'examples/params/test_in_class_parameterized.py:Tests.' - 'test_in_class_parameterized[param]'), - ('examples/test_case_id/test_case_id_decorator.py', - test_case_id_decorator.TEST_CASE_ID), - ('examples/test_case_id/test_case_id_decorator_params_false.py', - test_case_id_decorator_params_false.TEST_CASE_ID), - ('examples/test_case_id/test_case_id_decorator_params_no.py', - test_case_id_decorator_params_no.TEST_CASE_ID), + 'examples/params/test_in_class_parameterized.py:Tests.test_in_class_parameterized[param]'), + ('examples/test_case_id/test_case_id_decorator.py', test_case_id_decorator.TEST_CASE_ID), + ('examples/test_case_id/test_case_id_decorator_params_false.py', test_case_id_decorator_params_false.TEST_CASE_ID), + ('examples/test_case_id/test_case_id_decorator_params_no.py', test_case_id_decorator_params_no.TEST_CASE_ID), ('examples/test_case_id/test_case_id_decorator_params_partially.py', test_case_id_decorator_params_partially.TEST_CASE_ID + '[value1]'), ('examples/test_case_id/test_case_id_decorator_params_true.py', test_case_id_decorator_params_true.TEST_CASE_ID + '[value1,value2]'), ('examples/test_case_id/test_case_id_decorator_no_id.py', ''), ('examples/test_case_id/test_case_id_decorator_no_id_params_false.py', ''), - ('examples/test_case_id/test_case_id_decorator_no_id_params_true.py', - '[value1,value2]'), - ('examples/test_case_id/' - 'test_case_id_decorator_no_id_partial_params_true.py', - '[value2]') + ('examples/test_case_id/test_case_id_decorator_no_id_params_true.py', '[value1,value2]'), + ('examples/test_case_id/test_case_id_decorator_no_id_partial_params_true.py', '[value2]') ]) def test_parameters(mock_client_init, test, expected_id): """Verify different tests have correct Test Case IDs. @@ -54,14 +47,11 @@ def test_parameters(mock_client_init, test, expected_id): :param test: a test to run :param expected_id: an expected Test Case ID """ - variables = utils.DEFAULT_VARIABLES - result = utils.run_pytest_tests(tests=[test], - variables=variables) + result = utils.run_pytest_tests(tests=[test]) assert int(result) == 0, 'Exit code should be 0 (no errors)' mock_client = mock_client_init.return_value - assert mock_client.start_test_item.call_count > 0, \ - '"start_test_item" called incorrect number of times' + assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times' call_args = mock_client.start_test_item.call_args_list step_call_args = call_args[-1][1] diff --git a/tests/integration/test_code_reference.py b/tests/integration/test_code_reference.py index b093372b..4a3af896 100644 --- a/tests/integration/test_code_reference.py +++ b/tests/integration/test_code_reference.py @@ -24,13 +24,10 @@ @pytest.mark.parametrize(['test', 'code_ref'], [ ('examples/test_simple.py', 'examples/test_simple.py:test_simple'), ('examples/params/test_in_class_parameterized.py', - 'examples/params/test_in_class_parameterized.py:' - 'Tests.test_in_class_parameterized'), - ('examples/hierarchy/test_in_class.py', - 'examples/hierarchy/test_in_class.py:Tests.test_in_class'), + 'examples/params/test_in_class_parameterized.py:Tests.test_in_class_parameterized'), + ('examples/hierarchy/test_in_class.py', 'examples/hierarchy/test_in_class.py:Tests.test_in_class'), ('examples/hierarchy/test_in_class_in_class.py', - 'examples/hierarchy/test_in_class_in_class.py:' - 'Tests.Test.test_in_class_in_class') + 'examples/hierarchy/test_in_class_in_class.py:Tests.Test.test_in_class_in_class') ]) def test_code_reference(mock_client_init, test, code_ref): """Verify different tests have correct code reference. @@ -39,14 +36,11 @@ def test_code_reference(mock_client_init, test, code_ref): :param test: a test to run :param code_ref: an expected code reference value """ - variables = utils.DEFAULT_VARIABLES - result = utils.run_pytest_tests(tests=[test], - variables=variables) + result = utils.run_pytest_tests(tests=[test]) assert int(result) == 0, 'Exit code should be 0 (no errors)' mock_client = mock_client_init.return_value - assert mock_client.start_test_item.call_count > 0, \ - '"start_test_item" called incorrect number of times' + assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times' call_args = mock_client.start_test_item.call_args_list step_call_args = call_args[-1][1] diff --git a/tests/integration/test_connection_close.py b/tests/integration/test_connection_close.py index 87b4d068..33be3096 100644 --- a/tests/integration/test_connection_close.py +++ b/tests/integration/test_connection_close.py @@ -21,9 +21,7 @@ def test_connection_close(mock_client_init): mock_client = mock_client_init.return_value - result = utils.run_tests_with_client( - mock_client, ['examples/test_rp_logging.py']) + result = utils.run_tests_with_client(mock_client, ['examples/test_rp_logging.py']) assert int(result) == 0, 'Exit code should be 0 (no errors)' - assert mock_client.close.call_count == 1, \ - '"close" method was not called at the end of the test' + assert mock_client.close.call_count == 1, '"close" method was not called at the end of the test' diff --git a/tests/integration/test_debug_mode.py b/tests/integration/test_debug_mode.py index 434f6cc4..d380b313 100644 --- a/tests/integration/test_debug_mode.py +++ b/tests/integration/test_debug_mode.py @@ -13,17 +13,20 @@ """This module includes integration tests for the debug mode switch.""" -import pytest from unittest import mock +import pytest + from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils @mock.patch(REPORT_PORTAL_SERVICE) -@pytest.mark.parametrize(['mode', 'expected_mode'], [('DEFAULT', 'DEFAULT'), - ('DEBUG', 'DEBUG'), - (None, 'DEFAULT')]) +@pytest.mark.parametrize(['mode', 'expected_mode'], [ + ('DEFAULT', 'DEFAULT'), + ('DEBUG', 'DEBUG'), + (None, 'DEFAULT') +]) def test_launch_mode(mock_client_init, mode, expected_mode): """Verify different launch modes are passed to `start_launch` method. @@ -36,8 +39,7 @@ def test_launch_mode(mock_client_init, mode, expected_mode): if mode is not None: variables['rp_mode'] = mode variables.update(utils.DEFAULT_VARIABLES.items()) - result = utils.run_pytest_tests(tests=['examples/test_simple.py'], - variables=variables) + result = utils.run_pytest_tests(tests=['examples/test_simple.py'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' assert mock_client_init.call_count == 1, "client wasn't initialized" diff --git a/tests/integration/test_empty_run.py b/tests/integration/test_empty_run.py index 078e518e..b5e5b1ab 100644 --- a/tests/integration/test_empty_run.py +++ b/tests/integration/test_empty_run.py @@ -31,16 +31,12 @@ def test_empty_run(mock_client_init): assert int(result) == 5, 'Exit code should be 5 (no tests)' mock_client = mock_client_init.return_value - expect(mock_client.start_launch.call_count == 1, - '"start_launch" method was not called') - expect(mock_client.finish_launch.call_count == 1, - '"finish_launch" method was not called') + expect(mock_client.start_launch.call_count == 1, '"start_launch" method was not called') + expect(mock_client.finish_launch.call_count == 1, '"finish_launch" method was not called') assert_expectations() finish_args = mock_client.finish_launch.call_args_list - expect('status' not in finish_args[0][1], - 'Launch status should not be defined') + expect('status' not in finish_args[0][1], 'Launch status should not be defined') launch_end_time = finish_args[0][1]['end_time'] - expect(launch_end_time is not None and int(launch_end_time) > 0, - 'Launch end time is empty') + expect(launch_end_time is not None and int(launch_end_time) > 0, 'Launch end time is empty') assert_expectations() diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index 19410d1f..0965f125 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from tests.helpers import utils - +from unittest import mock -# TODO: finish these tests +from tests import REPORT_PORTAL_SERVICE +from tests.helpers import utils -def test_fixture_simple(): +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_simple(mock_client_init): variables = utils.DEFAULT_VARIABLES - result = utils.run_pytest_tests(tests=['examples/fixtures/before_test/test_fixture_setup.py'], variables=variables) + result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_setup'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' diff --git a/tests/integration/test_issue_report.py b/tests/integration/test_issue_report.py index b0717908..dde357d1 100644 --- a/tests/integration/test_issue_report.py +++ b/tests/integration/test_issue_report.py @@ -13,10 +13,11 @@ """This module includes integration test for issue type reporting.""" +from unittest import mock + import pytest from delayed_assert import expect, assert_expectations from reportportal_client.core.rp_issues import Issue -from unittest import mock from examples import test_issue_id from pytest_reportportal.service import NOT_ISSUE @@ -72,8 +73,7 @@ def test_issue_report(mock_client_init): variables = {'rp_issue_system_url': ISSUE_URL_PATTERN} variables.update(utils.DEFAULT_VARIABLES.items()) - result = utils.run_pytest_tests(tests=['examples/test_issue_id.py'], - variables=variables) + result = utils.run_pytest_tests(tests=['examples/test_issue_id.py'], variables=variables) assert int(result) == 1, 'Exit code should be 1 (test failed)' call_args = mock_client.finish_test_item.call_args_list @@ -87,9 +87,8 @@ def test_issue_report(mock_client_init): comments = issue.comment.split('\n') assert len(comments) == 1 comment = comments[0] - assert comment == "* {}: [{}]({})" \ - .format(test_issue_id.REASON, test_issue_id.ID, - ISSUE_URL_PATTERN.replace(ISSUE_PLACEHOLDER, test_issue_id.ID)) + assert comment == "* {}: [{}]({})".format( + test_issue_id.REASON, test_issue_id.ID, ISSUE_URL_PATTERN.replace(ISSUE_PLACEHOLDER, test_issue_id.ID)) @mock.patch(REPORT_PORTAL_SERVICE) @@ -112,9 +111,11 @@ def test_passed_no_issue_report(mock_client_init): assert 'issue' not in finish_test_step or finish_test_step['issue'] is None -@pytest.mark.parametrize(('flag_value', 'expected_issue'), [(True, None), - (False, NOT_ISSUE), - (None, None)]) +@pytest.mark.parametrize(('flag_value', 'expected_issue'), [ + (True, None), + (False, NOT_ISSUE), + (None, None) +]) @mock.patch(REPORT_PORTAL_SERVICE) def test_skipped_not_issue(mock_client_init, flag_value, expected_issue): """Verify 'rp_is_skipped_an_issue' option handling. @@ -131,10 +132,7 @@ def test_skipped_not_issue(mock_client_init, flag_value, expected_issue): variables['rp_is_skipped_an_issue'] = flag_value variables.update(utils.DEFAULT_VARIABLES.items()) - result = utils.run_pytest_tests( - tests=['examples/skip/test_simple_skip.py'], - variables=variables - ) + result = utils.run_pytest_tests(tests=['examples/skip/test_simple_skip.py'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no failures)' call_args = mock_client.finish_test_item.call_args_list diff --git a/tests/integration/test_parameters_report.py b/tests/integration/test_parameters_report.py index 0d9bcdf4..47177108 100644 --- a/tests/integration/test_parameters_report.py +++ b/tests/integration/test_parameters_report.py @@ -25,8 +25,7 @@ ('examples/test_simple.py', None), ('examples/params/test_in_class_parameterized.py', {'param': 'param'}), ('examples/params/test_different_parameter_types.py', - {'integer': 1, 'floating_point': 1.5, 'boolean': True, - 'none': None}) + {'integer': 1, 'floating_point': 1.5, 'boolean': True, 'none': None}) ]) def test_parameters(mock_client_init, test, expected_params): """Verify different tests have correct parameters. @@ -35,14 +34,11 @@ def test_parameters(mock_client_init, test, expected_params): :param test: a test to run :param expected_params: an expected parameter dictionary """ - variables = utils.DEFAULT_VARIABLES - result = utils.run_pytest_tests(tests=[test], - variables=variables) + result = utils.run_pytest_tests(tests=[test]) assert int(result) == 0, 'Exit code should be 0 (no errors)' mock_client = mock_client_init.return_value - assert mock_client.start_test_item.call_count > 0, \ - '"start_test_item" called incorrect number of times' + assert mock_client.start_test_item.call_count > 0, '"start_test_item" called incorrect number of times' call_args = mock_client.start_test_item.call_args_list step_call_args = call_args[-1][1] diff --git a/tests/integration/test_pass_failed_skipped.py b/tests/integration/test_pass_failed_skipped.py index cef2d850..f69c4e7d 100644 --- a/tests/integration/test_pass_failed_skipped.py +++ b/tests/integration/test_pass_failed_skipped.py @@ -13,24 +13,23 @@ """This module includes integration tests for item statuses report.""" +from unittest import mock + import pytest from delayed_assert import expect, assert_expectations -from unittest import mock from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils -@pytest.mark.parametrize(('test', 'expected_run_status', - 'expected_item_status'), [ - ('examples/test_simple.py', 0, 'PASSED'), - ('examples/test_simple_fail.py', 1, 'FAILED'), - ('examples/skip/test_simple_skip.py', 0, - 'SKIPPED') - ]) +@pytest.mark.parametrize(('test', 'expected_run_status', 'expected_item_status'), [ + ('examples/test_simple.py', 0, 'PASSED'), + ('examples/test_simple_fail.py', 1, 'FAILED'), + ('examples/skip/test_simple_skip.py', 0, + 'SKIPPED') +]) @mock.patch(REPORT_PORTAL_SERVICE) -def test_simple_tests(mock_client_init, test, expected_run_status, - expected_item_status): +def test_simple_tests(mock_client_init, test, expected_run_status, expected_item_status): """Verify a simple test creates correct structure and finishes all items. Report 'None' for suites and launch due to possible parallel execution. @@ -44,13 +43,11 @@ def test_simple_tests(mock_client_init, test, expected_run_status, mock_client.start_test_item.side_effect = utils.item_id_gen result = utils.run_pytest_tests(tests=[test]) - assert int(result) == expected_run_status, 'Exit code should be ' + str( - expected_run_status) + assert int(result) == expected_run_status, 'Exit code should be ' + str(expected_run_status) start_call_args = mock_client.start_test_item.call_args_list finish_call_args = mock_client.finish_test_item.call_args_list - assert len(start_call_args) == len(finish_call_args), \ - 'Number of started items should be equal to finished items' + assert len(start_call_args) == len(finish_call_args), 'Number of started items should be equal to finished items' for i in range(len(start_call_args)): start_test_step = start_call_args[-1 - i][1] @@ -60,8 +57,7 @@ def test_simple_tests(mock_client_init, test, expected_run_status, if i == 0: actual_status = finish_test_step['status'] expect(actual_status == expected_item_status, - 'Invalid item status, actual "{}", expected: "{}"' - .format(actual_status, expected_item_status)) + f'Invalid item status, actual "{actual_status}", expected: "{expected_item_status}"') finish_launch_call_args = mock_client.finish_launch.call_args_list expect(len(finish_launch_call_args) == 1) diff --git a/tests/integration/test_pytest_parallel.py b/tests/integration/test_pytest_parallel.py index 7488c7d1..7007882f 100644 --- a/tests/integration/test_pytest_parallel.py +++ b/tests/integration/test_pytest_parallel.py @@ -23,8 +23,7 @@ @mock.patch(REPORT_PORTAL_SERVICE) -@pytest.mark.skip(reason='This test breaks all other tests, so only for local ' - 'execution') +@pytest.mark.skip(reason='This test breaks all other tests, so only for local execution') def test_pytest_parallel_threads(mock_client_init): """Verify "pytest_parallel" plugin run tests in two threads. @@ -33,21 +32,17 @@ def test_pytest_parallel_threads(mock_client_init): mock_client = mock_client_init.return_value mock_client.start_test_item.side_effect = item_id_gen - result = utils.run_pytest_tests(tests=['examples/hierarchy'], - args=['--tests-per-worker', '2']) + result = utils.run_pytest_tests(tests=['examples/hierarchy'], args=['--tests-per-worker', '2']) assert int(result) == 0, 'Exit code should be 0 (no errors)' mock_client = mock_client_init.return_value - expect(mock_client.start_launch.call_count == 1, - '"start_launch" method was not called') - expect(mock_client.finish_launch.call_count == 1, - '"finish_launch" method was not called') + expect(mock_client.start_launch.call_count == 1, '"start_launch" method was not called') + expect(mock_client.finish_launch.call_count == 1, '"finish_launch" method was not called') assert_expectations() finish_args = mock_client.finish_launch.call_args_list expect(finish_args[0][1]['status'] in ('PASSED', None), 'Launch failed') launch_end_time = finish_args[0][1]['end_time'] - expect(launch_end_time is not None and int(launch_end_time) > 0, - 'Launch end time is empty') + expect(launch_end_time is not None and int(launch_end_time) > 0, 'Launch end time is empty') assert_expectations() diff --git a/tests/integration/test_suite_hierarchy.py b/tests/integration/test_suite_hierarchy.py index a537b52f..d61a272a 100644 --- a/tests/integration/test_suite_hierarchy.py +++ b/tests/integration/test_suite_hierarchy.py @@ -39,8 +39,7 @@ def verify_start_item_parameters(mock_client, expected_items): @pytest.mark.parametrize(('test', 'variables', 'expected_items'), HIERARCHY_TEST_PARAMETERS) @mock.patch(REPORT_PORTAL_SERVICE) -def test_rp_hierarchy_parameters(mock_client_init, test, variables, - expected_items): +def test_rp_hierarchy_parameters(mock_client_init, test, variables, expected_items): """Verify suite hierarchy with `rp_hierarchy_dirs=True`. :param mock_client_init: Pytest fixture diff --git a/tests/integration/test_threads_logs.py b/tests/integration/test_threads_logs.py index 40f89156..d6814f45 100644 --- a/tests/integration/test_threads_logs.py +++ b/tests/integration/test_threads_logs.py @@ -39,7 +39,6 @@ def init_thread_client(*_, **__): ) assert int(result) == 0, 'Exit code should be 0 (no errors)' - assert mock_client.start_launch.call_count == 1, \ - '"start_launch" method was not called' + assert mock_client.start_launch.call_count == 1, '"start_launch" method was not called' assert mock_client.log.call_count == 1 assert mock_thread_client.log.call_count == 2 From 22b024a5cb531923d16dcf995136574fb3533aeb Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 14:41:12 +0300 Subject: [PATCH 10/43] Add fixture on/off test --- pytest_reportportal/plugin.py | 21 ++++++++++----------- tests/integration/test_fixtures.py | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 934e1e9d..1f1f6c2f 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -22,7 +22,9 @@ import _pytest.logging import dill as pickle import pytest +# noinspection PyPackageRequirements import requests +# noinspection PyPackageRequirements from pluggy import Result from pytest import Config, FixtureDef, FixtureRequest, Parser, Session, Item from reportportal_client import RPLogHandler, RP, current @@ -174,18 +176,15 @@ def check_connection(agent_config: AgentConfig): :param agent_config: Instance of the AgentConfig class :return True on successful connection check, either False """ - url = '{0}/api/v1/project/{1}'.format(agent_config.rp_endpoint, - agent_config.rp_project) + url = '{0}/api/v1/project/{1}'.format(agent_config.rp_endpoint, agent_config.rp_project) headers = {'Authorization': 'bearer {0}'.format(agent_config.rp_api_key)} try: - resp = requests.get(url, headers=headers, - verify=agent_config.rp_verify_ssl) + resp = requests.get(url, headers=headers, verify=agent_config.rp_verify_ssl) resp.raise_for_status() return True except requests.exceptions.RequestException as exc: log.exception(exc) - log.error("Unable to connect to Report Portal, the launch won't be" - " reported") + log.error("Unable to connect to Report Portal, the launch won't be reported") return False @@ -305,14 +304,14 @@ def report_fixture(request: FixtureRequest, name: str, error_msg: str) -> None: :param error_msg: Error message """ config = request.config - # noinspection PyUnresolvedReferences, PyProtectedMember - agent_config = config._reporter_config - # noinspection PyUnresolvedReferences, PyProtectedMember - if not config._rp_enabled or not agent_config.rp_report_fixtures: + enabled = getattr(config, '_rp_enabled', False) + agent_config = getattr(config, '_reporter_config', None) + service = getattr(config, 'py_test_service', None) + if not enabled or not agent_config.rp_report_fixtures or not service: yield return - reporter = StepReporter(current()) + reporter = StepReporter(service.rp) item_id = reporter.start_nested_step(name, timestamp()) try: diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index 0965f125..2c97ab47 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -14,12 +14,22 @@ from unittest import mock +import pytest from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils +@pytest.mark.parametrize('switch', [True, False]) @mock.patch(REPORT_PORTAL_SERVICE) -def test_fixture_simple(mock_client_init): - variables = utils.DEFAULT_VARIABLES - result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_setup'], variables=variables) +def test_fixture_on_off(mock_client_init, switch): + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = switch + result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_teardown'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' + + mock_client = mock_client_init.return_value + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + expected_count = 3 if switch else 1 + assert start_count == finish_count == expected_count, \ + 'Incorrect number of "start_test_item" or "finish_test_item" calls' From 9c6e081d9140427a32b94c056764d176fcc5523b Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 14:41:30 +0300 Subject: [PATCH 11/43] Fix import --- pytest_reportportal/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 1f1f6c2f..32dfad37 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -27,7 +27,7 @@ # noinspection PyPackageRequirements from pluggy import Result from pytest import Config, FixtureDef, FixtureRequest, Parser, Session, Item -from reportportal_client import RPLogHandler, RP, current +from reportportal_client import RPLogHandler, RP from reportportal_client.errors import ResponseError from reportportal_client.helpers import timestamp from reportportal_client.logs import MAX_LOG_BATCH_PAYLOAD_SIZE From 86e264d23b9fcac04a2553d8ec2e6584bda722fc Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 14:48:47 +0300 Subject: [PATCH 12/43] Revert typing changes --- pytest_reportportal/service.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 1c086189..cbd162f5 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -27,7 +27,6 @@ from pytest import Class, Function, Module, Package, Item, Session, PytestWarning from reportportal_client.aio import Task from reportportal_client.core.rp_issues import Issue, ExternalIssue -from typing_extensions import TypeVar from .config import AgentConfig @@ -109,9 +108,6 @@ class LeafType(Enum): ROOT = auto() -Leaf = TypeVar('Leaf', bound=LeafType) - - @unique class ExecStatus(Enum): """This class stores test item path types.""" @@ -247,7 +243,7 @@ def _get_tree_path(self, item: Item) -> List[Item]: path.reverse() return path - def _get_leaf(self, leaf_type: Leaf, parent_item: Optional[Dict[str, Any]], item: Optional[Item], + def _get_leaf(self, leaf_type: LeafType, parent_item: Optional[Dict[str, Any]], item: Optional[Item], item_id: Optional[str] = None) -> Dict[str, Any]: """Construct a leaf for the itest tree. From 8b013dfe11eed3ae3e862c43f10f04b5c665d162 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 15:43:21 +0300 Subject: [PATCH 13/43] Fix tests --- tests/unit/conftest.py | 1 + tests/unit/test_plugin.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 2c7912d4..232d0734 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -70,6 +70,7 @@ def getoption_side_effect(name, default=None): mocked_config.option.rp_launch_uuid_print = 'False' mocked_config.option.rp_launch_uuid_print_output = 'STDOUT' mocked_config.option.rp_client_type = 'SYNC' + mocked_config.option.rp_report_fixtures = 'False' return mocked_config diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index b9499577..6aa125e3 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -366,7 +366,8 @@ def test_pytest_addoption_adds_correct_ini_file_arguments(): 'rp_launch_timeout', 'rp_client_type', 'rp_connect_timeout', - 'rp_read_timeout' + 'rp_read_timeout', + 'rp_report_fixtures' ) mock_parser = mock.MagicMock(spec=Parser) From 428dc3b4f3429c646070de6a7971ecb55ca40573 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 15:49:49 +0300 Subject: [PATCH 14/43] Fix for Python 3.7 --- pytest_reportportal/plugin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 32dfad37..946283cb 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -24,8 +24,6 @@ import pytest # noinspection PyPackageRequirements import requests -# noinspection PyPackageRequirements -from pluggy import Result from pytest import Config, FixtureDef, FixtureRequest, Parser, Session, Item from reportportal_client import RPLogHandler, RP from reportportal_client.errors import ResponseError @@ -315,7 +313,7 @@ def report_fixture(request: FixtureRequest, name: str, error_msg: str) -> None: item_id = reporter.start_nested_step(name, timestamp()) try: - outcome: Result = yield + outcome = yield if outcome.exception: log.error(error_msg) log.exception(outcome.exception) From 3070a681077089a12d959f6b2803ee822ce94423 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 15:52:46 +0300 Subject: [PATCH 15/43] Fix for Python 3.7 --- pytest_reportportal/plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 946283cb..ab927a30 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -24,7 +24,7 @@ import pytest # noinspection PyPackageRequirements import requests -from pytest import Config, FixtureDef, FixtureRequest, Parser, Session, Item +from pytest import Config, FixtureRequest, Parser, Session, Item from reportportal_client import RPLogHandler, RP from reportportal_client.errors import ResponseError from reportportal_client.helpers import timestamp @@ -327,14 +327,14 @@ def report_fixture(request: FixtureRequest, name: str, error_msg: str) -> None: @pytest.hookimpl(hookwrapper=True) -def pytest_fixture_setup(fixturedef: FixtureDef, request: FixtureRequest) -> None: +def pytest_fixture_setup(fixturedef, request: FixtureRequest) -> None: yield from report_fixture( request, f'{fixturedef.scope} fixture setup: {fixturedef.argname}', f'{fixturedef.scope} fixture setup failed: {fixturedef.argname}') @pytest.hookimpl(hookwrapper=True) -def pytest_fixture_post_finalizer(fixturedef: FixtureDef, request: FixtureRequest) -> None: +def pytest_fixture_post_finalizer(fixturedef, request: FixtureRequest) -> None: yield from report_fixture( request, f'{fixturedef.scope} fixture teardown: {fixturedef.argname}', f'{fixturedef.scope} fixture teardown failed: {fixturedef.argname}') From 512ad53dfa3e80556a79bb7cf97d013fdf09dda0 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 15:54:19 +0300 Subject: [PATCH 16/43] Fix for Python 3.7 --- pytest_reportportal/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index ab927a30..db6ee71e 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -326,6 +326,7 @@ def report_fixture(request: FixtureRequest, name: str, error_msg: str) -> None: reporter.finish_nested_step(item_id, timestamp(), 'FAILED') +# no 'fixturedef' type for backward compatibility for older pytest versions @pytest.hookimpl(hookwrapper=True) def pytest_fixture_setup(fixturedef, request: FixtureRequest) -> None: yield from report_fixture( @@ -333,6 +334,7 @@ def pytest_fixture_setup(fixturedef, request: FixtureRequest) -> None: f'{fixturedef.scope} fixture setup failed: {fixturedef.argname}') +# no 'fixturedef' type for backward compatibility for older pytest versions @pytest.hookimpl(hookwrapper=True) def pytest_fixture_post_finalizer(fixturedef, request: FixtureRequest) -> None: yield from report_fixture( From 6d95984c1e4ac67c01dc1976af2e333d8bfe0667 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 15:58:58 +0300 Subject: [PATCH 17/43] Fix tests --- tests/integration/test_config_handling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_config_handling.py b/tests/integration/test_config_handling.py index 6323e587..1ae30e8f 100644 --- a/tests/integration/test_config_handling.py +++ b/tests/integration/test_config_handling.py @@ -110,7 +110,7 @@ def test_rp_log_format(mock_client_init): expect(mock_client.log.call_count == 1) message = mock_client.log.call_args_list[0][0][1] expect(len(message) > 0) - expect(message == f'(examples.test_rp_logging) {LOG_MESSAGE} (test_rp_logging.py:24)') + expect(message == f'(test_rp_logging) {LOG_MESSAGE} (test_rp_logging.py:24)') assert_expectations() From 5b6fd5d27ea5d16b295bdd741e4c0b855ae8f5ba Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 16:25:44 +0300 Subject: [PATCH 18/43] Backward compatibility fixes --- pytest_reportportal/plugin.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index db6ee71e..24d226a4 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -24,7 +24,7 @@ import pytest # noinspection PyPackageRequirements import requests -from pytest import Config, FixtureRequest, Parser, Session, Item +from pytest import Session, Item from reportportal_client import RPLogHandler, RP from reportportal_client.errors import ResponseError from reportportal_client.helpers import timestamp @@ -62,7 +62,8 @@ def pytest_configure_node(node: Any) -> None: node.workerinput['py_test_service'] = pickle.dumps(node.config.py_test_service) -def is_control(config: Config) -> bool: +# no 'config' type for backward compatibility for older pytest versions +def is_control(config) -> bool: """Validate workerinput attribute of the Config object. True if the code, running the given pytest.config object, @@ -148,7 +149,8 @@ def pytest_sessionfinish(session: Session) -> None: config.py_test_service.stop() -def register_markers(config: Config) -> None: +# no 'config' type for backward compatibility for older pytest versions +def register_markers(config) -> None: """Register plugin's markers, to avoid declaring them in `pytest.ini`. :param config: Object of the pytest Config class @@ -186,8 +188,8 @@ def check_connection(agent_config: AgentConfig): return False -# noinspection PyProtectedMember -def pytest_configure(config: Config) -> None: +# no 'config' type for backward compatibility for older pytest versions +def pytest_configure(config) -> None: """Update Config object with attributes required for reporting to RP. :param config: Object of the pytest Config class @@ -198,6 +200,7 @@ def pytest_configure(config: Config) -> None: config.getoption('--collect-only', default=False) or config.getoption('--setup-plan', default=False) or not config.option.rp_enabled) + # noinspection PyProtectedMember if not config._rp_enabled: return @@ -294,7 +297,8 @@ def pytest_runtest_makereport(item: Item) -> None: service.process_results(item, report) -def report_fixture(request: FixtureRequest, name: str, error_msg: str) -> None: +# no 'request' type for backward compatibility for older pytest versions +def report_fixture(request, name: str, error_msg: str) -> None: """Report fixture setup and teardown. :param request: Object of the FixtureRequest class @@ -326,23 +330,24 @@ def report_fixture(request: FixtureRequest, name: str, error_msg: str) -> None: reporter.finish_nested_step(item_id, timestamp(), 'FAILED') -# no 'fixturedef' type for backward compatibility for older pytest versions +# no types for backward compatibility for older pytest versions @pytest.hookimpl(hookwrapper=True) -def pytest_fixture_setup(fixturedef, request: FixtureRequest) -> None: +def pytest_fixture_setup(fixturedef, request) -> None: yield from report_fixture( request, f'{fixturedef.scope} fixture setup: {fixturedef.argname}', f'{fixturedef.scope} fixture setup failed: {fixturedef.argname}') -# no 'fixturedef' type for backward compatibility for older pytest versions +# no types for backward compatibility for older pytest versions @pytest.hookimpl(hookwrapper=True) -def pytest_fixture_post_finalizer(fixturedef, request: FixtureRequest) -> None: +def pytest_fixture_post_finalizer(fixturedef, request) -> None: yield from report_fixture( request, f'{fixturedef.scope} fixture teardown: {fixturedef.argname}', f'{fixturedef.scope} fixture teardown failed: {fixturedef.argname}') -def pytest_addoption(parser: Parser) -> None: +# no types for backward compatibility for older pytest versions +def pytest_addoption(parser) -> None: """Add support for the RP-related options. :param parser: Object of the Parser class From 9226957986998842ac02e8f77e65b9738dd44eb4 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 16:28:59 +0300 Subject: [PATCH 19/43] Fix pydocstyle --- pytest_reportportal/plugin.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 24d226a4..6f9126e4 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -333,6 +333,11 @@ def report_fixture(request, name: str, error_msg: str) -> None: # no types for backward compatibility for older pytest versions @pytest.hookimpl(hookwrapper=True) def pytest_fixture_setup(fixturedef, request) -> None: + """Report fixture setup. + + :param fixturedef: represents definition of the texture class + :param request: represents fixture execution metadata + """ yield from report_fixture( request, f'{fixturedef.scope} fixture setup: {fixturedef.argname}', f'{fixturedef.scope} fixture setup failed: {fixturedef.argname}') @@ -341,6 +346,11 @@ def pytest_fixture_setup(fixturedef, request) -> None: # no types for backward compatibility for older pytest versions @pytest.hookimpl(hookwrapper=True) def pytest_fixture_post_finalizer(fixturedef, request) -> None: + """Report fixture teardown. + + :param fixturedef: represents definition of the texture class + :param request: represents fixture execution metadata + """ yield from report_fixture( request, f'{fixturedef.scope} fixture teardown: {fixturedef.argname}', f'{fixturedef.scope} fixture teardown failed: {fixturedef.argname}') From a18098dc69b45b7307bc6f29b9191cdefbf3d9e9 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 16:31:21 +0300 Subject: [PATCH 20/43] Fix warnings --- pytest_reportportal/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 6f9126e4..9957349a 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -189,6 +189,7 @@ def check_connection(agent_config: AgentConfig): # no 'config' type for backward compatibility for older pytest versions +# noinspection PyProtectedMember def pytest_configure(config) -> None: """Update Config object with attributes required for reporting to RP. @@ -200,7 +201,6 @@ def pytest_configure(config) -> None: config.getoption('--collect-only', default=False) or config.getoption('--setup-plan', default=False) or not config.option.rp_enabled) - # noinspection PyProtectedMember if not config._rp_enabled: return From 4b52e5a4c3db8499ee1294a13e4ef5ec9ca12573 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 17 Oct 2024 17:36:21 +0300 Subject: [PATCH 21/43] Add more tests --- .../fixtures/test_fixture_setup/conftest.py | 2 +- .../test_fixture_setup/test_fixture_setup.py | 4 +- .../test_fixture_setup_failure/conftest.py | 31 +++++ .../test_fixture_setup_failure.py | 29 +++++ .../test_fixture_teardown/conftest.py | 2 +- .../test_fixture_teardown.py | 4 +- tests/integration/test_fixtures.py | 113 ++++++++++++++++++ 7 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 examples/fixtures/test_fixture_setup_failure/conftest.py create mode 100644 examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py diff --git a/examples/fixtures/test_fixture_setup/conftest.py b/examples/fixtures/test_fixture_setup/conftest.py index eeecc1bb..a24ddeaf 100644 --- a/examples/fixtures/test_fixture_setup/conftest.py +++ b/examples/fixtures/test_fixture_setup/conftest.py @@ -26,6 +26,6 @@ @pytest.fixture -def mocked_config(): +def fixture_setup_config(): logging.error(LOG_MESSAGE_SETUP) return mock.Mock() diff --git a/examples/fixtures/test_fixture_setup/test_fixture_setup.py b/examples/fixtures/test_fixture_setup/test_fixture_setup.py index 04270723..de3ba499 100644 --- a/examples/fixtures/test_fixture_setup/test_fixture_setup.py +++ b/examples/fixtures/test_fixture_setup/test_fixture_setup.py @@ -25,5 +25,5 @@ # limitations under the License. -def test_fixture_setup(mocked_config): - assert mocked_config is not None +def test_fixture_setup(fixture_setup_config): + assert fixture_setup_config is not None diff --git a/examples/fixtures/test_fixture_setup_failure/conftest.py b/examples/fixtures/test_fixture_setup_failure/conftest.py new file mode 100644 index 00000000..f9775e0e --- /dev/null +++ b/examples/fixtures/test_fixture_setup_failure/conftest.py @@ -0,0 +1,31 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from unittest import mock + +import pytest +from reportportal_client import RPLogger + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logging.setLoggerClass(RPLogger) + +LOG_MESSAGE_SETUP = 'Log message for setup failure' + + +@pytest.fixture +def mocked_config(): + logging.error(LOG_MESSAGE_SETUP) + raise Exception('Fixture setup failed') diff --git a/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py b/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py new file mode 100644 index 00000000..04270723 --- /dev/null +++ b/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py @@ -0,0 +1,29 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_fixture_setup(mocked_config): + assert mocked_config is not None diff --git a/examples/fixtures/test_fixture_teardown/conftest.py b/examples/fixtures/test_fixture_teardown/conftest.py index 7e53f49f..5ac5d934 100644 --- a/examples/fixtures/test_fixture_teardown/conftest.py +++ b/examples/fixtures/test_fixture_teardown/conftest.py @@ -27,7 +27,7 @@ @pytest.fixture -def mocked_config(): +def fixture_teardown_config(): logging.error(LOG_MESSAGE_BEFORE_YIELD) yield mock.Mock() logging.error(LOG_MESSAGE_TEARDOWN) diff --git a/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py b/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py index 52be323c..47adc573 100644 --- a/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py +++ b/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py @@ -27,6 +27,6 @@ from time import sleep -def test_fixture_teardown(mocked_config): +def test_fixture_teardown(fixture_teardown_config): sleep(0.001) - assert mocked_config is not None + assert fixture_teardown_config is not None diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index 2c97ab47..8cd37b42 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -12,11 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from collections import defaultdict from unittest import mock import pytest +from reportportal_client import set_current + from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils +from examples.fixtures.test_fixture_setup.conftest import LOG_MESSAGE_SETUP as SINGLE_SETUP_MESSAGE +from examples.fixtures.test_fixture_teardown.conftest import LOG_MESSAGE_BEFORE_YIELD, LOG_MESSAGE_TEARDOWN @pytest.mark.parametrize('switch', [True, False]) @@ -33,3 +38,111 @@ def test_fixture_on_off(mock_client_init, switch): expected_count = 3 if switch else 1 assert start_count == finish_count == expected_count, \ 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + +ITEM_ID_DICT = defaultdict(lambda: 0) +ITEM_ID_LIST = [] + + +def generate_item_id(*args, **kwargs) -> str: + if args: + name = args[0] + else: + name = kwargs['name'] + ITEM_ID_DICT[name] += 1 + item_id = f'{name}_{ITEM_ID_DICT[name]}' + ITEM_ID_LIST.append(item_id) + return item_id + + +def get_last_item_id() -> str: + return ITEM_ID_LIST[-1] + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_setup(mock_client_init): + mock_client = mock_client_init.return_value + set_current(mock_client) + mock_client.start_test_item.side_effect = generate_item_id + mock_client.current_item.side_effect = get_last_item_id + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_setup'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 3, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + step_name = 'function fixture setup: fixture_setup_config' + assert setup_call_args[0] == step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + assert teardown_call_args[0] == 'function fixture teardown: fixture_setup_config' + + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] + + log_count = mock_client.log.call_count + assert log_count == 1, 'Incorrect number of "log" calls' + + log_call_args_list = mock_client.log.call_args_list + log_call_args = log_call_args_list[0][0] + log_call_kwargs = log_call_args_list[0][1] + + assert log_call_args[1] == SINGLE_SETUP_MESSAGE + assert log_call_kwargs['item_id'] == f'{step_name}_1' + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_teardown(mock_client_init): + mock_client = mock_client_init.return_value + set_current(mock_client) + mock_client.start_test_item.side_effect = generate_item_id + mock_client.current_item.side_effect = get_last_item_id + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_teardown'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 3, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + setup_step_name = 'function fixture setup: fixture_teardown_config' + assert setup_call_args[0] == setup_step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + teardown_step_name = 'function fixture teardown: fixture_teardown_config' + assert teardown_call_args[0] == teardown_step_name + + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] + + log_count = mock_client.log.call_count + assert log_count == 2, 'Incorrect number of "log" calls' + + log_call_args_list = mock_client.log.call_args_list + log_call_args = log_call_args_list[0][0] + log_call_kwargs = log_call_args_list[0][1] + + assert log_call_args[1] == LOG_MESSAGE_BEFORE_YIELD + assert log_call_kwargs['item_id'] == f'{setup_step_name}_1' + + log_call_args = log_call_args_list[-1][0] + log_call_kwargs = log_call_args_list[-1][1] + + assert log_call_args[1] == LOG_MESSAGE_TEARDOWN + assert log_call_kwargs['item_id'] == f'{setup_step_name}_1' From 72b1b36cea4492ac60deae68d2b6d58374ea93b4 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 18 Oct 2024 11:32:11 +0300 Subject: [PATCH 22/43] Fix codestyle --- examples/fixtures/test_fixture_setup_failure/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/fixtures/test_fixture_setup_failure/conftest.py b/examples/fixtures/test_fixture_setup_failure/conftest.py index f9775e0e..6a41e7f6 100644 --- a/examples/fixtures/test_fixture_setup_failure/conftest.py +++ b/examples/fixtures/test_fixture_setup_failure/conftest.py @@ -13,7 +13,6 @@ # limitations under the License. import logging -from unittest import mock import pytest from reportportal_client import RPLogger From 48224c53f360a766e57f657c133bd2125fd6be66 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 18 Oct 2024 14:09:42 +0300 Subject: [PATCH 23/43] Add another test example --- .../test_fixture_teardown_failure/conftest.py | 34 +++++++++++++++++++ .../test_fixture_teardown_failure.py | 32 +++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 examples/fixtures/test_fixture_teardown_failure/conftest.py create mode 100644 examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py diff --git a/examples/fixtures/test_fixture_teardown_failure/conftest.py b/examples/fixtures/test_fixture_teardown_failure/conftest.py new file mode 100644 index 00000000..d72b4944 --- /dev/null +++ b/examples/fixtures/test_fixture_teardown_failure/conftest.py @@ -0,0 +1,34 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from unittest import mock + +import pytest +from reportportal_client import RPLogger + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logging.setLoggerClass(RPLogger) + +LOG_MESSAGE_BEFORE_YIELD = 'Log message before yield and failure' +LOG_MESSAGE_TEARDOWN = 'Log message for failure teardown' + + +@pytest.fixture +def fixture_teardown_config(): + logging.error(LOG_MESSAGE_BEFORE_YIELD) + yield mock.Mock() + logging.error(LOG_MESSAGE_TEARDOWN) + raise Exception('Fixture teardown failed') diff --git a/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py b/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py new file mode 100644 index 00000000..47adc573 --- /dev/null +++ b/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py @@ -0,0 +1,32 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from time import sleep + + +def test_fixture_teardown(fixture_teardown_config): + sleep(0.001) + assert fixture_teardown_config is not None From be62554a535be66ca82389d4c761eba184096744 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 18 Oct 2024 14:26:42 +0300 Subject: [PATCH 24/43] Update examples --- .../fixtures/class_fixture_return/conftest.py | 2 +- .../test_fixture_session_setup.py | 16 ++++++++-------- .../fixtures/module_fixture_return/conftest.py | 2 +- .../test_fixture_module_setup.py | 8 ++++---- .../fixtures/package_fixture_return/conftest.py | 2 +- .../test_fixture_package_setup_first.py | 4 ++-- .../test_fixture_package_setup_second.py | 4 ++-- .../fixtures/session_fixture_return/conftest.py | 2 +- .../test_fixture_session_setup_first.py | 4 ++-- .../test_fixture_session_setup_second.py | 4 ++-- .../test_fixture_return_none/conftest.py | 4 ++-- .../test_fixture_return_none.py | 4 ++-- .../test_fixture_setup_failure/conftest.py | 2 +- .../test_fixture_setup_failure.py | 4 ++-- .../test_fixture_teardown_failure/conftest.py | 2 +- .../test_fixture_teardown_failure.py | 4 ++-- 16 files changed, 34 insertions(+), 34 deletions(-) diff --git a/examples/fixtures/class_fixture_return/conftest.py b/examples/fixtures/class_fixture_return/conftest.py index 3028b4ca..da43a33d 100644 --- a/examples/fixtures/class_fixture_return/conftest.py +++ b/examples/fixtures/class_fixture_return/conftest.py @@ -18,6 +18,6 @@ @pytest.fixture(scope='class') -def mocked_config(): +def class_fixture_return_config(): print('setup') return mock.Mock() diff --git a/examples/fixtures/class_fixture_return/test_fixture_session_setup.py b/examples/fixtures/class_fixture_return/test_fixture_session_setup.py index a2eecb76..78881afd 100644 --- a/examples/fixtures/class_fixture_return/test_fixture_session_setup.py +++ b/examples/fixtures/class_fixture_return/test_fixture_session_setup.py @@ -25,16 +25,16 @@ # limitations under the License. class TestClassOne: - def test_fixture_class_setup_first(self, mocked_config): - assert mocked_config is not None + def test_fixture_class_setup_first(self, class_fixture_return_config): + assert class_fixture_return_config is not None - def test_fixture_class_setup_second(self, mocked_config): - assert mocked_config is not None + def test_fixture_class_setup_second(self, class_fixture_return_config): + assert class_fixture_return_config is not None class TestClassTwo: - def test_fixture_class_setup_forth(self, mocked_config): - assert mocked_config is not None + def test_fixture_class_setup_forth(self, class_fixture_return_config): + assert class_fixture_return_config is not None - def test_fixture_class_setup_fifth(self, mocked_config): - assert mocked_config is not None + def test_fixture_class_setup_fifth(self, class_fixture_return_config): + assert class_fixture_return_config is not None diff --git a/examples/fixtures/module_fixture_return/conftest.py b/examples/fixtures/module_fixture_return/conftest.py index 48d49daa..3336e6ae 100644 --- a/examples/fixtures/module_fixture_return/conftest.py +++ b/examples/fixtures/module_fixture_return/conftest.py @@ -18,6 +18,6 @@ @pytest.fixture(scope='module') -def mocked_config(): +def module_fixture_return_config(): print('setup') return mock.Mock() diff --git a/examples/fixtures/module_fixture_return/test_fixture_module_setup.py b/examples/fixtures/module_fixture_return/test_fixture_module_setup.py index 3792099d..b11fe6dd 100644 --- a/examples/fixtures/module_fixture_return/test_fixture_module_setup.py +++ b/examples/fixtures/module_fixture_return/test_fixture_module_setup.py @@ -24,9 +24,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -def test_fixture_module_setup_first(mocked_config): - assert mocked_config is not None +def test_fixture_module_setup_first(module_fixture_return_config): + assert module_fixture_return_config is not None -def test_fixture_module_setup_second(mocked_config): - assert mocked_config is not None +def test_fixture_module_setup_second(module_fixture_return_config): + assert module_fixture_return_config is not None diff --git a/examples/fixtures/package_fixture_return/conftest.py b/examples/fixtures/package_fixture_return/conftest.py index 1ae95a23..f4069446 100644 --- a/examples/fixtures/package_fixture_return/conftest.py +++ b/examples/fixtures/package_fixture_return/conftest.py @@ -18,6 +18,6 @@ @pytest.fixture(scope='package') -def mocked_config(): +def package_fixture_return_config(): print('setup') return mock.Mock() diff --git a/examples/fixtures/package_fixture_return/test_fixture_package_setup_first.py b/examples/fixtures/package_fixture_return/test_fixture_package_setup_first.py index 955dad3c..4c42833e 100644 --- a/examples/fixtures/package_fixture_return/test_fixture_package_setup_first.py +++ b/examples/fixtures/package_fixture_return/test_fixture_package_setup_first.py @@ -24,5 +24,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -def test_fixture_package_setup_first(mocked_config): - assert mocked_config is not None +def test_fixture_package_setup_first(package_fixture_return_config): + assert package_fixture_return_config is not None diff --git a/examples/fixtures/package_fixture_return/test_fixture_package_setup_second.py b/examples/fixtures/package_fixture_return/test_fixture_package_setup_second.py index 138f4b91..7ba2f2af 100644 --- a/examples/fixtures/package_fixture_return/test_fixture_package_setup_second.py +++ b/examples/fixtures/package_fixture_return/test_fixture_package_setup_second.py @@ -24,5 +24,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -def test_fixture_package_setup_second(mocked_config): - assert mocked_config is not None +def test_fixture_package_setup_second(package_fixture_return_config): + assert package_fixture_return_config is not None diff --git a/examples/fixtures/session_fixture_return/conftest.py b/examples/fixtures/session_fixture_return/conftest.py index 0e4dc5c0..9ab20179 100644 --- a/examples/fixtures/session_fixture_return/conftest.py +++ b/examples/fixtures/session_fixture_return/conftest.py @@ -18,6 +18,6 @@ @pytest.fixture(scope='session') -def mocked_config(): +def session_fixture_return_config(): print('setup') return mock.Mock() diff --git a/examples/fixtures/session_fixture_return/test_fixture_session_setup_first.py b/examples/fixtures/session_fixture_return/test_fixture_session_setup_first.py index 83d4f1e1..3480e949 100644 --- a/examples/fixtures/session_fixture_return/test_fixture_session_setup_first.py +++ b/examples/fixtures/session_fixture_return/test_fixture_session_setup_first.py @@ -25,5 +25,5 @@ # limitations under the License. -def test_fixture_session_setup_first(mocked_config): - assert mocked_config is not None +def test_fixture_session_setup_first(session_fixture_return_config): + assert session_fixture_return_config is not None diff --git a/examples/fixtures/session_fixture_return/test_fixture_session_setup_second.py b/examples/fixtures/session_fixture_return/test_fixture_session_setup_second.py index 2bd544d8..9640d580 100644 --- a/examples/fixtures/session_fixture_return/test_fixture_session_setup_second.py +++ b/examples/fixtures/session_fixture_return/test_fixture_session_setup_second.py @@ -25,5 +25,5 @@ # limitations under the License. -def test_fixture_session_setup_second(mocked_config): - assert mocked_config is not None +def test_fixture_session_setup_second(session_fixture_return_config): + assert session_fixture_return_config is not None diff --git a/examples/fixtures/test_fixture_return_none/conftest.py b/examples/fixtures/test_fixture_return_none/conftest.py index 8496549f..e19435a4 100644 --- a/examples/fixtures/test_fixture_return_none/conftest.py +++ b/examples/fixtures/test_fixture_return_none/conftest.py @@ -25,5 +25,5 @@ @pytest.fixture -def mocked_config(): - LOGGER.warn(LOG_MESSAGE_SETUP) +def test_fixture_setup_config_none(): + LOGGER.warning(LOG_MESSAGE_SETUP) diff --git a/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py b/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py index 04270723..1d4fb05e 100644 --- a/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py +++ b/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py @@ -25,5 +25,5 @@ # limitations under the License. -def test_fixture_setup(mocked_config): - assert mocked_config is not None +def test_fixture_setup_none(test_fixture_setup_config_none): + assert test_fixture_setup_config_none is not None diff --git a/examples/fixtures/test_fixture_setup_failure/conftest.py b/examples/fixtures/test_fixture_setup_failure/conftest.py index 6a41e7f6..014f37db 100644 --- a/examples/fixtures/test_fixture_setup_failure/conftest.py +++ b/examples/fixtures/test_fixture_setup_failure/conftest.py @@ -25,6 +25,6 @@ @pytest.fixture -def mocked_config(): +def fixture_setup_failure_config(): logging.error(LOG_MESSAGE_SETUP) raise Exception('Fixture setup failed') diff --git a/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py b/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py index 04270723..47282c93 100644 --- a/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py +++ b/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py @@ -25,5 +25,5 @@ # limitations under the License. -def test_fixture_setup(mocked_config): - assert mocked_config is not None +def test_fixture_setup_failure(fixture_setup_failure_config): + assert fixture_setup_failure_config is not None diff --git a/examples/fixtures/test_fixture_teardown_failure/conftest.py b/examples/fixtures/test_fixture_teardown_failure/conftest.py index d72b4944..a38dc7de 100644 --- a/examples/fixtures/test_fixture_teardown_failure/conftest.py +++ b/examples/fixtures/test_fixture_teardown_failure/conftest.py @@ -27,7 +27,7 @@ @pytest.fixture -def fixture_teardown_config(): +def fixture_teardown_failure_config(): logging.error(LOG_MESSAGE_BEFORE_YIELD) yield mock.Mock() logging.error(LOG_MESSAGE_TEARDOWN) diff --git a/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py b/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py index 47adc573..132aac70 100644 --- a/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py +++ b/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py @@ -27,6 +27,6 @@ from time import sleep -def test_fixture_teardown(fixture_teardown_config): +def test_fixture_teardown_failure(fixture_teardown_failure_config): sleep(0.001) - assert fixture_teardown_config is not None + assert fixture_teardown_failure_config is not None From fcd26672b38a0c7ac8a85f0ab4de4bffa18bb030 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 18 Oct 2024 14:48:11 +0300 Subject: [PATCH 25/43] Update examples --- .../test_failure_fixture_teardown/conftest.py | 33 +++++++++++++++++++ .../test_fixture_teardown.py | 32 ++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 examples/fixtures/test_failure_fixture_teardown/conftest.py create mode 100644 examples/fixtures/test_failure_fixture_teardown/test_fixture_teardown.py diff --git a/examples/fixtures/test_failure_fixture_teardown/conftest.py b/examples/fixtures/test_failure_fixture_teardown/conftest.py new file mode 100644 index 00000000..5ac5d934 --- /dev/null +++ b/examples/fixtures/test_failure_fixture_teardown/conftest.py @@ -0,0 +1,33 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from unittest import mock + +import pytest +from reportportal_client import RPLogger + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logging.setLoggerClass(RPLogger) + +LOG_MESSAGE_BEFORE_YIELD = 'Log message before yield' +LOG_MESSAGE_TEARDOWN = 'Log message for teardown' + + +@pytest.fixture +def fixture_teardown_config(): + logging.error(LOG_MESSAGE_BEFORE_YIELD) + yield mock.Mock() + logging.error(LOG_MESSAGE_TEARDOWN) diff --git a/examples/fixtures/test_failure_fixture_teardown/test_fixture_teardown.py b/examples/fixtures/test_failure_fixture_teardown/test_fixture_teardown.py new file mode 100644 index 00000000..931fa45e --- /dev/null +++ b/examples/fixtures/test_failure_fixture_teardown/test_fixture_teardown.py @@ -0,0 +1,32 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from time import sleep + + +def test_failure_fixture_teardown(fixture_teardown_config): + sleep(0.001) + assert fixture_teardown_config is not None From 9ee0aa2187e900546c09218be6152362c7b06ba8 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 18 Oct 2024 16:13:44 +0300 Subject: [PATCH 26/43] Add more tests --- .../test_fixture_setup_failure.py | 11 ++++ pytest_reportportal/plugin.py | 25 ++----- pytest_reportportal/service.py | 22 +++++++ tests/integration/test_fixtures.py | 66 +++++++++++++++++-- 4 files changed, 102 insertions(+), 22 deletions(-) diff --git a/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py b/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py index 47282c93..fb041ea5 100644 --- a/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py +++ b/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py @@ -24,6 +24,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging + +from reportportal_client import RPLogger + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logging.setLoggerClass(RPLogger) + +LOG_MESSAGE_TEST = 'Log message for test of setup failure' + def test_fixture_setup_failure(fixture_setup_failure_config): + logging.error(LOG_MESSAGE_TEST) assert fixture_setup_failure_config is not None diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 9957349a..475c46ea 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -27,9 +27,7 @@ from pytest import Session, Item from reportportal_client import RPLogHandler, RP from reportportal_client.errors import ResponseError -from reportportal_client.helpers import timestamp from reportportal_client.logs import MAX_LOG_BATCH_PAYLOAD_SIZE -from reportportal_client.steps import StepReporter from pytest_reportportal import LAUNCH_WAIT_TIMEOUT from pytest_reportportal.config import AgentConfig @@ -297,7 +295,6 @@ def pytest_runtest_makereport(item: Item) -> None: service.process_results(item, report) -# no 'request' type for backward compatibility for older pytest versions def report_fixture(request, name: str, error_msg: str) -> None: """Report fixture setup and teardown. @@ -313,21 +310,7 @@ def report_fixture(request, name: str, error_msg: str) -> None: yield return - reporter = StepReporter(service.rp) - item_id = reporter.start_nested_step(name, timestamp()) - - try: - outcome = yield - if outcome.exception: - log.error(error_msg) - log.exception(outcome.exception) - reporter.finish_nested_step(item_id, timestamp(), 'FAILED') - else: - reporter.finish_nested_step(item_id, timestamp(), 'PASSED') - except Exception as e: - log.error('Failed to report fixture: %s', name) - log.exception(e) - reporter.finish_nested_step(item_id, timestamp(), 'FAILED') + yield from service.report_fixture(name, error_msg) # no types for backward compatibility for older pytest versions @@ -351,6 +334,12 @@ def pytest_fixture_post_finalizer(fixturedef, request) -> None: :param fixturedef: represents definition of the texture class :param request: represents fixture execution metadata """ + if fixturedef.cached_result and fixturedef.cached_result[2]: + exception = fixturedef.cached_result[2][0] + if exception and isinstance(exception, BaseException): + yield + return + yield from report_fixture( request, f'{fixturedef.scope} fixture teardown: {fixturedef.argname}', f'{fixturedef.scope} fixture teardown failed: {fixturedef.argname}') diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index cbd162f5..d1f1534c 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -864,6 +864,28 @@ def post_log(self, test_item, message, log_level='INFO', attachment=None): } self.rp.log(**sl_rq) + def report_fixture(self, name: str, error_msg: str) -> None: + """Report fixture setup and teardown. + + :param name: Name of the fixture + :param error_msg: Error message + """ + reporter = self.rp.step_reporter + item_id = reporter.start_nested_step(name, timestamp()) + + try: + outcome = yield + if outcome.exception: + log.error(error_msg) + log.exception(outcome.exception) + reporter.finish_nested_step(item_id, timestamp(), 'FAILED') + else: + reporter.finish_nested_step(item_id, timestamp(), 'PASSED') + except Exception as e: + log.error('Failed to report fixture: %s', name) + log.exception(e) + reporter.finish_nested_step(item_id, timestamp(), 'FAILED') + def start(self) -> None: """Start servicing Report Portal requests.""" self.parent_item_id = self._config.rp_parent_item_id diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index 8cd37b42..c6caf8c1 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -17,22 +17,26 @@ import pytest from reportportal_client import set_current +from reportportal_client.steps import StepReporter -from tests import REPORT_PORTAL_SERVICE -from tests.helpers import utils from examples.fixtures.test_fixture_setup.conftest import LOG_MESSAGE_SETUP as SINGLE_SETUP_MESSAGE +from examples.fixtures.test_fixture_setup_failure.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_SETUP_FAILURE from examples.fixtures.test_fixture_teardown.conftest import LOG_MESSAGE_BEFORE_YIELD, LOG_MESSAGE_TEARDOWN +from tests import REPORT_PORTAL_SERVICE +from tests.helpers import utils @pytest.mark.parametrize('switch', [True, False]) @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_on_off(mock_client_init, switch): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = switch result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_teardown'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' - mock_client = mock_client_init.return_value start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count expected_count = 3 if switch else 1 @@ -59,11 +63,18 @@ def get_last_item_id() -> str: return ITEM_ID_LIST[-1] +def remove_last_item_id(*_, **__) -> str: + if len(ITEM_ID_LIST) > 0: + return ITEM_ID_LIST.pop() + + @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_setup(mock_client_init): mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) set_current(mock_client) mock_client.start_test_item.side_effect = generate_item_id + mock_client.finish_test_item.side_effect = remove_last_item_id mock_client.current_item.side_effect = get_last_item_id variables = dict(utils.DEFAULT_VARIABLES) @@ -103,8 +114,10 @@ def test_fixture_setup(mock_client_init): @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_teardown(mock_client_init): mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) set_current(mock_client) mock_client.start_test_item.side_effect = generate_item_id + mock_client.finish_test_item.side_effect = remove_last_item_id mock_client.current_item.side_effect = get_last_item_id variables = dict(utils.DEFAULT_VARIABLES) @@ -145,4 +158,49 @@ def test_fixture_teardown(mock_client_init): log_call_kwargs = log_call_args_list[-1][1] assert log_call_args[1] == LOG_MESSAGE_TEARDOWN - assert log_call_kwargs['item_id'] == f'{setup_step_name}_1' + assert log_call_kwargs['item_id'] == \ + 'examples/fixtures/test_fixture_teardown/test_fixture_teardown.py::test_fixture_teardown_1' + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_setup_failure(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + set_current(mock_client) + mock_client.start_test_item.side_effect = generate_item_id + mock_client.finish_test_item.side_effect = remove_last_item_id + mock_client.current_item.side_effect = get_last_item_id + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_setup_failure'], variables=variables) + assert int(result) == 1, 'Exit code should be 1 (test failure)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 2, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + step_name = 'function fixture setup: fixture_setup_failure_config' + assert setup_call_args[0] == step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + log_count = mock_client.log.call_count + assert log_count == 2, 'Incorrect number of "log" calls' + + log_call_args_list = mock_client.log.call_args_list + log_call_args = log_call_args_list[0][0] + log_call_kwargs = log_call_args_list[0][1] + + assert log_call_args[1] == LOG_MESSAGE_SETUP_FAILURE + assert log_call_kwargs['item_id'] == f'{step_name}_1' + + log_call_kwargs = log_call_args_list[1][1] + + assert log_call_kwargs['message'].endswith( + 'examples/fixtures/test_fixture_setup_failure/conftest.py:30: Exception') + assert log_call_kwargs['item_id'] == \ + 'examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py::test_fixture_setup_failure_1' From 511a64c6ff5e8ad4abb68762dd57269cfe0712ca Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 18 Oct 2024 16:52:15 +0300 Subject: [PATCH 27/43] Add more tests --- tests/integration/test_fixtures.py | 98 ++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 18 deletions(-) diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index c6caf8c1..40fc7e3d 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -22,28 +22,12 @@ from examples.fixtures.test_fixture_setup.conftest import LOG_MESSAGE_SETUP as SINGLE_SETUP_MESSAGE from examples.fixtures.test_fixture_setup_failure.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_SETUP_FAILURE from examples.fixtures.test_fixture_teardown.conftest import LOG_MESSAGE_BEFORE_YIELD, LOG_MESSAGE_TEARDOWN +from examples.fixtures.test_fixture_teardown_failure.conftest import ( + LOG_MESSAGE_BEFORE_YIELD as LOG_MESSAGE_BEFORE_YIELD_FAILURE, LOG_MESSAGE_TEARDOWN as LOG_MESSAGE_TEARDOWN_FAILURE) from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils -@pytest.mark.parametrize('switch', [True, False]) -@mock.patch(REPORT_PORTAL_SERVICE) -def test_fixture_on_off(mock_client_init, switch): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) - - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = switch - result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_teardown'], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' - - start_count = mock_client.start_test_item.call_count - finish_count = mock_client.finish_test_item.call_count - expected_count = 3 if switch else 1 - assert start_count == finish_count == expected_count, \ - 'Incorrect number of "start_test_item" or "finish_test_item" calls' - - ITEM_ID_DICT = defaultdict(lambda: 0) ITEM_ID_LIST = [] @@ -68,6 +52,24 @@ def remove_last_item_id(*_, **__) -> str: return ITEM_ID_LIST.pop() +@pytest.mark.parametrize('switch', [True, False]) +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_on_off(mock_client_init, switch): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = switch + result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_teardown'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + expected_count = 3 if switch else 1 + assert start_count == finish_count == expected_count, \ + 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_setup(mock_client_init): mock_client = mock_client_init.return_value @@ -204,3 +206,63 @@ def test_fixture_setup_failure(mock_client_init): 'examples/fixtures/test_fixture_setup_failure/conftest.py:30: Exception') assert log_call_kwargs['item_id'] == \ 'examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py::test_fixture_setup_failure_1' + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_teardown_failure(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + set_current(mock_client) + mock_client.start_test_item.side_effect = generate_item_id + mock_client.finish_test_item.side_effect = remove_last_item_id + mock_client.current_item.side_effect = get_last_item_id + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_teardown_failure'], variables=variables) + assert int(result) == 1, 'Exit code should be 1 (test failure)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 3, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + setup_step_name = 'function fixture setup: fixture_teardown_failure_config' + assert setup_call_args[0] == setup_step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + teardown_step_name = 'function fixture teardown: fixture_teardown_failure_config' + assert teardown_call_args[0] == teardown_step_name + + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] + + log_count = mock_client.log.call_count + assert log_count == 3, 'Incorrect number of "log" calls' + + log_call_args_list = mock_client.log.call_args_list + log_call_args = log_call_args_list[0][0] + log_call_kwargs = log_call_args_list[0][1] + + assert log_call_args[1] == LOG_MESSAGE_BEFORE_YIELD_FAILURE + assert log_call_kwargs['item_id'] == f'{setup_step_name}_1' + + log_call_args = log_call_args_list[1][0] + log_call_kwargs = log_call_args_list[1][1] + + assert log_call_args[1] == LOG_MESSAGE_TEARDOWN_FAILURE + assert log_call_kwargs['item_id'] == \ + ('examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py::' + 'test_fixture_teardown_failure_1') + + log_call_kwargs = log_call_args_list[2][1] + + assert log_call_kwargs['message'].endswith( + 'examples/fixtures/test_fixture_teardown_failure/conftest.py:34: Exception') + assert log_call_kwargs['item_id'] == \ + ('examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py::' + 'test_fixture_teardown_failure_1') From 1643370cf3a8a75c5454726693f87612da681dfe Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 18 Oct 2024 16:59:12 +0300 Subject: [PATCH 28/43] Add more examples --- .../test_fixture_yield_none/conftest.py | 30 +++++++++++++++++++ .../test_fixture_return_none.py | 29 ++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 examples/fixtures/test_fixture_yield_none/conftest.py create mode 100644 examples/fixtures/test_fixture_yield_none/test_fixture_return_none.py diff --git a/examples/fixtures/test_fixture_yield_none/conftest.py b/examples/fixtures/test_fixture_yield_none/conftest.py new file mode 100644 index 00000000..5c79f886 --- /dev/null +++ b/examples/fixtures/test_fixture_yield_none/conftest.py @@ -0,0 +1,30 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +import pytest +from reportportal_client import RPLogger + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logging.setLoggerClass(RPLogger) + +LOG_MESSAGE_SETUP = 'Log message for setup and return None' + + +@pytest.fixture +def test_fixture_setup_yield_none(): + LOGGER.warning(LOG_MESSAGE_SETUP) + yield None diff --git a/examples/fixtures/test_fixture_yield_none/test_fixture_return_none.py b/examples/fixtures/test_fixture_yield_none/test_fixture_return_none.py new file mode 100644 index 00000000..d065881d --- /dev/null +++ b/examples/fixtures/test_fixture_yield_none/test_fixture_return_none.py @@ -0,0 +1,29 @@ +# Copyright 2024 EPAM Systems +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def test_fixture_setup_none(test_fixture_setup_yield_none): + assert test_fixture_setup_yield_none is not None From d9ffe35800cffc1635d233f73c2f7ad35ab37b41 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 18 Oct 2024 17:17:39 +0300 Subject: [PATCH 29/43] Add more tests --- .../test_fixture_return_none.py | 2 +- .../test_fixture_yield_none/conftest.py | 2 +- ...urn_none.py => test_fixture_yield_none.py} | 4 +- tests/integration/test_fixtures.py | 91 +++++++++++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) rename examples/fixtures/test_fixture_yield_none/{test_fixture_return_none.py => test_fixture_yield_none.py} (91%) diff --git a/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py b/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py index 1d4fb05e..6ac0192a 100644 --- a/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py +++ b/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py @@ -26,4 +26,4 @@ def test_fixture_setup_none(test_fixture_setup_config_none): - assert test_fixture_setup_config_none is not None + assert test_fixture_setup_config_none is None diff --git a/examples/fixtures/test_fixture_yield_none/conftest.py b/examples/fixtures/test_fixture_yield_none/conftest.py index 5c79f886..da8b40dc 100644 --- a/examples/fixtures/test_fixture_yield_none/conftest.py +++ b/examples/fixtures/test_fixture_yield_none/conftest.py @@ -21,7 +21,7 @@ LOGGER.setLevel(logging.DEBUG) logging.setLoggerClass(RPLogger) -LOG_MESSAGE_SETUP = 'Log message for setup and return None' +LOG_MESSAGE_SETUP = 'Log message for setup and yield None' @pytest.fixture diff --git a/examples/fixtures/test_fixture_yield_none/test_fixture_return_none.py b/examples/fixtures/test_fixture_yield_none/test_fixture_yield_none.py similarity index 91% rename from examples/fixtures/test_fixture_yield_none/test_fixture_return_none.py rename to examples/fixtures/test_fixture_yield_none/test_fixture_yield_none.py index d065881d..7c3839bf 100644 --- a/examples/fixtures/test_fixture_yield_none/test_fixture_return_none.py +++ b/examples/fixtures/test_fixture_yield_none/test_fixture_yield_none.py @@ -25,5 +25,5 @@ # limitations under the License. -def test_fixture_setup_none(test_fixture_setup_yield_none): - assert test_fixture_setup_yield_none is not None +def test_fixture_yield_none(test_fixture_setup_yield_none): + assert test_fixture_setup_yield_none is None diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index 40fc7e3d..a80d0a79 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -24,6 +24,8 @@ from examples.fixtures.test_fixture_teardown.conftest import LOG_MESSAGE_BEFORE_YIELD, LOG_MESSAGE_TEARDOWN from examples.fixtures.test_fixture_teardown_failure.conftest import ( LOG_MESSAGE_BEFORE_YIELD as LOG_MESSAGE_BEFORE_YIELD_FAILURE, LOG_MESSAGE_TEARDOWN as LOG_MESSAGE_TEARDOWN_FAILURE) +from examples.fixtures.test_fixture_yield_none.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_BEFORE_YIELD_NONE +from examples.fixtures.test_fixture_return_none.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_BEFORE_RETURN_NONE from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils @@ -266,3 +268,92 @@ def test_fixture_teardown_failure(mock_client_init): assert log_call_kwargs['item_id'] == \ ('examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py::' 'test_fixture_teardown_failure_1') + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_yield_none(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + set_current(mock_client) + mock_client.start_test_item.side_effect = generate_item_id + mock_client.finish_test_item.side_effect = remove_last_item_id + mock_client.current_item.side_effect = get_last_item_id + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_yield_none'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 3, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + setup_step_name = 'function fixture setup: test_fixture_setup_yield_none' + assert setup_call_args[0] == setup_step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + teardown_step_name = 'function fixture teardown: test_fixture_setup_yield_none' + assert teardown_call_args[0] == teardown_step_name + + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] + + log_count = mock_client.log.call_count + assert log_count == 1, 'Incorrect number of "log" calls' + + log_call_args_list = mock_client.log.call_args_list + log_call_args = log_call_args_list[0][0] + log_call_kwargs = log_call_args_list[0][1] + + assert log_call_args[1] == LOG_MESSAGE_BEFORE_YIELD_NONE + assert log_call_kwargs['item_id'] == f'{setup_step_name}_1' + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_return_none(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + set_current(mock_client) + mock_client.start_test_item.side_effect = generate_item_id + mock_client.finish_test_item.side_effect = remove_last_item_id + mock_client.current_item.side_effect = get_last_item_id + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_return_none'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 3, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + setup_step_name = 'function fixture setup: test_fixture_setup_config_none' + assert setup_call_args[0] == setup_step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + teardown_step_name = 'function fixture teardown: test_fixture_setup_config_none' + assert teardown_call_args[0] == teardown_step_name + + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] + + log_count = mock_client.log.call_count + assert log_count == 1, 'Incorrect number of "log" calls' + + log_call_args_list = mock_client.log.call_args_list + log_call_args = log_call_args_list[0][0] + log_call_kwargs = log_call_args_list[0][1] + + assert log_call_args[1] == LOG_MESSAGE_BEFORE_RETURN_NONE + assert log_call_kwargs['item_id'] == f'{setup_step_name}_1' + From 105f910fceaa2cc7b1d35b4693e13109f9c65255 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Fri, 18 Oct 2024 17:31:24 +0300 Subject: [PATCH 30/43] Add more tests --- .../test_failure_fixture_teardown/conftest.py | 6 +- ...wn.py => test_failure_fixture_teardown.py} | 8 +-- tests/integration/test_fixtures.py | 62 +++++++++++++++++++ 3 files changed, 67 insertions(+), 9 deletions(-) rename examples/fixtures/test_failure_fixture_teardown/{test_fixture_teardown.py => test_failure_fixture_teardown.py} (88%) diff --git a/examples/fixtures/test_failure_fixture_teardown/conftest.py b/examples/fixtures/test_failure_fixture_teardown/conftest.py index 5ac5d934..3100face 100644 --- a/examples/fixtures/test_failure_fixture_teardown/conftest.py +++ b/examples/fixtures/test_failure_fixture_teardown/conftest.py @@ -22,12 +22,12 @@ LOGGER.setLevel(logging.DEBUG) logging.setLoggerClass(RPLogger) -LOG_MESSAGE_BEFORE_YIELD = 'Log message before yield' -LOG_MESSAGE_TEARDOWN = 'Log message for teardown' +LOG_MESSAGE_BEFORE_YIELD = 'Log message before yield and test failure' +LOG_MESSAGE_TEARDOWN = 'Log message for teardown after test failure' @pytest.fixture -def fixture_teardown_config(): +def test_failure_fixture_teardown_config(): logging.error(LOG_MESSAGE_BEFORE_YIELD) yield mock.Mock() logging.error(LOG_MESSAGE_TEARDOWN) diff --git a/examples/fixtures/test_failure_fixture_teardown/test_fixture_teardown.py b/examples/fixtures/test_failure_fixture_teardown/test_failure_fixture_teardown.py similarity index 88% rename from examples/fixtures/test_failure_fixture_teardown/test_fixture_teardown.py rename to examples/fixtures/test_failure_fixture_teardown/test_failure_fixture_teardown.py index 931fa45e..31f68849 100644 --- a/examples/fixtures/test_failure_fixture_teardown/test_fixture_teardown.py +++ b/examples/fixtures/test_failure_fixture_teardown/test_failure_fixture_teardown.py @@ -24,9 +24,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -from time import sleep - - -def test_failure_fixture_teardown(fixture_teardown_config): - sleep(0.001) - assert fixture_teardown_config is not None +def test_failure_fixture_teardown(test_failure_fixture_teardown_config): + assert test_failure_fixture_teardown_config is None diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index a80d0a79..89e15374 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -26,6 +26,9 @@ LOG_MESSAGE_BEFORE_YIELD as LOG_MESSAGE_BEFORE_YIELD_FAILURE, LOG_MESSAGE_TEARDOWN as LOG_MESSAGE_TEARDOWN_FAILURE) from examples.fixtures.test_fixture_yield_none.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_BEFORE_YIELD_NONE from examples.fixtures.test_fixture_return_none.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_BEFORE_RETURN_NONE +from examples.fixtures.test_failure_fixture_teardown.conftest import ( + LOG_MESSAGE_BEFORE_YIELD as LOG_MESSAGE_BEFORE_YIELD_TEST_FAILURE, + LOG_MESSAGE_TEARDOWN as LOG_MESSAGE_TEARDOWN_TEST_FAILURE) from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils @@ -357,3 +360,62 @@ def test_fixture_return_none(mock_client_init): assert log_call_args[1] == LOG_MESSAGE_BEFORE_RETURN_NONE assert log_call_kwargs['item_id'] == f'{setup_step_name}_1' + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_failure_fixture_teardown(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + set_current(mock_client) + mock_client.start_test_item.side_effect = generate_item_id + mock_client.finish_test_item.side_effect = remove_last_item_id + mock_client.current_item.side_effect = get_last_item_id + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/test_failure_fixture_teardown'], variables=variables) + assert int(result) == 1, 'Exit code should be 1 (test failure)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 3, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + setup_step_name = 'function fixture setup: test_failure_fixture_teardown_config' + assert setup_call_args[0] == setup_step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + teardown_step_name = 'function fixture teardown: test_failure_fixture_teardown_config' + assert teardown_call_args[0] == teardown_step_name + + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] + + log_count = mock_client.log.call_count + assert log_count == 3, 'Incorrect number of "log" calls' + + log_call_args_list = mock_client.log.call_args_list + log_call_args = log_call_args_list[0][0] + log_call_kwargs = log_call_args_list[0][1] + + assert log_call_args[1] == LOG_MESSAGE_BEFORE_YIELD_TEST_FAILURE + assert log_call_kwargs['item_id'] == f'{setup_step_name}_1' + + log_call_args = log_call_args_list[2][0] + log_call_kwargs = log_call_args_list[2][1] + + assert log_call_args[1] == LOG_MESSAGE_TEARDOWN_TEST_FAILURE + assert log_call_kwargs['item_id'] == \ + ('examples/fixtures/test_failure_fixture_teardown/test_failure_fixture_teardown.py::' + 'test_failure_fixture_teardown_1') + + log_call_kwargs = log_call_args_list[1][1] + + assert log_call_kwargs['message'].endswith( + 'examples/fixtures/test_failure_fixture_teardown/test_failure_fixture_teardown.py:28: AssertionError') + assert log_call_kwargs['item_id'] == \ + ('examples/fixtures/test_failure_fixture_teardown/test_failure_fixture_teardown.py::' + 'test_failure_fixture_teardown_1') From 5b0cf3b1c0cbb19317efdef013656a6e1a8999fa Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 10:25:49 +0300 Subject: [PATCH 31/43] Add more tests --- ...n_setup.py => test_fixture_class_setup.py} | 0 tests/integration/test_fixtures.py | 125 ++++++++++++++++++ 2 files changed, 125 insertions(+) rename examples/fixtures/class_fixture_return/{test_fixture_session_setup.py => test_fixture_class_setup.py} (100%) diff --git a/examples/fixtures/class_fixture_return/test_fixture_session_setup.py b/examples/fixtures/class_fixture_return/test_fixture_class_setup.py similarity index 100% rename from examples/fixtures/class_fixture_return/test_fixture_session_setup.py rename to examples/fixtures/class_fixture_return/test_fixture_class_setup.py diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index 89e15374..b867107d 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -419,3 +419,128 @@ def test_failure_fixture_teardown(mock_client_init): assert log_call_kwargs['item_id'] == \ ('examples/fixtures/test_failure_fixture_teardown/test_failure_fixture_teardown.py::' 'test_failure_fixture_teardown_1') + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_session_fixture_setup(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/session_fixture_return'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 4, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + step_name = 'session fixture setup: session_fixture_return_config' + assert setup_call_args[0] == step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + assert teardown_call_args[0] == 'session fixture teardown: session_fixture_return_config' + + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_package_fixture_setup(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/package_fixture_return'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 4, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + step_name = 'package fixture setup: package_fixture_return_config' + assert setup_call_args[0] == step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + assert teardown_call_args[0] == 'package fixture teardown: package_fixture_return_config' + + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_module_fixture_setup(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/module_fixture_return'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 4, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + step_name = 'module fixture setup: module_fixture_return_config' + assert setup_call_args[0] == step_name + + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + assert teardown_call_args[0] == 'module fixture teardown: module_fixture_return_config' + + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_class_fixture_setup(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=['examples/fixtures/class_fixture_return'], variables=variables) + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + start_count = mock_client.start_test_item.call_count + finish_count = mock_client.finish_test_item.call_count + assert start_count == finish_count == 8, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + + call_args = mock_client.start_test_item.call_args_list + setup_call_args = call_args[1][0] + step_name = 'class fixture setup: class_fixture_return_config' + assert setup_call_args[0] == step_name + setup_call_kwargs = call_args[1][1] + assert not setup_call_kwargs['has_stats'] + + setup_call_args = call_args[-3][0] + assert setup_call_args[0] == step_name + setup_call_kwargs = call_args[-3][1] + assert not setup_call_kwargs['has_stats'] + + teardown_step_name = 'class fixture teardown: class_fixture_return_config' + teardown_call_args = call_args[-5][0] + assert teardown_call_args[0] == teardown_step_name + setup_call_kwargs = call_args[-5][1] + assert not setup_call_kwargs['has_stats'] + + teardown_call_args = call_args[-1][0] + assert teardown_call_args[0] == teardown_step_name + setup_call_kwargs = call_args[-1][1] + assert not setup_call_kwargs['has_stats'] From 13d8b38c7231949c4fa8ad5cd9d11bbe630a180a Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 10:35:46 +0300 Subject: [PATCH 32/43] Test fixes --- pytest_reportportal/plugin.py | 3 ++- tests/integration/test_fixtures.py | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pytest_reportportal/plugin.py b/pytest_reportportal/plugin.py index 475c46ea..2f76b061 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -334,7 +334,8 @@ def pytest_fixture_post_finalizer(fixturedef, request) -> None: :param fixturedef: represents definition of the texture class :param request: represents fixture execution metadata """ - if fixturedef.cached_result and fixturedef.cached_result[2]: + cached_result = getattr(fixturedef, 'cached_result', None) + if cached_result and cached_result[2]: exception = fixturedef.cached_result[2][0] if exception and isinstance(exception, BaseException): yield diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index b867107d..57b86154 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import sys from collections import defaultdict from unittest import mock @@ -19,20 +19,19 @@ from reportportal_client import set_current from reportportal_client.steps import StepReporter +from examples.fixtures.test_failure_fixture_teardown.conftest import ( + LOG_MESSAGE_BEFORE_YIELD as LOG_MESSAGE_BEFORE_YIELD_TEST_FAILURE, + LOG_MESSAGE_TEARDOWN as LOG_MESSAGE_TEARDOWN_TEST_FAILURE) +from examples.fixtures.test_fixture_return_none.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_BEFORE_RETURN_NONE from examples.fixtures.test_fixture_setup.conftest import LOG_MESSAGE_SETUP as SINGLE_SETUP_MESSAGE from examples.fixtures.test_fixture_setup_failure.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_SETUP_FAILURE from examples.fixtures.test_fixture_teardown.conftest import LOG_MESSAGE_BEFORE_YIELD, LOG_MESSAGE_TEARDOWN from examples.fixtures.test_fixture_teardown_failure.conftest import ( LOG_MESSAGE_BEFORE_YIELD as LOG_MESSAGE_BEFORE_YIELD_FAILURE, LOG_MESSAGE_TEARDOWN as LOG_MESSAGE_TEARDOWN_FAILURE) from examples.fixtures.test_fixture_yield_none.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_BEFORE_YIELD_NONE -from examples.fixtures.test_fixture_return_none.conftest import LOG_MESSAGE_SETUP as LOG_MESSAGE_BEFORE_RETURN_NONE -from examples.fixtures.test_failure_fixture_teardown.conftest import ( - LOG_MESSAGE_BEFORE_YIELD as LOG_MESSAGE_BEFORE_YIELD_TEST_FAILURE, - LOG_MESSAGE_TEARDOWN as LOG_MESSAGE_TEARDOWN_TEST_FAILURE) from tests import REPORT_PORTAL_SERVICE from tests.helpers import utils - ITEM_ID_DICT = defaultdict(lambda: 0) ITEM_ID_LIST = [] @@ -185,7 +184,10 @@ def test_fixture_setup_failure(mock_client_init): start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count - assert start_count == finish_count == 2, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + if sys.version_info < (3, 8): + assert start_count == finish_count == 3, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + else: + assert start_count == finish_count == 2, 'Incorrect number of "start_test_item" or "finish_test_item" calls' call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] @@ -421,6 +423,7 @@ def test_failure_fixture_teardown(mock_client_init): 'test_failure_fixture_teardown_1') +@pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_session_fixture_setup(mock_client_init): mock_client = mock_client_init.return_value @@ -450,6 +453,7 @@ def test_session_fixture_setup(mock_client_init): assert not setup_call_kwargs['has_stats'] +@pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_package_fixture_setup(mock_client_init): mock_client = mock_client_init.return_value @@ -479,6 +483,7 @@ def test_package_fixture_setup(mock_client_init): assert not setup_call_kwargs['has_stats'] +@pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_module_fixture_setup(mock_client_init): mock_client = mock_client_init.return_value @@ -508,6 +513,7 @@ def test_module_fixture_setup(mock_client_init): assert not setup_call_kwargs['has_stats'] +@pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_class_fixture_setup(mock_client_init): mock_client = mock_client_init.return_value From 8b13307a1812ec34554d9cbf7c19df1987736e20 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 10:49:37 +0300 Subject: [PATCH 33/43] Refactoring --- examples/fixtures/test_fixture_setup/conftest.py | 2 +- .../test_fixture_setup/test_fixture_setup.py | 4 ++-- .../fixtures/test_fixture_teardown/conftest.py | 2 +- .../test_fixture_teardown/test_fixture_teardown.py | 4 ++-- tests/integration/test_fixtures.py | 14 +++++++++----- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/fixtures/test_fixture_setup/conftest.py b/examples/fixtures/test_fixture_setup/conftest.py index a24ddeaf..36897fae 100644 --- a/examples/fixtures/test_fixture_setup/conftest.py +++ b/examples/fixtures/test_fixture_setup/conftest.py @@ -26,6 +26,6 @@ @pytest.fixture -def fixture_setup_config(): +def test_fixture_setup_config(): logging.error(LOG_MESSAGE_SETUP) return mock.Mock() diff --git a/examples/fixtures/test_fixture_setup/test_fixture_setup.py b/examples/fixtures/test_fixture_setup/test_fixture_setup.py index de3ba499..cc2f8566 100644 --- a/examples/fixtures/test_fixture_setup/test_fixture_setup.py +++ b/examples/fixtures/test_fixture_setup/test_fixture_setup.py @@ -25,5 +25,5 @@ # limitations under the License. -def test_fixture_setup(fixture_setup_config): - assert fixture_setup_config is not None +def test_fixture_setup(test_fixture_setup_config): + assert test_fixture_setup_config is not None diff --git a/examples/fixtures/test_fixture_teardown/conftest.py b/examples/fixtures/test_fixture_teardown/conftest.py index 5ac5d934..29b3e707 100644 --- a/examples/fixtures/test_fixture_teardown/conftest.py +++ b/examples/fixtures/test_fixture_teardown/conftest.py @@ -27,7 +27,7 @@ @pytest.fixture -def fixture_teardown_config(): +def test_fixture_teardown_config(): logging.error(LOG_MESSAGE_BEFORE_YIELD) yield mock.Mock() logging.error(LOG_MESSAGE_TEARDOWN) diff --git a/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py b/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py index 47adc573..aef60447 100644 --- a/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py +++ b/examples/fixtures/test_fixture_teardown/test_fixture_teardown.py @@ -27,6 +27,6 @@ from time import sleep -def test_fixture_teardown(fixture_teardown_config): +def test_fixture_teardown(test_fixture_teardown_config): sleep(0.001) - assert fixture_teardown_config is not None + assert test_fixture_teardown_config is not None diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index 57b86154..1db5a71b 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -85,6 +85,7 @@ def test_fixture_setup(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True + test_path = 'examples/fixtures/test_fixture_setup' result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_setup'], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' @@ -94,14 +95,15 @@ def test_fixture_setup(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - step_name = 'function fixture setup: fixture_setup_config' + fixture_name = f'{test_path.split("/")[-1]}_config' + step_name = f'function fixture setup: {fixture_name}' assert setup_call_args[0] == step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] teardown_call_args = call_args[-1][0] - assert teardown_call_args[0] == 'function fixture teardown: fixture_setup_config' + assert teardown_call_args[0] == f'function fixture teardown: {fixture_name}' setup_call_kwargs = call_args[-1][1] assert not setup_call_kwargs['has_stats'] @@ -128,7 +130,8 @@ def test_fixture_teardown(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_teardown'], variables=variables) + test_path = 'examples/fixtures/test_fixture_teardown' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' start_count = mock_client.start_test_item.call_count @@ -137,14 +140,15 @@ def test_fixture_teardown(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - setup_step_name = 'function fixture setup: fixture_teardown_config' + fixture_name = f'{test_path.split("/")[-1]}_config' + setup_step_name = f'function fixture setup: {fixture_name}' assert setup_call_args[0] == setup_step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] teardown_call_args = call_args[-1][0] - teardown_step_name = 'function fixture teardown: fixture_teardown_config' + teardown_step_name = f'function fixture teardown: {fixture_name}' assert teardown_call_args[0] == teardown_step_name setup_call_kwargs = call_args[-1][1] From 3e153f257aa4c8bd29476ef7c7e891373759f36e Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 12:49:01 +0300 Subject: [PATCH 34/43] Refactoring --- .../test_fixture_return_none/conftest.py | 2 +- .../test_fixture_return_none.py | 4 +- .../test_fixture_setup_failure/conftest.py | 2 +- .../test_fixture_setup_failure.py | 4 +- .../test_fixture_teardown_failure/conftest.py | 2 +- .../test_fixture_teardown_failure.py | 4 +- .../test_fixture_yield_none/conftest.py | 2 +- .../test_fixture_yield_none.py | 4 +- tests/integration/test_fixtures.py | 72 ++++++++++++------- 9 files changed, 57 insertions(+), 39 deletions(-) diff --git a/examples/fixtures/test_fixture_return_none/conftest.py b/examples/fixtures/test_fixture_return_none/conftest.py index e19435a4..a81a879e 100644 --- a/examples/fixtures/test_fixture_return_none/conftest.py +++ b/examples/fixtures/test_fixture_return_none/conftest.py @@ -25,5 +25,5 @@ @pytest.fixture -def test_fixture_setup_config_none(): +def test_fixture_return_none_config(): LOGGER.warning(LOG_MESSAGE_SETUP) diff --git a/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py b/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py index 6ac0192a..e0003762 100644 --- a/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py +++ b/examples/fixtures/test_fixture_return_none/test_fixture_return_none.py @@ -25,5 +25,5 @@ # limitations under the License. -def test_fixture_setup_none(test_fixture_setup_config_none): - assert test_fixture_setup_config_none is None +def test_fixture_setup_none(test_fixture_return_none_config): + assert test_fixture_return_none_config is None diff --git a/examples/fixtures/test_fixture_setup_failure/conftest.py b/examples/fixtures/test_fixture_setup_failure/conftest.py index 014f37db..a6dfce29 100644 --- a/examples/fixtures/test_fixture_setup_failure/conftest.py +++ b/examples/fixtures/test_fixture_setup_failure/conftest.py @@ -25,6 +25,6 @@ @pytest.fixture -def fixture_setup_failure_config(): +def test_fixture_setup_failure_config(): logging.error(LOG_MESSAGE_SETUP) raise Exception('Fixture setup failed') diff --git a/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py b/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py index fb041ea5..76c53b05 100644 --- a/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py +++ b/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py @@ -35,6 +35,6 @@ LOG_MESSAGE_TEST = 'Log message for test of setup failure' -def test_fixture_setup_failure(fixture_setup_failure_config): +def test_fixture_setup_failure(test_fixture_setup_failure_config): logging.error(LOG_MESSAGE_TEST) - assert fixture_setup_failure_config is not None + assert test_fixture_setup_failure_config is not None diff --git a/examples/fixtures/test_fixture_teardown_failure/conftest.py b/examples/fixtures/test_fixture_teardown_failure/conftest.py index a38dc7de..3315a867 100644 --- a/examples/fixtures/test_fixture_teardown_failure/conftest.py +++ b/examples/fixtures/test_fixture_teardown_failure/conftest.py @@ -27,7 +27,7 @@ @pytest.fixture -def fixture_teardown_failure_config(): +def test_fixture_teardown_failure_config(): logging.error(LOG_MESSAGE_BEFORE_YIELD) yield mock.Mock() logging.error(LOG_MESSAGE_TEARDOWN) diff --git a/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py b/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py index 132aac70..1a7e257e 100644 --- a/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py +++ b/examples/fixtures/test_fixture_teardown_failure/test_fixture_teardown_failure.py @@ -27,6 +27,6 @@ from time import sleep -def test_fixture_teardown_failure(fixture_teardown_failure_config): +def test_fixture_teardown_failure(test_fixture_teardown_failure_config): sleep(0.001) - assert fixture_teardown_failure_config is not None + assert test_fixture_teardown_failure_config is not None diff --git a/examples/fixtures/test_fixture_yield_none/conftest.py b/examples/fixtures/test_fixture_yield_none/conftest.py index da8b40dc..28839ecd 100644 --- a/examples/fixtures/test_fixture_yield_none/conftest.py +++ b/examples/fixtures/test_fixture_yield_none/conftest.py @@ -25,6 +25,6 @@ @pytest.fixture -def test_fixture_setup_yield_none(): +def test_fixture_yield_none_config(): LOGGER.warning(LOG_MESSAGE_SETUP) yield None diff --git a/examples/fixtures/test_fixture_yield_none/test_fixture_yield_none.py b/examples/fixtures/test_fixture_yield_none/test_fixture_yield_none.py index 7c3839bf..cf73de1c 100644 --- a/examples/fixtures/test_fixture_yield_none/test_fixture_yield_none.py +++ b/examples/fixtures/test_fixture_yield_none/test_fixture_yield_none.py @@ -25,5 +25,5 @@ # limitations under the License. -def test_fixture_yield_none(test_fixture_setup_yield_none): - assert test_fixture_setup_yield_none is None +def test_fixture_yield_none(test_fixture_yield_none_config): + assert test_fixture_yield_none_config is None diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index 1db5a71b..a6fe48e8 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -86,7 +86,7 @@ def test_fixture_setup(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/test_fixture_setup' - result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_setup'], variables=variables) + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' start_count = mock_client.start_test_item.call_count @@ -183,7 +183,8 @@ def test_fixture_setup_failure(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_setup_failure'], variables=variables) + test_path = 'examples/fixtures/test_fixture_setup_failure' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 1, 'Exit code should be 1 (test failure)' start_count = mock_client.start_test_item.call_count @@ -195,7 +196,8 @@ def test_fixture_setup_failure(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - step_name = 'function fixture setup: fixture_setup_failure_config' + fixture_name = f'{test_path.split("/")[-1]}_config' + step_name = f'function fixture setup: {fixture_name}' assert setup_call_args[0] == step_name setup_call_kwargs = call_args[1][1] @@ -230,7 +232,8 @@ def test_fixture_teardown_failure(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_teardown_failure'], variables=variables) + test_path = 'examples/fixtures/test_fixture_teardown_failure' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 1, 'Exit code should be 1 (test failure)' start_count = mock_client.start_test_item.call_count @@ -239,14 +242,15 @@ def test_fixture_teardown_failure(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - setup_step_name = 'function fixture setup: fixture_teardown_failure_config' + fixture_name = f'{test_path.split("/")[-1]}_config' + setup_step_name = f'function fixture setup: {fixture_name}' assert setup_call_args[0] == setup_step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] teardown_call_args = call_args[-1][0] - teardown_step_name = 'function fixture teardown: fixture_teardown_failure_config' + teardown_step_name = f'function fixture teardown: {fixture_name}' assert teardown_call_args[0] == teardown_step_name setup_call_kwargs = call_args[-1][1] @@ -290,7 +294,8 @@ def test_fixture_yield_none(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_yield_none'], variables=variables) + test_path = 'examples/fixtures/test_fixture_yield_none' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' start_count = mock_client.start_test_item.call_count @@ -299,14 +304,15 @@ def test_fixture_yield_none(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - setup_step_name = 'function fixture setup: test_fixture_setup_yield_none' + fixture_name = f'{test_path.split("/")[-1]}_config' + setup_step_name = f'function fixture setup: {fixture_name}' assert setup_call_args[0] == setup_step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] teardown_call_args = call_args[-1][0] - teardown_step_name = 'function fixture teardown: test_fixture_setup_yield_none' + teardown_step_name = f'function fixture teardown: {fixture_name}' assert teardown_call_args[0] == teardown_step_name setup_call_kwargs = call_args[-1][1] @@ -334,7 +340,8 @@ def test_fixture_return_none(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/test_fixture_return_none'], variables=variables) + test_path = 'examples/fixtures/test_fixture_return_none' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' start_count = mock_client.start_test_item.call_count @@ -343,14 +350,15 @@ def test_fixture_return_none(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - setup_step_name = 'function fixture setup: test_fixture_setup_config_none' + fixture_name = f'{test_path.split("/")[-1]}_config' + setup_step_name = f'function fixture setup: {fixture_name}' assert setup_call_args[0] == setup_step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] teardown_call_args = call_args[-1][0] - teardown_step_name = 'function fixture teardown: test_fixture_setup_config_none' + teardown_step_name = f'function fixture teardown: {fixture_name}' assert teardown_call_args[0] == teardown_step_name setup_call_kwargs = call_args[-1][1] @@ -378,7 +386,8 @@ def test_failure_fixture_teardown(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/test_failure_fixture_teardown'], variables=variables) + test_path = 'examples/fixtures/test_failure_fixture_teardown' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 1, 'Exit code should be 1 (test failure)' start_count = mock_client.start_test_item.call_count @@ -387,14 +396,15 @@ def test_failure_fixture_teardown(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - setup_step_name = 'function fixture setup: test_failure_fixture_teardown_config' + fixture_name = f'{test_path.split("/")[-1]}_config' + setup_step_name = f'function fixture setup: {fixture_name}' assert setup_call_args[0] == setup_step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] teardown_call_args = call_args[-1][0] - teardown_step_name = 'function fixture teardown: test_failure_fixture_teardown_config' + teardown_step_name = f'function fixture teardown: {fixture_name}' assert teardown_call_args[0] == teardown_step_name setup_call_kwargs = call_args[-1][1] @@ -435,7 +445,8 @@ def test_session_fixture_setup(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/session_fixture_return'], variables=variables) + test_path = 'examples/fixtures/session_fixture_return' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' start_count = mock_client.start_test_item.call_count @@ -444,14 +455,15 @@ def test_session_fixture_setup(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - step_name = 'session fixture setup: session_fixture_return_config' + fixture_name = f'{test_path.split("/")[-1]}_config' + step_name = f'session fixture setup: {fixture_name}' assert setup_call_args[0] == step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] teardown_call_args = call_args[-1][0] - assert teardown_call_args[0] == 'session fixture teardown: session_fixture_return_config' + assert teardown_call_args[0] == f'session fixture teardown: {fixture_name}' setup_call_kwargs = call_args[-1][1] assert not setup_call_kwargs['has_stats'] @@ -465,7 +477,8 @@ def test_package_fixture_setup(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/package_fixture_return'], variables=variables) + test_path = 'examples/fixtures/package_fixture_return' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' start_count = mock_client.start_test_item.call_count @@ -474,14 +487,15 @@ def test_package_fixture_setup(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - step_name = 'package fixture setup: package_fixture_return_config' + fixture_name = f'{test_path.split("/")[-1]}_config' + step_name = f'package fixture setup: {fixture_name}' assert setup_call_args[0] == step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] teardown_call_args = call_args[-1][0] - assert teardown_call_args[0] == 'package fixture teardown: package_fixture_return_config' + assert teardown_call_args[0] == f'package fixture teardown: {fixture_name}' setup_call_kwargs = call_args[-1][1] assert not setup_call_kwargs['has_stats'] @@ -495,7 +509,8 @@ def test_module_fixture_setup(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/module_fixture_return'], variables=variables) + test_path = 'examples/fixtures/module_fixture_return' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' start_count = mock_client.start_test_item.call_count @@ -504,14 +519,15 @@ def test_module_fixture_setup(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - step_name = 'module fixture setup: module_fixture_return_config' + fixture_name = f'{test_path.split("/")[-1]}_config' + step_name = f'module fixture setup: {fixture_name}' assert setup_call_args[0] == step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] teardown_call_args = call_args[-1][0] - assert teardown_call_args[0] == 'module fixture teardown: module_fixture_return_config' + assert teardown_call_args[0] == f'module fixture teardown: {fixture_name}' setup_call_kwargs = call_args[-1][1] assert not setup_call_kwargs['has_stats'] @@ -525,7 +541,8 @@ def test_class_fixture_setup(mock_client_init): variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = True - result = utils.run_pytest_tests(tests=['examples/fixtures/class_fixture_return'], variables=variables) + test_path = 'examples/fixtures/class_fixture_return' + result = utils.run_pytest_tests(tests=[test_path], variables=variables) assert int(result) == 0, 'Exit code should be 0 (no errors)' start_count = mock_client.start_test_item.call_count @@ -534,7 +551,8 @@ def test_class_fixture_setup(mock_client_init): call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] - step_name = 'class fixture setup: class_fixture_return_config' + fixture_name = f'{test_path.split("/")[-1]}_config' + step_name = f'class fixture setup: {fixture_name}' assert setup_call_args[0] == step_name setup_call_kwargs = call_args[1][1] assert not setup_call_kwargs['has_stats'] @@ -544,7 +562,7 @@ def test_class_fixture_setup(mock_client_init): setup_call_kwargs = call_args[-3][1] assert not setup_call_kwargs['has_stats'] - teardown_step_name = 'class fixture teardown: class_fixture_return_config' + teardown_step_name = f'class fixture teardown: {fixture_name}' teardown_call_args = call_args[-5][0] assert teardown_call_args[0] == teardown_step_name setup_call_kwargs = call_args[-5][1] From 461203f8a7054cd7bbf51842c1ee07ff69a1944a Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 13:10:21 +0300 Subject: [PATCH 35/43] Refactoring --- tests/integration/test_fixtures.py | 152 ++++++++++------------------- 1 file changed, 52 insertions(+), 100 deletions(-) diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index a6fe48e8..01487f30 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -56,11 +56,25 @@ def remove_last_item_id(*_, **__) -> str: return ITEM_ID_LIST.pop() +def setup_mock(mock_client_init): + mock_client = mock_client_init.return_value + mock_client.step_reporter = StepReporter(mock_client) + return mock_client + + +def setup_mock_for_logging(mock_client_init): + mock_client = setup_mock(mock_client_init) + set_current(mock_client) + mock_client.start_test_item.side_effect = generate_item_id + mock_client.finish_test_item.side_effect = remove_last_item_id + mock_client.current_item.side_effect = get_last_item_id + return mock_client + + @pytest.mark.parametrize('switch', [True, False]) @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_on_off(mock_client_init, switch): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) + mock_client = setup_mock(mock_client_init) variables = dict(utils.DEFAULT_VARIABLES) variables['rp_report_fixtures'] = switch @@ -74,20 +88,22 @@ def test_fixture_on_off(mock_client_init, switch): 'Incorrect number of "start_test_item" or "finish_test_item" calls' +def run_tests(test_path, should_fail=False): + variables = dict(utils.DEFAULT_VARIABLES) + variables['rp_report_fixtures'] = True + result = utils.run_pytest_tests(tests=[test_path], variables=variables) + if should_fail: + assert int(result) == 1, 'Exit code should be 1 (test failure)' + else: + assert int(result) == 0, 'Exit code should be 0 (no errors)' + + @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_setup(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) - set_current(mock_client) - mock_client.start_test_item.side_effect = generate_item_id - mock_client.finish_test_item.side_effect = remove_last_item_id - mock_client.current_item.side_effect = get_last_item_id + mock_client = setup_mock_for_logging(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/test_fixture_setup' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + run_tests(test_path) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count @@ -121,18 +137,10 @@ def test_fixture_setup(mock_client_init): @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_teardown(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) - set_current(mock_client) - mock_client.start_test_item.side_effect = generate_item_id - mock_client.finish_test_item.side_effect = remove_last_item_id - mock_client.current_item.side_effect = get_last_item_id + mock_client = setup_mock_for_logging(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/test_fixture_teardown' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + run_tests(test_path) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count @@ -172,27 +180,17 @@ def test_fixture_teardown(mock_client_init): 'examples/fixtures/test_fixture_teardown/test_fixture_teardown.py::test_fixture_teardown_1' +@pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_setup_failure(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) - set_current(mock_client) - mock_client.start_test_item.side_effect = generate_item_id - mock_client.finish_test_item.side_effect = remove_last_item_id - mock_client.current_item.side_effect = get_last_item_id + mock_client = setup_mock_for_logging(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/test_fixture_setup_failure' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 1, 'Exit code should be 1 (test failure)' + run_tests(test_path, True) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count - if sys.version_info < (3, 8): - assert start_count == finish_count == 3, 'Incorrect number of "start_test_item" or "finish_test_item" calls' - else: - assert start_count == finish_count == 2, 'Incorrect number of "start_test_item" or "finish_test_item" calls' + assert start_count == finish_count == 2, 'Incorrect number of "start_test_item" or "finish_test_item" calls' call_args = mock_client.start_test_item.call_args_list setup_call_args = call_args[1][0] @@ -223,18 +221,10 @@ def test_fixture_setup_failure(mock_client_init): @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_teardown_failure(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) - set_current(mock_client) - mock_client.start_test_item.side_effect = generate_item_id - mock_client.finish_test_item.side_effect = remove_last_item_id - mock_client.current_item.side_effect = get_last_item_id + mock_client = setup_mock_for_logging(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/test_fixture_teardown_failure' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 1, 'Exit code should be 1 (test failure)' + run_tests(test_path, True) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count @@ -285,18 +275,10 @@ def test_fixture_teardown_failure(mock_client_init): @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_yield_none(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) - set_current(mock_client) - mock_client.start_test_item.side_effect = generate_item_id - mock_client.finish_test_item.side_effect = remove_last_item_id - mock_client.current_item.side_effect = get_last_item_id + mock_client = setup_mock_for_logging(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/test_fixture_yield_none' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + run_tests(test_path) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count @@ -331,18 +313,10 @@ def test_fixture_yield_none(mock_client_init): @mock.patch(REPORT_PORTAL_SERVICE) def test_fixture_return_none(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) - set_current(mock_client) - mock_client.start_test_item.side_effect = generate_item_id - mock_client.finish_test_item.side_effect = remove_last_item_id - mock_client.current_item.side_effect = get_last_item_id + mock_client = setup_mock_for_logging(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/test_fixture_return_none' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + run_tests(test_path) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count @@ -377,18 +351,10 @@ def test_fixture_return_none(mock_client_init): @mock.patch(REPORT_PORTAL_SERVICE) def test_failure_fixture_teardown(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) - set_current(mock_client) - mock_client.start_test_item.side_effect = generate_item_id - mock_client.finish_test_item.side_effect = remove_last_item_id - mock_client.current_item.side_effect = get_last_item_id + mock_client = setup_mock_for_logging(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/test_failure_fixture_teardown' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 1, 'Exit code should be 1 (test failure)' + run_tests(test_path, True) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count @@ -440,14 +406,10 @@ def test_failure_fixture_teardown(mock_client_init): @pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_session_fixture_setup(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) + mock_client = setup_mock(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/session_fixture_return' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + run_tests(test_path) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count @@ -472,14 +434,10 @@ def test_session_fixture_setup(mock_client_init): @pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_package_fixture_setup(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) + mock_client = setup_mock(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/package_fixture_return' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + run_tests(test_path) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count @@ -504,14 +462,10 @@ def test_package_fixture_setup(mock_client_init): @pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_module_fixture_setup(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) + mock_client = setup_mock(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/module_fixture_return' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + run_tests(test_path) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count @@ -533,17 +487,15 @@ def test_module_fixture_setup(mock_client_init): assert not setup_call_kwargs['has_stats'] + + @pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_class_fixture_setup(mock_client_init): - mock_client = mock_client_init.return_value - mock_client.step_reporter = StepReporter(mock_client) + mock_client = setup_mock(mock_client_init) - variables = dict(utils.DEFAULT_VARIABLES) - variables['rp_report_fixtures'] = True test_path = 'examples/fixtures/class_fixture_return' - result = utils.run_pytest_tests(tests=[test_path], variables=variables) - assert int(result) == 0, 'Exit code should be 0 (no errors)' + run_tests(test_path) start_count = mock_client.start_test_item.call_count finish_count = mock_client.finish_test_item.call_count From bad8bedcb6536301d5deecef011919ef0dc434fe Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 13:10:42 +0300 Subject: [PATCH 36/43] Refactoring --- tests/integration/test_fixtures.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/integration/test_fixtures.py b/tests/integration/test_fixtures.py index 01487f30..4f949153 100644 --- a/tests/integration/test_fixtures.py +++ b/tests/integration/test_fixtures.py @@ -487,8 +487,6 @@ def test_module_fixture_setup(mock_client_init): assert not setup_call_kwargs['has_stats'] - - @pytest.mark.skipif(sys.version_info < (3, 8), reason='Python 3.8+ required due to bugs in older versions') @mock.patch(REPORT_PORTAL_SERVICE) def test_class_fixture_setup(mock_client_init): From 21b321d507f0c6dadb5fea4613a21411e5ea546b Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 13:20:35 +0300 Subject: [PATCH 37/43] CHANGELOG.md update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3c98e33..88b1fe10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Added +Issue [#332](https://github.com/reportportal/agent-python-pytest/issues/332): Support for fixture reporting, by @HardNorth ## [5.4.2] ### Fixed From 172dca7fa100bdecfeb2d50db0f7ae17c64a03ba Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 13:29:19 +0300 Subject: [PATCH 38/43] Add some type hinting --- pytest_reportportal/service.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index d1f1534c..d094c010 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -27,6 +27,7 @@ from pytest import Class, Function, Module, Package, Item, Session, PytestWarning from reportportal_client.aio import Task from reportportal_client.core.rp_issues import Issue, ExternalIssue +from reportportal_client.helpers import timestamp from .config import AgentConfig @@ -54,19 +55,13 @@ MAX_ITEM_NAME_LENGTH: int = 256 TRUNCATION_STR: str = '...' ROOT_DIR: str = str(os.path.abspath(curdir)) -PYTEST_MARKS_IGNORE: Set[str] = {'parametrize', 'usefixtures', - 'filterwarnings'} +PYTEST_MARKS_IGNORE: Set[str] = {'parametrize', 'usefixtures', 'filterwarnings'} NOT_ISSUE: Issue = Issue('NOT_ISSUE') ISSUE_DESCRIPTION_LINE_TEMPLATE: str = '* {}:{}' ISSUE_DESCRIPTION_URL_TEMPLATE: str = ' [{issue_id}]({url})' ISSUE_DESCRIPTION_ID_TEMPLATE: str = ' {issue_id}' -def timestamp(): - """Time for difference between start and finish tests.""" - return str(int(time() * 1000)) - - def trim_docstring(docstring: str) -> str: """ Convert docstring. @@ -102,7 +97,6 @@ def trim_docstring(docstring: str) -> str: @unique class LeafType(Enum): """This class stores test item path types.""" - DIR = auto() CODE = auto() ROOT = auto() @@ -111,7 +105,6 @@ class LeafType(Enum): @unique class ExecStatus(Enum): """This class stores test item path types.""" - CREATED = auto() IN_PROGRESS = auto() FINISHED = auto() @@ -125,7 +118,7 @@ def wrap(*args, **kwargs): if args and isinstance(args[0], PyTestServiceClass): if not args[0].rp: return - func(*args, **kwargs) + return func(*args, **kwargs) return wrap @@ -176,7 +169,7 @@ def issue_types(self) -> Dict[str, str]: self._issue_types[item["shortName"]] = item["locator"] return self._issue_types - def _get_launch_attributes(self, ini_attrs): + def _get_launch_attributes(self, ini_attrs: Optional[List[Dict[str, str]]]) -> List[Dict[str, str]]: """Generate launch attributes in the format supported by the client. :param list ini_attrs: List for attributes from the pytest.ini file @@ -187,7 +180,7 @@ def _get_launch_attributes(self, ini_attrs): '{}|{}'.format(self.agent_name, self.agent_version)) return attributes + dict_to_payload(system_attributes) - def _build_start_launch_rq(self): + def _build_start_launch_rq(self) -> Dict[str, Any]: rp_launch_attributes = self._config.rp_launch_attributes attributes = gen_attributes(rp_launch_attributes) if rp_launch_attributes else None From 8022b269e5394a503c028448c79f5b8941798fee Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 13:37:11 +0300 Subject: [PATCH 39/43] Code style fix --- pytest_reportportal/service.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index d094c010..cb531055 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -97,6 +97,7 @@ def trim_docstring(docstring: str) -> str: @unique class LeafType(Enum): """This class stores test item path types.""" + DIR = auto() CODE = auto() ROOT = auto() @@ -105,6 +106,7 @@ class LeafType(Enum): @unique class ExecStatus(Enum): """This class stores test item path types.""" + CREATED = auto() IN_PROGRESS = auto() FINISHED = auto() From 634ac327f1a5af7bb13fb52271fe1b4b4c01ead5 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 13:43:03 +0300 Subject: [PATCH 40/43] Code style fix --- pytest_reportportal/service.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index cb531055..83f4090b 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -403,16 +403,14 @@ def _lock(self, leaf, func): return func(leaf) def _build_start_suite_rq(self, leaf): - code_ref = str(leaf['item']) if leaf['type'] == LeafType.DIR \ - else str(leaf['item'].fspath) + code_ref = str(leaf['item']) if leaf['type'] == LeafType.DIR else str(leaf['item'].fspath) payload = { 'name': self._get_item_name(leaf['name']), 'description': self._get_item_description(leaf['item']), 'start_time': timestamp(), 'item_type': 'SUITE', 'code_ref': code_ref, - 'parent_item_id': self._lock(leaf['parent'], - lambda p: p['item_id']) + 'parent_item_id': self._lock(leaf['parent'], lambda p: p['item_id']) } return payload From 51124a89b98ba2d2d1a48f48d38ece22b069e3fc Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 13:50:20 +0300 Subject: [PATCH 41/43] Add parent item ID check --- pytest_reportportal/service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 83f4090b..f748b387 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -404,13 +404,14 @@ def _lock(self, leaf, func): def _build_start_suite_rq(self, leaf): code_ref = str(leaf['item']) if leaf['type'] == LeafType.DIR else str(leaf['item'].fspath) + parent_item_id = self._lock(leaf['parent'], lambda p: p.get('item_id')) if 'parent' in leaf else None payload = { 'name': self._get_item_name(leaf['name']), 'description': self._get_item_description(leaf['item']), 'start_time': timestamp(), 'item_type': 'SUITE', 'code_ref': code_ref, - 'parent_item_id': self._lock(leaf['parent'], lambda p: p['item_id']) + 'parent_item_id': parent_item_id } return payload From 1b4caba026720f03bb25edec9f02b5f044e187b1 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 13:54:53 +0300 Subject: [PATCH 42/43] Add type hinting --- pytest_reportportal/service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index f748b387..da81388e 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -20,7 +20,7 @@ from functools import wraps from os import curdir from time import time, sleep -from typing import List, Any, Optional, Set, Dict, Tuple, Union +from typing import List, Any, Optional, Set, Dict, Tuple, Union, Callable from _pytest.doctest import DoctestItem from aenum import auto, Enum, unique @@ -389,7 +389,7 @@ def _get_item_description(self, test_item): if isinstance(test_item, DoctestItem): return test_item.reportinfo()[2] - def _lock(self, leaf, func): + def _lock(self, leaf: Dict[str, Any], func: Callable[[Dict[str, Any]], Any]) -> Any: """ Lock test tree leaf and execute a function, bypass the leaf to it. From 76ceae67ad7541eec474f623cdbc1c9fc4727dcb Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Mon, 21 Oct 2024 14:00:57 +0300 Subject: [PATCH 43/43] Max Item name fix --- CHANGELOG.md | 4 +++- pytest_reportportal/service.py | 17 ++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b1fe10..4d383378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ # Changelog ## [Unreleased] +### Fixed +- Issue [#375](https://github.com/reportportal/agent-python-pytest/issues/375): Fix max Item name length, by @HardNorth ### Added -Issue [#332](https://github.com/reportportal/agent-python-pytest/issues/332): Support for fixture reporting, by @HardNorth +- Issue [#332](https://github.com/reportportal/agent-python-pytest/issues/332): Support for fixture reporting, by @HardNorth ## [5.4.2] ### Fixed diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index da81388e..12905c59 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -358,21 +358,16 @@ def collect_tests(self, session: Session) -> None: self._merge_code(test_tree) self._build_item_paths(test_tree, []) - def _get_item_name(self, name): + def _get_item_name(self, name: str) -> str: """Get name of item. - :param name: Item name - :return: name + :param name: Test Item name + :return: truncated to maximum length name if needed """ if len(name) > MAX_ITEM_NAME_LENGTH: - name = name[:MAX_ITEM_NAME_LENGTH - len(TRUNCATION_STR)] + \ - TRUNCATION_STR - log.warning( - PytestWarning( - 'Test leaf ID was truncated to "{}" because of name size ' - 'constrains on Report Portal'.format(name) - ) - ) + name = name[:MAX_ITEM_NAME_LENGTH - len(TRUNCATION_STR)] + TRUNCATION_STR + log.warning(PytestWarning( + f'Test leaf ID was truncated to "{name}" because of name size constrains on Report Portal')) return name def _get_item_description(self, test_item):