From d2a4910684aad3250880c6c7c54d43caedad3782 Mon Sep 17 00:00:00 2001 From: Daniel Carl Jones Date: Mon, 16 Jan 2023 18:04:18 +0000 Subject: [PATCH 1/3] Add real values for atime, ctime, and mtime --- Cargo.lock | 20 +++-------- s3-client/src/mock_client.rs | 18 ++++++---- s3-file-connector/Cargo.toml | 1 + s3-file-connector/src/fs.rs | 6 ++-- s3-file-connector/src/inode.rs | 66 +++++++++++++++++++++++++++++----- s3-file-connector/src/main.rs | 1 + 6 files changed, 79 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f41936bea..e35c80380 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1601,15 +1601,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - [[package]] name = "object" version = "0.29.0" @@ -2170,6 +2161,7 @@ dependencies = [ "tempfile", "test-case", "thiserror", + "time", "tokio", "tracing", "tracing-subscriber", @@ -2509,12 +2501,10 @@ dependencies = [ [[package]] name = "time" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "libc", - "num_threads", "serde", "time-core", "time-macros", @@ -2528,9 +2518,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" dependencies = [ "time-core", ] diff --git a/s3-client/src/mock_client.rs b/s3-client/src/mock_client.rs index 88ce2e734..6552db852 100644 --- a/s3-client/src/mock_client.rs +++ b/s3-client/src/mock_client.rs @@ -71,6 +71,7 @@ impl MockClient { pub struct MockObject { generator: Box Box<[u8]> + Send + Sync>, size: usize, + pub last_modified: OffsetDateTime, } impl MockObject { @@ -84,6 +85,7 @@ impl MockObject { Self { size: bytes.len(), generator: Box::new(move |offset, size| bytes[offset as usize..offset as usize + size].into()), + last_modified: OffsetDateTime::now_utc(), } } @@ -91,6 +93,7 @@ impl MockObject { Self { generator: Box::new(move |_offset, size| vec![v; size].into_boxed_slice()), size, + last_modified: OffsetDateTime::now_utc(), } } @@ -108,9 +111,14 @@ impl MockObject { vec.into_boxed_slice() }), size, + last_modified: OffsetDateTime::now_utc(), } } + pub fn with_last_modified(&mut self, last_modified: OffsetDateTime) { + self.last_modified = last_modified; + } + pub fn len(&self) -> usize { self.size } @@ -244,8 +252,7 @@ impl ObjectClient for MockClient { object: ObjectInfo { key: key.to_string(), size: object.size as u64, - // TODO real mtimes - last_modified: OffsetDateTime::from_unix_timestamp(0).unwrap(), + last_modified: object.last_modified, etag: "TODO".to_string(), storage_class: None, }, @@ -283,7 +290,7 @@ impl ObjectClient for MockClient { // start at the beginning of the bucket. let object_iterator = objects.range(continuation_token.unwrap_or("").to_string()..); - for (key, value) in object_iterator { + for (key, object) in object_iterator { // If the prefix is `n` characters long, and we encounter a key whose first `n` // characters are lexicographically larger than the prefix, then we can stop iterating. // Note that we cannot just do a direct comparison between the full key and prefix. For @@ -340,9 +347,8 @@ impl ObjectClient for MockClient { } else { object_vec.push(ObjectInfo { key: key.to_string(), - size: value.len() as u64, - // TODO real mtimes - last_modified: OffsetDateTime::from_unix_timestamp(0).unwrap(), + size: object.len() as u64, + last_modified: object.last_modified, etag: "TODO".to_string(), storage_class: None, }); diff --git a/s3-file-connector/Cargo.toml b/s3-file-connector/Cargo.toml index 7e1d6c147..12aa2c0b4 100644 --- a/s3-file-connector/Cargo.toml +++ b/s3-file-connector/Cargo.toml @@ -42,6 +42,7 @@ sha2 = "0.10.6" shuttle = { version = "0.5.0" } tempfile = "3.3.0" test-case = "2.2.2" +time = { version = "0.3.17", features = ["macros"] } tokio = { version = "1.23.1", features = ["rt", "macros"] } [features] diff --git a/s3-file-connector/src/fs.rs b/s3-file-connector/src/fs.rs index 36490424e..a0fe932ee 100644 --- a/s3-file-connector/src/fs.rs +++ b/s3-file-connector/src/fs.rs @@ -199,9 +199,9 @@ where ino, size: stat.size as u64, blocks: stat.size as u64 / BLOCK_SIZE, - atime: UNIX_EPOCH, - mtime: UNIX_EPOCH, // TODO - ctime: UNIX_EPOCH, + atime: stat.atime, + mtime: stat.mtime, + ctime: stat.ctime, crtime: UNIX_EPOCH, kind: (&stat.kind).into(), perm, diff --git a/s3-file-connector/src/inode.rs b/s3-file-connector/src/inode.rs index a48cfb4e7..b793e850d 100644 --- a/s3-file-connector/src/inode.rs +++ b/s3-file-connector/src/inode.rs @@ -22,7 +22,7 @@ use std::collections::{HashMap, VecDeque}; use std::ffi::{OsStr, OsString}; use std::os::unix::prelude::OsStrExt; -use std::time::Instant; +use std::time::{Instant, SystemTime, UNIX_EPOCH}; use fuser::FileType; use futures::{select_biased, FutureExt}; @@ -83,6 +83,9 @@ impl Superblock { stat_cache: RwLock::new(InodeStat { kind: InodeStatKind::Directory {}, size: 0, + atime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + mtime: UNIX_EPOCH, }), stat_cache_expiry: Instant::now(), data: InodeData::Directory { @@ -177,6 +180,9 @@ impl Superblock { let stat = InodeStat { kind: InodeStatKind::Directory {}, size: 0, + atime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + mtime: UNIX_EPOCH, }; let ino = self.inner @@ -194,9 +200,13 @@ impl Superblock { .map(|object| object.key == full_path.to_str().unwrap()) .unwrap_or(false) { + let last_modified: SystemTime = result.objects[0].last_modified.into(); let stat = InodeStat { kind: InodeStatKind::File {}, size: result.objects[0].size as usize, + atime: last_modified, + ctime: last_modified, + mtime: last_modified, }; file_state = Some(stat); } @@ -244,6 +254,9 @@ impl Superblock { let stat = InodeStat { kind: InodeStatKind::Directory {}, size: 0, + atime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + mtime: UNIX_EPOCH, }; let ino = self.inner @@ -532,6 +545,9 @@ impl ReaddirHandle { let stat = InodeStat { kind: InodeStatKind::Directory {}, size: 0, + atime: UNIX_EPOCH, + ctime: UNIX_EPOCH, + mtime: UNIX_EPOCH, }; let stat_clone = stat.clone(); @@ -556,9 +572,13 @@ impl ReaddirHandle { // Hide keys that end with '/', since they can be confused with directories .filter(|(name, _object)| valid_inode_name(name)) .flat_map(|(name, object)| { + let last_modified: SystemTime = object.last_modified.into(); let stat = InodeStat { kind: InodeStatKind::File {}, size: object.size as usize, + atime: last_modified, + ctime: last_modified, + mtime: last_modified, }; let stat_clone = stat.clone(); @@ -674,9 +694,16 @@ enum InodeData { /// Public inode stat data that can expire #[derive(Debug, Clone)] pub struct InodeStat { - // common metadata: mtime, ctime, ... + /// Size in bytes pub size: usize, + /// Time of last file content modification + pub mtime: SystemTime, + /// Time of last file metadata (or content) change + pub ctime: SystemTime, + /// Time of last access + pub atime: SystemTime, + /// Per-kind metadata pub kind: InodeStatKind, } @@ -730,6 +757,8 @@ pub struct DirEntryPlus { mod tests { use s3_client::mock_client::{MockClient, MockClientConfig, MockObject}; use test_case::test_case; + use time::macros::datetime; + use time::OffsetDateTime; use crate::fs::FUSE_ROOT_INODE; @@ -759,8 +788,11 @@ mod tests { format!("{}dir1/sdir3/file1.txt", prefix), ]; + const SOME_LAST_MODIFIED_TIME: OffsetDateTime = datetime!(2023-01-01 0:00 +0); for key in keys { - client.add_object(key, MockObject::constant(0xaa, 30)); + let mut obj = MockObject::constant(0xaa, 30); + obj.with_last_modified(SOME_LAST_MODIFIED_TIME); + client.add_object(key, obj); } let superblock = Superblock::new("test_bucket".to_string(), OsString::from(prefix)); @@ -771,37 +803,50 @@ mod tests { .lookup(&client, FUSE_ROOT_INODE, &OsString::from("dir0")) .await .expect("should exist"); - assert_eq!(dir0.stat.kind, InodeStatKind::Directory {}); + + let dir_stat_assertions = |stat: InodeStat| { + assert_eq!(stat.kind, InodeStatKind::Directory {}); + assert_eq!(stat.atime, OffsetDateTime::UNIX_EPOCH); + assert_eq!(stat.ctime, OffsetDateTime::UNIX_EPOCH); + assert_eq!(stat.mtime, OffsetDateTime::UNIX_EPOCH); + }; + + dir_stat_assertions(dir0.stat); assert_eq!(dir0.full_key, OsString::from(format!("{}dir0", prefix))); + let dir1 = superblock .lookup(&client, FUSE_ROOT_INODE, &OsString::from("dir1")) .await .expect("should exist"); - assert_eq!(dir1.stat.kind, InodeStatKind::Directory {}); + dir_stat_assertions(dir1.stat); assert_eq!(dir1.full_key, OsString::from(format!("{}dir1", prefix))); + let sdir0 = superblock .lookup(&client, dir0.ino, &OsString::from("sdir0")) .await .expect("should exist"); - assert_eq!(sdir0.stat.kind, InodeStatKind::Directory {}); + dir_stat_assertions(sdir0.stat); assert_eq!(sdir0.full_key, OsString::from(format!("{}dir0/sdir0", prefix))); + let sdir1 = superblock .lookup(&client, dir0.ino, &OsString::from("sdir1")) .await .expect("should exist"); - assert_eq!(sdir1.stat.kind, InodeStatKind::Directory {}); + dir_stat_assertions(sdir1.stat); assert_eq!(sdir1.full_key, OsString::from(format!("{}dir0/sdir1", prefix))); + let sdir2 = superblock .lookup(&client, dir1.ino, &OsString::from("sdir2")) .await .expect("should exist"); - assert_eq!(sdir2.stat.kind, InodeStatKind::Directory {}); + dir_stat_assertions(sdir2.stat); assert_eq!(sdir2.full_key, OsString::from(format!("{}dir1/sdir2", prefix))); + let sdir3 = superblock .lookup(&client, dir1.ino, &OsString::from("sdir3")) .await .expect("should exist"); - assert_eq!(sdir3.stat.kind, InodeStatKind::Directory {}); + dir_stat_assertions(sdir3.stat); assert_eq!(sdir3.full_key, OsString::from(format!("{}dir1/sdir3", prefix))); let file0 = superblock @@ -809,6 +854,9 @@ mod tests { .await .expect("should exist"); assert_eq!(file0.stat.kind, InodeStatKind::File {}); + assert_eq!(file0.stat.atime, SOME_LAST_MODIFIED_TIME); + assert_eq!(file0.stat.ctime, SOME_LAST_MODIFIED_TIME); + assert_eq!(file0.stat.mtime, SOME_LAST_MODIFIED_TIME); assert_eq!(file0.full_key, OsString::from(format!("{}dir0/file0.txt", prefix))); for (dir, sdir, ino, n) in &[ diff --git a/s3-file-connector/src/main.rs b/s3-file-connector/src/main.rs index 4f692a8a4..6bc57e973 100644 --- a/s3-file-connector/src/main.rs +++ b/s3-file-connector/src/main.rs @@ -99,6 +99,7 @@ fn main() -> anyhow::Result<()> { MountOption::RO, MountOption::DefaultPermissions, MountOption::FSName("fuse_sync".to_string()), + MountOption::NoAtime, ]; if args.auto_unmount { options.push(MountOption::AutoUnmount); From 79a3b0e63f7aaa8d73c2b11192679b80b26ffe3d Mon Sep 17 00:00:00 2001 From: Daniel Carl Jones Date: Wed, 18 Jan 2023 17:17:43 +0000 Subject: [PATCH 2/3] Update from PR feedback --- s3-client/src/mock_client.rs | 2 +- s3-file-connector/Cargo.toml | 2 +- s3-file-connector/src/fs.rs | 6 +- s3-file-connector/src/inode.rs | 171 ++++++++++++++++++--------------- 4 files changed, 96 insertions(+), 85 deletions(-) diff --git a/s3-client/src/mock_client.rs b/s3-client/src/mock_client.rs index 6552db852..d6b0b9a73 100644 --- a/s3-client/src/mock_client.rs +++ b/s3-client/src/mock_client.rs @@ -115,7 +115,7 @@ impl MockObject { } } - pub fn with_last_modified(&mut self, last_modified: OffsetDateTime) { + pub fn set_last_modified(&mut self, last_modified: OffsetDateTime) { self.last_modified = last_modified; } diff --git a/s3-file-connector/Cargo.toml b/s3-file-connector/Cargo.toml index 12aa2c0b4..6ce043d9e 100644 --- a/s3-file-connector/Cargo.toml +++ b/s3-file-connector/Cargo.toml @@ -25,6 +25,7 @@ thiserror = "1.0.34" tracing = { version = "0.1.35", default-features = false, features = ["std", "log"] } tracing-subscriber = { version = "0.3.14", features = ["fmt", "env-filter"] } nix = { version = "0.26.1", default-features = false, features = ["user"] } +time = "0.3.17" [dev-dependencies] assert_cmd = "2.0.6" @@ -42,7 +43,6 @@ sha2 = "0.10.6" shuttle = { version = "0.5.0" } tempfile = "3.3.0" test-case = "2.2.2" -time = { version = "0.3.17", features = ["macros"] } tokio = { version = "1.23.1", features = ["rt", "macros"] } [features] diff --git a/s3-file-connector/src/fs.rs b/s3-file-connector/src/fs.rs index a0fe932ee..bddb2250a 100644 --- a/s3-file-connector/src/fs.rs +++ b/s3-file-connector/src/fs.rs @@ -199,9 +199,9 @@ where ino, size: stat.size as u64, blocks: stat.size as u64 / BLOCK_SIZE, - atime: stat.atime, - mtime: stat.mtime, - ctime: stat.ctime, + atime: stat.atime.into(), + mtime: stat.mtime.into(), + ctime: stat.ctime.into(), crtime: UNIX_EPOCH, kind: (&stat.kind).into(), perm, diff --git a/s3-file-connector/src/inode.rs b/s3-file-connector/src/inode.rs index b793e850d..fade9b2fc 100644 --- a/s3-file-connector/src/inode.rs +++ b/s3-file-connector/src/inode.rs @@ -22,12 +22,13 @@ use std::collections::{HashMap, VecDeque}; use std::ffi::{OsStr, OsString}; use std::os::unix::prelude::OsStrExt; -use std::time::{Instant, SystemTime, UNIX_EPOCH}; +use std::time::Instant; use fuser::FileType; use futures::{select_biased, FutureExt}; use s3_client::ObjectClient; use thiserror::Error; +use time::OffsetDateTime; use tracing::{error, trace, warn}; use crate::sync::atomic::{AtomicU64, Ordering}; @@ -80,13 +81,7 @@ impl Superblock { // We stash the prefix in the root inode's name so that path resolution "just works" // with prefixes name: stripped_prefix, - stat_cache: RwLock::new(InodeStat { - kind: InodeStatKind::Directory {}, - size: 0, - atime: UNIX_EPOCH, - ctime: UNIX_EPOCH, - mtime: UNIX_EPOCH, - }), + stat_cache: RwLock::new(InodeStat::for_directory(OffsetDateTime::UNIX_EPOCH)), stat_cache_expiry: Instant::now(), data: InodeData::Directory { children: Default::default(), @@ -177,13 +172,7 @@ impl Superblock { { trace!(?parent, ?name, "unsuffixed lookup found a directory"); - let stat = InodeStat { - kind: InodeStatKind::Directory {}, - size: 0, - atime: UNIX_EPOCH, - ctime: UNIX_EPOCH, - mtime: UNIX_EPOCH, - }; + let stat = InodeStat::for_directory(OffsetDateTime::UNIX_EPOCH); let ino = self.inner .update_or_insert(parent, name, InodeKind::Directory, stat.clone(), Instant::now())?; @@ -200,14 +189,8 @@ impl Superblock { .map(|object| object.key == full_path.to_str().unwrap()) .unwrap_or(false) { - let last_modified: SystemTime = result.objects[0].last_modified.into(); - let stat = InodeStat { - kind: InodeStatKind::File {}, - size: result.objects[0].size as usize, - atime: last_modified, - ctime: last_modified, - mtime: last_modified, - }; + let last_modified = result.objects[0].last_modified; + let stat = InodeStat::for_file(result.objects[0].size as usize, last_modified); file_state = Some(stat); } } @@ -251,13 +234,7 @@ impl Superblock { if found_directory { trace!(?parent, ?name, kind=?InodeKind::Directory, "suffixed lookup found a directory"); - let stat = InodeStat { - kind: InodeStatKind::Directory {}, - size: 0, - atime: UNIX_EPOCH, - ctime: UNIX_EPOCH, - mtime: UNIX_EPOCH, - }; + let stat = InodeStat::for_directory(OffsetDateTime::UNIX_EPOCH); let ino = self.inner .update_or_insert(parent, name, InodeKind::Directory, stat.clone(), Instant::now())?; @@ -542,13 +519,7 @@ impl ReaddirHandle { .map(|prefix| OsString::from(&prefix[self.full_path.len()..prefix.len() - 1])) .filter(|name| valid_inode_name(name)) .map(|name| { - let stat = InodeStat { - kind: InodeStatKind::Directory {}, - size: 0, - atime: UNIX_EPOCH, - ctime: UNIX_EPOCH, - mtime: UNIX_EPOCH, - }; + let stat = InodeStat::for_directory(OffsetDateTime::UNIX_EPOCH); let stat_clone = stat.clone(); self.inner @@ -572,14 +543,8 @@ impl ReaddirHandle { // Hide keys that end with '/', since they can be confused with directories .filter(|(name, _object)| valid_inode_name(name)) .flat_map(|(name, object)| { - let last_modified: SystemTime = object.last_modified.into(); - let stat = InodeStat { - kind: InodeStatKind::File {}, - size: object.size as usize, - atime: last_modified, - ctime: last_modified, - mtime: last_modified, - }; + let last_modified = object.last_modified; + let stat = InodeStat::for_file(object.size as usize, last_modified); let stat_clone = stat.clone(); let result = self @@ -698,16 +663,40 @@ pub struct InodeStat { pub size: usize, /// Time of last file content modification - pub mtime: SystemTime, + pub mtime: OffsetDateTime, /// Time of last file metadata (or content) change - pub ctime: SystemTime, + pub ctime: OffsetDateTime, /// Time of last access - pub atime: SystemTime, + pub atime: OffsetDateTime, /// Per-kind metadata pub kind: InodeStatKind, } +impl InodeStat { + /// Initialize an [InodeStat] for a file, given some metadata. + pub fn for_file(size: usize, datetime: OffsetDateTime) -> InodeStat { + InodeStat { + size, + atime: datetime, + ctime: datetime, + mtime: datetime, + kind: InodeStatKind::File {}, + } + } + + /// Initialize an [InodeStat] for a directory, given some metadata. + pub fn for_directory(datetime: OffsetDateTime) -> InodeStat { + InodeStat { + size: 0, + atime: datetime, + ctime: datetime, + mtime: datetime, + kind: InodeStatKind::Directory {}, + } + } +} + /// Public inode stat data that can expire and differs by kind #[derive(Debug, PartialEq, Eq, Clone)] pub enum InodeStatKind { @@ -757,19 +746,30 @@ pub struct DirEntryPlus { mod tests { use s3_client::mock_client::{MockClient, MockClientConfig, MockObject}; use test_case::test_case; - use time::macros::datetime; - use time::OffsetDateTime; + use time::{Duration, OffsetDateTime}; use crate::fs::FUSE_ROOT_INODE; use super::*; + /// Check an [InodeStat] matches a series of fields. + macro_rules! assert_inode_stat { + ($stat:expr, $type:expr, $datetime:expr, $size:expr) => { + assert_eq!($stat.kind, $type); + assert_eq!($stat.atime, $datetime); + assert_eq!($stat.ctime, $datetime); + assert_eq!($stat.mtime, $datetime); + assert_eq!($stat.size, $size); + }; + } + #[test_case(""; "unprefixed")] #[test_case("test_prefix/"; "prefixed")] #[tokio::test] async fn test_lookup(prefix: &str) { + let bucket = "test_bucket"; let client_config = MockClientConfig { - bucket: "test_bucket".to_string(), + bucket: bucket.to_string(), part_size: 1024 * 1024, }; let client = Arc::new(MockClient::new(client_config)); @@ -788,14 +788,16 @@ mod tests { format!("{}dir1/sdir3/file1.txt", prefix), ]; - const SOME_LAST_MODIFIED_TIME: OffsetDateTime = datetime!(2023-01-01 0:00 +0); + let object_size = 30; + let mut last_modified = OffsetDateTime::UNIX_EPOCH; for key in keys { - let mut obj = MockObject::constant(0xaa, 30); - obj.with_last_modified(SOME_LAST_MODIFIED_TIME); + let mut obj = MockObject::constant(0xaa, object_size); + last_modified += Duration::days(1); + obj.set_last_modified(last_modified); client.add_object(key, obj); } - let superblock = Superblock::new("test_bucket".to_string(), OsString::from(prefix)); + let superblock = Superblock::new(bucket.to_string(), OsString::from(prefix)); // Try it twice to test the inode reuse path too for _ in 0..2 { @@ -803,62 +805,44 @@ mod tests { .lookup(&client, FUSE_ROOT_INODE, &OsString::from("dir0")) .await .expect("should exist"); - - let dir_stat_assertions = |stat: InodeStat| { - assert_eq!(stat.kind, InodeStatKind::Directory {}); - assert_eq!(stat.atime, OffsetDateTime::UNIX_EPOCH); - assert_eq!(stat.ctime, OffsetDateTime::UNIX_EPOCH); - assert_eq!(stat.mtime, OffsetDateTime::UNIX_EPOCH); - }; - - dir_stat_assertions(dir0.stat); + assert_inode_stat!(dir0.stat, InodeStatKind::Directory {}, OffsetDateTime::UNIX_EPOCH, 0); assert_eq!(dir0.full_key, OsString::from(format!("{}dir0", prefix))); let dir1 = superblock .lookup(&client, FUSE_ROOT_INODE, &OsString::from("dir1")) .await .expect("should exist"); - dir_stat_assertions(dir1.stat); + assert_inode_stat!(dir1.stat, InodeStatKind::Directory {}, OffsetDateTime::UNIX_EPOCH, 0); assert_eq!(dir1.full_key, OsString::from(format!("{}dir1", prefix))); let sdir0 = superblock .lookup(&client, dir0.ino, &OsString::from("sdir0")) .await .expect("should exist"); - dir_stat_assertions(sdir0.stat); + assert_inode_stat!(sdir0.stat, InodeStatKind::Directory {}, OffsetDateTime::UNIX_EPOCH, 0); assert_eq!(sdir0.full_key, OsString::from(format!("{}dir0/sdir0", prefix))); let sdir1 = superblock .lookup(&client, dir0.ino, &OsString::from("sdir1")) .await .expect("should exist"); - dir_stat_assertions(sdir1.stat); + assert_inode_stat!(sdir1.stat, InodeStatKind::Directory {}, OffsetDateTime::UNIX_EPOCH, 0); assert_eq!(sdir1.full_key, OsString::from(format!("{}dir0/sdir1", prefix))); let sdir2 = superblock .lookup(&client, dir1.ino, &OsString::from("sdir2")) .await .expect("should exist"); - dir_stat_assertions(sdir2.stat); + assert_inode_stat!(sdir2.stat, InodeStatKind::Directory {}, OffsetDateTime::UNIX_EPOCH, 0); assert_eq!(sdir2.full_key, OsString::from(format!("{}dir1/sdir2", prefix))); let sdir3 = superblock .lookup(&client, dir1.ino, &OsString::from("sdir3")) .await .expect("should exist"); - dir_stat_assertions(sdir3.stat); + assert_inode_stat!(sdir3.stat, InodeStatKind::Directory {}, OffsetDateTime::UNIX_EPOCH, 0); assert_eq!(sdir3.full_key, OsString::from(format!("{}dir1/sdir3", prefix))); - let file0 = superblock - .lookup(&client, dir0.ino, &OsString::from("file0.txt")) - .await - .expect("should exist"); - assert_eq!(file0.stat.kind, InodeStatKind::File {}); - assert_eq!(file0.stat.atime, SOME_LAST_MODIFIED_TIME); - assert_eq!(file0.stat.ctime, SOME_LAST_MODIFIED_TIME); - assert_eq!(file0.stat.mtime, SOME_LAST_MODIFIED_TIME); - assert_eq!(file0.full_key, OsString::from(format!("{}dir0/file0.txt", prefix))); - for (dir, sdir, ino, n) in &[ (0, 0, sdir0.ino, 3), (0, 1, sdir1.ino, 2), @@ -869,7 +853,15 @@ mod tests { let file = superblock .lookup(&client, *ino, &OsString::from(format!("file{}.txt", i))) .await - .expect("should exist"); + .expect("inode should exist"); + // Grab last modified time according to mock S3 + let modified_time = client + .head_object(bucket, file.full_key.to_str().unwrap()) + .await + .expect("object should exist") + .object + .last_modified; + assert_inode_stat!(file.stat, InodeStatKind::File {}, modified_time, object_size); assert_eq!( file.full_key, OsString::from(format!("{}dir{}/sdir{}/file{}.txt", prefix, dir, sdir, i)) @@ -1037,4 +1029,23 @@ mod tests { assert!(matches!(lookup, Err(InodeError::InvalidFileName(_)))); } } + + #[tokio::test] + async fn test_inodestat_constructors() { + let ts = OffsetDateTime::UNIX_EPOCH + Duration::days(90); + let file_inodestat = InodeStat::for_file(128, ts); + assert_eq!(file_inodestat.size, 128); + assert_eq!(file_inodestat.atime, ts); + assert_eq!(file_inodestat.ctime, ts); + assert_eq!(file_inodestat.mtime, ts); + assert_eq!(file_inodestat.kind, InodeStatKind::File {}); + + let ts = OffsetDateTime::UNIX_EPOCH + Duration::days(180); + let file_inodestat = InodeStat::for_directory(ts); + assert_eq!(file_inodestat.size, 0); + assert_eq!(file_inodestat.atime, ts); + assert_eq!(file_inodestat.ctime, ts); + assert_eq!(file_inodestat.mtime, ts); + assert_eq!(file_inodestat.kind, InodeStatKind::Directory {}); + } } From b23b39d5b2e619116f58e492f6f023da11dbfc97 Mon Sep 17 00:00:00 2001 From: Daniel Carl Jones Date: Wed, 18 Jan 2023 17:43:10 +0000 Subject: [PATCH 3/3] Update non-async fn to use std test attr macro --- s3-file-connector/src/inode.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/s3-file-connector/src/inode.rs b/s3-file-connector/src/inode.rs index fade9b2fc..0e4107786 100644 --- a/s3-file-connector/src/inode.rs +++ b/s3-file-connector/src/inode.rs @@ -1030,8 +1030,8 @@ mod tests { } } - #[tokio::test] - async fn test_inodestat_constructors() { + #[test] + fn test_inodestat_constructors() { let ts = OffsetDateTime::UNIX_EPOCH + Duration::days(90); let file_inodestat = InodeStat::for_file(128, ts); assert_eq!(file_inodestat.size, 128);