Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • lara/cs206-demos
  • gcharles/cs206-demos
  • gambhir/cs206-demos
3 results
Show changes
package midterm22
import org.junit.*
import org.junit.Assert.*
import instrumentation.*
class Part6Test:
@Test(expected = classOf[AssertionError])
def testQuestion21() =
TestHelper.testManySchedules(
2,
scheduler =>
val ticketsManager = ScheduledTicketsManager(1, scheduler)
(
List(
() =>
// Thread 1
ticketsManager.getTicket(),
() =>
// Thread 2
ticketsManager.getTicket()
),
results =>
if ticketsManager.remainingTickets < 0 then
(false, "Sold more tickets than available!")
else (true, "")
)
)
class ScheduledTicketsManager(totalTickets: Int, val scheduler: Scheduler)
extends TicketsManager(totalTickets)
with MockedMonitor
package midterm22
import org.junit.*
import org.junit.Assert.*
import instrumentation.*
class Part7Test:
@Test
def testNicManagerSequential() =
val nicsManager = NICManager(4)
assertEquals((0, 1), nicsManager.assignNICs())
assertEquals((2, 3), nicsManager.assignNICs())
@Test
def testQuestion22() =
testNicManagerParallel(2, 3)
@Test
def testQuestion23() =
val nicsManager = NICManager(2)
// Thread 1
assertEquals((0, 1), nicsManager.assignNICs())
nicsManager.nics(0).assigned = false
nicsManager.nics(1).assigned = false
// Thread 2
assertEquals((0, 1), nicsManager.assignNICs())
nicsManager.nics(0).assigned = false
nicsManager.nics(1).assigned = false
@Test
def testQuestion24() =
testNicManagerParallel(3, 2, true)
@Test
def testQuestion24NotLimitingRecvNICs() =
TestUtils.assertMaybeDeadlock(
testNicManagerParallel(3, 2)
)
def testNicManagerParallel(
threads: Int,
nics: Int,
limitRecvNICs: Boolean = false
) =
TestHelper.testManySchedules(
threads,
scheduler =>
val nicsManager = ScheduledNicsManager(nics, scheduler)
val tasks =
for i <- 0 until threads yield () =>
// Thread i
val (recvNIC, sendNIC) = nicsManager.assignNICs(limitRecvNICs)
// Do something with NICs...
// Un-assign NICs
nicsManager.nics(recvNIC).assigned = false
nicsManager.nics(sendNIC).assigned = false
(
tasks.toList,
results =>
if nicsManager.nics.count(_.assigned) != 0 then
(false, f"All NICs should have been released.")
else (true, "")
)
)
class ScheduledNicsManager(n: Int, scheduler: Scheduler)
extends NICManager(n):
class ScheduledNIC(
_index: Int,
_assigned: Boolean,
val scheduler: Scheduler
) extends NIC(_index, _assigned)
with MockedMonitor:
override def index = scheduler.exec { super.index }(
"",
Some(res => f"read NIC.index == $res")
)
override def assigned = scheduler.exec { super.assigned }(
"",
Some(res => f"read NIC.assigned == $res")
)
override def assigned_=(v: Boolean) =
scheduler.exec { super.assigned = v }(
f"write NIC.assigned = $v"
)
override val nics =
(for i <- 0 until n yield ScheduledNIC(i, false, scheduler)).toList
package midterm22
import org.junit.*
import org.junit.Assert.*
import instrumentation.*
import annotation.nowarn
import scala.collection.concurrent.TrieMap
import scala.collection.concurrent.{TrieMap, Map}
class Part8Test:
@Test
def usage() =
val insta = Instagram()
assertEquals(1, insta.add())
assertEquals(2, insta.add())
insta.follow(1, 2)
assertEquals(insta.graph, Map(1 -> List(2), 2 -> List()))
insta.follow(2, 1)
insta.unfollow(1, 2)
assertEquals(insta.graph, Map(1 -> List(), 2 -> List(1)))
insta.follow(3, 1) // fails silently
assertEquals(insta.graph, Map(1 -> List(), 2 -> List(1)))
insta.remove(1)
assertEquals(insta.graph, Map(2 -> List()))
insta.unfollow(1, 2) // fails silently
@Test
def testParallelFollowABRemoveA() =
TestHelper.testManySchedules(
2,
scheduler =>
val insta = new Instagram:
override val graph =
ScheduledTrieMap(TrieMap[Int, List[Int]](), scheduler)
val u1 = insta.add()
val u2 = insta.add()
(
List(
() =>
// Thread 1
insta.follow(u1, u2),
() =>
// Thread 2
insta.remove(u1)
),
results =>
val size = insta.graph.size
if size != 1 then
(false, f"Wrong number of user: expected 1 but got ${size}")
else validateGraph(insta)
)
)
@Test
def testParallelFollowABRemoveB() =
TestHelper.testManySchedules(
2,
scheduler =>
val insta = new Instagram:
override val graph =
ScheduledTrieMap(TrieMap[Int, List[Int]](), scheduler)
val u1 = insta.add()
val u2 = insta.add()
(
List(
() =>
// Thread 1
insta.follow(u1, u2),
() =>
// Thread 2
insta.remove(u2)
),
results =>
val size = insta.graph.size
if size != 1 then
(false, f"Wrong number of user: expected 1 but got ${size}")
else validateGraph(insta)
)
)
@Test
def testParallelFollowACRemoveB() =
TestHelper.testManySchedules(
2,
scheduler =>
val insta = new Instagram:
override val graph =
ScheduledTrieMap(TrieMap[Int, List[Int]](), scheduler)
val u1 = insta.add()
val u2 = insta.add()
val u3 = insta.add()
insta.follow(u1, u2)
(
List(
() =>
// Thread 1
insta.follow(u1, u3),
() =>
// Thread 2
insta.remove(u2)
),
results =>
val size = insta.graph.size
if size != 2 then
(false, f"Wrong number of user: expected 2 but got ${size}")
else validateGraph(insta)
)
)
@Test
def testParallelFollow() =
TestHelper.testManySchedules(
2,
scheduler =>
val insta = new Instagram:
override val graph =
ScheduledTrieMap(TrieMap[Int, List[Int]](), scheduler)
val u1 = insta.add()
val u2 = insta.add()
val u3 = insta.add()
(
List(
() =>
// Thread 1
insta.follow(u1, u2),
() =>
// Thread 2
insta.follow(u1, u3)
),
results =>
val u1FollowingSize = insta.graph(u1).size
if u1FollowingSize != 2 then
(
false,
f"Wrong number of users followed by user 1: expected 2 but got ${u1FollowingSize}"
)
else validateGraph(insta)
)
)
@Test
def testParallelRemove() =
TestHelper.testManySchedules(
2,
scheduler =>
val insta = new Instagram:
override val graph =
ScheduledTrieMap(TrieMap[Int, List[Int]](), scheduler)
// Setup
val u1 = insta.add()
val u2 = insta.add()
val u3 = insta.add()
insta.follow(u1, u2)
insta.follow(u2, u1)
insta.follow(u2, u3)
insta.follow(u3, u1)
(
List(
() =>
// Thread 1
insta.remove(u2),
() =>
// Thread 2
insta.remove(u3)
),
results =>
val size = insta.graph.size
if size != 1 then
(false, f"Wrong number of user: expected 1 but got ${size}")
else validateGraph(insta)
)
)
// We test wrong code here, so we expect an assertion error. You can replace
// the next line by `@Test` if you want to see the error with the failing
// schedule.
@Test(expected = classOf[AssertionError])
def testParallelWrongAdd() =
TestHelper.testManySchedules(
2,
scheduler =>
val insta = new Instagram:
override val graph =
ScheduledTrieMap(TrieMap[Int, List[Int]](), scheduler)
// This implementation of `add` is wrong, because two threads might
// allocate the same id.
// Consider the following schedule:
// T1: res = 1
// T2: res = 2
// T2: graph.update(2, Nil)
// T2: 2
// T1: graph.update(2, Nil)
// T1: 2
override def add(): Int =
val res = maxId.incrementAndGet
graph.update(maxId.get, Nil)
res
(
List(
() =>
// Thread 1
insta.add(),
() =>
// Thread 2
insta.add()
),
results =>
if results(0) != results(1) then
(false, f"Allocated twice id ${results(0)}")
else validateGraph(insta)
)
)
// We test wrong code here, so we expect an assertion error. You can replace
// the next line by `@Test` if you want to see the error with the failing
// schedule.
@Test(expected = classOf[AssertionError])
def testParallelWrongRemove() =
TestHelper.testManySchedules(
2,
scheduler =>
val insta = new Instagram:
override val graph =
ScheduledTrieMap(TrieMap[Int, List[Int]](), scheduler)
// This implementation of `remove` is wrong because we don't retry to
// call `graph.replace` when it fails. Therefore, user 1 might end up
// following user 2 that has been removed, or not following user 3
// which is concurrently followed.
override def remove(idToRemove: Int): Unit =
graph.remove(idToRemove)
for (key, value) <- graph do
graph.replace(key, value, value.filter(_ != idToRemove))
// Note: writing `graph(key) = value.filter(_ != idToRemove)` would also
// be wrong because it does not check the previous value.
// Therefore, it could erase a concurrent update.
val u1 = insta.add()
val u2 = insta.add()
val u3 = insta.add()
insta.follow(u1, u2)
(
List(
() =>
// Thread 1
insta.follow(u1, u3),
() =>
// Thread 2
insta.remove(u2)
),
results =>
val size = insta.graph.size
if insta.graph(u1).size != 1 then
(
false,
f"Wrong number of users followed by 1: expected 1 but got ${insta.graph(u1)}"
)
else validateGraph(insta)
)
)
// We test wrong code here, so we expect an assertion error. You can replace
// the next line by `@Test` if you want to see the error with the failing
// schedule.
@Test(expected = classOf[AssertionError])
def testParallelWrongUnfollow() =
TestHelper.testManySchedules(
2,
scheduler =>
val insta = new Instagram:
override val graph =
ScheduledTrieMap(TrieMap[Int, List[Int]](), scheduler)
override def unfollow(a: Int, b: Int): Unit =
if !graph.contains(a) then return
val prev = graph(a) // Might throw java.util.NoSuchElementException
if !graph.replace(a, prev, prev.filter(_ != b)) then unfollow(a, b)
val u1 = insta.add()
val u2 = insta.add()
insta.follow(u1, u2)
(
List(
() =>
// Thread 1
insta.unfollow(u1, u2),
() =>
// Thread 2
insta.remove(u1)
),
results =>
val size = insta.graph.size
if size != 1 then
(false, f"Wrong number of user: expected 1 but got ${size}")
else validateGraph(insta)
)
)
@nowarn
def validateGraph(insta: Instagram): (Boolean, String) =
for (a, following) <- insta.graph; b <- following do
if !insta.graph.contains(b) then
return (false, f"User $a follows non-existing user $b")
(true, "")
final class ScheduledIterator[T](
private val myIterator: Iterator[T],
private val scheduler: Scheduler
) extends Iterator[T]:
override def hasNext =
myIterator.hasNext
override def next() =
scheduler.exec(myIterator.next)("", Some(res => f"Iterator.next == $res"))
override def knownSize: Int =
myIterator.knownSize
final class ScheduledTrieMap[K, V](
private val myMap: Map[K, V],
private val scheduler: Scheduler
) extends Map[K, V]:
override def apply(key: K): V =
scheduler.exec(myMap(key))(
"",
Some(res => f"TrieMap.apply($key) == $res")
)
override def contains(key: K): Boolean =
scheduler.exec(myMap.contains(key))(
"",
Some(res => f"TrieMap.contains($key) == $res")
)
override def get(key: K): Option[V] =
scheduler.exec(myMap.get(key))(
"",
Some(res => f"TrieMap.get($key) == $res")
)
override def addOne(kv: (K, V)) =
scheduler.exec(myMap.addOne(kv))(f"TrieMap.addOne($kv)")
this
override def subtractOne(k: K) =
scheduler.exec(myMap.subtractOne(k))(f"TrieMap.subtractOne($k)")
this
override def iterator() =
scheduler.log("TrieMap.iterator")
ScheduledIterator(myMap.iterator, scheduler)
override def replace(k: K, v: V): Option[V] =
scheduler.exec(myMap.replace(k, v))(
"",
Some(res => f"TrieMap.replace($k, $v) == $res")
)
override def replace(k: K, oldvalue: V, newvalue: V): Boolean =
scheduler.exec(myMap.replace(k, oldvalue, newvalue))(
"",
Some(res => f"TrieMap.replace($k, $oldvalue, $newvalue) == $res")
)
override def putIfAbsent(k: K, v: V): Option[V] =
scheduler.exec(myMap.putIfAbsent(k, v))(
"",
Some(res => f"TrieMap.putIfAbsent($k, $v)")
)
override def remove(k: K): Option[V] =
scheduler.exec(myMap.remove(k))(
"",
Some(res => f"TrieMap.remove($k)")
)
override def remove(k: K, v: V): Boolean =
scheduler.exec(myMap.remove(k, v))(
"",
Some(res => f"TrieMap.remove($k, $v)")
)
package midterm23
import instrumentation.*
import java.util.concurrent.atomic.AtomicInteger
class BarberShopSolutionTest extends munit.FunSuite:
val implementations =
Map[String, (Int, Scheduler) => ScheduledBarberShopSolution](
"BarberShopSolution1" -> (new BarberShopSolution1(_)
with ScheduledBarberShopSolution(_)),
"BarberShopSolution2" -> (new BarberShopSolution2(_)
with ScheduledBarberShopSolution(_)),
"BarberShopSolution3" -> (new BarberShopSolution3(_)
with ScheduledBarberShopSolution(_))
)
for (name, makeShop) <- implementations do
for nCustomers <- 1 to 3 do
test(f"$name with $nCustomers customer(s)") {
testBarberShop(nCustomers, makeShop)
}
package midterm23
import instrumentation.{MockedMonitor, Monitor, Scheduler}
import java.util.concurrent.ConcurrentLinkedQueue
import scala.jdk.CollectionConverters.IterableHasAsScala
import scala.annotation.tailrec
trait ScheduledBarberShopSolution(scheduler0: Scheduler)
extends AbstractBarberShopSolution:
enum Event:
case HairCut
case CustomerLeaves
val trace = ConcurrentLinkedQueue[Event]()
override def notifyBarber = scheduler0.exec { super.notifyBarber }(
"",
Some(res => f"read notifyBarber == $res")
)
override def notifyBarber_=(v: Boolean) =
scheduler0.exec { super.notifyBarber = v }(
f"write notifyBarber = $v"
)
override def notifyCustomer = scheduler0.exec { super.notifyCustomer }(
"",
Some(res => f"read notifyCustomer == $res")
)
override def notifyCustomer_=(v: Boolean) =
scheduler0.exec { super.notifyCustomer = v }(
f"write notifyCustomer = $v"
)
override def hairCut() =
log("hair cut")
trace.add(Event.HairCut)
def customerTrace(n: Int): Unit =
this.customer(n)
log("customer leaves")
trace.add(Event.CustomerLeaves)
private val _lockObj = new MockedMonitor:
override def scheduler: Scheduler = scheduler0
override def lockObj: Monitor = _lockObj
override def log(s: String): Unit = scheduler0.log(s)
def nHaircuts: Int =
trace.asScala.filter(_ == Event.HairCut).size
def customerLeftEarly(): Boolean =
@tailrec
def loop(it: Iterable[Event], n: Int): Boolean =
if it.isEmpty then false
else
val newN = it.head match
case Event.HairCut => n + 1
case Event.CustomerLeaves => n - 1
if newN < 0 then true
else loop(it.tail, newN)
loop(trace.asScala, 0)
package midterm23
import instrumentation.{Scheduler, TestHelper}
/** Tests a barber shop implementation.
*
* @param nCustomers
* The number of customers to simulate.
* @param makeShop
* A function that creates a barber shop given the number of customers and a
* scheduler.
* @param checkEarlyCustomer
* If true, checks that no customer left early, i.e. that there is always a
* number of terminated customer threads equal or less than to the number of
* haircuts done.
*/
def testBarberShop(
nCustomers: Int,
makeShop: (Int, Scheduler) => ScheduledBarberShopSolution,
checkEarlyCustomer: Boolean = true
) =
TestHelper.testManySchedules(
nCustomers + 1,
scheduler =>
val barberShop = makeShop(nCustomers, scheduler)
(
(() => barberShop.barber())
:: (for i <- 1 to nCustomers
yield () => barberShop.customerTrace(i)).toList,
results =>
if barberShop.nHaircuts != nCustomers then
(false, f"Unexpected number of hair cuts: ${barberShop.nHaircuts}")
else if checkEarlyCustomer && barberShop.customerLeftEarly() then
(false, f"A customer left early")
else
(true, "")
)
)