;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-PPCRE-TEST; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/cl-ppcre/test/tests.lisp,v 1.13 2009/09/17 19:17:36 edi Exp $

;;; The tests in this file test CL-PPCRE against testdata generated by
;;; the Perl program `perltest.pl' from the input file `testinput' in
;;; order to check compatibility with Perl and correctness of the
;;; regex engine.

;;; Copyright (c) 2002-2009, Dr. Edmund Weitz. All rights reserved.

;;; Redistribution and use in source and binary forms, with or without
;;; modification, are permitted provided that the following conditions
;;; are met:

;;;   * Redistributions of source code must retain the above copyright
;;;     notice, this list of conditions and the following disclaimer.

;;;   * Redistributions in binary form must reproduce the above
;;;     copyright notice, this list of conditions and the following
;;;     disclaimer in the documentation and/or other materials
;;;     provided with the distribution.

;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
;;; ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

(in-package :cl-ppcre-test)

(defvar *this-file* (load-time-value
                     (or #.*compile-file-pathname* *load-pathname*))
  "The location of this source file.")

(defmacro do-tests ((name &optional show-progress-p) &body body)
  "Helper macro which repeatedly executes BODY until the code in body
calls the function DONE.  It is assumed that each invocation of BODY
will be the execution of one test which returns NIL in case of success
and list of string describing errors otherwise.

The macro prints a simple progress indicator \(one dots for ten tests)
to *STANDARD-OUTPUT* unless SHOW-PROGRESS-P is NIL and returns a true
value iff all tests succeeded.  Errors in BODY are caught and reported
\(and counted as failures)."
  `(let ((successp t)
         (testcount 1))
     (block test-block
       (flet ((done ()
                (return-from test-block successp)))
         (format t "~&Test: ~A~%" ,name)
         (loop
          (when (and ,show-progress-p (zerop (mod testcount 10)))
            (format t ".")
            (when (zerop (mod testcount 100))
              (terpri))
            (force-output))
          (let ((errors
                 (handler-case
                     (progn ,@body)
                   (error (msg)
                     (list (format nil "~&got an unexpected error: ~A" msg))))))
            (setq successp (and successp (null errors)))
            (when errors
              (format t "~&~4@A:~{~&   ~A~}~%" testcount errors))
            (incf testcount)))))
     successp))

(defun simple-tests (&key (file-name
                           (make-pathname :name "simple"
                                          :type nil :version nil
                                          :defaults *this-file*))
                          (external-format '(:latin-1 :eol-style :lf))
                          verbose)
  "Loops through all the forms in the file FILE-NAME and executes each
of them using EVAL.  It is assumed that each FORM specifies a test
which returns a true value iff it succeeds.  Prints each test form to
*STANDARD-OUTPUT* if VERBOSE is true and shows a simple progress
indicator otherwise.  EXTERNAL-FORMAT is the FLEXI-STREAMS external
format which is used to read the file.  Returns a true value iff all
tests succeeded."
  (with-open-file (binary-stream file-name :element-type 'flex:octet)
    (let ((stream (flex:make-flexi-stream binary-stream :external-format external-format))
          (*package* (find-package :cl-ppcre-test)))
      (do-tests ((format nil "Simple tests from file ~S" (file-namestring file-name))
                 (not verbose))
        (let ((form (or (read stream nil) (done))))
          (when verbose
            (format t "~&~S" form))
          (cond ((eval form) nil)
                (t (list (format nil "~S returned NIL" form)))))))))

(defun random-test-function (probability)
  "Returns a random character test function which contains each
character with probability PROBABILITY."
  (let ((hash-table (make-hash-table)))
    (dotimes (code char-code-limit)
      (let ((char (code-char code)))
        (when (and char (< (random 1.0d0) probability))
          (setf (gethash (code-char code) hash-table) t))))
    (lambda (char)
      (gethash char hash-table))))

(defun test-optimized-test-functions% (probability)
  "Creates a random test function with probability PROBABILITY and six
\(one for each possible \"kind\") corresponding optimized test
functions, then checks for each character in turn that all functions
agree on it."
  (let* ((test-function (random-test-function probability))
         (optimized-functions (loop for kind in '(nil
                                                  :hash-table
                                                  :hash-table*
                                                  :charset
                                                  :charset*
                                                  :charmap)
                                    collect (create-optimized-test-function test-function :kind kind))))
    (loop for code below char-code-limit
          for char = (code-char code)
          for expected-result = (and char (funcall test-function char))
          always (or (null char)
                     (loop for optimized-function in optimized-functions
                           always (eq (not (funcall optimized-function char))
                                      (not expected-result)))))))

(defun test-optimized-test-functions (&key verbose)
  "Runs TEST-OPTIMIZED-TEST-FUNCTIONS% with different probabilities."
  (let ((probabilities '(0 .001 .01 .1 1)))
    (do-tests ("Optimized test functions - this might take some time..." (not verbose))
      (let ((probability (or (pop probabilities) (done))))
        (when verbose
          (format t "~&Probability is ~A" probability))
        (not (test-optimized-test-functions% probability))))))

(defun run-all-tests (&key more-tests verbose)
  "Runs all tests for CL-PPCRE and returns a true value iff all tests
succeeded.  VERBOSE is interpreted by the individual test suites.
MORE-TESTS can be a list of function designators designating
additional tests to run.  This facility is used by the tests for
CL-PPCRE-UNICODE."
  (let ((successp t))
    (macrolet ((run-test-suite (&body body)
                 `(unless (progn ,@body)
                    (setq successp nil))))
      ;; run the automatically generated Perl tests
      (run-test-suite (perl-test :verbose verbose))
      (run-test-suite (test-optimized-test-functions :verbose verbose))
      (run-test-suite (simple-tests :verbose verbose))
      (when more-tests
        (unless (listp more-tests)
          (setq more-tests (list more-tests))
          (dolist (test more-tests)
            (run-test-suite (funcall test :verbose verbose))))))
    (format t "~2&~:[Some tests failed~;All tests passed~]." successp)
    successp))
