Replace flaky concurrent-access test with deterministic equivalent

test_real_concurrent_access depended on /mnt data (skipped otherwise)
and asserted equal counts over a partial 10-chunk read, which thread
scheduling could make uneven. The SymbolGuard ownership model closes
the underlying races, so concurrent consumers reading the same symbol
to completion read an identical, fixed record set regardless of
scheduling.

Replace it with a self-contained inline test: eight threads each fully
drain a stream over temp files and must read identical counts. Runs
everywhere with no /mnt dependency; verified deterministic across 30
consecutive runs.
This commit is contained in:
2026-06-04 17:51:14 +02:00
parent a31e26d3c1
commit 0f5e6655b9
2 changed files with 39 additions and 39 deletions
+39
View File
@@ -675,6 +675,45 @@ mod tests {
drop(iter2); drop(iter2);
} }
/// Property: concurrent consumers of the same symbol each read the
/// identical, complete record set — thread scheduling cannot change the
/// outcome. Replaces a former flaky `/mnt`-dependent test that asserted
/// over a partial read; with the `SymbolGuard` ownership model the races
/// are closed, so a full-drain count is deterministic under contention.
#[test]
fn test_concurrent_consumers_read_identical_data() {
let dir = tempfile::tempdir().unwrap();
write_m1_file(dir.path(), "EURUSD", 2017, 1);
write_m1_file(dir.path(), "EURUSD", 2017, 2);
write_m1_file(dir.path(), "EURUSD", 2017, 3);
let server = Arc::new(DataServer::new(dir.path()));
// Eight threads stream the same symbol at once, each draining fully.
let handles: Vec<_> = (0..8)
.map(|_| {
let srv = Arc::clone(&server);
std::thread::spawn(move || {
let mut iter = srv.stream_m1("EURUSD").unwrap();
let mut count = 0u64;
while let Some(chunk) = iter.next_chunk() {
count += chunk.len() as u64;
}
count
})
})
.collect();
let counts: Vec<u64> = handles.into_iter().map(|h| h.join().unwrap()).collect();
// 3 files, one record each: every thread must read exactly 3.
assert!(counts[0] > 0);
assert!(
counts.iter().all(|&c| c == counts[0]),
"concurrent consumers must read identical data: {counts:?}"
);
}
#[test] #[test]
fn test_symbol_index_scan() { fn test_symbol_index_scan() {
// Create a temp directory with mock files // Create a temp directory with mock files
-39
View File
@@ -119,42 +119,3 @@ fn test_real_m1_full_iteration() {
println!("AAPL.US M1: {total_records} records in {chunks} chunks"); println!("AAPL.US M1: {total_records} records in {chunks} chunks");
assert!(total_records > 0, "Should have read some records"); assert!(total_records > 0, "Should have read some records");
} }
// FIXME: This test is flaky — thread scheduling can cause uneven read counts.
#[test]
fn test_real_concurrent_access() {
if skip_if_no_data() {
return;
}
let server = Arc::new(data_server::DataServer::new(TICKDATA_PATH));
// Spawn 4 threads all reading the same symbol simultaneously
let handles: Vec<_> = (0..4)
.map(|_| {
let srv = Arc::clone(&server);
std::thread::spawn(move || {
let mut iter = srv.stream_m1("EURUSD").expect("EURUSD");
let mut count = 0u64;
// Read first 10 chunks only (for speed)
for _ in 0..10 {
if let Some(chunk) = iter.next_chunk() {
count += chunk.len() as u64;
} else {
break;
}
}
count
})
})
.collect();
let counts: Vec<u64> = handles.into_iter().map(|h| h.join().unwrap()).collect();
// All threads should read the same number of records
assert!(counts[0] > 0);
assert!(
counts.iter().all(|&c| c == counts[0]),
"All threads should read the same amount: {counts:?}"
);
println!("Concurrent access: 4 threads each read {} records", counts[0]);
}