   (gen/elements (vals blocks)))
 
 
-(defop :wait
-
-  (gen-args
-    [_]
-    (gen/choose 1 100))
-
-  (apply
-    [store duration]
-    (Thread/sleep duration)))
-
-
-(defop :stat
-
-  (gen-args choose-id)
-
-  (check
-    [model id result]
+(defop StatBlock
+  [id]
+
+  (gen-args
+    [ctx]
+    [(choose-id ctx)])
+
+  (apply-op
+    [this store]
+    (block/stat store id))
+
+  (check
+    [this model result]
     (if-let [block (get model id)]
-      (and (map? result)
-           (= (:id block) (:id result))
-           (= (:size block) (:size result))
-           (some? (:stored-at result)))
-      (nil? result))))
-
-
-(defop :list
-
-  (gen-args
-    [blocks]
-    (gen/fmap
-      (fn select
-        [[opts selection]]
-        (select-keys opts selection))
-      (gen/tuple
-        (gen/hash-map
-          :algorithm (gen/elements (keys digest/functions))
-          :after (gen/fmap hex/encode (gen/not-empty gen/bytes)) ; TODO: pick prefixes
-          :limit (gen/large-integer* {:min 1, :max (inc (count blocks))}))
-        (gen/set (gen/elements #{:algorithm :after :limit})))))
-
-  (apply
-    [store query]
+      (do (is (map? result))
+          (is (= (:id block) (:id result)))
+          (is (= (:size block) (:size result)))
+          (is (some? (:stored-at result))))
+      (is (nil? result)))))
+
+
+(defop ListBlocks
+  [query]
+
+  (gen-args
+    [blocks]
+    [(gen/fmap
+       (fn select
+         [[opts selection]]
+         (select-keys opts selection))
+       (gen/tuple
+         (gen/hash-map
+           :algorithm (gen/elements (keys digest/functions))
+           :after (gen/fmap hex/encode (gen/not-empty gen/bytes)) ; TODO: pick prefixes
+           :limit (gen/large-integer* {:min 1, :max (inc (count blocks))}))
+         (gen/set (gen/elements #{:algorithm :after :limit}))))])
+
+  (apply-op
+    [this store]
     (doall (block/list store query)))
 
   (check
-    [model query result]
+    [this model result]
     (let [expected-ids (cond->> (keys model)
                          (:after query)
                            (filter #(pos? (compare (multihash/hex %) (:after query))))

                           (sort)
                         (:limit query)
                           (take (:limit query)))]
      (and (is (sequential? result))
           (is (= (count result) (count expected-ids)))
           (is (every? #(check-op model (cons :stat %))
                       (map vector expected-ids result)))))))


(defop :get

  (gen-args choose-id)

  (check
    [model id result]
    (if-let [block (get model id)]
      (do
        (is (some? (:id result)))
        (is (integer? (:size result)))
        (is (= id (:id result)))
        (is (= (:size block) (:size result))))
      (nil? result))))


(defop :put!

  (gen-args choose-block)

  (check
    [model block result]
    (= block result))

  (update
    [model block]
    (assoc model (:id block) block)))


(defop :delete!

  (gen-args choose-id)

  (check
    [model id result]
    (if (contains? model id)
      (true? result)
      (false? result)))

  (update
    [model id]
    (dissoc model id)))


(defop :get-batch

  (gen-args
    [blocks]
    (gen/set (choose-id blocks)))

  (check
    [model ids result]
    (and (is (coll? result))
         (is (= (set (keep model ids))
                (set result))))))


(defop :put-batch!

  (gen-args
    [blocks]
    (gen/set (choose-block blocks)))

  (check
    [model blocks result]
    (and (coll? result)
         (= (set blocks) (set result))))

  (update
    [model blocks]
    (into model (map (juxt :id identity) blocks))))


(defop :delete-batch!

  (gen-args
    [blocks]
    (gen/set (choose-id blocks)))

  (check
    [model ids result]
    (and (set? result)
         (= result (set (filter (set ids) (keys model))))))

  (update
    [model ids]
    (apply dissoc model ids)))


(defop :erase!!

  (apply
    [store _]
    (block/erase!! store))

  (update
    [model _]
    (empty model)))


(defop :scan

  (gen-args
    [_]
    (gen/elements
      [nil
       (fn scan-pred
         [stat]
         (< (:size stat) 256))]))

  (check
    [model p result]
    (let [blocks (cond->> (vals model) p (filter p))]
      (and (= (count blocks) (:count result))
           (= (reduce + (map :size blocks)) (:size result))
           (map? (:sizes result))
           (every? integer? (keys (:sizes result)))
           (= (count blocks) (reduce + (vals (:sizes result))))
           (every? (partial sum/probably-contains? result) (map :id blocks))))))


(defop :open

  (gen-args choose-id)

  (apply
    [store id]
    (when-let [block (block/get store id)]
      (let [baos (java.io.ByteArrayOutputStream.)]
        (with-open [content (block/open block)]

        (.toByteArray baos))))

  (check
    [this model result]
    (if-let [block (get model id)]
      (let [baos (java.io.ByteArrayOutputStream.)
            length (- end start)
            subarray (byte-array length)]
        (block/write! block baos)
        (System/arraycopy (.toByteArray baos) start subarray 0 length)
        (is (bytes= subarray result)))
      (is (nil? result)))))


(def ^:private op-generators
  (juxt gen->StatBlock
        gen->ListBlocks
        ;gen->ScanStore
        gen->GetBlock
        gen->OpenBlock
        gen->OpenBlockRange
        gen->PutBlock
        gen->DeleteBlock
        gen->GetBlockBatch
        gen->PutBlockBatch
        gen->DeleteBlockBatch
        gen->EraseStore))



;; ## Operation Testing

(defn- start-store
  [constructor]
  (let [store (component/start (constructor))]
    (when-not (empty? (block/list store))
      (throw (IllegalStateException.
               (str "Cannot run integration test on " (pr-str store)
                    " as it already contains blocks!"))))
    (is (zero? (:count (block/scan store))))
    store))


(defn- stop-store
  [store]
  (block/erase!! store)
  (is (empty? (block/list store)) "ends empty")
  (component/stop store))


(defn check-store
