diff --git a/tests/c/decoder_framed b/tests/c/decoder_framed deleted file mode 100755 index 35e6c8a2..00000000 Binary files a/tests/c/decoder_framed and /dev/null differ diff --git a/tests/c/decoder_framed.c b/tests/c/decoder_framed.c deleted file mode 100644 index f120c7a5..00000000 --- a/tests/c/decoder_framed.c +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include -#include - -#include "serialization_test.sf.h" -#include "struct_frame_default_frame.h" - -// Decoder for cross-platform testing with framing -// Reads framed SerializationTestMessage from stdin and decodes it -int main() { - // Read binary data from stdin - uint8_t buffer[512]; - size_t size = fread(buffer, 1, sizeof(buffer), stdin); - - if (size == 0) { - fprintf(stderr, "ERROR: No data received from stdin\n"); - return 1; - } - - // Decode the message - packet_format_t* format = &default_frame_format; - msg_info_t decode_result = format->validate_packet(buffer, size); - - if (!decode_result.valid) { - fprintf(stderr, "ERROR: Failed to decode message\n"); - return 1; - } - - SerializationTestSerializationTestMessage msg = - serialization_test_serialization_test_message_get(decode_result); - - // Print decoded values in a consistent format - printf("magic_number=0x%08X\n", msg.magic_number); - printf("test_string=%.*s\n", msg.test_string.length, msg.test_string.data); - printf("test_float=%.5f\n", msg.test_float); - printf("test_bool=%s\n", msg.test_bool ? "True" : "False"); - printf("test_array="); - for (int i = 0; i < msg.test_array.count; i++) { - printf("%d", msg.test_array.data[i]); - if (i < msg.test_array.count - 1) printf(","); - } - printf("\n"); - - return 0; -} diff --git a/tests/c/encoder_framed b/tests/c/encoder_framed deleted file mode 100755 index 8cd1f397..00000000 Binary files a/tests/c/encoder_framed and /dev/null differ diff --git a/tests/c/encoder_framed.c b/tests/c/encoder_framed.c deleted file mode 100644 index 60298fce..00000000 --- a/tests/c/encoder_framed.c +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include - -#include "serialization_test.sf.h" -#include "struct_frame_default_frame.h" - -// Encoder for cross-platform testing with framing -// Encodes a SerializationTestMessage with framing and writes to stdout -int main() { - SerializationTestSerializationTestMessage msg = {0}; - - // Set test data - msg.magic_number = 0xDEADBEEF; - msg.test_string.length = strlen("Hello from C!"); - strncpy(msg.test_string.data, "Hello from C!", msg.test_string.length); - msg.test_float = 3.14159f; - msg.test_bool = true; - msg.test_array.count = 3; - msg.test_array.data[0] = 100; - msg.test_array.data[1] = 200; - msg.test_array.data[2] = 300; - - // Create encoding buffer - uint8_t encode_buffer[512]; - msg_encode_buffer buffer = {0}; - buffer.data = encode_buffer; - - // Encode the message with framing - packet_format_t* format = &default_frame_format; - bool encoded = serialization_test_serialization_test_message_encode(&buffer, format, &msg); - - if (!encoded) { - fprintf(stderr, "ERROR: Failed to encode message\n"); - return 1; - } - - // Write binary data to stdout - fwrite(encode_buffer, 1, buffer.size, stdout); - fflush(stdout); - - return 0; -} diff --git a/tests/c/test_arrays.c b/tests/c/test_arrays.c index b2863fd5..063df9c6 100644 --- a/tests/c/test_arrays.c +++ b/tests/c/test_arrays.c @@ -72,6 +72,7 @@ int test_array_operations() { msg.bounded_sensors.data[0].status = 2; strncpy(msg.bounded_sensors.data[0].name, "Pressure", 16); + // Encode message into BasicPacket format uint8_t encode_buffer[1024]; msg_encode_buffer buffer = {0}; buffer.data = encode_buffer; @@ -84,6 +85,7 @@ int test_array_operations() { return 0; } + // Validate and decode the BasicPacket msg_info_t decode_result = format->validate_packet(encode_buffer, buffer.size); if (!decode_result.valid) { print_failure_details("Validation failed", encode_buffer, buffer.size); @@ -93,8 +95,24 @@ int test_array_operations() { ComprehensiveArraysComprehensiveArrayMessage decoded_msg = comprehensive_arrays_comprehensive_array_message_get(decode_result); - if (decoded_msg.fixed_ints[0] != 1 || decoded_msg.bounded_uints.count != 3) { - print_failure_details("Value mismatch", encode_buffer, buffer.size); + // Compare original and decoded messages + if (decoded_msg.fixed_ints[0] != msg.fixed_ints[0]) { + print_failure_details("Value mismatch: fixed_ints[0]", encode_buffer, buffer.size); + return 0; + } + + if (decoded_msg.bounded_uints.count != msg.bounded_uints.count) { + print_failure_details("Value mismatch: bounded_uints.count", encode_buffer, buffer.size); + return 0; + } + + if (decoded_msg.bounded_uints.data[0] != msg.bounded_uints.data[0]) { + print_failure_details("Value mismatch: bounded_uints.data[0]", encode_buffer, buffer.size); + return 0; + } + + if (decoded_msg.fixed_sensors[0].id != msg.fixed_sensors[0].id) { + print_failure_details("Value mismatch: fixed_sensors[0].id", encode_buffer, buffer.size); return 0; } diff --git a/tests/c/test_basic_types.c b/tests/c/test_basic_types.c index 3aedd45d..e31f0d7a 100644 --- a/tests/c/test_basic_types.c +++ b/tests/c/test_basic_types.c @@ -59,6 +59,7 @@ int test_basic_types() { msg.description.length = strlen("Test description for basic types"); strncpy(msg.description.data, "Test description for basic types", msg.description.length); + // Encode message into BasicPacket format uint8_t encode_buffer[1024]; msg_encode_buffer buffer = {0}; buffer.data = encode_buffer; @@ -71,6 +72,7 @@ int test_basic_types() { return 0; } + // Validate and decode the BasicPacket msg_info_t decode_result = format->validate_packet(encode_buffer, buffer.size); if (!decode_result.valid) { print_failure_details("Validation failed", &msg, NULL, encode_buffer, buffer.size); @@ -79,9 +81,24 @@ int test_basic_types() { BasicTypesBasicTypesMessage decoded_msg = basic_types_basic_types_message_get(decode_result); - if (decoded_msg.small_int != -42 || decoded_msg.medium_int != -1000 || - decoded_msg.flag != true) { - print_failure_details("Value mismatch", &msg, &decoded_msg, encode_buffer, buffer.size); + // Compare original and decoded messages + if (decoded_msg.small_int != msg.small_int) { + print_failure_details("Value mismatch: small_int", &msg, &decoded_msg, encode_buffer, buffer.size); + return 0; + } + + if (decoded_msg.medium_int != msg.medium_int) { + print_failure_details("Value mismatch: medium_int", &msg, &decoded_msg, encode_buffer, buffer.size); + return 0; + } + + if (decoded_msg.flag != msg.flag) { + print_failure_details("Value mismatch: flag", &msg, &decoded_msg, encode_buffer, buffer.size); + return 0; + } + + if (decoded_msg.single_precision != msg.single_precision) { + print_failure_details("Value mismatch: single_precision", &msg, &decoded_msg, encode_buffer, buffer.size); return 0; } diff --git a/tests/c/test_cross_platform_deserialization.c b/tests/c/test_cross_platform_deserialization.c new file mode 100644 index 00000000..58aed256 --- /dev/null +++ b/tests/c/test_cross_platform_deserialization.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include + +#include "serialization_test.sf.h" +#include "struct_frame_default_frame.h" + +void print_failure_details(const char* label, const void* raw_data, size_t raw_data_size) { + printf("\n"); + printf("============================================================\n"); + printf("FAILURE DETAILS: %s\n", label); + printf("============================================================\n"); + + if (raw_data && raw_data_size > 0) { + printf("\nRaw Data (%zu bytes):\n Hex: ", raw_data_size); + for (size_t i = 0; i < raw_data_size && i < 64; i++) { + printf("%02x", ((const uint8_t*)raw_data)[i]); + } + if (raw_data_size > 64) printf("..."); + printf("\n"); + } + + printf("============================================================\n\n"); +} + +int validate_message(SerializationTestSerializationTestMessage* msg) { + // Expected values from expected_values.json + uint32_t expected_magic = 3735928559; // 0xDEADBEEF + const char* expected_string = "Cross-platform test!"; + float expected_float = 3.14159f; + bool expected_bool = true; + int expected_array[3] = {100, 200, 300}; + + if (msg->magic_number != expected_magic) { + printf(" Value mismatch: magic_number (expected %u, got %u)\n", + expected_magic, msg->magic_number); + return 0; + } + + if (strncmp(msg->test_string.data, expected_string, msg->test_string.length) != 0) { + printf(" Value mismatch: test_string\n"); + return 0; + } + + if (fabs(msg->test_float - expected_float) > 0.0001f) { + printf(" Value mismatch: test_float (expected %f, got %f)\n", + expected_float, msg->test_float); + return 0; + } + + if (msg->test_bool != expected_bool) { + printf(" Value mismatch: test_bool\n"); + return 0; + } + + if (msg->test_array.count != 3) { + printf(" Value mismatch: test_array.count (expected 3, got %u)\n", + msg->test_array.count); + return 0; + } + + for (int i = 0; i < 3; i++) { + if (msg->test_array.data[i] != expected_array[i]) { + printf(" Value mismatch: test_array[%d] (expected %d, got %d)\n", + i, expected_array[i], msg->test_array.data[i]); + return 0; + } + } + + return 1; +} + +int read_and_validate_test_data(const char* filename, const char* language) { + FILE* file = fopen(filename, "rb"); + if (!file) { + printf(" Skipping %s - file not found: %s\n", language, filename); + return 1; // Skip if file not available + } + + uint8_t buffer[512]; + size_t size = fread(buffer, 1, sizeof(buffer), file); + fclose(file); + + if (size == 0) { + print_failure_details("Empty file", buffer, size); + return 0; + } + + packet_format_t* format = &default_frame_format; + msg_info_t decode_result = format->validate_packet(buffer, size); + + if (!decode_result.valid) { + char label[256]; + snprintf(label, sizeof(label), "Failed to decode %s data", language); + print_failure_details(label, buffer, size); + return 0; + } + + SerializationTestSerializationTestMessage decoded_msg = + serialization_test_serialization_test_message_get(decode_result); + + if (!validate_message(&decoded_msg)) { + printf(" Validation failed for %s data\n", language); + return 0; + } + + printf(" āœ“ %s data validated successfully\n", language); + return 1; +} + +int main() { + printf("\n[TEST START] C Cross-Platform Deserialization\n"); + + int success = 1; + success = success && read_and_validate_test_data("python_test_data.bin", "Python"); + success = success && read_and_validate_test_data("c_test_data.bin", "C"); + success = success && read_and_validate_test_data("cpp_test_data.bin", "C++"); + success = success && read_and_validate_test_data("typescript_test_data.bin", "TypeScript"); + + const char* status = success ? "PASS" : "FAIL"; + printf("[TEST END] C Cross-Platform Deserialization: %s\n\n", status); + + return success ? 0 : 1; +} diff --git a/tests/c/test_serialization.c b/tests/c/test_cross_platform_serialization.c similarity index 64% rename from tests/c/test_serialization.c rename to tests/c/test_cross_platform_serialization.c index 1680f5a0..757f2b51 100644 --- a/tests/c/test_serialization.c +++ b/tests/c/test_cross_platform_serialization.c @@ -26,9 +26,10 @@ void print_failure_details(const char* label, const void* raw_data, size_t raw_d int create_test_data() { SerializationTestSerializationTestMessage msg = {0}; - msg.magic_number = 0xDEADBEEF; - msg.test_string.length = strlen("Hello from C!"); - strncpy(msg.test_string.data, "Hello from C!", msg.test_string.length); + // Use values from expected_values.json + msg.magic_number = 3735928559; // 0xDEADBEEF + msg.test_string.length = strlen("Cross-platform test!"); + strncpy(msg.test_string.data, "Cross-platform test!", msg.test_string.length); msg.test_float = 3.14159f; msg.test_bool = true; msg.test_array.count = 3; @@ -57,6 +58,7 @@ int create_test_data() { fwrite(encode_buffer, 1, buffer.size, file); fclose(file); + // Self-validate msg_info_t decode_result = format->validate_packet(encode_buffer, buffer.size); if (!decode_result.valid) { print_failure_details("Self-validation failed", encode_buffer, buffer.size); @@ -66,7 +68,7 @@ int create_test_data() { SerializationTestSerializationTestMessage decoded_msg = serialization_test_serialization_test_message_get(decode_result); - if (decoded_msg.magic_number != 0xDEADBEEF || decoded_msg.test_array.count != 3) { + if (decoded_msg.magic_number != 3735928559 || decoded_msg.test_array.count != 3) { print_failure_details("Self-verification failed", encode_buffer, buffer.size); return 0; } @@ -74,45 +76,13 @@ int create_test_data() { return 1; } -int read_test_data(const char* filename, const char* language) { - FILE* file = fopen(filename, "rb"); - if (!file) { - return 1; // Skip if file not available - } - - uint8_t buffer[512]; - size_t size = fread(buffer, 1, sizeof(buffer), file); - fclose(file); - - if (size == 0) { - print_failure_details("Empty file", buffer, size); - return 0; - } - - packet_format_t* format = &default_frame_format; - msg_info_t decode_result = format->validate_packet(buffer, size); - - if (!decode_result.valid) { - char label[256]; - snprintf(label, sizeof(label), "Failed to decode %s data", language); - print_failure_details(label, buffer, size); - return 0; - } - - return 1; -} - int main() { - printf("\n[TEST START] C Cross-Language Serialization\n"); + printf("\n[TEST START] C Cross-Platform Serialization\n"); int success = create_test_data(); - if (success) { - success = success && read_test_data("python_test_data.bin", "Python"); - success = success && read_test_data("typescript_test_data.bin", "TypeScript"); - } const char* status = success ? "PASS" : "FAIL"; - printf("[TEST END] C Cross-Language Serialization: %s\n\n", status); + printf("[TEST END] C Cross-Platform Serialization: %s\n\n", status); return success ? 0 : 1; } diff --git a/tests/cpp/test_arrays.cpp b/tests/cpp/test_arrays.cpp index 9f3c440d..e4fd9ca8 100644 --- a/tests/cpp/test_arrays.cpp +++ b/tests/cpp/test_arrays.cpp @@ -31,6 +31,7 @@ int main() { return 1; } + // Encode message into BasicPacket format uint8_t buffer[1024]; StructFrame::BasicPacket format; StructFrame::EncodeBuffer encoder(buffer, sizeof(buffer)); @@ -41,6 +42,13 @@ int main() { return 1; } + // Verify encoding produced data + if (encoder.size() == 0) { + print_failure_details("Encoded data is empty"); + std::cout << "[TEST END] C++ Array Operations: FAIL\n\n"; + return 1; + } + std::cout << "[TEST END] C++ Array Operations: PASS\n\n"; return 0; diff --git a/tests/cpp/test_basic_types.cpp b/tests/cpp/test_basic_types.cpp index ad10d892..7694526f 100644 --- a/tests/cpp/test_basic_types.cpp +++ b/tests/cpp/test_basic_types.cpp @@ -48,6 +48,7 @@ int main() { return 1; } + // Encode message into BasicPacket format uint8_t buffer[512]; StructFrame::BasicPacket format; StructFrame::EncodeBuffer encoder(buffer, sizeof(buffer)); @@ -58,6 +59,20 @@ int main() { return 1; } + // Verify encoding produced data + if (encoder.size() == 0) { + print_failure_details("Encoded data is empty", buffer, encoder.size()); + std::cout << "[TEST END] C++ Basic Types: FAIL\n\n"; + return 1; + } + + // Verify minimum packet structure (start byte + msg_id + data + checksums) + if (encoder.size() < 4) { + print_failure_details("Encoded data too small", buffer, encoder.size()); + std::cout << "[TEST END] C++ Basic Types: FAIL\n\n"; + return 1; + } + std::cout << "[TEST END] C++ Basic Types: PASS\n\n"; return 0; diff --git a/tests/cpp/test_cross_platform_deserialization.cpp b/tests/cpp/test_cross_platform_deserialization.cpp new file mode 100644 index 00000000..e6d45820 --- /dev/null +++ b/tests/cpp/test_cross_platform_deserialization.cpp @@ -0,0 +1,90 @@ +#include "serialization_test.sf.hpp" +#include +#include +#include +#include + +void print_failure_details(const char* label) { + std::cout << "\n============================================================\n"; + std::cout << "FAILURE DETAILS: " << label << "\n"; + std::cout << "============================================================\n\n"; +} + +bool validate_basic_frame(const uint8_t* buffer, size_t size, const char* language, uint32_t expected_magic) { + // Very basic frame validation + if (size < 4) { + std::cout << " " << language << " data too short\n"; + return false; + } + + // Check start byte (BasicPacket uses 0x90) + if (buffer[0] != 0x90) { + std::cout << " " << language << " invalid start byte\n"; + return false; + } + + // Check message ID (SerializationTestMessage is 204) + if (buffer[1] != 204) { + std::cout << " " << language << " invalid message ID\n"; + return false; + } + + // Extract magic number from payload (starts at byte 2, little-endian) + if (size < 6) { + std::cout << " " << language << " data too short for magic number\n"; + return false; + } + + uint32_t magic_number = buffer[2] | (buffer[3] << 8) | (buffer[4] << 16) | (buffer[5] << 24); + if (magic_number != expected_magic) { + std::cout << " " << language << " magic number mismatch (expected " << expected_magic + << ", got " << magic_number << ")\n"; + return false; + } + + std::cout << " āœ“ " << language << " data validated successfully\n"; + return true; +} + +bool read_and_validate_test_data(const char* filename, const char* language) { + std::ifstream file(filename, std::ios::binary); + if (!file) { + std::cout << " Skipping " << language << " - file not found: " << filename << "\n"; + return true; // Skip if file not available + } + + uint8_t buffer[512]; + file.read(reinterpret_cast(buffer), sizeof(buffer)); + size_t size = file.gcount(); + file.close(); + + if (size == 0) { + print_failure_details("Empty file"); + return false; + } + + // Expected values from expected_values.json + uint32_t expected_magic = 3735928559; // 0xDEADBEEF + + if (!validate_basic_frame(buffer, size, language, expected_magic)) { + std::cout << " Validation failed for " << language << " data\n"; + return false; + } + + return true; +} + +int main() { + std::cout << "\n[TEST START] C++ Cross-Platform Deserialization\n"; + + bool success = true; + success = success && read_and_validate_test_data("python_test_data.bin", "Python"); + success = success && read_and_validate_test_data("c_test_data.bin", "C"); + success = success && read_and_validate_test_data("cpp_test_data.bin", "C++"); + success = success && read_and_validate_test_data("typescript_test_data.bin", "TypeScript"); + + std::cout << "[TEST END] C++ Cross-Platform Deserialization: " + << (success ? "PASS" : "FAIL") << "\n\n"; + + return success ? 0 : 1; +} diff --git a/tests/cpp/test_serialization.cpp b/tests/cpp/test_cross_platform_serialization.cpp similarity index 74% rename from tests/cpp/test_serialization.cpp rename to tests/cpp/test_cross_platform_serialization.cpp index 6620bbd5..88a10ae1 100644 --- a/tests/cpp/test_serialization.cpp +++ b/tests/cpp/test_cross_platform_serialization.cpp @@ -10,13 +10,14 @@ void print_failure_details(const char* label) { } int main() { - std::cout << "\n[TEST START] C++ Cross-Language Serialization\n"; + std::cout << "\n[TEST START] C++ Cross-Platform Serialization\n"; try { SerializationTestSerializationTestMessage msg{}; - msg.magic_number = 0xDEADBEEF; - msg.test_string.length = 15; - std::strncpy(msg.test_string.data, "Hello from C++!", sizeof(msg.test_string.data)); + // Use values from expected_values.json + msg.magic_number = 3735928559; // 0xDEADBEEF + msg.test_string.length = 20; + std::strncpy(msg.test_string.data, "Cross-platform test!", sizeof(msg.test_string.data)); msg.test_float = 3.14159f; msg.test_bool = true; msg.test_array.count = 3; @@ -27,7 +28,7 @@ int main() { size_t msg_size = 0; if (!StructFrame::get_message_length(SERIALIZATION_TEST_SERIALIZATION_TEST_MESSAGE_MSG_ID, &msg_size)) { print_failure_details("Failed to get message length"); - std::cout << "[TEST END] C++ Cross-Language Serialization: FAIL\n\n"; + std::cout << "[TEST END] C++ Cross-Platform Serialization: FAIL\n\n"; return 1; } @@ -37,25 +38,25 @@ int main() { if (!encoder.encode(&format, SERIALIZATION_TEST_SERIALIZATION_TEST_MESSAGE_MSG_ID, &msg, msg_size)) { print_failure_details("Failed to encode message"); - std::cout << "[TEST END] C++ Cross-Language Serialization: FAIL\n\n"; + std::cout << "[TEST END] C++ Cross-Platform Serialization: FAIL\n\n"; return 1; } std::ofstream file("cpp_test_data.bin", std::ios::binary); if (!file) { print_failure_details("Failed to create test data file"); - std::cout << "[TEST END] C++ Cross-Language Serialization: FAIL\n\n"; + std::cout << "[TEST END] C++ Cross-Platform Serialization: FAIL\n\n"; return 1; } file.write(reinterpret_cast(buffer), encoder.size()); file.close(); - std::cout << "[TEST END] C++ Cross-Language Serialization: PASS\n\n"; + std::cout << "[TEST END] C++ Cross-Platform Serialization: PASS\n\n"; return 0; } catch (const std::exception& e) { print_failure_details(e.what()); - std::cout << "[TEST END] C++ Cross-Language Serialization: FAIL\n\n"; + std::cout << "[TEST END] C++ Cross-Platform Serialization: FAIL\n\n"; return 1; } } diff --git a/tests/cross_platform_test.py b/tests/cross_platform_test.py deleted file mode 100755 index d0f243f0..00000000 --- a/tests/cross_platform_test.py +++ /dev/null @@ -1,380 +0,0 @@ -#!/usr/bin/env python3 -""" -Cross-Platform Test Script for struct-frame - -This script tests cross-language compatibility by piping binary data between -different language implementations. Each language encodes a test message and -pipes it to decoders in all other languages (including itself) via stdin/stdout. - -Test Modes: -1. Struct-based tests (no framing) - Currently not fully supported due to - limitations in variable-length field serialization -2. Framed tests - Uses packet framing with headers and checksums - -Languages tested: C, Python, TypeScript (if available) -""" - -import sys -import os -import subprocess -from pathlib import Path -from typing import List, Tuple, Optional - - -class CrossPlatformTest: - def __init__(self, verbose=False): - self.verbose = verbose - self.tests_dir = Path(__file__).parent - self.c_dir = self.tests_dir / "c" - self.py_dir = self.tests_dir / "py" - self.ts_dir = self.tests_dir / "generated" / "ts" / "js" - self.gen_py_dir = self.tests_dir / "generated" / "py" - - # Test results - self.results = {} - self.total_tests = 0 - self.passed_tests = 0 - self.failed_tests = 0 - - def log(self, message, level="INFO"): - """Log a message""" - prefix = { - "INFO": "[INFO] ", - "ERROR": "[ERROR]", - "SUCCESS": "[OK]", - "WARNING": "[WARN] ", - "SKIP": "[SKIP] " - }.get(level, " ") - print(f"{prefix} {message}") - - def run_encoder(self, language: str, framed: bool = True) -> Optional[bytes]: - """Run an encoder and capture its binary output""" - if framed: - mode = "framed" - else: - mode = "struct" - - env = os.environ.copy() - - if language == "c": - encoder_path = self.c_dir / f"encoder_{mode}" - # On Windows, check for .exe extension if the file without extension doesn't exist - if os.name == 'nt' and not encoder_path.exists(): - encoder_path = self.c_dir / f"encoder_{mode}.exe" - if not encoder_path.exists(): - self.log(f"C encoder not found: {encoder_path}", "ERROR") - return None - cmd = [str(encoder_path)] - cwd = self.c_dir - elif language == "python": - encoder_path = self.py_dir / f"encoder_{mode}.py" - if not encoder_path.exists(): - self.log(f"Python encoder not found: {encoder_path}", "ERROR") - return None - # Set PYTHONPATH to include generated code - env["PYTHONPATH"] = str(self.gen_py_dir) - cmd = [sys.executable, str(encoder_path)] - cwd = self.py_dir - elif language == "typescript": - encoder_path = self.ts_dir / f"encoder_{mode}.js" - if not encoder_path.exists(): - self.log( - f"TypeScript encoder not found: {encoder_path}", "ERROR") - return None - cmd = ["node", str(encoder_path)] - cwd = self.ts_dir - else: - self.log(f"Unknown language: {language}", "ERROR") - return None - - try: - result = subprocess.run( - cmd, cwd=cwd, capture_output=True, timeout=5, env=env - ) - - if result.returncode != 0: - self.log(f"{language.capitalize()} encoder failed", "ERROR") - if result.stderr: - print( - f" Error: {result.stderr.decode('utf-8', errors='replace')}") - return None - - return result.stdout - except subprocess.TimeoutExpired: - self.log(f"{language.capitalize()} encoder timed out", "ERROR") - return None - except Exception as e: - self.log(f"{language.capitalize()} encoder exception: {e}", "ERROR") - return None - - def run_decoder(self, language: str, binary_data: bytes, framed: bool = True) -> Tuple[bool, Optional[dict]]: - """Run a decoder with binary input and capture its output""" - if framed: - mode = "framed" - else: - mode = "struct" - - env = os.environ.copy() - - if language == "c": - decoder_path = self.c_dir / f"decoder_{mode}" - # On Windows, check for .exe extension if the file without extension doesn't exist - if os.name == 'nt' and not decoder_path.exists(): - decoder_path = self.c_dir / f"decoder_{mode}.exe" - if not decoder_path.exists(): - self.log(f"C decoder not found: {decoder_path}", "ERROR") - return False, None - cmd = [str(decoder_path)] - cwd = self.c_dir - elif language == "python": - decoder_path = self.py_dir / f"decoder_{mode}.py" - if not decoder_path.exists(): - self.log(f"Python decoder not found: {decoder_path}", "ERROR") - return False, None - env["PYTHONPATH"] = str(self.gen_py_dir) - cmd = [sys.executable, str(decoder_path)] - cwd = self.py_dir - elif language == "typescript": - decoder_path = self.ts_dir / f"decoder_{mode}.js" - if not decoder_path.exists(): - self.log( - f"TypeScript decoder not found: {decoder_path}", "ERROR") - return False, None - cmd = ["node", str(decoder_path)] - cwd = self.ts_dir - else: - self.log(f"Unknown language: {language}", "ERROR") - return False, None - - try: - result = subprocess.run( - cmd, input=binary_data, cwd=cwd, - capture_output=True, timeout=5, env=env - ) - - if result.returncode != 0: - self.log(f"{language.capitalize()} decoder failed", "ERROR") - if result.stderr: - print( - f" Error: {result.stderr.decode('utf-8', errors='replace')}") - return False, None - - # Parse the output - decoded_data = {} - for line in result.stdout.decode('utf-8').strip().split('\n'): - if '=' in line: - key, value = line.split('=', 1) - decoded_data[key] = value - - return True, decoded_data - except subprocess.TimeoutExpired: - self.log(f"{language.capitalize()} decoder timed out", "ERROR") - return False, None - except Exception as e: - self.log(f"{language.capitalize()} decoder exception: {e}", "ERROR") - return False, None - - def verify_decoded_data(self, decoded_data: dict) -> bool: - """Verify that decoded data matches expected values""" - expected = { - "magic_number": "0xDEADBEEF", - "test_float": "3.14159", - # Different languages format differently - "test_bool": ["True", "true", "1"], - } - - for key, expected_value in expected.items(): - if key not in decoded_data: - self.log(f"Missing field: {key}", "ERROR") - return False - - # Clean up whitespace and newlines from decoded values - actual_value = decoded_data[key].strip() - - if isinstance(expected_value, list): - if actual_value not in expected_value: - self.log( - f"Field {key} mismatch: got '{actual_value}', expected one of {expected_value}", "ERROR") - return False - else: - if actual_value != expected_value: - self.log( - f"Field {key} mismatch: got '{actual_value}', expected '{expected_value}'", "ERROR") - return False - - return True - - def test_cross_platform(self, encoder_lang: str, decoder_lang: str, framed: bool = True): - """Test encoding in one language and decoding in another""" - mode = "framed" if framed else "struct" - test_name = f"{encoder_lang}→{decoder_lang} ({mode})" - self.total_tests += 1 - - if self.verbose: - self.log(f"Testing {test_name}...") - - # Encode - binary_data = self.run_encoder(encoder_lang, framed) - if binary_data is None: - self.log(f"{test_name}: Encoder failed", "ERROR") - self.failed_tests += 1 - self.results[test_name] = False - return False - - if self.verbose: - self.log(f"Encoded {len(binary_data)} bytes", "INFO") - self.log(f"Binary data: {binary_data.hex()}", "INFO") - - # Decode - success, decoded_data = self.run_decoder( - decoder_lang, binary_data, framed) - if not success: - self.log(f"{test_name}: Decoder failed", "ERROR") - # Print debugging information on failure - if self.verbose: - print(f"\n šŸ” Failure Details:") - print(f" Encoded by: {encoder_lang}") - print(f" Decoded by: {decoder_lang}") - print(f" Raw data length: {len(binary_data)} bytes") - print(f" Raw data (hex): {binary_data.hex()}") - if binary_data: - print(f" Raw data (bytes): {list(binary_data)}") - self.failed_tests += 1 - self.results[test_name] = False - return False - - # Verify - if decoded_data and self.verify_decoded_data(decoded_data): - if self.verbose: - self.log(f"{test_name}: PASS", "SUCCESS") - self.log(f"Decoded data: {decoded_data}", "INFO") - self.passed_tests += 1 - self.results[test_name] = True - return True - else: - self.log(f"{test_name}: Verification failed", "ERROR") - # Print debugging information on verification failure - if self.verbose: - print(f"\n šŸ” Verification Failure Details:") - print(f" Encoded by: {encoder_lang}") - print(f" Decoded by: {decoder_lang}") - print(f" Decoded data: {decoded_data}") - print(f" Raw data length: {len(binary_data)} bytes") - print(f" Raw data (hex): {binary_data.hex()}") - if binary_data: - print(f" Raw data (bytes): {list(binary_data)}") - self.failed_tests += 1 - self.results[test_name] = False - return False - - def check_language_available(self, language: str, mode: str = "framed") -> bool: - """Check if encoder/decoder for a language are available""" - - if language == "c": - encoder = self.c_dir / f"encoder_{mode}" - decoder = self.c_dir / f"decoder_{mode}" - # On Windows, also check for .exe extensions - if os.name == 'nt': - if not encoder.exists(): - encoder = self.c_dir / f"encoder_{mode}.exe" - if not decoder.exists(): - decoder = self.c_dir / f"decoder_{mode}.exe" - elif language == "python": - encoder = self.py_dir / f"encoder_{mode}.py" - decoder = self.py_dir / f"decoder_{mode}.py" - elif language == "typescript": - encoder = self.ts_dir / f"encoder_{mode}.js" - decoder = self.ts_dir / f"decoder_{mode}.js" - else: - return False - - return encoder.exists() and decoder.exists() - - def run_all_tests(self, test_struct=True, test_framed=True): - """Run all cross-platform tests""" - # Detect available languages - available_languages = [] - for lang in ["c", "python", "typescript"]: - if self.check_language_available(lang, "framed"): - available_languages.append(lang) - self.log( - f"{lang.capitalize()} encoder/decoder available", "SUCCESS") - else: - if self.verbose: - self.log( - f"{lang.capitalize()} encoder/decoder not available", "SKIP") - - if len(available_languages) == 0: - self.log("No languages available for testing!", "ERROR") - return False - - # Test struct mode (no framing) - currently not implemented - if test_struct: - if self.verbose: - print("\nStruct-based tests: NOT IMPLEMENTED") - self.log("Struct-based tests are currently not implemented", "WARNING") - self.log( - "This is due to encoder/decoder implementations not being complete", "WARNING") - self.log("for all languages in struct mode.", "WARNING") - - # Test framed mode - if test_framed: - # Test each language encoding to all languages - for encoder_lang in available_languages: - for decoder_lang in available_languages: - self.test_cross_platform( - encoder_lang, decoder_lang, framed=True) - - # Print summary - print() - if self.total_tests > 0: - pass_rate = (self.passed_tests / self.total_tests) * 100 - print(f"Pipe tests: {self.passed_tests}/{self.total_tests} passed ({pass_rate:.1f}%)") - - if pass_rate == 100: - return True - elif pass_rate >= 80: - return True - else: - return False - else: - print("NO TESTS RUN") - return False - - -def main(): - import argparse - parser = argparse.ArgumentParser( - description="Run cross-platform pipe tests for struct-frame" - ) - parser.add_argument( - "--verbose", "-v", - action="store_true", - help="Enable verbose output" - ) - parser.add_argument( - "--struct-only", - action="store_true", - help="Only test struct mode (no framing)" - ) - parser.add_argument( - "--framed-only", - action="store_true", - help="Only test framed mode" - ) - - args = parser.parse_args() - - tester = CrossPlatformTest(verbose=args.verbose) - - test_struct = not args.framed_only - test_framed = not args.struct_only - - success = tester.run_all_tests( - test_struct=test_struct, test_framed=test_framed) - - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main() diff --git a/tests/expected_values.json b/tests/expected_values.json new file mode 100644 index 00000000..e1a41785 --- /dev/null +++ b/tests/expected_values.json @@ -0,0 +1,10 @@ +{ + "serialization_test": { + "magic_number": 3735928559, + "magic_number_hex": "0xDEADBEEF", + "test_string": "Cross-platform test!", + "test_float": 3.14159, + "test_bool": true, + "test_array": [100, 200, 300] + } +} diff --git a/tests/py/decoder_framed.py b/tests/py/decoder_framed.py deleted file mode 100755 index a38568a9..00000000 --- a/tests/py/decoder_framed.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -""" -Decoder for cross-platform testing with framing. -Reads framed SerializationTestMessage from stdin and decodes it. -""" - -import sys -import os - -# Add generated code to path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../generated/py')) - -try: - from serialization_test_sf import SerializationTestSerializationTestMessage - from struct_frame_parser import BasicPacket, FrameParser - - # Read binary data from stdin - binary_data = sys.stdin.buffer.read() - - if len(binary_data) == 0: - sys.stderr.write("ERROR: No data received from stdin\n") - sys.exit(1) - - # Create parser for deserialization - packet_formats = {0x90: BasicPacket()} - msg_definitions = {204: SerializationTestSerializationTestMessage} - parser = FrameParser(packet_formats, msg_definitions) - - # Parse the binary data byte by byte - msg = None - for byte in binary_data: - result = parser.parse_char(byte) - if result: - msg = result - break - - if msg is None: - sys.stderr.write("ERROR: Failed to decode message\n") - sys.exit(1) - - # Print decoded values in a consistent format - print(f"magic_number=0x{msg.magic_number:08X}") - print(f"test_string={msg.test_string}") - print(f"test_float={msg.test_float:.5f}") - print(f"test_bool={msg.test_bool}") - print(f"test_array={','.join(map(str, msg.test_array))}") - -except ImportError as e: - sys.stderr.write(f"ERROR: Generated modules not found: {e}\n") - sys.exit(1) -except Exception as e: - sys.stderr.write(f"ERROR: Failed to decode: {e}\n") - import traceback - traceback.print_exc(file=sys.stderr) - sys.exit(1) diff --git a/tests/py/decoder_struct.py b/tests/py/decoder_struct.py deleted file mode 100755 index 3ab59f97..00000000 --- a/tests/py/decoder_struct.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -""" -Decoder for cross-platform struct testing (no framing). -Reads raw struct bytes from stdin and decodes SerializationTestMessage. -""" - -import sys -import os - -# Add generated code to path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../generated/py')) - -try: - from serialization_test_sf import SerializationTestSerializationTestMessage - - # Read binary data from stdin - binary_data = sys.stdin.buffer.read() - - if len(binary_data) == 0: - sys.stderr.write("ERROR: No data received from stdin\n") - sys.exit(1) - - # Deserialize from struct bytes (no framing) using create_unpack() - msg = SerializationTestSerializationTestMessage.create_unpack(binary_data) - - # Print decoded values in a consistent format - print(f"magic_number=0x{msg.magic_number:08X}") - print(f"test_string={msg.test_string}") - print(f"test_float={msg.test_float:.5f}") - print(f"test_bool={msg.test_bool}") - print(f"test_array={','.join(map(str, msg.test_array))}") - -except ImportError as e: - sys.stderr.write(f"ERROR: Generated modules not found: {e}\n") - sys.exit(1) -except Exception as e: - sys.stderr.write(f"ERROR: Failed to decode: {e}\n") - import traceback - traceback.print_exc(file=sys.stderr) - sys.exit(1) diff --git a/tests/py/encoder_framed.py b/tests/py/encoder_framed.py deleted file mode 100755 index 47667916..00000000 --- a/tests/py/encoder_framed.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -""" -Encoder for cross-platform testing with framing. -Encodes a SerializationTestMessage with framing and writes to stdout. -""" - -import sys -import os - -# Add generated code to path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../generated/py')) - -try: - from serialization_test_sf import SerializationTestSerializationTestMessage - from struct_frame_parser import BasicPacket - - # Create test message with known values - # The new struct-based generator uses direct values instead of wrapper classes - msg = SerializationTestSerializationTestMessage( - magic_number=0xDEADBEEF, - test_string=b"Hello from Python!", - test_float=3.14159, - test_bool=True, - test_array=[100, 200, 300] - ) - - # Serialize with framing using BasicPacket - packet = BasicPacket() - encoded_data = packet.encode_msg(msg) - - # Write binary data to stdout - sys.stdout.buffer.write(bytes(encoded_data)) - sys.stdout.buffer.flush() - -except ImportError as e: - sys.stderr.write(f"ERROR: Generated modules not found: {e}\n") - sys.exit(1) -except Exception as e: - sys.stderr.write(f"ERROR: Failed to encode: {e}\n") - import traceback - traceback.print_exc(file=sys.stderr) - sys.exit(1) diff --git a/tests/py/encoder_struct.py b/tests/py/encoder_struct.py deleted file mode 100755 index 87d9e501..00000000 --- a/tests/py/encoder_struct.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -""" -Encoder for cross-platform struct testing (no framing). -Encodes a SerializationTestMessage and writes raw struct bytes to stdout. -""" - -import sys -import os - -# Add generated code to path -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../generated/py')) - -try: - from serialization_test_sf import SerializationTestSerializationTestMessage - - # Create test message with known values - # The new struct-based generator uses bytes for strings - msg = SerializationTestSerializationTestMessage( - magic_number=0xDEADBEEF, - test_string=b"Hello from Python!", - test_float=3.14159, - test_bool=True, - test_array=[100, 200, 300] - ) - - # Serialize to struct bytes (no framing) using pack() - struct_bytes = msg.pack() - - # Write binary data to stdout - sys.stdout.buffer.write(bytes(struct_bytes)) - sys.stdout.buffer.flush() - -except ImportError as e: - sys.stderr.write(f"ERROR: Generated modules not found: {e}\n") - sys.exit(1) -except Exception as e: - sys.stderr.write(f"ERROR: Failed to encode: {e}\n") - import traceback - traceback.print_exc(file=sys.stderr) - sys.exit(1) diff --git a/tests/py/test_arrays.py b/tests/py/test_arrays.py index e2fe6d77..6432f617 100755 --- a/tests/py/test_arrays.py +++ b/tests/py/test_arrays.py @@ -31,7 +31,7 @@ def print_failure_details(label, expected_values=None, actual_values=None, raw_d def test_array_operations(): - """Test array operations serialization and deserialization""" + """Test array operations serialization and deserialization with BasicPacket""" try: sys.path.insert(0, '../generated/py') from comprehensive_arrays_sf import ( @@ -63,6 +63,7 @@ def test_array_operations(): sensor1, bounded_sensors ) + # Encode message into BasicPacket format packet = BasicPacket() encoded_data = packet.encode_msg(msg) @@ -74,6 +75,52 @@ def test_array_operations(): ) return False + # Decode the BasicPacket back into a message + packet_formats = {0x90: BasicPacket()} + msg_definitions = {203: ComprehensiveArraysComprehensiveArrayMessage} + parser = FrameParser(packet_formats, msg_definitions) + + decoded_msg = None + for byte in encoded_data: + result = parser.parse_char(byte) + if result: + decoded_msg = result + break + + if not decoded_msg: + print_failure_details( + "Failed to decode message", + expected_values={"decoded_message": "valid"}, + actual_values={"decoded_message": None}, + raw_data=encoded_data + ) + return False + + # Compare original and decoded messages + if decoded_msg.fixed_uints != msg.fixed_uints: + print_failure_details( + "Value mismatch: fixed_uints", + expected_values={"fixed_uints": msg.fixed_uints}, + actual_values={"fixed_uints": decoded_msg.fixed_uints} + ) + return False + + if decoded_msg.bounded_uints.count != msg.bounded_uints.count: + print_failure_details( + "Value mismatch: bounded_uints.count", + expected_values={"count": msg.bounded_uints.count}, + actual_values={"count": decoded_msg.bounded_uints.count} + ) + return False + + if decoded_msg.sensor.id != msg.sensor.id: + print_failure_details( + "Value mismatch: sensor.id", + expected_values={"sensor.id": msg.sensor.id}, + actual_values={"sensor.id": decoded_msg.sensor.id} + ) + return False + return True except ImportError: diff --git a/tests/py/test_basic_types.py b/tests/py/test_basic_types.py index ac881af4..299c006e 100755 --- a/tests/py/test_basic_types.py +++ b/tests/py/test_basic_types.py @@ -31,11 +31,11 @@ def print_failure_details(label, msg, expected_values=None, actual_values=None, def test_basic_types(): - """Test basic data types serialization and deserialization""" + """Test basic data types serialization and deserialization with BasicPacket""" try: sys.path.insert(0, '../generated/py') from basic_types_sf import BasicTypesBasicTypesMessage, _VariableString_description - from struct_frame_parser import BasicPacket + from struct_frame_parser import BasicPacket, FrameParser desc_text = b"Test description for basic types" description = _VariableString_description( @@ -51,6 +51,7 @@ def test_basic_types(): description ) + # Encode message into BasicPacket format packet = BasicPacket() encoded_data = packet.encode_msg(msg) @@ -64,6 +65,52 @@ def test_basic_types(): ) return False + # Decode the BasicPacket back into a message + packet_formats = {0x90: BasicPacket()} + msg_definitions = {201: BasicTypesBasicTypesMessage} + parser = FrameParser(packet_formats, msg_definitions) + + decoded_msg = None + for byte in encoded_data: + result = parser.parse_char(byte) + if result: + decoded_msg = result + break + + if not decoded_msg: + print_failure_details( + "Failed to decode message", + expected_values={"decoded_message": "valid"}, + actual_values={"decoded_message": None}, + raw_data=encoded_data + ) + return False + + # Compare original and decoded messages + if decoded_msg.small_int != msg.small_int: + print_failure_details( + "Value mismatch: small_int", + expected_values={"small_int": msg.small_int}, + actual_values={"small_int": decoded_msg.small_int} + ) + return False + + if decoded_msg.medium_int != msg.medium_int: + print_failure_details( + "Value mismatch: medium_int", + expected_values={"medium_int": msg.medium_int}, + actual_values={"medium_int": decoded_msg.medium_int} + ) + return False + + if decoded_msg.flag != msg.flag: + print_failure_details( + "Value mismatch: flag", + expected_values={"flag": msg.flag}, + actual_values={"flag": decoded_msg.flag} + ) + return False + return True except ImportError: diff --git a/tests/py/test_cross_platform_deserialization.py b/tests/py/test_cross_platform_deserialization.py new file mode 100644 index 00000000..904f7c95 --- /dev/null +++ b/tests/py/test_cross_platform_deserialization.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +""" +Test script for cross-platform deserialization - reads and validates test data from files. +This test reads binary files created by different language implementations and validates +them against expected_values.json. +""" + +import sys +import os +import json + + +def print_failure_details(label, expected_values=None, actual_values=None, raw_data=None): + """Print detailed failure information""" + print(f"\n{'='*60}") + print(f"FAILURE DETAILS: {label}") + print(f"{'='*60}") + + if expected_values: + print("\nExpected Values:") + for key, val in expected_values.items(): + print(f" {key}: {val}") + + if actual_values: + print("\nActual Values:") + for key, val in actual_values.items(): + print(f" {key}: {val}") + + if raw_data: + print(f"\nRaw Data ({len(raw_data)} bytes):") + print(f" Hex: {raw_data.hex()}") + + print(f"{'='*60}\n") + + +def load_expected_values(): + """Load expected values from JSON file""" + json_path = os.path.join(os.path.dirname(__file__), '..', 'expected_values.json') + try: + with open(json_path, 'r') as f: + data = json.load(f) + return data['serialization_test'] + except Exception as e: + print(f"Error loading expected values: {e}") + return None + + +def validate_message(msg, expected): + """Validate a decoded message against expected values""" + if msg.magic_number != expected['magic_number']: + print_failure_details( + "Value mismatch: magic_number", + expected_values={"magic_number": expected['magic_number']}, + actual_values={"magic_number": msg.magic_number} + ) + return False + + # Compare string (bytes vs string) + test_string = msg.test_string.decode('utf-8').rstrip('\x00') if isinstance(msg.test_string, bytes) else str(msg.test_string).rstrip('\x00') + if test_string != expected['test_string']: + print_failure_details( + "Value mismatch: test_string", + expected_values={"test_string": expected['test_string']}, + actual_values={"test_string": test_string} + ) + return False + + # Compare float with tolerance + if abs(msg.test_float - expected['test_float']) > 0.0001: + print_failure_details( + "Value mismatch: test_float", + expected_values={"test_float": expected['test_float']}, + actual_values={"test_float": msg.test_float} + ) + return False + + if msg.test_bool != expected['test_bool']: + print_failure_details( + "Value mismatch: test_bool", + expected_values={"test_bool": expected['test_bool']}, + actual_values={"test_bool": msg.test_bool} + ) + return False + + # Compare array + if list(msg.test_array) != expected['test_array']: + print_failure_details( + "Value mismatch: test_array", + expected_values={"test_array": expected['test_array']}, + actual_values={"test_array": list(msg.test_array)} + ) + return False + + return True + + +def read_and_validate_test_data(filename, language): + """Read and validate test data created by a specific language""" + try: + if not os.path.exists(filename): + print(f" Skipping {language} - file not found: {filename}") + return True # Skip if file not available + + with open(filename, 'rb') as f: + binary_data = f.read() + + if len(binary_data) == 0: + print_failure_details( + f"Empty data from {language}", + expected_values={"data_size": ">0"}, + actual_values={"data_size": 0}, + raw_data=binary_data + ) + return False + + sys.path.insert(0, '../generated/py') + from serialization_test_sf import SerializationTestSerializationTestMessage + from struct_frame_parser import BasicPacket, FrameParser + + packet_formats = {0x90: BasicPacket()} + msg_definitions = {204: SerializationTestSerializationTestMessage} + parser = FrameParser(packet_formats, msg_definitions) + + decoded_msg = None + for byte in binary_data: + result = parser.parse_char(byte) + if result: + decoded_msg = result + break + + if not decoded_msg: + print_failure_details( + f"Failed to decode {language} data", + expected_values={"decoded_message": "valid"}, + actual_values={"decoded_message": None}, + raw_data=binary_data + ) + return False + + # Load expected values and validate + expected = load_expected_values() + if not expected: + return False + + if not validate_message(decoded_msg, expected): + print(f" Validation failed for {language} data") + return False + + print(f" āœ“ {language} data validated successfully") + return True + + except ImportError: + return True # Skip if generated code not available + + except Exception as e: + print_failure_details( + f"Read {language} data exception: {type(e).__name__}", + expected_values={"result": "success"}, + actual_values={"exception": str(e)} + ) + import traceback + traceback.print_exc() + return False + + +def main(): + """Main test function""" + print("\n[TEST START] Python Cross-Platform Deserialization") + + success = True + success = success and read_and_validate_test_data('python_test_data.bin', 'Python') + success = success and read_and_validate_test_data('c_test_data.bin', 'C') + success = success and read_and_validate_test_data('cpp_test_data.bin', 'C++') + success = success and read_and_validate_test_data('typescript_test_data.bin', 'TypeScript') + + status = "PASS" if success else "FAIL" + print(f"[TEST END] Python Cross-Platform Deserialization: {status}\n") + + return success + + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) diff --git a/tests/py/test_cross_platform_serialization.py b/tests/py/test_cross_platform_serialization.py new file mode 100755 index 00000000..9aac1665 --- /dev/null +++ b/tests/py/test_cross_platform_serialization.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +""" +Test script for cross-platform serialization - writes test data to file. +This test populates a message from expected_values.json and writes it to a binary file. +""" + +import sys +import os +import json + + +def print_failure_details(label, expected_values=None, actual_values=None, raw_data=None): + """Print detailed failure information""" + print(f"\n{'='*60}") + print(f"FAILURE DETAILS: {label}") + print(f"{'='*60}") + + if expected_values: + print("\nExpected Values:") + for key, val in expected_values.items(): + print(f" {key}: {val}") + + if actual_values: + print("\nActual Values:") + for key, val in actual_values.items(): + print(f" {key}: {val}") + + if raw_data: + print(f"\nRaw Data ({len(raw_data)} bytes):") + print(f" Hex: {raw_data.hex()}") + + print(f"{'='*60}\n") + + +def load_expected_values(): + """Load expected values from JSON file""" + json_path = os.path.join(os.path.dirname(__file__), '..', 'expected_values.json') + try: + with open(json_path, 'r') as f: + data = json.load(f) + return data['serialization_test'] + except Exception as e: + print(f"Error loading expected values: {e}") + return None + + +def create_test_data(): + """Create test data for cross-platform compatibility testing""" + try: + sys.path.insert(0, '../generated/py') + from serialization_test_sf import SerializationTestSerializationTestMessage + from struct_frame_parser import BasicPacket + + # Load expected values from JSON + expected = load_expected_values() + if not expected: + return False + + # Create test message using values from JSON + msg = SerializationTestSerializationTestMessage( + magic_number=expected['magic_number'], + test_string=expected['test_string'].encode('utf-8'), + test_float=expected['test_float'], + test_bool=expected['test_bool'], + test_array=expected['test_array'] + ) + + packet = BasicPacket() + encoded_data = packet.encode_msg(msg) + + with open('python_test_data.bin', 'wb') as f: + f.write(bytes(encoded_data)) + + return True + + except ImportError: + return True # Skip if generated code not available + + except Exception as e: + print_failure_details( + f"Create test data exception: {type(e).__name__}", + expected_values={"result": "success"}, + actual_values={"exception": str(e)} + ) + import traceback + traceback.print_exc() + return False + + +def main(): + """Main test function""" + print("\n[TEST START] Python Cross-Platform Serialization") + + success = create_test_data() + + status = "PASS" if success else "FAIL" + print(f"[TEST END] Python Cross-Platform Serialization: {status}\n") + + return success + + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) diff --git a/tests/py/test_serialization.py b/tests/py/test_serialization.py deleted file mode 100755 index c251af0b..00000000 --- a/tests/py/test_serialization.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for cross-language serialization compatibility in Python. -""" - -import sys -import os - - -def print_failure_details(label, expected_values=None, actual_values=None, raw_data=None): - """Print detailed failure information""" - print(f"\n{'='*60}") - print(f"FAILURE DETAILS: {label}") - print(f"{'='*60}") - - if expected_values: - print("\nExpected Values:") - for key, val in expected_values.items(): - print(f" {key}: {val}") - - if actual_values: - print("\nActual Values:") - for key, val in actual_values.items(): - print(f" {key}: {val}") - - if raw_data: - print(f"\nRaw Data ({len(raw_data)} bytes):") - print(f" Hex: {raw_data.hex()}") - - print(f"{'='*60}\n") - - -def create_test_data(): - """Create test data for cross-language compatibility testing""" - try: - sys.path.insert(0, '../generated/py') - from serialization_test_sf import SerializationTestSerializationTestMessage - from struct_frame_parser import BasicPacket - - # Create test message with known values - # The new struct-based generator uses direct values instead of wrapper classes - msg = SerializationTestSerializationTestMessage( - magic_number=0xDEADBEEF, - test_string=b"Hello from Python!", - test_float=3.14159, - test_bool=True, - test_array=[100, 200, 300] - ) - - packet = BasicPacket() - encoded_data = packet.encode_msg(msg) - - with open('python_test_data.bin', 'wb') as f: - f.write(bytes(encoded_data)) - - return True - - except ImportError: - return True # Skip if generated code not available - - except Exception as e: - print_failure_details( - f"Create test data exception: {type(e).__name__}", - expected_values={"result": "success"}, - actual_values={"exception": str(e)} - ) - import traceback - traceback.print_exc() - return False - - -def read_test_data(filename, language): - """Try to read and decode test data created by other languages""" - try: - if not os.path.exists(filename): - return True # Skip if file not available - - with open(filename, 'rb') as f: - binary_data = f.read() - - if len(binary_data) == 0: - print_failure_details( - f"Empty data from {language}", - expected_values={"data_size": ">0"}, - actual_values={"data_size": 0}, - raw_data=binary_data - ) - return False - - sys.path.insert(0, './serialization_test') - from serialization_test_sf import SerializationTestSerializationTestMessage - from struct_frame_parser import BasicPacket, FrameParser - - packet_formats = {0x90: BasicPacket()} - msg_definitions = {204: SerializationTestSerializationTestMessage} - parser = FrameParser(packet_formats, msg_definitions) - - result = None - for byte in binary_data: - result = parser.parse_char(byte) - if result: - break - - if not result: - print_failure_details( - f"Failed to decode {language} data", - expected_values={"decoded_message": "valid"}, - actual_values={"decoded_message": None}, - raw_data=binary_data - ) - return False - - return True - - except ImportError: - return True # Skip if generated code not available - - except Exception as e: - print_failure_details( - f"Read {language} data exception: {type(e).__name__}", - expected_values={"result": "success"}, - actual_values={"exception": str(e)} - ) - import traceback - traceback.print_exc() - return False - - -def main(): - """Main test function""" - print("\n[TEST START] Python Cross-Language Serialization") - - success = create_test_data() - if success: - success = success and read_test_data('c_test_data.bin', 'C') - success = success and read_test_data('typescript_test_data.bin', 'TypeScript') - - status = "PASS" if success else "FAIL" - print(f"[TEST END] Python Cross-Language Serialization: {status}\n") - - return success - - -if __name__ == "__main__": - success = main() - sys.exit(0 if success else 1) diff --git a/tests/run_tests.py b/tests/run_tests.py index e8090794..52dbad3c 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -35,9 +35,9 @@ def __init__(self, verbose=False): 'compilation': {'c': False, 'ts': False, 'py': False, 'cpp': False}, 'basic_types': {'c': False, 'ts': False, 'py': False, 'cpp': False}, 'arrays': {'c': False, 'ts': False, 'py': False, 'cpp': False}, - 'serialization': {'c': False, 'ts': False, 'py': False, 'cpp': False}, - 'cross_language': False, - 'cross_platform_pipe': False + 'cross_platform_serialization': {'c': False, 'ts': False, 'py': False, 'cpp': False}, + 'cross_platform_deserialization': {'c': False, 'ts': False, 'py': False, 'cpp': False}, + 'cross_language': False } # Cross-language compatibility matrix @@ -230,7 +230,8 @@ def test_c_compilation(self): test_files = [ "test_basic_types.c", "test_arrays.c", - "test_serialization.c" + "test_cross_platform_serialization.c", + "test_cross_platform_deserialization.c" ] all_success = True @@ -264,7 +265,8 @@ def test_cpp_compilation(self): test_files = [ "test_basic_types.cpp", "test_arrays.cpp", - "test_serialization.cpp" + "test_cross_platform_serialization.cpp", + "test_cross_platform_deserialization.cpp" ] all_success = True @@ -291,7 +293,8 @@ def run_cpp_tests(self): test_executables = [ ("test_basic_types.exe", "basic_types"), ("test_arrays.exe", "arrays"), - ("test_serialization.exe", "serialization") + ("test_cross_platform_serialization.exe", "cross_platform_serialization"), + ("test_cross_platform_deserialization.exe", "cross_platform_deserialization") ] all_success = True @@ -329,15 +332,15 @@ def test_typescript_compilation(self): test_files = [ "test_basic_types.ts", "test_arrays.ts", - "test_serialization.ts", - "encoder_framed.ts", - "decoder_framed.ts" + "test_cross_platform_serialization.ts", + "test_cross_platform_deserialization.ts" ] for test_file in test_files: src = self.tests_dir / "ts" / test_file - dest = self.generated_dir / "ts" / test_file - shutil.copy2(src, dest) + if src.exists(): + dest = self.generated_dir / "ts" / test_file + shutil.copy2(src, dest) # Try to compile TypeScript files command = f"tsc --outDir {self.generated_dir / 'ts' / 'js'} {self.generated_dir / 'ts'}/*.ts" @@ -357,7 +360,8 @@ def run_c_tests(self): test_executables = [ ("test_basic_types.exe", "basic_types"), ("test_arrays.exe", "arrays"), - ("test_serialization.exe", "serialization") + ("test_cross_platform_serialization.exe", "cross_platform_serialization"), + ("test_cross_platform_deserialization.exe", "cross_platform_deserialization") ] all_success = True @@ -387,7 +391,8 @@ def run_python_tests(self): test_scripts = [ ("test_basic_types.py", "basic_types"), ("test_arrays.py", "arrays"), - ("test_serialization.py", "serialization") + ("test_cross_platform_serialization.py", "cross_platform_serialization"), + ("test_cross_platform_deserialization.py", "cross_platform_deserialization") ] all_success = True @@ -399,6 +404,11 @@ def run_python_tests(self): for script_name, test_type in test_scripts: script_path = self.tests_dir / "py" / script_name + if not script_path.exists(): + if self.verbose: + self.log(f"Script not found: {script_name}", "WARNING") + continue + try: result = subprocess.run( [sys.executable, str(script_path)], @@ -439,7 +449,8 @@ def run_typescript_tests(self): test_scripts = [ ("test_basic_types.ts", "basic_types"), ("test_arrays.ts", "arrays"), - ("test_serialization.ts", "serialization") + ("test_cross_platform_serialization.ts", "cross_platform_serialization"), + ("test_cross_platform_deserialization.ts", "cross_platform_deserialization") ] all_success = True @@ -477,236 +488,226 @@ def run_typescript_tests(self): return all_success - def run_cross_language_tests(self): - """Run cross-language compatibility tests""" + def run_cross_platform_tests(self, languages): + """Run cross-platform serialization/deserialization compatibility tests""" print("\n" + "="*60) - print("šŸ”„ CROSS-LANGUAGE DATA INTERCHANGE") + print("šŸ”„ CROSS-PLATFORM COMPATIBILITY MATRIX") print("="*60) - print("Testing if data serialized in one language can be decoded in another\n") + print("Testing serialization and deserialization across all languages\n") - # Initialize cross-language compatibility matrix + # Initialize cross-platform compatibility matrix # Structure: encoder_language -> {decoder_language: success_status} - self.cross_language_matrix = {} + self.cross_platform_matrix = {} + + # Map of language codes to display names + lang_map = { + 'c': 'C', + 'cpp': 'C++', + 'py': 'Python', + 'ts': 'TypeScript' + } + # Step 1: Run serialization tests for each language to generate data files + print("Generating test data files...") + serialization_results = {} + for lang in languages: + lang_name = lang_map.get(lang, lang.upper()) + + if lang == 'c': + exe_path = self.tests_dir / "c" / "test_cross_platform_serialization.exe" + if exe_path.exists(): + success, _, _ = self.run_command( + str(exe_path), cwd=self.tests_dir / "c", show_command=False) + serialization_results[lang_name] = success + if success and self.verbose: + self.log(f"{lang_name} serialization succeeded", "SUCCESS") + elif lang == 'cpp': + exe_path = self.tests_dir / "cpp" / "test_cross_platform_serialization.exe" + if exe_path.exists(): + success, _, _ = self.run_command( + str(exe_path), cwd=self.tests_dir / "cpp", show_command=False) + serialization_results[lang_name] = success + if success and self.verbose: + self.log(f"{lang_name} serialization succeeded", "SUCCESS") + elif lang == 'py': + script_path = self.tests_dir / "py" / "test_cross_platform_serialization.py" + if script_path.exists(): + env = os.environ.copy() + env["PYTHONPATH"] = str(self.generated_dir / "py") + try: + result = subprocess.run( + [sys.executable, str(script_path)], + cwd=self.tests_dir / "py", + capture_output=True, + text=True, + env=env, + timeout=30 + ) + serialization_results[lang_name] = (result.returncode == 0) + if result.returncode == 0 and self.verbose: + self.log(f"{lang_name} serialization succeeded", "SUCCESS") + except Exception: + serialization_results[lang_name] = False + elif lang == 'ts': + js_path = self.generated_dir / "ts" / "js" / "test_cross_platform_serialization.js" + if js_path.exists(): + success, _, _ = self.run_command( + f"node {js_path}", + cwd=self.project_root, + show_command=False + ) + serialization_results[lang_name] = success + if success and self.verbose: + self.log(f"{lang_name} serialization succeeded", "SUCCESS") + + # Step 2: For each language, test if it can deserialize all available test data + print("Testing cross-platform deserialization...") + # Define available languages and their test data files - languages = { + lang_info = { 'C': { 'data_file': 'c_test_data.bin', - 'data_locations': [self.tests_dir / "c"], - 'test_exe': self.tests_dir / "c" / "test_serialization.exe" + 'source_location': self.tests_dir / "c", }, 'C++': { 'data_file': 'cpp_test_data.bin', - 'data_locations': [self.tests_dir / "cpp"], - 'test_exe': self.tests_dir / "cpp" / "test_serialization.exe" + 'source_location': self.tests_dir / "cpp", }, 'Python': { 'data_file': 'python_test_data.bin', - 'data_locations': [self.tests_dir / "py"], - 'test_script': self.tests_dir / "py" / "test_serialization.py" + 'source_location': self.tests_dir / "py", }, 'TypeScript': { 'data_file': 'typescript_test_data.bin', - 'data_locations': [self.generated_dir / "ts" / "js", self.generated_dir / "ts"], - 'test_script': self.generated_dir / "ts" / "js" / "test_serialization.js" + 'source_location': self.generated_dir / "ts" / "js", } } - # First, check which languages have generated test data files - available_encoders = [] - for lang_name, lang_info in languages.items(): - for location in lang_info['data_locations']: - data_path = location / lang_info['data_file'] - if data_path.exists(): - available_encoders.append(lang_name) - if self.verbose: - self.log(f"Found {lang_name} test data: {data_path}", "SUCCESS") - break + # Find which data files were successfully created + available_data_files = {} + for lang_name, info in lang_info.items(): + data_path = info['source_location'] / info['data_file'] + if data_path.exists(): + available_data_files[lang_name] = data_path + if self.verbose: + self.log(f"Found {lang_name} test data", "SUCCESS") - if not available_encoders: - self.log("No cross-language test data files found", "WARNING") - return False + # For each decoder language, test if it can decode data from each encoder language + decoder_info = { + 'C': { + 'test_dir': self.tests_dir / "c", + 'test_exe': self.tests_dir / "c" / "test_cross_platform_deserialization.exe" + }, + 'C++': { + 'test_dir': self.tests_dir / "cpp", + 'test_exe': self.tests_dir / "cpp" / "test_cross_platform_deserialization.exe" + }, + 'Python': { + 'test_dir': self.tests_dir / "py", + 'test_script': self.tests_dir / "py" / "test_cross_platform_deserialization.py" + }, + 'TypeScript': { + 'test_dir': self.generated_dir / "ts" / "js", + 'test_script': self.generated_dir / "ts" / "js" / "test_cross_platform_deserialization.js" + } + } - # Test cross-language decoding for each available encoder/decoder pair - for encoder_lang in available_encoders: - self.cross_language_matrix[encoder_lang] = {} + # Build the matrix + for encoder_lang, encoder_data_path in available_data_files.items(): + self.cross_platform_matrix[encoder_lang] = {} - # Test decoding with each available language - for decoder_lang in available_encoders: - if decoder_lang == encoder_lang: - # Same language encoding/decoding should always work (tested in serialization tests) - self.cross_language_matrix[encoder_lang][decoder_lang] = True - continue + for decoder_lang_name, decoder_spec in decoder_info.items(): + # Copy the encoder's data file to the decoder's directory + import shutil + target_file = decoder_spec['test_dir'] / encoder_data_path.name + file_copied = False - # Test cross-language compatibility - success = self._test_cross_decode(encoder_lang, decoder_lang, languages) - self.cross_language_matrix[encoder_lang][decoder_lang] = success + try: + # Copy the test data file only if source and destination are different + if encoder_data_path.resolve() != target_file.resolve(): + shutil.copy2(encoder_data_path, target_file) + file_copied = True + + # Run the appropriate decoder test + success = False + if 'test_exe' in decoder_spec and decoder_spec['test_exe'].exists(): + result, _, _ = self.run_command( + str(decoder_spec['test_exe']), + cwd=decoder_spec['test_dir'], + show_command=False + ) + success = result + elif 'test_script' in decoder_spec and decoder_spec['test_script'].exists(): + if decoder_lang_name == 'Python': + env = os.environ.copy() + env["PYTHONPATH"] = str(self.generated_dir / "py") + result = subprocess.run( + [sys.executable, str(decoder_spec['test_script'])], + cwd=decoder_spec['test_dir'], + capture_output=True, + text=True, + env=env, + timeout=30 + ) + success = (result.returncode == 0) + elif decoder_lang_name == 'TypeScript': + result = subprocess.run( + ["node", str(decoder_spec['test_script'])], + cwd=self.project_root, + capture_output=True, + text=True, + timeout=30 + ) + success = (result.returncode == 0) + + self.cross_platform_matrix[encoder_lang][decoder_lang_name] = success + + except Exception as e: + if self.verbose: + self.log(f"{decoder_lang_name} decode of {encoder_lang} failed: {e}", "WARNING") + self.cross_platform_matrix[encoder_lang][decoder_lang_name] = False + finally: + # Clean up the copied file (only if we copied it) + try: + if file_copied and target_file.exists(): + target_file.unlink() + except: + pass # Print detailed compatibility matrix - self._print_cross_language_matrix() + self._print_cross_platform_matrix() # Determine overall success total_tests = 0 successful_tests = 0 - for encoder_lang, decoder_results in self.cross_language_matrix.items(): + for encoder_lang, decoder_results in self.cross_platform_matrix.items(): for decoder_lang, success in decoder_results.items(): - if encoder_lang != decoder_lang: # Skip self-tests - total_tests += 1 - if success: - successful_tests += 1 + total_tests += 1 + if success: + successful_tests += 1 + # Mark as successful if we have at least some passing tests if successful_tests > 0: self.results['cross_language'] = True return True else: self.results['cross_language'] = False - self.log("No cross-language compatibility tests passed", "WARNING") + self.log("No cross-platform compatibility tests passed", "WARNING") return False - def _test_cross_decode(self, encoder_lang, decoder_lang, languages): - """Test if decoder_lang can decode data from encoder_lang""" - try: - encoder_info = languages[encoder_lang] - decoder_info = languages[decoder_lang] - - # Find the encoder's data file - encoder_data_path = None - for location in encoder_info['data_locations']: - potential_path = location / encoder_info['data_file'] - if potential_path.exists(): - encoder_data_path = potential_path - break - - if not encoder_data_path: - return False - - # Test decoding based on decoder language - if decoder_lang in ['C', 'C++'] and 'test_exe' in decoder_info: - return self._test_c_cpp_cross_decode(encoder_data_path, encoder_lang, decoder_info['test_exe'], decoder_lang) - elif decoder_lang == 'Python' and 'test_script' in decoder_info: - return self._test_python_cross_decode(encoder_data_path, encoder_lang, decoder_info['test_script']) - elif decoder_lang == 'TypeScript' and 'test_script' in decoder_info: - return self._test_typescript_cross_decode(encoder_data_path, encoder_lang, decoder_info['test_script']) - else: - # Language decoder not available - return False - - except Exception as e: - self.log(f"Exception testing {decoder_lang} decode of {encoder_lang} data: {e}", "WARNING") - return False - - def _test_c_cpp_cross_decode(self, data_file_path, encoder_lang, test_exe_path, decoder_lang): - """Test C/C++ decoder with data from another language""" - if not test_exe_path.exists(): - return False - - # Determine the target directory based on decoder language - target_dir = self.tests_dir / ("c" if decoder_lang == "C" else "cpp") - target_file = target_dir / data_file_path.name - - try: - import shutil - shutil.copy2(data_file_path, target_file) - - # Run the C/C++ test (it will try to decode the copied file) - success, stdout, stderr = self.run_command(str(test_exe_path), cwd=target_dir) - return success - - except Exception as e: - self.log(f"{decoder_lang} cross-decode test failed: {e}", "WARNING") - return False - finally: - # Clean up the copied file - try: - if target_file.exists(): - target_file.unlink() - except: - pass - - def _test_python_cross_decode(self, data_file_path, encoder_lang, test_script_path): - """Test Python decoder with data from another language""" - if not test_script_path.exists(): - return False - - # Copy the data file to where the Python test expects it - target_file = self.tests_dir / "py" / data_file_path.name - try: - import shutil - shutil.copy2(data_file_path, target_file) - - # Set up Python environment and run the test - env = os.environ.copy() - env["PYTHONPATH"] = str(self.generated_dir / "py") - - result = subprocess.run( - [sys.executable, str(test_script_path)], - cwd=self.tests_dir / "py", - capture_output=True, - text=True, - env=env, - timeout=30 - ) - - return result.returncode == 0 - - except Exception as e: - self.log(f"Python cross-decode test failed: {e}", "WARNING") - return False - finally: - # Clean up the copied file - try: - if target_file.exists(): - target_file.unlink() - except: - pass - - def _test_typescript_cross_decode(self, data_file_path, encoder_lang, test_script_path): - """Test TypeScript decoder with data from another language""" - if not test_script_path.exists(): - return False - - # Copy the data file to where the TypeScript test expects it (running from project root now) - target_dir = self.project_root / "tests" / "generated" / "ts" / "js" - target_file = target_dir / data_file_path.name - try: - import shutil - shutil.copy2(data_file_path, target_file) - - # Run the TypeScript test from project root - result = subprocess.run( - ["node", str(test_script_path)], - cwd=self.project_root, # Run from project root - capture_output=True, - text=True, - timeout=30 - ) - - return result.returncode == 0 - - except Exception as e: - self.log(f"TypeScript cross-decode test failed: {e}", "WARNING") - return False - finally: - # Clean up the copied file - try: - if target_file.exists(): - target_file.unlink() - except: - pass - - def _print_cross_language_matrix(self): - """Print a detailed cross-language compatibility matrix""" - if not self.cross_language_matrix: + def _print_cross_platform_matrix(self): + """Print a detailed cross-platform compatibility matrix""" + if not self.cross_platform_matrix: return # Column width for consistent formatting ENCODER_COL_WIDTH = 18 - print("\nCompatibility Test Results:") + print("Compatibility Test Results:") # Get all languages involved all_languages = set() - for encoder_lang, decoder_results in self.cross_language_matrix.items(): + for encoder_lang, decoder_results in self.cross_platform_matrix.items(): all_languages.add(encoder_lang) all_languages.update(decoder_results.keys()) @@ -722,60 +723,27 @@ def _print_cross_language_matrix(self): # Print matrix rows for encoder_lang in sorted_languages: - if encoder_lang in self.cross_language_matrix: + if encoder_lang in self.cross_platform_matrix: row = encoder_lang.ljust(ENCODER_COL_WIDTH) for decoder_lang in sorted_languages: - if decoder_lang in self.cross_language_matrix[encoder_lang]: - success = self.cross_language_matrix[encoder_lang][decoder_lang] - if encoder_lang == decoder_lang: - symbol = " — " # Self-test symbol - else: - symbol = " āœ… " if success else " āŒ " + if decoder_lang in self.cross_platform_matrix[encoder_lang]: + success = self.cross_platform_matrix[encoder_lang][decoder_lang] + symbol = " āœ… " if success else " āŒ " else: symbol = " ⚫ " # Not tested row += f"{symbol:>8} " print(row) - # Count success rate - cross_success = sum(1 for encoder_lang, decoder_results in self.cross_language_matrix.items() + # Count success rate (all tests including self-tests) + total_success = sum(1 for encoder_lang, decoder_results in self.cross_platform_matrix.items() for decoder_lang, success in decoder_results.items() - if encoder_lang != decoder_lang and success) - cross_total = sum(1 for encoder_lang, decoder_results in self.cross_language_matrix.items() - for decoder_lang, success in decoder_results.items() - if encoder_lang != decoder_lang) + if success) + total_tests = sum(1 for encoder_lang, decoder_results in self.cross_platform_matrix.items() + for decoder_lang, success in decoder_results.items()) - if cross_total > 0: - print(f"\nSuccess rate: {cross_success}/{cross_total} ({100*cross_success/cross_total:.0f}%)") + if total_tests > 0: + print(f"\nSuccess rate: {total_success}/{total_tests} ({100*total_success/total_tests:.0f}%)") print() - - def run_cross_platform_pipe_tests(self): - """Run cross-platform pipe tests""" - print("\n" + "="*60) - print("šŸ”— STDIN/STDOUT STREAMING TESTS") - print("="*60) - print("Testing data piping between language implementations via stdin/stdout\n") - - cross_platform_script = self.tests_dir / "cross_platform_test.py" - if not cross_platform_script.exists(): - self.log("Cross-platform test script not found", "WARNING") - return True # Don't fail if test doesn't exist - - cmd = f"{sys.executable} {cross_platform_script}" - success, stdout, stderr = self.run_command(cmd, cwd=self.tests_dir, show_command=False) - - # Print the output regardless of success for visibility - if stdout: - print(stdout) - - if success: - self.results['cross_platform_pipe'] = True - return True - else: - if self.verbose: - self.log("Cross-platform pipe tests failed", "WARNING") - # Don't fail the entire suite for this - self.results['cross_platform_pipe'] = False - return True def run_test_by_type(self, test_type, languages): @@ -879,22 +847,26 @@ def print_summary(self, tested_languages=['c', 'ts', 'py', 'cpp']): passed_tests += 1 # Count test execution results - test_types = ['basic_types', 'arrays', 'serialization'] + test_types = ['basic_types', 'arrays'] for test_type in test_types: for lang in tested_languages: total_tests += 1 if self.results[test_type][lang]: passed_tests += 1 - # Cross-language compatibility - total_tests += 1 - if self.results['cross_language']: - passed_tests += 1 - - # Cross-platform pipe tests - total_tests += 1 - if self.results['cross_platform_pipe']: - passed_tests += 1 + # Cross-platform compatibility matrix (counts as multiple tests) + # Count the number of encoder/decoder pairs tested + if hasattr(self, 'cross_platform_matrix'): + for encoder_lang, decoder_results in self.cross_platform_matrix.items(): + for decoder_lang, success in decoder_results.items(): + total_tests += 1 + if success: + passed_tests += 1 + else: + # Fallback if matrix not created yet + total_tests += 1 + if self.results['cross_language']: + passed_tests += 1 # Overall result print(f"\nšŸ“ˆ {passed_tests}/{total_tests} tests passed") @@ -1002,15 +974,12 @@ def main(): runner.compile_all_languages(languages) # Run tests organized by test type (not by language) - test_types = ['basic_types', 'arrays', 'serialization'] + test_types = ['basic_types', 'arrays'] for test_type in test_types: runner.run_test_by_type(test_type, languages) - # Run cross-language compatibility tests - runner.run_cross_language_tests() - - # Run cross-platform pipe tests - runner.run_cross_platform_pipe_tests() + # Run cross-platform compatibility tests (combines serialization and deserialization) + runner.run_cross_platform_tests(languages) # Print summary overall_success = runner.print_summary(languages) diff --git a/tests/ts/decoder_framed.js b/tests/ts/decoder_framed.js deleted file mode 100755 index 43482f62..00000000 --- a/tests/ts/decoder_framed.js +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env node -// Decoder for cross-platform testing with framing -// Reads framed SerializationTestMessage from stdin and decodes it - -const serializationTestModule = require('./serialization_test.sf.js'); -const structFrameParserModule = require('./struct_frame_parser.js'); - -const serialization_test_SerializationTestMessage = serializationTestModule.serialization_test_SerializationTestMessage; -const FrameParser = structFrameParserModule.FrameParser; -const BasicPacketFormat = structFrameParserModule.BasicPacketFormat; - -try { - // Set up stdin to read binary data - const chunks = []; - process.stdin.on('data', (chunk) => { - chunks.push(chunk); - }); - - process.stdin.on('end', () => { - const binaryData = Buffer.concat(chunks); - - if (binaryData.length === 0) { - process.stderr.write('ERROR: No data received from stdin\n'); - process.exit(1); - } - - // Create parser for deserialization - const packetFormats = { - 0x90: new BasicPacketFormat() - }; - const msgDefinitions = { - 204: serialization_test_SerializationTestMessage - }; - - const parser = new FrameParser(packetFormats, msgDefinitions); - - // Parse the binary data byte by byte - let msg = null; - for (const byte of binaryData) { - const result = parser.parse_char(byte); - if (result) { - msg = result; - break; - } - } - - if (msg === null) { - process.stderr.write('ERROR: Failed to decode message\n'); - process.exit(1); - } - - // Print decoded values in a consistent format - console.log(`magic_number=0x${msg.magic_number.toString(16).toUpperCase().padStart(8, '0')}`); - console.log(`test_string=${msg.test_string_data.substring(0, msg.test_string_length)}`); - console.log(`test_float=${msg.test_float.toFixed(5)}`); - console.log(`test_bool=${msg.test_bool}`); - console.log(`test_array=${msg.test_array_data.slice(0, msg.test_array_count).join(',')}`); - }); - - // Resume stdin to start reading - process.stdin.resume(); -} catch (error) { - process.stderr.write(`ERROR: Failed to decode: ${error}\n`); - process.exit(1); -} diff --git a/tests/ts/decoder_framed.ts b/tests/ts/decoder_framed.ts deleted file mode 100644 index 2fa450ed..00000000 --- a/tests/ts/decoder_framed.ts +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node -// Decoder for cross-platform testing with framing -// Reads framed SerializationTestMessage from stdin and decodes it - -const serializationTestModule = require('./serialization_test.sf'); -const structFrameParserModule = require('./struct_frame_parser'); - -const serialization_test_SerializationTestMessage = serializationTestModule.serialization_test_SerializationTestMessage; -const FrameParser = structFrameParserModule.FrameParser; -const BasicPacketFormat = structFrameParserModule.BasicPacketFormat; - -try { - // Set up stdin to read binary data - const chunks: Buffer[] = []; - process.stdin.on('data', (chunk) => { - chunks.push(chunk); - }); - - process.stdin.on('end', () => { - const binaryData = Buffer.concat(chunks); - - if (binaryData.length === 0) { - process.stderr.write('ERROR: No data received from stdin\n'); - process.exit(1); - } - - // Create parser for deserialization - const packetFormats: { [key: number]: any } = { - 0x90: new BasicPacketFormat() - }; - const msgDefinitions: { [key: number]: any } = { - 204: serialization_test_SerializationTestMessage - }; - - const parser = new FrameParser(packetFormats, msgDefinitions); - - // Parse the binary data byte by byte - let msg: any = null; - for (const byte of binaryData) { - const result = parser.parse_char(byte); - if (result) { - msg = result; - break; - } - } - - if (msg === null) { - process.stderr.write('ERROR: Failed to decode message\n'); - process.exit(1); - } - - // Print decoded values in a consistent format - console.log(`magic_number=0x${msg.magic_number.toString(16).toUpperCase().padStart(8, '0')}`); - // Skip string output due to typed-struct limitations - console.log(`test_string=`); - console.log(`test_float=${msg.test_float.toFixed(5)}`); - console.log(`test_bool=${msg.test_bool}`); - console.log(`test_array=${msg.test_array_data.slice(0, msg.test_array_count).join(',')}`); - }); - - // Resume stdin to start reading - process.stdin.resume(); -} catch (error) { - process.stderr.write(`ERROR: Failed to decode: ${error}\n`); - process.exit(1); -} diff --git a/tests/ts/encoder_framed.js b/tests/ts/encoder_framed.js deleted file mode 100755 index baa4c13f..00000000 --- a/tests/ts/encoder_framed.js +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env node -// Encoder for cross-platform testing with framing -// Encodes a SerializationTestMessage with framing and writes to stdout - -const serializationTestModule = require('./serialization_test.sf.js'); -const structFrameModule = require('./struct_frame.js'); -const structFrameTypesModule = require('./struct_frame_types.js'); - -const serialization_test_SerializationTestMessage = serializationTestModule.serialization_test_SerializationTestMessage; -const msg_encode = structFrameModule.msg_encode; -const struct_frame_buffer = structFrameTypesModule.struct_frame_buffer; -const basic_frame_config = structFrameTypesModule.basic_frame_config; - -try { - // Create a message instance - const msg = new serialization_test_SerializationTestMessage(); - - // Set test data - msg.magic_number = 0xDEADBEEF; - msg.test_string_length = 'Hello from TypeScript!'.length; - msg.test_string_data = 'Hello from TypeScript!'; - msg.test_float = 3.14159; - msg.test_bool = true; - msg.test_array_count = 3; - msg.test_array_data = [100, 200, 300]; - - // Create encoding buffer - const buffer = new struct_frame_buffer(512); - buffer.config = basic_frame_config; - - // Encode the message - msg_encode(buffer, msg, 204); // Message ID from proto - - // Write binary data to stdout - const binaryData = buffer.data.slice(0, buffer.size); - process.stdout.write(binaryData); -} catch (error) { - process.stderr.write(`ERROR: Failed to encode: ${error}\n`); - process.exit(1); -} diff --git a/tests/ts/encoder_framed.ts b/tests/ts/encoder_framed.ts deleted file mode 100644 index 2a20f55a..00000000 --- a/tests/ts/encoder_framed.ts +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node -// Encoder for cross-platform testing with framing -// Encodes a SerializationTestMessage with framing and writes to stdout - -const serializationTestModule = require('./serialization_test.sf'); -const structFrameModule = require('./struct_frame'); -const structFrameTypesModule = require('./struct_frame_types'); - -const serialization_test_SerializationTestMessage = serializationTestModule.serialization_test_SerializationTestMessage; -const msg_encode = structFrameModule.msg_encode; -const struct_frame_buffer = structFrameTypesModule.struct_frame_buffer; -const basic_frame_config = structFrameTypesModule.basic_frame_config; - -try { - // Create a message instance - const msg = new serialization_test_SerializationTestMessage(); - - // Set test data - msg.magic_number = 0xDEADBEEF; - // Skip string fields due to typed-struct library bug with String setters - msg.test_string_length = 0; - // msg.test_string_data would crash, so leave it empty - msg.test_float = 3.14159; - msg.test_bool = true; - msg.test_array_count = 3; - // Set array elements individually - msg.test_array_data[0] = 100; - msg.test_array_data[1] = 200; - msg.test_array_data[2] = 300; - - // Create encoding buffer - const buffer = new struct_frame_buffer(512); - buffer.config = basic_frame_config; - - // Encode the message - msg_encode(buffer, msg, 204); // Message ID from proto - - // Write binary data to stdout - const binaryData = buffer.data.slice(0, buffer.size); - process.stdout.write(binaryData); -} catch (error) { - process.stderr.write(`ERROR: Failed to encode: ${error}\n`); - process.exit(1); -} diff --git a/tests/ts/test_cross_platform_deserialization.ts b/tests/ts/test_cross_platform_deserialization.ts new file mode 100644 index 00000000..a15524cc --- /dev/null +++ b/tests/ts/test_cross_platform_deserialization.ts @@ -0,0 +1,106 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +function printFailureDetails(label: string): void { + console.log('\n============================================================'); + console.log(`FAILURE DETAILS: ${label}`); + console.log('============================================================\n'); +} + +function loadExpectedValues(): any { + try { + const jsonPath = path.join(__dirname, '../../expected_values.json'); + const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')); + return data.serialization_test; + } catch (error) { + console.log(`Error loading expected values: ${error}`); + return null; + } +} + +function validateBasicFrame(buffer: Buffer, language: string, expected: any): boolean { + // Very basic frame validation + if (buffer.length < 4) { + console.log(` ${language} data too short`); + return false; + } + + // Check start byte + if (buffer[0] !== 0x90) { + console.log(` ${language} invalid start byte`); + return false; + } + + // Check message ID + if (buffer[1] !== 204) { + console.log(` ${language} invalid message ID`); + return false; + } + + // Extract magic number from payload (starts at byte 2) + const magicNumber = buffer.readUInt32LE(2); + if (magicNumber !== expected.magic_number) { + console.log(` ${language} magic number mismatch (expected ${expected.magic_number}, got ${magicNumber})`); + return false; + } + + console.log(` āœ“ ${language} data validated successfully`); + return true; +} + +function readAndValidateTestData(filename: string, language: string): boolean { + try { + if (!fs.existsSync(filename)) { + console.log(` Skipping ${language} - file not found: ${filename}`); + return true; // Skip if file not available + } + + const binaryData = fs.readFileSync(filename); + + if (binaryData.length === 0) { + printFailureDetails(`Empty data from ${language}`); + return false; + } + + const expected = loadExpectedValues(); + if (!expected) { + return false; + } + + if (!validateBasicFrame(binaryData, language, expected)) { + console.log(` Validation failed for ${language} data`); + return false; + } + + return true; + } catch (error) { + printFailureDetails(`Read ${language} data exception: ${error}`); + return false; + } +} + +function main(): boolean { + console.log('\n[TEST START] TypeScript Cross-Platform Deserialization'); + + try { + let success = true; + success = success && readAndValidateTestData('python_test_data.bin', 'Python'); + success = success && readAndValidateTestData('c_test_data.bin', 'C'); + success = success && readAndValidateTestData('cpp_test_data.bin', 'C++'); + success = success && readAndValidateTestData('typescript_test_data.bin', 'TypeScript'); + + console.log(`[TEST END] TypeScript Cross-Platform Deserialization: ${success ? 'PASS' : 'FAIL'}\n`); + return success; + } catch (error) { + printFailureDetails(`Exception: ${error}`); + console.log('[TEST END] TypeScript Cross-Platform Deserialization: FAIL\n'); + return false; + } +} + +if (require.main === module) { + const success = main(); + process.exit(success ? 0 : 1); +} + +export { main }; diff --git a/tests/ts/test_serialization.ts b/tests/ts/test_cross_platform_serialization.ts similarity index 62% rename from tests/ts/test_serialization.ts rename to tests/ts/test_cross_platform_serialization.ts index 3d7f6b73..a88f1142 100644 --- a/tests/ts/test_serialization.ts +++ b/tests/ts/test_cross_platform_serialization.ts @@ -1,4 +1,5 @@ import * as fs from 'fs'; +import * as path from 'path'; function printFailureDetails(label: string, expectedValues?: any, actualValues?: any, rawData?: Buffer): void { console.log('\n============================================================'); @@ -45,12 +46,23 @@ try { // Skip test if generated modules are not available } +function loadExpectedValues(): any { + try { + const jsonPath = path.join(__dirname, '../../../expected_values.json'); + const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')); + return data.serialization_test; + } catch (error) { + console.log(`Error loading expected values: ${error}`); + return null; + } +} + function createTestData(): boolean { try { - // Check if required modules are loaded - if (!serialization_test_SerializationTestMessage || !msg_encode || - !struct_frame_buffer || !basic_frame_config) { - return true; // Skip if modules not available + // Load expected values from JSON + const expected = loadExpectedValues(); + if (!expected) { + return false; } // Due to TypeScript code generation issues with array alignment, @@ -62,11 +74,11 @@ function createTestData(): boolean { const start_byte = 0x90; const msg_id = 204; - // Create simple payload with values that don't require arrays + // Create simple payload using values from JSON const payload = Buffer.alloc(20); - payload.writeUInt32LE(0xDEADBEEF, 0); // magic_number - payload.writeUInt8(22, 4); // test_string_length (length of "Hello from TypeScript!") - Buffer.from('Hello from TypeScript!').copy(payload, 5, 0, 22); // Copy only 15 chars to fit + payload.writeUInt32LE(expected.magic_number, 0); + payload.writeUInt8(expected.test_string.length, 4); + Buffer.from(expected.test_string).copy(payload, 5, 0, expected.test_string.length); // Calculate Fletcher checksum let byte1 = msg_id; @@ -84,8 +96,11 @@ function createTestData(): boolean { frame[frame.length - 2] = byte1; frame[frame.length - 1] = byte2; - // Write to file - fs.writeFileSync('tests/generated/ts/js/typescript_test_data.bin', frame); + // Write to file - determine correct path based on where we're running from + const outputPath = fs.existsSync('tests/generated/ts/js') + ? 'tests/generated/ts/js/typescript_test_data.bin' + : 'typescript_test_data.bin'; + fs.writeFileSync(outputPath, frame); return true; } catch (error) { @@ -94,61 +109,21 @@ function createTestData(): boolean { } } -function readTestData(filename: string, language: string): boolean { - try { - if (!fs.existsSync(filename)) { - return true; // Skip if file not available - } - - const binaryData = fs.readFileSync(filename); - - if (binaryData.length === 0) { - printFailureDetails(`Empty data from ${language}`, - { data_size: '>0' }, - { data_size: 0 }, - binaryData - ); - return false; - } - - // For now, just verify we can read the file - // Full decoding would require implementing a frame parser in TypeScript - return true; - } catch (error) { - printFailureDetails(`Read ${language} data exception: ${error}`); - return false; - } -} - function main(): boolean { - console.log('\n[TEST START] TypeScript Cross-Language Serialization'); + console.log('\n[TEST START] TypeScript Cross-Platform Serialization'); try { // Create TypeScript test data if (!createTestData()) { - console.log('[TEST END] TypeScript Cross-Language Serialization: FAIL\n'); + console.log('[TEST END] TypeScript Cross-Platform Serialization: FAIL\n'); return false; } - // Try to read data from other languages - const testFiles = [ - { file: 'tests/c/c_test_data.bin', lang: 'C' }, - { file: 'tests/cpp/cpp_test_data.bin', lang: 'C++' }, - { file: 'tests/py/python_test_data.bin', lang: 'Python' } - ]; - - for (const test of testFiles) { - if (!readTestData(test.file, test.lang)) { - console.log('[TEST END] TypeScript Cross-Language Serialization: FAIL\n'); - return false; - } - } - - console.log('[TEST END] TypeScript Cross-Language Serialization: PASS\n'); + console.log('[TEST END] TypeScript Cross-Platform Serialization: PASS\n'); return true; } catch (error) { printFailureDetails(`Exception: ${error}`); - console.log('[TEST END] TypeScript Cross-Language Serialization: FAIL\n'); + console.log('[TEST END] TypeScript Cross-Platform Serialization: FAIL\n'); return false; } }