diff --git a/Cargo.toml b/Cargo.toml index c26dd86..3d9d0b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,7 @@ edition = "2018" [dependencies] clap = "2.33.3" +toml = "0.5" +serde = "1.0" +serde_derive = "1.0" +function_name = "0.2.0" diff --git a/auto_test.sh b/auto_test.sh index 50fb9e3..9e93381 100755 --- a/auto_test.sh +++ b/auto_test.sh @@ -3,4 +3,7 @@ # Start test if one of the files was modified cargo install cargo-watch -RUST_BACKTRACE=full cargo watch -x "test -- --nocapture" +# Run the tests on change +# Set RUST_BACKTRACE to 1 to see a short backtrace in case of an error +# Set RUST_BACKTRACE to "full" to see a full backtrace in case of an error +RUST_BACKTRACE=0 cargo watch -x "test -- --nocapture" diff --git a/src/cfg_reader.rs b/src/cfg_reader.rs new file mode 100644 index 0000000..8ac4b12 --- /dev/null +++ b/src/cfg_reader.rs @@ -0,0 +1,52 @@ +use serde_derive::Deserialize; +use std::fs::File; +use std::{error::Error, io::Read}; + +extern crate toml; + +#[derive(Deserialize)] +pub struct Config { + channels: Vec, + // debounce_millis : Option, + // invert : Option, +} + +#[derive(Deserialize)] +pub struct S0Channel { + id: u8, + gpio: u8, + // debounce_millis: Option, +} + +// use std::any::type_name; +// fn type_of(_: T) -> &'static str { +// type_name::() +// } + +const MAX_CONFIG_FILE_SIZE: u64 = 1_000_000u64; + +pub fn parse_file(config_file_name: &str) -> Result> { + let mut cfg_file = File::open(String::from(config_file_name))?; + let cfg_file_meta = &cfg_file.metadata()?; + let cfg_file_len = &cfg_file_meta.len(); + if cfg_file_len > &MAX_CONFIG_FILE_SIZE { + return Err(From::from(format!( + "Size of config file is {} exceeds the limit of {}", + &cfg_file_len, MAX_CONFIG_FILE_SIZE + ))); + } + let mut cfg_file_content = String::new(); + cfg_file.read_to_string(&mut cfg_file_content).unwrap(); + parse_string(&cfg_file_content) +} + +fn parse_string(cfg_str: &String) -> Result> { + match toml::from_str(cfg_str) { + Ok(cfg) => Ok(cfg), + Err(err) => Err(From::from(err)), + } +} + +#[cfg(test)] +#[path = "./cfg_reader_test.rs"] +mod cfg_reader_test; diff --git a/src/cfg_reader_test.rs b/src/cfg_reader_test.rs new file mode 100644 index 0000000..956a581 --- /dev/null +++ b/src/cfg_reader_test.rs @@ -0,0 +1,172 @@ +use super::*; +use function_name::named; +use std::{fs, io::Write}; + +fn create_cfg_file(fn_name: &str, content: &str) -> String { + let cfg_file_name = format!("/tmp/{}.cfg", fn_name); + let mut cfg_file = File::create(&cfg_file_name).unwrap(); + cfg_file.write(content.as_bytes()).unwrap(); + cfg_file_name +} + +fn remove_cfg_file(cfg_file_name: &String) { + fs::remove_file(cfg_file_name).unwrap(); +} + +#[test] +fn parse_file_fails_if_file_does_not_exist() { + assert!(parse_file("/this/path/does/not/exist").is_err()); +} + +#[test] +#[named] +fn parse_file_fails_if_file_is_greater_than_1mb() { + let cfg_file_name = create_cfg_file( + function_name!(), + "a".repeat(MAX_CONFIG_FILE_SIZE as usize + 1).as_str(), + ); + assert!(parse_file(&cfg_file_name).is_err()); + remove_cfg_file(&cfg_file_name); +} + +#[test] +#[named] +fn parse_file_succeeds() { + let cfg_file_name = create_cfg_file( + function_name!(), + r#" + [[channels]] + id = 1 + gpio = 4 + "#, + ); + assert!(parse_file(&cfg_file_name).is_ok()); + remove_cfg_file(&cfg_file_name); +} + +#[test] +fn parse_string_fails_on_empty_string() { + let cfg_str = String::from(r#""#); + assert!(parse_string(&cfg_str).is_err()); +} + +#[test] +fn parse_string_fails_without_section_channels() { + let cfg_str = String::from( + r#" + id = 1 + gpio = 4 + "#, + ); + assert!(parse_string(&cfg_str).is_err()); +} + +#[test] +fn parse_string_fails_on_unknown_section() { + let cfg_str = String::from( + r#" + [[unknown]] + id = 1 + gpio = 4 + "#, + ); + assert!(parse_string(&cfg_str).is_err()); +} + +#[test] +fn parse_string_fails_on_section_format_error() { + let cfg_str = String::from( + r#" + [[channels] + id = 1 + gpio = 4 + "#, + ); + assert!(parse_string(&cfg_str).is_err()); + + let cfg_str = String::from( + r#" + [channels] + id = 1 + gpio = 4 + "#, + ); + assert!(parse_string(&cfg_str).is_err()); + + let cfg_str = String::from( + r#" + [[channels]] + id = + gpio = 4 + "#, + ); + assert!(parse_string(&cfg_str).is_err()); + + let cfg_str = String::from( + r#" + [[channels]] + id = 1 + gpio = 4 + 2 + "#, + ); + assert!(parse_string(&cfg_str).is_err()); +} + +#[test] +fn parse_string_fails_on_missing_field_id() { + let cfg_str = String::from( + r#" + [[channels]] + gpio = 4 + "#, + ); + assert!(parse_string(&cfg_str).is_err()); +} + +#[test] +fn parse_string_fails_on_missing_field_gpio() { + let cfg_str = String::from( + r#" + [[channels]] + id = 1 + "#, + ); + assert!(parse_string(&cfg_str).is_err()); +} + +#[test] +fn parse_string_ignores_unknown_attributes() { + let cfg_str = String::from( + r#" + [[channels]] + unknown = 1 + id = 1 + gpio = 4 + "#, + ); + assert!(parse_string(&cfg_str).is_ok()); +} + +#[test] +fn parse_string_succeeds() { + let cfg = String::from( + r#" + [[channels]] + id = 1 + gpio = 4 + + [[channels]] + id = 2 + gpio = 17 + "#, + ); + let config = parse_string(&cfg); + assert!(&config.is_ok()); + let config = config.unwrap(); + assert_eq!(config.channels.len(), 2); + assert_eq!(config.channels[0].id, 1); + assert_eq!(config.channels[0].gpio, 4); + assert_eq!(config.channels[1].id, 2); + assert_eq!(config.channels[1].gpio, 17); +} diff --git a/src/main.rs b/src/main.rs index c64f103..9ad9353 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #[macro_use] extern crate clap; +mod cfg_reader; + const APP_VERSION: &'static str = env!("CARGO_PKG_VERSION"); const DEFAULT_CONFIG_FILE_NAME: &str = "/etc/s0_logger.cfg";