bynk_testkit/lib.rs
1//! Content-ownership track (#1086) slice 3: the cross-crate replacement for
2//! `diagnose_project(&root, &HashMap::new())` and bare
3//! `CompileOptions::single`/`::split`'s reliance on `bynk-emit`'s disk
4//! fallback (`design/tracks/content-ownership.md` §3.3, §4).
5//!
6//! Every helper here walks a project exactly the way production code already
7//! does — `bynk_ide::discover_files` for `diagnose_project*`-style callers,
8//! the same `bynk_emit::project::Roots` a `CompileOptions` will itself
9//! compile for `compile_options_*` — and reads every file into a complete
10//! sources map, instead of reimplementing the walk. There is no second
11//! resolution to drift from the first: a test built on these helpers cannot
12//! silently miss a file because this crate's notion of "the project's files"
13//! diverged from the compiler's own.
14//!
15//! Dev-only: invisible to `fs_below_driver`'s probe
16//! (`design/greenfield-status.md`), which is scoped to the crates below the
17//! driver (`bynk-emit`, `bynk-ide`, `bynk-fmt`) — not to dev-only crates like
18//! this one, which is why this file's own real `std::fs` reads (`read_all`,
19//! the `bynk.toml` read below) never register, not because the probe walks
20//! only production code.
21
22use std::collections::HashMap;
23use std::path::PathBuf;
24
25/// Keyed by the literal discovered path, **not** canonicalised —
26/// `bynk-driver`'s own `sources_for_roots`/`read_bynk_tree` (the proven,
27/// production `CompileOptions.sources` populator, #1077/#1081) key theirs
28/// the same way. `CompileOptions.sources`'s own doc says filesystem
29/// discovery is skipped entirely once `sources` is `Some`, so whatever shape
30/// a file's identity/consistency checks expect has to come from these keys
31/// directly — canonicalising here (found the hard way: it broke
32/// `bynk.project.inconsistent_commons_name`'s path-shape check against a
33/// real multi-root example) would hand the compiler a different path shape
34/// than `discover_bynk_files`'s own walk produces.
35fn read_all(paths: Vec<PathBuf>) -> HashMap<PathBuf, String> {
36 paths
37 .into_iter()
38 .filter_map(|p| {
39 let content = std::fs::read_to_string(&p).ok()?;
40 Some((p, content))
41 })
42 .collect()
43}
44
45/// A complete `(path, content)` map for `roots`, resolved and enumerated the
46/// same way `bynk_ide::diagnose_project_with`'s own callers already do — the
47/// direct replacement for `diagnose_project(&root, &HashMap::new())`'s
48/// reliance on `bynk-emit`'s disk fallback filling in what the (empty)
49/// overlay doesn't cover.
50///
51/// Content-ownership track (#1086) slice 5 correction: for
52/// [`bynk_ide::AnalysisRoots::Project`], also reads `roots`'s own
53/// `bynk.toml` and includes it in the returned map — `bynk_ide::discover_files`
54/// needs it to resolve a non-conventional `[paths] include`/`exclude`, and a
55/// caller re-lowering `roots` against this map (e.g. a subsequent
56/// `diagnose_project_with`) needs it too. `bynk-ide` can no longer fall back
57/// to a disk read for a miss itself (R2.3), so this crate — a dev-only test
58/// seam, not gated by R2.3 at all — is where that real read belongs.
59pub fn read_project_sources(roots: &bynk_ide::AnalysisRoots) -> HashMap<PathBuf, String> {
60 let overlay = manifest_overlay(roots);
61 let mut sources = read_all(bynk_ide::discover_files(roots, &overlay));
62 sources.extend(overlay);
63 sources
64}
65
66/// `bynk.toml`'s real on-disk content for [`bynk_ide::AnalysisRoots::Project`],
67/// as a one-entry map — empty for `SingleTree` (no manifest consulted) or an
68/// unreadable/absent manifest, both already-handled "no manifest" cases.
69fn manifest_overlay(roots: &bynk_ide::AnalysisRoots) -> HashMap<PathBuf, String> {
70 let bynk_ide::AnalysisRoots::Project(root) = roots else {
71 return HashMap::new();
72 };
73 let toml_path = root.join("bynk.toml");
74 match std::fs::read_to_string(&toml_path) {
75 Ok(text) => HashMap::from([(toml_path, text)]),
76 Err(_) => HashMap::new(),
77 }
78}
79
80/// `CompileOptions::single(root)`, with every source pre-read — the direct
81/// replacement for its reliance on `bynk-emit`'s disk fallback.
82pub fn compile_options_single(root: impl Into<PathBuf>) -> bynk_emit::project::CompileOptions {
83 let root = root.into();
84 let sources = read_project_sources(&bynk_ide::AnalysisRoots::SingleTree(root.clone()));
85 bynk_emit::project::CompileOptions::single(root).sources(sources)
86}
87
88/// `CompileOptions::split(project_root, paths)`, with every source pre-read.
89/// Discovers exactly the files `CompileOptions::split` itself would compile —
90/// built from the same `paths` this call also hands to `split`, not
91/// re-derived from `bynk.toml` through a second read, so the two can never
92/// disagree.
93pub fn compile_options_split(
94 project_root: impl Into<PathBuf>,
95 paths: bynk_emit::project::ProjectPaths,
96) -> bynk_emit::project::CompileOptions {
97 let project_root = project_root.into();
98 let roots = bynk_emit::project::Roots::Split {
99 project_root: project_root.clone(),
100 paths: paths.clone(),
101 };
102 let sources = read_all(bynk_emit::project::discover_project_files(&roots));
103 bynk_emit::project::CompileOptions::split(project_root, paths).sources(sources)
104}