2222import struct
2323import 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
3840def _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" )
7172class 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