flowrs-engine/crates/flowrs-core/tests/seam_visibility.rs
flowRS fd958c0547 flowRS engine, cut from flowRS 3451b065
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 19:04:39 -05:00

313 lines
13 KiB
Rust

//! A widget whose generated program panics must say so before it is run, and a widget
//! that runs a substitute for the model or device it is named after must not be counted as
//! shipping.
//!
//! A seam is codegen that emits `unimplemented!` at declared defaults, in process or
//! init. The program compiles and then panics on the first run. That was the deliberate
//! choice over fabricating APIs that do not exist (`AudioBuffer::stft_magnitude`, a
//! one-call candle `txt2img`). The set is empty: every builtin emits a real call.
//!
//! A stand-in is the other way not to ship: it compiles AND runs, but what runs is a
//! deterministic substitute — seeded noise for Stable Diffusion, a byte histogram for
//! CLIP, a word rotation for a language model. Neither the seam scan nor the T0 census
//! sees it, so it declares itself through `Widget::stand_in`.
//!
//! The live census is [`flowrs_core::graph::widget::seam_widget_ids`],
//! [`flowrs_core::graph::widget::stand_in_widget_ids`] and
//! [`flowrs_core::graph::widget::shipping_widget_count`], not this comment.
//! [`the_launch_census_matches_the_registry`] locks the numbers those functions return.
use flowrs_core::Graph;
use flowrs_core::graph::diagnostics::check;
#[test]
fn a_widget_that_emits_no_seam_does_not_warn() {
// `gen.txt2img` used to be a seam. It is a stand-in now — no seam, and not shipping
// either — and `image.grayscale` always shipped. Neither generates a seam.
for id in ["gen.txt2img", "image.grayscale"] {
let mut g = Graph::new();
g.add_node(id, 0.0, 0.0);
assert!(
!check(&g)
.iter()
.any(|d| d.message.contains("generates a seam")),
"{id} emits a real implementation and must not be flagged",
);
}
}
/// The set is computed from what widgets emit, not from a hand-kept list — a list would
/// drift the moment a seam was filled in.
#[test]
fn the_seam_set_matches_what_the_widgets_actually_emit() {
let seams = flowrs_core::graph::widget::seam_widget_ids();
for id in seams {
let mut g = Graph::new();
g.add_node(id, 0.0, 0.0);
let src = g.to_rust().unwrap_or_default();
assert!(
src.contains("unimplemented!"),
"`{id}` is listed as a seam but its program has none",
);
}
// And nothing outside the set emits one.
let registry = flowrs_core::graph::widget::builtin_registry();
for w in registry.all() {
let id = w.id();
if seams.contains(&id) {
continue;
}
let mut g = Graph::new();
g.add_node(&id, 0.0, 0.0);
let Ok(src) = g.to_rust() else { continue };
assert!(
!src.contains("unimplemented!"),
"`{id}` emits a seam but is not listed, so no warning would be raised",
);
}
}
/// Launch census. Quoted by the site from library code; this test is what stops the
/// numbers drifting from what codegen actually emits and what the widgets declare.
///
/// Shipping is total builtins minus seams minus stand-ins. A seam is a widget whose
/// codegen emits `unimplemented!` (see [`the_seam_set_matches_what_the_widgets_actually_emit`]);
/// a stand-in is a widget whose `stand_in()` names the model or device it does not reach.
/// Counted from the builtin registry: 262 widgets, 0 seams, 16 stand-ins, 246 shipping.
// +3 (2026-10-18): live.serial, live.rtt, live.symbol — shipping, no stand-in.
// +10 (2026-10-18): capture.open and nine decode.* — shipping, no stand-in.
// +1 (2026-10-19): live.biosignal — shipping, no stand-in.
// +1 (2026-10-20): decode.harp — shipping, no stand-in.
// +2 (2026-10-20): unit.convert, clock.convert — shipping, no stand-in.
// -7 stand-ins (2026-10-20, work/mcuio): the UART, I2C, SPI, ADC and timer widgets lower
// to flowrs-hal on Embassy and RTIC and run as firmware on the F4 and RP2040 twins.
// +1 (2026-10-21, work/partition): imu.complementary — shipping, no stand-in.
// +3 (2026-10-22, work/ros2edge): imu.decode, ros.publish, ros.subscribe — shipping, no
// stand-in (the ROS endpoints publish and subscribe for real in the generated ROS 2 node).
// +4 (2026-10-28, work/seamsg): mavlink.telemetry, mavlink.heartbeat, mavlink.setpoint,
// mavlink.command — shipping, no stand-in (the generated program talks to the vehicle).
// +6 (2026-10-29, work/mcudsp): embedded.adc.capture, dsp.samples, dsp.fir, dsp.biquad,
// dsp.moving, dsp.decimate — shipping, no stand-in (fixed-size frames, lowered to the MCU).
// +1 (2026-10-29, work/teststation): instrument.station — shipping, no stand-in (the editor
// runs it on simulated instruments; the generated program drives the real ones).
const LAUNCH_BUILTIN_WIDGETS: usize = 273;
const LAUNCH_SEAM_WIDGETS: usize = 0;
const LAUNCH_STAND_IN_WIDGETS: usize = 16;
const LAUNCH_SHIPPING_WIDGETS: usize =
LAUNCH_BUILTIN_WIDGETS - LAUNCH_SEAM_WIDGETS - LAUNCH_STAND_IN_WIDGETS;
/// Seam ids in the order [`WidgetRegistry::seam_ids`](flowrs_core::graph::widget::WidgetRegistry::seam_ids)
/// returns them. The count above is `LAUNCH_SEAM_IDS.len()`; both have to move together.
const LAUNCH_SEAM_IDS: &[&str] = &[];
/// Stand-in ids, sorted, as [`WidgetRegistry::stand_in_ids`](flowrs_core::graph::widget::WidgetRegistry::stand_in_ids)
/// returns them. Models: the generative pillar, whose ten widgets run `gen_runtime`
/// substitutes at both tiers, and `ai.complete`, whose generated program answers with a
/// word rotation. Devices: `audio.record` and `microphone` return a synthetic 1 kHz tone,
/// `audio.play` only counts samples, and `video.open` / `video.write` move raw frames
/// through flowRS's own container instead of a codec. `neural.detect` and `neural.segment`
/// left this list when they began decoding the model they are given, and the seven
/// embedded peripherals (UART read and write, I2C read and write, SPI, ADC, the timer) when
/// Embassy and RTIC began lowering them to flowrs-hal: no embedded widget is on it now
/// ([`no_embedded_widget_is_a_stand_in`]).
const LAUNCH_STAND_IN_IDS: &[&str] = &[
"ai.complete",
"audio.play",
"audio.record",
"gen.caption",
"gen.clip_image",
"gen.clip_text",
"gen.img2img",
"gen.inpaint",
"gen.txt2img",
"gen.upscale",
"gen.vae_decode",
"gen.vae_encode",
"gen.whisper",
"microphone",
"video.open",
"video.write",
];
/// Every embedded widget reaches real hardware on the MCU targets, so none is a stand-in.
/// Pinned at zero: the plan's blocker was seven of eleven declared stand-ins (twelve
/// embedded widgets since the ADC capture).
#[test]
fn no_embedded_widget_is_a_stand_in() {
let registry = flowrs_core::graph::widget::builtin_registry();
let embedded: Vec<String> = registry
.all()
.iter()
.map(|w| w.id())
.filter(|id| id.starts_with("embedded."))
.collect();
assert_eq!(embedded.len(), 12, "{embedded:?}");
let stand_ins: Vec<&String> = embedded
.iter()
.filter(|id| flowrs_core::graph::widget::stand_in_widget_ids().contains(*id))
.collect();
assert_eq!(stand_ins.len(), 0, "embedded stand-ins: {stand_ins:?}");
for id in &embedded {
assert!(
flowrs_core::codegen::mcu::SURFACE.contains(&id.as_str()),
"{id} is not on the MCU surface"
);
}
}
#[test]
fn the_launch_census_matches_the_registry() {
let registry = flowrs_core::graph::widget::builtin_registry();
let seams = flowrs_core::graph::widget::seam_widget_ids();
let stand_ins = flowrs_core::graph::widget::stand_in_widget_ids();
let shipping = flowrs_core::graph::widget::shipping_widget_count();
assert_eq!(registry.len(), registry.all().len());
assert_eq!(registry.shipping_count(), shipping);
// A stand-in is never counted as shipping: the shipping widgets are exactly those in
// neither set, and the three numbers account for every builtin once.
let shipped: Vec<String> = registry
.all()
.iter()
.map(|w| w.id())
.filter(|id| !seams.contains(id) && !stand_ins.contains(id))
.collect();
assert_eq!(
shipping,
shipped.len(),
"shipping_widget_count() counts a stand-in or a seam as shipping"
);
for id in stand_ins {
assert!(
!shipped.contains(id),
"{id} is a stand-in and counted as shipping"
);
}
let overlap = seams.intersection(stand_ins).count();
assert_eq!(
shipping + seams.len() + stand_ins.len() - overlap,
registry.len()
);
let listed = registry.seam_ids();
let mut cached: Vec<_> = seams.iter().cloned().collect();
cached.sort();
assert_eq!(
listed, cached,
"seam_ids() must be seam_widget_ids(), sorted"
);
assert!(
listed.windows(2).all(|w| w[0] < w[1]),
"seam ids are sorted"
);
assert_eq!(LAUNCH_SEAM_IDS.len(), LAUNCH_SEAM_WIDGETS);
assert_eq!(
listed.iter().map(String::as_str).collect::<Vec<_>>(),
LAUNCH_SEAM_IDS,
"seam ids changed"
);
let standing = registry.stand_in_ids();
assert!(
standing.windows(2).all(|w| w[0] < w[1]),
"stand-in ids are sorted"
);
assert_eq!(LAUNCH_STAND_IN_IDS.len(), LAUNCH_STAND_IN_WIDGETS);
assert_eq!(
standing.iter().map(String::as_str).collect::<Vec<_>>(),
LAUNCH_STAND_IN_IDS,
"stand-in ids changed. A widget that now runs the real model drops its \
`stand_in()`; one that runs a substitute must declare it"
);
// Each names the real thing it stands in for, which is what the editor's badge says.
for w in registry.all() {
if let Some(real) = w.stand_in() {
assert!(
!real.trim().is_empty(),
"{} declares a stand-in for nothing",
w.id()
);
}
}
let total = registry.len();
assert_eq!(
(total, seams.len(), stand_ins.len(), shipping),
(
LAUNCH_BUILTIN_WIDGETS,
LAUNCH_SEAM_WIDGETS,
LAUNCH_STAND_IN_WIDGETS,
LAUNCH_SHIPPING_WIDGETS
),
"launch census changed: total={total} seams={listed:?} stand-ins={standing:?} \
shipping={shipping}. Update LAUNCH_* only when codegen really gained or lost a \
seam, a widget stopped or started standing in, or a widget was added",
);
}
/// The substitute runtimes, and what each stands in for. A widget whose program calls one
/// computes the substitute, whatever its name says, so it must declare a stand-in; the
/// census would otherwise count it as shipping. `host_pcm`'s WAV codec and the embedded
/// board's latches are not in the list: a WAV file is real, and the board is what the
/// Host target simulates for widgets the MCU targets lower for real (`embedded.rs`
/// holds that split to `codegen::mcu::SURFACE`).
const SUBSTITUTES: &[&str] = &[
"gen_runtime::",
"llm_local::",
"host_pcm::capture",
"host_pcm::playback",
"video_io::",
];
#[test]
fn every_widget_that_runs_a_substitute_declares_a_stand_in() {
use flowrs_core::graph::widget::CodegenContext;
use std::collections::HashMap;
let registry = flowrs_core::graph::widget::builtin_registry();
let mut caught = 0usize;
for w in registry.all() {
let ctx = CodegenContext {
var_names: HashMap::new(),
imports: vec![],
state_fields: vec![],
profile: flowrs_core::graph::Profile::scientific(),
params: w
.parameters()
.into_iter()
.map(|p| (p.name, p.default))
.collect(),
};
let inputs = w
.inputs()
.into_iter()
.map(|p| (p.name.clone(), p.name))
.collect();
let process = w.codegen(&ctx, inputs).process;
if let Some(sub) = SUBSTITUTES.iter().find(|s| process.contains(*s)) {
caught += 1;
assert!(
w.stand_in().is_some(),
"{} calls `{sub}` but does not declare a stand-in",
w.id()
);
}
}
// The scan has something to hold: the generative ten, ai.complete, the two audio
// stand-ins, the microphone and the video pair.
assert_eq!(caught, 16, "the substitute scan found {caught} widgets");
}
/// A widget that used to be a seam must still build. A seam, when one exists, is a
/// warning, not an error — refusing the build would block the person filling it in.
#[test]
fn a_seam_does_not_block_the_build() {
let mut g = Graph::new();
g.add_node("gen.txt2img", 0.0, 0.0);
assert!(
flowrs_core::graph::diagnostics::is_buildable(&g),
"a seam is a warning, not an error",
);
}