# -*- 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.
#
# RCS: @(#) $Id: ioTrans.test,v 1.2.2.6 2010/03/17 17:26:33 dgp Exp $
if {[lsearch [namespace children] ::tcltest] == -1} {
package require tcltest 2
namespace import -force ::tcltest::*
}
# Custom constraints used in this file
testConstraint testchannel [llength [info commands testchannel]]
testConstraint testthread [llength [info commands testthread]]
# testchannel cut|splice Both needed to test the reflection in threads.
# testthread 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 {[lsearch [namespace children] ::tcltest] == -1} {
package require tcltest 2
namespace import -force ::tcltest::*
}
proc note {item} {global res; lappend res $item; return}
#proc note {item} {global res; lappend res $item; puts $item ; flush stdout ; return}
proc track {} {upvar args item; note $item; return}
proc notes {items} {foreach i $items {note $i}}
# Use to prevent *'s in pattern to match beyond the expected end
# of the recording.
proc endnote {} {note |}
# This forces the return options to be in the order that the test
# expects!
proc noteOpts opts {global res; lappend res [dict merge {
-code !?! -level !?! -errorcode !?! -errorline !?! -errorinfo !?!
} $opts]; return}
# Helper command, canned result for 'initialize' method. Gets the
# optional methods as arguments. Use return features to post the
# result higher up.
proc init {args} {
lappend args initialize finalize read write
return -code return $args
}
proc oninit {args} {
upvar args hargs
if {[lindex $hargs 0] ne "initialize"} {return}
lappend args initialize finalize read write
return -code return $args
}
proc onfinal {} {
upvar args hargs
if {[lindex $hargs 0] ne "finalize"} {return}
return -code return ""
}
proc onread {} {
upvar args hargs
if {[lindex $hargs 0] ne "read"} {return}
return -code return "@"
}
proc ondrain {} {
upvar args hargs
if {[lindex $hargs 0] ne "drain"} {return}
return -code return "<>"
}
proc onclear {} {
upvar args hargs
if {[lindex $hargs 0] ne "clear"} {return}
return -code return ""
}
proc tempchan {{mode r+}} {
global tempchan
set tempchan [open [makeFile {test data} tempchanfile] $mode]
return $tempchan
}
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} {
catch {chan} msg
set msg
} {wrong # args: should be "chan subcommand ?arg ...?"}
test iortrans-1.1 {chan, unknown method} -body {
chan foo
} -returnCodes error -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} {
catch {chan push} msg
set msg
} {wrong # args: should be "chan push channel cmdprefix"}
test iortrans-2.1 {chan push, wrong#args, too many} {
catch {chan push a b c} msg
set msg
} {wrong # args: should be "chan push channel cmdprefix"}
test iortrans-2.2 {chan push, invalid channel} {
proc foo {} {}
catch {chan push {} foo} msg
rename foo {}
set msg
} {can not find channel named ""}
test iortrans-2.3 {chan push, bad handler, not a list} {
catch {chan push [tempchan] "foo \{"} msg
tempdone
set msg
} {unmatched open brace in list}
test iortrans-2.4 {chan push, bad handler, not a command} {
catch {chan push [tempchan] foo} msg
tempdone
set msg
} {invalid command name "foo"}
test iortrans-2.5 {chan push, initialize failed, bad signature} {
proc foo {} {}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} {wrong # args: should be "foo"}
test iortrans-2.6 {chan push, initialize failed, bad signature} {
proc foo {} {}
catch {chan push [tempchan] ::foo} msg
tempdone
rename foo {}
set msg
} {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} msg
tempdone
rename foo {}
set ::errorInfo
} -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 \{\{\}}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} -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} {}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} -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}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} -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}}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} -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}}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} -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}}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} -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}}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} -match glob -result {*makes the channel inacessible}
# 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}}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} -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}}
catch {chan push [tempchan] foo} msg
tempdone
rename foo {}
set msg
} -match glob -result {*supports "flush" but not "write"}
test iortrans-2.19 {chan push, initialize ok, creates channel} -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}
}
set res {}
lappend res [file channel rt*]
lappend res [chan push [tempchan] foo]
lappend res [close [lindex $res end]]
lappend res [file channel rt*]
tempdone
rename foo {}
set res
} -result {{} {initialize rt* {read write}} file* {drain rt*} {flush rt*} {finalize rt*} {} {}}
test iortrans-2.20 {chan push, init failure -> no channel, no finalize} -match glob -body {
proc foo {args} {
global res
lappend res $args
return {}
}
set res {}
lappend res [file channel rt*]
lappend res [catch {chan push [tempchan] foo} msg]
lappend res $msg
lappend res [file channel rt*]
tempdone
rename foo {}
set res
} -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} -match glob -body {
set res {}
proc foo {args} {track; oninit; return}
note [set c [chan push [tempchan] foo]]
rename foo {}
note [file channels file*]
note [file channels rt*]
note [catch {close $c} msg]; note $msg
note [file channels file*]
note [file channels rt*]
set res
} -result {{initialize rt* {read write}} file* file* {} 1 {invalid command name "foo"} {} {}}
test iortrans-3.2 {chan finalize, for close} -match glob -body {
set res {}
proc foo {args} {track; oninit; return {}}
note [set c [chan push [tempchan] foo]]
close $c
# Close deleted the channel.
note [file channels rt*]
# Channel destruction does not kill handler command!
note [info command foo]
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} {} foo}
test iortrans-3.3 {chan finalize, for close, error, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; return -code error 5}
note [set c [chan push [tempchan] foo]]
note [catch {close $c} msg]; note $msg
# Channel is gone despite error.
note [file channels rt*]
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 5 {}}
test iortrans-3.4 {chan finalize, for close, error, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; error FOO}
note [set c [chan push [tempchan] foo]]
note [catch {close $c} msg]; note $msg; note $::errorInfo
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 FOO {FOO
*"close $c"}}
test iortrans-3.5 {chan finalize, for close, arbitrary result, ignored} -match glob -body {
set res {}
proc foo {args} {track; oninit; return SOMETHING}
note [set c [chan push [tempchan] foo]]
note [catch {close $c} msg]; note $msg
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 0 {}}
test iortrans-3.6 {chan finalize, for close, break, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; return -code 3}
note [set c [chan push [tempchan] foo]]
note [catch {close $c} msg]; note $msg
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*}
test iortrans-3.7 {chan finalize, for close, continue, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; return -code 4}
note [set c [chan push [tempchan] foo]]
note [catch {close $c} msg]; note $msg
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*}
test iortrans-3.8 {chan finalize, for close, custom code, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; return -code 777 BANG}
note [set c [chan push [tempchan] foo]]
note [catch {close $c} msg]; note $msg
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*}
test iortrans-3.9 {chan finalize, for close, ignore level, close error} -match glob -setup {
set res {}
} -body {
proc foo {args} {track; oninit; return -level 5 -code 777 BANG}
note [set c [chan push [tempchan] foo]]
note [catch {close $c} msg opt]; note $msg; noteOpts $opt
return $res
} -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 (via read)
test iortrans-4.1 {chan read, transform call and return} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return snarf
}
set c [chan push [tempchan] foo]
note [read $c 10]
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} snarf}
test iortrans-4.2 {chan read, for non-readable channel} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track; note MUST_NOT_HAPPEN
}
set c [chan push [tempchan w] foo]
note [catch {read $c 2} msg]; note $msg
tempdone
rename foo {}
set res
} -result {1 {channel "file*" wasn't opened for reading}}
test iortrans-4.3 {chan read, error return} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -code error BOOM!
}
set c [chan push [tempchan] foo]
note [catch {read $c 2} msg]; note $msg
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} 1 BOOM!}
test iortrans-4.4 {chan read, break return is error} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -code break BOOM!
}
set c [chan push [tempchan] foo]
note [catch {read $c 2} msg]; note $msg
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} 1 *bad code*}
test iortrans-4.5 {chan read, continue return is error} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -code continue BOOM!
}
set c [chan push [tempchan] foo]
note [catch {read $c 2} msg]; note $msg
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} 1 *bad code*}
test iortrans-4.6 {chan read, custom return is error} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -code 777 BOOM!
}
set c [chan push [tempchan] foo]
note [catch {read $c 2} msg]; note $msg
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} 1 *bad code*}
test iortrans-4.7 {chan read, level is squashed} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -level 55 -code 777 BOOM!
}
set c [chan push [tempchan] foo]
note [catch {read $c 2} msg opt]; note $msg; noteOpts $opt
tempdone
rename foo {}
set res
} -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} -match glob -setup {
set res {}
proc foo {fd args} {
oninit; onfinal; track
# 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]]
} -body {
note [read $c]
#note [gets $c]
set res
} -cleanup {
tempdone
rename foo {}
} -result {{read rt* {test data
}} file*}
test iortrans-4.9 {chan read, gets, bug 2921116} -match glob -setup {
set res {}
proc foo {fd args} {
oninit; onfinal; track
# 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]]
} -body {
note [gets $c]
set res
} -cleanup {
tempdone
rename foo {}
} -result {{read rt* {test data
}} file*}
# --- === *** ###########################
# method write (via puts)
test iortrans-5.1 {chan write, regular write} -match glob -body {
set res {}
proc foo {args} { oninit; onfinal; track ; return transformresult }
set c [chan push [tempchan] foo]
puts -nonewline $c snarf; flush $c
close $c
note [tempview]
tempdone
rename foo {}
set res
} -result {{write rt* snarf} transformresult}
test iortrans-5.2 {chan write, no write is ok, no change to file} -match glob -body {
set res {}
proc foo {args} { oninit; onfinal; track ; return {} }
set c [chan push [tempchan] foo]
puts -nonewline $c snarfsnarfsnarf; flush $c
close $c
note [tempview];# This has to show the original data, as nothing was written
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} {test data}}
test iortrans-5.3 {chan write, failed write} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code error FAIL!}
set c [chan push [tempchan] foo]
puts -nonewline $c snarfsnarfsnarf
note [catch {flush $c} msg] ; note $msg
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 FAIL!}
test iortrans-5.4 {chan write, non-writable channel} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; note MUST_NOT_HAPPEN; return}
set c [chan push [tempchan r] foo]
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]; note $msg
close $c
tempdone
rename foo {}
set res
} -result {1 {channel "file*" wasn't opened for writing}}
test iortrans-5.5 {chan write, failed write, error return} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code error BOOM!}
set c [chan push [tempchan] foo]
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 BOOM!}
test iortrans-5.6 {chan write, failed write, error return} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; error BOOM!}
set c [chan push [tempchan] foo]
notes [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 BOOM!}
test iortrans-5.7 {chan write, failed write, break return is error} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code break BOOM!}
set c [chan push [tempchan] foo]
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*}
test iortrans-5.8 {chan write, failed write, continue return is error} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code continue BOOM!}
set c [chan push [tempchan] foo]
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*}
test iortrans-5.9 {chan write, failed write, custom return is error} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code 777 BOOM!}
set c [chan push [tempchan] foo]
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*}
test iortrans-5.10 {chan write, failed write, level is ignored} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -level 55 -code 777 BOOM!}
set c [chan push [tempchan] foo]
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg opt]
note $msg
noteOpts $opt
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline 1 -errorinfo *bad code*subcommand "write"*}}
test iortrans-5.11 {chan write, bug 2921116} -match glob -setup {
set res {}
set level 0
proc foo {fd args} {
oninit; onfinal; track
# 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]]
} -body {
note [puts -nonewline $c abcdef]
note [flush $c]
set res
} -cleanup {
tempdone
rename foo {}
} -result {{} {write rt* abcdef} {write rt* abcdef} {}}
# --- === *** ###########################
# method limit?, drain (via read)
test iortrans-6.1 {chan read, read limits} -match glob -body {
set res {}
proc foo {args} {
oninit limit?; onfinal; track ; onread
return 6
}
set c [chan push [tempchan] foo]
note [read $c 10]
tempdone
rename foo {}
set res
} -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} -match glob -body {
set res {}
proc foo {args} {
oninit drain; onfinal; track ; onread ; ondrain
return
}
set c [chan push [tempchan] foo]
note [read $c]
note [close $c]
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} {drain rt*} @<> {}}
# --- === *** ###########################
# method clear (via puts, seek)
test iortrans-7.1 {chan write, write clears read buffers} -match glob -body {
set res {}
proc foo {args} {
oninit clear; onfinal; track ; onclear
return transformresult
}
set c [chan push [tempchan] foo]
puts -nonewline $c snarf; flush $c
tempdone
rename foo {}
set res
} -result {{clear rt*} {write rt* snarf}}
test iortrans-7.2 {seek clears read buffers} -match glob -body {
set res {}
proc foo {args} {
oninit clear; onfinal; track
return
}
set c [chan push [tempchan] foo]
seek $c 2
tempdone
rename foo {}
set res
} -result {{clear rt*}}
test iortrans-7.3 {clear, any result is ignored} -match glob -body {
set res {}
proc foo {args} {
oninit clear; onfinal; track
return -code error "X"
}
set c [chan push [tempchan] foo]
seek $c 2
tempdone
rename foo {}
set res
} -result {{clear rt*}}
test iortrans-7.4 {chan clear, bug 2921116} -match glob -setup {
set res {}
proc foo {fd args} {
oninit clear; onfinal; track
# 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]]
} -body {
seek $c 2
set res
} -cleanup {
tempdone
rename foo {}
} -result {{clear rt*}}
# --- === *** ###########################
# method flush (via seek, close)
test iortrans-8.1 {seek flushes write buffers, ignores data} -match glob -body {
set res {}
proc foo {args} {
oninit flush; onfinal; track
return X
}
set c [chan push [tempchan] foo]
# Flush, no writing
seek $c 2
# The close flushes again, this modifies the file!
note | ; note [close $c] ; note |
note [tempview]
tempdone
rename foo {}
set res
} -result {{flush rt*} | {flush rt*} {} | {teXt data}}
test iortrans-8.2 {close flushes write buffers, writes data} -match glob -body {
set res {}
proc foo {args} {
oninit flush; track ; onfinal
return .flushed.
}
set c [chan push [tempchan] foo]
close $c
note [tempview]
tempdone
rename foo {}
set res
} -result {{flush rt*} {finalize rt*} .flushed.}
test iortrans-8.3 {chan flush, bug 2921116} -match glob -setup {
set res {}
proc foo {fd args} {
oninit flush; onfinal; track
# 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]]
} -body {
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} -match glob -body {
set ida [interp create];#puts <<$ida>>
set idb [interp create];#puts <<$idb>>
# Magic to get the test* commands in the slaves
load {} Tcltest $ida
load {} Tcltest $idb
# 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} {oninit clear drain flush limit? read write; onfinal; track; 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
lappend res [catch {interp eval $idb [list tell $chan]} msg] $msg
lappend res [catch {interp eval $idb [list seek $chan 1]} msg] $msg
lappend res [catch {interp eval $idb [list gets $chan]} msg] $msg
lappend res [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
tempdone
set res
# The 'tell' is ok, as it passed through the transform to the base
# channel without invoking the transform handler.
} -constraints {testchannel} \
-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} -match glob -body {
set ida [interp create];#puts <<$ida>>
set idb [interp create];#puts <<$idb>>
# Magic to get the test* commands in the slaves
load {} Tcltest $ida
load {} Tcltest $idb
# Set up channel in thread
set chan [interp eval $ida $helperscript]
set chan [interp eval $ida {
proc foo {args} {
oninit clear drain flush limit? read write; onfinal; track;
# destroy interpreter during channel access
# Actually not possible for an interp to destroy itself.
interp delete {}
return}
set chan [chan push [tempchan] foo]
fconfigure $chan -buffering none
set chan
}]
# 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 2000
catch { puts $chan shoo } res
set res
}]
tempdone
set res
} -constraints {testchannel impossible} \
-result {Owner lost}
# ### ### ### ######### ######### #########
## Same tests as above, but exercising the code forwarding and
## receiving driver operations to the originator thread.
# -*- tcl -*-
# ### ### ### ######### ######### #########
## 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.
# Duplicate of code in "thread.test", and "ioCmd.test". Find a better
# way of doing this without duplication. Maybe placement into a proc
# which transforms to nop after the first call, and placement of its
# defintion in a central location.
if {[testConstraint testthread]} {
testthread errorproc ThreadError
proc ThreadError {id info} {
global threadError
set threadError $info
}
proc ThreadNullError {id info} {
# ignore
}
}
# ### ### ### ######### ######### #########
## 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 [testthread create]
# Init thread configuration.
# - Listed variables
# - Id of main thread
# - A number of helper commands
foreach v $args {
upvar 1 $v x
testthread send $tid [list set $v $x]
}
testthread send $tid [list set mid $tcltest::mainThread]
testthread send $tid {
proc note {item} {global notes; lappend notes $item}
proc notes {} {global notes; return $notes}
proc noteOpts opts {global notes; lappend notes [dict merge {
-code !?! -level !?! -errorcode !?! -errorline !?! -errorinfo !?!
} $opts]}
}
testthread send $tid [list proc s {} [list uplevel 1 $script]]; # (*)
# Transfer channel (cut/splice aka detach/attach)
testchannel cut $chan
testthread 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 ""
testthread send -async $tid {
after 500
catch {s} res; # This runs the script, 's' was defined at (*)
testthread send -async $mid [list set ::tres $res]
}
vwait ::tres
# Remove test thread, and return the captured result.
tcltest::threadReap
return $::tres
}
# ### ### ### ######### ######### #########
# ### ### ### ######### ######### #########
test iortrans.tf-3.2 {chan finalize, for close} -match glob -body {
set res {}
proc foo {args} {track; oninit; return {}}
note [set c [chan push [tempchan] foo]]
note [inthread $c {
close $c
# Close the deleted the channel.
file channels rt*
} c]
# Channel destruction does not kill handler command!
note [info command foo]
rename foo {}
set res
} -constraints {testchannel testthread} \
-result {{initialize rt* {read write}} file* {finalize rt*} {} foo}
test iortrans.tf-3.3 {chan finalize, for close, error, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; return -code error 5}
note [set c [chan push [tempchan] foo]]
notes [inthread $c {
note [catch {close $c} msg]; note $msg
# Channel is gone despite error.
note [file channels rt*]
notes
} c]
rename foo {}
set res
} -constraints {testchannel testthread} \
-result {{initialize rt* {read write}} file* {finalize rt*} 1 5 {}}
test iortrans.tf-3.4 {chan finalize, for close, error, close errror} -match glob -body {
set res {}
proc foo {args} {track; oninit; error FOO}
note [set c [chan push [tempchan] foo]]
notes [inthread $c {
note [catch {close $c} msg]; note $msg
notes
} c]
rename foo {}
set res
} -constraints {testchannel testthread} \
-result {{initialize rt* {read write}} file* {finalize rt*} 1 FOO}
test iortrans.tf-3.5 {chan finalize, for close, arbitrary result} -match glob -body {
set res {}
proc foo {args} {track; oninit; return SOMETHING}
note [set c [chan push [tempchan] foo]]
notes [inthread $c {
note [catch {close $c} msg]; note $msg
notes
} c]
rename foo {}
set res
} -constraints {testchannel testthread} \
-result {{initialize rt* {read write}} file* {finalize rt*} 0 {}}
test iortrans.tf-3.6 {chan finalize, for close, break, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; return -code 3}
note [set c [chan push [tempchan] foo]]
notes [inthread $c {
note [catch {close $c} msg]; note $msg
notes
} c]
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*} \
-constraints {testchannel testthread}
test iortrans.tf-3.7 {chan finalize, for close, continue, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; return -code 4}
note [set c [chan push [tempchan] foo]]
notes [inthread $c {
note [catch {close $c} msg]; note $msg
notes
} c]
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*} \
-constraints {testchannel testthread}
test iortrans.tf-3.8 {chan finalize, for close, custom code, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; return -code 777 BANG}
note [set c [chan push [tempchan] foo]]
notes [inthread $c {
note [catch {close $c} msg]; note $msg
notes
} c]
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code*} \
-constraints {testchannel testthread}
test iortrans.tf-3.9 {chan finalize, for close, ignore level, close error} -match glob -body {
set res {}
proc foo {args} {track; oninit; return -level 5 -code 777 BANG}
note [set c [chan push [tempchan] foo]]
notes [inthread $c {
note [catch {close $c} msg opt]; note $msg; noteOpts $opt
notes
} c]
rename foo {}
set res
} -result {{initialize rt* {read write}} file* {finalize rt*} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline 1 -errorinfo *bad code*subcommand "finalize"*}} \
-constraints {testchannel testthread}
# --- === *** ###########################
# method read
test iortrans.tf-4.1 {chan read, transform call and return} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return snarf
}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [read $c 10]
close $c
notes
} c]
tempdone
rename foo {}
set res
} -constraints {testchannel testthread} -result {{read rt* {test data
}} snarf}
test iortrans.tf-4.2 {chan read, for non-readable channel} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track; note MUST_NOT_HAPPEN
}
set c [chan push [tempchan w] foo]
notes [inthread $c {
note [catch {[read $c 2]} msg]; note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -constraints {testchannel testthread} -result {1 {channel "file*" wasn't opened for reading}}
test iortrans.tf-4.3 {chan read, error return} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -code error BOOM!
}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {read $c 2} msg]; note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} 1 BOOM!} \
-constraints {testchannel testthread}
test iortrans.tf-4.4 {chan read, break return is error} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -code break BOOM!
}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {read $c 2} msg]; note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} 1 *bad code*} \
-constraints {testchannel testthread}
test iortrans.tf-4.5 {chan read, continue return is error} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -code continue BOOM!
}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {read $c 2} msg]; note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} 1 *bad code*} \
-constraints {testchannel testthread}
test iortrans.tf-4.6 {chan read, custom return is error} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -code 777 BOOM!
}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {read $c 2} msg]; note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} 1 *bad code*} \
-constraints {testchannel testthread}
test iortrans.tf-4.7 {chan read, level is squashed} -match glob -body {
set res {}
proc foo {args} {
oninit; onfinal; track
return -level 55 -code 777 BOOM!
}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {read $c 2} msg opt]; note $msg; noteOpts $opt
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline 1 -errorinfo *bad code*subcommand "read"*}} \
-constraints {testchannel testthread}
# --- === *** ###########################
# method write
test iortrans.tf-5.1 {chan write, regular write} -match glob -body {
set res {}
proc foo {args} { oninit; onfinal; track ; return transformresult }
set c [chan push [tempchan] foo]
inthread $c {
puts -nonewline $c snarf; flush $c
close $c
} c
note [tempview]
tempdone
rename foo {}
set res
} -constraints {testchannel testthread} -result {{write rt* snarf} transformresult}
test iortrans.tf-5.2 {chan write, no write is ok, no change to file} -match glob -body {
set res {}
proc foo {args} { oninit; onfinal; track ; return {} }
set c [chan push [tempchan] foo]
inthread $c {
puts -nonewline $c snarfsnarfsnarf; flush $c
close $c
} c
note [tempview];# This has to show the original data, as nothing was written
tempdone
rename foo {}
set res
} -constraints {testchannel testthread} \
-result {{write rt* snarfsnarfsnarf} {test data}}
test iortrans.tf-5.3 {chan write, failed write} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code error FAIL!}
set c [chan push [tempchan] foo]
notes [inthread $c {
puts -nonewline $c snarfsnarfsnarf
note [catch {flush $c} msg]
note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -constraints {testchannel testthread} \
-result {{write rt* snarfsnarfsnarf} 1 FAIL!}
test iortrans.tf-5.4 {chan write, non-writable channel} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; note MUST_NOT_HAPPEN; return}
set c [chan push [tempchan r] foo]
notes [inthread $c {
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -constraints {testchannel testthread} \
-result {1 {channel "file*" wasn't opened for writing}}
test iortrans.tf-5.5 {chan write, failed write, error return} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code error BOOM!}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 BOOM!} \
-constraints {testchannel testthread}
test iortrans.tf-5.6 {chan write, failed write, error return} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; error BOOM!}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 BOOM!} \
-constraints {testchannel testthread}
test iortrans.tf-5.7 {chan write, failed write, break return is error} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code break BOOM!}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*} \
-constraints {testchannel testthread}
test iortrans.tf-5.8 {chan write, failed write, continue return is error} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code continue BOOM!}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
close $c
notes
} c]
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*} \
-constraints {testchannel testthread}
test iortrans.tf-5.9 {chan write, failed write, custom return is error} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -code 777 BOOM!}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg]
note $msg
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 *bad code*} \
-constraints {testchannel testthread}
test iortrans.tf-5.10 {chan write, failed write, level is ignored} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return -level 55 -code 777 BOOM!}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [catch {puts -nonewline $c snarfsnarfsnarf; flush $c} msg opt]
note $msg
noteOpts $opt
close $c
notes
} c]
tempdone
rename foo {}
set res
} -result {{write rt* snarfsnarfsnarf} 1 *bad code* {-code 1 -level 0 -errorcode NONE -errorline 1 -errorinfo *bad code*subcommand "write"*}} \
-constraints {testchannel testthread}
# --- === *** ###########################
# method limit?, drain (via read)
test iortrans.tf-6.1 {chan read, read limits} -match glob -body {
set res {}
proc foo {args} {
oninit limit?; onfinal; track ; onread
return 6
}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [read $c 10]
close $c
set notes
} c]
tempdone
rename foo {}
set res
} -result {{limit? rt*} {read rt* {test d}} {limit? rt*} {read rt* {ata
}} {limit? rt*} @@} -constraints {testchannel testthread}
test iortrans.tf-6.2 {chan read, read transform drain on eof} -match glob -body {
set res {}
proc foo {args} {
oninit drain; onfinal; track ; onread ; ondrain
return
}
set c [chan push [tempchan] foo]
notes [inthread $c {
note [read $c]
note [close $c]
} c]
tempdone
rename foo {}
set res
} -result {{read rt* {test data
}} {drain rt*} @<> {}} -constraints {testchannel testthread}
# --- === *** ###########################
# method clear (via puts, seek)
test iortrans.tf-7.1 {chan write, write clears read buffers} -match glob -body {
set res {}
proc foo {args} {
oninit clear; onfinal; track ; onclear
return transformresult
}
set c [chan push [tempchan] foo]
inthread $c {
puts -nonewline $c snarf; flush $c
close $c
} c
tempdone
rename foo {}
set res
} -result {{clear rt*} {write rt* snarf}} -constraints {testchannel testthread}
test iortrans.tf-7.2 {seek clears read buffers} -match glob -body {
set res {}
proc foo {args} {
oninit clear; onfinal; track
return
}
set c [chan push [tempchan] foo]
inthread $c {
seek $c 2
close $c
} c
tempdone
rename foo {}
set res
} -result {{clear rt*}} -constraints {testchannel testthread}
test iortrans.tf-7.3 {clear, any result is ignored} -match glob -body {
set res {}
proc foo {args} {
oninit clear; onfinal; track
return -code error "X"
}
set c [chan push [tempchan] foo]
inthread $c {
seek $c 2
close $c
} c
tempdone
rename foo {}
set res
} -result {{clear rt*}} -constraints {testchannel testthread}
# --- === *** ###########################
# method flush (via seek, close)
test iortrans.tf-8.1 {seek flushes write buffers, ignores data} -match glob -body {
set res {}
proc foo {args} {
oninit flush; onfinal; track
return X
}
set c [chan push [tempchan] foo]
notes [inthread $c {
# Flush, no writing
seek $c 2
# The close flushes again, this modifies the file!
note | ; note [close $c] ; note |
# 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]
note [tempview]
tempdone
rename foo {}
set res
} -result {{flush rt*} {flush rt*} | {} | {teXt data}} -constraints {testchannel testthread}
test iortrans.tf-8.2 {close flushes write buffers, writes data} -match glob -body {
set res {}
proc foo {args} {
oninit flush; track ; onfinal
return .flushed.
}
set c [chan push [tempchan] foo]
inthread $c {
close $c
} c
note [tempview]
tempdone
rename foo {}
set res
} -result {{flush rt*} {finalize rt*} .flushed.} -constraints {testchannel testthread}
# --- === *** ###########################
# 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} -match glob -body {
#puts <<$tcltest::mainThread>>main
set tida [testthread create];#puts <<$tida>>
set tidb [testthread create];#puts <<$tidb>>
# Set up channel in thread
testthread send $tida $helperscript
set chan [testthread send $tida {
proc foo {args} {oninit clear drain flush limit? read write; onfinal; track; return}
set chan [chan push [tempchan] foo]
fconfigure $chan -buffering none
set chan
}]
# Move channel to 2nd thread, transform goes with it.
testthread send $tida [list testchannel cut $chan]
testthread send $tidb [list testchannel splice $chan]
# Kill origin thread, then access channel from 2nd thread.
testthread send -async $tida {testthread exit}
after 100
set res {}
lappend res [catch {testthread send $tidb [list puts $chan shoo]} msg] $msg
lappend res [catch {testthread send $tidb [list tell $chan]} msg] $msg
lappend res [catch {testthread send $tidb [list seek $chan 1]} msg] $msg
lappend res [catch {testthread send $tidb [list gets $chan]} msg] $msg
lappend res [catch {testthread send $tidb [list close $chan]} msg] $msg
tcltest::threadReap
tempdone
set res
# The 'tell' is ok, as it passed through the transform to the base
# channel without invoking the transform handler.
} -constraints {testchannel testthread} \
-result {1 {Owner lost} 0 0 1 {Owner lost} 1 {Owner lost} 1 {Owner lost}}
test iortrans.tf-11.1 {origin thread of moved transform destroyed during access} -match glob -body {
#puts <<$tcltest::mainThread>>main
set tida [testthread create];#puts <<$tida>>
set tidb [testthread create];#puts <<$tidb>>
# Set up channel in thread
set chan [testthread send $tida $helperscript]
set chan [testthread send $tida {
proc foo {args} {
oninit clear drain flush limit? read write; onfinal; track;
# destroy thread during channel access
testthread exit
return}
set chan [chan push [tempchan] foo]
fconfigure $chan -buffering none
set chan
}]
# Move channel to 2nd thread, transform goes with it.
testthread send $tida [list testchannel cut $chan]
testthread 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.
testthread send $tidb [list set chan $chan]
testthread send $tidb [list set mid $tcltest::mainThread]
testthread 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 2000
catch { puts $chan shoo } res
catch { close $chan }
testthread send -async $mid [list set ::res $res]
}
vwait ::res
tcltest::threadReap
tempdone
set res
} -constraints {testchannel testthread} \
-result {Owner lost}
# ### ### ### ######### ######### #########
# ### ### ### ######### ######### #########
rename track {}
cleanupTests
return