1 //! This file contains tests for the `stracciatella::vfs` module.
2 
3 // Vfs tests.
4 mod vfs {
5     #[test]
read()6     fn read() {
7         let (temp, dir, dir_fs) = create_temp_dir();
8         create_data_slf(&dir); // data.slf
9 
10         let mut vfs = Vfs::new();
11         add_slf(&mut vfs, &dir_fs, "data.slf");
12         let data = read_file_data(&vfs, "foo.txt");
13         assert_eq!(&data, b"data.slf");
14 
15         temp.close().expect("close temp dir");
16     }
17 
18     #[test]
seek()19     fn seek() {
20         let (temp, dir, dir_fs) = create_temp_dir();
21         create_data_slf(&dir); // data.slf
22 
23         let mut vfs = Vfs::new();
24         add_slf(&mut vfs, &dir_fs, "data.slf");
25         let mut file = vfs.open(&Nfc::caseless_path("foo.txt")).unwrap();
26 
27         assert_eq!(file.seek(SeekFrom::End(0)).unwrap(), 8);
28         assert_eq!(file.seek(SeekFrom::Current(4)).unwrap(), 12);
29         assert_eq!(file.seek(SeekFrom::Current(-8)).unwrap(), 4);
30         assert!(file.seek(SeekFrom::Current(-5)).is_err());
31         assert_eq!(file.seek(SeekFrom::Start(0)).unwrap(), 0);
32 
33         temp.close().expect("close temp dir");
34     }
35 
36     #[test]
order()37     fn order() {
38         let (temp, dir, dir_fs) = create_temp_dir();
39         create_foo_slf(&dir); // foo.slf
40         create_foobar_slf(&dir); // foobar.slf
41         create_file(&dir.join("foo/bar/baz.txt")); // baz.txt
42 
43         let mut vfs = Vfs::new();
44         vfs.add_dir(&dir).expect("dir");
45         add_slf(&mut vfs, &dir_fs, "foo.slf");
46         add_slf(&mut vfs, &dir_fs, "foobar.slf");
47 
48         let data = read_file_data(&vfs, "foo/bar/baz.txt");
49         assert_eq!(&data, b"baz.txt");
50 
51         temp.close().expect("close temp dir");
52     }
53 
54     #[test]
paths()55     fn paths() {
56         let (temp, dir, dir_fs) = create_temp_dir();
57         create_foo_slf(&dir); // foo.slf
58 
59         let mut vfs = Vfs::new();
60         add_slf(&mut vfs, &dir_fs, "foo.slf");
61         // case insensitive
62         let data = read_file_data(&vfs, "FOO/bar.txt");
63         assert_eq!(&data, b"foo.slf");
64         let data = read_file_data(&vfs, "foo/BAR.TXT");
65         assert_eq!(&data, b"foo.slf");
66         let data = read_file_data(&vfs, "foo/BAR/baz.txt");
67         assert_eq!(&data, b"foo.slf");
68         let data = read_file_data(&vfs, "foo/bar/BAZ.TXT");
69         assert_eq!(&data, b"foo.slf");
70         let data = read_file_data(&vfs, "foo\\bar/ὈΔΥΣΣΕΎΣ.baz");
71         assert_eq!(&data, b"foo.slf");
72         // separators
73         let data = read_file_data(&vfs, "foo/bar/baz.txt");
74         assert_eq!(&data, b"foo.slf");
75         let data = read_file_data(&vfs, "foo/bar\\baz.txt");
76         assert_eq!(&data, b"foo.slf");
77         let data = read_file_data(&vfs, "foo\\bar\\baz.txt");
78         assert_eq!(&data, b"foo.slf");
79         let data = read_file_data(&vfs, "foo\\bar/baz.txt");
80         assert_eq!(&data, b"foo.slf");
81         let data = read_file_data(&vfs, "foo\\bar/ὀδυσσεύς.baz");
82         assert_eq!(&data, b"foo.slf");
83 
84         temp.close().expect("close temp dir");
85     }
86 
87     #[test]
read_dir()88     fn read_dir() {
89         let (temp, dir, dir_fs) = create_temp_dir();
90         create_foo_slf(&dir); // foo.slf
91         create_foobar_slf(&dir); // foobar.slf
92         create_file(&dir.join("foo/foo1.txt"));
93         create_file(&dir.join("foo/Foo2.txt"));
94         // Non-ascii paths
95         create_file(&dir.join("foo/ὈΔΥΣΣΕΎΣ.txt"));
96         create_file(&dir.join("foo/ХЦЧ"));
97         create_file(&dir.join("foo/农历新年.txt"));
98 
99         let mut vfs = Vfs::new();
100         vfs.add_dir(&dir).expect("dir");
101         add_slf(&mut vfs, &dir_fs, "foo.slf");
102         add_slf(&mut vfs, &dir_fs, "foobar.slf");
103 
104         let root_paths = ["foo", "foo.slf", "foobar.slf"];
105         let foo_paths = [
106             "农历新年.txt",
107             "хцч",
108             "ὀδυσσεύς.txt",
109             "foo2.txt",
110             "foo1.txt",
111             "bar.txt",
112             "bar",
113         ];
114         let foo_bar_paths = ["baz.txt", "ὀδυσσεύς.baz"];
115         // case insensitive
116         assert_vfs_read_dir(&vfs, "foo", &foo_paths);
117         assert_vfs_read_dir(&vfs, "FOO", &foo_paths);
118         assert_vfs_read_dir(&vfs, "foo/bar", &foo_bar_paths);
119         assert_vfs_read_dir(&vfs, "FOO/Bar", &foo_bar_paths);
120 
121         // trailing path separators should not matter
122         assert_vfs_read_dir(&vfs, "foo/", &foo_paths);
123         assert_vfs_read_dir(&vfs, "FOO/", &foo_paths);
124         assert_vfs_read_dir(&vfs, "foo/bar/", &foo_bar_paths);
125         assert_vfs_read_dir(&vfs, "FOO/Bar/", &foo_bar_paths);
126 
127         // the kind of path separator should not matter
128         assert_vfs_read_dir(&vfs, "foo\\bar", &foo_bar_paths);
129         assert_vfs_read_dir(&vfs, "FOO\\Bar", &foo_bar_paths);
130         assert_vfs_read_dir(&vfs, "foo/bar\\", &foo_bar_paths);
131         assert_vfs_read_dir(&vfs, "FOO/Bar\\", &foo_bar_paths);
132 
133         // it should be possible to list root paths
134         assert_vfs_read_dir(&vfs, "", &root_paths);
135         assert_vfs_read_dir(&vfs, "/", &root_paths);
136 
137         let mut vfs = Vfs::new();
138         add_slf(&mut vfs, &dir_fs, "foobar.slf");
139 
140         // it should be possible to list root paths in vfs just consisting of slf
141         let root_paths = ["foo"];
142         assert_vfs_read_dir(&vfs, "", &root_paths);
143         assert_vfs_read_dir(&vfs, "/", &root_paths);
144 
145         // it should be possible to list paths that only exist in an slf prefix
146         let foo_paths = ["bar"];
147         assert_vfs_read_dir(&vfs, "foo", &foo_paths);
148         assert_vfs_read_dir(&vfs, "foo/", &foo_paths);
149 
150         temp.close().expect("close temp dir");
151     }
152 
153     // end of vfs tests
154     //------------------
155 
156     use std::collections::HashSet;
157     use std::io::{Read, Seek, SeekFrom, Write};
158     use std::iter::FromIterator;
159     use std::path::{Path, PathBuf};
160     use std::rc::Rc;
161 
162     use stracciatella::file_formats::slf::{SlfEntry, SlfEntryState, SlfHeader};
163     use stracciatella::fs;
164     use stracciatella::fs::{OpenOptions, TempDir};
165     use stracciatella::unicode::Nfc;
166     use stracciatella::vfs::dir::DirFs;
167     use stracciatella::vfs::{Vfs, VfsLayer};
168 
169     fn read_file_data(vfs: &Vfs, path: &str) -> Vec<u8> {
170         let mut file = vfs.open(&Nfc::caseless_path(path)).expect("open");
171         let mut data = Vec::new();
172         file.read_to_end(&mut data).expect("Vec<u8>");
173         data
174     }
175 
176     fn assert_vfs_read_dir(vfs: &Vfs, path: &str, expected: &[&str]) {
177         let result = vfs.read_dir(&Nfc::caseless_path(path)).expect("read_dir");
178         let expected = HashSet::from_iter(expected.iter().map(|s| Nfc::caseless_path(s)));
179         assert_eq!(result, expected);
180     }
181 
182     /// The inner file data is the name.
183     fn create_slf(dir: &Path, name: &str, library_path: &str, entry_paths: &[&str]) -> PathBuf {
184         let header = SlfHeader {
185             library_name: name.to_owned(),
186             library_path: library_path.to_owned(),
187             num_entries: entry_paths.len() as i32,
188             ok_entries: entry_paths.len() as i32,
189             sort: 0xFFFF,
190             version: 0x200,
191             contains_subdirectories: if library_path.is_empty() { 0 } else { 1 },
192         };
193         let path = dir.join(&name);
194         let mut file = OpenOptions::new()
195             .write(true)
196             .create_new(true)
197             .open(&path)
198             .expect("open new file for writing");
199         header.to_output(&mut file).expect("write header");
200         let mut entries = entry_paths
201             .iter()
202             .map(|&entry_path| {
203                 let offset = file.seek(SeekFrom::Current(0)).expect("seek to entry data");
204                 let data = name.as_bytes();
205                 file.write_all(&data).expect("write entry data");
206                 SlfEntry {
207                     file_path: entry_path.to_owned(),
208                     offset: offset as u32,
209                     length: data.len() as u32,
210                     state: SlfEntryState::Ok,
211                     file_time: 0,
212                 }
213             })
214             .collect::<Vec<SlfEntry>>();
215         entries.sort_by(|a, b| a.file_path.cmp(&b.file_path));
216         header
217             .entries_to_output(&mut file, &entries)
218             .expect("write entries");
219         file.sync_all().expect("sync_all");
220         path
221     }
222 
223     /// The file data is the same as the filename.
224     fn create_file(path: &Path) {
225         let dir = path.parent().expect("parent path");
226         let name = path
227             .file_name()
228             .expect("file name")
229             .to_str()
230             .expect("file name string");
231         if !dir.exists() {
232             fs::create_dir_all(&dir).expect("create_dir_all");
233         }
234         fs::write(&dir.join(&name), name.as_bytes()).expect("write");
235     }
236 
237     /// The inner file data is "foobar.slf".
238     fn create_foobar_slf(dir: &Path) {
239         create_slf(&dir, "foobar.slf", r"foo\bar\", &["baz.txt"]);
240     }
241 
242     /// The inner file data is "foo.slf".
243     fn create_foo_slf(dir: &Path) {
244         create_slf(
245             &dir,
246             "foo.slf",
247             "foo\\",
248             &["bar.txt", "bar\\baz.txt", "bar\\ὈΔΥΣΣΕΎΣ.baz"],
249         );
250     }
251 
252     /// The inner file data "data.slf".
253     fn create_data_slf(dir: &Path) {
254         create_slf(&dir, "data.slf", "", &["foo.txt"]);
255     }
256 
257     /// Add an slf to vfs
258     fn add_slf(vfs: &mut Vfs, dir_fs: &Rc<dyn VfsLayer>, name: &str) {
259         vfs.add_slf(dir_fs.open(&name.into()).expect("DirFs::open"))
260             .expect("add_slf");
261     }
262 
263     /// The temporary dir and it's contents are removed when TempDir is closed or goes out of scope.
264     fn create_temp_dir() -> (TempDir, PathBuf, Rc<dyn VfsLayer>) {
265         let temp = TempDir::new().expect("TempDir");
266         let dir = temp.path().to_owned();
267         let dir_fs = DirFs::new(&dir).expect("DirFs");
268         (temp, dir, dir_fs)
269     }
270 }
271