|
| 1 | +"""Regression tests for issue 050: single-owner contract documentation. |
| 2 | +
|
| 3 | +Issue 050 documented that the prior thread-safety analysis (issue 021) |
| 4 | ++ poison mechanism (issue 026) created a misleading impression that |
| 5 | +concurrent misuse would always surface as ``ProtocolError``. Empirical |
| 6 | +fuzz testing confirmed it does not — under threaded contention, |
| 7 | +``ReadBuffer`` produces duplicate messages and corrupt (garbage) bytes |
| 8 | +with ``is_poisoned`` remaining ``False``, because torn reads often |
| 9 | +yield valid-looking slices that decode cleanly. |
| 10 | +
|
| 11 | +The remediation is documentation, not code: the docstrings on |
| 12 | +``ReadBuffer``, ``WriteBuffer``, ``MessageDecoder``, and |
| 13 | +``MessageEncoder`` must make the single-owner contract explicit AND |
| 14 | +call out that the poison mechanism cannot and does not detect |
| 15 | +concurrent misuse. These tests assert the docstrings carry that |
| 16 | +warning so future refactors that reformat docstrings don't silently |
| 17 | +drop it. |
| 18 | +""" |
| 19 | + |
| 20 | +from __future__ import annotations |
| 21 | + |
| 22 | +from dqlitewire.buffer import ReadBuffer, WriteBuffer |
| 23 | +from dqlitewire.codec import MessageDecoder, MessageEncoder |
| 24 | + |
| 25 | + |
| 26 | +def _docstring(obj: object) -> str: |
| 27 | + return (obj.__doc__ or "").lower() |
| 28 | + |
| 29 | + |
| 30 | +class TestThreadSafetyContractDocumented: |
| 31 | + """Assert the thread-safety contract is explicit in class docstrings. |
| 32 | +
|
| 33 | + The contract has three parts, and all three must be mentioned: |
| 34 | +
|
| 35 | + 1. The class is NOT thread-safe / requires a single owner. |
| 36 | + 2. Concurrent misuse produces SILENT data corruption, not |
| 37 | + exceptions. |
| 38 | + 3. The poison mechanism does NOT detect concurrent misuse. |
| 39 | +
|
| 40 | + Any docstring-reformat that drops one of these silently |
| 41 | + undoes issue 050's documentation fix. |
| 42 | + """ |
| 43 | + |
| 44 | + def test_readbuffer_docstring_warns_about_thread_safety(self) -> None: |
| 45 | + doc = _docstring(ReadBuffer) |
| 46 | + assert "not thread-safe" in doc or "not thread safe" in doc, ( |
| 47 | + "ReadBuffer docstring must explicitly say it is not thread-safe" |
| 48 | + ) |
| 49 | + assert "silent" in doc and ("corruption" in doc or "corrupt" in doc), ( |
| 50 | + "ReadBuffer docstring must warn that concurrent misuse produces silent corruption" |
| 51 | + ) |
| 52 | + assert "poison" in doc, ( |
| 53 | + "ReadBuffer docstring must explain the poison mechanism's " |
| 54 | + "relationship to concurrent misuse" |
| 55 | + ) |
| 56 | + |
| 57 | + def test_writebuffer_docstring_warns_about_thread_safety(self) -> None: |
| 58 | + doc = _docstring(WriteBuffer) |
| 59 | + assert "not thread-safe" in doc or "not thread safe" in doc, ( |
| 60 | + "WriteBuffer docstring must explicitly say it is not thread-safe" |
| 61 | + ) |
| 62 | + |
| 63 | + def test_message_decoder_docstring_warns_about_thread_safety(self) -> None: |
| 64 | + doc = _docstring(MessageDecoder) |
| 65 | + assert "not thread-safe" in doc or "not thread safe" in doc, ( |
| 66 | + "MessageDecoder docstring must explicitly say it is not thread-safe" |
| 67 | + ) |
| 68 | + assert "silent" in doc and ("corruption" in doc or "corrupt" in doc), ( |
| 69 | + "MessageDecoder docstring must warn that concurrent misuse produces silent corruption" |
| 70 | + ) |
| 71 | + assert "poison" in doc, ( |
| 72 | + "MessageDecoder docstring must explain the poison mechanism's " |
| 73 | + "relationship to concurrent misuse" |
| 74 | + ) |
| 75 | + |
| 76 | + def test_message_encoder_docstring_warns_about_thread_safety(self) -> None: |
| 77 | + doc = _docstring(MessageEncoder) |
| 78 | + assert "not thread-safe" in doc or "not thread safe" in doc, ( |
| 79 | + "MessageEncoder docstring must explicitly say it is not thread-safe" |
| 80 | + ) |
0 commit comments