#![cfg(feature = "async")]
use fsys::{builder, AsyncSubstrate};
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
static C: AtomicU64 = AtomicU64::new(0);
fn tmp_path(tag: &str) -> PathBuf {
let n = C.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"fsys_async_substrate_{}_{}_{}",
std::process::id(),
n,
tag
))
}
struct Cleanup(PathBuf);
impl Drop for Cleanup {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.0);
}
}
#[tokio::test]
async fn auto_method_substrate_is_spawn_blocking() {
let fs = builder().build().expect("handle");
assert_eq!(
fs.async_substrate(),
AsyncSubstrate::SpawnBlocking,
"Auto on a non-Direct-capable runner should pick SpawnBlocking"
);
}
#[tokio::test]
async fn sync_method_substrate_is_spawn_blocking() {
let fs = builder()
.method(fsys::Method::Sync)
.build()
.expect("handle");
assert_eq!(
fs.async_substrate(),
AsyncSubstrate::SpawnBlocking,
"Method::Sync must always use SpawnBlocking"
);
}
#[tokio::test]
async fn data_method_substrate_is_spawn_blocking() {
let fs = builder()
.method(fsys::Method::Data)
.build()
.expect("handle");
assert_eq!(
fs.async_substrate(),
AsyncSubstrate::SpawnBlocking,
"Method::Data must always use SpawnBlocking (only Direct can be native)"
);
}
#[tokio::test]
async fn direct_method_pre_op_substrate_is_spawn_blocking() {
let fs = builder()
.method(fsys::Method::Direct)
.build()
.expect("handle");
assert_eq!(
fs.async_substrate(),
AsyncSubstrate::SpawnBlocking,
"Pre-first-op substrate must be SpawnBlocking (lazy ring construction)"
);
}
#[tokio::test]
async fn write_async_through_direct_works_on_either_substrate() {
let path = tmp_path("direct_write");
let _g = Cleanup(path.clone());
let fs = Arc::new(
builder()
.method(fsys::Method::Direct)
.build()
.expect("handle"),
);
fs.clone()
.write_async(&path, b"hello via Direct async".to_vec())
.await
.expect("write_async on Direct must succeed (native or fallback)");
let read = std::fs::read(&path).expect("read");
assert_eq!(read, b"hello via Direct async");
}
#[tokio::test]
async fn env_override_forces_spawn_blocking_substrate() {
let prior = std::env::var_os("FSYS_DISABLE_NATIVE_ASYNC");
unsafe {
std::env::set_var("FSYS_DISABLE_NATIVE_ASYNC", "1");
}
let fs = Arc::new(
builder()
.method(fsys::Method::Direct)
.build()
.expect("handle"),
);
let path = tmp_path("override");
let _g = Cleanup(path.clone());
let _ = fs
.clone()
.write_async(&path, b"forced fallback".to_vec())
.await;
assert_eq!(
fs.async_substrate(),
AsyncSubstrate::SpawnBlocking,
"FSYS_DISABLE_NATIVE_ASYNC=1 must force SpawnBlocking"
);
unsafe {
match prior {
Some(v) => std::env::set_var("FSYS_DISABLE_NATIVE_ASYNC", v),
None => std::env::remove_var("FSYS_DISABLE_NATIVE_ASYNC"),
}
}
}
#[tokio::test]
#[cfg(target_os = "linux")]
async fn linux_direct_async_transitions_to_native_after_first_op() {
if std::env::var_os("FSYS_DISABLE_NATIVE_ASYNC").is_some() {
return; }
let fs = Arc::new(
builder()
.method(fsys::Method::Direct)
.build()
.expect("handle"),
);
let path = tmp_path("linux_native_transition");
let _g = Cleanup(path.clone());
let _ = fs.clone().write_async(&path, vec![0xA5u8; 4096]).await;
let s = fs.async_substrate();
assert!(
s == AsyncSubstrate::NativeIoUring || s == AsyncSubstrate::SpawnBlocking,
"post-op substrate must be a valid variant; got {s:?}"
);
}
#[tokio::test]
async fn substrate_strings_match_enum_values() {
let fs = builder().build().expect("handle");
let s = fs.async_substrate();
let name = s.name();
if s.is_native() {
assert_eq!(name, "native io_uring");
} else {
assert_eq!(name, "spawn_blocking");
}
}