SlideShare a Scribd company logo
1 of 107
Download to read offline
Testing stateful,
concurrent, and
async systems
using test.check
Eric Normand
Outline
• Example-based testing is inadequate
• Generating test data
• Generating sequential tests
• Adding parallelism
A stateful example
• A key-value database
• Operations:
(db/create)
(db/clear! db)
(db/store! db k v)
(db/delete! db k)
(db/fetch db k)
(db/size db)
For the video and transcript
of this presentation,
click here:
https://lispcast.com/testing-stateful-and-concurrent-syste
ms-using-test-check/
Let’s test it!
DB should contain a key/value
after storing
(deftest store-contains
)
DB should contain a key/value
after storing
(deftest store-contains
(let [db (db/create)
k “a”
v “b”]
))
DB should contain a key/value
after storing
(deftest store-contains
(let [db (db/create)
k “a”
v “b”]
(db/store! db k v)
))
DB should contain a key/value
after storing
(deftest store-contains
(let [db (db/create)
k “a”
v “b”]
(db/store! db k v)
(db/fetch db k) ))
DB should contain a key/value
after storing
(deftest store-contains
(let [db (db/create)
k “a”
v “b”]
(db/store! db k v)
(is (= v (db/fetch db k)))))
store! should overwrite old
values
(deftest store-overwrite
)
store! should overwrite old
values
(deftest store-overwrite
(let [db (db/create)
k “a”
v1 “b”
v2 “c”]
))
store! should overwrite old
values
(deftest store-overwrite
(let [db (db/create)
k “a”
v1 “b”
v2 “c”]
(db/store! db k v1)
(db/store! db k v2)
))
store! should overwrite old
values
(deftest store-overwrite
(let [db (db/create)
k “a”
v1 “b”
v2 “c”]
(db/store! db k v1)
(db/store! db k v2)
(db/fetch db k) ))
store! should overwrite old
values
(deftest store-overwrite
(let [db (db/create)
k “a”
v1 “b”
v2 “c”]
(db/store! db k v1)
(db/store! db k v2)
(is (= v2 (db/fetch db k)))))
DB should be empty after
clearing
(deftest clear-empty
)
DB should be empty after
clearing
(deftest clear-empty
(let [db (db/create)
k “a”
v “b”]
))
DB should be empty after
clearing
(deftest clear-empty
(let [db (db/create)
k “a”
v “b”]
(db/store! db k v)
(db/clear! db)
))
DB should be empty after
clearing
(deftest clear-empty
(let [db (db/create)
k “a”
v “b”]
(db/store! db k v)
(db/clear! db)
(db/size db) ))
DB should be empty after
clearing
(deftest clear-empty
(let [db (db/create)
k “a”
v “b”]
(db/store! db k v)
(db/clear! db)
(is (zero? (db/size db)))))
I don’t want you to
feel bad, but . . .
you should feel bad
about these tests.
Not guilty, but scared.
How big is our system?
• How many strings are there?
• How many unicode characters are there?
• How many key-value pairs are there?
• How many operations are there?
• How many pairs of operations are there?
• How many triples of operations are there?
The description of the
database is small.
Let’s set up some
generators
(def gen-key gen/string)
(def gen-value gen/string)
> (gen/sample gen-key 20)
("" "Ï" "Û" "Ù" "þ7Ä" "Ì" "§" "þ8zÈäè" "" "Õ" "¢
öU÷¸" "Wb^è÷¯Ð" "ngz|µþÒW." "ño" "ô>,
ß¡wA,r!" ";ÊѲãÔ9" "þèIà0TzJÜbi" "ó¥è¬#À&ö"
ÈjF#" "u=?" "´ö")
Some easy properties…
DB should contain a key/value
after storing
(defspec store-contains 100
)
DB should contain a key/value
after storing
(defspec store-contains 100
(prop/for-all [k gen-key
v gen-value]
))
DB should contain a key/value
after storing
(defspec store-contains 100
(prop/for-all [k gen-key
v gen-value]
(let [db (db/create)]
)))
DB should contain a key/value
after storing
(defspec store-contains 100
(prop/for-all [k gen-key
v gen-value]
(let [db (db/create)]
(db/store! db k v)
)))
DB should contain a key/value
after storing
(defspec store-contains 100
(prop/for-all [k gen-key
v gen-value]
(let [db (db/create)]
(db/store! db k v)
(= v (db/fetch db k)))))
store! should overwrite old
values
(defspec store-overwrite 100
)
store! should overwrite old
values
(defspec store-overwrite 100
(prop/for-all [k gen-key
v1 gen-value
v2 gen-value]
))
store! should overwrite old
values
(defspec store-overwrite 100
(prop/for-all [k gen-key
v1 gen-value
v2 gen-value]
(let [db (db/create)]
)))
store! should overwrite old
values
(defspec store-overwrite 100
(prop/for-all [k gen-key
v1 gen-value
v2 gen-value]
(let [db (db/create)]
(db/store! db k v1)
(db/store! db k v2)
)))
store! should overwrite old
values
(defspec store-overwrite 100
(prop/for-all [k gen-key
v1 gen-value
v2 gen-value]
(let [db (db/create)]
(db/store! db k v1)
(db/store! db k v2)
)))
store! should overwrite old
values
(defspec store-overwrite 100
(prop/for-all [k gen-key
v1 gen-value
v2 gen-value]
(let [db (db/create)]
(db/store! db k v1)
(db/store! db k v2)
(= v2 (db/fetch db k)))))
DB should be empty after
clearing
(defspec clear-empty 100
(prop/for-all [k gen-key
v gen-value]
(let [db (db/create)]
(db/store! db k v)
(db/clear! db)
(zero? (db/size db)))))
{:result false,
:test-var “store-contains",
:failing-size 28,
:num-tests 29,
:fail ["æ]qÜ"̹±W¿þZ¢Ëµgä>Å " “õZãºí®"],
:shrunk {:total-nodes-visited 139,
:depth 33,
:result false,
:smallest ["æ" ""]},
:seed 1489522410083}
Can we describe the behavior
in one go?
1.Build a simple, pure model
• A key-value database is like a hash map.
2. Reify the operations and
make generators
(def gen-clear (gen/return [:clear!]))
(def gen-size (gen/return [:size]))
(def gen-store (gen/tuple (gen/return :store!)
gen-key
gen-value))
(def gen-delete (gen/tuple (gen/return :delete!)
gen-key))
(def gen-fetch (gen/tuple (gen/return :fetch)
gen-key))
(def gen-ops (gen/vector
(gen/one-of [gen-clear gen-store
gen-delete gen-fetch
gen-size])))
> (gen/sample gen-ops)
([]
[[:clear!]]
[]
[[:fetch "wº"] [:clear!]]
[[:clear!] [:fetch "*QZü"] [:clear!] [:fetch “¤'"]]
[[:size] [:size] [:delete! “K]¨j"]]
[]
[[:fetch “t"]]
[[:fetch "§6"] [:size] [:size] [:clear!]]
[[:fetch "P/7¹"] [:store! "Þ=" ""] [:delete! "Â"]
[:store! "B" "¬Ê'y"]])
3. Make 2 “runners”
(defn db-run [db ops]
)
3. Make 2 “runners”
(defn db-run [db ops]
(doseq [[op k v] ops]
))
3. Make 2 “runners”
(defn db-run [db ops]
(doseq [[op k v] ops]
(case op
)))
3. Make 2 “runners”
(defn db-run [db ops]
(doseq [[op k v] ops]
(case op
:clear! (db/clear! db)
)))
3. Make 2 “runners”
(defn db-run [db ops]
(doseq [[op k v] ops]
(case op
:clear! (db/clear! db)
)))
3. Make 2 “runners”
(defn db-run [db ops]
(doseq [[op k v] ops]
(case op
:clear! (db/clear! db)
:size (db/size db)
)))
3. Make 2 “runners”
(defn db-run [db ops]
(doseq [[op k v] ops]
(case op
:clear! (db/clear! db)
:size (db/size db)
:store! (db/store! db k v)
:delete! (db/delete! db k)
:fetch (db/fetch db k))))
3. Make 2 “runners”
(defn hm-run [db ops]
(reduce
(fn [hm [op k v]]
)
db ops))
3. Make 2 “runners”
(defn hm-run [db ops]
(reduce
(fn [hm [op k v]]
(case op
))
db ops))
3. Make 2 “runners”
(defn hm-run [db ops]
(reduce
(fn [hm [op k v]]
(case op
:clear! {}
))
db ops))
3. Make 2 “runners”
(defn hm-run [db ops]
(reduce
(fn [hm [op k v]]
(case op
:clear! {}
:size hm
))
db ops))
3. Make 2 “runners”
(defn hm-run [db ops]
(reduce
(fn [hm [op k v]]
(case op
:clear! {}
:size hm
:store! (assoc hm k v)
))
db ops))
3. Make 2 “runners”
(defn hm-run [db ops]
(reduce
(fn [hm [op k v]]
(case op
:clear! {}
:size hm
:store! (assoc hm k v)
:delete! (dissoc hm k)
))
db ops))
3. Make 2 “runners”
(defn hm-run [db ops]
(reduce
(fn [hm [op k v]]
(case op
:clear! {}
:size hm
:store! (assoc hm k v)
:delete! (dissoc hm k)
:fetch hm))
db ops))
4.Define your property
(defspec hash-map-equiv 100
(prop/for-all [ops gen-ops]
))
4.Define your property
(defspec hash-map-equiv 100
(prop/for-all [ops gen-ops]
(let [hm (hm-run {} ops)
db (db/create)]
)))
4.Define your property
(defspec hash-map-equiv 100
(prop/for-all [ops gen-ops]
(let [hm (hm-run {} ops)
db (db/create)]
(db-run db ops)
)))
4.Define your property
(defspec hash-map-equiv 100
(prop/for-all [ops gen-ops]
(let [hm (hm-run {} ops)
db (db/create)]
(db-run db ops)
(equiv? db hm))))
4.Define your property
(defn equiv? [db hm]
)
(defspec hash-map-equiv 100
(prop/for-all [ops gen-ops]
(let [hm (hm-run {} ops)
db (db/create)]
(db-run db ops)
(equiv? db hm))))
4.Define your property
(defn equiv? [db hm]
(and (= (count hm) (db/size db))
))
(defspec hash-map-equiv 100
(prop/for-all [ops gen-ops]
(let [hm (hm-run {} ops)
db (db/create)]
(db-run db ops)
(equiv? db hm))))
4.Define your property
(defn equiv? [db hm]
(and (= (count hm) (db/size db))
(every? (fn [[k v]]
(= v (db/fetch db k)))
hm)))
(defspec hash-map-equiv 100
(prop/for-all [ops gen-ops]
(let [hm (hm-run {} ops)
db (db/create)]
(db-run db ops)
(equiv? db hm))))
Encourage collisions
(def gen-clear (gen/return [:clear!]))
(def gen-size (gen/return [:size]))
(defn gen-store [keys]
(gen/tuple (gen/return :store!) (gen/elements keys) gen-value))
(defn gen-delete [keys]
(gen/tuple (gen/return :delete!) (gen/elements keys)))
(defn gen-fetch [keys]
(gen/tuple (gen/return :fetch) (gen/elements keys)))
(defn gen-ops* [keys]
(gen/vector
(gen/one-of [gen-clear (gen-store keys)
(gen-delete keys) (gen-fetch keys)
gen-size])))
(def gen-ops (gen/let [keys (gen/not-empty (gen/vector gen-key))]
(gen-ops* keys))
> (gen/sample gen-ops)
([]
[]
[[:fetch ""] [:size]]
[[:fetch "w?"] [:clear!]]
[[:delete! “Z¿"]]
[[:fetch "ü"] [:fetch "ü"] [:size] [:clear!]]
[]
[[:store! ")Á@" "k"] [:store! ")Á@" "c"] [:fetch "G,"]
[:clear!]]
[[:clear!]]
[[:store! "¸w·§" "ý}"] [:clear!] [:fetch " `¢þ"]
[:fetch "Ï*]p®_"] [:delete! " `¢þ"]])
> (apply max (map count (gen/sample gen-ops 100)))
91
> (apply max (map count (gen/sample gen-ops 1000)))
96
> (Math/pow 5 91)
4.038967834731581E63
{:result false,
:test-var "hash-map-equiv",
:seed 1489523387287,
:failing-size 26,
:num-tests 27,
:fail [[[:delete! "9kൾl!9ÞÀglÐÅF æy8ì)"] [:size] [:delete! "
re¯Ú²<×/Ho^|üç6lÉÊ"] [:clear!] [:size] [:clear!] [:delete! "t_®cWuPû"] [:size] [:clear!] [:fetch
"B·7{ÎÔ"] [:clear!] [:size] [:delete! "¥?t·íÂfZ"] [:clear!] [:clear!] [:delete! "Ê5Þí§­
uÉVzÓâH½ëì¥W#6"] [:size] [:size] [:size] [:delete! "ku"] [:delete! "Me±àÜJzCw ²²¾Yþ¬£~£ü¡ÁQWtU"]
[:delete! "9kൾl!9ÞÀglÐÅF æy8ì)"] [:delete! "ËKssy¸Îe>°Æµ¶qí3
sAËx¹føtdB§¦"] [:size] [:store! "V%tCÀx0û{½¼ô5·zØUy¦nåöþ§,òUCdGÁBl" "²ìÒ¿¸Ø
1Ö¹z¥­²fl">GÌ?2 ¹­3¢ÚÔ<¹°A·ç"] [:clear!] [:clear!] [:clear!] [:fetch "è²Ú'ÃË·Ý÷-Þ
(äkÿ2bß'FdóN²O£ÂH×"] [:delete! "Z¹5ýuá`&v"] [:store! "¥?t·íÂfZ" ""] [:store! "ËKssy¸Îe>°Æµ¶qí3
sAËx¹føtdB§¦" "rUmfûR¢)2õ´Ùß rÄ«ûbåÙ¶~±9"] [:delete! "ö§åRÁj÷9Èê³ÈxÎÂÐX³RB""] [:delete!
"t_®cWuPû"] [:clear!] [:delete! "®âl¾Tùwl°|Çi"] [:store! "ÏPtDár"jR
Îm]AÜ°SÊ É?kõfØ«WÌX·tÚ~ßCm0hÉ" "ïIô£·¨²¬xbtatøýóS)¶h)É9FÕ&"] [:fetch "e#ðE"] [:fetch "t_®cWuPû"]
[:delete! "(¼"] [:clear!] [:fetch "ö§åRÁj÷9Èê³ÈxÎÂÐX³RB""] [:fetch "è²Ú'ÃË·Ý÷-Þ(äkÿ2bß'FdóN²O
£ÂH×"] [:delete! "Z¹5ýuá`&v"] [:size] [:store! "úöYø
g¶Òr@¹òÕï" "3£>°Sn"] [:delete! "Oc®ªÄia"] [:store! "}Üòòr" "8ª$8,QøÛN+,Ø£kÐ)°ñ4½THA{öNó÷qñFÇ
W"] [:store! "5BÑþ9¿³'<TáÓBföQ" "×½Ut*Æ#B»GÑÝX¢taN'tºí"]]],
:shrunk {:total-nodes-visited 361,
:depth 162,
:result false,
:smallest [[[:store! "ø" ""]]]}}
49 operations
But what about race
conditions?
Run it in multiple
threads
(defn run-in-thread [db ops]
(.start (Thread. (fn []
(db-run db ops)))))
(defn thread-run [db ops-sequences]
(run! #(run-in-thread db %) ops-sequences))
Wait for them all to
finish
(defn run-in-thread [db ops]
(let [done (promise)]
(.start (Thread. (fn []
(db-run db ops)
(deliver done :done!))))
done))
Wait for them all to
finish
(defn run-in-thread [db ops]
(let [done (promise)]
(.start (Thread. (fn []
(db-run db ops)
(deliver done :done!))))
done))
(defn thread-run [db ops-sequences]
(let [threads (map #(run-in-thread db %)
ops-sequences)]
(dorun threads)
(run! deref threads))
Start all threads at
once(defn run-in-thread [latch db ops]
(let [done (promise)]
(.start (Thread. (fn []
@latch
(db-run db ops)
(deliver done :done!))))
done))
Start all threads at
once(defn run-in-thread [latch db ops]
(let [done (promise)]
(.start (Thread. (fn []
@latch
(db-run db ops)
(deliver done :done!))))
done))
(defn thread-run [db ops-sequences]
(let [latch (promise)
threads (map #(run-in-thread latch db %)
ops-sequences)]
(dorun threads)
(deliver latch :go!)
(run! deref threads)))
Test against the model
(defspec hash-map-equiv 100
)
Test against the model
(defspec hash-map-equiv 100
(prop/for-all [ops-a gen-ops
ops-b gen-ops]
))
Test against the model
(defspec hash-map-equiv 100
(prop/for-all [ops-a gen-ops
ops-b gen-ops]
(let [ops (concat ops-a ops-b)
]
)))
Test against the model
(defspec hash-map-equiv 100
(prop/for-all [ops-a gen-ops
ops-b gen-ops]
(let [ops (concat ops-a ops-b)
hm (hm-run {} ops)
db (db/create)]
)))
Test against the model
(defspec hash-map-equiv 100
(prop/for-all [ops-a gen-ops
ops-b gen-ops]
(let [ops (concat ops-a ops-b)
hm (hm-run {} ops)
db (db/create)]
(thread-run db [ops-a ops-b])
(equiv? db hm))))
Encourage collisions across
threads
(defn gen-ops-sequences [n]
)
Encourage collisions across
threads
(defn gen-ops-sequences [n]
(gen/let [keys (gen/not-empty
(gen/vector gen-key))]
))
Encourage collisions across
threads
(defn gen-ops-sequences [n]
(gen/let [keys (gen/not-empty
(gen/vector gen-key))]
(apply gen/tuple
(repeat n (gen-ops* keys)))))
Collisions
(defspec hash-map-equiv 100
(prop/for-all [[ops-a ops-b]
(gen-ops-sequences 2)]
))
Collisions
(defspec hash-map-equiv 100
(prop/for-all [[ops-a ops-b]
(gen-ops-sequences 2)]
(let [ops (concat ops-a ops-b)
hm (hm-run {} ops)
db (db/create)]
(thread-run db [ops-a ops-b])
(equiv? db hm))))
1
A B
1
2 2
Time DB
?
1
1
2
2
1
A B
1
2 2
Time DB
1
1
2
2
1
A B
1
2 2
Time DB
1
1
2
2
1
A B
1
2 2
Time DB
1
1
2
2
1
A B
1
2 2
Time DB
1 1
2 2
1 12
2
11
2
2
1 1
2
2
1
1
2
2
1
1
2 2
1
1
2
2
1
1
2
2
1
1 2 2 11
2
2
1
1
2
2
1
1
2
2 1
1
2
2
Possible interleavings
(defn children [{:keys [sequence threads]}]
(for [[k [v & thread]] threads]
{:sequence (conj sequence v)
:threads (if thread
(assoc threads k thread)
(dissoc threads k))}))
(defn branch? [x]
(-> x :threads not-empty))
(defn possible-interleavings [& sequences]
(let [threads (into {} (map vector (range) sequences))]
(->>
(tree-seq branch? children {:sequence [] :threads threads})
(remove branch?)
(map :sequence))))
Equivalent to some possible
interleaving
(defspec hash-map-equiv 100
(prop/for-all [[ops-a ops-b]
(gen-ops-sequences 2)]
(let [ops-i (possible-interleavings ops-a
ops-b)
]
)))
Equivalent to some possible
interleaving
(defspec hash-map-equiv 100
(prop/for-all [[ops-a ops-b]
(gen-ops-sequences 2)]
(let [ops-i (possible-interleavings ops-a
ops-b)
db (db/create)]
(thread-run db [ops-a ops-b])
))))
Equivalent to some possible
interleaving
(defspec hash-map-equiv 100
(prop/for-all [[ops-a ops-b]
(gen-ops-sequences 2)]
(let [ops-i (possible-interleavings ops-a
ops-b)
db (db/create)]
(thread-run db [ops-a ops-b])
(some? #(equiv? db %)
(map #(hm-run {} %) ops-i))))))
Repeatability (every?)
(defspec hash-map-equiv 100
(prop/for-all [[ops-a ops-b]
(gen-ops-sequences 2)]
(let [ops-i (possible-interleavings ops-a
ops-b)]
(every?
(for [_ (range 10)]
(let [db (db/create)]
(thread-run db [ops-a ops-b])
(some? #(equiv? db %)
(map #(hm-run {} %) ops-i)))))))))
A B
[:store! “a” “b”]
[:store! “a” “c”]
[:fetch “”]
[:delete! “”]
[:delete! “”]
[:size]
[:delete! “”]
[:fetch “”]
[:fetch “”]
[:size]
[:fetch “”]
…
[:delete! “a”]
Timing
(def gen-sleep (gen/tuple (gen/return :sleep)
(gen/choose 1 100)))
(defn gen-ops* [keys]
(gen/vector
(gen/one-of [gen-sleep
gen-size
(gen-fetch keys)
(gen-store keys)
(gen-delete keys)
gen-clear])))
DB Runner
(defn db-run [db ops]
(doseq [[op k v] ops]
(case op
:sleep (Thread/sleep k)
:clear! (db/clear! db)
:size (db/size db)
:store! (db/store! db k v)
:delete! (db/delete! db k)
:fetch (db/fetch db k))))
Hash map runner
(defn hm-run [db ops]
(reduce
(fn [hm [op k v]]
(case op
:sleep hm
:clear! {}
:size hm
:store! (assoc hm k v)
:delete! (dissoc hm k)
:fetch hm))
db ops))
A B
[:store! “a” “b”]
[:store! “a” “c”]
[:sleep 66]
[:delete! “a”]
Eric Normand
Follow Eric on:
Eric Normand @EricNormand
eric@lispcast.comlispcast.com

More Related Content

What's hot

Corephpcomponentpresentation 1211425966721657-8
Corephpcomponentpresentation 1211425966721657-8Corephpcomponentpresentation 1211425966721657-8
Corephpcomponentpresentation 1211425966721657-8PrinceGuru MS
 
Adventures in Optimization
Adventures in OptimizationAdventures in Optimization
Adventures in OptimizationDavid Golden
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in actionJace Ju
 
From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)Night Sailer
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday DeveloperRoss Tuck
 
Python Ireland Nov 2010 Talk: Unit Testing
Python Ireland Nov 2010 Talk: Unit TestingPython Ireland Nov 2010 Talk: Unit Testing
Python Ireland Nov 2010 Talk: Unit TestingPython Ireland
 
PHP 7 – What changed internally? (Forum PHP 2015)
PHP 7 – What changed internally? (Forum PHP 2015)PHP 7 – What changed internally? (Forum PHP 2015)
PHP 7 – What changed internally? (Forum PHP 2015)Nikita Popov
 
Damn Fine CoffeeScript
Damn Fine CoffeeScriptDamn Fine CoffeeScript
Damn Fine CoffeeScriptniklal
 
Terraform for fun and profit
Terraform for fun and profitTerraform for fun and profit
Terraform for fun and profitBram Vogelaar
 
Hive data migration (export/import)
Hive data migration (export/import)Hive data migration (export/import)
Hive data migration (export/import)Bopyo Hong
 
Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Kris Wallsmith
 
Web весна 2013 лекция 6
Web весна 2013 лекция 6Web весна 2013 лекция 6
Web весна 2013 лекция 6Technopark
 
Web осень 2012 лекция 6
Web осень 2012 лекция 6Web осень 2012 лекция 6
Web осень 2012 лекция 6Technopark
 
PHP data structures (and the impact of php 7 on them), phpDay Verona 2015, Italy
PHP data structures (and the impact of php 7 on them), phpDay Verona 2015, ItalyPHP data structures (and the impact of php 7 on them), phpDay Verona 2015, Italy
PHP data structures (and the impact of php 7 on them), phpDay Verona 2015, ItalyPatrick Allaert
 
Google guava overview
Google guava overviewGoogle guava overview
Google guava overviewSteve Min
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistenceHugo Hamon
 

What's hot (20)

Corephpcomponentpresentation 1211425966721657-8
Corephpcomponentpresentation 1211425966721657-8Corephpcomponentpresentation 1211425966721657-8
Corephpcomponentpresentation 1211425966721657-8
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
Adventures in Optimization
Adventures in OptimizationAdventures in Optimization
Adventures in Optimization
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 
From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)
 
Backbone.js
Backbone.jsBackbone.js
Backbone.js
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday Developer
 
DBI
DBIDBI
DBI
 
Python Ireland Nov 2010 Talk: Unit Testing
Python Ireland Nov 2010 Talk: Unit TestingPython Ireland Nov 2010 Talk: Unit Testing
Python Ireland Nov 2010 Talk: Unit Testing
 
PHP 7 – What changed internally? (Forum PHP 2015)
PHP 7 – What changed internally? (Forum PHP 2015)PHP 7 – What changed internally? (Forum PHP 2015)
PHP 7 – What changed internally? (Forum PHP 2015)
 
Damn Fine CoffeeScript
Damn Fine CoffeeScriptDamn Fine CoffeeScript
Damn Fine CoffeeScript
 
Terraform for fun and profit
Terraform for fun and profitTerraform for fun and profit
Terraform for fun and profit
 
Hive data migration (export/import)
Hive data migration (export/import)Hive data migration (export/import)
Hive data migration (export/import)
 
Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)Introducing Assetic (NYPHP)
Introducing Assetic (NYPHP)
 
Web весна 2013 лекция 6
Web весна 2013 лекция 6Web весна 2013 лекция 6
Web весна 2013 лекция 6
 
Web осень 2012 лекция 6
Web осень 2012 лекция 6Web осень 2012 лекция 6
Web осень 2012 лекция 6
 
PHP data structures (and the impact of php 7 on them), phpDay Verona 2015, Italy
PHP data structures (and the impact of php 7 on them), phpDay Verona 2015, ItalyPHP data structures (and the impact of php 7 on them), phpDay Verona 2015, Italy
PHP data structures (and the impact of php 7 on them), phpDay Verona 2015, Italy
 
Agile database access with CakePHP 3
Agile database access with CakePHP 3Agile database access with CakePHP 3
Agile database access with CakePHP 3
 
Google guava overview
Google guava overviewGoogle guava overview
Google guava overview
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 

Similar to Testing stateful, concurrent, and async systems using test.check

New in MongoDB 2.6
New in MongoDB 2.6New in MongoDB 2.6
New in MongoDB 2.6christkv
 
Designing a database like an archaeologist
Designing a database like an archaeologistDesigning a database like an archaeologist
Designing a database like an archaeologistyoavrubin
 
Riak at The NYC Cloud Computing Meetup Group
Riak at The NYC Cloud Computing Meetup GroupRiak at The NYC Cloud Computing Meetup Group
Riak at The NYC Cloud Computing Meetup Groupsiculars
 
JavaScript for Flex Devs
JavaScript for Flex DevsJavaScript for Flex Devs
JavaScript for Flex DevsAaronius
 
JIP Pipeline System Introduction
JIP Pipeline System IntroductionJIP Pipeline System Introduction
JIP Pipeline System Introductionthasso23
 
Drupal - dbtng 25th Anniversary Edition
Drupal - dbtng 25th Anniversary EditionDrupal - dbtng 25th Anniversary Edition
Drupal - dbtng 25th Anniversary Editionddiers
 
The Dom Scripting Toolkit J Query
The Dom Scripting Toolkit J QueryThe Dom Scripting Toolkit J Query
The Dom Scripting Toolkit J QueryQConLondon2008
 
High Performance Django 1
High Performance Django 1High Performance Django 1
High Performance Django 1DjangoCon2008
 
High Performance Django
High Performance DjangoHigh Performance Django
High Performance DjangoDjangoCon2008
 
Data driven testing using Integrant & Spec
Data driven testing using Integrant & SpecData driven testing using Integrant & Spec
Data driven testing using Integrant & SpecLeon Mergen
 
Learning Puppet basic thing
Learning Puppet basic thing Learning Puppet basic thing
Learning Puppet basic thing DaeHyung Lee
 
Clojure for Java developers - Stockholm
Clojure for Java developers - StockholmClojure for Java developers - Stockholm
Clojure for Java developers - StockholmJan Kronquist
 
Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...
Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...
Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...BradNeuberg
 
Practical Testing of Ruby Core
Practical Testing of Ruby CorePractical Testing of Ruby Core
Practical Testing of Ruby CoreHiroshi SHIBATA
 

Similar to Testing stateful, concurrent, and async systems using test.check (20)

New in MongoDB 2.6
New in MongoDB 2.6New in MongoDB 2.6
New in MongoDB 2.6
 
Play vs Rails
Play vs RailsPlay vs Rails
Play vs Rails
 
Designing a database like an archaeologist
Designing a database like an archaeologistDesigning a database like an archaeologist
Designing a database like an archaeologist
 
DataMapper
DataMapperDataMapper
DataMapper
 
Lobos Introduction
Lobos IntroductionLobos Introduction
Lobos Introduction
 
Riak at The NYC Cloud Computing Meetup Group
Riak at The NYC Cloud Computing Meetup GroupRiak at The NYC Cloud Computing Meetup Group
Riak at The NYC Cloud Computing Meetup Group
 
Spl Not A Bridge Too Far phpNW09
Spl Not A Bridge Too Far phpNW09Spl Not A Bridge Too Far phpNW09
Spl Not A Bridge Too Far phpNW09
 
JavaScript for Flex Devs
JavaScript for Flex DevsJavaScript for Flex Devs
JavaScript for Flex Devs
 
JIP Pipeline System Introduction
JIP Pipeline System IntroductionJIP Pipeline System Introduction
JIP Pipeline System Introduction
 
Drupal - dbtng 25th Anniversary Edition
Drupal - dbtng 25th Anniversary EditionDrupal - dbtng 25th Anniversary Edition
Drupal - dbtng 25th Anniversary Edition
 
The Dom Scripting Toolkit J Query
The Dom Scripting Toolkit J QueryThe Dom Scripting Toolkit J Query
The Dom Scripting Toolkit J Query
 
High Performance Django 1
High Performance Django 1High Performance Django 1
High Performance Django 1
 
High Performance Django
High Performance DjangoHigh Performance Django
High Performance Django
 
Introduction to Perl and BioPerl
Introduction to Perl and BioPerlIntroduction to Perl and BioPerl
Introduction to Perl and BioPerl
 
Data driven testing using Integrant & Spec
Data driven testing using Integrant & SpecData driven testing using Integrant & Spec
Data driven testing using Integrant & Spec
 
Learning Puppet basic thing
Learning Puppet basic thing Learning Puppet basic thing
Learning Puppet basic thing
 
How to eat Cucmber
How to eat CucmberHow to eat Cucmber
How to eat Cucmber
 
Clojure for Java developers - Stockholm
Clojure for Java developers - StockholmClojure for Java developers - Stockholm
Clojure for Java developers - Stockholm
 
Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...
Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...
Beyond Cookies, Persistent Storage For Web Applications Web Directions North ...
 
Practical Testing of Ruby Core
Practical Testing of Ruby CorePractical Testing of Ruby Core
Practical Testing of Ruby Core
 

More from Eric Normand

You are in a maze of deeply nested maps, all alike
You are in a maze of deeply nested maps, all alikeYou are in a maze of deeply nested maps, all alike
You are in a maze of deeply nested maps, all alikeEric Normand
 
The elements of a functional mindset
The elements of a functional mindsetThe elements of a functional mindset
The elements of a functional mindsetEric Normand
 
All I Needed for Functional Programming I Learned in High School Algebra
All I Needed for Functional Programming I Learned in High School AlgebraAll I Needed for Functional Programming I Learned in High School Algebra
All I Needed for Functional Programming I Learned in High School AlgebraEric Normand
 
Lies My OO Teacher Told Me
Lies My OO Teacher Told MeLies My OO Teacher Told Me
Lies My OO Teacher Told MeEric Normand
 
What is Functional Programming?
What is Functional Programming?What is Functional Programming?
What is Functional Programming?Eric Normand
 
Functional Programming for Business
Functional Programming for BusinessFunctional Programming for Business
Functional Programming for BusinessEric Normand
 
A Theory of Functional Programming LambdUp
A Theory of Functional Programming LambdUpA Theory of Functional Programming LambdUp
A Theory of Functional Programming LambdUpEric Normand
 
Building Composable Abstractions
Building Composable AbstractionsBuilding Composable Abstractions
Building Composable AbstractionsEric Normand
 
ClojureScript: I can't believe this is JavaScript
ClojureScript: I can't believe this is JavaScriptClojureScript: I can't believe this is JavaScript
ClojureScript: I can't believe this is JavaScriptEric Normand
 

More from Eric Normand (9)

You are in a maze of deeply nested maps, all alike
You are in a maze of deeply nested maps, all alikeYou are in a maze of deeply nested maps, all alike
You are in a maze of deeply nested maps, all alike
 
The elements of a functional mindset
The elements of a functional mindsetThe elements of a functional mindset
The elements of a functional mindset
 
All I Needed for Functional Programming I Learned in High School Algebra
All I Needed for Functional Programming I Learned in High School AlgebraAll I Needed for Functional Programming I Learned in High School Algebra
All I Needed for Functional Programming I Learned in High School Algebra
 
Lies My OO Teacher Told Me
Lies My OO Teacher Told MeLies My OO Teacher Told Me
Lies My OO Teacher Told Me
 
What is Functional Programming?
What is Functional Programming?What is Functional Programming?
What is Functional Programming?
 
Functional Programming for Business
Functional Programming for BusinessFunctional Programming for Business
Functional Programming for Business
 
A Theory of Functional Programming LambdUp
A Theory of Functional Programming LambdUpA Theory of Functional Programming LambdUp
A Theory of Functional Programming LambdUp
 
Building Composable Abstractions
Building Composable AbstractionsBuilding Composable Abstractions
Building Composable Abstractions
 
ClojureScript: I can't believe this is JavaScript
ClojureScript: I can't believe this is JavaScriptClojureScript: I can't believe this is JavaScript
ClojureScript: I can't believe this is JavaScript
 

Recently uploaded

College Call Girls Nashik Nehal 7001305949 Independent Escort Service Nashik
College Call Girls Nashik Nehal 7001305949 Independent Escort Service NashikCollege Call Girls Nashik Nehal 7001305949 Independent Escort Service Nashik
College Call Girls Nashik Nehal 7001305949 Independent Escort Service NashikCall Girls in Nagpur High Profile
 
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...ranjana rawat
 
MANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLS
MANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLSMANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLS
MANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLSSIVASHANKAR N
 
Decoding Kotlin - Your guide to solving the mysterious in Kotlin.pptx
Decoding Kotlin - Your guide to solving the mysterious in Kotlin.pptxDecoding Kotlin - Your guide to solving the mysterious in Kotlin.pptx
Decoding Kotlin - Your guide to solving the mysterious in Kotlin.pptxJoão Esperancinha
 
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur EscortsHigh Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur EscortsCall Girls in Nagpur High Profile
 
Processing & Properties of Floor and Wall Tiles.pptx
Processing & Properties of Floor and Wall Tiles.pptxProcessing & Properties of Floor and Wall Tiles.pptx
Processing & Properties of Floor and Wall Tiles.pptxpranjaldaimarysona
 
(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...
(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...
(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...ranjana rawat
 
SPICE PARK APR2024 ( 6,793 SPICE Models )
SPICE PARK APR2024 ( 6,793 SPICE Models )SPICE PARK APR2024 ( 6,793 SPICE Models )
SPICE PARK APR2024 ( 6,793 SPICE Models )Tsuyoshi Horigome
 
OSVC_Meta-Data based Simulation Automation to overcome Verification Challenge...
OSVC_Meta-Data based Simulation Automation to overcome Verification Challenge...OSVC_Meta-Data based Simulation Automation to overcome Verification Challenge...
OSVC_Meta-Data based Simulation Automation to overcome Verification Challenge...Soham Mondal
 
APPLICATIONS-AC/DC DRIVES-OPERATING CHARACTERISTICS
APPLICATIONS-AC/DC DRIVES-OPERATING CHARACTERISTICSAPPLICATIONS-AC/DC DRIVES-OPERATING CHARACTERISTICS
APPLICATIONS-AC/DC DRIVES-OPERATING CHARACTERISTICSKurinjimalarL3
 
HARMONY IN THE NATURE AND EXISTENCE - Unit-IV
HARMONY IN THE NATURE AND EXISTENCE - Unit-IVHARMONY IN THE NATURE AND EXISTENCE - Unit-IV
HARMONY IN THE NATURE AND EXISTENCE - Unit-IVRajaP95
 
(MEERA) Dapodi Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Escorts
(MEERA) Dapodi Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Escorts(MEERA) Dapodi Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Escorts
(MEERA) Dapodi Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Escortsranjana rawat
 
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...ranjana rawat
 
Coefficient of Thermal Expansion and their Importance.pptx
Coefficient of Thermal Expansion and their Importance.pptxCoefficient of Thermal Expansion and their Importance.pptx
Coefficient of Thermal Expansion and their Importance.pptxAsutosh Ranjan
 
Extrusion Processes and Their Limitations
Extrusion Processes and Their LimitationsExtrusion Processes and Their Limitations
Extrusion Processes and Their Limitations120cr0395
 
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130Suhani Kapoor
 
(TARA) Talegaon Dabhade Call Girls Just Call 7001035870 [ Cash on Delivery ] ...
(TARA) Talegaon Dabhade Call Girls Just Call 7001035870 [ Cash on Delivery ] ...(TARA) Talegaon Dabhade Call Girls Just Call 7001035870 [ Cash on Delivery ] ...
(TARA) Talegaon Dabhade Call Girls Just Call 7001035870 [ Cash on Delivery ] ...ranjana rawat
 

Recently uploaded (20)

College Call Girls Nashik Nehal 7001305949 Independent Escort Service Nashik
College Call Girls Nashik Nehal 7001305949 Independent Escort Service NashikCollege Call Girls Nashik Nehal 7001305949 Independent Escort Service Nashik
College Call Girls Nashik Nehal 7001305949 Independent Escort Service Nashik
 
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
(ANVI) Koregaon Park Call Girls Just Call 7001035870 [ Cash on Delivery ] Pun...
 
MANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLS
MANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLSMANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLS
MANUFACTURING PROCESS-II UNIT-5 NC MACHINE TOOLS
 
9953056974 Call Girls In South Ex, Escorts (Delhi) NCR.pdf
9953056974 Call Girls In South Ex, Escorts (Delhi) NCR.pdf9953056974 Call Girls In South Ex, Escorts (Delhi) NCR.pdf
9953056974 Call Girls In South Ex, Escorts (Delhi) NCR.pdf
 
Decoding Kotlin - Your guide to solving the mysterious in Kotlin.pptx
Decoding Kotlin - Your guide to solving the mysterious in Kotlin.pptxDecoding Kotlin - Your guide to solving the mysterious in Kotlin.pptx
Decoding Kotlin - Your guide to solving the mysterious in Kotlin.pptx
 
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur EscortsHigh Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
 
Processing & Properties of Floor and Wall Tiles.pptx
Processing & Properties of Floor and Wall Tiles.pptxProcessing & Properties of Floor and Wall Tiles.pptx
Processing & Properties of Floor and Wall Tiles.pptx
 
(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...
(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...
(SHREYA) Chakan Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Esc...
 
Call Us -/9953056974- Call Girls In Vikaspuri-/- Delhi NCR
Call Us -/9953056974- Call Girls In Vikaspuri-/- Delhi NCRCall Us -/9953056974- Call Girls In Vikaspuri-/- Delhi NCR
Call Us -/9953056974- Call Girls In Vikaspuri-/- Delhi NCR
 
SPICE PARK APR2024 ( 6,793 SPICE Models )
SPICE PARK APR2024 ( 6,793 SPICE Models )SPICE PARK APR2024 ( 6,793 SPICE Models )
SPICE PARK APR2024 ( 6,793 SPICE Models )
 
OSVC_Meta-Data based Simulation Automation to overcome Verification Challenge...
OSVC_Meta-Data based Simulation Automation to overcome Verification Challenge...OSVC_Meta-Data based Simulation Automation to overcome Verification Challenge...
OSVC_Meta-Data based Simulation Automation to overcome Verification Challenge...
 
APPLICATIONS-AC/DC DRIVES-OPERATING CHARACTERISTICS
APPLICATIONS-AC/DC DRIVES-OPERATING CHARACTERISTICSAPPLICATIONS-AC/DC DRIVES-OPERATING CHARACTERISTICS
APPLICATIONS-AC/DC DRIVES-OPERATING CHARACTERISTICS
 
HARMONY IN THE NATURE AND EXISTENCE - Unit-IV
HARMONY IN THE NATURE AND EXISTENCE - Unit-IVHARMONY IN THE NATURE AND EXISTENCE - Unit-IV
HARMONY IN THE NATURE AND EXISTENCE - Unit-IV
 
(MEERA) Dapodi Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Escorts
(MEERA) Dapodi Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Escorts(MEERA) Dapodi Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Escorts
(MEERA) Dapodi Call Girls Just Call 7001035870 [ Cash on Delivery ] Pune Escorts
 
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
 
Coefficient of Thermal Expansion and their Importance.pptx
Coefficient of Thermal Expansion and their Importance.pptxCoefficient of Thermal Expansion and their Importance.pptx
Coefficient of Thermal Expansion and their Importance.pptx
 
Extrusion Processes and Their Limitations
Extrusion Processes and Their LimitationsExtrusion Processes and Their Limitations
Extrusion Processes and Their Limitations
 
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
VIP Call Girls Service Kondapur Hyderabad Call +91-8250192130
 
★ CALL US 9953330565 ( HOT Young Call Girls In Badarpur delhi NCR
★ CALL US 9953330565 ( HOT Young Call Girls In Badarpur delhi NCR★ CALL US 9953330565 ( HOT Young Call Girls In Badarpur delhi NCR
★ CALL US 9953330565 ( HOT Young Call Girls In Badarpur delhi NCR
 
(TARA) Talegaon Dabhade Call Girls Just Call 7001035870 [ Cash on Delivery ] ...
(TARA) Talegaon Dabhade Call Girls Just Call 7001035870 [ Cash on Delivery ] ...(TARA) Talegaon Dabhade Call Girls Just Call 7001035870 [ Cash on Delivery ] ...
(TARA) Talegaon Dabhade Call Girls Just Call 7001035870 [ Cash on Delivery ] ...
 

Testing stateful, concurrent, and async systems using test.check

  • 1. Testing stateful, concurrent, and async systems using test.check Eric Normand
  • 2. Outline • Example-based testing is inadequate • Generating test data • Generating sequential tests • Adding parallelism
  • 3. A stateful example • A key-value database • Operations: (db/create) (db/clear! db) (db/store! db k v) (db/delete! db k) (db/fetch db k) (db/size db)
  • 4. For the video and transcript of this presentation, click here: https://lispcast.com/testing-stateful-and-concurrent-syste ms-using-test-check/
  • 6. DB should contain a key/value after storing (deftest store-contains )
  • 7. DB should contain a key/value after storing (deftest store-contains (let [db (db/create) k “a” v “b”] ))
  • 8. DB should contain a key/value after storing (deftest store-contains (let [db (db/create) k “a” v “b”] (db/store! db k v) ))
  • 9. DB should contain a key/value after storing (deftest store-contains (let [db (db/create) k “a” v “b”] (db/store! db k v) (db/fetch db k) ))
  • 10. DB should contain a key/value after storing (deftest store-contains (let [db (db/create) k “a” v “b”] (db/store! db k v) (is (= v (db/fetch db k)))))
  • 11. store! should overwrite old values (deftest store-overwrite )
  • 12. store! should overwrite old values (deftest store-overwrite (let [db (db/create) k “a” v1 “b” v2 “c”] ))
  • 13. store! should overwrite old values (deftest store-overwrite (let [db (db/create) k “a” v1 “b” v2 “c”] (db/store! db k v1) (db/store! db k v2) ))
  • 14. store! should overwrite old values (deftest store-overwrite (let [db (db/create) k “a” v1 “b” v2 “c”] (db/store! db k v1) (db/store! db k v2) (db/fetch db k) ))
  • 15. store! should overwrite old values (deftest store-overwrite (let [db (db/create) k “a” v1 “b” v2 “c”] (db/store! db k v1) (db/store! db k v2) (is (= v2 (db/fetch db k)))))
  • 16. DB should be empty after clearing (deftest clear-empty )
  • 17. DB should be empty after clearing (deftest clear-empty (let [db (db/create) k “a” v “b”] ))
  • 18. DB should be empty after clearing (deftest clear-empty (let [db (db/create) k “a” v “b”] (db/store! db k v) (db/clear! db) ))
  • 19. DB should be empty after clearing (deftest clear-empty (let [db (db/create) k “a” v “b”] (db/store! db k v) (db/clear! db) (db/size db) ))
  • 20. DB should be empty after clearing (deftest clear-empty (let [db (db/create) k “a” v “b”] (db/store! db k v) (db/clear! db) (is (zero? (db/size db)))))
  • 21. I don’t want you to feel bad, but . . .
  • 22. you should feel bad about these tests.
  • 23. Not guilty, but scared.
  • 24.
  • 25. How big is our system? • How many strings are there? • How many unicode characters are there? • How many key-value pairs are there? • How many operations are there? • How many pairs of operations are there? • How many triples of operations are there?
  • 26. The description of the database is small.
  • 27. Let’s set up some generators (def gen-key gen/string) (def gen-value gen/string) > (gen/sample gen-key 20) ("" "Ï" "Û" "Ù" "þ7Ä" "Ì" "§" "þ8zÈäè" "" "Õ" "¢ öU÷¸" "Wb^è÷¯Ð" "ngz|µþÒW." "ño" "ô>, ß¡wA,r!" ";ÊѲãÔ9" "þèIà0TzJÜbi" "ó¥è¬#À&ö" ÈjF#" "u=?" "´ö")
  • 29. DB should contain a key/value after storing (defspec store-contains 100 )
  • 30. DB should contain a key/value after storing (defspec store-contains 100 (prop/for-all [k gen-key v gen-value] ))
  • 31. DB should contain a key/value after storing (defspec store-contains 100 (prop/for-all [k gen-key v gen-value] (let [db (db/create)] )))
  • 32. DB should contain a key/value after storing (defspec store-contains 100 (prop/for-all [k gen-key v gen-value] (let [db (db/create)] (db/store! db k v) )))
  • 33. DB should contain a key/value after storing (defspec store-contains 100 (prop/for-all [k gen-key v gen-value] (let [db (db/create)] (db/store! db k v) (= v (db/fetch db k)))))
  • 34. store! should overwrite old values (defspec store-overwrite 100 )
  • 35. store! should overwrite old values (defspec store-overwrite 100 (prop/for-all [k gen-key v1 gen-value v2 gen-value] ))
  • 36. store! should overwrite old values (defspec store-overwrite 100 (prop/for-all [k gen-key v1 gen-value v2 gen-value] (let [db (db/create)] )))
  • 37. store! should overwrite old values (defspec store-overwrite 100 (prop/for-all [k gen-key v1 gen-value v2 gen-value] (let [db (db/create)] (db/store! db k v1) (db/store! db k v2) )))
  • 38. store! should overwrite old values (defspec store-overwrite 100 (prop/for-all [k gen-key v1 gen-value v2 gen-value] (let [db (db/create)] (db/store! db k v1) (db/store! db k v2) )))
  • 39. store! should overwrite old values (defspec store-overwrite 100 (prop/for-all [k gen-key v1 gen-value v2 gen-value] (let [db (db/create)] (db/store! db k v1) (db/store! db k v2) (= v2 (db/fetch db k)))))
  • 40. DB should be empty after clearing (defspec clear-empty 100 (prop/for-all [k gen-key v gen-value] (let [db (db/create)] (db/store! db k v) (db/clear! db) (zero? (db/size db)))))
  • 41. {:result false, :test-var “store-contains", :failing-size 28, :num-tests 29, :fail ["æ]qÜ"̹±W¿þZ¢Ëµgä>Å " “õZãºí®"], :shrunk {:total-nodes-visited 139, :depth 33, :result false, :smallest ["æ" ""]}, :seed 1489522410083}
  • 42. Can we describe the behavior in one go?
  • 43. 1.Build a simple, pure model • A key-value database is like a hash map.
  • 44. 2. Reify the operations and make generators (def gen-clear (gen/return [:clear!])) (def gen-size (gen/return [:size])) (def gen-store (gen/tuple (gen/return :store!) gen-key gen-value)) (def gen-delete (gen/tuple (gen/return :delete!) gen-key)) (def gen-fetch (gen/tuple (gen/return :fetch) gen-key)) (def gen-ops (gen/vector (gen/one-of [gen-clear gen-store gen-delete gen-fetch gen-size])))
  • 45. > (gen/sample gen-ops) ([] [[:clear!]] [] [[:fetch "wº"] [:clear!]] [[:clear!] [:fetch "*QZü"] [:clear!] [:fetch “¤'"]] [[:size] [:size] [:delete! “K]¨j"]] [] [[:fetch “t"]] [[:fetch "§6"] [:size] [:size] [:clear!]] [[:fetch "P/7¹"] [:store! "Þ=" ""] [:delete! "Â"] [:store! "B" "¬Ê'y"]])
  • 46. 3. Make 2 “runners” (defn db-run [db ops] )
  • 47. 3. Make 2 “runners” (defn db-run [db ops] (doseq [[op k v] ops] ))
  • 48. 3. Make 2 “runners” (defn db-run [db ops] (doseq [[op k v] ops] (case op )))
  • 49. 3. Make 2 “runners” (defn db-run [db ops] (doseq [[op k v] ops] (case op :clear! (db/clear! db) )))
  • 50. 3. Make 2 “runners” (defn db-run [db ops] (doseq [[op k v] ops] (case op :clear! (db/clear! db) )))
  • 51. 3. Make 2 “runners” (defn db-run [db ops] (doseq [[op k v] ops] (case op :clear! (db/clear! db) :size (db/size db) )))
  • 52. 3. Make 2 “runners” (defn db-run [db ops] (doseq [[op k v] ops] (case op :clear! (db/clear! db) :size (db/size db) :store! (db/store! db k v) :delete! (db/delete! db k) :fetch (db/fetch db k))))
  • 53. 3. Make 2 “runners” (defn hm-run [db ops] (reduce (fn [hm [op k v]] ) db ops))
  • 54. 3. Make 2 “runners” (defn hm-run [db ops] (reduce (fn [hm [op k v]] (case op )) db ops))
  • 55. 3. Make 2 “runners” (defn hm-run [db ops] (reduce (fn [hm [op k v]] (case op :clear! {} )) db ops))
  • 56. 3. Make 2 “runners” (defn hm-run [db ops] (reduce (fn [hm [op k v]] (case op :clear! {} :size hm )) db ops))
  • 57. 3. Make 2 “runners” (defn hm-run [db ops] (reduce (fn [hm [op k v]] (case op :clear! {} :size hm :store! (assoc hm k v) )) db ops))
  • 58. 3. Make 2 “runners” (defn hm-run [db ops] (reduce (fn [hm [op k v]] (case op :clear! {} :size hm :store! (assoc hm k v) :delete! (dissoc hm k) )) db ops))
  • 59. 3. Make 2 “runners” (defn hm-run [db ops] (reduce (fn [hm [op k v]] (case op :clear! {} :size hm :store! (assoc hm k v) :delete! (dissoc hm k) :fetch hm)) db ops))
  • 60. 4.Define your property (defspec hash-map-equiv 100 (prop/for-all [ops gen-ops] ))
  • 61. 4.Define your property (defspec hash-map-equiv 100 (prop/for-all [ops gen-ops] (let [hm (hm-run {} ops) db (db/create)] )))
  • 62. 4.Define your property (defspec hash-map-equiv 100 (prop/for-all [ops gen-ops] (let [hm (hm-run {} ops) db (db/create)] (db-run db ops) )))
  • 63. 4.Define your property (defspec hash-map-equiv 100 (prop/for-all [ops gen-ops] (let [hm (hm-run {} ops) db (db/create)] (db-run db ops) (equiv? db hm))))
  • 64. 4.Define your property (defn equiv? [db hm] ) (defspec hash-map-equiv 100 (prop/for-all [ops gen-ops] (let [hm (hm-run {} ops) db (db/create)] (db-run db ops) (equiv? db hm))))
  • 65. 4.Define your property (defn equiv? [db hm] (and (= (count hm) (db/size db)) )) (defspec hash-map-equiv 100 (prop/for-all [ops gen-ops] (let [hm (hm-run {} ops) db (db/create)] (db-run db ops) (equiv? db hm))))
  • 66. 4.Define your property (defn equiv? [db hm] (and (= (count hm) (db/size db)) (every? (fn [[k v]] (= v (db/fetch db k))) hm))) (defspec hash-map-equiv 100 (prop/for-all [ops gen-ops] (let [hm (hm-run {} ops) db (db/create)] (db-run db ops) (equiv? db hm))))
  • 67.
  • 68.
  • 69. Encourage collisions (def gen-clear (gen/return [:clear!])) (def gen-size (gen/return [:size])) (defn gen-store [keys] (gen/tuple (gen/return :store!) (gen/elements keys) gen-value)) (defn gen-delete [keys] (gen/tuple (gen/return :delete!) (gen/elements keys))) (defn gen-fetch [keys] (gen/tuple (gen/return :fetch) (gen/elements keys))) (defn gen-ops* [keys] (gen/vector (gen/one-of [gen-clear (gen-store keys) (gen-delete keys) (gen-fetch keys) gen-size]))) (def gen-ops (gen/let [keys (gen/not-empty (gen/vector gen-key))] (gen-ops* keys))
  • 70. > (gen/sample gen-ops) ([] [] [[:fetch ""] [:size]] [[:fetch "w?"] [:clear!]] [[:delete! “Z¿"]] [[:fetch "ü"] [:fetch "ü"] [:size] [:clear!]] [] [[:store! ")Á@" "k"] [:store! ")Á@" "c"] [:fetch "G,"] [:clear!]] [[:clear!]] [[:store! "¸w·§" "ý}"] [:clear!] [:fetch " `¢þ"] [:fetch "Ï*]p®_"] [:delete! " `¢þ"]])
  • 71. > (apply max (map count (gen/sample gen-ops 100))) 91 > (apply max (map count (gen/sample gen-ops 1000))) 96 > (Math/pow 5 91) 4.038967834731581E63
  • 72. {:result false, :test-var "hash-map-equiv", :seed 1489523387287, :failing-size 26, :num-tests 27, :fail [[[:delete! "9kൾl!9ÞÀglÐÅF æy8ì)"] [:size] [:delete! " re¯Ú²<×/Ho^|üç6lÉÊ"] [:clear!] [:size] [:clear!] [:delete! "t_®cWuPû"] [:size] [:clear!] [:fetch "B·7{ÎÔ"] [:clear!] [:size] [:delete! "¥?t·íÂfZ"] [:clear!] [:clear!] [:delete! "Ê5Þí§­ uÉVzÓâH½ëì¥W#6"] [:size] [:size] [:size] [:delete! "ku"] [:delete! "Me±àÜJzCw ²²¾Yþ¬£~£ü¡ÁQWtU"] [:delete! "9kൾl!9ÞÀglÐÅF æy8ì)"] [:delete! "ËKssy¸Îe>°Æµ¶qí3 sAËx¹føtdB§¦"] [:size] [:store! "V%tCÀx0û{½¼ô5·zØUy¦nåöþ§,òUCdGÁBl" "²ìÒ¿¸Ø 1Ö¹z¥­²fl">GÌ?2 ¹­3¢ÚÔ<¹°A·ç"] [:clear!] [:clear!] [:clear!] [:fetch "è²Ú'ÃË·Ý÷-Þ (äkÿ2bß'FdóN²O£ÂH×"] [:delete! "Z¹5ýuá`&v"] [:store! "¥?t·íÂfZ" ""] [:store! "ËKssy¸Îe>°Æµ¶qí3 sAËx¹føtdB§¦" "rUmfûR¢)2õ´Ùß rÄ«ûbåÙ¶~±9"] [:delete! "ö§åRÁj÷9Èê³ÈxÎÂÐX³RB""] [:delete! "t_®cWuPû"] [:clear!] [:delete! "®âl¾Tùwl°|Çi"] [:store! "ÏPtDár"jR Îm]AÜ°SÊ É?kõfØ«WÌX·tÚ~ßCm0hÉ" "ïIô£·¨²¬xbtatøýóS)¶h)É9FÕ&"] [:fetch "e#ðE"] [:fetch "t_®cWuPû"] [:delete! "(¼"] [:clear!] [:fetch "ö§åRÁj÷9Èê³ÈxÎÂÐX³RB""] [:fetch "è²Ú'ÃË·Ý÷-Þ(äkÿ2bß'FdóN²O £ÂH×"] [:delete! "Z¹5ýuá`&v"] [:size] [:store! "úöYø g¶Òr@¹òÕï" "3£>°Sn"] [:delete! "Oc®ªÄia"] [:store! "}Üòòr" "8ª$8,QøÛN+,Ø£kÐ)°ñ4½THA{öNó÷qñFÇ W"] [:store! "5BÑþ9¿³'<TáÓBföQ" "×½Ut*Æ#B»GÑÝX¢taN'tºí"]]], :shrunk {:total-nodes-visited 361, :depth 162, :result false, :smallest [[[:store! "ø" ""]]]}} 49 operations
  • 73. But what about race conditions?
  • 74. Run it in multiple threads (defn run-in-thread [db ops] (.start (Thread. (fn [] (db-run db ops))))) (defn thread-run [db ops-sequences] (run! #(run-in-thread db %) ops-sequences))
  • 75. Wait for them all to finish (defn run-in-thread [db ops] (let [done (promise)] (.start (Thread. (fn [] (db-run db ops) (deliver done :done!)))) done))
  • 76. Wait for them all to finish (defn run-in-thread [db ops] (let [done (promise)] (.start (Thread. (fn [] (db-run db ops) (deliver done :done!)))) done)) (defn thread-run [db ops-sequences] (let [threads (map #(run-in-thread db %) ops-sequences)] (dorun threads) (run! deref threads))
  • 77. Start all threads at once(defn run-in-thread [latch db ops] (let [done (promise)] (.start (Thread. (fn [] @latch (db-run db ops) (deliver done :done!)))) done))
  • 78. Start all threads at once(defn run-in-thread [latch db ops] (let [done (promise)] (.start (Thread. (fn [] @latch (db-run db ops) (deliver done :done!)))) done)) (defn thread-run [db ops-sequences] (let [latch (promise) threads (map #(run-in-thread latch db %) ops-sequences)] (dorun threads) (deliver latch :go!) (run! deref threads)))
  • 79. Test against the model (defspec hash-map-equiv 100 )
  • 80. Test against the model (defspec hash-map-equiv 100 (prop/for-all [ops-a gen-ops ops-b gen-ops] ))
  • 81. Test against the model (defspec hash-map-equiv 100 (prop/for-all [ops-a gen-ops ops-b gen-ops] (let [ops (concat ops-a ops-b) ] )))
  • 82. Test against the model (defspec hash-map-equiv 100 (prop/for-all [ops-a gen-ops ops-b gen-ops] (let [ops (concat ops-a ops-b) hm (hm-run {} ops) db (db/create)] )))
  • 83. Test against the model (defspec hash-map-equiv 100 (prop/for-all [ops-a gen-ops ops-b gen-ops] (let [ops (concat ops-a ops-b) hm (hm-run {} ops) db (db/create)] (thread-run db [ops-a ops-b]) (equiv? db hm))))
  • 84.
  • 85. Encourage collisions across threads (defn gen-ops-sequences [n] )
  • 86. Encourage collisions across threads (defn gen-ops-sequences [n] (gen/let [keys (gen/not-empty (gen/vector gen-key))] ))
  • 87. Encourage collisions across threads (defn gen-ops-sequences [n] (gen/let [keys (gen/not-empty (gen/vector gen-key))] (apply gen/tuple (repeat n (gen-ops* keys)))))
  • 88. Collisions (defspec hash-map-equiv 100 (prop/for-all [[ops-a ops-b] (gen-ops-sequences 2)] ))
  • 89. Collisions (defspec hash-map-equiv 100 (prop/for-all [[ops-a ops-b] (gen-ops-sequences 2)] (let [ops (concat ops-a ops-b) hm (hm-run {} ops) db (db/create)] (thread-run db [ops-a ops-b]) (equiv? db hm))))
  • 95. 1 1 2 2 1 12 2 11 2 2 1 1 2 2 1 1 2 2 1 1 2 2 1 1 2 2 1 1 2 2 1 1 2 2 11 2 2 1 1 2 2 1 1 2 2 1 1 2 2
  • 96. Possible interleavings (defn children [{:keys [sequence threads]}] (for [[k [v & thread]] threads] {:sequence (conj sequence v) :threads (if thread (assoc threads k thread) (dissoc threads k))})) (defn branch? [x] (-> x :threads not-empty)) (defn possible-interleavings [& sequences] (let [threads (into {} (map vector (range) sequences))] (->> (tree-seq branch? children {:sequence [] :threads threads}) (remove branch?) (map :sequence))))
  • 97. Equivalent to some possible interleaving (defspec hash-map-equiv 100 (prop/for-all [[ops-a ops-b] (gen-ops-sequences 2)] (let [ops-i (possible-interleavings ops-a ops-b) ] )))
  • 98. Equivalent to some possible interleaving (defspec hash-map-equiv 100 (prop/for-all [[ops-a ops-b] (gen-ops-sequences 2)] (let [ops-i (possible-interleavings ops-a ops-b) db (db/create)] (thread-run db [ops-a ops-b]) ))))
  • 99. Equivalent to some possible interleaving (defspec hash-map-equiv 100 (prop/for-all [[ops-a ops-b] (gen-ops-sequences 2)] (let [ops-i (possible-interleavings ops-a ops-b) db (db/create)] (thread-run db [ops-a ops-b]) (some? #(equiv? db %) (map #(hm-run {} %) ops-i))))))
  • 100. Repeatability (every?) (defspec hash-map-equiv 100 (prop/for-all [[ops-a ops-b] (gen-ops-sequences 2)] (let [ops-i (possible-interleavings ops-a ops-b)] (every? (for [_ (range 10)] (let [db (db/create)] (thread-run db [ops-a ops-b]) (some? #(equiv? db %) (map #(hm-run {} %) ops-i)))))))))
  • 101. A B [:store! “a” “b”] [:store! “a” “c”] [:fetch “”] [:delete! “”] [:delete! “”] [:size] [:delete! “”] [:fetch “”] [:fetch “”] [:size] [:fetch “”] … [:delete! “a”]
  • 102. Timing (def gen-sleep (gen/tuple (gen/return :sleep) (gen/choose 1 100))) (defn gen-ops* [keys] (gen/vector (gen/one-of [gen-sleep gen-size (gen-fetch keys) (gen-store keys) (gen-delete keys) gen-clear])))
  • 103. DB Runner (defn db-run [db ops] (doseq [[op k v] ops] (case op :sleep (Thread/sleep k) :clear! (db/clear! db) :size (db/size db) :store! (db/store! db k v) :delete! (db/delete! db k) :fetch (db/fetch db k))))
  • 104. Hash map runner (defn hm-run [db ops] (reduce (fn [hm [op k v]] (case op :sleep hm :clear! {} :size hm :store! (assoc hm k v) :delete! (dissoc hm k) :fetch hm)) db ops))
  • 105. A B [:store! “a” “b”] [:store! “a” “c”] [:sleep 66] [:delete! “a”]
  • 106.
  • 107. Eric Normand Follow Eric on: Eric Normand @EricNormand eric@lispcast.comlispcast.com