|
95 | 95 | SKIP_PLATFORM = 'platform' |
96 | 96 | SKIP_ANY = 'any' |
97 | 97 |
|
| 98 | + |
| 99 | +# Error tracker to stop suite after X consecutive ERRORs |
| 100 | +class ErrorTracker: |
| 101 | + consecutive_errors = 0 |
| 102 | + threshold_reached = False |
| 103 | + |
| 104 | +tracker = ErrorTracker() |
| 105 | + |
98 | 106 | @pytest.fixture(scope='session', autouse=True) |
99 | 107 | def log_session_context(record_testsuite_property): |
100 | 108 | """Autoused session fixture that records `version`,`architecture` and `mode` |
@@ -172,6 +180,19 @@ def pytest_addoption(parser, pluginmanager): |
172 | 180 | grp.addoption('--extend-xml', action='store_true', default=False, help="Extend XML JUnit report with additional information") |
173 | 181 | grp.addoption('--install-terminal', action='store_true', default=False, help="Use our own terminal reporter") |
174 | 182 | grp.addoption('--start-time', action='store_true', dest="start_time_info", default=False, help="Show tests start time info") |
| 183 | + parser.addoption("--max-errors", action="store", default=5, type=int, |
| 184 | + help="Number of consecutive errors before skipping. Set to 0 to disable.") |
| 185 | + |
| 186 | +@pytest.hookimpl(hookwrapper=True) |
| 187 | +def pytest_runtest_call(item): |
| 188 | + """ |
| 189 | + This wraps the actual test execution. If the threshold was hit by a |
| 190 | + previous test, we skip this one immediately. |
| 191 | + """ |
| 192 | + threshold = item.config.getoption("--max-errors") |
| 193 | + if threshold > 0 and tracker.threshold_reached: |
| 194 | + pytest.skip(f"Skipping: {threshold} consecutive failures reached.") |
| 195 | + yield # Run the actual test if threshold not reached |
175 | 196 |
|
176 | 197 | def pytest_report_header(config): |
177 | 198 | """Returns plugin-specific test session header. |
@@ -332,6 +353,18 @@ def pytest_runtest_makereport(item, call): |
332 | 353 |
|
333 | 354 | .. seealso:: `pytest documentation <_pytest.hookspec.pytest_runtest_makereport>` for details. |
334 | 355 | """ |
| 356 | + threshold = item.config.getoption("--max-errors") |
| 357 | + if threshold > 0: |
| 358 | + if call.excinfo is not None: |
| 359 | + # Check if the test resulted in an 'ERROR' (setup/teardown) or 'FAIL' |
| 360 | + # In pytest, a DB connection crash often happens in a fixture (ERROR) |
| 361 | + tracker.consecutive_errors += 1 |
| 362 | + tracker.threshold_reached = tracker.consecutive_errors >= threshold |
| 363 | + # We only care about the actual execution (call), not setup/teardown |
| 364 | + elif call.when == "call": |
| 365 | + # If a test passes, reset the streak |
| 366 | + tracker.consecutive_errors = 0 |
| 367 | + # |
335 | 368 | result = pytest.TestReport.from_item_and_call(item, call) |
336 | 369 | for attr in dir(item): |
337 | 370 | if attr.startswith('_qa_'): |
|
0 commit comments