pedalkernel_validate/pro_pedal.rs
1//! Helpers for loading proprietary `.pedal` files from the pedalkernel-pro
2//! private repository at test time.
3//!
4//! The `.pedal` sources are proprietary and must **never** be committed to the
5//! public engine repo, nor embedded via `include_str!` (that would fail to
6//! compile on public CI where the pro repo is absent).
7//!
8//! # Usage
9//!
10//! ```rust,ignore
11//! use pedalkernel_validate::pro_pedal::{load_pro_pedal_sub, skip_if_missing};
12//!
13//! #[test]
14//! fn my_test() {
15//! let source = skip_if_missing!(
16//! load_pro_pedal_sub("pedals/legends/screamer.pedal"),
17//! "pedals/legends/screamer.pedal"
18//! );
19//! // use source…
20//! }
21//! ```
22//!
23//! # CI behaviour
24//!
25//! When the pro repo is absent (public CI, forks, contributor machines), the
26//! `load_pro_pedal_sub` function returns `None`. The `skip_if_missing!` macro
27//! then calls `return` — causing the test to exit silently with no failure.
28//! Rust's test harness reports such a test as passed (not failed, not skipped
29//! with the ignored attribute).
30//!
31//! # Mirror note
32//!
33//! `pedalkernel::compiler::tb303_decomposition_tests::load_pro_pedal_sub` mirrors
34//! this logic for tests in the `pedalkernel` crate. A true shared crate is not
35//! possible because `pedalkernel-validate` depends on `pedalkernel`, so a
36//! reverse dependency would be circular.
37
38/// Try to load a file at `pro_sub_path` (relative to the pedalkernel-pro repo
39/// root, e.g. `"crates/acidattack/acidattack-core/tb303_filter.pedal"` or
40/// `"pedals/legends/screamer.pedal"`).
41///
42/// Tries several candidate `../` depths from `CARGO_MANIFEST_DIR` so that tests
43/// work in both normal checkout and git-worktree layouts.
44///
45/// Returns `None` — never panics — when the pro repo is absent, so callers can
46/// skip gracefully via [`skip_if_missing!`].
47pub fn load_pro_pedal_sub(pro_sub_path: &str) -> Option<String> {
48 let manifest_dir = env!("CARGO_MANIFEST_DIR");
49 // Try 2..=6 levels of `../` to cover:
50 // 2: normal checkout (pedalkernel-pro/ and pedalkernel/ are siblings)
51 // 3: worktree layout (.worktrees/<name>/ adds one extra level)
52 // 4..=6: deeper nesting in case of unusual CI layouts
53 for levels in 2..=6 {
54 let prefix: String = "../".repeat(levels);
55 let candidate = format!("{manifest_dir}/{prefix}pedalkernel-pro/{pro_sub_path}");
56 if let Ok(s) = std::fs::read_to_string(&candidate) {
57 eprintln!(
58 " loaded {} ({} bytes) from {candidate}",
59 pro_sub_path,
60 s.len()
61 );
62 return Some(s);
63 }
64 }
65 None
66}
67
68/// Early-return a test when a pro `.pedal` file is absent.
69///
70/// ```rust,ignore
71/// let source = skip_if_missing!(
72/// load_pro_pedal_sub("pedals/legends/screamer.pedal"),
73/// "pedals/legends/screamer.pedal"
74/// );
75/// ```
76///
77/// Expands to `match $source { Some(s) => s, None => { eprintln!(…); return; } }`.
78/// The enclosing `#[test]` function returns `()`, so the harness records a pass.
79#[macro_export]
80macro_rules! skip_if_missing {
81 ($source:expr, $name:expr) => {
82 match $source {
83 Some(s) => s,
84 None => {
85 eprintln!(" SKIP: {} not found (pro repo absent)", $name);
86 return;
87 }
88 }
89 };
90}