# -*- tcl -*- # Commands covered: transform, and stacking in general # # This file contains a collection of tests for Giot # # See the file "license.terms" for information on usage and redistribution of # this file, and for a DISCLAIMER OF ALL WARRANTIES. # # Copyright © 2000 Ajuba Solutions. # Copyright © 2000 Andreas Kupries. # All rights reserved. if {"::tcltest" ni [namespace children]} { package require tcltest 2.5 namespace import -force ::tcltest::* } ::tcltest::loadTestedCommands catch [list package require -exact tcl::test [info patchlevel]] namespace eval ::tcl::test::iogt { namespace import ::tcltest::* testConstraint testchannel [llength [info commands testchannel]] set path(dummy) [makeFile {abcdefghijklmnopqrstuvwxyz0123456789,./?><;'\|":[]\}\{`~!@#$%^&*()_+-= } dummy] # " capture coloring of quotes set path(dummyout) [makeFile {} dummyout] set path(__echo_srv__.tcl) [makeFile { #!/usr/local/bin/tclsh # -*- tcl -*- # echo server # # arguments, options: port to listen on for connections. # delay till echo of first block # delay between blocks # blocksize ... set port [lindex $argv 0] set fdelay [lindex $argv 1] set idelay [lindex $argv 2] set bsizes [lrange $argv 3 end] set c 0 proc newconn {sock rhost rport} { variable fdelay variable c incr c namespace upvar [namespace current] c$c conn #puts stdout "C $sock $rhost $rport / $fdelay" ; flush stdout set conn(after) {} set conn(state) 0 set conn(size) 0 set conn(data) "" set conn(delay) $fdelay fileevent $sock readable [list echoGet $c $sock] fconfigure $sock -translation binary -buffering none -blocking 0 } proc echoGet {c sock} { variable fdelay namespace upvar [namespace current] c$c conn if {[eof $sock]} { # one-shot echo exit } append conn(data) [read $sock] #puts stdout "G $c $sock $conn(data) <<$conn(data)>>" ; flush stdout if {$conn(after) == {}} { set conn(after) [after $conn(delay) [list echoPut $c $sock]] } } proc echoPut {c sock} { variable idelay variable fdelay variable bsizes namespace upvar [namespace current] c$c conn if {[string length $conn(data)] == 0} { #puts stdout "C $c $sock" ; flush stdout # auto terminate close $sock exit #set conn(delay) $fdelay return } set conn(delay) $idelay set n [lindex $bsizes $conn(size)] #puts stdout "P $c $sock $n >>" ; flush stdout #puts __________________________________________ #parray conn #puts n=<$n> if {[string length $conn(data)] >= $n} { puts -nonewline $sock [string range $conn(data) 0 $n] set conn(data) [string range $conn(data) [incr n] end] } incr conn(size) if {$conn(size) >= [llength $bsizes]} { set conn(size) [expr {[llength $bsizes]-1}] } set conn(after) [after $conn(delay) [list echoPut $c $sock]] } #fileevent stdin readable {exit ;#cut} # main socket -server newconn -myaddr 127.0.0.1 $port vwait forever } __echo_srv__.tcl] ######################################################################## proc fevent {fdelay idelay blocks script data} { # Start and initialize an echo server, prepare data transmission, then # hand over to the test script. This has to start real transmission via # 'flush'. The server is stopped after completion of the test. upvar 1 sock sk # Fixed port, not so good. Lets hope for the best, for now. set port 4000 exec tclsh __echo_srv__.tcl $port $fdelay $idelay {*}$blocks >@stdout & after 500 #puts stdout "> $port"; flush stdout set sk [socket localhost $port] fconfigure $sk -blocking 0 -buffering full \ -buffersize [expr {10+[llength $data]}] puts -nonewline $sk $data # The channel is prepared to go off. #puts stdout ">>>>>"; flush stdout set res [uplevel 1 $script] catch {close $sk} return $res } # -------------------------------------------------------------- # utility transformations ... proc id {op data} { switch -- $op { create/write - create/read - delete/write - delete/read - clear_read { #ignore } flush/write - flush/read - write - read { return $data } query/maxRead { return -1 } } } proc id_optrail {var op data} { variable $var upvar 0 $var trail lappend trail $op switch -- $op { create/write - create/read - delete/write - delete/read - flush/read - clear/read { #ignore } flush/write - write - read { return $data } query/maxRead { return -1 } default { lappend trail "error $op" error $op } } } proc id_fulltrail {var op data} { namespace upvar [namespace current] $var trail #puts stdout ">> $var $op $data" ; flush stdout switch -- $op { create/write - create/read - delete/write - delete/read - clear_read { set res *ignored* } flush/write - flush/read - write - read { set res $data } query/maxRead { set res -1 } } #catch {puts stdout "\t>* $res" ; flush stdout} #catch {puts stdout "x$res"} msg lappend trail [list $op $data $res] return $res } proc id_torture {chan op data} { switch -- $op { create/write - create/read - delete/write - delete/read - clear_read {;#ignore} flush/write - flush/read {} write { global level if {$level} { return } incr level testchannel unstack $chan testchannel transform $chan \ -command [namespace code [list id_torture $chan]] return $data } read { testchannel unstack $chan testchannel transform $chan \ -command [namespace code [list id_torture $chan]] return $data } query/maxRead {return -1} } } proc counter {var op data} { namespace upvar [namespace current] $var n switch -- $op { create/write - create/read - delete/write - delete/read - clear_read { #ignore } flush/write - flush/read { return {} } write { return $data } read { if {$n > 0} { incr n -[string length $data] if {$n < 0} { set n 0 } } return $data } query/maxRead { return $n } } } proc counter_audit {var vtrail op data} { namespace upvar [namespace current] $var n $vtrail trail switch -- $op { create/write - create/read - delete/write - delete/read - clear_read { set res {} } flush/write - flush/read { set res {} } write { set res $data } read { if {$n > 0} { incr n -[string length $data] if {$n < 0} { set n 0 } } set res $data } query/maxRead { set res $n } } lappend trail [list counter:$op $data $res] return $res } proc rblocks {var vtrail n op data} { namespace upvar [namespace current] $var buf $vtrail trail set res {} switch -- $op { create/write - create/read - delete/write - delete/read - clear_read { set buf {} } flush/write { } flush/read { set res $buf set buf {} } write { set data } read { append buf $data set b [expr {$n * ([string length $buf] / $n)}] append op " $n [string length $buf] :- $b" set res [string range $buf 0 [incr b -1]] set buf [string range $buf [incr b] end] #return $res } query/maxRead { set res -1 } } lappend trail [list rblock | $op $data $res | $buf] return $res } # -------------------------------------------------------------- # ... and convenience procedures to stack them proc identity {-attach channel} { testchannel transform $channel -command [namespace code id] } proc audit_ops {var -attach channel} { testchannel transform $channel -command [namespace code [list id_optrail $var]] } proc audit_flow {var -attach channel} { testchannel transform $channel -command [namespace code [list id_fulltrail $var]] } proc torture {-attach channel} { testchannel transform $channel -command [namespace code [list id_torture $channel]] } proc stopafter {var n -attach channel} { namespace upvar [namespace current] $var vn set vn $n testchannel transform $channel -command [namespace code [list counter $var]] } proc stopafter_audit {var trail n -attach channel} { namespace upvar [namespace current] $var vn set vn $n testchannel transform $channel -command [namespace code [list counter_audit $var $trail]] } proc rblocks_t {var trail n -attach channel} { testchannel transform $channel -command [namespace code [list rblocks $var $trail $n]] } # -------------------------------------------------------------- # serialize an array, with keys in sorted order. proc array_sget {v} { upvar $v a set res [list] foreach n [lsort [array names a]] { lappend res $n $a($n) } set res } proc asort {alist} { # sort a list of key/value pairs by key, removes duplicates too. array set a $alist array_sget a } ######################################################################## test iogt-1.1 {stack/unstack} testchannel { set fh [open $path(dummy) r] identity -attach $fh testchannel unstack $fh close $fh } {} test iogt-1.2 {stack/close} testchannel { set fh [open $path(dummy) r] identity -attach $fh close $fh } {} test iogt-1.3 {stack/unstack, configuration, options} testchannel { set fh [open $path(dummy) r] set ca [asort [fconfigure $fh]] identity -attach $fh set cb [asort [fconfigure $fh]] testchannel unstack $fh set cc [asort [fconfigure $fh]] close $fh # With this system none of the buffering, translation and encoding option # may change their values with channels stacked upon each other or not. # cb == ca == cc list [string equal $ca $cb] [string equal $cb $cc] [string equal $ca $cc] } {1 1 1} test iogt-1.4 {stack/unstack, configuration} -setup { set fh [open $path(dummy) r] } -constraints testchannel -body { set ca [asort [fconfigure $fh]] identity -attach $fh fconfigure $fh -buffering line -translation cr -encoding shiftjis testchannel unstack $fh set cc [asort [fconfigure $fh]] list [string equal $ca $cc] [fconfigure $fh -buffering] \ [fconfigure $fh -translation] [fconfigure $fh -encoding] } -cleanup { close $fh } -result {0 line cr shiftjis} test iogt-2.0 {basic I/O going through transform} -setup { set fin [open $path(dummy) r] set fout [open $path(dummyout) w] } -constraints testchannel -body { identity -attach $fin identity -attach $fout fcopy $fin $fout close $fin close $fout set fin [open $path(dummy) r] set fout [open $path(dummyout) r] list [string equal [set in [read $fin]] [set out [read $fout]]] \ [string length $in] [string length $out] } -cleanup { close $fin close $fout } -result {1 71 71} test iogt-2.1 {basic I/O, operation trail} {testchannel unix} { set fin [open $path(dummy) r] set fout [open $path(dummyout) w] set ain [list]; set aout [list] audit_ops ain -attach $fin audit_ops aout -attach $fout fconfigure $fin -buffersize 10 fconfigure $fout -buffersize 10 fcopy $fin $fout close $fin close $fout set res "[join $ain \n]\n--------\n[join $aout \n]" } {create/read query/maxRead read query/maxRead read query/maxRead read query/maxRead read query/maxRead read query/maxRead read query/maxRead read query/maxRead read query/maxRead flush/read query/maxRead delete/read -------- create/write write write write write write write write write flush/write delete/write} test iogt-2.2 {basic I/O, data trail} {testchannel unix} { set fin [open $path(dummy) r] set fout [open $path(dummyout) w] set ain [list]; set aout [list] audit_flow ain -attach $fin audit_flow aout -attach $fout fconfigure $fin -buffersize 10 fconfigure $fout -buffersize 10 fcopy $fin $fout close $fin close $fout set res "[join $ain \n]\n--------\n[join $aout \n]" } {create/read {} *ignored* query/maxRead {} -1 read abcdefghij abcdefghij query/maxRead {} -1 read klmnopqrst klmnopqrst query/maxRead {} -1 read uvwxyz0123 uvwxyz0123 query/maxRead {} -1 read 456789,./? 456789,./? query/maxRead {} -1 read {><;'\|":[]} {><;'\|":[]} query/maxRead {} -1 read {\}\{`~!@#$} {\}\{`~!@#$} query/maxRead {} -1 read %^&*()_+-= %^&*()_+-= query/maxRead {} -1 read { } { } query/maxRead {} -1 flush/read {} {} query/maxRead {} -1 delete/read {} *ignored* -------- create/write {} *ignored* write abcdefghij abcdefghij write klmnopqrst klmnopqrst write uvwxyz0123 uvwxyz0123 write 456789,./? 456789,./? write {><;'\|":[]} {><;'\|":[]} write {\}\{`~!@#$} {\}\{`~!@#$} write %^&*()_+-= %^&*()_+-= write { } { } flush/write {} {} delete/write {} *ignored*} test iogt-2.3 {basic I/O, mixed trail} {testchannel unix} { set fin [open $path(dummy) r] set fout [open $path(dummyout) w] set trail [list] audit_flow trail -attach $fin audit_flow trail -attach $fout fconfigure $fin -buffersize 20 fconfigure $fout -buffersize 10 fcopy $fin $fout close $fin close $fout join $trail \n } {create/read {} *ignored* create/write {} *ignored* query/maxRead {} -1 read abcdefghijklmnopqrst abcdefghijklmnopqrst write abcdefghij abcdefghij write klmnopqrst klmnopqrst query/maxRead {} -1 read uvwxyz0123456789,./? uvwxyz0123456789,./? write uvwxyz0123 uvwxyz0123 write 456789,./? 456789,./? query/maxRead {} -1 read {><;'\|":[]\}\{`~!@#$} {><;'\|":[]\}\{`~!@#$} write {><;'\|":[]} {><;'\|":[]} write {\}\{`~!@#$} {\}\{`~!@#$} query/maxRead {} -1 read {%^&*()_+-= } {%^&*()_+-= } query/maxRead {} -1 flush/read {} {} write %^&*()_+-= %^&*()_+-= write { } { } query/maxRead {} -1 delete/read {} *ignored* flush/write {} {} delete/write {} *ignored*} test iogt-2.4 {basic I/O, mixed trail} {testchannel} { set fh [open $path(dummy) r] torture -attach $fh chan configure $fh -buffersize 2 set x [read $fh] testchannel unstack $fh close $fh set x } {} test iogt-2.5 {basic I/O, mixed trail} {testchannel} { set ::level 0 set fh [open $path(dummyout) w] torture -attach $fh puts -nonewline $fh abcdef flush $fh testchannel unstack $fh close $fh } {} test iogt-3.0 {Tcl_Channel valid after stack/unstack, fevent handling} -setup { proc DoneCopy {n {err {}}} { variable copy 1 } } -constraints {testchannel knownBug} -body { # This test to check the validity of acquired Tcl_Channel references is not # possible because even a backgrounded fcopy will immediately start to # copy data, without waiting for the event loop. This is done only in case # of an underflow on the read size!. So stacking transforms after the # fcopy will miss information, or are not used at all. # # I was able to circumvent this by using the echo.tcl server with a big # delay, causing the fcopy to underflow immediately. set fin [open $path(dummy) r] fevent 1000 500 {20 20 20 10 1 1} { variable copy close $fin set fout [open dummyout w] flush $sock; # now, or fcopy will error us out # But the 1 second delay should be enough to initialize everything # else here. fcopy $sock $fout -command [namespace code DoneCopy] # Transform after fcopy got its handles! They should be still valid # for fcopy. set trail [list] audit_ops trail -attach $fout vwait [namespace which -variable copy] } [read $fin]; # {} close $fout # Check result of copy. set fin [open $path(dummy) r] set fout [open $path(dummyout) r] set res [string equal [read $fin] [read $fout]] close $fin close $fout list $res $trail } -cleanup { rename DoneCopy {} } -result {1 {create/write create/read write flush/write flush/read delete/write delete/read}} test iogt-4.0 {fileevent readable, after transform} -setup { set fin [open $path(dummy) r] set data [read $fin] close $fin set trail [list] set got [list] proc Done {args} { variable stop 1 } proc Get {sock} { variable trail variable got if {[eof $sock]} { Done lappend trail "xxxxxxxxxxxxx" close $sock return } lappend trail "vvvvvvvvvvvvv" lappend trail "\tgot: [lappend got "\[\[[read $sock]\]\]"]" lappend trail "=============" #puts stdout $__ ; flush stdout #read $sock } } -constraints {testchannel knownBug} -body { fevent 1000 500 {20 20 20 10 1} { variable stop audit_flow trail -attach $sock rblocks_t rbuf trail 23 -attach $sock fileevent $sock readable [namespace code [list Get $sock]] flush $sock; # Now, or fcopy will error us out # But the 1 second delay should be enough to initialize everything # else here. vwait [namespace which -variable stop] } $data join [list [join $got \n] ~~~~~~~~ [join $trail \n]] \n } -cleanup { rename Done {} rename Get {} } -result {[[]] [[abcdefghijklmnopqrstuvw]] [[xyz0123456789,./?><;'\|]] [[]] [[]] [[":[]\}\{`~!@#$%^&*()]] [[]] ~~~~~~~~ create/write {} *ignored* create/read {} *ignored* rblock | create/write {} {} | {} rblock | create/read {} {} | {} vvvvvvvvvvvvv rblock | query/maxRead {} -1 | {} query/maxRead {} -1 read abcdefghijklmnopqrstu abcdefghijklmnopqrstu query/maxRead {} -1 rblock | {read 23 21 :- 0} abcdefghijklmnopqrstu {} | abcdefghijklmnopqrstu rblock | query/maxRead {} -1 | abcdefghijklmnopqrstu query/maxRead {} -1 got: {[[]]} ============= vvvvvvvvvvvvv rblock | query/maxRead {} -1 | abcdefghijklmnopqrstu query/maxRead {} -1 read vwxyz0123456789,./?>< vwxyz0123456789,./?>< query/maxRead {} -1 rblock | {read 23 42 :- 23} vwxyz0123456789,./?>< abcdefghijklmnopqrstuvw | xyz0123456789,./?>< rblock | query/maxRead {} -1 | xyz0123456789,./?>< query/maxRead {} -1 got: {[[]]} {[[abcdefghijklmnopqrstuvw]]} ============= vvvvvvvvvvvvv rblock | query/maxRead {} -1 | xyz0123456789,./?>< query/maxRead {} -1 read {;'\|":[]\}\{`~!@#$%^&} {;'\|":[]\}\{`~!@#$%^&} query/maxRead {} -1 rblock | {read 23 40 :- 23} {;'\|":[]\}\{`~!@#$%^&} {xyz0123456789,./?><;'\|} | {":[]\}\{`~!@#$%^&} rblock | query/maxRead {} -1 | {":[]\}\{`~!@#$%^&} query/maxRead {} -1 got: {[[]]} {[[abcdefghijklmnopqrstuvw]]} {[[xyz0123456789,./?><;'\|]]} ============= vvvvvvvvvvvvv rblock | query/maxRead {} -1 | {":[]\}\{`~!@#$%^&} query/maxRead {} -1 read *( *( query/maxRead {} -1 rblock | {read 23 19 :- 0} *( {} | {":[]\}\{`~!@#$%^&*(} rblock | query/maxRead {} -1 | {":[]\}\{`~!@#$%^&*(} query/maxRead {} -1 got: {[[]]} {[[abcdefghijklmnopqrstuvw]]} {[[xyz0123456789,./?><;'\|]]} {[[]]} ============= vvvvvvvvvvvvv rblock | query/maxRead {} -1 | {":[]\}\{`~!@#$%^&*(} query/maxRead {} -1 read ) ) query/maxRead {} -1 rblock | {read 23 20 :- 0} ) {} | {":[]\}\{`~!@#$%^&*()} rblock | query/maxRead {} -1 | {":[]\}\{`~!@#$%^&*()} query/maxRead {} -1 got: {[[]]} {[[abcdefghijklmnopqrstuvw]]} {[[xyz0123456789,./?><;'\|]]} {[[]]} {[[]]} ============= vvvvvvvvvvvvv rblock | query/maxRead {} -1 | {":[]\}\{`~!@#$%^&*()} query/maxRead {} -1 flush/read {} {} rblock | flush/read {} {":[]\}\{`~!@#$%^&*()} | {} rblock | query/maxRead {} -1 | {} query/maxRead {} -1 got: {[[]]} {[[abcdefghijklmnopqrstuvw]]} {[[xyz0123456789,./?><;'\|]]} {[[]]} {[[]]} {[[":[]\}\{`~!@#$%^&*()]]} ============= vvvvvvvvvvvvv rblock | query/maxRead {} -1 | {} query/maxRead {} -1 got: {[[]]} {[[abcdefghijklmnopqrstuvw]]} {[[xyz0123456789,./?><;'\|]]} {[[]]} {[[]]} {[[":[]\}\{`~!@#$%^&*()]]} {[[]]} xxxxxxxxxxxxx rblock | flush/write {} {} | {} rblock | delete/write {} {} | {} rblock | delete/read {} {} | {} flush/write {} {} delete/write {} *ignored* delete/read {} *ignored*}; # catch unescaped quote " test iogt-5.0 {EOF simulation} -setup { set fin [open $path(dummy) r] set fout [open $path(dummyout) w] set trail [list] } -constraints {testchannel knownBug} -result { audit_flow trail -attach $fin stopafter_audit d trail 20 -attach $fin audit_flow trail -attach $fout fconfigure $fin -buffersize 20 fconfigure $fout -buffersize 10 fcopy $fin $fout testchannel unstack $fin # now copy the rest in the channel lappend trail {**after unstack**} fcopy $fin $fout close $fin close $fout join $trail \n } -result {create/read {} *ignored* counter:create/read {} {} create/write {} *ignored* counter:query/maxRead {} 20 query/maxRead {} -1 read {abcdefghijklmnopqrstuvwxyz0123456789,./?><;'\|":[]\}\{`~!@#$%^&*()_+-= } {abcdefghijklmnopqrstuvwxyz0123456789,./?><;'\|":[]\}\{`~!@#$%^&*()_+-= } query/maxRead {} -1 flush/read {} {} counter:read abcdefghijklmnopqrst abcdefghijklmnopqrst write abcdefghij abcdefghij write klmnopqrst klmnopqrst counter:query/maxRead {} 0 counter:flush/read {} {} counter:delete/read {} {} **after unstack** query/maxRead {} -1 write uvwxyz0123 uvwxyz0123 write 456789,./? 456789,./? write {><;'\|":[]} {><;'\|":[]} write {\}\{`~!@#$} {\}\{`~!@#$} write %^&*()_+-= %^&*()_+-= write { } { } query/maxRead {} -1 delete/read {} *ignored* flush/write {} {} delete/write {} *ignored*} proc constX {op data} { # replace anything coming in with a same-length string of x'es. switch -- $op { create/write - create/read - delete/write - delete/read - clear_read { #ignore } flush/write - flush/read - write - read { return [string repeat x [string length $data]] } query/maxRead { return -1 } } } proc constx {-attach channel} { testchannel transform $channel -command [namespace code constX] } test iogt-6.0 {Push back} -constraints testchannel -body { set f [open $path(dummy) r] # contents of dummy = "abcdefghi..." read $f 3; # skip behind "abc" constx -attach $f # expect to get "xxx" from the transform because of unread "def" input to # transform which returns "xxx". # # Actually the IO layer preread the whole file and will read "def" # directly from the buffer without bothering to consult the newly stacked # transformation. This is wrong. read $f 3 } -cleanup { close $f } -result {xxx} test iogt-6.1 {Push back and up} -constraints {testchannel knownBug} -body { # This test demonstrates the bug/misfeature in the stacked # channel implementation that data can be discarded if it is # read into the buffers of one channel in the stack, and then # that channel is popped before anything above it reads. # # This bug can be worked around by always setting -buffersize # to 1, but who wants to do that? set f [open $path(dummy) r] # contents of dummy = "abcdefghi..." read $f 3; # skip behind "abc" constx -attach $f set res [read $f 3] testchannel unstack $f append res [read $f 3] } -cleanup { close $f } -result {xxxghi} # Driver for a base channel that emits several short "files" # with each terminated by a fleeting EOF proc driver {cmd args} { variable buffer variable 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) 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 } } } test iogt-7.0 {Handle fleeting EOF} -constraints {testchannel} -body { set chan [chan create read [namespace which driver]] identity -attach $chan list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \ [read $chan] [eof $chan] } -cleanup { close $chan } -result {0 ..... 1 {} 0 ..... 1} proc delay {op data} { variable store switch -- $op { create/write - create/read - delete/write - delete/read - flush/write - write - clear_read {;#ignore} flush/read - read { if {![info exists store]} {set store {}} set reply $store set store $data return $reply } query/maxRead {return -1} } } test iogt-7.1 {Handle fleeting EOF} -constraints {testchannel} -body { set chan [chan create read [namespace which driver]] testchannel transform $chan -command [namespace code delay] 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 delay {} rename driver {} # cleanup foreach file [list dummy dummyout __echo_srv__.tcl] { removeFile $file } cleanupTests } namespace delete ::tcl::test::iogt return