Expand runtime event graph service
This commit is contained in:
parent
049ffa6bd8
commit
6ebe5fffeb
14 changed files with 1803 additions and 254 deletions
|
|
@ -4,7 +4,10 @@ use std::fs;
|
|||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rrt_fixtures::{FixtureValidationReport, load_fixture_document, validate_fixture_document};
|
||||
use rrt_fixtures::{
|
||||
FixtureValidationReport, JsonDiffEntry, compare_expected_state_fragment, diff_json_values,
|
||||
load_fixture_document, normalize_runtime_state, validate_fixture_document,
|
||||
};
|
||||
use rrt_model::{
|
||||
BINARY_SUMMARY_PATH, CANONICAL_EXE_PATH, CONTROL_LOOP_ATLAS_PATH, FUNCTION_MAP_PATH,
|
||||
REQUIRED_ATLAS_HEADINGS, REQUIRED_EXPORTS,
|
||||
|
|
@ -95,6 +98,10 @@ enum Command {
|
|||
fixture_path: PathBuf,
|
||||
output_path: PathBuf,
|
||||
},
|
||||
RuntimeDiffState {
|
||||
left_path: PathBuf,
|
||||
right_path: PathBuf,
|
||||
},
|
||||
RuntimeSummarizeState {
|
||||
snapshot_path: PathBuf,
|
||||
},
|
||||
|
|
@ -195,6 +202,8 @@ struct RuntimeFixtureSummaryReport {
|
|||
final_summary: RuntimeSummary,
|
||||
expected_summary_matches: bool,
|
||||
expected_summary_mismatches: Vec<String>,
|
||||
expected_state_fragment_matches: bool,
|
||||
expected_state_fragment_mismatches: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
@ -203,6 +212,13 @@ struct RuntimeStateSummaryReport {
|
|||
summary: RuntimeSummary,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RuntimeStateDiffReport {
|
||||
matches: bool,
|
||||
difference_count: usize,
|
||||
differences: Vec<JsonDiffEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RuntimeSmpInspectionOutput {
|
||||
path: String,
|
||||
|
|
@ -724,6 +740,12 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
} => {
|
||||
run_runtime_export_fixture_state(&fixture_path, &output_path)?;
|
||||
}
|
||||
Command::RuntimeDiffState {
|
||||
left_path,
|
||||
right_path,
|
||||
} => {
|
||||
run_runtime_diff_state(&left_path, &right_path)?;
|
||||
}
|
||||
Command::RuntimeSummarizeState { snapshot_path } => {
|
||||
run_runtime_summarize_state(&snapshot_path)?;
|
||||
}
|
||||
|
|
@ -859,6 +881,14 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
|||
output_path: PathBuf::from(output_path),
|
||||
})
|
||||
}
|
||||
[command, subcommand, left_path, right_path]
|
||||
if command == "runtime" && subcommand == "diff-state" =>
|
||||
{
|
||||
Ok(Command::RuntimeDiffState {
|
||||
left_path: PathBuf::from(left_path),
|
||||
right_path: PathBuf::from(right_path),
|
||||
})
|
||||
}
|
||||
[command, subcommand, path] if command == "runtime" && subcommand == "summarize-state" => {
|
||||
Ok(Command::RuntimeSummarizeState {
|
||||
snapshot_path: PathBuf::from(path),
|
||||
|
|
@ -1039,7 +1069,7 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
|||
})
|
||||
}
|
||||
_ => Err(
|
||||
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
|
||||
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
|
||||
.into(),
|
||||
),
|
||||
}
|
||||
|
|
@ -1083,20 +1113,31 @@ fn run_runtime_summarize_fixture(fixture_path: &Path) -> Result<(), Box<dyn std:
|
|||
}
|
||||
|
||||
let final_summary = RuntimeSummary::from_state(&state);
|
||||
let mismatches = fixture.expected_summary.compare(&final_summary);
|
||||
let expected_summary_mismatches = fixture.expected_summary.compare(&final_summary);
|
||||
let expected_state_fragment_mismatches = match &fixture.expected_state_fragment {
|
||||
Some(expected_fragment) => {
|
||||
let normalized_state = normalize_runtime_state(&state)?;
|
||||
compare_expected_state_fragment(expected_fragment, &normalized_state)
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
let report = RuntimeFixtureSummaryReport {
|
||||
fixture_id: fixture.fixture_id,
|
||||
command_count: fixture.commands.len(),
|
||||
expected_summary_matches: mismatches.is_empty(),
|
||||
expected_summary_mismatches: mismatches.clone(),
|
||||
expected_summary_matches: expected_summary_mismatches.is_empty(),
|
||||
expected_summary_mismatches: expected_summary_mismatches.clone(),
|
||||
expected_state_fragment_matches: expected_state_fragment_mismatches.is_empty(),
|
||||
expected_state_fragment_mismatches: expected_state_fragment_mismatches.clone(),
|
||||
final_summary,
|
||||
};
|
||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
||||
|
||||
if !mismatches.is_empty() {
|
||||
if !expected_summary_mismatches.is_empty() || !expected_state_fragment_mismatches.is_empty() {
|
||||
let mut mismatch_messages = expected_summary_mismatches;
|
||||
mismatch_messages.extend(expected_state_fragment_mismatches);
|
||||
return Err(format!(
|
||||
"fixture summary mismatched expected output: {}",
|
||||
mismatches.join("; ")
|
||||
mismatch_messages.join("; ")
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
|
@ -1159,6 +1200,33 @@ fn run_runtime_summarize_state(snapshot_path: &Path) -> Result<(), Box<dyn std::
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_runtime_diff_state(
|
||||
left_path: &Path,
|
||||
right_path: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let left = load_normalized_runtime_state(left_path)?;
|
||||
let right = load_normalized_runtime_state(right_path)?;
|
||||
let differences = diff_json_values(&left, &right);
|
||||
let report = RuntimeStateDiffReport {
|
||||
matches: differences.is_empty(),
|
||||
difference_count: differences.len(),
|
||||
differences,
|
||||
};
|
||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_normalized_runtime_state(path: &Path) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
if let Ok(snapshot) = load_runtime_snapshot_document(path) {
|
||||
validate_runtime_snapshot_document(&snapshot)
|
||||
.map_err(|err| format!("invalid runtime snapshot: {err}"))?;
|
||||
return normalize_runtime_state(&snapshot.state);
|
||||
}
|
||||
|
||||
let import = load_runtime_state_import(path)?;
|
||||
normalize_runtime_state(&import.state)
|
||||
}
|
||||
|
||||
fn run_runtime_import_state(
|
||||
input_path: &Path,
|
||||
output_path: &Path,
|
||||
|
|
@ -3834,6 +3902,14 @@ mod tests {
|
|||
"company_count": 0,
|
||||
"event_runtime_record_count": 0,
|
||||
"total_company_cash": 0
|
||||
},
|
||||
"expected_state_fragment": {
|
||||
"calendar": {
|
||||
"tick_slot": 3
|
||||
},
|
||||
"world_flags": {
|
||||
"sandbox": false
|
||||
}
|
||||
}
|
||||
});
|
||||
let path = write_temp_json("runtime-fixture", &fixture);
|
||||
|
|
@ -3938,6 +4014,121 @@ mod tests {
|
|||
let _ = fs::remove_file(output_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diffs_runtime_states_recursively() {
|
||||
let left = serde_json::json!({
|
||||
"format_version": 1,
|
||||
"snapshot_id": "left",
|
||||
"state": {
|
||||
"calendar": {
|
||||
"year": 1830,
|
||||
"month_slot": 0,
|
||||
"phase_slot": 0,
|
||||
"tick_slot": 1
|
||||
},
|
||||
"world_flags": {
|
||||
"sandbox": false
|
||||
},
|
||||
"companies": []
|
||||
}
|
||||
});
|
||||
let right = serde_json::json!({
|
||||
"format_version": 1,
|
||||
"snapshot_id": "right",
|
||||
"state": {
|
||||
"calendar": {
|
||||
"year": 1830,
|
||||
"month_slot": 0,
|
||||
"phase_slot": 0,
|
||||
"tick_slot": 2
|
||||
},
|
||||
"world_flags": {
|
||||
"sandbox": true
|
||||
},
|
||||
"companies": []
|
||||
}
|
||||
});
|
||||
let left_path = write_temp_json("runtime-diff-left", &left);
|
||||
let right_path = write_temp_json("runtime-diff-right", &right);
|
||||
|
||||
run_runtime_diff_state(&left_path, &right_path).expect("runtime diff should succeed");
|
||||
|
||||
let _ = fs::remove_file(left_path);
|
||||
let _ = fs::remove_file(right_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diffs_runtime_states_with_event_record_additions_and_removals() {
|
||||
let left = serde_json::json!({
|
||||
"format_version": 1,
|
||||
"snapshot_id": "left-events",
|
||||
"state": {
|
||||
"calendar": {
|
||||
"year": 1830,
|
||||
"month_slot": 0,
|
||||
"phase_slot": 0,
|
||||
"tick_slot": 1
|
||||
},
|
||||
"world_flags": {
|
||||
"sandbox": false
|
||||
},
|
||||
"companies": [],
|
||||
"event_runtime_records": [
|
||||
{
|
||||
"record_id": 1,
|
||||
"trigger_kind": 7,
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"record_id": 2,
|
||||
"trigger_kind": 7,
|
||||
"active": false
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
let right = serde_json::json!({
|
||||
"format_version": 1,
|
||||
"snapshot_id": "right-events",
|
||||
"state": {
|
||||
"calendar": {
|
||||
"year": 1830,
|
||||
"month_slot": 0,
|
||||
"phase_slot": 0,
|
||||
"tick_slot": 1
|
||||
},
|
||||
"world_flags": {
|
||||
"sandbox": false
|
||||
},
|
||||
"companies": [],
|
||||
"event_runtime_records": [
|
||||
{
|
||||
"record_id": 1,
|
||||
"trigger_kind": 7,
|
||||
"active": true
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
let left_path = write_temp_json("runtime-diff-events-left", &left);
|
||||
let right_path = write_temp_json("runtime-diff-events-right", &right);
|
||||
|
||||
let left_state =
|
||||
load_normalized_runtime_state(&left_path).expect("left runtime state should load");
|
||||
let right_state =
|
||||
load_normalized_runtime_state(&right_path).expect("right runtime state should load");
|
||||
let differences = diff_json_values(&left_state, &right_state);
|
||||
|
||||
assert!(
|
||||
differences
|
||||
.iter()
|
||||
.any(|entry| entry.path == "$.event_runtime_records[1]")
|
||||
);
|
||||
|
||||
let _ = fs::remove_file(left_path);
|
||||
let _ = fs::remove_file(right_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diffs_classic_profile_samples_across_multiple_files() {
|
||||
let sample_a = RuntimeClassicProfileSample {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue