|
1 | | -"""Test infrastructure shared across all test modules. |
2 | | -
|
3 | | -After #273 the paginated ``waterdata`` getters surface mid-walk HTTP |
4 | | -errors (429 / 5xx / connection drops) to the caller instead of silently |
5 | | -truncating the result. That's the correct behavior for users — but it |
6 | | -makes any CI test that hits the live USGS Water Data API susceptible to |
7 | | -flaking on a transient upstream blip (e.g. the 502 Bad Gateway that |
8 | | -broke CI on #273's merge to main). |
9 | | -
|
10 | | -This file: |
11 | | -
|
12 | | -* registers a ``live`` pytest marker; |
13 | | -* auto-applies it to every test that does **not** take ``requests_mock`` |
14 | | - as a fixture — the existing convention in this repo for mock-driven |
15 | | - tests, so the marker tracks "this test hits the network" without |
16 | | - needing to decorate 35 functions by hand; |
17 | | -* via ``pytest-rerunfailures``, configures live-marked tests to retry |
18 | | - up to twice (5-second backoff) ONLY when the failure trace matches a |
19 | | - transient-upstream pattern: ``429:`` / ``5xx:`` prefixes that |
20 | | - ``_raise_for_non_200`` produces, plus ``ConnectionError`` / timeout |
21 | | - shapes from the ``requests`` library. |
22 | | -
|
23 | | -Library bugs that raise unrelated exception types are NOT retried — |
24 | | -the regex set deliberately omits generic ``RuntimeError`` matches. |
| 1 | +"""Auto-retry live-API tests on transient upstream errors. |
| 2 | +
|
| 3 | +After PR #273, paginated ``waterdata`` getters propagate mid-walk HTTP |
| 4 | +errors (429 / 5xx / connection drops) instead of silently truncating the |
| 5 | +result. That's the correct behavior for users but makes any CI test that |
| 6 | +hits the live USGS Water Data API susceptible to flaking on a transient |
| 7 | +upstream blip — e.g. the HTTP 502 Bad Gateway that broke CI on PR #273's |
| 8 | +merge to main. |
| 9 | +
|
| 10 | +Heuristic: any test that does NOT request the ``requests_mock`` fixture |
| 11 | +is treated as live and gets retried on transient-error patterns only. |
| 12 | +Library bugs raising other exception types still fail on the first try. |
25 | 13 | """ |
26 | 14 |
|
27 | 15 | import pytest |
28 | 16 |
|
29 | | -# Match anywhere in the failure traceback. Anchor-free because the |
30 | | -# trace embeds the exception message after a long preamble. |
| 17 | +# Anchored loosely — the failure trace embeds the exception message |
| 18 | +# after a long preamble. |
31 | 19 | _TRANSIENT_PATTERNS = [ |
32 | 20 | r"RuntimeError:\s*(?:429|5\d\d):", # _raise_for_non_200 output |
33 | 21 | r"ConnectionError", # requests/urllib3 |
34 | 22 | r"ReadTimeout|ConnectTimeout|Timeout", |
35 | | - r"timed out", |
36 | | - r"Bad Gateway|Service Unavailable|Gateway Timeout", |
37 | 23 | ] |
38 | 24 |
|
| 25 | +# 5 seconds is generous enough for a USGS upstream replica to recover |
| 26 | +# from a brief 5xx but short enough that CI doesn't drag. |
| 27 | +_RETRY_DELAY_SEC = 5 |
39 | 28 | _MAX_RERUNS = 2 |
40 | | -_RERUN_DELAY_SEC = 5 |
41 | | - |
42 | | - |
43 | | -def pytest_configure(config): |
44 | | - """Register the ``live`` marker so pytest doesn't warn about it.""" |
45 | | - config.addinivalue_line( |
46 | | - "markers", |
47 | | - "live: marks tests that hit the live USGS Water Data API; " |
48 | | - "retried up to twice on transient upstream HTTP errors.", |
49 | | - ) |
50 | 29 |
|
51 | 30 |
|
52 | 31 | def pytest_collection_modifyitems(config, items): |
53 | | - """Auto-mark live-API tests with ``live`` + ``flaky`` retry config. |
54 | | -
|
55 | | - Heuristic: any test that does NOT request the ``requests_mock`` |
56 | | - fixture is treated as live. This catches every test in |
57 | | - ``waterdata_test.py`` and similar modules without needing to |
58 | | - decorate them individually, and it auto-tracks new tests written |
59 | | - in the same style. |
60 | | - """ |
61 | 32 | for item in items: |
62 | | - if "requests_mock" in getattr(item, "fixturenames", ()): |
| 33 | + if "requests_mock" in item.fixturenames: |
63 | 34 | continue |
64 | | - item.add_marker(pytest.mark.live) |
65 | 35 | item.add_marker( |
66 | 36 | pytest.mark.flaky( |
67 | 37 | reruns=_MAX_RERUNS, |
68 | | - reruns_delay=_RERUN_DELAY_SEC, |
| 38 | + reruns_delay=_RETRY_DELAY_SEC, |
69 | 39 | only_rerun=_TRANSIENT_PATTERNS, |
70 | 40 | ) |
71 | 41 | ) |
0 commit comments