suite.lisp (13626 bytes)
1 ;;; suite.lisp -- run a vendored R7RS test suite (tests/r7rs/r7rs-tests.scm, chibi-scheme) 2 ;;; on the chip. Loaded by host.lisp. 3 ;;; 4 ;;; The host splits the file into top-level forms and sends each one as a single line. 5 ;;; tests/r7rs/shim.scm defines `test`, `test-assert`, test-begin and test-end on the chip 6 ;;; as procedures that print <<pass>> or <<fail VALUE>>. tests/r7rs/skip.scm names what 7 ;;; cannot run yet, with a reason. Every skipped test is counted. A form that fails before 8 ;;; all of its tests have reported counts the missing ones as failures. 9 10 (in-package :lfl) 11 12 (defun split-forms (text) 13 "Top-level data of TEXT as strings. Understands ; #| |# #; comments, strings, #\\ chars, |sym|." 14 (let ((forms '()) (i 0) (n (length text))) 15 (labels ((peek (k) (if (< (+ i k) n) (char text (+ i k)) #\Nul)) 16 (delim-p (c) (or (member c '(#\Space #\Tab #\Newline #\Return #\( #\) #\" #\;)) (char= c #\Nul))) 17 (skip-ws () 18 (loop 19 (cond ((>= i n) (return)) 20 ((member (peek 0) '(#\Space #\Tab #\Newline #\Return #\Page)) (incf i)) 21 ((char= (peek 0) #\;) (loop until (or (>= i n) (char= (peek 0) #\Newline)) do (incf i))) 22 ((and (char= (peek 0) #\#) (char= (peek 1) #\|)) 23 (incf i 2) 24 (let ((depth 1)) 25 (loop while (and (< i n) (plusp depth)) 26 do (cond ((and (char= (peek 0) #\|) (char= (peek 1) #\#)) (decf depth) (incf i 2)) 27 ((and (char= (peek 0) #\#) (char= (peek 1) #\|)) (incf depth) (incf i 2)) 28 (t (incf i)))))) 29 ((and (char= (peek 0) #\#) (char= (peek 1) #\;)) (incf i 2) (skip-ws) (datum)) 30 (t (return))))) 31 (datum () 32 "Skip one datum starting at i." 33 (let ((c (peek 0))) 34 (cond ((member c '(#\' #\` #\,)) 35 (incf i) (when (char= (peek 0) #\@) (incf i)) (skip-ws) (datum)) 36 ((char= c #\() (incf i) 37 (loop (skip-ws) 38 (cond ((>= i n) (return)) 39 ((char= (peek 0) #\)) (incf i) (return)) 40 (t (datum))))) 41 ((char= c #\)) (incf i)) ; stray close: consume 42 ((char= c #\") (incf i) 43 (loop until (or (>= i n) (char= (peek 0) #\")) 44 do (incf i (if (char= (peek 0) #\\) 2 1))) 45 (incf i)) 46 ((char= c #\|) (incf i) 47 (loop until (or (>= i n) (char= (peek 0) #\|)) 48 do (incf i (if (char= (peek 0) #\\) 2 1))) 49 (incf i)) 50 ((and (char= c #\#) (char= (peek 1) #\\)) 51 (incf i 3) ; #\ plus at least one character 52 (loop until (delim-p (peek 0)) do (incf i))) 53 ((and (char= c #\#) (member (peek 1) '(#\( ))) 54 (incf i) (datum)) 55 ((and (char= c #\#) (char-equal (peek 1) #\u) (char= (peek 2) #\8) (char= (peek 3) #\()) 56 (incf i 3) (datum)) 57 (t (loop until (delim-p (peek 0)) do (incf i))))))) 58 (loop 59 (skip-ws) 60 (when (>= i n) (return)) 61 (let ((start i)) (datum) (push (subseq text start i) forms)))) 62 (nreverse forms))) 63 64 (defun one-line (form) 65 "FORM on one line: newlines outside strings become spaces, inside strings \\n; comments dropped." 66 (with-output-to-string (o) 67 (let ((i 0) (n (length form)) (in-str nil) (prev-space nil)) 68 (loop while (< i n) do 69 (let ((c (char form i))) 70 (cond (in-str 71 (cond ((char= c #\\) (write-char c o) (incf i) (when (< i n) (write-char (char form i) o))) 72 ((char= c #\") (setf in-str nil) (write-char c o)) 73 ((char= c #\Newline) (write-string "\\n" o)) 74 (t (write-char c o)))) 75 ((and (char= c #\#) (< (1+ i) n) (char= (char form (1+ i)) #\\)) 76 (write-string "#\\" o) (incf i 2) (when (< i n) (write-char (char form i) o)) (setf prev-space nil)) 77 ((char= c #\") (setf in-str t) (write-char c o) (setf prev-space nil)) 78 ((char= c #\;) (loop while (and (< i n) (char/= (char form i) #\Newline)) do (incf i)) (decf i)) 79 ((member c '(#\Space #\Tab #\Newline #\Return)) 80 (unless prev-space (write-char #\Space o)) (setf prev-space t)) 81 (t (write-char c o) (setf prev-space nil)))) 82 (incf i))))) 83 84 (defparameter *test-heads* '("test" "test-assert" "test-error" "test-values" "test-numeric-syntax" 85 "test-precision" "test-read-error" "test-write-syntax")) 86 87 (defun form-head (form) 88 (let* ((s (string-left-trim "(" (string-trim '(#\Space #\Newline) form))) 89 (e (or (position-if (lambda (c) (member c '(#\Space #\Newline #\( #\)))) s) (length s)))) 90 (subseq s 0 e))) 91 92 (defun nested-tests (form) 93 "Heads of the test calls inside FORM, in order." 94 (let ((heads '()) (start 0)) 95 (loop 96 (let ((p (search "(test" form :start2 start))) 97 (unless p (return (nreverse heads))) 98 (let ((h (form-head (subseq form p)))) 99 (when (member h *test-heads* :test #'string=) (push h heads))) 100 (setf start (1+ p)))))) 101 102 (defun read-skips (path) 103 "skip.scm entries: (section NAME REASON) (head NAME REASON) (match SUBSTRING REASON) 104 (literal KIND REASON) (nonlatin1 \"\" REASON)." 105 (with-open-file (s path) 106 (let ((*read-eval* nil)) 107 (loop for e = (read s nil) while e 108 collect (list (intern (string-upcase (symbol-name (first e))) :keyword) (second e) (third e)))))) 109 110 (defun tokens (form) 111 "Identifier-like tokens of FORM (a crude split on delimiters)." 112 (let ((out '()) (start nil)) 113 (loop for i from 0 to (length form) 114 for c = (if (< i (length form)) (char form i) #\Space) 115 do (if (member c '(#\Space #\Newline #\Tab #\( #\) #\' #\")) 116 (when start (push (subseq form start i) out) (setf start nil)) 117 (unless start (setf start i)))) 118 out)) 119 120 (defun defined-names (form) 121 "Names a skipped setup form would have defined: the name after define / define-syntax, or 122 every token of a define-record-type." 123 (let ((head (form-head form)) (toks (reverse (tokens form)))) 124 (cond ((string= head "define-record-type") 125 ;; type, constructor, predicate, and each field's accessor/modifier -- not field names 126 (let ((f (handler-case (let ((*readtable* (copy-readtable nil)) (*read-eval* nil)) 127 (setf (readtable-case *readtable*) :preserve) 128 (read-from-string form)) 129 (error () nil)))) 130 (if (and (consp f) (>= (length f) 4)) 131 (mapcar #'princ-to-string 132 (remove-if-not #'symbolp 133 (append (list (second f) (if (consp (third f)) (first (third f)) (third f)) (fourth f)) 134 (loop for fld in (nthcdr 4 f) when (consp fld) append (cdr fld))))) 135 (cdr toks)))) 136 ((member head '("define" "define-syntax" "define-values") :test #'string=) 137 (list (string-left-trim "(" (second toks)))) 138 (t '())))) 139 140 (defvar *skipped-names* '() "(name . reason) for definitions that were skipped.") 141 142 (defun literal-kind (tok) 143 "Which number literal TOK is: :bignum (beyond 31 bits), :complex, :float, :ratio, :prefix, or NIL." 144 (let ((digits (some #'digit-char-p tok)) (n (length tok))) 145 (cond ((zerop n) nil) 146 ((and (every (lambda (c) (or (digit-char-p c) (find c "+-"))) tok) digits 147 (let ((v (ignore-errors (parse-integer tok)))) (and v (not (<= -1073741824 v 1073741823))))) 148 :bignum) 149 ((and (> n 1) (char= (char tok 0) #\#) (find (char-downcase (char tok 1)) "eixbod")) :prefix) 150 ((member tok '("+inf.0" "-inf.0" "+nan.0" "-nan.0") :test #'string=) :float) 151 ((and (char-equal (char tok (1- n)) #\i) (or digits (member tok '("+i" "-i") :test #'string=)) 152 (every (lambda (c) (or (digit-char-p c) (find (char-downcase c) "+-./eianf"))) tok)) 153 :complex) 154 ((and digits (every (lambda (c) (or (digit-char-p c) (find c "+-/"))) tok) (find #\/ tok) 155 (> (count-if #'digit-char-p tok) 1)) 156 :ratio) 157 ((and digits (every (lambda (c) (or (digit-char-p c) (find (char-downcase c) "+-.e"))) tok) 158 (or (find #\. tok) (find #\e tok)) (digit-char-p (char tok (if (find (char tok 0) "+-.") (min 1 (1- n)) 0)))) 159 :float) 160 ((and (> n 1) (char= (char tok 0) #\.) (digit-char-p (char tok 1))) :float)))) 161 162 (defun skip-reason (skips section form heads) 163 (let ((toks (tokens form))) 164 (dolist (sn *skipped-names*) 165 (when (member (car sn) toks :test #'string=) 166 (return-from skip-reason (format nil "uses ~a, skipped: ~a" (car sn) (cdr sn)))))) 167 (dolist (e skips) 168 (destructuring-bind (kind what reason) e 169 (when (ecase kind 170 (:section (string= what section)) 171 (:head (or (string= what (form-head form)) (member what heads :test #'string=))) 172 (:match (search what form)) 173 (:nonlatin1 (some (lambda (c) (> (char-code c) 255)) form)) 174 (:literal (some (lambda (tk) (eq (literal-kind tk) (intern (string-upcase what) :keyword))) 175 (tokens form)))) 176 (return reason))))) 177 178 (defun run-suite (dir) 179 "Run DIR/r7rs-tests.scm with DIR/shim.scm and DIR/skip.scm. Write DIR/results.txt." 180 (let* ((text (with-open-file (s (here (concatenate 'string dir "r7rs-tests.scm")) :external-format :utf-8) 181 (let ((str (make-string (file-length s)))) (subseq str 0 (read-sequence str s))))) 182 (forms (split-forms text)) 183 (skips (read-skips (here (concatenate 'string dir "skip.scm")))) 184 (section "") (pass 0) (fail 0) (skip 0) (setup-skipped 0) 185 (reasons (make-hash-table :test 'equal)) (failures '())) 186 (let ((*log* nil)) 187 (dolist (l (file-lines (concatenate 'string dir "shim.scm"))) (lisp-eval l))) 188 (dolist (form forms) 189 (let* ((head (form-head form)) (heads (nested-tests form)) (k (length heads))) 190 (when (string= head "test-begin") 191 (let ((q (position #\" form))) (when q (setf section (subseq form (1+ q) (position #\" form :start (1+ q))))))) 192 (let ((reason (skip-reason skips section form heads))) 193 (cond (reason 194 (when (zerop k) 195 (dolist (nm (defined-names form)) (push (cons nm reason) *skipped-names*))) 196 (if (zerop k) (incf setup-skipped) (incf skip k)) 197 (incf (gethash reason reasons 0) (max k 0))) 198 (t 199 (let* ((line (one-line form)) 200 (out (handler-case (lisp-eval line :timeout 60) 201 (error (e) (format nil "HOST ERROR ~a" e)))) 202 (p (count-substr "<<pass>>" out))) 203 (incf pass p) 204 (when (> k p) 205 (incf fail (- k p)) 206 (push (format nil "[~a] ~a~% => ~a" section 207 (subseq line 0 (min 160 (length line))) 208 (subseq out 0 (min 160 (length out)))) 209 failures)) 210 (when (search "HOST ERROR" out) 211 (let ((rest (reduce #'+ (mapcar (lambda (f) (length (nested-tests f))) 212 (cdr (member form forms)))))) 213 (incf fail rest) 214 (push (format nil "ABORTED: the chip stopped answering; ~d later tests counted as failed" rest) failures)) 215 (return)))))))) 216 (let ((total (+ pass fail skip))) 217 (with-open-file (o (here (concatenate 'string dir "results.txt")) :direction :output :if-exists :supersede) 218 (format o "r7rs suite: ~d passed, ~d failed, ~d skipped, ~d total~%" pass fail skip total) 219 (format o "setup forms skipped: ~d~%~%skipped tests by reason:~%" setup-skipped) 220 (maphash (lambda (r c) (format o " ~4d ~a~%" c r)) reasons) 221 (format o "~%failures:~%~{~a~%~}" (reverse failures))) 222 (format t "~&r7rs suite: ~d passed, ~d failed, ~d skipped, ~d total~%" pass fail skip total)))) 223 224 (defun count-substr (sub s) 225 (loop with start = 0 for p = (search sub s :start2 start) while p count t do (setf start (1+ p)))) 226 227 (defun analyze (path) 228 "Host only: list form heads and the longest one-line form." 229 (let* ((text (with-open-file (s path :external-format :utf-8) 230 (let ((str (make-string (file-length s)))) (subseq str 0 (read-sequence str s))))) 231 (forms (split-forms text)) (counts (make-hash-table :test 'equal)) (tests 0)) 232 (dolist (f forms) 233 (incf (gethash (form-head f) counts 0)) 234 (incf tests (length (nested-tests f)))) 235 (format t "~d top-level forms, ~d test calls, longest line ~d~%" (length forms) tests 236 (reduce #'max (mapcar (lambda (f) (length (one-line f))) forms))) 237 (maphash (lambda (h c) (format t " ~4d ~a~%" c h)) counts)))