use crate::array_utils::{label_counts, unique_labels};
use crate::data_generation::*;
use crate::multiclass::type_of_target;
use crate::validation::check_finite;
use proptest::prelude::*;
use scirs2_core::ndarray::{Array1, Array2};
proptest! {
#[test]
fn test_make_classification_properties(
n_samples in 10..100usize,
n_features in 2..10usize,
n_classes in 2..5usize,
flip_y in 0.0..0.3f64,
class_sep in 0.5..3.0f64
) {
let result = make_classification(
n_samples, n_features, n_classes,
None, None, flip_y, class_sep, Some(42)
);
if let Ok((x, y)) = result {
prop_assert_eq!(x.shape(), &[n_samples, n_features]);
prop_assert_eq!(y.len(), n_samples);
for &label in y.iter() {
prop_assert!(label >= 0 && label < n_classes as i32);
}
let unique_classes: std::collections::HashSet<i32> = y.iter().copied().collect();
if flip_y < 0.9 {
prop_assert!(!unique_classes.is_empty());
}
for &val in x.iter() {
prop_assert!(val.is_finite());
}
}
}
#[test]
fn test_make_regression_properties(
n_samples in 10..100usize,
n_features in 2..10usize,
noise in 0.0..2.0f64,
bias in -10.0..10.0f64
) {
let result = make_regression(
n_samples, n_features, Some(n_features),
noise, bias, Some(42)
);
if let Ok((x, y)) = result {
prop_assert_eq!(x.shape(), &[n_samples, n_features]);
prop_assert_eq!(y.len(), n_samples);
for &val in x.iter() {
prop_assert!(val.is_finite());
}
for &val in y.iter() {
prop_assert!(val.is_finite());
}
}
}
#[test]
fn test_make_blobs_properties(
n_samples in 10..100usize,
n_features in 2..8usize,
centers in 2..6usize,
cluster_std in 0.1..3.0f64
) {
let result = make_blobs(
n_samples, n_features, Some(centers),
cluster_std, (-10.0, 10.0), Some(42)
);
if let Ok((x, y)) = result {
prop_assert_eq!(x.shape(), &[n_samples, n_features]);
prop_assert_eq!(y.len(), n_samples);
for &label in y.iter() {
prop_assert!(label >= 0 && label < centers as i32);
}
let unique_clusters: std::collections::HashSet<i32> = y.iter().copied().collect();
prop_assert_eq!(unique_clusters.len(), centers);
for &val in x.iter() {
prop_assert!(val.is_finite());
}
}
}
#[test]
fn test_check_finite_2d_properties(
values in prop::collection::vec(-100.0..100.0f64, 4..40)
) {
let size = values.len();
let rows = (size as f64).sqrt() as usize;
let cols = size / rows;
let actual_size = rows * cols;
if actual_size > 0 {
let trimmed_values: Vec<f64> = values.into_iter().take(actual_size).collect();
let array = Array2::from_shape_vec((rows, cols), trimmed_values).expect("operation should succeed");
prop_assert!(check_finite(&array).is_ok());
}
}
#[test]
fn test_unique_labels_properties(
labels in prop::collection::vec(0..10i32, 1..20)
) {
let array = Array1::from_vec(labels.clone());
let unique = unique_labels(&array);
let expected_unique: std::collections::HashSet<i32> = labels.into_iter().collect();
let actual_unique: std::collections::HashSet<i32> = unique.into_iter().collect();
prop_assert_eq!(expected_unique, actual_unique);
}
#[test]
fn test_label_counts_properties(
labels in prop::collection::vec(0..5i32, 1..50)
) {
let array = Array1::from_vec(labels.clone());
let counts = label_counts(&array);
let total_count: usize = counts.values().sum();
prop_assert_eq!(total_count, labels.len());
for &count in counts.values() {
prop_assert!(count > 0);
}
let manual_counts: std::collections::HashMap<i32, usize> =
labels.iter().fold(std::collections::HashMap::new(), |mut acc, &label| {
*acc.entry(label).or_insert(0) += 1;
acc
});
prop_assert_eq!(counts, manual_counts);
}
#[test]
fn test_type_of_target_properties(
target_type in 0..3usize,
size in 5..30usize
) {
let y = match target_type {
0 => Array1::from_vec((0..size).map(|i| (i % 2) as i32).collect()), 1 => Array1::from_vec((0..size).map(|i| (i % 5) as i32).collect()), _ => Array1::from_vec(vec![0i32; size]), };
let target_type_result = type_of_target(&y);
if let Ok(target_type_str) = target_type_result {
prop_assert!(target_type_str == "binary" ||
target_type_str == "multiclass" ||
target_type_str == "unknown");
}
}
#[test]
fn test_train_test_split_properties(
n_samples in 10..100usize,
n_features in 2..10usize,
test_size in 0.1..0.5f64
) {
let _x = Array2::<f64>::zeros((n_samples, n_features));
let _y = Array1::from_vec((0..n_samples).map(|i| (i % 3) as i32).collect());
use crate::random::train_test_split_indices;
let result = train_test_split_indices(n_samples, test_size, true, Some(42));
if let Ok((train_indices, test_indices)) = result {
let total_samples = train_indices.len() + test_indices.len();
prop_assert_eq!(total_samples, n_samples);
let actual_test_ratio = test_indices.len() as f64 / n_samples as f64;
let tolerance = 0.1; prop_assert!((actual_test_ratio - test_size).abs() <= tolerance);
let mut all_indices = train_indices.clone();
all_indices.extend(test_indices.clone());
all_indices.sort_unstable();
let expected_indices: Vec<usize> = (0..n_samples).collect();
prop_assert_eq!(all_indices, expected_indices);
}
}
#[test]
fn test_bootstrap_indices_properties(
n_samples in 10..100usize
) {
use crate::random::bootstrap_indices;
let indices = bootstrap_indices(n_samples, Some(42));
prop_assert_eq!(indices.len(), n_samples);
for &idx in indices.iter() {
prop_assert!(idx < n_samples);
}
if n_samples > 10 {
let unique_indices: std::collections::HashSet<usize> = indices.into_iter().collect();
prop_assert!(unique_indices.len() <= n_samples);
}
}
#[test]
fn test_stratified_split_properties(
n_samples in 20..100usize,
n_classes in 2..5usize,
test_size in 0.2..0.5f64
) {
use crate::random::stratified_split_indices;
let labels: Vec<i32> = (0..n_samples)
.map(|i| (i % n_classes) as i32)
.collect();
let result = stratified_split_indices(&labels, test_size, Some(42));
if let Ok((train_indices, test_indices)) = result {
prop_assert_eq!(train_indices.len() + test_indices.len(), n_samples);
let train_labels: Vec<i32> = train_indices.iter()
.map(|&i| labels[i])
.collect();
let test_labels: Vec<i32> = test_indices.iter()
.map(|&i| labels[i])
.collect();
let train_classes: std::collections::HashSet<i32> = train_labels.into_iter().collect();
let test_classes: std::collections::HashSet<i32> = test_labels.into_iter().collect();
let overlap: std::collections::HashSet<_> = train_classes.intersection(&test_classes).collect();
if test_size < 0.9 && n_samples >= n_classes * 4 {
prop_assert!(!overlap.is_empty());
}
}
}
#[test]
fn test_shuffle_indices_properties(
n_samples in 5..50usize
) {
use crate::random::random_permutation;
let indices = random_permutation(n_samples, Some(42));
prop_assert_eq!(indices.len(), n_samples);
let mut sorted_indices = indices.clone();
sorted_indices.sort_unstable();
let expected: Vec<usize> = (0..n_samples).collect();
prop_assert_eq!(sorted_indices, expected);
let unique_indices: std::collections::HashSet<usize> = indices.into_iter().collect();
prop_assert_eq!(unique_indices.len(), n_samples);
}
#[test]
fn test_k_fold_indices_properties(
n_samples in 10..100usize,
n_splits in 2..10usize
) {
use crate::random::k_fold_indices;
let result = k_fold_indices(n_samples, n_splits, true, Some(42));
if let Ok(folds) = result {
prop_assert_eq!(folds.len(), n_splits);
let mut all_test_indices = Vec::new();
for (_, test_indices) in &folds {
all_test_indices.extend(test_indices.iter().copied());
}
all_test_indices.sort_unstable();
let expected: Vec<usize> = (0..n_samples).collect();
prop_assert_eq!(all_test_indices, expected);
let mut fold_sizes: Vec<usize> = folds.iter().map(|(_, test)| test.len()).collect();
fold_sizes.sort_unstable();
if fold_sizes.len() > 1 {
prop_assert!(fold_sizes[fold_sizes.len() - 1] - fold_sizes[0] <= 1);
}
}
}
#[test]
fn test_cross_validation_split_consistency(
n_samples in 20..80usize,
n_splits in 3..8usize
) {
use crate::random::k_fold_indices;
let result1 = k_fold_indices(n_samples, n_splits, true, Some(42));
let result2 = k_fold_indices(n_samples, n_splits, true, Some(42));
if let (Ok(folds1), Ok(folds2)) = (result1, result2) {
prop_assert_eq!(folds1.len(), folds2.len());
for (fold1, fold2) in folds1.iter().zip(folds2.iter()) {
prop_assert_eq!(&fold1.0, &fold2.0); prop_assert_eq!(&fold1.1, &fold2.1); }
}
}
#[test]
fn test_array_validation_properties(
n_features in 2..10usize,
n_samples in 5..20usize
) {
use crate::validation::{check_array_2d, check_consistent_length_xy};
let x = Array2::<f64>::zeros((n_samples, n_features));
let y = Array1::<i32>::zeros(n_samples);
prop_assert!(check_array_2d(&x).is_ok());
prop_assert!(check_consistent_length_xy(&x, &y).is_ok());
let y_wrong = Array1::<i32>::zeros(n_samples + 1);
prop_assert!(check_consistent_length_xy(&x, &y_wrong).is_err());
}
#[test]
fn test_distance_computation_properties(
dim in 2..8usize
) {
use crate::metrics::{euclidean_distance, manhattan_distance};
let point1 = Array1::<f64>::zeros(dim);
let point2 = Array1::<f64>::ones(dim);
let euclidean_dist = euclidean_distance(&point1, &point2);
let manhattan_dist = manhattan_distance(&point1, &point2);
prop_assert!(euclidean_dist >= 0.0);
prop_assert!(manhattan_dist >= 0.0);
let self_euclidean = euclidean_distance(&point1, &point1);
let self_manhattan = manhattan_distance(&point1, &point1);
prop_assert!((self_euclidean - 0.0).abs() < 1e-10);
prop_assert!((self_manhattan - 0.0).abs() < 1e-10);
prop_assert!((manhattan_dist - dim as f64).abs() < 1e-10);
}
#[test]
fn test_data_scaling_properties(
n_samples in 10..50usize,
n_features in 2..10usize,
scale in 0.1..10.0f64
) {
let mut x = Array2::<f64>::zeros((n_samples, n_features));
for i in 0..n_samples {
for j in 0..n_features {
x[[i, j]] = scale * (i as f64 + j as f64);
}
}
prop_assert!(x.iter().all(|&val| val.is_finite()));
let max_val = x.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let min_val = x.iter().fold(f64::INFINITY, |a, &b| a.min(b));
prop_assert!(max_val >= min_val);
if scale > 0.0 {
prop_assert!(max_val > min_val || n_samples <= 1 || n_features <= 1);
}
}
}
#[allow(non_snake_case)]
#[cfg(test)]
#[test]
fn test_environment_detection_basic() {
use crate::environment::{EnvironmentInfo, FeatureChecker, HardwareDetector};
let hw = HardwareDetector::new();
let checker = FeatureChecker::new();
let env = EnvironmentInfo::detect();
assert!(hw.cpu_cores() >= 1);
assert!(hw.logical_cores() >= hw.cpu_cores());
assert!(hw.total_memory() > 0);
assert!(!hw.cpu_architecture().is_empty());
assert!(checker.recommended_thread_count() >= 1);
assert!(checker.recommended_simd_width() >= 1);
assert!(checker.high_resolution_timer_available());
let summary = env.summary();
assert!(!summary.is_empty());
assert!(summary.contains("Environment Summary"));
}
pub mod testing_framework {
use super::*;
use std::time::Duration;
pub struct TestSuite {
pub name: String,
pub tests: Vec<Box<dyn Fn() -> Result<(), String> + Send + Sync>>,
pub setup: Option<Box<dyn Fn() -> Result<(), String> + Send + Sync>>,
pub teardown: Option<Box<dyn Fn() -> Result<(), String> + Send + Sync>>,
#[allow(dead_code)] pub timeout: Duration,
}
impl TestSuite {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
tests: Vec::new(),
setup: None,
teardown: None,
timeout: Duration::from_secs(60),
}
}
pub fn add_test<F>(&mut self, test: F)
where
F: Fn() -> Result<(), String> + Send + Sync + 'static,
{
self.tests.push(Box::new(test));
}
#[allow(dead_code)] pub fn set_setup<F>(&mut self, setup: F)
where
F: Fn() -> Result<(), String> + Send + Sync + 'static,
{
self.setup = Some(Box::new(setup));
}
#[allow(dead_code)] pub fn set_teardown<F>(&mut self, teardown: F)
where
F: Fn() -> Result<(), String> + Send + Sync + 'static,
{
self.teardown = Some(Box::new(teardown));
}
pub fn run(&self) -> TestResults {
let mut results = TestResults::new(&self.name);
if let Some(ref setup) = self.setup {
if let Err(e) = setup() {
results.add_failure(format!("Setup failed: {e}"));
return results;
}
}
for (i, test) in self.tests.iter().enumerate() {
let test_name = format!("test_{i}");
match test() {
Ok(()) => results.add_success(test_name),
Err(e) => results.add_failure(format!("{test_name}: {e}")),
}
}
if let Some(ref teardown) = self.teardown {
if let Err(e) = teardown() {
results.add_failure(format!("Teardown failed: {e}"));
}
}
results
}
}
#[derive(Debug, Clone)]
pub struct TestResults {
#[allow(dead_code)] pub suite_name: String,
pub successes: Vec<String>,
pub failures: Vec<String>,
#[allow(dead_code)] pub duration: Duration,
}
impl TestResults {
pub fn new(suite_name: &str) -> Self {
Self {
suite_name: suite_name.to_string(),
successes: Vec::new(),
failures: Vec::new(),
duration: Duration::new(0, 0),
}
}
pub fn add_success(&mut self, test_name: String) {
self.successes.push(test_name);
}
pub fn add_failure(&mut self, error: String) {
self.failures.push(error);
}
pub fn total_tests(&self) -> usize {
self.successes.len() + self.failures.len()
}
#[allow(dead_code)] pub fn success_rate(&self) -> f64 {
if self.total_tests() == 0 {
0.0
} else {
self.successes.len() as f64 / self.total_tests() as f64
}
}
pub fn is_successful(&self) -> bool {
self.failures.is_empty()
}
#[allow(dead_code)] pub fn summary(&self) -> String {
format!(
"Test Suite: {}\n\
Total Tests: {}\n\
Successes: {}\n\
Failures: {}\n\
Success Rate: {:.1}%\n\
Duration: {:?}",
self.suite_name,
self.total_tests(),
self.successes.len(),
self.failures.len(),
self.success_rate() * 100.0,
self.duration
)
}
}
pub struct StressTester {
pub iterations: usize,
pub concurrent_threads: usize,
#[allow(dead_code)] pub timeout: Duration,
}
impl StressTester {
pub fn new() -> Self {
Self {
iterations: 1000,
concurrent_threads: num_cpus::get(),
timeout: Duration::from_secs(300),
}
}
pub fn with_iterations(mut self, iterations: usize) -> Self {
self.iterations = iterations;
self
}
pub fn with_threads(mut self, threads: usize) -> Self {
self.concurrent_threads = threads;
self
}
pub fn stress_test<F, T>(&self, test_fn: F) -> Result<Vec<T>, String>
where
F: Fn() -> Result<T, String> + Send + Sync + Clone + 'static,
T: Send + Clone + 'static,
{
use std::sync::{Arc, Mutex};
use std::thread;
let results = Arc::new(Mutex::new(Vec::<T>::new()));
let iterations_per_thread = self.iterations / self.concurrent_threads;
let test_fn = Arc::new(test_fn);
let handles: Vec<_> = (0..self.concurrent_threads)
.map(|_| {
let test_fn = Arc::clone(&test_fn);
let results = Arc::clone(&results);
thread::spawn(move || {
for _ in 0..iterations_per_thread {
match test_fn() {
Ok(result) => {
if let Ok(mut results) = results.lock() {
results.push(result);
}
}
Err(e) => return Err(e),
}
}
Ok(())
})
})
.collect();
for handle in handles {
handle
.join()
.map_err(|_| "Thread panicked".to_string())?
.map_err(|e| format!("Stress test failed: {e}"))?;
}
let results = results
.lock()
.map_err(|_| "Failed to acquire lock".to_string())?;
Ok(results.to_vec())
}
}
#[allow(dead_code)] pub struct PropertyTestGenerator;
impl PropertyTestGenerator {
#[allow(dead_code)] pub fn array_data_strategy() -> impl Strategy<Value = (usize, usize)> {
(10..100usize, 2..20usize)
}
#[allow(dead_code)] pub fn optimization_strategy() -> impl Strategy<Value = (f64, f64, usize)> {
(1e-8..1e-2f64, 1e-8..1e-2f64, 10..1000usize)
}
#[allow(dead_code)] pub fn spatial_strategy() -> impl Strategy<Value = (usize, usize, f64)> {
(2..4usize, 10..100usize, 1.0..1000.0f64)
}
#[allow(dead_code)] pub fn statistical_strategy() -> impl Strategy<Value = (usize, f64, f64)> {
(10..1000usize, -100.0..100.0f64, 0.1..10.0f64)
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod framework_tests {
use super::*;
#[test]
fn test_test_suite() {
let mut suite = TestSuite::new("example_suite");
suite.add_test(|| Ok(()));
suite.add_test(|| Err("Test failure".to_string()));
suite.add_test(|| Ok(()));
let results = suite.run();
assert_eq!(results.total_tests(), 3);
assert_eq!(results.successes.len(), 2);
assert_eq!(results.failures.len(), 1);
assert!(!results.is_successful());
}
#[test]
fn test_stress_tester() {
let tester = StressTester::new().with_iterations(100).with_threads(2);
let results = tester.stress_test(|| Ok(42));
assert!(results.is_ok());
let values = results.expect("operation should succeed");
assert_eq!(values.len(), 100);
assert!(values.iter().all(|&x| x == 42));
}
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_property_test_setup() {
let (x, y) = make_classification(20, 3, 2, None, None, 0.0, 1.0, Some(42))
.expect("operation should succeed");
assert_eq!(x.shape(), &[20, 3]);
assert_eq!(y.len(), 20);
}
}