# -*- tcl -*-
# Functionality covered: operation of the reflected transformation
#
# This file contains a collection of tests for one or more of the Tcl
# built-in commands.  Sourcing this file into Tcl runs the tests and
# generates output for errors.  No output means no errors were found.
#
# Copyright (c) 2007 Andreas Kupries <andreask@activestate.com>
#                                    <akupries@shaw.ca>
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.

if {"::tcltest" ni [namespace children]} {
    package require tcltest 2.5
    namespace import -force ::tcltest::*
}

::tcltest::loadTestedCommands
catch [list package require -exact Tcltest [info patchlevel]]

# Custom constraints used in this file
testConstraint testchannel [llength [info commands testchannel]]
testConstraint thread [expr {0 == [catch {package require Thread 2.7-}]}]

# testchannel cut|splice  Both needed to test the reflection in threads.
# thread::send

#----------------------------------------------------------------------

# ### ### ### ######### ######### #########
## Testing the reflected transformation.

# Helper commands to record the arguments to handler methods.  Stored in a
# script so that the tests needing this code do not need their own copy but
# can access this variable.

set helperscript {
    if {"::tcltest" ni [namespace children]} {
	package require tcltest 2.5
	namespace import -force ::tcltest::*
    }

    # This forces the return options to be in the order that the test expects!
    variable optorder {
	-code !?! -level !?! -errorcode !?! -errorline !?! -errorinfo !?!
	-errorstack !?!
    }
    proc noteOpts opts {
	variable optorder
	lappend ::res [dict merge $optorder $opts]
    }

    # Helper command, canned result for 'initialize' method.  Gets the
    # optional methods as arguments. Use return features to post the result
    # higher up.

    proc handle.initialize {args} {
	upvar args hargs
	if {[lindex $hargs 0] eq "initialize"} {
	    return -code return [list {*}$args initialize finalize read write]
	}
    }
    proc handle.finalize {} {
	upvar args hargs
	if {[lindex $hargs 0] eq "finalize"} {
	    return -code return ""
	}
    }
    proc handle.read {} {
	upvar args hargs
	if {[lindex $hargs 0] eq "read"} {
	    return -code return "@"
	}
    }
    proc handle.drain {} {
	upvar args hargs
	if {[lindex $hargs 0] eq "drain"} {
	    return -code return "<>"
	}
    }
    proc handle.clear {} {
	upvar args hargs
	if {[lindex $hargs 0] eq "clear"} {
	    return -code return ""
	}
    }

    proc tempchan {{mode r+}} {
	global tempchan
	return [set tempchan [open [makeFile {test data} tempchanfile] $mode]]
    }
    proc tempdone {} {
	global tempchan
	catch {close $tempchan}
	removeFile tempchanfile
	return
    }
    proc tempview {} { viewFile tempchanfile }
}

# Set everything up in the main thread.
eval $helperscript

#puts <<[file channels]>>

# ### ### ### ######### ######### #########

test iortrans-1.0 {chan, wrong#args} -returnCodes error -body {
    chan
} -result {wrong # args: should be "chan subcommand ?arg ...?"}
test iortrans-1.1 {chan, unknown method} -returnCodes error -body {
    chan foo
} -match glob -result {unknown or ambiguous subcommand "foo": must be*}

# --- --- --- --------- --------- ---------
# chan push, and method "initalize"

test iortrans-2.0 {chan push, wrong#args, not enough} -returnCodes error -body {
    chan push
} -result {wrong # args: should be "chan push channel cmdprefix"}
test iortrans-2.1 {chan push, wrong#args, too many} -returnCodes error -body {
    chan push a b c
} -result {wrong # args: should be "chan push channel cmdprefix"}
test iortrans-2.2 {chan push, invalid channel} -setup {
    proc foo {} {}
} -returnCodes error -body {
    chan push {} foo
} -cleanup {
    rename foo {}
} -result {can not find channel named ""}
test iortrans-2.3 {chan push, bad handler, not a list} -body {
    chan push [tempchan] "foo \{"
} -returnCodes error -cleanup {
    tempdone
} -result {unmatched open brace in list}
test iortrans-2.4 {chan push, bad handler, not a command} -body {
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
} -result {invalid command name "foo"}
test iortrans-2.5 {chan push, initialize failed, bad signature} -body {
    proc foo {} {}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -result {wrong # args: should be "foo"}
test iortrans-2.6 {chan push, initialize failed, bad signature} -body {
    proc foo {} {}
    chan push [tempchan] ::foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -result {wrong # args: should be "::foo"}
test iortrans-2.7 {chan push, initialize failed, bad result, not a list} -body {
    proc foo {args} {return "\{"}
    catch {chan push [tempchan] foo}
    return $::errorInfo
} -cleanup {
    tempdone
    rename foo {}
} -match glob -result {chan handler "foo initialize" returned non-list: *}
test iortrans-2.8 {chan push, initialize failed, bad result, not a list} -body {
    proc foo {args} {return \{\{\}}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -match glob -result {chan handler "foo initialize" returned non-list: *}
test iortrans-2.9 {chan push, initialize failed, bad result, empty list} -body {
    proc foo {args} {}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -match glob -result {*all required methods*}
test iortrans-2.10 {chan push, initialize failed, bad result, bogus method name} -body {
    proc foo {args} {return 1}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -match glob -result {*bad method "1": must be *}
test iortrans-2.11 {chan push, initialize failed, bad result, bogus method name} -body {
    proc foo {args} {return {a b c}}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -match glob -result {*bad method "c": must be *}
test iortrans-2.12 {chan push, initialize failed, bad result, required methods missing} -body {
    # Required: initialize, and finalize.
    proc foo {args} {return {initialize}}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -match glob -result {*all required methods*}
test iortrans-2.13 {chan push, initialize failed, bad result, illegal method name} -body {
    proc foo {args} {return {initialize finalize BOGUS}}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -match glob -result {*returned bad method "BOGUS": must be clear, drain, finalize, flush, initialize, limit?, read, or write}
test iortrans-2.14 {chan push, initialize failed, bad result, mode/handler mismatch} -body {
    proc foo {args} {return {initialize finalize}}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -match glob -result {*makes the channel inaccessible}
# iortrans-2.15 event/watch methods elimimated, removed these tests.
# iortrans-2.16
test iortrans-2.17 {chan push, initialize failed, bad result, drain/read mismatch} -body {
    proc foo {args} {return {initialize finalize drain write}}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -match glob -result {*supports "drain" but not "read"}
test iortrans-2.18 {chan push, initialize failed, bad result, flush/write mismatch} -body {
    proc foo {args} {return {initialize finalize flush read}}
    chan push [tempchan] foo
} -returnCodes error -cleanup {
    tempdone
    rename foo {}
} -match glob -result {*supports "flush" but not "write"}
test iortrans-2.19 {chan push, initialize ok, creates channel} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	global res
	lappend res $args
	if {[lindex $args 0] ne "initialize"} {return}
	return {initialize finalize drain flush read write}
    }
    lappend res [file channel rt*]
    lappend res [chan push [tempchan] foo]
    lappend res [close [lindex $res end]]
    lappend res [file channel rt*]
} -cleanup {
    tempdone
    rename foo {}
} -result {{} {initialize rt* {read write}} file* {drain rt*} {flush rt*} {finalize rt*} {} {}}
test iortrans-2.20 {chan push, init failure -> no channel, no finalize} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	global res
	lappend res $args
	return
    }
    lappend res [file channel rt*]
    lappend res [catch {chan push [tempchan] foo} msg] $msg
    lappend res [file channel rt*]
} -cleanup {
    tempdone
    rename foo {}
} -result {{} {initialize rt* {read write}} 1 {*all required methods*} {}}

# --- --- --- --------- --------- ---------
# method finalize (via close)

# General note: file channels rt* finds the transform channel, however the
# name reported will be that of the underlying base driver, fileXX here.  This
# actually allows us to see if the whole channel is gone, or only the
# transformation, but not the base.

test iortrans-3.1 {chan finalize, handler destruction has no effect on channel} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return
    }
    lappend res [set c [chan push [tempchan] foo]]
    rename foo {}
    lappend res [file channels file*]
    lappend res [file channels rt*]
    lappend res [catch {close $c} msg] $msg
    lappend res [file channels file*]
    lappend res [file channels rt*]
} -cleanup {
    tempdone
} -result {{initialize rt* {read write}} file* file* {} 1 {invalid command name "foo"} {} {}}
test iortrans-3.2 {chan finalize, for close} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return
    }
    lappend res [set c [chan push [tempchan] foo]]
    close $c
    # Close deleted the channel.
    lappend res [file channels rt*]
    # Channel destruction does not kill handler command!
    lappend res [info command foo]
} -cleanup {
    rename foo {}
    tempdone
} -result {{initialize rt* {read write}} file* {finalize rt*} {} foo}
test iortrans-3.3 {chan finalize, for close, error, close error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -code error 5
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res [catch {close $c} msg] $msg
    # Channel is gone despite error.
    lappend res [file channels rt*]
} -cleanup {
    rename foo {}
    tempdone
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 5 {}}
test iortrans-3.4 {chan finalize, for close, error, close error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	error FOO
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res [catch {close $c} msg] $msg $::errorInfo
} -cleanup {
    rename foo {}
    tempdone
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 FOO {FOO
*"close $c"}}
test iortrans-3.5 {chan finalize, for close, arbitrary result, ignored} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return SOMETHING
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res [catch {close $c} msg] $msg
} -cleanup {
    rename foo {}
    tempdone
} -result {{initialize rt* {read write}} file* {finalize rt*} 0 {}}
test iortrans-3.6 {chan finalize, for close, break, close error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -code 3
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res [catch {close $c} msg] $msg
} -cleanup {
    rename foo {}
    tempdone
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*}
test iortrans-3.7 {chan finalize, for close, continue, close error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -code 4
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res [catch {close $c} msg] $msg
} -cleanup {
    rename foo {}
    tempdone
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*}
test iortrans-3.8 {chan finalize, for close, custom code, close error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -code 777 BANG
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res [catch {close $c} msg] $msg
} -cleanup {
    rename foo {}
    tempdone
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*}
test iortrans-3.9 {chan finalize, for close, ignore level, close error} -setup {
    set res {}
} -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -level 5 -code 777 BANG
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res [catch {close $c} msg opt] $msg
    noteOpts $opt
} -match glob -cleanup {
    rename foo {}
    tempdone
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline 1 -errorinfo *bad code*subcommand "finalize"*}}

# --- === *** ###########################
# method read (via read)

test iortrans-4.1 {chan read, transform call and return} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return snarf
    }
    set c [chan push [tempchan] foo]
    lappend res [read $c 10]
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} snarf}
test iortrans-4.2 {chan read, for non-readable channel} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args MUST_NOT_HAPPEN
    }
    set c [chan push [tempchan w] foo]
    lappend res [catch {read $c 2} msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {1 {channel "file*" wasn't opened for reading}}
test iortrans-4.3 {chan read, error return} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code error BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {read $c 2} msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} 1 BOOM!}
test iortrans-4.4 {chan read, break return is error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code break BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {read $c 2} msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} 1 *bad code*}
test iortrans-4.5 {chan read, continue return is error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code continue BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {read $c 2} msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} 1 *bad code*}
test iortrans-4.6 {chan read, custom return is error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code 777 BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {read $c 2} msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} 1 *bad code*}
test iortrans-4.7 {chan read, level is squashed} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -level 55 -code 777 BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {read $c 2} msg opt] $msg
    noteOpts $opt
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline 1 -errorinfo *bad code*subcommand "read"*}}
test iortrans-4.8 {chan read, read, bug 2921116} -setup {
    set res {}
} -match glob -body {
    proc foo {fd args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	# Kill and recreate transform while it is operating
	chan pop $fd
	chan push $fd [list foo $fd]
    }
    set c [chan push [set c [tempchan]] [list foo $c]]
    lappend res [read $c]
    #lappend res [gets $c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} {}}
test iortrans-4.8.1 {chan read, bug 721ec69271} -setup {
    set res {}
} -match glob -body {
    proc foo {fd args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	# Kill and recreate transform while it is operating
	chan pop $fd
	chan push $fd [list foo $fd]
    }
    set c [chan push [set c [tempchan]] [list foo $c]]
    chan configure $c -buffersize 2
    lappend res [read $c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* te} {read rt* st} {read rt* { d}} {read rt* at} {read rt* {a
}} {}}
test iortrans-4.8.2 {chan read, bug 721ec69271} -setup {
    set res {}
} -match glob -body {
    proc foo {fd args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	# Kill and recreate transform while it is operating
	chan pop $fd
	chan push $fd [list foo $fd]
	return x
    }
    set c [chan push [set c [tempchan]] [list foo $c]]
    chan configure $c -buffersize 1
    lappend res [read $c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* t} {read rt* e} {read rt* s} {read rt* t} {read rt* { }} {read rt* d} {read rt* a} {read rt* t} {read rt* a} {read rt* {
}} {}}
test iortrans-4.9 {chan read, gets, bug 2921116} -setup {
    set res {}
} -match glob -body {
    proc foo {fd args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	# Kill and recreate transform while it is operating
	chan pop $fd
	chan push $fd [list foo $fd]
    }
    set c [chan push [set c [tempchan]] [list foo $c]]
    lappend res [gets $c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} {}}

# Driver for a base channel that emits several short "files"
# with each terminated by a fleeting EOF
    proc driver {cmd args} {
        variable ::tcl::buffer
        variable ::tcl::index
        set chan [lindex $args 0]
        switch -- $cmd {
            initialize {
                set index($chan) 0
                set buffer($chan) .....
                return {initialize finalize watch read}
            }
            finalize {
                if {![info exists index($chan)]} {return}
                unset index($chan) buffer($chan)
		array unset index
		array unset buffer
                return
            }
            watch {}
            read {
                set n [lindex $args 1]
                if {![info exists index($chan)]} {
                    driver initialize $chan
                }
                set new [expr {$index($chan) + $n}]
                set result [string range $buffer($chan) $index($chan) $new-1]
                set index($chan) $new
                if {[string length $result] == 0} {
                    driver finalize $chan
                }
                return $result
            }
        }
    }

# Channel read transform that is just the identity - pass all through
    proc idxform {cmd handle args} {
      switch -- $cmd {
        initialize {
            return {initialize finalize read}
        }
        finalize {
            return
        }
        read {
            lassign $args buffer
            return $buffer
        }
      }
    }

# Test that all EOFs pass through full xform stack.  Proper data boundaries.
# Check robustness against buffer sizes.
test iortrans-4.10 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] idxform]
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}
test iortrans-4.10.1 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] idxform]
    chan configure $chan -buffersize 3
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}
test iortrans-4.10.2 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] idxform]
    chan configure $chan -buffersize 5
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}

rename idxform {}

# Channel read transform that delays the data and always returns something
    proc delayxform {cmd handle args} {
      variable store
      switch -- $cmd {
        initialize {
	    set store($handle) {}
            return {initialize finalize read drain}
        }
        finalize {
	    unset store($handle)
            return
        }
        read {
            lassign $args buffer
	    if {$store($handle) eq {}} {
		set reply [string index $buffer 0]
		set store($handle) [string range $buffer 1 end]
	    } else {
		set reply $store($handle)
		set store($handle) $buffer
	    }
            return $reply
        }
	drain {
	    delayxform read $handle {}
	}
      }
    }

# Test that all EOFs pass through full xform stack.  Proper data boundaries.
# Check robustness against buffer sizes.
test iortrans-4.11 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] delayxform]
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}
test iortrans-4.11.1 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] delayxform]
    chan configure $chan -buffersize 3
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}
test iortrans-4.11.2 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] delayxform]
    chan configure $chan -buffersize 5
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}

    rename delayxform {}

# Channel read transform that delays the data and may return {}
    proc delay2xform {cmd handle args} {
      variable store
      switch -- $cmd {
        initialize {
	    set store($handle) {}
            return {initialize finalize read drain}
        }
        finalize {
	    unset store($handle)
            return
        }
        read {
            lassign $args buffer
		set reply $store($handle)
		set store($handle) $buffer
            return $reply
        }
	drain {
	    delay2xform read $handle {}
	}
      }
    }

test iortrans-4.12 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] delay2xform]
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}

    rename delay2xform {}
    rename driver {}


# --- === *** ###########################
# method write (via puts)

test iortrans-5.1 {chan write, regular write} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return transformresult
    }
    set c [chan push [tempchan] foo]
    puts -nonewline $c snarf
    flush $c
    close $c
    lappend res [tempview]
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarf} transformresult}
test iortrans-5.2 {chan write, no write is ok, no change to file} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return
    }
    set c [chan push [tempchan] foo]
    puts -nonewline $c snarfsnarfsnarf
    flush $c
    close $c
    lappend res [tempview];	# This has to show the original data, as nothing was written
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} {test data}}
test iortrans-5.3 {chan write, failed write} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code error FAIL!
    }
    set c [chan push [tempchan] foo]
    puts -nonewline $c snarfsnarfsnarf
    lappend res [catch {flush $c} msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 FAIL!}
test iortrans-5.4 {chan write, non-writable channel} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args MUST_NOT_HAPPEN
	return
    }
    set c [chan push [tempchan r] foo]
    lappend res [catch {
	puts -nonewline $c snarfsnarfsnarf
	flush $c
    } msg] $msg
} -cleanup {
    close $c
    tempdone
    rename foo {}
} -result {1 {channel "file*" wasn't opened for writing}}
test iortrans-5.5 {chan write, failed write, error return} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code error BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {
	puts -nonewline $c snarfsnarfsnarf
	flush $c
    } msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 BOOM!}
test iortrans-5.6 {chan write, failed write, error return} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	error BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[catch {
	puts -nonewline $c snarfsnarfsnarf
	flush $c
    } msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 BOOM!}
test iortrans-5.7 {chan write, failed write, break return is error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code break BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {
	puts -nonewline $c snarfsnarfsnarf
	flush $c
    } msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*}
test iortrans-5.8 {chan write, failed write, continue return is error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code continue BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {
	puts -nonewline $c snarfsnarfsnarf
	flush $c
    } msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*}
test iortrans-5.9 {chan write, failed write, custom return is error} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code 777 BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {
	puts -nonewline $c snarfsnarfsnarf
	flush $c
    } msg] $msg
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*}
test iortrans-5.10 {chan write, failed write, level is ignored} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -level 55 -code 777 BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res [catch {
	puts -nonewline $c snarfsnarfsnarf
	flush $c
    } msg opt] $msg
    noteOpts $opt
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline * -errorinfo *bad code*subcommand "write"*}}
test iortrans-5.11 {chan write, bug 2921116} -match glob -setup {
    set res {}
    set level 0
} -body {
    proc foo {fd args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	# pop - invokes flush - invokes 'foo write' - infinite recursion - stop it
	global level
	if {$level} {
	    return
	}
	incr level
	# Kill and recreate transform while it is operating
	chan pop $fd
	chan push $fd [list foo $fd]
    }
    set c [chan push [set c [tempchan]] [list foo $c]]
    lappend res [puts -nonewline $c abcdef]
    lappend res [flush $c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{} {write rt* abcdef} {write rt* abcdef} {}}

# --- === *** ###########################
# method limit?, drain (via read)

test iortrans-6.1 {chan read, read limits} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize limit?
	handle.finalize
	lappend ::res $args
	handle.read
	return 6
    }
    set c [chan push [tempchan] foo]
    lappend res [read $c 10]
} -cleanup {
    tempdone
    rename foo {}
} -result {{limit? rt*} {read rt* {test d}} {limit? rt*} {read rt* {ata
}} {limit? rt*} @@}
test iortrans-6.2 {chan read, read transform drain on eof} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize drain
	handle.finalize
	lappend ::res $args
	handle.read
	handle.drain
	return
    }
    set c [chan push [tempchan] foo]
    lappend res [read $c]
    lappend res [close $c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} {drain rt*} @<> {}}

# --- === *** ###########################
# method clear (via puts, seek)

test iortrans-7.1 {chan write, write clears read buffers} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize clear
	handle.finalize
	lappend ::res $args
	handle.clear
	return transformresult
    }
    set c [chan push [tempchan] foo]
    puts -nonewline $c snarf
    flush $c
    return $res
} -cleanup {
    tempdone
    rename foo {}
} -result {{clear rt*} {write rt* snarf}}
test iortrans-7.2 {seek clears read buffers} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize clear
	handle.finalize
	lappend ::res $args
	return
    }
    set c [chan push [tempchan] foo]
    seek $c 2
    return $res
} -cleanup {
    tempdone
    rename foo {}
} -result {{clear rt*}}
test iortrans-7.3 {clear, any result is ignored} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize clear
	handle.finalize
	lappend ::res $args
	return -code error "X"
    }
    set c [chan push [tempchan] foo]
    seek $c 2
    return $res
} -cleanup {
    tempdone
    rename foo {}
} -result {{clear rt*}}
test iortrans-7.4 {chan clear, bug 2921116} -match glob -setup {
    set res {}
} -body {
    proc foo {fd args} {
	handle.initialize clear
	handle.finalize
	lappend ::res $args
	# Kill and recreate transform while it is operating
	chan pop $fd
	chan push $fd [list foo $fd]
    }
    set c [chan push [set c [tempchan]] [list foo $c]]
    seek $c 2
    return $res
} -cleanup {
    tempdone
    rename foo {}
} -result {{clear rt*}}

# --- === *** ###########################
# method flush (via seek, close)

test iortrans-8.1 {seek flushes write buffers, ignores data} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize flush
	handle.finalize
	lappend ::res $args
	return X
    }
    set c [chan push [tempchan] foo]
    # Flush, no writing
    seek $c 2
    # The close flushes again, this modifies the file!
    lappend res |
    lappend res [close $c] | [tempview]
} -cleanup {
    tempdone
    rename foo {}
} -result {{flush rt*} | {flush rt*} {} | {teXt data}}
test iortrans-8.2 {close flushes write buffers, writes data} -setup {
    set res {}
} -match glob -body {
    proc foo {args} {
	handle.initialize flush
	lappend ::res $args
	handle.finalize
	return .flushed.
    }
    set c [chan push [tempchan] foo]
    close $c
    lappend res [tempview]
} -cleanup {
    tempdone
    rename foo {}
} -result {{flush rt*} {finalize rt*} .flushed.}
test iortrans-8.3 {chan flush, bug 2921116} -match glob -setup {
    set res {}
} -body {
    proc foo {fd args} {
	handle.initialize flush
	handle.finalize
	lappend ::res $args
	# Kill and recreate transform while it is operating
	chan pop $fd
	chan push $fd [list foo $fd]
    }
    set c [chan push [set c [tempchan]] [list foo $c]]
    seek $c 2
    set res
} -cleanup {
    tempdone
    rename foo {}
} -result {{flush rt*}}

# --- === *** ###########################
# method watch - removed from TIP (rev 1.12+)

# --- === *** ###########################
# method event - removed from TIP (rev 1.12+)

# --- === *** ###########################
# 'Pull the rug' tests. Create channel in a interpreter A, move to other
# interpreter B, destroy the origin interpreter (A) before or during access
# from B. Must not crash, must return proper errors.
test iortrans-11.0 {origin interpreter of moved transform gone} -setup {
    set ida [interp create];	#puts <<$ida>>
    set idb [interp create];	#puts <<$idb>>
    # Magic to get the test* commands in the children
    load {} Tcltest $ida
    load {} Tcltest $idb
} -constraints {testchannel} -match glob -body {
    # Set up channel and transform in interpreter
    interp eval $ida $helperscript
    interp eval $ida [list ::variable tempchan [tempchan]]
    interp transfer {} $::tempchan $ida
    set chan [interp eval $ida {
	variable tempchan
	proc foo {args} {
	    handle.initialize clear drain flush limit? read write
	    handle.finalize
	    lappend ::res $args
	    return
	}
	set chan [chan push $tempchan foo]
	fconfigure $chan -buffering none
	set chan
    }]
    # Move channel to 2nd interpreter, transform goes with it.
    interp eval $ida [list testchannel cut $chan]
    interp eval $idb [list testchannel splice $chan]
    # Kill origin interpreter, then access channel from 2nd interpreter.
    interp delete $ida
    set res {}
    lappend res \
	[catch {interp eval $idb [list puts $chan shoo]} msg] $msg \
	[catch {interp eval $idb [list tell $chan]} msg] $msg \
	[catch {interp eval $idb [list seek $chan 1]} msg] $msg \
	[catch {interp eval $idb [list gets $chan]} msg] $msg \
	[catch {interp eval $idb [list close $chan]} msg] $msg
    #lappend res [interp eval $ida {set res}]
    # actions: clear|write|clear|write|clear|flush|limit?|drain|flush
    # The 'tell' is ok, as it passed through the transform to the base channel
    # without invoking the transform handler.
} -cleanup {
    tempdone
    interp delete $idb
} -result {1 {Owner lost} 0 0 1 {Owner lost} 1 {Owner lost} 1 {Owner lost}}
test iortrans-11.1 {origin interpreter of moved transform destroyed during access} -setup {
    set ida [interp create];	#puts <<$ida>>
    set idb [interp create];	#puts <<$idb>>
    # Magic to get the test* commands in the children
    load {} Tcltest $ida
    load {} Tcltest $idb
} -constraints {testchannel} -match glob -body {
    # Set up channel in thread
    set chan [interp eval $ida $helperscript]
    interp eval $ida [list ::variable tempchan [tempchan]]
    interp transfer {} $::tempchan $ida
    set chan [interp eval $ida {
	proc foo {args} {
	    handle.initialize clear drain flush limit? read write
	    handle.finalize
	    lappend ::res $args
	    # Destroy interpreter during channel access.
	    suicide
	}
	set chan [chan push $tempchan foo]
	fconfigure $chan -buffering none
	set chan
    }]
    interp alias $ida suicide {} interp delete $ida
    # Move channel to 2nd thread, transform goes with it.
    interp eval $ida [list testchannel cut $chan]
    interp eval $idb [list testchannel splice $chan]
    # Run access from interpreter B, this will give us a synchronous response.
    interp eval $idb [list set chan $chan]
    interp eval $idb [list set mid $tcltest::mainThread]
    set res [interp eval $idb {
	# Wait a bit, give the main thread the time to start its event loop to
	# wait for the response from B
	after 50
	catch { puts $chan shoo } res
	set res
    }]
} -cleanup {
    interp delete $idb
    tempdone
} -result {Owner lost}
test iortrans-11.2 {delete interp of reflected transform} -setup {
    interp create child
    # Magic to get the test* commands into the child
    load {} Tcltest child
} -constraints {testchannel} -body {
    # Get base channel into the child
    set c [tempchan]
    testchannel cut $c
    interp eval child [list testchannel splice $c]
    interp eval child [list set c $c]
    child eval {
	proc no-op args {}
	proc driver {c sub args} {
	    return {initialize finalize read write}
	}
	set t [chan push $c [list driver $c]]
	chan event $c readable no-op
    }
    interp delete child
} -cleanup {
    tempdone
} -result {}

# ### ### ### ######### ######### #########
## Same tests as above, but exercising the code forwarding and receiving
## driver operations to the originator thread.

# ### ### ### ######### ######### #########
## Testing the reflected channel (Thread forwarding).
#
## The id numbers refer to the original test without thread forwarding, and
## gaps due to tests not applicable to forwarding are left to keep this
## association.

# ### ### ### ######### ######### #########
## Helper command. Runs a script in a separate thread and returns the result.
## A channel is transfered into the thread as well, and a list of configuation
## variables

proc inthread {chan script args} {
    # Test thread.
    set tid [thread::create -preserved]
    thread::send $tid {load {} Tcltest}

    # Init thread configuration.
    # - Listed variables
    # - Id of main thread
    # - A number of helper commands

    foreach v $args {
	upvar 1 $v x
	thread::send $tid [list set $v $x]
    }
    thread::send $tid [list set mid [thread::id]]
    thread::send $tid {
	proc notes {} {
	    return $::notes
	}
	proc noteOpts opts {
	    lappend ::notes [dict merge {
		-code !?! -level !?! -errorcode !?! -errorline !?!
		-errorinfo !?! -errorstack !?!
	    } $opts]
	}
    }
    thread::send $tid [list proc s {} [list uplevel 1 $script]]; # (*)

    # Transfer channel (cut/splice aka detach/attach)

    testchannel cut $chan
    thread::send $tid [list testchannel splice $chan]

    # Run test script, also run local event loop!  The local event loop waits
    # for the result to come back.  It is also necessary for the execution of
    # forwarded channel operations.

    set ::tres ""
    thread::send -async $tid {
	after 50
	catch {s} res;	# This runs the script, 's' was defined at (*)
	thread::send -async $mid [list set ::tres $res]
    }
    vwait ::tres
    # Remove test thread, and return the captured result.

    thread::release $tid
    return $::tres
}

# ### ### ### ######### ######### #########

test iortrans.tf-3.2 {chan finalize, for close} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return {}
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res [inthread $c {
	close $c
	# Close the deleted the channel.
	file channels rt*
    } c]
    # Channel destruction does not kill handler command!
    lappend res [info command foo]
} -cleanup {
    rename foo {}
} -result {{initialize rt* {read write}} file* {finalize rt*} {} foo}
test iortrans.tf-3.3 {chan finalize, for close, error, close error} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -code error 5
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res {*}[inthread $c {
	lappend notes [catch {close $c} msg] $msg
	# Channel is gone despite error.
	lappend notes [file channels rt*]
	notes
    } c]
} -cleanup {
    rename foo {}
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 5 {}}
test iortrans.tf-3.4 {chan finalize, for close, error, close errror} -setup {
    set res {}
} -constraints {testchannel thread} -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	error FOO
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res {*}[inthread $c {
	lappend notes [catch {close $c} msg] $msg
	notes
    } c]
} -match glob -cleanup {
    rename foo {}
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 FOO}
test iortrans.tf-3.5 {chan finalize, for close, arbitrary result} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return SOMETHING
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res {*}[inthread $c {
	lappend notes [catch {close $c} msg] $msg
	notes
    } c]
} -cleanup {
    rename foo {}
} -result {{initialize rt* {read write}} file* {finalize rt*} 0 {}}
test iortrans.tf-3.6 {chan finalize, for close, break, close error} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -code 3
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res {*}[inthread $c {
	lappend notes [catch {close $c} msg] $msg
	notes
    } c]
} -cleanup {
    rename foo {}
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*}
test iortrans.tf-3.7 {chan finalize, for close, continue, close error} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -code 4
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res {*}[inthread $c {
	lappend notes [catch {close $c} msg] $msg
	notes
    } c]
} -cleanup {
    rename foo {}
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*}
test iortrans.tf-3.8 {chan finalize, for close, custom code, close error} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -code 777 BANG
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res {*}[inthread $c {
	lappend notes [catch {close $c} msg] $msg
	notes
    } c]
} -cleanup {
    rename foo {}
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*}
test iortrans.tf-3.9 {chan finalize, for close, ignore level, close error} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	lappend ::res $args
	handle.initialize
	return -level 5 -code 777 BANG
    }
    lappend res [set c [chan push [tempchan] foo]]
    lappend res {*}[inthread $c {
	lappend notes [catch {close $c} msg opt] $msg
	noteOpts $opt
	notes
    } c]
} -cleanup {
    rename foo {}
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline 1 -errorinfo *bad code*subcommand "finalize"*}}

# --- === *** ###########################
# method read

test iortrans.tf-4.1 {chan read, transform call and return} -setup {
    set res {}
} -constraints {testchannel thread} -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return snarf
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [read $c 10]
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -match glob -result {{read rt* {test data
}} snarf}
test iortrans.tf-4.2 {chan read, for non-readable channel} -setup {
    set res {}
} -constraints {testchannel thread} -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args MUST_NOT_HAPPEN
    }
    set c [chan push [tempchan w] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {[read $c 2]} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -match glob -result {1 {channel "file*" wasn't opened for reading}}
test iortrans.tf-4.3 {chan read, error return} -setup {
    set res {}
} -constraints {testchannel thread} -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code error BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {read $c 2} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -match glob -result {{read rt* {test data
}} 1 BOOM!}
test iortrans.tf-4.4 {chan read, break return is error} -setup {
    set res {}
} -constraints {testchannel thread} -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code break BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {read $c 2} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -match glob -result {{read rt* {test data
}} 1 *bad code*}
test iortrans.tf-4.5 {chan read, continue return is error} -setup {
    set res {}
} -constraints {testchannel thread} -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code continue BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {read $c 2} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -match glob -result {{read rt* {test data
}} 1 *bad code*}
test iortrans.tf-4.6 {chan read, custom return is error} -setup {
    set res {}
} -constraints {testchannel thread} -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code 777 BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {read $c 2} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -match glob -result {{read rt* {test data
}} 1 *bad code*}
test iortrans.tf-4.7 {chan read, level is squashed} -setup {
    set res {}
} -constraints {testchannel thread} -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -level 55 -code 777 BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {read $c 2} msg opt] $msg
	noteOpts $opt
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -match glob -result {{read rt* {test data
}} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline 1 -errorinfo *bad code*subcommand "read"*}}

# --- === *** ###########################
# method write

test iortrans.tf-5.1 {chan write, regular write} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return transformresult
    }
    set c [chan push [tempchan] foo]
    inthread $c {
	puts -nonewline $c snarf
	flush $c
	close $c
    } c
    lappend res [tempview]
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarf} transformresult}
test iortrans.tf-5.2 {chan write, no write is ok, no change to file} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return
    }
    set c [chan push [tempchan] foo]
    inthread $c {
	puts -nonewline $c snarfsnarfsnarf
	flush $c
	close $c
    } c
    lappend res [tempview];	# This has to show the original data, as nothing was written
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} {test data}}
test iortrans.tf-5.3 {chan write, failed write} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code error FAIL!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	puts -nonewline $c snarfsnarfsnarf
	lappend notes [catch {flush $c} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 FAIL!}
test iortrans.tf-5.4 {chan write, non-writable channel} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args MUST_NOT_HAPPEN
	return
    }
    set c [chan push [tempchan r] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {
	    puts -nonewline $c snarfsnarfsnarf
	    flush $c
	} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -result {1 {channel "file*" wasn't opened for writing}}
test iortrans.tf-5.5 {chan write, failed write, error return} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code error BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {
	    puts -nonewline $c snarfsnarfsnarf
	    flush $c
	} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 BOOM!}
test iortrans.tf-5.6 {chan write, failed write, error return} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	error BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {
	    puts -nonewline $c snarfsnarfsnarf
	    flush $c
	} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 BOOM!}
test iortrans.tf-5.7 {chan write, failed write, break return is error} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code break BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {
	    puts -nonewline $c snarfsnarfsnarf
	    flush $c
	} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*}
test iortrans.tf-5.8 {chan write, failed write, continue return is error} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code continue BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {
	    puts -nonewline $c snarfsnarfsnarf
	    flush $c
	} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*}
test iortrans.tf-5.9 {chan write, failed write, custom return is error} -setup {
    set res {}
} -constraints {testchannel thread} -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -code 777 BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {
	    puts -nonewline $c snarfsnarfsnarf
	    flush $c
	} msg] $msg
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -match glob -result {{write rt* snarfsnarfsnarf} 1 *bad code*}
test iortrans.tf-5.10 {chan write, failed write, level is ignored} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize
	handle.finalize
	lappend ::res $args
	return -level 55 -code 777 BOOM!
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [catch {
	    puts -nonewline $c snarfsnarfsnarf
	    flush $c
	} msg opt] $msg
	noteOpts $opt
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{write rt* snarfsnarfsnarf} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline * -errorinfo *bad code*subcommand "write"*}}

# --- === *** ###########################
# method limit?, drain (via read)

test iortrans.tf-6.1 {chan read, read limits} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize limit?
	handle.finalize
	lappend ::res $args
	handle.read
	return 6
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [read $c 10]
	close $c
	notes
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{limit? rt*} {read rt* {test d}} {limit? rt*} {read rt* {ata
}} {limit? rt*} @@}
test iortrans.tf-6.2 {chan read, read transform drain on eof} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize drain
	handle.finalize
	lappend ::res $args
	handle.read
	handle.drain
	return
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	lappend notes [read $c]
	lappend notes [close $c]
    } c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} {drain rt*} @<> {}}

# --- === *** ###########################
# method clear (via puts, seek)

test iortrans.tf-7.1 {chan write, write clears read buffers} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize clear
	handle.finalize
	lappend ::res $args
	handle.clear
	return transformresult
    }
    set c [chan push [tempchan] foo]
    inthread $c {
	puts -nonewline $c snarf
	flush $c
	close $c
    } c
    return $res
} -cleanup {
    tempdone
    rename foo {}
} -result {{clear rt*} {write rt* snarf}}
test iortrans.tf-7.2 {seek clears read buffers} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize clear
	handle.finalize
	lappend ::res $args
	return
    }
    set c [chan push [tempchan] foo]
    inthread $c {
	seek $c 2
	close $c
    } c
    return $res
} -cleanup {
    tempdone
    rename foo {}
} -result {{clear rt*}}
test iortrans.tf-7.3 {clear, any result is ignored} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize clear
	handle.finalize
	lappend ::res $args
	return -code error "X"
    }
    set c [chan push [tempchan] foo]
    inthread $c {
	seek $c 2
	close $c
    } c
    return $res
} -cleanup {
    tempdone
    rename foo {}
} -result {{clear rt*}}

# --- === *** ###########################
# method flush (via seek, close)

test iortrans.tf-8.1 {seek flushes write buffers, ignores data} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize flush
	handle.finalize
	lappend ::res $args
	return X
    }
    set c [chan push [tempchan] foo]
    lappend res {*}[inthread $c {
	# Flush, no writing
	seek $c 2
	# The close flushes again, this modifies the file!
	lappend notes | [close $c] |
	# NOTE: The flush generated by the close is recorded immediately, the
	# other note's here are defered until after the thread is done. This
	# changes the order of the result a bit from the non-threaded case
	# (The first | moves one to the right). This is an artifact of the
	# 'inthread' framework, not of the transformation itself.
	notes
    } c]
    lappend res [tempview]
} -cleanup {
    tempdone
    rename foo {}
} -result {{flush rt*} {flush rt*} | {} | {teXt data}}
test iortrans.tf-8.2 {close flushes write buffers, writes data} -setup {
    set res {}
} -constraints {testchannel thread} -match glob -body {
    proc foo {args} {
	handle.initialize flush
	lappend ::res $args
	handle.finalize
	return .flushed.
    }
    set c [chan push [tempchan] foo]
    inthread $c {
	close $c
    } c
    lappend res [tempview]
} -cleanup {
    tempdone
    rename foo {}
} -result {{flush rt*} {finalize rt*} .flushed.}

# --- === *** ###########################
# method watch - removed from TIP (rev 1.12+)

# --- === *** ###########################
# method event - removed from TIP (rev 1.12+)

# --- === *** ###########################
# 'Pull the rug' tests. Create channel in a thread A, move to other thread B,
# destroy the origin thread (A) before or during access from B. Must not
# crash, must return proper errors.

test iortrans.tf-11.0 {origin thread of moved transform gone} -setup {
    #puts <<$tcltest::mainThread>>main
    set tida [thread::create -preserved];	#puts <<$tida>>
    thread::send $tida {load {} Tcltest}
    set tidb [thread::create -preserved];	#puts <<$tida>>
    thread::send $tidb {load {} Tcltest}
} -constraints {testchannel thread} -match glob -body {
    # Set up channel in thread
    thread::send $tida $helperscript
    thread::send $tidb $helperscript
    set chan [thread::send $tida {
	proc foo {args} {
	    handle.initialize clear drain flush limit? read write
	    handle.finalize
	    lappend ::res $args
	    return
	}
	set chan [chan push [tempchan] foo]
	fconfigure $chan -buffering none
	set chan
    }]

    # Move channel to 2nd thread, transform goes with it.
    thread::send $tida [list testchannel cut $chan]
    thread::send $tidb [list testchannel splice $chan]

    # Kill origin thread, then access channel from 2nd thread.
    thread::release -wait $tida

    set res {}
    lappend res [catch {thread::send $tidb [list puts $chan shoo]} msg] $msg
    lappend res [catch {thread::send $tidb [list tell $chan]} msg] $msg
    lappend res [catch {thread::send $tidb [list seek $chan 1]} msg] $msg
    lappend res [catch {thread::send $tidb [list gets $chan]} msg] $msg
    lappend res [catch {thread::send $tidb [list close $chan]} msg] $msg
    # The 'tell' is ok, as it passed through the transform to the base
    # channel without invoking the transform handler.
} -cleanup {
    thread::send $tidb tempdone
    thread::release $tidb
} -result {1 {Owner lost} 0 0 1 {Owner lost} 1 {Owner lost} 1 {Owner lost}}

testConstraint notValgrind [expr {![testConstraint valgrind]}]

test iortrans.tf-11.1 {origin thread of moved transform destroyed during access} -setup {
    #puts <<$tcltest::mainThread>>main
    set tida [thread::create -preserved];	#puts <<$tida>>
    thread::send $tida {load {} Tcltest}
    set tidb [thread::create -preserved];	#puts <<$tidb>>
    thread::send $tidb {load {} Tcltest}
} -constraints {testchannel thread notValgrind} -match glob -body {
    # Set up channel in thread
    thread::send $tida $helperscript
    thread::send $tidb $helperscript
    set chan [thread::send $tida {
	proc foo {args} {
	    handle.initialize clear drain flush limit? read write
	    handle.finalize
	    lappend ::res $args
	    # destroy thread during channel access
	    thread::exit
	}
	set chan [chan push [tempchan] foo]
	fconfigure $chan -buffering none
	set chan
    }]

    # Move channel to 2nd thread, transform goes with it.
    thread::send $tida [list testchannel cut $chan]
    thread::send $tidb [list testchannel splice $chan]

    # Run access from thread B, wait for response from A (A is not using event
    # loop at this point, so the event pile up in the queue.
    thread::send $tidb [list set chan $chan]
    thread::send $tidb [list set mid [thread::id]]
    thread::send -async $tidb {
	# Wait a bit, give the main thread the time to start its event loop to
	# wait for the response from B
	after 50
	catch { puts $chan shoo } res
	catch { close $chan }
	thread::send -async $mid [list set ::res $res]
    }
    vwait ::res
    set res
} -cleanup {
    thread::send $tidb tempdone
    thread::release $tidb
} -result {Owner lost}

# ### ### ### ######### ######### #########

cleanupTests
return
