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, 2009 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 ;; Make sure we are set up for 8-bit Latin-1 data.
37 (fluid-set! %default-port-encoding "ISO-8859-1")
39 (set-port-encoding! p (fluid-ref %default-port-encoding)))
40 (list (current-input-port) (current-output-port)
41 (current-error-port)))
43 ;;; Read from PORT until EOF, and return the result as a string.
44 (define (read-all port)
45 (let loop ((chars '()))
46 (let ((char (read-char port)))
47 (if (eof-object? char)
48 (list->string (reverse! chars))
49 (loop (cons char chars))))))
51 (define (read-file filename)
52 (let* ((port (open-input-file filename))
53 (string (read-all port)))
58 ;;;; Normal file ports.
60 ;;; Write out an s-expression, and read it back.
61 (let ((string '("From fairest creatures we desire increase,"
62 "That thereby beauty's rose might never die,"))
63 (filename (test-file)))
64 (let ((port (open-output-file filename)))
67 (let ((port (open-input-file filename)))
68 (let ((in-string (read port)))
69 (pass-if "file: write and read back list of strings"
70 (equal? string in-string)))
72 (delete-file filename))
74 ;;; Write out a string, and read it back a character at a time.
75 (let ((string "This is a test string\nwith no newline at the end")
76 (filename (test-file)))
77 (let ((port (open-output-file filename)))
80 (let ((in-string (read-file filename)))
81 (pass-if "file: write and read back characters"
82 (equal? string in-string)))
83 (delete-file filename))
85 ;;; Buffered input/output port with seeking.
86 (let* ((filename (test-file))
87 (port (open-file filename "w+")))
88 (display "J'Accuse" port)
89 (seek port -1 SEEK_CUR)
90 (pass-if "file: r/w 1"
91 (char=? (read-char port) #\e))
92 (pass-if "file: r/w 2"
93 (eof-object? (read-char port)))
94 (seek port -1 SEEK_CUR)
96 (seek port 7 SEEK_SET)
97 (pass-if "file: r/w 3"
98 (char=? (read-char port) #\x))
99 (seek port -2 SEEK_END)
100 (pass-if "file: r/w 4"
101 (char=? (read-char port) #\s))
103 (delete-file filename))
105 ;;; Unbuffered input/output port with seeking.
106 (let* ((filename (test-file))
107 (port (open-file filename "w+0")))
108 (display "J'Accuse" port)
109 (seek port -1 SEEK_CUR)
110 (pass-if "file: ub r/w 1"
111 (char=? (read-char port) #\e))
112 (pass-if "file: ub r/w 2"
113 (eof-object? (read-char port)))
114 (seek port -1 SEEK_CUR)
115 (write-char #\x port)
116 (seek port 7 SEEK_SET)
117 (pass-if "file: ub r/w 3"
118 (char=? (read-char port) #\x))
119 (seek port -2 SEEK_END)
120 (pass-if "file: ub r/w 4"
121 (char=? (read-char port) #\s))
123 (delete-file filename))
125 ;;; Buffered output-only and input-only ports with seeking.
126 (let* ((filename (test-file))
127 (port (open-output-file filename)))
128 (display "J'Accuse" port)
129 (pass-if "file: out tell"
130 (= (seek port 0 SEEK_CUR) 8))
131 (seek port -1 SEEK_CUR)
132 (write-char #\x port)
134 (let ((iport (open-input-file filename)))
135 (pass-if "file: in tell 0"
136 (= (seek iport 0 SEEK_CUR) 0))
138 (pass-if "file: in tell 1"
139 (= (seek iport 0 SEEK_CUR) 1))
140 (unread-char #\z iport)
141 (pass-if "file: in tell 0 after unread"
142 (= (seek iport 0 SEEK_CUR) 0))
143 (pass-if "file: unread char still there"
144 (char=? (read-char iport) #\z))
145 (seek iport 7 SEEK_SET)
146 (pass-if "file: in last char"
147 (char=? (read-char iport) #\x))
149 (delete-file filename))
151 ;;; unusual characters.
152 (let* ((filename (test-file))
153 (port (open-output-file filename)))
154 (display (string #\nul (integer->char 255) (integer->char 128)
157 (let* ((port (open-input-file filename))
158 (line (read-line port)))
159 (pass-if "file: read back NUL 1"
160 (char=? (string-ref line 0) #\nul))
161 (pass-if "file: read back 255"
162 (char=? (string-ref line 1) (integer->char 255)))
163 (pass-if "file: read back 128"
164 (char=? (string-ref line 2) (integer->char 128)))
165 (pass-if "file: read back NUL 2"
166 (char=? (string-ref line 3) #\nul))
168 (eof-object? (read-char port)))
170 (delete-file filename))
172 ;;; line buffering mode.
173 (let* ((filename (test-file))
174 (port (open-file filename "wl"))
175 (test-string "one line more or less"))
176 (write-line test-string port)
177 (let* ((in-port (open-input-file filename))
178 (line (read-line in-port)))
181 (pass-if "file: line buffering"
182 (string=? line test-string)))
183 (delete-file filename))
185 ;;; ungetting characters and strings.
186 (with-input-from-string "walk on the moon\nmoon"
189 (unread-char #\a (current-input-port))
190 (pass-if "unread-char"
191 (char=? (read-char) #\a))
193 (let ((replacenoid "chicken enchilada"))
194 (unread-char #\newline (current-input-port))
195 (unread-string replacenoid (current-input-port))
196 (pass-if "unread-string"
197 (string=? (read-line) replacenoid)))
198 (pass-if "unread residue"
199 (string=? (read-line) "moon"))))
201 ;;; non-blocking mode on a port. create a pipe and set O_NONBLOCK on
202 ;;; the reading end. try to read a byte: should get EAGAIN or
203 ;;; EWOULDBLOCK error.
206 (fcntl r F_SETFL (logior (fcntl r F_GETFL) O_NONBLOCK))
207 (pass-if "non-blocking-I/O"
209 (lambda () (read-char r) #f)
211 (and (eq? key 'system-error)
212 (let ((errno (car (list-ref args 3))))
214 (= errno EWOULDBLOCK))))))))
217 ;;;; Pipe (popen) ports.
219 ;;; Run a command, and read its output.
220 (let* ((pipe (open-pipe "echo 'Howdy there, partner!'" "r"))
221 (in-string (read-all pipe)))
223 (pass-if "pipe: read"
224 (equal? in-string "Howdy there, partner!\n")))
226 ;;; Run a command, send some output to it, and see if it worked.
227 (let* ((filename (test-file))
228 (pipe (open-pipe (string-append "grep Mommy > " filename) "w")))
229 (display "Now Jimmy lives on a mushroom cloud\n" pipe)
230 (display "Mommy, why does everybody have a bomb?\n" pipe)
232 (let ((in-string (read-file filename)))
233 (pass-if "pipe: write"
234 (equal? in-string "Mommy, why does everybody have a bomb?\n")))
235 (delete-file filename))
238 ;;;; Void ports. These are so trivial we don't test them.
243 (with-test-prefix "string ports"
245 ;; Write text to a string port.
246 (let* ((string "Howdy there, partner!")
247 (in-string (call-with-output-string
249 (display string port)
251 (pass-if "display text"
252 (equal? in-string (string-append string "\n"))))
254 ;; Write an s-expression to a string port.
255 (let* ((sexpr '("more utterly random text" 1729 #(a vector) 3.1415926))
257 (call-with-input-string (call-with-output-string
261 (pass-if "write/read sexpr"
262 (equal? in-sexpr sexpr)))
264 ;; seeking and unreading from an input string.
265 (let ((text "that text didn't look random to me"))
266 (call-with-input-string text
268 (pass-if "input tell 0"
269 (= (seek p 0 SEEK_CUR) 0))
271 (pass-if "input tell 1"
272 (= (seek p 0 SEEK_CUR) 1))
274 (pass-if "input tell back to 0"
275 (= (seek p 0 SEEK_CUR) 0))
276 (pass-if "input ungetted char"
277 (char=? (read-char p) #\x))
279 (pass-if "input seek to end"
280 (= (seek p 0 SEEK_CUR)
281 (string-length text)))
283 (pass-if "input seek to beginning"
284 (= (seek p 0 SEEK_SET) 0))
285 (pass-if "input reread first char"
286 (char=? (read-char p)
287 (string-ref text 0))))))
289 ;; seeking an output string.
290 (let* ((text (string-copy "123456789"))
291 (len (string-length text))
292 (result (call-with-output-string
294 (pass-if "output tell 0"
295 (= (seek p 0 SEEK_CUR) 0))
297 (pass-if "output tell end"
298 (= (seek p 0 SEEK_CUR) len))
299 (pass-if "output seek to beginning"
300 (= (seek p 0 SEEK_SET) 0))
303 (pass-if "output seek to last char"
304 (= (seek p 0 SEEK_CUR)
306 (write-char #\b p)))))
307 (string-set! text 0 #\a)
308 (string-set! text (- len 1) #\b)
309 (pass-if "output check"
310 (string=? text result))))
312 (with-test-prefix "call-with-output-string"
314 ;; In Guile 1.6.4, closing the port resulted in a segv, check that doesn't
316 (pass-if-exception "proc closes port" exception:wrong-type-arg
317 (call-with-output-string close-port)))
321 ;;;; Soft ports. No tests implemented yet.
324 ;;;; Generic operations across all port types.
326 (let ((port-loop-temp (test-file)))
328 ;; Return a list of input ports that all return the same text.
329 ;; We map tests over this list.
330 (define (input-port-list text)
332 ;; Create a text file some of the ports will use.
333 (let ((out-port (open-output-file port-loop-temp)))
334 (display text out-port)
335 (close-port out-port))
337 (list (open-input-file port-loop-temp)
338 (open-input-pipe (string-append "cat " port-loop-temp))
339 (call-with-input-string text (lambda (x) x))
340 ;; We don't test soft ports at the moment.
343 (define port-list-names '("file" "pipe" "string"))
345 ;; Test the line counter.
346 (define (test-line-counter text second-line final-column)
347 (with-test-prefix "line counter"
348 (let ((ports (input-port-list text)))
350 (lambda (port port-name)
351 (with-test-prefix port-name
352 (pass-if "at beginning of input"
353 (= (port-line port) 0))
354 (pass-if "read first character"
355 (eqv? (read-char port) #\x))
356 (pass-if "after reading one character"
357 (= (port-line port) 0))
358 (pass-if "read first newline"
359 (eqv? (read-char port) #\newline))
360 (pass-if "after reading first newline char"
361 (= (port-line port) 1))
362 (pass-if "second line read correctly"
363 (equal? (read-line port) second-line))
364 (pass-if "read-line increments line number"
365 (= (port-line port) 2))
366 (pass-if "read-line returns EOF"
369 ((eof-object? (read-line port)) #t)
371 (else (loop (+ i 1))))))
372 (pass-if "line count is 5 at EOF"
373 (= (port-line port) 5))
374 (pass-if "column is correct at EOF"
375 (= (port-column port) final-column))))
376 ports port-list-names)
377 (for-each close-port ports)
378 (delete-file port-loop-temp))))
380 (with-test-prefix "newline"
383 "He who receives an idea from me, receives instruction\n"
384 "himself without lessening mine; as he who lights his\n"
385 "taper at mine, receives light without darkening me.\n"
386 " --- Thomas Jefferson\n")
387 "He who receives an idea from me, receives instruction"
390 (with-test-prefix "no newline"
393 "He who receives an idea from me, receives instruction\n"
394 "himself without lessening mine; as he who lights his\n"
395 "taper at mine, receives light without darkening me.\n"
396 " --- Thomas Jefferson\n"
398 "He who receives an idea from me, receives instruction"
401 ;; Test port-line and port-column for output ports
403 (define (test-output-line-counter text final-column)
404 (with-test-prefix "port-line and port-column for output ports"
405 (let ((port (open-output-string)))
406 (pass-if "at beginning of input"
407 (and (= (port-line port) 0)
408 (= (port-column port) 0)))
409 (write-char #\x port)
410 (pass-if "after writing one character"
411 (and (= (port-line port) 0)
412 (= (port-column port) 1)))
413 (write-char #\newline port)
414 (pass-if "after writing first newline char"
415 (and (= (port-line port) 1)
416 (= (port-column port) 0)))
418 (pass-if "line count is 5 at end"
419 (= (port-line port) 5))
420 (pass-if "column is correct at end"
421 (= (port-column port) final-column)))))
423 (test-output-line-counter
424 (string-append "He who receives an idea from me, receives instruction\n"
425 "himself without lessening mine; as he who lights his\n"
426 "taper at mine, receives light without darkening me.\n"
427 " --- Thomas Jefferson\n"
431 (with-test-prefix "port-column"
433 (with-test-prefix "output"
436 (let ((port (open-output-string)))
438 (= 1 (port-column port))))
441 (let ((port (open-output-string)))
443 (= 0 (port-column port))))
446 (let ((port (open-output-string)))
448 (= 1 (port-column port))))
450 (pass-if "\\x08 backspace"
451 (let ((port (open-output-string)))
452 (display "\x08" port)
453 (= 0 (port-column port))))
455 (pass-if "x\\x08 backspace"
456 (let ((port (open-output-string)))
457 (display "x\x08" port)
458 (= 0 (port-column port))))
461 (let ((port (open-output-string)))
463 (= 0 (port-column port))))
466 (let ((port (open-output-string)))
468 (= 0 (port-column port))))
471 (let ((port (open-output-string)))
473 (= 0 (port-column port))))
476 (let ((port (open-output-string)))
478 (= 0 (port-column port))))
481 (let ((port (open-output-string)))
483 (= 8 (port-column port))))
486 (let ((port (open-output-string)))
488 (= 8 (port-column port)))))
490 (with-test-prefix "input"
493 (let ((port (open-input-string "x")))
494 (while (not (eof-object? (read-char port))))
495 (= 1 (port-column port))))
498 (let ((port (open-input-string "\a")))
499 (while (not (eof-object? (read-char port))))
500 (= 0 (port-column port))))
503 (let ((port (open-input-string "x\a")))
504 (while (not (eof-object? (read-char port))))
505 (= 1 (port-column port))))
507 (pass-if "\\x08 backspace"
508 (let ((port (open-input-string "\x08")))
509 (while (not (eof-object? (read-char port))))
510 (= 0 (port-column port))))
512 (pass-if "x\\x08 backspace"
513 (let ((port (open-input-string "x\x08")))
514 (while (not (eof-object? (read-char port))))
515 (= 0 (port-column port))))
518 (let ((port (open-input-string "\n")))
519 (while (not (eof-object? (read-char port))))
520 (= 0 (port-column port))))
523 (let ((port (open-input-string "x\n")))
524 (while (not (eof-object? (read-char port))))
525 (= 0 (port-column port))))
528 (let ((port (open-input-string "\r")))
529 (while (not (eof-object? (read-char port))))
530 (= 0 (port-column port))))
533 (let ((port (open-input-string "x\r")))
534 (while (not (eof-object? (read-char port))))
535 (= 0 (port-column port))))
538 (let ((port (open-input-string "\t")))
539 (while (not (eof-object? (read-char port))))
540 (= 8 (port-column port))))
543 (let ((port (open-input-string "x\t")))
544 (while (not (eof-object? (read-char port))))
545 (= 8 (port-column port))))))
547 (with-test-prefix "port-line"
549 ;; in guile 1.8.1 and earlier port-line was truncated to an int, whereas
550 ;; scm_t_port actually holds a long; this restricted the range on 64-bit
552 (pass-if "set most-positive-fixnum/2"
553 (let ((n (quotient most-positive-fixnum 2))
554 (port (open-output-string)))
555 (set-port-line! port n)
556 (eqv? n (port-line port)))))
562 (with-test-prefix "port-for-each"
564 ;; In guile 1.8.0 through 1.8.2, port-for-each could pass a freed cell to
565 ;; its iterator func if a port was inaccessible in the last gc mark but
566 ;; the lazy sweeping has not yet reached it to remove it from the port
567 ;; table (scm_i_port_table). Provoking those gc conditions is a little
568 ;; tricky, but the following code made it happen in 1.8.2.
569 (pass-if "passing freed cell"
571 ;; clear out the heap
573 ;; allocate cells so the opened ports aren't at the start of the heap
575 (open-input-file "/dev/null")
577 (open-input-file "/dev/null")
578 ;; this gc leaves the above ports unmarked, ie. inaccessible
580 ;; but they're still in the port table, so this sees them
581 (port-for-each (lambda (port)
582 (set! lst (cons port lst))))
583 ;; this forces completion of the sweeping
585 ;; and (if the bug is present) the cells accumulated in LST are now
586 ;; freed cells, which give #f from `port?'
587 (not (memq #f (map port? lst))))))
591 (pass-if "fdes->ports finds port"
592 (let ((port (open-file (test-file) "w")))
594 (not (not (memq port (fdes->ports (port->fdes port))))))))
600 (with-test-prefix "seek"
602 (with-test-prefix "file port"
605 (call-with-output-file (test-file)
607 (display "abcde" port)))
608 (let ((port (open-file (test-file) "r")))
610 (seek port 2 SEEK_CUR)
611 (eqv? #\d (read-char port))))
614 (call-with-output-file (test-file)
616 (display "abcde" port)))
617 (let ((port (open-file (test-file) "r")))
619 (seek port 3 SEEK_SET)
620 (eqv? #\d (read-char port))))
623 (call-with-output-file (test-file)
625 (display "abcde" port)))
626 (let ((port (open-file (test-file) "r")))
628 (seek port -2 SEEK_END)
629 (eqv? #\d (read-char port))))))
635 (with-test-prefix "truncate-file"
637 (pass-if-exception "flonum file" exception:wrong-type-arg
638 (truncate-file 1.0 123))
640 (pass-if-exception "frac file" exception:wrong-type-arg
641 (truncate-file 7/3 123))
643 (with-test-prefix "filename"
645 (pass-if-exception "flonum length" exception:wrong-type-arg
646 (call-with-output-file (test-file)
648 (display "hello" port)))
649 (truncate-file (test-file) 1.0))
652 (call-with-output-file (test-file)
654 (display "hello" port)))
655 (truncate-file (test-file) 1)
656 (eqv? 1 (stat:size (stat (test-file)))))
658 (pass-if-exception "shorten to current pos" exception:miscellaneous-error
659 (call-with-output-file (test-file)
661 (display "hello" port)))
662 (truncate-file (test-file))))
664 (with-test-prefix "file descriptor"
667 (call-with-output-file (test-file)
669 (display "hello" port)))
670 (let ((fd (open-fdes (test-file) O_RDWR)))
673 (eqv? 1 (stat:size (stat (test-file)))))
675 (pass-if "shorten to current pos"
676 (call-with-output-file (test-file)
678 (display "hello" port)))
679 (let ((fd (open-fdes (test-file) O_RDWR)))
683 (eqv? 1 (stat:size (stat (test-file))))))
685 (with-test-prefix "file port"
688 (call-with-output-file (test-file)
690 (display "hello" port)))
691 (let ((port (open-file (test-file) "r+")))
692 (truncate-file port 1))
693 (eqv? 1 (stat:size (stat (test-file)))))
695 (pass-if "shorten to current pos"
696 (call-with-output-file (test-file)
698 (display "hello" port)))
699 (let ((port (open-file (test-file) "r+")))
701 (truncate-file port))
702 (eqv? 1 (stat:size (stat (test-file)))))))
705 ;;;; testing read-delimited and friends
707 (with-test-prefix "read-delimited!"
708 (let ((c (make-string 20 #\!)))
709 (call-with-input-string
713 (read-delimited! "\n" c port 'concat)
714 (pass-if "read-delimited! reads a first line"
715 (string=? c "defdef\n!!!!!!!!!!!!!"))
717 (read-delimited! "\n" c port 'concat 3)
718 (pass-if "read-delimited! reads a first line"
719 (string=? c "defghighi\n!!!!!!!!!!"))))))
724 (call-with-input-string
727 (pass-if "char-ready? returns true on string port"
728 (char-ready? port))))
730 ;;; This segfaults on some versions of Guile. We really should run
731 ;;; the tests in a subprocess...
733 (call-with-input-string
736 (with-input-from-port
739 (pass-if "char-ready? returns true on string port as default port"
743 ;;;; Close current-input-port, and make sure everyone can handle it.
745 (with-test-prefix "closing current-input-port"
746 (for-each (lambda (procedure name)
747 (with-input-from-port
748 (call-with-input-string "foo" (lambda (p) p))
750 (close-port (current-input-port))
751 (pass-if-exception name
752 exception:wrong-type-arg
754 (list read read-char read-line)
755 '("read" "read-char" "read-line")))
757 (delete-file (test-file))