1use std::rc::Rc;
4
5use crate::tvix_store_io::TvixStoreIO;
6
7mod derivation;
8mod errors;
9mod import;
11mod utils;
12
13pub use errors::{DerivationError, FetcherError, ImportError};
14
15pub fn add_derivation_builtins<'co, 'ro, 'env>(
24 eval_builder: tvix_eval::EvaluationBuilder<'co, 'ro, 'env>,
25 io: Rc<TvixStoreIO>,
26) -> tvix_eval::EvaluationBuilder<'co, 'ro, 'env> {
27 eval_builder
28 .add_builtins(derivation::derivation_builtins::builtins(Rc::clone(&io)))
29 .add_src_builtin("derivation", include_str!("derivation.nix"))
31}
32
33pub fn add_import_builtins<'co, 'ro, 'env>(
56 eval_builder: tvix_eval::EvaluationBuilder<'co, 'ro, 'env>,
57 io: Rc<TvixStoreIO>,
58) -> tvix_eval::EvaluationBuilder<'co, 'ro, 'env> {
59 eval_builder.add_builtins(import::import_builtins(io))
60}
61
62#[cfg(test)]
63mod tests {
64 use std::{fs, rc::Rc};
65
66 use crate::tvix_store_io::TvixStoreIO;
67
68 use super::{add_derivation_builtins, add_import_builtins};
69 use nix_compat::store_path::hash_placeholder;
70 use rstest::rstest;
71 use tempfile::TempDir;
72 use tvix_eval::{EvalIO, EvaluationResult};
73
74 fn eval(str: &str) -> EvaluationResult {
78 let io = Rc::new(TvixStoreIO::new(Default::default()));
79
80 let mut eval_builder = tvix_eval::Evaluation::builder(io.clone() as Rc<dyn EvalIO>);
81 eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&io));
82 eval_builder = add_import_builtins(eval_builder, io);
84 let eval = eval_builder.build();
85
86 eval.evaluate(str, None)
88 }
89
90 #[test]
91 fn derivation() {
92 let result = eval(
93 r#"(derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux";}).outPath"#,
94 );
95
96 assert!(result.errors.is_empty(), "expect evaluation to succeed");
97 let value = result.value.expect("must be some");
98
99 match value {
100 tvix_eval::Value::String(s) => {
101 assert_eq!(*s, "/nix/store/xpcvxsx5sw4rbq666blz6sxqlmsqphmr-foo",);
102 }
103 _ => panic!("unexpected value type: {value:?}"),
104 }
105 }
106
107 #[test]
109 fn derivation_empty_name_fail() {
110 let result = eval(
111 r#"(derivation { name = ""; builder = "/bin/sh"; system = "x86_64-linux";}).outPath"#,
112 );
113
114 assert!(!result.errors.is_empty(), "expect evaluation to fail");
115 }
116
117 #[rstest]
120 #[case::r_sha256(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
121 #[case::r_sha256_other_name(r#"(builtins.derivation { name = "foo2"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/gi0p8vd635vpk1nq029cz3aa3jkhar5k-foo2")]
122 #[case::r_sha1(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha1"; outputHash = "sha1-VUCRC+16gU5lcrLYHlPSUyx0Y/Q="; }).outPath"#, "/nix/store/p5sammmhpa84ama7ymkbgwwzrilva24x-foo")]
123 #[case::r_md5(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "md5"; outputHash = "md5-07BzhNET7exJ6qYjitX/AA=="; }).outPath"#, "/nix/store/gmmxgpy1jrzs86r5y05wy6wiy2m15xgi-foo")]
124 #[case::r_sha512(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha512"; outputHash = "sha512-DPkYCnZKuoY6Z7bXLwkYvBMcZ3JkLLLc5aNPCnAvlHDdwr8SXBIZixmVwjPDS0r9NGxUojNMNQqUilG26LTmtg=="; }).outPath"#, "/nix/store/lfi2bfyyap88y45mfdwi4j99gkaxaj19-foo")]
125 #[case::r_sha256_base16(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "4374173a8cbe88de152b609f96f46e958bcf65762017474eec5a05ec2bd61530"; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
126 #[case::r_sha256_nixbase32(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "0c0msqmyq1asxi74f5r0frjwz2wmdvs9d7v05caxx25yihx1fx23"; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
127 #[case::r_sha256_base64(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
128 #[case::r_sha256_base64_nopad(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-fgIr3TyFGDAXP5+qoAaiMKDg/a1MlT6Fv/S/DaA24S8="; }).outPath"#, "/nix/store/xm1l9dx4zgycv9qdhcqqvji1z88z534b-foo")]
129 #[case::sha256(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "sha256"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/q4pkwkxdib797fhk22p0k3g1q32jmxvf-foo")]
130 #[case::sha256_other_name(r#"(builtins.derivation { name = "foo2"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "sha256"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/znw17xlmx9r6gw8izjkqxkl6s28sza4l-foo2")]
131 #[case::sha1(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "sha1"; outputHash = "sha1-VUCRC+16gU5lcrLYHlPSUyx0Y/Q="; }).outPath"#, "/nix/store/zgpnjjmga53d8srp8chh3m9fn7nnbdv6-foo")]
132 #[case::md5(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "md5"; outputHash = "md5-07BzhNET7exJ6qYjitX/AA=="; }).outPath"#, "/nix/store/jfhcwnq1852ccy9ad9nakybp2wadngnd-foo")]
133 #[case::sha512(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "flat"; outputHashAlgo = "sha512"; outputHash = "sha512-DPkYCnZKuoY6Z7bXLwkYvBMcZ3JkLLLc5aNPCnAvlHDdwr8SXBIZixmVwjPDS0r9NGxUojNMNQqUilG26LTmtg=="; }).outPath"#, "/nix/store/as736rr116ian9qzg457f96j52ki8bm3-foo")]
134 #[case::r_sha256_outputhashalgo_omitted(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/17wgs52s7kcamcyin4ja58njkf91ipq8-foo")]
135 #[case::r_sha256_outputhashalgo_and_outputhashmode_omitted(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#, "/nix/store/q4pkwkxdib797fhk22p0k3g1q32jmxvf-foo")]
136 #[case::outputhash_omitted(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; }).outPath"#, "/nix/store/xpcvxsx5sw4rbq666blz6sxqlmsqphmr-foo")]
137 #[case::multiple_outputs(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; outputs = ["foo" "bar"]; system = "x86_64-linux"; }).outPath"#, "/nix/store/hkwdinvz2jpzgnjy9lv34d2zxvclj4s3-foo-foo")]
138 #[case::args(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; args = ["--foo" "42" "--bar"]; system = "x86_64-linux"; }).outPath"#, "/nix/store/365gi78n2z7vwc1bvgb98k0a9cqfp6as-foo")]
139 #[case::full(r#"
140 let
141 bar = builtins.derivation {
142 name = "bar";
143 builder = ":";
144 system = ":";
145 outputHash = "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba";
146 outputHashAlgo = "sha256";
147 outputHashMode = "recursive";
148 };
149 in
150 (builtins.derivation {
151 name = "foo";
152 builder = ":";
153 system = ":";
154 inherit bar;
155 }).outPath
156 "#, "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo")]
157 #[case::pass_as_file(r#"(builtins.derivation { "name" = "foo"; passAsFile = ["bar"]; bar = "baz"; system = ":"; builder = ":";}).outPath"#, "/nix/store/25gf0r1ikgmh4vchrn8qlc4fnqlsa5a1-foo")]
158 #[case::ignore_nulls_true_no_arg_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; }).drvPath"#, "/nix/store/xa96w6d7fxrlkk60z1fmx2ffp2wzmbqx-foo.drv")]
160 #[case::ignore_nulls_true_no_arg_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; }).outPath"#, "/nix/store/pk2agn9za8r9bxsflgh1y7fyyrmwcqkn-foo")]
161 #[case::ignore_nulls_true_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; ignoreme = null; }).drvPath"#, "/nix/store/xa96w6d7fxrlkk60z1fmx2ffp2wzmbqx-foo.drv")]
163 #[case::ignore_nulls_true_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; ignoreme = null; }).outPath"#, "/nix/store/pk2agn9za8r9bxsflgh1y7fyyrmwcqkn-foo")]
164 #[case::ignore_nulls_false_no_arg_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; }).drvPath"#, "/nix/store/xa96w6d7fxrlkk60z1fmx2ffp2wzmbqx-foo.drv")]
166 #[case::ignore_nulls_false_no_arg_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; }).outPath"#, "/nix/store/pk2agn9za8r9bxsflgh1y7fyyrmwcqkn-foo")]
167 #[case::ignore_nulls_fales_arg_path_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; foo = null; }).drvPath"#, "/nix/store/xwkwbajfiyhdqmksrbzm0s4g4ib8d4ms-foo.drv")]
169 #[case::ignore_nulls_fales_arg_path_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; foo = null; }).outPath"#, "/nix/store/2n2jqm6l7r2ahi19m58pl896ipx9cyx6-foo")]
170 #[case::structured_attrs_false_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = false; foo = "bar"; }).drvPath"#, "/nix/store/qs39krwr2lsw6ac910vqx4pnk6m63333-foo.drv")]
172 #[case::structured_attrs_false_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = false; foo = "bar"; }).outPath"#, "/nix/store/9yy3764rdip3fbm8ckaw4j9y7vh4d231-foo")]
173 #[case::structured_attrs_simple_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; }).drvPath"#, "/nix/store/k6rlb4k10cb9iay283037ml1nv3xma2f-foo.drv")]
175 #[case::structured_attrs_simple_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; }).outPath"#, "/nix/store/6lmv3hyha1g4cb426iwjyifd7nrdv1xn-foo")]
176 #[case::structured_attrs_output_checks_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; outputChecks = {out = {maxClosureSize = 256 * 1024 * 1024; disallowedRequisites = [ "dev" ];};}; }).drvPath"#, "/nix/store/fx9qzpchh5wchchhy39bwsml978d6wp1-foo.drv")]
178 #[case::structured_attrs_output_checks_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; outputChecks = {out = {maxClosureSize = 256 * 1024 * 1024; disallowedRequisites = [ "dev" ];};}; }).outPath"#, "/nix/store/pcywah1nwym69rzqdvpp03sphfjgyw1l-foo")]
179 #[case::structured_attrs_and_ignore_nulls_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; foo = null; __structuredAttrs = true; }).drvPath"#, "/nix/store/rldskjdcwa3p7x5bqy3r217va1jsbjsc-foo.drv")]
181 #[case::structured_attrs_outputs_drvpath(r#"(builtins.derivation { name = "test"; system = "aarch64-linux"; builder = "/bin/sh"; __structuredAttrs = true; outputs = [ "out"]; }).drvPath"#, "/nix/store/6sgawp30zibsh525p7c948xxd22y2ngy-test.drv")]
183 fn test_outpath(#[case] code: &str, #[case] expected_path: &str) {
184 let value = eval(code).value.expect("must succeed");
185
186 match value {
187 tvix_eval::Value::String(s) => {
188 assert_eq!(*s, expected_path);
189 }
190 _ => panic!("unexpected value type: {value:?}"),
191 }
192 }
193
194 #[rstest]
196 #[case::invalid_outputhash(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-00"; }).outPath"#)]
197 #[case::sha1_and_sha256(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha1"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath"#)]
198 #[case::duplicate_output_names(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; outputs = ["foo" "foo"]; system = "x86_64-linux"; }).outPath"#)]
199 fn test_outpath_invalid(#[case] code: &str) {
200 let resp = eval(code);
201 assert!(resp.value.is_none(), "Value should be None");
202 assert!(
203 !resp.errors.is_empty(),
204 "There should have been some errors"
205 );
206 }
207
208 #[test]
211 fn test_fod_outpath() {
212 let code = r#"
213 (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath ==
214 (builtins.derivation { name = "foo"; builder = "/bin/aa"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath
215 "#;
216
217 let value = eval(code).value.expect("must succeed");
218 match value {
219 tvix_eval::Value::Bool(v) => {
220 assert!(v);
221 }
222 _ => panic!("unexpected value type: {value:?}"),
223 }
224 }
225
226 #[test]
229 fn test_fod_outpath_different_name() {
230 let code = r#"
231 (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath ==
232 (builtins.derivation { name = "foo"; builder = "/bin/aa"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath
233 "#;
234
235 let value = eval(code).value.expect("must succeed");
236 match value {
237 tvix_eval::Value::Bool(v) => {
238 assert!(v);
239 }
240 _ => panic!("unexpected value type: {value:?}"),
241 }
242 }
243
244 #[test]
248 fn test_unsafe_discard_string_context() {
249 let code = r#"
250 let
251 dep = builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; };
252 in
253 (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; env = "${dep}"; }).outPath !=
254 (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; env = "${builtins.unsafeDiscardStringContext dep}"; }).outPath
255 "#;
256
257 let value = eval(code).value.expect("must succeed");
258 match value {
259 tvix_eval::Value::Bool(v) => {
260 assert!(v);
261 }
262 _ => panic!("unexpected value type: {value:?}"),
263 }
264 }
265
266 #[test]
269 fn test_unsafe_discard_string_context_of_coercible() {
270 let code = r#"
271 let
272 dep = builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; };
273 attr = { __toString = _: dep; };
274 in
275 builtins.typeOf (builtins.unsafeDiscardStringContext attr) == "string"
276 "#;
277
278 let value = eval(code).value.expect("must succeed");
279 match value {
280 tvix_eval::Value::Bool(v) => {
281 assert!(v);
282 }
283 _ => panic!("unexpected value type: {value:?}"),
284 }
285 }
286
287 #[rstest]
288 #[case::input_in_args(r#"
289 let
290 bar = builtins.derivation {
291 name = "bar";
292 builder = ":";
293 system = ":";
294 outputHash = "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba";
295 outputHashAlgo = "sha256";
296 outputHashMode = "recursive";
297 };
298 in
299 (builtins.derivation {
300 name = "foo";
301 builder = ":";
302 args = [ "${bar}" ];
303 system = ":";
304 }).drvPath
305 "#, "/nix/store/50yl2gmmljyl0lzyrp1mcyhn53vhjhkd-foo.drv")]
306 fn test_inputs_derivation_from_context(#[case] code: &str, #[case] expected_drvpath: &str) {
307 let eval_result = eval(code);
308
309 let value = eval_result.value.expect("must succeed");
310
311 match value {
312 tvix_eval::Value::String(s) => {
313 assert_eq!(*s, expected_drvpath);
314 }
315
316 _ => panic!("unexpected value type: {value:?}"),
317 };
318 }
319
320 #[test]
321 fn builtins_placeholder_hashes() {
322 assert_eq!(
323 hash_placeholder("out").as_str(),
324 "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"
325 );
326
327 assert_eq!(
328 hash_placeholder("").as_str(),
329 "/171rf4jhx57xqz3p7swniwkig249cif71pa08p80mgaf0mqz5bmr"
330 );
331 }
332
333 #[rstest]
335 #[case::r_sha256_wrong_padding(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-fgIr3TyFGDAXP5+qoAaiMKDg/a1MlT6Fv/S/DaA24S8===="; }).outPath"#, "/nix/store/xm1l9dx4zgycv9qdhcqqvji1z88z534b-foo")]
336 fn builtins_derivation_hash_wrong_padding_warn(
337 #[case] code: &str,
338 #[case] expected_path: &str,
339 ) {
340 let eval_result = eval(code);
341
342 let value = eval_result.value.expect("must succeed");
343
344 match value {
345 tvix_eval::Value::String(s) => {
346 assert_eq!(*s, expected_path);
347 }
348 _ => panic!("unexpected value type: {value:?}"),
349 }
350
351 assert!(
352 !eval_result.warnings.is_empty(),
353 "warnings should not be empty"
354 );
355 }
356
357 #[rstest]
361 #[cfg(target_family = "unix")]
362 #[case::complicated_filter_nothing(
363 r#"(builtins.filterSource (p: t: true) @fixtures)"#,
364 "/nix/store/bqh6kd0x3vps2rzagzpl7qmbbgnx19cp-import_fixtures"
365 )]
366 #[case::complicated_filter_everything(
367 r#"(builtins.filterSource (p: t: false) @fixtures)"#,
368 "/nix/store/giq6czz24lpjg97xxcxk6rg950lcpib1-import_fixtures"
369 )]
370 #[case::simple_dir_with_one_file_filter_dirs(
371 r#"(builtins.filterSource (p: t: t != "directory") @fixtures/a_dir)"#,
372 "/nix/store/8vbqaxapywkvv1hacdja3pi075r14d43-a_dir"
373 )]
374 #[case::simple_dir_with_one_file_filter_files(
375 r#"(builtins.filterSource (p: t: t != "regular") @fixtures/a_dir)"#,
376 "/nix/store/zphlqc93s2iq4xm393l06hzf8hp85r4z-a_dir"
377 )]
378 #[case::simple_dir_with_one_file_filter_symlinks(
379 r#"(builtins.filterSource (p: t: t != "symlink") @fixtures/a_dir)"#,
380 "/nix/store/8vbqaxapywkvv1hacdja3pi075r14d43-a_dir"
381 )]
382 #[case::simple_dir_with_one_file_filter_nothing(
383 r#"(builtins.filterSource (p: t: true) @fixtures/a_dir)"#,
384 "/nix/store/8vbqaxapywkvv1hacdja3pi075r14d43-a_dir"
385 )]
386 #[case::simple_dir_with_one_file_filter_everything(
387 r#"(builtins.filterSource (p: t: false) @fixtures/a_dir)"#,
388 "/nix/store/zphlqc93s2iq4xm393l06hzf8hp85r4z-a_dir"
389 )]
390 #[case::simple_dir_with_one_dir_filter_dirs(
391 r#"builtins.filterSource (p: t: t != "directory") @fixtures/b_dir"#,
392 "/nix/store/xzsfzdgrxg93icaamjm8zq1jq6xvf2fz-b_dir"
393 )]
394 #[case::simple_dir_with_one_dir_filter_files(
395 r#"builtins.filterSource (p: t: t != "regular") @fixtures/b_dir"#,
396 "/nix/store/8rjx64mm7173xp60rahv7cl3ixfkv3rf-b_dir"
397 )]
398 #[case::simple_dir_with_one_dir_filter_symlinks(
399 r#"builtins.filterSource (p: t: t != "symlink") @fixtures/b_dir"#,
400 "/nix/store/8rjx64mm7173xp60rahv7cl3ixfkv3rf-b_dir"
401 )]
402 #[case::simple_dir_with_one_dir_filter_nothing(
403 r#"builtins.filterSource (p: t: true) @fixtures/b_dir"#,
404 "/nix/store/8rjx64mm7173xp60rahv7cl3ixfkv3rf-b_dir"
405 )]
406 #[case::simple_dir_with_one_dir_filter_everything(
407 r#"builtins.filterSource (p: t: false) @fixtures/b_dir"#,
408 "/nix/store/xzsfzdgrxg93icaamjm8zq1jq6xvf2fz-b_dir"
409 )]
410 #[case::simple_dir_with_one_symlink_to_file_filter_dirs(
411 r#"builtins.filterSource (p: t: t != "directory") @fixtures/c_dir"#,
412 "/nix/store/riigfmmzzrq65zqiffcjk5sbqr9c9h09-c_dir"
413 )]
414 #[case::simple_dir_with_one_symlink_to_file_filter_files(
415 r#"builtins.filterSource (p: t: t != "regular") @fixtures/c_dir"#,
416 "/nix/store/riigfmmzzrq65zqiffcjk5sbqr9c9h09-c_dir"
417 )]
418 #[case::simple_dir_with_one_symlink_to_file_filter_symlinks(
419 r#"builtins.filterSource (p: t: t != "symlink") @fixtures/c_dir"#,
420 "/nix/store/y5g1fz04vzjvf422q92qmv532axj5q26-c_dir"
421 )]
422 #[case::simple_dir_with_one_symlink_to_file_filter_nothing(
423 r#"builtins.filterSource (p: t: true) @fixtures/c_dir"#,
424 "/nix/store/riigfmmzzrq65zqiffcjk5sbqr9c9h09-c_dir"
425 )]
426 #[case::simple_dir_with_one_symlink_to_file_filter_everything(
427 r#"builtins.filterSource (p: t: false) @fixtures/c_dir"#,
428 "/nix/store/y5g1fz04vzjvf422q92qmv532axj5q26-c_dir"
429 )]
430 #[case::simple_dir_with_dangling_symlink_filter_dirs(
431 r#"builtins.filterSource (p: t: t != "directory") @fixtures/d_dir"#,
432 "/nix/store/f2d1aixwiqy4lbzrd040ala2s4m2z199-d_dir"
433 )]
434 #[case::simple_dir_with_dangling_symlink_filter_files(
435 r#"builtins.filterSource (p: t: t != "regular") @fixtures/d_dir"#,
436 "/nix/store/f2d1aixwiqy4lbzrd040ala2s4m2z199-d_dir"
437 )]
438 #[case::simple_dir_with_dangling_symlink_filter_symlinks(
439 r#"builtins.filterSource (p: t: t != "symlink") @fixtures/d_dir"#,
440 "/nix/store/7l371xax8kknhpska4wrmyll1mzlhzvl-d_dir"
441 )]
442 #[case::simple_dir_with_dangling_symlink_filter_nothing(
443 r#"builtins.filterSource (p: t: true) @fixtures/d_dir"#,
444 "/nix/store/f2d1aixwiqy4lbzrd040ala2s4m2z199-d_dir"
445 )]
446 #[case::simple_dir_with_dangling_symlink_filter_everything(
447 r#"builtins.filterSource (p: t: false) @fixtures/d_dir"#,
448 "/nix/store/7l371xax8kknhpska4wrmyll1mzlhzvl-d_dir"
449 )]
450 #[case::simple_symlinked_dir_with_one_file_filter_dirs(
451 r#"builtins.filterSource (p: t: t != "directory") @fixtures/symlink_to_a_dir"#,
452 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
453 )]
454 #[case::simple_symlinked_dir_with_one_file_filter_files(
455 r#"builtins.filterSource (p: t: t != "regular") @fixtures/symlink_to_a_dir"#,
456 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
457 )]
458 #[case::simple_symlinked_dir_with_one_file_filter_symlinks(
459 r#"builtins.filterSource (p: t: t != "symlink") @fixtures/symlink_to_a_dir"#,
460 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
461 )]
462 #[case::simple_symlinked_dir_with_one_file_filter_nothing(
463 r#"builtins.filterSource (p: t: true) @fixtures/symlink_to_a_dir"#,
464 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
465 )]
466 #[case::simple_symlinked_dir_with_one_file_filter_everything(
467 r#"builtins.filterSource (p: t: false) @fixtures/symlink_to_a_dir"#,
468 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
469 )]
470 fn builtins_filter_source_succeed(#[case] code: &str, #[case] expected_outpath: &str) {
471 let temp = TempDir::new().expect("create temporary directory");
473 let p = temp.path().join("import_fixtures");
474
475 {
480 fs::create_dir(&p).expect("creating import_fixtures");
481
482 fs::create_dir(p.join("a_dir")).expect("creating /a_dir");
484 fs::write(p.join("a_dir").join("a_file"), "").expect("creating /a_dir/a_file");
485
486 fs::write(p.join("a_file"), "").expect("creating /a_file");
488
489 fs::create_dir_all(p.join("b_dir").join("a_dir")).expect("creating /b_dir/a_dir");
491
492 fs::create_dir(p.join("c_dir")).expect("creating /c_dir");
494 std::os::unix::fs::symlink(
495 "../a_dir/a_file",
496 p.join("c_dir").join("symlink_to_a_file"),
497 )
498 .expect("creating /c_dir/symlink_to_a_file");
499
500 fs::create_dir(p.join("d_dir")).expect("creating /d_dir");
503 std::os::unix::fs::symlink("a_dir/a_file", p.join("d_dir").join("dangling_symlink"))
504 .expect("creating /d_dir/dangling_symlink");
505
506 std::os::unix::fs::symlink("a_dir", p.join("symlink_to_a_dir"))
508 .expect("creating /symlink_to_a_dir");
509 }
510
511 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
513
514 let eval_result = eval(&code_replaced);
515
516 let value = eval_result.value.expect("must succeed");
517
518 match value {
519 tvix_eval::Value::String(s) => {
520 assert_eq!(expected_outpath, s.as_bstr());
521 }
522 _ => panic!("unexpected value type: {value:?}"),
523 }
524
525 assert!(eval_result.errors.is_empty(), "errors should be empty");
526 }
527
528 #[rstest]
530 #[case::rename_success(
531 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = true; })"#,
532 true
533 )]
534 #[case::rename_with_spaces_fail(
535 r#"(builtins.path { name = "invalid name"; path = @fixtures + "/te st"; recursive = true; })"#,
536 false
537 )]
538 fn builtins_path_recursive_rename(#[case] code: &str, #[case] success: bool) {
539 let temp = TempDir::new().expect("create temporary directory");
541 let p = temp.path().join("import_fixtures");
542
543 {
548 fs::create_dir(&p).expect("creating import_fixtures");
549 fs::write(p.join("te st"), "").expect("creating `/te st`");
550 }
551 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
553
554 let eval_result = eval(&code_replaced);
555
556 let value = eval_result.value;
557
558 if success {
559 match value.expect("expected successful evaluation on legal rename") {
560 tvix_eval::Value::String(s) => {
561 assert_eq!(
562 "/nix/store/nd5z11x7zjqqz44rkbhc6v7yifdkn659-valid-name",
563 s.as_bstr()
564 );
565 }
566 v => panic!("unexpected value type: {v:?}"),
567 }
568 } else {
569 assert!(value.is_none(), "unexpected success on illegal store paths");
570 }
571 }
572
573 #[rstest]
575 #[case::rename_success(
576 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = false; })"#,
577 true
578 )]
579 #[case::rename_with_spaces_fail(
580 r#"(builtins.path { name = "invalid name"; path = @fixtures + "/te st"; recursive = false; })"#,
581 false
582 )]
583 fn builtins_path_nonrecursive_rename(#[case] code: &str, #[case] success: bool) {
585 let temp = TempDir::new().expect("create temporary directory");
587 let p = temp.path().join("import_fixtures");
588
589 {
594 fs::create_dir(&p).expect("creating import_fixtures");
595 fs::write(p.join("te st"), "").expect("creating `/te st`");
596 }
597 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
599
600 let eval_result = eval(&code_replaced);
601
602 let value = eval_result.value;
603
604 if success {
605 match value.expect("expected successful evaluation on legal rename") {
606 tvix_eval::Value::String(s) => {
607 assert_eq!(
608 "/nix/store/il2rmfbqgs37rshr8w7x64hd4d3b4bsa-valid-name",
609 s.as_bstr()
610 );
611 }
612 v => panic!("unexpected value type: {v:?}"),
613 }
614 } else {
615 assert!(value.is_none(), "unexpected success on illegal store paths");
616 }
617 }
618
619 #[rstest]
620 #[case::flat_success(
621 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = false; sha256 = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="; })"#,
622 true
623 )]
624 #[case::flat_fail(
625 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = false; sha256 = "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY="; })"#,
626 false
627 )]
628 #[case::recursive_success(
629 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = true; sha256 = "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY="; })"#,
630 true
631 )]
632 #[case::recursive_fail(
633 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = true; sha256 = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="; })"#,
634 false
635 )]
636 fn builtins_path_fod_locking(#[case] code: &str, #[case] exp_success: bool) {
637 let temp = TempDir::new().expect("create temporary directory");
639 let p = temp.path().join("import_fixtures");
640
641 {
646 fs::create_dir(&p).expect("creating import_fixtures");
647 fs::write(p.join("te st"), "").expect("creating `/te st`");
648 }
649 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
651
652 let eval_result = eval(&code_replaced);
653
654 let value = eval_result.value;
655
656 if exp_success {
657 assert!(
658 value.is_some(),
659 "expected successful evaluation on legal rename and valid FOD sha256"
660 );
661 } else {
662 assert!(value.is_none(), "unexpected success on invalid FOD sha256");
663 }
664 }
665
666 #[rstest]
667 #[case(
668 r#"(builtins.path { name = "valid-path"; path = @fixtures + "/te st dir"; filter = _: _: true; })"#,
669 "/nix/store/i28jmi4fwym4fw3flkrkp2mdxx50pdy0-valid-path"
670 )]
671 #[case(
672 r#"(builtins.path { name = "valid-path"; path = @fixtures + "/te st dir"; filter = _: _: false; })"#,
673 "/nix/store/pwza2ij9gk1fmzhbjnynmfv2mq2sgcap-valid-path"
674 )]
675 fn builtins_path_filter(#[case] code: &str, #[case] expected_outpath: &str) {
676 let temp = TempDir::new().expect("create temporary directory");
678 let p = temp.path().join("import_fixtures");
679
680 {
685 fs::create_dir(&p).expect("creating import_fixtures");
686 fs::create_dir(p.join("te st dir")).expect("creating `/te st dir`");
687 fs::write(p.join("te st dir").join("test"), "").expect("creating `/te st dir/test`");
688 }
689 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
691
692 let eval_result = eval(&code_replaced);
693
694 if !eval_result.errors.is_empty() {
695 panic!("evaluation failed: {:?}", eval_result.errors);
696 }
697
698 let value = eval_result.value.unwrap();
699
700 match value {
701 tvix_eval::Value::String(s) => {
702 assert_eq!(expected_outpath, s.as_bstr());
703 }
704 _ => panic!("unexpected value type: {value:?}"),
705 }
706 }
707
708 #[rstest]
711 #[cfg(target_family = "unix")]
712 #[case::fail_kept_unknowns(
718 r#"(builtins.filterSource (p: t: t == "unknown") @fixtures)"#,
719 false
720 )]
721 #[case::succeed_filter_unknowns(
723 r#"(builtins.filterSource (p: t: t != "unknown") @fixtures)"#,
724 true
725 )]
726 #[case::fail_kept_charnode(
727 r#"(builtins.filterSource (p: t: (builtins.baseNameOf p) != "a_charnode") @fixtures)"#,
728 false
729 )]
730 #[case::fail_kept_socket(
731 r#"(builtins.filterSource (p: t: (builtins.baseNameOf p) != "a_socket") @fixtures)"#,
732 false
733 )]
734 #[case::fail_kept_fifo(
735 r#"(builtins.filterSource (p: t: (builtins.baseNameOf p) != "a_fifo") @fixtures)"#,
736 false
737 )]
738 fn builtins_filter_source_unsupported_files(#[case] code: &str, #[case] exp_success: bool) {
739 use nix::errno::Errno;
740 use nix::sys::stat;
741 use nix::unistd;
742 use std::os::unix::net::UnixListener;
743 use tempfile::TempDir;
744
745 let temp = TempDir::with_prefix("foo").expect("Failed to create a temporary directory");
753
754 unistd::mkfifo(&temp.path().join("a_fifo"), stat::Mode::S_IRWXU)
756 .expect("Failed to create the FIFO");
757
758 UnixListener::bind(temp.path().join("a_socket")).expect("Failed to create the socket");
759
760 stat::mknod(
761 &temp.path().join("a_charnode"),
762 stat::SFlag::S_IFCHR,
763 stat::Mode::S_IRWXU,
764 0,
765 )
766 .inspect_err(|e| {
767 if *e == Errno::EPERM {
768 eprintln!(
769 "\
770Missing permissions to create a character device node with mknod(2).
771Please run this test as root or set CAP_MKNOD."
772 );
773 }
774 })
775 .expect("Failed to create a character device node");
776
777 let code_replaced = code.replace("@fixtures", &temp.path().to_string_lossy());
778 let eval_result = eval(&code_replaced);
779
780 if exp_success {
781 assert!(
782 eval_result.value.is_some(),
783 "unexpected failure on a directory of unsupported file types but all filtered: {:?}",
784 eval_result.errors
785 );
786 } else {
787 assert!(
788 eval_result.value.is_none(),
789 "unexpected success on unsupported file type ingestion: {:?}",
790 eval_result.value
791 );
792 }
793 }
794}