flowrs-engine/crates/flowrs-cli/tests/spec_cli.rs
flowRS 2b6f30d348 flowRS engine, cut from flowRS f907e0756a7c
Built alone from an empty CARGO_HOME, its tests passed, and flowrsc checked, ran and exported a
document from the conformance corpus; the export built alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-10 08:54:33 -05:00

146 lines
4.9 KiB
Rust

//! `flowrsc` reads graphs as the document specification (`spec/flowrs-document-2.md`) says:
//! a `.flowrs` document, the graph language and graph JSON, wherever it takes a graph; the
//! older spellings as what they meant; a graph text's `app { … }` block as its app.
use flowrs_cli::{Cli, Command, EXIT_INVALID, EXIT_OK, EXIT_USAGE, run};
use std::path::{Path, PathBuf};
fn root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.unwrap()
.to_path_buf()
}
fn corpus(rel: &str) -> PathBuf {
root().join("spec/conformance").join(rel)
}
fn check(path: &Path) -> (i32, String) {
let cli = Cli {
command: Command::Check {
graph: path.to_path_buf(),
},
quiet: false,
};
let (mut out, mut err) = (Vec::new(), Vec::new());
let code = run(&cli, &mut out, &mut err);
(code, String::from_utf8(err).unwrap())
}
#[derive(serde::Deserialize)]
struct Manifest {
flowrs: Vec<Case>,
fdsl: Vec<TextCase>,
}
#[derive(serde::Deserialize)]
struct Case {
file: String,
reader: String,
}
#[derive(serde::Deserialize)]
struct TextCase {
file: String,
parses: bool,
}
fn manifest() -> Manifest {
serde_json::from_str(
&std::fs::read_to_string(root().join("spec/conformance/manifest.json")).unwrap(),
)
.unwrap()
}
/// Every corpus document the reader accepts, `flowrsc check` reads (and judges the graph:
/// exit 0 or 1); every one it refuses is a usage error, never a graph verdict.
#[test]
fn flowrsc_check_reads_every_document_the_reader_accepts_and_refuses_the_rest() {
let m = manifest();
for c in &m.flowrs {
let (code, err) = check(&corpus(&c.file));
if c.reader == "accepts" {
assert!(
code == EXIT_OK || code == EXIT_INVALID,
"{}: exit {code}: {err}",
c.file
);
} else {
assert_eq!(code, EXIT_USAGE, "{}: {err}", c.file);
}
}
for c in &m.fdsl {
let (code, err) = check(&corpus(&c.file));
if c.parses {
assert!(code == EXIT_OK || code == EXIT_INVALID, "{}: {err}", c.file);
} else {
assert_eq!(code, EXIT_USAGE, "{}: {err}", c.file);
}
}
// The specification's example is a valid graph, in either form.
for f in ["flowrs/add-two-numbers.flowrs", "fdsl/add.fdsl"] {
let (code, err) = check(&corpus(f));
assert_eq!(code, EXIT_OK, "{f}: {err}");
assert!(err.contains("ok: 4 nodes, 3 wires"), "{f}: {err}");
}
// A document from a newer flowRS says so.
let (_, err) = check(&corpus("flowrs/format-3.flowrs"));
assert!(err.contains("update flowRS"), "{err}");
// The specification's function example is a valid graph in both forms; a call of a
// function the document does not define is an invalid graph, named.
for f in ["flowrs/functions.flowrs", "fdsl/function.fdsl"] {
let (code, err) = check(&corpus(f));
assert_eq!(code, EXIT_OK, "{f}: {err}");
assert!(err.contains("ok: 4 nodes, 3 wires"), "{f}: {err}");
}
let (code, err) = check(&corpus("flowrs/call-to-an-undefined-function.flowrs"));
assert_eq!(code, EXIT_INVALID, "{err}");
assert!(err.contains("no function `g@1` is defined"), "{err}");
}
/// The older `tab` property is moved into the app's panel, as the editor opens it.
#[test]
fn an_older_tab_property_is_read_into_the_apps_panel() {
let (graph, app) = flowrs_cli::load_document(&corpus("flowrs/legacy-tab.flowrs")).unwrap();
let node = &graph.nodes[&flowrs_core::NodeId(0)];
assert!(
!node.properties.contains_key("tab"),
"{:?}",
node.properties
);
assert_eq!(node.properties["value"], "2");
let app = app.expect("an app is made for the panel");
let card = app
.panel
.cards
.iter()
.find(|c| c.card == "ctl:0")
.expect("the control's card is placed");
assert_eq!(card.tab, Some(1));
assert_eq!(app.panel.tabs, ["Tab 1", "Tab 2"]);
// And `check` reads the same graph, with the property gone.
let g = flowrs_cli::load_graph(&corpus("flowrs/legacy-tab.flowrs")).unwrap();
assert!(
!g.nodes[&flowrs_core::NodeId(0)]
.properties
.contains_key("tab")
);
}
/// A graph text's `app { … }` block is its app's source, read by the exporter's reader.
#[test]
fn a_graph_texts_app_block_is_its_app() {
let (graph, app) = flowrs_cli::load_document(&corpus("fdsl/every-form.fdsl")).unwrap();
assert!(graph.nodes.len() >= 8);
let app = app.expect("the text's app");
assert_eq!(app.version, "1.0");
assert!(
app.source.contains("Heading(\"Every statement\")"),
"{}",
app.source
);
let (_, none) = flowrs_cli::load_document(&corpus("fdsl/add.fdsl")).unwrap();
assert!(none.is_none(), "a text with no block has no app");
}