Some checks failed
Build base image / Build and push base image (push) Successful in 14s
Build and Push / Build CI test image (push) Successful in 14s
Build and Push / Build and Push Docker Image (push) Failing after 18m31s
Build prop-grid-rs / Test, build, push (push) Successful in 23m55s
json_weights_golden reads priv/algo/band_weights.json via a bare relative path. That resolves inside `cargo test` at the crate root but not inside the image build, whose context is rust/prop_grid_rs and whose WORKDIR is /src — so the test aborted with "JSON file must exist" and the build never produced an image. grid-rs CI has been red since2fd88a94, the commit that added the test. Production still runs main-1785606663-95976b6 (95976b6b, the last green build), so every prop-grid-rs change since then — including the HRDPS rotated-pole decode fix — has silently failed to ship. Three parts: * resolve the fixture from CARGO_MANIFEST_DIR so the path no longer depends on the cwd * stage the file into the build context in the workflow * COPY it to /priv in the builder stage, where CARGO_MANIFEST_DIR/../../priv resolves from /src
155 lines
5.4 KiB
Rust
155 lines
5.4 KiB
Rust
//! Golden test verifying that per-band weight resolution matches the
|
|
//! JSON produced by `scripts/recalibrate.py`.
|
|
//!
|
|
//! This test sets `PROP_BAND_WEIGHTS_JSON` before any weights lookup so
|
|
//! the `OnceLock` cache in `band_config` seeds from the real JSON file.
|
|
//! It runs in a separate test binary from `scorer_golden.rs`, so the
|
|
//! env var does not affect the existing golden-fixture test.
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use serde_json::Value;
|
|
|
|
use prop_grid_rs::band_config::{self, Weights};
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
|
|
fn weights_sum(w: &Weights) -> f64 {
|
|
w.humidity
|
|
+ w.time_of_day
|
|
+ w.td_depression
|
|
+ w.refractivity
|
|
+ w.sky
|
|
+ w.season
|
|
+ w.wind
|
|
+ w.rain
|
|
+ w.pressure
|
|
+ w.pwat
|
|
}
|
|
|
|
fn parse_json_override_weights(json_path: &str) -> HashMap<u32, Weights> {
|
|
let data = std::fs::read_to_string(json_path).expect("JSON file must exist");
|
|
let root: Value = serde_json::from_str(&data).expect("valid JSON");
|
|
|
|
let overrides = root["band_overrides"]
|
|
.as_object()
|
|
.expect("band_overrides must be an object");
|
|
|
|
let mut map = HashMap::new();
|
|
for (freq_str, entry) in overrides {
|
|
let freq: u32 = freq_str.parse().expect("band override key must be a u32");
|
|
let w = &entry["weights"];
|
|
let humidity = w["humidity"].as_f64().expect("humidity");
|
|
let time_of_day = w["time_of_day"].as_f64().expect("time_of_day");
|
|
let td_depression = w["td_depression"].as_f64().expect("td_depression");
|
|
let refractivity = w["refractivity"].as_f64().expect("refractivity");
|
|
let sky = w["sky"].as_f64().expect("sky");
|
|
let season = w["season"].as_f64().expect("season");
|
|
let wind = w["wind"].as_f64().expect("wind");
|
|
let rain = w["rain"].as_f64().expect("rain");
|
|
let pwat = w["pwat"].as_f64().expect("pwat");
|
|
let pressure = w["pressure"].as_f64().expect("pressure");
|
|
|
|
map.insert(
|
|
freq,
|
|
Weights::new(
|
|
humidity,
|
|
time_of_day,
|
|
td_depression,
|
|
refractivity,
|
|
sky,
|
|
season,
|
|
wind,
|
|
rain,
|
|
pressure,
|
|
pwat,
|
|
),
|
|
);
|
|
}
|
|
map
|
|
}
|
|
|
|
fn assert_weights_equal(label: &str, got: &Weights, expected: &Weights) {
|
|
let tolerance = 1e-9;
|
|
let factors: [(&str, f64, f64); 10] = [
|
|
("humidity", got.humidity, expected.humidity),
|
|
("time_of_day", got.time_of_day, expected.time_of_day),
|
|
("td_depression", got.td_depression, expected.td_depression),
|
|
("refractivity", got.refractivity, expected.refractivity),
|
|
("sky", got.sky, expected.sky),
|
|
("season", got.season, expected.season),
|
|
("wind", got.wind, expected.wind),
|
|
("rain", got.rain, expected.rain),
|
|
("pressure", got.pressure, expected.pressure),
|
|
("pwat", got.pwat, expected.pwat),
|
|
];
|
|
for (name, g, e) in &factors {
|
|
assert!(
|
|
(g - e).abs() < tolerance,
|
|
"{label} factor {name}: got {g}, expected {e}"
|
|
);
|
|
}
|
|
}
|
|
|
|
// ── Tests ────────────────────────────────────────────────────────────
|
|
|
|
/// Set env var before any test accesses the OnceLock cache. `std::sync::Once`
|
|
/// ensures this runs exactly once even if the test runner spawns multiple
|
|
/// threads for `#[test]` functions in this binary.
|
|
static ENSURE_ENV: std::sync::Once = std::sync::Once::new();
|
|
|
|
/// Absolute path to the repo's calibration JSON, resolved from the
|
|
/// crate root rather than the cwd. `cargo test` runs with the cwd at
|
|
/// the crate root, but the Docker build stages the file at an absolute
|
|
/// path, and a bare relative path silently resolved to a nonexistent
|
|
/// file there — which is what turned this golden test into a hard CI
|
|
/// failure that blocked every prop-grid-rs image build.
|
|
const BAND_WEIGHTS_JSON: &str = concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/../../priv/algo/band_weights.json"
|
|
);
|
|
|
|
fn ensure_json_env() {
|
|
ENSURE_ENV.call_once(|| {
|
|
std::env::set_var("PROP_BAND_WEIGHTS_JSON", BAND_WEIGHTS_JSON);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn json_override_weights_match_file() {
|
|
ensure_json_env();
|
|
|
|
let expected = parse_json_override_weights(BAND_WEIGHTS_JSON);
|
|
|
|
for band in band_config::all_bands() {
|
|
let w = band.weights();
|
|
|
|
// Every weight vector must sum to ~1.0
|
|
let sum = weights_sum(&w);
|
|
assert!(
|
|
(sum - 1.0).abs() < 0.001,
|
|
"band {} MHz weights sum = {sum} (expected 1.0 ± 0.001)",
|
|
band.freq_mhz,
|
|
);
|
|
|
|
// If JSON has an override for this band, it must match exactly
|
|
if let Some(exp) = expected.get(&band.freq_mhz) {
|
|
assert_weights_equal(&format!("band {} MHz", band.freq_mhz), &w, exp);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn all_bands_weights_sum_to_one() {
|
|
ensure_json_env();
|
|
|
|
for band in band_config::all_bands() {
|
|
let w = band.weights();
|
|
let sum = weights_sum(&w);
|
|
assert!(
|
|
(sum - 1.0).abs() < 0.001,
|
|
"band {} MHz weights sum = {sum}",
|
|
band.freq_mhz,
|
|
);
|
|
}
|
|
}
|