setac: a phased deterministic testing framework for scala...
TRANSCRIPT
Setac: A Phased Deterministic Testing Framework for Scala
Actors
Samira TasharofiJun 02, 2011Stanford, CA
Motivation• Schedule is a source of non-determinism in
concurrent programso Shared memory: order of accesses o Message-passing: order of messages
• The output of the program might be different for a given input depending on the schedule
• The bugs may only show up during some specific scheduleso Systematic exploration can lead to state space explosion
7/17/2011Setac Testing Framework 2
Actor Model• Actor is a computational entity with a mail box and
local stateo Deliver a message
• Append the message to the mail boxo Process a message
• Extract a message from the mail box and execute that• Determined by message handler
• In response to a message it processes:o Changes its local stateo Sends messages to other actorso Creates new actors
7/17/2011Setac Testing Framework 3
Actors in Scala• Package scala.actors in Scala library• Developed by Philipp Haller• Features
o Synchronous and asynchronous communicationo Dynamic creation/destroying of actorso Exception handlingo Remote actorso Customization of the thread pool executing actors
7/17/2011Setac Testing Framework 4
Example: BoundedBufferclass BoundedBuffer(maxSize: Int) extends Actor {
var content = new Array[Int](size)var head, tail, curSize = 0startoverride def act() = loop {
react {case Put(x) if (curSize < maxSize) => {
content(tail) = x tail = (tail + 1) % size curSize += 1
}case Get if (curSize > 0) => {
val r = content(head) head = (head + 1) % size curSize -= 1 reply(r)
}}
}}
class Consumer(buf: Actor) extends Actor {var token = -1startoverride def act() = loop {
react {case Consume(count) =>
for (i <- 0 to count-1)token = (buf !? Get).asInstanceOf[Int]
}}
}
class Producer(buf: Actor) extends Actor {start
override def act() = loop {react {
case Produce(values) => values.foreach(v => buf ! Put(v))
}}
}
7/17/2011Setac Testing Framework 5
BufferTest
7/17/2011Setac Testing Framework 6
buffer(1)
Put(4), Put(5)
Get, Get
producer
consumer 4, 5
class BufferTest {@Testdef testBuffer() {
val buf = new BoundedBuffer(1)val consumer = new Consumer(buf)val producer = new Producer(buf)
producer ! Produce(List(4, 5))consumer ! Consume(2)
// consumer should receive 4 and 5assert(… )
}
Buggy BoundedBufferclass BoundedBuffer(maxSize: Int) extends Actor {
var content = new Array[Int](size)var head, tail, curSize = 0startoverride def act() = loop {
react {case Put(x) if (curSize maxSize) => {
content(tail) = x tail = (tail + 1) % size curSize += 1
}case Get if (curSize > 0) => {
val r = content(head) head = (head + 1) % size curSize -= 1 reply(r)
}}
}}
7/17/2011Setac Testing Framework 7
<=<
Bug
Buggy BoundedBuffer: Schedules in BufferTest
class BoundedBuffer(maxSize: Int) extends Actor {var content = new Array[Int](size)var head, tail, curSize = 0startoverride def act() = loop {
react {case Put(x) if (curSize maxSize) => {
content(tail) = x tail = (tail + 1) % size curSize += 1
}case Get if (curSize > 0) => {
val r = content(head) head = (head + 1) % size curSize -= 1 reply(r)
}}
}}
7/17/2011Setac Testing Framework 8
<=
buffer(1)producerconsumer
Put(4)
Get
Put(5)
Get
5
4
5
5
Error!
Correct BoundedBuffer: Checking Assertions in Test
class BoundedBuffer(maxSize: Int) extends Actor {var content = new Array[Int](size)var head, tail, curSize = 0startoverride def act() = loop {
react {case Put(x) if (curSize < maxSize) => {
content(tail) = x tail = (tail + 1) % size curSize += 1
}case Get if (curSize > 0) => {
val r = content(head) head = (head + 1) % size curSize -= 1 reply(r)
}}
}}
7/17/2011Setac Testing Framework 9
class SimpleBufferTest {@Testdef testBuffer() {
val buf = new BoundedBuffer(1)buf ! Put(4)assert(buf.curSize == 1)
}
bufferPut(4)
…mailbox
content, head, tail,
curSize
local state
assert
Fails!!
Fails!!
Testing Actor Programs• Problems:
o How to write unit tests for Actor programs while controlling the schedule?o How to check assertions at appropriate times?
• Current Solutions:o Using synchronization constructs, e.g. Latches and Barrierso Using Thread.sleep
7/17/2011Setac Testing Framework 10
BufferTest: A Test ScheduleBoundedeBuffer(1)ProducerConsumer
Check assertionsGet
Put(4)
Put(5)
Get
4
5
Check assertions
Check assertions
Phase 1
Phase 2
Phase 3
7/17/2011 11Setac Testing Framework
Traditional: BufferTestclass BufferTest {
@Testdef testBuffer() {
val putLatch = new CountDownLatch(2)val getLatch = new CountDownLatch(2)val buf = new BoundedBuffer(1, putLatch, getLatch)
val consumeLatch = new CountDownLatch(1)val consumer = new Consumer(buf, consumeLatch)val producer = new Producer(buf)
//Phase 1consumer ! Consume(1)consumeLatch.await()Thread.sleep(1000)assert(consumer.getState == State.Blocked &&
buf.curSize == 0)
// … }
}
class Consumer(buf: Actor, consumeLatch: CountDownLatch) extends Actor {
var token = -1startoverride def act() = loop {
react {case Consume(count) => consumeLatch.countDown()for (i <- 0 to count-1)
token = (buf !? Get).asInstanceOf[Int]}
}}
7/17/2011Setac Testing Framework 12
Phase 1
consumer producer buffer(1)
Get
Traditional: BufferTest (cont)
class BufferTest {@Testdef testBuffer() {// …//Phase 2producer ! Produce(List(4, 5))putLatch.await()assert(consumer.token == 4 && buf.curSize == 1)
// Phase 3consumer ! Consume(1)getLatch.await()assert(consumer.token == 5 && buf.curSize == 0)
}}
class BoundedBuffer(maxSize: Int, putLatch: CountDownLatch, getLatch: CountDownLatch) extends Actor {var content = new Array[Int](size)var head, tail, curSize = 0startoverride def act() = loop {
react {case Put(x) if (curSize < maxSize) => {content(tail) = x tail = (tail + 1) % size curSize += 1putLatch.countDown()
}case Get if (curSize > 0) => {val r = content(head) head = (head + 1) % size curSize -= 1 reply(r)getLatch.countDown()
}}
}}
7/17/2011Setac Testing Framework 13
Phase 2
consumer producer buffer(1)Put(4)
Put(5)
Get
4
5Phase 3
Traditional BufferTestProblems
• Unreliableo Thread.sleep
• Complexity and Deadlock possibilityo Latches and Barriers
• Costlyo Changing the program under test
7/17/2011Setac Testing Framework 14
Setac• Specify tests with some constraints on the schedule
o Partial order of schedule messageso Centralized schedule, less complexity
• Checking assertions when the system is stableo There is no message that can be processedo No progress in the system
• No change in the run time environment
• Minimal changes in the program under test
7/17/2011Setac Testing Framework 15
Setac: BufferTestclass BufferTest extends SetacTest {@Testdef testBuffer() {val buf = new BoundedBuffer(1)val consumer = new Consumer(buf)val producer = new Producer(buf)val put4 = createScheduleMessage(producer, buf, Put(4))val put5 = createScheduleMessage(producer, buf, Put(5))val gets = createMultipleScheduleMessage(2, consumer, buf, Get)
producer ! Produce(List(4, 5))consumer ! Consume(2)
//Phase 1setSchedule(gets(0))assertWhenStable(consumer.isBlocked && buf.curSize == 0)
//Phase 2setSchedule(put4 -> put5)assertWhenStable(consumer.token == 4 && buf.curSize == 1 && put5.isProcessed)
// Phase 3setSchedule(gets(1))assertWhenStable(consumer.token == 5 && buf.curSize == 0)
}}
7/17/2011Setac Testing Framework 16
Setac: BufferTest (cont)class BoundedBuffer(size: Int) extends Actor {
// …}
class Consumer(buf: Actor) extends Actor {
// …}
class Producer(buf: Actor) extends Actor {
// …}
7/17/2011Setac Testing Framework 17
TestActor {
TestActor {
TestActor {
Setac: BufferTest• Removed Thread.sleep
o More reliable
• Removed synchronization constructs, e.g. Latcheso Reduced complexity
• Minimized changes in the program under test
7/17/2011Setac Testing Framework 18
Setac APIs• Follows widely used style for unit testing
o Integrated with Junito Refined over time based on examples
• Checking assertions when system is stable
• Total and partial order of schedule messages
• Status of Test messageso Processed, delivered, etc.
• Status of actorso Mail box: Number of messages, content of mail box, etc.o Execution Status: Blocked, Running, Suspended
7/17/2011Setac Testing Framework 19
Limitations• Handling non-stable systems
o receiveWithin, reactWithin, etc. in a loopo Use assertAfter API in Setac
• Managing anonymous actorso E.g., Futures
• Handling actors whose source code is not available
7/17/2011Setac Testing Framework 20
Future Work• Integrate Setac with Scala actor library
o To eliminate the required change in the program under testo To remove some of its limitations
• Extend the solution for Akka actors
• Integrate with ScalaTest
• Evaluate Setac on larger number of exampleso Speed, expressiveness
7/17/2011Setac Testing Framework 21
Conclusions• Large number of schedules in actor programs
o Only some schedules might be important to test
• It is non-trivial to force specific schedule and check assertions at appropriate timeso Sleeps - unreliableo Latches – hard to write/reado Changes in the program under test- high cost
• Setaco Reliableo Easy to writeo Very minimal changes in the program under test
• Open for collaborationo Please try it: http://mir.cs.illinois.edu/setac/
7/17/2011Setac Testing Framework 22