use std::{sync::Barrier, thread};
use hxcfe::{HeadId, Hxcfe, SectorId, TrackEncoding, TrackId};
const HFE: &str = "tests/EXPERTS.HFE";
const DSK: &str = "tests/EXPERTS.DSK";
fn ctx() -> &'static Hxcfe {
Hxcfe::get()
}
fn scan_track_0(img: &hxcfe::Img) -> usize {
let sa = img.sector_access().expect("sector_access");
sa.reset_search_track_position();
let mut count = 0usize;
while let Some(cfg) = sa.get_next_sector(HeadId::new(0), TrackId::new(0), TrackEncoding::IsoibmMfm) {
let _ = cfg.read(); count += 1;
}
count
}
fn read_sector_c1(img: &hxcfe::Img) -> usize {
let sa = img.sector_access().expect("sector_access");
sa.search_sector(HeadId::new(0), TrackId::new(0), SectorId::new(0xC1), TrackEncoding::IsoibmMfm)
.map(|cfg| cfg.read().len())
.unwrap_or(0)
}
#[test]
fn concurrent_hfe_reads() {
const THREADS: usize = 8;
let baseline = scan_track_0(&ctx().load(HFE).expect("baseline load"));
assert!(baseline > 0, "baseline must find at least one sector");
let barrier = std::sync::Arc::new(Barrier::new(THREADS));
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let b = barrier.clone();
thread::spawn(move || {
let img = ctx().load(HFE).expect("thread load");
b.wait();
let count = scan_track_0(&img);
assert_eq!(count, baseline, "sector count must be deterministic");
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
}
#[test]
fn concurrent_mixed_format_reads() {
const THREADS_PER_FORMAT: usize = 4;
let barrier = std::sync::Arc::new(Barrier::new(THREADS_PER_FORMAT * 2));
let mut handles = Vec::new();
for format in [HFE, DSK] {
for _ in 0..THREADS_PER_FORMAT {
let b = barrier.clone();
handles.push(thread::spawn(move || {
let img = ctx().load(format).expect("thread load");
b.wait();
let len = read_sector_c1(&img);
assert!(len > 0, "sector 0xC1 must have non-zero size in {format}");
}));
}
}
for h in handles {
h.join().expect("thread panicked");
}
}
#[test]
fn concurrent_sector_access_create_drop() {
const THREADS: usize = 6;
const ITERATIONS: usize = 30;
let barrier = std::sync::Arc::new(Barrier::new(THREADS));
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let b = barrier.clone();
thread::spawn(move || {
let img = ctx().load(HFE).expect("load");
b.wait();
for _ in 0..ITERATIONS {
let sa = img.sector_access().expect("sector_access");
drop(sa);
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
}
#[test]
fn concurrent_full_scan() {
const THREADS: usize = 4;
let barrier = std::sync::Arc::new(Barrier::new(THREADS));
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let b = barrier.clone();
thread::spawn(move || {
let img = ctx().load(HFE).expect("load");
let nb_tracks = img.nb_tracks() as u32;
let nb_sides = img.nb_sides() as u32;
b.wait();
let sa = img.sector_access().expect("sector_access");
let mut total = 0usize;
for track in 0..nb_tracks {
for side in 0..nb_sides {
let sca = sa.all_track_sectors(
HeadId::new(side as i32),
TrackId::new(track as i32),
TrackEncoding::IsoibmMfm,
);
if let Some(array) = sca {
for i in 0..array.nb_sectors() {
let cfg = array.sector_config(i);
let _ = cfg.read();
total += 1;
}
}
}
}
assert!(total > 0, "full scan must find sectors");
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
}
#[test]
fn concurrent_load_drop_cycle() {
const THREADS: usize = 6;
const ITERATIONS: usize = 10;
let barrier = std::sync::Arc::new(Barrier::new(THREADS));
let handles: Vec<_> = (0..THREADS)
.map(|i| {
let b = barrier.clone();
thread::spawn(move || {
let files = if i % 2 == 0 { [HFE, DSK] } else { [DSK, HFE] };
b.wait();
for iter in 0..ITERATIONS {
let file = files[iter % 2];
let img = ctx().load(file).expect("load");
assert!(img.nb_tracks() > 0);
drop(img); }
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
}
#[test]
fn concurrent_loader_manager_with_readers() {
const READER_THREADS: usize = 4;
const LOADER_THREADS: usize = 2;
const ITERATIONS: usize = 20;
let barrier = std::sync::Arc::new(Barrier::new(READER_THREADS + LOADER_THREADS));
let mut handles = Vec::new();
for _ in 0..READER_THREADS {
let b = barrier.clone();
handles.push(thread::spawn(move || {
let img = ctx().load(HFE).expect("load");
b.wait();
for _ in 0..ITERATIONS {
let _ = read_sector_c1(&img);
}
}));
}
for _ in 0..LOADER_THREADS {
let b = barrier.clone();
handles.push(thread::spawn(move || {
b.wait();
for _ in 0..ITERATIONS {
let mgr = ctx().loaders_manager().expect("loaders_manager");
let n = mgr.nb_loaders();
assert!(n > 0);
drop(mgr);
}
}));
}
for h in handles {
h.join().expect("thread panicked");
}
}