tvix_glue/
tvix_store_io.rs1use std::{
3 cell::RefCell,
4 io,
5 path::{Path, PathBuf},
6};
7use tvix_eval::{EvalIO, FileType};
8use tvix_simstore::SimulatedStoreIO;
9
10use crate::known_paths::KnownPaths;
12
13pub struct TvixStoreIO {
29 pub(crate) simulated_store: SimulatedStoreIO,
31
32 pub known_paths: RefCell<KnownPaths>,
34}
35
36impl TvixStoreIO {
37 pub fn new(simulated_store: SimulatedStoreIO) -> Self {
38 Self {
39 simulated_store,
40 known_paths: Default::default(),
41 }
42 }
43}
44
45impl EvalIO for TvixStoreIO {
46 fn path_exists(&self, path: &Path) -> io::Result<bool> {
47 self.simulated_store.path_exists(path)
48 }
49
50 fn open(&self, path: &Path) -> io::Result<Box<dyn io::Read>> {
51 self.simulated_store.open(path)
52 }
53
54 fn file_type(&self, path: &Path) -> io::Result<FileType> {
55 self.simulated_store.file_type(path)
56 }
57
58 fn read_dir(&self, path: &Path) -> io::Result<Vec<(bytes::Bytes, FileType)>> {
59 self.simulated_store.read_dir(path)
60 }
61
62 fn import_path(&self, path: &Path) -> io::Result<PathBuf> {
63 self.simulated_store.import_path(path)
64 }
65
66 fn store_dir(&self) -> Option<String> {
67 self.simulated_store.store_dir()
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use std::{path::Path, rc::Rc};
74
75 use bstr::ByteSlice;
76 use tempfile::TempDir;
77 use tvix_eval::{EvalIO, EvaluationResult};
78
79 use super::TvixStoreIO;
80 use crate::builtins::{add_derivation_builtins, add_import_builtins};
81
82 fn eval(str: &str) -> EvaluationResult {
86 let io = Rc::new(TvixStoreIO::new(Default::default()));
87
88 let mut eval_builder =
89 tvix_eval::Evaluation::builder(io.clone() as Rc<dyn EvalIO>).enable_import();
90 eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&io));
91 eval_builder = add_import_builtins(eval_builder, io);
93 let eval = eval_builder.build();
94
95 eval.evaluate(str, None)
97 }
98
99 fn import_path_and_compare<P: AsRef<Path>>(p: P) -> Option<String> {
103 let code = format!(r#""${{{}}}""#, p.as_ref().display());
107 let result = eval(&code);
108
109 if !result.errors.is_empty() {
110 return None;
111 }
112
113 let value = result.value.expect("must be some");
114 match value {
115 tvix_eval::Value::String(s) => Some(s.to_str_lossy().into_owned()),
116 _ => panic!("unexpected value type: {value:?}"),
117 }
118 }
119
120 #[test]
123 fn import_directory() {
124 let tmpdir = TempDir::new().unwrap();
125
126 let src_path = tmpdir.path().join("test");
128 std::fs::create_dir(&src_path).unwrap();
129
130 std::fs::write(src_path.join(".keep"), vec![]).unwrap();
132
133 assert_eq!(
135 Some("/nix/store/gq3xcv4xrj4yr64dflyr38acbibv3rm9-test".to_string()),
136 import_path_and_compare(&src_path)
137 );
138
139 assert_eq!(
141 Some("/nix/store/gq3xcv4xrj4yr64dflyr38acbibv3rm9-test".to_string()),
142 import_path_and_compare(src_path.join("."))
143 );
144 }
145
146 #[test]
149 fn import_file() {
150 let tmpdir = TempDir::new().unwrap();
151
152 std::fs::write(tmpdir.path().join("empty"), vec![]).unwrap();
154
155 assert_eq!(
156 Some("/nix/store/lx5i78a4izwk2qj1nq8rdc07y8zrwy90-empty".to_string()),
157 import_path_and_compare(tmpdir.path().join("empty"))
158 );
159
160 std::fs::write(tmpdir.path().join("hello.txt"), b"Hello World!").unwrap();
162
163 assert_eq!(
164 Some("/nix/store/925f1jb1ajrypjbyq7rylwryqwizvhp0-hello.txt".to_string()),
165 import_path_and_compare(tmpdir.path().join("hello.txt"))
166 );
167 }
168
169 #[test]
173 fn nonexisting_path_without_import() {
174 let result = eval("toString ({ line = 42; col = 42; file = /deep/thought; }.file)");
175
176 assert!(result.errors.is_empty(), "expect evaluation to succeed");
177 let value = result.value.expect("must be some");
178
179 match value {
180 tvix_eval::Value::String(s) => {
181 assert_eq!(*s, "/deep/thought");
182 }
183 _ => panic!("unexpected value type: {value:?}"),
184 }
185 }
186}