Skip to main content

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