diff --git a/src/lib.rs b/src/lib.rs index 38b3239..46427e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2130,7 +2130,7 @@ fn prepare_and_validate_graphs( )> { use std::collections::HashMap; - let rank_graphs: HashMap> = runtime_estimations + let mut rank_graphs: HashMap> = runtime_estimations .iter() .map(|gr| { ( @@ -2144,6 +2144,12 @@ fn prepare_and_validate_graphs( acc }); + // Artifacts arrive in filesystem order, which can differ across ranks. + // Align matching graphs before comparing their runtimes by position. + for graphs in rank_graphs.values_mut() { + graphs.sort_by(|a, b| a.0.cmp(b.0)); + } + let max_graphs = rank_graphs.values().map(|graphs| graphs.len()).max()?; let min_graphs = rank_graphs.values().map(|graphs| graphs.len()).min()?; diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 9a1b0e2..3e30f02 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -2434,6 +2434,59 @@ fn test_runtime_analysis_working() -> Result<(), Box> { Ok(()) } +#[test] +fn test_runtime_analysis_independent_of_artifact_order() -> Result<(), Box> { + let (input_dir, output_dir) = setup_runtime_test_with_ranks(&[0, 1])?; + + Command::cargo_bin("tlparse")? + .arg(input_dir.path()) + .arg("--all-ranks-html") + .arg("--overwrite") + .arg("-o") + .arg(output_dir.path()) + .arg("--no-browser") + .assert() + .success(); + + let mut runtimes = + parsers::read_runtime_estimations(&output_dir.path().to_path_buf(), &[0, 1])?; + runtimes.sort_by(|a, b| a.rank.cmp(&b.rank).then_with(|| a.graph.cmp(&b.graph))); + let ordered = analyze_graph_runtime_deltas(&runtimes).expect("runtime analysis should exist"); + + // Artifact discovery uses directory enumeration, whose order is not guaranteed. + // Reverse only rank 1's graphs to exercise differing discovery orders across ranks. + runtimes.sort_by(|a, b| { + a.rank.cmp(&b.rank).then_with(|| { + if a.rank == 1 { + b.graph.cmp(&a.graph) + } else { + a.graph.cmp(&b.graph) + } + }) + }); + let permuted = analyze_graph_runtime_deltas(&runtimes).expect("runtime analysis should exist"); + + assert!(!permuted.has_mismatched_graph_counts); + assert_eq!(permuted.graphs.len(), 4); + for (graph, (expected_id, expected_delta)) in permuted.graphs.iter().zip([ + ("-_0_0_0", 0.690), + ("-_0_1_0", 0.693), + ("-_0_2_0", 0.693), + ("-_0_3_0", 0.693), + ]) { + assert_eq!(graph.graph_id, expected_id); + assert_eq!(graph.delta_ms, expected_delta, "graph {expected_id}"); + assert_eq!(graph.rank_details[0].rank, 0); + assert_eq!(graph.rank_details[1].rank, 1); + } + assert_eq!( + serde_json::to_value(permuted)?, + serde_json::to_value(ordered)? + ); + + Ok(()) +} + #[test] fn test_runtime_analysis_mismatched_graphs() -> Result<(), Box> { // Use entire directory - rank 4 is missing a graph compared to ranks 0,1,2,3