diff --git a/Cargo.toml b/Cargo.toml index 06770e30..61f66c38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "reveaal" version = "0.1.0" build = "src/build.rs" -authors = ["Peter Greve "] +authors = ["Thomas Lohse", "Sebastian Lund", "Thorulf Neustrup", "Peter Greve"] edition = "2018" [lib] @@ -20,14 +20,14 @@ logging = ["dep:env_logger", "dep:chrono"] [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -clap = { version = "~2.34.0", features = ["yaml"] } +clap = { version = "4.2.1", features = [ "derive" ] } pest = "2.5.6" pest_derive = "2.5.6" xml-rs = "0.8.3" serde-xml-rs = "0.6.0" elementtree = "1.2.2" dyn-clone = "1.0" -tonic = "0.8.2" +tonic = "0.8.3" prost = "0.11.0" tokio = { version = "1.0", features = ["macros", "rt"] } colored = "2.0.0" diff --git a/benches/bench_helper.rs b/benches/bench_helper.rs index 49f63e4b..c8c30570 100644 --- a/benches/bench_helper.rs +++ b/benches/bench_helper.rs @@ -4,8 +4,7 @@ use reveaal::{ComponentLoader, JsonProjectLoader}; const UNI_PATH: &str = "samples/json/EcdarUniversity"; pub fn get_uni_loader() -> Box { - let mut loader = - JsonProjectLoader::new_loader(UNI_PATH.to_string(), TEST_SETTINGS).to_comp_loader(); + let mut loader = JsonProjectLoader::new_loader(UNI_PATH, TEST_SETTINGS).to_comp_loader(); let _ = loader.get_component("Adm2"); let _ = loader.get_component("Administration"); let _ = loader.get_component("HalfAdm1"); diff --git a/src/DataReader/component_loader.rs b/src/DataReader/component_loader.rs index 13a8a062..10952e03 100644 --- a/src/DataReader/component_loader.rs +++ b/src/DataReader/component_loader.rs @@ -11,6 +11,7 @@ use crate::ProtobufServer::services::query_request::Settings; use crate::System::input_enabler; use std::collections::HashMap; use std::num::NonZeroUsize; +use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; pub type ComponentsMap = HashMap; @@ -198,12 +199,12 @@ fn parse_xml_components(xml: &str) -> Vec { pub trait ProjectLoader: ComponentLoader { fn get_declarations(&self) -> &SystemDeclarations; fn get_queries(&self) -> &Vec; - fn get_project_path(&self) -> &str; + fn get_project_path(&self) -> &PathBuf; fn to_comp_loader(self: Box) -> Box; } pub struct JsonProjectLoader { - project_path: String, + project_path: PathBuf, loaded_components: ComponentsMap, system_declarations: SystemDeclarations, queries: Vec, @@ -247,7 +248,7 @@ impl ProjectLoader for JsonProjectLoader { &self.queries } - fn get_project_path(&self) -> &str { + fn get_project_path(&self) -> &PathBuf { &self.project_path } @@ -257,12 +258,16 @@ impl ProjectLoader for JsonProjectLoader { } impl JsonProjectLoader { - pub fn new_loader(project_path: String, settings: Settings) -> Box { + #[allow(clippy::new_ret_no_self)] + pub fn new_loader>( + project_path: P, + settings: Settings, + ) -> Box { let system_declarations = json_reader::read_system_declarations(&project_path).unwrap(); let queries = json_reader::read_queries(&project_path).unwrap(); Box::new(JsonProjectLoader { - project_path, + project_path: project_path.as_ref().to_path_buf(), loaded_components: HashMap::new(), system_declarations, queries, @@ -290,7 +295,7 @@ impl JsonProjectLoader { } pub struct XmlProjectLoader { - project_path: String, + project_path: PathBuf, loaded_components: ComponentsMap, system_declarations: SystemDeclarations, queries: Vec, @@ -328,7 +333,7 @@ impl ProjectLoader for XmlProjectLoader { &self.queries } - fn get_project_path(&self) -> &str { + fn get_project_path(&self) -> &PathBuf { &self.project_path } @@ -338,7 +343,11 @@ impl ProjectLoader for XmlProjectLoader { } impl XmlProjectLoader { - pub fn new_loader(project_path: String, settings: Settings) -> Box { + #[allow(clippy::new_ret_no_self)] + pub fn new_loader>( + project_path: P, + settings: Settings, + ) -> Box { let (comps, system_declarations, queries) = parse_xml_from_file(&project_path); let mut map = HashMap::::new(); @@ -353,7 +362,7 @@ impl XmlProjectLoader { } Box::new(XmlProjectLoader { - project_path, + project_path: project_path.as_ref().to_path_buf(), loaded_components: map, system_declarations, queries, diff --git a/src/DataReader/json_reader.rs b/src/DataReader/json_reader.rs index 795eeee5..ba968ad8 100644 --- a/src/DataReader/json_reader.rs +++ b/src/DataReader/json_reader.rs @@ -4,39 +4,35 @@ use std::fs::File; use std::io::Read; use std::path::Path; -pub fn read_system_declarations(project_path: &str) -> Option { - let sysdecl_path = format!( - "{}{}SystemDeclarations.json", - project_path, - std::path::MAIN_SEPARATOR - ); +pub fn read_system_declarations>(project_path: P) -> Option { + let sysdecl_path = project_path.as_ref().join("SystemDeclarations.json"); if !Path::new(&sysdecl_path).exists() { return None; } - match read_json::(&sysdecl_path) { + match read_json::(&sysdecl_path) { Ok(sys_decls) => Some(sys_decls), Err(error) => panic!( "We got error {}, and could not parse json file {} to component", - error, &sysdecl_path + error, + sysdecl_path.display() ), } } -pub fn read_json_component(project_path: &str, component_name: &str) -> Component { - let component_path = format!( - "{0}{1}Components{1}{2}.json", - project_path, - std::path::MAIN_SEPARATOR, - component_name - ); +pub fn read_json_component>(project_path: P, component_name: &str) -> Component { + let component_path = project_path + .as_ref() + .join("Components") + .join(format!("{}.json", component_name)); let component: Component = match read_json(&component_path) { Ok(json) => json, Err(error) => panic!( "We got error {}, and could not parse json file {} to component", - error, component_path + error, + component_path.display() ), }; @@ -46,14 +42,18 @@ pub fn read_json_component(project_path: &str, component_name: &str) -> Componen //Input:File name //Description:uses the filename to open the file and then reads the file. //Output: Result type, if more info about this type is need please go to: https://doc.rust-lang.org/std/result/ -pub fn read_json(filename: &str) -> serde_json::Result { - let mut file = - File::open(filename).unwrap_or_else(|_| panic!("Could not find file {}", filename)); +pub fn read_json>(filename: P) -> serde_json::Result { + let mut file = File::open(&filename) + .unwrap_or_else(|_| panic!("Could not find file {}", filename.as_ref().display())); let mut data = String::new(); file.read_to_string(&mut data).unwrap(); - let json_file = serde_json::from_str(&data) - .unwrap_or_else(|_| panic!("{}: Json format is not as expected", filename)); + let json_file = serde_json::from_str(&data).unwrap_or_else(|_| { + panic!( + "{}: Json format is not as expected", + filename.as_ref().display() + ) + }); Ok(json_file) } @@ -65,8 +65,8 @@ pub fn json_to_component(json_str: &str) -> Result //Input:Filename //Description: transforms json into query type //Output:Result -pub fn read_queries(project_path: &str) -> Option> { - let queries_path = format!("{}{}Queries.json", project_path, std::path::MAIN_SEPARATOR); +pub fn read_queries>(project_path: P) -> Option> { + let queries_path = project_path.as_ref().join("Queries.json"); if !Path::new(&queries_path).exists() { return None; @@ -76,7 +76,8 @@ pub fn read_queries(project_path: &str) -> Option> { Ok(json) => Some(json), Err(error) => panic!( "We got error {}, and could not parse json file {} to query", - error, &queries_path + error, + queries_path.display() ), } } diff --git a/src/DataReader/json_writer.rs b/src/DataReader/json_writer.rs index 16f45482..a1bef706 100644 --- a/src/DataReader/json_writer.rs +++ b/src/DataReader/json_writer.rs @@ -1,13 +1,12 @@ use crate::ModelObjects::Component; -use std::fs::File; +use std::{fs::File, path::Path}; + +pub fn component_to_json_file>(project_path: P, component: &Component) { + let path = project_path + .as_ref() + .join("Components") + .join(format!("{}.json", component.name)); -pub fn component_to_json_file(project_path: &str, component: &Component) { - let path = format!( - "{0}{1}Components{1}{2}.json", - project_path, - std::path::MAIN_SEPARATOR, - component.name - ); let file = File::create(path).expect("Couldnt open file"); serde_json::to_writer_pretty(&file, component).expect("Failed to serialize component"); diff --git a/src/DataReader/parse_queries.rs b/src/DataReader/parse_queries.rs index 47064872..1a1f8891 100644 --- a/src/DataReader/parse_queries.rs +++ b/src/DataReader/parse_queries.rs @@ -220,8 +220,8 @@ pub fn test_parse() { } pub fn parse_to_query(input: &str) -> Vec { - let queries = parse_to_expression_tree(input).unwrap(); - queries + parse_to_expression_tree(input) + .expect("Parsing failed") .into_iter() .map(|q| Query { query: Option::from(q), diff --git a/src/DataReader/xml_parser.rs b/src/DataReader/xml_parser.rs index 478c2b8c..a6689347 100644 --- a/src/DataReader/xml_parser.rs +++ b/src/DataReader/xml_parser.rs @@ -10,14 +10,15 @@ use std::collections::HashMap; use std::fs::File; use std::io::BufReader; use std::io::Read; +use std::path::Path; -pub fn is_xml_project(project_path: &str) -> bool { - project_path.ends_with(".xml") +pub fn is_xml_project>(project_path: P) -> bool { + project_path.as_ref().ends_with(".xml") } ///Used to parse systems described in xml -pub(crate) fn parse_xml_from_file( - fileName: &str, +pub(crate) fn parse_xml_from_file>( + fileName: P, ) -> (Vec, SystemDeclarations, Vec) { //Open file and read xml let file = File::open(fileName).unwrap(); diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 00000000..45ad90a1 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,163 @@ +use clap::Parser; +use std::path::PathBuf; + +#[derive(Parser, Debug)] +#[command(author, version, about="Reveaal is a model checking engine for ECDAR (Environment for Compositional Design and Analysis of Real Time Systems)\nFor more information about ECDAR see https://www.ecdar.net/", long_about = Some("With Reveaal you can either run a single query with the 'query' command or run it as a server with the 'serve' command"))] +pub enum Args { + /// Start a gRPC server with the protocol defined in the protobuf file + /// + /// Examples of usage: + /// + /// Reveaal serve 127.0.0.1:4242 + /// + /// Reveaal serve -t 1 -c 50 127.0.0.1:4242 + Serve { + /// Ip address and port to serve the gRPC server on + #[clap(value_name = "IP:PORT")] + endpoint: String, + + /// The number of threads to use when running queries on the server + #[arg(short, long, default_value_t = num_cpus::get())] + thread_count: usize, + + /// The maximal number of component saved in the server cache + #[arg(short, long, default_value_t = 100)] + cache_size: usize, + }, + /// Run a query + /// + /// Examples of usage: + /// + /// Reveaal query "refinement: Researcher || Machine || Administration <= Spec" -i samples/json/EcdarUniversity + /// + /// Reveaal query "consistency: Machine" -i samples/json/EcdarUniversity + /// + /// Reveaal query "determinism: Researcher" -i samples/json/EcdarUniversity + Query { + /// The query to execute + #[clap(value_name = "QUERY_TYPE: refinement|consistency|reachability|save-component", value_parser = query_check)] + query: String, + + /// File (XML) or folder (JSON) with component definitions + #[arg(short, long, value_name = "XML|JSON")] + input_folder: PathBuf, + + /// Whether to enable clock reduction + #[arg(short, long, default_value_t = false)] + enable_clock_reduction: bool, + + /// Save file for refinement relations + #[arg(short, long, value_name = "FILE")] + save_refinement_relations: Option, + // TODO: Maybe add this later + // /// The number of threads to use when running the query + // #[arg(short, long, default_value_t = num_cpus::get())] + // thread_count: usize, + }, +} + +fn query_check(arg: &str) -> Result { + crate::parse_queries::parse_to_expression_tree(arg).map(|_| arg.to_string()) +} + +#[cfg(test)] +mod tests { + use super::Args; + use clap::Parser; + use std::path::PathBuf; + use std::str::FromStr; + use test_case::test_case; + + #[test] + fn serve_command_with_t_and_c_flags() { + println!("{:?}", PathBuf::from_str(" fucking pis path")); + // 0th argument does not matter, but it must be present + let input_args = vec!["", "serve", "-t", "10", "-c", "100", "127.0.0.1:4242"]; + let args_matches = Args::parse_from(input_args); + check_args( + args_matches, + Args::Serve { + endpoint: "127.0.0.1:4242".to_string(), + thread_count: 10, + cache_size: 100, + }, + ); + } + + #[test_case( + &["", "query", "-i", "/path/to/system", "-e", "-s", "saved-comp", "refinement: some <= refinement"], Args::Query { + query: "refinement: some <= refinement".to_string(), + input_folder: PathBuf::from("/path/to/system"), + enable_clock_reduction: true, + save_refinement_relations: Some(PathBuf::from("saved-comp")), + } ; "All fields" + )] + #[test_case( + &["", "query", "-i", "/path/to/system", "-s", "saved-comp", "refinement: some <= refinement"], Args::Query { + query: "refinement: some <= refinement".to_string(), + input_folder: PathBuf::from("/path/to/system"), + enable_clock_reduction: Default::default(), + save_refinement_relations: Some(PathBuf::from("saved-comp")), + } ; "Default clock-reduction" + )] + #[test_case( + &["", "query", "-i", "/path/to/system", "refinement: some <= refinement"], Args::Query { + query: "refinement: some <= refinement".to_string(), + input_folder: PathBuf::from("/path/to/system"), + enable_clock_reduction: Default::default(), + save_refinement_relations: None, + } ; "No saved path" + )] + fn query_command_tests(input_args: &[&str], expected: Args) { + check_args(Args::parse_from(input_args), expected); + } + + #[test_case(&["", "query", "-i", "/path/to/system", "-s", "refinement: some <= refinement"] ; "Not supplying needed argument")] + #[test_case(&["", "query", "-i", "/path/to/system", "refinement: some refinement"] ; "Bad query")] + #[test_case(&["", "serve", "-i", "/path/to/system", "refinement: some <= refinement"] ; "Wrong command")] + #[should_panic] + fn query_command_tests_panics(input_args: &[&str]) { + Args::try_parse_from(input_args).unwrap(); + } + + fn check_args(actual: Args, expected: Args) { + match (actual, expected) { + ( + Args::Query { + query: qa, + input_folder: ia, + enable_clock_reduction: da, + save_refinement_relations: sa, + }, + Args::Query { + query: qe, + input_folder: ie, + enable_clock_reduction: de, + save_refinement_relations: se, + }, + ) => { + assert_eq!(qa, qe); + assert_eq!(ia, ie); + assert_eq!(da, de); + assert_eq!(sa, se); + } + ( + Args::Serve { + endpoint: ea, + thread_count: ta, + cache_size: ca, + }, + Args::Serve { + endpoint: ee, + thread_count: te, + cache_size: ce, + }, + ) => { + assert_eq!(ea, ee); + assert_eq!(ta, te); + assert_eq!(ca, ce); + } + (a, e) => panic!("Not same, expected {:?}, got {:?}", e, a), + } + } +} diff --git a/src/cli.yml b/src/cli.yml deleted file mode 100644 index dfaa7f6a..00000000 --- a/src/cli.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: reveaal -version: "1.0" -about: Rust engine for ecdar -args: - - endpoint: - short: p - long: proto - required: false - takes_value: true - - folder: - short: i - long: input-folder - #help: input folder with components as json and a Queries.json file - required: false - takes_value: true - default_value: "." - #index: 1 - - query: - #short: q - #long: query - #help: query in a format of function:component<=component, if -query is present, the queries file will be ignored - required: false - takes_value: true - index: 1 - - save-relation: - short: s - long: save-relation - required: false - takes_value: false - - clock-reduction: - long: disable-clock-reduction - required: false - takes_value: false - - cache-size: - short: cs - long: cache-size - required: false - takes_value: true - default_value: "100" - - thread-number: - short: tn - long: thread-number - required: false - takes_value: true -# - checkInputOutput: -# short: c -# long: checkInputOutput -# help: returns extra ouputs which are present on the left side and vise versa inputs -# required: false -# takes_value: false diff --git a/src/lib.rs b/src/lib.rs index fec7e691..4a421870 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod ProtobufServer; pub mod Simulation; pub mod System; pub mod TransitionSystems; +pub mod cli; pub mod logging; pub mod tests; diff --git a/src/main.rs b/src/main.rs index 5c0a2d30..9d062dfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,43 +1,38 @@ #![allow(non_snake_case)] -use clap::{load_yaml, App}; +use reveaal::cli::Args; use reveaal::logging::setup_logger; +use reveaal::ModelObjects::Query; use reveaal::System::query_failures::QueryResult; +use clap::Parser; use reveaal::ProtobufServer::services::query_request::Settings; use reveaal::{ extract_system_rep, parse_queries, start_grpc_server_with_tokio, xml_parser, ComponentLoader, JsonProjectLoader, ProjectLoader, XmlProjectLoader, }; use std::env; +use std::path::Path; fn main() -> Result<(), Box> { + let args = Args::parse(); + #[cfg(feature = "logging")] - let yaml = load_yaml!("cli.yml"); - let matches = App::from(yaml).get_matches(); setup_logger().unwrap(); - if let Some(ip_endpoint) = matches.value_of("endpoint") { - let thread_count: usize = match matches.value_of("thread_number") { - Some(num_of_threads) => num_of_threads - .parse() - .expect("Could not parse the input for the number of threads"), - None => num_cpus::get(), - }; - let cache_count: usize = matches - .value_of("cache-size") - .unwrap() - .parse() - .expect("Could not parse input for the cache_size"); - - start_grpc_server_with_tokio(ip_endpoint, cache_count, thread_count)?; - } else { - start_using_cli(&matches); + + match args { + Args::Serve { + endpoint, + thread_count, + cache_size, + } => start_grpc_server_with_tokio(&endpoint, cache_size, thread_count)?, + Args::Query { .. } => start_using_cli(args), } Ok(()) } -fn start_using_cli(matches: &clap::ArgMatches) { - let (mut comp_loader, queries) = parse_args(matches); +fn start_using_cli(args: Args) { + let (mut comp_loader, queries) = parse_args(args); let mut results = vec![]; for query in &queries { @@ -60,27 +55,41 @@ fn start_using_cli(matches: &clap::ArgMatches) { } } -fn parse_args( - matches: &clap::ArgMatches, -) -> (Box, Vec) { - let folder_path = matches.value_of("folder").unwrap_or(""); - let query = matches.value_of("query").unwrap_or(""); - let settings = Settings { - disable_clock_reduction: matches.is_present("clock-reduction"), - }; - - let project_loader = get_project_loader(folder_path.to_string(), settings); - - let queries = if query.is_empty() { - project_loader.get_queries().clone() - } else { - parse_queries::parse_to_query(query) - }; - - (project_loader.to_comp_loader(), queries) +fn parse_args(args: Args) -> (Box, Vec) { + match args { + Args::Query { + query, + input_folder, + enable_clock_reduction, + save_refinement_relations, + //thread_count, + } => { + if save_refinement_relations.is_some() { + unimplemented!("Saving refinement relations is not yet implemented"); + } + + let settings = Settings { + disable_clock_reduction: !enable_clock_reduction, + }; + + let project_loader = get_project_loader(input_folder, settings); + + let queries = if query.is_empty() { + project_loader.get_queries().clone() + } else { + parse_queries::parse_to_query(&query) + }; + + (project_loader.to_comp_loader(), queries) + } + _ => unreachable!("This function should only be called when the args are a query"), + } } -fn get_project_loader(project_path: String, settings: Settings) -> Box { +fn get_project_loader>( + project_path: P, + settings: Settings, +) -> Box { if xml_parser::is_xml_project(&project_path) { XmlProjectLoader::new_loader(project_path, settings) } else { diff --git a/src/tests/reachability/helper_functions.rs b/src/tests/reachability/helper_functions.rs index 1e7c143d..c0aa0f22 100644 --- a/src/tests/reachability/helper_functions.rs +++ b/src/tests/reachability/helper_functions.rs @@ -22,9 +22,9 @@ pub mod reachability_test_helper_functions { folder_path: &str, ) -> (Box, Box) { let mut comp_loader = if xml_parser::is_xml_project(folder_path) { - XmlProjectLoader::new_loader(folder_path.to_string(), crate::tests::TEST_SETTINGS) + XmlProjectLoader::new_loader(folder_path, crate::tests::TEST_SETTINGS) } else { - JsonProjectLoader::new_loader(folder_path.to_string(), crate::tests::TEST_SETTINGS) + JsonProjectLoader::new_loader(folder_path, crate::tests::TEST_SETTINGS) } .to_comp_loader(); let mut dim: ClockIndex = 0; diff --git a/src/tests/sample.rs b/src/tests/sample.rs index 060e7716..f6614de3 100644 --- a/src/tests/sample.rs +++ b/src/tests/sample.rs @@ -6,10 +6,8 @@ mod samples { #[test] fn test_locations_T1() { - let mut project_loader = JsonProjectLoader::new_loader( - CONJUNCTION_SAMPLE.to_string(), - crate::tests::TEST_SETTINGS, - ); + let mut project_loader = + JsonProjectLoader::new_loader(CONJUNCTION_SAMPLE, crate::tests::TEST_SETTINGS); let t1 = project_loader.get_component("Test1"); assert_eq!(t1.name, "Test1"); @@ -18,10 +16,8 @@ mod samples { #[test] fn test_locations_T2() { - let mut project_loader = JsonProjectLoader::new_loader( - CONJUNCTION_SAMPLE.to_string(), - crate::tests::TEST_SETTINGS, - ); + let mut project_loader = + JsonProjectLoader::new_loader(CONJUNCTION_SAMPLE, crate::tests::TEST_SETTINGS); let t2 = project_loader.get_component("Test2"); assert_eq!(t2.name, "Test2"); @@ -30,10 +26,8 @@ mod samples { #[test] fn test_locations_T3() { - let mut project_loader = JsonProjectLoader::new_loader( - CONJUNCTION_SAMPLE.to_string(), - crate::tests::TEST_SETTINGS, - ); + let mut project_loader = + JsonProjectLoader::new_loader(CONJUNCTION_SAMPLE, crate::tests::TEST_SETTINGS); let t3 = project_loader.get_component("Test3"); assert_eq!(t3.name, "Test3"); @@ -42,10 +36,8 @@ mod samples { #[test] fn test_names_T1_through_T12() { - let mut project_loader = JsonProjectLoader::new_loader( - CONJUNCTION_SAMPLE.to_string(), - crate::tests::TEST_SETTINGS, - ); + let mut project_loader = + JsonProjectLoader::new_loader(CONJUNCTION_SAMPLE, crate::tests::TEST_SETTINGS); for i in 1..12 { let t = project_loader.get_component(&format!("Test{}", i).to_string());