Repository: aisk/rust-memcache (language: rust)

Issue:
Support CAS commands



Discussion / hints:

Fix patch:
diff --git a/src/client.rs b/src/client.rs
index 5e953ed..15fdc35 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -9,7 +9,7 @@ use connection::Connection;
 use error::MemcacheError;
 use protocol::{Protocol, ProtocolTrait};
 use stream::Stream;
-use value::{FromMemcacheValue, ToMemcacheValue};
+use value::{FromMemcacheValueExt, ToMemcacheValue};
 
 pub type Stats = HashMap<String, String>;
 
@@ -182,7 +182,7 @@ impl Client {
     /// let mut client = memcache::Client::connect("memcache://localhost:12345").unwrap();
     /// let _: Option<String> = client.get("foo").unwrap();
     /// ```
-    pub fn get<V: FromMemcacheValue>(&mut self, key: &str) -> Result<Option<V>, MemcacheError> {
+    pub fn get<V: FromMemcacheValueExt>(&mut self, key: &str) -> Result<Option<V>, MemcacheError> {
         return self.get_connection(key).protocol.get(key);
     }
 
@@ -197,7 +197,7 @@ impl Client {
     /// assert_eq!(result.len(), 1);
     /// assert_eq!(result["foo"], "42");
     /// ```
-    pub fn gets<V: FromMemcacheValue>(&mut self, keys: Vec<&str>) -> Result<HashMap<String, V>, MemcacheError> {
+    pub fn gets<V: FromMemcacheValueExt>(&mut self, keys: Vec<&str>) -> Result<HashMap<String, V>, MemcacheError> {
         let mut con_keys: HashMap<usize, Vec<&str>> = HashMap::new();
         let mut result: HashMap<String, V> = HashMap::new();
         let connections_count = self.connections.len();
@@ -231,6 +231,30 @@ impl Client {
         return self.get_connection(key).protocol.set(key, value, expiration);
     }
 
+    /// Compare and swap a key with the associate value into memcached server with expiration seconds.
+    /// `cas_id` should be obtained from a previous `gets` call.
+    ///
+    /// Example:
+    ///
+    /// ```rust
+    /// use std::collections::HashMap;
+    /// let mut client = memcache::Client::connect("memcache://localhost:12345").unwrap();
+    /// client.set("foo", "bar", 10).unwrap();
+    /// let result: HashMap<String, (Vec<u8>, u32, Option<u64>)> = client.gets(vec!["foo"]).unwrap();
+    /// let (_, _, cas) = result.get("foo").unwrap();
+    /// let cas = cas.unwrap();
+    /// assert_eq!(true, client.cas("foo", "bar2", 10, cas).unwrap());
+    /// ```
+    pub fn cas<V: ToMemcacheValue<Stream>>(
+        &mut self,
+        key: &str,
+        value: V,
+        expiration: u32,
+        cas_id: u64,
+    ) -> Result<bool, MemcacheError> {
+        self.get_connection(key).protocol.cas(key, value, expiration, cas_id)
+    }
+
     /// Add a key with associate value into memcached server with expiration seconds.
     ///
     /// Example:
diff --git a/src/lib.rs b/src/lib.rs
index c055d7c..29359a9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -80,4 +80,4 @@ mod value;
 
 pub use client::{Client, Connectable};
 pub use error::MemcacheError;
-pub use value::{FromMemcacheValue, ToMemcacheValue};
+pub use value::{FromMemcacheValue, FromMemcacheValueExt, ToMemcacheValue};
diff --git a/src/protocol/ascii.rs b/src/protocol/ascii.rs
index fbaf38c..cd62157 100644
--- a/src/protocol/ascii.rs
+++ b/src/protocol/ascii.rs
@@ -5,16 +5,19 @@ use std::io::{BufRead, BufReader, Read, Write};
 use client::Stats;
 use error::MemcacheError;
 use stream::Stream;
-use value::{FromMemcacheValue, ToMemcacheValue};
+use value::{FromMemcacheValueExt, ToMemcacheValue};
 
 #[derive(Default)]
 pub struct Options {
     pub noreply: bool,
     pub exptime: u32,
     pub flags: u32,
+    pub cas: Option<u64>,
 }
 
+#[derive(PartialEq)]
 enum StoreCommand {
+    Cas,
     Set,
     Add,
     Replace,
@@ -30,6 +33,7 @@ impl fmt::Display for StoreCommand {
             StoreCommand::Replace => write!(f, "replace"),
             StoreCommand::Append => write!(f, "append"),
             StoreCommand::Prepend => write!(f, "prepend"),
+            StoreCommand::Cas => write!(f, "cas"),
         }
     }
 }
@@ -48,7 +52,7 @@ impl AsciiProtocol<Stream> {
         key: &str,
         value: V,
         options: &Options,
-    ) -> Result<(), MemcacheError> {
+    ) -> Result<bool, MemcacheError> {
         if key.len() > 250 {
             return Err(MemcacheError::ClientError(String::from("key is too long")));
         }
@@ -61,6 +65,15 @@ impl AsciiProtocol<Stream> {
             options.exptime,
             value.get_length()
         );
+        if command == StoreCommand::Cas {
+            if options.cas.is_none() {
+                return Err(MemcacheError::ClientError(String::from(
+                    "cas command should have a casid",
+                )));
+            }
+            let cas = options.cas.unwrap();
+            header += &format!(" {}", cas);
+        }
         if options.noreply {
             header += " noreply";
         }
@@ -71,7 +84,7 @@ impl AsciiProtocol<Stream> {
         self.reader.get_mut().flush()?;
 
         if options.noreply {
-            return Ok(());
+            return Ok(true);
         }
 
         let mut s = String::new();
@@ -79,9 +92,11 @@ impl AsciiProtocol<Stream> {
         if is_memcache_error(s.as_str()) {
             return Err(MemcacheError::from(s));
         } else if s == "STORED\r\n" {
-            return Ok(());
+            return Ok(true);
         } else if s == "NOT_STORED\r\n" {
-            return Ok(());
+            return Ok(false);
+        } else if command == StoreCommand::Cas && (s == "EXISTS\r\n" || s == "NOT_FOUND\r\n") {
+            return Ok(false);
         } else {
             return Err(MemcacheError::ClientError("invalid server response".into()));
         }
@@ -132,7 +147,7 @@ impl AsciiProtocol<Stream> {
         return Ok(());
     }
 
-    pub(super) fn get<V: FromMemcacheValue>(&mut self, key: &str) -> Result<Option<V>, MemcacheError> {
+    pub(super) fn get<V: FromMemcacheValueExt>(&mut self, key: &str) -> Result<Option<V>, MemcacheError> {
         write!(self.reader.get_mut(), "get {}\r\n", key)?;
 
         let mut s = String::new();
@@ -172,10 +187,13 @@ impl AsciiProtocol<Stream> {
             return Err(MemcacheError::ClientError("invalid server response".into()));
         }
 
-        return Ok(Some(FromMemcacheValue::from_memcache_value(buffer, flags)?));
+        return Ok(Some(FromMemcacheValueExt::from_memcache_value(buffer, flags, None)?));
     }
 
-    pub(super) fn gets<V: FromMemcacheValue>(&mut self, keys: Vec<&str>) -> Result<HashMap<String, V>, MemcacheError> {
+    pub(super) fn gets<V: FromMemcacheValueExt>(
+        &mut self,
+        keys: Vec<&str>,
+    ) -> Result<HashMap<String, V>, MemcacheError> {
         write!(self.reader.get_mut(), "gets {}\r\n", keys.join(" "))?;
 
         let mut result: HashMap<String, V> = HashMap::new();
@@ -192,18 +210,22 @@ impl AsciiProtocol<Stream> {
             }
 
             let header: Vec<_> = s.trim_end_matches("\r\n").split(" ").collect();
-            if header.len() != 4 && header.len() != 5 {
+            if header.len() != 5 {
                 return Err(MemcacheError::ClientError("invalid server response".into()));
             }
 
             let key = header[1];
             let flags = header[2].parse()?;
             let length = header[3].parse()?;
+            let cas = header[4].parse()?;
 
             let mut buffer = vec![0; length];
             self.reader.read_exact(buffer.as_mut_slice())?;
 
-            result.insert(key.to_string(), FromMemcacheValue::from_memcache_value(buffer, flags)?);
+            result.insert(
+                key.to_string(),
+                FromMemcacheValueExt::from_memcache_value(buffer, flags, Some(cas))?,
+            );
 
             // read the rest \r\n
             let mut s = String::new();
@@ -216,6 +238,21 @@ impl AsciiProtocol<Stream> {
         return Ok(result);
     }
 
+    pub(super) fn cas<V: ToMemcacheValue<Stream>>(
+        &mut self,
+        key: &str,
+        value: V,
+        expiration: u32,
+        cas: u64,
+    ) -> Result<bool, MemcacheError> {
+        let options = Options {
+            exptime: expiration,
+            cas: Some(cas),
+            ..Default::default()
+        };
+        self.store(StoreCommand::Cas, key, value, &options)
+    }
+
     pub(super) fn set<V: ToMemcacheValue<Stream>>(
         &mut self,
         key: &str,
@@ -226,7 +263,7 @@ impl AsciiProtocol<Stream> {
             exptime: expiration,
             ..Default::default()
         };
-        return self.store(StoreCommand::Set, key, value, &options);
+        self.store(StoreCommand::Set, key, value, &options).map(|_| ())
     }
 
     pub(super) fn add<V: ToMemcacheValue<Stream>>(
@@ -239,7 +276,7 @@ impl AsciiProtocol<Stream> {
             exptime: expiration,
             ..Default::default()
         };
-        return self.store(StoreCommand::Add, key, value, &options);
+        self.store(StoreCommand::Add, key, value, &options).map(|_| ())
     }
 
     pub(super) fn replace<V: ToMemcacheValue<Stream>>(
@@ -252,21 +289,23 @@ impl AsciiProtocol<Stream> {
             exptime: expiration,
             ..Default::default()
         };
-        return self.store(StoreCommand::Replace, key, value, &options);
+        self.store(StoreCommand::Replace, key, value, &options).map(|_| ())
     }
 
     pub(super) fn append<V: ToMemcacheValue<Stream>>(&mut self, key: &str, value: V) -> Result<(), MemcacheError> {
         if key.len() > 250 {
             return Err(MemcacheError::ClientError(String::from("key is too long")));
         }
-        return self.store(StoreCommand::Append, key, value, &Default::default());
+        self.store(StoreCommand::Append, key, value, &Default::default())
+            .map(|_| ())
     }
 
     pub(super) fn prepend<V: ToMemcacheValue<Stream>>(&mut self, key: &str, value: V) -> Result<(), MemcacheError> {
         if key.len() > 250 {
             return Err(MemcacheError::ClientError(String::from("key is too long")));
         }
-        return self.store(StoreCommand::Prepend, key, value, &Default::default());
+        self.store(StoreCommand::Prepend, key, value, &Default::default())
+            .map(|_| ())
     }
 
     pub(super) fn delete(&mut self, key: &str) -> Result<bool, MemcacheError> {
diff --git a/src/protocol/binary.rs b/src/protocol/binary.rs
index 8e06b4e..b44bb2f 100644
--- a/src/protocol/binary.rs
+++ b/src/protocol/binary.rs
@@ -6,7 +6,7 @@ use client::Stats;
 use error::MemcacheError;
 use protocol::binary_packet::{self, Magic, Opcode, PacketHeader};
 use stream::Stream;
-use value::{FromMemcacheValue, ToMemcacheValue};
+use value::{FromMemcacheValueExt, ToMemcacheValue};
 
 pub struct BinaryProtocol {
     pub stream: Stream,
@@ -31,12 +31,13 @@ impl BinaryProtocol {
         return Ok(());
     }
 
-    fn store<V: ToMemcacheValue<Stream>>(
+    fn send_request<V: ToMemcacheValue<Stream>>(
         &mut self,
         opcode: Opcode,
         key: &str,
         value: V,
         expiration: u32,
+        cas: Option<u64>,
     ) -> Result<(), MemcacheError> {
         if key.len() > 250 {
             return Err(MemcacheError::ClientError(String::from("key is too long")));
@@ -47,6 +48,7 @@ impl BinaryProtocol {
             key_length: key.len() as u16,
             extras_length: 8,
             total_body_length: (8 + key.len() + value.get_length()) as u32,
+            cas: cas.unwrap_or(0),
             ..Default::default()
         };
         let extras = binary_packet::StoreExtras {
@@ -58,7 +60,18 @@ impl BinaryProtocol {
         self.stream.write_u32::<BigEndian>(extras.expiration)?;
         self.stream.write_all(key.as_bytes())?;
         value.write_to(&mut self.stream)?;
-        self.stream.flush()?;
+        self.stream.flush().map_err(Into::into)
+    }
+
+    fn store<V: ToMemcacheValue<Stream>>(
+        &mut self,
+        opcode: Opcode,
+        key: &str,
+        value: V,
+        expiration: u32,
+        cas: Option<u64>,
+    ) -> Result<(), MemcacheError> {
+        self.send_request(opcode, key, value, expiration, cas)?;
         return binary_packet::parse_header_only_response(&mut self.stream);
     }
 
@@ -101,7 +114,7 @@ impl BinaryProtocol {
         return Ok(());
     }
 
-    pub(super) fn get<V: FromMemcacheValue>(&mut self, key: &str) -> Result<Option<V>, MemcacheError> {
+    pub(super) fn get<V: FromMemcacheValueExt>(&mut self, key: &str) -> Result<Option<V>, MemcacheError> {
         if key.len() > 250 {
             return Err(MemcacheError::ClientError(String::from("key is too long")));
         }
@@ -118,7 +131,10 @@ impl BinaryProtocol {
         return binary_packet::parse_get_response(&mut self.stream);
     }
 
-    pub(super) fn gets<V: FromMemcacheValue>(&mut self, keys: Vec<&str>) -> Result<HashMap<String, V>, MemcacheError> {
+    pub(super) fn gets<V: FromMemcacheValueExt>(
+        &mut self,
+        keys: Vec<&str>,
+    ) -> Result<HashMap<String, V>, MemcacheError> {
         for key in keys {
             if key.len() > 250 {
                 return Err(MemcacheError::ClientError(String::from("key is too long")));
@@ -142,13 +158,24 @@ impl BinaryProtocol {
         return binary_packet::parse_gets_response(&mut self.stream);
     }
 
+    pub(super) fn cas<V: ToMemcacheValue<Stream>>(
+        &mut self,
+        key: &str,
+        value: V,
+        expiration: u32,
+        cas: u64,
+    ) -> Result<bool, MemcacheError> {
+        self.send_request(Opcode::Set, key, value, expiration, Some(cas))?;
+        binary_packet::parse_cas_response(&mut self.stream)
+    }
+
     pub(super) fn set<V: ToMemcacheValue<Stream>>(
         &mut self,
         key: &str,
         value: V,
         expiration: u32,
     ) -> Result<(), MemcacheError> {
-        return self.store(Opcode::Set, key, value, expiration);
+        return self.store(Opcode::Set, key, value, expiration, None);
     }
 
     pub(super) fn add<V: ToMemcacheValue<Stream>>(
@@ -157,7 +184,7 @@ impl BinaryProtocol {
         value: V,
         expiration: u32,
     ) -> Result<(), MemcacheError> {
-        return self.store(Opcode::Add, key, value, expiration);
+        return self.store(Opcode::Add, key, value, expiration, None);
     }
 
     pub(super) fn replace<V: ToMemcacheValue<Stream>>(
@@ -166,7 +193,7 @@ impl BinaryProtocol {
         value: V,
         expiration: u32,
     ) -> Result<(), MemcacheError> {
-        return self.store(Opcode::Replace, key, value, expiration);
+        return self.store(Opcode::Replace, key, value, expiration, None);
     }
 
     pub(super) fn append<V: ToMemcacheValue<Stream>>(&mut self, key: &str, value: V) -> Result<(), MemcacheError> {
diff --git a/src/protocol/binary_packet.rs b/src/protocol/binary_packet.rs
index cb9aef2..a3f2eb5 100644
--- a/src/protocol/binary_packet.rs
+++ b/src/protocol/binary_packet.rs
@@ -2,7 +2,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
 use error::MemcacheError;
 use std::collections::HashMap;
 use std::io;
-use value::FromMemcacheValue;
+use value::FromMemcacheValueExt;
 
 #[allow(dead_code)]
 pub enum Opcode {
@@ -33,7 +33,7 @@ pub enum Magic {
 pub enum ResponseStatus {
     NoError = 0x00,
     KeyNotFound = 0x01,
-    KeyExits = 0x02,
+    KeyExists = 0x02,
     ValueTooLarge = 0x03,
     InvalidArguments = 0x04,
     AuthenticationRequired = 0x20,
@@ -102,6 +102,22 @@ impl PacketHeader {
     }
 }
 
+pub fn parse_cas_response<R: io::Read>(stream: &mut R) -> Result<bool, MemcacheError> {
+    let header = PacketHeader::read(stream)?;
+    let status = header.vbucket_id_or_status;
+    // In case of KeyExists and KeyNotFound errors, there is also an error string
+    let value_len = header.total_body_length - u32::from(header.extras_length);
+    let mut err_message = vec![0x0; value_len as usize];
+    stream.read_exact(err_message.as_mut_slice())?;
+    if status == ResponseStatus::KeyExists as u16 || status == ResponseStatus::KeyNotFound as u16 {
+        Ok(false)
+    } else if status == ResponseStatus::NoError as u16 {
+        Ok(true)
+    } else {
+        Err(MemcacheError::from(header.vbucket_id_or_status))
+    }
+}
+
 pub fn parse_header_only_response<R: io::Read>(reader: &mut R) -> Result<(), MemcacheError> {
     let header = PacketHeader::read(reader)?;
     if header.vbucket_id_or_status != ResponseStatus::NoError as u16 {
@@ -120,7 +136,7 @@ pub fn parse_version_response<R: io::Read>(reader: &mut R) -> Result<String, Mem
     return Ok(String::from_utf8(buffer)?);
 }
 
-pub fn parse_get_response<R: io::Read, V: FromMemcacheValue>(reader: &mut R) -> Result<Option<V>, MemcacheError> {
+pub fn parse_get_response<R: io::Read, V: FromMemcacheValueExt>(reader: &mut R) -> Result<Option<V>, MemcacheError> {
     let header = PacketHeader::read(reader)?;
     if header.vbucket_id_or_status == ResponseStatus::KeyNotFound as u16 {
         let mut buffer = vec![0; header.total_body_length as usize];
@@ -133,10 +149,14 @@ pub fn parse_get_response<R: io::Read, V: FromMemcacheValue>(reader: &mut R) ->
     let value_length = header.total_body_length - u32::from(header.extras_length);
     let mut buffer = vec![0; value_length as usize];
     reader.read_exact(buffer.as_mut_slice())?;
-    return Ok(Some(FromMemcacheValue::from_memcache_value(buffer, flags)?));
+    return Ok(Some(FromMemcacheValueExt::from_memcache_value(
+        buffer,
+        flags,
+        Some(header.cas),
+    )?));
 }
 
-pub fn parse_gets_response<R: io::Read, V: FromMemcacheValue>(
+pub fn parse_gets_response<R: io::Read, V: FromMemcacheValueExt>(
     reader: &mut R,
 ) -> Result<HashMap<String, V>, MemcacheError> {
     let mut result = HashMap::new();
@@ -156,7 +176,10 @@ pub fn parse_gets_response<R: io::Read, V: FromMemcacheValue>(
         let key = String::from_utf8(key_buffer)?;
         let mut value_buffer = vec![0; value_length as usize];
         reader.read_exact(value_buffer.as_mut_slice())?;
-        result.insert(key, FromMemcacheValue::from_memcache_value(value_buffer, flags)?);
+        result.insert(
+            key,
+            FromMemcacheValueExt::from_memcache_value(value_buffer, flags, Some(header.cas))?,
+        );
     }
     return Ok(result);
 }
diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs
index 268a730..0d68aac 100644
--- a/src/protocol/mod.rs
+++ b/src/protocol/mod.rs
@@ -9,7 +9,7 @@ pub(crate) use protocol::ascii::AsciiProtocol;
 pub(crate) use protocol::binary::BinaryProtocol;
 use std::collections::HashMap;
 use stream::Stream;
-use value::{FromMemcacheValue, ToMemcacheValue};
+use value::{FromMemcacheValueExt, ToMemcacheValue};
 
 #[enum_dispatch]
 pub enum Protocol {
@@ -23,9 +23,16 @@ pub trait ProtocolTrait {
     fn version(&mut self) -> Result<String, MemcacheError>;
     fn flush(&mut self) -> Result<(), MemcacheError>;
     fn flush_with_delay(&mut self, delay: u32) -> Result<(), MemcacheError>;
-    fn get<V: FromMemcacheValue>(&mut self, key: &str) -> Result<Option<V>, MemcacheError>;
-    fn gets<V: FromMemcacheValue>(&mut self, keys: Vec<&str>) -> Result<HashMap<String, V>, MemcacheError>;
+    fn get<V: FromMemcacheValueExt>(&mut self, key: &str) -> Result<Option<V>, MemcacheError>;
+    fn gets<V: FromMemcacheValueExt>(&mut self, keys: Vec<&str>) -> Result<HashMap<String, V>, MemcacheError>;
     fn set<V: ToMemcacheValue<Stream>>(&mut self, key: &str, value: V, expiration: u32) -> Result<(), MemcacheError>;
+    fn cas<V: ToMemcacheValue<Stream>>(
+        &mut self,
+        key: &str,
+        value: V,
+        expiration: u32,
+        cas: u64,
+    ) -> Result<bool, MemcacheError>;
     fn add<V: ToMemcacheValue<Stream>>(&mut self, key: &str, value: V, expiration: u32) -> Result<(), MemcacheError>;
     fn replace<V: ToMemcacheValue<Stream>>(
         &mut self,
diff --git a/src/value.rs b/src/value.rs
index f03edb8..a053e48 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -106,6 +106,22 @@ pub trait FromMemcacheValue: Sized {
     fn from_memcache_value(Vec<u8>, u32) -> MemcacheValue<Self>;
 }
 
+pub trait FromMemcacheValueExt: Sized {
+    fn from_memcache_value(value: Vec<u8>, flags: u32, cas: Option<u64>) -> MemcacheValue<Self>;
+}
+
+impl<V: FromMemcacheValue> FromMemcacheValueExt for V {
+    fn from_memcache_value(value: Vec<u8>, flags: u32, _cas: Option<u64>) -> MemcacheValue<Self> {
+        FromMemcacheValue::from_memcache_value(value, flags)
+    }
+}
+
+impl FromMemcacheValueExt for (Vec<u8>, u32, Option<u64>) {
+    fn from_memcache_value(value: Vec<u8>, flags: u32, cas: Option<u64>) -> MemcacheValue<Self> {
+        return Ok((value, flags, cas));
+    }
+}
+
 impl FromMemcacheValue for (Vec<u8>, u32) {
     fn from_memcache_value(value: Vec<u8>, flags: u32) -> MemcacheValue<Self> {
         return Ok((value, flags));
@@ -128,7 +144,7 @@ macro_rules! impl_from_memcache_value_for_number {
     ($ty:ident) => {
         impl FromMemcacheValue for $ty {
             fn from_memcache_value(value: Vec<u8>, _: u32) -> MemcacheValue<Self> {
-                let s: String = String::from_memcache_value(value, 0)?;
+                let s: String = FromMemcacheValue::from_memcache_value(value, 0)?;
                 match Self::from_str(s.as_str()) {
                     Ok(v) => return Ok(v),
                     Err(e) => Err(MemcacheError::from(e)),


Test patch:
diff --git a/tests/test_ascii.rs b/tests/test_ascii.rs
index 6aaf0e5..a965366 100644
--- a/tests/test_ascii.rs
+++ b/tests/test_ascii.rs
@@ -16,10 +16,13 @@ fn test_ascii() {
     assert_eq!(value, Some("bar".into()));
 
     client.set("ascii_baz", "qux", 0).unwrap();
-    let values: HashMap<String, String> = client.gets(vec!["ascii_foo", "ascii_baz", "not_exists_key"]).unwrap();
+    let values: HashMap<String, (Vec<u8>, u32)> =
+        client.gets(vec!["ascii_foo", "ascii_baz", "not_exists_key"]).unwrap();
     assert_eq!(values.len(), 2);
-    assert_eq!(values.get("ascii_foo"), Some(&"bar".to_string()));
-    assert_eq!(values.get("ascii_baz"), Some(&"qux".to_string()));
+    let ascii_foo_value = values.get("ascii_foo").unwrap();
+    let ascii_baz_value = values.get("ascii_baz").unwrap();
+    assert_eq!(String::from_utf8(ascii_foo_value.0.clone()).unwrap(), "bar".to_string());
+    assert_eq!(String::from_utf8(ascii_baz_value.0.clone()).unwrap(), "qux".to_string());
 
     client.touch("ascii_foo", 1000).unwrap();
 
diff --git a/tests/tests.rs b/tests/tests.rs
index 76b0d56..64e7c14 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -173,3 +173,47 @@ fn udp_test() {
         handles[i].take().unwrap().join().unwrap();
     }
 }
+
+#[test]
+fn test_cas() {
+    use memcache::Client;
+    use std::collections::HashMap;
+    let clients = vec![
+        Client::connect("memcache://localhost:12345").unwrap(),
+        Client::connect("memcache://localhost:12345?protocol=ascii").unwrap(),
+    ];
+    for mut client in clients {
+        client.flush().unwrap();
+
+        client.set("ascii_foo", "bar", 0).unwrap();
+        let value: Option<String> = client.get("ascii_foo").unwrap();
+        assert_eq!(value, Some("bar".into()));
+
+        client.set("ascii_baz", "qux", 0).unwrap();
+
+        let values: HashMap<String, (Vec<u8>, u32, Option<u64>)> =
+            client.gets(vec!["ascii_foo", "ascii_baz", "not_exists_key"]).unwrap();
+        assert_eq!(values.len(), 2);
+        let ascii_foo_value = values.get("ascii_foo").unwrap();
+        let ascii_baz_value = values.get("ascii_baz").unwrap();
+
+        assert!(ascii_foo_value.2.is_some());
+        assert!(ascii_baz_value.2.is_some());
+        assert_eq!(
+            true,
+            client.cas("ascii_foo", "bar2", 0, ascii_foo_value.2.unwrap()).unwrap()
+        );
+        assert_eq!(
+            false,
+            client.cas("ascii_foo", "bar3", 0, ascii_foo_value.2.unwrap()).unwrap()
+        );
+
+        assert_eq!(
+            false,
+            client
+                .cas("not_exists_key", "bar", 0, ascii_foo_value.2.unwrap())
+                .unwrap()
+        );
+        client.flush().unwrap();
+    }
+}