~ chicken-core (chicken-5) /tests/port-tests.scm


  1(import chicken.condition chicken.file chicken.file.posix
  2	chicken.flonum chicken.format chicken.io chicken.port
  3	chicken.process chicken.process.signal chicken.tcp srfi-4)
  4
  5(include "test.scm")
  6(test-begin "ports")
  7
  8(define-syntax assert-error
  9  (syntax-rules ()
 10    ((_ expr) 
 11     (assert (handle-exceptions _ #t expr #f)))))
 12
 13(define *text* #<<EOF
 14this is a test
 15<foof> #;33> (let ((in (open-input-string ""))) (close-input-port in)
 16       (read-char in)) [09:40]
 17<foof> Error: (read-char) port already closed: #<input port "(string)">
 18<foof> #;33> (let ((in (open-input-string ""))) (close-input-port in)
 19       (read-line in))
 20<foof> Error: call of non-procedure: #t
 21<foof> ... that's a little odd
 22<Bunny351> yuck. [09:44]
 23<Bunny351> double yuck. [10:00]
 24<sjamaan> yuck squared! [10:01]
 25<Bunny351> yuck powered by yuck
 26<Bunny351> (to the power of yuck, of course) [10:02]
 27<pbusser3> My yuck is bigger than yours!!!
 28<foof> yuck!
 29<foof> (that's a factorial)
 30<sjamaan> heh
 31<sjamaan> I think you outyucked us all [10:03]
 32<foof> well, for large enough values of yuck, yuck! ~= yuck^yuck [10:04]
 33ERC> 
 34EOF
 35)
 36
 37(define p (open-input-string *text*))
 38
 39(assert (string=? "this is a test" (read-line p)))
 40
 41(assert
 42 (string=? 
 43  "<foof> #;33> (let ((in (open-input-string \"\"))) (close-input-port in)"
 44  (read-line p)))
 45(assert (= 20 (length (read-lines (open-input-string *text*)))))
 46
 47(assert (char-ready? (open-input-string "")))
 48
 49(let ((out (open-output-string)))
 50  (test-equal "Initially, output string is empty"
 51              (get-output-string out) "")
 52  (display "foo" out)
 53  (test-equal "output can be extracted from output string"
 54              (get-output-string out) "foo")
 55  (close-output-port out)
 56  (test-equal "closing a string output port has no effect on the returned data"
 57              (get-output-string out) "foo")
 58  (test-error "writing to a closed string output port is an error"
 59              (display "bar" out)))
 60
 61;;; copy-port
 62
 63(assert
 64 (string=? 
 65  *text*
 66  (with-output-to-string
 67    (lambda ()
 68      (copy-port (open-input-string *text*) (current-output-port)))))) ; read-char -> write-char
 69
 70(assert 
 71 (equal? 
 72  '(3 2 1)
 73  (let ((out '()))
 74    (copy-port				; read -> custom
 75     (open-input-string "1 2 3")
 76     #f
 77     read
 78     (lambda (x port) (set! out (cons x out))))
 79    out)))
 80
 81(assert
 82 (equal? 
 83  "abc"
 84  (let ((out (open-output-string)))
 85    (copy-port				; read-char -> custom
 86     (open-input-string "abc") 
 87     out
 88     read-char
 89     (lambda (x out) (write-char x out)))
 90    (get-output-string out))))
 91
 92(assert
 93 (equal? 
 94  "abc"
 95  (let ((in (open-input-string "abc") )
 96	(out (open-output-string)))
 97    (copy-port				; custom -> write-char
 98     in out
 99     (lambda (in) (read-char in)))
100    (get-output-string out))))
101
102;; {input,output}-port-open?
103
104(assert (input-port-open? (open-input-string "abc")))
105(assert (output-port-open? (open-output-string)))
106(assert-error (input-port-open? (open-output-string)))
107(assert-error (output-port-open? (open-input-string "abc")))
108
109;; direction-specific port closure
110
111(let* ((n 0)
112       (p (make-input-port (constantly #\a)
113			   (constantly #t)
114			   (lambda () (set! n (add1 n))))))
115  (close-output-port p)
116  (assert (input-port-open? p))
117  (assert (= n 0))
118  (close-input-port p)
119  (assert (not (input-port-open? p)))
120  (assert (= n 1))
121  (close-input-port p)
122  (assert (not (input-port-open? p)))
123  (assert (= n 1)))
124
125(let* ((n 0)
126       (p (make-output-port (lambda () (display #\a))
127			    (lambda () (set! n (add1 n))))))
128  (close-input-port p)
129  (assert (output-port-open? p))
130  (assert (= n 0))
131  (close-output-port p)
132  (assert (not (output-port-open? p)))
133  (assert (= n 1))
134  (close-output-port p)
135  (assert (not (output-port-open? p)))
136  (assert (= n 1)))
137
138;; bidirectional ports
139
140(let* ((b (string))
141       (w (lambda (s)
142	    (set! b (string-append b s))))
143       (e (lambda ()
144	    (positive? (string-length b))))
145       (r (lambda ()
146	    (let ((s b))
147	      (set! b (substring s 1))
148	      (string-ref s 0))))
149       (i (make-input-port r e void))
150       (o (make-output-port w void))
151       (p (make-bidirectional-port i o)))
152  (assert (input-port? p))
153  (assert (output-port? p))
154  (assert (input-port-open? p))
155  (assert (output-port-open? p))
156  (display "quartz ruby" p)
157  (newline p)
158  (assert (equal? (read p) 'quartz))
159  (assert (equal? (read i) 'ruby))
160  (display "emerald topaz" p)
161  (newline p)
162  (close-output-port p)
163  (assert (not (output-port-open? o)))
164  (assert (not (output-port-open? p)))
165  (assert (equal? (read p) 'emerald))
166  (assert (equal? (read i) 'topaz))
167  (close-input-port p)
168  (assert (not (input-port-open? i)))
169  (assert (not (input-port-open? p))))
170
171;; fill buffers
172(with-input-from-file "compiler.scm" read-string)
173
174(print "slow...")
175(time
176 (with-input-from-file "compiler.scm"
177   (lambda ()
178     (with-output-to-file "compiler.scm.2"
179       (lambda ()
180	 (copy-port 
181	  (current-input-port) (current-output-port)
182	  (lambda (port) (read-char port))
183	  (lambda (x port) (write-char x port))))))))
184
185(print "fast...")
186(time
187 (with-input-from-file "compiler.scm"
188   (lambda ()
189     (with-output-to-file "compiler.scm.2"
190       (lambda ()
191	 (copy-port (current-input-port) (current-output-port)))))))
192
193(delete-file "compiler.scm.2")
194
195(define-syntax check
196  (syntax-rules ()
197    ((_ (expr-head expr-rest ...))
198     (check 'expr-head (expr-head expr-rest ...)))
199    ((_ name expr)
200     (let ((okay (list 'okay)))
201       (assert
202        (eq? okay
203             (condition-case
204                 (begin (print* name "...")
205                        (flush-output)
206                        (let ((output expr))
207                          (printf "FAIL [ ~S ]\n" output)))
208               ((exn i/o file) (printf "OK\n") okay))))))))
209
210(cond-expand
211  ((not mingw32)
212
213   (define proc (process-fork (lambda () (tcp-accept (tcp-listen 8080)))))
214
215   (on-exit (lambda () (handle-exceptions exn #f (process-signal proc))))
216
217   (print "\n\nProcedures check on TCP ports being closed\n")
218
219   (receive (in out)
220       (let lp ()
221	 (condition-case (tcp-connect "localhost" 8080)
222	   ((exn i/o net) (lp))))
223     (close-output-port out)
224     (close-input-port in)
225     (check (tcp-addresses in))
226     (check (tcp-port-numbers in))
227     (check (tcp-abandon-port in)))	; Not sure about abandon-port
228
229   
230   ;; This tests for two bugs which occurred on NetBSD and possibly
231   ;; other platforms, possibly due to multiprocessing:
232   ;; read-line with EINTR would loop endlessly and process-wait would
233   ;; signal a condition when interrupted rather than retrying.
234   (set-signal-handler! signal/chld void) ; Should be a noop but triggers EINTR
235   (receive (in out)
236     (create-pipe)
237     (receive (pid ok? status)
238       (process-wait
239        (process-fork
240         (lambda ()
241           (file-close in)              ; close receiving end
242           (with-output-to-port (open-output-file* out)
243             (lambda ()
244               (display "hello, world\n")
245               ;; exit prevents buffers from being discarded by implicit _exit
246               (exit 0))))))
247       (file-close out)                 ; close sending end
248       (assert (equal? '(#t 0 ("hello, world"))
249                       (list ok? status (read-lines (open-input-file* in)))))))
250   )
251  (else))
252
253(print "\n\nProcedures check on output ports being closed\n")
254
255(with-output-to-file "empty-file" void)
256
257(call-with-output-file "empty-file"
258  (lambda (out)
259    (close-output-port out)
260    (check (write '(foo) out))
261    (check (fprintf out "blabla"))
262    (check "print-call-chain" (begin (print-call-chain out) (void)))
263    (check (print-error-message (make-property-condition 'exn 'message "foo") out))
264    (check "print" (with-output-to-port out
265		     (lambda () (print "foo"))))
266    (check "print*" (with-output-to-port out
267		      (lambda () (print* "foo"))))
268    (check (display "foo" out))
269    (check (terminal-port? out))   ; Calls isatty() on C_SCHEME_FALSE?
270    (check (newline out))
271    (check (write-char #\x out))
272    (check (write-line "foo" out))
273    (check (write-u8vector '#u8(1 2 3) out))
274    ;;(check (port->fileno in))
275    (check (flush-output out))
276
277    #+(not mingw32) 
278    (begin
279      (check (file-test-lock out))
280      (check (file-lock out))
281      (check (file-lock/blocking out)))
282
283    (check (write-byte 120 out))
284    (check (write-string "foo" #f out))))
285
286(print "\n\nProcedures check on input ports being closed\n")
287(call-with-input-file "empty-file"
288  (lambda (in)
289    (close-input-port in)
290    (check (read in))
291    (check (read-char in))
292    (check (char-ready? in))
293    (check (peek-char in))
294    ;;(check (port->fileno in))
295    (check (terminal-port? in))	   ; Calls isatty() on C_SCHEME_FALSE?
296    (check (read-line in 5))
297    (check (read-u8vector 5 in))
298    (check "read-u8vector!" (let ((dest (make-u8vector 5)))
299                              (read-u8vector! 5 dest in)))
300    #+(not mingw32) 
301    (begin
302      (check (file-test-lock in))
303      (check (file-lock in))
304      (check (file-lock/blocking in)))
305
306    (check (read-byte in))
307    (check (read-token (constantly #t) in))
308    (check (read-string 10 in))
309    (check "read-string!" (let ((buf (make-string 10)))
310                            (read-string! 10 buf in) buf))))
311
312(print "\nEmbedded NUL bytes in filenames are rejected\n")
313(assert-error (with-output-to-file "embedded\x00null-byte" void))
314
315;;; #978 -- port-position checks for read-line
316
317(define (read-line/pos p limit)  ;; common
318  (let ((s (read-line p limit)))
319    (let-values (((row col) (port-position p)))
320      (list s row col))))
321
322(define (read-string-line/pos str limit)
323  (read-line/pos (open-input-string str) limit))
324
325(define (read-process-line/pos cmd args limit)
326  (let-values (((i o pid) (process cmd args)))
327    (let ((rc (read-line/pos i limit)))
328      (close-input-port i)
329      (close-output-port o)
330      rc)))
331(define (read-echo-line/pos str limit)
332  (read-process-line/pos "echo" (list "-n" str) limit))
333
334(define (test-port-position proc)
335  (test-equal "advance row when encountering delim" 
336	      (proc "abcde\nfghi" 6)
337	      '("abcde" 2 0))
338  (test-equal "reaching limit sets col to limit, and does not advance row"
339	      (proc "abcdefghi" 6)
340	      '("abcdef" 1 6))
341  (test-equal "delimiter counted in limit" ;; observed behavior, strange
342	      (proc "abcdef\nghi" 6)
343	      '("abcdef" 1 6))
344  (test-equal "EOF reached"
345	      (proc "abcde" 6)
346	      '("abcde" 1 5)))
347
348(test-group
349 "read-line string port position tests"
350 (test-port-position read-string-line/pos))
351
352(test-group "read-string!"
353  (let ((in (open-input-string "1234567890"))
354        (buf (make-string 5)))
355    (test-equal "peek-char won't influence the result of read-string!"
356                (peek-char in)
357                #\1)
358    (test-equal "read-string! won't read past buffer if given #f"
359                (read-string! #f buf in)
360                5)
361    (test-equal "read-string! reads the requested bytes with #f"
362                buf
363                "12345")
364    (test-equal "read-string! won't read past buffer if given #f and offset"
365                (read-string! #f buf in 3)
366                2)
367    (test-equal "read-string! reads the requested bytes with #f and offset"
368                buf
369                "12367")
370    (test-equal "read-string! reads until the end correctly"
371                (read-string! #f buf in)
372                3)
373    (test-equal "read-string! leaves the buffer's tail intact"
374                buf
375                "89067")
376    (test-equal "after peek-char at EOF, read-string! doesn't mutate the buffer"
377                (begin (peek-char in)
378                       (read-string! #f buf in)
379                       buf)
380                "89067"))
381  (let ((in (open-input-string "1234567890"))
382        (buf (make-string 5)))
383    (test-equal "read-string! won't read past buffer if given size"
384                (read-string! 10 buf in)
385                5)
386    (test-equal "read-string! reads the requested bytes with buffer size"
387                buf
388                "12345")
389    (test-equal "read-string! won't read past buffer if given size and offset"
390                (read-string! 10 buf in 3)
391                2)
392    (test-equal "read-string! reads the requested bytes with buffer size and offset"
393                buf
394                "12367")
395    (test-equal "read-string! reads until the end correctly with buffer size"
396                (read-string! 10 buf in)
397                3)
398    (test-equal "read-string! leaves the buffer's tail intact"
399                buf
400                "89067")
401    (test-equal "read-string! at EOF reads nothing"
402                (read-string! 10 buf in)
403                0)
404    (test-equal "read-string! at EOF doesn't mutate the buffer"
405                buf
406                "89067")))
407
408(test-group "line endings"
409  (let ((s "foo\nbar\rbaz\r\nqux")
410	(f (lambda ()
411	     (test-equal "\\n" (read-line) "foo")
412	     (test-equal "\\r" (read-line) "bar")
413	     (test-equal "\\r\\n" (read-line) "baz")
414	     (test-equal "eof" (read-line) "qux"))))
415    (test-group "string port"
416      (with-input-from-string s f))
417    (test-group "file port"
418      (let ((file "mixed-line-endings"))
419	(with-output-to-file file (lambda () (display s)))
420	(with-input-from-file file f)
421	(delete-file* file)))
422    (test-group "custom port"
423      (let* ((p (open-input-string s))
424	     (p* (make-input-port (lambda () (read-char p))
425				  (lambda () (char-ready? p))
426				  (lambda () (close-input-port p)))))
427	(with-input-from-port p* f)))))
428
429;; Disabled because it requires `echo -n` for
430;; the EOF test, and that is not available on all systems.
431;; Uncomment locally to run.
432#;
433(test-group
434 "read-line process port position tests"
435 (test-port-position read-echo-line/pos))
436
437;;;
438
439(test-end)
440
441(test-exit)
Trap