use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use tenshift_core::error::{Error, Result};
use tenshift_core::pipeline::ErrorPolicy;
use tenshift_core::sample::{Sample, Tensor};
use tenshift_core::source::{Source, SourceIterator};
use tenshift_core::Pipeline;
struct Phase6ChaosSource {
target_samples: usize,
chaos_seed: u64,
}
struct Phase6ChaosIterator {
yielded_count: usize,
target_samples: usize,
state: u64,
}
impl Source for Phase6ChaosSource {
fn open(&self) -> Result<Box<dyn SourceIterator>> {
Ok(Box::new(Phase6ChaosIterator {
yielded_count: 0,
target_samples: self.target_samples,
state: self.chaos_seed,
}))
}
fn name(&self) -> &str {
"phase6-chaos"
}
}
impl Phase6ChaosIterator {
fn next_rand(&mut self) -> u64 {
self.state ^= self.state << 13;
self.state ^= self.state >> 17;
self.state ^= self.state << 5;
self.state
}
}
impl SourceIterator for Phase6ChaosIterator {
fn next_sample(&mut self) -> Option<Result<Sample>> {
if self.yielded_count >= self.target_samples {
return None;
}
let roll = self.next_rand() % 100;
self.yielded_count += 1;
if roll < 20 {
return Some(Err(Error::CorruptData {
path: "chaos".into(),
reason: "Phase6 EIO: read failed".into(),
}));
}
if roll < 25 {
thread::sleep(Duration::from_millis(1));
}
if roll < 35 {
return Some(Ok(Sample::new().with("data", Tensor::u8(vec![], vec![0]))));
}
let data = if roll % 2 == 0 {
vec![0u8; 100]
} else {
vec![0xFFu8; 100]
};
let sample = Sample::new()
.with("data", Tensor::u8(data, vec![100]))
.with_metadata("chaos", self.yielded_count as u64);
Some(Ok(sample))
}
}
#[test]
fn test_phase6_oom_adversarial_memory_blowup() {
let source = Phase6ChaosSource {
target_samples: 10,
chaos_seed: 0xBAD_C0DE,
};
let pipeline = Pipeline::from_source(source)
.workers(2)
.prefetch(2) .flat_map(|mut sample| {
if let Some(meta) = sample.metadata() {
if meta.index == 5 {
let large = vec![0xAA; 1024 * 1024];
sample.insert("huge", Tensor::u8(large, vec![1024 * 1024]));
}
}
Ok(vec![sample])
})
.batch(2);
let mut iter = pipeline.start().unwrap();
let mut total = 0;
while let Some(batch) = iter.next() {
total += batch.len();
}
assert!(total < 10);
assert!(total > 0);
}
#[test]
fn test_phase6_io_error_injection_skip() {
let source = Phase6ChaosSource {
target_samples: 1000,
chaos_seed: 0x1234_5678,
};
let pipeline = Pipeline::from_source(source)
.workers(4)
.on_error(ErrorPolicy::Skip);
let iter = pipeline.start().unwrap();
let mut valid_samples = 0;
for batch in iter {
valid_samples += batch.len();
}
assert!(
valid_samples > 700 && valid_samples < 900,
"Expected ~800 valid samples, got {}",
valid_samples
);
}
#[test]
fn test_phase6_concurrent_stress_32_threads() {
let source = Phase6ChaosSource {
target_samples: 50_000,
chaos_seed: 0x8765_4321,
};
let pipeline = Pipeline::from_source(source)
.workers(8) .on_error(ErrorPolicy::Skip)
.batch(10);
let iter = pipeline.start().unwrap();
let shared_iter = Arc::new(Mutex::new(iter));
let mut handles = vec![];
for _ in 0..32 {
let iter_clone = Arc::clone(&shared_iter);
handles.push(thread::spawn(move || {
let mut local_count = 0;
loop {
let batch_opt = {
let mut lock = iter_clone.lock().unwrap();
lock.next()
};
match batch_opt {
Some(batch) => {
local_count += batch.len();
}
None => break,
}
}
local_count
}));
}
let mut total_consumed = 0;
for handle in handles {
total_consumed += handle.join().unwrap();
}
assert!(
total_consumed > 300,
"Should have consumed hundreds of non-error items, got {}",
total_consumed
);
}
#[test]
fn test_phase6_integer_overflow_sequence() {
struct OverflowSource;
impl Source for OverflowSource {
fn open(&self) -> Result<Box<dyn SourceIterator>> {
Ok(Box::new(OverflowIterator { count: 0 }))
}
fn name(&self) -> &str {
"overflow"
}
}
struct OverflowIterator {
count: usize,
}
impl SourceIterator for OverflowIterator {
fn next_sample(&mut self) -> Option<Result<Sample>> {
if self.count >= 10 {
return None;
}
self.count += 1;
let sample = Sample::new()
.with("data", Tensor::u8(vec![1, 2, 3], vec![3]))
.with_metadata("overflow", u64::MAX - self.count as u64);
Some(Ok(sample))
}
}
let pipeline = Pipeline::from_source(OverflowSource).workers(2).batch(1);
let mut iter = pipeline.start().unwrap();
let mut total = 0;
while let Some(batch) = iter.next() {
total += 1; for sample in batch {
if let Some(meta) = sample.metadata() {
assert!(meta.index > u64::MAX - 20);
}
}
}
assert_eq!(total, 10);
}