-;;;; r6rs-ports.test --- Exercise the R6RS I/O port API.
+;;;; r6rs-ports.test --- R6RS I/O port tests. -*- coding: utf-8; -*-
;;;;
-;;;; Copyright (C) 2009 Free Software Foundation, Inc.
-;;;; Ludovic Courtès
+;;;; Copyright (C) 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+;;;; Ludovic Courtès
;;;;
;;;; This library is free software; you can redistribute it and/or
;;;; modify it under the terms of the GNU Lesser General Public
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
(define-module (test-io-ports)
- :use-module (test-suite lib)
- :use-module (srfi srfi-1)
- :use-module (srfi srfi-11)
- :use-module (rnrs io ports)
- :use-module (rnrs bytevector))
+ #:use-module (test-suite lib)
+ #:use-module (test-suite guile-test)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-11)
+ #:use-module (rnrs io ports)
+ #:use-module (rnrs io simple)
+ #:use-module (rnrs exceptions)
+ #:use-module (rnrs bytevectors))
+
+(define-syntax pass-if-condition
+ (syntax-rules ()
+ ((_ name predicate body0 body ...)
+ (let ((cookie (list 'cookie)))
+ (pass-if name
+ (eq? cookie (guard (c ((predicate c) cookie))
+ body0 body ...)))))))
+
+(define (test-file)
+ (data-file-name "ports-test.tmp"))
+
+;; A input/output port that swallows all output, and produces just
+;; spaces on input. Reading and writing beyond `failure-position'
+;; produces `system-error' exceptions. Used for testing exception
+;; behavior.
+(define* (make-failing-port #:optional (failure-position 0))
+ (define (maybe-fail index errno)
+ (if (> index failure-position)
+ (scm-error 'system-error
+ 'failing-port
+ "I/O beyond failure position" '()
+ (list errno))))
+ (let ((read-index 0)
+ (write-index 0))
+ (define (write-char chr)
+ (set! write-index (+ 1 write-index))
+ (maybe-fail write-index ENOSPC))
+ (make-soft-port
+ (vector write-char
+ (lambda (str) ;; write-string
+ (for-each write-char (string->list str)))
+ (lambda () #t) ;; flush-output
+ (lambda () ;; read-char
+ (set! read-index (+ read-index 1))
+ (maybe-fail read-index EIO)
+ #\space)
+ (lambda () #t)) ;; close-port
+ "rw")))
-;;; All these tests assume Guile 1.8's port system, where characters are
-;;; treated as octets.
+(define (call-with-bytevector-output-port/transcoded transcoder receiver)
+ (call-with-bytevector-output-port
+ (lambda (bv-port)
+ (call-with-port (transcoded-port bv-port transcoder)
+ receiver))))
\f
(with-test-prefix "7.2.5 End-of-File Object"
(pass-if "eof-object"
(and (eqv? (eof-object) (eof-object))
- (eq? (eof-object) (eof-object)))))
+ (eq? (eof-object) (eof-object))))
+
+ (pass-if "port-eof?"
+ (port-eof? (open-input-string ""))))
\f
(with-test-prefix "7.2.8 Binary Input"
(pass-if "lookahead-u8"
(let ((port (open-input-string "A")))
(and (= (char->integer #\A) (lookahead-u8 port))
- (not (eof-object? port))
+ (= (char->integer #\A) (lookahead-u8 port))
(= (char->integer #\A) (get-u8 port))
(eof-object? (get-u8 port)))))
+ (pass-if "lookahead-u8 non-ASCII"
+ (let ((port (with-fluids ((%default-port-encoding "UTF-8"))
+ (open-input-string "λ"))))
+ (and (= 206 (lookahead-u8 port))
+ (= 206 (lookahead-u8 port))
+ (= 206 (get-u8 port))
+ (= 187 (lookahead-u8 port))
+ (= 187 (lookahead-u8 port))
+ (= 187 (get-u8 port))
+ (eof-object? (lookahead-u8 port))
+ (eof-object? (get-u8 port)))))
+
+ (pass-if "lookahead-u8: result is unsigned"
+ ;; Bug #31081.
+ (let ((port (open-bytevector-input-port #vu8(255))))
+ (= (lookahead-u8 port) 255)))
+
(pass-if "get-bytevector-n [short]"
(let* ((port (open-input-string "GNU Guile"))
(bv (get-bytevector-n port 4)))
(put-u8 port 77)
(equal? (get-u8 port) 77)))
+ ;; Note: The `put-bytevector' tests below require a Latin-1 locale so
+ ;; that the `scm_from_locale_stringn' call in `sf_write' will let all
+ ;; the bytes through, unmodified. This is hacky, but we can't use
+ ;; "custom binary output ports" here because they're only tested
+ ;; later.
+
(pass-if "put-bytevector [2 args]"
- (let ((port (make-soft-output-port))
- (bv (make-bytevector 256)))
- (put-bytevector port bv)
- (equal? (bytevector->u8-list bv)
- (bytevector->u8-list
- (get-bytevector-n port (bytevector-length bv))))))
+ (with-latin1-locale
+ (let ((port (make-soft-output-port))
+ (bv (make-bytevector 256)))
+ (put-bytevector port bv)
+ (equal? (bytevector->u8-list bv)
+ (bytevector->u8-list
+ (get-bytevector-n port (bytevector-length bv)))))))
(pass-if "put-bytevector [3 args]"
- (let ((port (make-soft-output-port))
- (bv (make-bytevector 256))
- (start 10))
- (put-bytevector port bv start)
- (equal? (drop (bytevector->u8-list bv) start)
- (bytevector->u8-list
- (get-bytevector-n port (- (bytevector-length bv) start))))))
+ (with-latin1-locale
+ (let ((port (make-soft-output-port))
+ (bv (make-bytevector 256))
+ (start 10))
+ (put-bytevector port bv start)
+ (equal? (drop (bytevector->u8-list bv) start)
+ (bytevector->u8-list
+ (get-bytevector-n port (- (bytevector-length bv) start)))))))
(pass-if "put-bytevector [4 args]"
- (let ((port (make-soft-output-port))
- (bv (make-bytevector 256))
- (start 10)
- (count 77))
- (put-bytevector port bv start count)
- (equal? (take (drop (bytevector->u8-list bv) start) count)
- (bytevector->u8-list
- (get-bytevector-n port count)))))
+ (with-latin1-locale
+ (let ((port (make-soft-output-port))
+ (bv (make-bytevector 256))
+ (start 10)
+ (count 77))
+ (put-bytevector port bv start count)
+ (equal? (take (drop (bytevector->u8-list bv) start) count)
+ (bytevector->u8-list
+ (get-bytevector-n port count))))))
(pass-if-exception "put-bytevector with closed port"
exception:wrong-type-arg
(port (%make-void-port "w")))
(close-port port)
- (put-bytevector port bv))))
+ (put-bytevector port bv)))
+
+ (pass-if "put-bytevector with UTF-16 string port"
+ (let* ((str "hello, world")
+ (bv (string->utf16 str)))
+ (equal? str
+ (with-fluids ((%default-port-encoding "UTF-16BE"))
+ (call-with-output-string
+ (lambda (port)
+ (put-bytevector port bv)))))))
+
+ (pass-if "put-bytevector with wrong-encoding string port"
+ (let* ((str "hello, world")
+ (bv (string->utf16 str)))
+ (catch 'decoding-error
+ (lambda ()
+ (with-fluids ((%default-port-encoding "UTF-32")
+ (%default-port-conversion-strategy 'error))
+ (call-with-output-string
+ (lambda (port)
+ (put-bytevector port bv)))
+ #f)) ; fail if we reach this point
+ (lambda (key subr message errno port)
+ (string? (strerror errno)))))))
\f
+(define (test-input-file-opener open filename)
+ (let ((contents (string->utf8 "GNU λ")))
+ ;; Create file
+ (call-with-output-file filename
+ (lambda (port) (put-bytevector port contents)))
+
+ (pass-if "opens binary input port with correct contents"
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ (call-with-port (open-file-input-port filename)
+ (lambda (port)
+ (and (binary-port? port)
+ (input-port? port)
+ (bytevector=? contents (get-bytevector-all port))))))))
+
+ (delete-file filename))
+
(with-test-prefix "7.2.7 Input Ports"
+ (with-test-prefix "open-file-input-port"
+ (test-input-file-opener open-file-input-port (test-file)))
+
;; This section appears here so that it can use the binary input
;; primitives.
(equal? (read-to-string port) str)))
+ (pass-if "bytevector-input-port is binary"
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ (binary-port? (open-bytevector-input-port #vu8(1 2 3)))))
+
(pass-if-exception "bytevector-input-port is read-only"
exception:wrong-type-arg
(u8-list->bytevector
(map char->integer (string->list "Port!")))))))
+ (pass-if "bytevector input port can seek to very end"
+ (let ((empty (open-bytevector-input-port '#vu8()))
+ (not-empty (open-bytevector-input-port '#vu8(1 2 3))))
+ (and (begin (set-port-position! empty (port-position empty))
+ (= 0 (port-position empty)))
+ (begin (get-bytevector-n not-empty 3)
+ (set-port-position! not-empty (port-position not-empty))
+ (= 3 (port-position not-empty))))))
+
(pass-if-exception "make-custom-binary-input-port [wrong-num-args]"
exception:wrong-num-args
(port (make-custom-binary-input-port "the port" read!
#f #f #f)))
- (bytevector=? (get-bytevector-all port) source)))
+ (and (binary-port? port)
+ (input-port? port)
+ (bytevector=? (get-bytevector-all port) source))))
(pass-if "custom binary input port does not support `port-position'"
(let* ((str "Hello Port!")
(close-port port)
(gc) ; Test for marking a closed port.
- closed?)))
+ closed?))
+
+ (pass-if "standard-input-port is binary"
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ (binary-port? (standard-input-port)))))
\f
+(define (test-output-file-opener open filename)
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ (pass-if "opens binary output port"
+ (call-with-port (open filename)
+ (lambda (port)
+ (put-bytevector port '#vu8(1 2 3))
+ (and (binary-port? port)
+ (output-port? port))))))
+
+ (pass-if-condition "exception: already-exists"
+ i/o-file-already-exists-error?
+ (open filename))
+
+ (pass-if "no-fail no-truncate"
+ (and
+ (call-with-port (open filename (file-options no-fail no-truncate))
+ (lambda (port)
+ (= 0 (port-position port))))
+ (= 3 (stat:size (stat filename)))))
+
+ (pass-if "no-fail"
+ (and
+ (call-with-port (open filename (file-options no-fail))
+ binary-port?)
+ (= 0 (stat:size (stat filename)))))
+
+ (delete-file filename)
+
+ (pass-if-condition "exception: does-not-exist"
+ i/o-file-does-not-exist-error?
+ (open filename (file-options no-create))))
+
(with-test-prefix "8.2.10 Output ports"
+ (with-test-prefix "open-file-output-port"
+ (test-output-file-opener open-file-output-port (test-file)))
+
(pass-if "open-bytevector-output-port"
(let-values (((port get-content)
(open-bytevector-output-port #f)))
(and (bytevector=? (get-content) source)
(bytevector=? (get-content) (make-bytevector 0))))))
+ (pass-if "bytevector-output-port is binary"
+ (binary-port? (open-bytevector-output-port)))
+
+ (pass-if "open-bytevector-output-port [extract after close]"
+ (let-values (((port get-content)
+ (open-bytevector-output-port)))
+ (let ((source (make-bytevector 12345 #xFE)))
+ (put-bytevector port source)
+ (close-port port)
+ (bytevector=? (get-content) source))))
+
(pass-if "open-bytevector-output-port [put-u8]"
(let-values (((port get-content)
(open-bytevector-output-port)))
(bytevector=? (get-content) source)
(bytevector=? (get-content) (make-bytevector 0))))))
- (pass-if "make-custom-binary-output"
+ (pass-if "make-custom-binary-output-port"
(let ((port (make-custom-binary-output-port "cbop"
(lambda (x y z) 0)
#f #f #f)))
(put-bytevector port source)
(and (= sink-pos (bytevector-length source))
(not eof?)
- (bytevector=? sink source)))))
+ (bytevector=? sink source))))
+
+ (pass-if "standard-output-port is binary"
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ (binary-port? (standard-output-port))))
+ (pass-if "standard-error-port is binary"
+ (with-fluids ((%default-port-encoding "UTF-8"))
+ (binary-port? (standard-error-port)))))
+
+\f
+(with-test-prefix "8.2.6 Input and output ports"
+
+ (pass-if "transcoded-port [output]"
+ (let ((s "Hello\nÄÖÜ"))
+ (bytevector=?
+ (string->utf8 s)
+ (call-with-bytevector-output-port/transcoded (make-transcoder (utf-8-codec))
+ (lambda (utf8-port)
+ (put-string utf8-port s))))))
+
+ (pass-if "transcoded-port [input]"
+ (let ((s "Hello\nÄÖÜ"))
+ (string=?
+ s
+ (get-string-all
+ (transcoded-port (open-bytevector-input-port (string->utf8 s))
+ (make-transcoder (utf-8-codec)))))))
+
+ (pass-if "transcoded-port [input line]"
+ (string=? "ÄÖÜ"
+ (get-line (transcoded-port
+ (open-bytevector-input-port (string->utf8 "ÄÖÜ\nFooBar"))
+ (make-transcoder (utf-8-codec))))))
+
+ (pass-if "transcoded-port [error handling mode = raise]"
+ (let* ((t (make-transcoder (utf-8-codec) (native-eol-style)
+ (error-handling-mode raise)))
+ (b (open-bytevector-input-port #vu8(255 2 1)))
+ (tp (transcoded-port b t)))
+ (guard (c ((i/o-decoding-error? c)
+ (eq? (i/o-error-port c) tp)))
+ (get-line tp)
+ #f))) ; fail if we reach this point
+
+ (pass-if "transcoded-port [error handling mode = replace]"
+ (let* ((t (make-transcoder (utf-8-codec) (native-eol-style)
+ (error-handling-mode replace)))
+ (b (open-bytevector-input-port #vu8(255 1 2 3 103 110 117)))
+ (tp (transcoded-port b t)))
+ (string-suffix? "gnu" (get-line tp))))
+
+ (pass-if "transcoded-port, output [error handling mode = raise]"
+ (let-values (((p get)
+ (open-bytevector-output-port)))
+ (let* ((t (make-transcoder (latin-1-codec) (native-eol-style)
+ (error-handling-mode raise)))
+ (tp (transcoded-port p t)))
+ (guard (c ((i/o-encoding-error? c)
+ (and (eq? (i/o-error-port c) tp)
+ (char=? (i/o-encoding-error-char c) #\λ)
+ (bytevector=? (get) (string->utf8 "The letter ")))))
+ (put-string tp "The letter λ cannot be represented in Latin-1.")
+ #f))))
+
+ (pass-if "port-transcoder [binary port]"
+ (not (port-transcoder (open-bytevector-input-port #vu8()))))
+
+ (pass-if "port-transcoder [transcoded port]"
+ (let* ((p (transcoded-port (open-bytevector-input-port (string->utf8 "foo"))
+ (make-transcoder (utf-8-codec))))
+ (t (port-transcoder p)))
+ (and t
+ (transcoder-codec t)
+ (eq? (native-eol-style)
+ (transcoder-eol-style t))
+ (eq? (error-handling-mode replace)
+ (transcoder-error-handling-mode t))))))
+
+(with-test-prefix "8.2.9 Textual input"
+
+ (pass-if "get-string-n [short]"
+ (let ((port (open-input-string "GNU Guile")))
+ (string=? "GNU " (get-string-n port 4))))
+ (pass-if "get-string-n [long]"
+ (let ((port (open-input-string "GNU Guile")))
+ (string=? "GNU Guile" (get-string-n port 256))))
+ (pass-if "get-string-n [eof]"
+ (let ((port (open-input-string "")))
+ (eof-object? (get-string-n port 4))))
+
+ (pass-if "get-string-n! [short]"
+ (let ((port (open-input-string "GNU Guile"))
+ (s (string-copy "Isn't XXX great?")))
+ (and (= 3 (get-string-n! port s 6 3))
+ (string=? s "Isn't GNU great?"))))
+
+ (with-test-prefix "read error"
+ (pass-if-condition "get-char" i/o-read-error?
+ (get-char (make-failing-port)))
+ (pass-if-condition "lookahead-char" i/o-read-error?
+ (lookahead-char (make-failing-port)))
+ ;; FIXME: these are not yet exception-correct
+ #|
+ (pass-if-condition "get-string-n" i/o-read-error?
+ (get-string-n (make-failing-port) 5))
+ (pass-if-condition "get-string-n!" i/o-read-error?
+ (get-string-n! (make-failing-port) (make-string 5) 0 5))
+ |#
+ (pass-if-condition "get-string-all" i/o-read-error?
+ (get-string-all (make-failing-port 100)))
+ (pass-if-condition "get-line" i/o-read-error?
+ (get-line (make-failing-port)))
+ (pass-if-condition "get-datum" i/o-read-error?
+ (get-datum (make-failing-port)))))
+
+(define (encoding-error-predicate char)
+ (lambda (c)
+ (and (i/o-encoding-error? c)
+ (char=? char (i/o-encoding-error-char c)))))
+
+(with-test-prefix "8.2.12 Textual Output"
+
+ (with-test-prefix "write error"
+ (pass-if-condition "put-char" i/o-write-error?
+ (put-char (make-failing-port) #\G))
+ (pass-if-condition "put-string" i/o-write-error?
+ (put-string (make-failing-port) "Hello World!"))
+ (pass-if-condition "put-datum" i/o-write-error?
+ (put-datum (make-failing-port) '(hello world!))))
+ (with-test-prefix "encoding error"
+ (pass-if-condition "put-char" (encoding-error-predicate #\λ)
+ (call-with-bytevector-output-port/transcoded
+ (make-transcoder (latin-1-codec)
+ (native-eol-style)
+ (error-handling-mode raise))
+ (lambda (port)
+ (put-char port #\λ))))
+ (pass-if-condition "put-string" (encoding-error-predicate #\λ)
+ (call-with-bytevector-output-port/transcoded
+ (make-transcoder (latin-1-codec)
+ (native-eol-style)
+ (error-handling-mode raise))
+ (lambda (port)
+ (put-string port "FooλBar"))))))
+
+(with-test-prefix "8.3 Simple I/O"
+ (with-test-prefix "read error"
+ (pass-if-condition "read-char" i/o-read-error?
+ (read-char (make-failing-port)))
+ (pass-if-condition "peek-char" i/o-read-error?
+ (peek-char (make-failing-port)))
+ (pass-if-condition "read" i/o-read-error?
+ (read (make-failing-port))))
+ (with-test-prefix "write error"
+ (pass-if-condition "display" i/o-write-error?
+ (display "Hi there!" (make-failing-port)))
+ (pass-if-condition "write" i/o-write-error?
+ (write '(hi there!) (make-failing-port)))
+ (pass-if-condition "write-char" i/o-write-error?
+ (write-char #\G (make-failing-port)))
+ (pass-if-condition "newline" i/o-write-error?
+ (newline (make-failing-port))))
+ (let ((filename (test-file)))
+ ;; ensure the test file exists
+ (call-with-output-file filename
+ (lambda (port) (write "foo" port)))
+ (pass-if "call-with-input-file [port is textual]"
+ (call-with-input-file filename textual-port?))
+ (pass-if-condition "call-with-input-file [exception: not-found]"
+ i/o-file-does-not-exist-error?
+ (call-with-input-file ",this-is-highly-unlikely-to-exist!"
+ values))
+ (pass-if-condition "call-with-output-file [exception: already-exists]"
+ i/o-file-already-exists-error?
+ (call-with-output-file filename
+ values))
+ (delete-file filename)))
+
+(with-test-prefix "8.2.13 Input/output ports"
+ (with-test-prefix "open-file-input/output-port [output]"
+ (test-output-file-opener open-file-input/output-port (test-file)))
+ (with-test-prefix "open-file-input/output-port [input]"
+ (test-input-file-opener open-file-input/output-port (test-file))))
;;; Local Variables:
-;;; coding: latin-1
;;; mode: scheme
+;;; eval: (put 'guard 'scheme-indent-function 1)
;;; End: