This commit is contained in:
2026-03-12 17:47:46 +08:00
parent 7a7229d70e
commit 0c9a391618
511 changed files with 234 additions and 12772 deletions

View File

@@ -0,0 +1,69 @@
use fix::value::Value;
use crate::utils::{eval, eval_result};
#[test_log::test]
fn arithmetic() {
assert_eq!(eval("1 + 1"), Value::Int(2));
}
#[test_log::test]
fn simple_function_application() {
assert_eq!(eval("(x: x) 1"), Value::Int(1));
}
#[test_log::test]
fn curried_function() {
assert_eq!(eval("(x: y: x - y) 2 1"), Value::Int(1));
}
#[test_log::test]
fn rec_attrset() {
assert_eq!(eval("rec { b = a; a = 1; }.b"), Value::Int(1));
}
#[test_log::test]
fn let_binding() {
assert_eq!(eval("let b = a; a = 1; in b"), Value::Int(1));
}
#[test_log::test]
fn fibonacci() {
assert_eq!(
eval(
"let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30"
),
Value::Int(832040)
);
}
#[test_log::test]
fn fixed_point_combinator() {
assert_eq!(
eval("((f: let x = f x; in x)(self: { x = 1; y = self.x + 1; })).y"),
Value::Int(2)
);
}
#[test_log::test]
fn conditional_true() {
assert_eq!(eval("if true then 1 else 0"), Value::Int(1));
}
#[test_log::test]
fn conditional_false() {
assert_eq!(eval("if false then 1 else 0"), Value::Int(0));
}
#[test_log::test]
fn nested_let() {
assert_eq!(
eval("let x = 1; in let y = x + 1; z = y + 1; in z"),
Value::Int(3)
);
}
#[test_log::test]
fn rec_inherit_fails() {
assert!(eval_result("{ inherit x; }").is_err());
}

326
fix/tests/tests/builtins.rs Normal file
View File

@@ -0,0 +1,326 @@
use std::collections::BTreeMap;
use fix::value::{AttrSet, List, Value};
use crate::utils::eval;
#[test_log::test]
fn builtins_accessible() {
let result = eval("builtins");
assert!(matches!(result, Value::AttrSet(_)));
}
#[test_log::test]
fn builtins_self_reference() {
let result = eval("builtins.builtins");
assert!(matches!(result, Value::AttrSet(_)));
}
#[test_log::test]
fn builtins_add() {
assert_eq!(eval("builtins.add 1 2"), Value::Int(3));
}
#[test_log::test]
fn builtins_length() {
assert_eq!(eval("builtins.length [1 2 3]"), Value::Int(3));
}
#[test_log::test]
fn builtins_map() {
assert_eq!(
eval("builtins.map (x: x * 2) [1 2 3]"),
Value::List(List::new(vec![Value::Int(2), Value::Int(4), Value::Int(6)]))
);
}
#[test_log::test]
fn builtins_filter() {
assert_eq!(
eval("builtins.filter (x: x > 1) [1 2 3]"),
Value::List(List::new(vec![Value::Int(2), Value::Int(3)]))
);
}
#[test_log::test]
fn builtins_attrnames() {
let result = eval("builtins.attrNames { a = 1; b = 2; }");
assert!(matches!(result, Value::List(_)));
if let Value::List(list) = result {
assert_eq!(format!("{:?}", list).matches(',').count() + 1, 2);
}
}
#[test_log::test]
fn builtins_head() {
assert_eq!(eval("builtins.head [1 2 3]"), Value::Int(1));
}
#[test_log::test]
fn builtins_tail() {
assert_eq!(
eval("builtins.tail [1 2 3]"),
Value::List(List::new(vec![Value::Int(2), Value::Int(3)]))
);
}
#[test_log::test]
fn builtins_in_let() {
assert_eq!(eval("let b = builtins; in b.add 5 3"), Value::Int(8));
}
#[test_log::test]
fn builtins_in_with() {
assert_eq!(eval("with builtins; add 10 20"), Value::Int(30));
}
#[test_log::test]
fn builtins_nested_calls() {
assert_eq!(
eval("builtins.add (builtins.mul 2 3) (builtins.sub 10 5)"),
Value::Int(11)
);
}
#[test_log::test]
fn builtins_is_list() {
assert_eq!(eval("builtins.isList [1 2 3]"), Value::Bool(true));
}
#[test_log::test]
fn builtins_is_attrs() {
assert_eq!(eval("builtins.isAttrs { a = 1; }"), Value::Bool(true));
}
#[test_log::test]
fn builtins_is_function() {
assert_eq!(eval("builtins.isFunction (x: x)"), Value::Bool(true));
}
#[test_log::test]
fn builtins_is_null() {
assert_eq!(eval("builtins.isNull null"), Value::Bool(true));
}
#[test_log::test]
fn builtins_is_bool() {
assert_eq!(eval("builtins.isBool true"), Value::Bool(true));
}
#[test_log::test]
fn builtins_shadowing() {
assert_eq!(
eval("let builtins = { add = x: y: x - y; }; in builtins.add 5 3"),
Value::Int(2)
);
}
#[test_log::test]
fn builtins_lazy_evaluation() {
let result = eval("builtins.builtins.builtins.add 1 1");
assert_eq!(result, Value::Int(2));
}
#[test_log::test]
fn builtins_foldl() {
assert_eq!(
eval("builtins.foldl' (acc: x: acc + x) 0 [1 2 3 4 5]"),
Value::Int(15)
);
}
#[test_log::test]
fn builtins_elem() {
assert_eq!(eval("builtins.elem 2 [1 2 3]"), Value::Bool(true));
assert_eq!(eval("builtins.elem 5 [1 2 3]"), Value::Bool(false));
}
#[test_log::test]
fn builtins_concat_lists() {
assert_eq!(
eval("builtins.concatLists [[1 2] [3 4] [5]]"),
Value::List(List::new(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
Value::Int(5)
]))
);
}
#[test_log::test]
fn builtins_compare_versions_basic() {
assert_eq!(
eval("builtins.compareVersions \"1.0\" \"2.3\""),
Value::Int(-1)
);
assert_eq!(
eval("builtins.compareVersions \"2.1\" \"2.3\""),
Value::Int(-1)
);
assert_eq!(
eval("builtins.compareVersions \"2.3\" \"2.3\""),
Value::Int(0)
);
assert_eq!(
eval("builtins.compareVersions \"2.5\" \"2.3\""),
Value::Int(1)
);
assert_eq!(
eval("builtins.compareVersions \"3.1\" \"2.3\""),
Value::Int(1)
);
}
#[test_log::test]
fn builtins_compare_versions_components() {
assert_eq!(
eval("builtins.compareVersions \"2.3.1\" \"2.3\""),
Value::Int(1)
);
assert_eq!(
eval("builtins.compareVersions \"2.3\" \"2.3.1\""),
Value::Int(-1)
);
}
#[test_log::test]
fn builtins_compare_versions_numeric_vs_alpha() {
// Numeric component comes before alpha component
assert_eq!(
eval("builtins.compareVersions \"2.3.1\" \"2.3a\""),
Value::Int(1)
);
assert_eq!(
eval("builtins.compareVersions \"2.3a\" \"2.3.1\""),
Value::Int(-1)
);
}
#[test_log::test]
fn builtins_compare_versions_pre() {
// "pre" is special: comes before everything except another "pre"
assert_eq!(
eval("builtins.compareVersions \"2.3pre1\" \"2.3\""),
Value::Int(-1)
);
assert_eq!(
eval("builtins.compareVersions \"2.3pre3\" \"2.3pre12\""),
Value::Int(-1)
);
assert_eq!(
eval("builtins.compareVersions \"2.3pre1\" \"2.3c\""),
Value::Int(-1)
);
assert_eq!(
eval("builtins.compareVersions \"2.3pre1\" \"2.3q\""),
Value::Int(-1)
);
}
#[test_log::test]
fn builtins_compare_versions_alpha() {
// Alphabetic comparison
assert_eq!(
eval("builtins.compareVersions \"2.3a\" \"2.3c\""),
Value::Int(-1)
);
assert_eq!(
eval("builtins.compareVersions \"2.3c\" \"2.3a\""),
Value::Int(1)
);
}
#[test_log::test]
fn builtins_compare_versions_symmetry() {
// Test symmetry: compareVersions(a, b) == -compareVersions(b, a)
assert_eq!(
eval("builtins.compareVersions \"1.0\" \"2.3\""),
Value::Int(-1)
);
assert_eq!(
eval("builtins.compareVersions \"2.3\" \"1.0\""),
Value::Int(1)
);
}
#[test_log::test]
fn builtins_compare_versions_complex() {
// Complex version strings with multiple components
assert_eq!(
eval("builtins.compareVersions \"1.2.3.4\" \"1.2.3.5\""),
Value::Int(-1)
);
assert_eq!(
eval("builtins.compareVersions \"1.2.10\" \"1.2.9\""),
Value::Int(1)
);
assert_eq!(
eval("builtins.compareVersions \"1.2a3\" \"1.2a10\""),
Value::Int(-1)
);
}
#[test_log::test]
fn builtins_generic_closure() {
assert_eq!(
eval(
"with builtins; length (genericClosure { startSet = [ { key = 1; } ]; operator = { key }: [ { key = key / 1.; } ]; a = 1; })"
),
Value::Int(1),
);
assert_eq!(
eval(
"with builtins; (elemAt (genericClosure { startSet = [ { key = 1; } ]; operator = { key }: [ { key = key / 1.; } ]; a = 1; }) 0).key"
),
Value::Int(1),
);
}
#[test_log::test]
fn builtins_function_args() {
assert_eq!(
eval("builtins.functionArgs (x: 1)"),
Value::AttrSet(AttrSet::default())
);
assert_eq!(
eval("builtins.functionArgs ({}: 1)"),
Value::AttrSet(AttrSet::default())
);
assert_eq!(
eval("builtins.functionArgs ({...}: 1)"),
Value::AttrSet(AttrSet::default())
);
assert_eq!(
eval("builtins.functionArgs ({a}: 1)"),
Value::AttrSet(AttrSet::new(BTreeMap::from([(
"a".into(),
Value::Bool(false)
)])))
);
assert_eq!(
eval("builtins.functionArgs ({a, b ? 1}: 1)"),
Value::AttrSet(AttrSet::new(BTreeMap::from([
("a".into(), Value::Bool(false)),
("b".into(), Value::Bool(true))
])))
);
assert_eq!(
eval("builtins.functionArgs ({a, b ? 1, ...}: 1)"),
Value::AttrSet(AttrSet::new(BTreeMap::from([
("a".into(), Value::Bool(false)),
("b".into(), Value::Bool(true))
])))
);
}
#[test_log::test]
fn builtins_parse_drv_name() {
let result = eval(r#"builtins.parseDrvName "nix-js-0.1.0pre""#).unwrap_attr_set();
assert_eq!(result.get("name"), Some(&Value::String("nix-js".into())));
assert_eq!(
result.get("version"),
Some(&Value::String("0.1.0pre".into()))
);
}

View File

@@ -0,0 +1,193 @@
use fix::value::Value;
use crate::utils::eval_result;
#[test_log::test]
fn to_file_simple() {
let result =
eval_result(r#"builtins.toFile "hello.txt" "Hello, World!""#).expect("Failed to evaluate");
match result {
Value::String(path) => {
assert!(path.contains("-hello.txt"));
assert!(std::path::Path::new(&path).exists());
let contents = std::fs::read_to_string(&path).expect("Failed to read file");
assert_eq!(contents, "Hello, World!");
}
_ => panic!("Expected string, got {:?}", result),
}
}
#[test_log::test]
fn to_file_with_references() {
let result = eval_result(
r#"
let
dep = builtins.toFile "dep.txt" "dependency";
in
builtins.toFile "main.txt" "Reference: ${dep}"
"#,
)
.expect("Failed to evaluate");
match result {
Value::String(path) => {
assert!(path.contains("-main.txt"));
let contents = std::fs::read_to_string(&path).expect("Failed to read file");
assert!(contents.contains("Reference: "));
assert!(contents.contains("-dep.txt"));
}
_ => panic!("Expected string"),
}
}
#[test_log::test]
fn to_file_invalid_name_with_slash() {
let result = eval_result(r#"builtins.toFile "foo/bar.txt" "content""#);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("name cannot contain '/'")
);
}
#[test_log::test]
fn to_file_invalid_name_dot() {
let result = eval_result(r#"builtins.toFile "." "content""#);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("invalid name"));
}
#[test_log::test]
fn to_file_invalid_name_dotdot() {
let result = eval_result(r#"builtins.toFile ".." "content""#);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("invalid name"));
}
#[test_log::test]
fn store_path_validation_not_in_store() {
let result = eval_result(r#"builtins.storePath "/tmp/foo""#);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("not in the Nix store")
);
}
#[test_log::test]
fn store_path_validation_malformed_hash() {
let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#)
.expect("Failed to create dummy file");
let dummy_path = match dummy_file_result {
Value::String(ref p) => p.clone(),
_ => panic!("Expected string"),
};
let store_dir = std::path::Path::new(&dummy_path)
.parent()
.expect("Failed to get parent dir")
.to_str()
.expect("Failed to convert to string");
let test_path = format!("{}/invalid-hash-hello", store_dir);
let result = eval_result(&format!(r#"builtins.storePath "{}""#, test_path));
assert!(result.is_err());
let err_str = result.unwrap_err().to_string();
assert!(
err_str.contains("invalid") || err_str.contains("hash"),
"Expected hash validation error, got: {}",
err_str
);
}
#[test_log::test]
fn store_path_validation_missing_name() {
let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#)
.expect("Failed to create dummy file");
let dummy_path = match dummy_file_result {
Value::String(ref p) => p.clone(),
_ => panic!("Expected string"),
};
let store_dir = std::path::Path::new(&dummy_path)
.parent()
.expect("Failed to get parent dir")
.to_str()
.expect("Failed to convert to string");
let test_path = format!("{}/abcd1234abcd1234abcd1234abcd1234", store_dir);
let result = eval_result(&format!(r#"builtins.storePath "{}""#, test_path));
assert!(result.is_err());
let err_str = result.unwrap_err().to_string();
assert!(
err_str.contains("missing name") || err_str.contains("format"),
"Expected missing name error, got: {}",
err_str
);
}
#[test_log::test]
fn to_file_curried_application() {
let result = eval_result(
r#"
let
makeFile = builtins.toFile "test.txt";
in
makeFile "test content"
"#,
)
.expect("Failed to evaluate");
match result {
Value::String(path) => {
assert!(path.contains("-test.txt"));
let contents = std::fs::read_to_string(&path).expect("Failed to read file");
assert_eq!(contents, "test content");
}
_ => panic!("Expected string"),
}
}
#[test_log::test]
fn to_file_number_conversion() {
let result = eval_result(r#"builtins.toFile "number.txt" (builtins.toString 42)"#)
.expect("Failed to evaluate");
match result {
Value::String(path) => {
let contents = std::fs::read_to_string(&path).expect("Failed to read file");
assert_eq!(contents, "42");
}
_ => panic!("Expected string"),
}
}
#[test_log::test]
fn to_file_list_conversion() {
let result = eval_result(
r#"builtins.toFile "list.txt" (builtins.concatStringsSep "\n" ["line1" "line2" "line3"])"#,
)
.expect("Failed to evaluate");
match result {
Value::String(path) => {
let contents = std::fs::read_to_string(&path).expect("Failed to read file");
assert_eq!(contents, "line1\nline2\nline3");
}
_ => panic!("Expected string"),
}
}

View File

@@ -0,0 +1,687 @@
use fix::value::Value;
use crate::utils::{eval_deep, eval_deep_result};
#[test_log::test]
fn add_operator_preserves_derivation_context() {
let result = eval_deep(
r#"
let
dep = derivation { name = "dep"; builder = "/bin/sh"; system = "x86_64-linux"; outputs = ["out" "dev"]; };
getOutput = output: pkg: pkg.${output} or pkg.out or pkg;
user = derivation {
name = "user";
builder = "/bin/sh";
system = "x86_64-linux";
libPath = (getOutput "lib" dep) + "/lib";
devPath = dep.dev + "/include";
};
in user.drvPath
"#,
);
let nix_result = eval_deep(
r#"
let
dep = derivation { name = "dep"; builder = "/bin/sh"; system = "x86_64-linux"; outputs = ["out" "dev"]; };
user = derivation {
name = "user";
builder = "/bin/sh";
system = "x86_64-linux";
libPath = "${dep.out}/lib";
devPath = "${dep.dev}/include";
};
in user.drvPath
"#,
);
assert_eq!(result, nix_result);
}
#[test_log::test]
fn derivation_minimal() {
let result = eval_deep(
r#"derivation { name = "hello"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
);
match result {
Value::AttrSet(attrs) => {
assert_eq!(attrs.get("type"), Some(&Value::String("derivation".into())));
assert_eq!(attrs.get("name"), Some(&Value::String("hello".into())));
assert_eq!(attrs.get("builder"), Some(&Value::String("/bin/sh".into())));
assert_eq!(
attrs.get("system"),
Some(&Value::String("x86_64-linux".into()))
);
assert!(attrs.contains_key("outPath"));
assert!(attrs.contains_key("drvPath"));
if let Some(Value::String(path)) = attrs.get("outPath") {
assert_eq!(path, "/nix/store/pnwh4xsfs4j508bs9iw6bpkyc4zw6ryx-hello");
} else {
panic!("outPath should be a string");
}
if let Some(Value::String(path)) = attrs.get("drvPath") {
assert_eq!(
path,
"/nix/store/x0sj6ynccvc1a8kxr8fifnlf7qlxw6hd-hello.drv"
);
} else {
panic!("drvPath should be a string");
}
}
_ => panic!("Expected AttrSet, got {:?}", result),
}
}
#[test_log::test]
fn derivation_with_args() {
let result = eval_deep(
r#"derivation {
name = "test";
builder = "/bin/sh";
system = "x86_64-linux";
args = ["-c" "echo hello"];
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("args"));
if let Some(Value::List(args)) = attrs.get("args") {
assert_eq!(args.len(), 2);
}
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn derivation_to_string() {
let result = eval_deep(
r#"toString (derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; })"#,
);
match result {
Value::String(s) => assert_eq!(s, "/nix/store/xpcvxsx5sw4rbq666blz6sxqlmsqphmr-foo"),
_ => panic!("Expected String, got {:?}", result),
}
}
#[test_log::test]
fn derivation_missing_name() {
let result =
eval_deep_result(r#"derivation { builder = "/bin/sh"; system = "x86_64-linux"; }"#);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("missing required attribute 'name'"));
}
#[test_log::test]
fn derivation_invalid_name_with_drv_suffix() {
let result = eval_deep_result(
r#"derivation { name = "foo.drv"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("cannot end with .drv"));
}
#[test_log::test]
fn derivation_missing_builder() {
let result = eval_deep_result(r#"derivation { name = "test"; system = "x86_64-linux"; }"#);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("missing required attribute 'builder'"));
}
#[test_log::test]
fn derivation_missing_system() {
let result = eval_deep_result(r#"derivation { name = "test"; builder = "/bin/sh"; }"#);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("missing required attribute 'system'"));
}
#[test_log::test]
fn derivation_with_env_vars() {
let result = eval_deep(
r#"derivation {
name = "test";
builder = "/bin/sh";
system = "x86_64-linux";
MY_VAR = "hello";
ANOTHER = "world";
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert_eq!(attrs.get("MY_VAR"), Some(&Value::String("hello".into())));
assert_eq!(attrs.get("ANOTHER"), Some(&Value::String("world".into())));
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn derivation_strict() {
let result = eval_deep(
r#"builtins.derivationStrict { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("drvPath"));
assert!(attrs.contains_key("out"));
assert!(!attrs.contains_key("type"));
assert!(!attrs.contains_key("outPath"));
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn derivation_deterministic_paths() {
let expr = r#"derivation { name = "hello"; builder = "/bin/sh"; system = "x86_64-linux"; }"#;
let result1 = eval_deep(expr);
let result2 = eval_deep(expr);
match (result1, result2) {
(Value::AttrSet(attrs1), Value::AttrSet(attrs2)) => {
assert_eq!(attrs1.get("drvPath"), attrs2.get("drvPath"));
assert_eq!(attrs1.get("outPath"), attrs2.get("outPath"));
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn derivation_escaping_in_aterm() {
let result = eval_deep(
r#"derivation {
name = "test";
builder = "/bin/sh";
system = "x86_64-linux";
args = ["-c" "echo \"hello\nworld\""];
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("drvPath"));
assert!(attrs.contains_key("outPath"));
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn multi_output_two_outputs() {
let drv = eval_deep(
r#"derivation {
name = "multi";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
}"#,
);
match drv {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("drvPath"));
assert!(attrs.contains_key("out"));
assert!(attrs.contains_key("dev"));
assert!(attrs.contains_key("outPath"));
if let Some(Value::String(drv_path)) = attrs.get("drvPath") {
assert_eq!(
drv_path,
"/nix/store/vmyjryfipkn9ss3ya23hk8p3m58l6dsl-multi.drv"
);
} else {
panic!(
"drvPath should be a string, got: {:?}",
attrs.get("drvPath")
);
}
if let Some(Value::String(out_path)) = attrs.get("outPath") {
assert_eq!(
out_path,
"/nix/store/a3d95yg9d215c54n0ybr4npmpnj29229-multi"
);
} else {
panic!("outPath should be a string");
}
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn multi_output_three_outputs() {
let result = eval_deep(
r#"derivation {
name = "three";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev" "doc"];
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("out"));
assert!(attrs.contains_key("dev"));
assert!(attrs.contains_key("doc"));
// Verify exact paths match CppNix
if let Some(Value::String(drv_path)) = attrs.get("drvPath") {
assert_eq!(
drv_path,
"/nix/store/w08rpwvs5j9yxvdx5f5yg0p5i3ncazdx-three.drv"
);
}
if let Some(Value::String(out_path)) = attrs.get("out") {
assert_eq!(
out_path,
"/nix/store/i479clih5pb6bn2d2b758sbaylvbs2cl-three"
);
}
if let Some(Value::String(dev_path)) = attrs.get("dev") {
assert_eq!(
dev_path,
"/nix/store/gg8v395vci5xg1i9grc8ifh5xagw5f2j-three-dev"
);
}
if let Some(Value::String(doc_path)) = attrs.get("doc") {
assert_eq!(
doc_path,
"/nix/store/p2avgz16qx5k2jgnq3ch04k154xj1ac0-three-doc"
);
}
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn multi_output_backward_compat() {
let result = eval_deep(
r#"derivation {
name = "compat";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out"];
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("outPath"));
assert!(attrs.contains_key("out"));
if let (Some(Value::String(out_path)), Some(Value::String(out))) =
(attrs.get("outPath"), attrs.get("out"))
{
assert_eq!(out_path, out);
}
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn multi_output_deterministic() {
let result1 = eval_deep(
r#"derivation {
name = "determ";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
}"#,
);
let result2 = eval_deep(
r#"derivation {
name = "determ";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
}"#,
);
assert_eq!(result1, result2);
}
#[test_log::test]
fn fixed_output_sha256_flat() {
let result = eval_deep(
r#"derivation {
name = "fixed";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
outputHashAlgo = "sha256";
outputHashMode = "flat";
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("outPath"));
assert!(attrs.contains_key("drvPath"));
// Verify exact paths match CppNix
if let Some(Value::String(out_path)) = attrs.get("outPath") {
assert_eq!(
out_path,
"/nix/store/ap9h69qwrm5060ldi96axyklh3pr3yjn-fixed"
);
}
if let Some(Value::String(drv_path)) = attrs.get("drvPath") {
assert_eq!(
drv_path,
"/nix/store/kj9gsfz5cngc38n1xlf6ljlgvnsfg0cj-fixed.drv"
);
}
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn fixed_output_missing_hashalgo() {
assert!(
eval_deep_result(
r#"derivation {
name = "default";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
}"#,
)
.is_err()
);
}
#[test_log::test]
fn fixed_output_recursive_mode() {
let result = eval_deep(
r#"derivation {
name = "recursive";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "1111111111111111111111111111111111111111111111111111111111111111";
outputHashAlgo = "sha256";
outputHashMode = "recursive";
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("outPath"));
assert!(attrs.contains_key("drvPath"));
// Verify exact path matches CppNix
if let Some(Value::String(out_path)) = attrs.get("outPath") {
assert_eq!(
out_path,
"/nix/store/qyal5s16hfwxhz5zwpf8h8yv2bs84z56-recursive"
);
}
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn fixed_output_rejects_multi_output() {
let result = eval_deep_result(
r#"derivation {
name = "invalid";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
outputHashAlgo = "sha256";
outputs = ["out" "dev"];
}"#,
);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("fixed-output") && err_msg.contains("one"));
}
#[test_log::test]
fn fixed_output_invalid_hash_mode() {
let result = eval_deep_result(
r#"derivation {
name = "invalid";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
outputHashMode = "invalid";
}"#,
);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("outputHashMode") && err_msg.contains("invalid"));
}
#[test_log::test]
fn structured_attrs_basic() {
let result = eval_deep(
r#"derivation {
name = "struct";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
foo = "bar";
count = 42;
enabled = true;
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("drvPath"));
assert!(attrs.contains_key("outPath"));
assert!(attrs.contains_key("foo"));
assert!(attrs.contains_key("count"));
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn structured_attrs_nested() {
let result = eval_deep(
r#"derivation {
name = "nested";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
data = { x = 1; y = [2 3]; };
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("drvPath"));
assert!(attrs.contains_key("data"));
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn structured_attrs_rejects_functions() {
let result = eval_deep_result(
r#"derivation {
name = "invalid";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
func = x: x + 1;
}"#,
);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("cannot convert lambda to JSON"));
}
#[test_log::test]
fn structured_attrs_false() {
let result = eval_deep(
r#"derivation {
name = "normal";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = false;
foo = "bar";
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("foo"));
if let Some(Value::String(val)) = attrs.get("foo") {
assert_eq!(val, "bar");
}
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn ignore_nulls_true() {
let result = eval_deep(
r#"derivation {
name = "ignore";
builder = "/bin/sh";
system = "x86_64-linux";
__ignoreNulls = true;
foo = "bar";
nullValue = null;
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("foo"));
assert!(attrs.contains_key("nullValue"));
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn ignore_nulls_false() {
let result = eval_deep(
r#"derivation {
name = "keep";
builder = "/bin/sh";
system = "x86_64-linux";
__ignoreNulls = false;
nullValue = null;
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("nullValue"));
if let Some(Value::String(val)) = attrs.get("nullValue") {
assert_eq!(val, "");
}
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn ignore_nulls_with_structured_attrs() {
let result = eval_deep(
r#"derivation {
name = "combined";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
__ignoreNulls = true;
foo = "bar";
nullValue = null;
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("drvPath"));
assert!(attrs.contains_key("foo"));
assert!(attrs.contains_key("nullValue"));
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn all_features_combined() {
let result = eval_deep(
r#"derivation {
name = "all";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
__structuredAttrs = true;
__ignoreNulls = true;
data = { x = 1; };
nullValue = null;
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("out"));
assert!(attrs.contains_key("dev"));
assert!(attrs.contains_key("outPath"));
assert!(attrs.contains_key("data"));
assert!(attrs.contains_key("nullValue"));
}
_ => panic!("Expected AttrSet"),
}
}
#[test_log::test]
fn fixed_output_with_structured_attrs() {
let result = eval_deep(
r#"derivation {
name = "fixstruct";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
outputHashAlgo = "sha256";
__structuredAttrs = true;
data = { key = "value"; };
}"#,
);
match result {
Value::AttrSet(attrs) => {
assert!(attrs.contains_key("outPath"));
assert!(attrs.contains_key("drvPath"));
assert!(attrs.contains_key("data"));
}
_ => panic!("Expected AttrSet"),
}
}

View File

@@ -0,0 +1,36 @@
use crate::utils::eval;
#[test_log::test]
fn test_find_file_corepkg_fetchurl() {
let result = eval(
r#"
let
searchPath = [];
lookupPath = "nix/fetchurl.nix";
in
builtins.findFile searchPath lookupPath
"#,
);
assert!(result.to_string().contains("fetchurl.nix"));
}
#[test_log::test]
fn test_lookup_path_syntax() {
let result = eval(r#"<nix/fetchurl.nix>"#);
assert!(result.to_string().contains("fetchurl.nix"));
}
#[test_log::test]
fn test_import_corepkg() {
let result = eval(
r#"
let
fetchurl = import <nix/fetchurl.nix>;
in
builtins.typeOf fetchurl
"#,
);
assert_eq!(result.to_string(), "\"lambda\"");
}

View File

@@ -0,0 +1,74 @@
use fix::value::{List, Value};
use crate::utils::{eval, eval_result};
#[test_log::test]
fn true_literal() {
assert_eq!(eval("true"), Value::Bool(true));
}
#[test_log::test]
fn false_literal() {
assert_eq!(eval("false"), Value::Bool(false));
}
#[test_log::test]
fn null_literal() {
assert_eq!(eval("null"), Value::Null);
}
#[test_log::test]
fn map_function() {
assert_eq!(
eval("map (x: x * 2) [1 2 3]"),
Value::List(List::new(vec![Value::Int(2), Value::Int(4), Value::Int(6)]))
);
}
#[test_log::test]
fn is_null_function() {
assert_eq!(eval("isNull null"), Value::Bool(true));
assert_eq!(eval("isNull 5"), Value::Bool(false));
}
#[test_log::test]
fn shadow_true() {
assert_eq!(eval("let true = false; in true"), Value::Bool(false));
}
#[test_log::test]
fn shadow_map() {
assert_eq!(eval("let map = x: y: x; in map 1 2"), Value::Int(1));
}
#[test_log::test]
fn mixed_usage() {
assert_eq!(
eval("if true then map (x: x + 1) [1 2] else []"),
Value::List(List::new(vec![Value::Int(2), Value::Int(3)]))
);
}
#[test_log::test]
fn in_let_bindings() {
assert_eq!(
eval("let x = true; y = false; in x && y"),
Value::Bool(false)
);
}
#[test_log::test]
fn shadow_in_function() {
assert_eq!(eval("(true: true) false"), Value::Bool(false));
}
#[test_log::test]
fn throw_function() {
let result = eval_result("throw \"error message\"");
assert!(result.is_err());
}
#[test_log::test]
fn to_string_function() {
assert_eq!(eval("toString 42"), Value::String("42".to_string()));
}

View File

@@ -0,0 +1,120 @@
use fix::value::Value;
use crate::utils::{eval, eval_result};
#[test_log::test]
fn required_parameters() {
assert_eq!(eval("({ a, b }: a + b) { a = 1; b = 2; }"), Value::Int(3));
}
#[test_log::test]
fn missing_required_parameter() {
let result = eval_result("({ a, b }: a + b) { a = 1; }");
assert!(result.is_err());
}
#[test_log::test]
fn all_required_parameters_present() {
assert_eq!(
eval("({ x, y, z }: x + y + z) { x = 1; y = 2; z = 3; }"),
Value::Int(6)
);
}
#[test_log::test]
fn reject_unexpected_arguments() {
let result = eval_result("({ a, b }: a + b) { a = 1; b = 2; c = 3; }");
assert!(result.is_err());
}
#[test_log::test]
fn ellipsis_accepts_extra_arguments() {
assert_eq!(
eval("({ a, b, ... }: a + b) { a = 1; b = 2; c = 3; }"),
Value::Int(3)
);
}
#[test_log::test]
fn default_parameters() {
assert_eq!(eval("({ a, b ? 5 }: a + b) { a = 1; }"), Value::Int(6));
}
#[test_log::test]
fn override_default_parameter() {
assert_eq!(
eval("({ a, b ? 5 }: a + b) { a = 1; b = 10; }"),
Value::Int(11)
);
}
#[test_log::test]
fn at_pattern_alias() {
assert_eq!(
eval("(args@{ a, b }: args.a + args.b) { a = 1; b = 2; }"),
Value::Int(3)
);
}
#[test_log::test]
fn simple_parameter_no_validation() {
assert_eq!(eval("(x: x.a + x.b) { a = 1; b = 2; }"), Value::Int(3));
}
#[test_log::test]
fn simple_parameter_accepts_any_argument() {
assert_eq!(eval("(x: x) 42"), Value::Int(42));
}
#[test_log::test]
fn nested_function_parameters() {
assert_eq!(
eval("({ a }: { b }: a + b) { a = 5; } { b = 3; }"),
Value::Int(8)
);
}
#[test_log::test]
fn pattern_param_simple_reference_in_default() {
assert_eq!(eval("({ a, b ? a }: b) { a = 10; }"), Value::Int(10));
}
#[test_log::test]
fn pattern_param_multiple_references_in_default() {
assert_eq!(
eval("({ a, b ? a + 5, c ? 1 }: b + c) { a = 10; }"),
Value::Int(16)
);
}
#[test_log::test]
fn pattern_param_mutual_reference() {
assert_eq!(
eval("({ a, b ? c + 1, c ? 5 }: b) { a = 1; }"),
Value::Int(6)
);
}
#[test_log::test]
fn pattern_param_override_mutual_reference() {
assert_eq!(
eval("({ a, b ? c + 1, c ? 5 }: b) { a = 1; c = 10; }"),
Value::Int(11)
);
}
#[test_log::test]
fn pattern_param_reference_list() {
assert_eq!(
eval("({ a, b ? [ a 2 ] }: builtins.elemAt b 0) { a = 42; }"),
Value::Int(42)
);
}
#[test_log::test]
fn pattern_param_alias_in_default() {
assert_eq!(
eval("(args@{ a, b ? args.a + 10 }: b) { a = 5; }"),
Value::Int(15)
);
}

View File

@@ -0,0 +1,368 @@
use fix::context::Context;
use fix::error::Source;
use fix::value::Value;
use crate::utils::{eval, eval_result};
#[test_log::test]
fn import_absolute_path() {
let temp_dir = tempfile::tempdir().unwrap();
let lib_path = temp_dir.path().join("nix_test_lib.nix");
std::fs::write(&lib_path, "{ add = a: b: a + b; }").unwrap();
let expr = format!(r#"(import "{}").add 3 5"#, lib_path.display());
assert_eq!(eval(&expr), Value::Int(8));
}
#[test_log::test]
fn import_nested() {
let temp_dir = tempfile::tempdir().unwrap();
let lib_path = temp_dir.path().join("lib.nix");
std::fs::write(&lib_path, "{ add = a: b: a + b; }").unwrap();
let main_path = temp_dir.path().join("main.nix");
let main_content = format!(
r#"let lib = import {}; in {{ result = lib.add 10 20; }}"#,
lib_path.display()
);
std::fs::write(&main_path, main_content).unwrap();
let expr = format!(r#"(import "{}").result"#, main_path.display());
assert_eq!(eval(&expr), Value::Int(30));
}
#[test_log::test]
fn import_relative_path() {
let temp_dir = tempfile::tempdir().unwrap();
let subdir = temp_dir.path().join("subdir");
std::fs::create_dir_all(&subdir).unwrap();
let lib_path = temp_dir.path().join("lib.nix");
std::fs::write(&lib_path, "{ multiply = a: b: a * b; }").unwrap();
let helper_path = subdir.join("helper.nix");
std::fs::write(&helper_path, "{ subtract = a: b: a - b; }").unwrap();
let main_path = temp_dir.path().join("main.nix");
let main_content = r#"
let
lib = import ./lib.nix;
helper = import ./subdir/helper.nix;
in {
result1 = lib.multiply 3 4;
result2 = helper.subtract 10 3;
}
"#;
std::fs::write(&main_path, main_content).unwrap();
let expr = format!(r#"let x = import "{}"; in x.result1"#, main_path.display());
assert_eq!(eval(&expr), Value::Int(12));
let expr = format!(r#"let x = import "{}"; in x.result2"#, main_path.display());
assert_eq!(eval(&expr), Value::Int(7));
}
#[test_log::test]
fn import_returns_function() {
let temp_dir = tempfile::tempdir().unwrap();
let func_path = temp_dir.path().join("nix_test_func.nix");
std::fs::write(&func_path, "x: x * 2").unwrap();
let expr = format!(r#"(import "{}") 5"#, func_path.display());
assert_eq!(eval(&expr), Value::Int(10));
}
#[test_log::test]
fn import_with_complex_dependency_graph() {
let temp_dir = tempfile::tempdir().unwrap();
let utils_path = temp_dir.path().join("utils.nix");
std::fs::write(&utils_path, "{ double = x: x * 2; }").unwrap();
let math_path = temp_dir.path().join("math.nix");
let math_content = r#"let utils = import ./utils.nix; in { triple = x: x + utils.double x; }"#;
std::fs::write(&math_path, math_content).unwrap();
let main_path = temp_dir.path().join("main.nix");
let main_content = r#"let math = import ./math.nix; in math.triple 5"#;
std::fs::write(&main_path, main_content).unwrap();
let expr = format!(r#"import "{}""#, main_path.display());
assert_eq!(eval(&expr), Value::Int(15));
}
// Tests for builtins.path
#[test_log::test]
fn path_with_file() {
let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "Hello, World!").unwrap();
let expr = format!(r#"builtins.path {{ path = {}; }}"#, test_file.display());
let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap();
// Should return a store path string
if let Value::String(store_path) = result {
assert!(store_path.starts_with(ctx.get_store_dir()));
assert!(store_path.contains("test.txt"));
} else {
panic!("Expected string, got {:?}", result);
}
}
#[test_log::test]
fn path_with_custom_name() {
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("original.txt");
std::fs::write(&test_file, "Content").unwrap();
let expr = format!(
r#"builtins.path {{ path = {}; name = "custom-name"; }}"#,
test_file.display()
);
let result = eval(&expr);
if let Value::String(store_path) = result {
assert!(store_path.contains("custom-name"));
assert!(!store_path.contains("original.txt"));
} else {
panic!("Expected string, got {:?}", result);
}
}
#[test_log::test]
fn path_with_directory_recursive() {
let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("mydir");
std::fs::create_dir_all(&test_dir).unwrap();
std::fs::write(test_dir.join("file1.txt"), "Content 1").unwrap();
std::fs::write(test_dir.join("file2.txt"), "Content 2").unwrap();
let expr = format!(
r#"builtins.path {{ path = {}; recursive = true; }}"#,
test_dir.display()
);
let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap();
if let Value::String(store_path) = result {
assert!(store_path.starts_with(ctx.get_store_dir()));
assert!(store_path.contains("mydir"));
} else {
panic!("Expected string, got {:?}", result);
}
}
#[test_log::test]
fn path_flat_with_file() {
let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("flat.txt");
std::fs::write(&test_file, "Flat content").unwrap();
let expr = format!(
r#"builtins.path {{ path = {}; recursive = false; }}"#,
test_file.display()
);
let result = ctx.eval(Source::new_eval(expr).unwrap()).unwrap();
if let Value::String(store_path) = result {
assert!(store_path.starts_with(ctx.get_store_dir()));
} else {
panic!("Expected string, got {:?}", result);
}
}
#[test_log::test]
fn path_flat_with_directory_fails() {
let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("mydir");
std::fs::create_dir_all(&test_dir).unwrap();
let expr = format!(
r#"builtins.path {{ path = {}; recursive = false; }}"#,
test_dir.display()
);
let result = eval_result(&expr);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("recursive") || err_msg.contains("regular file"));
}
#[test_log::test]
fn path_nonexistent_fails() {
let expr = r#"builtins.path { path = "/nonexistent/path/that/should/not/exist"; }"#;
let result = eval_result(expr);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("does not exist"));
}
#[test_log::test]
fn path_missing_path_param() {
let expr = r#"builtins.path { name = "test"; }"#;
let result = eval_result(expr);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("path") && err_msg.contains("required"));
}
#[test_log::test]
fn path_with_sha256() {
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("hash_test.txt");
std::fs::write(&test_file, "Test content for hashing").unwrap();
// First, get the hash by calling without sha256
let expr1 = format!(r#"builtins.path {{ path = {}; }}"#, test_file.display());
let result1 = eval(&expr1);
let store_path1 = match result1 {
Value::String(s) => s,
_ => panic!("Expected string"),
};
// Compute the actual hash (for testing, we'll just verify the same path is returned)
// In real usage, the user would know the hash beforehand
let expr2 = format!(r#"builtins.path {{ path = {}; }}"#, test_file.display());
let result2 = eval(&expr2);
let store_path2 = match result2 {
Value::String(s) => s,
_ => panic!("Expected string"),
};
// Same input should produce same output
assert_eq!(store_path1, store_path2);
}
#[test_log::test]
fn path_deterministic() {
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("deterministic.txt");
std::fs::write(&test_file, "Same content").unwrap();
let expr = format!(
r#"builtins.path {{ path = {}; name = "myfile"; }}"#,
test_file.display()
);
let result1 = eval(&expr);
let result2 = eval(&expr);
// Same inputs should produce same store path
assert_eq!(result1, result2);
}
#[test_log::test]
fn read_file_type_regular_file() {
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "Test content").unwrap();
let expr = format!(r#"builtins.readFileType {}"#, test_file.display());
assert_eq!(eval(&expr), Value::String("regular".to_string()));
}
#[test_log::test]
fn read_file_type_directory() {
let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("testdir");
std::fs::create_dir(&test_dir).unwrap();
let expr = format!(r#"builtins.readFileType {}"#, test_dir.display());
assert_eq!(eval(&expr), Value::String("directory".to_string()));
}
#[test_log::test]
fn read_file_type_symlink() {
let temp_dir = tempfile::tempdir().unwrap();
let target = temp_dir.path().join("target.txt");
let symlink = temp_dir.path().join("link.txt");
std::fs::write(&target, "Target content").unwrap();
#[cfg(unix)]
std::os::unix::fs::symlink(&target, &symlink).unwrap();
#[cfg(unix)]
{
let expr = format!(r#"builtins.readFileType {}"#, symlink.display());
assert_eq!(eval(&expr), Value::String("symlink".to_string()));
}
}
#[test_log::test]
fn read_dir_basic() {
let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("readdir_test");
std::fs::create_dir(&test_dir).unwrap();
std::fs::write(test_dir.join("file1.txt"), "Content 1").unwrap();
std::fs::write(test_dir.join("file2.txt"), "Content 2").unwrap();
std::fs::create_dir(test_dir.join("subdir")).unwrap();
let expr = format!(r#"builtins.readDir {}"#, test_dir.display());
let result = eval(&expr);
if let Value::AttrSet(attrs) = result {
assert_eq!(
attrs.get("file1.txt"),
Some(&Value::String("regular".to_string()))
);
assert_eq!(
attrs.get("file2.txt"),
Some(&Value::String("regular".to_string()))
);
assert_eq!(
attrs.get("subdir"),
Some(&Value::String("directory".to_string()))
);
assert_eq!(attrs.len(), 3);
} else {
panic!("Expected AttrSet, got {:?}", result);
}
}
#[test_log::test]
fn read_dir_empty() {
let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("empty_dir");
std::fs::create_dir(&test_dir).unwrap();
let expr = format!(r#"builtins.readDir {}"#, test_dir.display());
let result = eval(&expr);
if let Value::AttrSet(attrs) = result {
assert_eq!(attrs.len(), 0);
} else {
panic!("Expected AttrSet, got {:?}", result);
}
}
#[test_log::test]
fn read_dir_nonexistent_fails() {
let expr = r#"builtins.readDir "/nonexistent/directory""#;
let result = eval_result(expr);
assert!(result.is_err());
}
#[test_log::test]
fn read_dir_on_file_fails() {
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "Test content").unwrap();
let expr = format!(r#"builtins.readDir {}"#, test_file.display());
let result = eval_result(&expr);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("not a directory"));
}

319
fix/tests/tests/lang.rs Normal file
View File

@@ -0,0 +1,319 @@
#![allow(non_snake_case)]
use std::path::PathBuf;
use fix::context::Context;
use fix::error::Source;
use fix::value::Value;
fn get_lang_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
}
fn eval_file(name: &str) -> Result<(Value, Source), String> {
let lang_dir = get_lang_dir();
let nix_path = lang_dir.join(format!("{name}.nix"));
let expr = format!(r#"import "{}""#, nix_path.display());
let mut ctx = Context::new().map_err(|e| e.to_string())?;
let source = Source {
ty: fix::error::SourceType::File(nix_path.into()),
src: expr.into(),
};
ctx.eval_deep(source.clone())
.map(|val| (val, source))
.map_err(|e| e.to_string())
}
fn read_expected(name: &str) -> String {
let lang_dir = get_lang_dir();
let exp_path = lang_dir.join(format!("{name}.exp"));
std::fs::read_to_string(exp_path)
.expect("expected file should exist")
.trim_end()
.to_string()
}
fn format_value(value: &Value) -> String {
value.display_compat().to_string()
}
macro_rules! eval_okay_test {
($(#[$attr:meta])* $name:ident$(, $pre:expr)?) => {
$(#[$attr])*
#[test_log::test]
fn $name() {
$(($pre)();)?
let test_name = concat!("eval-okay-", stringify!($name))
.replace("_", "-")
.replace("r#", "");
let result = eval_file(&test_name);
match result {
Ok((value, source)) => {
let actual = format_value(&value);
let actual = actual.replace(
source
.get_dir()
.parent()
.unwrap()
.to_string_lossy()
.as_ref(),
"/pwd",
);
let expected = read_expected(&test_name);
assert_eq!(actual, expected, "Output mismatch for {}", test_name);
}
Err(e) => {
panic!("Test {} failed to evaluate: {}", test_name, e);
}
}
}
};
}
macro_rules! eval_fail_test {
($name:ident) => {
#[test_log::test]
fn $name() {
let test_name = concat!("eval-fail-", stringify!($name))
.replace("_", "-")
.replace("r#", "");
let result = eval_file(&test_name);
assert!(
result.is_err(),
"Test {} should have failed but succeeded with: {:?}",
test_name,
result
);
}
};
}
eval_okay_test!(any_all);
eval_okay_test!(arithmetic);
eval_okay_test!(attrnames);
eval_okay_test!(attrs);
eval_okay_test!(attrs2);
eval_okay_test!(attrs3);
eval_okay_test!(attrs4);
eval_okay_test!(attrs5);
eval_okay_test!(
#[ignore = "__overrides is not supported"]
attrs6
);
eval_okay_test!(
#[ignore = "requires --arg/--argstr CLI flags"]
autoargs
);
eval_okay_test!(backslash_newline_1);
eval_okay_test!(backslash_newline_2);
eval_okay_test!(baseNameOf);
eval_okay_test!(builtins);
eval_okay_test!(builtins_add);
eval_okay_test!(callable_attrs);
eval_okay_test!(catattrs);
eval_okay_test!(closure);
eval_okay_test!(comments);
eval_okay_test!(concat);
eval_okay_test!(concatmap);
eval_okay_test!(concatstringssep);
eval_okay_test!(context);
eval_okay_test!(context_introspection);
eval_okay_test!(convertHash);
eval_okay_test!(curpos);
eval_okay_test!(deepseq);
eval_okay_test!(delayed_with);
eval_okay_test!(delayed_with_inherit);
eval_okay_test!(deprecate_cursed_or);
eval_okay_test!(derivation_legacy);
eval_okay_test!(dynamic_attrs);
eval_okay_test!(dynamic_attrs_2);
eval_okay_test!(dynamic_attrs_bare);
eval_okay_test!(elem);
eval_okay_test!(empty_args);
eval_okay_test!(eq);
eval_okay_test!(eq_derivations);
eval_okay_test!(filter);
eval_okay_test!(
#[ignore = "not implemented: flakeRefToString"]
flake_ref_to_string
);
eval_okay_test!(flatten);
eval_okay_test!(float);
eval_okay_test!(floor_ceil);
eval_okay_test!(foldlStrict);
eval_okay_test!(foldlStrict_lazy_elements);
eval_okay_test!(foldlStrict_lazy_initial_accumulator);
eval_okay_test!(fromjson);
eval_okay_test!(fromjson_escapes);
eval_okay_test!(fromTOML);
eval_okay_test!(
#[ignore = "timestamps are not supported"]
fromTOML_timestamps
);
eval_okay_test!(functionargs);
eval_okay_test!(hashfile);
eval_okay_test!(hashstring);
eval_okay_test!(getattrpos);
eval_okay_test!(getattrpos_functionargs);
eval_okay_test!(getattrpos_undefined);
eval_okay_test!(getenv, || {
unsafe { std::env::set_var("TEST_VAR", "foo") };
});
eval_okay_test!(groupBy);
eval_okay_test!(r#if);
eval_okay_test!(ind_string);
eval_okay_test!(import);
eval_okay_test!(inherit_attr_pos);
eval_okay_test!(
#[ignore = "__overrides is not supported"]
inherit_from
);
eval_okay_test!(intersectAttrs);
eval_okay_test!(r#let);
eval_okay_test!(list);
eval_okay_test!(listtoattrs);
eval_okay_test!(logic);
eval_okay_test!(map);
eval_okay_test!(mapattrs);
eval_okay_test!(merge_dynamic_attrs);
eval_okay_test!(nested_with);
eval_okay_test!(new_let);
eval_okay_test!(null_dynamic_attrs);
eval_okay_test!(
#[ignore = "__overrides is not supported"]
overrides
);
eval_okay_test!(
#[ignore = "not implemented: parseFlakeRef"]
parse_flake_ref
);
eval_okay_test!(partition);
eval_okay_test!(path);
eval_okay_test!(pathexists);
eval_okay_test!(path_string_interpolation, || {
unsafe {
std::env::set_var("HOME", "/fake-home");
}
});
eval_okay_test!(patterns);
eval_okay_test!(print);
eval_okay_test!(readDir);
eval_okay_test!(readfile);
eval_okay_test!(readFileType);
eval_okay_test!(redefine_builtin);
eval_okay_test!(regex_match);
eval_okay_test!(regex_split);
eval_okay_test!(regression_20220122);
eval_okay_test!(regression_20220125);
eval_okay_test!(regrettable_rec_attrset_merge);
eval_okay_test!(remove);
eval_okay_test!(repeated_empty_attrs);
eval_okay_test!(repeated_empty_list);
eval_okay_test!(replacestrings);
eval_okay_test!(
#[ignore = "requires -I CLI flags"]
search_path
);
eval_okay_test!(scope_1);
eval_okay_test!(scope_2);
eval_okay_test!(scope_3);
eval_okay_test!(scope_4);
eval_okay_test!(scope_6);
eval_okay_test!(scope_7);
eval_okay_test!(seq);
eval_okay_test!(sort);
eval_okay_test!(splitversion);
eval_okay_test!(string);
eval_okay_test!(strings_as_attrs_names);
eval_okay_test!(substring);
eval_okay_test!(substring_context);
eval_okay_test!(symlink_resolution);
eval_okay_test!(
#[ignore = "TCO not implemented, also disabled in CppNix"]
tail_call_1
);
eval_okay_test!(tojson);
eval_okay_test!(toxml);
eval_okay_test!(toxml2);
eval_okay_test!(tryeval);
eval_okay_test!(types);
eval_okay_test!(versions);
eval_okay_test!(with);
eval_okay_test!(zipAttrsWith);
eval_fail_test!(fail_abort);
eval_fail_test!(fail_addDrvOutputDependencies_empty_context);
eval_fail_test!(fail_addDrvOutputDependencies_multi_elem_context);
eval_fail_test!(fail_addDrvOutputDependencies_wrong_element_kind);
eval_fail_test!(fail_addErrorContext_example);
eval_fail_test!(fail_assert);
eval_fail_test!(fail_assert_equal_attrs_names);
eval_fail_test!(fail_assert_equal_attrs_names_2);
eval_fail_test!(fail_assert_equal_derivations);
eval_fail_test!(fail_assert_equal_derivations_extra);
eval_fail_test!(fail_assert_equal_floats);
eval_fail_test!(fail_assert_equal_function_direct);
eval_fail_test!(fail_assert_equal_int_float);
eval_fail_test!(fail_assert_equal_ints);
eval_fail_test!(fail_assert_equal_list_length);
eval_fail_test!(fail_assert_equal_paths);
eval_fail_test!(fail_assert_equal_type);
eval_fail_test!(fail_assert_equal_type_nested);
eval_fail_test!(fail_assert_nested_bool);
eval_fail_test!(fail_attr_name_type);
eval_fail_test!(fail_attrset_merge_drops_later_rec);
eval_fail_test!(fail_bad_string_interpolation_1);
eval_fail_test!(fail_bad_string_interpolation_2);
eval_fail_test!(fail_bad_string_interpolation_3);
eval_fail_test!(fail_bad_string_interpolation_4);
eval_fail_test!(fail_blackhole);
eval_fail_test!(fail_call_primop);
eval_fail_test!(fail_deepseq);
eval_fail_test!(fail_derivation_name);
eval_fail_test!(fail_dup_dynamic_attrs);
eval_fail_test!(fail_duplicate_traces);
eval_fail_test!(fail_eol_1);
eval_fail_test!(fail_eol_2);
eval_fail_test!(fail_eol_3);
eval_fail_test!(fail_fetchTree_negative);
eval_fail_test!(fail_fetchurl_baseName);
eval_fail_test!(fail_fetchurl_baseName_attrs);
eval_fail_test!(fail_fetchurl_baseName_attrs_name);
eval_fail_test!(fail_flake_ref_to_string_negative_integer);
eval_fail_test!(fail_foldlStrict_strict_op_application);
eval_fail_test!(fail_fromJSON_keyWithNullByte);
eval_fail_test!(fail_fromJSON_overflowing);
eval_fail_test!(fail_fromJSON_valueWithNullByte);
eval_fail_test!(fail_fromTOML_keyWithNullByte);
eval_fail_test!(fail_fromTOML_timestamps);
eval_fail_test!(fail_fromTOML_valueWithNullByte);
eval_fail_test!(fail_hashfile_missing);
eval_fail_test!(fail_infinite_recursion_lambda);
eval_fail_test!(fail_list);
eval_fail_test!(fail_missing_arg);
eval_fail_test!(fail_mutual_recursion);
eval_fail_test!(fail_nested_list_items);
eval_fail_test!(fail_nonexist_path);
eval_fail_test!(fail_not_throws);
eval_fail_test!(fail_overflowing_add);
eval_fail_test!(fail_overflowing_div);
eval_fail_test!(fail_overflowing_mul);
eval_fail_test!(fail_overflowing_sub);
eval_fail_test!(fail_path_slash);
eval_fail_test!(fail_pipe_operators);
eval_fail_test!(fail_recursion);
eval_fail_test!(fail_remove);
eval_fail_test!(fail_scope_5);
eval_fail_test!(fail_seq);
eval_fail_test!(fail_set);
eval_fail_test!(fail_set_override);
eval_fail_test!(fail_string_nul_1);
eval_fail_test!(fail_string_nul_2);
eval_fail_test!(fail_substring);
eval_fail_test!(fail_toJSON);
eval_fail_test!(fail_toJSON_non_utf_8);
eval_fail_test!(fail_to_path);
eval_fail_test!(fail_undeclared_arg);
eval_fail_test!(fail_using_set_as_attr_name);

Binary file not shown.

View File

@@ -0,0 +1 @@
foo

View File

@@ -0,0 +1 @@
"a"

View File

@@ -0,0 +1 @@
"X"

View File

@@ -0,0 +1 @@
"b"

View File

@@ -0,0 +1 @@
"X"

View File

@@ -0,0 +1 @@
"X"

View File

@@ -0,0 +1 @@
"c"

View File

@@ -0,0 +1 @@
"X"

View File

@@ -0,0 +1 @@
"X"

View File

@@ -0,0 +1,8 @@
error:
… while calling the 'abort' builtin
at /pwd/lang/eval-fail-abort.nix:1:14:
1| if true then abort "this should fail" else 1
| ^
2|
error: evaluation aborted with the following error message: 'this should fail'

View File

@@ -0,0 +1 @@
if true then abort "this should fail" else 1

View File

@@ -0,0 +1,8 @@
error:
… while calling the 'addDrvOutputDependencies' builtin
at /pwd/lang/eval-fail-addDrvOutputDependencies-empty-context.nix:1:1:
1| builtins.addDrvOutputDependencies ""
| ^
2|
error: context of string '' must have exactly one element, but has 0

View File

@@ -0,0 +1 @@
builtins.addDrvOutputDependencies ""

View File

@@ -0,0 +1,9 @@
error:
… while calling the 'addDrvOutputDependencies' builtin
at /pwd/lang/eval-fail-addDrvOutputDependencies-multi-elem-context.nix:25:1:
24| in
25| builtins.addDrvOutputDependencies combo-path
| ^
26|
error: context of string '/nix/store/pg9yqs4yd85yhdm3f4i5dyaqp5jahrsz-fail.drv/nix/store/2dxd5frb715z451vbf7s8birlf3argbk-fail-2.drv' must have exactly one element, but has 2

View File

@@ -0,0 +1,25 @@
let
drv0 = derivation {
name = "fail";
builder = "/bin/false";
system = "x86_64-linux";
outputs = [
"out"
"foo"
];
};
drv1 = derivation {
name = "fail-2";
builder = "/bin/false";
system = "x86_64-linux";
outputs = [
"out"
"foo"
];
};
combo-path = "${drv0.drvPath}${drv1.drvPath}";
in
builtins.addDrvOutputDependencies combo-path

View File

@@ -0,0 +1,9 @@
error:
… while calling the 'addDrvOutputDependencies' builtin
at /pwd/lang/eval-fail-addDrvOutputDependencies-wrong-element-kind.nix:13:1:
12| in
13| builtins.addDrvOutputDependencies drv.outPath
| ^
14|
error: `addDrvOutputDependencies` can only act on derivations, not on a derivation output such as 'out'

View File

@@ -0,0 +1,13 @@
let
drv = derivation {
name = "fail";
builder = "/bin/false";
system = "x86_64-linux";
outputs = [
"out"
"foo"
];
};
in
builtins.addDrvOutputDependencies drv.outPath

View File

@@ -0,0 +1,24 @@
error:
… while counting down; n = 10
… while counting down; n = 9
… while counting down; n = 8
… while counting down; n = 7
… while counting down; n = 6
… while counting down; n = 5
… while counting down; n = 4
… while counting down; n = 3
… while counting down; n = 2
… while counting down; n = 1
(stack trace truncated; use '--show-trace' to show the full, detailed trace)
error: kaboom

View File

@@ -0,0 +1,9 @@
let
countDown =
n:
if n == 0 then
throw "kaboom"
else
builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
in
countDown 10

View File

@@ -0,0 +1,8 @@
error:
… while evaluating the condition of the assertion '({ a = true; } == { a = true; b = true; })'
at /pwd/lang/eval-fail-assert-equal-attrs-names-2.nix:1:1:
1| assert
| ^
2| {
error: attribute names of attribute set '{ a = true; }' differs from attribute set '{ a = true; b = true; }'

View File

@@ -0,0 +1,8 @@
assert
{
a = true;
} == {
a = true;
b = true;
};
throw "unreachable"

View File

@@ -0,0 +1,8 @@
error:
… while evaluating the condition of the assertion '({ a = true; b = true; } == { a = true; })'
at /pwd/lang/eval-fail-assert-equal-attrs-names.nix:1:1:
1| assert
| ^
2| {
error: attribute names of attribute set '{ a = true; b = true; }' differs from attribute set '{ a = true; }'

View File

@@ -0,0 +1,8 @@
assert
{
a = true;
b = true;
} == {
a = true;
};
throw "unreachable"

View File

@@ -0,0 +1,26 @@
error:
… while evaluating the condition of the assertion '({ foo = { outPath = "/nix/store/0"; type = "derivation"; }; } == { foo = { devious = true; outPath = "/nix/store/1"; type = "derivation"; }; })'
at /pwd/lang/eval-fail-assert-equal-derivations-extra.nix:1:1:
1| assert
| ^
2| {
… while comparing attribute 'foo'
… where left hand side is
at /pwd/lang/eval-fail-assert-equal-derivations-extra.nix:3:5:
2| {
3| foo = {
| ^
4| type = "derivation";
… where right hand side is
at /pwd/lang/eval-fail-assert-equal-derivations-extra.nix:8:5:
7| } == {
8| foo = {
| ^
9| type = "derivation";
… while comparing a derivation by its 'outPath' attribute
error: string '"/nix/store/0"' is not equal to string '"/nix/store/1"'

View File

@@ -0,0 +1,14 @@
assert
{
foo = {
type = "derivation";
outPath = "/nix/store/0";
};
} == {
foo = {
type = "derivation";
outPath = "/nix/store/1";
devious = true;
};
};
throw "unreachable"

View File

@@ -0,0 +1,26 @@
error:
… while evaluating the condition of the assertion '({ foo = { ignored = (abort "not ignored"); outPath = "/nix/store/0"; type = "derivation"; }; } == { foo = { ignored = (abort "not ignored"); outPath = "/nix/store/1"; type = "derivation"; }; })'
at /pwd/lang/eval-fail-assert-equal-derivations.nix:1:1:
1| assert
| ^
2| {
… while comparing attribute 'foo'
… where left hand side is
at /pwd/lang/eval-fail-assert-equal-derivations.nix:3:5:
2| {
3| foo = {
| ^
4| type = "derivation";
… where right hand side is
at /pwd/lang/eval-fail-assert-equal-derivations.nix:9:5:
8| } == {
9| foo = {
| ^
10| type = "derivation";
… while comparing a derivation by its 'outPath' attribute
error: string '"/nix/store/0"' is not equal to string '"/nix/store/1"'

View File

@@ -0,0 +1,15 @@
assert
{
foo = {
type = "derivation";
outPath = "/nix/store/0";
ignored = abort "not ignored";
};
} == {
foo = {
type = "derivation";
outPath = "/nix/store/1";
ignored = abort "not ignored";
};
};
throw "unreachable"

View File

@@ -0,0 +1,22 @@
error:
… while evaluating the condition of the assertion '({ b = 1; } == { b = 1.01; })'
at /pwd/lang/eval-fail-assert-equal-floats.nix:1:1:
1| assert { b = 1.0; } == { b = 1.01; };
| ^
2| abort "unreachable"
… while comparing attribute 'b'
… where left hand side is
at /pwd/lang/eval-fail-assert-equal-floats.nix:1:10:
1| assert { b = 1.0; } == { b = 1.01; };
| ^
2| abort "unreachable"
… where right hand side is
at /pwd/lang/eval-fail-assert-equal-floats.nix:1:26:
1| assert { b = 1.0; } == { b = 1.01; };
| ^
2| abort "unreachable"
error: a float with value '1' is not equal to a float with value '1.01'

View File

@@ -0,0 +1,2 @@
assert { b = 1.0; } == { b = 1.01; };
abort "unreachable"

View File

@@ -0,0 +1,9 @@
error:
… while evaluating the condition of the assertion '((x: x) == (x: x))'
at /pwd/lang/eval-fail-assert-equal-function-direct.nix:3:1:
2| # This only compares a direct comparison and makes no claims about functions in nested structures.
3| assert (x: x) == (x: x);
| ^
4| abort "unreachable"
error: distinct functions and immediate comparisons of identical functions compare as unequal

View File

@@ -0,0 +1,4 @@
# Note: functions in nested structures, e.g. attributes, may be optimized away by pointer identity optimization.
# This only compares a direct comparison and makes no claims about functions in nested structures.
assert (x: x) == (x: x);
abort "unreachable"

View File

@@ -0,0 +1,8 @@
error:
… while evaluating the condition of the assertion '(1 == 1.1)'
at /pwd/lang/eval-fail-assert-equal-int-float.nix:1:1:
1| assert 1 == 1.1;
| ^
2| throw "unreachable"
error: an integer with value '1' is not equal to a float with value '1.1'

View File

@@ -0,0 +1,2 @@
assert 1 == 1.1;
throw "unreachable"

View File

@@ -0,0 +1,22 @@
error:
… while evaluating the condition of the assertion '({ b = 1; } == { b = 2; })'
at /pwd/lang/eval-fail-assert-equal-ints.nix:1:1:
1| assert { b = 1; } == { b = 2; };
| ^
2| abort "unreachable"
… while comparing attribute 'b'
… where left hand side is
at /pwd/lang/eval-fail-assert-equal-ints.nix:1:10:
1| assert { b = 1; } == { b = 2; };
| ^
2| abort "unreachable"
… where right hand side is
at /pwd/lang/eval-fail-assert-equal-ints.nix:1:24:
1| assert { b = 1; } == { b = 2; };
| ^
2| abort "unreachable"
error: an integer with value '1' is not equal to an integer with value '2'

View File

@@ -0,0 +1,2 @@
assert { b = 1; } == { b = 2; };
abort "unreachable"

View File

@@ -0,0 +1,8 @@
error:
… while evaluating the condition of the assertion '([ (1) (0) ] == [ (10) ])'
at /pwd/lang/eval-fail-assert-equal-list-length.nix:1:1:
1| assert
| ^
2| [
error: list of size '2' is not equal to list of size '1', left hand side is '[ 1 0 ]', right hand side is '[ 10 ]'

View File

@@ -0,0 +1,6 @@
assert
[
1
0
] == [ 10 ];
throw "unreachable"

View File

@@ -0,0 +1,8 @@
error:
… while evaluating the condition of the assertion '(/pwd/lang/foo == /pwd/lang/bar)'
at /pwd/lang/eval-fail-assert-equal-paths.nix:1:1:
1| assert ./foo == ./bar;
| ^
2| throw "unreachable"
error: path '/pwd/lang/foo' is not equal to path '/pwd/lang/bar'

View File

@@ -0,0 +1,2 @@
assert ./foo == ./bar;
throw "unreachable"

View File

@@ -0,0 +1,22 @@
error:
… while evaluating the condition of the assertion '({ ding = false; } == { ding = null; })'
at /pwd/lang/eval-fail-assert-equal-type-nested.nix:1:1:
1| assert { ding = false; } == { ding = null; };
| ^
2| abort "unreachable"
… while comparing attribute 'ding'
… where left hand side is
at /pwd/lang/eval-fail-assert-equal-type-nested.nix:1:10:
1| assert { ding = false; } == { ding = null; };
| ^
2| abort "unreachable"
… where right hand side is
at /pwd/lang/eval-fail-assert-equal-type-nested.nix:1:31:
1| assert { ding = false; } == { ding = null; };
| ^
2| abort "unreachable"
error: a Boolean of value 'false' is not equal to null of value 'null'

View File

@@ -0,0 +1,2 @@
assert { ding = false; } == { ding = null; };
abort "unreachable"

View File

@@ -0,0 +1,8 @@
error:
… while evaluating the condition of the assertion '(false == null)'
at /pwd/lang/eval-fail-assert-equal-type.nix:1:1:
1| assert false == null;
| ^
2| abort "unreachable"
error: a Boolean of value 'false' is not equal to null of value 'null'

View File

@@ -0,0 +1,2 @@
assert false == null;
abort "unreachable"

View File

@@ -0,0 +1,66 @@
error:
… while evaluating the condition of the assertion '({ a = { b = [ ({ c = { d = true; }; }) ]; }; } == { a = { b = [ ({ c = { d = false; }; }) ]; }; })'
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:1:
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
| ^
2|
… while comparing attribute 'a'
… where left hand side is
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:10:
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
| ^
2|
… where right hand side is
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:44:
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
| ^
2|
… while comparing attribute 'b'
… where left hand side is
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:10:
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
| ^
2|
… where right hand side is
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:44:
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
| ^
2|
… while comparing list element 0
… while comparing attribute 'c'
… where left hand side is
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:20:
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
| ^
2|
… where right hand side is
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:54:
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
| ^
2|
… while comparing attribute 'd'
… where left hand side is
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:20:
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
| ^
2|
… where right hand side is
at /pwd/lang/eval-fail-assert-nested-bool.nix:1:54:
1| assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
| ^
2|
error: boolean 'true' is not equal to boolean 'false'

View File

@@ -0,0 +1,3 @@
assert { a.b = [ { c.d = true; } ]; } == { a.b = [ { c.d = false; } ]; };
abort "unreachable"

View File

@@ -0,0 +1,30 @@
error:
… while evaluating the attribute 'body'
at /pwd/lang/eval-fail-assert.nix:7:3:
6|
7| body = x "x";
| ^
8| }
… from call site
at /pwd/lang/eval-fail-assert.nix:7:10:
6|
7| body = x "x";
| ^
8| }
… while calling 'x'
at /pwd/lang/eval-fail-assert.nix:3:5:
2| x =
3| arg:
| ^
4| assert arg == "y";
… while evaluating the condition of the assertion '(arg == "y")'
at /pwd/lang/eval-fail-assert.nix:4:5:
3| arg:
4| assert arg == "y";
| ^
5| 123;
error: string '"x"' is not equal to string '"y"'

View File

@@ -0,0 +1,8 @@
let {
x =
arg:
assert arg == "y";
123;
body = x "x";
}

View File

@@ -0,0 +1,21 @@
error:
… while evaluating the attribute 'puppy."${key}"'
at /pwd/lang/eval-fail-attr-name-type.nix:3:5:
2| attrs = {
3| puppy.doggy = { };
| ^
4| };
… while evaluating an attribute name
at /pwd/lang/eval-fail-attr-name-type.nix:7:15:
6| in
7| attrs.puppy.${key}
| ^
8|
error: expected a string but found an integer: 1
at /pwd/lang/eval-fail-attr-name-type.nix:7:15:
6| in
7| attrs.puppy.${key}
| ^
8|

View File

@@ -0,0 +1,7 @@
let
attrs = {
puppy.doggy = { };
};
key = 1;
in
attrs.puppy.${key}

View File

@@ -0,0 +1,6 @@
error: undefined variable 'd'
at /pwd/lang/eval-fail-attrset-merge-drops-later-rec.nix:4:9:
3| a = rec {
4| c = d + 2;
| ^
5| d = 3;

View File

@@ -0,0 +1,8 @@
{
a.b = 1;
a = rec {
c = d + 2;
d = 3;
};
}
.c

View File

@@ -0,0 +1,8 @@
error:
… while evaluating a path segment
at /pwd/lang/eval-fail-bad-string-interpolation-1.nix:1:2:
1| "${x: x}"
| ^
2|
error: cannot coerce a function to a string: «lambda @ /pwd/lang/eval-fail-bad-string-interpolation-1.nix:1:4»

View File

@@ -0,0 +1 @@
"${x: x}"

View File

@@ -0,0 +1 @@
error: path '/pwd/lang/fnord' does not exist

View File

@@ -0,0 +1 @@
"${./fnord}"

View File

@@ -0,0 +1,8 @@
error:
… while evaluating a path segment
at /pwd/lang/eval-fail-bad-string-interpolation-3.nix:1:3:
1| ''${x: x}''
| ^
2|
error: cannot coerce a function to a string: «lambda @ /pwd/lang/eval-fail-bad-string-interpolation-3.nix:1:5»

View File

@@ -0,0 +1 @@
''${x: x}''

View File

@@ -0,0 +1,9 @@
error:
… while evaluating a path segment
at /pwd/lang/eval-fail-bad-string-interpolation-4.nix:19:3:
18| # The error message should not be too long.
19| ''${pkgs}''
| ^
20|
error: cannot coerce a set to a string: { a = { a = { a = { a = "ha"; b = "ha"; c = "ha"; d = "ha"; e = "ha"; f = "ha"; g = "ha"; h = "ha"; j = "ha"; }; «8 attributes elided» }; «8 attributes elided» }; «8 attributes elided» }

View File

@@ -0,0 +1,19 @@
let
# Basically a "billion laughs" attack, but toned down to simulated `pkgs`.
ha = x: y: {
a = x y;
b = x y;
c = x y;
d = x y;
e = x y;
f = x y;
g = x y;
h = x y;
j = x y;
};
has = ha (ha (ha (ha (x: x)))) "ha";
# A large structure that has already been evaluated.
pkgs = builtins.deepSeq has has;
in
# The error message should not be too long.
''${pkgs}''

View File

@@ -0,0 +1,14 @@
error:
… while evaluating the attribute 'body'
at /pwd/lang/eval-fail-blackhole.nix:2:3:
1| let {
2| body = x;
| ^
3| x = y;
error: infinite recursion encountered
at /pwd/lang/eval-fail-blackhole.nix:3:7:
2| body = x;
3| x = y;
| ^
4| y = x;

View File

@@ -0,0 +1,5 @@
let {
body = x;
x = y;
y = x;
}

View File

@@ -0,0 +1,10 @@
error:
… while calling the 'length' builtin
at /pwd/lang/eval-fail-call-primop.nix:1:1:
1| builtins.length 1
| ^
2|
… while evaluating the first argument passed to builtins.length
error: expected a list but found an integer: 1

View File

@@ -0,0 +1 @@
builtins.length 1

View File

@@ -0,0 +1,20 @@
error:
… while calling the 'deepSeq' builtin
at /pwd/lang/eval-fail-deepseq.nix:1:1:
1| builtins.deepSeq { x = abort "foo"; } 456
| ^
2|
… while evaluating the attribute 'x'
at /pwd/lang/eval-fail-deepseq.nix:1:20:
1| builtins.deepSeq { x = abort "foo"; } 456
| ^
2|
… while calling the 'abort' builtin
at /pwd/lang/eval-fail-deepseq.nix:1:24:
1| builtins.deepSeq { x = abort "foo"; } 456
| ^
2|
error: evaluation aborted with the following error message: 'foo'

View File

@@ -0,0 +1 @@
builtins.deepSeq { x = abort "foo"; } 456

View File

@@ -0,0 +1,26 @@
error:
… while evaluating the attribute 'outPath'
at <nix/derivation-internal.nix>:<number>:<number>:
<number>| value = commonAttrs // {
<number>| outPath = builtins.getAttr outputName strict;
| ^
<number>| drvPath = strict.drvPath;
… while calling the 'getAttr' builtin
at <nix/derivation-internal.nix>:<number>:<number>:
<number>| value = commonAttrs // {
<number>| outPath = builtins.getAttr outputName strict;
| ^
<number>| drvPath = strict.drvPath;
… while calling the 'derivationStrict' builtin
at <nix/derivation-internal.nix>:<number>:<number>:
<number>|
<number>| strict = derivationStrict drvAttrs;
| ^
<number>|
… while evaluating derivation '~jiggle~'
whose name attribute is located at /pwd/lang/eval-fail-derivation-name.nix:<number>:<number>
error: invalid derivation name: name '~jiggle~' contains illegal character '~'. Please pass a different 'name'.

View File

@@ -0,0 +1,5 @@
derivation {
name = "~jiggle~";
system = "some-system";
builder = "/dontcare";
}

View File

@@ -0,0 +1,14 @@
error:
… while evaluating the attribute 'set'
at /pwd/lang/eval-fail-dup-dynamic-attrs.nix:2:3:
1| {
2| set = {
| ^
3| "${"" + "b"}" = 1;
error: dynamic attribute 'b' already defined at /pwd/lang/eval-fail-dup-dynamic-attrs.nix:3:5
at /pwd/lang/eval-fail-dup-dynamic-attrs.nix:6:5:
5| set = {
6| "${"b" + ""}" = 2;
| ^
7| };

View File

@@ -0,0 +1,8 @@
{
set = {
"${"" + "b"}" = 1;
};
set = {
"${"b" + ""}" = 2;
};
}

View File

@@ -0,0 +1,51 @@
error:
… from call site
at /pwd/lang/eval-fail-duplicate-traces.nix:6:1:
5| in
6| throwAfter 2
| ^
7|
… while calling 'throwAfter'
at /pwd/lang/eval-fail-duplicate-traces.nix:4:16:
3| let
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
| ^
5| in
… from call site
at /pwd/lang/eval-fail-duplicate-traces.nix:4:33:
3| let
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
| ^
5| in
… while calling 'throwAfter'
at /pwd/lang/eval-fail-duplicate-traces.nix:4:16:
3| let
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
| ^
5| in
… from call site
at /pwd/lang/eval-fail-duplicate-traces.nix:4:33:
3| let
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
| ^
5| in
… while calling 'throwAfter'
at /pwd/lang/eval-fail-duplicate-traces.nix:4:16:
3| let
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
| ^
5| in
… while calling the 'throw' builtin
at /pwd/lang/eval-fail-duplicate-traces.nix:4:57:
3| let
4| throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
| ^
5| in
error: Uh oh!

View File

@@ -0,0 +1,6 @@
# Check that we only omit duplicate stack traces when there's a bunch of them.
# Here, there's only a couple duplicate entries, so we output them all.
let
throwAfter = n: if n > 0 then throwAfter (n - 1) else throw "Uh oh!";
in
throwAfter 2

View File

@@ -0,0 +1,6 @@
error: undefined variable 'invalid'
at /pwd/lang/eval-fail-eol-1.nix:2:1:
1| # foo
2| invalid
| ^
3| # bar

View File

@@ -0,0 +1,3 @@
# foo
invalid
# bar

View File

@@ -0,0 +1,6 @@
error: undefined variable 'invalid'
at /pwd/lang/eval-fail-eol-2.nix:2:1:
1| # foo
2| invalid
| ^
3| # bar

View File

@@ -0,0 +1,2 @@
# foo
invalid

View File

@@ -0,0 +1,6 @@
error: undefined variable 'invalid'
at /pwd/lang/eval-fail-eol-3.nix:2:1:
1| # foo
2| invalid
| ^
3| # bar

View File

@@ -0,0 +1,3 @@
# foo
invalid
# bar

View File

@@ -0,0 +1,8 @@
error:
… while calling the 'fetchTree' builtin
at /pwd/lang/eval-fail-fetchTree-negative.nix:1:1:
1| builtins.fetchTree {
| ^
2| type = "file";
error: negative value given for 'fetchTree' argument 'owner': -1

View File

@@ -0,0 +1,5 @@
builtins.fetchTree {
type = "file";
url = "file://eval-fail-fetchTree-negative.nix";
owner = -1;
}

View File

@@ -0,0 +1,8 @@
error:
… while calling the 'fetchurl' builtin
at /pwd/lang/eval-fail-fetchurl-baseName-attrs-name.nix:1:1:
1| builtins.fetchurl {
| ^
2| url = "https://example.com/foo.tar.gz";
error: invalid store path name when fetching URL 'https://example.com/foo.tar.gz': name '~wobble~' contains illegal character '~'. Please change the value for the 'name' attribute passed to 'fetchurl', so that it can create a valid store path.

View File

@@ -0,0 +1,4 @@
builtins.fetchurl {
url = "https://example.com/foo.tar.gz";
name = "~wobble~";
}

View File

@@ -0,0 +1,8 @@
error:
… while calling the 'fetchurl' builtin
at /pwd/lang/eval-fail-fetchurl-baseName-attrs.nix:1:1:
1| builtins.fetchurl { url = "https://example.com/~wiggle~"; }
| ^
2|
error: invalid store path name when fetching URL 'https://example.com/~wiggle~': name '~wiggle~' contains illegal character '~'. Please add a valid 'name' attribute to the argument for 'fetchurl', so that it can create a valid store path.

View File

@@ -0,0 +1 @@
builtins.fetchurl { url = "https://example.com/~wiggle~"; }

View File

@@ -0,0 +1,8 @@
error:
… while calling the 'fetchurl' builtin
at /pwd/lang/eval-fail-fetchurl-baseName.nix:1:1:
1| builtins.fetchurl "https://example.com/~wiggle~"
| ^
2|
error: invalid store path name when fetching URL 'https://example.com/~wiggle~': name '~wiggle~' contains illegal character '~'. Please pass an attribute set with 'url' and 'name' attributes to 'fetchurl', so that it can create a valid store path.

View File

@@ -0,0 +1 @@
builtins.fetchurl "https://example.com/~wiggle~"

View File

@@ -0,0 +1,16 @@
error:
… while calling the 'seq' builtin
at /pwd/lang/eval-fail-flake-ref-to-string-negative-integer.nix:4:1:
3| in
4| builtins.seq n (
| ^
5| builtins.flakeRefToString {
… while calling the 'flakeRefToString' builtin
at /pwd/lang/eval-fail-flake-ref-to-string-negative-integer.nix:5:3:
4| builtins.seq n (
5| builtins.flakeRefToString {
| ^
6| type = "github";
error: negative value given for flake ref attr repo: -1

View File

@@ -0,0 +1,12 @@
let
n = -1;
in
builtins.seq n (
builtins.flakeRefToString {
type = "github";
owner = "NixOS";
repo = n;
ref = "23.05";
dir = "lib";
}
)

View File

@@ -0,0 +1,37 @@
error:
… while calling the 'foldl'' builtin
at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:2:1:
1| # Tests that the result of applying op is forced even if the value is never used
2| builtins.foldl' (_: f: f null) null [
| ^
3| (_: throw "Not the final value, but is still forced!")
… while calling anonymous lambda
at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:2:21:
1| # Tests that the result of applying op is forced even if the value is never used
2| builtins.foldl' (_: f: f null) null [
| ^
3| (_: throw "Not the final value, but is still forced!")
… from call site
at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:2:24:
1| # Tests that the result of applying op is forced even if the value is never used
2| builtins.foldl' (_: f: f null) null [
| ^
3| (_: throw "Not the final value, but is still forced!")
… while calling anonymous lambda
at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:3:4:
2| builtins.foldl' (_: f: f null) null [
3| (_: throw "Not the final value, but is still forced!")
| ^
4| (_: 23)
… while calling the 'throw' builtin
at /pwd/lang/eval-fail-foldlStrict-strict-op-application.nix:3:7:
2| builtins.foldl' (_: f: f null) null [
3| (_: throw "Not the final value, but is still forced!")
| ^
4| (_: 23)
error: Not the final value, but is still forced!

View File

@@ -0,0 +1,5 @@
# Tests that the result of applying op is forced even if the value is never used
builtins.foldl' (_: f: f null) null [
(_: throw "Not the final value, but is still forced!")
(_: 23)
]

View File

@@ -0,0 +1,8 @@
error:
… while calling the 'fromJSON' builtin
at /pwd/lang/eval-fail-fromJSON-keyWithNullByte.nix:1:1:
1| builtins.fromJSON ''{"a\u0000b": 1}''
| ^
2|
error: input string 'a␀b' cannot be represented as Nix string because it contains null bytes

Some files were not shown because too many files have changed in this diff Show More