Skip to content

Commit 05aa8dc

Browse files
committed
test: strengthen round-trip validation and simplify fixtures
1 parent 6df5da0 commit 05aa8dc

28 files changed

Lines changed: 585 additions & 384 deletions

examples/regenerate_fixtures.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
/// Regenerate example database fixture files from builder code.
22
///
33
/// This binary recomputes all model and rule examples using BruteForce/ILP
4-
/// and writes the results to `src/example_db/fixtures/`. Run this in release
5-
/// mode after changing any model or rule to update the stored expected results:
4+
/// and writes them to `src/example_db/fixtures/` as JSON Lines, one example per
5+
/// line. Run this in release mode after changing any model or rule to update
6+
/// the stored expected results:
67
///
78
/// ```
89
/// cargo run --release --example regenerate_fixtures --features example-db
910
/// ```
1011
use problemreductions::example_db::{compute_model_db, compute_rule_db};
12+
use problemreductions::export::{write_model_db_to, write_rule_db_to};
1113
use std::fs;
1214
use std::path::Path;
1315

@@ -21,11 +23,8 @@ fn main() {
2123
let models_path = fixtures_dir.join("models.json");
2224
let rules_path = fixtures_dir.join("rules.json");
2325

24-
let models_json = serde_json::to_string(&model_db).expect("Failed to serialize models");
25-
let rules_json = serde_json::to_string(&rule_db).expect("Failed to serialize rules");
26-
27-
fs::write(&models_path, &models_json).expect("Failed to write models fixture");
28-
fs::write(&rules_path, &rules_json).expect("Failed to write rules fixture");
26+
write_model_db_to(&fixtures_dir, &model_db);
27+
write_rule_db_to(&fixtures_dir, &rule_db);
2928

3029
println!(
3130
"Regenerated fixtures: {} rule examples, {} model examples",

src/example_db/fixtures/models.json

Lines changed: 31 additions & 1 deletion
Large diffs are not rendered by default.

src/example_db/fixtures/rules.json

Lines changed: 43 additions & 1 deletion
Large diffs are not rendered by default.

src/example_db/mod.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
//! The example database has two layers:
44
//!
55
//! - **Fixtures** (`fixtures/models.json`, `fixtures/rules.json`): pre-computed
6-
//! expected results embedded at compile time. These are the "stored expected
7-
//! results" used for fast export and lookups.
6+
//! expected results embedded at compile time as JSON Lines, one example per
7+
//! line. These are the "stored expected results" used for fast export and
8+
//! lookups.
89
//!
910
//! - **Builders** (`model_builders`, `rule_builders`): code that constructs
1011
//! problem instances and computes solutions via BruteForce/ILP. Used only
@@ -15,7 +16,8 @@
1516
1617
use crate::error::{ProblemError, Result};
1718
use crate::export::{
18-
examples_output_dir, ModelDb, ModelExample, ProblemRef, RuleDb, RuleExample, EXAMPLE_DB_VERSION,
19+
examples_output_dir, parse_model_db_json_lines, parse_rule_db_json_lines, ModelDb,
20+
ModelExample, ProblemRef, RuleDb, RuleExample,
1921
};
2022
use std::collections::BTreeSet;
2123
use std::path::PathBuf;
@@ -65,17 +67,15 @@ fn validate_model_uniqueness(models: &[ModelExample]) -> Result<()> {
6567
/// Load the model database from the embedded fixture file.
6668
pub fn build_model_db() -> Result<ModelDb> {
6769
static MODELS_JSON: &str = include_str!("fixtures/models.json");
68-
let db: ModelDb =
69-
serde_json::from_str(MODELS_JSON).expect("embedded models fixture should parse");
70+
let db = parse_model_db_json_lines(MODELS_JSON);
7071
validate_model_uniqueness(&db.models)?;
7172
Ok(db)
7273
}
7374

7475
/// Load the rule database from the embedded fixture file.
7576
pub fn build_rule_db() -> Result<RuleDb> {
7677
static RULES_JSON: &str = include_str!("fixtures/rules.json");
77-
let db: RuleDb =
78-
serde_json::from_str(RULES_JSON).expect("embedded rules fixture should parse");
78+
let db = parse_rule_db_json_lines(RULES_JSON);
7979
validate_rule_uniqueness(&db.rules)?;
8080
Ok(db)
8181
}
@@ -87,21 +87,15 @@ pub fn compute_model_db() -> Result<ModelDb> {
8787
let mut models = model_builders::build_model_examples();
8888
models.sort_by_key(model_key);
8989
validate_model_uniqueness(&models)?;
90-
Ok(ModelDb {
91-
version: EXAMPLE_DB_VERSION,
92-
models,
93-
})
90+
Ok(ModelDb { models })
9491
}
9592

9693
/// Recompute the rule database from builder code (runs BruteForce/ILP).
9794
pub fn compute_rule_db() -> Result<RuleDb> {
9895
let mut rules = rule_builders::build_rule_examples();
9996
rules.sort_by_key(rule_key);
10097
validate_rule_uniqueness(&rules)?;
101-
Ok(RuleDb {
102-
version: EXAMPLE_DB_VERSION,
103-
rules,
104-
})
98+
Ok(RuleDb { rules })
10599
}
106100

107101
pub fn find_rule_example(source: &ProblemRef, target: &ProblemRef) -> Result<RuleExample> {

src/export.rs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::expr::Expr;
44
use crate::rules::registry::ReductionOverhead;
55
use crate::rules::ReductionGraph;
66
use crate::traits::Problem;
7+
use serde::de::DeserializeOwned;
78
use serde::{Deserialize, Serialize};
89
use std::collections::BTreeMap;
910
use std::env;
@@ -118,19 +119,15 @@ impl ModelExample {
118119
/// Canonical exported database of rule examples.
119120
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
120121
pub struct RuleDb {
121-
pub version: u32,
122122
pub rules: Vec<RuleExample>,
123123
}
124124

125125
/// Canonical exported database of model examples.
126126
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
127127
pub struct ModelDb {
128-
pub version: u32,
129128
pub models: Vec<ModelExample>,
130129
}
131130

132-
pub const EXAMPLE_DB_VERSION: u32 = 1;
133-
134131
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
135132
pub struct SampleEval {
136133
pub config: Vec<usize>,
@@ -198,6 +195,50 @@ fn write_json_file<T: Serialize>(dir: &Path, name: &str, payload: &T) {
198195
println!("Exported: {}", path.display());
199196
}
200197

198+
fn parse_json_lines<T>(json_lines: &str, label: &str) -> Vec<T>
199+
where
200+
T: DeserializeOwned,
201+
{
202+
json_lines
203+
.lines()
204+
.enumerate()
205+
.filter_map(|(index, line)| {
206+
let line = line.trim();
207+
if line.is_empty() {
208+
return None;
209+
}
210+
Some(serde_json::from_str(line).unwrap_or_else(|err| {
211+
panic!("Failed to parse {label} fixture line {}: {err}", index + 1)
212+
}))
213+
})
214+
.collect()
215+
}
216+
217+
fn write_json_lines_file<T: Serialize>(dir: &Path, name: &str, values: &[T]) {
218+
fs::create_dir_all(dir).expect("Failed to create examples directory");
219+
let path = dir.join(format!("{name}.json"));
220+
let mut json_lines = String::new();
221+
for value in values {
222+
let line = serde_json::to_string(value).expect("Failed to serialize JSON line");
223+
json_lines.push_str(&line);
224+
json_lines.push('\n');
225+
}
226+
fs::write(&path, json_lines).expect("Failed to write example JSON lines");
227+
println!("Exported: {}", path.display());
228+
}
229+
230+
pub(crate) fn parse_rule_db_json_lines(json_lines: &str) -> RuleDb {
231+
RuleDb {
232+
rules: parse_json_lines(json_lines, "rule"),
233+
}
234+
}
235+
236+
pub(crate) fn parse_model_db_json_lines(json_lines: &str) -> ModelDb {
237+
ModelDb {
238+
models: parse_json_lines(json_lines, "model"),
239+
}
240+
}
241+
201242
/// Write a merged rule example JSON file.
202243
pub fn write_rule_example_to(dir: &Path, name: &str, example: &RuleExample) {
203244
write_json_file(dir, name, example);
@@ -220,12 +261,12 @@ pub fn write_model_example(name: &str, example: &ModelExample) {
220261

221262
/// Write the canonical rule database to `rules.json`.
222263
pub fn write_rule_db_to(dir: &Path, db: &RuleDb) {
223-
write_json_file(dir, "rules", db);
264+
write_json_lines_file(dir, "rules", &db.rules);
224265
}
225266

226267
/// Write the canonical model database to `models.json`.
227268
pub fn write_model_db_to(dir: &Path, db: &ModelDb) {
228-
write_json_file(dir, "models", db);
269+
write_json_lines_file(dir, "models", &db.models);
229270
}
230271

231272
#[cfg(test)]

src/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub(crate) mod spinglass_maxcut;
3636
pub(crate) mod spinglass_qubo;
3737
mod traits;
3838
pub(crate) mod travelingsalesman_qubo;
39+
#[cfg(test)]
40+
pub(crate) mod test_helpers;
3941

4042
pub mod unitdiskmapping;
4143

0 commit comments

Comments
 (0)