         (.toByteArray baos))))
 
   (check
-    [model id result]
+    [this model result]
     (if-let [block (get model id)]
-      (bytes= (.toBuffer ^PersistentBytes @block) result)
-      (nil? result))))
-
-
-(defop :open-range
+      (is (bytes= (.toBuffer ^PersistentBytes @block) result))
+      (is (nil? result)))))
+
+
+(defop OpenBlockRange
+  [id start end]
 
   (gen-args
     [blocks]

        (.toByteArray baos))))

  (check
    [model [id start end] 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)
        (bytes= subarray result))
      (nil? result))))


(defn- op-generators
  [blocks]
  [(gen-list-op blocks)
   (gen-scan-op blocks)
   (gen-stat-op blocks)
   (gen-get-op blocks)
   (gen-get-batch-op blocks)
   (gen-open-op blocks)
   (gen-open-range-op blocks)
   (gen-put!-op blocks)
   (gen-put-batch!-op blocks)
   (gen-delete!-op blocks)
   (gen-delete-batch!-op blocks)
   (gen-erase!!-op blocks)])


(defn- gen-op-seq
  "Generates non-empty sequences of store operations for linear testing."
  [blocks]
  (->> (op-generators blocks)
       (gen/one-of)
       (gen/list)
       (gen/not-empty)))


(defn- gen-op-seq*
  "Generates non-empty sequences of store operations for multithread testing,
  including timing wait ops."
  [blocks]
  (->> (op-generators blocks)
       (cons (gen-wait-op nil))
       (gen/one-of)
       (gen/list)
       (gen/not-empty)))



;; ## Operation Testing

(defn- apply-ops!
  "Apply a sequence of operations to a store, returning a list of op vectors
  with appended results."
  [store ops]
  (reduce
    (fn [results op]
      (conj results (conj op (apply-op store op))))
    []
    ops))


(defn- valid-ops?
  "Determines whether the given sequence of operations produced valid results
  when applied to the store. Returns true if the store behavior is valid."
  [ops]
  (loop [model {}
         ops ops]
    (if-let [op (first ops)]
      (if (check-op model op)
        (recur (update-model model op) (rest ops))
        false)
      true)))


(defn check-store

  (gen/elements (vals blocks)))


(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)]
      (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
    [this model result]
    (let [expected-ids (cond->> (keys model)
                         (:after query)
                           (filter #(pos? (compare (multihash/hex %) (:after query))))
