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..18e7b1e 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_ex_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 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 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_exat_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_exat_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());
+ }
}
diff --git a/tests/integration.rs b/tests/integration.rs
index d509f78..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")
@@ -63,25 +94,25 @@ async fn test_set_and_get() {
.await;
}
+#[tokio::test]
+#[serial]
+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");
+
+ 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() {
- type Response = (
- Value,
- Value,
- Value,
- Value,
- Value,
- Value,
- Value,
- Value,
- Value,
- Value,
- Value,
- Value,
- );
-
- test_compare::(|p| {
+ 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 +134,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 +148,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 +165,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");
@@ -148,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;
}
@@ -161,9 +189,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");
@@ -171,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")
@@ -184,19 +213,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");
@@ -206,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;
}
@@ -219,9 +239,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");
@@ -229,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;
}
@@ -242,19 +263,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 +282,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 +298,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 +317,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| {