Skip to content

Commit 79618d3

Browse files
Align Redis poison tests with ADR-0035 and skip lupa-only checks without lupa (#10)
* test: align Redis poison tests with ADR-0035 and skip lupa-only checks without lupa * ci: run the Python 3.9 job on ubuntu-24.04
1 parent d13c772 commit 79618d3

3 files changed

Lines changed: 20 additions & 5 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ permissions:
1212
jobs:
1313
test:
1414
name: Python ${{ matrix.python }}
15-
runs-on: ubuntu-latest
15+
# setup-python ships no 3.9 build for ubuntu-26.04 (ubuntu-latest); keep 3.9 on 24.04 while it stays supported.
16+
runs-on: ${{ matrix.python == '3.9' && 'ubuntu-24.04' || 'ubuntu-latest' }}
1617
strategy:
1718
fail-fast: false
1819
matrix:

‎tests/test_behaviour_conformance.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,11 @@ def run(self) -> None:
10301030
def _eval_redis_layout(case: Mapping[str, Any]) -> None:
10311031
"""``redis_layout``: the three key-name cases (no broker) and the behavioural scenarios (Redis)."""
10321032
if "layout" in case:
1033+
if case.get("name", "").startswith("encode-fails-") and redis_backend.connect()[1] == "emulated":
1034+
# fakeredis + lupa serialises inf/NaN without error where real lua-cjson raises, so the
1035+
# cjson.encode guard cannot be exercised on the emulator (see PopScriptWithoutRedisTest
1036+
# for the broker-free proof). CI runs these against a real redis:7 (BQ_REQUIRE_REDIS).
1037+
raise unittest.SkipTest("encode-fails-* needs a real Redis: the emulator's cjson masks the bug")
10331038
_LayoutScenario(case).run()
10341039
else:
10351040
_eval_redis_key_names(case)

‎tests/test_redis_transport.py‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@
5353
SOURCE = Path(__file__).resolve().parent.parent / "src" / "babelqueue" / "redis_transport.py"
5454

5555

56+
def _reject_constant(name: str) -> None:
57+
raise ValueError(f"{name} is not JSON (RFC 8259)")
58+
59+
5660
def assert_poison_record(case: unittest.TestCase, record: str, body: str, queue: str) -> None:
5761
"""``record`` is the ADR-0035 poison DLQ record for ``body`` read from ``queue``."""
5862
parsed = json.loads(record)
@@ -66,7 +70,9 @@ def assert_poison_record(case: unittest.TestCase, record: str, body: str, queue:
6670
members = None
6771
if decoded is not None:
6872
try:
69-
members = json.loads(decoded)
73+
# ADR-0035 section 1: a strict RFC 8259 object is spliced; NaN / Infinity / -Infinity
74+
# (which Python's json.loads accepts by default) make the body a wrapper case.
75+
members = json.loads(decoded, parse_constant=_reject_constant)
7076
except ValueError:
7177
members = None
7278
if decoded is None:
@@ -255,7 +261,7 @@ class PopScriptWithoutRedisTest(unittest.TestCase):
255261
def _lua(self):
256262
try:
257263
from lupa import LuaRuntime
258-
except ImportError: # pragma: no cover - lupa ships with the test extras
264+
except ImportError: # pragma: no cover - lupa is optional (it comes with fakeredis[lua])
259265
self.skipTest("lupa is not installed")
260266
return LuaRuntime(unpack_returned_tuples=True)
261267

@@ -294,6 +300,7 @@ def test_the_stub_models_the_bug_an_unguarded_encode_raises(self):
294300
"reserved = cjson.encode(decoded)",
295301
)
296302
self.assertNotEqual(unguarded, _POP)
303+
self._lua() # skips when lupa is not installed (the matrix jobs install no Redis extras)
297304
from lupa import LuaError
298305

299306
with self.assertRaises(LuaError):
@@ -1101,7 +1108,8 @@ def test_undecodable_body_is_dead_lettered_raw_on_first_delivery(self):
11011108
app = self.app(max_attempts=3, dead_letter=True)
11021109
app.register(URN, lambda data, meta: None)
11031110
self.assertEqual(app.consume(self.queue, max_messages=1, timeout=0), 1)
1104-
self.assertEqual(self.client.lrange(f"{self.queue}.dlq", 0, -1), [garbage])
1111+
(record,) = self.client.lrange(f"{self.queue}.dlq", 0, -1)
1112+
assert_poison_record(self, record, garbage, self.queue) # ADR-0035 wrapper, not the raw body
11051113
self.assertEqual(self.client.llen(self.processing), 0)
11061114
self.assertEqual(self.client.zcard(self.deadlines), 0)
11071115
self.assertEqual(self.client.llen(self.queue), 0) # never re-queued for a retry
@@ -1111,7 +1119,8 @@ def test_undecodable_body_is_dead_lettered_with_the_dlq_off_too(self):
11111119
app = self.app(max_attempts=3, dead_letter=False)
11121120
app.register(URN, lambda data, meta: None)
11131121
self.assertEqual(app.consume(self.queue, max_messages=1, timeout=0), 1)
1114-
self.assertEqual(self.client.lrange(f"{self.queue}.dlq", 0, -1), ["[1]"])
1122+
(record,) = self.client.lrange(f"{self.queue}.dlq", 0, -1)
1123+
assert_poison_record(self, record, "[1]", self.queue)
11151124
self.assertEqual(self.client.llen(self.processing), 0)
11161125

11171126
def test_publish_with_headers_stores_a_frame_and_pop_unframes(self):

0 commit comments

Comments
 (0)