#[cfg(test)]
mod tests {
use crate::eclipselib::{lunar_eclipses, LunarEclipseType};
use crate::jplephem::kernel::SpiceKernel;
use crate::jplephem_ext::SpiceKernelExt;
use crate::pybridge::bridge::PyRustBridge;
use crate::pybridge::helpers::PythonResult;
use crate::time::Timescale;
fn parse_string(result: &str) -> String {
let parsed = PythonResult::try_from(result).expect("Failed to parse Python result");
match parsed {
PythonResult::String(s) => s,
_ => panic!("Expected String result, got {:?}", parsed),
}
}
fn test_kernel() -> SpiceKernel {
SpiceKernel::open("test_data/de421.bsp").expect("de421.bsp required")
}
#[test]
fn test_lunar_eclipses_2015_vs_skyfield() {
let bridge = PyRustBridge::new().expect("Failed to create Python bridge");
let mut kernel = test_kernel();
let ts = Timescale::default();
let py_result = bridge
.run_py_to_json(
r#"
from skyfield.api import load
from skyfield.eclipselib import lunar_eclipses
ts = load.timescale()
eph = load('de421.bsp')
t0 = ts.tt_jd(2457023.5) # 2015-01-01
t1 = ts.tt_jd(2457388.5) # 2016-01-01
times, types, details = lunar_eclipses(t0, t1, eph)
results = []
for i in range(len(times)):
results.append(f"{times.tt[i]:.4f}:{int(types[i])}")
rust.collect_string('|'.join(results))
"#,
)
.expect("Failed to run Python code");
let py_str = parse_string(&py_result);
let py_eclipses: Vec<(f64, i32)> = py_str
.split('|')
.filter(|s| !s.is_empty())
.map(|s| {
let parts: Vec<&str> = s.split(':').collect();
(parts[0].parse().unwrap(), parts[1].parse().unwrap())
})
.collect();
let t0 = ts.tt_jd(2457023.5, None).tdb();
let t1 = ts.tt_jd(2457388.5, None).tdb();
let rust_eclipses = lunar_eclipses(&mut kernel, t0, t1);
assert_eq!(
rust_eclipses.len(),
py_eclipses.len(),
"Eclipse count mismatch: rust={} python={}",
rust_eclipses.len(),
py_eclipses.len()
);
for (i, (py_jd, py_type)) in py_eclipses.iter().enumerate() {
let rust_jd = rust_eclipses[i].jd_tdb;
let diff_days = (rust_jd - py_jd).abs();
assert!(
diff_days < 1.0,
"Eclipse {i}: date mismatch: rust={rust_jd:.4} python={py_jd:.4} diff={diff_days:.4} days"
);
let rust_type = rust_eclipses[i].eclipse_type as i32;
assert_eq!(
rust_type, *py_type,
"Eclipse {i}: type mismatch: rust={rust_type} ({}) python={py_type}",
rust_eclipses[i].eclipse_type
);
}
}
#[test]
fn test_lunar_eclipses_2019_vs_skyfield() {
let bridge = PyRustBridge::new().expect("Failed to create Python bridge");
let mut kernel = test_kernel();
let ts = Timescale::default();
let py_result = bridge
.run_py_to_json(
r#"
from skyfield.api import load
from skyfield.eclipselib import lunar_eclipses
ts = load.timescale()
eph = load('de421.bsp')
t0 = ts.tt_jd(2458484.5) # 2019-01-01
t1 = ts.tt_jd(2458849.5) # 2020-01-01
times, types, details = lunar_eclipses(t0, t1, eph)
results = []
for i in range(len(times)):
results.append(f"{times.tt[i]:.4f}:{int(types[i])}")
rust.collect_string('|'.join(results))
"#,
)
.expect("Failed to run Python code");
let py_str = parse_string(&py_result);
let py_count = py_str.split('|').filter(|s| !s.is_empty()).count();
let t0 = ts.tt_jd(2458484.5, None).tdb();
let t1 = ts.tt_jd(2458849.5, None).tdb();
let rust_eclipses = lunar_eclipses(&mut kernel, t0, t1);
assert!(
rust_eclipses.len() >= 2,
"Expected at least 2 lunar eclipses in 2019"
);
assert_eq!(
rust_eclipses.len(),
py_count,
"Eclipse count: rust={} python={}",
rust_eclipses.len(),
py_count
);
let has_total = rust_eclipses
.iter()
.any(|e| e.eclipse_type == LunarEclipseType::Total);
assert!(has_total, "Expected a total lunar eclipse in Jan 2019");
}
}