d093b64cf92c

Add simple fuzz tests against Yason
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Wed, 12 Aug 2020 23:09:58 -0400
parents a450f8f200cd
children d23a34c34dc3
branches/tags (none)
files .TODO.done TODO jarl.asd test/tests.lisp

Changes

--- a/.TODO.done	Wed Aug 12 00:38:21 2020 -0400
+++ b/.TODO.done	Wed Aug 12 23:09:58 2020 -0400
@@ -1,2 +1,3 @@
 Optimize discarding | id:93105ef9d21d33bfb10c67fcac36bc60434d3fb4
+Add fuzz tests against other implementations | id:cd53968480f72453ee820d65a180efe6da0fef71
 Add basic unit tests | id:ed372a368fce619ce8230043635de5258212f607
--- a/TODO	Wed Aug 12 00:38:21 2020 -0400
+++ b/TODO	Wed Aug 12 23:09:58 2020 -0400
@@ -3,5 +3,4 @@
 Add after-read and before-print functions | id:9f982ca45b68d644159e0c64f0dc1b185f72a2f8
 Add opaque-json type | id:a1a380eb9782d088693dfb75402b99c2b30cf039
 Add dynamic extent wrapper definition | id:a937f1179ff1fac77ca501ce7c70449464411f58
-Add fuzz tests against other implementations | id:cd53968480f72453ee820d65a180efe6da0fef71
 Add extra key preservation | id:cfb63b37d87893083fc98477ec3d488fb909a984
--- a/jarl.asd	Wed Aug 12 00:38:21 2020 -0400
+++ b/jarl.asd	Wed Aug 12 23:09:58 2020 -0400
@@ -22,7 +22,7 @@
   :author "Steve Losh <steve@stevelosh.com>"
   :license "MIT"
 
-  :depends-on (:jarl :1am :alexandria)
+  :depends-on (:jarl :1am :alexandria :yason :chancery)
 
   :serial t
   :components ((:module "test"
--- a/test/tests.lisp	Wed Aug 12 00:38:21 2020 -0400
+++ b/test/tests.lisp	Wed Aug 12 23:09:58 2020 -0400
@@ -13,23 +13,29 @@
   ;; This makes it less miserable to write JSON strings in Lisp.
   (substitute #\" #\' string))
 
+(defun obj= (a b &key (test #'equal))
+  (and (= (hash-table-count a)
+          (hash-table-count b))
+       (not (maphash (lambda (ak av)
+                       (multiple-value-bind (bv found) (gethash ak b)
+                         (when (or (not found) (not (funcall test av bv)))
+                           (return-from obj= nil))))
+                     a))))
+
+(defun vec= (a b &key (test #'equal))
+  (and (= (length a) (length b))
+       (every test a b)))
+
 (defgeneric same (a b))
 
 (defmethod same (a b)
   (equal a b))
 
 (defmethod same ((a vector) (b vector))
-  (and (= (length a) (length b))
-       (every #'same a b)))
+  (vec= a b :test #'same))
 
 (defmethod same ((a hash-table) (b hash-table))
-  (and (= (hash-table-count a)
-          (hash-table-count b))
-       (not (maphash (lambda (ak av)
-                       (multiple-value-bind (bv found) (gethash ak b)
-                         (when (or (not found) (not (same av bv)))
-                           (return-from same nil))))
-                     a))))
+  (obj= a b :test #'same))
 
 (defun h (&rest keys-and-values)
   (alexandria:plist-hash-table keys-and-values :test #'equal))
@@ -273,3 +279,110 @@
   (1 1 "00")
   (1 1 "00.0"))
 
+
+;;;; Fuzz Tests ---------------------------------------------------------------
+(defparameter *basic-chars*
+  " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_,'[]{}+=")
+
+(defparameter *gen-depth* 0)
+(defparameter *max-gen-depth* 5)
+
+
+(chancery:define-rule gen/keyword
+  :true
+  :false)
+
+(chancery:define-rule gen/null
+  nil)
+
+(chancery:define-rule (random-char :distribution :weighted)
+  (50 (chancery:eval (string (alexandria:random-elt *basic-chars*))))
+  (1 "\\\\")
+  (1 "\\\"")
+  (1 "\\n")
+  (1 "\\b")
+  (1 "\\f")
+  (1 "\\t")
+  ;; todo more unicode
+  (1 (chancery:eval (format nil "\\u~4,'0X" (char-code (alexandria:random-elt *basic-chars*))))))
+
+(defun random-string ()
+  (with-output-to-string (s)
+    (dotimes (i (random 20))
+      (write-string (random-char) s))))
+
+(chancery:define-rule (gen/string :distribution :weighted)
+  (1 "")
+  (5 random-string))
+
+(chancery:define-rule gen/float
+  0.0d0
+  (chancery:eval (- (random 1d12) 0.5d12)))
+
+(defun gen/integer ()
+  (- (random 100000) 50000))
+
+(chancery:define-rule gen/number
+  0
+  0d0
+  gen/integer
+  gen/float)
+
+(defun gen/vector ()
+  (if (> *gen-depth* *max-gen-depth*)
+    (vector)
+    (let ((result (make-array (random 20)))
+          (*gen-depth* (1+ *gen-depth*)))
+      (dotimes (i (length result))
+        (setf (aref result i) (gen/any)))
+      result)))
+
+(defun gen/object ()
+  (if (> *gen-depth* *max-gen-depth*)
+    (h)
+    (let* ((result (h))
+           (*gen-depth* (1+ *gen-depth*))
+           (keys (remove-duplicates (loop :repeat (random 10)
+                                          :collect (gen/string))
+                                    :test #'equal)))
+      (dolist (k keys)
+        (setf (gethash k result) (gen/any)))
+      result)))
+
+(chancery:define-rule gen/any
+  gen/keyword
+  gen/null
+  gen/string
+  gen/number
+  gen/vector
+  gen/object)
+
+
+(defun yason/read (string)
+  (let ((yason:*parse-json-arrays-as-vectors* t)
+        (yason:*parse-json-booleans-as-symbols* t)
+        (*read-default-float-format* 'double-float))
+    (yason:parse string)))
+
+(defgeneric yason= (a b))
+
+(defmethod yason= (a b)
+  (equal a b))
+
+(defmethod yason= ((a symbol) (b symbol))
+  ;; Yason uses yason:true and yason:false instead of :true and :false.
+  (string= a b))
+
+(defmethod yason= ((a vector) (b vector))
+  (vec= a b :test #'yason=))
+
+(defmethod yason= ((a hash-table) (b hash-table))
+  (obj= a b :test #'yason=))
+
+
+(define-test fuzz-against-yason
+  (dotimes (i 1000)
+    (let ((s (jarl:print (gen/any) nil)))
+      ;; (format t "~&Fuzzing ~S" s)
+      (is (yason= (jarl:read t s)
+                  (yason/read s))))))