//! 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::>() ) }) .v .clone() }; // The label is the name, Teleplot's separators made spaces; unlabelled, ` · 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 = (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 = 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()); }