Files
data-server/tests/data_server.rs
T
Brummel 0f5e6655b9 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.
2026-06-04 17:51:14 +02:00

122 lines
3.6 KiB
Rust

//! Integration tests for the DataServer, verifying correct parsing of real
//! tick data files from /mnt/tickdata/Pepperstone/.
use std::path::Path;
use std::sync::Arc;
const TICKDATA_PATH: &str = "/mnt/tickdata/Pepperstone";
/// Skip tests when the tick data directory is not available.
fn skip_if_no_data() -> bool {
!Path::new(TICKDATA_PATH).exists()
}
#[test]
fn test_real_symbol_index() {
if skip_if_no_data() {
return;
}
let server = Arc::new(data_server::DataServer::new(TICKDATA_PATH));
let symbols = server.symbols();
assert!(!symbols.is_empty(), "Should find at least one symbol");
assert!(
symbols.iter().any(|s| s.as_ref() == "EURUSD"),
"EURUSD should be in the symbol list"
);
// Verify file count for a known symbol
let m1_count = server
.file_count("EURUSD", data_server::records::DataFormat::M1)
.unwrap();
assert!(m1_count > 0, "EURUSD should have M1 files, found {m1_count}");
}
#[test]
fn test_real_m1_stream_eurusd() {
if skip_if_no_data() {
return;
}
let server = Arc::new(data_server::DataServer::new(TICKDATA_PATH));
let mut iter = server.stream_m1("EURUSD").expect("EURUSD M1 stream");
// Read the first chunk
let first_chunk = iter.next_chunk().expect("At least one chunk");
assert!(!first_chunk.is_empty());
let rec = &first_chunk[0];
// Verify the record has sane values
assert!(rec.time_ms > 0, "Timestamp should be positive");
assert!(rec.open > 0.0, "EURUSD open should be > 0");
assert!(rec.high >= rec.low, "High should be >= Low");
assert!(rec.close > 0.0, "EURUSD close should be > 0");
// EURUSD is always between ~0.8 and ~1.6
assert!(
(0.5..2.0).contains(&rec.open),
"EURUSD open should be between 0.5 and 2.0, got {}",
rec.open
);
println!(
"First EURUSD M1 record: time_ms={}, O={:.5} H={:.5} L={:.5} C={:.5} S={:.1} V={}",
rec.time_ms, rec.open, rec.high, rec.low, rec.close, rec.spread, rec.volume
);
}
#[test]
fn test_real_tick_stream_eurusd() {
if skip_if_no_data() {
return;
}
let server = Arc::new(data_server::DataServer::new(TICKDATA_PATH));
// EURUSD tick data may not exist — check gracefully
let Some(mut iter) = server.stream_tick("EURUSD") else {
println!("No EURUSD tick data found, skipping");
return;
};
let first_chunk = iter.next_chunk().expect("At least one tick chunk");
let rec = &first_chunk[0];
assert!(rec.time_ms > 0);
assert!(rec.ask > 0.0);
assert!(rec.bid > 0.0);
// Spread should be small for EURUSD
let spread = (rec.ask - rec.bid).abs();
assert!(
spread < 0.01,
"EURUSD spread should be < 100 pips, got {spread}"
);
println!(
"First EURUSD tick: time_ms={}, Ask={:.5} Bid={:.5} Spread={:.5}",
rec.time_ms, rec.ask, rec.bid, spread
);
}
#[test]
fn test_real_m1_full_iteration() {
if skip_if_no_data() {
return;
}
let server = Arc::new(data_server::DataServer::new(TICKDATA_PATH));
// Use a symbol with few files for speed (AAPL.US starts 2006)
let Some(mut iter) = server.stream_m1("AAPL.US") else {
println!("No AAPL.US data found, skipping");
return;
};
let mut total_records = 0u64;
let mut chunks = 0u64;
while let Some(chunk) = iter.next_chunk() {
chunks += 1;
total_records += chunk.len() as u64;
}
println!("AAPL.US M1: {total_records} records in {chunks} chunks");
assert!(total_records > 0, "Should have read some records");
}