use pi_async_fs::{
FileMetadata, FileTimes, FileType, PortablePermissions, ResourceVersion,
};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use std::time::{Duration, UNIX_EPOCH};
#[test]
fn test_file_type_value_semantics_cover_every_public_variant() {
let kinds = [
FileType::RegularFile,
FileType::Directory,
FileType::SymbolicLink,
FileType::RemoteObject,
FileType::BlockDevice,
FileType::CharacterDevice,
FileType::Fifo,
FileType::Socket,
FileType::Other,
FileType::Unknown,
];
let displays = [
"regular file",
"directory",
"symbolic link",
"remote object",
"block device",
"character device",
"fifo",
"socket",
"other",
"unknown",
];
let debugs = [
"RegularFile",
"Directory",
"SymbolicLink",
"RemoteObject",
"BlockDevice",
"CharacterDevice",
"Fifo",
"Socket",
"Other",
"Unknown",
];
for ((kind, display), debug) in kinds.iter().zip(displays).zip(debugs) {
assert_eq!(kind.to_string(), display);
assert_eq!(format!("{kind:?}"), debug);
assert_eq!(kind, &kind.clone());
let mut original_hasher = DefaultHasher::new();
kind.hash(&mut original_hasher);
let mut clone_hasher = DefaultHasher::new();
kind.clone().hash(&mut clone_hasher);
assert_eq!(original_hasher.finish(), clone_hasher.finish());
}
for adjacent in kinds.windows(2) {
assert!(adjacent[0] < adjacent[1]);
}
}
#[test]
fn test_portable_permissions_cover_all_known_and_unknown_combinations() {
let permissions = [
PortablePermissions {
read_only: None,
executable: None,
},
PortablePermissions {
read_only: None,
executable: Some(false),
},
PortablePermissions {
read_only: None,
executable: Some(true),
},
PortablePermissions {
read_only: Some(false),
executable: None,
},
PortablePermissions {
read_only: Some(false),
executable: Some(false),
},
PortablePermissions {
read_only: Some(false),
executable: Some(true),
},
PortablePermissions {
read_only: Some(true),
executable: None,
},
PortablePermissions {
read_only: Some(true),
executable: Some(false),
},
PortablePermissions {
read_only: Some(true),
executable: Some(true),
},
];
let displays = [
"read-only: unknown, executable: unknown",
"read-only: unknown, executable: no",
"read-only: unknown, executable: yes",
"read-only: no, executable: unknown",
"read-only: no, executable: no",
"read-only: no, executable: yes",
"read-only: yes, executable: unknown",
"read-only: yes, executable: no",
"read-only: yes, executable: yes",
];
for (permission, display) in permissions.iter().zip(displays) {
assert_eq!(permission.to_string(), display);
assert_eq!(permission, &permission.clone());
let mut original_hasher = DefaultHasher::new();
permission.hash(&mut original_hasher);
let mut clone_hasher = DefaultHasher::new();
permission.clone().hash(&mut clone_hasher);
assert_eq!(original_hasher.finish(), clone_hasher.finish());
}
assert_eq!(
format!("{:?}", permissions[0]),
"PortablePermissions { read_only: None, executable: None }"
);
for adjacent in permissions.windows(2) {
assert!(adjacent[0] < adjacent[1]);
}
}
#[test]
fn test_file_times_format_known_missing_and_pre_epoch_values_as_datetimes() {
let times = FileTimes {
created: Some(UNIX_EPOCH),
modified: Some(UNIX_EPOCH + Duration::new(1, 250_000_000)),
accessed: Some(UNIX_EPOCH - Duration::from_nanos(100)),
metadata_changed: None,
};
let display = times.to_string();
let debug = format!("{times:?}");
assert_eq!(
display,
"created: 1970-01-01T00:00:00.000000000Z, \
modified: 1970-01-01T00:00:01.250000000Z, \
accessed: 1969-12-31T23:59:59.999999900Z, \
metadata changed: unknown"
);
for expected in [
"1970-01-01T00:00:00.000000000Z",
"1970-01-01T00:00:01.250000000Z",
"1969-12-31T23:59:59.999999900Z",
"unknown",
] {
assert!(debug.contains(expected));
}
}
#[test]
fn test_file_times_value_semantics_follow_field_values_and_declaration_order() {
let times = [
FileTimes {
created: None,
modified: None,
accessed: None,
metadata_changed: None,
},
FileTimes {
created: Some(UNIX_EPOCH),
modified: None,
accessed: None,
metadata_changed: None,
},
FileTimes {
created: Some(UNIX_EPOCH),
modified: Some(UNIX_EPOCH),
accessed: None,
metadata_changed: None,
},
FileTimes {
created: Some(UNIX_EPOCH),
modified: Some(UNIX_EPOCH),
accessed: Some(UNIX_EPOCH),
metadata_changed: None,
},
FileTimes {
created: Some(UNIX_EPOCH),
modified: Some(UNIX_EPOCH),
accessed: Some(UNIX_EPOCH),
metadata_changed: Some(UNIX_EPOCH),
},
FileTimes {
created: Some(UNIX_EPOCH + Duration::from_secs(1)),
modified: None,
accessed: None,
metadata_changed: None,
},
];
for time in × {
let cloned = time.clone();
assert_eq!(time, &cloned);
let mut original_hasher = DefaultHasher::new();
time.hash(&mut original_hasher);
let mut clone_hasher = DefaultHasher::new();
cloned.hash(&mut clone_hasher);
assert_eq!(original_hasher.finish(), clone_hasher.finish());
}
for adjacent in times.windows(2) {
assert!(adjacent[0] < adjacent[1]);
}
}
#[test]
fn test_file_times_format_extreme_representable_system_times_without_panicking() {
let magnitude = Duration::new(9_000_000_000_000, 7);
let future = UNIX_EPOCH.checked_add(magnitude);
let past = UNIX_EPOCH.checked_sub(magnitude);
let times = FileTimes {
created: future,
modified: None,
accessed: past,
metadata_changed: None,
};
let display = times.to_string();
let debug = format!("{times:?}");
if future.is_some() {
assert!(display.contains("unix+9000000000000.000000007s"));
assert!(debug.contains("unix+9000000000000.000000007s"));
}
if past.is_some() {
assert!(display.contains("unix-9000000000000.000000007s"));
assert!(debug.contains("unix-9000000000000.000000007s"));
}
}
#[test]
fn test_resource_version_preserves_locator_and_opaque_token_allocation() {
for token in [
Arc::<[u8]>::from([]),
Arc::<[u8]>::from([0x00, 0xff, 0x80, 0x2f]),
] {
let locator = String::from("objects/example");
let version = ResourceVersion::new(locator.clone(), Arc::clone(&token));
assert_eq!(version.locator(), &locator);
assert_eq!(version.token(), token.as_ref());
let (returned_locator, returned_token) = version.into_parts();
assert_eq!(returned_locator, locator);
assert!(Arc::ptr_eq(&returned_token, &token));
}
}
#[test]
fn test_resource_version_clone_shares_token_bytes() {
let token = Arc::<[u8]>::from([1, 2, 3, 4]);
let original = ResourceVersion::new(String::from("objects/example"), token);
let cloned = original.clone();
let (original_locator, original_token) = original.into_parts();
let (cloned_locator, cloned_token) = cloned.into_parts();
assert_eq!(original_locator, cloned_locator);
assert!(Arc::ptr_eq(&original_token, &cloned_token));
}
#[test]
fn test_resource_version_diagnostic_formats_redact_token_content() {
let version = ResourceVersion::new(
String::from("objects/example"),
Arc::<[u8]>::from(*b"secret"),
);
let display = version.to_string();
let debug = format!("{version:?}");
assert_eq!(
display,
"objects/example (opaque version token: 6 bytes)"
);
assert_eq!(
debug,
"ResourceVersion { locator: \"objects/example\", token_len: 6 }"
);
assert!(!display.contains("secret"));
assert!(!debug.contains("secret"));
}
#[test]
fn test_resource_version_value_semantics_use_locator_then_token_bytes() {
let versions = [
ResourceVersion::new(String::from("a"), Arc::<[u8]>::from([1])),
ResourceVersion::new(String::from("a"), Arc::<[u8]>::from([1, 0])),
ResourceVersion::new(String::from("a"), Arc::<[u8]>::from([2])),
ResourceVersion::new(String::from("b"), Arc::<[u8]>::from([])),
];
let equal_from_distinct_allocation =
ResourceVersion::new(String::from("a"), Arc::<[u8]>::from([1]));
assert_eq!(versions[0], equal_from_distinct_allocation);
assert_ne!(versions[0], versions[1]);
assert_ne!(versions[0], versions[3]);
for adjacent in versions.windows(2) {
assert!(adjacent[0] < adjacent[1]);
}
let mut left_hasher = DefaultHasher::new();
versions[0].hash(&mut left_hasher);
let mut right_hasher = DefaultHasher::new();
equal_from_distinct_allocation.hash(&mut right_hasher);
assert_eq!(left_hasher.finish(), right_hasher.finish());
}
#[test]
fn test_file_metadata_constructor_preserves_all_length_states_and_fields() {
for byte_len in [None, Some(0), Some(u64::MAX)] {
let metadata = FileMetadata::new(
FileType::RegularFile,
byte_len,
PortablePermissions {
read_only: Some(false),
executable: None,
},
FileTimes {
created: Some(UNIX_EPOCH),
modified: None,
accessed: None,
metadata_changed: None,
},
Some(ResourceVersion::new(
String::from("objects/example"),
Arc::<[u8]>::from([0xff]),
)),
);
assert_eq!(metadata.file_type, FileType::RegularFile);
assert_eq!(metadata.byte_len, byte_len);
assert_eq!(metadata.permissions.read_only, Some(false));
assert_eq!(metadata.permissions.executable, None);
assert_eq!(metadata.times.created, Some(UNIX_EPOCH));
let version = metadata.version.expect("version must be preserved");
assert_eq!(version.locator(), "objects/example");
assert_eq!(version.token(), &[0xff]);
}
}
#[test]
fn test_file_metadata_clone_preserves_snapshot_and_shares_version_token() {
let metadata = FileMetadata::new(
FileType::RegularFile,
Some(42),
PortablePermissions {
read_only: Some(true),
executable: Some(false),
},
FileTimes {
created: None,
modified: Some(UNIX_EPOCH),
accessed: None,
metadata_changed: None,
},
Some(ResourceVersion::new(
String::from("objects/example"),
Arc::<[u8]>::from([1, 2, 3]),
)),
);
let cloned = metadata.clone();
assert_eq!(metadata.file_type, cloned.file_type);
assert_eq!(metadata.byte_len, cloned.byte_len);
assert_eq!(metadata.permissions, cloned.permissions);
assert_eq!(metadata.times, cloned.times);
let (_, original_token) = metadata.version.expect("original version").into_parts();
let (_, cloned_token) = cloned.version.expect("cloned version").into_parts();
assert!(Arc::ptr_eq(&original_token, &cloned_token));
}
#[test]
fn test_file_metadata_diagnostic_formats_preserve_unknowns_and_redact_versions() {
let metadata = FileMetadata::new(
FileType::RegularFile,
Some(42),
PortablePermissions {
read_only: Some(true),
executable: Some(false),
},
FileTimes {
created: None,
modified: Some(UNIX_EPOCH),
accessed: None,
metadata_changed: None,
},
Some(ResourceVersion::new(
String::from("objects/example"),
Arc::<[u8]>::from(*b"secret"),
)),
);
let display = metadata.to_string();
let debug = format!("{metadata:?}");
assert_eq!(
display,
"type: regular file, byte length: 42 bytes, \
permissions: read-only: yes, executable: no, \
times: created: unknown, modified: 1970-01-01T00:00:00.000000000Z, \
accessed: unknown, metadata changed: unknown, \
version: objects/example (opaque version token: 6 bytes)"
);
assert_eq!(
debug,
"FileMetadata { file_type: RegularFile, byte_len: Some(42), \
permissions: PortablePermissions { read_only: Some(true), executable: Some(false) }, \
times: FileTimes { created: unknown, modified: 1970-01-01T00:00:00.000000000Z, \
accessed: unknown, metadata_changed: unknown }, \
version: Some(ResourceVersion { locator: \"objects/example\", token_len: 6 }) }"
);
assert!(!display.contains("secret"));
assert!(!debug.contains("secret"));
let unknown = FileMetadata::<String>::new(
FileType::Directory,
None,
PortablePermissions {
read_only: None,
executable: None,
},
FileTimes {
created: None,
modified: None,
accessed: None,
metadata_changed: None,
},
None,
)
.to_string();
assert!(unknown.contains("byte length: unknown"));
assert!(unknown.ends_with("version: none"));
}
#[test]
fn test_file_metadata_value_semantics_follow_public_field_order() {
let make_metadata = |
file_type,
byte_len,
read_only,
created,
version: Option<ResourceVersion<String>>,
| {
FileMetadata::new(
file_type,
byte_len,
PortablePermissions {
read_only,
executable: None,
},
FileTimes {
created,
modified: None,
accessed: None,
metadata_changed: None,
},
version,
)
};
let metadata = [
make_metadata(FileType::RegularFile, None, None, None, None),
make_metadata(FileType::RegularFile, Some(0), None, None, None),
make_metadata(
FileType::RegularFile,
Some(0),
Some(false),
None,
None,
),
make_metadata(
FileType::RegularFile,
Some(0),
Some(false),
Some(UNIX_EPOCH),
None,
),
make_metadata(
FileType::RegularFile,
Some(0),
Some(false),
Some(UNIX_EPOCH),
Some(ResourceVersion::new(
String::from("objects/example"),
Arc::<[u8]>::from([1]),
)),
),
make_metadata(FileType::Directory, None, None, None, None),
];
let equal_from_distinct_token_allocation = make_metadata(
FileType::RegularFile,
Some(0),
Some(false),
Some(UNIX_EPOCH),
Some(ResourceVersion::new(
String::from("objects/example"),
Arc::<[u8]>::from([1]),
)),
);
assert_eq!(metadata[4], equal_from_distinct_token_allocation);
for adjacent in metadata.windows(2) {
assert!(adjacent[0] < adjacent[1]);
}
let mut left_hasher = DefaultHasher::new();
metadata[4].hash(&mut left_hasher);
let mut right_hasher = DefaultHasher::new();
equal_from_distinct_token_allocation.hash(&mut right_hasher);
assert_eq!(left_hasher.finish(), right_hasher.finish());
}