Vim window logic, slimv

This commit is contained in:
Ian Keane 2020-02-24 20:27:04 -05:00
parent babcc9e44b
commit 515847d07e
791 changed files with 51552 additions and 86 deletions

View file

@ -0,0 +1,144 @@
(in-package :cl-user)
(defpackage prove.asdf
(:nicknames :prove-asdf)
(:use :cl
:asdf)
(:import-from :prove.output
:*test-result-output*
:*default-reporter*)
(:export :test-file
:run-test-system
:run
:*last-suite-report*))
(in-package :prove.asdf)
(defvar *last-suite-report* nil)
(defvar *system-test-files* (make-hash-table))
(defclass test-file (asdf:cl-source-file) ())
(defmethod asdf:perform ((op asdf:compile-op) (c test-file))
;; do nothing
)
#+asdf3
(defmethod asdf::compute-action-stamp :around (plan (o asdf:operation) (c test-file) &key just-done)
(declare (ignore just-done))
(let ((*error-output* (make-broadcast-stream)))
(call-next-method)))
(defmethod asdf:perform ((op asdf:load-op) (c test-file))
(pushnew c (gethash (asdf:component-system c) *system-test-files*)
:key #'asdf:component-pathname
:test #'equal))
(defun run-test-system (system-designator)
"Runs a testing ASDF system."
#+quicklisp (ql:quickload (if (typep system-designator 'asdf:system)
(asdf:component-name system-designator)
system-designator))
#-quicklisp (asdf:load-system system-designator)
(let ((passed-files '()) (failed-files '()))
(restart-case
(dolist (c (reverse
(gethash (asdf:find-system system-designator) *system-test-files*)))
(setf *last-suite-report* nil)
(format *test-result-output* "~2&Running a test file '~A'~%" (asdf:component-pathname c))
(restart-case
(progn
(asdf:perform 'asdf:load-source-op c)
(unless *last-suite-report*
(warn "Test completed without 'finalize'd."))
(if (eql (getf *last-suite-report* :failed) 0)
(push (asdf:component-pathname c) passed-files)
(push (asdf:component-pathname c) failed-files)))
(skip-test-file ()
:report "Skip this test file."
(push (asdf:component-pathname c) failed-files))))
(skip-all-test-files ()
:report "Give up all test files."
nil))
(setf passed-files (nreverse passed-files)
failed-files (nreverse failed-files))
(format t "~2&Summary:~%")
(if failed-files
(format t " ~D file~:*~P failed.~{~% - ~A~}
"
(length failed-files)
failed-files)
(format t " All ~D file~:*~P passed.~%"
(length passed-files)))
(values (null failed-files)
passed-files
failed-files)))
(defun test-files-in-directory (directory)
(check-type directory pathname)
(flet ((always-true (&rest args)
(declare (ignore args))
T))
(let ((directories '()))
(#+asdf3 uiop:collect-sub*directories
#-asdf3 asdf::collect-sub*directories
directory
#'always-true
#'always-true
(lambda (dir)
(push dir directories)))
(mapcan (lambda (dir)
(#+asdf3 uiop:directory-files
#-asdf3 asdf::directory-files dir "*.lisp"))
(nreverse directories)))))
(defun run (object &key (reporter *default-reporter*))
"Runs a test. OBJECT can be one of a file pathname, a directory pathname or an ASDF system name.
Returns 3 multiple-values, a flag if the tests passed as T or NIL, passed test files as a list and failed test files also as a list.
Example:
(prove:run :myapp-test)
(prove:run #P\"myapp/tests/\")
(prove:run #P\"myapp/tests/01-main.lisp\")
"
(check-type reporter keyword)
(flet ((directory-pathname-p (path)
(string= (file-namestring path) "")))
(let ((*default-reporter* reporter))
(cond
((and (stringp object)
(asdf:find-system object nil))
(run-test-system object))
((stringp object)
(run (pathname object)))
((and (pathnamep object)
(directory-pathname-p object))
(let ((all-passed-p T) (all-passed-files '()) (all-failed-files '()))
(restart-case
(dolist (file (test-files-in-directory object))
(multiple-value-bind (passedp passed-files failed-files)
(run file)
(setf all-passed-files (append all-passed-files passed-files))
(setf all-failed-files (append all-failed-files failed-files))
(unless passedp
(setf all-passed-p nil))))
(skip-all-test-files ()
:report "Give up all test files."
nil))
(values all-passed-p all-passed-files all-failed-files)))
((pathnamep object)
(setf *last-suite-report* nil)
(restart-case
(progn
(load object)
(unless *last-suite-report*
(warn "Test completed without 'finalize'd.")))
(skip-test-file ()
:report "Skip this test file."
nil))
(if (eql (getf *last-suite-report* :failed) 0)
(values T (list object) '())
(values NIL '() (list object))))
(T (run-test-system object))))))
(import 'test-file :asdf)

View file

@ -0,0 +1,37 @@
(in-package :cl-user)
(defpackage prove.color
(:use :cl)
(:import-from :cl-ansi-text
:generate-color-string)
(:import-from :cl-colors
:+gray+
:+grey+)
(:export :*enable-colors*
:with-color))
(in-package :prove.color)
(defvar *enable-colors*
(not (equal (uiop:getenv "EMACS") "t"))
"Flag whether colorize a test report. The default is T except on Emacs (SLIME).")
(defmacro with-gray (stream &body body)
`(progn
(format ,stream (cl-ansi-text::generate-color-string 90))
(unwind-protect (progn ,@body)
(format ,stream (cl-ansi-text::generate-color-string 0)))))
(defmacro with-color ((color &rest args) &body body)
(cond
((or (eq color :gray)
(eq color :grey))
`(if *enable-colors*
(with-gray ,(or (getf args :stream) t) ,@body)
(progn ,@body)))
(T `(if *enable-colors*
(if (or (eq ,color :gray)
(eq ,color :grey)
(eq ,color cl-colors:+gray+)
(eq ,color cl-colors:+grey+))
(with-gray ,(or (getf args :stream) t) ,@body)
(cl-ansi-text:with-color (,color ,@args) ,@body))
(progn ,@body)))))

View file

@ -0,0 +1,13 @@
(in-package :cl-user)
(defpackage prove.output
(:use :cl)
(:export :*test-result-output*
:test-result-output
:*default-reporter*))
(in-package :prove.output)
(defvar *test-result-output* (make-synonym-stream '*standard-output*))
;; This should be in prove.reporter,
;; but it's here because this will also be used in prove-asdf.
(defvar *default-reporter* :list)

View file

@ -0,0 +1,87 @@
(in-package :cl-user)
(defpackage prove
(:nicknames :cl-test-more :test-more)
(:use :cl)
(:import-from :prove.output
:*test-result-output*
:*default-reporter*)
(:import-from :prove.asdf
:test-file
:run-test-system
:run)
(:import-from :prove.test
:*debug-on-error*
:*default-test-function*
:ok
:is
:isnt
:is-values
:is-print
:is-condition
:is-error
:is-type
:like
:is-expand
:diag
:skip
:pass
:fail
:subtest
:deftest
:run-test
:run-test-package
:run-test-all
:remove-test
:remove-test-all
:*gensym-prefix*)
(:import-from :prove.suite
:*default-slow-threshold*
:slow-threshold
:plan
:finalize
:current-suite
:*suite*
:reset-suite
:suite
:package-suite)
(:import-from :prove.color
:*enable-colors*)
(:export :*debug-on-error*
:*test-result-output*
:*default-test-function*
:*default-reporter*
:test-file
:run-test-system
:run
:ok
:is
:isnt
:is-values
:is-print
:is-condition
:is-error
:is-type
:like
:is-expand
:diag
:skip
:pass
:fail
:subtest
:deftest
:run-test
:run-test-package
:run-test-all
:remove-test
:remove-test-all
:plan
:finalize
:*gensym-prefix*
:*default-slow-threshold*
:slow-threshold
:current-suite
:*suite*
:reset-suite
:suite
:package-suite
:*enable-colors*))

View file

@ -0,0 +1,113 @@
(in-package :cl-user)
(defpackage prove.report
(:use :cl)
(:export :report
:test-report
:normal-test-report
:passed-test-report
:failed-test-report
:error-test-report
:skipped-test-report
:comment-report
:composed-test-report
:test-report-p
:passed-report-p
:failed-report-p
:error-report-p
:skipped-report-p
:plan
:children
:description
:notp
:got
:got-form
:expected
:report-expected-label
:duration
:slow-threshold
:print-error-detail))
(in-package :prove.report)
(defclass report ()
((description :type (or null string)
:initarg :description
:initform nil)))
(defclass comment-report (report) ())
(defclass test-report (report)
((duration :initarg :duration
:initform nil)
(slow-threshold :initarg :slow-threshold)
(print-error-detail :type boolean
:initarg :print-error-detail
:initform t)))
(defclass normal-test-report (test-report)
((test-function :type (or function symbol)
:initarg :test-function)
(notp :type boolean
:initarg :notp
:initform nil)
(got :initarg :got
:initform (error ":got is required"))
(got-form :initarg :got-form
:initform '#:unbound)
(expected :initarg :expected
:initform (error ":expected is required"))
(report-expected-label :type (or null string)
:initarg :report-expected-label
:initform nil)))
(defclass composed-test-report (test-report)
((plan :initarg :plan
:initform nil)
(children :initarg :children
:initform (make-array 0 :adjustable t :fill-pointer 0))))
(defclass passed-test-report (normal-test-report) ())
(defclass failed-test-report (normal-test-report) ())
(defclass error-test-report (failed-test-report) ())
(defclass skipped-test-report (normal-test-report) ())
(defun test-report-p (report)
(typep report 'test-report))
(defun passed-report-p (report)
(typecase report
(skipped-test-report nil)
(passed-test-report t)
(composed-test-report
(every #'passed-report-p (slot-value report 'children)))
(otherwise nil)))
(defun failed-report-p (report)
(typecase report
(skipped-test-report nil)
(failed-test-report t)
(composed-test-report
(some #'failed-report-p (slot-value report 'children)))
(otherwise nil)))
(defun error-report-p (report)
(typep report 'error-test-report))
(defun skipped-report-p (report)
(typecase report
(skipped-test-report t)
(composed-test-report
(some #'skipped-report-p (slot-value report 'children)))
(otherwise nil)))
(defmethod print-object ((report normal-test-report) stream)
(with-slots (got notp expected description) report
(format stream
"#<~A RESULT: ~S, GOT: ~S, ~:[~;NOT ~]EXPECTED: ~S~:[~;~:*, DESCRIPTION: ~A~]>"
(type-of report)
(passed-report-p report)
got
notp
expected
description)))

View file

@ -0,0 +1,175 @@
(in-package :cl-user)
(defpackage prove.reporter
(:use :cl)
(:import-from :prove.report
:report
:test-report
:description)
(:import-from :prove.output
:*default-reporter*)
(:export :*indent-level*
:indent-space
:format/indent
:reporter
:format-report
:print-error-report
:print-plan-report
:print-finalize-report
:with-additional-indent))
(in-package :prove.reporter)
(defparameter *indent-level* 0
"Level for nested test-cases output.
Number of spaces, added for each indentation level
is described in reporter's indent-space slot.
Also, macro shift-indent could be used to slightly
indent content inside the main indentation level.
full-indent = indent-space * indent-level + additional-indent
Here is an example of the output:
1| x Blah minor.
2| Next line of description:
3|
4| x Nested test.
5| Also has multiline description.
In this example, indent-space is 4, that is why
text on lines 1 and 4 have 4 spaces between the 'x'
horizontally.
Outputting the first line \" x \", reporter sets
*additional-indent* to 4. That is why these additional
4 lines are prepended to the rest lines of the main
test case description.
When inner testcase runs, it increments *indent-level*,
which shifts output to another 4 spaces (indent-space)
to the right, simultaneously resetting *additional-indent*
to zero.
For nested test, reporter writes \" x \" and again,
sets *additional-indent* to 4 and every other lines now
shifted by 1 * 4 + 4 = 8 spaces.
")
(defparameter *additional-indent* 0
"Number of spaces to add to each line. see *indent-level* docstring for full description.")
(defvar *debug-indentation* nil
"If True, then indentation will have '=' and '-' symbols for main indentaion and additional, instead of spaces.")
(defun indent (space &optional (count *indent-level*))
"Creates a string with a number of spaces to indent new line
of a test report."
(if *debug-indentation*
(concatenate 'string
(make-string (* count space)
:initial-element #\=)
(make-string *additional-indent*
:initial-element #\-))
(make-string (+ (* count space)
*additional-indent*)
:initial-element #\space)))
(defmacro with-additional-indent ((reporter stream control-string &rest format-arguments) &body body)
(declare (ignorable reporter stream control-string))
(let* ((need-new-line (ppcre:scan "^~&" control-string))
(string (apply #'format nil control-string format-arguments))
(increment (length string)))
`(with-slots (indent-space) reporter
(let* ((first-line-indent (indent indent-space))
(*additional-indent* ,(if need-new-line
increment
`(+ *additional-indent*
,increment))))
(declare (ignorable first-line-indent))
,(if need-new-line
`(progn (fresh-line stream)
(write-string first-line-indent ,stream)
;; because we just started a new line, we
;; should use format/indent to write string
;; taking into account a main indentation level
(format/indent ,reporter ,stream ,string))
;; otherwise, just output our prefix
`(write-string ,string ,stream))
,@body))))
(defun format/indent (reporter stream control-string &rest format-arguments)
"Writes a text to given stream with indentation, dictated by
*indent-level* and *additional-indent*.
If first line start with ~&, then output will start from a fresh line.
Otherwise, all lines except the first one are indented."
(with-slots (indent-space) reporter
(let ((output (apply #'format nil control-string format-arguments)))
;; if string starts with new line, then we have to add indentation
;; otherwise we think it is already written to the stream
(when (ppcre:scan "^~&" control-string)
(fresh-line stream)
(format stream (indent indent-space)))
;; if this (?!$) is indended to not insert spaces
;; into empty lines, then (?m) should be inserted
;; before
;; TODO: make a pull-request
(write-string (ppcre:regex-replace-all
"(\\n)(?!$)"
output
(format nil "\\1~A"
(indent indent-space)))
stream))))
(defclass reporter ()
((indent-space :initform 2)))
(defun find-reporter (name)
(make-instance
(intern (format nil "~:@(~A~)-~A" name #.(string :reporter))
(intern (format nil "~A.~:@(~A~)"
#.(string :prove.reporter)
name)
:keyword))))
(defgeneric format-report (stream reporter report &rest args)
(:method (stream (reporter null) (report report) &rest args)
(apply #'format-report
stream
(find-reporter *default-reporter*)
report
args))
(:method (stream (reporter reporter) (report report) &rest args)
(declare (ignore args))
(format/indent reporter stream "~&~A~%"
(slot-value report 'description))))
(defgeneric print-error-report (reporter report stream)
(:method ((reporter reporter) (report report) stream)
;; Do nothing.
)
(:method ((reporter null) (report test-report) stream)
(print-error-report (find-reporter *default-reporter*) report stream)))
(defgeneric print-plan-report (reporter num stream)
(:method ((reporter null) num stream)
(print-plan-report (find-reporter *default-reporter*) num stream))
(:method ((reporter t) num stream)
(declare (ignore reporter num))
;; Do nothing
))
(defgeneric print-finalize-report (reporter plan reports stream)
(:method ((reporter null) plan reports stream)
(print-finalize-report (find-reporter *default-reporter*)
plan
reports
stream)))

View file

@ -0,0 +1,42 @@
(in-package :cl-user)
(defpackage prove.reporter.dot
(:use :cl
:prove.report
:prove.reporter
:prove.reporter.list
:prove.color))
(in-package :prove.reporter.dot)
(defclass dot-reporter (list-reporter) ())
(defmethod format-report (stream (reporter dot-reporter) (report comment-report) &rest args)
(declare (ignore args))
;; Do nothing. This reporter doesn't support 'diag'.
)
(defmethod format-report (stream (reporter dot-reporter) (report test-report) &rest args)
(declare (ignore args))
(when (zerop *indent-level*)
(if *enable-colors*
(with-color ((cond
((failed-report-p report) :red)
((skipped-report-p report) :cyan)
(T :gray)) :stream stream)
(format stream (if (error-report-p report)
"x"
".")))
(write-char (if (failed-report-p report) #\f #\.) stream))))
(defmethod print-finalize-report :before ((reporter dot-reporter) plan reports stream)
(declare (ignore plan reports))
(fresh-line stream))
(defmethod print-finalize-report :after ((reporter dot-reporter) plan reports stream)
(let ((failed-reports (remove-if-not #'failed-report-p reports))
(list-reporter (make-instance 'list-reporter)))
(when failed-reports
(format stream "~2&")
(map nil
(lambda (report)
(format-report stream list-reporter report))
failed-reports))))

View file

@ -0,0 +1,71 @@
(in-package :cl-user)
(defpackage prove.reporter.fiveam
(:use :cl
:prove.report
:prove.reporter))
(in-package :prove.reporter.fiveam)
(defclass fiveam-reporter (reporter) ())
(defmethod format-report (stream (reporter fiveam-reporter) (report comment-report) &rest args)
(declare (ignore stream reporter report args))
;; Do nothing. This reporter doesn't support 'diag'.
)
(defmethod format-report (stream (reporter fiveam-reporter) (report test-report) &rest args)
(declare (ignore args))
(when (zerop *indent-level*)
(write-char (if (failed-report-p report) #\f #\.) stream)))
(defmethod print-error-report ((reporter fiveam-reporter) (report failed-test-report) stream)
(with-slots (description got got-form expected notp report-expected-label print-error-detail) report
(cond
(print-error-detail
(format/indent reporter
stream "~& ~:[(no description)~;~:*~A~]:~% ~S~:[~*~; => ~S~]~% is ~:[~;not ~]expected to ~:[be~;~:*~A~]~% ~S~%"
description
got-form
(not (eq got got-form))
got
notp
report-expected-label
expected))
(T (format/indent reporter stream "~& ~:[(no description)~;~:*~A~]: Failed~%"
description)))))
(defmethod print-error-report ((reporter fiveam-reporter) (report composed-test-report) stream)
(with-slots (plan children description) report
(format/indent reporter stream "~& ~:[(no description)~;~:*~A~]:~%"
description)
(let ((*indent-level* (1+ *indent-level*)))
(print-finalize-report reporter plan children stream))))
(defmethod print-error-report ((reporter fiveam-reporter) (report comment-report) stream)
(format/indent reporter stream "~& ~A~%"
(slot-value report 'description)))
(defmethod print-finalize-report ((reporter fiveam-reporter) plan reports stream)
(let ((failed-count (count-if #'failed-report-p reports))
(passed-count (count-if #'passed-report-p reports))
(skipped-count (count-if #'skipped-report-p reports))
(count (count-if #'test-report-p reports)))
(format/indent reporter stream
"~& Did ~D checks.~:[~*~; (planned ~D tests)~]~%"
count
(not (eql plan count))
plan)
(unless (zerop count)
(format/indent reporter
stream " Pass: ~D (~3D%)~%" passed-count (round (* (/ passed-count count) 100)))
(unless (zerop skipped-count)
(format/indent reporter
stream " Skip: ~D (~3D%)~%" skipped-count (round (* (/ skipped-count count) 100))))
(format/indent reporter
stream " Fail: ~D (~3D%)~%" failed-count (round (* (/ failed-count count) 100))))
(unless (zerop failed-count)
(format/indent reporter
stream "~2& Failure Details:~% --------------------------------~%")
(loop for report across reports
when (failed-report-p report)
do (print-error-report reporter report stream)
(format/indent reporter stream " --------------------------------~%")))))

View file

@ -0,0 +1,171 @@
(in-package :cl-user)
(defpackage prove.reporter.list
(:use :cl
:prove.report
:prove.reporter)
(:import-from :prove.color
:with-color)
(:export :list-reporter
:report-expected-line))
(in-package :prove.reporter.list)
(defclass list-reporter (reporter) ())
(defmethod format-report (stream (reporter list-reporter) (report comment-report) &rest args)
(declare (ignore args))
(with-additional-indent (reporter stream "~& ")
(with-color (:white :stream stream)
(format/indent reporter stream (slot-value report 'description)))
(terpri stream)))
(defun omit-long-value (value)
(typecase value
(string
(if (< 500 (length value))
(format nil "\"~A ...\"" (subseq value 0 94))
(prin1-to-string value)))
(otherwise
(let ((value (prin1-to-string value)))
(if (< 500 (length value))
(format nil "~A ..." (subseq value 0 96))
value)))))
(defgeneric report-expected-line (report)
(:documentation "Reports about failed or passed test.
Should return a string with description of what have happened.")
(:method ((report normal-test-report))
(with-slots (got got-form notp report-expected-label expected) report
(escape-tildes
(format nil "~A is ~:[~;not ~]expected to ~:[be~;~:*~A~] ~A~:[ (got ~S)~;~*~]"
(omit-long-value (or got-form got))
notp
report-expected-label
(omit-long-value expected)
(eq got got-form)
got)))))
(defun escape-tildes (text)
(ppcre:regex-replace-all "~" text "~~"))
(defun possible-report-description (report)
(cond
((slot-value report 'description)
(format nil "~A~:[~; (Skipped)~]"
(escape-tildes (slot-value report 'description))
(skipped-report-p report)))
(T (report-expected-line report))))
(defun print-duration (stream duration &optional slow-threshold)
(let ((color (if slow-threshold
(cond
((< slow-threshold duration) :red)
((< (/ slow-threshold 2) duration) :yellow))
:gray)))
(when color
(with-color (color :stream stream)
(format stream "(~Dms)" duration)))))
(defmethod format-report (stream (reporter list-reporter) (report normal-test-report) &rest args)
(declare (ignore args))
(with-additional-indent (reporter stream "~& ")
(with-color (:green :stream stream)
(with-additional-indent (reporter stream "✓ ")
(let ((description (possible-report-description report))
(duration (slot-value report 'duration)))
(when description
(with-color (:gray :stream stream)
(format/indent reporter stream description)))
(when duration
(format stream " ")
(print-duration stream duration (slot-value report 'slow-threshold))))
(terpri stream)))))
(defmethod format-report (stream (reporter list-reporter) (report skipped-test-report) &rest args)
(declare (ignore args))
(with-additional-indent (reporter stream "~& ")
(with-color (:cyan :stream stream)
(with-additional-indent (reporter stream "- ")
(let ((description (possible-report-description report)))
(when description
(format/indent reporter stream description))))
(terpri stream))))
(defmethod format-report (stream (reporter list-reporter) (report failed-test-report) &rest args)
(declare (ignore args))
(with-additional-indent (reporter stream "~& ")
(with-color (:red :stream stream)
(with-additional-indent (reporter stream "× ")
(let ((description (possible-report-description report))
(duration (slot-value report 'duration)))
(when description
(format/indent reporter stream description))
(when duration
(format stream " ")
(print-duration stream duration (slot-value report 'slow-threshold))))
(when (slot-value report 'description)
(format/indent reporter stream
(concatenate 'string "~&" (report-expected-line report)))))
(terpri stream))))
(defmethod format-report (stream (reporter list-reporter) (report error-test-report) &rest args)
(declare (ignore args))
;; format/indent
(with-additional-indent (reporter stream "~& ")
(with-color (:red :stream stream)
(with-additional-indent (reporter stream "× ")
(when (slot-value report 'description)
(format/indent reporter stream "~A~%" (slot-value report 'description)))
(format/indent reporter stream "Raised an error ~A (expected: ~S)"
(slot-value report 'got)
(slot-value report 'expected)))))
(terpri stream))
(defmethod format-report (stream (reporter list-reporter) (report composed-test-report) &rest args)
(declare (ignore args))
;; Do nothing
)
(defmethod print-plan-report ((reporter list-reporter) num stream)
(when (numberp num)
(format/indent reporter stream "~&1..~A~2%" num)))
(defmethod print-finalize-report ((reporter list-reporter) plan reports stream)
(let ((failed-count (count-if #'failed-report-p reports))
(skipped-count (count-if #'skipped-report-p reports))
(count (count-if #'test-report-p reports)))
(format/indent reporter stream "~2&")
(cond
((eq plan :unspecified)
(with-color (:yellow :stream stream)
(format/indent reporter stream
"△ Tests were run but no plan was declared.~%")))
((and plan
(not (= count plan)))
(with-color (:yellow :stream stream)
(format/indent reporter stream
"△ Looks like you planned ~D test~:*~P but ran ~A.~%"
plan count))))
(if (< 0 failed-count)
(with-color (:red :stream stream)
(format/indent reporter stream
"× ~D of ~D test~:*~P failed"
failed-count count))
(with-color (:green :stream stream)
(format/indent reporter stream
"✓ ~D test~:*~P completed" count)))
(format stream " ")
(print-duration stream
(reduce #'+
(remove-if-not #'test-report-p reports)
:key (lambda (report) (or (slot-value report 'duration) 0))))
(terpri stream)
(unless (zerop skipped-count)
(with-color (:cyan :stream stream)
(format/indent reporter stream "● ~D test~:*~P skipped" skipped-count))
(terpri stream))))

View file

@ -0,0 +1,70 @@
(in-package :cl-user)
(defpackage prove.reporter.tap
(:use :cl
:prove.report
:prove.reporter))
(in-package :prove.reporter.tap)
(defclass tap-reporter (reporter)
((indent-space :initform 4)))
(defmethod format-report (stream (reporter tap-reporter) (report comment-report) &rest args)
(declare (ignore args))
(format/indent reporter stream "~&# ~A~%"
(slot-value report 'description)))
(defmethod format-report (stream (reporter tap-reporter) (report test-report) &key count)
(with-slots (description print-error-detail) report
(format/indent reporter stream
"~&~:[not ~;~]ok~:[~;~:* ~D~]~:[~;~:* - ~A~]~%"
(or (passed-report-p report)
(skipped-report-p report))
count
description)
(print-error-report reporter report stream)))
(defmethod format-report (stream (reporter tap-reporter) (report skipped-test-report) &key count)
(format/indent reporter stream
"~&ok~:[~;~:* ~D~] - skip~:[~;~:* ~A~]~%"
count
(slot-value report 'description)))
(defmethod print-error-report ((reporter tap-reporter) (report failed-test-report) stream)
(with-slots (got got-form expected notp report-expected-label print-error-detail) report
(when print-error-detail
(format/indent reporter stream
"~&# got: ~S~:[~*~; => ~S~]~%# ~:[~;not ~]expected~:[~;~:* to ~A~]: ~S~%"
got-form
(not (eq got got-form))
got
notp
report-expected-label
expected))))
(defmethod print-plan-report ((reporter tap-reporter) num stream)
(when (numberp num)
(format-report stream
reporter
(make-instance 'report
:description (format nil "1..~A" num)))))
(defmethod print-finalize-report ((reporter tap-reporter) plan reports stream)
(let ((failed-count (count-if #'failed-report-p reports))
(count (count-if #'test-report-p reports)))
(cond
((eq plan :unspecified)
(format/indent reporter stream
"~&# Tests were run but no plan was declared.~%"))
((and plan
(not (= count plan)))
(format/indent reporter stream
"~&# Looks like you planned ~D test~:*~P but ran ~A.~%"
plan count)))
(fresh-line stream)
(if (< 0 failed-count)
(format/indent reporter stream
"# Looks like you failed ~D test~:*~P of ~A run."
failed-count count)
(format/indent reporter stream "# All ~D test~:*~P passed."
count))
(terpri stream)))

View file

@ -0,0 +1,92 @@
(in-package :cl-user)
(defpackage prove.suite
(:use :cl)
(:import-from :prove.output
:*test-result-output*)
(:import-from :prove.report
:report
:failed-report-p)
(:import-from :prove.reporter
:print-plan-report
:print-finalize-report)
(:import-from :prove.asdf
:*last-suite-report*)
(:export :*suite*
:current-suite
:suite
:package-suite
:suite-plan
:test-count
:failed
:reports
:slow-threshold
:*default-slow-threshold*
:add-report
:plan
:finalize))
(in-package :prove.suite)
(defparameter *suite* nil)
(defparameter *default-slow-threshold* 75)
(defclass suite ()
((plan :initarg :plan
:initform :unspecified
:accessor suite-plan)
(slow-threshold :initarg :slow-threshold
:initform *default-slow-threshold*)
(test-count :initform 0
:accessor test-count)
(failed :initform 0
:accessor failed)
(reports :initform (make-array 0 :adjustable t :fill-pointer 0)
:accessor reports)))
(defun slow-threshold (&optional new-threshold)
(if new-threshold
(setf (slot-value (current-suite) 'slow-threshold) new-threshold)
(slot-value (current-suite) 'slow-threshold)))
(defclass package-suite (suite) ())
(defvar *defined-suites* (make-hash-table :test 'equal))
(defun find-package-suite (package-designator)
(let ((package (typecase package-designator
(package package-designator)
(T (find-package package-designator)))))
(or (gethash (package-name package) *defined-suites*)
(setf (gethash (package-name package) *defined-suites*)
(make-instance 'package-suite)))))
(defun current-suite ()
(or *suite*
(find-package-suite *package*)))
(defun reset-suite (suite)
(with-slots (test-count failed reports) suite
(setf test-count 0)
(setf failed 0)
(setf reports (make-array 0 :adjustable t :fill-pointer 0))))
(defun add-report (report suite)
(check-type report report)
(when (failed-report-p report)
(incf (slot-value suite 'failed)))
(vector-push-extend report (slot-value suite 'reports)))
(defun plan (num)
(let ((suite (current-suite)))
(setf (slot-value suite 'plan) num)
(reset-suite suite))
(print-plan-report nil num *test-result-output*))
(defun finalize (&optional (suite (current-suite)))
(with-slots (plan reports failed) suite
(print-finalize-report nil plan reports *test-result-output*)
(setf *last-suite-report*
(list :plan plan :failed failed))
(zerop failed)))

View file

@ -0,0 +1,366 @@
(in-package :cl-user)
(defpackage prove.test
(:use :cl)
(:import-from :prove.output
:*test-result-output*)
(:import-from :prove.report
:test-report-p
:passed-test-report
:failed-test-report
:error-test-report
:skipped-test-report
:comment-report
:composed-test-report
:failed-report-p
:duration)
(:import-from :prove.reporter
:format-report
:*indent-level*
:*additional-indent*)
(:import-from :prove.suite
:suite
:*suite*
:suite-plan
:test-count
:failed
:reports
:slow-threshold
:current-suite
:finalize
:add-report)
(:import-from :alexandria
:with-gensyms
:once-only)
(:export :*default-test-function*
:*debug-on-error*
:ok
:is
:isnt
:is-values
:is-print
:is-condition
:is-error
:is-type
:like
:is-expand
:diag
:skip
:pass
:fail
:subtest
:*gensym-prefix*
:deftest
:run-test
:run-test-package
:run-test-all
:remove-test
:remove-test-all))
(in-package :prove.test)
(defvar *debug-on-error* nil)
(defvar *default-test-function* #'equal)
(defun parse-description-and-test (args)
(if (consp args)
(case (length args)
(1 (car args))
(2 (if (eq :test (car args))
(values nil (cadr args))
(car args)))
(t (let ((k (member :test args)))
(case (length k)
((0 1) (car args))
(2 (values (car args) (cadr k)))
(t (values (nth 2 k) (cadr k)))))))
args))
(defun test (got expected args
&key notp
duration
(got-form nil got-form-supplied-p)
(test-fn *default-test-function*)
(passed-report-class 'passed-test-report)
(failed-report-class 'failed-test-report)
report-expected-label
(print-error-detail t)
(output t))
(multiple-value-bind (desc arg-test)
(parse-description-and-test args)
(let* ((test-function (or arg-test test-fn))
(result (funcall test-function got expected))
(result (if notp (not result) result))
(suite (current-suite))
(report (apply #'make-instance
(if result
passed-report-class
failed-report-class)
:duration duration
:slow-threshold (slot-value suite 'slow-threshold)
:test-function test-function
:notp notp
:got got
:got-form (if got-form-supplied-p
got-form
got)
:expected expected
:description desc
:print-error-detail print-error-detail
(and report-expected-label
(list :report-expected-label report-expected-label)))))
(add-report report suite)
(unless result
(incf (failed suite)))
(incf (test-count suite))
(when output
(format-report *test-result-output* nil report :count (test-count suite)))
(values result report))))
(defmacro with-duration (((duration result) form) &body body)
(with-gensyms (start end)
`(let* ((,start (get-internal-real-time))
(,result ,form)
(,end (get-internal-real-time))
(,duration (- ,end ,start)))
,@body)))
(defmacro with-catching-errors ((&key description expected) &body body)
(with-gensyms (e suite report)
`(if *debug-on-error*
(progn ,@body)
(handler-case (progn ,@body)
(error (,e)
(let ((,suite (current-suite))
(,report (make-instance 'error-test-report
:got ,e
:got-form ,e
:expected ,expected
:description ,description
:duration nil)))
(add-report ,report ,suite)
(incf (failed ,suite))
(incf (test-count ,suite))
(format-report *test-result-output* nil ,report :count (test-count ,suite))))))))
(defmacro ok (test &optional desc)
(with-gensyms (duration result)
(once-only (test desc)
`(with-catching-errors (:expected T :description ,desc)
(with-duration ((,duration ,result) ,test)
(test ,result t ,desc
:duration ,duration
:test-fn (lambda (x y)
(eq (not (null x)) y))
:got-form ,test))))))
(defmacro is (got expected &rest args)
(with-gensyms (duration result new-args desc)
(once-only (expected)
`(let* ((,new-args (list ,@args))
(,desc (parse-description-and-test ,new-args)))
(with-catching-errors (:description ,desc :expected ,expected)
(with-duration ((,duration ,result) ,got)
(test ,result ,expected ,new-args
:duration ,duration)))))))
(defmacro isnt (got expected &rest args)
(with-gensyms (duration result new-args desc)
(once-only (expected)
`(let* ((,new-args (list ,@args))
(,desc (parse-description-and-test ,new-args)))
(with-catching-errors (:description ,desc :expected ,expected)
(with-duration ((,duration ,result) ,got)
(test ,result ,expected ,new-args
:notp t
:duration ,duration)))))))
(defmacro is-values (got expected &rest args)
`(is (multiple-value-list ,got) ,expected ,@args))
(defmacro is-print (got expected &optional desc)
(with-gensyms (output duration duration-inner)
(once-only (expected desc)
`(with-catching-errors (:description ,desc :expected ,expected)
(let* (,duration
(,output (with-output-to-string (*standard-output*)
(with-duration ((,duration-inner ,output) ,got)
(declare (ignore ,output))
(setq ,duration ,duration-inner)))))
(test ,output ,expected ,desc
:duration ,duration
:got-form ',got
:test-fn #'string=
:report-expected-label "output"))))))
(defmacro is-condition (form condition &optional desc)
(with-gensyms (error duration)
`(with-duration ((,duration ,error) (handler-case ,form
(condition (,error) ,error)))
(test ,error
,(if (and (listp condition) (eq 'quote (car condition)))
condition
`(quote ,condition))
,desc
:duration ,duration
:got-form ',form
:test-fn #'typep
:report-expected-label "raise a condition"))))
;;; alias is-error to is-condition
(setf (macro-function 'is-error) (macro-function 'is-condition))
(defmacro is-type (got expected-type &optional desc)
(with-gensyms (duration result)
(once-only (desc expected-type)
`(with-catching-errors (:description ,desc :expected ,expected-type)
(with-duration ((,duration ,result) ,got)
(test ,result ,expected-type ,desc
:duration ,duration
:got-form ',got
:test-fn #'typep
:report-expected-label "be a type of"))))))
(defmacro like (got regex &optional desc)
(with-gensyms (duration result)
(once-only (regex desc)
`(with-catching-errors (:description ,desc :expected ,regex)
(with-duration ((,duration ,result) ,got)
(test ,result ,regex ,desc
:duration ,duration
:test-fn (lambda (x y) (not (null (ppcre:scan y x))))
:report-expected-label "be like"))))))
(defvar *gensym-prefix* "$")
(defvar *gensym-alist* nil)
(defun gensymp (val)
(and (symbolp val)
(string= (subseq (symbol-name val) 0 (length *gensym-prefix*)) *gensym-prefix*)))
(defgeneric gensym-tree-equal (x y)
(:method (x y)
(if (and (gensymp y) (symbolp x))
(if (assoc y *gensym-alist*)
(eq x (cdr (assoc y *gensym-alist*)))
(unless (rassoc x *gensym-alist*)
(setf *gensym-alist* `((,y . ,x) ,@*gensym-alist*))
t))
(equal x y)))
(:method ((x cons) (y cons))
(loop for a in x for b in y
always (gensym-tree-equal a b))))
(defmacro is-expand (got expected &optional desc)
(with-gensyms (duration expanded)
(once-only (desc)
`(with-duration ((,duration ,expanded) (macroexpand-1 ',got))
(let (*gensym-alist*)
(test ,expanded ',expected ,desc
:duration ,duration
:got-form ',got
:report-expected-label "be expanded to"
:test-fn #'gensym-tree-equal))))))
(defun diag (desc)
(let ((report (make-instance 'comment-report
:description desc)))
(add-report report (current-suite))
(format-report *test-result-output* nil report)))
(defun skip (how-many why &rest format-args)
(check-type how-many integer)
(dotimes (i how-many)
(test t t (apply #'format nil why format-args)
:passed-report-class 'skipped-test-report)))
(defun pass (desc)
(test t t desc))
(defun fail (desc)
(test t nil desc
:print-error-detail nil))
(defun %subtest (desc body-fn)
(diag desc)
(let ((report
(let ((*suite* (make-instance 'suite))
(*indent-level* (1+ *indent-level*))
(*additional-indent* 0))
(if *debug-on-error*
(funcall body-fn)
(handler-case (funcall body-fn)
(error (e)
(let ((error-report
(make-instance 'error-test-report
:expected :non-error
:got e
:description (format nil "Aborted due to an error in subtest ~S" desc))))
(add-report error-report *suite*)
(format-report *test-result-output* nil error-report :count (test-count *suite*))))))
(make-instance 'composed-test-report
:duration (reduce #'+
(remove-if-not #'test-report-p (reports *suite*))
:key (lambda (report) (or (slot-value report 'duration) 0)))
:plan (suite-plan *suite*)
:description desc
:children (reports *suite*))))
(suite (current-suite)))
(add-report report suite)
(incf (test-count suite))
(format-report *test-result-output* nil report :count (test-count suite))))
(defmacro subtest (desc &body body)
`(%subtest ,desc (lambda () ,@body)))
(defvar *package-tests* (make-hash-table))
(defmacro deftest (name &body test-forms)
(let ((tests (gensym "TESTS"))
(test (gensym "TEST"))
(test-fn (gensym "TEST-FN")))
`(progn
(unless (nth-value 1 (gethash *package* *package-tests*))
(setf (gethash *package* *package-tests*) '()))
(let* ((,tests (gethash *package* *package-tests*))
(,test (assoc ',name ,tests :test #'string=))
(,test-fn (lambda ()
(subtest (princ-to-string ',name)
,@test-forms))))
(if ,test
(rplacd ,test ,test-fn)
(push (cons ',name ,test-fn) (gethash *package* *package-tests*)))
',name))))
(defun run-test (name)
(let ((test (assoc name
(gethash *package* *package-tests*)
:test #'string=)))
(unless test
(error "Test not found: ~S" name))
(funcall (cdr test))))
(defun run-test-package (package-designator)
(let ((*package* (typecase package-designator
(package package-designator)
(T (find-package package-designator)))))
(loop for (name . test-fn) in (reverse (gethash *package* *package-tests*))
do (funcall test-fn))
(finalize)))
(defun run-test-all ()
(maphash (lambda (package tests)
(declare (ignore tests))
(run-test-package package))
*package-tests*))
(defun remove-test (name)
(setf (gethash *package* *package-tests*)
(delete name
(gethash *package* *package-tests*)
:key #'car
:test #'string=)))
(defun remove-test-all ()
(setf (gethash *package* *package-tests*) nil))