Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ pub(crate) struct TestHit {
/// Why a test was selected. The triple (`file`, `matched_hunk`, `kind`)
/// describes one path through the selection logic; `stored_range` names the
/// row that matched (absent for [`HitKind::StructuralBackstop`], where the
/// whole point is that nothing matched).
/// whole point is that nothing matched, and for [`HitKind::ConfigRule`],
/// which is anchored to a config rule rather than to coverage).
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct HitReason {
/// `collect_sha` the diff was anchored against. Hunks live in this
Expand All @@ -170,7 +171,8 @@ pub(crate) struct HitReason {
/// The diff hunk that triggered the selection. Inclusive `(start, end)`.
pub(crate) matched_hunk: (i64, i64),
/// The stored row that overlapped the hunk. `None` when [`HitKind::StructuralBackstop`]
/// fired (no row overlapped — the test was pulled in by file-presence alone).
/// fired (no row overlapped — the test was pulled in by file-presence alone),
/// and for [`HitKind::ConfigRule`], which isn't anchored to coverage at all.
pub(crate) stored_range: Option<(i64, i64)>,
}

Expand Down
1 change: 0 additions & 1 deletion src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,6 @@ pub(crate) fn write_selection_report(inputs: SelectionReport, path: &Path) -> Re
&plan.changed_ranges,
changed_files,
)?,
include_changed_files: true,
};
Report::build_selection(report_inputs).write_json(path)
}
Expand Down
64 changes: 52 additions & 12 deletions src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ pub(crate) struct ReasonEntry {
pub(crate) file: String,
pub(crate) kind: ReasonKind,
/// `[line_start, line_end]` of the stored row that matched. `None`
/// for `structural_backstop` (no row matched by definition).
/// for `structural_backstop` and `config_rule` — neither is anchored
/// to a coverage row, by definition.
pub(crate) stored_range: Option<[i64; 2]>,
/// `[start, end]` of the diff hunk that triggered selection.
pub(crate) matched_hunk: [i64; 2],
Expand Down Expand Up @@ -294,9 +295,6 @@ pub(crate) struct SelectionInputs<'a> {
pub(crate) status: CacheStatus,
pub(crate) selection: &'a Selection,
pub(crate) changed_files: Vec<ChangedFileInput>,
/// `false` collapses to `selection.changed_files = None` (no diff
/// anchor was usable).
pub(crate) include_changed_files: bool,
}

/// Inputs for [`Report::build_full_suite`] — the partial-report path for
Expand Down Expand Up @@ -486,14 +484,12 @@ impl Report {
mode: SelectionMode::Selection,
};

let changed_files = if inputs.include_changed_files {
Some(build_changed_files_entries(
&inputs.changed_files,
&selection.diagnostics.per_file,
))
} else {
None
};
// Selection mode always has a usable diff anchor, so this is always
// `Some`; the `null` shape belongs to `build_full_suite`.
let changed_files = Some(build_changed_files_entries(
&inputs.changed_files,
&selection.diagnostics.per_file,
));

let selected_tests = selection.diagnostics.per_test.as_ref().map(|per_test| {
build_selected_tests(
Expand Down Expand Up @@ -953,6 +949,50 @@ mod tests {
assert_eq!(entries[2].commits_ahead, None);
}

/// A `config_rule` reason names the triggering input rather than a
/// coverage hunk: `stored_range` is null, `collect_sha` is empty and
/// `matched_hunk` is `[0, 0]`. `selection.rs` fills those three fields by
/// hand, so nothing else pins the encoding the v1 schema documents.
#[test]
fn config_rule_reason_carries_no_coverage_anchor() {
let test = TestId::new("sample::tests", "reads_snapshot");
let per_test: BTreeMap<TestId, Vec<HitReason>> = [(
test.clone(),
vec![HitReason {
collect_sha: String::new(),
file: "tests/snapshots/x.snap".to_string(),
kind: HitKind::ConfigRule,
matched_hunk: (0, 0),
stored_range: None,
}],
)]
.into_iter()
.collect();
let config_tests: BTreeSet<TestId> = [test].into_iter().collect();

let entries = build_selected_tests(
&BTreeSet::new(),
&config_tests,
&BTreeSet::new(),
&BTreeSet::new(),
&per_test,
);

assert_eq!(entries.len(), 1);
assert_eq!(entries[0].kind, SelectedTestKind::ConfigRule);
let json = serde_json::to_value(&entries[0]).unwrap();
assert_eq!(json["reasons"][0]["kind"], "config_rule");
assert!(
json["reasons"][0]["stored_range"].is_null(),
"config_rule matches no coverage row, so stored_range must be null"
);
assert_eq!(json["reasons"][0]["collect_sha"], "");
assert_eq!(
json["reasons"][0]["matched_hunk"],
serde_json::json!([0, 0])
);
}

/// Build a minimal full-suite report and verify the shape callers
/// expect — null counts, omitted changed_files/selected_tests, mode
/// = "full-suite-no-listing".
Expand Down
Loading