phink_lib/cli/ui/monitor/
corpus.rs

1use crate::cli::{
2    config::PFiles,
3    ui::traits::FromPath,
4};
5use std::{
6    fs,
7    path::PathBuf,
8    time::UNIX_EPOCH,
9};
10
11#[derive(Clone, Debug)]
12pub struct CorpusWatcher {
13    corpus_folder: PathBuf,
14}
15
16#[derive(Clone, Debug)]
17pub struct PlotEntry {
18    x: f64,
19    y: f64,
20}
21
22impl PlotEntry {
23    pub fn new(x: f64, y: f64) -> PlotEntry {
24        PlotEntry { x, y }
25    }
26}
27
28impl From<PlotEntry> for (f64, f64) {
29    fn from(entry: PlotEntry) -> Self {
30        (entry.x, entry.y)
31    }
32}
33
34impl FromPath for CorpusWatcher {
35    type Output = CorpusWatcher;
36
37    fn create_instance(log_fullpath: PathBuf) -> Self::Output {
38        CorpusWatcher {
39            corpus_folder: log_fullpath,
40        }
41    }
42
43    fn get_filetype() -> PFiles {
44        PFiles::CorpusPath
45    }
46}
47
48impl CorpusWatcher {
49    pub fn as_tuple_slice(&mut self) -> Vec<(f64, f64)> {
50        self.data().iter().map(|entry| (entry.x, entry.y)).collect()
51    }
52
53    pub fn data(&mut self) -> Vec<PlotEntry> {
54        let mut data: Vec<PlotEntry> = Vec::new();
55        if let Ok(entries) = fs::read_dir(&self.corpus_folder) {
56            let mut entries: Vec<_> = entries.filter_map(Result::ok).collect();
57
58            // Sort entries by their creation time
59            entries.sort_by_key(|entry| entry.metadata().unwrap().created().unwrap());
60
61            for (i, entry) in entries.into_iter().enumerate() {
62                let x = entry
63                    .metadata()
64                    .unwrap()
65                    .created()
66                    .unwrap()
67                    .duration_since(UNIX_EPOCH)
68                    .unwrap()
69                    .as_secs_f64();
70
71                data.push(PlotEntry::new(x, i as f64));
72            }
73        }
74
75        data
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::EmptyResult;
83    use std::{
84        fs,
85        io::Write,
86        path::Path,
87        thread::sleep,
88        time::Duration,
89    };
90    use tempfile::{
91        NamedTempFile,
92        TempDir,
93    };
94
95    #[test]
96    fn test_from_fullpath_existing_folder() {
97        let temp_dir = TempDir::new().unwrap();
98        let path = temp_dir.path().to_path_buf();
99
100        let result = CorpusWatcher::from_fullpath(path.clone());
101        assert!(result.is_ok());
102
103        let mut watcher = result.unwrap();
104        assert_eq!(watcher.corpus_folder, path);
105        assert!(watcher.data().is_empty());
106    }
107
108    #[test]
109    fn test_from_fullpath_non_existing_folder() {
110        let non_existing_path = PathBuf::from("/non/existing/path");
111        let result = CorpusWatcher::from_fullpath(non_existing_path);
112        assert!(result.is_err());
113    }
114    #[test]
115    fn test_corpus_watcher_data() -> EmptyResult {
116        let corpus_path = Path::new("tests/fixtures/corpus").to_path_buf();
117
118        let mut watcher = CorpusWatcher::from_fullpath(corpus_path.clone())?;
119
120        // Initial check
121        // let initial_data = watcher.data();
122
123        // Add a file and check again
124        let mut temp_file = NamedTempFile::new_in(corpus_path.clone())?;
125        writeln!(temp_file, "just a random seed")?;
126
127        sleep(Duration::from_secs(1)); // Sleep to ensure different timestamp
128        let data_after_one_file = watcher.data();
129        assert_eq!(data_after_one_file.get(3).unwrap().y, 3f64); // One file, so y should be 1
130
131        // Add another file and check again
132        let mut temp_file = NamedTempFile::new_in(corpus_path.clone())?;
133        writeln!(temp_file, "just a random seed but again")?;
134
135        sleep(Duration::from_secs(1)); // Sleep to ensure different timestamp
136        let data_after_one_file = watcher.data();
137        assert_eq!(data_after_one_file.get(2).unwrap().y, 2f64); // Two files, so y should be 2
138
139        // Check that x values (timestamps) are increasing
140        // let second = data_after_one_file.get(40).unwrap().x; // we do 40 because if we take 2,
141        // it'll have the same timestamp let first = data_after_one_file.first().unwrap().x;
142        // println!("second: {} & first: {}", second, first);
143        // assert!(second > first);
144        Ok(())
145    }
146
147    #[test]
148    fn test_from_output_non_existing_folder() {
149        let non_existing_path = PathBuf::from("/non/existing/path");
150        let result = CorpusWatcher::from_output(non_existing_path);
151        assert!(result.is_err());
152    }
153
154    #[test]
155    fn test_data_empty_folder() {
156        let temp_dir = TempDir::new().unwrap();
157        let path = temp_dir.path().to_path_buf();
158
159        let mut watcher = CorpusWatcher::from_fullpath(path).unwrap();
160        let data = watcher.data();
161
162        assert_eq!(data.len(), 0);
163    }
164
165    #[test]
166    fn test_data_non_empty_folder() {
167        let temp_dir = TempDir::new().unwrap();
168        let path = temp_dir.path().to_path_buf();
169
170        // Create some files in the temporary directory
171        fs::File::create(path.join("file1.txt")).unwrap();
172        fs::File::create(path.join("file2.txt")).unwrap();
173
174        let mut watcher = CorpusWatcher::from_fullpath(path).unwrap();
175        let data = watcher.data();
176
177        assert_eq!(data.len(), 2);
178        assert_eq!(data[1].y, 1.0);
179    }
180
181    #[test]
182    fn test_plot_entry_conversion() {
183        let entry = PlotEntry { x: 1.0, y: 2.0 };
184        let tuple: (f64, f64) = entry.into();
185        assert_eq!(tuple, (1.0, 2.0));
186    }
187}