class: center, bottom, heading-black background-image: url(images/example-based.jpeg)
???
- We basically write property-based tests exclusively
- Property testing is a powerful tool for testing
class: center, bottom, heading-black background-image: url("images/chopsticks.jpg")
???
- Property-based testing is our default
- We have a majority of property-based tests
- Changed my life and the way I test
- I write better code now
class: bottom, left, heading-white background-image: url(images/goal.jpeg)
???
- Why do we test code in the first place?
- I believe we all want to write correct and reliable software
- Safety
- At Ambiata it costs $$$
- But, it's very hard
- Testing is one tool to achieve that
class: bottom, right, heading-black background-image: url(images/example-based.jpeg)
???
- Testing is first line of defence
class: bottom, right, heading-black background-image: url(images/example-based.jpeg)
???
- The hard part is coming up with the examples
- I want to demonstrate now...
class: code
def substring(s: String, i: Int): String
class: code
def substring(s: String, i: Int): String
def testSubstring = {
substring("abc", 1) == "bc"
}
class: code
def substring(s: String, i: Int): String
def testSubstring = {
substring("abc", 1) == "bc"
substring("abc", 3) == ""
}
class: code
def substring(s: String, i: Int): String
def testSubstring = {
substring("abc", 1) == "bc"
substring("abc", 3) == ""
substring("돪돪", 1) == "돪"
}
class: code
def substring(s: String, i: Int): String
def testSubstring = {
substring("abc", 1) == "bc"
substring("abc", 3) == ""
substring("돪돪", 1) == "돪"
substring("abc", 4) == ""
}
class: code
def substring(s: String, i: Int): String
def testSubstring = {
substring("abc", 1) == "bc"
substring("abc", 3) == ""
substring("돪돪", 1) == "돪"
substring("abc", 4) == ""
substring("abc", -1) == "c"
}
???
- How many examples is enough?
class: middle, center
???
- Writing good examples means knowing about the internals of the functions
- Doesn't help with other systems or libraries, or new edge cases
background-image: url(images/computer.jpeg)
???
- Let's get the computer to generate them instead!
class: middle, center
???
- Year 2000!
class: middle
class: middle
class: middle
http://www.quviq.com/volvo-quickcheck/
http://www.quviq.com/volvo-quickcheck/
- 3,000 pages of specifications
- 1,000,000 LOC, 6 suppliers
http://www.quviq.com/volvo-quickcheck/
- 3,000 pages of specifications
- 1,000,000 LOC, 6 suppliers
- 20,000 lines of QuickCheck
- 200 problems
- 100 problems in the standard
- MongoDB
- Cassandra
- RabbitMQ
- Redis
- Riak
- Zookeeper
- VoltDB
- RethinkDB
- Elasticsearch
???
- Kyle Kingsbury
- "Call me maybe"
- Distributed systems are hard
- Documentation is usually wrong
- If you're using one of these DBs go an read them
- Has changed how people test DBs
- Industry standard
- Badge of honour
class: middle, center
31 Languages
???
- Using ScalaCheck, a derivitive of QuickCheck for Scala
- I want to empaphasise, this is not about Scala
- This talk is about property testing
class: top, left, heading-black background-image: url(images/fuzzy.jpg)
???
- Throw random values at code/systems
- Usually for security purposes
class: code
def substring(s: String, i: Int): String
def testSubstring =
substring("abc", 1) == "bc"
class: code
def substring(s: String, i: Int): String
def testSubstring =
substring("abc", 1)
substring("abc", 1) == "bc"
class: code
def substring(s: String, i: Int): String
def testSubstring =
substring("abc", 1)
class: code
generateString s
s
def substring(s: String, i: Int): String
def testSubstring =
generateString { s =>
substring(s, 1)
}
class: code
forAll( )
def forAll[A](Gen[A], A => Prop): Prop
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
substring(s, 1)
}
def forAll[A](Gen[A], A => Prop): Prop
class: code
genString
def genString: Gen[String]
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
substring(s, 1)
}
def forAll[A](Gen[A], A => Prop): Prop
def genString: Gen[String]
???
class: code
genInt i
i
def genInt: Gen[Int]
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
substring(s, i)
}}
def forAll[A](Gen[A], A => Prop): Prop
def genString: Gen[String]
def genInt: Gen[Int]
class: code
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
substring(s, i)
}}
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
substring(s, i)
}}
???
class: code
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
substring(s, i)
}}
! testSubstring: StringIndexOutOfBoundsException
String index out of range: 0
> ARG_0: ""
> ARG_1: 0
class: code
i < s.length ==>
def ==>(p: Boolean, Prop): Prop
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
i < s.length ==>
substring(s, i)
}}
def ==>(p: Boolean, Prop): Prop
class: code
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
i < s.length ==>
substring(s, i)
}}
! testSubstring: StringIndexOutOfBoundsException
String index out of range: -1
> ARG_0: ""
> ARG_1: -1
class: code
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
i >= 0 && i < s.length ==>
substring(s, i)
}}
class: code
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
i >= 0 && i < s.length ==>
substring(s, i)
}}
+ HelloWorld.substring: OK, passed 100 tests.
???
- Same test found different edge cases (bugs?)
- Learned about substring
class: code
withMinSuccessfulTests(1000)
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
i >= 0 && i < s.length ==>
substring(s, i)
}}
withMinSuccessfulTests(1000)
+ HelloWorld.substring: OK, passed 1000 tests.
???
- We might lower the number of tests for databases
- Test doesn't change
- Could increase for a different build
class: code
substring(s, i) == ???
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
i >= 0 && i < s.length ==>
substring(s, i) == ???
}}
???
- This is where things get tricky
- Know from personal experience
- End up rewriting
class: code
substring(s, i) == substringAgain(s, i)
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
i >= 0 && i < s.length ==>
substring(s, i) == substringAgain(s, i)
}}
???
- Frustrating
- Actually not the idea!
class: center
???
- Patterns
- Not just writing list sorting functions
- Can be hard to get started
class: middle, center
class: code
def toBytes(s: String): Array[Byte]
def fromBytes(b: Array[Bytes]): String
class: code
forAll(genString) { s =>
val b = toBytes(s)
fromBytes(b) == s
}
def toBytes(s: String): Array[Byte]
def fromBytes(b: Array[Bytes]): String
forAll(genString) { s =>
val b = toBytes(s)
fromBytes(b) == s
}
class: code
forAll(genString) { s =>
val b = toBytes(s)
fromBytes(b) == s
}
def toBytes(s: String): Array[Byte]
def fromBytes(b: Array[Bytes]): String
forAll(genString) { s =>
val b = toBytes(s)
fromBytes(b) == s
}
"돪" != "?"
> ARG_0: "돪"
class: code
c: Charset
c: Charset
genCharset
def toBytes(s: String, c: Charset): Array[Byte]
def fromBytes
(b: Array[Bytes], c: Charset): String
forAll(genString) { s =>
forAll(genCharset) { c =>
val b = toBytes(s, c)
fromBytes(b, c) == s
}}
class: code
c: Charset
c: Charset
genCharset
def toBytes(s: String, c: Charset): Array[Byte]
def fromBytes
(b: Array[Bytes], c: Charset): String
forAll(genString) { s =>
forAll(genCharset) { c =>
val b = toBytes(s, c)
fromBytes(b, c) == s
}}
"돪" != "?"
> ARG_0: "돪"
> ARG_1: windows-1252
class: code
import org.joda.time._
forAll(genDateTime) { dt =>
val formatter = DateTimeFormat.fullDateTime()
val s = formatter.print(dt)
formatter.parseDateTime(s) == dt
}
???
- Symmetrical
class: code
import org.joda.time._
forAll(genDateTime) { dt =>
val formatter = DateTimeFormat.fullDateTime()
val s = formatter.print(dt)
formatter.parseDateTime(s) == dt
}
Invalid format:
"Sunday, September 22, 2148 9:08:08 PM ART"
is malformed at "ART"
class: code
def toJson(user: User): Json
def fromJson(json: Json): Option[User]
class: code
def toJson(user: User): Json
def fromJson(json: Json): Option[User]
forAll(genUser) { user =>
val json = toJson(user)
fromJson(json) == Some(user)
}
class: code
def toJson(user: User): Json
def fromJson(json: Json): Option[User]
forAll(genUser) { user =>
val json = toJson(user)
fromJson(json) == Some(user)
}
2015-01-01T12:57:39.123456
!= 2015-01-01T12:57:39.123
???
- haskell/aeson#240
- Output UTCTime to microsecond precision
class: code
def insertUser(u: User): UserId
def getUser(u: UserId): Option[User]
class: code
def insertUser(u: User): UserId
def getUser(u: UserId): Option[User]
forAll(genUser) { user =>
val id = insertUser(user)
getUser(id) == Some(user)
}
class: code
def insertUser(u: User): UserId
def getUser(u: UserId): Option[User]
forAll(genUser) { user =>
val id = insertUser(user)
getUser(id) == Some(user)
}
Some(User(\NULL)) != Some(User())
???
- Date/Double precision
class: bottom, right, heading-white background-image: url(images/edges.jpeg)
???
- Discovered edge cases in our libraries/systems
- Only wrote one test
class: middle, center
class: code
def timSort(l: List[Int]): List[Int]
class: code
def timSort(l: List[Int]): List[Int]
forAll(genList(genInt)) { l =>
timSort(l) == bubbleSort(l)
}
class: code
def timSort(l: List[Int]): List[Int]
forAll(genList(genInt)) { l =>
timSort(l) == bubbleSort(l)
}
https://bugs.openjdk.java.net/browse/JDK-8072909
TimSort fails with ArrayIndexOutOfBounds
on worst case long arrays
class: code
def listUsersSortByName: List[User] =
"SELECT * FROM user ORDER BY name ASC"
class: code
forAll(genList(genUser)) { users =>
users.foreach(u => insertUser(u))
listUsersSortByName ==
users.sortBy(_.name)
}
def listUsersSortByName: List[User] =
"SELECT * FROM user ORDER BY name ASC"
forAll(genList(genUser)) { users =>
users.foreach(u => insertUser(u))
listUsersSortByName ==
users.sortBy(_.name)
}
class: code
forAll(genList(genUser)) { users =>
users.foreach(u => insertUser(u))
listUsersSortByName ==
users.sortBy(_.name)
}
def listUsersSortByName: List[User] =
"SELECT * FROM user ORDER BY name ASC"
forAll(genList(genUser)) { users =>
users.foreach(u => insertUser(u))
listUsersSortByName ==
users.sortBy(_.name)
}
List("a", "b", "A") != List("a", "A", "b")
class: code
case class Date(value: Int)
class: code
case class Date(value: Int)
def toJoda(date: Date): JodaDate
def fromJoda(date: JodaDate): Date
class: code
case class Date(value: Int)
def toJoda(date: Date): JodaDate
def fromJoda(date: JodaDate): Date
forAll { d: Date =>
d.toJoda.fromJoda == d
}
class: code
def dayPlus(d: Date, i: Int): Date = {
...
}
class: code
def dayPlus(d: Date, i: Int): Date = {
...
}
forAll { (d: Date, i: Int) =>
dayPlus(d, i) ==
d.toJoda.plusDays(i).fromJoda
}
class: code
def dayPlus(d: Date, i: Int): Date = {
...
}
forAll { (d: Date, i: Int) =>
dayPlus(d, i) ==
d.toJoda.plusDays(i).fromJoda
}
Date(2004,2,29) != Date(2004,3,1)
ARG_0: Date(2004,2,28)
ARG_1: 1
class: bottom, left, heading-white background-image: url(images/property-based.jpeg)
class: center, middle
class: code
forAll(genList(genInt)) { s =>
l.distinct.distinct == l.distinct
}
class: center
fn foo() {
#[cfg(target_os = "freertos")]
match port_id {
fn foo() {
#[cfg(target_os = "freertos")]
#[cfg(target_os = "freertos")]
match port_id {
We do test for idempotency by running the 'target' of every test through rustfmt as a 'source'. Obviously, this only catches bugs if there is a relevant test.
???
We do test for idempotency by running the 'target' of every test through rustfmt as a 'source'. Obviously, this only catches bugs if there is a relevant test.
class: top, center, heading-black background-image: url(images/rebuild.jpg)
???
class: code
def testSubstring =
forAll(genString) { s =>
forAll(genInt) { i =>
substring(s, i) == ???
}}
def substring(s: String, i: Int): String
class: code
def testSubstring =
forAll(genString) { s =>
forAll(genString) { t =>
substring(s + t, s.length) == t
}}
def substring(s: String, i: Int): String
class: code
def testSubstring =
forAll(genString) { s =>
forAll(genString) { t =>
substring(s + t, s.length) == t
}}
def substring(s: String, i: Int): String
def testSubstring =
forAll(genString) { s =>
forAll(genString) { t =>
substring(s + t, s.length) == t
}}
substring("a" + "bc", "a".length) == "bc"
substring("xy" + "z", "xy".length) == "z"
???
class: center, middle, heading-black
???
- Need to be combined with multiple properties to test the entire function
class: code
forAll(genString) { s =>
s.toLowerCase.length == s.length
}
class: code
forAll(genString) { s =>
s.toLowerCase.length == s.length
}
Expected 2 but got 1
ARG_0: "İ"
Class: right, top, heading-white background-image: url(images/shrink.jpeg)
class: code
forAll(genString) { s =>
s.toLowerCase.length == s.length
}
class: code
forAll(genString) { s =>
s.toLowerCase.length == s.length
}
! testLowerCase: Falsified after 89 passed tests.
Expected 15 but got 14
ARG_0: "()*@#%KFPSDlİDcx;lk1&(#"
class: code
forAll(genString) { s =>
s.toLowerCase.length == s.length
}
! testLowerCase: Falsified after 89 passed tests.
Expected 15 but got 14
ARG_0: "İ"
ARG_0_ORIGINAL: "()*@#%KFPSDlİDcx;lk1&(#"
class: code
forAll(genInt) { i =>
i % 2 == 0
}
class: code
forAll(genInt) { i =>
i % 2 == 0
}
ARG_0: 1
ARG_0_ORIGINAL: 481771087
class: code
forAll(genInt) { i =>
println(i)
i % 2 == 0
}
class: code
forAll(genInt) { i =>
println(i)
i % 2 == 0
}
15936 -15936 23904 -23904 27888 -27888 29880
-29880 30876 -30876 31374 -31374 31623 0 15812
-15812 23718 -23718 27671 0 13836 -13836 20754
-20754 24213 0 12107 0 6054 -6054 9081 0 4541
0 2271 0 1136 -1136 1704 -1704 1988 -1988 2130
-2130 2201 0 1101 0 551 0 276 -276 414 -414 483
0 242 -242 363 0 182 -182 273 0 137 0 69 0 35
0 18 -18 27 0 14 -14 21 0 11 0 6 -6 9 0 5 0 1
class: center, middle, heading-white background-image: url(images/generators.jpeg)
???
- Investment!
class: code
def testInsertUser =
insertUser("bob")
def insertUser(user: String)
class: code
def testInsertUser =
forAll(genString) { user =>
insertUser(user)
}
class: code
def testInsertUser =
forAll(genString) { user =>
insertUser(user)
}
! testInsertUser:
Name cannot be blank
> ARG_0: ""
class: code
user != "" ==>
def testInsertUser =
forAll(genString) { user =>
user != "" ==>
insertUser(user)
}
class: code
user != "" ==>
def testInsertUser =
forAll(genString) { user =>
user != "" ==>
insertUser(user)
}
! testInsertUser:
Name cannot contain spaces
> ARG_0: "a b"
class: code
user != "" && !user.contains(" ") ==>
def testInsertUser =
forAll(genString) { user =>
user != "" && !user.contains(" ") ==>
insertUser(user)
}
class: code
user != "" && !user.contains(" ") && ... ==>
def testInsertUser =
forAll(genString) { user =>
user != "" && !user.contains(" ") && ... ==>
insertUser(user)
}
class: code
user != "" && !user.contains(" ") && ... ==>
def testInsertUser =
forAll(genString) { user =>
user != "" && !user.contains(" ") && ... ==>
insertUser(user)
}
Gave up after only 15 passed tests.
85 tests were discarded.
class: code
genList1(choose('a', 'z')
def choose(a: Char, b: Char): Gen[Char]
def genList1[A](g: Gen[A]): Gen[List[A]]
def testInsertUser =
forAll(genList1(choose('a', 'z'))) { user =>
insertUser(user)
}
def choose(a: Char, b: Char): Gen[Char]
def genList1[A](g: Gen[A]): Gen[List[A]]
class: code
genUser
def genUser: Gen[String] =
genList1(choose('a', 'z'))
def testInsertUser =
forAll(genUser) { user =>
insertUser(user)
}
def genUser: Gen[String] =
genList1(choose('a', 'z'))
class: code
genUser
def genUser: Gen[String] =
genList1(choose('a', 'z'))
genUser
def testInsertUser =
forAll(genUser) { user =>
insertUser(user)
}
def genUser: Gen[String] =
genList1(choose('a', 'z'))
def testDeleteUser =
forAll(genUser) { user =>
deleteUser(user)
}
class: code
genUser
def genUser: Gen[User] =
for {
name <- genList1(choose('a', 'z'))
age <- choose(0, 100)
} yield User(name, age)
def testInsertUser =
forAll(genUser) { user =>
insertUser(user)
}
def genUser: Gen[User] =
for {
name <- genList1(choose('a', 'z'))
age <- choose(0, 100)
} yield User(name, age)
class: middle, center
???
class: code
"null"
class: code
def testMigration =
migrateUser("bob")
def testMigrationNull =
migrateUser("null")
???
- Only tests one code-path
class: code
def testMigration =
forAll(genUser) { user =>
migrateUser(user)
}
def genUser: Gen[String] =
genList1(choose('a', 'z'))
???
- Ideally you don't have to add any more tests
class: code
genFrequency(
1 -> genConst("null")
)
def testMigration =
forAll(genUser) { user =>
migrateUser(user)
}
def genUser: Gen[String] =
genFrequency(
19 -> genList1(choose('a', 'z'))
, 1 -> genConst("null")
)
class: bottom, right, heading-white background-image: url(images/state-based.jpeg)
???
- "State of the art"
- Unit test vs integration testing
- Generate the whole test
class: code
forAll(genUser) { user =>
val id = insertUser(user)
getUser(id) == Some(user)
}
def insertUser(u: User): UserId
def getUser(u: UserId): Option[User]
forAll(genUser) { user =>
val id = insertUser(user)
getUser(id) == Some(user)
}
class: code
forAll(genUser) { user =>
forAll(genUser) { user2 =>
val id = insertUser(user)
val id2 = insertUser(user2)
getUser(id) == Some(user)
}
def insertUser(u: User): UserId
def getUser(u: UserId): Option[User]
forAll(genUser) { user =>
forAll(genUser) { user2 =>
val id = insertUser(user)
val id2 = insertUser(user2)
getUser(id) == Some(user)
}
class: code
forAll(genUser) { user =>
forAll(genUser) { user2 =>
val id = insertUser(user)
val id2 = insertUser(user2)
getUser(id) == Some(user)
deleteUser(id)
getUser(id) == None
}}
def insertUser(u: User): UserId
def getUser(u: UserId): Option[User]
forAll(genUser) { user =>
forAll(genUser) { user2 =>
val id = insertUser(user)
val id2 = insertUser(user2)
getUser(id) == Some(user)
deleteUser(id)
getUser(id) == None
}}
class: middle, center
class: code
case class State(users: Map[UserId, User])
class: code
case class State(users: Map[UserId, User])
case class Insert(user: User) { .. }
class: code
case class State(users: Map[UserId, User])
case class Insert(user: User) extend Commands {
def run: Result =
...
}
class: code
case class State(users: Map[UserId, User])
case class Insert(user: User) extend Commands {
def run: Result =
...
def nextState(state: State): State =
...
}
class: code
case class State(users: Map[UserId, User])
case class Insert(user: User) extend Commands {
def run: Result =
...
def nextState(state: State): State =
...
def postCondition(s: State, r: Result): Prop =
...
}
class: code
case class State(users: Map[UserId, User])
case class Insert(user: User) { .. }
case class Get(id: UserId) { .. }
class: code
case class State(users: Map[UserId, User])
case class Insert(user: User) { .. }
case class Get(id: UserId) { .. }
def genCommand: Gen[Command] =
oneOf(Insert(..), Get(..))
class: code
case class State(users: Map[UserId, User])
case class Insert(user: User) { .. }
case class Get(id: UserId) { .. }
! Falsified after 11 passed tests.
Expected User("a") but got User("b")
Steps:
Insert(User("a"))
Insert(User("b"))
Get(1)
class: middle, center
http://www.quviq.com/google-leveldb/
???
- LevelDB is a fast key-value storage C++ library written at Google that provides an ordered mapping from string keys to string values.
class: code, thinner
1. open new database
2. put key1 and val1
3. close database
4. open database
5. delete key2
6. delete key1
7. close database
4. open database
5. delete key2
6. delete key1
7. close database
11. open database
12. put key3 and val1
13. close database
14. open database
15. close database
16. open database
17. seek first
???
- 17 steps.
- THEN 33 steps
https://groups.google.com/forum/#!topic/leveldb/gnQEgMhxZAs
- Was in 2013
class: middle, center
???
- Concurrency is hard!
- Testint concurrent programmers is really hard!!!
class: code
def property(threadCount: Int): Prop
class: code
def property(threadCount: Int): Prop
! transfer: Falsified after 9 passed tests.
Expected User("a") but got User("b")
Sequential:
1. Insert(User("a")) => 1
2. Insert(User("b")) => 2
Parallel:
1. Get(1) => User("a")
2. Get(1) => User("b")
class: middle
???
class: bottom, left, heading-white background-image: url(images/goal.jpeg)
class: bottom, right, heading-black background-image: url(images/example-based.jpeg)
background-image: url(images/computer.jpeg)
class: top, left, heading-black background-image: url(images/fuzzy.jpg)
class: center, middle, heading-white
class: center, middle
class: center, middle
class: top, center, heading-black background-image: url(images/rebuild.jpg)
class: right, top, heading-white background-image: url(images/shrink.jpeg)
class: center, middle, heading-white background-image: url(images/generators.jpeg)
class: bottom, right, heading-white background-image: url(images/state-based.jpeg)
class: middle, center
31 Languages
???
- Call to arms
- Can introduce gradually
- Fuzzing
- Not a framework
- One test at a time
- Property testing is not specific to one language
- It's an idea
- I would write my own if I had to
https://github.com/pholser/junit-quickcheck
@RunWith(JUnitQuickcheck.class)
public class StringProperties {
@Property
public void testSubstring(String s1, String s2) {
assertEquals((s1 + s2).substring(s1.length), s2);
}
}
https://github.com/jsverify/jsverify
jsc.property("substring", jsc.string, jsc.string,
function (s1, s2) {
return (s1 + s2).substring(s1.length) === s2;
}
);
https://hypothesis.readthedocs.io/
@given(text(), text())
def test_substring(s1, s2):
assert substring(s1 + s2, len(s1)) == s2
???
- Hypothesis is particularly good
- Good community
class: bottom, right, heading-white background-image: url(images/edges.jpeg)
???
- Basically the same across different langauges
- No longer feels like you're writing tests for test sake
- Enjoyable tot think about your system invariants
- Live longer than other tests
- File multiple bugs with one test
- Less code
class: bottom, left, heading-white background-image: url(images/property-based.jpeg)
class: bottom, left, heading-white background-image: url(images/property-based.jpeg)
???
- Find better bugs
- "Choosing properties for property-based testing"
- John Hughes - "Testing the Hard Stuff and Staying Sane"
- @charlesofarrell