// A prototype implementation of the subsystems mechanism for provably safe exception handling -- test suite // Bart Jacobs and Frank Piessens , 2008 // http://www.cs.kuleuven.be/~bartj/subsystems // Last update: 2008-03-20 import subsystems._ import SubsystemsPredef._ object TestSuite { def main(args: Array[String]): unit = { println("The test suite passes iff it does not complete abruptly with an exception.") // not in a subsystem test1(null) test2() test3() // in a subsystem { val s0 = new Subsystem s0 enter { test1(s0) test2() test3() } } test4() } // The basic enter - try - enter scenario def test1(parent: Subsystem): unit = { // Subsystem creation { val s0 = new Subsystem assert(s0.parent == parent) assert(!s0.hasFailed) s0.checkNotFailed } { val s0 = new Subsystem val s1 = new Subsystem(s0) assert(s1.parent == s0) assert(!s0.hasFailed) s0.checkNotFailed assert(!s1.hasFailed) s1.checkNotFailed } // enter: normal path { val s0 = new Subsystem var ok: boolean = false s0 enter { ok = true } assert(ok) assert(!s0.hasFailed) s0.checkNotFailed } // enter: normal path: two times { val s0 = new Subsystem; { var ok: boolean = false s0 enter { ok = true } assert(ok) } { var ok: boolean = false s0 enter { ok = true } assert(ok) } assert(!s0.hasFailed) s0.checkNotFailed } // enter: body completes abruptly { val s0 = new Subsystem val ex = new RuntimeException try { s0 enter { throw ex } assert(false) } catch { case e => assert(e == ex) } assert(s0.hasFailed) try { s0.checkNotFailed assert(false) } catch { case e => assert(e.isInstanceOf[SubsystemException]) } } // enter: attempt to enter a failed subsystem { val s0 = new Subsystem var notOk: boolean = false val e = new RuntimeException() try { s0 enter { throw e } } catch { case ex => assert(ex == e) } try { s0 enter { notOk = true } assert(false) } catch { case ex => assert(ex.isInstanceOf[SubsystemException]) assert(!notOk) assert(s0.hasFailed) } } // try-catch: normal path { val s0 = new Subsystem var ok: boolean = false var s1: Subsystem = null s0 enter { doTry { s1 = Subsystem.current ok = true } doCatch { case e => assert(false) } } assert(s1 != s0) assert(s1.parent == s0) assert(ok) } // try-catch: try block completes abruptly { val s0 = new Subsystem val e = new RuntimeException() var ok: boolean = false var s1: Subsystem = null var s2: Subsystem = null var ex: Throwable = null s0 enter { doTry { s1 = Subsystem.current throw e } doCatch { case exc => ex = exc s2 = Subsystem.current ok = true } } assert(ok) assert(ex == e) assert(s2 == s0) assert(s1.hasFailed) assert(!s0.hasFailed) } // try-catch: catch block completes abruptly { val s0 = new Subsystem var s1: Subsystem = null val ex = new RuntimeException try { s0 enter { doTry { s1 = Subsystem.current throw new RuntimeException } doCatch { case e => throw ex } assert(false) } assert(false) } catch { case e => assert(e == ex) assert(s0.hasFailed) assert(s1.hasFailed) } } // try-catch and nested enter: normal path { val s0 = new Subsystem var ok: boolean = false var s1: Subsystem = null var s2: Subsystem = null var notOk: boolean = false s0 enter { doTry { s1 = Subsystem.current s0 enter { s2 = Subsystem.current ok = true } } doCatch { case e => notOk = true } } assert(ok) assert(!notOk) assert(!s0.hasFailed) assert(!s1.hasFailed) assert(s2 == s0) } // try-catch and nested enter: body of nested enter completes abruptly { val s0 = new Subsystem var s1: Subsystem = null val e = new RuntimeException var notOk: boolean = false try { s0 enter { doTry { s1 = Subsystem.current s0 enter { throw e } notOk = true } doCatch { case ex => notOk = true } notOk = true } assert(false) } catch { case ex => assert(ex.isInstanceOf[SubsystemException]) assert(!notOk) assert(s0.hasFailed) assert(s1.hasFailed) } } } // The basic multithreading scenario def test2(): unit = { import scala.concurrent.ops._ import scala.concurrent.Lock // Monitors perform mutual exclusion { val m1 = new Monitor @volatile var x: int = 0 var ok1: boolean = false var ok2: boolean = false par({ m1 enter { x = 1; Thread.sleep(100); ok1 = (x == 1) } }, { m1 enter { x = 2; Thread.sleep(100); ok2 = (x == 2) } }) assert(ok1) assert(ok2) } // Monitors propagate failure info { val lock = new Lock lock.acquire val s0 = new Subsystem val m1 = new Monitor var ok: boolean = false var ok1: boolean = false var notOk: boolean = false par({ val e = new RuntimeException try { m1 enter { s0 enter { throw e } } } catch { case ex => ok1 = (e == ex) } lock.release }, { lock.acquire try { m1 enter { s0 enter { notOk = true } } } catch { case ex => ok = ex.isInstanceOf[SubsystemException] } }) assert(ok1) assert(!notOk) assert(ok) } } // The basic cleanup scenario def test3(): unit = { // rscope, normal path { var ok: boolean = false rscope { ok = true } assert(ok) } // rscope, exception { val e = new RuntimeException try { rscope { throw e } assert(false) } catch { case ex => assert(ex == e) } } // Cleanup, normal path { val s0 = new Subsystem var ok: boolean = false val d = new Disposable { def dispose(): unit = { ok = true } } var d2: Disposable = null rscope { d2 = s0 allocate { d } } assert(!s0.hasFailed) assert(d2 == d) assert(ok) } // Cleanup, exception after allocation { val s0 = new Subsystem var ok: boolean = false val d = new Disposable { def dispose(): unit = { ok = true } } val e = new RuntimeException try { rscope { s0 allocate { d } throw e } assert(false) } catch { case ex => assert(ex == e) assert(ok) } } // Cleanup, exception during allocation { val s0 = new Subsystem val e = new RuntimeException var ok1: boolean = true try { rscope { s0 allocate { throw e } ok1 = false } assert(false) } catch { case ex => assert(ex == e) assert(ok1) assert(s0.hasFailed) } } // Cleanup, exception during disposal { val s0 = new Subsystem val e = new RuntimeException val d = new Disposable { def dispose(): unit = { throw e } } try { rscope { s0 allocate { d } } assert(false) } catch { case ex => assert(ex == e) assert(s0.hasFailed) } } // Cleanup, two resources, normal path { val s1 = new Subsystem var ok1 = false var ok2 = false val d1 = new Disposable { def dispose(): unit = { ok1 = ok2 } } val s2 = new Subsystem val d2 = new Disposable { def dispose(): unit = { ok2 = !ok1 } } rscope { s1 allocate d1 s2 allocate d2 } assert(ok1 && ok2) assert(!s1.hasFailed) assert(!s2.hasFailed) } // Cleanup, two resources, inner fails { var ok1 = false val s1 = new Subsystem val d1 = new Disposable { def dispose(): unit = { ok1 = true } } val e = new RuntimeException val s2 = new Subsystem val d2 = new Disposable { def dispose(): unit = { throw e } } try { rscope { s1 allocate d1 s2 allocate d2 } assert(false) } catch { case ex => assert(ex == e) assert(s2.hasFailed) assert(!s1.hasFailed) assert(ok1) } } } def test4(): unit = { // Hierarchy, root failure { val s0 = new Subsystem val s1 = new Subsystem(s0) val s11 = new Subsystem(s1) val s12 = new Subsystem(s1) val s2 = new Subsystem(s0) val s21 = new Subsystem(s2) val s3 = new Subsystem(s0) s0.fail(null) assert(s0.hasFailed) assert(s1.hasFailed) assert(s11.hasFailed) assert(s12.hasFailed) assert(s2.hasFailed) assert(s21.hasFailed) assert(s3.hasFailed) } // Hierarchy, non-root failure { val s0 = new Subsystem val s1 = new Subsystem(s0) val s11 = new Subsystem(s1) val s12 = new Subsystem(s1) val s2 = new Subsystem(s0) val s21 = new Subsystem(s2) val s3 = new Subsystem(s0) s1.fail(null) assert(!s0.hasFailed) assert(s1.hasFailed) assert(s11.hasFailed) assert(s12.hasFailed) assert(!s2.hasFailed) assert(!s21.hasFailed) assert(!s3.hasFailed) } // Hierarchy, garbage collection of dead children { val s0 = new Subsystem val maxMem = Runtime.getRuntime().maxMemory() val size = (maxMem / 5).asInstanceOf[int] class BigException extends RuntimeException { val data = new Array[byte](size) } var i = 0 while (i < 10) { // Generate 2 * maxMem worth of dead subsystems val s1 = new Subsystem(s0) s1.fail(new BigException) i += 1 } } // Multiple entries { import scala.concurrent.ops.par val s0 = new Subsystem par({ par({ s0 enter { Thread.sleep(100) } }, { s0 enter { Thread.sleep(100) } }) }, { par({ s0 enter { Thread.sleep(100) } }, { s0 enter { Thread.sleep(100) } }) }) } } }