xl-expectations.l minimal なの

(expect (+ 1 2 3)
  (returns 6))
=> t

(expect (concat "foo" "bar")
  (returns "foobar"))
=> t

(expect (add 1 2 3)
  (returns 6))
(add 1 2 3)
   Expected: return 6
   Actually: signaled undefined-function.
=> nil

(expect (+ 'foo 'bar)
  (signals type-error))
=> t

expect: example-form &rest expectations

  • example-form: 式
  • expectations: (returns ...) か (signal ...) を何個でも
    • returns: &rest values
      • values: 値をいくつか指定、値は評価される、n個目の値が(多値の)n個目に対応、equal で比較される
    • signals: condition-type &optional message
      • condition-type: コンディション名、クォートしてはいけない、typep で比較される
      • message: 指定されていれば si:*condition-string と string= で比較される

fail したら *standard-output* になんか書き出す。戻り値は t or nil

code

(defstruct behavior
  source return-values condition)

(defmacro inspect (form)
  (let ((actual (gensym)))
    `(let ((,actual (make-behavior :source ',form)))
       (handler-case
           (setf (behavior-return-values ,actual)
                 (multiple-value-list ,form))
         (condition (c)
           (setf (behavior-condition ,actual) c)))
       ,actual)))

(defun compile-expectation (expectation)
  `(lambda (actual)
     ,(case (car expectation)
        (returns
         `(if (equal #1=(behavior-return-values actual)
                     (list ,@(cdr expectation)))
              t
            (progn
              (format t "~S~%   Expected: return ~S~@{, ~S~}~%   Actually: "
                      #4=(behavior-source actual) ,@(cdr expectation))
              (if #1#
                  (apply #'format t "returned ~S~@{, ~S~}.~%" #1#)
                (format t "signaled ~S.~%"
                        (si:*structure-definition-name
                         (si:*structure-definition
                          #2=(behavior-condition actual))))))))
        (signals
         `(if ,(if (null (third expectation))
                   #3=`(typep #2# ',(second expectation))
                 `(and #3# (string= (si:*condition-string #2#)
                                    ,(third expectation))))
              t
            (progn
              (format t "~S~%   Expected: signal ~S~%   Actually: "
                      #4# ',(second expectation))
              (if #2#
                  (format t "signaled ~S.~%" (si:*structure-definition-name
                                              (si:*structure-definition #2#)))
                (apply #'format t "returned ~S~@{, ~S~}.~%" #1#)))))
        (t (error "unknown effect specifier: ~S" expectation)))))

(defmacro expect (form &rest expectations)
  `(let ((actual (inspect ,form)))
       (every (lambda (expectation)
                (funcall expectation actual))
         (list ,@(mapcar #'compile-expectation
                   expectations)))))

#+xyzzy
(setf (get 'expect 'lisp-indent-hook) 1)

TODO

  • package どうしよう
  • report の出力先とかを変更しやすく
  • effect (returns とか signals とか)の追加
  • spec としてまとめる->まとめて verify
  • 値の要件
  • save-buffer したときに勝手に verify する
  • 話それるけど OSC-do どうしよう