diff --git a/CHANGELOG.md b/CHANGELOG.md index d3c98e33..4d383378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +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 ## [5.4.2] ### Fixed 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..da43a33d --- /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 class_fixture_return_config(): + print('setup') + return mock.Mock() diff --git a/examples/fixtures/class_fixture_return/test_fixture_class_setup.py b/examples/fixtures/class_fixture_return/test_fixture_class_setup.py new file mode 100644 index 00000000..78881afd --- /dev/null +++ b/examples/fixtures/class_fixture_return/test_fixture_class_setup.py @@ -0,0 +1,40 @@ +# 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 TestClassOne: + 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, class_fixture_return_config): + assert class_fixture_return_config is not None + + +class TestClassTwo: + 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, 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 new file mode 100644 index 00000000..3336e6ae --- /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 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 new file mode 100644 index 00000000..b11fe6dd --- /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(module_fixture_return_config): + assert module_fixture_return_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/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..f4069446 --- /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 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 new file mode 100644 index 00000000..4c42833e --- /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(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 new file mode 100644 index 00000000..7ba2f2af --- /dev/null +++ b/examples/fixtures/package_fixture_return/test_fixture_package_setup_second.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_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 new file mode 100644 index 00000000..9ab20179 --- /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 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 new file mode 100644 index 00000000..3480e949 --- /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(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 new file mode 100644 index 00000000..9640d580 --- /dev/null +++ b/examples/fixtures/session_fixture_return/test_fixture_session_setup_second.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_second(session_fixture_return_config): + assert session_fixture_return_config is not None 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..3100face --- /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 and test failure' +LOG_MESSAGE_TEARDOWN = 'Log message for teardown after test failure' + + +@pytest.fixture +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_failure_fixture_teardown.py b/examples/fixtures/test_failure_fixture_teardown/test_failure_fixture_teardown.py new file mode 100644 index 00000000..31f68849 --- /dev/null +++ b/examples/fixtures/test_failure_fixture_teardown/test_failure_fixture_teardown.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_failure_fixture_teardown(test_failure_fixture_teardown_config): + assert test_failure_fixture_teardown_config is None diff --git a/examples/fixtures/test_fixture_return_none/conftest.py b/examples/fixtures/test_fixture_return_none/conftest.py new file mode 100644 index 00000000..a81a879e --- /dev/null +++ b/examples/fixtures/test_fixture_return_none/conftest.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. + +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_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 new file mode 100644 index 00000000..e0003762 --- /dev/null +++ b/examples/fixtures/test_fixture_return_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_return_none_config): + assert test_fixture_return_none_config is None diff --git a/examples/fixtures/test_fixture_setup/conftest.py b/examples/fixtures/test_fixture_setup/conftest.py new file mode 100644 index 00000000..36897fae --- /dev/null +++ b/examples/fixtures/test_fixture_setup/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' + + +@pytest.fixture +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 new file mode 100644 index 00000000..cc2f8566 --- /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(test_fixture_setup_config): + assert test_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..a6dfce29 --- /dev/null +++ b/examples/fixtures/test_fixture_setup_failure/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 failure' + + +@pytest.fixture +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 new file mode 100644 index 00000000..76c53b05 --- /dev/null +++ b/examples/fixtures/test_fixture_setup_failure/test_fixture_setup_failure.py @@ -0,0 +1,40 @@ +# 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. + +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(test_fixture_setup_failure_config): + logging.error(LOG_MESSAGE_TEST) + assert test_fixture_setup_failure_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..29b3e707 --- /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 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 new file mode 100644 index 00000000..aef60447 --- /dev/null +++ b/examples/fixtures/test_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_fixture_teardown(test_fixture_teardown_config): + sleep(0.001) + assert test_fixture_teardown_config is not None 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..3315a867 --- /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 test_fixture_teardown_failure_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..1a7e257e --- /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_failure(test_fixture_teardown_failure_config): + sleep(0.001) + 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 new file mode 100644 index 00000000..28839ecd --- /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 yield None' + + +@pytest.fixture +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 new file mode 100644 index 00000000..cf73de1c --- /dev/null +++ b/examples/fixtures/test_fixture_yield_none/test_fixture_yield_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_yield_none(test_fixture_yield_none_config): + assert test_fixture_yield_none_config is 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/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 a4b27872..2f76b061 100644 --- a/pytest_reportportal/plugin.py +++ b/pytest_reportportal/plugin.py @@ -16,35 +16,39 @@ import logging import os.path import time +from logging import Logger +from typing import Any import _pytest.logging import dill as pickle import pytest +# noinspection PyPackageRequirements import requests -from reportportal_client import RPLogHandler +from pytest import 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 +60,8 @@ def pytest_configure_node(node): node.workerinput['py_test_service'] = pickle.dumps(node.config.py_test_service) -def is_control(config): +# 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, @@ -65,7 +70,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 +84,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 +116,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 +130,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 +147,8 @@ def pytest_sessionfinish(session): config.py_test_service.stop() -def register_markers(config): +# 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 @@ -161,30 +167,28 @@ 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. :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 +# no 'config' type for backward compatibility for older pytest versions # noinspection PyProtectedMember -def pytest_configure(config): +def pytest_configure(config) -> None: """Update Config object with attributes required for reporting to RP. :param config: Object of the pytest Config class @@ -227,7 +231,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,9 +250,8 @@ def pytest_runtestloop(session): # noinspection PyProtectedMember @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_protocol(item): - """ - Control start and finish of pytest items. +def pytest_runtest_protocol(item: Item) -> None: + """Control start and finish of pytest items. :param item: Pytest.Item :return: generator object @@ -262,11 +265,9 @@ def pytest_runtest_protocol(item): 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)) @@ -278,9 +279,8 @@ def pytest_runtest_protocol(item): # noinspection PyProtectedMember @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item): - """ - Change runtest_makereport function. +def pytest_runtest_makereport(item: Item) -> None: + """Change runtest_makereport function. :param item: pytest.Item :return: None @@ -295,7 +295,59 @@ def pytest_runtest_makereport(item): service.process_results(item, report) -def pytest_addoption(parser): +def report_fixture(request, 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 + 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 + + yield from service.report_fixture(name, error_msg) + + +# 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}') + + +# 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 + """ + 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 + return + + yield from report_fixture( + request, f'{fixturedef.scope} fixture teardown: {fixturedef.argname}', + f'{fixturedef.scope} fixture teardown failed: {fixturedef.argname}') + + +# 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 @@ -343,7 +395,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', @@ -377,7 +429,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 ' @@ -513,3 +565,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]' + ) 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: ... diff --git a/pytest_reportportal/service.py b/pytest_reportportal/service.py index 5eb5a5d4..12905c59 100644 --- a/pytest_reportportal/service.py +++ b/pytest_reportportal/service.py @@ -20,14 +20,14 @@ 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 -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 reportportal_client.helpers import timestamp from .config import AgentConfig @@ -55,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. @@ -126,7 +120,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 @@ -177,7 +171,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 @@ -188,7 +182,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 @@ -215,7 +209,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 +222,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 +238,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: 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. :param leaf_type: the leaf type @@ -259,14 +253,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 +274,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 +291,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 +327,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 +343,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 """ @@ -370,27 +358,20 @@ def collect_tests(self, session): self._merge_code(test_tree) self._build_item_paths(test_tree, []) - def _get_item_name(self, name): - """ - Get name of item. + 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): - """ - Get description of item. + """Get description of item. :param test_item: pytest.Item :return string description @@ -403,7 +384,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. @@ -417,16 +398,15 @@ 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) + 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 @@ -443,7 +423,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 +435,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 +665,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. @@ -874,6 +853,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/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 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_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() 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 new file mode 100644 index 00000000..4f949153 --- /dev/null +++ b/tests/integration/test_fixtures.py @@ -0,0 +1,524 @@ +# 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 sys +from collections import defaultdict +from unittest import mock + +import pytest +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 tests import REPORT_PORTAL_SERVICE +from tests.helpers import utils + +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] + + +def remove_last_item_id(*_, **__) -> str: + if len(ITEM_ID_LIST) > 0: + 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 = setup_mock(mock_client_init) + + 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' + + +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 = setup_mock_for_logging(mock_client_init) + + test_path = 'examples/fixtures/test_fixture_setup' + run_tests(test_path) + + 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] + 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] == f'function fixture teardown: {fixture_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] == 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 = setup_mock_for_logging(mock_client_init) + + test_path = 'examples/fixtures/test_fixture_teardown' + run_tests(test_path) + + 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] + 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 = f'function fixture teardown: {fixture_name}' + 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'] == \ + '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 = setup_mock_for_logging(mock_client_init) + + test_path = 'examples/fixtures/test_fixture_setup_failure' + run_tests(test_path, True) + + 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] + 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'] + + 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' + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_teardown_failure(mock_client_init): + mock_client = setup_mock_for_logging(mock_client_init) + + test_path = 'examples/fixtures/test_fixture_teardown_failure' + run_tests(test_path, True) + + 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] + 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 = f'function fixture teardown: {fixture_name}' + 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') + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_fixture_yield_none(mock_client_init): + mock_client = setup_mock_for_logging(mock_client_init) + + test_path = 'examples/fixtures/test_fixture_yield_none' + run_tests(test_path) + + 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] + 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 = f'function fixture teardown: {fixture_name}' + 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 = setup_mock_for_logging(mock_client_init) + + test_path = 'examples/fixtures/test_fixture_return_none' + run_tests(test_path) + + 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] + 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 = f'function fixture teardown: {fixture_name}' + 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' + + +@mock.patch(REPORT_PORTAL_SERVICE) +def test_failure_fixture_teardown(mock_client_init): + mock_client = setup_mock_for_logging(mock_client_init) + + test_path = 'examples/fixtures/test_failure_fixture_teardown' + run_tests(test_path, True) + + 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] + 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 = f'function fixture teardown: {fixture_name}' + 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') + + +@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 = setup_mock(mock_client_init) + + test_path = 'examples/fixtures/session_fixture_return' + run_tests(test_path) + + 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] + 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] == f'session fixture teardown: {fixture_name}' + + setup_call_kwargs = call_args[-1][1] + 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 = setup_mock(mock_client_init) + + test_path = 'examples/fixtures/package_fixture_return' + run_tests(test_path) + + 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] + 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] == f'package fixture teardown: {fixture_name}' + + setup_call_kwargs = call_args[-1][1] + 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 = setup_mock(mock_client_init) + + test_path = 'examples/fixtures/module_fixture_return' + run_tests(test_path) + + 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] + 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] == f'module fixture teardown: {fixture_name}' + + setup_call_kwargs = call_args[-1][1] + 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 = setup_mock(mock_client_init) + + test_path = 'examples/fixtures/class_fixture_return' + run_tests(test_path) + + 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] + 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'] + + 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 = 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] + 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'] 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 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)