//! `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, fdsl: Vec, } #[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"); }