brewery
notashelf /
531855d91ac47228559590d52518734601d57fe8

nixir

public

Import-resolving Nix IR plugin

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/nixir.git

Commit 531855d91ac4

tarball

NotAShelf <raf@notashelf.dev> · 2026-04-24 20:13 UTC

unverified4 files changed+171-2
tests: cover flake refs and lexer/parser regressions

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I1b5f90bbb210262a9287a9b8eac02e9d6a6a6964
diff --git a/src/irc/types.h b/src/irc/types.hindex 79f4275..328bf82 100644--- a/src/irc/types.h+++ b/src/irc/types.h@@ -352,8 +352,6 @@ public:   template <typename T> bool holds() const { return std::holds_alternative<T>(data); } }; -- struct SourceFile {   std::string path;   std::string content;diff --git a/tests/integration/flake_ref/flake.nix b/tests/integration/flake_ref/flake.nixnew file mode 100644index 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.shindex 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.cppindex 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<BinaryOpNode>();+  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<VarNode>();+    auto* right = outer->right->get_if<BinaryOpNode>();+    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("<nixpkgs> 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<ImportNode>();+  TEST_CHECK(import_node != nullptr, "Parsed expression is ImportNode");++  if (import_node && import_node->path) {+    auto* path_node = import_node->path->get_if<ConstPathNode>();+    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<Node>(ConstStringNode("/tmp/example-flake"));+  auto builtin =+      std::make_shared<Node>(BuiltinCallNode("getFlake", std::vector<std::shared_ptr<Node>>{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<BuiltinCallNode>();+  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<ConstStringNode>();+    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;