File: test.lisp

package info (click to toggle)
flexi-streams 20200913.git911a3e2-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 2,064 kB
  • sloc: lisp: 52,097; makefile: 2
file content (765 lines) | stat: -rw-r--r-- 42,020 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: FLEXI-STREAMS-TEST; Base: 10 -*-
;;; $Header: /usr/local/cvsrep/flexi-streams/test/test.lisp,v 1.39 2008/05/30 09:10:55 edi Exp $

;;; Copyright (c) 2006-2008, 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 :flexi-streams-test)

(defmacro with-test-suite ((test-description &key show-progress-p) &body body)
  "Defines a test suite.  Three utilities are available inside of the
body of the macro: The function FAIL, and the macros CHECK and
WITH-EXPECTED-ERROR.  FAIL, the lowest level utility, marks the test
defined by WITH-TEST-SUITE as failed.  CHECK checks whether its argument is
true, otherwise it calls FAIL. If during evaluation of the specified
expression any condition is signalled, this is also considered a
failure.  WITH-EXPECTED-ERROR executes its body and considers the test
a success if the specified error was signalled, otherwise it calls
FAIL.

WITH-TEST-SUITE prints a simple progress report if SHOW-PROGRESS-P is true."
  (with-unique-names (successp testcount)
    (with-rebinding (show-progress-p)
      `(let ((,successp t)
             (,testcount 1))
         (when (and ,show-progress-p (not (numberp ,show-progress-p)))
           (setq ,show-progress-p 1))
         (flet ((fail (format-str &rest format-args)
                  (apply #'format t format-str format-args)
                  (setq ,successp nil))
                (maybe-show-progress ()
                  (when (and ,show-progress-p (zerop (mod ,testcount ,show-progress-p)))
                    (format t ".")
                    (when (zerop (mod ,testcount (* 10 ,show-progress-p)))
                      (terpri))
                    (force-output))
                  (incf ,testcount)))
           (macrolet ((check (expression)
                        `(progn
                           (maybe-show-progress)
                           (handler-case
                               (unless ,expression
                                 (fail "~&Test ~S failed.~%" ',expression))
                             (error (c)
                               (fail "~&Test ~S failed signalling error of type ~A: ~A.~%" 
                                     ',expression (type-of c) c)))))
                      (with-expected-error ((condition-type) &body body)
                        `(progn
                           (maybe-show-progress)
                           (handler-case (progn ,@body)
                             (,condition-type () t)
                             (:no-error (&rest args)
                               (declare (ignore args))                           
                               (fail "~&Expected condition ~S not signalled.~%"
                                     ',condition-type))))))
             (format t "~&Test suite: ~S~%" ,test-description)
             ,@body))
         ,successp))))

;; LW can't indent this correctly because it's in a MACROLET
#+:lispworks
(editor:setup-indent "with-expected-error" 1 2 4)

(defconstant +buffer-size+ 8192
  "Size of buffers for COPY-STREAM* below.")

(defvar *copy-function* nil
  "Which function to use when copying from one stream to the other -
see for example COPY-FILE below.")

(defvar *this-file* (load-time-value
                     (or #.*compile-file-pathname* *load-pathname*))
  "The pathname of the file \(`test.lisp') where this variable was
defined.")

#+:lispworks
(defun get-env-variable-as-directory (name)
  (lw:when-let (string (lw:environment-variable name))
    (when (plusp (length string))
      (cond ((find (char string (1- (length string))) "\\/" :test #'char=) string)
            (t (lw:string-append string "/"))))))

(defvar *tmp-dir*
  (load-time-value
    (merge-pathnames "odd-streams-test/"
                     #+:allegro (system:temporary-directory)
                     #+:lispworks (pathname (or (get-env-variable-as-directory "TEMP")
                                                (get-env-variable-as-directory "TMP")
                                                #+:win32 "C:/"
                                                #-:win32 "/tmp/"))
                     #-(or :allegro :lispworks) #p"/tmp/"))
  "The pathname of a temporary directory used for testing.")

(defvar *test-files*
  '(("kafka" (:utf8 :latin1 :cp1252))
    ("tilton" (:utf8 :ascii))
    ("hebrew" (:utf8 :latin8))
    ("russian" (:utf8 :koi8r))
    ("xjt" (:gbk))
    ("unicode_demo" (:utf8 :ucs2 :ucs4)))
  "A list of test files where each entry consists of the name
prefix and a list of encodings.")

(defun create-file-variants (file-name symbol)
  "For a name suffix FILE-NAME and a symbol SYMBOL denoting an
encoding returns a list of pairs where the car is a full file
name and the cdr is the corresponding external format.  This list
contains all possible variants w.r.t. to line-end conversion and
endianness."
  (let ((args (ecase symbol
                (:ascii '(:ascii))
                (:latin1 '(:latin-1))
                (:latin8 '(:hebrew))
                (:cp1252 '(:code-page :id 1252))
                (:koi8r '(:koi8-r))
                (:utf8 '(:utf-8))
                (:gbk '(:gbk))
                (:ucs2 '(:utf-16))
                (:ucs4 '(:utf-32))))
        (endianp (member symbol '(:ucs2 :ucs4))))
    (loop for little-endian in (if endianp '(t nil) '(t))
          for endian-suffix in (if endianp '("_le" "_be") '(""))
          nconc (loop for eol-style in '(:lf :cr :crlf)
                      collect (cons (format nil "~A_~(~A~)_~(~A~)~A.txt"
                                            file-name symbol eol-style endian-suffix)
                                    (apply #'make-external-format
                                           (append args `(:eol-style ,eol-style
                                                          :little-endian ,little-endian))))))))

(defun create-test-combinations (file-name symbols &optional simplep)
  "For a name suffix FILE-NAME and a list of symbols SYMBOLS denoting
different encodings of the corresponding file returns a list of lists
which can be used as arglists by COMPARE-FILES.  If SIMPLEP is true, a
list which can be used for the string and sequence tests below is
returned."
  (let ((file-variants (loop for symbol in symbols
                             nconc (create-file-variants file-name symbol))))
    (loop for (name-in . external-format-in) in file-variants
          when simplep
          collect (list name-in external-format-in)
          else
          nconc (loop for (name-out . external-format-out) in file-variants
                      collect (list name-in external-format-in name-out external-format-out)))))
                      
(defun file-equal (file1 file2)
  "Returns a true value iff FILE1 and FILE2 have the same
contents \(viewed as binary files)."
  (with-open-file (stream1 file1 :element-type 'octet)
    (with-open-file (stream2 file2 :element-type 'octet)
      (and (= (file-length stream1) (file-length stream2))
           (loop for byte1 = (read-byte stream1 nil nil)
                 for byte2 = (read-byte stream2 nil nil)
                 while (and byte1 byte2)
                 always (= byte1 byte2))))))

(defun copy-stream (stream-in external-format-in stream-out external-format-out)
  "Copies the contents of the binary stream STREAM-IN to the
binary stream STREAM-OUT using flexi streams - STREAM-IN is read
with the external format EXTERNAL-FORMAT-IN and STREAM-OUT is
written with EXTERNAL-FORMAT-OUT."
  (let ((in (make-flexi-stream stream-in :external-format external-format-in))
        (out (make-flexi-stream stream-out :external-format external-format-out)))
    (loop for line = (read-line in nil nil)
          while line
          do (write-line line out))))

(defun copy-stream* (stream-in external-format-in stream-out external-format-out)
  "Like COPY-STREAM, but uses READ-SEQUENCE and WRITE-SEQUENCE instead
of READ-LINE and WRITE-LINE."
  (let ((in (make-flexi-stream stream-in :external-format external-format-in))
        (out (make-flexi-stream stream-out :external-format external-format-out))
        (buffer (make-array +buffer-size+ :element-type 'char*)))
    (loop
     (let ((position (read-sequence buffer in)))
       (when (zerop position) (return))
       (write-sequence buffer out :end position)))))

(defun copy-file (path-in external-format-in path-out external-format-out direction-out direction-in)
  "Copies the contents of the file denoted by the pathname
PATH-IN to the file denoted by the pathname PATH-OUT using flexi
streams - STREAM-IN is read with the external format
EXTERNAL-FORMAT-IN and STREAM-OUT is written with
EXTERNAL-FORMAT-OUT.  The input file is opened with
the :DIRECTION keyword argument DIRECTION-IN, the output file is
opened with the :DIRECTION keyword argument DIRECTION-OUT."
  (with-open-file (in path-in
                      :element-type 'octet
                      :direction direction-in
                      :if-does-not-exist :error
                      :if-exists :overwrite)
    (with-open-file (out path-out
                         :element-type 'octet
                         :direction direction-out
                         :if-does-not-exist :create
                         :if-exists :supersede)
      (funcall *copy-function* in external-format-in out external-format-out))))

#+:lispworks
(defun copy-file-lw (path-in external-format-in path-out external-format-out direction-out direction-in)
  "Same as COPY-FILE, but uses character streams instead of
binary streams.  Only used to test LispWorks-specific behaviour."
  (with-open-file (in path-in
                      :external-format '(:latin-1 :eol-style :lf)
                      :element-type 'base-char
                      :direction direction-in
                      :if-does-not-exist :error
                      :if-exists :overwrite)
    (with-open-file (out path-out
                         :external-format '(:latin-1 :eol-style :lf)
                         :element-type 'base-char
                         :direction direction-out
                         :direction :output
                         :if-does-not-exist :create
                         :if-exists :supersede)
      (funcall *copy-function* in external-format-in out external-format-out))))

(defun compare-files (&key verbose)
  "Each test in this suite copies the contents of one file \(in the
`test' directory) to another file \(in a temporary directory) using
flexi streams with different external formats.  The resulting file is
compared with an existing file in the `test' directory to check if the
outcome is as expected.  Uses various variants of the :DIRECTION
keyword when opening the files.

Returns a true value iff all tests succeeded.  Prints information
about each individual comparison if VERBOSE is true."
  (with-test-suite ("Reading/writing files" :show-progress-p (not verbose))      
    (flet ((one-comparison (path-in external-format-in path-out external-format-out verbose) 
             (when verbose
               (format t "~&File ~S, using copy function ~S" (file-namestring path-in) *copy-function*)
               (format t "~&  and external formats ~S --> ~S"
                       (normalize-external-format external-format-in)
                       (normalize-external-format external-format-out)))
             (let ((full-path-in (merge-pathnames path-in *this-file*))
                   (full-path-out (ensure-directories-exist
                                   (merge-pathnames path-out *tmp-dir*)))
                   (full-path-orig (merge-pathnames path-out *this-file*)))
               (dolist (direction-out '(:output :io))
                 (dolist (direction-in '(:input))
                   (when verbose
                     (format t "~&...directions ~S --> ~S" direction-in direction-out))
                   (copy-file full-path-in external-format-in
                              full-path-out external-format-out
                              direction-out direction-in)
                   (check (file-equal full-path-out full-path-orig))
                   #+:lispworks
                   (progn
                   (when verbose
                     (format t "~&...directions ~S --> ~S \(LispWorks)" direction-in direction-out))
                     (copy-file-lw full-path-in external-format-in
                                   full-path-out external-format-out
                                   direction-out direction-in)
                     (check (file-equal full-path-out full-path-orig))))))))
      (loop with compare-files-args-list = (loop for (file-name symbols) in *test-files*
                                                 nconc (create-test-combinations file-name symbols))
            for *copy-function* in '(copy-stream copy-stream*)
            do (loop for (path-in external-format-in path-out external-format-out) in compare-files-args-list
                     do (one-comparison path-in external-format-in path-out external-format-out verbose))))))

(defun file-as-octet-vector (pathspec)
  "Returns the contents of the file denoted by PATHSPEC as a vector of
octets."
  (with-open-file (in pathspec :element-type 'octet)
    (let ((vector (make-array (file-length in) :element-type 'octet)))
      (read-sequence vector in)
      vector)))

(defun file-as-string (pathspec external-format)
  "Reads the contents of the file denoted by PATHSPEC using the
external format EXTERNAL-FORMAT and returns the result as a string."
  (with-open-file (in pathspec :element-type 'octet)
    (let* ((number-of-octets (file-length in))
           (in (make-flexi-stream in :external-format external-format))
           (string (make-array number-of-octets
                               :element-type #+:lispworks 'lw:simple-char
                                             #-:lispworks 'character
                               :fill-pointer t)))
      (setf (fill-pointer string) (read-sequence string in))
      string)))

(defun old-string-to-octets (string &key
                                    (external-format (make-external-format :latin1))
                                    (start 0) end)
  "The old version of STRING-TO-OCTETS.  We can use it to test
in-memory streams."
  (declare (optimize speed))
  (with-output-to-sequence (out)
    (let ((flexi (make-flexi-stream out :external-format external-format)))
      (write-string string flexi :start start :end end))))

(defun old-octets-to-string (vector &key
                                    (external-format (make-external-format :latin1))
                                    (start 0) (end (length vector)))
  "The old version of OCTETS-TO-STRING.  We can use it to test
in-memory streams."
  (declare (optimize speed))
  (with-input-from-sequence (in vector :start start :end end)
    (let ((flexi (make-flexi-stream in :external-format external-format))
          (result (make-array (- end start)
                              :element-type #+:lispworks 'lw:simple-char
                                            #-:lispworks 'character
                              :fill-pointer t)))
      (setf (fill-pointer result)
            (read-sequence result flexi))
      result)))

(defun string-tests (&key verbose)
  "Tests whether conversion from strings to octets and vice versa
works as expected.  Also tests with the old versions of the conversion
functions in order to test in-memory streams."
  (with-test-suite ("String tests" :show-progress-p (and (not verbose) 10))
    (flet ((one-string-test (pathspec external-format verbose)
             (when verbose
               (format t "~&With external format ~S:" (normalize-external-format external-format)))
             (let* ((full-path (merge-pathnames pathspec *this-file*))
                    (octets-vector (file-as-octet-vector full-path))
                    (octets-list (coerce octets-vector 'list))
                    (string (file-as-string full-path external-format)))
               (when verbose
                 (format t "~&...testing OCTETS-TO-STRING"))
               (check (string= (octets-to-string octets-vector :external-format external-format) string))
               (check (string= (octets-to-string octets-list :external-format external-format) string))
               (when verbose
                 (format t "~&...testing STRING-TO-OCTETS"))
               (check (equalp (string-to-octets string :external-format external-format) octets-vector))
               (when verbose
                 (format t "~&...testing in-memory streams"))
               (check (string= (old-octets-to-string octets-vector :external-format external-format) string))
               (check (string= (old-octets-to-string octets-list :external-format external-format) string))
               (check (equalp (old-string-to-octets string :external-format external-format) octets-vector)))))
      (loop with simple-test-args-list = (loop for (file-name symbols) in *test-files*
                                               nconc (create-test-combinations file-name symbols t))
            for (pathspec external-format) in simple-test-args-list
            do (one-string-test pathspec external-format verbose)))))
      

(defun sequence-equal (seq1 seq2)
  "Whether the two sequences have the same elements."
  (and (= (length seq1) (length seq2))
       (loop for i below (length seq1)
             always (eql (elt seq1 i) (elt seq2 i)))))

(defun sequence-tests (&key verbose)
  "Several tests to confirm that READ-SEQUENCE and WRITE-SEQUENCE
behave as expected."
  (with-test-suite ("Sequence tests" :show-progress-p (and (not verbose) 10))
    (flet ((one-sequence-test (pathspec external-format verbose)
             (when verbose
               (format t "~&With external format ~S:" (normalize-external-format external-format)))
             (let* ((full-path (merge-pathnames pathspec *this-file*))
                    (file-string (file-as-string full-path external-format))
                    (string-length (length file-string))
                    (octets (file-as-octet-vector full-path))
                    (octet-length (length octets)))
               (when (external-format-equal external-format (make-external-format :utf8))
                 (when verbose
                   (format t "~&...reading octets"))
                 (with-open-file (in full-path :element-type 'octet)
                   (let* ((in (make-flexi-stream in :external-format external-format))
                          (list (make-list octet-length)))
                     (setf (flexi-stream-element-type in) 'octet)
                     (let ((position #-:clisp
                                     (read-sequence list in)
                                     #+:clisp
                                     (ext:read-byte-sequence list in)))
                       (check (= position
                                 (flexi-stream-position in))))
                     (check (sequence-equal list octets))))
                 (with-open-file (in full-path :element-type 'octet)
                   (let* ((in (make-flexi-stream in :external-format external-format))
                          (list (make-list octet-length)))
                     (setf (flexi-stream-element-type in) 'octet)
                     (setf (flexi-stream-bound in) (1- octet-length))
                     (let ((position #-:clisp
                                     (read-sequence list in)
                                     #+:clisp
                                     (ext:read-byte-sequence list in)))
                       (check (= position (1- octet-length))))
                     (check (null (car (last list))))
                     (check (sequence-equal (subseq list 0 (1- octet-length))
                                            (subseq octets 0 (1- octet-length))))))
                 (with-open-file (in full-path :element-type 'octet)
                   (let* ((in (make-flexi-stream in :external-format external-format))
                          (third (floor octet-length 3))
                          (half (floor octet-length 2))
                          (vector (make-array half :element-type 'octet)))
                     (check (sequence-equal (loop repeat third
                                                  collect (read-byte in))
                                            (subseq octets 0 third)))
                     (read-sequence vector in)
                     (check (sequence-equal vector (subseq octets third (+ third half)))))))
               (when verbose
                 (format t "~&...reading characters"))
               (with-open-file (in full-path :element-type 'octet)
                 (let* ((in (make-flexi-stream in :external-format external-format))
                        (string (make-string (- string-length 10) :element-type 'char*)))
                   (setf (flexi-stream-element-type in) 'octet)
                   (check (sequence-equal (loop repeat 10
                                                collect (read-char in))
                                          (subseq file-string 0 10)))
                   (read-sequence string in)
                   (check (sequence-equal string (subseq file-string 10)))))
               (with-open-file (in full-path :element-type 'octet)
                 (let* ((in (make-flexi-stream in :external-format external-format))
                        (list (make-list (- string-length 100))))
                   (check (sequence-equal (loop repeat 50
                                                collect (read-char in))
                                          (subseq file-string 0 50)))
                   #-:clisp
                   (read-sequence list in)
                   #+:clisp
                   (ext:read-char-sequence list in)
                   (check (sequence-equal list (subseq file-string 50 (- string-length 50))))
                   (check (sequence-equal (loop repeat 50
                                                collect (read-char in))
                                          (subseq file-string (- string-length 50))))))
               (with-open-file (in full-path :element-type 'octet)
                 (let* ((in (make-flexi-stream in :external-format external-format))
                        (array (make-array (- string-length 50))))
                   (check (sequence-equal (loop repeat 25
                                                collect (read-char in))
                                          (subseq file-string 0 25)))
                   #-:clisp
                   (read-sequence array in)
                   #+:clisp
                   (ext:read-char-sequence array in)
                   (check (sequence-equal array (subseq file-string 25 (- string-length 25))))
                   (check (sequence-equal (loop repeat 25
                                                collect (read-char in))
                                          (subseq file-string (- string-length 25))))))
               (let ((path-out (ensure-directories-exist (merge-pathnames pathspec *tmp-dir*))))
                 (when verbose
                   (format t "~&...writing sequences"))
                 (with-open-file (out path-out
                                      :direction :output
                                      :if-exists :supersede
                                      :element-type 'octet)
                   (let ((out (make-flexi-stream out :external-format external-format)))
                     (write-sequence octets out)))
                 (check (file-equal full-path path-out))
                 (with-open-file (out path-out
                                      :direction :output
                                      :if-exists :supersede
                                      :element-type 'octet)
                   (let ((out (make-flexi-stream out :external-format external-format)))
                     (write-sequence file-string out)))
                 (check (file-equal full-path path-out))
                 (with-open-file (out path-out
                                      :direction :output
                                      :if-exists :supersede
                                      :element-type 'octet)
                   (let ((out (make-flexi-stream out :external-format external-format)))
                     (write-sequence file-string out :end 100)
                     (write-sequence octets out
                                     :start (length (string-to-octets file-string
                                                                      :external-format external-format
                                                                      :end 100)))))
                 (check (file-equal full-path path-out))))))

      (loop with simple-test-args-list = (loop for (file-name symbols) in *test-files*
                                               nconc (create-test-combinations file-name symbols t))
            for (pathspec external-format) in simple-test-args-list
            do (one-sequence-test pathspec external-format verbose)))))

(defmacro using-values ((&rest values) &body body)
  "Executes BODY and feeds an element from VALUES to the USE-VALUE
restart each time a EXTERNAL-FORMAT-ENCODING-ERROR is signalled.
Signals an error when there are more or less
EXTERNAL-FORMAT-ENCODING-ERRORs than there are elements in VALUES."
  (with-unique-names (value-stack condition-counter)
    `(let ((,value-stack ',values)
	   (,condition-counter 0))
       (handler-bind ((external-format-encoding-error
                       #'(lambda (c)
                           (declare (ignore c)) 
                           (unless ,value-stack
                             (error "Too many encoding errors signalled, expected only ~A."
                                    ,(length values)))
                           (incf ,condition-counter)
                           (use-value (pop ,value-stack)))))
         (prog1 (progn ,@body)
           (when ,value-stack
             (error "~A encoding errors signalled, but ~A were expected."
                    ,condition-counter ,(length values))))))))

(defun accept-overlong (octets code-point)
  "Converts the `overlong' UTF-8 sequence OCTETS to using
OCTETS-TO-STRINGS, accepts the expected error with the corresponding
restart and checks that the result is CODE-POINT."
  (handler-bind ((external-format-encoding-error
                  (lambda (c)
                    (declare (ignore c))
                    (invoke-restart 'accept-overlong-sequence))))
    (string= (octets-to-string octets :external-format :utf-8)
             (string (code-char code-point)))))

(defun read-flexi-line (sequence external-format)
  "Creates and returns a string from the octet sequence SEQUENCE using
the external format EXTERNAL-FORMAT."
  (with-input-from-sequence (in sequence)
    (setq in (make-flexi-stream in :external-format external-format))
    (read-line in)))

(defun read-flexi-line* (sequence external-format)
  "Like READ-FLEXI-LINE but uses OCTETS-TO-STRING internally."
  (octets-to-string sequence :external-format external-format))

(defun error-handling-tests (&key verbose)
  "Tests several possible errors and how they are handled."
  (with-test-suite ("Testing error handling" :show-progress-p (not verbose))
    (macrolet ((want-encoding-error (input format)
                 `(with-expected-error (external-format-encoding-error)
                    (read-flexi-line* ,input ,format))))
      (when verbose
        (format t "~&\"Overlong\" UTF-8 sequences"))
      (want-encoding-error #(#b11000000 #b10000000) :utf-8)
      (want-encoding-error #(#b11000001 #b10000000) :utf-8)
      (want-encoding-error #(#b11100000 #b10011111 #b10000000) :utf-8)
      (want-encoding-error #(#b11110000 #b10001111 #b10000000 #b10000000) :utf-8)
      (check (accept-overlong #(#b11000000 #b10000000) #b00000000))
      (check (accept-overlong #(#b11000001 #b10000000) #b01000000))
      (check (accept-overlong #(#b11100000 #b10011111 #b10000000) #b011111000000))
      (check (accept-overlong #(#b11110000 #b10001111 #b10000000 #b10000000)
                              #b1111000000000000))
      (when verbose
        (format t "~&Invalid lead octets in UTF-8"))
      (want-encoding-error #(#b11111000) :utf-8)
      (want-encoding-error #(#b11111001) :utf-8)
      (want-encoding-error #(#b11111100) :utf-8)
      (want-encoding-error #(#b11111101) :utf-8)
      (want-encoding-error #(#b11111110) :utf-8)
      (want-encoding-error #(#b11111111) :utf-8)
      (when verbose
        (format t "~&Illegal code points"))
      (want-encoding-error #(#x00 #x00 #x11 #x00) :utf-32le)
      (want-encoding-error #(#x00 #xd8) :utf-16le)
      (want-encoding-error #(#xff #xdf) :utf-16le))
    (macrolet ((want-encoding-error (input format)
                 `(with-expected-error (external-format-encoding-error)
                    (read-flexi-line* ,input ,format))))                 
      (when verbose
        (format t "~&UTF-8 sequences which are too short"))
      (want-encoding-error #(#xe4 #xf6 #xfc) :utf8)
      (want-encoding-error #(#xc0) :utf8)
      (want-encoding-error #(#xe0 #xff) :utf8)
      (want-encoding-error #(#xf0 #xff #xff) :utf8)
      (when verbose
        (format t "~&UTF-16 sequences with an odd number of octets"))
      (want-encoding-error #(#x01) :utf-16le)
      (want-encoding-error #(#x01 #x01 #x01) :utf-16le)
      (want-encoding-error #(#x01) :utf-16be)
      (want-encoding-error #(#x01 #x01 #x01) :utf-16be)
      (when verbose
        (format t "~&Missing words in UTF-16"))
      (want-encoding-error #(#x01 #xd8) :utf-16le)
      (want-encoding-error #(#xd8 #x01) :utf-16be)
      (when verbose
        (format t "~&Missing octets in UTF-32"))
      (want-encoding-error #(#x01) :utf-32le)
      (want-encoding-error #(#x01 #x01) :utf-32le)
      (want-encoding-error #(#x01 #x01 #x01) :utf-32le)
      (want-encoding-error #(#x01 #x01 #x01 #x01 #x01) :utf-32le)
      (want-encoding-error #(#x01) :utf-32be)
      (want-encoding-error #(#x01 #x01) :utf-32be)
      (want-encoding-error #(#x01 #x01 #x01) :utf-32be)
      (want-encoding-error #(#x01 #x01 #x01 #x01 #x01) :utf-32be))
    (when verbose
      (format t "~&Handling of EOF in the middle of CRLF"))
    (check (string= #.(string #\Return)
                    (read-flexi-line `(,(char-code #\Return)) '(:ascii :eol-style :crlf))))
    (let ((*substitution-char* #\?))
      (when verbose
        (format t "~&Fixed substitution character #\?")
        (format t "~&:ASCII doesn't have characters with char codes > 127"))
      (check (string= "a??" (read-flexi-line `(,(char-code #\a) 128 200) :ascii)))
      (check (string= "a??" (read-flexi-line* `#(,(char-code #\a) 128 200) :ascii)))
      (when verbose
        (format t "~&:WINDOWS-1253 doesn't have a characters with codes 170 and 210"))
      (check (string= "a??" (read-flexi-line `(,(char-code #\a) 170 210) :windows-1253)))
      (check (string= "a??" (read-flexi-line* `#(,(char-code #\a) 170 210) :windows-1253)))
      (when verbose
        (format t "~&Not a valid UTF-8 sequence"))
      (check (string= "??" (read-flexi-line '(#xe4 #xf6 #xfc) :utf8))))
    (let ((*substitution-char* nil))
      (when verbose
        (format t "~&Variable substitution using USE-VALUE restart")
        (format t "~&:ASCII doesn't have characters with char codes > 127"))
      (check (string= "abc" (using-values (#\b #\c)
                              (read-flexi-line `(,(char-code #\a) 128 200) :ascii))))
      (check (string= "abc" (using-values (#\b #\c)
                              (read-flexi-line* `#(,(char-code #\a) 128 200) :ascii))))
      (when verbose
        (format t "~&:WINDOWS-1253 doesn't have a characters with codes 170 and 210"))
      (check (string= "axy" (using-values (#\x #\y)
                              (read-flexi-line `(,(char-code #\a) 170 210) :windows-1253))))
      (check (string= "axy" (using-values (#\x #\y)
                              (read-flexi-line* `#(,(char-code #\a) 170 210) :windows-1253))))
      (when verbose
        (format t "~&Not a valid UTF-8 sequence"))
      (check (string= "QW" (using-values (#\Q #\W) (read-flexi-line '(#xe4 #xf6 #xfc) :utf8))))
      (when verbose
        (format t "~&UTF-8 can't start neither with #b11111110 nor with #b11111111"))
      (check (string= "QW" (using-values (#\Q #\W) (read-flexi-line '(#b11111110 #b11111111) :utf8))))
      (when verbose
        (format t "~&Only one octet in UTF-16 sequence"))
      (check (string= "E" (using-values (#\E) (read-flexi-line '(#x01) :utf-16le))))
      (when verbose
        (format t "~&Two octets in UTF-16, but value of resulting word suggests that another word follows"))
      (check (string= "R" (using-values (#\R) (read-flexi-line '(#x01 #xd8) :utf-16le))))
      (when verbose
        (format t "~&The second word must fit into the [#xdc00; #xdfff] interval, but it is #xdbff"))
      (check (string= "T" (using-values (#\T) (read-flexi-line '(#x01 #xd8 #xff #xdb) :utf-16le))))
      (check (string= "T" (using-values (#\T) (read-flexi-line* #(#x01 #xd8 #xff #xdb) :utf-16le))))
      (when verbose
        (format t "~&The same as for little endian above, but using inverse order of bytes in words"))
      (check (string= "E" (using-values (#\E) (read-flexi-line '(#x01) :utf-16be))))
      (check (string= "R" (using-values (#\R) (read-flexi-line '(#xd8 #x01) :utf-16be))))
      (check (string= "T" (using-values (#\T) (read-flexi-line '(#xd8 #x01 #xdb #xff) :utf-16be))))
      (check (string= "T" (using-values (#\T) (read-flexi-line* #(#xd8 #x01 #xdb #xff) :utf-16be))))
      (check (string= "T" (using-values (#\T) (read-flexi-line* #(#xd8 #x01) :gbk))))
      (when verbose
        (format t "~&EOF in the middle of a 4-octet sequence in UTF-32"))
      (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01) :utf-32le))))
      (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01 #x01) :utf-32le))))
      (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01 #x01 #x01) :utf-32le))))
      (check (string= "aY" (using-values (#\Y)
                             (read-flexi-line `(,(char-code #\a) #x00 #x00 #x00 #x01) :utf-32le))))
      (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01) :utf-32be))))
      (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01 #x01) :utf-32be))))
      (check (string= "Y" (using-values (#\Y) (read-flexi-line '(#x01 #x01 #x01) :utf-32be))))
      (check (string= "aY" (using-values (#\Y)
                             (read-flexi-line `(#x00 #x00 #x00 ,(char-code #\a) #x01) :utf-32be)))))))

(defun unread-char-tests (&key verbose)
  "Tests whether UNREAD-CHAR behaves as expected."
  (with-test-suite ("UNREAD-CHAR behaviour." :show-progress-p (and (not verbose) 100))
    (flet ((test-one-file (file-name external-format)
             (when verbose
               (format t "~&  ...and external format ~A" (normalize-external-format external-format)))
             (with-open-file (in (merge-pathnames file-name *this-file*)
                                 :element-type 'flex:octet)
               (let ((in (make-flexi-stream in :external-format external-format)))
                 (loop repeat 300
                       for char = (read-char in)
                       do (unread-char char in)
                          (check (char= (read-char in) char)))))))
      (loop for (file-name symbols) in *test-files*
            when verbose
            do (format t "~&With file ~S" file-name)
            do (loop for symbol in symbols
                     do (loop for (file-name . external-format) in (create-file-variants file-name symbol)
                              do (test-one-file file-name external-format)))))))

(defun column-tests (&key verbose)
  (with-test-suite ("STREAM-LINE-COLUMN tests" :show-progress-p (not verbose))
    (let* ((binary-stream (flexi-streams:make-in-memory-output-stream))
           (stream (flexi-streams:make-flexi-stream binary-stream :external-format :iso-8859-1)))
      (write-sequence "hello" stream)
      (format stream "~12Tworld")
      (finish-output stream)
      (check (string= "hello       world"
                      (flexi-streams:octets-to-string
                       (flexi-streams::vector-stream-vector binary-stream)
                       :external-format :iso-8859-1)))
      (terpri stream)
      (check (= 0 (flexi-stream-column stream)))
      (write-sequence "abc" stream)
      (check (= 3 (flexi-stream-column stream)))
      (terpri stream)
      (check (= 0 (flexi-stream-column stream))))))

(defun make-external-format-tests (&key verbose)
  (with-test-suite ("MAKE-EXTERNAL-FORMAT tests" :show-progress-p (not verbose))
    (flet ((make-case (real-name &key id name)
           (list real-name
                 :id id
                 :input-names (list name (string-upcase name) (string-downcase name)))))
      (let ((cases (append '((:utf-8 :id nil
                                     :input-names (:utf8 :utf-8 "utf8" "utf-8" "UTF8" "UTF-8")))
                           (loop for (name . real-name) in +name-map+
                                 unless (member :code-page (list name real-name))
                                   append (list (make-case real-name :name name)
                                                (make-case real-name :name real-name)))
                           (loop for (name . definition) in +shortcut-map+
                                 for key = (car definition)
                                 for id = (getf (cdr definition) :id)
                                 for expected = (or (cdr (assoc key +name-map+)) key)
                                 collect (make-case expected :id id :name name)))))

        (loop for (expected-name . kwargs) in cases
              for id = (getf kwargs :id)
              for input-names = (getf kwargs :input-names)
              do (loop for name in input-names
                       for ext-format = (make-external-format name)
                       do (check (eq (flex:external-format-name ext-format) expected-name))
                       when id
                         do (check (= (flex:external-format-id ext-format) id))))))

    (let ((error-cases '("utf-8 " " utf-8" "utf8 " " utf8" "utf89" :utf89 utf89 :code-page nil)))
      (loop for input-name in error-cases
            do (with-expected-error (external-format-error)
                 (make-external-format input-name))))))

(defun peek-byte-tests (&key verbose)
  (with-test-suite ("PEEK-BYTE tests" :show-progress-p (not verbose))
    (flex:with-input-from-sequence (input #(0 1 2))
      (let ((stream (flex:make-flexi-stream input)))
        ;; If peek-type was specified as 2 we need to peek the first octect equal to 2
        (check (= 2 (flex::peek-byte stream 2 nil 1)))
        ;; also, the octet should be unread to the stream so that we can peek it again
        (check (= 2 (flex::peek-byte stream nil nil nil)))))))

(defun in-memory-stream-tests (&key verbose)
  (with-test-suite ("IN-MEMORY-STREAM tests" :show-progress-p (not verbose))
    (let ((z (make-array 4)))
      (read-sequence  z (make-in-memory-input-stream #(1 2 3 4)))
      (check (equalp z #(1 2 3 4)))
      (read-sequence  z (make-in-memory-input-stream '(4 3 2 1)))
      (check (equalp z #(4 3 2 1)))
      (read-sequence  z (make-in-memory-input-stream #(1 2 3 4) :transformer #'1+))
      (check (equalp z #(2 3 4 5)))
      (read-sequence  z (make-in-memory-input-stream '(1 2 3 4) :transformer #'1-))
      (check (equalp z #(0 1 2 3))))))

(defun run-all-tests (&key verbose)
  "Runs all tests for FLEXI-STREAMS and returns a true value iff all
tests succeeded.  VERBOSE is interpreted by the individual test suites
above."
  (let ((successp t))
    (macrolet ((run-test-suite (&body body)
                 `(unless (progn ,@body)
                    (setq successp nil))))
      (run-test-suite (compare-files :verbose verbose))
      (run-test-suite (string-tests :verbose verbose))
      (run-test-suite (sequence-tests :verbose verbose))
      (run-test-suite (error-handling-tests :verbose verbose))
      (run-test-suite (unread-char-tests :verbose verbose))
      (run-test-suite (column-tests :verbose verbose))
      (run-test-suite (make-external-format-tests :verbose verbose))
      (run-test-suite (peek-byte-tests :verbose verbose))
      (run-test-suite (in-memory-stream-tests :verbose verbose))
      (format t "~2&~:[Some tests failed~;All tests passed~]." successp)
      successp)))