Commit 7c44bbb1 authored by antirez's avatar antirez
Browse files

Scala client added thanks to Alejanro Crosa

parent 45636487
2009-09-02 QuickStart added
2009-09-01 Fixed crash with only space and newline as command (issue 61), thanks to a guy having as nick "fixxxerrr"
2009-08-11 TODO list modified
2009-07-24 more snow leopard related fixes (for 32bit systems)
2009-07-24 fixed compilation with Snow Leopard, thanks to Lon Baker for providing SSH access to Snow Leopard box
2009-07-22 Fixed NetBSD compile problems
2009-07-17 now the size of the shared pool can be really modified via config, also the number of objects in the sharing pool is logged when the log level is set to debug. Thanks to Aman Gupta
2009-07-05 added utils/redis-copy.rb, a script that is able to copy data from one Redis server to another one on the fly.
2009-07-04 Applied three different patches thanks to Chris Lamb, one to fix compilation and get the IP register value on Linux IA64 and other systems. One in order to log the overcommit problem on the logs instead of the standard output when Redis is demonized. The latest in order to suggest a more consistent way in order to switch to 1 the memory overcommit Linux feature.
2009-07-03 bugfix: EXPIRE now propagates to the Slave.
2009-06-16 Redis version modified to 0.900
2009-06-16 update-ruby-client script already points to ezmobius repo 2009-06-16 update-ruby-client script already points to ezmobius repo
2009-06-16 client libraries updated 2009-06-16 client libraries updated
2009-06-16 Redis release candidate 1 2009-06-16 Redis release candidate 1
......
.DS_Store
lib_managed
project/boot
target
target/
target/**/*
# Redis Scala client
## Key features of the library
- Native Scala types Set and List responses.
- Consisten Hashing on the client.
- Support for Clustering of Redis nodes.
## Information about redis
Redis is a key-value database. It is similar to memcached but the dataset is not volatile, and values can be strings, exactly like in memcached, but also lists and sets with atomic operations to push/pop elements.
http://code.google.com/p/redis/
### Key features of Redis
- Fast in-memory store with asynchronous save to disk.
- Key value get, set, delete, etc.
- Atomic operations on sets and lists, union, intersection, trim, etc.
## Requirements
- sbt (get it at http://code.google.com/p/simple-build-tool/)
## Usage
Start your redis instance (usually redis-server will do it)
$ cd scala-redis
$ sbt
> update
> test (optional to run the tests)
> console
And you are ready to start issuing commands to the server(s)
let's connect and get a key:
scala> import com.redis._
scala> val r = new Redis("localhost", 6379)
scala> val r.set("key", "some value")
scala> val r.get("key")
Alejandro Crosa <<alejandrocrosa@gmail.com>>
#Project properties
#Wed Aug 19 07:54:05 ART 2009
project.organization=com.redis
project.name=RedisClient
sbt.version=0.5.1
project.version=1.0.1
scala.version=2.7.5
project.initialize=false
import sbt._
class RedisClientProject(info: ProjectInfo) extends DefaultProject(info) with AutoCompilerPlugins
{
override def useDefaultConfigurations = true
val scalatest = "org.scala-tools.testing" % "scalatest" % "0.9.5" % "test->default"
val specs = "org.scala-tools.testing" % "specs" % "1.5.0"
val mockito = "org.mockito" % "mockito-all" % "1.7"
val junit = "junit" % "junit" % "4.5"
val sxr = compilerPlugin("org.scala-tools.sxr" %% "sxr" % "0.2.1")
}
package com.redis
/**
* Redis client Connection
*
*/
case class Connection(val host: String, val port: Int) extends SocketOperations
package com.redis
/**
* Hash Ring
*
*/
import java.util.zip.CRC32
import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.Map
trait HashRing {
val replicas: Int
var sortedKeys: List[Long] = List()
var cluster = new ArrayBuffer[Redis]
val ring = Map[Long, Redis]()
// Adds the node to the hashRing
// including a number of replicas.
def addNode(node: Redis) = {
cluster += node
(1 to replicas).foreach{ replica =>
val key = calculateChecksum(node+":"+replica)
ring += (key -> node)
sortedKeys = sortedKeys ::: List(key)
}
sortedKeys = sortedKeys.sort(_ < _)
}
// get the node in the hash ring for this key
def getNode(key: String) = getNodePos(key)._1
def getNodePos(key: String): (Redis, Int) = {
val crc = calculateChecksum(key)
val idx = binarySearch(crc)
(ring(sortedKeys(idx)), idx)
}
// TODO this should perform a Bynary search
def binarySearch(value: Long): Int = {
var upper = (sortedKeys.length -1)
var lower = 0
var idx = 0
var comp: Long = 0
while(lower <= upper){
idx = (lower + upper) / 2
comp = sortedKeys(idx)
if(comp == value) { return idx }
if(comp < value) { upper = idx -1 }
if(comp > value) { lower = idx +1 }
}
return upper
}
// Computes the CRC-32 of the given String
def calculateChecksum(value: String): Long = {
val checksum = new CRC32
checksum.update(value.getBytes)
checksum.getValue
}
}
package com.redis.operations
/**
* Redis key space operations
*
*/
trait KeySpaceOperations{
val connection: Connection
var db: Int
// KEYS
// returns all the keys matching the glob-style pattern.
def keys(pattern: String): Array[String] = {
connection.write("KEYS "+pattern+"\r\n")
connection.readResponse.toString.split(" ")
}
// RANDKEY
// return a randomly selected key from the currently selected DB.
def randomKey: String = {
connection.write("RANDOMKEY\r\n")
connection.readResponse.toString.split('+')(1)
}
// RENAME (oldkey, newkey)
// atomically renames the key oldkey to newkey.
def rename(oldkey: String, newkey: String): Boolean = {
connection.write("RENAME "+oldkey+" "+newkey+"\r\n")
connection.readBoolean
}
// RENAMENX (oldkey, newkey)
// rename oldkey into newkey but fails if the destination key newkey already exists.
def renamenx(oldkey: String, newkey: String): Boolean = {
connection.write("RENAMENX "+oldkey+" "+newkey+"\r\n")
connection.readBoolean
}
// DBSIZE
// return the size of the db.
def dbSize: Int = {
connection.write("DBSIZE\r\n")
connection.readInt
}
}
\ No newline at end of file
package com.redis.operations
/**
* Redis list operations
*
*/
trait ListOperations{
def getConnection(key: String): Connection
// add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key.
// If the key does not exist an empty list is created just before the append operation. If the key exists but is not a List an error is returned.
// LPUSH
def pushHead(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("LPUSH "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// RPUSH
def pushTail(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("RPUSH "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// LPOP
// atomically return and remove the first (LPOP) or last (RPOP) element of the list
def popHead(key: String): String = {
val connection = getConnection(key)
connection.write("LPOP "+key+"\r\n")
connection.readString
}
// RPOP
// atomically return and remove the first (LPOP) or last (RPOP) element of the list
def popTail(key: String): String = {
val connection = getConnection(key)
connection.write("RPOP "+key+"\r\n")
connection.readString
}
// LINDEX
// return the especified element of the list stored at the specified key. 0 is the first element, 1 the second and so on.
// Negative indexes are supported, for example -1 is the last element, -2 the penultimate and so on.
def listIndex(key: String, index: Int): String = {
val connection = getConnection(key)
connection.write("LINDEX "+key+" "+index+"\r\n")
connection.readString
}
// LSET
// set the list element at index with the new value. Out of range indexes will generate an error
def listSet(key: String, index: Int, value: String): Boolean = {
val connection = getConnection(key)
connection.write("LSET "+key+" "+index+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// LLEN
// return the length of the list stored at the specified key.
// If the key does not exist zero is returned (the same behaviour as for empty lists). If the value stored at key is not a list an error is returned.
def listLength(key: String): Int = {
val connection = getConnection(key)
connection.write("LLEN "+key+"\r\n")
connection.readInt
}
// LRANGE
// return the specified elements of the list stored at the specified key.
// Start and end are zero-based indexes. 0 is the first element of the list (the list head), 1 the next element and so on.
def listRange(key: String, start: Int, end: Int): List[String] = {
val connection = getConnection(key)
connection.write("LRANGE "+key+" "+start+" "+end+"\r\n")
connection.readList
}
// LTRIM
// Trim an existing list so that it will contain only the specified range of elements specified.
def listTrim(key: String, start: Int, end: Int): Boolean = {
val connection = getConnection(key)
connection.write("LTRIM "+key+" "+start+" "+end+"\r\n")
connection.readBoolean
}
// LREM
// Remove the first count occurrences of the value element from the list.
def listRem(key: String, count: Int, value: String): Boolean = {
val connection = getConnection(key)
connection.write("LREM "+key+" "+count+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
}
package com.redis.operations
/**
* Redis node operations
*
*/
trait NodeOperations {
val connection: Connection
var db: Int
// SAVE
// save the DB on disk now.
def save: Boolean = {
connection.write("SAVE\r\n")
connection.readBoolean
}
// BGSAVE
// save the DB in the background.
def bgSave: Boolean = {
connection.write("BGSAVE\r\n")
connection.readBoolean
}
// LASTSAVE
// return the UNIX TIME of the last DB SAVE executed with success.
def lastSave: Int = {
connection.write("LASTSAVE\r\n")
connection.readInt
}
// SHUTDOWN
// Stop all the clients, save the DB, then quit the server.
def shutdown: Boolean = {
connection.write("SHUTDOWN\r\n")
connection.readBoolean
}
// MGET (key, key, key, ...)
// get the values of all the specified keys.
def mget(keys: String*) = {
connection.write("MGET "+keys.mkString(" ")+"\r\n")
connection.readList
}
// INFO
// the info command returns different information and statistics about the server.
def info = {
connection.write("INFO\r\n")
connection.readResponse
}
// MONITOR
// is a debugging command that outputs the whole sequence of commands received by the Redis server.
def monitor: Boolean = {
connection.write("MONITOR\r\n")
connection.readBoolean
}
// SLAVEOF
// The SLAVEOF command can change the replication settings of a slave on the fly.
def slaveOf(options: Any): Boolean = options match {
case (host: String, port: Int) => {
connection.write("SLAVEOF "+host+" "+port.toString+"\r\n")
connection.readBoolean
}
case _ => setAsMaster
}
def setAsMaster: Boolean = {
connection.write("SLAVEOF NO ONE\r\n")
connection.readBoolean
}
// SELECT (index)
// selects the DB to connect, defaults to 0 (zero).
def selectDb(index: Int): Boolean = {
connection.write("SELECT "+index+"\r\n")
connection.readBoolean match {
case true => { db = index; true }
case _ => false
}
}
// FLUSHDB the DB
// removes all the DB data.
def flushDb: Boolean = {
connection.write("FLUSHDB\r\n")
connection.readBoolean
}
// FLUSHALL the DB's
// removes data from all the DB's.
def flushAll: Boolean = {
connection.write("FLUSHALL\r\n")
connection.readBoolean
}
// MOVE
// Move the specified key from the currently selected DB to the specified destination DB.
def move(key: String, db: Int) = {
connection.write("MOVE "+key+" "+db.toString+"\r\n")
connection.readBoolean
}
// QUIT
// exits the server.
def quit: Boolean = {
connection.write("QUIT\r\n")
connection.disconnect
}
// AUTH
// auths with the server.
def auth(secret: String): Boolean = {
connection.write("AUTH "+secret+"\r\n")
connection.readBoolean
}
}
package com.redis.operations
/**
* Redis operations
*
*/
trait Operations{
def getConnection(key: String): Connection
// SET (key, value)
// SET (key, value, expiry)
// sets the key with the specified value, and with an optional expiry.
def set(key: String, value: String) = setKey(key, value)
def set(key: String, value: String, expiry: Int) = { setKey(key, value) && expire(key, expiry) }
// SET KEY (key, value)
// sets the key with the specified value.
def setKey(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SET "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// EXPIRE (key, expiry)
// sets the expire time (in sec.) for the specified key.
def expire(key: String, expiry: Int): Boolean = {
val connection = getConnection(key)
connection.write("EXPIRE "+key+" "+expiry+"\r\n")
connection.readBoolean
}
// GET (key)
// gets the value for the specified key.
def get(key: String): String = {
val connection = getConnection(key)
val a = connection.write("GET "+key+"\r\n")
connection.readResponse match {
case r: String => r.toString
case _ => null
}
}
// GETSET (key, value)
// is an atomic set this value and return the old value command.
def getSet(key: String, value: String): String = {
val connection = getConnection(key)
val a = connection.write("GETSET "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readResponse match {
case r: String => r.toString
case _ => null
}
}
// SETNX (key, value)
// sets the value for the specified key, only if the key is not there.
def setUnlessExists(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SETNX "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// DELETE (key)
// deletes the specified key.
def delete(key: String): Boolean = {
val connection = getConnection(key)
connection.write("DEL "+key+"\r\n")
connection.readBoolean
}
// INCR (key)
// INCR (key, increment)
// increments the specified key, optional the increment value.
def incr(x: Any): Int = x match {
case (key: String, increment: Int) => incrBy(key, increment)
case (key: String) => incrOne(key)
case _ => 0
}
def incrBy(key: String, increment: Int): Int = {
val connection = getConnection(key)
connection.write("INCRBY "+key+" "+increment+"\r\n")
connection.readInt
}
def incrOne(key: String): Int = {
val connection = getConnection(key)
connection.write("INCR "+key+"\r\n")
connection.readInt
}
// DECR (key)
// DECRBY (key, decrement)
// decrements the specified key, optional the decrement value.
def decr(key: String, decrement: Int) = decrBy(key, decrement)
def decr(key: String) = decrOne(key)
def decrBy(key: String, decrement: Int): Int = {
val connection = getConnection(key)
connection.write("DECRBY "+key+" "+decrement+"\r\n")
connection.readInt
}
def decrOne(key: String): Int = {
val connection = getConnection(key)
connection.write("DECR "+key+"\r\n")
connection.readInt
}
// EXISTS (key)
// test if the specified key exists.
def exists(key: String): Boolean = {
val connection = getConnection(key)
connection.write("EXISTS "+key+"\r\n")
connection.readBoolean
}
// TYPE (key)
// return the type of the value stored at key in form of a string.
def getType(key: String): Any = {
val connection = getConnection(key)
connection.write("TYPE "+key+"\r\n")
connection.readResponse
}
}
\ No newline at end of file
package com.redis.operations
/**
* Redis set operations
*
*/
trait SetOperations{
def getConnection(key: String): Connection
// SADD
// Add the specified member to the set value stored at key.
def setAdd(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SADD "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// SREM
// Remove the specified member from the set value stored at key.
def setDelete(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SREM "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// SCARD
// Return the number of elements (the cardinality) of the Set at key.
def setCount(key: String): Int = {
val connection = getConnection(key)
connection.write("SCARD "+key+"\r\n")
connection.readInt
}
// SMEMBERS
// Return all the members of the Set value at key.
def setMembers(key: String): Set[String] = {
val connection = getConnection(key)
connection.write("SMEMBERS "+key+"\r\n")
connection.readSet
}
// SPOP
// Remove and return (pop) a random element from the Set value at key.
def setPop(key: String): String = {
val connection = getConnection(key)
connection.write("SPOP "+key+"\r\n")
connection.readString
}
// SMOVE
// Move the specified member from one Set to another atomically.
def setMove(sourceKey: String, destKey: String, value: String): Boolean = {
val connection = getConnection(sourceKey)
connection.write("SMOVE "+sourceKey+" "+destKey+" "+value+"\r\n")
connection.readBoolean
}
// SISMEMBER
// Test if the specified value is a member of the Set at key.
def setMemberExists(key: String, value: String): Boolean = {
val connection = getConnection(key)
connection.write("SISMEMBER "+key+" "+value.length+"\r\n"+value+"\r\n")
connection.readBoolean
}
// SINTER
// Return the intersection between the Sets stored at key1, key2, ..., keyN.
def setIntersect(keys: String*): Set[String] = {
val connection = getConnection(keys(0))
connection.write("SINTER "+keys.mkString(" ")+"\r\n")
connection.readSet
}
// SINTERSTORE
// Compute the intersection between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey.
def setInterStore(key: String, keys: String*): Boolean = {
val connection = getConnection(key)
connection.write("SINTERSTORE "+key+" "+keys.mkString(" ")+"\r\n")
connection.readBoolean
}
// SDIFF
// Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN.
def setDiff(keys: String*): Set[String] = {
val connection = getConnection(keys(0))
connection.write("SDIFF "+keys.mkString(" ")+"\r\n")
connection.readSet
}
// SDIFFSTORE
// Compute the difference between the Set key1 and all the Sets key2, ..., keyN, and store the resulting Set at dstkey.
def setDiffStore(key: String, keys: String*): Boolean = {
val connection = getConnection(key)
connection.write("SDIFFSTORE "+key+" "+keys.mkString(" ")+"\r\n")
connection.readBoolean
}
// SUNION
// Return the union between the Sets stored at key1, key2, ..., keyN.
def setUnion(keys: String*): Set[String] = {
val connection = getConnection(keys(0))
connection.write("SUNION "+keys.mkString(" ")+"\r\n")
connection.readSet
}
// SUNIONSTORE
// Compute the union between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey.
def setUnionStore(key: String, keys: String*): Boolean = {
val connection = getConnection(key)
connection.write("SUNIONSTORE "+key+" "+keys.mkString(" ")+"\r\n")
connection.readBoolean
}
}
\ No newline at end of file
package com.redis.operations
/**
* Redis sort operations
*
*/
trait SortOperations{
def getConnection(key: String): Connection
// SORT
// Sort a Set or a List accordingly to the specified parameters.
def sort(args: Any): List[String] = args match {
case (key: String, command: String) => doSort(key, command)
case (key: String) => doSort(key, "")
}
def doSort(key: String, command: String): List[String] = {
val connection = getConnection(key)
if(command != "") {
connection.write("SORT "+key+" "+command+"\r\n")
} else {
connection.write("SORT "+key+"\r\n")
}
connection.readList
}
}
\ No newline at end of file
package com.redis
import com.redis.operations._
/**
* Redis client
*
*/
class Redis(val host: String, val port: Int) extends Operations with ListOperations with SetOperations with NodeOperations with KeySpaceOperations with SortOperations {
// auxiliary constructor
def this() = this("localhost", 6379)
// Points to the connection to a server instance
val connection = Connection(host, port)
var db: Int = 0
// Connect and Disconnect to the Redis server
def connect = connection.connect
def disconnect = connection.disconnect
def connected: Boolean = connection.connected
// Establish the connection to the server instance on initialize
connect
// Get Redis Client connection.
def getConnection(key: String) = getConnection
def getConnection = connection
// Outputs a formatted representation of the Redis server.
override def toString = connection.host+":"+connection.port+" <connected:"+connection.connected+">"
}
package com.redis
import com.redis.operations._
/**
* Redis cluster
*
*/
import scala.collection.mutable.ArrayBuffer
class RedisCluster(val hosts: String*) extends Operations with ListOperations with SetOperations with HashRing with SortOperations {
// Get Redis Client connection inside the HashRing.
def getConnection(key: String) = {
getNode(key).connection
}
// Default value used on MemCache client.
private val NUMBER_OF_REPLICAS = 160
val replicas = NUMBER_OF_REPLICAS
// Outputs a formatted representation of the Redis server.
override def toString = cluster.mkString(", ")
// Connect the client and add it to the cluster.
def connectClient(host: String): Boolean = {
val h = host.split(":")(0)
val p = host.split(":")(1)
val client = new Redis(h.toString, p.toString.toInt)
addNode(client)
client.connected
}
// Connect all clients in the cluster.
def connect = cluster.map(c => c.connect)
// Initialize cluster.
def initialize_cluster = hosts.map(connectClient(_))
initialize_cluster
}
package com.redis
/**
* Socket operations
*
*/
import java.io._
import java.net.Socket
trait SocketOperations {
// Response codes from the Redis server
// they tell you what's coming next from the server.
val ERR: String = "-"
val OK: String = "+OK"
val SINGLE: String = "+"
val BULK: String = "$"
val MULTI: String = "*"
val INT:String = ":"
val host: String
val port: Int
// File descriptors.
var socket: Socket = null
var out: OutputStream = null
var in: BufferedReader = null
def getOutputStream: OutputStream = out
def getInputStream: BufferedReader = in
def getSocket: Socket = socket
def connected = { getSocket != null }
def reconnect = { disconnect && connect; }
// Connects the socket, and sets the input and output streams.
def connect: Boolean = {
try {
socket = new Socket(host, port)
out = getSocket.getOutputStream
in = new BufferedReader(new InputStreamReader(getSocket.getInputStream));
true
} catch {
case _ => clear_fd; false;
}
}
// Disconnects the socket.
def disconnect: Boolean = {
try {
socket.close
out.close
in.close
clear_fd
true
} catch {
case _ => false
}
}
def clear_fd = {
socket = null
out = null
in = null
}
// Reads the server responses as Scala types.
def readString: String = readResponse.toString // Reads the server response as an Int
def readInt: Int = Integer.parseInt(readResponse.toString) // Reads the server response as an Int
def readList: List[String] = listReply(readResponse.toString) // Reads the server response as a List
def readSet: Set[String] = setReply(readResponse.toString) // Reads the server response as a String
def readBoolean: Boolean = readResponse match {
case 1 => true
case OK => true
case _ => false
}
// Read from Input Stream.
def readline: String = {
try {
getInputStream.readLine()
} catch {
case _ => ERR;
}
}
// Gets the type of response the server is going to send.
def readtype = {
val res = readline
if(res !=null){
(res(0).toString(), res)
}else{
("-", "")
}
}
// Reads the response from the server based on the response code.
def readResponse = {
val responseType = readtype
try{
responseType._1 match {
case ERR => reconnect; // RECONNECT
case SINGLE => lineReply(responseType._2)
case BULK => bulkReply(responseType._2)
case MULTI => responseType._2
case INT => integerReply(responseType._2)
case _ => reconnect; // RECONNECT
}
}catch{
case e: Exception => false
}
}
def integerReply(response: String): Int = Integer.parseInt(response.split(":")(1).toString)
def lineReply(response: String): String = response
def listReply(response: String): List[String] = {
val total = Integer.parseInt(response.split('*')(1))
var list: List[String] = List()
for(i <- 1 to total){
list = (list ::: List(bulkReply(readtype._2)))
}
list
}
def bulkReply(response: String) = {
if(response(1).toString() != ERR){
var length: Int = Integer.parseInt(response.split('$')(1).split("\r\n")(0))
var line, res: String = ""
while(length >= 0){
line = readline
length -= (line.length+2)
res += line
if(length > 0) res += "\r\n"
}
res
}else{ null }
}
def setReply(response: String): Set[String] = {
val total = Integer.parseInt(response.split('*')(1))
var set: Set[String] = Set()
for(i <- 1 to total){
set += bulkReply(readtype._2)
}
set
}
// Wraper for the socket write operation.
def write_to_socket(data: String)(op: OutputStream => Unit) = op(getOutputStream)
// Writes data to a socket using the specified block.
def write(data: String) = {
if(!connected) connect;
write_to_socket(data){
getSocket =>
try {
getSocket.write(data.getBytes)
} catch {
case _ => reconnect;
}
}
}
}
\ No newline at end of file
import org.specs._
import com.redis._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
object RedisClientSpec extends Specification with Mockito {
"Redis Client" should {
var client: Redis = null
"print formatted client status" in {
client = new Redis("localhost", 121212)
client.toString must be matching("localhost:121212 <connected:false>")
}
"get the same connection when passing key para or not since it's a single node" in {
client.getConnection("key") mustEqual client.getConnection
}
"use db zero as default" in {
client.db mustEqual 0
}
}
}
import org.specs._
import com.redis._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
object RedisClusterSpec extends Specification with Mockito {
"Redis Cluster" should {
var cluster: RedisCluster = null
var mockedRedis: Redis = null
doBefore {
cluster = new RedisCluster("localhost:11221", "localhost:99991")
mockedRedis = mock[Redis]
}
"print formatted client status" in {
cluster.toString must be matching("localhost:11221 <connected:false>, localhost:99991 <connected:false>")
}
"get the connection for the specified key" in {
cluster.getConnection("key") mustEqual Connection("localhost", 99991)
cluster.getConnection("anotherkey") mustEqual Connection("localhost", 11221)
}
"use the default number of replicas" in {
cluster.replicas mustEqual 160
}
"initialize cluster" in {
val initializedCluster = cluster.initialize_cluster
initializedCluster.size mustEqual 2
initializedCluster(0) mustEqual false
initializedCluster(1) mustEqual false
}
"connect all the redis instances" in {
cluster.cluster(1) = mockedRedis
cluster.cluster(1).connect returns true
val connectResult = cluster.connect
connectResult.size mustEqual 2
connectResult(0) mustEqual false
connectResult(1) mustEqual true
cluster.cluster(1).connect was called
}
}
}
import org.specs._
import com.redis._
import java.io._
import java.net.Socket
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
class SocketOperationTest(val host:String, val port: Int) extends SocketOperations
object SocketOperationsSpec extends Specification with Mockito {
"Socket Operations" should {
var socketOperation: SocketOperationTest = null
var socket: Socket = null
var in: BufferedReader = null
doBefore {
socketOperation = new SocketOperationTest("localhost", 6379666)
socket = mock[Socket]
in = mock[BufferedReader]
socketOperation.socket = socket
socketOperation.in = in
}
def readOkFromInput = { when(in.readLine()).thenReturn(socketOperation.OK) }
def readSingleFromInput = { when(in.readLine()).thenReturn(socketOperation.SINGLE) }
def readBulkFromInput = { in.readLine() returns("$6\r\nfoobar\r\n") thenReturns("$6\r\nfoobar\r\n") }
def readIntFromInput = { when(in.readLine()).thenReturn(socketOperation.INT+"666") }
"tell if it's connected" in {
socketOperation.connected mustEqual true
socketOperation.socket = null
socketOperation.connected mustEqual false
}
"return false when can't connect" in {
socketOperation.connect mustEqual false
}
"return current data input stream" in {
socketOperation.getInputStream mustEqual in
}
"read a line from socket" in {
readOkFromInput
socketOperation.in mustEqual in
socketOperation.readline mustEqual socketOperation.OK
}
"read type response" in {
readOkFromInput
socketOperation.readtype mustEqual ("+", socketOperation.OK)
}
"when reading responses" in {
"read OK" in {
readOkFromInput
socketOperation.readResponse mustEqual socketOperation.OK
}
"read single line" in {
readSingleFromInput
socketOperation.readResponse mustEqual socketOperation.SINGLE
}
"reconnect on error" in {
socketOperation.readResponse mustEqual false
socket.close was called
socketOperation.connected mustEqual true
}
"read in bulk" in {
// readBulkFromInput
// this shouldn't be the response, it doesn't seem to work return and then returns.
// Here's what should happen: '$6\r\n' on first readLine and then 'foobar\r\n'
readBulkFromInput
socketOperation.readtype mustEqual ("$", "$6\r\nfoobar\r\n")
socketOperation.readResponse mustEqual "$6\r\nfoobar\r\n"
socketOperation.bulkReply("$6\r\nfoobar\r\n") was called
}
"read integer" in {
readIntFromInput
socketOperation.readInt mustEqual 666
}
"read a boolean return value" in {
readOkFromInput
socketOperation.readBoolean mustEqual true
}
}
}
}
import org.specs._
import com.redis._
import com.redis.operations._
import org.specs.mock.Mockito
import org.mockito.Mock._
import org.mockito.Mockito._
import org.mockito.Mockito.doNothing
class RedisTestClient(val connection: Connection) extends Operations with ListOperations with SetOperations with NodeOperations with KeySpaceOperations with SortOperations {
var db: Int = 0
def getConnection(key: String): Connection = connection
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment