diff --git a/Cargo.lock b/Cargo.lock index 573c0d48..01a08ddd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,7 @@ dependencies = [ "maplit", "nom 7.1.1", "nom_locate", + "pretty_assertions", "serde_json", "strum", ] diff --git a/air/tests/test_module/issues/issue_221.rs b/air/tests/test_module/issues/issue_221.rs index 78fac5bc..677d30e6 100644 --- a/air/tests/test_module/issues/issue_221.rs +++ b/air/tests/test_module/issues/issue_221.rs @@ -14,6 +14,7 @@ * limitations under the License. */ +use air_test_framework::TestExecutor; use air_test_utils::prelude::*; #[test] @@ -27,24 +28,17 @@ fn issue_221() { let peer_1_value = "peer_1_value"; let peer_2_value = "peer_2_value"; - let mut peer_1 = create_avm(set_variable_call_service(json!(peer_1_value)), peer_1_id); - let mut peer_2 = create_avm(set_variable_call_service(json!(peer_2_value)), peer_2_id); - let mut join_1 = create_avm(echo_call_service(), join_1_id); - let mut set_variable = create_avm( - set_variable_call_service(json!([peer_1_id, peer_2_id])), - set_variable_id, - ); let script = f!(r#" (seq (seq (seq ;; let's peers be an array of two values [peer_1_id, peer_2_id] - (call "{set_variable_id}" ("" "") [] peers) + (call "{set_variable_id}" ("" "") [] peers) ; ok = ["{peer_1_id}", "{peer_2_id}"] (fold peers peer (par (seq - (call peer ("" "") [] value) + (call peer ("" "") [peer] value) ; map = {{"{peer_1_id}": "{peer_1_value}", "{peer_2_id}": "{peer_2_value}"}} ;; it's crucial to reproduce this bug to add value to stream ;; with help of ap instruction (ap value $stream) @@ -61,8 +55,8 @@ fn issue_221() { ;; appropriate way and state for (1) is returned (par (par - (call "{join_1_id}" ("" "") [iterator]) - (call "{join_2_id}" ("" "") [iterator]) + (call "{join_1_id}" ("" "") [iterator]) ; behaviour = echo + (call "{join_2_id}" ("" "") [iterator]) ; behaviour = echo ) (next iterator) ) @@ -72,18 +66,20 @@ fn issue_221() { ) "#); - let result = checked_call_vm!(set_variable, <_>::default(), &script, "", ""); - let peer_1_result = checked_call_vm!(peer_1, <_>::default(), &script, "", result.data.clone()); - let peer_2_result = checked_call_vm!(peer_2, <_>::default(), &script, "", result.data.clone()); - - let join_1_result = checked_call_vm!(join_1, <_>::default(), &script, "", peer_1_result.data.clone()); - let join_1_result = checked_call_vm!( - join_1, - <_>::default(), + let executor = TestExecutor::new( + TestRunParameters::from_init_peer_id("set_variable_id"), + vec![], + vec![peer_1_id, peer_2_id].into_iter().map(Into::into), &script, - join_1_result.data, - peer_2_result.data.clone() - ); // before 0.20.9 it fails here + ) + .expect("Invalid annotated AIR script"); + + let _result = executor.execute_one(set_variable_id).unwrap(); + let _peer_1_result = executor.execute_one(peer_1_id).unwrap(); + let _peer_2_result = executor.execute_one(peer_2_id).unwrap(); + + let _join_1_result = executor.execute_one(join_1_id).unwrap(); + let join_1_result = executor.execute_one(join_1_id).unwrap(); // before 0.20.9 it fails here let actual_trace = trace_from_result(&join_1_result); let expected_trace = vec![ executed_state::scalar(json!([peer_1_id, peer_2_id])), diff --git a/crates/testing-framework/Cargo.toml b/crates/testing-framework/Cargo.toml index 02943909..506a32ae 100644 --- a/crates/testing-framework/Cargo.toml +++ b/crates/testing-framework/Cargo.toml @@ -24,5 +24,7 @@ serde_json = "1.0.85" [dev-dependencies] maplit = "1.0.2" +pretty_assertions = "0.6.1" + # We do not want to depend on wasm binary path air-test-utils = { path = "../air-lib/test-utils", features = ["test_with_native_code"] } diff --git a/crates/testing-framework/src/asserts/mod.rs b/crates/testing-framework/src/asserts/mod.rs index 550dc576..05b0d0a7 100644 --- a/crates/testing-framework/src/asserts/mod.rs +++ b/crates/testing-framework/src/asserts/mod.rs @@ -36,11 +36,14 @@ pub enum ServiceDefinition { Error(CallServiceResult), /// Service that may return a new value on subsequent call. Its keys are either /// call number string starting from "0", or "default". - // TODO We need to return error results too, so we need to define a call result - // for default and individual errors. - #[strum_discriminants(strum(serialize = "seq_result"))] - SeqResult(HashMap), + #[strum_discriminants(strum(serialize = "seq_ok"))] + SeqOk(HashMap), + #[strum_discriminants(strum(serialize = "seq_error"))] + SeqError(HashMap), /// Some known service by name: "echo", "unit" (more to follow). #[strum_discriminants(strum(serialize = "behaviour"))] Behaviour(String), + /// Maps first argument to a value + #[strum_discriminants(strum(serialize = "map"))] + Map(HashMap), } diff --git a/crates/testing-framework/src/asserts/parser.rs b/crates/testing-framework/src/asserts/parser.rs index 669875ec..864bfbc1 100644 --- a/crates/testing-framework/src/asserts/parser.rs +++ b/crates/testing-framework/src/asserts/parser.rs @@ -16,9 +16,10 @@ use super::{ServiceDefinition, ServiceTagName}; use crate::services::JValue; +use crate::transform::parser::delim_ws; use air_test_utils::CallServiceResult; -use nom::{error::VerboseError, IResult, InputTakeAtPosition, Parser}; +use nom::{error::VerboseError, IResult}; use std::{collections::HashMap, str::FromStr}; @@ -50,8 +51,10 @@ pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> { alt(( tag(ServiceTagName::Ok.as_ref()), tag(ServiceTagName::Error.as_ref()), - tag(ServiceTagName::SeqResult.as_ref()), + tag(ServiceTagName::SeqOk.as_ref()), + tag(ServiceTagName::SeqError.as_ref()), tag(ServiceTagName::Behaviour.as_ref()), + tag(ServiceTagName::Map.as_ref()), )), equal(), cut(context( @@ -68,33 +71,26 @@ pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> { Ok(ServiceTagName::Error) => { serde_json::from_str::(value).map(ServiceDefinition::Error) } - Ok(ServiceTagName::SeqResult) => { - serde_json::from_str::>(value) - .map(ServiceDefinition::SeqResult) + Ok(ServiceTagName::SeqOk) => { + serde_json::from_str(value).map(ServiceDefinition::SeqOk) + } + Ok(ServiceTagName::SeqError) => { + serde_json::from_str::>(value) + .map(ServiceDefinition::SeqError) } Ok(ServiceTagName::Behaviour) => Ok(ServiceDefinition::Behaviour(value.to_owned())), + Ok(ServiceTagName::Map) => serde_json::from_str(value).map(ServiceDefinition::Map), Err(_) => unreachable!("unknown tag {:?}", tag), } }, ))(inp) } -pub(crate) fn delim_ws(f: F) -> impl FnMut(I) -> IResult -where - F: Parser, - E: nom::error::ParseError, - I: InputTakeAtPosition, - ::Item: nom::AsChar + Clone, -{ - use nom::character::complete::multispace0; - use nom::sequence::delimited; - - delimited(multispace0, f, multispace0) -} - #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; + use serde_json::json; #[test] fn test_parse_empty() { @@ -151,13 +147,13 @@ mod tests { } #[test] - fn test_seq_result() { + fn test_seq_ok() { use serde_json::json; - let res = ServiceDefinition::from_str(r#"seq_result={"default": 42, "1": true, "3": []}"#); + let res = ServiceDefinition::from_str(r#"seq_ok={"default": 42, "1": true, "3": []}"#); assert_eq!( res, - Ok(ServiceDefinition::SeqResult(maplit::hashmap! { + Ok(ServiceDefinition::SeqOk(maplit::hashmap! { "default".to_owned() => json!(42), "1".to_owned() => json!(true), "3".to_owned() => json!([]), @@ -166,15 +162,45 @@ mod tests { } #[test] - fn test_seq_result_malformed() { - let res = ServiceDefinition::from_str(r#"seq_result={"default": 42, "1": true, "3": ]}"#); + fn test_seq_ok_malformed() { + let res = ServiceDefinition::from_str(r#"seq_ok={"default": 42, "1": true, "3": ]}"#); assert!(res.is_err()); } #[test] - fn test_seq_result_invalid() { + fn test_seq_ok_invalid() { // TODO perhaps, we should support both arrays and maps - let res = ServiceDefinition::from_str(r#"seq_result=[42, 43]"#); + let res = ServiceDefinition::from_str(r#"seq_ok=[42, 43]"#); + assert!(res.is_err()); + } + + #[test] + fn test_seq_error() { + use serde_json::json; + + let res = ServiceDefinition::from_str( + r#"seq_error={"default": {"ret_code": 0, "result": 42}, "1": {"ret_code": 0, "result": true}, "3": {"ret_code": 1, "result": "error"}}"#, + ); + assert_eq!( + res, + Ok(ServiceDefinition::SeqError(maplit::hashmap! { + "default".to_owned() => CallServiceResult::ok(json!(42)), + "1".to_owned() => CallServiceResult::ok(json!(true)), + "3".to_owned() => CallServiceResult::err(1, json!("error")), + })), + ); + } + + #[test] + fn test_seq_error_malformed() { + let res = ServiceDefinition::from_str(r#"seq_error={"default": 42, "1": true]}"#); + assert!(res.is_err()); + } + + #[test] + fn test_seq_error_invalid() { + // TODO perhaps, we should support both arrays and maps + let res = ServiceDefinition::from_str(r#"seq_error=[42, 43]"#); assert!(res.is_err()); } @@ -183,4 +209,16 @@ mod tests { let res = ServiceDefinition::from_str(r#"behaviour=echo"#); assert_eq!(res, Ok(ServiceDefinition::Behaviour("echo".to_owned())),); } + + #[test] + fn test_map() { + let res = ServiceDefinition::from_str(r#"map = {"42": [], "a": 2}"#); + assert_eq!( + res, + Ok(ServiceDefinition::Map(maplit::hashmap! { + "42".to_owned() => json!([]), + "a".to_owned() => json!(2) + })) + ); + } } diff --git a/crates/testing-framework/src/execution/mod.rs b/crates/testing-framework/src/execution/mod.rs index b5c5f84d..4fd9b5a7 100644 --- a/crates/testing-framework/src/execution/mod.rs +++ b/crates/testing-framework/src/execution/mod.rs @@ -145,10 +145,13 @@ fn build_peers( #[cfg(test)] mod tests { - use air_test_utils::prelude::*; - use super::*; + use air_test_utils::prelude::*; + use pretty_assertions::assert_eq; + + use std::ops::Deref; + #[test] fn test_execution() { let exec = TestExecutor::new( @@ -248,7 +251,7 @@ mod tests { } #[test] - fn test_seq_result() { + fn test_seq_ok() { let exec = TestExecutor::new( TestRunParameters::from_init_peer_id("init_peer_id"), vec![], @@ -260,7 +263,7 @@ mod tests { (ap 1 k) (fold var i (seq - (call i.$.p ("service" "func") [i k] k) ; seq_result = {"0":12,"default":42} + (call i.$.p ("service" "func") [i k] k) ; seq_ok = {"0":12,"default":42} (next i))))) (call "init_peer_id" ("a" "b") []) ; ok = 0 )"#, @@ -322,6 +325,152 @@ mod tests { } } + #[test] + fn test_map() { + let exec = TestExecutor::new( + TestRunParameters::from_init_peer_id("peer1"), + vec![], + IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into), + r#" +(seq + (call "peer1" ("" "") [] peers) ; ok = ["peer2", "peer3"] + (fold peers p + (seq + (call p ("" "") [p]) ; map = {"peer2": 42, "peer3": 43} + (next p) +))) +"#, + ) + .unwrap(); + + let result_init: Vec<_> = exec.execution_iter("peer1").unwrap().collect(); + + assert_eq!(result_init.len(), 1); + let outcome1 = &result_init[0]; + assert_eq!(outcome1.ret_code, 0); + assert_eq!(outcome1.error_message, ""); + assert_next_pks!(&outcome1.next_peer_pks, ["peer2"]); + + { + let results2 = exec.execute_all("peer2").unwrap(); + assert_eq!(results2.len(), 1); + let outcome2 = &results2[0]; + assert_eq!(outcome2.ret_code, 0, "{:?}", outcome2); + assert!(exec.execution_iter("peer2").unwrap().next().is_none()); + assert_next_pks!(&outcome2.next_peer_pks, ["peer3"]); + } + + { + let results3 = exec.execute_all("peer3").unwrap(); + assert_eq!(results3.len(), 1); + let outcome3 = &results3[0]; + assert_eq!(outcome3.ret_code, 0, "{:?}", outcome3); + assert_next_pks!(&outcome3.next_peer_pks, []); + + let trace = trace_from_result(outcome3); + + assert_eq!( + trace.deref(), + vec![ + executed_state::scalar(json!(["peer2", "peer3"])), + executed_state::scalar(json!(42)), + executed_state::scalar(json!(43)), + ] + ); + } + } + + #[test] + #[should_panic] + fn test_map_no_arg() { + let exec = TestExecutor::new( + TestRunParameters::from_init_peer_id("peer1"), + vec![], + IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into), + r#" +(call "peer1" ("" "") [] p) ; map = {"any": "key"} +"#, + ) + .unwrap(); + let _result_init: Vec<_> = exec.execution_iter("peer1").unwrap().collect(); + } + + #[test] + fn test_seq_error() { + let exec = TestExecutor::new( + TestRunParameters::from_init_peer_id("init_peer_id"), + vec![], + IntoIterator::into_iter(["peer2", "peer3"]).map(Into::into), + r#"(seq + (seq + (call "peer1" ("service" "func") [] var) ; ok = [{"p":"peer2","v":2},{"p":"peer3","v":3}, {"p":"peer4"}] + (seq + (ap 1 k) + (fold var i + (seq + (call i.$.p ("service" "func") [i.$.v k] k) ; seq_error = {"0":{"ret_code":0,"result":12},"default":{"ret_code":1,"result":42}} + (next i))))) + (call "init_peer_id" ("a" "b") []) ; ok = 0 +)"#, + ) + .unwrap(); + + let result_init: Vec<_> = exec.execution_iter("init_peer_id").unwrap().collect(); + + assert_eq!(result_init.len(), 1); + let outcome1 = &result_init[0]; + assert_eq!(outcome1.ret_code, 0); + assert_eq!(outcome1.error_message, ""); + + assert!(exec.execution_iter("peer2").unwrap().next().is_none()); + { + let results1 = exec.execute_all("peer1").unwrap(); + assert_eq!(results1.len(), 1); + let outcome1 = &results1[0]; + assert_eq!(outcome1.ret_code, 0, "{:?}", outcome1); + assert!(exec.execution_iter("peer1").unwrap().next().is_none()); + assert_next_pks!(&outcome1.next_peer_pks, ["peer2"]); + } + + { + let results2: Vec<_> = exec.execute_all("peer2").unwrap(); + assert_eq!(results2.len(), 1); + let outcome2 = &results2[0]; + assert_eq!(outcome2.ret_code, 0, "{:?}", outcome2); + assert!(exec.execution_iter("peer2").unwrap().next().is_none()); + assert_next_pks!(&outcome2.next_peer_pks, ["peer3"]); + + let trace = trace_from_result(outcome2); + assert_eq!( + trace, + ExecutionTrace::from(vec![ + scalar(json!([{"p":"peer2","v":2},{"p":"peer3","v":3},{"p":"peer4"}])), + scalar_number(12), + request_sent_by("peer2"), + ]) + ); + } + + { + let results3: Vec<_> = exec.execute_all("peer3").unwrap(); + assert_eq!(results3.len(), 1); + // TODO why doesn't it fail? + let outcome3 = &results3[0]; + assert_eq!(outcome3.ret_code, 0, "{:?}", outcome3); + assert!(exec.execution_iter("peer3").unwrap().next().is_none()); + + let trace = trace_from_result(outcome3); + assert_eq!( + trace, + ExecutionTrace::from(vec![ + scalar(json!([{"p":"peer2","v":2},{"p":"peer3","v":3},{"p":"peer4"}])), + scalar_number(12), + request_sent_by("peer2"), + ]) + ); + } + } + #[test] fn test_echo() { let exec = TestExecutor::new( diff --git a/crates/testing-framework/src/services/results.rs b/crates/testing-framework/src/services/results.rs index c238991a..03e3d043 100644 --- a/crates/testing-framework/src/services/results.rs +++ b/crates/testing-framework/src/services/results.rs @@ -21,8 +21,9 @@ use air_test_utils::{ prelude::{echo_call_service, unit_call_service}, CallRequestParams, CallServiceClosure, CallServiceResult, }; +use serde_json::json; -use std::{cell::Cell, collections::HashMap, convert::TryInto, time::Duration}; +use std::{borrow::Cow, cell::Cell, collections::HashMap, convert::TryInto, time::Duration}; pub struct ResultService { results: HashMap, @@ -37,8 +38,10 @@ impl TryInto for ServiceDefinition { Ok(Box::new(move |_| CallServiceResult::ok(jvalue.clone()))) } ServiceDefinition::Error(call_result) => Ok(Box::new(move |_| call_result.clone())), - ServiceDefinition::SeqResult(call_map) => Ok(seq_result_closure(call_map)), + ServiceDefinition::SeqOk(call_map) => Ok(seq_ok_closure(call_map)), + ServiceDefinition::SeqError(call_map) => Ok(seq_error_closure(call_map)), ServiceDefinition::Behaviour(name) => named_service_closure(name), + ServiceDefinition::Map(map) => Ok(map_service_closure(map)), } } } @@ -51,7 +54,7 @@ fn named_service_closure(name: String) -> Result { } } -fn seq_result_closure(call_map: HashMap) -> CallServiceClosure { +fn seq_ok_closure(call_map: HashMap) -> CallServiceClosure { let call_number_seq = Cell::new(0); Box::new(move |_| { @@ -74,6 +77,45 @@ fn seq_result_closure(call_map: HashMap) -> CallServi }) } +fn seq_error_closure(call_map: HashMap) -> CallServiceClosure { + let call_number_seq = Cell::new(0); + + Box::new(move |_| { + let call_number = call_number_seq.get(); + let call_num_str = call_number.to_string(); + call_number_seq.set(call_number + 1); + + call_map + .get(&call_num_str) + .or_else(|| call_map.get("default")) + .unwrap_or_else(|| { + panic!( + "neither value {} nor default value not found in the {:?}", + call_num_str, call_map + ) + }) + .clone() + }) +} + +fn map_service_closure(map: HashMap) -> CallServiceClosure { + Box::new(move |args| { + let key = args + .arguments + .get(0) + .expect("At least one arugment expected"); + // Strings are looked up by value, other objects -- by string representation. + // + // For example, `"key"` is looked up as `"key"`, `5` is looked up as `"5"`, `["test"]` is looked up + // as `"[\"test\"]"`. + let key_repr = match key { + serde_json::Value::String(s) => Cow::Borrowed(s.as_str()), + val => Cow::Owned(val.to_string()), + }; + CallServiceResult::ok(json!(map.get(key_repr.as_ref()).cloned())) + }) +} + impl ResultService { pub(crate) fn new(results: HashMap) -> Result { Ok(Self { diff --git a/crates/testing-framework/src/transform/mod.rs b/crates/testing-framework/src/transform/mod.rs index 448a4dcf..63b27320 100644 --- a/crates/testing-framework/src/transform/mod.rs +++ b/crates/testing-framework/src/transform/mod.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -mod parser; +pub(crate) mod parser; pub(crate) mod walker; use crate::asserts::ServiceDefinition; diff --git a/crates/testing-framework/src/transform/parser.rs b/crates/testing-framework/src/transform/parser.rs index 7e73d2df..5b51457b 100644 --- a/crates/testing-framework/src/transform/parser.rs +++ b/crates/testing-framework/src/transform/parser.rs @@ -15,16 +15,16 @@ */ use super::{Call, Sexp, Triplet}; -use crate::asserts::{parser::delim_ws, ServiceDefinition}; +use crate::asserts::ServiceDefinition; use nom::branch::alt; use nom::bytes::complete::{is_not, tag}; use nom::character::complete::{alphanumeric1, multispace0, multispace1, one_of, space1}; use nom::combinator::{cut, map, map_res, opt, recognize, value}; use nom::error::{context, VerboseError, VerboseErrorKind}; -use nom::multi::{many1_count, separated_list0}; +use nom::multi::{many0, many1, many1_count, separated_list0}; use nom::sequence::{delimited, pair, preceded, separated_pair, terminated}; -use nom::IResult; +use nom::{IResult, InputTakeAtPosition}; use nom_locate::LocatedSpan; use std::str::FromStr; @@ -87,11 +87,11 @@ fn parse_sexp_list(inp: Input<'_>) -> IResult, Sexp, ParseError<'_>> { context( "within generic list", preceded( - terminated(tag("("), multispace0), + terminated(tag("("), sexp_multispace0), cut(terminated( - map(separated_list0(multispace1, parse_sexp), Sexp::list), + map(separated_list0(sexp_multispace1, parse_sexp), Sexp::list), preceded( - multispace0, + sexp_multispace0, context("closing parentheses not found", tag(")")), ), )), @@ -150,12 +150,12 @@ fn parse_sexp_call_content(inp: Input<'_>) -> IResult, Sexp, ParseErro // possible variable, closing ")", possible annotation pair( terminated( - opt(preceded(multispace1, map(parse_sexp_symbol, Box::new))), - preceded(multispace0, tag(")")), + opt(preceded(sexp_multispace1, map(parse_sexp_symbol, Box::new))), + preceded(sexp_multispace0, tag(")")), ), alt(( opt(preceded(pair(space1, tag("; ")), parse_annotation)), - value(None, multispace0), + value(None, sexp_multispace0), )), ), ), @@ -183,12 +183,12 @@ fn parse_sexp_call_triplet(inp: Input<'_>) -> IResult, Box, P map( separated_pair( context("triplet peer_id", parse_sexp), - multispace0, + sexp_multispace0, delimited( delim_ws(tag("(")), separated_pair( context("triplet service name", parse_sexp_string), - multispace0, + sexp_multispace0, context("triplet function name", parse_sexp), ), delim_ws(tag(")")), @@ -202,13 +202,124 @@ fn parse_sexp_call_arguments(inp: Input<'_>) -> IResult, Vec, Pa delimited(tag("["), separated_list0(multispace1, parse_sexp), tag("]"))(inp) } +pub(crate) fn delim_ws(f: F) -> impl FnMut(I) -> IResult +where + F: nom::Parser, + E: nom::error::ParseError, + I: nom::InputTakeAtPosition + + nom::InputLength + + for<'a> nom::Compare<&'a str> + + nom::InputTake + + Clone, + ::Item: nom::AsChar + Clone, + for<'a> &'a str: nom::FindToken<::Item>, +{ + delimited(sexp_multispace0, f, sexp_multispace0) +} + +pub(crate) fn sexp_multispace0(inp: I) -> IResult +where + E: nom::error::ParseError, + I: InputTakeAtPosition + + nom::InputLength + + for<'a> nom::Compare<&'a str> + + nom::InputTake + + Clone, + ::Item: nom::AsChar + Clone, + for<'a> &'a str: nom::FindToken<::Item>, +{ + map( + opt(many0(pair( + // white space + multispace1, + // possible ;;, ;;; comment + opt(pair(tag(";;"), is_not("\r\n"))), + ))), + |_| (), + )(inp) +} + +pub(crate) fn sexp_multispace1(inp: Input<'_>) -> IResult, (), ParseError<'_>> { + map( + // It's not the fastest implementation, but easiest to write. + // It passes initial whitespace two times. + many1(alt(( + map( + pair( + // white space + multispace0, + // ;;, ;;;, etc comment + pair(tag(";;"), is_not("\r\n")), + ), + |_| (), + ), + map(multispace1, |_| ()), + ))), + |_| (), + )(inp) +} + #[cfg(test)] mod tests { + use super::*; + use crate::asserts::ServiceDefinition; + + use pretty_assertions::assert_eq; use serde_json::json; - use super::*; + #[test] + fn test_multispace0_empty() { + let res = sexp_multispace0::<_, ()>(""); + assert!(res.is_ok(), "{}", res.unwrap_err()); + } - use crate::asserts::ServiceDefinition; + #[test] + fn test_multispace0_spaces() { + let res = sexp_multispace0::<_, ()>(" "); + assert!(res.is_ok(), "{}", res.unwrap_err()); + } + + #[test] + fn test_multispace0_comment() { + let res = sexp_multispace0::<_, ()>(";; this is comment"); + assert!(res.is_ok(), "{}", res.unwrap_err()); + } + + #[test] + fn test_multispace0_comment_with_space() { + let res = sexp_multispace0::<_, ()>(" ;; "); + assert!(res.is_ok(), "{}", res.unwrap_err()); + } + + #[test] + fn test_multispace0_multiline() { + let res = sexp_multispace0::<_, ()>(" ;; \n ;;;; \n "); + assert!(res.is_ok(), "{}", res.unwrap_err()); + } + + #[test] + fn test_multispace1_empty() { + let res = sexp_multispace1("".into()); + assert!(res.is_err()); + } + + #[test] + fn test_multispace1_space() { + let res = sexp_multispace1(" ".into()); + assert!(res.is_ok(), "{}", res.unwrap_err()); + } + + #[test] + fn test_multispace1_comment() { + let res = sexp_multispace1(" ;; ".into()); + assert!(res.is_ok(), "{}", res.unwrap_err()); + } + + #[test] + fn test_multispace1_multiline() { + let res = sexp_multispace1(" ;; \n ;;;; \n ".into()); + assert!(res.is_ok(), "{}", res.unwrap_err()); + } #[test] fn test_symbol() { @@ -516,4 +627,37 @@ mod tests { let res = Sexp::from_str(sexp_str); assert!(res.is_ok(), "{:?}", res); } + + #[test] + fn test_comments() { + let sexp_str = r#" ;; One comment +( ;;; Second comment + ;; The third one + (par ;;;; Comment comment comment + ;;;; Comment comment comment + (null) ;;;;; Comment + (fail ;; Fails + 1 ;; Retcode + "test" ;; Message + ;; Nothing more + ) + ) ;;;;; Comment + ;;;;; Comment + ) ;;;;; Comment +;;; Comment +"#; + let res = Sexp::from_str(sexp_str); + assert_eq!( + res, + Ok(Sexp::list(vec![Sexp::list(vec![ + Sexp::symbol("par"), + Sexp::list(vec![Sexp::symbol("null"),]), + Sexp::list(vec![ + Sexp::symbol("fail"), + Sexp::symbol("1"), + Sexp::string("test"), + ]), + ])])) + ); + } }