Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollup of 6 pull requests #138486

Closed
wants to merge 25 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5014353
Don't attempt to export compiler-builtins symbols from rust dylibs
bjorn3 Feb 27, 2025
d73479e
Fix lint name in unused linker_messages warning
bjorn3 Feb 27, 2025
61e550a
uefi: helpers: Add DevicePathNode abstractions
Ayush1325 Feb 22, 2025
6f214c5
Fix pluralization of tests
Kobzol Mar 13, 2025
208cef4
Add note about the experimental status
Kobzol Mar 13, 2025
2192d5c
Print the compared SHAs
Kobzol Mar 13, 2025
7ed913b
Add cache for downloading job metrics
Kobzol Mar 13, 2025
5a7f227
Collapse report in `<details>`
Kobzol Mar 13, 2025
d5d633d
Group diffs by tests, rather than job groups
Kobzol Mar 13, 2025
f981a0a
Do not print doctest diffs in the report
Kobzol Mar 13, 2025
c437178
Create libgccjit.so.0 alias also for CI-downloaded GCC
Kobzol Mar 12, 2025
955aef5
Change GCC build flags
Kobzol Mar 12, 2025
3fc7ca0
Use GCC for building GCC
Kobzol Mar 13, 2025
34272a5
Do not overwrite original `config.toml` in `opt-dist`
Kobzol Mar 13, 2025
38fc116
Store libgccjit.so in a lib directory in the GCC CI tarball
Kobzol Mar 13, 2025
4c32adb
Deny impls for BikeshedGuaranteedNoDrop
compiler-errors Mar 14, 2025
90bf2b1
Show valid crate types when the user passes unknown `--crate-type` value
malezjaa Dec 24, 2024
6ef465b
Add clarification about doctests
Kobzol Mar 14, 2025
bf095f6
Ensure that GCC is not built using Clang, as it misbehaves
Kobzol Mar 14, 2025
52b9169
Rollup merge of #134720 - malezjaa:feat/crate-type-valid-values, r=ji…
jhpratt Mar 14, 2025
a95e9ef
Rollup merge of #137424 - Ayush1325:uefi-path-node, r=nicholasbishop,…
jhpratt Mar 14, 2025
693bada
Rollup merge of #137736 - bjorn3:compiler_builtins_export_fix, r=petr…
jhpratt Mar 14, 2025
e7a3465
Rollup merge of #138451 - Kobzol:gcc-ci-build-gcc, r=GuillaumeGomez
jhpratt Mar 14, 2025
3ec4d0e
Rollup merge of #138454 - Kobzol:post-merge-workflow-fixes, r=jieyouxu
jhpratt Mar 14, 2025
98c85a3
Rollup merge of #138477 - compiler-errors:deny-bikeshed-guaranteed-no…
jhpratt Mar 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Group diffs by tests, rather than job groups
  • Loading branch information
Kobzol committed Mar 13, 2025
commit d5d633d2460e45df25c679933ea678367b4d94a3
162 changes: 86 additions & 76 deletions src/ci/citool/src/merge_report.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::cmp::Reverse;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;

use anyhow::Context;
@@ -14,10 +13,10 @@ type JobName = String;
/// Computes a post merge CI analysis report between the `parent` and `current` commits.
pub fn post_merge_report(job_db: JobDatabase, parent: Sha, current: Sha) -> anyhow::Result<()> {
let jobs = download_all_metrics(&job_db, &parent, &current)?;
let diffs = aggregate_test_diffs(&jobs)?;
let aggregated_test_diffs = aggregate_test_diffs(&jobs)?;

println!("Comparing {parent} (base) -> {current} (this PR)\n");
report_test_changes(diffs);
report_test_diffs(aggregated_test_diffs);

Ok(())
}
@@ -95,81 +94,80 @@ fn get_metrics_url(job_name: &str, sha: &str) -> String {
format!("https://ci-artifacts.rust-lang.org/rustc-builds{suffix}/{sha}/metrics-{job_name}.json")
}

/// Represents a difference in the outcome of tests between a base and a current commit.
/// Maps test diffs to jobs that contained them.
#[derive(Debug)]
struct AggregatedTestDiffs {
diffs: HashMap<TestDiff, Vec<JobName>>,
}

fn aggregate_test_diffs(
jobs: &HashMap<JobName, JobMetrics>,
) -> anyhow::Result<Vec<AggregatedTestDiffs>> {
let mut job_diffs = vec![];
) -> anyhow::Result<AggregatedTestDiffs> {
let mut diffs: HashMap<TestDiff, Vec<JobName>> = HashMap::new();

// Aggregate test suites
for (name, metrics) in jobs {
if let Some(parent) = &metrics.parent {
let tests_parent = aggregate_tests(parent);
let tests_current = aggregate_tests(&metrics.current);
let test_diffs = calculate_test_diffs(tests_parent, tests_current);
if !test_diffs.is_empty() {
job_diffs.push((name.clone(), test_diffs));
for diff in calculate_test_diffs(tests_parent, tests_current) {
diffs.entry(diff).or_default().push(name.to_string());
}
}
}

// Aggregate jobs with the same diff, as often the same diff will appear in many jobs
let job_diffs: HashMap<Vec<(Test, TestOutcomeDiff)>, Vec<String>> =
job_diffs.into_iter().fold(HashMap::new(), |mut acc, (job, diffs)| {
acc.entry(diffs).or_default().push(job);
acc
});

Ok(job_diffs
.into_iter()
.map(|(test_diffs, jobs)| AggregatedTestDiffs { jobs, test_diffs })
.collect())
Ok(AggregatedTestDiffs { diffs })
}

#[derive(Eq, PartialEq, Hash, Debug)]
enum TestOutcomeDiff {
ChangeOutcome { before: TestOutcome, after: TestOutcome },
Missing { before: TestOutcome },
Added(TestOutcome),
}

#[derive(Eq, PartialEq, Hash, Debug)]
struct TestDiff {
test: Test,
diff: TestOutcomeDiff,
}

fn calculate_test_diffs(
reference: TestSuiteData,
current: TestSuiteData,
) -> Vec<(Test, TestOutcomeDiff)> {
let mut diffs = vec![];
fn calculate_test_diffs(parent: TestSuiteData, current: TestSuiteData) -> HashSet<TestDiff> {
let mut diffs = HashSet::new();
for (test, outcome) in &current.tests {
match reference.tests.get(test) {
match parent.tests.get(test) {
Some(before) => {
if before != outcome {
diffs.push((
test.clone(),
TestOutcomeDiff::ChangeOutcome {
diffs.insert(TestDiff {
test: test.clone(),
diff: TestOutcomeDiff::ChangeOutcome {
before: before.clone(),
after: outcome.clone(),
},
));
});
}
}
None => diffs.push((test.clone(), TestOutcomeDiff::Added(outcome.clone()))),
None => {
diffs.insert(TestDiff {
test: test.clone(),
diff: TestOutcomeDiff::Added(outcome.clone()),
});
}
}
}
for (test, outcome) in &reference.tests {
for (test, outcome) in &parent.tests {
if !current.tests.contains_key(test) {
diffs.push((test.clone(), TestOutcomeDiff::Missing { before: outcome.clone() }));
diffs.insert(TestDiff {
test: test.clone(),
diff: TestOutcomeDiff::Missing { before: outcome.clone() },
});
}
}

diffs
}

/// Represents a difference in the outcome of tests between a base and a current commit.
#[derive(Debug)]
struct AggregatedTestDiffs {
/// All jobs that had the exact same test diffs.
jobs: Vec<String>,
test_diffs: Vec<(Test, TestOutcomeDiff)>,
}

#[derive(Eq, PartialEq, Hash, Debug)]
enum TestOutcomeDiff {
ChangeOutcome { before: TestOutcome, after: TestOutcome },
Missing { before: TestOutcome },
Added(TestOutcome),
}

/// Aggregates test suite executions from all bootstrap invocations in a given CI job.
#[derive(Default)]
struct TestSuiteData {
@@ -200,16 +198,13 @@ fn normalize_test_name(name: &str) -> String {
}

/// Prints test changes in Markdown format to stdout.
fn report_test_changes(mut diffs: Vec<AggregatedTestDiffs>) {
fn report_test_diffs(mut diff: AggregatedTestDiffs) {
println!("## Test differences");
if diffs.is_empty() {
if diff.diffs.is_empty() {
println!("No test diffs found");
return;
}

// Sort diffs in decreasing order by diff count
diffs.sort_by_key(|entry| Reverse(entry.test_diffs.len()));

fn format_outcome(outcome: &TestOutcome) -> String {
match outcome {
TestOutcome::Passed => "pass".to_string(),
@@ -238,36 +233,51 @@ fn report_test_changes(mut diffs: Vec<AggregatedTestDiffs>) {
}
}

let max_diff_count = 10;
let max_job_count = 5;
let max_test_count = 10;

for diff in diffs.iter().take(max_diff_count) {
let mut jobs = diff.jobs.clone();
// It would be quite noisy to repeat the jobs that contained the test changes after/next to
// every test diff. At the same time, grouping the test diffs by
// [unique set of jobs that contained them] also doesn't work well, because the test diffs
// would have to be duplicated several times.
// Instead, we create a set of unique job groups, and then print a job group after each test.
// We then print the job groups at the end, as a sort of index.
let mut grouped_diffs: Vec<(&TestDiff, u64)> = vec![];
let mut job_list_to_group: HashMap<&[JobName], u64> = HashMap::new();
let mut job_index: Vec<&[JobName]> = vec![];

for (_, jobs) in diff.diffs.iter_mut() {
jobs.sort();
}

let jobs = jobs.iter().take(max_job_count).map(|j| format!("`{j}`")).collect::<Vec<_>>();

let extra_jobs = diff.jobs.len().saturating_sub(max_job_count);
let suffix = if extra_jobs > 0 {
format!(" (and {extra_jobs} {})", pluralize("other", extra_jobs))
} else {
String::new()
let max_diff_count = 100;
for (diff, jobs) in diff.diffs.iter().take(max_diff_count) {
let jobs = &*jobs;
let job_group = match job_list_to_group.get(jobs.as_slice()) {
Some(id) => *id,
None => {
let id = job_index.len() as u64;
job_index.push(jobs);
job_list_to_group.insert(jobs, id);
id
}
};
println!("- {}{suffix}", jobs.join(","));
grouped_diffs.push((diff, job_group));
}

let extra_tests = diff.test_diffs.len().saturating_sub(max_test_count);
for (test, outcome_diff) in diff.test_diffs.iter().take(max_test_count) {
println!(" - {}: {}", test.name, format_diff(&outcome_diff));
}
if extra_tests > 0 {
println!(" - (and {extra_tests} additional {})", pluralize("test", extra_tests));
}
// Sort diffs by job group and test name
grouped_diffs.sort_by(|(d1, g1), (d2, g2)| g1.cmp(&g2).then(d1.test.name.cmp(&d2.test.name)));

for (diff, job_group) in grouped_diffs {
println!("- `{}`: {} (*J{job_group}*)", diff.test.name, format_diff(&diff.diff));
}

let extra_diffs = diffs.len().saturating_sub(max_diff_count);
let extra_diffs = diff.diffs.len().saturating_sub(max_diff_count);
if extra_diffs > 0 {
println!("\n(and {extra_diffs} additional {})", pluralize("diff", extra_diffs));
println!("\n(and {extra_diffs} additional {})", pluralize("test diff", extra_diffs));
}

// Now print the job index
println!("\n**Job index**\n");
for (group, jobs) in job_index.into_iter().enumerate() {
println!("- J{group}: `{}`", jobs.join(", "));
}
}