105 lines
3.8 KiB
Rust
105 lines
3.8 KiB
Rust
//! A widget with no inputs must produce its outputs from its parameters alone.
|
|
//!
|
|
//! `vec3.constant` did not. Its `value` parameter defaults to the empty string,
|
|
//! `parse_vec3("")` fails, and `execute` returned an empty map — while `codegen` fell back
|
|
//! to `[0.0; 3]`. So a freshly dropped node computed zeros when compiled and nothing when
|
|
//! run, and the T0 half was invisible: until recently a wire carrying nothing was dropped
|
|
//! from the probe list and looked exactly like a wire whose probe was switched off.
|
|
//!
|
|
//! It was found by asking which source widgets produce nothing: of 25, sixteen did, and
|
|
//! fifteen of those read a file, a device or target hardware and cannot answer in a test.
|
|
//! `vec3.constant` reads a string. That is what made it the odd one out rather than one of
|
|
//! a class, and this test is that question kept.
|
|
|
|
use flowrs_core::graph::widget::{ExecContext, builtin_registry};
|
|
use std::collections::HashMap;
|
|
|
|
/// Sources that legitimately produce nothing with default parameters, because their value
|
|
/// comes from somewhere this process cannot reach.
|
|
///
|
|
/// Every entry is a file, a capture device, a model registry or target hardware. A widget
|
|
/// belongs here only if it CANNOT answer without one — not because it happens not to.
|
|
const NEEDS_THE_WORLD: &[&str] = &[
|
|
// Files and captured media.
|
|
"audio.load",
|
|
"audio.record",
|
|
"data.csv.load",
|
|
"data.json.load",
|
|
"image.camera",
|
|
"image.load",
|
|
"microphone",
|
|
"video.open",
|
|
// Models, held in a side registry the browser populates.
|
|
"ml.onnx",
|
|
"neural.load_model",
|
|
// A file the browser's store published, read by name from the same kind of registry.
|
|
// With no file behind the name it produces nothing, because an absent file is not an
|
|
// empty one.
|
|
"record.file",
|
|
// A TDMS channel from the same store, read by name.
|
|
"data.tdms",
|
|
// A logic or waveform capture from the same store, read by name.
|
|
"capture.open",
|
|
// A published twin capture.
|
|
"twin.capture",
|
|
// A stream a live source published: a serial port, an RTT channel or sampled globals,
|
|
// from a board or a twin. Nothing published under the name is not an empty stream.
|
|
"live.serial",
|
|
"live.rtt",
|
|
"live.symbol",
|
|
"live.biosignal",
|
|
// On-target peripherals: these run on the MCU, not here.
|
|
"embedded.adc",
|
|
"embedded.gpio.digital_in",
|
|
"embedded.timer.interval",
|
|
"embedded.uart.read",
|
|
];
|
|
|
|
#[test]
|
|
fn every_parameter_only_source_produces_a_value() {
|
|
let registry = builtin_registry();
|
|
let mut silent = Vec::new();
|
|
|
|
for w in registry.all() {
|
|
let id = w.id();
|
|
if !w.inputs().is_empty()
|
|
|| w.outputs().is_empty()
|
|
|| NEEDS_THE_WORLD.contains(&id.as_str())
|
|
{
|
|
continue;
|
|
}
|
|
let params: HashMap<String, String> = w
|
|
.parameters()
|
|
.into_iter()
|
|
.map(|p| (p.name, p.default))
|
|
.collect();
|
|
if w.execute(&ExecContext::new(params), HashMap::new())
|
|
.is_empty()
|
|
{
|
|
silent.push(id);
|
|
}
|
|
}
|
|
|
|
silent.sort();
|
|
assert!(
|
|
silent.is_empty(),
|
|
"these widgets take no inputs, so their output is a function of their parameters \
|
|
alone — and with the parameters they declare by default they produced nothing:\n {}",
|
|
silent.join("\n "),
|
|
);
|
|
}
|
|
|
|
/// The exception list must not rot into a place to hide a bug.
|
|
#[test]
|
|
fn the_exception_list_only_names_widgets_that_exist_and_are_sources() {
|
|
let registry = builtin_registry();
|
|
for id in NEEDS_THE_WORLD {
|
|
let w = registry
|
|
.get(id)
|
|
.unwrap_or_else(|| panic!("`{id}` is excused but is not registered any more"));
|
|
assert!(
|
|
w.inputs().is_empty(),
|
|
"`{id}` has inputs, so it is not a source and does not need excusing",
|
|
);
|
|
}
|
|
}
|