1 ;;;; ports.test --- test suite for Guile I/O ports -*- scheme -*-
2 ;;;; Jim Blandy <jimb@red-bean.com> --- May 1999
4 ;;;; Copyright (C) 1999 Free Software Foundation, Inc.
6 ;;;; This program is free software; you can redistribute it and/or modify
7 ;;;; it under the terms of the GNU General Public License as published by
8 ;;;; the Free Software Foundation; either version 2, or (at your option)
9 ;;;; any later version.
11 ;;;; This program is distributed in the hope that it will be useful,
12 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;;;; GNU General Public License for more details.
16 ;;;; You should have received a copy of the GNU General Public License
17 ;;;; along with this software; see the file COPYING. If not, write to
18 ;;;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
19 ;;;; Boston, MA 02111-1307 USA
21 (use-modules (test-suite lib)
24 (define (display-line . args)
25 (for-each display args)
32 ;;;; Some general utilities for testing ports.
34 ;;; Read from PORT until EOF, and return the result as a string.
35 (define (read-all port)
36 (let loop ((chars '()))
37 (let ((char (read-char port)))
38 (if (eof-object? char)
39 (list->string (reverse! chars))
40 (loop (cons char chars))))))
42 (define (read-file filename)
43 (let* ((port (open-input-file filename))
44 (string (read-all port)))
49 ;;;; Normal file ports.
51 ;;; Write out an s-expression, and read it back.
52 (let ((string '("From fairest creatures we desire increase,"
53 "That thereby beauty's rose might never die,"))
54 (filename (test-file)))
55 (let ((port (open-output-file filename)))
58 (let ((port (open-input-file filename)))
59 (let ((in-string (read port)))
60 (pass-if "file: write and read back list of strings"
61 (equal? string in-string)))
63 (delete-file filename))
65 ;;; Write out a string, and read it back a character at a time.
66 (let ((string "This is a test string\nwith no newline at the end")
67 (filename (test-file)))
68 (let ((port (open-output-file filename)))
71 (let ((in-string (read-file filename)))
72 (pass-if "file: write and read back characters"
73 (equal? string in-string)))
74 (delete-file filename))
76 ;;; Buffered input/output port with seeking.
77 (let* ((filename (test-file))
78 (port (open-file filename "w+")))
79 (display "J'Accuse" port)
80 (seek port -1 SEEK_CUR)
81 (pass-if "file: r/w 1"
82 (char=? (read-char port) #\e))
83 (pass-if "file: r/w 2"
84 (eof-object? (read-char port)))
85 (seek port -1 SEEK_CUR)
87 (seek port 7 SEEK_SET)
88 (pass-if "file: r/w 3"
89 (char=? (read-char port) #\x))
90 (seek port -2 SEEK_END)
91 (pass-if "file: r/w 4"
92 (char=? (read-char port) #\s))
93 (delete-file filename))
95 ;;; Unbuffered input/output port with seeking.
96 (let* ((filename (test-file))
97 (port (open-file filename "w+0")))
98 (display "J'Accuse" port)
99 (seek port -1 SEEK_CUR)
100 (pass-if "file: ub r/w 1"
101 (char=? (read-char port) #\e))
102 (pass-if "file: ub r/w 2"
103 (eof-object? (read-char port)))
104 (seek port -1 SEEK_CUR)
105 (write-char #\x port)
106 (seek port 7 SEEK_SET)
107 (pass-if "file: ub r/w 3"
108 (char=? (read-char port) #\x))
109 (seek port -2 SEEK_END)
110 (pass-if "file: ub r/w 4"
111 (char=? (read-char port) #\s))
112 (delete-file filename))
114 ;;; Buffered output-only and input-only ports with seeking.
115 (let* ((filename (test-file))
116 (port (open-output-file filename)))
117 (display "J'Accuse" port)
118 (pass-if "file: out tell"
119 (= (seek port 0 SEEK_CUR) 8))
120 (seek port -1 SEEK_CUR)
121 (write-char #\x port)
123 (let ((iport (open-input-file filename)))
124 (pass-if "file: in tell 0"
125 (= (seek iport 0 SEEK_CUR) 0))
127 (pass-if "file: in tell 1"
128 (= (seek iport 0 SEEK_CUR) 1))
129 (unread-char #\z iport)
130 (pass-if "file: in tell 0 after unread"
131 (= (seek iport 0 SEEK_CUR) 0))
132 (pass-if "file: unread char still there"
133 (char=? (read-char iport) #\z))
134 (seek iport 7 SEEK_SET)
135 (pass-if "file: in last char"
136 (char=? (read-char iport) #\x))
138 (delete-file filename))
140 ;;; unusual characters.
141 (let* ((filename (test-file))
142 (port (open-output-file filename)))
143 (display (string #\nul (integer->char 255) (integer->char 128)
146 (let* ((port (open-input-file filename))
147 (line (read-line port)))
148 (pass-if "file: read back NUL 1"
149 (char=? (string-ref line 0) #\nul))
150 (pass-if "file: read back 255"
151 (char=? (string-ref line 1) (integer->char 255)))
152 (pass-if "file: read back 128"
153 (char=? (string-ref line 2) (integer->char 128)))
154 (pass-if "file: read back NUL 2"
155 (char=? (string-ref line 3) #\nul))
157 (eof-object? (read-char port))))
158 (delete-file filename))
160 ;;; line buffering mode.
161 (let* ((filename (test-file))
162 (port (open-file filename "wl"))
163 (test-string "one line more or less"))
164 (write-line test-string port)
165 (let* ((in-port (open-input-file filename))
166 (line (read-line in-port)))
169 (pass-if "file: line buffering"
170 (string=? line test-string)))
171 (delete-file filename))
173 ;;; ungetting characters and strings.
174 (with-input-from-string "walk on the moon\nmoon"
177 (unread-char #\a (current-input-port))
178 (pass-if "unread-char"
179 (char=? (read-char) #\a))
181 (let ((replacenoid "chicken enchilada"))
182 (unread-char #\newline (current-input-port))
183 (unread-string replacenoid (current-input-port))
184 (pass-if "unread-string"
185 (string=? (read-line) replacenoid)))
186 (pass-if "unread residue"
187 (string=? (read-line) "moon"))))
189 ;;; non-blocking mode on a port. create a pipe and set O_NONBLOCK on
190 ;;; the reading end. try to read a byte: should get EAGAIN or
191 ;;; EWOULDBLOCK error.
194 (fcntl r F_SETFL (logior (fcntl r F_GETFL) O_NONBLOCK))
195 (pass-if "non-blocking-I/O"
197 (lambda () (read-char r) #f)
199 (and (eq? key 'system-error)
200 (let ((errno (car (list-ref args 3))))
202 (= errno EWOULDBLOCK))))))))
204 ;;;; Pipe (popen) ports.
206 ;;; Run a command, and read its output.
207 (let* ((pipe (open-pipe "echo 'Howdy there, partner!'" "r"))
208 (in-string (read-all pipe)))
210 (pass-if "pipe: read"
211 (equal? in-string "Howdy there, partner!\n")))
213 ;;; Run a command, send some output to it, and see if it worked.
214 (let* ((filename (test-file))
215 (pipe (open-pipe (string-append "grep Mommy > " filename) "w")))
216 (display "Now Jimmy lives on a mushroom cloud\n" pipe)
217 (display "Mommy, why does everybody have a bomb?\n" pipe)
219 (let ((in-string (read-file filename)))
220 (pass-if "pipe: write"
221 (equal? in-string "Mommy, why does everybody have a bomb?\n")))
222 (delete-file filename))
225 ;;;; Void ports. These are so trivial we don't test them.
230 (with-test-prefix "string ports"
232 ;; Write text to a string port.
233 (let* ((string "Howdy there, partner!")
234 (in-string (call-with-output-string
236 (display string port)
238 (pass-if "display text"
239 (equal? in-string (string-append string "\n"))))
241 ;; Write an s-expression to a string port.
242 (let* ((sexpr '("more utterly random text" 1729 #(a vector) 3.1415926))
244 (call-with-input-string (call-with-output-string
248 (pass-if "write/read sexpr"
249 (equal? in-sexpr sexpr)))
251 ;; seeking and unreading from an input string.
252 (let ((text "that text didn't look random to me"))
253 (call-with-input-string text
255 (pass-if "input tell 0"
256 (= (seek p 0 SEEK_CUR) 0))
258 (pass-if "input tell 1"
259 (= (seek p 0 SEEK_CUR) 1))
261 (pass-if "input tell back to 0"
262 (= (seek p 0 SEEK_CUR) 0))
263 (pass-if "input ungetted char"
264 (char=? (read-char p) #\x))
266 (pass-if "input seek to end"
267 (= (seek p 0 SEEK_CUR)
268 (string-length text)))
270 (pass-if "input seek to beginning"
271 (= (seek p 0 SEEK_SET) 0))
272 (pass-if "input reread first char"
273 (char=? (read-char p)
274 (string-ref text 0))))))
276 ;; seeking an output string.
277 (let* ((text "123456789")
278 (len (string-length text))
279 (result (call-with-output-string
281 (pass-if "output tell 0"
282 (= (seek p 0 SEEK_CUR) 0))
284 (pass-if "output tell end"
285 (= (seek p 0 SEEK_CUR) len))
286 (pass-if "output seek to beginning"
287 (= (seek p 0 SEEK_SET) 0))
290 (pass-if "output seek to last char"
291 (= (seek p 0 SEEK_CUR)
293 (write-char #\b p)))))
294 (string-set! text 0 #\a)
295 (string-set! text (- len 1) #\b)
296 (pass-if "output check"
297 (string=? text result))))
301 ;;;; Soft ports. No tests implemented yet.
304 ;;;; Generic operations across all port types.
306 (let ((port-loop-temp (test-file)))
308 ;; Return a list of input ports that all return the same text.
309 ;; We map tests over this list.
310 (define (input-port-list text)
312 ;; Create a text file some of the ports will use.
313 (let ((out-port (open-output-file port-loop-temp)))
314 (display text out-port)
315 (close-port out-port))
317 (list (open-input-file port-loop-temp)
318 (open-input-pipe (string-append "cat " port-loop-temp))
319 (call-with-input-string text (lambda (x) x))
320 ;; We don't test soft ports at the moment.
323 (define port-list-names '("file" "pipe" "string"))
325 ;; Test the line counter.
326 (define (test-line-counter text second-line final-column)
327 (with-test-prefix "line counter"
328 (let ((ports (input-port-list text)))
330 (lambda (port port-name)
331 (with-test-prefix port-name
332 (pass-if "at beginning of input"
333 (= (port-line port) 0))
334 (pass-if "read first character"
335 (eqv? (read-char port) #\x))
336 (pass-if "after reading one character"
337 (= (port-line port) 0))
338 (pass-if "read first newline"
339 (eqv? (read-char port) #\newline))
340 (pass-if "after reading first newline char"
341 (= (port-line port) 1))
342 (pass-if "second line read correctly"
343 (equal? (read-line port) second-line))
344 (pass-if "read-line increments line number"
345 (= (port-line port) 2))
346 (pass-if "read-line returns EOF"
349 ((eof-object? (read-line port)) #t)
351 (else (loop (+ i 1))))))
352 (pass-if "line count is 5 at EOF"
353 (= (port-line port) 5))
354 (pass-if "column is correct at EOF"
355 (= (port-column port) final-column))))
356 ports port-list-names)
357 (for-each close-port ports)
358 (delete-file port-loop-temp))))
360 (with-test-prefix "newline"
363 "He who receives an idea from me, receives instruction\n"
364 "himself without lessening mine; as he who lights his\n"
365 "taper at mine, receives light without darkening me.\n"
366 " --- Thomas Jefferson\n")
367 "He who receives an idea from me, receives instruction"
370 (with-test-prefix "no newline"
373 "He who receives an idea from me, receives instruction\n"
374 "himself without lessening mine; as he who lights his\n"
375 "taper at mine, receives light without darkening me.\n"
376 " --- Thomas Jefferson\n"
378 "He who receives an idea from me, receives instruction"
382 ;;;; testing read-delimited and friends
384 (with-test-prefix "read-delimited!"
385 (let ((c (make-string 20 #\!)))
386 (call-with-input-string
390 (read-delimited! "\n" c port 'concat)
391 (pass-if "read-delimited! reads a first line"
392 (string=? c "defdef\n!!!!!!!!!!!!!"))
394 (read-delimited! "\n" c port 'concat 3)
395 (pass-if "read-delimited! reads a first line"
396 (string=? c "defghighi\n!!!!!!!!!!"))))))
401 (call-with-input-string
404 (pass-if "char-ready? returns true on string port"
405 (char-ready? port))))
407 ;;; This segfaults on some versions of Guile. We really should run
408 ;;; the tests in a subprocess...
410 (call-with-input-string
413 (with-input-from-port
416 (pass-if "char-ready? returns true on string port as default port"
420 ;;;; Close current-input-port, and make sure everyone can handle it.
422 (with-test-prefix "closing current-input-port"
423 (for-each (lambda (procedure name)
424 (with-input-from-port
425 (call-with-input-string "foo" (lambda (p) p))
427 (close-port (current-input-port))
429 (signals-error? 'wrong-type-arg (procedure))))))
430 (list read read-char read-line)
431 '("read" "read-char" "read-line")))