1use std::collections::HashSet;
16
17use crate::symbols::SymbolGraph;
18use crate::tokens::estimate_tokens;
19use crate::{skeleton, Lang};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum Archetype {
25 SingleFileEdit,
26 Refactor,
27 Rename,
28 Feature,
29}
30
31pub struct Fixture {
33 pub name: &'static str,
34 pub archetype: Archetype,
35 pub files: Vec<(Lang, String)>,
37 pub target: String,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct BudgetReport {
44 pub radius: u8,
45 pub est_tokens: usize,
46 pub kept_symbols: usize,
47}
48
49impl Fixture {
50 pub fn context_at(&self, radius: u8) -> String {
54 let graph = SymbolGraph::from_sources(self.files.iter().map(|(l, s)| (*l, s.as_str())));
55 let keep = graph.neighbors_within_by_file(&self.target, radius);
58 self.files
59 .iter()
60 .enumerate()
61 .map(|(i, (lang, source))| skeleton::skeletonize(source, *lang, &keep[i]))
62 .collect::<Vec<_>>()
63 .join("\n")
64 }
65
66 pub fn measure(&self, radius: u8) -> BudgetReport {
68 let graph = SymbolGraph::from_sources(self.files.iter().map(|(l, s)| (*l, s.as_str())));
69 let keep = graph.neighbors_within_by_file(&self.target, radius);
70 let context = self
71 .files
72 .iter()
73 .enumerate()
74 .map(|(i, (lang, source))| skeleton::skeletonize(source, *lang, &keep[i]))
75 .collect::<Vec<_>>()
76 .join("\n");
77 BudgetReport {
78 radius,
79 est_tokens: estimate_tokens(&context),
80 kept_symbols: keep.iter().map(HashSet::len).sum(),
81 }
82 }
83
84 pub fn sweep(&self, max_radius: u8) -> Vec<BudgetReport> {
86 (0..=max_radius).map(|r| self.measure(r)).collect()
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 fn fixture() -> Fixture {
95 Fixture {
96 name: "demo",
97 archetype: Archetype::SingleFileEdit,
98 files: vec![(
99 Lang::Rust,
100 "\
101fn a() -> i32 { b() + 1 }
102fn b() -> i32 { c() + 1 }
103fn c() -> i32 { 1 }
104fn unrelated() -> i32 { 99 }
105"
106 .to_string(),
107 )],
108 target: "a".to_string(),
109 }
110 }
111
112 #[test]
113 fn kept_set_never_shrinks_with_radius() {
114 let reports = fixture().sweep(3);
118 for pair in reports.windows(2) {
119 assert!(
120 pair[1].kept_symbols >= pair[0].kept_symbols,
121 "kept set shrank from radius {} ({}) to {} ({})",
122 pair[0].radius,
123 pair[0].kept_symbols,
124 pair[1].radius,
125 pair[1].kept_symbols,
126 );
127 }
128 }
129
130 #[test]
131 fn radius_is_a_real_lever_for_substantial_bodies() {
132 let f = Fixture {
135 name: "big_dep",
136 archetype: Archetype::SingleFileEdit,
137 files: vec![(
138 Lang::Rust,
139 "\
140fn target() -> i32 { big() }
141fn big() -> i32 {
142 let mut total = 0;
143 for i in 0..100 { total += i * i - 3 * i + 7; }
144 total
145}
146"
147 .to_string(),
148 )],
149 target: "target".to_string(),
150 };
151 assert!(f.measure(1).est_tokens > f.measure(0).est_tokens);
152 }
153
154 #[test]
155 fn radius_expands_the_kept_set_along_the_dependency_chain() {
156 let f = fixture();
157 assert_eq!(f.measure(0).kept_symbols, 1); assert_eq!(f.measure(1).kept_symbols, 2); assert_eq!(f.measure(2).kept_symbols, 3); assert_eq!(f.measure(3).kept_symbols, 3); }
163}