1 ;;;; ports.test --- test suite for Guile I/O ports -*- scheme -*-
2 ;;;; Jim Blandy <jimb@red-bean.com> --- May 1999
4 ;;;; Copyright (C) 1999, 2001, 2004, 2006, 2007 Free Software Foundation, Inc.
6 ;;;; This library is free software; you can redistribute it and/or
7 ;;;; modify it under the terms of the GNU Lesser General Public
8 ;;;; License as published by the Free Software Foundation; either
9 ;;;; version 3 of the License, or (at your option) any later version.
11 ;;;; This library 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 GNU
14 ;;;; Lesser General Public License for more details.
16 ;;;; You should have received a copy of the GNU Lesser General Public
17 ;;;; License along with this library; if not, write to the Free Software
18 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 (define-module (test-suite test-ports)
21 :use-module (test-suite lib)
22 :use-module (test-suite guile-test)
23 :use-module (ice-9 popen)
24 :use-module (ice-9 rdelim))
26 (define (display-line . args)
27 (for-each display args)
31 (data-file-name "ports-test.tmp"))
34 ;;;; Some general utilities for testing ports.
36 ;;; Read from PORT until EOF, and return the result as a string.
37 (define (read-all port)
38 (let loop ((chars '()))
39 (let ((char (read-char port)))
40 (if (eof-object? char)
41 (list->string (reverse! chars))
42 (loop (cons char chars))))))
44 (define (read-file filename)
45 (let* ((port (open-input-file filename))
46 (string (read-all port)))
51 ;;;; Normal file ports.
53 ;;; Write out an s-expression, and read it back.
54 (let ((string '("From fairest creatures we desire increase,"
55 "That thereby beauty's rose might never die,"))
56 (filename (test-file)))
57 (let ((port (open-output-file filename)))
60 (let ((port (open-input-file filename)))
61 (let ((in-string (read port)))
62 (pass-if "file: write and read back list of strings"
63 (equal? string in-string)))
65 (delete-file filename))
67 ;;; Write out a string, and read it back a character at a time.
68 (let ((string "This is a test string\nwith no newline at the end")
69 (filename (test-file)))
70 (let ((port (open-output-file filename)))
73 (let ((in-string (read-file filename)))
74 (pass-if "file: write and read back characters"
75 (equal? string in-string)))
76 (delete-file filename))
78 ;;; Buffered input/output port with seeking.
79 (let* ((filename (test-file))
80 (port (open-file filename "w+")))
81 (display "J'Accuse" port)
82 (seek port -1 SEEK_CUR)
83 (pass-if "file: r/w 1"
84 (char=? (read-char port) #\e))
85 (pass-if "file: r/w 2"
86 (eof-object? (read-char port)))
87 (seek port -1 SEEK_CUR)
89 (seek port 7 SEEK_SET)
90 (pass-if "file: r/w 3"
91 (char=? (read-char port) #\x))
92 (seek port -2 SEEK_END)
93 (pass-if "file: r/w 4"
94 (char=? (read-char port) #\s))
96 (delete-file filename))
98 ;;; Unbuffered input/output port with seeking.
99 (let* ((filename (test-file))
100 (port (open-file filename "w+0")))
101 (display "J'Accuse" port)
102 (seek port -1 SEEK_CUR)
103 (pass-if "file: ub r/w 1"
104 (char=? (read-char port) #\e))
105 (pass-if "file: ub r/w 2"
106 (eof-object? (read-char port)))
107 (seek port -1 SEEK_CUR)
108 (write-char #\x port)
109 (seek port 7 SEEK_SET)
110 (pass-if "file: ub r/w 3"
111 (char=? (read-char port) #\x))
112 (seek port -2 SEEK_END)
113 (pass-if "file: ub r/w 4"
114 (char=? (read-char port) #\s))
116 (delete-file filename))
118 ;;; Buffered output-only and input-only ports with seeking.
119 (let* ((filename (test-file))
120 (port (open-output-file filename)))
121 (display "J'Accuse" port)
122 (pass-if "file: out tell"
123 (= (seek port 0 SEEK_CUR) 8))
124 (seek port -1 SEEK_CUR)
125 (write-char #\x port)
127 (let ((iport (open-input-file filename)))
128 (pass-if "file: in tell 0"
129 (= (seek iport 0 SEEK_CUR) 0))
131 (pass-if "file: in tell 1"
132 (= (seek iport 0 SEEK_CUR) 1))
133 (unread-char #\z iport)
134 (pass-if "file: in tell 0 after unread"
135 (= (seek iport 0 SEEK_CUR) 0))
136 (pass-if "file: unread char still there"
137 (char=? (read-char iport) #\z))
138 (seek iport 7 SEEK_SET)
139 (pass-if "file: in last char"
140 (char=? (read-char iport) #\x))
142 (delete-file filename))
144 ;;; unusual characters.
145 (let* ((filename (test-file))
146 (port (open-output-file filename)))
147 (display (string #\nul (integer->char 255) (integer->char 128)
150 (let* ((port (open-input-file filename))
151 (line (read-line port)))
152 (pass-if "file: read back NUL 1"
153 (char=? (string-ref line 0) #\nul))
154 (pass-if "file: read back 255"
155 (char=? (string-ref line 1) (integer->char 255)))
156 (pass-if "file: read back 128"
157 (char=? (string-ref line 2) (integer->char 128)))
158 (pass-if "file: read back NUL 2"
159 (char=? (string-ref line 3) #\nul))
161 (eof-object? (read-char port)))
163 (delete-file filename))
165 ;;; line buffering mode.
166 (let* ((filename (test-file))
167 (port (open-file filename "wl"))
168 (test-string "one line more or less"))
169 (write-line test-string port)
170 (let* ((in-port (open-input-file filename))
171 (line (read-line in-port)))
174 (pass-if "file: line buffering"
175 (string=? line test-string)))
176 (delete-file filename))
178 ;;; ungetting characters and strings.
179 (with-input-from-string "walk on the moon\nmoon"
182 (unread-char #\a (current-input-port))
183 (pass-if "unread-char"
184 (char=? (read-char) #\a))
186 (let ((replacenoid "chicken enchilada"))
187 (unread-char #\newline (current-input-port))
188 (unread-string replacenoid (current-input-port))
189 (pass-if "unread-string"
190 (string=? (read-line) replacenoid)))
191 (pass-if "unread residue"
192 (string=? (read-line) "moon"))))
194 ;;; non-blocking mode on a port. create a pipe and set O_NONBLOCK on
195 ;;; the reading end. try to read a byte: should get EAGAIN or
196 ;;; EWOULDBLOCK error.
199 (fcntl r F_SETFL (logior (fcntl r F_GETFL) O_NONBLOCK))
200 (pass-if "non-blocking-I/O"
202 (lambda () (read-char r) #f)
204 (and (eq? key 'system-error)
205 (let ((errno (car (list-ref args 3))))
207 (= errno EWOULDBLOCK))))))))
210 ;;;; Pipe (popen) ports.
212 ;;; Run a command, and read its output.
213 (let* ((pipe (open-pipe "echo 'Howdy there, partner!'" "r"))
214 (in-string (read-all pipe)))
216 (pass-if "pipe: read"
217 (equal? in-string "Howdy there, partner!\n")))
219 ;;; Run a command, send some output to it, and see if it worked.
220 (let* ((filename (test-file))
221 (pipe (open-pipe (string-append "grep Mommy > " filename) "w")))
222 (display "Now Jimmy lives on a mushroom cloud\n" pipe)
223 (display "Mommy, why does everybody have a bomb?\n" pipe)
225 (let ((in-string (read-file filename)))
226 (pass-if "pipe: write"
227 (equal? in-string "Mommy, why does everybody have a bomb?\n")))
228 (delete-file filename))
231 ;;;; Void ports. These are so trivial we don't test them.
236 (with-test-prefix "string ports"
238 ;; Write text to a string port.
239 (let* ((string "Howdy there, partner!")
240 (in-string (call-with-output-string
242 (display string port)
244 (pass-if "display text"
245 (equal? in-string (string-append string "\n"))))
247 ;; Write an s-expression to a string port.
248 (let* ((sexpr '("more utterly random text" 1729 #(a vector) 3.1415926))
250 (call-with-input-string (call-with-output-string
254 (pass-if "write/read sexpr"
255 (equal? in-sexpr sexpr)))
257 ;; seeking and unreading from an input string.
258 (let ((text "that text didn't look random to me"))
259 (call-with-input-string text
261 (pass-if "input tell 0"
262 (= (seek p 0 SEEK_CUR) 0))
264 (pass-if "input tell 1"
265 (= (seek p 0 SEEK_CUR) 1))
267 (pass-if "input tell back to 0"
268 (= (seek p 0 SEEK_CUR) 0))
269 (pass-if "input ungetted char"
270 (char=? (read-char p) #\x))
272 (pass-if "input seek to end"
273 (= (seek p 0 SEEK_CUR)
274 (string-length text)))
276 (pass-if "input seek to beginning"
277 (= (seek p 0 SEEK_SET) 0))
278 (pass-if "input reread first char"
279 (char=? (read-char p)
280 (string-ref text 0))))))
282 ;; seeking an output string.
283 (let* ((text (string-copy "123456789"))
284 (len (string-length text))
285 (result (call-with-output-string
287 (pass-if "output tell 0"
288 (= (seek p 0 SEEK_CUR) 0))
290 (pass-if "output tell end"
291 (= (seek p 0 SEEK_CUR) len))
292 (pass-if "output seek to beginning"
293 (= (seek p 0 SEEK_SET) 0))
296 (pass-if "output seek to last char"
297 (= (seek p 0 SEEK_CUR)
299 (write-char #\b p)))))
300 (string-set! text 0 #\a)
301 (string-set! text (- len 1) #\b)
302 (pass-if "output check"
303 (string=? text result))))
305 (with-test-prefix "call-with-output-string"
307 ;; In Guile 1.6.4, closing the port resulted in a segv, check that doesn't
309 (pass-if-exception "proc closes port" exception:wrong-type-arg
310 (call-with-output-string close-port)))
314 ;;;; Soft ports. No tests implemented yet.
317 ;;;; Generic operations across all port types.
319 (let ((port-loop-temp (test-file)))
321 ;; Return a list of input ports that all return the same text.
322 ;; We map tests over this list.
323 (define (input-port-list text)
325 ;; Create a text file some of the ports will use.
326 (let ((out-port (open-output-file port-loop-temp)))
327 (display text out-port)
328 (close-port out-port))
330 (list (open-input-file port-loop-temp)
331 (open-input-pipe (string-append "cat " port-loop-temp))
332 (call-with-input-string text (lambda (x) x))
333 ;; We don't test soft ports at the moment.
336 (define port-list-names '("file" "pipe" "string"))
338 ;; Test the line counter.
339 (define (test-line-counter text second-line final-column)
340 (with-test-prefix "line counter"
341 (let ((ports (input-port-list text)))
343 (lambda (port port-name)
344 (with-test-prefix port-name
345 (pass-if "at beginning of input"
346 (= (port-line port) 0))
347 (pass-if "read first character"
348 (eqv? (read-char port) #\x))
349 (pass-if "after reading one character"
350 (= (port-line port) 0))
351 (pass-if "read first newline"
352 (eqv? (read-char port) #\newline))
353 (pass-if "after reading first newline char"
354 (= (port-line port) 1))
355 (pass-if "second line read correctly"
356 (equal? (read-line port) second-line))
357 (pass-if "read-line increments line number"
358 (= (port-line port) 2))
359 (pass-if "read-line returns EOF"
362 ((eof-object? (read-line port)) #t)
364 (else (loop (+ i 1))))))
365 (pass-if "line count is 5 at EOF"
366 (= (port-line port) 5))
367 (pass-if "column is correct at EOF"
368 (= (port-column port) final-column))))
369 ports port-list-names)
370 (for-each close-port ports)
371 (delete-file port-loop-temp))))
373 (with-test-prefix "newline"
376 "He who receives an idea from me, receives instruction\n"
377 "himself without lessening mine; as he who lights his\n"
378 "taper at mine, receives light without darkening me.\n"
379 " --- Thomas Jefferson\n")
380 "He who receives an idea from me, receives instruction"
383 (with-test-prefix "no newline"
386 "He who receives an idea from me, receives instruction\n"
387 "himself without lessening mine; as he who lights his\n"
388 "taper at mine, receives light without darkening me.\n"
389 " --- Thomas Jefferson\n"
391 "He who receives an idea from me, receives instruction"
394 ;; Test port-line and port-column for output ports
396 (define (test-output-line-counter text final-column)
397 (with-test-prefix "port-line and port-column for output ports"
398 (let ((port (open-output-string)))
399 (pass-if "at beginning of input"
400 (and (= (port-line port) 0)
401 (= (port-column port) 0)))
402 (write-char #\x port)
403 (pass-if "after writing one character"
404 (and (= (port-line port) 0)
405 (= (port-column port) 1)))
406 (write-char #\newline port)
407 (pass-if "after writing first newline char"
408 (and (= (port-line port) 1)
409 (= (port-column port) 0)))
411 (pass-if "line count is 5 at end"
412 (= (port-line port) 5))
413 (pass-if "column is correct at end"
414 (= (port-column port) final-column)))))
416 (test-output-line-counter
417 (string-append "He who receives an idea from me, receives instruction\n"
418 "himself without lessening mine; as he who lights his\n"
419 "taper at mine, receives light without darkening me.\n"
420 " --- Thomas Jefferson\n"
424 (with-test-prefix "port-column"
426 (with-test-prefix "output"
429 (let ((port (open-output-string)))
431 (= 1 (port-column port))))
434 (let ((port (open-output-string)))
436 (= 0 (port-column port))))
439 (let ((port (open-output-string)))
441 (= 1 (port-column port))))
443 (pass-if "\\x08 backspace"
444 (let ((port (open-output-string)))
445 (display "\x08" port)
446 (= 0 (port-column port))))
448 (pass-if "x\\x08 backspace"
449 (let ((port (open-output-string)))
450 (display "x\x08" port)
451 (= 0 (port-column port))))
454 (let ((port (open-output-string)))
456 (= 0 (port-column port))))
459 (let ((port (open-output-string)))
461 (= 0 (port-column port))))
464 (let ((port (open-output-string)))
466 (= 0 (port-column port))))
469 (let ((port (open-output-string)))
471 (= 0 (port-column port))))
474 (let ((port (open-output-string)))
476 (= 8 (port-column port))))
479 (let ((port (open-output-string)))
481 (= 8 (port-column port)))))
483 (with-test-prefix "input"
486 (let ((port (open-input-string "x")))
487 (while (not (eof-object? (read-char port))))
488 (= 1 (port-column port))))
491 (let ((port (open-input-string "\a")))
492 (while (not (eof-object? (read-char port))))
493 (= 0 (port-column port))))
496 (let ((port (open-input-string "x\a")))
497 (while (not (eof-object? (read-char port))))
498 (= 1 (port-column port))))
500 (pass-if "\\x08 backspace"
501 (let ((port (open-input-string "\x08")))
502 (while (not (eof-object? (read-char port))))
503 (= 0 (port-column port))))
505 (pass-if "x\\x08 backspace"
506 (let ((port (open-input-string "x\x08")))
507 (while (not (eof-object? (read-char port))))
508 (= 0 (port-column port))))
511 (let ((port (open-input-string "\n")))
512 (while (not (eof-object? (read-char port))))
513 (= 0 (port-column port))))
516 (let ((port (open-input-string "x\n")))
517 (while (not (eof-object? (read-char port))))
518 (= 0 (port-column port))))
521 (let ((port (open-input-string "\r")))
522 (while (not (eof-object? (read-char port))))
523 (= 0 (port-column port))))
526 (let ((port (open-input-string "x\r")))
527 (while (not (eof-object? (read-char port))))
528 (= 0 (port-column port))))
531 (let ((port (open-input-string "\t")))
532 (while (not (eof-object? (read-char port))))
533 (= 8 (port-column port))))
536 (let ((port (open-input-string "x\t")))
537 (while (not (eof-object? (read-char port))))
538 (= 8 (port-column port))))))
540 (with-test-prefix "port-line"
542 ;; in guile 1.8.1 and earlier port-line was truncated to an int, whereas
543 ;; scm_t_port actually holds a long; this restricted the range on 64-bit
545 (pass-if "set most-positive-fixnum/2"
546 (let ((n (quotient most-positive-fixnum 2))
547 (port (open-output-string)))
548 (set-port-line! port n)
549 (eqv? n (port-line port)))))
555 (with-test-prefix "port-for-each"
557 ;; In guile 1.8.0 through 1.8.2, port-for-each could pass a freed cell to
558 ;; its iterator func if a port was inaccessible in the last gc mark but
559 ;; the lazy sweeping has not yet reached it to remove it from the port
560 ;; table (scm_i_port_table). Provoking those gc conditions is a little
561 ;; tricky, but the following code made it happen in 1.8.2.
562 (pass-if "passing freed cell"
564 ;; clear out the heap
566 ;; allocate cells so the opened ports aren't at the start of the heap
568 (open-input-file "/dev/null")
570 (open-input-file "/dev/null")
571 ;; this gc leaves the above ports unmarked, ie. inaccessible
573 ;; but they're still in the port table, so this sees them
574 (port-for-each (lambda (port)
575 (set! lst (cons port lst))))
576 ;; this forces completion of the sweeping
578 ;; and (if the bug is present) the cells accumulated in LST are now
579 ;; freed cells, which give #f from `port?'
580 (not (memq #f (map port? lst))))))
584 (pass-if "fdes->ports finds port"
585 (let ((port (open-file (test-file) "w")))
587 (not (not (memq port (fdes->ports (port->fdes port))))))))
593 (with-test-prefix "seek"
595 (with-test-prefix "file port"
598 (call-with-output-file (test-file)
600 (display "abcde" port)))
601 (let ((port (open-file (test-file) "r")))
603 (seek port 2 SEEK_CUR)
604 (eqv? #\d (read-char port))))
607 (call-with-output-file (test-file)
609 (display "abcde" port)))
610 (let ((port (open-file (test-file) "r")))
612 (seek port 3 SEEK_SET)
613 (eqv? #\d (read-char port))))
616 (call-with-output-file (test-file)
618 (display "abcde" port)))
619 (let ((port (open-file (test-file) "r")))
621 (seek port -2 SEEK_END)
622 (eqv? #\d (read-char port))))))
628 (with-test-prefix "truncate-file"
630 (pass-if-exception "flonum file" exception:wrong-type-arg
631 (truncate-file 1.0 123))
633 (pass-if-exception "frac file" exception:wrong-type-arg
634 (truncate-file 7/3 123))
636 (with-test-prefix "filename"
638 (pass-if-exception "flonum length" exception:wrong-type-arg
639 (call-with-output-file (test-file)
641 (display "hello" port)))
642 (truncate-file (test-file) 1.0))
645 (call-with-output-file (test-file)
647 (display "hello" port)))
648 (truncate-file (test-file) 1)
649 (eqv? 1 (stat:size (stat (test-file)))))
651 (pass-if-exception "shorten to current pos" exception:miscellaneous-error
652 (call-with-output-file (test-file)
654 (display "hello" port)))
655 (truncate-file (test-file))))
657 (with-test-prefix "file descriptor"
660 (call-with-output-file (test-file)
662 (display "hello" port)))
663 (let ((fd (open-fdes (test-file) O_RDWR)))
666 (eqv? 1 (stat:size (stat (test-file)))))
668 (pass-if "shorten to current pos"
669 (call-with-output-file (test-file)
671 (display "hello" port)))
672 (let ((fd (open-fdes (test-file) O_RDWR)))
676 (eqv? 1 (stat:size (stat (test-file))))))
678 (with-test-prefix "file port"
681 (call-with-output-file (test-file)
683 (display "hello" port)))
684 (let ((port (open-file (test-file) "r+")))
685 (truncate-file port 1))
686 (eqv? 1 (stat:size (stat (test-file)))))
688 (pass-if "shorten to current pos"
689 (call-with-output-file (test-file)
691 (display "hello" port)))
692 (let ((port (open-file (test-file) "r+")))
694 (truncate-file port))
695 (eqv? 1 (stat:size (stat (test-file)))))))
698 ;;;; testing read-delimited and friends
700 (with-test-prefix "read-delimited!"
701 (let ((c (make-string 20 #\!)))
702 (call-with-input-string
706 (read-delimited! "\n" c port 'concat)
707 (pass-if "read-delimited! reads a first line"
708 (string=? c "defdef\n!!!!!!!!!!!!!"))
710 (read-delimited! "\n" c port 'concat 3)
711 (pass-if "read-delimited! reads a first line"
712 (string=? c "defghighi\n!!!!!!!!!!"))))))
717 (call-with-input-string
720 (pass-if "char-ready? returns true on string port"
721 (char-ready? port))))
723 ;;; This segfaults on some versions of Guile. We really should run
724 ;;; the tests in a subprocess...
726 (call-with-input-string
729 (with-input-from-port
732 (pass-if "char-ready? returns true on string port as default port"
736 ;;;; Close current-input-port, and make sure everyone can handle it.
738 (with-test-prefix "closing current-input-port"
739 (for-each (lambda (procedure name)
740 (with-input-from-port
741 (call-with-input-string "foo" (lambda (p) p))
743 (close-port (current-input-port))
744 (pass-if-exception name
745 exception:wrong-type-arg
747 (list read read-char read-line)
748 '("read" "read-char" "read-line")))
750 (delete-file (test-file))