Sync from SUSE:SLFO:Main python-flaky revision 245a4f999fde6aa8682c79e0f49aed06
This commit is contained in:
commit
c51011a502
23
.gitattributes
vendored
Normal file
23
.gitattributes
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
## Default LFS
|
||||||
|
*.7z filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.bsp filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.gem filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.gz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.jar filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.lz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.lzma filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.obscpio filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.oxt filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.rpm filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tbz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tbz2 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.tgz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.txz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.whl filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.xz filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.zip filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.zst filter=lfs diff=lfs merge=lfs -text
|
3
_multibuild
Normal file
3
_multibuild
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<multibuild>
|
||||||
|
<package>test</package>
|
||||||
|
</multibuild>
|
BIN
flaky-3.7.0.tar.gz
(Stored with Git LFS)
Normal file
BIN
flaky-3.7.0.tar.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
84
python-flaky.changes
Normal file
84
python-flaky.changes
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
-------------------------------------------------------------------
|
||||||
|
Fri Apr 21 12:25:08 UTC 2023 - Dirk Müller <dmueller@suse.com>
|
||||||
|
|
||||||
|
- add sle15_python_module_pythons (jsc#PED-68)
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Thu Apr 13 22:41:16 UTC 2023 - Matej Cepl <mcepl@suse.com>
|
||||||
|
|
||||||
|
- Make calling of %{sle15modernpython} optional.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Mon Dec 14 00:48:47 UTC 2020 - Benjamin Greiner <code@bnavigator.de>
|
||||||
|
|
||||||
|
- Fix condition around BuildRequirement
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Sun Dec 13 20:27:15 UTC 2020 - Matej Cepl <mcepl@suse.com>
|
||||||
|
|
||||||
|
- We don't need to break Python 2.7
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Fri Dec 11 15:14:53 UTC 2020 - Matej Cepl <mcepl@suse.com>
|
||||||
|
|
||||||
|
- Add remove_mock.patch to remove dependency on the external mock package.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Fri Dec 11 14:24:39 UTC 2020 - Matej Cepl <mcepl@suse.com>
|
||||||
|
|
||||||
|
- Add remove_nose.patch to remove dependency on nose.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Wed Jul 29 05:39:42 UTC 2020 - Steve Kowalik <steven.kowalik@suse.com>
|
||||||
|
|
||||||
|
- Update to 3.7.0:
|
||||||
|
* Flaky now retries tests which fail during setup.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Fri Jan 31 01:54:06 UTC 2020 - Stefan Brüns <stefan.bruens@rwth-aachen.de>
|
||||||
|
|
||||||
|
- Add required suffix to package name for test package. Although
|
||||||
|
no binary package is created, the OBS blocks any dependent
|
||||||
|
packages until also the test package has been built (Giveaway:
|
||||||
|
main and test flavor create the same .src.rpm)
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Thu Sep 12 12:33:03 UTC 2019 - Tomáš Chvátal <tchvatal@suse.com>
|
||||||
|
|
||||||
|
- Update to 3.6.1:
|
||||||
|
* Reraise KeyboardInterrupt when running tests under pytest.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Fri Jul 26 10:48:12 UTC 2019 - pgajdos@suse.com
|
||||||
|
|
||||||
|
- version update to 3.6.0
|
||||||
|
* Do not print an empty report if no tests marked 'flaky' were run at all (#116). NOTE: This change could be breaking if you relied on the flaky report being printed.
|
||||||
|
- test via multibuild
|
||||||
|
- added sources
|
||||||
|
+ _multibuild
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Tue Mar 12 15:24:55 UTC 2019 - Tomáš Chvátal <tchvatal@suse.com>
|
||||||
|
|
||||||
|
- Update to 3.5.3:
|
||||||
|
* Bugfixes - Flaky is now compatible with pytest >= 4.1.
|
||||||
|
* Officially support and test on Python 3.6 and 3.7.
|
||||||
|
* Adds a pytest marker that can be used instead of `@flaky.
|
||||||
|
* Replaced references to 'slaveoutput', where possible with 'workeroutput', following the convention chosen by pytest.
|
||||||
|
* Prints formatted tracebacks in the flaky report when using nose.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Thu Nov 9 06:10:34 UTC 2017 - arun@gmx.de
|
||||||
|
|
||||||
|
- specfile:
|
||||||
|
* update copyright year
|
||||||
|
|
||||||
|
- update to version 3.4.0:
|
||||||
|
* Bugfixes - Flaky for pytest will no longer silently swallow errors
|
||||||
|
that occur during test setup.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Tue Dec 20 17:37:08 UTC 2016 - jmatejek@suse.com
|
||||||
|
|
||||||
|
- initial commit
|
||||||
|
|
94
python-flaky.spec
Normal file
94
python-flaky.spec
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#
|
||||||
|
# spec file
|
||||||
|
#
|
||||||
|
# Copyright (c) 2023 SUSE LLC
|
||||||
|
#
|
||||||
|
# All modifications and additions to the file contributed by third parties
|
||||||
|
# remain the property of their copyright owners, unless otherwise agreed
|
||||||
|
# upon. The license for this file, and modifications and additions to the
|
||||||
|
# file, is the same license as for the pristine package itself (unless the
|
||||||
|
# license for the pristine package is not an Open Source License, in which
|
||||||
|
# case the license is the MIT License). An "Open Source License" is a
|
||||||
|
# license that conforms to the Open Source Definition (Version 1.9)
|
||||||
|
# published by the Open Source Initiative.
|
||||||
|
|
||||||
|
# Please submit bugfixes or comments via https://bugs.opensuse.org/
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
|
||||||
|
%global flavor @BUILD_FLAVOR@%{nil}
|
||||||
|
%if "%{flavor}" == "test"
|
||||||
|
%define psuffix -test
|
||||||
|
%bcond_without test
|
||||||
|
%else
|
||||||
|
%bcond_with test
|
||||||
|
%endif
|
||||||
|
%{?sle15_python_module_pythons}
|
||||||
|
Name: python-flaky%{?psuffix}
|
||||||
|
Version: 3.7.0
|
||||||
|
Release: 0
|
||||||
|
Summary: Plugin for nose or py.test that automatically reruns flaky tests
|
||||||
|
License: Apache-2.0
|
||||||
|
URL: https://github.com/box/flaky
|
||||||
|
Source: https://files.pythonhosted.org/packages/source/f/flaky/flaky-%{version}.tar.gz
|
||||||
|
# PATCH-FEATURE-UPSTREAM remove_nose.patch gh#box/flaky#171 mcepl@suse.com
|
||||||
|
# remove dependency on nose
|
||||||
|
Patch0: remove_nose.patch
|
||||||
|
# PATCH-FEATURE-UPSTREAM remove_mock.patch gh#box/flaky#171 mcepl@suse.com
|
||||||
|
# this patch makes things totally awesome
|
||||||
|
Patch1: remove_mock.patch
|
||||||
|
BuildRequires: %{python_module setuptools}
|
||||||
|
BuildRequires: fdupes
|
||||||
|
BuildRequires: python-rpm-macros
|
||||||
|
BuildArch: noarch
|
||||||
|
%if %{with test}
|
||||||
|
BuildRequires: %{python_module flaky >= %{version}}
|
||||||
|
BuildRequires: %{python_module genty}
|
||||||
|
BuildRequires: %{python_module pytest}
|
||||||
|
%if 0%{?suse_version} <= 1500
|
||||||
|
BuildRequires: python-mock
|
||||||
|
%endif
|
||||||
|
%endif
|
||||||
|
%python_subpackages
|
||||||
|
|
||||||
|
%description
|
||||||
|
Flaky is a plugin for py.test that automatically reruns flaky tests.
|
||||||
|
|
||||||
|
Ideally, tests reliably pass or fail, but sometimes test fixtures must rely on components that aren't 100%
|
||||||
|
reliable. With flaky, instead of removing those tests or marking them to @skip, they can be automatically
|
||||||
|
retried.
|
||||||
|
|
||||||
|
For more information about flaky, see `this presentation <http://opensource.box.com/flaky/>`_.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%autosetup -p1 -n flaky-%{version}
|
||||||
|
|
||||||
|
%if !%{with test}
|
||||||
|
%build
|
||||||
|
%python_build
|
||||||
|
%endif
|
||||||
|
|
||||||
|
%if !%{with test}
|
||||||
|
%install
|
||||||
|
%python_install
|
||||||
|
%python_expand %fdupes %{buildroot}%{$python_sitelib}
|
||||||
|
%endif
|
||||||
|
|
||||||
|
%if %{with test}
|
||||||
|
%check
|
||||||
|
%pytest -k 'example and not options' --doctest-modules test/test_pytest/
|
||||||
|
%pytest -k 'example and not options' test/test_pytest/
|
||||||
|
%pytest -p no:flaky test/test_pytest/test_flaky_pytest_plugin.py
|
||||||
|
export PYTEST_ADDOPTS="--force-flaky --max-runs 2"
|
||||||
|
%pytest test/test_pytest/test_pytest_options_example.py
|
||||||
|
%endif
|
||||||
|
|
||||||
|
%if !%{with test}
|
||||||
|
%files %{python_files}
|
||||||
|
%doc README.rst
|
||||||
|
%license LICENSE
|
||||||
|
%{python_sitelib}/*
|
||||||
|
%endif
|
||||||
|
|
||||||
|
%changelog
|
14
remove_mock.patch
Normal file
14
remove_mock.patch
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
--- a/test/test_pytest/test_flaky_pytest_plugin.py
|
||||||
|
+++ b/test/test_pytest/test_flaky_pytest_plugin.py
|
||||||
|
@@ -2,7 +2,10 @@
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from io import StringIO
|
||||||
|
-from mock import Mock, patch
|
||||||
|
+try:
|
||||||
|
+ from unittest.mock import Mock, patch
|
||||||
|
+except ImportError:
|
||||||
|
+ from mock import Mock, patch
|
||||||
|
# pylint:disable=import-error
|
||||||
|
import pytest
|
||||||
|
from _pytest.runner import CallInfo
|
913
remove_nose.patch
Normal file
913
remove_nose.patch
Normal file
@ -0,0 +1,913 @@
|
|||||||
|
--- a/test/test_nose/__init__.py
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,3 +0,0 @@
|
||||||
|
-# coding: utf-8
|
||||||
|
-
|
||||||
|
-from __future__ import unicode_literals, absolute_import
|
||||||
|
--- a/test/test_nose/test_flaky_nose_plugin.py
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,446 +0,0 @@
|
||||||
|
-# coding: utf-8
|
||||||
|
-
|
||||||
|
-from __future__ import unicode_literals
|
||||||
|
-
|
||||||
|
-from unittest import TestCase
|
||||||
|
-
|
||||||
|
-from genty import genty, genty_dataset
|
||||||
|
-import mock
|
||||||
|
-from mock import MagicMock, Mock, patch
|
||||||
|
-
|
||||||
|
-from flaky import defaults, flaky_nose_plugin
|
||||||
|
-from flaky.flaky_decorator import flaky
|
||||||
|
-from flaky.names import FlakyNames
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-@genty
|
||||||
|
-class TestFlakyNosePlugin(TestCase):
|
||||||
|
- def setUp(self):
|
||||||
|
- super(TestFlakyNosePlugin, self).setUp()
|
||||||
|
-
|
||||||
|
- self._mock_test_result = MagicMock()
|
||||||
|
- self._mock_stream = None
|
||||||
|
- self._flaky_plugin = flaky_nose_plugin.FlakyPlugin()
|
||||||
|
- self._mock_nose_result = Mock(flaky_nose_plugin.TextTestResult)
|
||||||
|
- self._flaky_plugin.prepareTestResult(self._mock_nose_result)
|
||||||
|
- self._mock_test = MagicMock(name='flaky_plugin_test')
|
||||||
|
- self._mock_test_case = MagicMock(
|
||||||
|
- name='flaky_plugin_test_case',
|
||||||
|
- spec=TestCase
|
||||||
|
- )
|
||||||
|
- self._mock_test_case.address = MagicMock()
|
||||||
|
- self._mock_test_case.test = self._mock_test
|
||||||
|
- self._mock_test_module_name = 'test_module'
|
||||||
|
- self._mock_test_class_name = 'TestClass'
|
||||||
|
- self._mock_test_method_name = 'test_method'
|
||||||
|
- self._mock_test_names = '{}:{}.{}'.format(
|
||||||
|
- self._mock_test_module_name,
|
||||||
|
- self._mock_test_class_name,
|
||||||
|
- self._mock_test_method_name
|
||||||
|
- )
|
||||||
|
- self._mock_exception = Exception('Error in {}'.format(
|
||||||
|
- self._mock_test_method_name)
|
||||||
|
- )
|
||||||
|
- self._mock_stack_trace = ''
|
||||||
|
- self._mock_exception_type = Exception
|
||||||
|
- self._mock_error = (
|
||||||
|
- self._mock_exception_type,
|
||||||
|
- self._mock_exception,
|
||||||
|
- None,
|
||||||
|
- )
|
||||||
|
- self._mock_test_method = MagicMock(
|
||||||
|
- name=self._mock_test_method_name,
|
||||||
|
- spec=['__call__'] + list(FlakyNames().items()),
|
||||||
|
- )
|
||||||
|
- setattr(
|
||||||
|
- self._mock_test,
|
||||||
|
- self._mock_test_method_name,
|
||||||
|
- self._mock_test_method,
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- def _assert_flaky_plugin_configured(self):
|
||||||
|
- options = Mock()
|
||||||
|
- options.multiprocess_workers = 0
|
||||||
|
- conf = Mock()
|
||||||
|
- self._flaky_plugin.enabled = True
|
||||||
|
- with patch.object(flaky_nose_plugin, 'TextTestResult') as flaky_result:
|
||||||
|
- flaky_result.return_value = self._mock_test_result
|
||||||
|
- from io import StringIO
|
||||||
|
- self._mock_stream = MagicMock(spec=StringIO)
|
||||||
|
- with patch.object(self._flaky_plugin, '_get_stream') as get_stream:
|
||||||
|
- get_stream.return_value = self._mock_stream
|
||||||
|
- self._flaky_plugin.configure(options, conf)
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_report(self):
|
||||||
|
- flaky_report = 'Flaky tests passed; others failed. ' \
|
||||||
|
- 'No more tests; that ship has sailed.'
|
||||||
|
- self._test_flaky_plugin_report(flaky_report)
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_success_for_test_method(self):
|
||||||
|
- self._test_flaky_plugin_handles_success()
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_success_for_test_instance(self):
|
||||||
|
- self._test_flaky_plugin_handles_success(is_test_method=False)
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_success_for_needs_rerun(self):
|
||||||
|
- self._test_flaky_plugin_handles_success(min_passes=2)
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_ignores_success_for_non_flaky_test(self):
|
||||||
|
- self._expect_test_not_flaky()
|
||||||
|
- self._flaky_plugin.addSuccess(self._mock_test_case)
|
||||||
|
- self._assert_test_ignored()
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_ignores_error_for_non_flaky_test(self):
|
||||||
|
- self._expect_test_not_flaky()
|
||||||
|
- self._flaky_plugin.handleError(self._mock_test_case, None)
|
||||||
|
- self._assert_test_ignored()
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_ignores_failure_for_non_flaky_test(self):
|
||||||
|
- self._expect_test_not_flaky()
|
||||||
|
- self._flaky_plugin.handleFailure(self._mock_test_case, None)
|
||||||
|
- self._assert_test_ignored()
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_ignores_error_for_nose_failure(self):
|
||||||
|
- self._mock_test_case.address.return_value = (
|
||||||
|
- None,
|
||||||
|
- self._mock_test_module_name,
|
||||||
|
- None,
|
||||||
|
- )
|
||||||
|
- self._flaky_plugin.handleError(self._mock_test_case, None)
|
||||||
|
- self._assert_test_ignored()
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_error_for_test_method(self):
|
||||||
|
- self._test_flaky_plugin_handles_failure_or_error()
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_error_for_test_instance(self):
|
||||||
|
- self._test_flaky_plugin_handles_failure_or_error(is_test_method=False)
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_failure_for_test_method(self):
|
||||||
|
- self._test_flaky_plugin_handles_failure_or_error(is_failure=True)
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_failure_for_test_instance(self):
|
||||||
|
- self._test_flaky_plugin_handles_failure_or_error(
|
||||||
|
- is_failure=True,
|
||||||
|
- is_test_method=False
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_failure_for_no_more_retries(self):
|
||||||
|
- self._test_flaky_plugin_handles_failure_or_error(
|
||||||
|
- is_failure=True,
|
||||||
|
- max_runs=1
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_additional_errors(self):
|
||||||
|
- self._test_flaky_plugin_handles_failure_or_error(
|
||||||
|
- current_errors=[self._mock_error]
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- def test_flaky_plugin_handles_bare_test(self):
|
||||||
|
- self._mock_test_names = self._mock_test_method_name
|
||||||
|
- self._mock_test.test = Mock()
|
||||||
|
- self._expect_call_test_address()
|
||||||
|
- attrib = defaults.default_flaky_attributes(2, 1)
|
||||||
|
- for name, value in attrib.items():
|
||||||
|
- setattr(
|
||||||
|
- self._mock_test.test,
|
||||||
|
- name,
|
||||||
|
- value,
|
||||||
|
- )
|
||||||
|
- delattr(self._mock_test, self._mock_test_method_name)
|
||||||
|
- self._flaky_plugin.prepareTestCase(self._mock_test_case)
|
||||||
|
- self.assertTrue(self._flaky_plugin.handleError(
|
||||||
|
- self._mock_test_case,
|
||||||
|
- self._mock_error,
|
||||||
|
- ))
|
||||||
|
- self.assertFalse(self._flaky_plugin.handleError(
|
||||||
|
- self._mock_test_case,
|
||||||
|
- self._mock_error,
|
||||||
|
- ))
|
||||||
|
-
|
||||||
|
- def _expect_call_test_address(self):
|
||||||
|
- self._mock_test_case.address.return_value = (
|
||||||
|
- None,
|
||||||
|
- None,
|
||||||
|
- self._mock_test_names
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- def _expect_test_flaky(self, is_test_method, max_runs, min_passes):
|
||||||
|
- self._expect_call_test_address()
|
||||||
|
- if is_test_method:
|
||||||
|
- mock_test_method = getattr(
|
||||||
|
- self._mock_test,
|
||||||
|
- self._mock_test_method_name
|
||||||
|
- )
|
||||||
|
- for flaky_attr in FlakyNames():
|
||||||
|
- setattr(self._mock_test, flaky_attr, None)
|
||||||
|
- setattr(mock_test_method, flaky_attr, None)
|
||||||
|
- flaky(max_runs, min_passes)(mock_test_method)
|
||||||
|
- else:
|
||||||
|
- flaky(max_runs, min_passes)(self._mock_test)
|
||||||
|
- mock_test_method = getattr(
|
||||||
|
- self._mock_test,
|
||||||
|
- self._mock_test_method_name
|
||||||
|
- )
|
||||||
|
- for flaky_attr in FlakyNames():
|
||||||
|
- setattr(mock_test_method, flaky_attr, None)
|
||||||
|
-
|
||||||
|
- def _expect_test_not_flaky(self):
|
||||||
|
- self._expect_call_test_address()
|
||||||
|
- for test_object in (
|
||||||
|
- self._mock_test,
|
||||||
|
- getattr(self._mock_test, self._mock_test_method_name)
|
||||||
|
- ):
|
||||||
|
- for flaky_attr in FlakyNames():
|
||||||
|
- setattr(test_object, flaky_attr, None)
|
||||||
|
-
|
||||||
|
- def _assert_test_ignored(self):
|
||||||
|
- self._mock_test_case.address.assert_called_with()
|
||||||
|
- self.assertEqual(
|
||||||
|
- self._mock_test_case.mock_calls,
|
||||||
|
- [mock.call.address()],
|
||||||
|
- )
|
||||||
|
- self.assertEqual(self._mock_test.mock_calls, [])
|
||||||
|
- self.assertEqual(self._mock_nose_result.mock_calls, [])
|
||||||
|
-
|
||||||
|
- def _get_flaky_attributes(self):
|
||||||
|
- actual_flaky_attributes = {
|
||||||
|
- attr: getattr(
|
||||||
|
- self._mock_test_case,
|
||||||
|
- attr,
|
||||||
|
- None,
|
||||||
|
- ) for attr in FlakyNames()
|
||||||
|
- }
|
||||||
|
- for key, value in actual_flaky_attributes.items():
|
||||||
|
- if isinstance(value, list):
|
||||||
|
- actual_flaky_attributes[key] = tuple(value)
|
||||||
|
- return actual_flaky_attributes
|
||||||
|
-
|
||||||
|
- def _set_flaky_attribute(self, attr, value):
|
||||||
|
- setattr(self._mock_test, attr, value)
|
||||||
|
-
|
||||||
|
- def _assert_flaky_attributes_contains(
|
||||||
|
- self,
|
||||||
|
- expected_flaky_attributes,
|
||||||
|
- ):
|
||||||
|
- actual_flaky_attributes = self._get_flaky_attributes()
|
||||||
|
- self.assertDictContainsSubset(
|
||||||
|
- expected_flaky_attributes,
|
||||||
|
- actual_flaky_attributes,
|
||||||
|
- 'Unexpected flaky attributes. Expected {} got {}'.format(
|
||||||
|
- expected_flaky_attributes,
|
||||||
|
- actual_flaky_attributes
|
||||||
|
- )
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- def _test_flaky_plugin_handles_failure_or_error(
|
||||||
|
- self,
|
||||||
|
- current_errors=None,
|
||||||
|
- current_passes=0,
|
||||||
|
- current_runs=0,
|
||||||
|
- is_failure=False,
|
||||||
|
- is_test_method=True,
|
||||||
|
- max_runs=2,
|
||||||
|
- min_passes=1,
|
||||||
|
- ):
|
||||||
|
- self._assert_flaky_plugin_configured()
|
||||||
|
- self._expect_test_flaky(is_test_method, max_runs, min_passes)
|
||||||
|
- if current_errors is None:
|
||||||
|
- current_errors = [self._mock_error]
|
||||||
|
- else:
|
||||||
|
- current_errors.append(self._mock_error)
|
||||||
|
- self._set_flaky_attribute(
|
||||||
|
- FlakyNames.CURRENT_ERRORS,
|
||||||
|
- current_errors,
|
||||||
|
- )
|
||||||
|
- self._set_flaky_attribute(
|
||||||
|
- FlakyNames.CURRENT_PASSES,
|
||||||
|
- current_passes,
|
||||||
|
- )
|
||||||
|
- self._set_flaky_attribute(
|
||||||
|
- FlakyNames.CURRENT_RUNS,
|
||||||
|
- current_runs,
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- retries_remaining = current_runs + 1 < max_runs
|
||||||
|
- too_few_passes = current_passes < min_passes
|
||||||
|
- expected_plugin_handles_failure = too_few_passes and retries_remaining
|
||||||
|
- did_plugin_retry_test = max_runs > 1
|
||||||
|
-
|
||||||
|
- self._flaky_plugin.prepareTestCase(self._mock_test_case)
|
||||||
|
- if is_failure:
|
||||||
|
- actual_plugin_handles_failure = self._flaky_plugin.handleFailure(
|
||||||
|
- self._mock_test_case,
|
||||||
|
- self._mock_error,
|
||||||
|
- )
|
||||||
|
- else:
|
||||||
|
- actual_plugin_handles_failure = self._flaky_plugin.handleError(
|
||||||
|
- self._mock_test_case,
|
||||||
|
- self._mock_error,
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- self.assertEqual(
|
||||||
|
- expected_plugin_handles_failure or None,
|
||||||
|
- actual_plugin_handles_failure,
|
||||||
|
- 'Expected plugin{} to handle the test run, but it did{}.'.format(
|
||||||
|
- ' to' if expected_plugin_handles_failure else '',
|
||||||
|
- '' if actual_plugin_handles_failure else ' not'
|
||||||
|
- ),
|
||||||
|
- )
|
||||||
|
- self._assert_flaky_attributes_contains(
|
||||||
|
- {
|
||||||
|
- FlakyNames.CURRENT_RUNS: current_runs + 1,
|
||||||
|
- FlakyNames.CURRENT_ERRORS: tuple(current_errors),
|
||||||
|
- },
|
||||||
|
- )
|
||||||
|
- expected_test_case_calls = [mock.call.address(), mock.call.address()]
|
||||||
|
- expected_result_calls = []
|
||||||
|
- if expected_plugin_handles_failure:
|
||||||
|
- expected_test_case_calls.append(('__hash__',))
|
||||||
|
- expected_stream_calls = [mock.call.writelines([
|
||||||
|
- self._mock_test_method_name,
|
||||||
|
- ' failed ({} runs remaining out of {}).'.format(
|
||||||
|
- max_runs - current_runs - 1, max_runs
|
||||||
|
- ),
|
||||||
|
- 'Exception: Error in test_method',
|
||||||
|
- '\n',
|
||||||
|
- ])]
|
||||||
|
- else:
|
||||||
|
- if did_plugin_retry_test:
|
||||||
|
- if is_failure:
|
||||||
|
- expected_result_calls.append(
|
||||||
|
- mock.call.addFailure(
|
||||||
|
- self._mock_test_case,
|
||||||
|
- self._mock_error,
|
||||||
|
- ),
|
||||||
|
- )
|
||||||
|
- else:
|
||||||
|
- expected_result_calls.append(mock.call.addError(
|
||||||
|
- self._mock_test_case,
|
||||||
|
- self._mock_error,
|
||||||
|
- ))
|
||||||
|
- expected_stream_calls = [mock.call.writelines([
|
||||||
|
- self._mock_test_method_name,
|
||||||
|
- ' failed; it passed {} out of the required {} times.'.format(
|
||||||
|
- current_passes,
|
||||||
|
- min_passes
|
||||||
|
- ),
|
||||||
|
- 'Exception: Error in test_method',
|
||||||
|
- '\n'
|
||||||
|
- ])]
|
||||||
|
- self.assertEqual(
|
||||||
|
- self._mock_nose_result.mock_calls,
|
||||||
|
- expected_result_calls,
|
||||||
|
- )
|
||||||
|
- self.assertEqual(
|
||||||
|
- self._mock_test_case.mock_calls,
|
||||||
|
- expected_test_case_calls,
|
||||||
|
- 'Unexpected TestCase calls: {} vs {}'.format(
|
||||||
|
- self._mock_test_case.mock_calls,
|
||||||
|
- expected_test_case_calls
|
||||||
|
- )
|
||||||
|
- )
|
||||||
|
- self.assertEqual(self._mock_stream.mock_calls, expected_stream_calls)
|
||||||
|
-
|
||||||
|
- def _test_flaky_plugin_handles_success(
|
||||||
|
- self,
|
||||||
|
- current_passes=0,
|
||||||
|
- current_runs=0,
|
||||||
|
- is_test_method=True,
|
||||||
|
- max_runs=2,
|
||||||
|
- min_passes=1
|
||||||
|
- ):
|
||||||
|
- self._assert_flaky_plugin_configured()
|
||||||
|
- self._expect_test_flaky(is_test_method, max_runs, min_passes)
|
||||||
|
- self._set_flaky_attribute(
|
||||||
|
- FlakyNames.CURRENT_PASSES,
|
||||||
|
- current_passes,
|
||||||
|
- )
|
||||||
|
- self._set_flaky_attribute(
|
||||||
|
- FlakyNames.CURRENT_RUNS,
|
||||||
|
- current_runs,
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- retries_remaining = current_runs + 1 < max_runs
|
||||||
|
- too_few_passes = current_passes + 1 < min_passes
|
||||||
|
- expected_plugin_handles_success = too_few_passes and retries_remaining
|
||||||
|
-
|
||||||
|
- self._flaky_plugin.prepareTestCase(self._mock_test_case)
|
||||||
|
- actual_plugin_handles_success = self._flaky_plugin.addSuccess(
|
||||||
|
- self._mock_test_case,
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- self.assertEqual(
|
||||||
|
- expected_plugin_handles_success or None,
|
||||||
|
- actual_plugin_handles_success,
|
||||||
|
- 'Expected plugin{} to handle the test run, but it did{}.'.format(
|
||||||
|
- ' not' if expected_plugin_handles_success else '',
|
||||||
|
- '' if actual_plugin_handles_success else ' not'
|
||||||
|
- ),
|
||||||
|
- )
|
||||||
|
- self._assert_flaky_attributes_contains(
|
||||||
|
- {
|
||||||
|
- FlakyNames.CURRENT_RUNS: current_runs + 1,
|
||||||
|
- FlakyNames.CURRENT_PASSES: current_passes + 1,
|
||||||
|
- },
|
||||||
|
- )
|
||||||
|
- expected_test_case_calls = [mock.call.address(), mock.call.address()]
|
||||||
|
- expected_stream_calls = [mock.call.writelines([
|
||||||
|
- self._mock_test_method_name,
|
||||||
|
- " passed {} out of the required {} times. ".format(
|
||||||
|
- current_passes + 1,
|
||||||
|
- min_passes,
|
||||||
|
- ),
|
||||||
|
- ])]
|
||||||
|
- if expected_plugin_handles_success:
|
||||||
|
- _rerun_text = 'Running test again until it passes {0} times.\n'
|
||||||
|
- expected_test_case_calls.append(('__hash__',))
|
||||||
|
- expected_stream_calls.append(
|
||||||
|
- mock.call.write(_rerun_text.format(min_passes)),
|
||||||
|
- )
|
||||||
|
- else:
|
||||||
|
- expected_stream_calls.append(mock.call.write('Success!\n'))
|
||||||
|
- self.assertEqual(
|
||||||
|
- self._mock_test_case.mock_calls,
|
||||||
|
- expected_test_case_calls,
|
||||||
|
- 'Unexpected TestCase calls = {} vs {}'.format(
|
||||||
|
- self._mock_test_case.mock_calls,
|
||||||
|
- expected_test_case_calls,
|
||||||
|
- ),
|
||||||
|
- )
|
||||||
|
- self.assertEqual(self._mock_stream.mock_calls, expected_stream_calls)
|
||||||
|
-
|
||||||
|
- def _test_flaky_plugin_report(self, expected_stream_value):
|
||||||
|
- self._assert_flaky_plugin_configured()
|
||||||
|
- mock_stream = Mock()
|
||||||
|
- self._mock_stream.getvalue.return_value = expected_stream_value
|
||||||
|
-
|
||||||
|
- self._flaky_plugin.report(mock_stream)
|
||||||
|
-
|
||||||
|
- self.assertEqual(
|
||||||
|
- mock_stream.mock_calls,
|
||||||
|
- [
|
||||||
|
- mock.call.write('===Flaky Test Report===\n\n'),
|
||||||
|
- mock.call.write(expected_stream_value),
|
||||||
|
- mock.call.write('\n===End Flaky Test Report===\n'),
|
||||||
|
- ],
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- @genty_dataset(
|
||||||
|
- multiprocess_plugin_absent=(None, 'StringIO'),
|
||||||
|
- processes_argument_absent=(0, 'StringIO'),
|
||||||
|
- processes_equals_one=(1, 'MultiprocessingStringIO'),
|
||||||
|
- processes_equals_two=(2, 'MultiprocessingStringIO'),
|
||||||
|
- )
|
||||||
|
- def test_flaky_plugin_get_stream(self, mp_workers, expected_class_name):
|
||||||
|
- options = Mock()
|
||||||
|
- conf = Mock()
|
||||||
|
- self._flaky_plugin.enabled = True
|
||||||
|
- options.multiprocess_workers = mp_workers
|
||||||
|
- if mp_workers is None:
|
||||||
|
- del options.multiprocess_workers
|
||||||
|
- self._flaky_plugin.configure(options, conf)
|
||||||
|
- # pylint:disable=protected-access
|
||||||
|
- self.assertEqual(
|
||||||
|
- self._flaky_plugin._stream.__class__.__name__,
|
||||||
|
- expected_class_name,
|
||||||
|
- )
|
||||||
|
--- a/test/test_nose/test_nose_example.py
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,98 +0,0 @@
|
||||||
|
-# coding: utf-8
|
||||||
|
-
|
||||||
|
-from __future__ import unicode_literals
|
||||||
|
-
|
||||||
|
-from unittest import TestCase, skip
|
||||||
|
-
|
||||||
|
-from genty import genty, genty_dataset
|
||||||
|
-from nose.tools import raises
|
||||||
|
-
|
||||||
|
-from flaky import flaky
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-# This is an end-to-end example of the flaky package in action. Consider it
|
||||||
|
-# a live tutorial, showing the various features in action.
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-class ExampleTests(TestCase):
|
||||||
|
- _threshold = -1
|
||||||
|
-
|
||||||
|
- def test_non_flaky_thing(self):
|
||||||
|
- """Flaky will not interact with this test"""
|
||||||
|
-
|
||||||
|
- @raises(AssertionError)
|
||||||
|
- def test_non_flaky_failing_thing(self):
|
||||||
|
- """Flaky will also not interact with this test"""
|
||||||
|
- self.assertEqual(0, 1)
|
||||||
|
-
|
||||||
|
- @flaky(3, 2)
|
||||||
|
- def test_flaky_thing_that_fails_then_succeeds(self):
|
||||||
|
- """
|
||||||
|
- Flaky will run this test 3 times. It will fail once and then succeed twice.
|
||||||
|
- """
|
||||||
|
- self._threshold += 1
|
||||||
|
- if self._threshold < 1:
|
||||||
|
- raise Exception("Threshold is not high enough: {} vs {}.".format(
|
||||||
|
- self._threshold, 1),
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
- @flaky(3, 2)
|
||||||
|
- def test_flaky_thing_that_succeeds_then_fails_then_succeeds(self):
|
||||||
|
- """
|
||||||
|
- Flaky will run this test 3 times. It will succeed once, fail once, and then succeed one more time.
|
||||||
|
- """
|
||||||
|
- self._threshold += 1
|
||||||
|
- if self._threshold == 1:
|
||||||
|
- self.assertEqual(0, 1)
|
||||||
|
-
|
||||||
|
- @flaky(2, 2)
|
||||||
|
- def test_flaky_thing_that_always_passes(self):
|
||||||
|
- """Flaky will run this test twice. Both will succeed."""
|
||||||
|
-
|
||||||
|
- @skip("This really fails! Remove this decorator to see the test failure.")
|
||||||
|
- @flaky()
|
||||||
|
- def test_flaky_thing_that_always_fails(self):
|
||||||
|
- """Flaky will run this test twice. Both will fail."""
|
||||||
|
- self.assertEqual(0, 1)
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-@flaky
|
||||||
|
-class ExampleFlakyTests(TestCase):
|
||||||
|
- _threshold = -1
|
||||||
|
-
|
||||||
|
- def test_flaky_thing_that_fails_then_succeeds(self):
|
||||||
|
- """
|
||||||
|
- Flaky will run this test twice. It will fail once and then succeed.
|
||||||
|
- """
|
||||||
|
- self._threshold += 1
|
||||||
|
- if self._threshold < 1:
|
||||||
|
- raise Exception("Threshold is not high enough: {} vs {}.".format(
|
||||||
|
- self._threshold, 1),
|
||||||
|
- )
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-def test_function():
|
||||||
|
- """
|
||||||
|
- Nose will import this function and wrap it in a :class:`FunctionTestCase`.
|
||||||
|
- It's included in the example to make sure flaky handles it correctly.
|
||||||
|
- """
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-@flaky
|
||||||
|
-def test_flaky_function(param=[]):
|
||||||
|
- # pylint:disable=dangerous-default-value
|
||||||
|
- param_length = len(param)
|
||||||
|
- param.append(None)
|
||||||
|
- assert param_length == 1
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-@genty
|
||||||
|
-class ExampleFlakyTestsWithUnicodeTestNames(ExampleFlakyTests):
|
||||||
|
- @genty_dataset('ascii name', 'ńőń ȁŝćȉȉ ŝƭȕƒƒ')
|
||||||
|
- def test_non_flaky_thing(self, message):
|
||||||
|
- self._threshold += 1
|
||||||
|
- if self._threshold < 1:
|
||||||
|
- raise Exception(
|
||||||
|
- "Threshold is not high enough: {} vs {} for '{}'.".format(
|
||||||
|
- self._threshold, 1, message),
|
||||||
|
- )
|
||||||
|
--- a/test/test_nose/test_nose_options_example.py
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,54 +0,0 @@
|
||||||
|
-# coding: utf-8
|
||||||
|
-
|
||||||
|
-from __future__ import unicode_literals
|
||||||
|
-
|
||||||
|
-from unittest import TestCase
|
||||||
|
-
|
||||||
|
-from flaky import flaky
|
||||||
|
-
|
||||||
|
-# This is a series of tests that do not use the flaky decorator; the flaky
|
||||||
|
-# behavior is intended to be enabled with the --force-flaky option on the
|
||||||
|
-# command line.
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-class ExampleTests(TestCase):
|
||||||
|
- _threshold = -2
|
||||||
|
-
|
||||||
|
- def test_something_flaky(self):
|
||||||
|
- """
|
||||||
|
- Flaky will run this test twice.
|
||||||
|
- It will fail once and then succeed once.
|
||||||
|
- This ensures that we mark tests as flaky even if they don't have a
|
||||||
|
- decorator when we use the command-line options.
|
||||||
|
- """
|
||||||
|
- self._threshold += 1
|
||||||
|
- if self._threshold < 0:
|
||||||
|
- raise Exception("Threshold is not high enough.")
|
||||||
|
-
|
||||||
|
- @flaky(3, 1)
|
||||||
|
- def test_flaky_thing_that_fails_then_succeeds(self):
|
||||||
|
- """
|
||||||
|
- Flaky will run this test 3 times.
|
||||||
|
- It will fail twice and then succeed once.
|
||||||
|
- This ensures that the flaky decorator overrides any command-line
|
||||||
|
- options we specify.
|
||||||
|
- """
|
||||||
|
- self._threshold += 1
|
||||||
|
- if self._threshold < 1:
|
||||||
|
- raise Exception("Threshold is not high enough.")
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-@flaky(3, 1)
|
||||||
|
-class ExampleFlakyTests(TestCase):
|
||||||
|
- _threshold = -1
|
||||||
|
-
|
||||||
|
- def test_flaky_thing_that_fails_then_succeeds(self):
|
||||||
|
- """
|
||||||
|
- Flaky will run this test 3 times.
|
||||||
|
- It will fail twice and then succeed once.
|
||||||
|
- This ensures that the flaky decorator on a test suite overrides any
|
||||||
|
- command-line options we specify.
|
||||||
|
- """
|
||||||
|
- self._threshold += 1
|
||||||
|
- if self._threshold < 1:
|
||||||
|
- raise Exception("Threshold is not high enough.")
|
||||||
|
--- a/setup.py
|
||||||
|
+++ b/setup.py
|
||||||
|
@@ -68,9 +68,6 @@ def main():
|
||||||
|
cmdclass={'test': Tox},
|
||||||
|
zip_safe=False,
|
||||||
|
entry_points={
|
||||||
|
- 'nose.plugins.0.10': [
|
||||||
|
- 'flaky = flaky.flaky_nose_plugin:FlakyPlugin'
|
||||||
|
- ],
|
||||||
|
'pytest11': [
|
||||||
|
'flaky = flaky.flaky_pytest_plugin'
|
||||||
|
]
|
||||||
|
--- a/flaky/flaky_nose_plugin.py
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,285 +0,0 @@
|
||||||
|
-# coding: utf-8
|
||||||
|
-
|
||||||
|
-from __future__ import unicode_literals
|
||||||
|
-
|
||||||
|
-import logging
|
||||||
|
-from optparse import OptionGroup
|
||||||
|
-import os
|
||||||
|
-
|
||||||
|
-from nose.failure import Failure
|
||||||
|
-from nose.plugins import Plugin
|
||||||
|
-from nose.result import TextTestResult
|
||||||
|
-
|
||||||
|
-from flaky._flaky_plugin import _FlakyPlugin
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-class FlakyPlugin(_FlakyPlugin, Plugin):
|
||||||
|
- """
|
||||||
|
- Plugin for nosetests that allows retrying flaky tests.
|
||||||
|
- """
|
||||||
|
- name = 'flaky'
|
||||||
|
-
|
||||||
|
- def __init__(self):
|
||||||
|
- super(FlakyPlugin, self).__init__()
|
||||||
|
- self._logger = logging.getLogger('nose.plugins.flaky')
|
||||||
|
- self._flaky_result = None
|
||||||
|
- self._nose_result = None
|
||||||
|
- self._flaky_report = True
|
||||||
|
- self._force_flaky = False
|
||||||
|
- self._max_runs = None
|
||||||
|
- self._min_passes = None
|
||||||
|
- self._test_status = {}
|
||||||
|
- self._tests_that_reran = set()
|
||||||
|
- self._tests_that_have_been_reported = set()
|
||||||
|
-
|
||||||
|
- def options(self, parser, env=os.environ):
|
||||||
|
- """
|
||||||
|
- Base class override.
|
||||||
|
- Add options to the nose argument parser.
|
||||||
|
- """
|
||||||
|
- # pylint:disable=dangerous-default-value
|
||||||
|
- super(FlakyPlugin, self).options(parser, env=env)
|
||||||
|
- self.add_report_option(parser.add_option)
|
||||||
|
- group = OptionGroup(
|
||||||
|
- parser, "Force flaky", "Force all tests to be flaky.")
|
||||||
|
- self.add_force_flaky_options(group.add_option)
|
||||||
|
- parser.add_option_group(group)
|
||||||
|
-
|
||||||
|
- def _get_stream(self, multiprocess=False):
|
||||||
|
- """
|
||||||
|
- Get the stream used to store the flaky report.
|
||||||
|
- If this nose run is going to use the multiprocess plugin, then use
|
||||||
|
- a multiprocess-list backed StringIO proxy; otherwise, use the default
|
||||||
|
- stream.
|
||||||
|
-
|
||||||
|
- :param multiprocess:
|
||||||
|
- Whether or not this test run is configured for multiprocessing.
|
||||||
|
- :type multiprocess:
|
||||||
|
- `bool`
|
||||||
|
- :return:
|
||||||
|
- The stream to use for storing the flaky report.
|
||||||
|
- :rtype:
|
||||||
|
- :class:`StringIO` or :class:`MultiprocessingStringIO`
|
||||||
|
- """
|
||||||
|
- if multiprocess:
|
||||||
|
- from flaky.multiprocess_string_io import MultiprocessingStringIO
|
||||||
|
- return MultiprocessingStringIO()
|
||||||
|
- return self._stream
|
||||||
|
-
|
||||||
|
- def configure(self, options, conf):
|
||||||
|
- """Base class override."""
|
||||||
|
- super(FlakyPlugin, self).configure(options, conf)
|
||||||
|
- if not self.enabled:
|
||||||
|
- return
|
||||||
|
- is_multiprocess = int(getattr(options, 'multiprocess_workers', 0)) > 0
|
||||||
|
- self._stream = self._get_stream(is_multiprocess)
|
||||||
|
- self._flaky_result = TextTestResult(self._stream, [], 0)
|
||||||
|
- self._flaky_report = options.flaky_report
|
||||||
|
- self._flaky_success_report = options.flaky_success_report
|
||||||
|
- self._force_flaky = options.force_flaky
|
||||||
|
- self._max_runs = options.max_runs
|
||||||
|
- self._min_passes = options.min_passes
|
||||||
|
-
|
||||||
|
- def startTest(self, test):
|
||||||
|
- """
|
||||||
|
- Base class override. Called before a test is run.
|
||||||
|
-
|
||||||
|
- Add the test to the test status tracker, so it can potentially
|
||||||
|
- be rerun during afterTest.
|
||||||
|
-
|
||||||
|
- :param test:
|
||||||
|
- The test that is going to be run.
|
||||||
|
- :type test:
|
||||||
|
- :class:`nose.case.Test`
|
||||||
|
- """
|
||||||
|
- # pylint:disable=invalid-name
|
||||||
|
- self._test_status[test] = None
|
||||||
|
-
|
||||||
|
- def afterTest(self, test):
|
||||||
|
- """
|
||||||
|
- Base class override. Called after a test is run.
|
||||||
|
-
|
||||||
|
- If the test was marked for rerun, rerun the test.
|
||||||
|
-
|
||||||
|
- :param test:
|
||||||
|
- The test that has been run.
|
||||||
|
- :type test:
|
||||||
|
- :class:`nose.case.Test`
|
||||||
|
- """
|
||||||
|
- # pylint:disable=invalid-name
|
||||||
|
- if self._test_status[test]:
|
||||||
|
- self._tests_that_reran.add(id(test))
|
||||||
|
- test.run(self._flaky_result)
|
||||||
|
- self._test_status.pop(test, None)
|
||||||
|
-
|
||||||
|
- def _mark_test_for_rerun(self, test):
|
||||||
|
- """
|
||||||
|
- Base class override. Rerun a flaky test.
|
||||||
|
-
|
||||||
|
- In this case, don't actually rerun the test, but mark it for
|
||||||
|
- rerun during afterTest.
|
||||||
|
-
|
||||||
|
- :param test:
|
||||||
|
- The test that is going to be rerun.
|
||||||
|
- :type test:
|
||||||
|
- :class:`nose.case.Test`
|
||||||
|
- """
|
||||||
|
- self._test_status[test] = True
|
||||||
|
-
|
||||||
|
- def handleError(self, test, err):
|
||||||
|
- """
|
||||||
|
- Baseclass override. Called when a test raises an exception.
|
||||||
|
-
|
||||||
|
- If the test isn't going to be rerun again, then report the error
|
||||||
|
- to the nose test result.
|
||||||
|
-
|
||||||
|
- :param test:
|
||||||
|
- The test that has raised an error
|
||||||
|
- :type test:
|
||||||
|
- :class:`nose.case.Test`
|
||||||
|
- :param err:
|
||||||
|
- Information about the test failure (from sys.exc_info())
|
||||||
|
- :type err:
|
||||||
|
- `tuple` of `class`, :class:`Exception`, `traceback`
|
||||||
|
- :return:
|
||||||
|
- True, if the test will be rerun; False, if nose should handle it.
|
||||||
|
- :rtype:
|
||||||
|
- `bool`
|
||||||
|
- """
|
||||||
|
- # pylint:disable=invalid-name
|
||||||
|
- want_error = self._handle_test_error_or_failure(test, err)
|
||||||
|
- if not want_error and id(test) in self._tests_that_reran:
|
||||||
|
- self._nose_result.addError(test, err)
|
||||||
|
- return want_error or None
|
||||||
|
-
|
||||||
|
- def handleFailure(self, test, err):
|
||||||
|
- """
|
||||||
|
- Baseclass override. Called when a test fails.
|
||||||
|
-
|
||||||
|
- If the test isn't going to be rerun again, then report the failure
|
||||||
|
- to the nose test result.
|
||||||
|
-
|
||||||
|
- :param test:
|
||||||
|
- The test that has raised an error
|
||||||
|
- :type test:
|
||||||
|
- :class:`nose.case.Test`
|
||||||
|
- :param err:
|
||||||
|
- Information about the test failure (from sys.exc_info())
|
||||||
|
- :type err:
|
||||||
|
- `tuple` of `class`, :class:`Exception`, `traceback`
|
||||||
|
- :return:
|
||||||
|
- True, if the test will be rerun; False, if nose should handle it.
|
||||||
|
- :rtype:
|
||||||
|
- `bool`
|
||||||
|
- """
|
||||||
|
- # pylint:disable=invalid-name
|
||||||
|
- want_failure = self._handle_test_error_or_failure(test, err)
|
||||||
|
- if not want_failure and id(test) in self._tests_that_reran:
|
||||||
|
- self._nose_result.addFailure(test, err)
|
||||||
|
- return want_failure or None
|
||||||
|
-
|
||||||
|
- def addSuccess(self, test):
|
||||||
|
- """
|
||||||
|
- Baseclass override. Called when a test succeeds.
|
||||||
|
-
|
||||||
|
- Count remaining retries and compare with number of required successes
|
||||||
|
- that have not yet been achieved; retry if necessary.
|
||||||
|
-
|
||||||
|
- Returning True from this method keeps the test runner from reporting
|
||||||
|
- the test as a success; this way we can retry and only report as a
|
||||||
|
- success if we have achieved the required number of successes.
|
||||||
|
-
|
||||||
|
- :param test:
|
||||||
|
- The test that has succeeded
|
||||||
|
- :type test:
|
||||||
|
- :class:`nose.case.Test`
|
||||||
|
- :return:
|
||||||
|
- True, if the test will be rerun; False, if nose should handle it.
|
||||||
|
- :rtype:
|
||||||
|
- `bool`
|
||||||
|
- """
|
||||||
|
- # pylint:disable=invalid-name
|
||||||
|
- will_handle = self._handle_test_success(test)
|
||||||
|
- test_id = id(test)
|
||||||
|
- # If this isn't a rerun, the builtin reporter is going to report it as a success
|
||||||
|
- if will_handle and test_id not in self._tests_that_reran:
|
||||||
|
- self._tests_that_have_been_reported.add(test_id)
|
||||||
|
- # If this test hasn't already been reported as successful, then do it now
|
||||||
|
- if not will_handle and test_id in self._tests_that_reran and test_id not in self._tests_that_have_been_reported:
|
||||||
|
- self._nose_result.addSuccess(test)
|
||||||
|
- return will_handle or None
|
||||||
|
-
|
||||||
|
- def report(self, stream):
|
||||||
|
- """
|
||||||
|
- Baseclass override. Write details about flaky tests to the test report.
|
||||||
|
-
|
||||||
|
- :param stream:
|
||||||
|
- The test stream to which the report can be written.
|
||||||
|
- :type stream:
|
||||||
|
- `file`
|
||||||
|
- """
|
||||||
|
- if self._flaky_report:
|
||||||
|
- self._add_flaky_report(stream)
|
||||||
|
-
|
||||||
|
- def prepareTestResult(self, result):
|
||||||
|
- """
|
||||||
|
- Baseclass override. Called right before the first test is run.
|
||||||
|
-
|
||||||
|
- Stores the test result so that errors and failures can be reported
|
||||||
|
- to the nose test result.
|
||||||
|
-
|
||||||
|
- :param result:
|
||||||
|
- The nose test result that needs to be informed of test failures.
|
||||||
|
- :type result:
|
||||||
|
- :class:`nose.result.TextTestResult`
|
||||||
|
- """
|
||||||
|
- # pylint:disable=invalid-name
|
||||||
|
- self._nose_result = result
|
||||||
|
-
|
||||||
|
- def prepareTestCase(self, test):
|
||||||
|
- """
|
||||||
|
- Baseclass override. Called right before a test case is run.
|
||||||
|
-
|
||||||
|
- If the test class is marked flaky and the test callable is not, copy
|
||||||
|
- the flaky attributes from the test class to the test callable.
|
||||||
|
-
|
||||||
|
- :param test:
|
||||||
|
- The test that is being prepared to run
|
||||||
|
- :type test:
|
||||||
|
- :class:`nose.case.Test`
|
||||||
|
- """
|
||||||
|
- # pylint:disable=invalid-name
|
||||||
|
- if not isinstance(test.test, Failure):
|
||||||
|
- test_class = test.test
|
||||||
|
- self._copy_flaky_attributes(test, test_class)
|
||||||
|
- if self._force_flaky and not self._has_flaky_attributes(test):
|
||||||
|
- self._make_test_flaky(
|
||||||
|
- test, self._max_runs, self._min_passes)
|
||||||
|
-
|
||||||
|
- @staticmethod
|
||||||
|
- def _get_test_callable_name(test):
|
||||||
|
- """
|
||||||
|
- Base class override.
|
||||||
|
- """
|
||||||
|
- _, _, class_and_callable_name = test.address()
|
||||||
|
- first_dot_index = class_and_callable_name.find('.')
|
||||||
|
- test_callable_name = class_and_callable_name[first_dot_index + 1:]
|
||||||
|
- return test_callable_name
|
||||||
|
-
|
||||||
|
- @classmethod
|
||||||
|
- def _get_test_callable(cls, test):
|
||||||
|
- """
|
||||||
|
- Base class override.
|
||||||
|
-
|
||||||
|
- :param test:
|
||||||
|
- The test that has raised an error or succeeded
|
||||||
|
- :type test:
|
||||||
|
- :class:`nose.case.Test`
|
||||||
|
- """
|
||||||
|
- callable_name = cls._get_test_callable_name(test)
|
||||||
|
- test_callable = getattr(
|
||||||
|
- test.test,
|
||||||
|
- callable_name,
|
||||||
|
- getattr(test.test, 'test', test.test),
|
||||||
|
- )
|
||||||
|
- return test_callable
|
Loading…
Reference in New Issue
Block a user