flowrs-engine/crates/flowrs-cli/tests/run.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

174 lines
6.1 KiB
Rust

//! `flowrsc run`: a document run with no editor, at T0 and from its exported program, the two
//! side by side. The T1 runs build real exports with cargo (`--locked`), each program alone, so
//! they run on the build host with the rest of the suite.
use flowrs_cli::{Cli, Command, EXIT_INVALID, EXIT_OK, EXIT_USAGE, run};
use std::path::{Path, PathBuf};
use tempfile::TempDir;
fn root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.unwrap()
.to_path_buf()
}
struct Ran {
code: i32,
out: String,
err: String,
}
impl Ran {
fn json(&self) -> serde_json::Value {
serde_json::from_str(&self.out)
.unwrap_or_else(|e| panic!("{e}: {}\n{}", self.out, self.err))
}
}
fn flowrsc_run(graph: &Path, tier: &str, target: Option<&Path>, files: &[String]) -> Ran {
let cli = Cli {
command: Command::Run {
graph: graph.to_path_buf(),
tier: tier.into(),
json: true,
steps: None,
files: files.to_vec(),
project: None,
target_dir: target.map(Path::to_path_buf),
offline: false,
tolerance: 0.0,
timeout: 120,
},
quiet: false,
};
let (mut out, mut err) = (Vec::new(), Vec::new());
let code = run(&cli, &mut out, &mut err);
Ran {
code,
out: String::from_utf8(out).unwrap(),
err: String::from_utf8(err).unwrap(),
}
}
/// The reading that carries `port`, at `tier`.
fn reading(j: &serde_json::Value, port: &str, tier: &str) -> serde_json::Value {
j["readings"]
.as_array()
.unwrap()
.iter()
.find(|r| r["ports"].as_array().unwrap().iter().any(|p| p == port))
.unwrap_or_else(|| panic!("no reading carries {port}: {j}"))[tier]
.clone()
}
/// The specification's example runs with no compiler: two and three make five, at T0.
#[test]
fn a_document_runs_at_t0_with_no_compiler() {
let doc = root().join("spec/conformance/flowrs/add-two-numbers.flowrs");
let r = flowrsc_run(&doc, "t0", None, &[]);
assert_eq!(r.code, EXIT_OK, "{}", r.err);
let j = r.json();
assert_eq!(j["tiers"], serde_json::json!(["t0"]));
assert_eq!(reading(&j, "n2.sum", "t0").as_f64(), Some(5.0));
assert!(j["agree"].is_null(), "one tier agrees with nothing");
// The graph text of the same graph reads the same.
let text = flowrsc_run(
&root().join("spec/conformance/fdsl/add.fdsl"),
"t0",
None,
&[],
);
assert_eq!(reading(&text.json(), "n2.sum", "t0").as_f64(), Some(5.0));
}
/// The exported program computes what the interpreter does, reading for reading, for documents
/// across the corpus: arithmetic, comparisons, a clamp, a rail check, lists, text, a sine, a
/// filtered spectrum (flowRS's DSP crates carried in vendor/) and a held loop over ten passes.
#[test]
fn t0_and_the_exported_program_agree_on_the_corpus_documents() {
let target = TempDir::new().unwrap();
let mut compared = 0;
let mut read = 0;
for rel in [
"e2e/fixtures/apps/float_sum.flowrs",
"e2e/fixtures/apps/scale_and_offset.flowrs",
"e2e/fixtures/apps/compare_threshold.flowrs",
"e2e/fixtures/apps/clamp_reading.flowrs",
"e2e/fixtures/apps/greater_than.flowrs",
"e2e/fixtures/apps/sum_to_5.flowrs",
"e2e/fixtures/apps/rail_check.flowrs",
"e2e/fixtures/apps/vec_push_len.flowrs",
"e2e/fixtures/apps/logic_and_strings.flowrs",
"e2e/fixtures/apps/sine_plot.flowrs",
"e2e/fixtures/apps/dsp_filter_spectrum.flowrs",
"e2e/fixtures/appexport/counter.fdsl",
] {
let r = flowrsc_run(&root().join(rel), "both", Some(target.path()), &[]);
assert_eq!(r.code, EXIT_OK, "{rel}: {}\n{}", r.out, r.err);
let j = r.json();
assert_eq!(j["agree"], true, "{rel}: {j}");
assert_eq!(j["tiers"], serde_json::json!(["t0", "t1"]));
for rd in j["readings"].as_array().unwrap() {
compared += 1;
if !rd["t1"].is_null() {
read += 1;
}
}
}
assert!(compared >= 12, "{compared} readings compared");
assert!(
read * 10 >= compared * 9,
"{read} of {compared} readings were printed"
);
}
/// A held loop runs the passes its export runs: the counter has counted to the export's steps.
#[test]
fn a_held_loop_runs_the_exports_passes() {
let doc = root().join("e2e/fixtures/appexport/counter.fdsl");
let r = flowrsc_run(&doc, "t0", None, &[]);
assert_eq!(r.code, EXIT_OK, "{}", r.err);
let j = r.json();
let steps = j["steps"].as_f64().unwrap();
assert!(steps > 1.0, "a step loop runs more than one pass");
assert_eq!(reading(&j, "n1.sum", "t0").as_f64(), Some(steps));
}
/// A document whose app reads nothing has no readings, and says so rather than looking empty.
#[test]
fn a_document_whose_app_reads_nothing_says_so() {
let r = flowrsc_run(
&root().join("e2e/fixtures/apps/held_counter.flowrs"),
"t0",
None,
&[],
);
assert_eq!(r.code, EXIT_OK, "{}", r.err);
assert_eq!(r.json()["readings"], serde_json::json!([]));
assert!(r.err.contains("reads nothing"), "{}", r.err);
}
#[test]
fn what_cannot_run_is_refused_with_why() {
let dir = TempDir::new().unwrap();
let missing = dir.path().join("nope.flowrs");
assert_eq!(flowrsc_run(&missing, "t0", None, &[]).code, EXIT_USAGE);
let doc = root().join("spec/conformance/flowrs/add-two-numbers.flowrs");
let r = flowrsc_run(&doc, "t2", None, &[]);
assert_eq!(r.code, EXIT_USAGE);
assert!(r.err.contains("t0, t1 or both"), "{}", r.err);
let r = flowrsc_run(&doc, "t0", None, &["no-equals-sign".into()]);
assert_eq!(r.code, EXIT_USAGE);
assert!(r.err.contains("NAME=PATH"), "{}", r.err);
// A graph whose wire comes from nowhere is invalid: judged, not run.
let r = flowrsc_run(
&root().join("spec/conformance/flowrs/dangling-wire.flowrs"),
"t0",
None,
&[],
);
assert_eq!(r.code, EXIT_INVALID, "{}", r.err);
assert!(r.out.is_empty());
}