test.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
       test.lisp (41033B)
       ---
            1 ;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: FLEXI-STREAMS-TEST; Base: 10 -*-
            2 ;;; $Header: /usr/local/cvsrep/flexi-streams/test/test.lisp,v 1.39 2008/05/30 09:10:55 edi Exp $
            3 
            4 ;;; Copyright (c) 2006-2008, Dr. Edmund Weitz.  All rights reserved.
            5 
            6 ;;; Redistribution and use in source and binary forms, with or without
            7 ;;; modification, are permitted provided that the following conditions
            8 ;;; are met:
            9 
           10 ;;;   * Redistributions of source code must retain the above copyright
           11 ;;;     notice, this list of conditions and the following disclaimer.
           12 
           13 ;;;   * Redistributions in binary form must reproduce the above
           14 ;;;     copyright notice, this list of conditions and the following
           15 ;;;     disclaimer in the documentation and/or other materials
           16 ;;;     provided with the distribution.
           17 
           18 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
           19 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
           20 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
           21 ;;; ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
           22 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
           23 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
           24 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
           25 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
           26 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
           27 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
           28 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
           29 
           30 (in-package :flexi-streams-test)
           31 
           32 (defmacro with-test-suite ((test-description &key show-progress-p) &body body)
           33   "Defines a test suite.  Three utilities are available inside of the
           34 body of the macro: The function FAIL, and the macros CHECK and
           35 WITH-EXPECTED-ERROR.  FAIL, the lowest level utility, marks the test
           36 defined by WITH-TEST-SUITE as failed.  CHECK checks whether its argument is
           37 true, otherwise it calls FAIL. If during evaluation of the specified
           38 expression any condition is signalled, this is also considered a
           39 failure.  WITH-EXPECTED-ERROR executes its body and considers the test
           40 a success if the specified error was signalled, otherwise it calls
           41 FAIL.
           42 
           43 WITH-TEST-SUITE prints a simple progress report if SHOW-PROGRESS-P is true."
           44   (with-unique-names (successp testcount)
           45     (with-rebinding (show-progress-p)
           46       `(let ((,successp t)
           47              (,testcount 1))
           48          (when (and ,show-progress-p (not (numberp ,show-progress-p)))
           49            (setq ,show-progress-p 1))
           50          (flet ((fail (format-str &rest format-args)
           51                   (apply #'format t format-str format-args)
           52                   (setq ,successp nil))
           53                 (maybe-show-progress ()
           54                   (when (and ,show-progress-p (zerop (mod ,testcount ,show-progress-p)))
           55                     (format t ".")
           56                     (when (zerop (mod ,testcount (* 10 ,show-progress-p)))
           57                       (terpri))
           58                     (force-output))
           59                   (incf ,testcount)))
           60            (macrolet ((check (expression)
           61                         `(progn
           62                            (maybe-show-progress)
           63                            (handler-case
           64                                (unless ,expression
           65                                  (fail "~&Test ~S failed.~%" ',expression))
           66                              (error (c)
           67                                (fail "~&Test ~S failed signalling error of type ~A: ~A.~%" 
           68                                      ',expression (type-of c) c)))))
           69                       (with-expected-error ((condition-type) &body body)
           70                         `(progn
           71                            (maybe-show-progress)
           72                            (handler-case (progn ,@body)
           73                              (,condition-type () t)
           74                              (:no-error (&rest args)
           75                                (declare (ignore args))                           
           76                                (fail "~&Expected condition ~S not signalled.~%"
           77                                      ',condition-type))))))
           78              (format t "~&Test suite: ~S~%" ,test-description)
           79              ,@body))
           80          ,successp))))
           81 
           82 ;; LW can't indent this correctly because it's in a MACROLET
           83 #+:lispworks
           84 (editor:setup-indent "with-expected-error" 1 2 4)
           85 
           86 (defconstant +buffer-size+ 8192
           87   "Size of buffers for COPY-STREAM* below.")
           88 
           89 (defvar *copy-function* nil
           90   "Which function to use when copying from one stream to the other -
           91 see for example COPY-FILE below.")
           92 
           93 (defvar *this-file* (load-time-value
           94                      (or #.*compile-file-pathname* *load-pathname*))
           95   "The pathname of the file \(`test.lisp') where this variable was
           96 defined.")
           97 
           98 #+:lispworks
           99 (defun get-env-variable-as-directory (name)
          100   (lw:when-let (string (lw:environment-variable name))
          101     (when (plusp (length string))
          102       (cond ((find (char string (1- (length string))) "\\/" :test #'char=) string)
          103             (t (lw:string-append string "/"))))))
          104 
          105 (defvar *tmp-dir*
          106   (load-time-value
          107     (merge-pathnames "odd-streams-test/"
          108                      #+:allegro (system:temporary-directory)
          109                      #+:lispworks (pathname (or (get-env-variable-as-directory "TEMP")
          110                                                 (get-env-variable-as-directory "TMP")
          111                                                 #+:win32 "C:/"
          112                                                 #-:win32 "/tmp/"))
          113                      #-(or :allegro :lispworks) #p"/tmp/"))
          114   "The pathname of a temporary directory used for testing.")
          115 
          116 (defvar *test-files*
          117   '(("kafka" (:utf8 :latin1 :cp1252))
          118     ("tilton" (:utf8 :ascii))
          119     ("hebrew" (:utf8 :latin8))
          120     ("russian" (:utf8 :koi8r))
          121     ("unicode_demo" (:utf8 :ucs2 :ucs4)))
          122   "A list of test files where each entry consists of the name
          123 prefix and a list of encodings.")
          124 
          125 (defun create-file-variants (file-name symbol)
          126   "For a name suffix FILE-NAME and a symbol SYMBOL denoting an
          127 encoding returns a list of pairs where the car is a full file
          128 name and the cdr is the corresponding external format.  This list
          129 contains all possible variants w.r.t. to line-end conversion and
          130 endianness."
          131   (let ((args (ecase symbol
          132                 (:ascii '(:ascii))
          133                 (:latin1 '(:latin-1))
          134                 (:latin8 '(:hebrew))
          135                 (:cp1252 '(:code-page :id 1252))
          136                 (:koi8r '(:koi8-r))
          137                 (:utf8 '(:utf-8))
          138                 (:ucs2 '(:utf-16))
          139                 (:ucs4 '(:utf-32))))
          140         (endianp (member symbol '(:ucs2 :ucs4))))
          141     (loop for little-endian in (if endianp '(t nil) '(t))
          142           for endian-suffix in (if endianp '("_le" "_be") '(""))
          143           nconc (loop for eol-style in '(:lf :cr :crlf)
          144                       collect (cons (format nil "~A_~(~A~)_~(~A~)~A.txt"
          145                                             file-name symbol eol-style endian-suffix)
          146                                     (apply #'make-external-format
          147                                            (append args `(:eol-style ,eol-style
          148                                                           :little-endian ,little-endian))))))))
          149 
          150 (defun create-test-combinations (file-name symbols &optional simplep)
          151   "For a name suffix FILE-NAME and a list of symbols SYMBOLS denoting
          152 different encodings of the corresponding file returns a list of lists
          153 which can be used as arglists by COMPARE-FILES.  If SIMPLEP is true, a
          154 list which can be used for the string and sequence tests below is
          155 returned."
          156   (let ((file-variants (loop for symbol in symbols
          157                              nconc (create-file-variants file-name symbol))))
          158     (loop for (name-in . external-format-in) in file-variants
          159           when simplep
          160           collect (list name-in external-format-in)
          161           else
          162           nconc (loop for (name-out . external-format-out) in file-variants
          163                       collect (list name-in external-format-in name-out external-format-out)))))
          164                       
          165 (defun file-equal (file1 file2)
          166   "Returns a true value iff FILE1 and FILE2 have the same
          167 contents \(viewed as binary files)."
          168   (with-open-file (stream1 file1 :element-type 'octet)
          169     (with-open-file (stream2 file2 :element-type 'octet)
          170       (and (= (file-length stream1) (file-length stream2))
          171            (loop for byte1 = (read-byte stream1 nil nil)
          172                  for byte2 = (read-byte stream2 nil nil)
          173                  while (and byte1 byte2)
          174                  always (= byte1 byte2))))))
          175 
          176 (defun copy-stream (stream-in external-format-in stream-out external-format-out)
          177   "Copies the contents of the binary stream STREAM-IN to the
          178 binary stream STREAM-OUT using flexi streams - STREAM-IN is read
          179 with the external format EXTERNAL-FORMAT-IN and STREAM-OUT is
          180 written with EXTERNAL-FORMAT-OUT."
          181   (let ((in (make-flexi-stream stream-in :external-format external-format-in))
          182         (out (make-flexi-stream stream-out :external-format external-format-out)))
          183     (loop for line = (read-line in nil nil)
          184           while line
          185           do (write-line line out))))
          186 
          187 (defun copy-stream* (stream-in external-format-in stream-out external-format-out)
          188   "Like COPY-STREAM, but uses READ-SEQUENCE and WRITE-SEQUENCE instead
          189 of READ-LINE and WRITE-LINE."
          190   (let ((in (make-flexi-stream stream-in :external-format external-format-in))
          191         (out (make-flexi-stream stream-out :external-format external-format-out))
          192         (buffer (make-array +buffer-size+ :element-type 'char*)))
          193     (loop
          194      (let ((position (read-sequence buffer in)))
          195        (when (zerop position) (return))
          196        (write-sequence buffer out :end position)))))
          197 
          198 (defun copy-file (path-in external-format-in path-out external-format-out direction-out direction-in)
          199   "Copies the contents of the file denoted by the pathname
          200 PATH-IN to the file denoted by the pathname PATH-OUT using flexi
          201 streams - STREAM-IN is read with the external format
          202 EXTERNAL-FORMAT-IN and STREAM-OUT is written with
          203 EXTERNAL-FORMAT-OUT.  The input file is opened with
          204 the :DIRECTION keyword argument DIRECTION-IN, the output file is
          205 opened with the :DIRECTION keyword argument DIRECTION-OUT."
          206   (with-open-file (in path-in
          207                       :element-type 'octet
          208                       :direction direction-in
          209                       :if-does-not-exist :error
          210                       :if-exists :overwrite)
          211     (with-open-file (out path-out
          212                          :element-type 'octet
          213                          :direction direction-out
          214                          :if-does-not-exist :create
          215                          :if-exists :supersede)
          216       (funcall *copy-function* in external-format-in out external-format-out))))
          217 
          218 #+:lispworks
          219 (defun copy-file-lw (path-in external-format-in path-out external-format-out direction-out direction-in)
          220   "Same as COPY-FILE, but uses character streams instead of
          221 binary streams.  Only used to test LispWorks-specific behaviour."
          222   (with-open-file (in path-in
          223                       :external-format '(:latin-1 :eol-style :lf)
          224                       :element-type 'base-char
          225                       :direction direction-in
          226                       :if-does-not-exist :error
          227                       :if-exists :overwrite)
          228     (with-open-file (out path-out
          229                          :external-format '(:latin-1 :eol-style :lf)
          230                          :element-type 'base-char
          231                          :direction direction-out
          232                          :direction :output
          233                          :if-does-not-exist :create
          234                          :if-exists :supersede)
          235       (funcall *copy-function* in external-format-in out external-format-out))))
          236 
          237 (defun compare-files (&key verbose)
          238   "Each test in this suite copies the contents of one file \(in the
          239 `test' directory) to another file \(in a temporary directory) using
          240 flexi streams with different external formats.  The resulting file is
          241 compared with an existing file in the `test' directory to check if the
          242 outcome is as expected.  Uses various variants of the :DIRECTION
          243 keyword when opening the files.
          244 
          245 Returns a true value iff all tests succeeded.  Prints information
          246 about each individual comparison if VERBOSE is true."
          247   (with-test-suite ("Reading/writing files" :show-progress-p (not verbose))      
          248     (flet ((one-comparison (path-in external-format-in path-out external-format-out verbose) 
          249              (when verbose
          250                (format t "~&File ~S, using copy function ~S" (file-namestring path-in) *copy-function*)
          251                (format t "~&  and external formats ~S --> ~S"
          252                        (normalize-external-format external-format-in)
          253                        (normalize-external-format external-format-out)))
          254              (let ((full-path-in (merge-pathnames path-in *this-file*))
          255                    (full-path-out (ensure-directories-exist
          256                                    (merge-pathnames path-out *tmp-dir*)))
          257                    (full-path-orig (merge-pathnames path-out *this-file*)))
          258                (dolist (direction-out '(:output :io))
          259                  (dolist (direction-in '(:input :io))
          260                    (when verbose
          261                      (format t "~&...directions ~S --> ~S" direction-in direction-out))
          262                    (copy-file full-path-in external-format-in
          263                               full-path-out external-format-out
          264                               direction-out direction-in)
          265                    (check (file-equal full-path-out full-path-orig))
          266                    #+:lispworks
          267                    (progn
          268                    (when verbose
          269                      (format t "~&...directions ~S --> ~S \(LispWorks)" direction-in direction-out))
          270                      (copy-file-lw full-path-in external-format-in
          271                                    full-path-out external-format-out
          272                                    direction-out direction-in)
          273                      (check (file-equal full-path-out full-path-orig))))))))
          274       (loop with compare-files-args-list = (loop for (file-name symbols) in *test-files*
          275                                                  nconc (create-test-combinations file-name symbols))
          276             for *copy-function* in '(copy-stream copy-stream*)
          277             do (loop for (path-in external-format-in path-out external-format-out) in compare-files-args-list
          278                      do (one-comparison path-in external-format-in path-out external-format-out verbose))))))
          279 
          280 (defun file-as-octet-vector (pathspec)
          281   "Returns the contents of the file denoted by PATHSPEC as a vector of
          282 octets."
          283   (with-open-file (in pathspec :element-type 'octet)
          284     (let ((vector (make-array (file-length in) :element-type 'octet)))
          285       (read-sequence vector in)
          286       vector)))
          287 
          288 (defun file-as-string (pathspec external-format)
          289   "Reads the contents of the file denoted by PATHSPEC using the
          290 external format EXTERNAL-FORMAT and returns the result as a string."
          291   (with-open-file (in pathspec :element-type 'octet)
          292     (let* ((number-of-octets (file-length in))
          293            (in (make-flexi-stream in :external-format external-format))
          294            (string (make-array number-of-octets
          295                                :element-type #+:lispworks 'lw:simple-char
          296                                              #-:lispworks 'character
          297                                :fill-pointer t)))
          298       (setf (fill-pointer string) (read-sequence string in))
          299       string)))
          300 
          301 (defun old-string-to-octets (string &key
          302                                     (external-format (make-external-format :latin1))
          303                                     (start 0) end)
          304   "The old version of STRING-TO-OCTETS.  We can use it to test
          305 in-memory streams."
          306   (declare (optimize speed))
          307   (with-output-to-sequence (out)
          308     (let ((flexi (make-flexi-stream out :external-format external-format)))
          309       (write-string string flexi :start start :end end))))
          310 
          311 (defun old-octets-to-string (vector &key
          312                                     (external-format (make-external-format :latin1))
          313                                     (start 0) (end (length vector)))
          314   "The old version of OCTETS-TO-STRING.  We can use it to test
          315 in-memory streams."
          316   (declare (optimize speed))
          317   (with-input-from-sequence (in vector :start start :end end)
          318     (let ((flexi (make-flexi-stream in :external-format external-format))
          319           (result (make-array (- end start)
          320                               :element-type #+:lispworks 'lw:simple-char
          321                                             #-:lispworks 'character
          322                               :fill-pointer t)))
          323       (setf (fill-pointer result)
          324             (read-sequence result flexi))
          325       result)))
          326 
          327 (defun string-tests (&key verbose)
          328   "Tests whether conversion from strings to octets and vice versa
          329 works as expected.  Also tests with the old versions of the conversion
          330 functions in order to test in-memory streams."
          331   (with-test-suite ("String tests" :show-progress-p (and (not verbose) 10))
          332     (flet ((one-string-test (pathspec external-format verbose)
          333              (when verbose
          334                (format t "~&With external format ~S:" (normalize-external-format external-format)))
          335              (let* ((full-path (merge-pathnames pathspec *this-file*))
          336                     (octets-vector (file-as-octet-vector full-path))
          337                     (octets-list (coerce octets-vector 'list))
          338                     (string (file-as-string full-path external-format)))
          339                (when verbose
          340                  (format t "~&...testing OCTETS-TO-STRING"))
          341                (check (string= (octets-to-string octets-vector :external-format external-format) string))
          342                (check (string= (octets-to-string octets-list :external-format external-format) string))
          343                (when verbose
          344                  (format t "~&...testing STRING-TO-OCTETS"))
          345                (check (equalp (string-to-octets string :external-format external-format) octets-vector))
          346                (when verbose
          347                  (format t "~&...testing in-memory streams"))
          348                (check (string= (old-octets-to-string octets-vector :external-format external-format) string))
          349                (check (string= (old-octets-to-string octets-list :external-format external-format) string))
          350                (check (equalp (old-string-to-octets string :external-format external-format) octets-vector)))))
          351       (loop with simple-test-args-list = (loop for (file-name symbols) in *test-files*
          352                                                nconc (create-test-combinations file-name symbols t))
          353             for (pathspec external-format) in simple-test-args-list
          354             do (one-string-test pathspec external-format verbose)))))
          355       
          356 
          357 (defun sequence-equal (seq1 seq2)
          358   "Whether the two sequences have the same elements."
          359   (and (= (length seq1) (length seq2))
          360        (loop for i below (length seq1)
          361              always (eql (elt seq1 i) (elt seq2 i)))))
          362 
          363 (defun sequence-tests (&key verbose)
          364   "Several tests to confirm that READ-SEQUENCE and WRITE-SEQUENCE
          365 behave as expected."
          366   (with-test-suite ("Sequence tests" :show-progress-p (and (not verbose) 10))
          367     (flet ((one-sequence-test (pathspec external-format verbose)
          368              (when verbose
          369                (format t "~&With external format ~S:" (normalize-external-format external-format)))
          370              (let* ((full-path (merge-pathnames pathspec *this-file*))
          371                     (file-string (file-as-string full-path external-format))
          372                     (string-length (length file-string))
          373                     (octets (file-as-octet-vector full-path))
          374                     (octet-length (length octets)))
          375                (when (external-format-equal external-format (make-external-format :utf8))
          376                  (when verbose
          377                    (format t "~&...reading octets"))
          378                  (with-open-file (in full-path :element-type 'octet)
          379                    (let* ((in (make-flexi-stream in :external-format external-format))
          380                           (list (make-list octet-length)))
          381                      (setf (flexi-stream-element-type in) 'octet)
          382                      (let ((position #-:clisp
          383                                      (read-sequence list in)
          384                                      #+:clisp
          385                                      (ext:read-byte-sequence list in)))
          386                        (check (= position
          387                                  (flexi-stream-position in))))
          388                      (check (sequence-equal list octets))))
          389                  (with-open-file (in full-path :element-type 'octet)
          390                    (let* ((in (make-flexi-stream in :external-format external-format))
          391                           (third (floor octet-length 3))
          392                           (half (floor octet-length 2))
          393                           (vector (make-array half :element-type 'octet)))
          394                      (check (sequence-equal (loop repeat third
          395                                                   collect (read-byte in))
          396                                             (subseq octets 0 third)))
          397                      (read-sequence vector in)
          398                      (check (sequence-equal vector (subseq octets third (+ third half)))))))
          399                (when verbose
          400                  (format t "~&...reading characters"))
          401                (with-open-file (in full-path :element-type 'octet)
          402                  (let* ((in (make-flexi-stream in :external-format external-format))
          403                         (string (make-string (- string-length 10) :element-type 'char*)))
          404                    (setf (flexi-stream-element-type in) 'octet)
          405                    (check (sequence-equal (loop repeat 10
          406                                                 collect (read-char in))
          407                                           (subseq file-string 0 10)))
          408                    (read-sequence string in)
          409                    (check (sequence-equal string (subseq file-string 10)))))
          410                (with-open-file (in full-path :element-type 'octet)
          411                  (let* ((in (make-flexi-stream in :external-format external-format))
          412                         (list (make-list (- string-length 100))))
          413                    (check (sequence-equal (loop repeat 50
          414                                                 collect (read-char in))
          415                                           (subseq file-string 0 50)))
          416                    #-:clisp
          417                    (read-sequence list in)
          418                    #+:clisp
          419                    (ext:read-char-sequence list in)
          420                    (check (sequence-equal list (subseq file-string 50 (- string-length 50))))
          421                    (check (sequence-equal (loop repeat 50
          422                                                 collect (read-char in))
          423                                           (subseq file-string (- string-length 50))))))
          424                (with-open-file (in full-path :element-type 'octet)
          425                  (let* ((in (make-flexi-stream in :external-format external-format))
          426                         (array (make-array (- string-length 50))))
          427                    (check (sequence-equal (loop repeat 25
          428                                                 collect (read-char in))
          429                                           (subseq file-string 0 25)))
          430                    #-:clisp
          431                    (read-sequence array in)
          432                    #+:clisp
          433                    (ext:read-char-sequence array in)
          434                    (check (sequence-equal array (subseq file-string 25 (- string-length 25))))
          435                    (check (sequence-equal (loop repeat 25
          436                                                 collect (read-char in))
          437                                           (subseq file-string (- string-length 25))))))
          438                (let ((path-out (ensure-directories-exist (merge-pathnames pathspec *tmp-dir*))))
          439                  (when verbose
          440                    (format t "~&...writing sequences"))
          441                  (with-open-file (out path-out
          442                                       :direction :output
          443                                       :if-exists :supersede
          444                                       :element-type 'octet)
          445                    (let ((out (make-flexi-stream out :external-format external-format)))
          446                      (write-sequence octets out)))
          447                  (check (file-equal full-path path-out))
          448                  (with-open-file (out path-out
          449                                       :direction :output
          450                                       :if-exists :supersede
          451                                       :element-type 'octet)
          452                    (let ((out (make-flexi-stream out :external-format external-format)))
          453                      (write-sequence file-string out)))
          454                  (check (file-equal full-path path-out))
          455                  (with-open-file (out path-out
          456                                       :direction :output
          457                                       :if-exists :supersede
          458                                       :element-type 'octet)
          459                    (let ((out (make-flexi-stream out :external-format external-format)))
          460                      (write-sequence file-string out :end 100)
          461                      (write-sequence octets out
          462                                      :start (length (string-to-octets file-string
          463                                                                       :external-format external-format
          464                                                                       :end 100)))))
          465                  (check (file-equal full-path path-out))))))
          466 
          467       (loop with simple-test-args-list = (loop for (file-name symbols) in *test-files*
          468                                                nconc (create-test-combinations file-name symbols t))
          469             for (pathspec external-format) in simple-test-args-list
          470             do (one-sequence-test pathspec external-format verbose)))))
          471 
          472 (defmacro using-values ((&rest values) &body body)
          473   "Executes BODY and feeds an element from VALUES to the USE-VALUE
          474 restart each time a EXTERNAL-FORMAT-ENCODING-ERROR is signalled.
          475 Signals an error when there are more or less
          476 EXTERNAL-FORMAT-ENCODING-ERRORs than there are elements in VALUES."
          477   (with-unique-names (value-stack condition-counter)
          478     `(let ((,value-stack ',values)
          479            (,condition-counter 0))
          480        (handler-bind ((external-format-encoding-error
          481                        #'(lambda (c)
          482                            (declare (ignore c)) 
          483                            (unless ,value-stack
          484                              (error "Too many encoding errors signalled, expected only ~A."
          485                                     ,(length values)))
          486                            (incf ,condition-counter)
          487                            (use-value (pop ,value-stack)))))
          488          (prog1 (progn ,@body)
          489            (when ,value-stack
          490              (error "~A encoding errors signalled, but ~A were expected."
          491                     ,condition-counter ,(length values))))))))
          492 
          493 (defun accept-overlong (octets code-point)
          494   "Converts the `overlong' UTF-8 sequence OCTETS to using
          495 OCTETS-TO-STRINGS, accepts the expected error with the corresponding
          496 restart and checks that the result is CODE-POINT."
          497   (handler-bind ((external-format-encoding-error
          498                   (lambda (c)
          499                     (declare (ignore c))
          500                     (invoke-restart 'accept-overlong-sequence))))
          501     (string= (octets-to-string octets :external-format :utf-8)
          502              (string (code-char code-point)))))
          503 
          504 (defun read-flexi-line (sequence external-format)
          505   "Creates and returns a string from the octet sequence SEQUENCE using
          506 the external format EXTERNAL-FORMAT."
          507   (with-input-from-sequence (in sequence)
          508     (setq in (make-flexi-stream in :external-format external-format))
          509     (read-line in)))
          510 
          511 (defun read-flexi-line* (sequence external-format)
          512   "Like READ-FLEXI-LINE but uses OCTETS-TO-STRING internally."
          513   (octets-to-string sequence :external-format external-format))
          514 
          515 (defun error-handling-tests (&key verbose)
          516   "Tests several possible errors and how they are handled."
          517   (with-test-suite ("Testing error handling" :show-progress-p (not verbose))
          518     (macrolet ((want-encoding-error (input format)
          519                  `(with-expected-error (external-format-encoding-error)
          520                     (read-flexi-line* ,input ,format))))
          521       (when verbose
          522         (format t "~&\"Overlong\" UTF-8 sequences"))
          523       (want-encoding-error #(#b11000000 #b10000000) :utf-8)
          524       (want-encoding-error #(#b11000001 #b10000000) :utf-8)
          525       (want-encoding-error #(#b11100000 #b10011111 #b10000000) :utf-8)
          526       (want-encoding-error #(#b11110000 #b10001111 #b10000000 #b10000000) :utf-8)
          527       (check (accept-overlong #(#b11000000 #b10000000) #b00000000))
          528       (check (accept-overlong #(#b11000001 #b10000000) #b01000000))
          529       (check (accept-overlong #(#b11100000 #b10011111 #b10000000) #b011111000000))
          530       (check (accept-overlong #(#b11110000 #b10001111 #b10000000 #b10000000)
          531                               #b1111000000000000))
          532       (when verbose
          533         (format t "~&Invalid lead octets in UTF-8"))
          534       (want-encoding-error #(#b11111000) :utf-8)
          535       (want-encoding-error #(#b11111001) :utf-8)
          536       (want-encoding-error #(#b11111100) :utf-8)
          537       (want-encoding-error #(#b11111101) :utf-8)
          538       (want-encoding-error #(#b11111110) :utf-8)
          539       (want-encoding-error #(#b11111111) :utf-8)
          540       (when verbose
          541         (format t "~&Illegal code points"))
          542       (want-encoding-error #(#x00 #x00 #x11 #x00) :utf-32le)
          543       (want-encoding-error #(#x00 #xd8) :utf-16le)
          544       (want-encoding-error #(#xff #xdf) :utf-16le))
          545     (macrolet ((want-encoding-error (input format)
          546                  `(with-expected-error (external-format-encoding-error)
          547                     (read-flexi-line* ,input ,format))))                 
          548       (when verbose
          549         (format t "~&UTF-8 sequences which are too short"))
          550       (want-encoding-error #(#xe4 #xf6 #xfc) :utf8)
          551       (want-encoding-error #(#xc0) :utf8)
          552       (want-encoding-error #(#xe0 #xff) :utf8)
          553       (want-encoding-error #(#xf0 #xff #xff) :utf8)
          554       (when verbose
          555         (format t "~&UTF-16 sequences with an odd number of octets"))
          556       (want-encoding-error #(#x01) :utf-16le)
          557       (want-encoding-error #(#x01 #x01 #x01) :utf-16le)
          558       (want-encoding-error #(#x01) :utf-16be)
          559       (want-encoding-error #(#x01 #x01 #x01) :utf-16be)
          560       (when verbose
          561         (format t "~&Missing words in UTF-16"))
          562       (want-encoding-error #(#x01 #xd8) :utf-16le)
          563       (want-encoding-error #(#xd8 #x01) :utf-16be)
          564       (when verbose
          565         (format t "~&Missing octets in UTF-32"))
          566       (want-encoding-error #(#x01) :utf-32le)
          567       (want-encoding-error #(#x01 #x01) :utf-32le)
          568       (want-encoding-error #(#x01 #x01 #x01) :utf-32le)
          569       (want-encoding-error #(#x01 #x01 #x01 #x01 #x01) :utf-32le)
          570       (want-encoding-error #(#x01) :utf-32be)
          571       (want-encoding-error #(#x01 #x01) :utf-32be)
          572       (want-encoding-error #(#x01 #x01 #x01) :utf-32be)
          573       (want-encoding-error #(#x01 #x01 #x01 #x01 #x01) :utf-32be))
          574     (when verbose
          575       (format t "~&Handling of EOF in the middle of CRLF"))
          576     (check (string= #.(string #\Return)
          577                     (read-flexi-line `(,(char-code #\Return)) '(:ascii :eol-style :crlf))))
          578     (let ((*substitution-char* #\?))
          579       (when verbose
          580         (format t "~&Fixed substitution character #\?")
          581         (format t "~&:ASCII doesn't have characters with char codes > 127"))
          582       (check (string= "a??" (read-flexi-line `(,(char-code #\a) 128 200) :ascii)))
          583       (check (string= "a??" (read-flexi-line* `#(,(char-code #\a) 128 200) :ascii)))
          584       (when verbose
          585         (format t "~&:WINDOWS-1253 doesn't have a characters with codes 170 and 210"))
          586       (check (string= "a??" (read-flexi-line `(,(char-code #\a) 170 210) :windows-1253)))
          587       (check (string= "a??" (read-flexi-line* `#(,(char-code #\a) 170 210) :windows-1253)))
          588       (when verbose
          589         (format t "~&Not a valid UTF-8 sequence"))
          590       (check (string= "??" (read-flexi-line '(#xe4 #xf6 #xfc) :utf8))))
          591     (let ((*substitution-char* nil))
          592       (when verbose
          593         (format t "~&Variable substitution using USE-VALUE restart")
          594         (format t "~&:ASCII doesn't have characters with char codes > 127"))
          595       (check (string= "abc" (using-values (#\b #\c)
          596                               (read-flexi-line `(,(char-code #\a) 128 200) :ascii))))
          597       (check (string= "abc" (using-values (#\b #\c)
          598                               (read-flexi-line* `#(,(char-code #\a) 128 200) :ascii))))
          599       (when verbose
          600         (format t "~&:WINDOWS-1253 doesn't have a characters with codes 170 and 210"))
          601       (check (string= "axy" (using-values (#\x #\y)
          602                               (read-flexi-line `(,(char-code #\a) 170 210) :windows-1253))))
          603       (check (string= "axy" (using-values (#\x #\y)
          604                               (read-flexi-line* `#(,(char-code #\a) 170 210) :windows-1253))))
          605       (when verbose
          606         (format t "~&Not a valid UTF-8 sequence"))
          607       (check (string= "QW" (using-values (#\Q #\W) (read-flexi-line '(#xe4 #xf6 #xfc) :utf8))))
          608       (when verbose
          609         (format t "~&UTF-8 can't start neither with #b11111110 nor with #b11111111"))
          610       (check (string= "QW" (using-values (#\Q #\W) (read-flexi-line '(#b11111110 #b11111111) :utf8))))
          611       (when verbose
          612         (format t "~&Only one octet in UTF-16 sequence"))
          613       (check (string= "E" (using-values (#\E) (read-flexi-line '(#x01) :utf-16le))))
          614       (when verbose
          615         (format t "~&Two octets in UTF-16, but value of resulting word suggests that another word follows"))
          616       (check (string= "R" (using-values (#\R) (read-flexi-line '(#x01 #xd8) :utf-16le))))
          617       (when verbose
          618         (format t "~&The second word must fit into the [#xdc00; #xdfff] interval, but it is #xdbff"))
          619       (check (string= "T" (using-values (#\T) (read-flexi-line '(#x01 #xd8 #xff #xdb) :utf-16le))))
          620       (check (string= "T" (using-values (#\T) (read-flexi-line* #(#x01 #xd8 #xff #xdb) :utf-16le))))
          621       (when verbose
          622         (format t "~&The same as for little endian above, but using inverse order of bytes in words"))
          623       (check (string= "E" (using-values (#\E) (read-flexi-line '(#x01) :utf-16be))))
          624       (check (string= "R" (using-values (#\R) (read-flexi-line '(#xd8 #x01) :utf-16be))))
          625       (check (string= "T" (using-values (#\T) (read-flexi-line '(#xd8 #x01 #xdb #xff) :utf-16be))))
          626       (check (string= "T" (using-values (#\T) (read-flexi-line* #(#xd8 #x01 #xdb #xff) :utf-16be))))
          627       (when verbose
          628         (format t "~&EOF in the middle of a 4-octet sequence in UTF-32"))
          629       (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01) :utf-32le))))
          630       (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01 #x01) :utf-32le))))
          631       (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01 #x01 #x01) :utf-32le))))
          632       (check (string= "aY" (using-values (#\Y)
          633                              (read-flexi-line `(,(char-code #\a) #x00 #x00 #x00 #x01) :utf-32le))))
          634       (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01) :utf-32be))))
          635       (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01 #x01) :utf-32be))))
          636       (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01 #x01 #x01) :utf-32be))))
          637       (check (string= "aY" (using-values (#\Y)
          638                              (read-flexi-line `(#x00 #x00 #x00 ,(char-code #\a) #x01) :utf-32be)))))))
          639 
          640 (defun unread-char-tests (&key verbose)
          641   "Tests whether UNREAD-CHAR behaves as expected."
          642   (with-test-suite ("UNREAD-CHAR behaviour." :show-progress-p (and (not verbose) 100))
          643     (flet ((test-one-file (file-name external-format)
          644              (when verbose
          645                (format t "~&  ...and external format ~A" (normalize-external-format external-format)))
          646              (with-open-file (in (merge-pathnames file-name *this-file*)
          647                                  :element-type 'flex:octet)
          648                (let ((in (make-flexi-stream in :external-format external-format)))
          649                  (loop repeat 300
          650                        for char = (read-char in)
          651                        do (unread-char char in)
          652                           (check (char= (read-char in) char)))))))
          653       (loop for (file-name symbols) in *test-files*
          654             when verbose
          655             do (format t "~&With file ~S" file-name)
          656             do (loop for symbol in symbols
          657                      do (loop for (file-name . external-format) in (create-file-variants file-name symbol)
          658                               do (test-one-file file-name external-format)))))))
          659 
          660 (defun column-tests (&key verbose)
          661   (with-test-suite ("STREAM-LINE-COLUMN tests" :show-progress-p (not verbose))
          662     (let* ((binary-stream (flexi-streams:make-in-memory-output-stream))
          663            (stream (flexi-streams:make-flexi-stream binary-stream :external-format :iso-8859-1)))
          664       (write-sequence "hello" stream)
          665       (format stream "~12Tworld")
          666       (finish-output stream)
          667       (check (string= "hello       world"
          668                       (flexi-streams:octets-to-string
          669                        (flexi-streams::vector-stream-vector binary-stream)
          670                        :external-format :iso-8859-1)))
          671       (terpri stream)
          672       (check (= 0 (flexi-stream-column stream)))
          673       (write-sequence "abc" stream)
          674       (check (= 3 (flexi-stream-column stream)))
          675       (terpri stream)
          676       (check (= 0 (flexi-stream-column stream))))))
          677 
          678 (defun make-external-format-tests (&key verbose)
          679   (with-test-suite ("MAKE-EXTERNAL-FORMAT tests" :show-progress-p (not verbose))
          680     (flet ((make-case (real-name &key id name)
          681            (list real-name
          682                  :id id
          683                  :input-names (list name (string-upcase name) (string-downcase name)))))
          684       (let ((cases (append '((:utf-8 :id nil
          685                                      :input-names (:utf8 :utf-8 "utf8" "utf-8" "UTF8" "UTF-8")))
          686                            (loop for (name . real-name) in +name-map+
          687                                  unless (member :code-page (list name real-name))
          688                                    append (list (make-case real-name :name name)
          689                                                 (make-case real-name :name real-name)))
          690                            (loop for (name . definition) in +shortcut-map+
          691                                  for key = (car definition)
          692                                  for id = (getf (cdr definition) :id)
          693                                  for expected = (or (cdr (assoc key +name-map+)) key)
          694                                  collect (make-case expected :id id :name name)))))
          695 
          696         (loop for (expected-name . kwargs) in cases
          697               for id = (getf kwargs :id)
          698               for input-names = (getf kwargs :input-names)
          699               do (loop for name in input-names
          700                        for ext-format = (make-external-format name)
          701                        do (check (eq (flex:external-format-name ext-format) expected-name))
          702                        when id
          703                          do (check (= (flex:external-format-id ext-format) id))))))
          704 
          705     (let ((error-cases '("utf-8 " " utf-8" "utf8 " " utf8" "utf89" :utf89 utf89 :code-page nil)))
          706       (loop for input-name in error-cases
          707             do (with-expected-error (external-format-error)
          708                  (make-external-format input-name))))))
          709 
          710 (defun peek-byte-tests (&key verbose)
          711   (with-test-suite ("PEEK-BYTE tests" :show-progress-p (not verbose))
          712     (flex:with-input-from-sequence (input #(0 1 2))
          713       (let ((stream (flex:make-flexi-stream input)))
          714         ;; If peek-type was specified as 2 we need to peek the first octect equal to 2
          715         (check (= 2 (flex::peek-byte stream 2 nil 1)))
          716         ;; also, the octet should be unread to the stream so that we can peek it again
          717         (check (= 2 (flex::peek-byte stream nil nil nil)))))))
          718 
          719 (defun in-memory-stream-tests (&key verbose)
          720   (with-test-suite ("IN-MEMORY-STREAM tests" :show-progress-p (not verbose))
          721     (let ((z (make-array 4)))
          722       (read-sequence  z (make-in-memory-input-stream #(1 2 3 4)))
          723       (check (equalp z #(1 2 3 4)))
          724       (read-sequence  z (make-in-memory-input-stream '(4 3 2 1)))
          725       (check (equalp z #(4 3 2 1)))
          726       (read-sequence  z (make-in-memory-input-stream #(1 2 3 4) :transformer #'1+))
          727       (check (equalp z #(2 3 4 5)))
          728       (read-sequence  z (make-in-memory-input-stream '(1 2 3 4) :transformer #'1-))
          729       (check (equalp z #(0 1 2 3))))))
          730 
          731 (defun run-all-tests (&key verbose)
          732   "Runs all tests for FLEXI-STREAMS and returns a true value iff all
          733 tests succeeded.  VERBOSE is interpreted by the individual test suites
          734 above."
          735   (let ((successp t))
          736     (macrolet ((run-test-suite (&body body)
          737                  `(unless (progn ,@body)
          738                     (setq successp nil))))
          739       (run-test-suite (compare-files :verbose verbose))
          740       (run-test-suite (string-tests :verbose verbose))
          741       (run-test-suite (sequence-tests :verbose verbose))
          742       (run-test-suite (error-handling-tests :verbose verbose))
          743       (run-test-suite (unread-char-tests :verbose verbose))
          744       (run-test-suite (column-tests :verbose verbose))
          745       (run-test-suite (make-external-format-tests :verbose verbose))
          746       (run-test-suite (peek-byte-tests :verbose verbose))
          747       (run-test-suite (in-memory-stream-tests :verbose verbose))
          748       (format t "~2&~:[Some tests failed~;All tests passed~]." successp)
          749       successp)))