|
2 | 2 | Comprehensive Encoding/Decoding Test Suite |
3 | 3 |
|
4 | 4 | This consolidated module provides complete testing for encoding/decoding functionality |
5 | | -in mssql-python, ensuring pyodbc compatibility, thread safety, and connection pooling support. |
| 5 | +in mssql-python, thread safety, and connection pooling support. |
6 | 6 |
|
7 | 7 | Total Tests: 131 |
8 | 8 |
|
|
43 | 43 | - European: Latin-1, CP1252, ISO-8859 family |
44 | 44 | - UTF-8 and UTF-16 variants |
45 | 45 |
|
46 | | -6. PYODBC COMPATIBILITY (12 tests) |
47 | | - - No automatic fallback behavior |
48 | | - - UTF-16 BOM rejection for SQL_WCHAR |
49 | | - - SQL_WMETADATA flexibility |
50 | | - - API compatibility and behavior matching |
51 | | -
|
52 | 46 | 7. THREAD SAFETY (8 tests) |
53 | 47 | - Race condition prevention in setencoding/setdecoding |
54 | 48 | - Thread-safe reads with getencoding/getdecoding |
@@ -3144,13 +3138,8 @@ def test_encoding_length_limit_security(db_connection): |
3144 | 3138 | db_connection.setencoding(encoding=enc_name, ctype=SQL_CHAR) |
3145 | 3139 |
|
3146 | 3140 |
|
3147 | | -# ==================================================================================== |
3148 | | -# UTF-8 ENCODING TESTS (pyodbc Compatibility) |
3149 | | -# ==================================================================================== |
3150 | | - |
3151 | | - |
3152 | 3141 | def test_utf8_encoding_strict_no_fallback(db_connection): |
3153 | | - """Test that UTF-8 encoding does NOT fallback to latin-1 (pyodbc compatibility).""" |
| 3142 | + """Test that UTF-8 encoding does NOT fallback to latin-1""" |
3154 | 3143 | db_connection.setencoding(encoding="utf-8", ctype=SQL_CHAR) |
3155 | 3144 |
|
3156 | 3145 | cursor = db_connection.cursor() |
@@ -3180,7 +3169,7 @@ def test_utf8_encoding_strict_no_fallback(db_connection): |
3180 | 3169 |
|
3181 | 3170 |
|
3182 | 3171 | def test_utf8_decoding_strict_no_fallback(db_connection): |
3183 | | - """Test that UTF-8 decoding does NOT fallback to latin-1 (pyodbc compatibility).""" |
| 3172 | + """Test that UTF-8 decoding does NOT fallback to latin-1""" |
3184 | 3173 | db_connection.setdecoding(SQL_CHAR, encoding="utf-8", ctype=SQL_CHAR) |
3185 | 3174 |
|
3186 | 3175 | cursor = db_connection.cursor() |
@@ -3506,11 +3495,6 @@ def test_utf16_unicode_preservation(db_connection): |
3506 | 3495 | cursor.close() |
3507 | 3496 |
|
3508 | 3497 |
|
3509 | | -# ==================================================================================== |
3510 | | -# ERROR HANDLING TESTS (Strict Mode, pyodbc Compatibility) |
3511 | | -# ==================================================================================== |
3512 | | - |
3513 | | - |
3514 | 3498 | def test_encoding_error_strict_mode(db_connection): |
3515 | 3499 | """Test that encoding errors are raised or data is mangled in strict mode (no fallback).""" |
3516 | 3500 | db_connection.setencoding(encoding="ascii", ctype=SQL_CHAR) |
@@ -6058,19 +6042,6 @@ def test_encoding_error_propagation_in_bind_parameters(db_connection): |
6058 | 6042 | cursor.close() |
6059 | 6043 |
|
6060 | 6044 |
|
6061 | | -# ============================================================================ |
6062 | | -# ADDITIONAL COVERAGE TESTS FOR MISSING LINES |
6063 | | -# ============================================================================ |
6064 | | - |
6065 | | - |
6066 | | -# Note: Tests for cursor._get_encoding_settings() and cursor._get_decoding_settings() |
6067 | | -# fallback paths (lines 318, 327, 357) are not easily testable because: |
6068 | | -# 1. The connection property is read-only and cannot be mocked |
6069 | | -# 2. These are defensive code paths for unusual error conditions |
6070 | | -# 3. The default fallback behavior (line 327) is tested implicitly by all other tests |
6071 | | -# Coverage for these lines may require integration tests with actual connection failures |
6072 | | - |
6073 | | - |
6074 | 6045 | def test_sql_c_char_encoding_with_bytes_and_bytearray(db_connection): |
6075 | 6046 | """Test SQL_C_CHAR encoding with bytes and bytearray parameters (lines 327-358 in ddbc_bindings.cpp).""" |
6076 | 6047 | db_connection.setencoding(encoding="utf-8", ctype=mssql_python.SQL_CHAR) |
@@ -6418,13 +6389,178 @@ def test_binary_lob_fetching(db_connection): |
6418 | 6389 | cursor.close() |
6419 | 6390 |
|
6420 | 6391 |
|
6421 | | -# Note: Removed test_comprehensive_encoding_decoding_coverage |
6422 | | -# The individual test functions already provide comprehensive coverage of: |
6423 | | -# - SQL_C_CHAR encoding paths (test_sql_c_char_encoding_with_bytes_and_bytearray) |
6424 | | -# - DAE paths (test_dae_sql_c_char_with_various_data_types) |
6425 | | -# - Executemany paths (test_executemany_sql_c_char_encoding_paths) |
6426 | | -# - LOB decoding (test_lob_decoding_with_fallback, test_binary_lob_fetching) |
6427 | | -# - Character decoding (test_char_column_decoding_with_fallback) |
| 6392 | +def test_cpp_bind_params_str_encoding(db_connection): |
| 6393 | + """str encoding with SQL_C_CHAR.""" |
| 6394 | + db_connection.setencoding(encoding="utf-8", ctype=mssql_python.SQL_CHAR) |
| 6395 | + cursor = db_connection.cursor() |
| 6396 | + try: |
| 6397 | + cursor.execute("CREATE TABLE #test_cpp_str (data VARCHAR(50))") |
| 6398 | + # This hits: py::isinstance<py::str>(param) == true |
| 6399 | + # and: param.attr("encode")(charEncoding, "strict") |
| 6400 | + # Note: VARCHAR stores in DB collation (Latin1), so we use ASCII-compatible chars |
| 6401 | + cursor.execute("INSERT INTO #test_cpp_str VALUES (?)", "Hello UTF-8 Test") |
| 6402 | + cursor.execute("SELECT data FROM #test_cpp_str") |
| 6403 | + assert cursor.fetchone()[0] == "Hello UTF-8 Test" |
| 6404 | + finally: |
| 6405 | + cursor.close() |
| 6406 | + |
| 6407 | + |
| 6408 | +def test_cpp_bind_params_bytes_encoding(db_connection): |
| 6409 | + """bytes handling with SQL_C_CHAR.""" |
| 6410 | + db_connection.setencoding(encoding="utf-8", ctype=mssql_python.SQL_CHAR) |
| 6411 | + cursor = db_connection.cursor() |
| 6412 | + try: |
| 6413 | + cursor.execute("CREATE TABLE #test_cpp_bytes (data VARCHAR(50))") |
| 6414 | + # This hits: py::isinstance<py::bytes>(param) == true |
| 6415 | + cursor.execute("INSERT INTO #test_cpp_bytes VALUES (?)", b"Bytes data") |
| 6416 | + cursor.execute("SELECT data FROM #test_cpp_bytes") |
| 6417 | + assert cursor.fetchone()[0] == "Bytes data" |
| 6418 | + finally: |
| 6419 | + cursor.close() |
| 6420 | + |
| 6421 | + |
| 6422 | +def test_cpp_bind_params_bytearray_encoding(db_connection): |
| 6423 | + """bytearray handling with SQL_C_CHAR.""" |
| 6424 | + db_connection.setencoding(encoding="utf-8", ctype=mssql_python.SQL_CHAR) |
| 6425 | + cursor = db_connection.cursor() |
| 6426 | + try: |
| 6427 | + cursor.execute("CREATE TABLE #test_cpp_bytearray (data VARCHAR(50))") |
| 6428 | + # This hits: bytearray branch - PyByteArray_AsString/Size |
| 6429 | + cursor.execute("INSERT INTO #test_cpp_bytearray VALUES (?)", bytearray(b"Bytearray data")) |
| 6430 | + cursor.execute("SELECT data FROM #test_cpp_bytearray") |
| 6431 | + assert cursor.fetchone()[0] == "Bytearray data" |
| 6432 | + finally: |
| 6433 | + cursor.close() |
| 6434 | + |
| 6435 | + |
| 6436 | +def test_cpp_bind_params_encoding_error(db_connection): |
| 6437 | + """encoding error handling.""" |
| 6438 | + db_connection.setencoding(encoding="ascii", ctype=mssql_python.SQL_CHAR) |
| 6439 | + cursor = db_connection.cursor() |
| 6440 | + try: |
| 6441 | + cursor.execute("CREATE TABLE #test_cpp_encode_err (data VARCHAR(50))") |
| 6442 | + # This should trigger the catch block (lines 337-345) |
| 6443 | + try: |
| 6444 | + cursor.execute("INSERT INTO #test_cpp_encode_err VALUES (?)", "Non-ASCII: 你好") |
| 6445 | + # If no error, that's OK - some drivers might handle it |
| 6446 | + except Exception as e: |
| 6447 | + # Expected: encoding error caught by C++ layer |
| 6448 | + assert "encode" in str(e).lower() or "ascii" in str(e).lower() |
| 6449 | + finally: |
| 6450 | + cursor.close() |
| 6451 | + |
| 6452 | + |
| 6453 | +def test_cpp_dae_str_encoding(db_connection): |
| 6454 | + """str encoding in Data-At-Execution.""" |
| 6455 | + db_connection.setencoding(encoding="utf-8", ctype=mssql_python.SQL_CHAR) |
| 6456 | + cursor = db_connection.cursor() |
| 6457 | + try: |
| 6458 | + cursor.execute("CREATE TABLE #test_cpp_dae_str (data VARCHAR(MAX))") |
| 6459 | + # Large string triggers DAE |
| 6460 | + # This hits: py::isinstance<py::str>(pyObj) == true in DAE path |
| 6461 | + # Note: VARCHAR stores in DB collation, so we use ASCII-compatible chars |
| 6462 | + large_str = "A" * 10000 + " END_MARKER" |
| 6463 | + cursor.execute("INSERT INTO #test_cpp_dae_str VALUES (?)", large_str) |
| 6464 | + cursor.execute("SELECT data FROM #test_cpp_dae_str") |
| 6465 | + result = cursor.fetchone()[0] |
| 6466 | + assert len(result) > 10000 |
| 6467 | + assert "END_MARKER" in result |
| 6468 | + finally: |
| 6469 | + cursor.close() |
| 6470 | + |
| 6471 | + |
| 6472 | +def test_cpp_dae_bytes_encoding(db_connection): |
| 6473 | + """bytes encoding in Data-At-Execution.""" |
| 6474 | + db_connection.setencoding(encoding="utf-8", ctype=mssql_python.SQL_CHAR) |
| 6475 | + cursor = db_connection.cursor() |
| 6476 | + try: |
| 6477 | + cursor.execute("CREATE TABLE #test_cpp_dae_bytes (data VARCHAR(MAX))") |
| 6478 | + # Large bytes triggers DAE with bytes branch |
| 6479 | + # This hits: else branch (line 1751) - encodedStr = pyObj.cast<std::string>() |
| 6480 | + large_bytes = b"B" * 10000 |
| 6481 | + cursor.execute("INSERT INTO #test_cpp_dae_bytes VALUES (?)", large_bytes) |
| 6482 | + cursor.execute("SELECT LEN(data) FROM #test_cpp_dae_bytes") |
| 6483 | + assert cursor.fetchone()[0] == 10000 |
| 6484 | + finally: |
| 6485 | + cursor.close() |
| 6486 | + |
| 6487 | + |
| 6488 | +def test_cpp_dae_encoding_error(db_connection): |
| 6489 | + """encoding error in Data-At-Execution.""" |
| 6490 | + db_connection.setencoding(encoding="ascii", ctype=mssql_python.SQL_CHAR) |
| 6491 | + cursor = db_connection.cursor() |
| 6492 | + try: |
| 6493 | + cursor.execute("CREATE TABLE #test_cpp_dae_err (data VARCHAR(MAX))") |
| 6494 | + # Large non-ASCII string to trigger DAE + encoding error |
| 6495 | + large_unicode = "你好世界 " * 3000 |
| 6496 | + try: |
| 6497 | + cursor.execute("INSERT INTO #test_cpp_dae_err VALUES (?)", large_unicode) |
| 6498 | + # No error is OK - some implementations may handle it |
| 6499 | + except Exception as e: |
| 6500 | + # Expected: catch block lines 1753-1756 |
| 6501 | + error_msg = str(e).lower() |
| 6502 | + assert "encode" in error_msg or "ascii" in error_msg |
| 6503 | + finally: |
| 6504 | + cursor.close() |
| 6505 | + |
| 6506 | + |
| 6507 | +def test_cpp_executemany_str_encoding(db_connection): |
| 6508 | + """str encoding in executemany.""" |
| 6509 | + db_connection.setencoding(encoding="utf-8", ctype=mssql_python.SQL_CHAR) |
| 6510 | + cursor = db_connection.cursor() |
| 6511 | + try: |
| 6512 | + cursor.execute("CREATE TABLE #test_cpp_many_str (id INT, data VARCHAR(50))") |
| 6513 | + # This hits: columnValues[i].attr("encode")(charEncoding, "strict") for each row |
| 6514 | + params = [ |
| 6515 | + (1, "Row 1 UTF-8 ✓"), |
| 6516 | + (2, "Row 2 UTF-8 ✓"), |
| 6517 | + (3, "Row 3 UTF-8 ✓"), |
| 6518 | + ] |
| 6519 | + cursor.executemany("INSERT INTO #test_cpp_many_str VALUES (?, ?)", params) |
| 6520 | + cursor.execute("SELECT COUNT(*) FROM #test_cpp_many_str") |
| 6521 | + assert cursor.fetchone()[0] == 3 |
| 6522 | + finally: |
| 6523 | + cursor.close() |
| 6524 | + |
| 6525 | + |
| 6526 | +def test_cpp_executemany_bytes_encoding(db_connection): |
| 6527 | + """bytes/bytearray in executemany.""" |
| 6528 | + db_connection.setencoding(encoding="utf-8", ctype=mssql_python.SQL_CHAR) |
| 6529 | + cursor = db_connection.cursor() |
| 6530 | + try: |
| 6531 | + cursor.execute("CREATE TABLE #test_cpp_many_bytes (id INT, data VARCHAR(50))") |
| 6532 | + # This hits: else branch (line 2065) - bytes/bytearray handling |
| 6533 | + params = [ |
| 6534 | + (1, b"Bytes 1"), |
| 6535 | + (2, b"Bytes 2"), |
| 6536 | + ] |
| 6537 | + cursor.executemany("INSERT INTO #test_cpp_many_bytes VALUES (?, ?)", params) |
| 6538 | + cursor.execute("SELECT COUNT(*) FROM #test_cpp_many_bytes") |
| 6539 | + assert cursor.fetchone()[0] == 2 |
| 6540 | + finally: |
| 6541 | + cursor.close() |
| 6542 | + |
| 6543 | + |
| 6544 | +def test_cpp_executemany_encoding_error(db_connection): |
| 6545 | + """encoding error in executemany.""" |
| 6546 | + db_connection.setencoding(encoding="ascii", ctype=mssql_python.SQL_CHAR) |
| 6547 | + cursor = db_connection.cursor() |
| 6548 | + try: |
| 6549 | + cursor.execute("CREATE TABLE #test_cpp_many_err (id INT, data VARCHAR(50))") |
| 6550 | + # This should trigger catch block lines 2055-2063 |
| 6551 | + params = [ |
| 6552 | + (1, "OK ASCII"), |
| 6553 | + (2, "Non-ASCII 中文"), # Should trigger error |
| 6554 | + ] |
| 6555 | + try: |
| 6556 | + cursor.executemany("INSERT INTO #test_cpp_many_err VALUES (?, ?)", params) |
| 6557 | + # No error is OK |
| 6558 | + except Exception as e: |
| 6559 | + # Expected: catch block with error message |
| 6560 | + error_msg = str(e).lower() |
| 6561 | + assert "encode" in error_msg or "ascii" in error_msg or "parameter" in error_msg |
| 6562 | + finally: |
| 6563 | + cursor.close() |
6428 | 6564 |
|
6429 | 6565 |
|
6430 | 6566 | if __name__ == "__main__": |
|
0 commit comments