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]>
146 lines
4.9 KiB
Rust
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");
|
|
}
|