Skip to content

Commit a9815c1

Browse files
committed
Address review comments: use cythontest decorator, add importorskip guards, add invalid-input tests
- Replace hand-rolled try/except ImportError with the project-standard cythontest decorator and HAVE_CYTHON conditional imports, so VERIFY_CYTHON=True CI mode fails loudly instead of silently skipping. - Add pytest.importorskip guards to the benchmark file so it skips gracefully when pytest-benchmark or Cython extensions are missing. - Add test_utf8_invalid_bytes and test_ascii_invalid_bytes to confirm error propagation through the DriverException wrapper.
1 parent 34d69bc commit a9815c1

2 files changed

Lines changed: 39 additions & 5 deletions

File tree

benchmarks/utf8_decode_benchmark.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
import struct
3131
import pytest
3232

33+
pytest.importorskip("pytest_benchmark")
34+
pytest.importorskip("cassandra.obj_parser")
35+
3336
from cassandra.obj_parser import ListParser
3437
from cassandra.bytesio import BytesIOReader
3538
from cassandra.parsing import ParseDesc

tests/unit/cython/test_deserializers.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,19 @@
2222
import struct
2323
import unittest
2424

25-
try:
25+
from tests.unit.cython.utils import cythontest
26+
27+
from cassandra.cython_deps import HAVE_CYTHON
28+
29+
if HAVE_CYTHON:
2630
from cassandra.obj_parser import ListParser
2731
from cassandra.bytesio import BytesIOReader
2832
from cassandra.parsing import ParseDesc
2933
from cassandra.deserializers import make_deserializers
3034
from cassandra.cqltypes import UTF8Type, AsciiType
3135
from cassandra.policies import ColDesc
3236

33-
HAS_CYTHON = True
34-
except ImportError:
35-
HAS_CYTHON = False
37+
from cassandra import DriverException
3638

3739

3840
def _build_text_rows_buffer(num_rows, num_cols, text_data):
@@ -67,10 +69,10 @@ def _make_ascii_desc(num_cols, protocol_version=4):
6769
return ParseDesc(colnames, coltypes, None, coldescs, desers, protocol_version)
6870

6971

70-
@unittest.skipUnless(HAS_CYTHON, "Cython extensions not available")
7172
class TestCythonDeserializerCorrectness(unittest.TestCase):
7273
"""Verify that the optimized Cython decode produces correct results."""
7374

75+
@cythontest
7476
def test_utf8_empty_string(self):
7577
"""Empty string should return empty string."""
7678
buf = _build_text_rows_buffer(1, 1, b"")
@@ -80,6 +82,7 @@ def test_utf8_empty_string(self):
8082
rows = parser.parse_rows(reader, desc)
8183
self.assertEqual(rows[0][0], "")
8284

85+
@cythontest
8386
def test_utf8_ascii_only(self):
8487
"""Pure ASCII content."""
8588
text = b"Hello, World! 12345"
@@ -90,6 +93,7 @@ def test_utf8_ascii_only(self):
9093
rows = parser.parse_rows(reader, desc)
9194
self.assertEqual(rows[0][0], "Hello, World! 12345")
9295

96+
@cythontest
9397
def test_utf8_multibyte(self):
9498
"""Multibyte UTF-8 characters."""
9599
text = "Héllo wörld! こんにちは 🌍".encode("utf-8")
@@ -100,6 +104,7 @@ def test_utf8_multibyte(self):
100104
rows = parser.parse_rows(reader, desc)
101105
self.assertEqual(rows[0][0], "Héllo wörld! こんにちは 🌍")
102106

107+
@cythontest
103108
def test_utf8_long_string(self):
104109
"""Long string (10KB)."""
105110
text = ("x" * 10000).encode("utf-8")
@@ -110,6 +115,7 @@ def test_utf8_long_string(self):
110115
rows = parser.parse_rows(reader, desc)
111116
self.assertEqual(rows[0][0], "x" * 10000)
112117

118+
@cythontest
113119
def test_ascii_basic(self):
114120
"""Basic ASCII decode."""
115121
text = b"Simple ASCII text 12345 !@#"
@@ -120,6 +126,7 @@ def test_ascii_basic(self):
120126
rows = parser.parse_rows(reader, desc)
121127
self.assertEqual(rows[0][0], "Simple ASCII text 12345 !@#")
122128

129+
@cythontest
123130
def test_utf8_null_value(self):
124131
"""NULL value (negative length) should return None."""
125132
# Build buffer: 1 row, 1 column with length = -1 (NULL)
@@ -130,6 +137,7 @@ def test_utf8_null_value(self):
130137
rows = parser.parse_rows(reader, desc)
131138
self.assertIsNone(rows[0][0])
132139

140+
@cythontest
133141
def test_utf8_multiple_rows_columns(self):
134142
"""Multiple rows and columns."""
135143
texts = [b"alpha", b"beta", b"gamma"]
@@ -143,3 +151,26 @@ def test_utf8_multiple_rows_columns(self):
143151
reader = BytesIOReader(buf)
144152
rows = parser.parse_rows(reader, desc)
145153
self.assertEqual([r[0] for r in rows], ["alpha", "beta", "gamma"])
154+
155+
@cythontest
156+
def test_utf8_invalid_bytes(self):
157+
"""Invalid UTF-8 bytes should raise an error (DriverException wrapping UnicodeDecodeError)."""
158+
# 0xFF 0xFE is not valid UTF-8
159+
buf = _build_text_rows_buffer(1, 1, b"\xff\xfe\x80\x81")
160+
desc = _make_text_desc(1)
161+
parser = ListParser()
162+
reader = BytesIOReader(buf)
163+
with self.assertRaises(DriverException) as ctx:
164+
parser.parse_rows(reader, desc)
165+
self.assertIn("utf-8", str(ctx.exception).lower())
166+
167+
@cythontest
168+
def test_ascii_invalid_bytes(self):
169+
"""Non-ASCII bytes in an ASCII column should raise an error (DriverException wrapping UnicodeDecodeError)."""
170+
buf = _build_text_rows_buffer(1, 1, b"\x80\x81\x82")
171+
desc = _make_ascii_desc(1)
172+
parser = ListParser()
173+
reader = BytesIOReader(buf)
174+
with self.assertRaises(DriverException) as ctx:
175+
parser.parse_rows(reader, desc)
176+
self.assertIn("ascii", str(ctx.exception).lower())

0 commit comments

Comments
 (0)