diff --git a/src/irc/types.h b/src/irc/types.h index 79f4275..328bf82 100644 --- a/src/irc/types.h +++ b/src/irc/types.h @@ -352,8 +352,6 @@ public: template bool holds() const { return std::holds_alternative(data); } }; - - struct SourceFile { std::string path; std::string content; diff --git a/tests/integration/flake_ref/flake.nix b/tests/integration/flake_ref/flake.nix new file mode 100644 index 0000000..b820ce6 --- /dev/null +++ b/tests/integration/flake_ref/flake.nix @@ -0,0 +1,19 @@ +{ + description = "Local flake fixture for nixir integration tests"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + + outputs = { self, nixpkgs }: { + value = 42; + nixosConfigurations.demo = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ({ ... }: { + networking.hostName = "nixir-demo"; + system.stateVersion = "24.11"; + services.openssh.enable = true; + }) + ]; + }; + }; +} diff --git a/tests/integration/run.sh b/tests/integration/run.sh index 7c978ba..bbdea6c 100755 --- a/tests/integration/run.sh +++ b/tests/integration/run.sh @@ -72,4 +72,32 @@ else fi echo "" +echo "Test 6: Flake Reference Compilation" +echo "-----------------------------------" +flake_ir=$(mktemp /tmp/nixir-flake-value-XXXXXX.nixir) +"$(pwd)/build/nix-irc" "$TEST_DIR/flake_ref#value" "$flake_ir" +result=$(nix-instantiate --plugin-files "$PLUGIN_PATH" --eval --strict --json --expr "builtins.nixIR_loadIR \"$flake_ir\"" 2>&1) +if echo "$result" | grep -q '^42$'; then + echo "[PASS] Flake reference compiles and evaluates correctly" +else + echo "[FAIL] Flake reference compilation broken" + echo "$result" + exit 1 +fi +echo "" + +echo "Test 7: NixOS Configuration Attribute Path" +echo "------------------------------------------" +config_ir=$(mktemp /tmp/nixir-flake-config-XXXXXX.nixir) +"$(pwd)/build/nix-irc" "$TEST_DIR/flake_ref#nixosConfigurations.demo.config.networking.hostName" "$config_ir" +result=$(nix-instantiate --plugin-files "$PLUGIN_PATH" --eval --strict --json --expr "builtins.nixIR_loadIR \"$config_ir\"" 2>&1) +if echo "$result" | grep -q '"nixir-demo"'; then + echo "[PASS] Nested flake attribute selection works for nixosConfigurations" +else + echo "[FAIL] NixOS configuration flake selection broken" + echo "$result" + exit 1 +fi +echo "" + echo "Integration Tests Complete" diff --git a/tests/regression_test.cpp b/tests/regression_test.cpp index 71d7d92..267837e 100644 --- a/tests/regression_test.cpp +++ b/tests/regression_test.cpp @@ -1,3 +1,4 @@ +#include "irc/lexer.h" #include "irc/parser.h" #include "irc/serializer.h" #include "irc/types.h" @@ -166,6 +167,64 @@ void test_parser_expect_in_speculative_parsing() { } } +void test_implication_right_associativity() { + std::cout << "> Implication right associativity..." << std::endl; + + Parser parser; + auto ast = parser.parse("a -> b -> c"); + + auto* outer = ast->get_if(); + TEST_CHECK(outer != nullptr, "Top-level node is BinaryOpNode"); + TEST_CHECK(outer && outer->op == BinaryOp::IMPL, "Top-level operator is implication"); + + if (outer) { + auto* left = outer->left->get_if(); + auto* right = outer->right->get_if(); + TEST_CHECK(left != nullptr && left->name && *left->name == "a", "Left branch is variable 'a'"); + TEST_CHECK(right != nullptr && right->op == BinaryOp::IMPL, + "Right branch is nested implication"); + } +} + +void test_lookup_path_lexer_position() { + std::cout << "> Lookup path lexer position..." << std::endl; + + Lexer lexer(" x"); + auto tokens = lexer.tokenize(); + + TEST_CHECK(tokens.size() >= 3, "Lexer produced lookup path, identifier, and EOF"); + TEST_CHECK(tokens[0].type == Token::LOOKUP_PATH, "First token is LOOKUP_PATH"); + TEST_CHECK(tokens[1].type == Token::IDENT && tokens[1].value == "x", + "Second token is identifier 'x'"); + TEST_CHECK(tokens[1].col == 11, "Identifier column reflects consumed lookup path width"); +} + +void test_unterminated_block_comment_rejected() { + std::cout << "> Unterminated block comment rejection..." << std::endl; + + try { + Lexer lexer("/* unterminated"); + auto tokens = lexer.tokenize(); + (void) tokens; + TEST_FAIL("Lexer should reject unterminated block comments"); + } catch (const std::exception& e) { + TEST_PASS("Lexer rejects unterminated block comments"); + } +} + +void test_unknown_character_rejected() { + std::cout << "> Unknown character rejection..." << std::endl; + + try { + Lexer lexer("1 $ 2"); + auto tokens = lexer.tokenize(); + (void) tokens; + TEST_FAIL("Lexer should reject unexpected characters"); + } catch (const std::exception& e) { + TEST_PASS("Lexer rejects unexpected characters"); + } +} + void test_lookup_path_node() { std::cout << "> Lookup path serialization..." << std::endl; @@ -233,6 +292,53 @@ void test_import_with_lookup_path() { } } +void test_relative_path_import_parsing() { + std::cout << "> Relative path import parsing..." << std::endl; + + Parser parser; + auto ast = parser.parse("import ./simple.nix"); + + auto* import_node = ast->get_if(); + TEST_CHECK(import_node != nullptr, "Parsed expression is ImportNode"); + + if (import_node && import_node->path) { + auto* path_node = import_node->path->get_if(); + TEST_CHECK(path_node != nullptr, "Import argument is ConstPathNode"); + TEST_CHECK(path_node && path_node->value == "./simple.nix", + "Relative path is preserved as './simple.nix'"); + } +} + +void test_builtin_call_node() { + std::cout << "> BuiltinCallNode serialization..." << std::endl; + + auto arg = std::make_shared(ConstStringNode("/tmp/example-flake")); + auto builtin = + std::make_shared(BuiltinCallNode("getFlake", std::vector>{arg})); + + IRModule module; + module.entry = builtin; + + Serializer ser; + auto bytes = ser.serialize_to_bytes(module); + + Deserializer deser; + auto loaded = deser.deserialize(bytes); + + auto* loaded_builtin = loaded.entry->get_if(); + TEST_CHECK(loaded_builtin != nullptr, "Deserialized node is BuiltinCallNode"); + TEST_CHECK(loaded_builtin && loaded_builtin->builtin_name == "getFlake", + "Builtin name is 'getFlake'"); + TEST_CHECK(loaded_builtin && loaded_builtin->args.size() == 1, "Builtin has one argument"); + + if (loaded_builtin && loaded_builtin->args.size() == 1) { + auto* loaded_arg = loaded_builtin->args[0]->get_if(); + TEST_CHECK(loaded_arg != nullptr, "Builtin argument is ConstStringNode"); + TEST_CHECK(loaded_arg && loaded_arg->value == "/tmp/example-flake", + "Builtin argument value round-trips"); + } +} + void test_uri_node() { std::cout << "> URI node serialization..." << std::endl; @@ -642,6 +748,18 @@ int main() { test_parser_expect_in_speculative_parsing(); std::cout << std::endl; + test_implication_right_associativity(); + std::cout << std::endl; + + test_lookup_path_lexer_position(); + std::cout << std::endl; + + test_unterminated_block_comment_rejected(); + std::cout << std::endl; + + test_unknown_character_rejected(); + std::cout << std::endl; + test_lookup_path_node(); std::cout << std::endl; @@ -651,6 +769,12 @@ int main() { test_import_with_lookup_path(); std::cout << std::endl; + test_relative_path_import_parsing(); + std::cout << std::endl; + + test_builtin_call_node(); + std::cout << std::endl; + test_uri_node(); std::cout << std::endl;