Fix stupid bug in gen-character
[fiveam.git] / src / random.lisp
index 6cf070d..a1f16bb 100644 (file)
@@ -2,7 +2,7 @@
 
 (in-package :it.bese.FiveAM)
 
-;;;; * Random (QuickCheck-ish) testing
+;;;; ** Random (QuickCheck-ish) testing
 
 ;;;; FiveAM provides the ability to automatically generate a
 ;;;; collection of random input data for a specific test and run a
@@ -104,35 +104,70 @@ returning true. This second run limit prevents that.")
 (defclass for-all-test-never-run (test-failure for-all-test-result)
   ())
 
-;;;; ** Generators.
+;;;; *** Generators
 
 ;;;; Since this is random testing we need some way of creating random
-;;;; data to feed to our code. Generators are regular functions whcih
+;;;; data to feed to our code. Generators are regular functions which
 ;;;; create this random data.
 
 ;;;; We provide a set of built-in generators.
 
-(defmacro defgenerator (name arguments &body body)
-  `(defun ,name ,arguments
-     (lambda () ,@body)))
-
-(defgenerator gen-integer (&key (max (1+ most-positive-fixnum))
-                                (min (1+ most-negative-fixnum)))
-  (+ min (random (1+ (- max min)))))
-
-(defgenerator gen-character (&key (code (gen-integer :min 0 :max (1- char-code-limit)))
-                                  (alphanumericp nil))
-  (if alphanumericp
-      (code-char (funcall code))
-      (loop
-         for char = (code-char (funcall code))
-         until (alphanumericp char)
-         finally (return char))))
-
-(defun gen-string (&key
-                   (length (gen-integer :min 0 :max 80))
-                   (elements (gen-character))
-                   (element-type 'character))
+(defun gen-integer (&key (max (1+ most-positive-fixnum))
+                    (min (1- most-negative-fixnum)))
+  "Returns a generator which produces random integers greater
+than or equal to MIN and less than or equal to MIN."
+  (lambda ()
+    (+ min (random (1+ (- max min))))))
+
+(defun gen-float (&key bound (type 'short-float))
+  "Returns a generator which producs floats of type TYPE. BOUND,
+if specified, constrains the ruselts to be in the range (-BOUND,
+BOUND)."
+  (lambda ()
+    (let* ((most-negative (ecase type
+                            (short-float most-negative-short-float)
+                            (single-float most-negative-single-float)
+                            (double-float most-negative-double-float)
+                            (long-float most-negative-long-float)))
+           (most-positive (ecase type
+                            (short-float most-positive-short-float)
+                            (single-float most-positive-single-float)
+                            (double-float most-positive-double-float)
+                            (long-float most-positive-long-float)))
+           (bound (or bound (max most-positive (- most-negative)))))
+      (coerce 
+       (ecase (random 2)
+         (0 ;; generate a positive number
+          (random (min most-positive bound)))
+         (1 ;; generate a negative number
+          (- (random (min (- most-negative) bound)))))
+       type))))
+
+(defun gen-character (&key (code (gen-integer :min 0 :max (1- char-code-limit)))
+                           (alphanumericp nil))
+  "Returns a generator of characters.
+
+CODE must be a generator of random integers. ALPHANUMERICP, if
+non-NIL, limits the returned chars to those which pass
+alphanumericp."
+  (lambda ()
+    (if alphanumericp            
+        (loop
+           for count upfrom 0
+           for char = (code-char (funcall code))
+           until (alphanumericp char)
+           when (= 1000 count)
+             do (error "After 1000 iterations ~S has still not generated an alphanumeric character :(."
+                       code)
+           finally (return char))
+        (code-char (funcall code)))))
+
+(defun gen-string (&key (length (gen-integer :min 0 :max 80))
+                        (elements (gen-character))
+                        (element-type 'character))
+  "Returns a generator which producs random strings. LENGTH must
+be a generator which producs integers, ELEMENTS must be a
+generator which produces characters of type ELEMENT-TYPE."
   (lambda ()
     (loop
        with length = (funcall length)
@@ -141,9 +176,11 @@ returning true. This second run limit prevents that.")
        do (setf (aref string index) (funcall elements))
        finally (return string))))
 
-(defun gen-list (&key
-                 (length (gen-integer :min 0 :max 10))
-                 (elements (gen-integer :min -10 :max 10)))
+(defun gen-list (&key (length (gen-integer :min 0 :max 10))
+                      (elements (gen-integer :min -10 :max 10)))
+  "Returns a generator which producs random lists. LENGTH must be
+an integer generator and ELEMENTS must be a generator which
+producs objects."
   (lambda ()
     (loop
        repeat (funcall length)