use std::fs::File; use std::fs::{self}; use std::io::Read; use std::io::Write; use std::io::{self}; use std::path::Path; use std::path::PathBuf; use filetime::set_file_times; use filetime::FileTime; use tempfile::TempDir; use ritecache::DiskCacheError; use ritecache::LruDiskCache; struct TestFixture { /// Temp directory. pub tempdir: TempDir, } fn create_file, F: FnOnce(File) -> io::Result<()>>( dir: &Path, path: T, fill_contents: F, ) -> io::Result { let b = dir.join(path); fs::create_dir_all(b.parent().unwrap())?; let f = fs::File::create(&b)?; fill_contents(f)?; b.canonicalize() } /// Set the last modified time of `path` backwards by `seconds` seconds. fn set_mtime_back>(path: T, seconds: usize) { let m = fs::metadata(path.as_ref()).unwrap(); let t = FileTime::from_last_modification_time(&m); let t = FileTime::from_unix_time(t.unix_seconds() - seconds as i64, t.nanoseconds()); set_file_times(path, t, t).unwrap(); } fn read_all(r: &mut R) -> io::Result> { let mut v = vec![]; r.read_to_end(&mut v)?; Ok(v) } impl TestFixture { pub fn new() -> TestFixture { TestFixture { tempdir: tempfile::Builder::new().prefix("lru-disk-cache-test").tempdir().unwrap(), } } pub fn tmp(&self) -> &Path { self.tempdir.path() } pub fn create_file>(&self, path: T, size: usize) -> PathBuf { create_file(self.tempdir.path(), path, |mut f| f.write_all(&vec![0; size])).unwrap() } } #[test] fn test_empty_dir() { let f = TestFixture::new(); LruDiskCache::new(f.tmp(), 1024).unwrap(); } #[test] fn test_missing_root() { let f = TestFixture::new(); LruDiskCache::new(f.tmp().join("not-here"), 1024).unwrap(); } #[test] fn test_some_existing_files() { let f = TestFixture::new(); f.create_file("file1", 10); f.create_file("file2", 10); let c = LruDiskCache::new(f.tmp(), 20).unwrap(); assert_eq!(c.size(), 20); assert_eq!(c.len(), 2); } #[test] fn test_existing_file_too_large() { let f = TestFixture::new(); // Create files explicitly in the past. set_mtime_back(f.create_file("file1", 10), 10); set_mtime_back(f.create_file("file2", 10), 5); let c = LruDiskCache::new(f.tmp(), 15).unwrap(); assert_eq!(c.size(), 10); assert_eq!(c.len(), 1); assert!(!c.contains_key("file1")); assert!(c.contains_key("file2")); } #[test] fn test_existing_files_lru_mtime() { let f = TestFixture::new(); // Create files explicitly in the past. set_mtime_back(f.create_file("file1", 10), 5); set_mtime_back(f.create_file("file2", 10), 10); let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); assert_eq!(c.size(), 20); c.insert_bytes("file3", &[0; 10]).unwrap(); assert_eq!(c.size(), 20); // The oldest file on disk should have been removed. assert!(!c.contains_key("file2")); assert!(c.contains_key("file1")); } #[test] fn test_insert_bytes() { let f = TestFixture::new(); let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); c.insert_bytes("a/b/c", &[0; 10]).unwrap(); assert!(c.contains_key("a/b/c")); c.insert_bytes("a/b/d", &[0; 10]).unwrap(); assert_eq!(c.size(), 20); // Adding this third file should put the cache above the limit. c.insert_bytes("x/y/z", &[0; 10]).unwrap(); assert_eq!(c.size(), 20); // The least-recently-used file should have been removed. assert!(!c.contains_key("a/b/c")); assert!(!f.tmp().join("a/b/c").exists()); } #[test] fn test_insert_bytes_exact() { // Test that files adding up to exactly the size limit works. let f = TestFixture::new(); let mut c = LruDiskCache::new(f.tmp(), 20).unwrap(); c.insert_bytes("file1", &[1; 10]).unwrap(); c.insert_bytes("file2", &[2; 10]).unwrap(); assert_eq!(c.size(), 20); c.insert_bytes("file3", &[3; 10]).unwrap(); assert_eq!(c.size(), 20); assert!(!c.contains_key("file1")); } #[test] fn test_add_get_lru() { let f = TestFixture::new(); { let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); c.insert_bytes("file1", &[1; 10]).unwrap(); c.insert_bytes("file2", &[2; 10]).unwrap(); // Get the file to bump its LRU status. assert_eq!(read_all(&mut c.get("file1").unwrap()).unwrap(), vec![1u8; 10]); // Adding this third file should put the cache above the limit. c.insert_bytes("file3", &[3; 10]).unwrap(); assert_eq!(c.size(), 20); // The least-recently-used file should have been removed. assert!(!c.contains_key("file2")); } // Get rid of the cache, to test that the LRU persists on-disk as mtimes. // This is hacky, but mtime resolution on my mac with HFS+ is only 1 second, so we either // need to have a 1 second sleep in the test (boo) or adjust the mtimes back a bit so // that updating one file to the current time actually works to make it newer. set_mtime_back(f.tmp().join("file1"), 5); set_mtime_back(f.tmp().join("file3"), 5); { let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); // Bump file1 again. c.get("file1").unwrap(); } // Now check that the on-disk mtimes were updated and used. { let mut c = LruDiskCache::new(f.tmp(), 25).unwrap(); assert!(c.contains_key("file1")); assert!(c.contains_key("file3")); assert_eq!(c.size(), 20); // Add another file to bump out the least-recently-used. c.insert_bytes("file4", &[4; 10]).unwrap(); assert_eq!(c.size(), 20); assert!(!c.contains_key("file3")); assert!(c.contains_key("file1")); } } #[test] fn test_insert_bytes_too_large() { let f = TestFixture::new(); let mut c = LruDiskCache::new(f.tmp(), 1).unwrap(); match c.insert_bytes("a/b/c", &[0; 2]) { Err(DiskCacheError::FileTooLarge) => {} x => panic!("Unexpected result: {:?}", x), } } #[test] fn test_insert_file() { let f = TestFixture::new(); let p1 = f.create_file("file1", 10); let p2 = f.create_file("file2", 10); let p3 = f.create_file("file3", 10); let mut c = LruDiskCache::new(f.tmp().join("cache"), 25).unwrap(); c.insert_file("file1", &p1).unwrap(); assert_eq!(c.len(), 1); c.insert_file("file2", &p2).unwrap(); assert_eq!(c.len(), 2); // Get the file to bump its LRU status. assert_eq!(read_all(&mut c.get("file1").unwrap()).unwrap(), vec![0u8; 10]); // Adding this third file should put the cache above the limit. c.insert_file("file3", &p3).unwrap(); assert_eq!(c.len(), 2); assert_eq!(c.size(), 20); // The least-recently-used file should have been removed. assert!(!c.contains_key("file2")); assert!(!p1.exists()); assert!(!p2.exists()); assert!(!p3.exists()); } #[test] fn test_remove() { let f = TestFixture::new(); let p1 = f.create_file("file1", 10); let p2 = f.create_file("file2", 10); let p3 = f.create_file("file3", 10); let mut c = LruDiskCache::new(f.tmp().join("cache"), 25).unwrap(); c.insert_file("file1", &p1).unwrap(); c.insert_file("file2", &p2).unwrap(); c.remove("file1").unwrap(); c.insert_file("file3", &p3).unwrap(); assert_eq!(c.len(), 2); assert_eq!(c.size(), 20); // file1 should have been removed. assert!(!c.contains_key("file1")); assert!(!f.tmp().join("cache").join("file1").exists()); assert!(f.tmp().join("cache").join("file2").exists()); assert!(f.tmp().join("cache").join("file3").exists()); assert!(!p1.exists()); assert!(!p2.exists()); assert!(!p3.exists()); let p4 = f.create_file("file1", 10); c.insert_file("file1", &p4).unwrap(); assert_eq!(c.len(), 2); // file2 should have been removed. assert!(c.contains_key("file1")); assert!(!c.contains_key("file2")); assert!(!f.tmp().join("cache").join("file2").exists()); assert!(!p4.exists()); }