use super::*;
use tenferro_tensor::{ErrorKind, ValidationKind};
#[test]
fn cpu_context_from_env_respects_rayon_num_threads() {
with_rayon_num_threads(Some("3"), || {
let ctx = CpuContext::from_env();
assert_eq!(ctx.num_threads(), 3);
});
}
#[test]
fn cpu_context_from_env_falls_back_to_affinity_when_rayon_num_threads_is_absent() {
with_rayon_num_threads(None, || {
let ctx = CpuContext::from_env();
assert_eq!(ctx.num_threads(), crate::available_parallelism());
});
}
#[test]
fn cpu_context_from_env_falls_back_to_single_threaded_when_rayon_num_threads_is_invalid() {
with_rayon_num_threads(Some("not-a-number"), || {
let ctx = CpuContext::from_env();
assert_eq!(ctx.num_threads(), 1);
});
}
#[test]
fn cpu_context_try_from_env_rejects_invalid_rayon_num_threads() {
with_rayon_num_threads(Some("not-a-number"), || {
assert!(CpuContext::try_from_env().is_err());
});
}
#[cfg(unix)]
#[test]
fn cpu_context_try_from_env_rejects_non_unicode_rayon_num_threads() {
use std::os::unix::ffi::OsStringExt;
let _guard = RayonNumThreadsEnvGuard::new(None);
std::env::set_var("RAYON_NUM_THREADS", OsString::from_vec(vec![0xff]));
let err = CpuContext::try_from_env().unwrap_err();
assert!(matches!(
&err,
Error::Extension {
op: "CpuContext::try_from_env",
family: "cpu",
kind: ErrorKind::Validation(ValidationKind::InvalidArgument),
..
}
));
assert!(std::error::Error::source(&err).is_some());
}
#[test]
fn cpu_context_try_from_env_rejects_zero_rayon_num_threads() {
with_rayon_num_threads(Some("0"), || {
let err = match CpuContext::try_from_env() {
Ok(_) => panic!("expected zero RAYON_NUM_THREADS to be rejected"),
Err(err) => err,
};
assert!(format!("{err}").contains("CpuContext::try_from_env"));
assert!(format!("{err}").contains("thread count must be at least 1"));
});
}
#[test]
fn cpu_context_with_threads_zero_returns_error() {
assert!(CpuContext::with_threads(0).is_err());
}
#[test]
fn cpu_backend_new_matches_context_from_env() {
with_rayon_num_threads(Some("2"), || {
let backend = CpuBackend::new();
assert_eq!(backend.num_threads(), 2);
});
}
#[test]
fn cpu_backend_new_falls_back_to_affinity_when_rayon_num_threads_is_absent() {
with_rayon_num_threads(None, || {
let backend = CpuBackend::new();
assert_eq!(backend.num_threads(), crate::available_parallelism());
});
}
#[test]
fn cpu_backend_try_new_propagates_invalid_rayon_num_threads() {
with_rayon_num_threads(Some("not-a-number"), || {
assert!(CpuBackend::try_new().is_err());
});
}
#[test]
fn test_with_backend_session_runs_compiled_ops() {
let mut backend = CpuBackend::with_threads(2).unwrap();
let result = backend.with_backend_session(|session| {
session
.add(
&Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0, 2.0]).unwrap()),
&Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![3.0, 4.0]).unwrap()),
)
.unwrap()
});
assert_eq!(get_f64(&result, &[0]), 4.0);
assert_eq!(get_f64(&result, &[1]), 6.0);
}
#[test]
fn cpu_context_install_enters_owned_pool() {
let ctx = CpuContext::with_threads(2).unwrap();
let seen_threads = ctx.install(rayon::current_num_threads);
assert_eq!(seen_threads, 2);
}
#[test]
fn cpu_install_accepts_send_state() {
let ctx = CpuContext::with_threads(2).unwrap();
let state = Arc::new(41usize);
let seen = ctx.install(|| *state + 1);
assert_eq!(seen, 42);
let backend = CpuBackend::with_threads(2).unwrap();
let state = Arc::new(20usize);
let seen = backend.install(|| *state + 2);
assert_eq!(seen, 22);
}
#[test]
fn cpu_backend_multi_operation_session_enters_executor_once() {
let context = Arc::new(CpuContext::with_threads(2).unwrap());
let mut backend = CpuBackend::from_context(Arc::clone(&context));
let lhs = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0]).unwrap());
let rhs = Tensor::F64(TypedTensor::from_vec_col_major(vec![2], vec![3.0_f64, 4.0]).unwrap());
let before = context.executor_install_calls_for_test();
backend.with_backend_session(|session| {
session.add(&lhs, &rhs).unwrap();
session.neg(&lhs).unwrap();
session.mul(&lhs, &rhs).unwrap();
session
.dot_general(
&lhs,
&rhs,
&DotGeneralConfig {
lhs_contracting_dims: vec![0],
rhs_contracting_dims: vec![0],
lhs_batch_dims: vec![],
rhs_batch_dims: vec![],
},
)
.unwrap();
});
let install_delta = context.executor_install_calls_for_test() - before;
match backend.execution_info().execution_mode() {
crate::CpuExecutionMode::ProviderDefaultExclusive => assert_eq!(install_delta, 4),
_ => assert_eq!(install_delta, 1),
}
let before_standalone = context.executor_install_calls_for_test();
backend.add(&lhs, &rhs).unwrap();
assert_eq!(
context.executor_install_calls_for_test() - before_standalone,
1
);
}
#[test]
fn cpu_backend_shared_context() {
let ctx = Arc::new(CpuContext::with_threads(3).unwrap());
let b1 = CpuBackend::from_context(ctx.clone());
let b2 = CpuBackend::from_context(ctx);
assert_eq!(b1.context_id_for_test(), b2.context_id_for_test());
}
#[test]
fn cpu_affinity_available_parallelism_reports_positive_count() {
assert!(crate::available_parallelism() >= 1);
}
#[test]
fn cpu_backend_from_context_shares_runtime_owner() {
let ctx = Arc::new(CpuContext::with_threads(3).unwrap());
let b1 = CpuBackend::from_context(ctx.clone());
let b2 = CpuBackend::from_context(ctx);
assert_eq!(b1.num_threads(), 3);
assert_eq!(b2.num_threads(), 3);
}
#[test]
fn performance_notes_match_current_cpu_threading_contract() {
let notes = include_str!("../../../../../docs/performance/tt-inner-product-overhead.md");
let performance_tips = include_str!("../../../../../PERFORMANCE_TIPS.md");
assert!(
!notes.contains("The faer backend is therefore run without a tenferro-owned Rayon pool")
&& notes.contains("maps multi-threaded execution to explicit `Par::rayon(n)`")
&& !notes.contains("The global-Rayon columns became the production policy"),
"performance notes must describe the explicit CpuContext thread budget, not ambient global-Rayon policy"
);
assert!(
performance_tips.contains("explicit `Par::rayon(n)`")
&& !performance_tips
.contains("Use `Par::Seq` for one-thread contexts and `Par::rayon(0)`"),
"performance tips must derive Faer parallelism from the configured CpuContext degree"
);
}
#[test]
fn cpu_context_with_threads_reports_requested_size() {
let ctx = CpuContext::with_threads(2).unwrap();
assert_eq!(ctx.num_threads(), 2);
}
#[test]
fn cpu_context_install_executes_closure() {
let ctx = CpuContext::with_threads(1).unwrap();
let seen = ctx.install(|| 1 + 1);
assert_eq!(seen, 2);
}
fn env_lock() -> MutexGuard<'static, ()> {
static ENV_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
ENV_LOCK
.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
struct RayonNumThreadsEnvGuard {
_lock: MutexGuard<'static, ()>,
prev: Option<OsString>,
}
impl RayonNumThreadsEnvGuard {
fn new(value: Option<&str>) -> Self {
let lock = env_lock();
let prev = std::env::var_os("RAYON_NUM_THREADS");
match value {
Some(value) => std::env::set_var("RAYON_NUM_THREADS", value),
None => std::env::remove_var("RAYON_NUM_THREADS"),
}
Self { _lock: lock, prev }
}
}
impl Drop for RayonNumThreadsEnvGuard {
fn drop(&mut self) {
match self.prev.take() {
Some(value) => std::env::set_var("RAYON_NUM_THREADS", value),
None => std::env::remove_var("RAYON_NUM_THREADS"),
}
}
}
fn with_rayon_num_threads<T>(value: Option<&str>, f: impl FnOnce() -> T) -> T {
let _guard = RayonNumThreadsEnvGuard::new(value);
f()
}