2010-03-18 10:51:22 +05:30
|
|
|
package se.scalablesolutions.akka.persistence.redis
|
|
|
|
|
|
|
|
|
|
import org.scalatest.Spec
|
|
|
|
|
import org.scalatest.Assertions
|
|
|
|
|
import org.scalatest.matchers.ShouldMatchers
|
|
|
|
|
import org.scalatest.BeforeAndAfterAll
|
|
|
|
|
import org.scalatest.junit.JUnitRunner
|
|
|
|
|
import org.junit.runner.RunWith
|
|
|
|
|
|
2010-05-03 09:30:36 +02:00
|
|
|
import se.scalablesolutions.akka.actor.{Actor, ActorID, Transactor}
|
|
|
|
|
import Actor._
|
2010-03-18 10:51:22 +05:30
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A persistent actor based on Redis sortedset storage.
|
|
|
|
|
* <p/>
|
|
|
|
|
* Needs a running Redis server.
|
|
|
|
|
* @author <a href="http://debasishg.blogspot.com">Debasish Ghosh</a>
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
trait ZScorable {
|
|
|
|
|
def zscore: Float
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case class Hacker(name: String, birth: String) extends ZScorable {
|
|
|
|
|
def zscore = birth.toFloat
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class SetThresholdViolationException extends RuntimeException
|
|
|
|
|
|
|
|
|
|
// add hacker to the set
|
|
|
|
|
case class ADD(h: Hacker)
|
|
|
|
|
|
|
|
|
|
// remove hacker from set
|
|
|
|
|
case class REMOVE(h: Hacker)
|
|
|
|
|
|
|
|
|
|
// size of the set
|
|
|
|
|
case object SIZE
|
|
|
|
|
|
|
|
|
|
// zscore of the hacker
|
|
|
|
|
case class SCORE(h: Hacker)
|
|
|
|
|
|
|
|
|
|
// zrange
|
|
|
|
|
case class RANGE(start: Int, end: Int)
|
|
|
|
|
|
|
|
|
|
// add and remove subject to the condition that there will be at least 3 hackers
|
2010-05-03 09:30:36 +02:00
|
|
|
case class MULTI(add: List[Hacker], rem: List[Hacker], failer: ActorID)
|
2010-03-18 10:51:22 +05:30
|
|
|
|
|
|
|
|
class SortedSetActor extends Transactor {
|
|
|
|
|
timeout = 100000
|
|
|
|
|
private lazy val hackers = RedisStorage.newSortedSet
|
|
|
|
|
|
|
|
|
|
def receive = {
|
|
|
|
|
case ADD(h) =>
|
|
|
|
|
hackers.+(h.name.getBytes, h.zscore)
|
|
|
|
|
reply(true)
|
|
|
|
|
|
|
|
|
|
case REMOVE(h) =>
|
|
|
|
|
hackers.-(h.name.getBytes)
|
|
|
|
|
reply(true)
|
|
|
|
|
|
|
|
|
|
case SIZE =>
|
|
|
|
|
reply(hackers.size)
|
|
|
|
|
|
|
|
|
|
case SCORE(h) =>
|
|
|
|
|
reply(hackers.zscore(h.name.getBytes))
|
|
|
|
|
|
|
|
|
|
case RANGE(s, e) =>
|
|
|
|
|
reply(hackers.zrange(s, e))
|
|
|
|
|
|
|
|
|
|
case MULTI(a, r, failer) =>
|
|
|
|
|
a.foreach{ h: Hacker =>
|
|
|
|
|
hackers.+(h.name.getBytes, h.zscore)
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
r.foreach{ h =>
|
|
|
|
|
if (hackers.size <= 3)
|
|
|
|
|
throw new SetThresholdViolationException
|
|
|
|
|
hackers.-(h.name.getBytes)
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
case e: Exception =>
|
|
|
|
|
failer !! "Failure"
|
|
|
|
|
}
|
|
|
|
|
reply((a.size, r.size))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
import RedisStorageBackend._
|
|
|
|
|
|
|
|
|
|
@RunWith(classOf[JUnitRunner])
|
|
|
|
|
class RedisPersistentSortedSetSpec extends
|
|
|
|
|
Spec with
|
|
|
|
|
ShouldMatchers with
|
|
|
|
|
BeforeAndAfterAll {
|
|
|
|
|
|
|
|
|
|
override def beforeAll {
|
|
|
|
|
flushDB
|
|
|
|
|
println("** destroyed database")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def afterAll {
|
|
|
|
|
flushDB
|
|
|
|
|
println("** destroyed database")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val h1 = Hacker("Alan kay", "1940")
|
|
|
|
|
val h2 = Hacker("Richard Stallman", "1953")
|
|
|
|
|
val h3 = Hacker("Yukihiro Matsumoto", "1965")
|
|
|
|
|
val h4 = Hacker("Claude Shannon", "1916")
|
|
|
|
|
val h5 = Hacker("Linus Torvalds", "1969")
|
|
|
|
|
val h6 = Hacker("Alan Turing", "1912")
|
|
|
|
|
|
|
|
|
|
describe("Add and report cardinality of the set") {
|
2010-05-03 09:30:36 +02:00
|
|
|
val qa = newActor[SortedSetActor]
|
2010-03-18 10:51:22 +05:30
|
|
|
qa.start
|
|
|
|
|
|
|
|
|
|
it("should enter 6 hackers") {
|
|
|
|
|
qa !! ADD(h1)
|
|
|
|
|
qa !! ADD(h2)
|
|
|
|
|
qa !! ADD(h3)
|
|
|
|
|
qa !! ADD(h4)
|
|
|
|
|
qa !! ADD(h5)
|
|
|
|
|
qa !! ADD(h6)
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(6)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it("should fetch correct scores for hackers") {
|
|
|
|
|
(qa !! SCORE(h1)).get.asInstanceOf[Float] should equal(1940.0f)
|
|
|
|
|
(qa !! SCORE(h5)).get.asInstanceOf[Float] should equal(1969.0f)
|
|
|
|
|
(qa !! SCORE(h6)).get.asInstanceOf[Float] should equal(1912.0f)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it("should fetch proper range") {
|
|
|
|
|
(qa !! RANGE(0, 4)).get.asInstanceOf[List[_]].size should equal(5)
|
|
|
|
|
(qa !! RANGE(0, 6)).get.asInstanceOf[List[_]].size should equal(6)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it("should remove and throw exception for removing non-existent hackers") {
|
|
|
|
|
qa !! REMOVE(h2)
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(5)
|
|
|
|
|
qa !! REMOVE(h3)
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(4)
|
|
|
|
|
val h7 = Hacker("Paul Snively", "1952")
|
|
|
|
|
try {
|
|
|
|
|
qa !! REMOVE(h7)
|
|
|
|
|
}
|
|
|
|
|
catch {
|
2010-03-25 21:50:27 +01:00
|
|
|
case e: NoSuchElementException =>
|
2010-03-18 10:51:22 +05:30
|
|
|
e.getMessage should endWith("not present")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it("should change score for entering the same hacker name with diff score") {
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(4)
|
|
|
|
|
|
|
|
|
|
// same name as h6
|
|
|
|
|
val h7 = Hacker("Alan Turing", "1992")
|
|
|
|
|
qa !! ADD(h7)
|
|
|
|
|
|
|
|
|
|
// size remains same
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(4)
|
|
|
|
|
|
|
|
|
|
// score updated
|
|
|
|
|
(qa !! SCORE(h7)).get.asInstanceOf[Float] should equal(1992.0f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe("Transaction semantics") {
|
|
|
|
|
it("should rollback on exception") {
|
2010-05-03 09:30:36 +02:00
|
|
|
val qa = newActor[SortedSetActor]
|
2010-03-18 10:51:22 +05:30
|
|
|
qa.start
|
|
|
|
|
|
2010-05-03 09:30:36 +02:00
|
|
|
val failer = newActor[PersistentFailerActor]
|
2010-03-18 10:51:22 +05:30
|
|
|
failer.start
|
|
|
|
|
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(0)
|
|
|
|
|
val add = List(h1, h2, h3, h4)
|
|
|
|
|
val rem = List(h2)
|
|
|
|
|
(qa !! MULTI(add, rem, failer)).get.asInstanceOf[Tuple2[Int, Int]] should equal((4,1))
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(3)
|
|
|
|
|
// size == 3
|
|
|
|
|
|
|
|
|
|
// add 2 more
|
|
|
|
|
val add1 = List(h5, h6)
|
|
|
|
|
|
|
|
|
|
// remove 3
|
|
|
|
|
val rem1 = List(h1, h3, h4)
|
|
|
|
|
try {
|
|
|
|
|
qa !! MULTI(add1, rem1, failer)
|
|
|
|
|
} catch { case e: Exception => {}
|
|
|
|
|
}
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(3)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe("zrange") {
|
|
|
|
|
it ("should report proper range") {
|
2010-05-03 09:30:36 +02:00
|
|
|
val qa = newActor[SortedSetActor]
|
2010-03-18 10:51:22 +05:30
|
|
|
qa.start
|
|
|
|
|
qa !! ADD(h1)
|
|
|
|
|
qa !! ADD(h2)
|
|
|
|
|
qa !! ADD(h3)
|
|
|
|
|
qa !! ADD(h4)
|
|
|
|
|
qa !! ADD(h5)
|
|
|
|
|
qa !! ADD(h6)
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(6)
|
|
|
|
|
val l = (qa !! RANGE(0, 6)).get.asInstanceOf[List[(Array[Byte], Float)]]
|
|
|
|
|
l.map { case (e, s) => (new String(e), s) }.head should equal(("Alan Turing", 1912.0f))
|
|
|
|
|
val h7 = Hacker("Alan Turing", "1992")
|
|
|
|
|
qa !! ADD(h7)
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(6)
|
|
|
|
|
val m = (qa !! RANGE(0, 6)).get.asInstanceOf[List[(Array[Byte], Float)]]
|
|
|
|
|
m.map { case (e, s) => (new String(e), s) }.head should equal(("Claude Shannon", 1916.0f))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it ("should report proper rge") {
|
2010-05-03 09:30:36 +02:00
|
|
|
val qa = newActor[SortedSetActor]
|
2010-03-18 10:51:22 +05:30
|
|
|
qa.start
|
|
|
|
|
qa !! ADD(h1)
|
|
|
|
|
qa !! ADD(h2)
|
|
|
|
|
qa !! ADD(h3)
|
|
|
|
|
qa !! ADD(h4)
|
|
|
|
|
qa !! ADD(h5)
|
|
|
|
|
qa !! ADD(h6)
|
|
|
|
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(6)
|
|
|
|
|
(qa !! RANGE(0, 5)).get.asInstanceOf[List[_]].size should equal(6)
|
|
|
|
|
(qa !! RANGE(0, 6)).get.asInstanceOf[List[_]].size should equal(6)
|
|
|
|
|
(qa !! RANGE(0, 3)).get.asInstanceOf[List[_]].size should equal(4)
|
|
|
|
|
(qa !! RANGE(0, 1)).get.asInstanceOf[List[_]].size should equal(2)
|
|
|
|
|
(qa !! RANGE(0, 0)).get.asInstanceOf[List[_]].size should equal(1)
|
|
|
|
|
(qa !! RANGE(3, 1)).get.asInstanceOf[List[_]].size should equal(0)
|
|
|
|
|
(qa !! RANGE(0, -1)).get.asInstanceOf[List[_]].size should equal(6)
|
|
|
|
|
(qa !! RANGE(0, -2)).get.asInstanceOf[List[_]].size should equal(5)
|
|
|
|
|
(qa !! RANGE(0, -4)).get.asInstanceOf[List[_]].size should equal(3)
|
|
|
|
|
(qa !! RANGE(-4, -1)).get.asInstanceOf[List[_]].size should equal(4)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|