# -*- 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 # # # 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.8 2010/03/17 16:35:42 andreas_kupries 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