git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

tests/doc_claims.rs (11.9K)

  1 //! CLAUDE.md, checked against the crate it describes.
  2 //!
  3 //! An integration test on purpose: anything under `src/` would be counted by
  4 //! its own scans — a `#[cfg(test)]` block added here to measure which modules
  5 //! carry tests promptly made this module one of them, and the line-count
  6 //! figures would drift by however long this file happens to be.
  7 //!
  8 //! Deliberately MIRRORED per crate rather than shared from a helper: each is
  9 //! its own git repository that must build standalone, and this needs nothing
 10 //! but std.
 11 
 12 /// CLAUDE.md's `(~Nk lines)` figures, checked against the files they describe.
 13 ///
 14 /// Those numbers exist to set expectations before opening a file — "this is
 15 /// the big one" — and they drift in total silence, because a stale number
 16 /// reads exactly like a fresh one. A workspace sweep on 2026-09-19 found
 17 /// EVERY size figure in every CLAUDE.md stale, all of them undercounts, the
 18 /// worst by 49% (cce-designer's app.rs, written ~5.4k at 8046 lines).
 19 ///
 20 /// Tolerance is 10%: loose enough that ordinary work does not trip it, tight
 21 /// enough that a file cannot quietly double. When it fails, write the number
 22 /// it reports — that is the whole fix.
 23 ///
 24 /// Deliberately MIRRORED into each crate that carries such a figure rather
 25 /// than shared from a helper: every crate here is its own git repository and
 26 /// must build standalone, and this needs nothing but `std`. Same call
 27 /// `ramp.rs` makes about its cce-ui parser.
 28 pub mod size_claims {
 29     use std::path::{Path, PathBuf};
 30 
 31     /// One `(~Nk lines)` claim: the name as CLAUDE.md spells it, the figure,
 32     /// and whether the claim also calls it the largest file.
 33     fn claims(doc: &str) -> Vec<(String, f64, bool)> {
 34         const TAIL: &str = " lines)";
 35         let mut out = Vec::new();
 36         let mut i = 0;
 37         while let Some(p) = doc[i..].find(TAIL) {
 38             let end = i + p;
 39             i = end + TAIL.len();
 40             let Some(open) = doc[..end].rfind('(') else { continue };
 41             let inner = &doc[open + 1..end];
 42             // "~8k", or "largest file, ~6.9k" — take the last word.
 43             let largest = inner.contains("largest file");
 44             let word = inner.rsplit([' ', ',']).next().unwrap_or("").trim();
 45             let digits = word.trim_start_matches('~');
 46             let value = match digits.strip_suffix('k') {
 47                 Some(k) => k.parse::<f64>().ok().map(|v| v * 1000.0),
 48                 None => digits.parse::<f64>().ok(),
 49             };
 50             // The backticked name immediately before the parenthetical.
 51             let before = &doc[..open];
 52             let name = before.rfind('`').and_then(|e| {
 53                 before[..e].rfind('`').map(|s| before[s + 1..e].to_string())
 54             });
 55             if let (Some(v), Some(n)) = (value, name) {
 56                 if v > 0.0 {
 57                     out.push((n, v, largest));
 58                 }
 59             }
 60         }
 61         out
 62     }
 63 
 64     /// Every `.rs` file under `src/`, as (path, line count).
 65     fn sources(root: &Path) -> Vec<(PathBuf, usize)> {
 66         fn walk(dir: &Path, out: &mut Vec<(PathBuf, usize)>) {
 67             let Ok(entries) = std::fs::read_dir(dir) else { return };
 68             for e in entries.flatten() {
 69                 let p = e.path();
 70                 if p.is_dir() {
 71                     walk(&p, out);
 72                 } else if p.extension().and_then(|x| x.to_str()) == Some("rs") {
 73                     if let Ok(s) = std::fs::read_to_string(&p) {
 74                         out.push((p, s.lines().count()));
 75                     }
 76                 }
 77             }
 78         }
 79         let mut v = Vec::new();
 80         walk(&root.join("src"), &mut v);
 81         v
 82     }
 83 
 84     #[test]
 85     fn test_claude_md_line_counts_match_the_source() {
 86         let root = Path::new(env!("CARGO_MANIFEST_DIR"));
 87         let doc = std::fs::read_to_string(root.join("CLAUDE.md"))
 88             .expect("CLAUDE.md is missing next to Cargo.toml");
 89         let files = sources(root);
 90         let claims = claims(&doc);
 91         assert!(
 92             !claims.is_empty(),
 93             "no `(~N lines)` figure found in CLAUDE.md — either the syntax changed \
 94              and this scan needs updating, or the figures were removed and so \
 95              should this test"
 96         );
 97 
 98         let mut bad: Vec<String> = Vec::new();
 99         for (name, claimed, largest) in &claims {
100             // A path relative to the crate root, else a unique basename.
101             let hits: Vec<&(PathBuf, usize)> = if root.join(name).is_file() {
102                 files.iter().filter(|(p, _)| *p == root.join(name)).collect()
103             } else {
104                 files
105                     .iter()
106                     .filter(|(p, _)| p.file_name().and_then(|x| x.to_str()) == Some(name.as_str()))
107                     .collect()
108             };
109             let [(path, actual)] = hits[..] else {
110                 bad.push(format!("`{name}`: names {} files under src/, cannot check", hits.len()));
111                 continue;
112             };
113             let actual = *actual as f64;
114             let drift = (actual - claimed) / claimed;
115             if drift.abs() > 0.10 {
116                 bad.push(format!(
117                     "`{name}` is documented as ~{} lines but has {} ({:+.0}%) — write ~{}",
118                     round_k(*claimed),
119                     actual as usize,
120                     drift * 100.0,
121                     round_k(actual)
122                 ));
123             }
124             if *largest {
125                 if let Some((big, n)) = files.iter().max_by_key(|(_, n)| *n) {
126                     if big != path {
127                         bad.push(format!(
128                             "`{name}` is called the largest file, but {} has {n} lines",
129                             big.strip_prefix(root).unwrap_or(big).display()
130                         ));
131                     }
132                 }
133             }
134         }
135         assert!(bad.is_empty(), "CLAUDE.md size claims are stale:\n  {}", bad.join("\n  "));
136     }
137 
138     /// "~8k" for 8046, "~3.1k" for 3134, "~950" for 950 — the spelling the
139     /// docs already use, so the failure message can be pasted straight in.
140     fn round_k(n: f64) -> String {
141         if n < 1000.0 {
142             return format!("{}", n.round() as usize);
143         }
144         let k = n / 1000.0;
145         if (k - k.round()).abs() < 0.05 {
146             format!("{}k", k.round() as usize)
147         } else {
148             format!("{k:.1}k")
149         }
150     }
151 }
152 
153 /// CLAUDE.md's NAMES, checked against the crate: the tests it cites as the
154 /// proof of a claim, and the source paths it points at.
155 ///
156 /// The line counts above were the only check until 2026-10-08, when an audit
157 /// found a test cited by a name it no longer had, a constant cited by a name
158 /// it never had, `layout.rs` called the largest file after it had become a
159 /// directory, and claims about a `build.rs` that existed and `view*` methods
160 /// that did not — every one reading exactly like a true statement. A name
161 /// that is meant to be CURRENT is checkable; history ("until 2026-10-02 it
162 /// was…") names things on purpose that are gone, so only these two
163 /// unambiguous kinds are checked.
164 pub mod name_claims {
165     use std::collections::HashSet;
166     use std::path::Path;
167 
168     fn doc() -> String {
169         std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("CLAUDE.md"))
170             .expect("CLAUDE.md is missing next to Cargo.toml")
171     }
172 
173     fn rust_files(dir: &Path, out: &mut Vec<String>) {
174         let Ok(entries) = std::fs::read_dir(dir) else { return };
175         for e in entries.flatten() {
176             let p = e.path();
177             if p.is_dir() {
178                 rust_files(&p, out);
179             } else if p.extension().and_then(|x| x.to_str()) == Some("rs") {
180                 if let Ok(s) = std::fs::read_to_string(&p) {
181                     out.push(s);
182                 }
183             }
184         }
185     }
186 
187     /// Every `fn` name defined under src/, tests/ and examples/.
188     fn defined_fns() -> HashSet<String> {
189         let root = Path::new(env!("CARGO_MANIFEST_DIR"));
190         let mut texts = Vec::new();
191         for d in ["src", "tests", "examples"] {
192             rust_files(&root.join(d), &mut texts);
193         }
194         let mut names = HashSet::new();
195         for t in &texts {
196             let mut rest = t.as_str();
197             while let Some(i) = rest.find("fn ") {
198                 let after = &rest[i + 3..];
199                 let name: String = after.chars().take_while(|c| c.is_alphanumeric() || *c == '_').collect();
200                 if !name.is_empty() {
201                     names.insert(name);
202                 }
203                 rest = after;
204             }
205         }
206         names
207     }
208 
209     /// The backticked snake_case identifiers in the sentence that ends at
210     /// `end` (exclusive): from the previous sentence break to there.
211     fn identifiers_before(doc: &str, end: usize) -> Vec<String> {
212         let head = &doc[..end];
213         let start = [". ", ".\n", "\n\n", ": "]
214             .iter()
215             .filter_map(|b| head.rfind(b).map(|i| i + b.len()))
216             .max()
217             .unwrap_or(0);
218         let sentence = &head[start..];
219         sentence
220             .split('`')
221             .skip(1)
222             .step_by(2)
223             .filter(|t| {
224                 t.contains('_')
225                     && t.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
226             })
227             .map(str::to_string)
228             .collect()
229     }
230 
231     /// "`a_toggle_is_a_field_whose_run_glides` is the test", "`x` and `y` are
232     /// the tests": each one names a function that exists.
233     #[test]
234     fn test_the_tests_claude_md_cites_exist() {
235         let doc = doc();
236         let fns = defined_fns();
237         let mut cited = 0;
238         let mut bad = Vec::new();
239         for phrase in [" is the test", " are the tests"] {
240             for (at, _) in doc.match_indices(phrase) {
241                 for name in identifiers_before(&doc, at) {
242                     cited += 1;
243                     if !fns.contains(&name) {
244                         bad.push(format!("`{name}` is cited as a test, and no such fn exists"));
245                     }
246                 }
247             }
248         }
249         assert!(cited > 0, "no `… is the test` citation found — did the phrasing change?");
250         assert!(bad.is_empty(), "CLAUDE.md cites tests that do not exist:\n  {}", bad.join("\n  "));
251     }
252 
253     /// A backticked path into this crate's source tree — `src/…`, or one that
254     /// starts with a directory under src/ (`backend/text.rs`, `widget/`) —
255     /// exists. Bare file names are left alone: `osk.rs` is the compositor's.
256     #[test]
257     fn test_the_source_paths_claude_md_names_exist() {
258         let root = Path::new(env!("CARGO_MANIFEST_DIR"));
259         let src_dirs: HashSet<String> = std::fs::read_dir(root.join("src"))
260             .expect("src/")
261             .flatten()
262             .filter(|e| e.path().is_dir())
263             .map(|e| e.file_name().to_string_lossy().into_owned())
264             .collect();
265         let doc = doc();
266         let mut bad = Vec::new();
267         let mut checked = 0;
268         for token in doc.split('`').skip(1).step_by(2) {
269             if token.contains(' ') || !token.contains('/') || token.contains("::") || token.starts_with("../") {
270                 continue;
271             }
272             let path = token.trim_end_matches("/*").trim_end_matches('/');
273             let first = path.split('/').next().unwrap_or("");
274             let in_src = first == "src" || src_dirs.contains(first);
275             let looks_like_source = path.ends_with(".rs") || path.ends_with(".wgsl") || !path.contains('.');
276             if !in_src || !looks_like_source {
277                 continue;
278             }
279             checked += 1;
280             if !(root.join(path).exists() || root.join("src").join(path).exists()) {
281                 bad.push(format!("`{token}` is not a path in this crate"));
282             }
283         }
284         assert!(checked > 0, "no source path found in CLAUDE.md — did the spelling change?");
285         assert!(bad.is_empty(), "CLAUDE.md names source paths that do not exist:\n  {}", bad.join("\n  "));
286     }
287 }