Skip to main content

backhopper_xref/
suites.rs

1// Copyright (C) 2026 Michael S. Klishin and Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5//! Test-suite-selection adapter.
6//!
7//! Given a set of modified modules `S`, find suites that reference them.
8//! With the xref module call graph this is one reverse-traversal query.
9
10use std::collections::BTreeSet;
11
12use backhopper_core::{ApplicationName, Mfa, ModuleName};
13use backhopper_xref_graph::{Functions, Vertex, VertexSet};
14use serde::{Deserialize, Serialize};
15
16use crate::xref::Xref;
17
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
19pub struct SuiteRef {
20    pub application: Option<ApplicationName>,
21    pub module: ModuleName,
22}
23
24/// Modules that call into any of `modules` and match `suite_predicate`.
25///
26/// Useful for R3, R4, R5 from the test-suite-selection rules: pass the
27/// modified-modules set and a predicate that recognises `_SUITE` modules.
28pub fn suites_referencing(
29    xref: &Xref<Functions>,
30    modules: &BTreeSet<ModuleName>,
31    suite_predicate: impl Fn(&ModuleName) -> bool,
32) -> BTreeSet<SuiteRef> {
33    let targets: VertexSet = modules.iter().map(|m| Vertex::Module(m.clone())).collect();
34    let callers = xref.graph().module_edges().preimage(&targets);
35    callers
36        .iter()
37        .filter_map(|v| v.as_module().cloned())
38        .filter(|m| suite_predicate(m))
39        .map(|module| {
40            let application = xref
41                .graph()
42                .module(&module)
43                .and_then(|s| s.application.clone());
44            SuiteRef {
45                application,
46                module,
47            }
48        })
49        .collect()
50}
51
52/// Suites that call any of the listed MFAs.
53pub fn suites_referencing_mfas(
54    xref: &Xref<Functions>,
55    targets: &BTreeSet<Mfa>,
56    suite_predicate: impl Fn(&ModuleName) -> bool,
57) -> BTreeSet<SuiteRef> {
58    let target_vs: VertexSet = targets
59        .iter()
60        .map(|m| Vertex::Function(m.clone()))
61        .collect();
62    let callers = xref.graph().external_calls().preimage(&target_vs);
63    let mut suite_modules: BTreeSet<ModuleName> = BTreeSet::new();
64    for v in callers.iter() {
65        if let Vertex::Function(mfa) = v {
66            if suite_predicate(&mfa.module) {
67                suite_modules.insert(mfa.module.clone());
68            }
69        }
70    }
71    suite_modules
72        .into_iter()
73        .map(|module| {
74            let application = xref
75                .graph()
76                .module(&module)
77                .and_then(|s| s.application.clone());
78            SuiteRef {
79                application,
80                module,
81            }
82        })
83        .collect()
84}
85
86/// Default suite predicate: module name ends with `_SUITE`.
87pub fn is_suite_module(name: &ModuleName) -> bool {
88    name.as_str().ends_with("_SUITE")
89}