pkgs/runner/
load.rs

1use std::path::PathBuf;
2
3use super::{LoadError, Runner, RunnerError};
4use crate::config::NamedPackage;
5use crate::logger::LoggerOutput;
6use crate::trace::PkgTrace;
7
8impl<O: LoggerOutput> Runner<O> {
9    pub fn load_module(
10        &mut self,
11        package: &NamedPackage,
12        trace: Option<&PkgTrace>,
13    ) -> Result<PkgTrace, RunnerError> {
14        self.logger.load_module(package.name());
15
16        let result = if let Some(trace) = trace {
17            self.load_with_trace(package, trace)
18        } else {
19            self.load_directly(package)
20        };
21
22        result.map_err(|e| RunnerError::LoadModuleError {
23            source: e,
24            module: package.name().to_string(),
25        })
26    }
27
28    fn load_directly(&mut self, package: &NamedPackage) -> Result<PkgTrace, LoadError> {
29        let mut trace = PkgTrace::new(package.get_directory());
30
31        let pkg_dir = self.absolute_path_from(&trace.directory);
32        if !pkg_dir.exists() {
33            return Err(LoadError::PkgDirNotFound(package.name().to_string()));
34        }
35
36        for (src, dst) in package.maps() {
37            let src_path = pkg_dir.join(src);
38            if !src_path.exists() {
39                return Err(LoadError::SrcNotExists(src.to_string()));
40            }
41
42            let dst_path = PathBuf::from(&dst);
43            if dst_path.exists() {
44                return Err(LoadError::DstAlreadyExists {
45                    src: src.clone(),
46                    dst: dst_path,
47                });
48            }
49
50            if let Some(parent) = dst_path.parent()
51                && !parent.exists()
52            {
53                self.create_dir(parent)?;
54            }
55
56            self.create_symlink(&src_path, &dst_path)?;
57
58            trace.maps.insert(src.into(), dst.into());
59        }
60
61        Ok(trace)
62    }
63
64    fn load_with_trace(
65        &mut self,
66        package: &NamedPackage,
67        old_trace: &PkgTrace,
68    ) -> Result<PkgTrace, LoadError> {
69        let directory = package.get_directory();
70        if directory != old_trace.directory {
71            return self.load_with_pkg_dir_changed(package, old_trace);
72        }
73
74        let mut trace = PkgTrace::new(directory);
75
76        let pkg_dir = self.absolute_path_from(&trace.directory);
77        if !pkg_dir.exists() {
78            return Err(LoadError::PkgDirNotFound(package.name().to_string()));
79        }
80
81        for (src, dst) in package.maps() {
82            let src_path = pkg_dir.join(src);
83            if !src_path.exists() {
84                return Err(LoadError::SrcNotExists(src.to_string()));
85            }
86
87            let dst_path = PathBuf::from(&dst);
88
89            if let Some(dst_in_trace) = old_trace.maps.get(src) {
90                let dst_in_trace = PathBuf::from(dst_in_trace);
91                if dst_in_trace.exists() {
92                    if !dst_in_trace.is_symlink() {
93                        return Err(LoadError::DstNotSymlink {
94                            src: src.clone(),
95                            dst: dst_in_trace,
96                        });
97                    }
98
99                    if dst_path == dst_in_trace {
100                        trace.maps.insert(src.into(), dst.into());
101                        continue;
102                    }
103
104                    self.remove_symlink(&src_path, dst_in_trace)?;
105                }
106            }
107
108            if dst_path.exists() {
109                return Err(LoadError::DstAlreadyExists {
110                    src: src.clone(),
111                    dst: dst_path,
112                });
113            }
114
115            if let Some(parent) = dst_path.parent()
116                && !parent.exists()
117            {
118                self.create_dir(parent)?;
119            }
120
121            self.create_symlink(&src_path, dst)?;
122
123            trace.maps.insert(src.into(), dst.into());
124        }
125
126        for (src, dst) in &old_trace.maps {
127            let dst_path = PathBuf::from(&dst);
128
129            if dst_path.exists() && !trace.maps.contains_key(src) {
130                if !dst_path.is_symlink() {
131                    return Err(LoadError::DstNotSymlink {
132                        src: src.clone(),
133                        dst: dst_path,
134                    });
135                }
136                self.remove_symlink(pkg_dir.join(src), dst)?;
137            }
138        }
139
140        Ok(trace)
141    }
142
143    fn load_with_pkg_dir_changed(
144        &mut self,
145        _package: &NamedPackage,
146        _old_trace: &PkgTrace,
147    ) -> Result<PkgTrace, LoadError> {
148        todo!()
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use std::fs;
155
156    use super::*;
157    use crate::test_utils::prelude::*;
158
159    mod load_without_trace {
160        use super::*;
161
162        #[gtest]
163        fn it_works() -> Result<()> {
164            let (td, pkg, mut runner) = common_local_pkg()?;
165
166            let trace = runner.load_module(&pkg, None)?;
167
168            let dst_file = td.join(DST_FILE_PATH);
169            let dst_dir = td.join(DST_DIR_PATH);
170
171            expect_that!(
172                dst_file,
173                is_symlink_for(td.join(SRC_FILE_PATH).canonicalize()?),
174                "dst_file should point to the absolute path of src_file"
175            );
176
177            expect_that!(
178                dst_dir,
179                is_symlink_for(td.join(SRC_DIR_PATH).canonicalize()?),
180                "dst_dir should point to the absolute path of src_dir"
181            );
182
183            expect_eq!(trace.directory, "test_package");
184            expect_eq!(trace.maps.len(), 2);
185            expect_eq!(
186                trace.maps["src_file"],
187                td.join(DST_FILE_PATH).to_str().unwrap()
188            );
189            expect_eq!(
190                trace.maps["src_dir"],
191                td.join(DST_DIR_PATH).to_str().unwrap()
192            );
193
194            Ok(())
195        }
196
197        #[gtest]
198        fn runner_output() -> Result<()> {
199            let (td, pkg, mut runner) = common_local_pkg()?;
200            runner.load_module(&pkg, None)?;
201
202            let messages = runner.messages();
203            expect_eq!(
204                *messages,
205                [
206                    LogMessage::LoadModule("test_package".into()),
207                    LogMessage::CreateDir(td.join("./test_pkg")),
208                    LogMessage::CreateSymlink {
209                        src: td.join(SRC_FILE_PATH).canonicalize()?,
210                        dst: td.join(DST_FILE_PATH)
211                    },
212                    LogMessage::CreateDir(td.join("./test_a/test_b")),
213                    LogMessage::CreateSymlink {
214                        src: td.join(SRC_DIR_PATH).canonicalize()?,
215                        dst: td.join(DST_DIR_PATH)
216                    }
217                ]
218            );
219
220            Ok(())
221        }
222
223        #[gtest]
224        fn no_pkg_dir() -> Result<()> {
225            let (td, pkg, mut runner) = common_local_pkg()?;
226            fs::remove_dir_all(td.join("test_package"))?;
227
228            let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
229            expect_that!(result, pat!(LoadError::PkgDirNotFound("test_package")));
230
231            Ok(())
232        }
233
234        #[gtest]
235        fn src_not_exists() -> Result<()> {
236            let (td, pkg, mut runner) = common_local_pkg()?;
237            fs::remove_file(td.join(SRC_FILE_PATH))?;
238
239            let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
240            expect_that!(result, pat!(LoadError::SrcNotExists("src_file")));
241
242            Ok(())
243        }
244
245        #[gtest]
246        fn dst_already_exists() -> Result<()> {
247            let (td, pkg, mut runner) = common_local_pkg()?;
248            fs::create_dir_all(td.join(DST_FILE_PATH))?;
249
250            let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
251            expect_that!(
252                result,
253                pat!(LoadError::DstAlreadyExists {
254                    src: "src_file",
255                    dst: &td.join(DST_FILE_PATH)
256                })
257            );
258
259            Ok(())
260        }
261    }
262
263    mod load_with_trace_without_dir_changed {
264        use super::*;
265
266        fn setup() -> Result<(TempDir, NamedPackage, PkgTrace)> {
267            let (td, pkg, mut runner) = common_local_pkg()?;
268            let trace = runner.load_module(&pkg, None)?;
269            Ok((td, pkg, trace))
270        }
271
272        #[gtest]
273        fn no_pkg_dir() -> Result<()> {
274            let (td, pkg, trace) = setup()?;
275            let mut runner = common_runner(td.path());
276            fs::remove_dir_all(td.join("test_package"))?;
277
278            let result = runner
279                .load_module(&pkg, Some(&trace))
280                .unwrap_err()
281                .unwrap_load();
282            expect_that!(result, pat!(LoadError::PkgDirNotFound("test_package")));
283
284            Ok(())
285        }
286
287        #[gtest]
288        fn no_changed() -> Result<()> {
289            let (td, pkg, trace) = setup()?;
290            let mut runner = common_runner(td.path());
291            let new_trace = runner.load_module(&pkg, Some(&trace))?;
292
293            expect_eq!(new_trace, trace);
294            expect_eq!(runner.messages().len(), 1);
295            expect_that!(
296                runner.messages()[0],
297                pat!(LogMessage::LoadModule("test_package"))
298            );
299
300            Ok(())
301        }
302
303        #[gtest]
304        fn just_update() -> Result<()> {
305            let (td, mut pkg, trace) = setup()?;
306            pkg.insert_map("src_file", td.join("new_dest_file").to_string_lossy());
307
308            let mut runner = common_runner(td.path());
309            let new_trace = runner.load_module(&pkg, Some(&trace))?;
310
311            expect_eq!(new_trace.directory, trace.directory);
312            expect_eq!(new_trace.maps.len(), trace.maps.len());
313            expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
314            expect_eq!(
315                new_trace.maps["src_file"],
316                td.join("new_dest_file").to_str().unwrap()
317            );
318
319            expect_that!(
320                runner.messages(),
321                superset_of([
322                    &LogMessage::RemoveSymlink {
323                        src: td.join(SRC_FILE_PATH).canonicalize()?,
324                        dst: td.join(DST_FILE_PATH)
325                    },
326                    &LogMessage::CreateSymlink {
327                        src: td.join(SRC_FILE_PATH).canonicalize()?,
328                        dst: td.join("new_dest_file")
329                    },
330                ])
331            );
332
333            Ok(())
334        }
335
336        #[gtest]
337        fn add_new() -> Result<()> {
338            let (td, mut pkg, trace) = setup()?;
339            let td = td.file("test_package/new_src_file", "")?;
340            let new_dst_path = td.join("nonexistent_parent/new_dest_file");
341            pkg.insert_map("new_src_file", new_dst_path.to_string_lossy());
342
343            let mut runner = common_runner(td.path());
344            let new_trace = runner.load_module(&pkg, Some(&trace))?;
345
346            expect_eq!(new_trace.directory, trace.directory);
347            expect_eq!(new_trace.maps.len(), trace.maps.len() + 1);
348            expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
349            expect_eq!(new_trace.maps["src_file"], trace.maps["src_file"]);
350            expect_eq!(
351                new_trace.maps["new_src_file"],
352                new_dst_path.to_str().unwrap(),
353            );
354
355            expect_that!(
356                runner.messages(),
357                superset_of([&LogMessage::CreateSymlink {
358                    src: td.join("test_package/new_src_file"),
359                    dst: new_dst_path
360                },])
361            );
362
363            Ok(())
364        }
365
366        #[gtest]
367        fn remove_old() -> Result<()> {
368            let (td, mut pkg, trace) = setup()?;
369            pkg.remove_map("src_file");
370
371            let mut runner = common_runner(td.path());
372            let new_trace = runner.load_module(&pkg, Some(&trace))?;
373
374            expect_eq!(new_trace.directory, trace.directory);
375            expect_eq!(new_trace.maps.len(), trace.maps.len() - 1);
376            expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
377            expect_eq!(new_trace.maps.get("src_file"), None);
378
379            expect_that!(
380                runner.messages(),
381                superset_of([&LogMessage::RemoveSymlink {
382                    src: td.join(SRC_FILE_PATH),
383                    dst: td.join(DST_FILE_PATH)
384                },])
385            );
386
387            Ok(())
388        }
389
390        #[gtest]
391        fn remove_old_but_dst_not_a_symlink() -> Result<()> {
392            let (td, mut pkg, trace) = setup()?;
393            pkg.remove_map("src_file");
394
395            fs::remove_file(td.join(DST_FILE_PATH))?;
396            fs::write(td.join(DST_FILE_PATH), "")?;
397
398            let mut runner = common_runner(td.path());
399            let err = runner
400                .load_module(&pkg, Some(&trace))
401                .unwrap_err()
402                .unwrap_load();
403            expect_that!(
404                err,
405                pat!(LoadError::DstNotSymlink {
406                    src: "src_file",
407                    dst: &td.join(DST_FILE_PATH)
408                })
409            );
410
411            Ok(())
412        }
413
414        #[gtest]
415        fn src_not_exists() -> Result<()> {
416            let (td, pkg, trace) = setup()?;
417            fs::remove_file(td.join(SRC_FILE_PATH))?;
418
419            let mut runner = common_runner(td.path());
420            let err = runner
421                .load_module(&pkg, Some(&trace))
422                .unwrap_err()
423                .unwrap_load();
424            expect_that!(err, pat!(LoadError::SrcNotExists("src_file")));
425
426            Ok(())
427        }
428
429        #[gtest]
430        fn dst_exists_but_not_in_trace() -> Result<()> {
431            let (td, mut pkg, trace) = setup()?;
432            let td = td
433                .file("test_package/new_src_file", "")?
434                .file("new_dest_file", "")?;
435            pkg.insert_map("new_src_file", td.join("new_dest_file").to_string_lossy());
436
437            let mut runner = common_runner(td.path());
438            let err = runner
439                .load_module(&pkg, Some(&trace))
440                .unwrap_err()
441                .unwrap_load();
442            expect_that!(
443                err,
444                pat!(LoadError::DstAlreadyExists {
445                    src: "new_src_file",
446                    dst: &td.join("new_dest_file")
447                })
448            );
449
450            Ok(())
451        }
452
453        #[gtest]
454        fn dst_in_trace_but_not_a_symlink() -> Result<()> {
455            let (td, pkg, trace) = setup()?;
456            fs::remove_file(td.join(DST_FILE_PATH))?;
457            fs::write(td.join(DST_FILE_PATH), "")?;
458
459            let mut runner = common_runner(td.path());
460            let err = runner
461                .load_module(&pkg, Some(&trace))
462                .unwrap_err()
463                .unwrap_load();
464            expect_that!(
465                err,
466                pat!(LoadError::DstNotSymlink {
467                    src: "src_file",
468                    dst: &td.join(DST_FILE_PATH)
469                })
470            );
471
472            Ok(())
473        }
474
475        #[gtest]
476        fn dst_in_trace_but_not_exists() -> Result<()> {
477            let (td, pkg, trace) = setup()?;
478            fs::remove_file(td.join(DST_FILE_PATH))?;
479
480            let mut runner = common_runner(td.path());
481            let new_trace = runner.load_module(&pkg, Some(&trace))?;
482
483            expect_eq!(new_trace, trace);
484            expect_that!(
485                runner.messages(),
486                superset_of([&LogMessage::CreateSymlink {
487                    src: td.join(SRC_FILE_PATH),
488                    dst: td.join(DST_FILE_PATH)
489                }])
490            );
491
492            Ok(())
493        }
494    }
495}