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!(messages.len(), 5);
204            expect_that!(messages[0], pat!(LogMessage::LoadModule("test_package")));
205            expect_that!(
206                messages,
207                superset_of([
208                    &LogMessage::CreateDir(td.join("./test_pkg")),
209                    &LogMessage::CreateSymlink {
210                        src: td.join(SRC_FILE_PATH).canonicalize()?,
211                        dst: td.join(DST_FILE_PATH)
212                    }
213                ])
214            );
215            expect_that!(
216                messages,
217                superset_of([
218                    &LogMessage::CreateDir(td.join("./test_a/test_b")),
219                    &LogMessage::CreateSymlink {
220                        src: td.join(SRC_DIR_PATH).canonicalize()?,
221                        dst: td.join(DST_DIR_PATH)
222                    }
223                ])
224            );
225
226            Ok(())
227        }
228
229        #[gtest]
230        fn no_pkg_dir() -> Result<()> {
231            let (td, pkg, mut runner) = common_local_pkg()?;
232            fs::remove_dir_all(td.join("test_package"))?;
233
234            let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
235            expect_that!(result, pat!(LoadError::PkgDirNotFound("test_package")));
236
237            Ok(())
238        }
239
240        #[gtest]
241        fn src_not_exists() -> Result<()> {
242            let (td, pkg, mut runner) = common_local_pkg()?;
243            fs::remove_file(td.join(SRC_FILE_PATH))?;
244
245            let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
246            expect_that!(result, pat!(LoadError::SrcNotExists("src_file")));
247
248            Ok(())
249        }
250
251        #[gtest]
252        fn dst_already_exists() -> Result<()> {
253            let (td, pkg, mut runner) = common_local_pkg()?;
254            fs::create_dir_all(td.join(DST_FILE_PATH))?;
255
256            let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
257            expect_that!(
258                result,
259                pat!(LoadError::DstAlreadyExists {
260                    src: "src_file",
261                    dst: &td.join(DST_FILE_PATH)
262                })
263            );
264
265            Ok(())
266        }
267    }
268
269    mod load_with_trace_without_dir_changed {
270        use super::*;
271
272        fn setup() -> Result<(TempDir, NamedPackage, PkgTrace)> {
273            let (td, pkg, mut runner) = common_local_pkg()?;
274            let trace = runner.load_module(&pkg, None)?;
275            Ok((td, pkg, trace))
276        }
277
278        #[gtest]
279        fn no_pkg_dir() -> Result<()> {
280            let (td, pkg, trace) = setup()?;
281            let mut runner = common_runner(td.path());
282            fs::remove_dir_all(td.join("test_package"))?;
283
284            let result = runner
285                .load_module(&pkg, Some(&trace))
286                .unwrap_err()
287                .unwrap_load();
288            expect_that!(result, pat!(LoadError::PkgDirNotFound("test_package")));
289
290            Ok(())
291        }
292
293        #[gtest]
294        fn no_changed() -> Result<()> {
295            let (td, pkg, trace) = setup()?;
296            let mut runner = common_runner(td.path());
297            let new_trace = runner.load_module(&pkg, Some(&trace))?;
298
299            expect_eq!(new_trace, trace);
300            expect_eq!(runner.messages().len(), 1);
301            expect_that!(
302                runner.messages()[0],
303                pat!(LogMessage::LoadModule("test_package"))
304            );
305
306            Ok(())
307        }
308
309        #[gtest]
310        fn just_update() -> Result<()> {
311            let (td, mut pkg, trace) = setup()?;
312            pkg.insert_map("src_file", td.join("new_dest_file").to_string_lossy());
313
314            let mut runner = common_runner(td.path());
315            let new_trace = runner.load_module(&pkg, Some(&trace))?;
316
317            expect_eq!(new_trace.directory, trace.directory);
318            expect_eq!(new_trace.maps.len(), trace.maps.len());
319            expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
320            expect_eq!(
321                new_trace.maps["src_file"],
322                td.join("new_dest_file").to_str().unwrap()
323            );
324
325            expect_that!(
326                runner.messages(),
327                superset_of([
328                    &LogMessage::RemoveSymlink {
329                        src: td.join(SRC_FILE_PATH).canonicalize()?,
330                        dst: td.join(DST_FILE_PATH)
331                    },
332                    &LogMessage::CreateSymlink {
333                        src: td.join(SRC_FILE_PATH).canonicalize()?,
334                        dst: td.join("new_dest_file")
335                    },
336                ])
337            );
338
339            Ok(())
340        }
341
342        #[gtest]
343        fn add_new() -> Result<()> {
344            let (td, mut pkg, trace) = setup()?;
345            let td = td.file("test_package/new_src_file", "")?;
346            let new_dst_path = td.join("nonexistent_parent/new_dest_file");
347            pkg.insert_map("new_src_file", new_dst_path.to_string_lossy());
348
349            let mut runner = common_runner(td.path());
350            let new_trace = runner.load_module(&pkg, Some(&trace))?;
351
352            expect_eq!(new_trace.directory, trace.directory);
353            expect_eq!(new_trace.maps.len(), trace.maps.len() + 1);
354            expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
355            expect_eq!(new_trace.maps["src_file"], trace.maps["src_file"]);
356            expect_eq!(
357                new_trace.maps["new_src_file"],
358                new_dst_path.to_str().unwrap(),
359            );
360
361            expect_that!(
362                runner.messages(),
363                superset_of([&LogMessage::CreateSymlink {
364                    src: td.join("test_package/new_src_file"),
365                    dst: new_dst_path
366                },])
367            );
368
369            Ok(())
370        }
371
372        #[gtest]
373        fn remove_old() -> Result<()> {
374            let (td, mut pkg, trace) = setup()?;
375            pkg.remove_map("src_file");
376
377            let mut runner = common_runner(td.path());
378            let new_trace = runner.load_module(&pkg, Some(&trace))?;
379
380            expect_eq!(new_trace.directory, trace.directory);
381            expect_eq!(new_trace.maps.len(), trace.maps.len() - 1);
382            expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
383            expect_eq!(new_trace.maps.get("src_file"), None);
384
385            expect_that!(
386                runner.messages(),
387                superset_of([&LogMessage::RemoveSymlink {
388                    src: td.join(SRC_FILE_PATH),
389                    dst: td.join(DST_FILE_PATH)
390                },])
391            );
392
393            Ok(())
394        }
395
396        #[gtest]
397        fn remove_old_but_dst_not_a_symlink() -> Result<()> {
398            let (td, mut pkg, trace) = setup()?;
399            pkg.remove_map("src_file");
400
401            fs::remove_file(td.join(DST_FILE_PATH))?;
402            fs::write(td.join(DST_FILE_PATH), "")?;
403
404            let mut runner = common_runner(td.path());
405            let err = runner
406                .load_module(&pkg, Some(&trace))
407                .unwrap_err()
408                .unwrap_load();
409            expect_that!(
410                err,
411                pat!(LoadError::DstNotSymlink {
412                    src: "src_file",
413                    dst: &td.join(DST_FILE_PATH)
414                })
415            );
416
417            Ok(())
418        }
419
420        #[gtest]
421        fn src_not_exists() -> Result<()> {
422            let (td, pkg, trace) = setup()?;
423            fs::remove_file(td.join(SRC_FILE_PATH))?;
424
425            let mut runner = common_runner(td.path());
426            let err = runner
427                .load_module(&pkg, Some(&trace))
428                .unwrap_err()
429                .unwrap_load();
430            expect_that!(err, pat!(LoadError::SrcNotExists("src_file")));
431
432            Ok(())
433        }
434
435        #[gtest]
436        fn dst_exists_but_not_in_trace() -> Result<()> {
437            let (td, mut pkg, trace) = setup()?;
438            let td = td
439                .file("test_package/new_src_file", "")?
440                .file("new_dest_file", "")?;
441            pkg.insert_map("new_src_file", td.join("new_dest_file").to_string_lossy());
442
443            let mut runner = common_runner(td.path());
444            let err = runner
445                .load_module(&pkg, Some(&trace))
446                .unwrap_err()
447                .unwrap_load();
448            expect_that!(
449                err,
450                pat!(LoadError::DstAlreadyExists {
451                    src: "new_src_file",
452                    dst: &td.join("new_dest_file")
453                })
454            );
455
456            Ok(())
457        }
458
459        #[gtest]
460        fn dst_in_trace_but_not_a_symlink() -> Result<()> {
461            let (td, pkg, trace) = setup()?;
462            fs::remove_file(td.join(DST_FILE_PATH))?;
463            fs::write(td.join(DST_FILE_PATH), "")?;
464
465            let mut runner = common_runner(td.path());
466            let err = runner
467                .load_module(&pkg, Some(&trace))
468                .unwrap_err()
469                .unwrap_load();
470            expect_that!(
471                err,
472                pat!(LoadError::DstNotSymlink {
473                    src: "src_file",
474                    dst: &td.join(DST_FILE_PATH)
475                })
476            );
477
478            Ok(())
479        }
480
481        #[gtest]
482        fn dst_in_trace_but_not_exists() -> Result<()> {
483            let (td, pkg, trace) = setup()?;
484            fs::remove_file(td.join(DST_FILE_PATH))?;
485
486            let mut runner = common_runner(td.path());
487            let new_trace = runner.load_module(&pkg, Some(&trace))?;
488
489            expect_eq!(new_trace, trace);
490            expect_that!(
491                runner.messages(),
492                superset_of([&LogMessage::CreateSymlink {
493                    src: td.join(SRC_FILE_PATH),
494                    dst: td.join(DST_FILE_PATH)
495                }])
496            );
497
498            Ok(())
499        }
500    }
501}