559 lines
17 KiB
Rust
559 lines
17 KiB
Rust
use fix::error::Source;
|
|
use fix::runtime::Runtime;
|
|
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 = Runtime::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("/nix/store"));
|
|
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 = Runtime::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("/nix/store"));
|
|
assert!(store_path.contains("mydir"));
|
|
} else {
|
|
panic!("Expected string, got {:?}", result);
|
|
}
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn path_flat_with_file() {
|
|
let mut ctx = Runtime::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("/nix/store"));
|
|
} 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"));
|
|
}
|
|
|
|
#[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"),
|
|
}
|
|
}
|