feat: initial nix-daemon implementation

This commit is contained in:
2026-01-17 19:27:59 +08:00
parent 52bf46407a
commit 2ad662c765
28 changed files with 1625 additions and 463 deletions

View File

@@ -0,0 +1,225 @@
mod utils;
use std::sync::Once;
use nix_js::value::Value;
use utils::eval_result;
fn init() {
static INIT: Once = Once::new();
INIT.call_once(|| {
unsafe { std::env::set_var("NIX_JS_STORE_MODE", "simulated") };
});
}
#[test]
fn to_file_simple() {
init();
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]
fn to_file_with_references() {
init();
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]
fn to_file_invalid_name_with_slash() {
init();
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]
fn to_file_invalid_name_dot() {
init();
let result = eval_result(r#"builtins.toFile "." "content""#);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("invalid name"));
}
#[test]
fn to_file_invalid_name_dotdot() {
init();
let result = eval_result(r#"builtins.toFile ".." "content""#);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("invalid name"));
}
#[test]
fn store_path_validation_not_in_store() {
init();
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]
fn store_path_validation_malformed_hash() {
init();
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]
fn store_path_validation_missing_name() {
init();
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]
fn to_file_curried_application() {
init();
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]
fn to_file_number_conversion() {
init();
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]
fn to_file_list_conversion() {
init();
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

@@ -1,14 +1,14 @@
use nix_js::context::Context;
#![cfg(feature = "daemon")]
mod utils;
use nix_js::value::Value;
use utils::{eval, eval_result};
#[test]
fn derivation_minimal() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation { name = "hello"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
)
.unwrap();
let result =
eval(r#"derivation { name = "hello"; builder = "/bin/sh"; system = "x86_64-linux"; }"#);
match result {
Value::AttrSet(attrs) => {
@@ -44,17 +44,14 @@ fn derivation_minimal() {
#[test]
fn derivation_with_args() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "test";
builder = "/bin/sh";
system = "x86_64-linux";
args = ["-c" "echo hello"];
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "test";
builder = "/bin/sh";
system = "x86_64-linux";
args = ["-c" "echo hello"];
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -69,12 +66,9 @@ fn derivation_with_args() {
#[test]
fn derivation_to_string() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"toString (derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; })"#,
)
.unwrap();
let result = eval(
r#"toString (derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; })"#,
);
match result {
Value::String(s) => assert_eq!(s, "/nix/store/xpcvxsx5sw4rbq666blz6sxqlmsqphmr-foo"),
@@ -84,8 +78,7 @@ fn derivation_to_string() {
#[test]
fn derivation_missing_name() {
let mut ctx = Context::new().unwrap();
let result = ctx.eval_code(r#"derivation { builder = "/bin/sh"; system = "x86_64-linux"; }"#);
let result = eval_result(r#"derivation { builder = "/bin/sh"; system = "x86_64-linux"; }"#);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
@@ -94,8 +87,7 @@ fn derivation_missing_name() {
#[test]
fn derivation_invalid_name_with_drv_suffix() {
let mut ctx = Context::new().unwrap();
let result = ctx.eval_code(
let result = eval_result(
r#"derivation { name = "foo.drv"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
);
@@ -106,8 +98,7 @@ fn derivation_invalid_name_with_drv_suffix() {
#[test]
fn derivation_missing_builder() {
let mut ctx = Context::new().unwrap();
let result = ctx.eval_code(r#"derivation { name = "test"; system = "x86_64-linux"; }"#);
let result = eval_result(r#"derivation { name = "test"; system = "x86_64-linux"; }"#);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
@@ -116,8 +107,7 @@ fn derivation_missing_builder() {
#[test]
fn derivation_missing_system() {
let mut ctx = Context::new().unwrap();
let result = ctx.eval_code(r#"derivation { name = "test"; builder = "/bin/sh"; }"#);
let result = eval_result(r#"derivation { name = "test"; builder = "/bin/sh"; }"#);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
@@ -126,18 +116,15 @@ fn derivation_missing_system() {
#[test]
fn derivation_with_env_vars() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "test";
builder = "/bin/sh";
system = "x86_64-linux";
MY_VAR = "hello";
ANOTHER = "world";
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "test";
builder = "/bin/sh";
system = "x86_64-linux";
MY_VAR = "hello";
ANOTHER = "world";
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -150,12 +137,9 @@ fn derivation_with_env_vars() {
#[test]
fn derivation_strict() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"builtins.derivationStrict { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
)
.unwrap();
let result = eval(
r#"builtins.derivationStrict { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; }"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -169,12 +153,10 @@ fn derivation_strict() {
#[test]
fn derivation_deterministic_paths() {
let mut ctx = Context::new().unwrap();
let expr = r#"derivation { name = "hello"; builder = "/bin/sh"; system = "x86_64-linux"; }"#;
let result1 = ctx.eval_code(expr).unwrap();
let result2 = ctx.eval_code(expr).unwrap();
let result1 = eval(expr);
let result2 = eval(expr);
match (result1, result2) {
(Value::AttrSet(attrs1), Value::AttrSet(attrs2)) => {
@@ -187,17 +169,14 @@ fn derivation_deterministic_paths() {
#[test]
fn derivation_escaping_in_aterm() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "test";
builder = "/bin/sh";
system = "x86_64-linux";
args = ["-c" "echo \"hello\nworld\""];
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "test";
builder = "/bin/sh";
system = "x86_64-linux";
args = ["-c" "echo \"hello\nworld\""];
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -210,17 +189,14 @@ fn derivation_escaping_in_aterm() {
#[test]
fn multi_output_two_outputs() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "multi";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "multi";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -271,17 +247,14 @@ fn multi_output_two_outputs() {
#[test]
fn multi_output_three_outputs() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "three";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev" "doc"];
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "three";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev" "doc"];
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -322,17 +295,14 @@ fn multi_output_three_outputs() {
#[test]
fn multi_output_backward_compat() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "compat";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out"];
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "compat";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out"];
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -351,47 +321,39 @@ fn multi_output_backward_compat() {
#[test]
fn multi_output_deterministic() {
let mut ctx = Context::new().unwrap();
let result1 = ctx
.eval_code(
r#"derivation {
name = "determ";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
}"#,
)
.unwrap();
let result1 = eval(
r#"derivation {
name = "determ";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
}"#,
);
let result2 = ctx
.eval_code(
r#"derivation {
name = "determ";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
}"#,
)
.unwrap();
let result2 = eval(
r#"derivation {
name = "determ";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
}"#,
);
assert_eq!(result1, result2);
}
#[test]
fn fixed_output_sha256_flat() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "fixed";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
outputHashAlgo = "sha256";
outputHashMode = "flat";
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "fixed";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
outputHashAlgo = "sha256";
outputHashMode = "flat";
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -419,17 +381,14 @@ fn fixed_output_sha256_flat() {
#[test]
fn fixed_output_default_algo() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "default";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "default";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -445,19 +404,16 @@ fn fixed_output_default_algo() {
#[test]
fn fixed_output_recursive_mode() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "recursive";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "1111111111111111111111111111111111111111111111111111111111111111";
outputHashAlgo = "sha256";
outputHashMode = "recursive";
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "recursive";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "1111111111111111111111111111111111111111111111111111111111111111";
outputHashAlgo = "sha256";
outputHashMode = "recursive";
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -478,8 +434,7 @@ fn fixed_output_recursive_mode() {
#[test]
fn fixed_output_rejects_multi_output() {
let mut ctx = Context::new().unwrap();
let result = ctx.eval_code(
let result = eval_result(
r#"derivation {
name = "invalid";
builder = "/bin/sh";
@@ -496,8 +451,7 @@ fn fixed_output_rejects_multi_output() {
#[test]
fn fixed_output_invalid_hash_mode() {
let mut ctx = Context::new().unwrap();
let result = ctx.eval_code(
let result = eval_result(
r#"derivation {
name = "invalid";
builder = "/bin/sh";
@@ -514,20 +468,17 @@ fn fixed_output_invalid_hash_mode() {
#[test]
fn structured_attrs_basic() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "struct";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
foo = "bar";
count = 42;
enabled = true;
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "struct";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
foo = "bar";
count = 42;
enabled = true;
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -542,18 +493,15 @@ fn structured_attrs_basic() {
#[test]
fn structured_attrs_nested() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "nested";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
data = { x = 1; y = [2 3]; };
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "nested";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
data = { x = 1; y = [2 3]; };
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -566,8 +514,7 @@ fn structured_attrs_nested() {
#[test]
fn structured_attrs_rejects_functions() {
let mut ctx = Context::new().unwrap();
let result = ctx.eval_code(
let result = eval_result(
r#"derivation {
name = "invalid";
builder = "/bin/sh";
@@ -584,18 +531,15 @@ fn structured_attrs_rejects_functions() {
#[test]
fn structured_attrs_false() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "normal";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = false;
foo = "bar";
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "normal";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = false;
foo = "bar";
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -610,19 +554,16 @@ fn structured_attrs_false() {
#[test]
fn ignore_nulls_true() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "ignore";
builder = "/bin/sh";
system = "x86_64-linux";
__ignoreNulls = true;
foo = "bar";
nullValue = null;
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "ignore";
builder = "/bin/sh";
system = "x86_64-linux";
__ignoreNulls = true;
foo = "bar";
nullValue = null;
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -635,18 +576,15 @@ fn ignore_nulls_true() {
#[test]
fn ignore_nulls_false() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "keep";
builder = "/bin/sh";
system = "x86_64-linux";
__ignoreNulls = false;
nullValue = null;
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "keep";
builder = "/bin/sh";
system = "x86_64-linux";
__ignoreNulls = false;
nullValue = null;
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -661,20 +599,17 @@ fn ignore_nulls_false() {
#[test]
fn ignore_nulls_with_structured_attrs() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "combined";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
__ignoreNulls = true;
foo = "bar";
nullValue = null;
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "combined";
builder = "/bin/sh";
system = "x86_64-linux";
__structuredAttrs = true;
__ignoreNulls = true;
foo = "bar";
nullValue = null;
}"#,
);
match result {
Value::AttrSet(attrs) => {
@@ -688,21 +623,18 @@ fn ignore_nulls_with_structured_attrs() {
#[test]
fn all_features_combined() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "all";
builder = "/bin/sh";
system = "x86_64-linux";
outputs = ["out" "dev"];
__structuredAttrs = true;
__ignoreNulls = true;
data = { x = 1; };
nullValue = null;
}"#,
)
.unwrap();
let result = eval(
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) => {
@@ -718,19 +650,16 @@ fn all_features_combined() {
#[test]
fn fixed_output_with_structured_attrs() {
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"derivation {
name = "fixstruct";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "abc123";
__structuredAttrs = true;
data = { key = "value"; };
}"#,
)
.unwrap();
let result = eval(
r#"derivation {
name = "fixstruct";
builder = "/bin/sh";
system = "x86_64-linux";
outputHash = "abc123";
__structuredAttrs = true;
data = { key = "value"; };
}"#,
);
match result {
Value::AttrSet(attrs) => {

View File

@@ -104,7 +104,7 @@ fn import_with_complex_dependency_graph() {
// Tests for builtins.path
#[test]
fn test_path_with_file() {
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");
@@ -115,7 +115,7 @@ fn test_path_with_file() {
// Should return a store path string
if let Value::String(store_path) = result {
assert!(store_path.starts_with("/nix/store/"));
assert!(store_path.starts_with(ctx.get_store_dir()));
assert!(store_path.contains("test.txt"));
} else {
panic!("Expected string, got {:?}", result);
@@ -123,7 +123,7 @@ fn test_path_with_file() {
}
#[test]
fn test_path_with_custom_name() {
fn path_with_custom_name() {
let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("original.txt");
@@ -144,7 +144,7 @@ fn test_path_with_custom_name() {
}
#[test]
fn test_path_with_directory_recursive() {
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");
@@ -159,7 +159,7 @@ fn test_path_with_directory_recursive() {
let result = ctx.eval_code(&expr).unwrap();
if let Value::String(store_path) = result {
assert!(store_path.starts_with("/nix/store/"));
assert!(store_path.starts_with(ctx.get_store_dir()));
assert!(store_path.contains("mydir"));
} else {
panic!("Expected string, got {:?}", result);
@@ -167,7 +167,7 @@ fn test_path_with_directory_recursive() {
}
#[test]
fn test_path_flat_with_file() {
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");
@@ -180,14 +180,14 @@ fn test_path_flat_with_file() {
let result = ctx.eval_code(&expr).unwrap();
if let Value::String(store_path) = result {
assert!(store_path.starts_with("/nix/store/"));
assert!(store_path.starts_with(ctx.get_store_dir()));
} else {
panic!("Expected string, got {:?}", result);
}
}
#[test]
fn test_path_flat_with_directory_fails() {
fn path_flat_with_directory_fails() {
let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("mydir");
@@ -205,7 +205,7 @@ fn test_path_flat_with_directory_fails() {
}
#[test]
fn test_path_nonexistent_fails() {
fn path_nonexistent_fails() {
let mut ctx = Context::new().unwrap();
let expr = r#"builtins.path { path = "/nonexistent/path/that/should/not/exist"; }"#;
@@ -217,7 +217,7 @@ fn test_path_nonexistent_fails() {
}
#[test]
fn test_path_missing_path_param() {
fn path_missing_path_param() {
let mut ctx = Context::new().unwrap();
let expr = r#"builtins.path { name = "test"; }"#;
@@ -229,7 +229,7 @@ fn test_path_missing_path_param() {
}
#[test]
fn test_path_with_sha256() {
fn path_with_sha256() {
let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("hash_test.txt");
@@ -257,7 +257,7 @@ fn test_path_with_sha256() {
}
#[test]
fn test_path_deterministic() {
fn path_deterministic() {
let mut ctx = Context::new().unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("deterministic.txt");

View File

@@ -4,113 +4,113 @@ use nix_js::value::Value;
use utils::{eval, eval_result};
#[test]
fn test_path_type_of() {
fn path_type_of() {
let result = eval("builtins.typeOf ./foo");
assert_eq!(result, Value::String("path".to_string()));
}
#[test]
fn test_is_path_true() {
fn is_path_true() {
let result = eval("builtins.isPath ./foo");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_is_path_false_string() {
fn is_path_false_string() {
let result = eval(r#"builtins.isPath "./foo""#);
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_is_path_false_number() {
fn is_path_false_number() {
let result = eval("builtins.isPath 42");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_path_concat_type() {
fn path_concat_type() {
// path + string = path
let result = eval(r#"builtins.typeOf (./foo + "/bar")"#);
assert_eq!(result, Value::String("path".to_string()));
}
#[test]
fn test_string_path_concat_type() {
fn string_path_concat_type() {
// string + path = string
let result = eval(r#"builtins.typeOf ("prefix-" + ./foo)"#);
assert_eq!(result, Value::String("string".to_string()));
}
#[test]
fn test_basename_of_path() {
fn basename_of_path() {
let result = eval("builtins.baseNameOf ./path/to/file.nix");
assert!(matches!(result, Value::String(s) if s == "file.nix"));
}
#[test]
fn test_basename_of_string() {
fn basename_of_string() {
let result = eval(r#"builtins.baseNameOf "/path/to/file.nix""#);
assert_eq!(result, Value::String("file.nix".to_string()));
}
#[test]
fn test_dir_of_path_type() {
fn dir_of_path_type() {
// dirOf preserves path type
let result = eval("builtins.typeOf (builtins.dirOf ./path/to/file.nix)");
assert_eq!(result, Value::String("path".to_string()));
}
#[test]
fn test_dir_of_string_type() {
fn dir_of_string_type() {
// dirOf preserves string type
let result = eval(r#"builtins.typeOf (builtins.dirOf "/path/to/file.nix")"#);
assert_eq!(result, Value::String("string".to_string()));
}
#[test]
fn test_path_equality() {
fn path_equality() {
// Same path should be equal
let result = eval("./foo == ./foo");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_path_not_equal_string() {
fn path_not_equal_string() {
// Paths and strings are different types - should not be equal
let result = eval(r#"./foo == "./foo""#);
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_to_path_absolute() {
fn to_path_absolute() {
// toPath with absolute path returns string
let result = eval(r#"builtins.toPath "/foo/bar""#);
assert_eq!(result, Value::String("/foo/bar".to_string()));
}
#[test]
fn test_to_path_type_is_string() {
fn to_path_type_is_string() {
// toPath returns a string, not a path
let result = eval(r#"builtins.typeOf (builtins.toPath "/foo")"#);
assert_eq!(result, Value::String("string".to_string()));
}
#[test]
fn test_to_path_relative_fails() {
fn to_path_relative_fails() {
// toPath with relative path should fail
let result = eval_result(r#"builtins.toPath "foo/bar""#);
assert!(result.is_err());
}
#[test]
fn test_to_path_empty_fails() {
fn to_path_empty_fails() {
// toPath with empty string should fail
let result = eval_result(r#"builtins.toPath """#);
assert!(result.is_err());
}
#[test]
fn test_to_path_from_path_value() {
fn to_path_from_path_value() {
// toPath can accept a path value too (coerces to string first)
let result = eval("builtins.toPath ./foo");
// Should succeed and return the absolute path as a string

View File

@@ -4,7 +4,7 @@ use nix_js::value::{List, Value};
use utils::eval;
#[test]
fn test_match_exact_full_string() {
fn match_exact_full_string() {
assert_eq!(
eval(r#"builtins.match "foobar" "foobar""#),
Value::List(List::new(vec![]))
@@ -12,12 +12,12 @@ fn test_match_exact_full_string() {
}
#[test]
fn test_match_partial_returns_null() {
fn match_partial_returns_null() {
assert_eq!(eval(r#"builtins.match "foo" "foobar""#), Value::Null);
}
#[test]
fn test_match_with_capture_groups() {
fn match_with_capture_groups() {
assert_eq!(
eval(r#"builtins.match "(.*)\\.nix" "foobar.nix""#),
Value::List(List::new(vec![Value::String("foobar".into())]))
@@ -25,7 +25,7 @@ fn test_match_with_capture_groups() {
}
#[test]
fn test_match_multiple_capture_groups() {
fn match_multiple_capture_groups() {
assert_eq!(
eval(r#"builtins.match "((.*)/)?([^/]*)\\.nix" "foobar.nix""#),
Value::List(List::new(vec![
@@ -37,7 +37,7 @@ fn test_match_multiple_capture_groups() {
}
#[test]
fn test_match_with_path() {
fn match_with_path() {
assert_eq!(
eval(r#"builtins.match "((.*)/)?([^/]*)\\.nix" "/path/to/foobar.nix""#),
Value::List(List::new(vec![
@@ -49,7 +49,7 @@ fn test_match_with_path() {
}
#[test]
fn test_match_posix_space_class() {
fn match_posix_space_class() {
assert_eq!(
eval(r#"builtins.match "[[:space:]]+([^[:space:]]+)[[:space:]]+" " foo ""#),
Value::List(List::new(vec![Value::String("foo".into())]))
@@ -57,7 +57,7 @@ fn test_match_posix_space_class() {
}
#[test]
fn test_match_posix_upper_class() {
fn match_posix_upper_class() {
assert_eq!(
eval(r#"builtins.match "[[:space:]]+([[:upper:]]+)[[:space:]]+" " foo ""#),
Value::Null
@@ -70,7 +70,7 @@ fn test_match_posix_upper_class() {
}
#[test]
fn test_match_quantifiers() {
fn match_quantifiers() {
assert_eq!(
eval(r#"builtins.match "fo*" "f""#),
Value::List(List::new(vec![]))
@@ -87,7 +87,7 @@ fn test_match_quantifiers() {
}
#[test]
fn test_split_non_capturing() {
fn split_non_capturing() {
assert_eq!(
eval(r#"builtins.split "foobar" "foobar""#),
Value::List(List::new(vec![
@@ -99,7 +99,7 @@ fn test_split_non_capturing() {
}
#[test]
fn test_split_no_match() {
fn split_no_match() {
assert_eq!(
eval(r#"builtins.split "fo+" "f""#),
Value::List(List::new(vec![Value::String("f".into())]))
@@ -107,7 +107,7 @@ fn test_split_no_match() {
}
#[test]
fn test_split_with_capture_group() {
fn split_with_capture_group() {
assert_eq!(
eval(r#"builtins.split "(fo*)" "foobar""#),
Value::List(List::new(vec![
@@ -119,7 +119,7 @@ fn test_split_with_capture_group() {
}
#[test]
fn test_split_multiple_matches() {
fn split_multiple_matches() {
assert_eq!(
eval(r#"builtins.split "(b)" "foobarbaz""#),
Value::List(List::new(vec![
@@ -133,7 +133,7 @@ fn test_split_multiple_matches() {
}
#[test]
fn test_split_with_multiple_groups() {
fn split_with_multiple_groups() {
assert_eq!(
eval(r#"builtins.split "(f)(o*)" "foo""#),
Value::List(List::new(vec![
@@ -148,7 +148,7 @@ fn test_split_with_multiple_groups() {
}
#[test]
fn test_split_with_optional_groups() {
fn split_with_optional_groups() {
assert_eq!(
eval(r#"builtins.split "(a)|(c)" "abc""#),
Value::List(List::new(vec![
@@ -162,7 +162,7 @@ fn test_split_with_optional_groups() {
}
#[test]
fn test_split_greedy_matching() {
fn split_greedy_matching() {
assert_eq!(
eval(r#"builtins.split "(o+)" "oooofoooo""#),
Value::List(List::new(vec![
@@ -176,7 +176,7 @@ fn test_split_greedy_matching() {
}
#[test]
fn test_split_posix_classes() {
fn split_posix_classes() {
assert_eq!(
eval(r#"builtins.split "([[:upper:]]+)" " FOO ""#),
Value::List(List::new(vec![
@@ -188,7 +188,7 @@ fn test_split_posix_classes() {
}
#[test]
fn test_replace_basic() {
fn replace_basic() {
assert_eq!(
eval(r#"builtins.replaceStrings ["o"] ["a"] "foobar""#),
Value::String("faabar".into())
@@ -196,7 +196,7 @@ fn test_replace_basic() {
}
#[test]
fn test_replace_with_empty() {
fn replace_with_empty() {
assert_eq!(
eval(r#"builtins.replaceStrings ["o"] [""] "foobar""#),
Value::String("fbar".into())
@@ -204,7 +204,7 @@ fn test_replace_with_empty() {
}
#[test]
fn test_replace_multiple_patterns() {
fn replace_multiple_patterns() {
assert_eq!(
eval(r#"builtins.replaceStrings ["oo" "a"] ["a" "oo"] "foobar""#),
Value::String("faboor".into())
@@ -212,7 +212,7 @@ fn test_replace_multiple_patterns() {
}
#[test]
fn test_replace_first_match_wins() {
fn replace_first_match_wins() {
assert_eq!(
eval(r#"builtins.replaceStrings ["oo" "oo"] ["u" "i"] "foobar""#),
Value::String("fubar".into())
@@ -220,7 +220,7 @@ fn test_replace_first_match_wins() {
}
#[test]
fn test_replace_empty_pattern() {
fn replace_empty_pattern() {
assert_eq!(
eval(r#"builtins.replaceStrings [""] ["X"] "abc""#),
Value::String("XaXbXcX".into())
@@ -228,7 +228,7 @@ fn test_replace_empty_pattern() {
}
#[test]
fn test_replace_empty_pattern_empty_string() {
fn replace_empty_pattern_empty_string() {
assert_eq!(
eval(r#"builtins.replaceStrings [""] ["X"] """#),
Value::String("X".into())
@@ -236,7 +236,7 @@ fn test_replace_empty_pattern_empty_string() {
}
#[test]
fn test_replace_simple_char() {
fn replace_simple_char() {
assert_eq!(
eval(r#"builtins.replaceStrings ["-"] ["_"] "a-b""#),
Value::String("a_b".into())
@@ -244,7 +244,7 @@ fn test_replace_simple_char() {
}
#[test]
fn test_replace_longer_pattern() {
fn replace_longer_pattern() {
assert_eq!(
eval(r#"builtins.replaceStrings ["oo"] ["u"] "foobar""#),
Value::String("fubar".into())
@@ -252,14 +252,14 @@ fn test_replace_longer_pattern() {
}
#[test]
fn test_replace_different_lengths() {
fn replace_different_lengths() {
let result =
std::panic::catch_unwind(|| eval(r#"builtins.replaceStrings ["a" "b"] ["x"] "test""#));
assert!(result.is_err());
}
#[test]
fn test_split_version_simple() {
fn split_version_simple() {
assert_eq!(
eval(r#"builtins.splitVersion "1.2.3""#),
Value::List(List::new(vec![
@@ -271,7 +271,7 @@ fn test_split_version_simple() {
}
#[test]
fn test_split_version_with_pre() {
fn split_version_with_pre() {
assert_eq!(
eval(r#"builtins.splitVersion "2.3.0pre1234""#),
Value::List(List::new(vec![
@@ -285,7 +285,7 @@ fn test_split_version_with_pre() {
}
#[test]
fn test_split_version_with_letters() {
fn split_version_with_letters() {
assert_eq!(
eval(r#"builtins.splitVersion "2.3a""#),
Value::List(List::new(vec![
@@ -297,7 +297,7 @@ fn test_split_version_with_letters() {
}
#[test]
fn test_split_version_with_dashes() {
fn split_version_with_dashes() {
assert_eq!(
eval(r#"builtins.splitVersion "2.3-beta1""#),
Value::List(List::new(vec![
@@ -310,7 +310,7 @@ fn test_split_version_with_dashes() {
}
#[test]
fn test_split_version_empty() {
fn split_version_empty() {
assert_eq!(
eval(r#"builtins.splitVersion """#),
Value::List(List::new(vec![]))

View File

@@ -152,8 +152,10 @@ fn string_add_merges_context() {
#[test]
fn context_in_derivation_args() {
let result = eval(
r#"
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"
let
dep = derivation { name = "dep"; builder = "/bin/sh"; system = "x86_64-linux"; };
drv = derivation {
@@ -164,10 +166,11 @@ fn context_in_derivation_args() {
};
in drv.drvPath
"#,
);
)
.unwrap();
match result {
Value::String(s) => {
assert!(s.starts_with("/nix/store/"), "Should be a store path");
assert!(s.starts_with(ctx.get_store_dir()), "Should be a store path");
assert!(s.ends_with(".drv"), "Should be a .drv file");
}
_ => panic!("Expected String, got {:?}", result),
@@ -176,8 +179,10 @@ fn context_in_derivation_args() {
#[test]
fn context_in_derivation_env() {
let result = eval(
r#"
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"
let
dep = derivation { name = "dep"; builder = "/bin/sh"; system = "x86_64-linux"; };
drv = derivation {
@@ -188,10 +193,11 @@ fn context_in_derivation_env() {
};
in drv.drvPath
"#,
);
)
.unwrap();
match result {
Value::String(s) => {
assert!(s.starts_with("/nix/store/"), "Should be a store path");
assert!(s.starts_with(ctx.get_store_dir()), "Should be a store path");
assert!(s.ends_with(".drv"), "Should be a .drv file");
}
_ => panic!("Expected String, got {:?}", result),
@@ -213,16 +219,19 @@ fn tostring_preserves_context() {
#[test]
fn interpolation_derivation_returns_outpath() {
let result = eval(
r#"
let mut ctx = Context::new().unwrap();
let result = ctx
.eval_code(
r#"
let
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
in "${drv}"
"#,
);
)
.unwrap();
match result {
Value::String(s) => {
assert!(s.starts_with("/nix/store/"), "Should be a store path");
assert!(s.starts_with(ctx.get_store_dir()), "Should be a store path");
assert!(s.ends_with("-test"), "Should end with derivation name");
}
_ => panic!("Expected String, got {:?}", result),
@@ -332,6 +341,7 @@ fn substring_zero_length_empty_value() {
}
#[test]
#[allow(non_snake_case)]
fn concatStringsSep_preserves_context() {
let result = eval(
r#"
@@ -348,6 +358,7 @@ fn concatStringsSep_preserves_context() {
}
#[test]
#[allow(non_snake_case)]
fn concatStringsSep_merges_contexts() {
let result = eval(
r#"
@@ -365,6 +376,7 @@ fn concatStringsSep_merges_contexts() {
}
#[test]
#[allow(non_snake_case)]
fn concatStringsSep_separator_has_context() {
let result = eval(
r#"