-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
deterministic testing of protocol violations
- Loading branch information
Showing
6 changed files
with
219 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
(ns hyperfiddle.detest | ||
(:import #?(:clj [clojure.lang IFn IDeref]) | ||
#?(:clj [clojure.lang ExceptionInfo]) | ||
#?(:cljs [goog.math Long]) | ||
[missionary Cancelled]) | ||
(:require [hyperfiddle.incseq.flow-protocol-enforcer :as fpe] | ||
#?(:cljs [contrib.data :refer [->box]]) | ||
[contrib.debug :as dbg] | ||
[clojure.string :as str])) | ||
|
||
;; DETErministic TESTing, so naturally, DETEST | ||
|
||
#?(:clj | ||
(defn ->xorshift64 [seed] | ||
(let [!v (atom seed)] | ||
(fn step | ||
([] (swap! !v (fn [v] | ||
(let [v (bit-xor v (bit-shift-left v 7))] | ||
(bit-xor v (unsigned-bit-shift-right v 9)))))) | ||
([n] (if (zero? n) n (mod (step) n))))))) | ||
|
||
#?(:clj (defn random-seed [] (-> java.security.SecureRandom new .nextLong))) | ||
|
||
#?(:cljs | ||
(defn ->xorshift64 [seed] | ||
(let [<v> (->box seed) | ||
step (fn [] (let [^Long v (<v>) | ||
^Long v (.xor v (.shiftLeft v 7))] | ||
(<v> (.xor v (.shiftRightUnsigned v 9)))))] | ||
(fn self | ||
([] (let [^Long v (step)] (.toNumber v))) | ||
([n] (if (zero? n) n (let [^Long v (step)] (mod (.toNumber v) n)))))))) | ||
|
||
#?(:cljs (defn random-seed [] (Long/fromBits (rand-int 0x100000000) (rand-int 0x100000000)))) | ||
|
||
(defprotocol Engine | ||
(exercise [this opts flow]) | ||
(roll [this] [this nm]) | ||
(add-proc [this proc]) | ||
(del-proc [this proc])) | ||
|
||
(defn instrument [nm flow] | ||
(fn [step done] | ||
(let [it ((dbg/instrument* nm flow) step done)] | ||
(reify | ||
IFn | ||
(#?(:clj invoke :cljs -invoke) [_] (it)) | ||
(#?(:clj invoke :cljs -invoke) [_ n] ((it :process) n)) | ||
IDeref | ||
(#?(:clj deref :cljs -deref) [_] @it))))) | ||
|
||
(defn on-violate | ||
([nm msg] (on-violate nm msg nil)) | ||
([nm msg e] (throw (ex-info (str nm " flow protocol violation: " msg) {} e)))) | ||
|
||
(defn ->engine | ||
([] (->engine {})) | ||
([{:keys [seed]}] | ||
(let [seed (or seed (random-seed)), rng (->xorshift64 seed), !proc* (atom [])] | ||
(reify Engine | ||
(add-proc [_ proc] (swap! !proc* conj proc)) | ||
(del-proc [_ proc] (swap! !proc* (fn [proc*] (filterv #(not= % proc) proc*)))) | ||
(roll [_] (rng)) | ||
(roll [_ n] (rng n)) | ||
(exercise [this opts flow] | ||
(try (let [flow (fpe/enforce {:name ::root, :on-violate on-violate} flow) | ||
!s (atom nil) | ||
root (flow #(reset! !s :step) #(reset! !s :done))] | ||
(add-proc this root) | ||
(while (not= :done @!s) | ||
(let [proc* @!proc*, n (rng (count proc*)), proc (nth proc* n)] | ||
(if (= proc root) | ||
(case @!s | ||
(:done nil) (when (> 1 (rng 100)) (root)) | ||
(:step) (condp > (rng 100) | ||
1 (root) | ||
25 nil | ||
#_else (try (reset! !s nil) @root | ||
(catch Cancelled _) | ||
(catch ExceptionInfo e | ||
(when-not (str/starts-with? (ex-message e) "[DETEST OK] ") | ||
(throw e)))))) | ||
(proc (rng))))) | ||
(dotimes [_ 3] (root))) | ||
(catch #?(:clj Throwable :cljs :default) e | ||
(throw (ex-info (str "exercise failed with seed " seed) {:seed seed} e))))))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
(ns hyperfiddle.detest.incseq-test | ||
(:import #?(:clj [clojure.lang IFn IDeref]) | ||
[missionary Cancelled]) | ||
(:require [hyperfiddle.detest :as dt] | ||
[hyperfiddle.incseq :as i] | ||
[hyperfiddle.incseq.diff-impl :as d] | ||
[hyperfiddle.incseq.perm-impl :as p] | ||
[clojure.set :as set] | ||
[contrib.debug :as dbg] | ||
[clojure.test :as t])) | ||
|
||
(defn %next-diff [prev-degree ngn next-fn] | ||
(let [grow (dt/roll ngn 10), degree (+ prev-degree grow) | ||
shrink (dt/roll ngn degree) | ||
;; TODO if shrank ensure grown items are permuted so they stay | ||
perm (p/rotation (dt/roll ngn degree) (dt/roll ngn degree)) | ||
inv (set/map-invert perm) | ||
change (reduce (fn [m i] (assoc m (inv i i) (next-fn ngn))) {} (range prev-degree (+ prev-degree grow))) | ||
change (reduce (fn [m i] (cond-> m (zero? (dt/roll ngn 2)) (assoc i (next-fn ngn)))) | ||
change (range (- degree shrink)))] | ||
{:grow grow :degree degree :shrink shrink :permutation perm :change change :freeze #{}})) | ||
|
||
(defn %rand-incseq [ngn next-incseq-fn] | ||
(fn [step done] | ||
(step) | ||
(let [!should-step? (atom false), !v (atom (d/empty-diff 0)), !done? (atom false), !cancelled? (atom false) | ||
fin #(when-not (first (reset-vals! !done? true)) (done)) | ||
proc | ||
(reify | ||
IFn | ||
(#?(:clj invoke :cljs -invoke) [_] | ||
(let [cancelled? (first (reset-vals! !cancelled? true))] | ||
(when (and @!should-step? (not @!done?) (not cancelled?)) | ||
(swap! !should-step? not) (step)))) | ||
(#?(:clj invoke :cljs -invoke) [this n] | ||
(if @!done? | ||
(dt/del-proc ngn this) | ||
(if (> 1 (mod n 100)) | ||
(when @!should-step? (fin)) | ||
(when (and @!should-step? (not @!done?)) (swap! !should-step? not) (step))))) | ||
IDeref | ||
(#?(:clj deref :cljs -deref) [_] | ||
(cond | ||
@!done? | ||
(throw (ex-info "transfer after done" {})) | ||
|
||
@!cancelled? | ||
(do (fin) (throw (Cancelled.))) | ||
|
||
@!should-step? | ||
(throw (ex-info "transfer without step" {})) | ||
|
||
:else | ||
(do (swap! !should-step? not) | ||
(if (> 1 (dt/roll ngn 100)) | ||
(do (fin) (throw (ex-info "[DETEST OK] random incseq throw" {}))) | ||
(do (condp > (dt/roll ngn 100) | ||
1 (fin) | ||
25 (do (swap! !should-step? not) (step)) | ||
#_else nil) | ||
(swap! !v next-incseq-fn ngn)))))))] | ||
(dt/add-proc ngn proc) | ||
proc))) | ||
|
||
(defn next-diff [prev-diff ngn] | ||
(%next-diff (- (:degree prev-diff) (:shrink prev-diff)) ngn dt/roll)) | ||
|
||
(defn rand-incseq [ngn] (%rand-incseq ngn next-diff)) | ||
|
||
(defn next-lc-diff [prev-diff ngn] | ||
(%next-diff (- (:degree prev-diff) (:shrink prev-diff)) ngn rand-incseq)) | ||
|
||
(defn rand-lc-incseq [ngn] (%rand-incseq ngn next-lc-diff)) | ||
|
||
(t/deftest detest-latest-product | ||
(dotimes [_ 100] | ||
(let [ngn (dt/->engine)] | ||
(t/is (nil? | ||
(dt/exercise ngn {} (i/latest-product vector (rand-incseq ngn) (rand-incseq ngn)))))))) | ||
|
||
(t/deftest detest-latest-concat | ||
(dotimes [_ 500] | ||
(let [ngn (dt/->engine)] | ||
(t/is (nil? | ||
(dt/exercise ngn {} (i/latest-concat (rand-lc-incseq ngn)))))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters