tests: cover flake refs and lexer/parser regressions

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I1b5f90bbb210262a9287a9b8eac02e9d6a6a6964
This commit is contained in:
raf 2026-04-24 18:35:20 +03:00
commit 531855d91a
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
4 changed files with 171 additions and 2 deletions

View file

@ -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;

View file

@ -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;
})
];
};
};
}

View file

@ -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"

View file

@ -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;