From 586e3302befb2c7608f2cc0a76b46f67eab407b2 Mon Sep 17 00:00:00 2001 From: gillchristian Date: Fri, 9 Aug 2024 10:40:26 +0200 Subject: [PATCH 1/4] Parse set arguments --- src/commands/mod.rs | 29 +++- src/commands/set.rs | 375 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 395 insertions(+), 9 deletions(-) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 7850078..39303ae 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -117,6 +117,7 @@ pub enum Command { impl Executable for Command { fn exec(self, store: Store) -> Result { + // TODO: can we use a macro for this patter matching ??? match self { Command::Append(cmd) => cmd.exec(store), Command::Client(cmd) => cmd.exec(store), @@ -171,6 +172,7 @@ impl TryFrom for Command { } }; + // TODO: should we pass the frame directly ? let parser = &mut CommandParser { parts: frames.into_iter(), }; @@ -219,7 +221,9 @@ impl TryFrom for Command { } } +// TODO: can we use Impl ??? Or Iterator struct CommandParser { + // parts: dyn Iterator, parts: vec::IntoIter, } @@ -339,6 +343,11 @@ impl CommandParser { }), } } + + // TODO: !!! + fn has_more(&mut self) -> bool { + self.parts.clone().peekable().peek().is_some() + } } #[derive(Debug, ThisError, PartialEq)] @@ -407,7 +416,10 @@ mod tests { set_command, Command::Set(Set { key: String::from("foo"), - value: Bytes::from("baz") + value: Bytes::from("baz"), + ttl: None, + behavior: None, + get: false }) ); @@ -423,7 +435,10 @@ mod tests { set_command, Command::Set(Set { key: String::from("foo"), - value: Bytes::from("baz") + value: Bytes::from("baz"), + ttl: None, + behavior: None, + get: false }) ); @@ -439,7 +454,10 @@ mod tests { set_command, Command::Set(Set { key: String::from("foo"), - value: Bytes::from("baz") + value: Bytes::from("baz"), + ttl: None, + behavior: None, + get: false }) ); @@ -455,7 +473,10 @@ mod tests { set_command, Command::Set(Set { key: String::from("foo"), - value: Bytes::from("baz") + value: Bytes::from("baz"), + ttl: None, + behavior: None, + get: false }) ); } diff --git a/src/commands/set.rs b/src/commands/set.rs index dae3392..178e9cc 100644 --- a/src/commands/set.rs +++ b/src/commands/set.rs @@ -1,7 +1,7 @@ use bytes::Bytes; use crate::commands::executable::Executable; -use crate::commands::CommandParser; +use crate::commands::{CommandParser, CommandParserError}; use crate::frame::Frame; use crate::store::Store; use crate::Error; @@ -13,15 +13,47 @@ use crate::Error; pub struct Set { pub key: String, pub value: Bytes, + + pub ttl: Option, + pub behavior: Option, + pub get: bool, +} + +#[derive(Debug, PartialEq)] +pub enum SetBehavior { + Nx, // Only set the key if it does not already exist. + Xx, // Only set the key if it already exists. +} + +#[derive(Debug, PartialEq)] +pub enum Ttl { + Ex(u64), + Px(u64), + ExAt(u64), + PxAt(u64), + KeepTtl, // Retain the time to live associated with the key. } impl Executable for Set { fn exec(self, store: Store) -> Result { let mut store = store.lock(); + let value = store.get(&self.key); + + match self.behavior { + Some(SetBehavior::Nx) if value.is_some() => return Ok(Frame::NullBulkString), + Some(SetBehavior::Xx) if value.is_none() => return Ok(Frame::NullBulkString), + _ => {} + } + store.set(self.key, self.value); - let res = Frame::Simple("OK".to_string()); + let res = if self.get { + value.map_or(Frame::NullBulkString, Frame::Bulk) + } else { + Frame::Simple("OK".to_string()) + }; + Ok(res) } } @@ -33,7 +65,66 @@ impl TryFrom<&mut CommandParser> for Set { let key = parser.next_string()?; let value = parser.next_bytes()?; - Ok(Self { key, value }) + let mut ttl = None; + let mut behavior = None; + let mut get = false; + + while parser.has_more() { + let opt = parser.next_string()?; + + match opt.as_str() { + // TTL options + "EX" if ttl.is_none() => { + let val = parser.next_integer()?; + ttl = Some(Ttl::Ex(val as u64)); + } + "PX" if ttl.is_none() => { + let val = parser.next_integer()?; + ttl = Some(Ttl::Px(val as u64)); + } + "EXAT" if ttl.is_none() => { + let val = parser.next_integer()?; + ttl = Some(Ttl::ExAt(val as u64)); + } + "PXAT" if ttl.is_none() => { + let val = parser.next_integer()?; + ttl = Some(Ttl::PxAt(val as u64)); + } + "KEEPTTL" if ttl.is_none() => { + ttl = Some(Ttl::KeepTtl); + } + + // Behavior options + "NX" if behavior.is_none() => { + behavior = Some(SetBehavior::Nx); + } + "XX" if behavior.is_none() => { + behavior = Some(SetBehavior::Xx); + } + + // Get option + "GET" => { + get = true; + } + + // Unexpected option + _ => { + return Err(CommandParserError::InvalidCommandArgument { + command: "SET".to_string(), + argument: opt, + } + .into()) + } + } + } + + Ok(Self { + key, + value, + ttl, + behavior, + get, + }) } } @@ -59,7 +150,10 @@ mod tests { cmd, Command::Set(Set { key: String::from("key1"), - value: Bytes::from("1") + value: Bytes::from("1"), + ttl: None, + behavior: None, + get: false }) ); @@ -84,7 +178,10 @@ mod tests { cmd, Command::Set(Set { key: String::from("key1"), - value: Bytes::from("2") + value: Bytes::from("2"), + ttl: None, + behavior: None, + get: false }) ); @@ -97,4 +194,272 @@ mod tests { assert_eq!(res, Frame::Simple("OK".to_string())); assert_eq!(store.lock().get("key1"), Some(Bytes::from("2"))); } + + #[tokio::test] + async fn ttl_exat_and_xx_behavior() { + let store = Store::new(); + + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("EX")), + Frame::Bulk(Bytes::from("10")), + Frame::Bulk(Bytes::from("XX")), + ]); + let cmd = Command::try_from(frame).unwrap(); + + assert_eq!( + cmd, + Command::Set(Set { + key: String::from("key1"), + value: Bytes::from("3"), + ttl: Some(Ttl::Ex(10)), + behavior: Some(SetBehavior::Xx), + get: false + }) + ); + + let res = cmd.exec(store.clone()).unwrap(); + + assert_eq!(res, Frame::NullBulkString); + assert_eq!(store.lock().get("key1"), None); + } + + #[tokio::test] + async fn ttl_xx_behavior() { + let store = Store::new(); + + store.lock().set(String::from("key1"), Bytes::from("1")); + + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("XX")), + ]); + let cmd = Command::try_from(frame).unwrap(); + + assert_eq!( + cmd, + Command::Set(Set { + key: String::from("key1"), + value: Bytes::from("3"), + ttl: None, + behavior: Some(SetBehavior::Xx), + get: false + }) + ); + + let res = cmd.exec(store.clone()).unwrap(); + + assert_eq!(res, Frame::Simple("OK".to_string())); + assert_eq!(store.lock().get("key1"), Some(Bytes::from("3"))); + } + + #[tokio::test] + async fn ttl_nx_behavior() { + let store = Store::new(); + + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("NX")), + ]); + let cmd = Command::try_from(frame).unwrap(); + + assert_eq!( + cmd, + Command::Set(Set { + key: String::from("key1"), + value: Bytes::from("3"), + ttl: None, + behavior: Some(SetBehavior::Nx), + get: false + }) + ); + + let res = cmd.exec(store.clone()).unwrap(); + + assert_eq!(res, Frame::Simple("OK".to_string())); + assert_eq!(store.lock().get("key1"), Some(Bytes::from("3"))); + } + + #[tokio::test] + async fn ttl_ex_and_nx_behavior() { + let store = Store::new(); + + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("EXAT")), + Frame::Bulk(Bytes::from("10")), + Frame::Bulk(Bytes::from("NX")), + ]); + let cmd = Command::try_from(frame).unwrap(); + + assert_eq!( + cmd, + Command::Set(Set { + key: String::from("key1"), + value: Bytes::from("3"), + ttl: Some(Ttl::ExAt(10)), + behavior: Some(SetBehavior::Nx), + get: false + }) + ); + + let res = cmd.exec(store.clone()).unwrap(); + + assert_eq!(res, Frame::Simple("OK".to_string())); + assert_eq!(store.lock().get("key1"), Some(Bytes::from("3"))); + } + + #[tokio::test] + async fn ttl_ex_and_nx_behavior_and_get_order_swapped() { + let store = Store::new(); + + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("GET")), + Frame::Bulk(Bytes::from("NX")), + Frame::Bulk(Bytes::from("EXAT")), + Frame::Bulk(Bytes::from("10")), + ]); + let cmd = Command::try_from(frame).unwrap(); + + assert_eq!( + cmd, + Command::Set(Set { + key: String::from("key1"), + value: Bytes::from("3"), + ttl: Some(Ttl::ExAt(10)), + behavior: Some(SetBehavior::Nx), + get: true + }) + ); + + let res = cmd.exec(store.clone()).unwrap(); + + assert_eq!(res, Frame::NullBulkString); + assert_eq!(store.lock().get("key1"), Some(Bytes::from("3"))); + } + + #[tokio::test] + async fn with_get() { + let store = Store::new(); + + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("GET")), + ]); + let cmd = Command::try_from(frame).unwrap(); + + assert_eq!( + cmd, + Command::Set(Set { + key: String::from("key1"), + value: Bytes::from("3"), + ttl: None, + behavior: None, + get: true + }) + ); + + let res = cmd.exec(store.clone()).unwrap(); + + assert_eq!(res, Frame::NullBulkString); + assert_eq!(store.lock().get("key1"), Some(Bytes::from("3"))); + } + + #[tokio::test] + async fn keepttl() { + let store = Store::new(); + + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("KEEPTTL")), + ]); + let cmd = Command::try_from(frame).unwrap(); + + assert_eq!( + cmd, + Command::Set(Set { + key: String::from("key1"), + value: Bytes::from("3"), + ttl: Some(Ttl::KeepTtl), + behavior: None, + get: false + }) + ); + + let res = cmd.exec(store.clone()).unwrap(); + + assert_eq!(res, Frame::Simple("OK".to_string())); + assert_eq!(store.lock().get("key1"), Some(Bytes::from("3"))); + } + + #[tokio::test] + async fn missing_ttl_argument() { + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("EX")), + ]); + let res = Command::try_from(frame); + + assert!(res.is_err()); + } + + #[tokio::test] + async fn repeated_behavior_options() { + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("NX")), + Frame::Bulk(Bytes::from("XX")), + ]); + let res = Command::try_from(frame); + + assert!(res.is_err()); + } + + #[tokio::test] + async fn repeated_ttl_options() { + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("EX")), + Frame::Bulk(Bytes::from("10")), + Frame::Bulk(Bytes::from("PX")), + Frame::Bulk(Bytes::from("10")), + ]); + let res = Command::try_from(frame); + + assert!(res.is_err()); + } + + #[tokio::test] + async fn invalid_command() { + let frame = Frame::Array(vec![ + Frame::Bulk(Bytes::from("SET")), + Frame::Bulk(Bytes::from("key1")), + Frame::Bulk(Bytes::from("3")), + Frame::Bulk(Bytes::from("INVALID")), + ]); + let res = Command::try_from(frame); + + assert!(res.is_err()); + } } From c3a4bcba9e10412fbb8f3e8b87e6ae66e269dc75 Mon Sep 17 00:00:00 2001 From: gillchristian Date: Fri, 23 Aug 2024 09:38:37 +0200 Subject: [PATCH 2/4] Fix test names --- src/commands/set.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commands/set.rs b/src/commands/set.rs index 178e9cc..18e7b1e 100644 --- a/src/commands/set.rs +++ b/src/commands/set.rs @@ -196,7 +196,7 @@ mod tests { } #[tokio::test] - async fn ttl_exat_and_xx_behavior() { + async fn ttl_ex_and_xx_behavior() { let store = Store::new(); let frame = Frame::Array(vec![ @@ -227,7 +227,7 @@ mod tests { } #[tokio::test] - async fn ttl_xx_behavior() { + async fn xx_behavior() { let store = Store::new(); store.lock().set(String::from("key1"), Bytes::from("1")); @@ -258,7 +258,7 @@ mod tests { } #[tokio::test] - async fn ttl_nx_behavior() { + async fn nx_behavior() { let store = Store::new(); let frame = Frame::Array(vec![ @@ -287,7 +287,7 @@ mod tests { } #[tokio::test] - async fn ttl_ex_and_nx_behavior() { + async fn ttl_exat_and_nx_behavior() { let store = Store::new(); let frame = Frame::Array(vec![ @@ -318,7 +318,7 @@ mod tests { } #[tokio::test] - async fn ttl_ex_and_nx_behavior_and_get_order_swapped() { + async fn ttl_exat_and_nx_behavior_and_get_order_swapped() { let store = Store::new(); let frame = Frame::Array(vec![ From b82a531c078d22161154318081732819b620e6c7 Mon Sep 17 00:00:00 2001 From: gillchristian Date: Fri, 23 Aug 2024 10:05:21 +0200 Subject: [PATCH 3/4] Use generic response --- tests/integration.rs | 101 +++++++++++-------------------------------- 1 file changed, 25 insertions(+), 76 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index d509f78..5d90860 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -65,23 +65,23 @@ async fn test_set_and_get() { #[tokio::test] #[serial] -async fn test_del() { - type Response = ( - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - ); +async fn test_set_args() { + test_compare::>(|p| { + p.cmd("SET").arg("set_args_key_1").arg(1).arg("XX"); + p.cmd("SET").arg("set_args_key_1").arg(2).arg("NX"); + p.cmd("SET").arg("set_args_key_1").arg(3).arg("XX"); + p.cmd("GET").arg("set_args_key_1"); - test_compare::(|p| { + p.cmd("SET").arg("set_args_key_2").arg(1).arg("GET"); + p.cmd("SET").arg("set_args_key_2").arg(2).arg("GET"); + }) + .await; +} + +#[tokio::test] +#[serial] +async fn test_del() { + test_compare::>(|p| { p.cmd("SET").arg("del_key_1").arg(1); p.cmd("SET").arg("del_key_2").arg("Argentina"); p.cmd("SET").arg("del_key_3").arg("Thailand"); @@ -103,9 +103,7 @@ async fn test_del() { #[tokio::test] #[serial] async fn test_exists() { - type Response = (Value, Value, Value, Value, Value); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("SET").arg("exists_key_1").arg(1); p.cmd("SET").arg("exists_key_2").arg("Argentina"); @@ -119,9 +117,7 @@ async fn test_exists() { #[tokio::test] #[serial] async fn test_incr() { - type Response = (Value, Value, Value, Value, Value, Value, Value); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("SET").arg("incr_key_1").arg(1); p.cmd("SET").arg("incr_key_2").arg(1); p.cmd("SET").arg("incr_key_3").arg("1"); @@ -138,9 +134,7 @@ async fn test_incr() { #[tokio::test] #[serial] async fn test_incr_by() { - type Response = (Value, Value, Value, Value, Value, Value); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("SET").arg("incr_by_key_1").arg(2); p.cmd("SET").arg("incr_by_key_2").arg(10); p.cmd("SET").arg("incr_by_key_3").arg("2"); @@ -161,9 +155,7 @@ async fn test_incr_by() { #[tokio::test] #[serial] async fn test_incr_by_float() { - type Response = (Value, Value, Value, Value, Value, Value, Value); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("SET").arg("incr_by_float_key_1").arg("10.50"); p.cmd("SET").arg("incr_by_float_key_2").arg(4); p.cmd("SET").arg("incr_by_float_key_3").arg("2.2"); @@ -184,19 +176,7 @@ async fn test_incr_by_float() { #[tokio::test] #[serial] async fn test_decr() { - type Response = ( - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - ); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("SET").arg("decr_key_1").arg(2); p.cmd("SET").arg("decr_key_2").arg(2); p.cmd("SET").arg("decr_key_3").arg("2"); @@ -219,9 +199,7 @@ async fn test_decr() { #[tokio::test] #[serial] async fn test_decr_by() { - type Response = (Value, Value, Value, Value, Value, Value); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("SET").arg("decr_by_key_1").arg(2); p.cmd("SET").arg("decr_by_key_2").arg(10); p.cmd("SET").arg("decr_by_key_3").arg("2"); @@ -242,19 +220,7 @@ async fn test_decr_by() { #[tokio::test] #[serial] async fn test_append() { - type Response = ( - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - ); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("APPEND").arg("append_key_1").arg("hello"); p.cmd("APPEND").arg("append_key_1").arg(" World"); p.cmd("GET").arg("append_key_1"); @@ -273,9 +239,7 @@ async fn test_append() { #[tokio::test] #[serial] async fn test_getdel() { - type Response = (Value, Value, Value, Value, Value, Value); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("SET").arg("getdel_key_1").arg(2); p.cmd("SET").arg("getdel_key_2").arg("2"); @@ -291,20 +255,7 @@ async fn test_getdel() { #[tokio::test] #[serial] async fn test_getrange() { - type Response = ( - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - Value, - ); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("SET").arg("getrange_key_1").arg("This is a string"); p.cmd("GETRANGE").arg("getrange_key_1").arg(0).arg(0); p.cmd("GETRANGE").arg("getrange_key_1").arg(0).arg(3); @@ -323,8 +274,6 @@ async fn test_getrange() { #[tokio::test] #[serial] async fn test_keys() { - type Response = (Value, Value, Value, Value, Value, Value); - // TODO: The response order from the server is not guaranteed, to ensure accurate comparison // with the expected result, we need to sort the response before performing the comparison. test_compare::>(|p| { From 03d37277c375df3a473160237617f622252f800b Mon Sep 17 00:00:00 2001 From: gillchristian Date: Fri, 23 Aug 2024 10:28:48 +0200 Subject: [PATCH 4/4] Add error checking in integrations --- tests/integration.rs | 73 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 5d90860..123c663 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -40,15 +40,46 @@ where .unwrap(); let their_response: Result = pipeline.clone().query_async(&mut their_connection).await; + + assert!(our_response.is_ok(), "Not Ok, use `test_compare_err` instead if expecting an error"); + assert!(their_response.is_ok(), "Not Ok, use `test_compare_err` instead if expecting an error"); + assert_eq!(our_response, their_response); +} + +/// When the server responds with an error, the client parses it into `Err(RedisError)`, +/// ignoring all the other values from previous commands in the pipeline. +/// +/// Thus, when testing errors, we want to run the least number of commands in the pipeline, +/// because their outputs will be ignored. +async fn test_compare_err(f: impl FnOnce(&mut redis::Pipeline)) { + let (mut our_connection, mut their_connection) = connect().await.unwrap(); + + let mut pipeline = redis::pipe(); + f(&mut pipeline); + + type Res = Result<(), RedisError>; + + let our_response: Res = pipeline.clone().query_async(&mut our_connection).await; + + // Since we use the same Redis instance for all tests, we flush it to start fresh. + // NOTE: our implementation doesn't yet persist data between runs. + let _: Value = redis::pipe() + .cmd("FLUSHDB") + .query_async(&mut their_connection) + .await + .unwrap(); + + let their_response: Res = pipeline.clone().query_async(&mut their_connection).await; + + assert!(our_response.is_err(), "Not Err, use `test_compare` instead if expecting a value"); + assert!(their_response.is_err(), "Not Err, use `test_compare` instead if expecting a value"); assert_eq!(our_response, their_response); } #[tokio::test] #[serial] async fn test_set_and_get() { - type Response = (Value, Value, Value, Value, Value, Value, Value); - - test_compare::(|p| { + test_compare::>(|p| { p.cmd("SET").arg("set_get_key_1").arg(1); p.cmd("SET").arg("set_get_key_2").arg("Argentina"); p.cmd("SET") @@ -142,12 +173,15 @@ async fn test_incr_by() { p.cmd("INCRBY").arg("incr_by_key_1").arg(10); p.cmd("INCRBY").arg("incr_by_key_2").arg("7"); p.cmd("INCRBY").arg("incr_by_key_3").arg(-2); + }) + .await; + test_compare_err(|p| { // Value is not an integer or out of range error. - // p.cmd("SET") - // .arg("incr_by_key_4") - // .arg("234293482390480948029348230948"); - // p.cmd("INCRBY").arg("incr_by_key_4").arg(1); + p.cmd("SET") + .arg("incr_by_key_4") + .arg("234293482390480948029348230948"); + p.cmd("INCRBY").arg("incr_by_key_4").arg(1); }) .await; } @@ -163,7 +197,10 @@ async fn test_incr_by_float() { p.cmd("INCRBYFLOAT").arg("incr_by_float_key_1").arg("0.1"); p.cmd("INCRBYFLOAT").arg("incr_by_float_key_2").arg("-5"); p.cmd("INCRBYFLOAT").arg("incr_by_float_key_3").arg("-1.2"); + }) + .await; + test_compare_err(|p| { // Value is not an integer or out of range error. p.cmd("SET") .arg("incr_by_float_key_4") @@ -186,12 +223,15 @@ async fn test_decr() { p.cmd("DECR").arg("decr_key_3"); p.cmd("DECR").arg("decr_key_4"); + }) + .await; + test_compare_err(|p| { // Value is not an integer or out of range error. - // p.cmd("SET") - // .arg("decr_key_5") - // .arg("234293482390480948029348230948"); - // p.cmd("DECR").arg("decr_key_5"); + p.cmd("SET") + .arg("decr_key_5") + .arg("234293482390480948029348230948"); + p.cmd("DECR").arg("decr_key_5"); }) .await; } @@ -207,12 +247,15 @@ async fn test_decr_by() { p.cmd("DECRBY").arg("decr_by_key_1").arg(10); p.cmd("DECRBY").arg("decr_by_key_2").arg("7"); p.cmd("DECRBY").arg("decr_by_key_3").arg(2); + }) + .await; + test_compare_err(|p| { // Value is not an integer or out of range error. - // p.cmd("SET") - // .arg("decr_by_key_4") - // .arg("234293482390480948029348230948"); - // p.cmd("DECRBY").arg("decr_by_key_4").arg(1); + p.cmd("SET") + .arg("decr_by_key_4") + .arg("234293482390480948029348230948"); + p.cmd("DECRBY").arg("decr_by_key_4").arg(1); }) .await; }