Skip to content

Commit a49d744

Browse files
zackeesclaude
andcommitted
test(library-selection): #205 add pass-2 reconciliation test, fix teensylc env, add CI badges
Three final polish items before closing #205. ## test — `r04_pass2_reconciliation_catches_cpp_only_dependency` The canonical "why 2-pass beats single-pass BFS" scenario, which the existing 16-test suite was missing as an explicit guard: - Project includes `<SPI.h>`. `SPI.h` is silent (no transitive includes). - `SPI.cpp` is the *only* place that includes `<Wire.h>`. A single-pass BFS over headers selects {SPI} and silently misses Wire — link-time undefined symbols. The LDF's pass 2 re-seeds with each selected lib's full source set, so Wire is reached and selected. Test asserts {SPI, Wire} and is the regression guard against accidentally collapsing the resolver back to a single pass. Brings the resolver test count to 17 (10 LDF + 7 cache). ## fix — teensylc acceptance gate env name `tests/teensylc_acceptance.rs` passed `env_name: "teensyLC"` (camelCase) and looked up `compile_commands.json` under `<build>/teensyLC/`. The actual env in `tests/platform/teensylc/platformio.ini` is `[env:teensylc]` (lowercase) — same case-sensitivity bug fixed in #220 / #221 for `measure_baseline_205.py`. The acceptance gate has been failing on every run (including main) for this reason. ## badges Adds badges for the two #205 workflows to README.md's CI status block: - `acceptance-205.yml` — AC#1 (teensyLC) and AC#4 (stm32 SPI) gates - `bench-205.yml` — P-02 (cold) / P-03 (scanner) / P-01-mini (warm) perf gates Both have been live since #211 / #210 but were not discoverable from the README. Refs: #205. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 787cc82 commit a49d744

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
[![Documentation](https://github.com/fastled/fbuild/actions/workflows/docs.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/docs.yml)
1313
[![Validate Boards](https://github.com/fastled/fbuild/actions/workflows/validate-boards.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/validate-boards.yml)
1414
[![Build Native Binaries](https://github.com/fastled/fbuild/actions/workflows/build.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/build.yml)
15+
[![Library Selection Acceptance (#205)](https://github.com/fastled/fbuild/actions/workflows/acceptance-205.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/acceptance-205.yml)
16+
[![Library Selection Perf (#205)](https://github.com/fastled/fbuild/actions/workflows/bench-205.yml/badge.svg)](https://github.com/fastled/fbuild/actions/workflows/bench-205.yml)
1517

1618
<details>
1719
<summary><strong>Per-platform board build badges</strong> (click to expand)</summary>

crates/fbuild-build/tests/teensylc_acceptance.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ fn teensylc_blink_meets_205_acceptance_criteria() {
3030

3131
let params = BuildParams {
3232
project_dir: project_dir.clone(),
33-
env_name: "teensyLC".to_string(),
33+
// WHY: env names are case-sensitive and must match the
34+
// [env:teensylc] key in tests/platform/teensylc/platformio.ini.
35+
// Same root-cause family as #220 / #221 in measure_baseline_205.py.
36+
env_name: "teensylc".to_string(),
3437
clean: true,
3538
profile: BuildProfile::Release,
3639
build_dir: build_dir.path().to_path_buf(),
@@ -79,7 +82,7 @@ fn teensylc_blink_meets_205_acceptance_criteria() {
7982
}
8083

8184
// ── compile_commands.json probes (AC#1, A-20..A-22) ─────────────────
82-
let compdb_path = locate_compile_commands(build_dir.path(), "teensyLC")
85+
let compdb_path = locate_compile_commands(build_dir.path(), "teensylc")
8386
.expect("compile_commands.json should land in build dir");
8487
let db = CompileDb::from_path(&compdb_path).expect("parse compile_commands.json");
8588
assert!(

crates/fbuild-library-select/src/lib.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,48 @@ mod tests {
248248
);
249249
}
250250

251+
#[test]
252+
fn r04_pass2_reconciliation_catches_cpp_only_dependency() {
253+
// The whole reason the LDF resolver is 2-pass instead of single-pass
254+
// BFS: a lib's `.cpp` may pull in a second lib that the first lib's
255+
// `.h` does NOT mention. Pass 1 (BFS from project seeds + reached
256+
// headers) cannot see that edge; pass 2 re-seeds with each selected
257+
// lib's full source set and catches it.
258+
//
259+
// Setup: project includes <SPI.h>. SPI.h is silent. SPI.cpp includes
260+
// <Wire.h>. Wire is only reachable through SPI.cpp.
261+
//
262+
// Expected: pass 1 selects {SPI}; pass 2 (with SPI.cpp as a seed)
263+
// selects {SPI, Wire}. A regression that drops the second pass would
264+
// produce {SPI} only and silently miss Wire at link time.
265+
let tmp = TempDir::new().unwrap();
266+
let project_src = tmp.path().join("project").join("src");
267+
write(&project_src.join("main.cpp"), "#include <SPI.h>\n");
268+
269+
let mut spi = lib(tmp.path(), "SPI");
270+
write(
271+
&spi.include_dirs[0].join("SPI.h"),
272+
"// no transitive includes\n",
273+
);
274+
let spi_cpp = spi.include_dirs[0].join("SPI.cpp");
275+
write(&spi_cpp, "#include <Wire.h>\n");
276+
spi.source_files.push(spi_cpp);
277+
278+
let mut wire = lib(tmp.path(), "Wire");
279+
write(&wire.include_dirs[0].join("Wire.h"), "");
280+
let wire_cpp = wire.include_dirs[0].join("Wire.cpp");
281+
write(&wire_cpp, "");
282+
wire.source_files.push(wire_cpp);
283+
284+
let seeds = vec![project_src.join("main.cpp")];
285+
let sel = resolve(&seeds, &[project_src], &[spi, wire]);
286+
assert_eq!(
287+
sel.required_libraries,
288+
vec!["SPI".to_string(), "Wire".to_string()],
289+
"pass 2 reconciliation must catch Wire reached only via SPI.cpp"
290+
);
291+
}
292+
251293
#[test]
252294
fn r03_no_includes_selects_nothing() {
253295
let tmp = TempDir::new().unwrap();

0 commit comments

Comments
 (0)