1use std::rc::Rc;
4
5use crate::snix_store_io::SnixStoreIO;
6
7mod derivation;
8mod errors;
9mod fetchers;
10mod import;
11mod utils;
12
13pub use errors::{DerivationError, FetcherError, ImportError};
14
15pub fn add_derivation_builtins<'co, 'ro, 'env, IO>(
24 eval_builder: snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>,
25 io: Rc<SnixStoreIO>,
26) -> snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> {
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_fetcher_builtins<'co, 'ro, 'env, IO>(
39 eval_builder: snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>,
40 io: Rc<SnixStoreIO>,
41) -> snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> {
42 eval_builder.add_builtins(fetchers::fetcher_builtins::builtins(Rc::clone(&io)))
43}
44
45pub fn add_import_builtins<'co, 'ro, 'env, IO>(
54 eval_builder: snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>,
55 io: Rc<SnixStoreIO>,
56) -> snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> {
57 eval_builder.add_builtins(import::import_builtins(io))
58}
59
60#[cfg(test)]
61mod tests {
62 use std::{fs, rc::Rc, sync::Arc};
63
64 use crate::snix_store_io::SnixStoreIO;
65
66 use super::{add_derivation_builtins, add_fetcher_builtins, add_import_builtins};
67 use clap::Parser;
68 use nix_compat::store_path::hash_placeholder;
69 use rstest::rstest;
70 use snix_build::buildservice::DummyBuildService;
71 use snix_eval::{EvalIO, EvaluationResult};
72 use snix_store::utils::{ServiceUrlsMemory, construct_services};
73 use tempfile::TempDir;
74
75 fn eval(str: &str) -> EvaluationResult {
79 let runtime = tokio::runtime::Runtime::new().expect("Failed to build a Tokio runtime");
81 let (blob_service, directory_service, path_info_service, nar_calculation_service) = runtime
82 .block_on(async {
83 construct_services(ServiceUrlsMemory::parse_from(std::iter::empty::<&str>())).await
84 })
85 .expect("Failed to construct store services in memory");
86
87 let io = Rc::new(SnixStoreIO::new(
88 blob_service,
89 directory_service,
90 path_info_service,
91 nar_calculation_service.into(),
92 Arc::<DummyBuildService>::default(),
93 runtime.handle().clone(),
94 ));
95
96 let mut eval_builder = snix_eval::Evaluation::builder(io.clone() as Rc<dyn EvalIO>);
97 eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&io));
98 eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&io));
99 eval_builder = add_import_builtins(eval_builder, io);
100 let eval = eval_builder.build();
101
102 eval.evaluate(str, None)
104 }
105
106 #[test]
107 fn derivation() {
108 let result = eval(
109 r#"(derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux";}).outPath"#,
110 );
111
112 assert!(result.errors.is_empty(), "expect evaluation to succeed");
113 let value = result.value.expect("must be some");
114
115 match value {
116 snix_eval::Value::String(s) => {
117 assert_eq!(*s, "/nix/store/xpcvxsx5sw4rbq666blz6sxqlmsqphmr-foo",);
118 }
119 _ => panic!("unexpected value type: {:?}", value),
120 }
121 }
122
123 #[test]
125 fn derivation_empty_name_fail() {
126 let result = eval(
127 r#"(derivation { name = ""; builder = "/bin/sh"; system = "x86_64-linux";}).outPath"#,
128 );
129
130 assert!(!result.errors.is_empty(), "expect evaluation to fail");
131 }
132
133 #[rstest]
136 #[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")]
137 #[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")]
138 #[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")]
139 #[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")]
140 #[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")]
141 #[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")]
142 #[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")]
143 #[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")]
144 #[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")]
145 #[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")]
146 #[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")]
147 #[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")]
148 #[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")]
149 #[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")]
150 #[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")]
151 #[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")]
152 #[case::outputhash_omitted(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; }).outPath"#, "/nix/store/xpcvxsx5sw4rbq666blz6sxqlmsqphmr-foo")]
153 #[case::multiple_outputs(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; outputs = ["foo" "bar"]; system = "x86_64-linux"; }).outPath"#, "/nix/store/hkwdinvz2jpzgnjy9lv34d2zxvclj4s3-foo-foo")]
154 #[case::args(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; args = ["--foo" "42" "--bar"]; system = "x86_64-linux"; }).outPath"#, "/nix/store/365gi78n2z7vwc1bvgb98k0a9cqfp6as-foo")]
155 #[case::full(r#"
156 let
157 bar = builtins.derivation {
158 name = "bar";
159 builder = ":";
160 system = ":";
161 outputHash = "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba";
162 outputHashAlgo = "sha256";
163 outputHashMode = "recursive";
164 };
165 in
166 (builtins.derivation {
167 name = "foo";
168 builder = ":";
169 system = ":";
170 inherit bar;
171 }).outPath
172 "#, "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo")]
173 #[case::pass_as_file(r#"(builtins.derivation { "name" = "foo"; passAsFile = ["bar"]; bar = "baz"; system = ":"; builder = ":";}).outPath"#, "/nix/store/25gf0r1ikgmh4vchrn8qlc4fnqlsa5a1-foo")]
174 #[case::ignore_nulls_true_no_arg_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; }).drvPath"#, "/nix/store/xa96w6d7fxrlkk60z1fmx2ffp2wzmbqx-foo.drv")]
176 #[case::ignore_nulls_true_no_arg_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; }).outPath"#, "/nix/store/pk2agn9za8r9bxsflgh1y7fyyrmwcqkn-foo")]
177 #[case::ignore_nulls_true_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; ignoreme = null; }).drvPath"#, "/nix/store/xa96w6d7fxrlkk60z1fmx2ffp2wzmbqx-foo.drv")]
179 #[case::ignore_nulls_true_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = true; ignoreme = null; }).outPath"#, "/nix/store/pk2agn9za8r9bxsflgh1y7fyyrmwcqkn-foo")]
180 #[case::ignore_nulls_false_no_arg_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; }).drvPath"#, "/nix/store/xa96w6d7fxrlkk60z1fmx2ffp2wzmbqx-foo.drv")]
182 #[case::ignore_nulls_false_no_arg_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; }).outPath"#, "/nix/store/pk2agn9za8r9bxsflgh1y7fyyrmwcqkn-foo")]
183 #[case::ignore_nulls_fales_arg_path_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; foo = null; }).drvPath"#, "/nix/store/xwkwbajfiyhdqmksrbzm0s4g4ib8d4ms-foo.drv")]
185 #[case::ignore_nulls_fales_arg_path_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __ignoreNulls = false; foo = null; }).outPath"#, "/nix/store/2n2jqm6l7r2ahi19m58pl896ipx9cyx6-foo")]
186 #[case::structured_attrs_false_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = false; foo = "bar"; }).drvPath"#, "/nix/store/qs39krwr2lsw6ac910vqx4pnk6m63333-foo.drv")]
188 #[case::structured_attrs_false_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = false; foo = "bar"; }).outPath"#, "/nix/store/9yy3764rdip3fbm8ckaw4j9y7vh4d231-foo")]
189 #[case::structured_attrs_simple_drvpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; }).drvPath"#, "/nix/store/k6rlb4k10cb9iay283037ml1nv3xma2f-foo.drv")]
191 #[case::structured_attrs_simple_outpath(r#"(builtins.derivation { name = "foo"; system = ":"; builder = ":"; __structuredAttrs = true; foo = "bar"; }).outPath"#, "/nix/store/6lmv3hyha1g4cb426iwjyifd7nrdv1xn-foo")]
192 #[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")]
194 #[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")]
195 #[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")]
197 #[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")]
199 fn test_outpath(#[case] code: &str, #[case] expected_path: &str) {
200 let value = eval(code).value.expect("must succeed");
201
202 match value {
203 snix_eval::Value::String(s) => {
204 assert_eq!(*s, expected_path);
205 }
206 _ => panic!("unexpected value type: {:?}", value),
207 }
208 }
209
210 #[rstest]
212 #[case::invalid_outputhash(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-00"; }).outPath"#)]
213 #[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"#)]
214 #[case::duplicate_output_names(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; outputs = ["foo" "foo"]; system = "x86_64-linux"; }).outPath"#)]
215 fn test_outpath_invalid(#[case] code: &str) {
216 let resp = eval(code);
217 assert!(resp.value.is_none(), "Value should be None");
218 assert!(
219 !resp.errors.is_empty(),
220 "There should have been some errors"
221 );
222 }
223
224 #[test]
227 fn test_fod_outpath() {
228 let code = r#"
229 (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath ==
230 (builtins.derivation { name = "foo"; builder = "/bin/aa"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath
231 "#;
232
233 let value = eval(code).value.expect("must succeed");
234 match value {
235 snix_eval::Value::Bool(v) => {
236 assert!(v);
237 }
238 _ => panic!("unexpected value type: {:?}", value),
239 }
240 }
241
242 #[test]
245 fn test_fod_outpath_different_name() {
246 let code = r#"
247 (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath ==
248 (builtins.derivation { name = "foo"; builder = "/bin/aa"; system = "x86_64-linux"; outputHash = "sha256-Q3QXOoy+iN4VK2CflvRulYvPZXYgF0dO7FoF7CvWFTA="; }).outPath
249 "#;
250
251 let value = eval(code).value.expect("must succeed");
252 match value {
253 snix_eval::Value::Bool(v) => {
254 assert!(v);
255 }
256 _ => panic!("unexpected value type: {:?}", value),
257 }
258 }
259
260 #[test]
264 fn test_unsafe_discard_string_context() {
265 let code = r#"
266 let
267 dep = builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; };
268 in
269 (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; env = "${dep}"; }).outPath !=
270 (builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; env = "${builtins.unsafeDiscardStringContext dep}"; }).outPath
271 "#;
272
273 let value = eval(code).value.expect("must succeed");
274 match value {
275 snix_eval::Value::Bool(v) => {
276 assert!(v);
277 }
278 _ => panic!("unexpected value type: {:?}", value),
279 }
280 }
281
282 #[test]
285 fn test_unsafe_discard_string_context_of_coercible() {
286 let code = r#"
287 let
288 dep = builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; };
289 attr = { __toString = _: dep; };
290 in
291 builtins.typeOf (builtins.unsafeDiscardStringContext attr) == "string"
292 "#;
293
294 let value = eval(code).value.expect("must succeed");
295 match value {
296 snix_eval::Value::Bool(v) => {
297 assert!(v);
298 }
299 _ => panic!("unexpected value type: {:?}", value),
300 }
301 }
302
303 #[rstest]
304 #[case::input_in_args(r#"
305 let
306 bar = builtins.derivation {
307 name = "bar";
308 builder = ":";
309 system = ":";
310 outputHash = "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba";
311 outputHashAlgo = "sha256";
312 outputHashMode = "recursive";
313 };
314 in
315 (builtins.derivation {
316 name = "foo";
317 builder = ":";
318 args = [ "${bar}" ];
319 system = ":";
320 }).drvPath
321 "#, "/nix/store/50yl2gmmljyl0lzyrp1mcyhn53vhjhkd-foo.drv")]
322 fn test_inputs_derivation_from_context(#[case] code: &str, #[case] expected_drvpath: &str) {
323 let eval_result = eval(code);
324
325 let value = eval_result.value.expect("must succeed");
326
327 match value {
328 snix_eval::Value::String(s) => {
329 assert_eq!(*s, expected_drvpath);
330 }
331
332 _ => panic!("unexpected value type: {:?}", value),
333 };
334 }
335
336 #[test]
337 fn builtins_placeholder_hashes() {
338 assert_eq!(
339 hash_placeholder("out").as_str(),
340 "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"
341 );
342
343 assert_eq!(
344 hash_placeholder("").as_str(),
345 "/171rf4jhx57xqz3p7swniwkig249cif71pa08p80mgaf0mqz5bmr"
346 );
347 }
348
349 #[rstest]
351 #[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")]
352 fn builtins_derivation_hash_wrong_padding_warn(
353 #[case] code: &str,
354 #[case] expected_path: &str,
355 ) {
356 let eval_result = eval(code);
357
358 let value = eval_result.value.expect("must succeed");
359
360 match value {
361 snix_eval::Value::String(s) => {
362 assert_eq!(*s, expected_path);
363 }
364 _ => panic!("unexpected value type: {:?}", value),
365 }
366
367 assert!(
368 !eval_result.warnings.is_empty(),
369 "warnings should not be empty"
370 );
371 }
372
373 #[rstest]
377 #[cfg(target_family = "unix")]
378 #[case::complicated_filter_nothing(
379 r#"(builtins.filterSource (p: t: true) @fixtures)"#,
380 "/nix/store/bqh6kd0x3vps2rzagzpl7qmbbgnx19cp-import_fixtures"
381 )]
382 #[case::complicated_filter_everything(
383 r#"(builtins.filterSource (p: t: false) @fixtures)"#,
384 "/nix/store/giq6czz24lpjg97xxcxk6rg950lcpib1-import_fixtures"
385 )]
386 #[case::simple_dir_with_one_file_filter_dirs(
387 r#"(builtins.filterSource (p: t: t != "directory") @fixtures/a_dir)"#,
388 "/nix/store/8vbqaxapywkvv1hacdja3pi075r14d43-a_dir"
389 )]
390 #[case::simple_dir_with_one_file_filter_files(
391 r#"(builtins.filterSource (p: t: t != "regular") @fixtures/a_dir)"#,
392 "/nix/store/zphlqc93s2iq4xm393l06hzf8hp85r4z-a_dir"
393 )]
394 #[case::simple_dir_with_one_file_filter_symlinks(
395 r#"(builtins.filterSource (p: t: t != "symlink") @fixtures/a_dir)"#,
396 "/nix/store/8vbqaxapywkvv1hacdja3pi075r14d43-a_dir"
397 )]
398 #[case::simple_dir_with_one_file_filter_nothing(
399 r#"(builtins.filterSource (p: t: true) @fixtures/a_dir)"#,
400 "/nix/store/8vbqaxapywkvv1hacdja3pi075r14d43-a_dir"
401 )]
402 #[case::simple_dir_with_one_file_filter_everything(
403 r#"(builtins.filterSource (p: t: false) @fixtures/a_dir)"#,
404 "/nix/store/zphlqc93s2iq4xm393l06hzf8hp85r4z-a_dir"
405 )]
406 #[case::simple_dir_with_one_dir_filter_dirs(
407 r#"builtins.filterSource (p: t: t != "directory") @fixtures/b_dir"#,
408 "/nix/store/xzsfzdgrxg93icaamjm8zq1jq6xvf2fz-b_dir"
409 )]
410 #[case::simple_dir_with_one_dir_filter_files(
411 r#"builtins.filterSource (p: t: t != "regular") @fixtures/b_dir"#,
412 "/nix/store/8rjx64mm7173xp60rahv7cl3ixfkv3rf-b_dir"
413 )]
414 #[case::simple_dir_with_one_dir_filter_symlinks(
415 r#"builtins.filterSource (p: t: t != "symlink") @fixtures/b_dir"#,
416 "/nix/store/8rjx64mm7173xp60rahv7cl3ixfkv3rf-b_dir"
417 )]
418 #[case::simple_dir_with_one_dir_filter_nothing(
419 r#"builtins.filterSource (p: t: true) @fixtures/b_dir"#,
420 "/nix/store/8rjx64mm7173xp60rahv7cl3ixfkv3rf-b_dir"
421 )]
422 #[case::simple_dir_with_one_dir_filter_everything(
423 r#"builtins.filterSource (p: t: false) @fixtures/b_dir"#,
424 "/nix/store/xzsfzdgrxg93icaamjm8zq1jq6xvf2fz-b_dir"
425 )]
426 #[case::simple_dir_with_one_symlink_to_file_filter_dirs(
427 r#"builtins.filterSource (p: t: t != "directory") @fixtures/c_dir"#,
428 "/nix/store/riigfmmzzrq65zqiffcjk5sbqr9c9h09-c_dir"
429 )]
430 #[case::simple_dir_with_one_symlink_to_file_filter_files(
431 r#"builtins.filterSource (p: t: t != "regular") @fixtures/c_dir"#,
432 "/nix/store/riigfmmzzrq65zqiffcjk5sbqr9c9h09-c_dir"
433 )]
434 #[case::simple_dir_with_one_symlink_to_file_filter_symlinks(
435 r#"builtins.filterSource (p: t: t != "symlink") @fixtures/c_dir"#,
436 "/nix/store/y5g1fz04vzjvf422q92qmv532axj5q26-c_dir"
437 )]
438 #[case::simple_dir_with_one_symlink_to_file_filter_nothing(
439 r#"builtins.filterSource (p: t: true) @fixtures/c_dir"#,
440 "/nix/store/riigfmmzzrq65zqiffcjk5sbqr9c9h09-c_dir"
441 )]
442 #[case::simple_dir_with_one_symlink_to_file_filter_everything(
443 r#"builtins.filterSource (p: t: false) @fixtures/c_dir"#,
444 "/nix/store/y5g1fz04vzjvf422q92qmv532axj5q26-c_dir"
445 )]
446 #[case::simple_dir_with_dangling_symlink_filter_dirs(
447 r#"builtins.filterSource (p: t: t != "directory") @fixtures/d_dir"#,
448 "/nix/store/f2d1aixwiqy4lbzrd040ala2s4m2z199-d_dir"
449 )]
450 #[case::simple_dir_with_dangling_symlink_filter_files(
451 r#"builtins.filterSource (p: t: t != "regular") @fixtures/d_dir"#,
452 "/nix/store/f2d1aixwiqy4lbzrd040ala2s4m2z199-d_dir"
453 )]
454 #[case::simple_dir_with_dangling_symlink_filter_symlinks(
455 r#"builtins.filterSource (p: t: t != "symlink") @fixtures/d_dir"#,
456 "/nix/store/7l371xax8kknhpska4wrmyll1mzlhzvl-d_dir"
457 )]
458 #[case::simple_dir_with_dangling_symlink_filter_nothing(
459 r#"builtins.filterSource (p: t: true) @fixtures/d_dir"#,
460 "/nix/store/f2d1aixwiqy4lbzrd040ala2s4m2z199-d_dir"
461 )]
462 #[case::simple_dir_with_dangling_symlink_filter_everything(
463 r#"builtins.filterSource (p: t: false) @fixtures/d_dir"#,
464 "/nix/store/7l371xax8kknhpska4wrmyll1mzlhzvl-d_dir"
465 )]
466 #[case::simple_symlinked_dir_with_one_file_filter_dirs(
467 r#"builtins.filterSource (p: t: t != "directory") @fixtures/symlink_to_a_dir"#,
468 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
469 )]
470 #[case::simple_symlinked_dir_with_one_file_filter_files(
471 r#"builtins.filterSource (p: t: t != "regular") @fixtures/symlink_to_a_dir"#,
472 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
473 )]
474 #[case::simple_symlinked_dir_with_one_file_filter_symlinks(
475 r#"builtins.filterSource (p: t: t != "symlink") @fixtures/symlink_to_a_dir"#,
476 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
477 )]
478 #[case::simple_symlinked_dir_with_one_file_filter_nothing(
479 r#"builtins.filterSource (p: t: true) @fixtures/symlink_to_a_dir"#,
480 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
481 )]
482 #[case::simple_symlinked_dir_with_one_file_filter_everything(
483 r#"builtins.filterSource (p: t: false) @fixtures/symlink_to_a_dir"#,
484 "/nix/store/apmdprm8fwl2zrjpbyfcd99zrnhvf47q-symlink_to_a_dir"
485 )]
486 fn builtins_filter_source_succeed(#[case] code: &str, #[case] expected_outpath: &str) {
487 let temp = TempDir::new().expect("create temporary directory");
489 let p = temp.path().join("import_fixtures");
490
491 {
496 fs::create_dir(&p).expect("creating import_fixtures");
497
498 fs::create_dir(p.join("a_dir")).expect("creating /a_dir");
500 fs::write(p.join("a_dir").join("a_file"), "").expect("creating /a_dir/a_file");
501
502 fs::write(p.join("a_file"), "").expect("creating /a_file");
504
505 fs::create_dir_all(p.join("b_dir").join("a_dir")).expect("creating /b_dir/a_dir");
507
508 fs::create_dir(p.join("c_dir")).expect("creating /c_dir");
510 std::os::unix::fs::symlink(
511 "../a_dir/a_file",
512 p.join("c_dir").join("symlink_to_a_file"),
513 )
514 .expect("creating /c_dir/symlink_to_a_file");
515
516 fs::create_dir(p.join("d_dir")).expect("creating /d_dir");
519 std::os::unix::fs::symlink("a_dir/a_file", p.join("d_dir").join("dangling_symlink"))
520 .expect("creating /d_dir/dangling_symlink");
521
522 std::os::unix::fs::symlink("a_dir", p.join("symlink_to_a_dir"))
524 .expect("creating /symlink_to_a_dir");
525 }
526
527 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
529
530 let eval_result = eval(&code_replaced);
531
532 let value = eval_result.value.expect("must succeed");
533
534 match value {
535 snix_eval::Value::String(s) => {
536 assert_eq!(expected_outpath, s.as_bstr());
537 }
538 _ => panic!("unexpected value type: {:?}", value),
539 }
540
541 assert!(eval_result.errors.is_empty(), "errors should be empty");
542 }
543
544 #[rstest]
546 #[case::rename_success(
547 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = true; })"#,
548 true
549 )]
550 #[case::rename_with_spaces_fail(
551 r#"(builtins.path { name = "invalid name"; path = @fixtures + "/te st"; recursive = true; })"#,
552 false
553 )]
554 fn builtins_path_recursive_rename(#[case] code: &str, #[case] success: bool) {
555 let temp = TempDir::new().expect("create temporary directory");
557 let p = temp.path().join("import_fixtures");
558
559 {
564 fs::create_dir(&p).expect("creating import_fixtures");
565 fs::write(p.join("te st"), "").expect("creating `/te st`");
566 }
567 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
569
570 let eval_result = eval(&code_replaced);
571
572 let value = eval_result.value;
573
574 if success {
575 match value.expect("expected successful evaluation on legal rename") {
576 snix_eval::Value::String(s) => {
577 assert_eq!(
578 "/nix/store/nd5z11x7zjqqz44rkbhc6v7yifdkn659-valid-name",
579 s.as_bstr()
580 );
581 }
582 v => panic!("unexpected value type: {:?}", v),
583 }
584 } else {
585 assert!(value.is_none(), "unexpected success on illegal store paths");
586 }
587 }
588
589 #[rstest]
591 #[case::rename_success(
592 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = false; })"#,
593 true
594 )]
595 #[case::rename_with_spaces_fail(
596 r#"(builtins.path { name = "invalid name"; path = @fixtures + "/te st"; recursive = false; })"#,
597 false
598 )]
599 fn builtins_path_nonrecursive_rename(#[case] code: &str, #[case] success: bool) {
601 let temp = TempDir::new().expect("create temporary directory");
603 let p = temp.path().join("import_fixtures");
604
605 {
610 fs::create_dir(&p).expect("creating import_fixtures");
611 fs::write(p.join("te st"), "").expect("creating `/te st`");
612 }
613 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
615
616 let eval_result = eval(&code_replaced);
617
618 let value = eval_result.value;
619
620 if success {
621 match value.expect("expected successful evaluation on legal rename") {
622 snix_eval::Value::String(s) => {
623 assert_eq!(
624 "/nix/store/il2rmfbqgs37rshr8w7x64hd4d3b4bsa-valid-name",
625 s.as_bstr()
626 );
627 }
628 v => panic!("unexpected value type: {:?}", v),
629 }
630 } else {
631 assert!(value.is_none(), "unexpected success on illegal store paths");
632 }
633 }
634
635 #[rstest]
636 #[case::flat_success(
637 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = false; sha256 = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="; })"#,
638 true
639 )]
640 #[case::flat_fail(
641 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = false; sha256 = "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY="; })"#,
642 false
643 )]
644 #[case::recursive_success(
645 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = true; sha256 = "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY="; })"#,
646 true
647 )]
648 #[case::recursive_fail(
649 r#"(builtins.path { name = "valid-name"; path = @fixtures + "/te st"; recursive = true; sha256 = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="; })"#,
650 false
651 )]
652 fn builtins_path_fod_locking(#[case] code: &str, #[case] exp_success: bool) {
653 let temp = TempDir::new().expect("create temporary directory");
655 let p = temp.path().join("import_fixtures");
656
657 {
662 fs::create_dir(&p).expect("creating import_fixtures");
663 fs::write(p.join("te st"), "").expect("creating `/te st`");
664 }
665 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
667
668 let eval_result = eval(&code_replaced);
669
670 let value = eval_result.value;
671
672 if exp_success {
673 assert!(
674 value.is_some(),
675 "expected successful evaluation on legal rename and valid FOD sha256"
676 );
677 } else {
678 assert!(value.is_none(), "unexpected success on invalid FOD sha256");
679 }
680 }
681
682 #[rstest]
683 #[case(
684 r#"(builtins.path { name = "valid-path"; path = @fixtures + "/te st dir"; filter = _: _: true; })"#,
685 "/nix/store/i28jmi4fwym4fw3flkrkp2mdxx50pdy0-valid-path"
686 )]
687 #[case(
688 r#"(builtins.path { name = "valid-path"; path = @fixtures + "/te st dir"; filter = _: _: false; })"#,
689 "/nix/store/pwza2ij9gk1fmzhbjnynmfv2mq2sgcap-valid-path"
690 )]
691 fn builtins_path_filter(#[case] code: &str, #[case] expected_outpath: &str) {
692 let temp = TempDir::new().expect("create temporary directory");
694 let p = temp.path().join("import_fixtures");
695
696 {
701 fs::create_dir(&p).expect("creating import_fixtures");
702 fs::create_dir(p.join("te st dir")).expect("creating `/te st dir`");
703 fs::write(p.join("te st dir").join("test"), "").expect("creating `/te st dir/test`");
704 }
705 let code_replaced = code.replace("@fixtures", &p.to_string_lossy());
707
708 let eval_result = eval(&code_replaced);
709
710 let value = eval_result.value.expect("must succeed");
711
712 match value {
713 snix_eval::Value::String(s) => {
714 assert_eq!(expected_outpath, s.as_bstr());
715 }
716 _ => panic!("unexpected value type: {:?}", value),
717 }
718
719 assert!(eval_result.errors.is_empty(), "errors should be empty");
720 }
721
722 #[rstest]
725 #[cfg(target_family = "unix")]
726 #[case::fail_kept_unknowns(
732 r#"(builtins.filterSource (p: t: t == "unknown") @fixtures)"#,
733 false
734 )]
735 #[case::succeed_filter_unknowns(
737 r#"(builtins.filterSource (p: t: t != "unknown") @fixtures)"#,
738 true
739 )]
740 #[case::fail_kept_charnode(
741 r#"(builtins.filterSource (p: t: (builtins.baseNameOf p) != "a_charnode") @fixtures)"#,
742 false
743 )]
744 #[case::fail_kept_socket(
745 r#"(builtins.filterSource (p: t: (builtins.baseNameOf p) != "a_socket") @fixtures)"#,
746 false
747 )]
748 #[case::fail_kept_fifo(
749 r#"(builtins.filterSource (p: t: (builtins.baseNameOf p) != "a_fifo") @fixtures)"#,
750 false
751 )]
752 fn builtins_filter_source_unsupported_files(#[case] code: &str, #[case] exp_success: bool) {
753 use nix::errno::Errno;
754 use nix::sys::stat;
755 use nix::unistd;
756 use std::os::unix::net::UnixListener;
757 use tempfile::TempDir;
758
759 let temp = TempDir::with_prefix("foo").expect("Failed to create a temporary directory");
767
768 unistd::mkfifo(&temp.path().join("a_fifo"), stat::Mode::S_IRWXU)
770 .expect("Failed to create the FIFO");
771
772 UnixListener::bind(temp.path().join("a_socket")).expect("Failed to create the socket");
773
774 stat::mknod(
775 &temp.path().join("a_charnode"),
776 stat::SFlag::S_IFCHR,
777 stat::Mode::S_IRWXU,
778 0,
779 )
780 .inspect_err(|e| {
781 if *e == Errno::EPERM {
782 eprintln!(
783 "\
784Missing permissions to create a character device node with mknod(2).
785Please run this test as root or set CAP_MKNOD."
786 );
787 }
788 })
789 .expect("Failed to create a character device node");
790
791 let code_replaced = code.replace("@fixtures", &temp.path().to_string_lossy());
792 let eval_result = eval(&code_replaced);
793
794 if exp_success {
795 assert!(
796 eval_result.value.is_some(),
797 "unexpected failure on a directory of unsupported file types but all filtered: {:?}",
798 eval_result.errors
799 );
800 } else {
801 assert!(
802 eval_result.value.is_none(),
803 "unexpected success on unsupported file type ingestion: {:?}",
804 eval_result.value
805 );
806 }
807 }
808}