Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ permissions:
jobs:
test:
name: Python ${{ matrix.python }}
runs-on: ubuntu-latest
# setup-python ships no 3.9 build for ubuntu-26.04 (ubuntu-latest); keep 3.9 on 24.04 while it stays supported.
runs-on: ${{ matrix.python == '3.9' && 'ubuntu-24.04' || 'ubuntu-latest' }}
strategy:
fail-fast: false
matrix:
Expand Down
5 changes: 5 additions & 0 deletions tests/test_behaviour_conformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,11 @@ def run(self) -> None:
def _eval_redis_layout(case: Mapping[str, Any]) -> None:
"""``redis_layout``: the three key-name cases (no broker) and the behavioural scenarios (Redis)."""
if "layout" in case:
if case.get("name", "").startswith("encode-fails-") and redis_backend.connect()[1] == "emulated":
# fakeredis + lupa serialises inf/NaN without error where real lua-cjson raises, so the
# cjson.encode guard cannot be exercised on the emulator (see PopScriptWithoutRedisTest
# for the broker-free proof). CI runs these against a real redis:7 (BQ_REQUIRE_REDIS).
raise unittest.SkipTest("encode-fails-* needs a real Redis: the emulator's cjson masks the bug")
_LayoutScenario(case).run()
else:
_eval_redis_key_names(case)
Expand Down
17 changes: 13 additions & 4 deletions tests/test_redis_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
SOURCE = Path(__file__).resolve().parent.parent / "src" / "babelqueue" / "redis_transport.py"


def _reject_constant(name: str) -> None:
raise ValueError(f"{name} is not JSON (RFC 8259)")


def assert_poison_record(case: unittest.TestCase, record: str, body: str, queue: str) -> None:
"""``record`` is the ADR-0035 poison DLQ record for ``body`` read from ``queue``."""
parsed = json.loads(record)
Expand All @@ -66,7 +70,9 @@ def assert_poison_record(case: unittest.TestCase, record: str, body: str, queue:
members = None
if decoded is not None:
try:
members = json.loads(decoded)
# ADR-0035 section 1: a strict RFC 8259 object is spliced; NaN / Infinity / -Infinity
# (which Python's json.loads accepts by default) make the body a wrapper case.
members = json.loads(decoded, parse_constant=_reject_constant)
except ValueError:
members = None
if decoded is None:
Expand Down Expand Up @@ -255,7 +261,7 @@ class PopScriptWithoutRedisTest(unittest.TestCase):
def _lua(self):
try:
from lupa import LuaRuntime
except ImportError: # pragma: no cover - lupa ships with the test extras
except ImportError: # pragma: no cover - lupa is optional (it comes with fakeredis[lua])
self.skipTest("lupa is not installed")
return LuaRuntime(unpack_returned_tuples=True)

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

with self.assertRaises(LuaError):
Expand Down Expand Up @@ -1101,7 +1108,8 @@ def test_undecodable_body_is_dead_lettered_raw_on_first_delivery(self):
app = self.app(max_attempts=3, dead_letter=True)
app.register(URN, lambda data, meta: None)
self.assertEqual(app.consume(self.queue, max_messages=1, timeout=0), 1)
self.assertEqual(self.client.lrange(f"{self.queue}.dlq", 0, -1), [garbage])
(record,) = self.client.lrange(f"{self.queue}.dlq", 0, -1)
assert_poison_record(self, record, garbage, self.queue) # ADR-0035 wrapper, not the raw body
self.assertEqual(self.client.llen(self.processing), 0)
self.assertEqual(self.client.zcard(self.deadlines), 0)
self.assertEqual(self.client.llen(self.queue), 0) # never re-queued for a retry
Expand All @@ -1111,7 +1119,8 @@ def test_undecodable_body_is_dead_lettered_with_the_dlq_off_too(self):
app = self.app(max_attempts=3, dead_letter=False)
app.register(URN, lambda data, meta: None)
self.assertEqual(app.consume(self.queue, max_messages=1, timeout=0), 1)
self.assertEqual(self.client.lrange(f"{self.queue}.dlq", 0, -1), ["[1]"])
(record,) = self.client.lrange(f"{self.queue}.dlq", 0, -1)
assert_poison_record(self, record, "[1]", self.queue)
self.assertEqual(self.client.llen(self.processing), 0)

def test_publish_with_headers_stores_a_frame_and_pop_unframes(self):
Expand Down
Loading