1.0.21.19: Eagerly find a stream's external-format when a coding-error occurs.
[sbcl.git] / tests / external-format.impure.lisp
index c86b6ef..99e2bda 100644 (file)
        (let ((,xf (first (first ,nxf))))
          ,@body))))
 
+(defvar *test-path* "external-format-test.tmp")
+
 (do-external-formats (xf)
-  (with-open-file (s "/dev/null" :direction :input :external-format xf)
+  (with-open-file (s #-win32 "/dev/null" #+win32 "nul" :direction :input :external-format xf)
     (assert (eq (read-char s nil s) s))))
 
 ;;; Test standard character read-write equivalency over all external formats.
 (let ((standard-characters "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!$\"'(),_-./:;?+<=>#%&*@[\\]{|}`^~"))
   (do-external-formats (xf)
-    (with-open-file (s "external-format-test.txt" :direction :output
+    (with-open-file (s *test-path* :direction :output
                      :if-exists :supersede :external-format xf)
       (loop for character across standard-characters
             do (write-char character s)))
-    (with-open-file (s "external-format-test.txt" :direction :input
+    (with-open-file (s *test-path* :direction :input
                      :external-format xf)
       (loop for character across standard-characters
-            do (assert (eql (read-char s) character))))))
+            do (let ((got (read-char s)))
+                 (unless (eql character got)
+                   (error "wanted ~S, got ~S" character got)))))))
 
-(delete-file "external-format-test.txt")
+(delete-file *test-path*)
 #-sb-unicode
 (progn
   (test-util:report-test-status)
 (dotimes (width-1 4)
   (let ((character (code-char (elt '(1 #x81 #x801 #x10001) width-1))))
     (dotimes (offset (+ width-1 1))
-      (with-open-file (s "external-format-test.txt" :direction :output
+      (with-open-file (s *test-path* :direction :output
                        :if-exists :supersede :external-format :utf-8)
         (dotimes (n offset)
           (write-char #\a s))
-        (dotimes (n 4097)
+        (dotimes (n (+ 4 sb-impl::+bytes-per-buffer+))
           (write-char character s)))
-      (with-open-file (s "external-format-test.txt" :direction :input
+      (with-open-file (s *test-path* :direction :input
                        :external-format :utf-8)
         (dotimes (n offset)
           (assert (eql (read-char s) #\a)))
-        (dotimes (n 4097)
-          (assert (eql (read-char s) character)))
+        (dotimes (n (+ 4 sb-impl::+bytes-per-buffer+))
+          (let ((got (read-char s)))
+            (unless (eql got character)
+              (error "wanted ~S, got ~S (~S)" character got n))))
         (assert (eql (read-char s nil s) s))))))
 
 ;;; Test character decode restarts.
-(with-open-file (s "external-format-test.txt" :direction :output
+(with-open-file (s *test-path* :direction :output
                  :if-exists :supersede :element-type '(unsigned-byte 8))
   (write-byte 65 s)
   (write-byte 66 s)
   (write-byte #xe0 s)
   (write-byte 67 s))
-(with-open-file (s "external-format-test.txt" :direction :input
+(with-open-file (s *test-path* :direction :input
                  :external-format :utf-8)
   (handler-bind
       ((sb-int:character-decoding-error #'(lambda (decoding-error)
@@ -79,7 +85,7 @@
                                              'sb-int:attempt-resync))))
     (assert (equal (read-line s nil s) "ABC"))
     (assert (equal (read-line s nil s) s))))
-(with-open-file (s "external-format-test.txt" :direction :input
+(with-open-file (s *test-path* :direction :input
                  :external-format :utf-8)
   (handler-bind
       ((sb-int:character-decoding-error #'(lambda (decoding-error)
     (assert (equal (read-line s nil s) "AB"))
     (assert (equal (read-line s nil s) s))))
 
+;;; And again with more data to account for buffering (this was briefly)
+;;; broken in early 0.9.6.
+(with-open-file (s *test-path* :direction :output
+                 :if-exists :supersede :element-type '(unsigned-byte 8))
+  (let ((a (make-array 50
+                       :element-type '(unsigned-byte 64)
+                       :initial-contents (map 'list #'char-code
+                                              "1234567890123456789012345678901234567890123456789."))))
+    (setf (aref a 49) (char-code #\Newline))
+    (dotimes (i 40)
+      (write-sequence a s))
+    (write-byte #xe0 s)
+    (dotimes (i 40)
+      (write-sequence a s))))
+(with-test (:name (:character-decode-large :attempt-resync))
+  (with-open-file (s *test-path* :direction :input
+                     :external-format :utf-8)
+    (handler-bind
+        ((sb-int:character-decoding-error #'(lambda (decoding-error)
+                                              (declare (ignore decoding-error))
+                                              (invoke-restart
+                                               'sb-int:attempt-resync)))
+         ;; The failure mode is an infinite loop, add a timeout to detetct it.
+         (sb-ext:timeout (lambda () (error "Timeout"))))
+      (sb-ext:with-timeout 5
+        (dotimes (i 80)
+          (assert (equal (read-line s nil s)
+                         "1234567890123456789012345678901234567890123456789")))))))
+
+(with-test (:name (:character-decode-large :force-end-of-file)
+            :fails-on :sbcl)
+  (error "We can't reliably test this due to WITH-TIMEOUT race condition")
+  ;; This test will currently fail. But sometimes it will fail in
+  ;; ungracefully due to the WITH-TIMEOUT race mentioned above. This
+  ;; rightfully confuses some people, so we'll skip running the code
+  ;; for now. -- JES, 2006-01-27
+  #+nil
+  (with-open-file (s *test-path* :direction :input
+                     :external-format :utf-8)
+    (handler-bind
+        ((sb-int:character-decoding-error #'(lambda (decoding-error)
+                                              (declare (ignore decoding-error))
+                                              (invoke-restart
+                                               'sb-int:force-end-of-file)))
+         ;; The failure mode is an infinite loop, add a timeout to detetct it.
+         (sb-ext:timeout (lambda () (error "Timeout"))))
+      (sb-ext:with-timeout 5
+        (dotimes (i 80)
+          (assert (equal (read-line s nil s)
+                         "1234567890123456789012345678901234567890123456789")))
+        (assert (equal (read-line s nil s) s))))))
+
 ;;; Test character encode restarts.
-(with-open-file (s "external-format-test.txt" :direction :output
+(with-open-file (s *test-path* :direction :output
                  :if-exists :supersede :external-format :latin-1)
   (handler-bind
       ((sb-int:character-encoding-error #'(lambda (encoding-error)
     (write-char #\B s)
     (write-char (code-char 322) s)
     (write-char #\C s)))
-(with-open-file (s "external-format-test.txt" :direction :input
+(with-open-file (s *test-path* :direction :input
                  :external-format :latin-1)
   (assert (equal (read-line s nil s) "ABC"))
   (assert (equal (read-line s nil s) s)))
 
-(with-open-file (s "external-format-test.txt" :direction :output
+(with-open-file (s *test-path* :direction :output
                  :if-exists :supersede :external-format :latin-1)
   (handler-bind
       ((sb-int:character-encoding-error #'(lambda (encoding-error)
                               :initial-contents `(#\A #\B ,(code-char 322)
                                                       #\C))))
       (write-string string s))))
-(with-open-file (s "external-format-test.txt" :direction :input
+(with-open-file (s *test-path* :direction :input
                  :external-format :latin-1)
   (assert (equal (read-line s nil s) "ABC"))
   (assert (equal (read-line s nil s) s)))
 
 \f
 ;;;; KOI8-R external format
-(with-open-file (s "external-format-test.txt" :direction :output
+(with-open-file (s *test-path* :direction :output
                  :if-exists :supersede :external-format :koi8-r)
   (write-char (code-char #xB0) s)
   (assert (eq
              (sb-int:character-encoding-error ()
                :good))
            :good)))
-(with-open-file (s "external-format-test.txt" :direction :input
+(with-open-file (s *test-path* :direction :input
                  :element-type '(unsigned-byte 8))
   (let ((byte (read-byte s)))
     (assert (= (eval byte) #x9C))))
-(with-open-file (s "external-format-test.txt" :direction :input
+(with-open-file (s *test-path* :direction :input
                  :external-format :koi8-r)
   (let ((char (read-char s)))
     (assert (= (char-code (eval char)) #xB0))))
+(delete-file *test-path*)
+
+(let* ((koi8-r-codes (coerce '(240 210 201 215 197 212 33) '(vector (unsigned-byte 8))))
+       (uni-codes #(1055 1088 1080 1074 1077 1090 33))
 
+       (string (octets-to-string koi8-r-codes :external-format :koi8-r))
+       (uni-decoded (map 'vector #'char-code string)))
+  (assert (equalp (map 'vector #'char-code (octets-to-string koi8-r-codes :external-format :koi8-r))
+                  uni-codes))
+  (assert (equalp (string-to-octets (map 'string #'code-char uni-codes) :external-format :koi8-r)
+                  koi8-r-codes)))
 \f
-(delete-file "external-format-test.txt")
+;;; tests of FILE-STRING-LENGTH
+(let ((standard-characters "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!$\"'(),_-./:;?+<=>#%&*@[\\]{|}`^~"))
+  (do-external-formats (xf)
+    (with-open-file (s *test-path* :direction :output
+                       :external-format xf)
+      (loop for x across standard-characters
+            for position = (file-position s)
+            for char-length = (file-string-length s x)
+            do (write-char x s)
+            do (assert (= (file-position s) (+ position char-length))))
+      (let ((position (file-position s))
+            (string-length (file-string-length s standard-characters)))
+        (write-string standard-characters s)
+        (assert (= (file-position s) (+ position string-length)))))
+    (delete-file *test-path*)))
+
+(let ((char-codes '(0 1 255 256 511 512 1023 1024 2047 2048 4095 4096
+                    8191 8192 16383 16384 32767 32768 65535 65536 131071
+                    131072 262143 262144)))
+  (with-open-file (s *test-path* :direction :output
+                     :external-format :utf-8)
+    (dolist (code char-codes)
+      (let* ((char (code-char code))
+             (position (file-position s))
+             (char-length (file-string-length s char)))
+        (write-char char s)
+        (assert (= (file-position s) (+ position char-length)))))
+    (let* ((string (map 'string #'code-char char-codes))
+           (position (file-position s))
+           (string-length (file-string-length s string)))
+      (write-string string s)
+      (assert (= (file-position s) (+ position string-length))))))
+\f
+
+;;; See sbcl-devel "Subject: Bug in FILE-POSITION on UTF-8-encoded files"
+;;; by Lutz Euler on 2006-03-05 for more details.
+(with-test (:name (:file-position :utf-8))
+  (let ((path *test-path*))
+    (with-open-file (s path
+                       :direction :output
+                       :if-exists :supersede
+                       :element-type '(unsigned-byte 8))
+      ;; Write #\*, encoded in UTF-8, to the file.
+      (write-byte 42 s)
+      ;; Append #\adiaeresis, encoded in UTF-8, to the file.
+      (write-sequence '(195 164) s))
+    (with-open-file (s path :external-format :utf-8)
+      (read-char s)
+      (let ((pos (file-position s))
+            (char (read-char s)))
+        (format t "read character with code ~a successfully from file position ~a~%"
+                (char-code char) pos)
+        (file-position s pos)
+        (format t "set file position back to ~a, trying to read-char again~%" pos)
+        (let ((new-char (read-char s)))
+          (assert (char= char new-char)))))
+    (values)))
+(delete-file *test-path*)
+
+;;; We used to call STREAM-EXTERNAL-FORMAT on the stream in the error
+;;; when printing a coding error, but that didn't work if the stream
+;;; was closed by the time the error was printed.  See sbcl-devel
+;;; "Subject: Printing coding errors for closed streams" by Zach Beane
+;;; on 2008-10-16 for more info.
+(with-test (:name (:character-coding-error-stream-external-format))
+  (flet ((first-file-character ()
+           (with-open-file (stream *test-file* :external-format :utf-8)
+             (read-char stream))))
+    (with-open-file (stream *test-file*
+                            :direction :output
+                            :if-exists :supersede
+                            :element-type '(unsigned-byte 8))
+      (write-byte 192 stream))
+    (princ-to-string (nth-value 1 (ignore-errors (first-file-character))))))
+(delete-file *test-path*)
+
+;;; External format support in SB-ALIEN
+
+(with-test (:name (:sb-alien :vanilla))
+  (define-alien-routine strdup c-string (str c-string))
+  (assert (equal "foo" (strdup "foo"))))
+
+(with-test (:name (:sb-alien :utf-8 :utf-8))
+  (define-alien-routine strdup (c-string :external-format :utf-8)
+    (str (c-string :external-format :utf-8)))
+  (assert (equal "foo" (strdup "foo"))))
+
+(with-test (:name (:sb-alien :latin-1 :utf-8))
+  (define-alien-routine strdup (c-string :external-format :latin-1)
+    (str (c-string :external-format :utf-8)))
+  (assert (= (length (strdup (string (code-char 246))))
+             2)))
+
+(with-test (:name (:sb-alien :utf-8 :latin-1))
+  (define-alien-routine strdup (c-string :external-format :utf-8)
+    (str (c-string :external-format :latin-1)))
+  (assert (equal (string (code-char 228))
+                 (strdup (concatenate 'string
+                                      (list (code-char 195))
+                                      (list (code-char 164)))))))
+
+(with-test (:name (:sb-alien :ebcdic :ebcdic))
+  (define-alien-routine strdup (c-string :external-format :ebcdic-us)
+    (str (c-string :external-format :ebcdic-us)))
+  (assert (equal "foo" (strdup "foo"))))
+
+(with-test (:name (:sb-alien :latin-1 :ebcdic))
+  (define-alien-routine strdup (c-string :external-format :latin-1)
+    (str (c-string :external-format :ebcdic-us)))
+  (assert (not (equal "foo" (strdup "foo")))))
+
+(with-test (:name (:sb-alien :simple-base-string))
+  (define-alien-routine strdup (c-string :external-format :ebcdic-us
+                                         :element-type base-char)
+    (str (c-string :external-format :ebcdic-us)))
+  (assert (typep (strdup "foo") 'simple-base-string)))
+
+;;;; success