add refresh hint tests

This commit is contained in:
Gered 2016-05-27 15:29:42 -04:00
parent 1f92c3fd2e
commit 6baf8c3c48
2 changed files with 289 additions and 0 deletions

276
test/views/hint_tests.clj Normal file
View file

@ -0,0 +1,276 @@
(ns views.hint-tests
(:use
clojure.test
views.test-helpers
views.protocols
views.core
views.test-memory-db))
(def test-sent-data
(atom []))
(defn test-send-fn [subscriber-key [view-sig view-data]]
(swap! test-sent-data conj {:subscriber-key subscriber-key
:view-sig view-sig
:view-data view-data}))
; fixtures
(defn reset-system-fixture [f]
(reset! view-system {})
(reset! statistics {})
(f)
(shutdown! true))
(defn clear-sent-data-fixture [f]
(reset! test-sent-data [])
(f))
(use-fixtures :each clear-sent-data-fixture reset-system-fixture reset-memory-db-fixture)
;; tests
(deftest refresh-views!-instantly-attempts-view-refresh-with-given-hints
(let [options default-options
hints-refreshed (atom [])]
; 1. init views
(init! views test-send-fn options)
; with a view subscription (any subscription will do)
(with-redefs [subscribed-views (fn [] #{(->view-sig :namespace :fake-view [])})
refresh-view! (fn [hints _] (swap! hints-refreshed into hints))]
; 2. trigger refresh by calling refresh-views! with hints
(refresh-views! [(hint :namespace [:foo] :fake-type)])
(is (contains-only? @hints-refreshed
[(hint :namespace [:foo] :fake-type)]))
(reset! hints-refreshed [])
; 3. same thing again, but passing in multiple hints
(refresh-views! [(hint :namespace [:foo] :fake-type)
(hint :namespace [:bar] :fake-type)])
(is (contains-only? @hints-refreshed
[(hint :namespace [:foo] :fake-type)
(hint :namespace [:bar] :fake-type)]))
(reset! hints-refreshed []))
; now, without any view subscriptions
(with-redefs [subscribed-views (fn [] #{})
refresh-view! (fn [hints _] (swap! hints-refreshed into hints))]
; 4. again trigger refresh by calling refresh-views! with hints
(refresh-views! [(hint :namespace [:foo] :fake-type)])
(is (empty? @hints-refreshed))
(reset! hints-refreshed []))))
(deftest refresh-watcher-runs-at-specified-interval-and-picks-up-queued-hints
(let [options default-options
hints-refreshed (atom [])]
(with-redefs [subscribed-views (fn [] #{(->view-sig :namespace :fake-view [])})
refresh-view! (fn [hints _] (swap! hints-refreshed into hints))]
; 1. init views
(init! views test-send-fn options)
; 2. queue a hint and wait until the next refresh interval
(queue-hints! [(hint :namespace [:foo] :fake-type)])
(wait-for-refresh-interval options)
(is (contains-only? @hints-refreshed
[(hint :namespace [:foo] :fake-type)]))
(reset! hints-refreshed [])
; 3. queue multiple hints and wait again
(queue-hints! [(hint :namespace [:foo] :fake-type)
(hint :namespace [:bar] :fake-type)])
(wait-for-refresh-interval options)
(is (contains-only? @hints-refreshed
[(hint :namespace [:foo] :fake-type)
(hint :namespace [:bar] :fake-type)]))
(reset! hints-refreshed [])
; 4. queue up no hints and wait
(wait-for-refresh-interval options)
(reset! hints-refreshed []))))
(deftest refresh-worker-thread-processes-relevant-hints
(let [options default-options
views-refreshed (atom [])]
; 1. init views
(init! views test-send-fn options)
(with-redefs [subscribed-views (fn [] #{(->view-sig :a :foo [])})
do-view-refresh! (fn [view-sig] (swap! views-refreshed into [view-sig]))]
; 2. trigger refresh by calling refresh-views! with relevant hint
(refresh-views! [(hint :a [:foo] memory-view-hint-type)])
(wait-for-refresh-views)
(is (contains-only? @views-refreshed [(->view-sig :a :foo [])]))
(reset! views-refreshed [])
; 3. same thing again, but passing in multiple hints (1 relevant, 1 not)
(refresh-views! [(hint :a [:foo] memory-view-hint-type)
(hint :a [:bar] memory-view-hint-type)])
(wait-for-refresh-views)
(is (contains-only? @views-refreshed [(->view-sig :a :foo [])]))
(reset! views-refreshed [])
; 4. and lastly, passing in only irrelevant hints
(refresh-views! [(hint :b [:foo] memory-view-hint-type)
(hint :a [:foo] :some-other-type)])
(wait-for-refresh-views)
(is (empty? @views-refreshed))
(reset! views-refreshed []))))
; this test is really just testing that our helper function memory-db-assoc-in! works as we expect it to
; (otherwise, it is entirely redundant given the above tests)
(deftest test-memory-db-operation-triggers-proper-refresh-hints
(let [options default-options
hints-refreshed (atom [])
views-refreshed (atom [])]
; 1. init views
(init! views test-send-fn options)
; first tests verifying that correct hints are being sent out (don't care if relevant or not yet)
(with-redefs [subscribed-views (fn [] #{(->view-sig :a :foo [])})
refresh-view! (fn [hints _] (swap! hints-refreshed into hints))]
(memory-db-assoc-in! :a [:foo] 42)
(memory-db-assoc-in! :a [:bar] 3.14)
(memory-db-assoc-in! :b [:baz] [10 20 30])
(wait-for-refresh-views)
(is (contains-only? @hints-refreshed
[(hint :a [:foo] memory-view-hint-type)
(hint :a [:bar] memory-view-hint-type)
(hint :b [:baz] memory-view-hint-type)]))
(reset! views-refreshed []))
; now we test that relevant views were recognized as relevant and forwarded on to be used to
; trigger actual refreshes of view data
(with-redefs [subscribed-views (fn [] #{(->view-sig :a :foo [])})
do-view-refresh! (fn [view-sig] (swap! views-refreshed into [view-sig]))]
; 2. update memory database (in a location covered by the subscribed view)
(memory-db-assoc-in! :a [:foo] 1337)
(wait-for-refresh-interval options)
(is (contains-only? @views-refreshed [(->view-sig :a :foo [])]))
(reset! views-refreshed [])
; 3. same thing again, but update a different location not covered by any subscription
(memory-db-assoc-in! :a [:bar] 1234.5678)
(wait-for-refresh-interval options)
(is (empty? @views-refreshed)))))
(deftest relevant-hints-cause-refreshed-data-to-be-sent-to-subscriber
(let [options default-options
subscriber-key 123
view-sig (->view-sig :a :foo [])]
; 1. init views
(init! views test-send-fn options)
; 2. subscribe to a view
(let [original-view-data (get-view-data view-sig)
updated-view-data 21
subscribe-result (subscribe! view-sig subscriber-key nil)]
; 3. block until subscription finishes. we don't care about the initial view data refresh
(while (not (realized? subscribe-result)))
(reset! test-sent-data [])
(is (= (hash original-view-data) (get-in @view-system [:hashes view-sig])))
; 4. change some test data that is covered by the view subscription
(memory-db-assoc-in! :a [:foo] updated-view-data)
(wait-for-refresh-views)
(is (= (hash updated-view-data) (get-in @view-system [:hashes view-sig])))
(is (contains-only? @test-sent-data
[{:subscriber-key subscriber-key
:view-sig (dissoc view-sig :namespace)
:view-data updated-view-data}])))))
(deftest irrelevant-hints-dont-trigger-refreshes
(let [options default-options
subscriber-key 123
view-sig (->view-sig :a :foo [])]
; 1. init views
(init! views test-send-fn options)
; 2. subscribe to a view
(let [subscribe-result (subscribe! view-sig subscriber-key nil)]
; 3. block until subscription finishes. we don't care about the initial view data refresh
(while (not (realized? subscribe-result)))
(reset! test-sent-data [])
; 4. change some test data that is NOT covered by the view subscription
(memory-db-assoc-in! :b [:foo] 6)
(memory-db-assoc-in! :a [:bar] 7)
(wait-for-refresh-views)
(is (empty? @test-sent-data)))))
(deftest refreshes-not-sent-if-view-data-is-unchanged-since-last-refresh
(let [options default-options
subscriber-key 123
view-sig (->view-sig :a :foo [])]
; 1. init views
(init! views test-send-fn options)
; 2. subscribe to a view
(let [updated-view-data 1111
subscribe-result (subscribe! view-sig subscriber-key nil)]
; 3. block until subscription finishes. we don't care about the initial view data refresh
(while (not (realized? subscribe-result)))
(reset! test-sent-data [])
; 4. change some test data, will cause a refresh to be sent out
(memory-db-assoc-in! :a [:foo] updated-view-data)
(wait-for-refresh-views)
(is (= (hash updated-view-data) (get-in @view-system [:hashes view-sig])))
(is (contains-only? @test-sent-data
[{:subscriber-key subscriber-key
:view-sig (dissoc view-sig :namespace)
:view-data updated-view-data}]))
(reset! test-sent-data [])
; 5. manually trigger another refresh for the view
(refresh-views! [(hint :a [:foo] memory-view-hint-type)])
(wait-for-refresh-views)
(is (empty? @test-sent-data))
; 6. also try "updating" the db with the same values
(memory-db-assoc-in! :a [:foo] updated-view-data)
(wait-for-refresh-views)
(is (empty? @test-sent-data)))))
(deftest refresh-queue-drops-duplicate-hints
(let [options (-> default-options
; enable statistics collection
(assoc :stats-log-interval 10000))
subscriber-key 123
view-sig (->view-sig :a :foo [])]
; 1. init views
(init! views test-send-fn options)
; 2. prematurely stop refresh worker threads so that we can more easily inspect the
; internal refresh queue's entries. the refresh worker threads are what remove
; hints from the refresh queue as they are added to it.
(stop-refresh-worker-threads)
; 3. subscribe to a view
(let [subscribe-result (subscribe! view-sig subscriber-key nil)]
; 4. block until subscription finishes
(while (not (realized? subscribe-result)))
(is (= 0 (:deduplicated @statistics)))
; 5. add duplicate hints by changing the same set of data twice
; (hints will stay in the queue forever because we stopped the worker threads)
(memory-db-assoc-in! :a [:foo] 6)
(memory-db-assoc-in! :a [:foo] 7)
(wait-for-refresh-views)
(is (= 1 (:deduplicated @statistics)))
(is (= [view-sig]
(vec (:refresh-queue @view-system)))))))
(deftest refresh-queue-drops-hints-when-full
(let [options (-> default-options
; enable statistics collection
(assoc :stats-log-interval 10000
:refresh-queue-size 1))
subscriber-key 123
view-sig-a (->view-sig :a :foo [])
view-sig-b (->view-sig :b :foo [])]
; 1. init views
(init! views test-send-fn options)
; 2. prematurely stop refresh worker threads so that we can more easily inspect the
; internal refresh queue's entries. the refresh worker threads are what remove
; hints from the refresh queue as they are added to it.
(stop-refresh-worker-threads)
; 3. subscribe to a view
; note: log* redef is to suppress error log output which will normally happen whenever
; another item is added to the refresh queue when it's already full
(with-redefs [clojure.tools.logging/log* (fn [& _])]
(let [subscribe-a (subscribe! view-sig-a subscriber-key nil)
subscribe-b (subscribe! view-sig-b subscriber-key nil)]
; 4. block until subscription finishes
(while (or (not (realized? subscribe-a))
(not (realized? subscribe-b))))
(is (= 0 (:dropped @statistics)))
; 5. change some data affecting the subscribed view, resulting in more then 1 hint
; being added to the refresh queue
(memory-db-assoc-in! :a [:foo] 101010)
(memory-db-assoc-in! :b [:foo] 010101)
(wait-for-refresh-views)
(is (= 1 (:dropped @statistics)))
(is (= [view-sig-a]
(vec (:refresh-queue @view-system))))))))

View file

@ -26,3 +26,16 @@
(data (get-in @view-system [:views (:view-id view-sig)])
(:namespace view-sig)
(:parameters view-sig)))
(defn wait-for-refresh-views []
(Thread/sleep 200))
(defn wait-for-refresh-interval [options]
(Thread/sleep (+ 200 (:refresh-interval options))))
(defn stop-refresh-worker-threads []
(swap! view-system assoc :stop-workers? true)
(doseq [^Thread t (:workers @view-system)]
(.interrupt t)
(.join t))
(swap! view-system assoc :workers nil))