Skip to content

Commit

Permalink
Refactor test logic. Break general stages into substages
Browse files Browse the repository at this point in the history
In PR #421, tests were unintentionally passing due to expected files
being overwritten regardless of whether the generated solutions matched
Conjure’s solutions.

This commit refactors the integration_test_inner function and related
file I/O utilities so that:

* When ACCEPT=false, tests still compare generated outputs (parse,
  rewrite, solutions, and traces) against the expected results as
  before.

* When ACCEPT=true, tests now first verify that the generated solutions
  match the Conjure solutions. Only if they match are the expected files
  updated. If they don’t match, the test fails without updating the
  expected files.

Stages desciption:
	1a: Parse (Convert the Essence model into an internal format.)
	1b: Check against other parser(s) (Compare output from different parsing methods (e.g., native parser vs JSON parser).)
	2a: Rewrite (Apply transformations to the parsed model (e.g., simplifying expressions, normalizing).)
	2b: Check model properties (extra_asserts) (Verify additional model properties (e.g., ensure vector operators are evaluated).)
	3a: Solve (Run the model through the solver to find solutions.)
	3b: Check solutions against Conjure (Compare solutions with expected output from the Conjure solver.)
	4a: Check that the generated rules match expected traces
  • Loading branch information
YehorBoiar committed Feb 7, 2025
1 parent 0ef8705 commit 74fbd4b
Show file tree
Hide file tree
Showing 2 changed files with 279 additions and 258 deletions.
128 changes: 30 additions & 98 deletions conjure_oxide/src/utils/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,10 @@ pub fn save_model_json(
path: &str,
test_name: &str,
test_stage: &str,
accept: bool,
) -> Result<(), std::io::Error> {
let generated_json_str = serialise_model(model)?;

File::create(format!(
"{path}/{test_name}.generated-{test_stage}.serialised.json"
))?
.write_all(generated_json_str.as_bytes())?;

if accept {
std::fs::copy(
format!("{path}/{test_name}.generated-{test_stage}.serialised.json"),
format!("{path}/{test_name}.expected-{test_stage}.serialised.json"),
)?;
}

let filename = format!("{path}/{test_name}.generated-{test_stage}.serialised.json");
File::create(&filename)?.write_all(generated_json_str.as_bytes())?;
Ok(())
}

Expand Down Expand Up @@ -147,27 +135,17 @@ pub fn minion_solutions_from_json(
Ok(solutions)
}

/// Writes the minion solutions to a generated JSON file, and returns the JSON structure.
pub fn save_minion_solutions_json(
solutions: &Vec<BTreeMap<Name, Literal>>,
path: &str,
test_name: &str,
accept: bool,
) -> Result<JsonValue, std::io::Error> {
let json_solutions = minion_solutions_to_json(solutions);

let generated_json_str = serde_json::to_string_pretty(&json_solutions)?;

File::create(format!(
"{path}/{test_name}.generated-minion.solutions.json"
))?
.write_all(generated_json_str.as_bytes())?;

if accept {
std::fs::copy(
format!("{path}/{test_name}.generated-minion.solutions.json"),
format!("{path}/{test_name}.expected-minion.solutions.json"),
)?;
}
let filename = format!("{path}/{test_name}.generated-minion.solutions.json");
File::create(&filename)?.write_all(generated_json_str.as_bytes())?;

Ok(json_solutions)
}
Expand All @@ -186,97 +164,51 @@ pub fn read_minion_solutions_json(
Ok(expected_solutions)
}

/// Reads a rule trace from a file. For the generated prefix, it appends a count message.
/// Returns the lines of the file as a vector of strings.
pub fn read_rule_trace(
path: &str,
test_name: &str,
prefix: &str,
accept: bool,
) -> Result<JsonValue, anyhow::Error> {
) -> Result<Vec<String>, std::io::Error> {
let filename = format!("{path}/{test_name}-{prefix}-rule-trace.json");
let mut rules_trace: Vec<String> = read_to_string(&filename)?
.lines()
.map(String::from)
.collect();

let rule_traces = if prefix == "generated" {
count_and_sort_rules(&filename)?
} else {
let file_contents = std::fs::read_to_string(filename)?;
serde_json::from_str(&file_contents)?
};

if accept {
std::fs::copy(
format!("{path}/{test_name}-generated-rule-trace.json"),
format!("{path}/{test_name}-expected-rule-trace.json"),
)?;
}

Ok(rule_traces)
}

pub fn count_and_sort_rules(filename: &str) -> Result<JsonValue, anyhow::Error> {
let file_contents = read_to_string(filename)?;

let sorted_json_rules = if file_contents.trim().is_empty() {
let rule_count_message = json!({
"Number of rules applied": 0,
});
rule_count_message
} else {
let rule_count = file_contents.lines().count();
let mut sorted_json_rules = sort_json_rules(&file_contents)?;

let rule_count_message = json!({
"Number of rules applied": rule_count,
// If prefix is "generated", append the count message
if prefix == "generated" {
let rule_count = rules_trace.len();
let count_message = json!({
"message": "Number of rules applied",
"count": rule_count
});

if let Some(array) = sorted_json_rules.as_array_mut() {
array.push(rule_count_message);
} else {
return Err(anyhow::anyhow!("Expected JSON array"));
}
sort_json_object(&sorted_json_rules, false)
};

let generated_sorted_json_rules = serde_json::to_string_pretty(&sorted_json_rules)?;

let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.open(filename)?;

file.write_all(generated_sorted_json_rules.as_bytes())?;

Ok(sorted_json_rules)
}

fn sort_json_rules(json_rule_traces: &str) -> Result<JsonValue, anyhow::Error> {
let mut sorted_rule_traces = Vec::new();

for line in json_rule_traces.lines() {
let pretty_json = sort_json_object(&serde_json::from_str(line)?, true);
sorted_rule_traces.push(pretty_json);
let count_message_string = serde_json::to_string(&count_message)?;
rules_trace.push(count_message_string);

// Overwrite the file with updated content (including the count message)
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.open(&filename)?;
writeln!(file, "{}", rules_trace.join("\n"))?;
}

Ok(JsonValue::Array(sorted_rule_traces))
Ok(rules_trace)
}

/// Reads a human-readable rule trace text file.
pub fn read_human_rule_trace(
path: &str,
test_name: &str,
prefix: &str,
accept: bool,
) -> Result<Vec<String>, std::io::Error> {
let filename = format!("{path}/{test_name}-{prefix}-rule-trace-human.txt");
let rules_trace: Vec<String> = read_to_string(&filename)
.unwrap()
let rules_trace: Vec<String> = read_to_string(&filename)?
.lines()
.map(String::from)
.collect();

if accept {
std::fs::copy(
format!("{path}/{test_name}-generated-rule-trace-human.txt"),
format!("{path}/{test_name}-expected-rule-trace-human.txt"),
)?;
}

Ok(rules_trace)
}
Loading

0 comments on commit 74fbd4b

Please sign in to comment.