SlideShare a Scribd company logo
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-8
PrinceGuru MS
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
Marcello Duarte
 
Adventures in Optimization
Adventures in OptimizationAdventures in Optimization
Adventures in Optimization
David Golden
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
Jace Ju
 
From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)
Night Sailer
 
Backbone.js
Backbone.jsBackbone.js
Backbone.js
Chris Neale
 
Redis for the Everyday Developer
Redis for the Everyday DeveloperRedis for the Everyday Developer
Redis for the Everyday Developer
Ross Tuck
 
DBI
DBIDBI
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
Python 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 CoffeeScript
niklal
 
Terraform for fun and profit
Terraform for fun and profitTerraform for fun and profit
Terraform for fun and profit
Bram 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 лекция 6
Technopark
 
Web осень 2012 лекция 6
Web осень 2012 лекция 6Web осень 2012 лекция 6
Web осень 2012 лекция 6
Technopark
 
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
Patrick Allaert
 
Agile database access with CakePHP 3
Agile database access with CakePHP 3Agile database access with CakePHP 3
Agile database access with CakePHP 3
José Lorenzo Rodríguez Urdaneta
 
Google guava overview
Google guava overviewGoogle guava overview
Google guava overview
Steve Min
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
Hugo 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.6
christkv
 
Play vs Rails
Play vs RailsPlay vs Rails
Play vs Rails
Daniel Cukier
 
Designing a database like an archaeologist
Designing a database like an archaeologistDesigning a database like an archaeologist
Designing a database like an archaeologist
yoavrubin
 
DataMapper
DataMapperDataMapper
DataMapper
Yehuda Katz
 
Lobos Introduction
Lobos IntroductionLobos Introduction
Lobos Introduction
Nicolas Buduroi
 
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
siculars
 
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
Michelangelo van Dam
 
JavaScript for Flex Devs
JavaScript for Flex DevsJavaScript for Flex Devs
JavaScript for Flex Devs
Aaronius
 
JIP Pipeline System Introduction
JIP Pipeline System IntroductionJIP Pipeline System Introduction
JIP Pipeline System Introduction
thasso23
 
Drupal - dbtng 25th Anniversary Edition
Drupal - dbtng 25th Anniversary EditionDrupal - dbtng 25th Anniversary Edition
Drupal - dbtng 25th Anniversary Edition
ddiers
 
The Dom Scripting Toolkit J Query
The Dom Scripting Toolkit J QueryThe Dom Scripting Toolkit J Query
The Dom Scripting Toolkit J Query
QConLondon2008
 
High Performance Django 1
High Performance Django 1High Performance Django 1
High Performance Django 1
DjangoCon2008
 
High Performance Django
High Performance DjangoHigh Performance Django
High Performance Django
DjangoCon2008
 
Introduction to Perl and BioPerl
Introduction to Perl and BioPerlIntroduction to Perl and BioPerl
Introduction to Perl and BioPerl
Bioinformatics and Computational Biosciences Branch
 
Data driven testing using Integrant & Spec
Data driven testing using Integrant & SpecData driven testing using Integrant & Spec
Data driven testing using Integrant & Spec
Leon Mergen
 
Learning Puppet basic thing
Learning Puppet basic thing Learning Puppet basic thing
Learning Puppet basic thing
DaeHyung Lee
 
How to eat Cucmber
How to eat CucmberHow to eat Cucmber
How to eat Cucmber
Naoki Nishiguchi
 
Clojure for Java developers - Stockholm
Clojure for Java developers - StockholmClojure for Java developers - Stockholm
Clojure for Java developers - Stockholm
Jan 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 Core
Hiroshi 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 alike
Eric Normand
 
The elements of a functional mindset
The elements of a functional mindsetThe elements of a functional mindset
The elements of a functional mindset
Eric 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 Algebra
Eric Normand
 
Lies My OO Teacher Told Me
Lies My OO Teacher Told MeLies My OO Teacher Told Me
Lies My OO Teacher Told Me
Eric 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 Business
Eric Normand
 
A Theory of Functional Programming LambdUp
A Theory of Functional Programming LambdUpA Theory of Functional Programming LambdUp
A Theory of Functional Programming LambdUp
Eric Normand
 
Building Composable Abstractions
Building Composable AbstractionsBuilding Composable Abstractions
Building Composable Abstractions
Eric 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 JavaScript
Eric 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

artificial intelligence and data science contents.pptx
artificial intelligence and data science contents.pptxartificial intelligence and data science contents.pptx
artificial intelligence and data science contents.pptx
GauravCar
 
CompEx~Manual~1210 (2).pdf COMPEX GAS AND VAPOURS
CompEx~Manual~1210 (2).pdf COMPEX GAS AND VAPOURSCompEx~Manual~1210 (2).pdf COMPEX GAS AND VAPOURS
CompEx~Manual~1210 (2).pdf COMPEX GAS AND VAPOURS
RamonNovais6
 
4. Mosca vol I -Fisica-Tipler-5ta-Edicion-Vol-1.pdf
4. Mosca vol I -Fisica-Tipler-5ta-Edicion-Vol-1.pdf4. Mosca vol I -Fisica-Tipler-5ta-Edicion-Vol-1.pdf
4. Mosca vol I -Fisica-Tipler-5ta-Edicion-Vol-1.pdf
Gino153088
 
Use PyCharm for remote debugging of WSL on a Windo cf5c162d672e4e58b4dde5d797...
Use PyCharm for remote debugging of WSL on a Windo cf5c162d672e4e58b4dde5d797...Use PyCharm for remote debugging of WSL on a Windo cf5c162d672e4e58b4dde5d797...
Use PyCharm for remote debugging of WSL on a Windo cf5c162d672e4e58b4dde5d797...
shadow0702a
 
Curve Fitting in Numerical Methods Regression
Curve Fitting in Numerical Methods RegressionCurve Fitting in Numerical Methods Regression
Curve Fitting in Numerical Methods Regression
Nada Hikmah
 
一比一原版(CalArts毕业证)加利福尼亚艺术学院毕业证如何办理
一比一原版(CalArts毕业证)加利福尼亚艺术学院毕业证如何办理一比一原版(CalArts毕业证)加利福尼亚艺术学院毕业证如何办理
一比一原版(CalArts毕业证)加利福尼亚艺术学院毕业证如何办理
ecqow
 
An Introduction to the Compiler Designss
An Introduction to the Compiler DesignssAn Introduction to the Compiler Designss
An Introduction to the Compiler Designss
ElakkiaU
 
AI assisted telemedicine KIOSK for Rural India.pptx
AI assisted telemedicine KIOSK for Rural India.pptxAI assisted telemedicine KIOSK for Rural India.pptx
AI assisted telemedicine KIOSK for Rural India.pptx
architagupta876
 
22CYT12-Unit-V-E Waste and its Management.ppt
22CYT12-Unit-V-E Waste and its Management.ppt22CYT12-Unit-V-E Waste and its Management.ppt
22CYT12-Unit-V-E Waste and its Management.ppt
KrishnaveniKrishnara1
 
Generative AI leverages algorithms to create various forms of content
Generative AI leverages algorithms to create various forms of contentGenerative AI leverages algorithms to create various forms of content
Generative AI leverages algorithms to create various forms of content
Hitesh Mohapatra
 
ITSM Integration with MuleSoft.pptx
ITSM  Integration with MuleSoft.pptxITSM  Integration with MuleSoft.pptx
ITSM Integration with MuleSoft.pptx
VANDANAMOHANGOUDA
 
原版制作(Humboldt毕业证书)柏林大学毕业证学位证一模一样
原版制作(Humboldt毕业证书)柏林大学毕业证学位证一模一样原版制作(Humboldt毕业证书)柏林大学毕业证学位证一模一样
原版制作(Humboldt毕业证书)柏林大学毕业证学位证一模一样
ydzowc
 
Seminar on Distillation study-mafia.pptx
Seminar on Distillation study-mafia.pptxSeminar on Distillation study-mafia.pptx
Seminar on Distillation study-mafia.pptx
Madan Karki
 
Null Bangalore | Pentesters Approach to AWS IAM
Null Bangalore | Pentesters Approach to AWS IAMNull Bangalore | Pentesters Approach to AWS IAM
Null Bangalore | Pentesters Approach to AWS IAM
Divyanshu
 
IEEE Aerospace and Electronic Systems Society as a Graduate Student Member
IEEE Aerospace and Electronic Systems Society as a Graduate Student MemberIEEE Aerospace and Electronic Systems Society as a Graduate Student Member
IEEE Aerospace and Electronic Systems Society as a Graduate Student Member
VICTOR MAESTRE RAMIREZ
 
Material for memory and display system h
Material for memory and display system hMaterial for memory and display system h
Material for memory and display system h
gowrishankartb2005
 
integral complex analysis chapter 06 .pdf
integral complex analysis chapter 06 .pdfintegral complex analysis chapter 06 .pdf
integral complex analysis chapter 06 .pdf
gaafergoudaay7aga
 
An improved modulation technique suitable for a three level flying capacitor ...
An improved modulation technique suitable for a three level flying capacitor ...An improved modulation technique suitable for a three level flying capacitor ...
An improved modulation technique suitable for a three level flying capacitor ...
IJECEIAES
 
官方认证美国密歇根州立大学毕业证学位证书原版一模一样
官方认证美国密歇根州立大学毕业证学位证书原版一模一样官方认证美国密歇根州立大学毕业证学位证书原版一模一样
官方认证美国密歇根州立大学毕业证学位证书原版一模一样
171ticu
 
Design and optimization of ion propulsion drone
Design and optimization of ion propulsion droneDesign and optimization of ion propulsion drone
Design and optimization of ion propulsion drone
bjmsejournal
 

Recently uploaded (20)

artificial intelligence and data science contents.pptx
artificial intelligence and data science contents.pptxartificial intelligence and data science contents.pptx
artificial intelligence and data science contents.pptx
 
CompEx~Manual~1210 (2).pdf COMPEX GAS AND VAPOURS
CompEx~Manual~1210 (2).pdf COMPEX GAS AND VAPOURSCompEx~Manual~1210 (2).pdf COMPEX GAS AND VAPOURS
CompEx~Manual~1210 (2).pdf COMPEX GAS AND VAPOURS
 
4. Mosca vol I -Fisica-Tipler-5ta-Edicion-Vol-1.pdf
4. Mosca vol I -Fisica-Tipler-5ta-Edicion-Vol-1.pdf4. Mosca vol I -Fisica-Tipler-5ta-Edicion-Vol-1.pdf
4. Mosca vol I -Fisica-Tipler-5ta-Edicion-Vol-1.pdf
 
Use PyCharm for remote debugging of WSL on a Windo cf5c162d672e4e58b4dde5d797...
Use PyCharm for remote debugging of WSL on a Windo cf5c162d672e4e58b4dde5d797...Use PyCharm for remote debugging of WSL on a Windo cf5c162d672e4e58b4dde5d797...
Use PyCharm for remote debugging of WSL on a Windo cf5c162d672e4e58b4dde5d797...
 
Curve Fitting in Numerical Methods Regression
Curve Fitting in Numerical Methods RegressionCurve Fitting in Numerical Methods Regression
Curve Fitting in Numerical Methods Regression
 
一比一原版(CalArts毕业证)加利福尼亚艺术学院毕业证如何办理
一比一原版(CalArts毕业证)加利福尼亚艺术学院毕业证如何办理一比一原版(CalArts毕业证)加利福尼亚艺术学院毕业证如何办理
一比一原版(CalArts毕业证)加利福尼亚艺术学院毕业证如何办理
 
An Introduction to the Compiler Designss
An Introduction to the Compiler DesignssAn Introduction to the Compiler Designss
An Introduction to the Compiler Designss
 
AI assisted telemedicine KIOSK for Rural India.pptx
AI assisted telemedicine KIOSK for Rural India.pptxAI assisted telemedicine KIOSK for Rural India.pptx
AI assisted telemedicine KIOSK for Rural India.pptx
 
22CYT12-Unit-V-E Waste and its Management.ppt
22CYT12-Unit-V-E Waste and its Management.ppt22CYT12-Unit-V-E Waste and its Management.ppt
22CYT12-Unit-V-E Waste and its Management.ppt
 
Generative AI leverages algorithms to create various forms of content
Generative AI leverages algorithms to create various forms of contentGenerative AI leverages algorithms to create various forms of content
Generative AI leverages algorithms to create various forms of content
 
ITSM Integration with MuleSoft.pptx
ITSM  Integration with MuleSoft.pptxITSM  Integration with MuleSoft.pptx
ITSM Integration with MuleSoft.pptx
 
原版制作(Humboldt毕业证书)柏林大学毕业证学位证一模一样
原版制作(Humboldt毕业证书)柏林大学毕业证学位证一模一样原版制作(Humboldt毕业证书)柏林大学毕业证学位证一模一样
原版制作(Humboldt毕业证书)柏林大学毕业证学位证一模一样
 
Seminar on Distillation study-mafia.pptx
Seminar on Distillation study-mafia.pptxSeminar on Distillation study-mafia.pptx
Seminar on Distillation study-mafia.pptx
 
Null Bangalore | Pentesters Approach to AWS IAM
Null Bangalore | Pentesters Approach to AWS IAMNull Bangalore | Pentesters Approach to AWS IAM
Null Bangalore | Pentesters Approach to AWS IAM
 
IEEE Aerospace and Electronic Systems Society as a Graduate Student Member
IEEE Aerospace and Electronic Systems Society as a Graduate Student MemberIEEE Aerospace and Electronic Systems Society as a Graduate Student Member
IEEE Aerospace and Electronic Systems Society as a Graduate Student Member
 
Material for memory and display system h
Material for memory and display system hMaterial for memory and display system h
Material for memory and display system h
 
integral complex analysis chapter 06 .pdf
integral complex analysis chapter 06 .pdfintegral complex analysis chapter 06 .pdf
integral complex analysis chapter 06 .pdf
 
An improved modulation technique suitable for a three level flying capacitor ...
An improved modulation technique suitable for a three level flying capacitor ...An improved modulation technique suitable for a three level flying capacitor ...
An improved modulation technique suitable for a three level flying capacitor ...
 
官方认证美国密歇根州立大学毕业证学位证书原版一模一样
官方认证美国密歇根州立大学毕业证学位证书原版一模一样官方认证美国密歇根州立大学毕业证学位证书原版一模一样
官方认证美国密歇根州立大学毕业证学位证书原版一模一样
 
Design and optimization of ion propulsion drone
Design and optimization of ion propulsion droneDesign and optimization of ion propulsion drone
Design and optimization of ion propulsion drone
 

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