babashka/test-resources/lib_tests/minimallist/generator_test.cljc
Gabriel Horner 665ae4dd97
Finish up library tests (#1120)
* Add tests for markdown-clj and tools.namespace

See comment for why only one markdown test could be run.
Closes #1069 and #1064

* Convert 10 test libs using add-libtest

Also improved add-libtest to only require maven artifact
and rely on clojars for getting git-url most of the time

* Convert 8 more test libs using add-libtest

Also updated table and added comment for newline test

* Fix doric test

* Disable tools.namespace test that fails on windows

* Added dozen manual test libs and converted 2 test libs

add-libtest.clj supports manually-added and test-directories options

* Converts last tests to test namespaces and write libraries.csv

* Add a number of library tests from projects.md

Also add more docs around adding test libs and tweak add script

* Use :sha for gitlib and older clojure cli

* Revert "Use :sha for gitlib and older clojure cli"

This reverts commit c663ab8368.

* Fix and disable failing tests

Disabled tests that fail consistently and fixed windows one
2021-12-29 16:35:14 +01:00

546 lines
19 KiB
Clojure

(ns minimallist.generator-test
(:require [clojure.test :refer [deftest testing is are]]
[clojure.test.check.generators :as tcg]
[clojure.string :as str]
[minimallist.core :refer [valid?]]
[minimallist.helper :as h]
[minimallist.util :as util]
[minimallist.generator :as mg :refer [gen fn-any? fn-int? fn-string? fn-char?
fn-symbol? fn-simple-symbol? fn-qualified-symbol?
fn-keyword? fn-simple-keyword? fn-qualified-keyword?]]))
(defn- path-test-visitor []
;; Testing using side effects.
;; A little ugly, but good enough for tests.
(let [paths (atom [])]
(fn
([] @paths)
([model stack path]
(swap! paths conj path)
model))))
(deftest postwalk-visit-order-test
(are [model expected-paths]
(let [visitor (path-test-visitor)]
(mg/postwalk model visitor) ; Create side effects
(= (visitor) expected-paths)) ; Collect and compare the side effects
(h/let ['leaf (h/fn int?)
'tree (h/ref 'leaf)]
(h/ref 'tree))
[[:bindings 'leaf]
[:bindings 'tree]
[:body]
[]]
(h/let ['root (h/let ['leaf (h/fn int?)
'tree (h/ref 'leaf)]
(h/ref 'tree))]
(h/ref 'root))
[[:bindings 'root :bindings 'leaf]
[:bindings 'root :bindings 'tree]
[:bindings 'root :body]
[:bindings 'root]
[:body]
[]]
(h/let ['leaf (h/fn int?)
'root (h/let ['tree (h/ref 'leaf)]
(h/ref 'tree))]
(h/ref 'root))
[[:bindings 'leaf]
[:bindings 'root :bindings 'tree]
[:bindings 'root :body]
[:bindings 'root]
[:body]
[]]
; test of no visit more than once
(h/let ['leaf (h/fn int?)
'tree (h/tuple (h/ref 'leaf) (h/ref 'leaf))]
(h/ref 'tree))
[[:bindings 'leaf]
[:bindings 'tree :entries 0 :model]
[:bindings 'tree :entries 1 :model]
[:bindings 'tree]
[:body]
[]]
; test of no visit more than once, infinite loop otherwise
(h/let ['leaf (h/fn int?)
'tree (h/tuple (h/ref 'tree) (h/ref 'leaf))]
(h/ref 'tree))
[[:bindings 'tree :entries 0 :model]
[:bindings 'leaf]
[:bindings 'tree :entries 1 :model]
[:bindings 'tree]
[:body]
[]]
#__))
(deftest assoc-leaf-distance-visitor-test
(are [model expected-walked-model]
(= (-> model
(mg/postwalk mg/assoc-leaf-distance-visitor)
(util/walk-map-dissoc :fn))
expected-walked-model)
; Recursive data-structure impossible to generate
; This one is trying to bring the generator function in an infinite loop.
(h/let ['loop (h/ref 'loop)]
(h/ref 'loop))
{:type :let
:bindings {'loop {:type :ref
:key 'loop}}
:body {:type :ref
:key 'loop}}
; Recursive data-structure impossible to generate
(h/let ['leaf (h/fn int?)
'tree (h/tuple (h/ref 'tree) (h/ref 'leaf))]
(h/ref 'tree))
{:type :let
:bindings {'leaf {:type :fn
::mg/leaf-distance 0}
'tree {:type :sequence
:entries [{:model {:type :ref
:key 'tree}}
{:model {:type :ref
:key 'leaf
::mg/leaf-distance 1}}]}}
:body {:type :ref
:key 'tree}}
; Recursive data-structure impossible to generate
(h/let ['rec-map (h/map [:a (h/fn int?)]
[:b (h/ref 'rec-map)])]
(h/ref 'rec-map))
{:type :let
:bindings {'rec-map {:type :map
:entries [{:key :a
:model {:type :fn
::mg/leaf-distance 0}}
{:key :b
:model {:type :ref
:key 'rec-map}}]}}
:body {:type :ref
:key 'rec-map}}
; Recursive data-structure which can be generated
(h/let ['leaf (h/fn int?)
'tree (h/alt (h/ref 'tree) (h/ref 'leaf))]
(h/ref 'tree))
{:type :let
:bindings {'leaf {:type :fn
::mg/leaf-distance 0}
'tree {:type :alt
:entries [{:model {:type :ref
:key 'tree}}
{:model {:type :ref
:key 'leaf
::mg/leaf-distance 1}}]
::mg/leaf-distance 2}}
:body {:type :ref
:key 'tree
::mg/leaf-distance 3}
::mg/leaf-distance 4}
(h/let ['rec-map (h/map [:a (h/fn int?)]
[:b {:optional true} (h/ref 'rec-map)])]
(h/ref 'rec-map))
{:type :let
:bindings {'rec-map {:type :map
:entries [{:key :a
:model {:type :fn
::mg/leaf-distance 0}}
{:key :b
:optional true
:model {:type :ref
:key 'rec-map}}]
::mg/leaf-distance 1}}
:body {:type :ref
:key 'rec-map
::mg/leaf-distance 2}
::mg/leaf-distance 3}
#__))
(deftest assoc-min-cost-visitor-test
(are [model expected-walked-model]
(= (-> model
(mg/postwalk mg/assoc-min-cost-visitor)
(util/walk-map-dissoc :fn))
expected-walked-model)
(h/tuple (h/fn int?) (h/fn string?))
{:type :sequence
:entries [{:model {:type :fn
::mg/min-cost 1}}
{:model {:type :fn
::mg/min-cost 1}}]
::mg/min-cost 3}
(h/cat (h/fn int?) (h/fn string?))
{:type :cat
:entries [{:model {:type :fn
::mg/min-cost 1}}
{:model {:type :fn
::mg/min-cost 1}}]
::mg/min-cost 3}
(h/in-vector (h/cat (h/fn int?) (h/fn string?)))
{:type :cat
:coll-type :vector
:entries [{:model {:type :fn
::mg/min-cost 1}}
{:model {:type :fn
::mg/min-cost 1}}]
::mg/min-cost 3}
(h/not-inlined (h/cat (h/fn int?) (h/fn string?)))
{:type :cat
:inlined false
:entries [{:model {:type :fn
::mg/min-cost 1}}
{:model {:type :fn
::mg/min-cost 1}}]
::mg/min-cost 3}
(h/map [:a (h/fn int?)]
[:b {:optional true} (h/fn int?)])
{:type :map
:entries [{:key :a
:model {:type :fn
::mg/min-cost 1}}
{:key :b
:optional true
:model {:type :fn
::mg/min-cost 1}}]
::mg/min-cost 2}
(h/map-of (h/vector (h/fn keyword?) (h/fn int?)))
{:type :map-of
:entry-model {:type :sequence
:coll-type :vector
:entries [{:model {:type :fn
::mg/min-cost 1}}
{:model {:type :fn
::mg/min-cost 1}}]
::mg/min-cost 3}
::mg/min-cost 1}
(-> (h/map-of (h/vector (h/fn keyword?) (h/fn int?)))
(h/with-count (h/enum #{3 4})))
{:type :map-of
:entry-model {:type :sequence
:coll-type :vector
:entries [{:model {:type :fn
::mg/min-cost 1}}
{:model {:type :fn
::mg/min-cost 1}}]
::mg/min-cost 3}
:count-model {:type :enum
:values #{3 4}}
::mg/min-cost 7}
(h/set-of (h/fn any?))
{:type :set-of
:elements-model {:type :fn
::mg/min-cost 1}
::mg/min-cost 1}
(-> (h/set-of (h/fn any?))
(h/with-count (h/val 3)))
{:type :set-of
:elements-model {:type :fn
::mg/min-cost 1}
:count-model {:type :enum
:values #{3}}
::mg/min-cost 4}
(h/let ['foo (-> (h/set-of (h/fn int?))
(h/with-count (h/val 3)))]
(h/ref 'foo))
{:type :let
:bindings {'foo {:type :set-of
:count-model {:type :enum
:values #{3}}
:elements-model {:type :fn
::mg/min-cost 1}
::mg/min-cost 4}}
:body {:type :ref
:key 'foo
::mg/min-cost 4}
::mg/min-cost 4}
#__))
(deftest budget-split-gen-test
(is (every? (fn [[a b c]]
(and (<= 0 a 5)
(<= 5 b 10)
(<= 10 c 15)))
(-> (#'mg/budget-split-gen 20.0 [0 5 10])
tcg/sample)))
(is (every? #(= % [5 10 10])
(-> (#'mg/budget-split-gen 20.0 [5 10 10])
tcg/sample)))
(is (every? empty?
(-> (#'mg/budget-split-gen 10.0 [])
tcg/sample))))
(comment
;; For occasional hand testing
(tcg/sample (gen (-> (h/set-of fn-any?)
(h/with-count (h/enum #{1 2 3 10}))
(h/with-condition (h/fn (comp #{1 2 3} count))))))
(tcg/sample (gen (h/map-of (h/vector fn-int? fn-simple-symbol?))))
(tcg/sample (gen (-> (h/map [:a fn-int?])
(h/with-optional-entries [:b fn-string?]))))
(tcg/sample (gen (h/sequence-of fn-int?)))
(tcg/sample (gen (h/tuple fn-int? fn-string?)))
(tcg/sample (gen (h/cat fn-int? fn-string?)))
(tcg/sample (gen (h/repeat 2 3 fn-int?)))
(tcg/sample (gen (h/repeat 2 3 (h/cat fn-int? fn-string?))))
(tcg/sample (gen (h/let ['int? fn-int?
'string? fn-string?
'int-string? (h/cat (h/ref 'int?) (h/ref 'string?))]
(h/repeat 2 3 (h/ref 'int-string?)))))
(tcg/sample (gen (-> (h/set-of fn-int?)
(h/with-condition (h/fn (fn [coll]
(or (empty? coll)
(some even? coll))))))))
(tcg/sample (gen (-> (h/set-of fn-any?)
(h/with-count (h/enum #{1 2 3 10}))
(h/with-condition (h/fn (comp #{1 2 3} count))))))
(tcg/sample (gen (h/let ['node (h/set-of (h/ref 'node))]
(h/ref 'node))))
(tcg/sample (gen (h/let ['node (h/map-of (h/vector fn-int? (h/ref 'node)))]
(h/ref 'node)) 50))
(tcg/sample (gen (h/let ['node (h/map-of (h/vector fn-keyword? (h/ref 'node)))]
(h/ref 'node)) 100) 1)
(tcg/sample (gen (h/map [:a fn-int?])))
(tcg/sample (gen (-> (h/map [:a fn-int?])
(h/with-optional-entries [:b fn-string?]))))
(tcg/sample (gen (h/cat (h/vector-of fn-int?)
(h/vector-of fn-int?)) 20))
(tcg/sample (gen (h/repeat 5 10 fn-int?)))
(tcg/sample (gen fn-symbol?))
(tcg/sample (gen fn-simple-symbol?))
(tcg/sample (gen fn-qualified-symbol?))
(tcg/sample (gen fn-keyword?))
(tcg/sample (gen fn-simple-keyword?))
(tcg/sample (gen fn-qualified-keyword?))
(tcg/sample (gen (-> (h/cat (h/char-cat "good")
(h/val \space)
(h/alt (h/char-cat "morning")
(h/char-cat "afternoon")
(h/repeat 3 10 (h/char-set "#?!@_*+%"))))
(h/in-string)))
100)
#__)
(deftest gen-test
(let [model fn-string?]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (h/enum #{:1 2 "3"})]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (-> (h/set-of fn-int?)
(h/with-condition (h/fn (fn [coll]
(or (empty? coll)
(some even? coll))))))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (-> (h/set-of fn-any?)
(h/with-count (h/enum #{1 2 3 10}))
(h/with-condition (h/fn (comp #{1 2 3} count))))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (h/map-of (h/vector fn-int? fn-string?))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (-> (h/map [:a fn-int?])
(h/with-optional-entries [:b fn-string?])
(h/with-entries [:c fn-int?])
(h/with-optional-entries [:d fn-string?]))
sample (tcg/sample (gen model) 100)]
(is (and (every? (partial valid? model) sample)
(every? (fn [element] (contains? element :a)) sample)
(some (fn [element] (contains? element :b)) sample)
(some (fn [element] (not (contains? element :b))) sample)
(every? (fn [element] (contains? element :c)) sample)
(some (fn [element] (contains? element :d)) sample)
(some (fn [element] (not (contains? element :d))) sample))))
(let [model (h/sequence-of fn-int?)]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (h/tuple fn-int? fn-string?)
sample (tcg/sample (gen model) 100)]
(is (and (every? (partial valid? model) sample)
(some list? sample)
(some vector? sample))))
(let [model (h/list fn-int? fn-string?)
sample (tcg/sample (gen model))]
(is (and (every? (partial valid? model) sample)
(every? list? sample))))
(let [model (h/vector fn-int? fn-string?)
sample (tcg/sample (gen model))]
(is (and (every? (partial valid? model) sample)
(every? vector? sample))))
(let [model (h/string-tuple fn-char? fn-char?)
sample (tcg/sample (gen model))]
(is (and (every? (partial valid? model) sample)
(every? string? sample))))
(let [model (h/in-list (h/cat fn-int? fn-string?))
sample (tcg/sample (gen model))]
(is (and (every? (partial valid? model) sample)
(every? list? sample))))
(let [model (h/in-vector (h/cat fn-int? fn-string?))
sample (tcg/sample (gen model))]
(is (and (every? (partial valid? model) sample)
(every? vector? sample))))
(let [model (h/in-string (h/cat fn-char? fn-char?))
sample (tcg/sample (gen model))]
(is (and (every? (partial valid? model) sample)
(every? string? sample))))
(let [model (h/alt fn-int? fn-string?)]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (h/cat fn-int? fn-string?)]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (h/repeat 2 3 fn-int?)]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (h/repeat 2 3 (h/cat fn-int? fn-string?))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (h/not-inlined (h/cat fn-int?))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (h/not-inlined (h/repeat 1 2 fn-int?))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
(let [model (h/let ['int? fn-int?
'string? fn-string?
'int-string? (h/cat (h/ref 'int?) (h/ref 'string?))]
(h/repeat 2 3 (h/ref 'int-string?)))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
;; Budget-based limit on model choice.
(let [model (h/let ['tree (h/alt [:leaf fn-int?]
[:branch (h/vector (h/ref 'tree)
(h/ref 'tree))])]
(h/ref 'tree))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
;; Budget-based limit on variable set size.
(let [model (h/let ['node (h/set-of (h/ref 'node))]
(h/ref 'node))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
;; Budget-based limit on variable sequence size.
(let [model (h/let ['node (h/vector-of (h/ref 'node))]
(h/ref 'node))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
;; Budget-based limit on variable map size.
(let [model (h/let ['node (h/map-of (h/vector fn-int? (h/ref 'node)))]
(h/ref 'node))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
;; Budget-based limit on optional entries in a map.
(let [model (h/let ['node (-> (h/map [:a fn-int?])
(h/with-optional-entries [:x (h/ref 'node)]
[:y (h/ref 'node)]
[:z (h/ref 'node)]))]
(h/ref 'node))]
(is (every? (partial valid? model)
(tcg/sample (gen model)))))
;;; Budget-based limit on number of occurrences in a repeat.
;(let [model (h/let ['node (h/repeat 0 1 (h/ref 'node))]
; (h/ref 'node))]
; (is (every? (partial valid? model)
; (tcg/sample (gen model)))))
;; Model impossible to generate.
(let [model (h/let ['node (h/map [:a (h/ref 'node)])]
(h/ref 'node))]
(is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model)))))
;; Model impossible to generate.
(let [model (h/let ['node (h/tuple (h/ref 'node))]
(h/ref 'node))]
(is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model)))))
;; Model impossible to generate.
(let [model (h/let ['node (h/cat (h/ref 'node))]
(h/ref 'node))]
(is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model)))))
;; Model impossible to generate.
(let [model (h/let ['node (h/cat (h/ref 'node))]
(h/ref 'node))]
(is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model)))))
(let [model (h/let ['node (h/repeat 1 2 (h/ref 'node))]
(h/ref 'node))]
(is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model))))))
;; TODO: [later] reuse the cat-ipsum model for parsing the output.
;; TODO: in the :alt node, introduce a property :occurrence for the generator.
;; TODO: generate models, use them to generate data, should not stack overflow.