Fix CLOSE method on MY-FILE-STREAM in the stream test suite.
[iolib.git] / tests / streams.lisp
blob2a45988ec0f9a4698b2ce2978616392c0771d064
1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
2 ;;;
3 ;;; streams.lisp --- IO.STREAMS test suite.
4 ;;;
5 ;;; Copyright (c) 2006-2007, Dr. Edmund Weitz. All rights reserved.
6 ;;; Copyright (c) 2007, Luis Oliveira <loliveira@common-lisp.net>
7 ;;;
8 ;;; Redistribution and use in source and binary forms, with or without
9 ;;; modification, are permitted provided that the following conditions
10 ;;; are met:
11 ;;;
12 ;;; * Redistributions of source code must retain the above copyright
13 ;;; notice, this list of conditions and the following disclaimer.
14 ;;;
15 ;;; * Redistributions in binary form must reproduce the above
16 ;;; copyright notice, this list of conditions and the following
17 ;;; disclaimer in the documentation and/or other materials
18 ;;; provided with the distribution.
19 ;;;
20 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
21 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
24 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
26 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 (in-package #:iolib-tests)
34 (defclass my-file-stream (dual-channel-single-fd-gray-stream)
35 ((path :initarg :path :reader file-stream-path)))
37 (defmethod close :around ((file my-file-stream) &key abort)
38 (declare (ignore abort))
39 (call-next-method)
40 (when (fd-of file)
41 (nix:close (fd-of file)))
42 (setf (fd-of file) nil))
44 ;;; Very ad-hoc: doesn't do :DIRECTION :PROBE, or handle errors,
45 ;;; :IF-DOES-NOT-EXIST, among many other things. This kind of thing
46 ;;; should be moved into OSICAT, btw.
47 ;;;
48 ;;; FIXME: implement single-channel stream
49 (defun make-file-stream (path &key
50 (direction :input)
51 (if-exists :error)
52 (if-does-not-exist (ecase direction
53 (:input :error)
54 ((:io :output) :create)))
55 (external-format :default))
56 (declare (ignore if-does-not-exist))
57 ;; move OPEN to INITIALIZE-INSTANCE
58 (let ((fd (nix:open path
59 (logior (ecase direction
60 (:input nix:o-rdonly)
61 (:output (logior nix:o-creat nix:o-wronly))
62 (:io (logior nix:o-creat nix:o-rdwr)))
63 (ecase if-exists
64 (:error nix:o-excl)
65 (:supersede nix:o-trunc)
66 (:append nix:o-append)
67 (:overwrite 0)))
68 (logior nix:s-irusr nix:s-iwusr))))
69 (make-instance 'my-file-stream
70 :path path
71 :input-fd fd
72 :output-fd fd
73 :external-format external-format)))
75 (defmacro with-open-file-stream ((var path &rest options) &body body)
76 (with-gensyms (stream)
77 `(let ((,stream (make-file-stream ,path ,@options)))
78 (with-open-stream (,var ,stream)
79 ,@body))))
81 (defvar *data-dir*
82 (let ((sys-pn (truename (asdf:system-definition-pathname
83 (asdf:find-system 'iolib-tests)))))
84 (make-pathname :directory (append (pathname-directory sys-pn)
85 '("tests" "data")))))
87 (defvar *test-dir*
88 (ensure-directories-exist
89 (merge-pathnames
90 (make-pathname :directory '(:relative "test-dir"))
91 (make-pathname :directory
92 (pathname-directory
93 (or *load-truename* *compile-file-truename*))))))
95 ;;; A list of test files where each entry consists of the name
96 ;;; prefix and a list of encodings.
97 (defvar *test-files*
98 '(("kafka" (:utf-8 :latin-1 #|:cp1252|#))
99 ("tilton" (:utf-8 :ascii))
100 ("hebrew" (:utf-8 #|:latin-8|#))
101 ("russian" (:utf-8 #|:koi8r|#))
102 ("unicode_demo" (:utf-8 #|:utf-16 :utf-32|#))))
104 ;;; For a name suffix FILE-NAME and a symbol SYMBOL denoting an
105 ;;; encoding returns a list of pairs where the car is a full file name
106 ;;; and the cdr is the corresponding external format. This list
107 ;;; contains all possible line-end conversions.
108 (defun create-file-variants (file-name symbol)
109 (loop :for eol-style :in '(:lf :cr :crlf) :collect
110 (cons (format nil "~A_~(~A~)_~(~A~).txt"
111 file-name symbol eol-style)
112 (babel:make-external-format symbol :eol-style eol-style))))
114 ;;; For a name suffix FILE-NAME and a list of symbols SYMBOLS denoting
115 ;;; different encodings of the corresponding file returns a list of
116 ;;; lists which can be used as arglists for COMPARE-FILES.
117 (defun create-test-combinations (file-name symbols)
118 (let ((file-variants (loop :for symbol :in symbols
119 :nconc (create-file-variants file-name symbol))))
120 (loop :for (name-in . external-format-in) :in file-variants
121 :nconc (loop :for (name-out . external-format-out) :in file-variants
122 :collect (list name-in external-format-in
123 name-out external-format-out)))))
125 ;;; Returns a true value iff FILE1 and FILE2 have the same contents
126 ;;; (viewed as binary files).
127 (defun file-equal (file1 file2)
128 (with-open-file (stream1 file1 :element-type '(unsigned-byte 8))
129 (with-open-file (stream2 file2 :element-type '(unsigned-byte 8))
130 (and (= (file-length stream1) (file-length stream2))
131 (loop :for byte1 := (read-byte stream1 nil nil)
132 :for byte2 := (read-byte stream2 nil nil)
133 :while (and byte1 byte2)
134 :always (= byte1 byte2))))))
136 ;;; Copies the contents of the file denoted by the pathname PATH-IN to
137 ;;; the file denoted by the pathname PATH-OUT using flexi streams -
138 ;;; STREAM-IN is read with the external format EXTERNAL-FORMAT-IN and
139 ;;; STREAM-OUT is written with EXTERNAL-FORMAT-OUT. The input file is
140 ;;; opened with the :DIRECTION keyword argument DIRECTION-IN, the
141 ;;; output file is opened with the :DIRECTION keyword argument
142 ;;; DIRECTION-OUT.
143 (defun copy-file (path-in external-format-in path-out external-format-out
144 direction-out direction-in)
145 (with-open-file-stream (in path-in
146 :direction direction-in
147 :if-does-not-exist :error
148 :if-exists :overwrite
149 :external-format external-format-in)
150 (with-open-file-stream (out path-out
151 :direction direction-out
152 :if-does-not-exist :create
153 :if-exists :supersede
154 :external-format external-format-out)
155 (loop :for line := (read-line in nil nil)
156 :while line :do (write-line line out)))))
158 (defun ef-name (ef)
159 (format nil "~A ~A"
160 (babel-encodings:enc-name (babel:external-format-encoding ef))
161 (babel:external-format-eol-style ef)))
163 ;;; Copies the contents of the file (in the 'test') denoted by the
164 ;;; relative pathname PATH-IN to the file (in a temporary directory)
165 ;;; denoted by the relative pathname PATH-OUT using flexi streams -
166 ;;; STREAM-IN is read with the external format EXTERNAL-FORMAT-IN and
167 ;;; STREAM-OUT is written with EXTERNAL-FORMAT-OUT. The resulting
168 ;;; file is compared with an existing file in the 'test' directory to
169 ;;; check if the outcome is as expected. Uses various variants of the
170 ;;; :DIRECTION keyword when opening the files."
171 (defun compare-files (path-in external-format-in path-out external-format-out)
172 (let ((full-path-in (merge-pathnames path-in *data-dir*))
173 (full-path-out (merge-pathnames path-out *test-dir*))
174 (full-path-orig (merge-pathnames path-out *data-dir*)))
175 (dolist (direction-out '(:output :io) t)
176 (dolist (direction-in '(:input :io))
177 (let ((description (format nil "Test ~S ~A [~A] --> ~A [~A]"
178 path-in (ef-name external-format-in)
179 direction-in (ef-name external-format-out)
180 direction-out)))
181 (format *error-output* "~&;; ~A.~%" description)
182 (copy-file full-path-in external-format-in
183 full-path-out external-format-out
184 direction-out direction-in)
185 (unless (file-equal full-path-out full-path-orig)
186 (format *error-output* "~&;; Test failed!!!~%")
187 (return-from compare-files nil)))))))
189 (deftest big-stream-comparision-test
190 (let ((args-list (loop :for (file-name symbols) :in *test-files*
191 :nconc (create-test-combinations file-name symbols))))
192 (loop :for args :in args-list
193 :unless (apply #'compare-files args)
194 :collect args))
195 nil)