167 lines
6.3 KiB
Rust
167 lines
6.3 KiB
Rust
//! An Indicator in a compiled program prints a labelled Teleplot line (`>name:value`), not
|
|
//! an unlabelled `println!`: what Teleplot, flowrs-live and an exported app's shell read.
|
|
//!
|
|
//! Each case is compiled with rustc, run, and its stdout read by flowrs-live's Teleplot
|
|
//! decoder, which was written to Teleplot's own reader and knows nothing of codegen. The
|
|
//! value it reads must be the interpreter's value on the port the Indicator shows.
|
|
|
|
use flowrs_core::codegen::rust::{indicator_names, teleplot_name};
|
|
use flowrs_core::graph::dsl::parse_graph;
|
|
use flowrs_live::telemetry::{Dialect, decode_all};
|
|
|
|
fn run(src: &str, name: &str) -> String {
|
|
let dir = std::env::temp_dir().join(format!("flowrs_teleplot_{}_{name}", std::process::id()));
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let main = dir.join("main.rs");
|
|
let bin = dir.join("prog");
|
|
std::fs::write(&main, src).unwrap();
|
|
let build = std::process::Command::new("rustc")
|
|
.args(["--edition", "2021", "-o"])
|
|
.arg(&bin)
|
|
.arg(&main)
|
|
.output()
|
|
.expect("rustc runs");
|
|
assert!(
|
|
build.status.success(),
|
|
"{name} does not compile:\n{}\n{src}",
|
|
String::from_utf8_lossy(&build.stderr)
|
|
);
|
|
let out = std::process::Command::new(&bin).output().expect("it runs");
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
assert!(out.status.success(), "{name}: {out:?}");
|
|
String::from_utf8(out.stdout).unwrap()
|
|
}
|
|
|
|
#[test]
|
|
fn an_indicator_prints_its_label_and_its_value_as_teleplot() {
|
|
let g = parse_graph(
|
|
"let n0 = FloatLiteral { value: 2.5 };\n\
|
|
let n1 = FloatLiteral { value: 4 };\n\
|
|
let n2 = math.mul(a: n0.value, b: n1.value);\n\
|
|
let n3 = Indicator(input: n2.product) { label: \"Power: out\" };\n\
|
|
let n4 = IntLiteral { value: 7 };\n\
|
|
let n5 = Indicator(input: n4.value);\n\
|
|
let n6 = BoolLiteral { value: true };\n\
|
|
let n7 = Indicator(input: n6.value) { label: \"ok\" };\n\
|
|
let n8 = Linspace;\n\
|
|
let n9 = MapSin(input: n8.values);\n\
|
|
let n10 = Indicator(input: n9.values) { label: \"sin(x)\" };\n\
|
|
let n11 = StringLiteral { value: \"a:b; c|d\" };\n\
|
|
let n12 = Indicator(input: &n11.value) { label: \"note\" };\n\
|
|
let n13 = Indicator;",
|
|
)
|
|
.expect("the graph parses")
|
|
.graph;
|
|
let src = g.to_rust().expect("codegen");
|
|
assert!(
|
|
!src.contains("println!(\"{}\", 0)"),
|
|
"an unwired Indicator printed a zero: {src}"
|
|
);
|
|
let out = run(&src, "kinds");
|
|
let live = decode_all(out.as_bytes(), Some(Dialect::Teleplot), "program", 0.0);
|
|
assert!(
|
|
live.flags.is_empty(),
|
|
"flowrs-live flagged {:?}\n{out}",
|
|
live.flags
|
|
);
|
|
let v = |name: &str| {
|
|
live.series
|
|
.iter()
|
|
.find(|s| s.name == name)
|
|
.unwrap_or_else(|| {
|
|
panic!(
|
|
"no series {name:?} in {:?}\n{out}",
|
|
live.series.iter().map(|s| &s.name).collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.v
|
|
.clone()
|
|
};
|
|
// The label is the name, Teleplot's separators made spaces; unlabelled, `<Source> · port`.
|
|
assert_eq!(v("Power out"), vec![10.0]);
|
|
assert_eq!(v("IntLiteral · value"), vec![7.0]);
|
|
assert_eq!(v("ok"), vec![1.0]);
|
|
// A series arrives as `index:value` pairs, which flowrs-live reads as x and y.
|
|
let ys = v("sin(x).y");
|
|
assert_eq!(ys.len(), 64, "{out}");
|
|
// flowrs-math's sine, as the editor and the program both compute it.
|
|
let want: Vec<f64> = (0..64)
|
|
.map(|i| flowrs_math::sin(std::f64::consts::TAU * i as f64 / 63.0))
|
|
.collect();
|
|
assert_eq!(ys, want);
|
|
// Text is its `{:?}`, Teleplot's separators escaped, and reads back whole.
|
|
let note = live
|
|
.logs
|
|
.iter()
|
|
.find(|l| l.text.starts_with("note: "))
|
|
.unwrap_or_else(|| panic!("no text line\n{out}"));
|
|
assert_eq!(note.text, "note: \"a%3Ab%3B c%7Cd\"");
|
|
// Every line the program printed is a variable: nothing unlabelled is left.
|
|
assert!(out.lines().all(|l| l.starts_with('>')), "{out}");
|
|
assert_eq!(out.lines().count(), 5, "{out}");
|
|
}
|
|
|
|
#[test]
|
|
fn indicator_names_are_their_labels_made_teleplot_safe_and_unique() {
|
|
let g = parse_graph(
|
|
"let n0 = FloatLiteral { value: 1 };\n\
|
|
let n1 = Indicator(input: n0.value) { label: \"t\" };\n\
|
|
let n2 = Indicator(input: n0.value) { label: \"t\" };\n\
|
|
let n3 = Indicator(input: n0.value);",
|
|
)
|
|
.unwrap()
|
|
.graph;
|
|
let names: Vec<String> = indicator_names(&g).into_values().collect();
|
|
assert_eq!(names, vec!["t", "t (n2)", "FloatLiteral · value"]);
|
|
assert_eq!(teleplot_name("a:b,c|d;e§f\ng"), "a b c d e f g");
|
|
assert_eq!(teleplot_name(" "), "value");
|
|
}
|
|
|
|
#[test]
|
|
fn an_indicator_in_a_step_loop_prints_every_pass() {
|
|
let g = parse_graph(
|
|
"let n0 = FloatLiteral { value: 1 };\n\
|
|
let n1 = math.add(a: n1.sum.copy().prev(), b: n0.value);\n\
|
|
let n2 = Indicator(input: n1.sum) { label: \"count\" };",
|
|
)
|
|
.unwrap()
|
|
.graph;
|
|
let out = run(&g.to_rust_steps(3).expect("codegen"), "steps");
|
|
let live = decode_all(out.as_bytes(), Some(Dialect::Teleplot), "program", 0.0);
|
|
let s = live
|
|
.series
|
|
.iter()
|
|
.find(|s| s.name == "count")
|
|
.expect("count");
|
|
assert_eq!(s.v, vec![1.0, 2.0, 3.0], "{out}");
|
|
}
|
|
|
|
#[test]
|
|
fn a_long_series_goes_over_lines_teleplot_readers_take() {
|
|
let g = parse_graph(
|
|
"let n0 = IntLiteral { value: 600 };\n\
|
|
let n1 = Linspace(count: n0.value);\n\
|
|
let n2 = MapSin(input: n1.values);\n\
|
|
let n3 = Indicator(input: n2.values) { label: \"wave\" };",
|
|
)
|
|
.unwrap()
|
|
.graph;
|
|
let out = run(&g.to_rust().expect("codegen"), "long");
|
|
assert!(out.lines().count() > 1, "one line of {} bytes", out.len());
|
|
assert!(
|
|
out.lines()
|
|
.all(|l| l.len() < flowrs_live::telemetry::MAX_LINE),
|
|
"{out}"
|
|
);
|
|
let live = decode_all(out.as_bytes(), Some(Dialect::Teleplot), "program", 0.0);
|
|
assert!(live.flags.is_empty(), "{:?}", live.flags);
|
|
let ys = &live
|
|
.series
|
|
.iter()
|
|
.find(|s| s.name == "wave.y")
|
|
.expect("wave")
|
|
.v;
|
|
assert_eq!(ys.len(), 600);
|
|
let tau = std::f64::consts::TAU;
|
|
assert_eq!(ys[599], (0.0 + (tau - 0.0) * 599.0 / 599.0).sin());
|
|
}
|