source [file dirname [info script]]/testing.tcl

needs constraint jim
constraint cmd socket
constraint cmd os.fork
constraint expr posixaio {$tcl_platform(platform) eq {unix} && !$tcl_platform(bootstrap)}

# Create and open in binary mode for compatibility between Windows and Unix
set f [open testdata.in wb]
$f puts test-data
$f close
# create a test file file with several lines
set f [open copy.in wb]
$f puts line1
$f puts line2
$f puts line3
$f close

set f [open testdata.in rb]

defer {
	$f close
	file delete testdata.in
	file delete copy.in
	file delete copy.out
}

test aio-1.1 {seek usage} -body {
	$f seek
} -returnCodes error -match glob -result {wrong # args: should be "* seek offset ?start|current|end"}

test aio-1.2 {seek start} -body {
	$f seek 2
	$f tell
} -result {2}

test aio-1.3 {seek start} -body {
	$f seek 4 start
	$f tell
} -result {4}

test aio-1.4 {read after seek} -body {
	set c [$f read 1]
	list $c [$f tell]
} -result {- 5}

test aio-1.5 {seek backwards} -body {
	$f seek -2 current
	set c [$f read 1]
	list $c [$f tell]
} -result {t 4}

test aio-1.6 {seek from end} -body {
	$f seek -2 end
	set c [$f read 2]
	list $c [$f tell]
} -result [list "a\n" 10]

test aio-1.7 {seek usage} -body {
	$f seek 4 bad
} -returnCodes error -match glob -result {wrong # args: should be "* seek offset ?start|current|end"}

test aio-1.8 {seek usage} -body {
	$f seek badint
} -returnCodes error -match glob -result {expected integer but got "badint"}

test aio-1.9 {seek bad pos} -body {
	$f seek -20
} -returnCodes error -match glob -result {testdata.in: Invalid argument}

test aio-2.1 {read usage} -body {
	$f read -nonoption
} -returnCodes error -result {bad option "-nonoption": must be -nonewline, or -pending}

test aio-2.2 {read usage} -body {
	$f read badint
} -returnCodes error -result {expected integer but got "badint"}

test aio-2.3 {read -ve len} -body {
	$f read " -20"
} -returnCodes error -result {invalid parameter: negative len}

test aio-2.4 {read too many args} -body {
	$f read 20 extra
} -returnCodes error -match glob -result {wrong # args: should be "* read ?-nonewline|len?"}

test aio-3.1 {copy to invalid fh} -body {
	$f copy lambda
} -returnCodes error -result {Not a filehandle: "lambda"}

test aio-3.2 {copy bad length} -body {
	$f copy stdout invalid
} -returnCodes error -result {expected integer but got "invalid"}

set badvar a

test aio-4.1 {gets invalid var} -body {
	$f gets badvar(abc)
} -returnCodes error -result {can't set "badvar(abc)": variable isn't array}

test aio-5.1 {puts usage} -body {
	stdout puts -badopt abc
} -returnCodes error -result {wrong # args: should be "stdout puts ?-nonewline? str"}

test aio-6.1 {eof} {
	$f seek 0
	$f eof
} {0}

test aio-6.2 {eof} {
	# eof won't trigger until we try to read
	$f seek 0 end
	$f eof
} {0}

test aio-6.3 {eof} {
	$f read 1
	$f eof
} {1}

test aio-7.1 {close args} -constraints socket -body {
	$f close badopt
} -returnCodes error -result {bad option "badopt": must be -nodelete, r, or w}

test aio-7.2 {close w on non-socket} -constraints socket -body {
	$f close w
} -returnCodes error -match regexp -result {(bad|socket)}

test aio-7.3 {close -nodelete on non-socket} -constraints socket -body {
	$f close -nodelete
} -returnCodes error -result {not supported}

test aio-8.1 {filename} {
	$f filename
} testdata.in

test aio-9.1 {open: posix modes} -constraints posixaio -body {
	set in [open testdata.in RDONLY]
	set buf [$in gets]
	$in close
	set buf
} -result {test-data}

test aio-9.2 {open: posix modes, bad modes} -constraints posixaio -body {
	open testdata.in {CREAT TRUNC}
} -returnCodes error -result {testdata.in: invalid open mode 'CREAT TRUNC'}

test aio-9.3 {open: posix modes, bad modes} -constraints posixaio -body {
	open testdata.in {WRONG TRUNC}
} -returnCodes error -result {bad access mode "WRONG": must be APPEND, BINARY, CREAT, EXCL, NOCTTY, RDONLY, RDWR, TRUNC, or WRONLY}

test aio-9.4 {open: posix modes} -constraints posixaio -cleanup {
	file delete testdata.out
} -body {
	set out [open testdata.out {WRONLY CREAT TRUNC}]
	$out puts write-data
	$out close
	# Now open for readwrite without truncate
	set io [open testdata.out {RDWR CREAT}]
	set buf [$io gets]
	$io close
	set buf
} -result {write-data}

test aio-10.1 {open: -noclose} -constraints os.fork -cleanup {
	file delete testdata.out
} -body {
	# Keep this file descriptor open for children
	set f [open testdata.out -noclose w]
	$f puts parent
	$f flush
	# Now the child process can write to the same file via the file descriptor
	exec sh -c "echo child >&[$f getfd]"
	$f close
	set in [open testdata.out]
	set lines [list [$in gets] [$in gets]]
	$in close
	set lines
} -result {parent child}

test copyto-1.1 {basic copyto} {
	set in [open copy.in]
	set out [open copy.out w]
	$in copyto $out
	$in close
	$out close
	set ff [open copy.out]
	set result [list [$ff gets] [$ff gets] [$ff gets]]
	$ff close
	set result
} {line1 line2 line3}

test copyto-1.2 {copyto with limit} {
	set in [open copy.in]
	set out [open copy.out w]
	$in copyto $out 8
	$in close
	$out close
	set ff [open copy.out]
	set result [list [$ff gets] [$ff gets] [$ff gets]]
	$ff close
	set result
} {line1 li {}}

test copyto-1.3 {copyto after gets} {
	set in [open copy.in]
	set out [open copy.out w]
	$in gets
	$in copyto $out
	$in close
	$out close
	set ff [open copy.out]
	set result [list [$ff gets] [$ff gets] [$ff gets]]
	$ff close
	set result
} {line2 line3 {}}

test copyto-1.4 {copyto after read} {
	set in [open copy.in]
	$in read 3
	set out [open copy.out w]
	$in copyto $out
	$in close
	$out close
	set ff [open copy.out]
	set result [list [$ff gets] [$ff gets] [$ff gets]]
	$ff close
	set result
} {e1 line2 line3}

test copyto-1.5 {copyto after gets, seek} {
	set in [open copy.in]
	$in gets
	$in seek 2 start
	set out [open copy.out w]
	$in copyto $out
	$in close
	$out close
	set ff [open copy.out]
	set result [list [$ff gets] [$ff gets] [$ff gets]]
	$ff close
	set result
} {ne1 line2 line3}

test copyto-1.6 {copyto from pipe} {
	set in [open "|cat copy.in"]
	set out [open copy.out w]
	$in copyto $out
	$in close
	$out close
	set ff [open copy.out]
	set result [list [$ff gets] [$ff gets] [$ff gets]]
	$ff close
	set result
} {line1 line2 line3}

test copyto-1.6 {copyto to pipe} {
	set out [open "|cat >copy.out" w]
	set in [open copy.in]
	$in copyto $out
	$in close
	$out close
	set ff [open copy.out]
	set result [list [$ff gets] [$ff gets] [$ff gets]]
	$ff close
	set result
} {line1 line2 line3}

# Creates a child process and returns {pid writehandle}
# The child expects to read $numlines lines of input and exits with a return
# code of 0 if ok
proc child_reader {numlines} {
	# create a pipe with the child as a slightly slow reader
	lassign [socket pipe] r w

	set pid [os.fork]
	if {$pid == 0} {
		# child
		$w close
		# sleep a moment to make sure the parent fills up the send buffer
		sleep 0.5
		set n 0
		while {[$r gets buf] >= 0} {
			incr n
		}
		#puts "child got $n/$numlines lines"
		$r close
		if {$n == $numlines} {
			# This is what we expect
			exit 99
		}
		# This is not expected
		exit 98
	}
	# parent
	$r close

	list $pid $w
}

test autoflush-1.1 {pipe writer, blocking} -constraints {socket os.fork} -body {
	lassign [child_reader 10000] pid w

	# Send data fast enough to fill up the send buffer
	loop i 10000 {
		$w puts "this is line $i"
	}

	# No autoflush needed. The write won't return
	# until queued
	$w close

	lassign [wait $pid] - - rc

	list $rc
} -result {99}

test autoflush-1.2 {pipe writer, non blocking} -constraints {socket os.fork} -body {
	lassign [child_reader 10000] pid w

	$w ndelay 1

	# Send data fast enough to fill up the send buffer
	# With older jimtcl this would return an error "pipe: Resource temporarily unavailable"
	loop i 10000 {
		$w puts "this is line $i"
	}

	# Now data should still be queued, wait for autoflush
	lassign [time {
		after idle {}
		vwait done
	}] t1

	# puts "autoflush finished in ${t1}us, closing pipe"
	$w close

	lassign [wait $pid] - - rc

	list $rc $t1
} -match glob -result {99 *}

testreport
