use fix::Evaluator; use fix_common::Value; use fix_error::Source; 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 = Evaluator::new(); 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 = Evaluator::new(); 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 = Evaluator::new(); 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"), } }