#include "irc/parser.h" #include "irc/serializer.h" #include "irc/types.h" #include #include using namespace nix_irc; int failures = 0; #define TEST_CHECK(cond, msg) \ do { \ if (!(cond)) { \ std::cerr << " FAIL: " << msg << std::endl; \ failures++; \ } else { \ std::cout << " PASS: " << msg << std::endl; \ } \ } while (0) #define TEST_PASS(msg) std::cout << " PASS: " << msg << std::endl #define TEST_FAIL(msg) \ do { \ std::cerr << " FAIL: " << msg << std::endl; \ failures++; \ } while (0) void test_enum_compatibility() { std::cout << "> Enum compatibility..." << std::endl; if (static_cast(NodeType::WITH) == 0x32) { std::cout << " PASS: WITH has correct value 0x32" << std::endl; } else { std::cerr << " FAIL: WITH should be 0x32, got " << static_cast(NodeType::WITH) << std::endl; } if (static_cast(NodeType::HAS_ATTR) == 0x34) { std::cout << " PASS: HAS_ATTR has value 0x34 (new slot after WITH bump)" << std::endl; } else if (static_cast(NodeType::HAS_ATTR) == 0x33 && static_cast(NodeType::WITH) == 0x32) { std::cout << " PASS: HAS_ATTR has value 0x33 (restored original with WITH " "at 0x32)" << std::endl; } else { std::cerr << " FAIL: HAS_ATTR value is " << static_cast(NodeType::HAS_ATTR) << " (expected 0x34 or 0x33 with WITH=0x32)" << std::endl; } if (IR_VERSION == 2) { std::cout << " PASS: IR_VERSION bumped to 2 for breaking change" << std::endl; } else if (static_cast(NodeType::WITH) == 0x32) { std::cout << " PASS: IR_VERSION unchanged but WITH restored to 0x32" << std::endl; } else { std::cerr << " FAIL: Either bump IR_VERSION or fix enum values" << std::endl; } } void test_serializer_select_with_default() { std::cout << "> SELECT serialization with default_expr..." << std::endl; auto expr = std::make_shared(ConstIntNode(42)); auto attr = std::make_shared(ConstStringNode("key")); auto default_val = std::make_shared(ConstIntNode(100)); SelectNode select_node(expr, attr); select_node.default_expr = default_val; auto select = std::make_shared(select_node); IRModule module; module.entry = select; Serializer ser; auto bytes = ser.serialize_to_bytes(module); Deserializer deser; auto loaded = deser.deserialize(bytes); auto* loaded_select = loaded.entry->get_if(); if (loaded_select && loaded_select->default_expr && *loaded_select->default_expr) { auto* def_val = (*loaded_select->default_expr)->get_if(); if (def_val && def_val->value == 100) { std::cout << " PASS: SELECT with default_expr round-trips correctly" << std::endl; } else { std::cerr << " FAIL: default_expr value incorrect" << std::endl; } } else { std::cerr << " FAIL: default_expr not deserialized (missing u8 flag read)" << std::endl; } } void test_serializer_select_without_default() { std::cout << "> SELECT serialization without default_expr..." << std::endl; auto expr = std::make_shared(ConstIntNode(42)); auto attr = std::make_shared(ConstStringNode("key")); SelectNode select_node(expr, attr); auto select = std::make_shared(select_node); IRModule module; module.entry = select; Serializer ser; auto bytes = ser.serialize_to_bytes(module); Deserializer deser; auto loaded = deser.deserialize(bytes); auto* loaded_select = loaded.entry->get_if(); if (loaded_select && (!loaded_select->default_expr || !*loaded_select->default_expr)) { std::cout << " PASS: SELECT without default_expr round-trips correctly" << std::endl; } else { std::cerr << " FAIL: default_expr should be null/absent" << std::endl; } } void test_parser_brace_depth_in_strings() { std::cout << "> Parser brace depth handling in strings..." << std::endl; std::string test_input = R"(let s = "test}"; in s)"; try { Parser parser; auto ast = parser.parse(test_input); TEST_PASS("Brace inside string does not confuse parser"); } catch (const std::exception& e) { TEST_FAIL("Parser should handle '}' inside strings"); } } void test_parser_has_ellipsis_usage() { std::cout << "> Parser has_ellipsis usage..." << std::endl; std::string with_ellipsis = "{ a, ... }: a"; std::string without_ellipsis = "{ a, b }: a + b"; try { Parser parser1; auto ast1 = parser1.parse(with_ellipsis); TEST_PASS("Pattern with ellipsis parses correctly"); Parser parser2; auto ast2 = parser2.parse(without_ellipsis); TEST_PASS("Pattern without ellipsis parses correctly"); } catch (const std::exception& e) { TEST_FAIL("Pattern parsing failed"); } } void test_parser_expect_in_speculative_parsing() { std::cout << "> Parser expect() in speculative parsing..." << std::endl; std::string not_a_lambda = "1 + 2"; std::string actual_lambda = "x: x + 1"; try { Parser parser1; auto ast1 = parser1.parse(not_a_lambda); TEST_PASS("Non-lambda input does not cause parser to throw"); Parser parser2; auto ast2 = parser2.parse(actual_lambda); TEST_PASS("Actual lambda parses correctly"); } catch (const std::exception& e) { TEST_FAIL("Parser should handle both lambda and non-lambda input"); } } void test_lookup_path_node() { std::cout << "> Lookup path serialization..." << std::endl; auto lookup = std::make_shared(ConstLookupPathNode("nixpkgs")); IRModule module; module.entry = lookup; Serializer ser; auto bytes = ser.serialize_to_bytes(module); Deserializer deser; auto loaded = deser.deserialize(bytes); auto* loaded_lookup = loaded.entry->get_if(); TEST_CHECK(loaded_lookup != nullptr, "Deserialized node is ConstLookupPathNode"); TEST_CHECK(loaded_lookup && loaded_lookup->value == "nixpkgs", "Lookup path value is 'nixpkgs'"); } void test_import_node() { std::cout << "> Import node serialization..." << std::endl; auto path = std::make_shared(ConstPathNode("./test.nix")); auto import_node = std::make_shared(ImportNode(path)); IRModule module; module.entry = import_node; Serializer ser; auto bytes = ser.serialize_to_bytes(module); Deserializer deser; auto loaded = deser.deserialize(bytes); auto* loaded_import = loaded.entry->get_if(); TEST_CHECK(loaded_import != nullptr, "Deserialized node is ImportNode"); TEST_CHECK(loaded_import && loaded_import->path != nullptr, "Import node has path"); if (loaded_import && loaded_import->path) { auto* path_node = loaded_import->path->get_if(); TEST_CHECK(path_node != nullptr, "Import path is ConstPathNode"); TEST_CHECK(path_node && path_node->value == "./test.nix", "Import path value is './test.nix'"); } } void test_import_with_lookup_path() { std::cout << "> Import with lookup path..." << std::endl; auto lookup = std::make_shared(ConstLookupPathNode("nixpkgs")); auto import_node = std::make_shared(ImportNode(lookup)); IRModule module; module.entry = import_node; Serializer ser; auto bytes = ser.serialize_to_bytes(module); Deserializer deser; auto loaded = deser.deserialize(bytes); auto* loaded_import = loaded.entry->get_if(); TEST_CHECK(loaded_import != nullptr, "Deserialized node is ImportNode"); if (loaded_import && loaded_import->path) { auto* lookup_node = loaded_import->path->get_if(); TEST_CHECK(lookup_node != nullptr, "Import path is ConstLookupPathNode"); TEST_CHECK(lookup_node && lookup_node->value == "nixpkgs", "Lookup path value is 'nixpkgs'"); } } void test_uri_node() { std::cout << "> URI node serialization..." << std::endl; auto uri = std::make_shared(ConstURINode("https://example.com")); IRModule module; module.entry = uri; Serializer ser; auto bytes = ser.serialize_to_bytes(module); Deserializer deser; auto loaded = deser.deserialize(bytes); auto* loaded_uri = loaded.entry->get_if(); TEST_CHECK(loaded_uri != nullptr, "Deserialized node is ConstURINode"); TEST_CHECK(loaded_uri && loaded_uri->value == "https://example.com", "URI value is 'https://example.com'"); } void test_float_node() { std::cout << "> Float node serialization..." << std::endl; auto float_val = std::make_shared(ConstFloatNode(3.14159)); IRModule module; module.entry = float_val; Serializer ser; auto bytes = ser.serialize_to_bytes(module); Deserializer deser; auto loaded = deser.deserialize(bytes); auto* loaded_float = loaded.entry->get_if(); TEST_CHECK(loaded_float != nullptr, "Deserialized node is ConstFloatNode"); TEST_CHECK(loaded_float && loaded_float->value > 3.14 && loaded_float->value < 3.15, "Float value is approximately 3.14159"); } int main() { std::cout << "=== Regression Tests for Nixir ===" << std::endl << std::endl; test_enum_compatibility(); std::cout << std::endl; test_serializer_select_with_default(); std::cout << std::endl; test_serializer_select_without_default(); std::cout << std::endl; test_parser_brace_depth_in_strings(); std::cout << std::endl; test_parser_has_ellipsis_usage(); std::cout << std::endl; test_parser_expect_in_speculative_parsing(); std::cout << std::endl; test_lookup_path_node(); std::cout << std::endl; test_import_node(); std::cout << std::endl; test_import_with_lookup_path(); std::cout << std::endl; test_uri_node(); std::cout << std::endl; test_float_node(); std::cout << std::endl; std::cout << "=== Tests Complete ===" << std::endl; std::cout << "Failures: " << failures << std::endl; return failures > 0 ? 1 : 0; }