564 lines
16 KiB
Rust
564 lines
16 KiB
Rust
use fix::runtime::Runtime;
|
|
use fix::value::Value;
|
|
|
|
use crate::utils::eval_result;
|
|
|
|
fn eval(expr: &str) -> Value {
|
|
eval_result(expr).unwrap_or_else(|e| panic!("{}", e))
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn hascontext_plain_string() {
|
|
let result = eval(r#"builtins.hasContext "hello""#);
|
|
assert_eq!(result, Value::Bool(false));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn hascontext_derivation_output() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
in builtins.hasContext (builtins.toString drv)
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn getcontext_plain_string() {
|
|
let result = eval(r#"builtins.getContext "hello""#);
|
|
match result {
|
|
Value::AttrSet(attrs) => {
|
|
assert!(attrs.is_empty(), "Plain string should have empty context");
|
|
}
|
|
_ => panic!("Expected AttrSet, got {:?}", result),
|
|
}
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn getcontext_derivation_output() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str = builtins.toString drv;
|
|
ctx = builtins.getContext str;
|
|
in builtins.attrNames ctx
|
|
"#,
|
|
);
|
|
match result {
|
|
Value::List(list) => {
|
|
assert_eq!(list.len(), 1, "Should have exactly one context entry");
|
|
match list.first().unwrap() {
|
|
Value::String(s) => {
|
|
assert!(s.ends_with(".drv"), "Context key should be a .drv path");
|
|
}
|
|
other => panic!("Expected String, got {:?}", other),
|
|
}
|
|
}
|
|
_ => panic!("Expected List, got {:?}", result),
|
|
}
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn unsafediscardstringcontext() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
strWithContext = builtins.toString drv;
|
|
strWithoutContext = builtins.unsafeDiscardStringContext strWithContext;
|
|
in builtins.hasContext strWithoutContext
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(false));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn unsafediscardstringcontext_preserves_value() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
strWithContext = builtins.toString drv;
|
|
strWithoutContext = builtins.unsafeDiscardStringContext strWithContext;
|
|
in strWithContext == strWithoutContext
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn appendcontext_basic() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
str = builtins.appendContext "hello" {
|
|
"/nix/store/0000000000000000000000000000000-test.drv" = { outputs = ["out"]; };
|
|
};
|
|
in builtins.hasContext str
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn appendcontext_preserves_value() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
str = builtins.appendContext "hello" {
|
|
"/nix/store/0000000000000000000000000000000-test.drv" = { outputs = ["out"]; };
|
|
};
|
|
in str == "hello"
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn string_concat_merges_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv1 = derivation { name = "test1"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
drv2 = derivation { name = "test2"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str1 = builtins.toString drv1;
|
|
str2 = builtins.toString drv2;
|
|
combined = str1 + " " + str2;
|
|
ctx = builtins.getContext combined;
|
|
in builtins.length (builtins.attrNames ctx)
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Int(2));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn string_add_merges_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv1 = derivation { name = "test1"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
drv2 = derivation { name = "test2"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str1 = builtins.toString drv1;
|
|
str2 = builtins.toString drv2;
|
|
combined = str1 + " " + str2;
|
|
ctx = builtins.getContext combined;
|
|
in builtins.length (builtins.attrNames ctx)
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Int(2));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn context_in_derivation_args() {
|
|
let mut rt = Runtime::new().unwrap();
|
|
let result = rt
|
|
.eval(
|
|
r#"
|
|
let
|
|
dep = derivation { name = "dep"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
drv = derivation {
|
|
name = "test";
|
|
builder = "/bin/sh";
|
|
system = "x86_64-linux";
|
|
args = [ ((builtins.toString dep) + "/bin/run") ];
|
|
};
|
|
in drv.drvPath
|
|
"#
|
|
.try_into()
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
match result {
|
|
Value::String(s) => {
|
|
assert!(s.starts_with("/nix/store"), "Should be a store path");
|
|
assert!(s.ends_with(".drv"), "Should be a .drv file");
|
|
}
|
|
_ => panic!("Expected String, got {:?}", result),
|
|
}
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn context_in_derivation_env() {
|
|
let mut rt = Runtime::new().unwrap();
|
|
let result = rt
|
|
.eval(
|
|
r#"
|
|
let
|
|
dep = derivation { name = "dep"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
drv = derivation {
|
|
name = "test";
|
|
builder = "/bin/sh";
|
|
system = "x86_64-linux";
|
|
myDep = builtins.toString dep;
|
|
};
|
|
in drv.drvPath
|
|
"#
|
|
.try_into()
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
match result {
|
|
Value::String(s) => {
|
|
assert!(s.starts_with("/nix/store"), "Should be a store path");
|
|
assert!(s.ends_with(".drv"), "Should be a .drv file");
|
|
}
|
|
_ => panic!("Expected String, got {:?}", result),
|
|
}
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn tostring_preserves_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str = builtins.toString drv;
|
|
in builtins.hasContext str
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn interpolation_derivation_returns_outpath() {
|
|
let mut rt = Runtime::new().unwrap();
|
|
let result = rt
|
|
.eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
in "${drv}"
|
|
"#
|
|
.try_into()
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
match result {
|
|
Value::String(s) => {
|
|
assert!(s.starts_with("/nix/store"), "Should be a store path");
|
|
assert!(s.ends_with("-test"), "Should end with derivation name");
|
|
}
|
|
_ => panic!("Expected String, got {:?}", result),
|
|
}
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn interpolation_derivation_has_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
in builtins.hasContext "${drv}"
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn interpolation_derivation_context_correct() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
ctx = builtins.getContext "${drv}";
|
|
keys = builtins.attrNames ctx;
|
|
drvPath = builtins.head keys;
|
|
in ctx.${drvPath}.outputs
|
|
"#,
|
|
);
|
|
match result {
|
|
Value::List(list) => {
|
|
assert_eq!(list.len(), 1);
|
|
assert_eq!(list.first().unwrap(), &Value::String("out".to_string()));
|
|
}
|
|
_ => panic!("Expected List with ['out'], got {:?}", result),
|
|
}
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn interpolation_multiple_derivations() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv1 = derivation { name = "test1"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
drv2 = derivation { name = "test2"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
combined = "prefix-${drv1}-middle-${drv2}-suffix";
|
|
ctx = builtins.getContext combined;
|
|
in builtins.length (builtins.attrNames ctx)
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Int(2));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn interpolation_derivation_equals_tostring() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
in "${drv}" == builtins.toString drv
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn substring_preserves_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str = builtins.toString drv;
|
|
sub = builtins.substring 0 10 str;
|
|
in builtins.hasContext sub
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn substring_zero_length_preserves_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str = builtins.toString drv;
|
|
empty = builtins.substring 0 0 str;
|
|
in builtins.hasContext empty
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn substring_zero_length_empty_value() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str = builtins.toString drv;
|
|
empty = builtins.substring 0 0 str;
|
|
in empty == ""
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
#[allow(non_snake_case)]
|
|
fn concatStringsSep_preserves_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv1 = derivation { name = "test1"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
drv2 = derivation { name = "test2"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str1 = builtins.toString drv1;
|
|
str2 = builtins.toString drv2;
|
|
combined = builtins.concatStringsSep ":" [str1 str2];
|
|
in builtins.hasContext combined
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
#[allow(non_snake_case)]
|
|
fn concatStringsSep_merges_contexts() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv1 = derivation { name = "test1"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
drv2 = derivation { name = "test2"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str1 = builtins.toString drv1;
|
|
str2 = builtins.toString drv2;
|
|
combined = builtins.concatStringsSep ":" [str1 str2];
|
|
ctx = builtins.getContext combined;
|
|
in builtins.length (builtins.attrNames ctx)
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Int(2));
|
|
}
|
|
|
|
#[test_log::test]
|
|
#[allow(non_snake_case)]
|
|
fn concatStringsSep_separator_has_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
sep = builtins.toString drv;
|
|
combined = builtins.concatStringsSep sep ["a" "b"];
|
|
in builtins.hasContext combined
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
#[allow(non_snake_case)]
|
|
fn replaceStrings_input_context_preserved() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str = builtins.toString drv;
|
|
replaced = builtins.replaceStrings ["x"] ["y"] str;
|
|
in builtins.hasContext replaced
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
#[allow(non_snake_case)]
|
|
fn replaceStrings_replacement_context_collected() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
replacement = builtins.toString drv;
|
|
replaced = builtins.replaceStrings ["foo"] [replacement] "hello foo world";
|
|
in builtins.hasContext replaced
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
#[allow(non_snake_case)]
|
|
fn replaceStrings_merges_contexts() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv1 = derivation { name = "test1"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
drv2 = derivation { name = "test2"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str = builtins.toString drv1;
|
|
replacement = builtins.toString drv2;
|
|
replaced = builtins.replaceStrings ["x"] [replacement] str;
|
|
ctx = builtins.getContext replaced;
|
|
in builtins.length (builtins.attrNames ctx)
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Int(2));
|
|
}
|
|
|
|
#[test_log::test]
|
|
#[allow(non_snake_case)]
|
|
fn replaceStrings_lazy_evaluation_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
replacement = builtins.toString drv;
|
|
replaced = builtins.replaceStrings ["a" "b"] [replacement "unused"] "hello";
|
|
in builtins.hasContext replaced
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(false));
|
|
}
|
|
|
|
#[test_log::test]
|
|
#[allow(non_snake_case)]
|
|
fn baseNameOf_preserves_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str = builtins.toString drv;
|
|
base = builtins.baseNameOf str;
|
|
in builtins.hasContext base
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn split_no_match_preserves_context() {
|
|
let result = eval(
|
|
r#"
|
|
let
|
|
drv = derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; };
|
|
str = builtins.toString drv;
|
|
result = builtins.split "xyz" str;
|
|
in builtins.hasContext (builtins.head result)
|
|
"#,
|
|
);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn builtins_path_has_context() {
|
|
let temp_dir = tempfile::tempdir().unwrap();
|
|
let test_file = temp_dir.path().join("test.txt");
|
|
std::fs::write(&test_file, "hello").unwrap();
|
|
|
|
let expr = format!(
|
|
r#"builtins.hasContext (builtins.path {{ path = {}; name = "test-ctx"; }})"#,
|
|
test_file.display()
|
|
);
|
|
let result = eval(&expr);
|
|
assert_eq!(result, Value::Bool(true));
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn builtins_path_context_tracked_in_structured_attrs_derivation() {
|
|
let temp_dir = tempfile::tempdir().unwrap();
|
|
let test_file = temp_dir.path().join("test-patch.txt");
|
|
std::fs::write(&test_file, "patch content").unwrap();
|
|
|
|
let expr = format!(
|
|
r#"
|
|
let
|
|
patch = builtins.path {{ path = {}; name = "test-patch"; }};
|
|
in
|
|
(derivation {{
|
|
__structuredAttrs = true;
|
|
name = "test-input-srcs";
|
|
system = "x86_64-linux";
|
|
builder = "/bin/sh";
|
|
patches = [ patch ];
|
|
}}).drvPath
|
|
"#,
|
|
test_file.display()
|
|
);
|
|
let result = eval(&expr);
|
|
|
|
if let Value::String(s) = &result {
|
|
assert!(s.contains("/nix/store/"), "drvPath should be a store path");
|
|
} else {
|
|
panic!("Expected string, got {:?}", result);
|
|
}
|
|
}
|
|
|
|
#[test_log::test]
|
|
fn builtins_path_context_tracked_in_non_structured_derivation() {
|
|
let temp_dir = tempfile::tempdir().unwrap();
|
|
let test_file = temp_dir.path().join("dep.txt");
|
|
std::fs::write(&test_file, "dependency content").unwrap();
|
|
|
|
let expr = format!(
|
|
r#"
|
|
let
|
|
dep = builtins.path {{ path = {}; name = "dep-file"; }};
|
|
in
|
|
(derivation {{
|
|
name = "test-non-structured";
|
|
system = "x86_64-linux";
|
|
builder = "/bin/sh";
|
|
myDep = dep;
|
|
}}).drvPath
|
|
"#,
|
|
test_file.display()
|
|
);
|
|
let result = eval(&expr);
|
|
|
|
if let Value::String(s) = &result {
|
|
assert!(s.contains("/nix/store/"), "drvPath should be a store path");
|
|
} else {
|
|
panic!("Expected string, got {:?}", result);
|
|
}
|
|
}
|