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