;;;;
;;;; test.stk   -- STklos regression testing
;;;;
;;;; Copyright © 2005-2023 Erick Gallesio <eg@stklos.net>
;;;;
;;;;
;;;; This program is free software; you can redistribute it and/or modify
;;;; it under the terms of the GNU General Public License as published by
;;;; the Free Software Foundation; either version 2 of the License, or
;;;; (at your option) any later version.
;;;;
;;;; This program is distributed in the hope that it will be useful,
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;;; GNU General Public License for more details.
;;;;
;;;; You should have received a copy of the GNU General Public License
;;;; along with this program; if not, write to the Free Software
;;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
;;;; USA.
;;;;
;;;;           Author: Erick Gallesio [eg@essi.fr]
;;;;    Creation date:  3-May-2005 11:19 (eg)
;;;;

(define *all-errors* '())
(define *log* (current-output-port))
(define *err* (current-error-port))
(define *test-failed* (vector 'fail))
(define *test-counter* 0)
(define *test-ko* 0)

;; ----------------------------------------------------------------------
;;  %tester ...
;; ----------------------------------------------------------------------
(define (%tester message expect thunk code compare)
  (let ((expected (if (equal? expect *test-failed*) 'failure expect)))
    (format *log* "  testing ~A expects ~S ==> " message expected)
    (flush-output-port)
    (let ((res (with-handler
                (lambda (c) *test-failed*)
                (thunk))))
      (set! *test-counter* (+ *test-counter* 1))
      (if (compare expect res)
          (format *log* "OK.\n")
          (begin
            (set! *test-ko* (+ *test-ko* 1))
            (format *log* "ERROR: got ~S.\n" res)
            (set! *all-errors* (cons (list message code expected res)
                                     *all-errors*)))))
    (flush-output-port *log*)))

;; ----------------------------------------------------------------------
;;  test-init ...
;; ----------------------------------------------------------------------
(define (test-init log-file)
  (let ((port (open-output-file log-file)))
    (set! *log* port)))

;; ----------------------------------------------------------------------
;;  test-end ...
;; ----------------------------------------------------------------------
(define (test-terminate)
  (for-each (lambda (port)
              (format port "~A\n" (make-string 70 #\-))
              (format port "Number of tests: ~A (OK: ~A Error: ~A)\n"
                      *test-counter* (- *test-counter* *test-ko*) *test-ko*)
              (format port "   Elapsed Time: ~Ams\n"
                      (inexact->exact (round (clock))))
              (format port "*** End of tests ***\n")
              (close-output-port port))
            (list *log* *err*))
  (exit (if (positive? *test-ko*) 1 0)))

;; ----------------------------------------------------------------------
;;  test-section ...
;; ----------------------------------------------------------------------
(define (test-section msg)
  (let* ((s   (format "==== Testing ~a " msg))
         (len (string-length s)))
    (set! *all-errors* '())
    ;; Log
    (let ((delim (make-string 70 #\=)))
      (format *log* "\n~a\n" delim)
      (format *log* "~a ~a\n" s
              (make-string (max (- 70 len 1) 0) #\=))
      (format *log* "~a\n\n" delim)
      (flush-output-port *log*))
    ;; Output
    (format *err* "~a ... ~a" s (make-string (max (- 60 len) 0) #\space))
    (flush-output-port *err*)))

;; ----------------------------------------------------------------------
;;  test-section-end ...
;; ----------------------------------------------------------------------
(define (test-section-end)
  (define (fmt . args)
    (apply format *log* args)
    (apply format *err* args)
    (flush-output-port *log*)
    (flush-output-port *err*))

  (if (null? *all-errors*)
      (fmt "passed\n")
      (begin
        (fmt "failed\n")
        (fmt "Errors found in this section:\n")
        (for-each (lambda (x) (apply fmt "test ~a on ~S expected ~S but got ~S\n" x))
                  (reverse! *all-errors*)))))

;; ----------------------------------------------------------------------
;;  test-subsection ...
;; ----------------------------------------------------------------------
(define (test-subsection msg)
  (let ((delim (make-string 50 #\-)))
    (format *log* "\n~a\n" delim)
    (format *log* "---- ~a\n" msg)
    (format *log* "~a\n\n" delim)
    (flush-output-port *log*)))

;; ----------------------------------------------------------------------
;;  test ...
;; ----------------------------------------------------------------------
(define-macro (test msg expect expr :optional (compare equal?))
  `(%tester ,msg ,expect (lambda () ,expr) ',expr ,compare))

;; ----------------------------------------------------------------------
;; test/error ...
;; ----------------------------------------------------------------------
(define-syntax test/error
   (syntax-rules ()
     ((_ str code)
      (test str *test-failed* code eq?))))


(define-syntax test/compile-error
  (syntax-rules ()
    ((_ str code)
     (test str *test-failed* (eval 'code) eq?))))
  
(provide "test")
