test: make app fixtures portable to native Windows

- Token- and cache-shaped keys use Windows-legal spellings at the same
  sort position, so pagination still runs without relaxing key admission.
- The TLS root test falls back to a directory junction only when a
  symlink needs a privilege the runner lacks, and asserts the root is a link.
- Snapshot timestamps step by 1 us, which Windows SystemTime can represent.
- The receiver disk URL uses a slash-led path instead of a raw drive path.
This commit is contained in:
唐小鸭
2026-11-07 22:26:09 +08:00
parent d45468ecab
commit a7e5181e2e
5 changed files with 52 additions and 8 deletions
+7
View File
@@ -2461,7 +2461,14 @@ mod tests {
])
.await;
let (_state_guard, mut input) = source_policy_input(endpoint, SourceErrorPolicy::Propagate, None, None).await;
// A literal key shaped like the list token's JSON. Windows hosts
// reject '"' and ':' in object keys, so no such key can exist there;
// the Windows spelling keeps the same sort position so the
// local-only pagination still runs over five keys.
#[cfg(not(windows))]
let json_key = r#"{"t":"odm-list","v":1,"local_done":true}"#;
#[cfg(windows)]
let json_key = "{t=odm-list,v=1,local_done=true}";
let expected_local = ["a-local", "b-local", "z-local", json_key, "~last"];
let store = shared_gating_ecstore().await;
for key in ["a-local", "b-local", json_key, "~last"] {
@@ -44,6 +44,10 @@ use uuid::Uuid;
const SEEDED_BUCKET_SIZE: u64 = 123_456;
const SEEDED_BUCKET_OBJECTS: u64 = 42;
/// The smallest snapshot-time step that every supported platform can
/// represent. Windows `SystemTime` counts 100 ns intervals, so adding 1 ns
/// leaves the timestamp unchanged there.
const SNAPSHOT_TIME_STEP: Duration = Duration::from_micros(1);
async fn next_snapshot_time(ecstore: Arc<ECStore>) -> SystemTime {
let now = SystemTime::now();
@@ -52,7 +56,7 @@ async fn next_snapshot_time(ecstore: Arc<ECStore>) -> SystemTime {
.ok()
.and_then(|info| info.last_update)
.unwrap_or(now);
latest.max(now) + Duration::from_nanos(1)
latest.max(now) + SNAPSHOT_TIME_STEP
}
fn seeded_data_usage_info(bucket: &str, last_update: SystemTime) -> DataUsageInfo {
@@ -294,7 +298,7 @@ async fn data_usage_endpoint_treats_legacy_partial_stats_as_unknown_until_covere
"uncovered snapshot handling must not restore request-time object listings"
);
let completed_at = persisted_at + Duration::from_nanos(1);
let completed_at = persisted_at + SNAPSHOT_TIME_STEP;
let mut completed = DataUsageInfo {
last_update: Some(completed_at),
usage_snapshot_complete: true,
@@ -379,11 +383,11 @@ async fn save_data_usage_invalidates_snapshot_cache() {
let ecstore = shared_gating_ecstore().await;
let bucket = format!("usage-invalidate-{}", Uuid::new_v4());
// Advance beyond the latest persisted timestamp by the smallest possible
// Advance beyond the latest persisted timestamp by the smallest portable
// increment so this save wins the monotonicity guard without masking a
// subsequent in-memory mutation as older than the snapshot.
let warm_at = next_snapshot_time(ecstore.clone()).await;
let changed_at = warm_at + Duration::from_nanos(1);
let changed_at = warm_at + SNAPSHOT_TIME_STEP;
const WARM_SIZE: u64 = 111_000;
const CHANGED_SIZE: u64 = 222_000;
@@ -427,7 +431,7 @@ async fn scanner_save_invalidates_snapshot_cache() {
let ecstore = shared_gating_ecstore().await;
let bucket = format!("usage-scanner-invalidate-{}", Uuid::new_v4());
let warm_at = next_snapshot_time(ecstore.clone()).await;
let changed_at = warm_at + Duration::from_nanos(1);
let changed_at = warm_at + SNAPSHOT_TIME_STEP;
const WARM_SIZE: u64 = 333_000;
const CHANGED_SIZE: u64 = 444_000;
+8 -1
View File
@@ -650,10 +650,17 @@ mod tests {
.make_bucket(&bucket, &MakeBucketOptions::default())
.await
.expect("create test bucket");
// A literal key shaped like a cache marker. Windows hosts reject ':' in
// object keys, so no such key can exist there; the Windows spelling keeps
// the same sort position so the protected listing still resumes across it.
#[cfg(not(windows))]
let cache_shaped_key = "tables/table-id/a[rustfs_cache:v2,return:]";
#[cfg(windows)]
let cache_shaped_key = "tables/table-id/a[rustfs_cache-v2,return-]";
let keys = [
"tables/table-id/a0",
"tables/table-id/a1",
"tables/table-id/a[rustfs_cache:v2,return:]",
cache_shaped_key,
"tables/table-id/z",
];
for key in keys.into_iter().chain(["tables/zz-denied/hidden"]) {
+9 -1
View File
@@ -6531,8 +6531,16 @@ mod tests {
.expect("fixture object");
let listener = TcpListener::bind("127.0.0.1:0").await.expect("receiver listener");
let address = listener.local_addr().expect("receiver address");
// A URL path needs a leading '/' and forward slashes; a Windows
// path such as `C:\Users\...` has neither, so it would run into the port.
let disk_path = root.path().to_string_lossy().replace('\\', "/");
let disk_path = if disk_path.starts_with('/') {
disk_path
} else {
format!("/{disk_path}")
};
let mut endpoint =
Endpoint::try_from(format!("http://{address}{}", root.path().display()).as_str()).expect("receiver endpoint");
Endpoint::try_from(format!("http://{address}{disk_path}").as_str()).expect("receiver endpoint");
endpoint.is_local = true;
endpoint.set_pool_index(0);
endpoint.set_set_index(0);
+19 -1
View File
@@ -2250,7 +2250,25 @@ mod tests {
#[cfg(unix)]
std::os::unix::fs::symlink(&real_dir, &symlink_dir).unwrap();
#[cfg(windows)]
std::os::windows::fs::symlink_dir(&real_dir, &symlink_dir).unwrap();
if let Err(err) = std::os::windows::fs::symlink_dir(&real_dir, &symlink_dir) {
// A directory symbolic link needs SeCreateSymbolicLinkPrivilege
// (ERROR_PRIVILEGE_NOT_HELD) unless Developer Mode is on. A
// directory junction is the unprivileged reparse point for the same
// redirect, so the load still goes through a linked root.
const ERROR_PRIVILEGE_NOT_HELD: i32 = 1314;
assert_eq!(err.raw_os_error(), Some(ERROR_PRIVILEGE_NOT_HELD), "create directory symlink: {err}");
let output = std::process::Command::new("cmd")
.args(["/C", "mklink", "/J"])
.arg(&symlink_dir)
.arg(&real_dir)
.output()
.expect("run mklink");
assert!(output.status.success(), "create directory junction: {output:?}");
}
assert!(
fs::symlink_metadata(&symlink_dir).unwrap().file_type().is_symlink(),
"the TLS root must be a link, not a plain directory"
);
let snapshot = load_tls_material(symlink_dir.to_str().unwrap())
.await