# Commands covered: tailcall # # This file contains a collection of tests for experimental commands that are # found in ::tcl::unsupported. The tests will migrate to normal test files # if/when the commands find their way into the core. # # Copyright © 2008 Miguel Sofer. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. if {"::tcltest" ni [namespace children]} { package require tcltest 2.5 namespace import -force ::tcltest::* } ::tcltest::loadTestedCommands catch [list package require -exact Tcltest [info patchlevel]] testConstraint testnrelevels [llength [info commands testnrelevels]] # # The tests that risked blowing the C stack on failure have been removed: we # can now actually measure using testnrelevels. # if {[testConstraint testnrelevels]} { namespace eval testnre { # # [testnrelevels] returns a 6-list with: C-stack depth, iPtr->numlevels, # cmdFrame level, callFrame level, tosPtr and callback depth # variable last [testnrelevels] proc depthDiff {} { variable last set depth [testnrelevels] set res {} foreach t $depth l $last { lappend res [expr {$t-$l}] } set last $depth return $res } namespace export * } namespace import testnre::* } proc errorcode options { dict get [dict merge {-errorcode NONE} $options] -errorcode } test tailcall-0.1 {tailcall is constant space} -constraints testnrelevels -setup { proc a i { # # NOTE: there may be a diff in callback depth with the first call # ($i==0) due to the fact that the first is from an eval. Successive # calls should add nothing to any stack depths. # if {$i == 1} { depthDiff } if {[incr i] > 10} { return [depthDiff] } tailcall a $i } } -body { a 0 } -cleanup { rename a {} } -result {0 0 0 0 0 0} test tailcall-0.2 {tailcall is constant space} -constraints testnrelevels -setup { set a { i { if {$i == 1} { depthDiff } if {[incr i] > 10} { return [depthDiff] } upvar 1 a a tailcall apply $a $i }} } -body { apply $a 0 } -cleanup { unset a } -result {0 0 0 0 0 0} test tailcall-0.3 {tailcall is constant space} -constraints testnrelevels -setup { proc a i { if {$i == 1} { depthDiff } if {[incr i] > 10} { return [depthDiff] } tailcall b $i } interp alias {} b {} a } -body { b 0 } -cleanup { rename a {} rename b {} } -result {0 0 0 0 0 0} test tailcall-0.4 {tailcall is constant space} -constraints testnrelevels -setup { namespace eval ::ns { namespace export * } proc ::ns::a i { if {$i == 1} { depthDiff } if {[incr i] > 10} { return [depthDiff] } set b [uplevel 1 [list namespace which b]] tailcall $b $i } namespace import ::ns::a rename a b } -body { b 0 } -cleanup { rename b {} namespace delete ::ns } -result {0 0 0 0 0 0} test tailcall-0.5 {tailcall is constant space} -constraints testnrelevels -setup { proc b i { if {$i == 1} { depthDiff } if {[incr i] > 10} { return [depthDiff] } tailcall a b $i } namespace ensemble create -command a -map {b b} } -body { a b 0 } -cleanup { rename a {} rename b {} } -result {0 0 0 0 0 0} test tailcall-0.5.1 {tailcall is constant space} -constraints testnrelevels -setup { # # This test is related to [bug d87cb182053fd79b3]: the fix to that bug was # to remove a call to TclSkipTailcall, which caused a violation of the # constant-space property of tailcall in that particular # configuration. This test was added to detect that, and insure that the # problem is fixed. # proc b i { if {$i == 1} { depthDiff } if {[incr i] > 10} { return [depthDiff] } tailcall dict b $i } set map0 [namespace ensemble configure dict -map] set map $map0 dict set map b b namespace ensemble configure dict -map $map } -body { dict b 0 } -cleanup { rename b {} namespace ensemble configure dict -map $map0 unset map map0 } -result {0 0 0 0 0 0} test tailcall-0.6 {tailcall is constant space} -constraints {testnrelevels knownBug} -setup { # # This test fails because ns-unknown is not NR-enabled # proc c i { if {$i == 1} { depthDiff } if {[incr i] > 10} { return [depthDiff] } tailcall a b $i } proc d {ens sub args} { return [list $ens c] } namespace ensemble create -command a -unknown d } -body { a b 0 } -cleanup { rename a {} rename c {} rename d {} } -result {0 0 0 0 0 0} test tailcall-0.7 {tailcall is constant space} -constraints testnrelevels -setup { catch {rename foo {}} oo::class create foo { method b i { if {$i == 1} { depthDiff } if {[incr i] > 10} { return [depthDiff] } tailcall [self] b $i } } } -body { foo create a a b 0 } -cleanup { rename a {} rename foo {} } -result {0 0 0 0 0 0} test tailcall-1 {tailcall} -body { namespace eval a { variable x *::a proc xset {} { set tmp {} set ns {[namespace current]} set level [info level] for {set i 0} {$i <= [info level]} {incr i} { uplevel #$i "set x $i$ns" lappend tmp "$i [info level $i]" } lrange $tmp 1 end } proc foo {} {tailcall xset; set x noreach} } namespace eval b { variable x *::b proc xset args {error b::xset} proc moo {} {set x 0; variable y [::a::foo]; set x} } variable x *:: proc xset args {error ::xset} list [::b::moo] | $x $a::x $b::x | $::b::y } -cleanup { unset x rename xset {} namespace delete a b } -result {1::b | 0:: *::a *::b | {{1 ::b::moo} {2 xset}}} test tailcall-2 {tailcall in non-proc} -body { namespace eval a [list tailcall set x 1] } -match glob -result *tailcall* -returnCodes error test tailcall-3 {tailcall falls off tebc} -body { unset -nocomplain x proc foo {} {tailcall set x 1} list [catch foo msg] $msg [set x] } -cleanup { rename foo {} unset x } -result {0 1 1} test tailcall-4 {tailcall falls off tebc} -body { set x 2 proc foo {} {tailcall set x 1} foo set x } -cleanup { rename foo {} unset x } -result 1 test tailcall-5 {tailcall falls off tebc} -body { set x 2 namespace eval bar { variable x 3 proc foo {} {tailcall set x 1} } bar::foo list $x $bar::x } -cleanup { unset x namespace delete bar } -result {1 3} test tailcall-6 {tailcall does remove callframes} -body { proc foo {} {info level} proc moo {} {tailcall foo} proc boo {} {expr {[moo] - [info level]}} boo } -cleanup { rename foo {} rename moo {} rename boo {} } -result 1 test tailcall-7 {tailcall does return} -setup { namespace eval ::foo { variable res {} proc a {} { variable res append res a tailcall set x 1 append res a } proc b {} { variable res append res b a append res b } proc c {} { variable res append res c b append res c } } } -body { namespace eval ::foo c } -cleanup { namespace delete ::foo } -result cbabc test tailcall-8 {tailcall tailcall} -setup { namespace eval ::foo { variable res {} proc a {} { variable res append res a tailcall tailcall set x 1 append res a } proc b {} { variable res append res b a append res b } proc c {} { variable res append res c b append res c } } } -body { namespace eval ::foo c } -cleanup { namespace delete ::foo } -result cbac test tailcall-9 {tailcall factorial} -setup { proc fact {n {b 1}} { if {$n == 1} { return $b } tailcall fact [expr {$n-1}] [expr {$n*$b}] } } -body { list [fact 1] [fact 5] [fact 10] [fact 15] } -cleanup { rename fact {} } -result {1 120 3628800 1307674368000} test tailcall-10a {tailcall and eval} -setup { set ::x 0 proc a {} { eval [list tailcall lappend ::x 2] set ::x 1 } } -body { list [a] $::x } -cleanup { unset -nocomplain ::x } -result {{0 2} {0 2}} test tailcall-10b {tailcall and eval} -setup { set ::x 0 proc a {} { eval {tailcall lappend ::x 2} set ::x 1 } } -body { list [a] $::x } -cleanup { unset -nocomplain ::x } -result {{0 2} {0 2}} test tailcall-11a {tailcall and uplevel} -setup { proc a {} { uplevel 1 [list tailcall set ::x 2] set ::x 1 } } -body { list [a] $::x } -cleanup { unset -nocomplain ::x } -match glob -result *tailcall* -returnCodes error test tailcall-11b {tailcall and uplevel} -setup { proc a {} { uplevel 1 {tailcall set ::x 2} set ::x 1 } } -body { list [a] $::x } -cleanup { unset -nocomplain ::x } -match glob -result *tailcall* -returnCodes error test tailcall-11c {tailcall and uplevel} -setup { proc a {} { uplevel 1 {tailcall lappend ::x 2} set ::x 1 } proc b {} {set ::x 0; a; lappend ::x 3} } -body { list [b] $::x } -cleanup { rename a {} rename b {} unset -nocomplain ::x } -result {{0 3 2} {0 3 2}} test tailcall-12.1 {[Bug 2649975]} -setup { proc dump {{text {}}} { set text [uplevel 1 [list subst $text]] set l [expr {[info level] -1}] if {$text eq {}} { set text [info level $l] } puts "$l: $text" } # proc dump args {} proc bravo {} { upvar 1 v w dump {inside bravo, v -> $w} set v "procedure bravo" #uplevel 1 [list delta ::betty] uplevel 1 {delta ::betty} return $::resolution } proc delta name { upvar 1 v w dump {inside delta, v -> $w} set v "procedure delta" tailcall foxtrot } proc foxtrot {} { upvar 1 v w dump {inside foxtrot, v -> $w} global resolution set ::resolution $w } set v "global level" } -body { set result [bravo] if {$result ne $v} { puts "v should have been found at $v but was found in $result" } } -cleanup { unset v rename dump {} rename bravo {} rename delta {} rename foxtrot {} } -output {1: inside bravo, v -> global level 1: inside delta, v -> global level 1: inside foxtrot, v -> global level } test tailcall-12.2 {[Bug 2649975]} -setup { proc dump {{text {}}} { set text [uplevel 1 [list subst $text]] set l [expr {[info level] -1}] if {$text eq {}} { set text [info level $l] } puts "$l: $text" } # proc dump args {} set v "global level" oo::class create foo { # like connection method alpha {} { # like connections 'tables' method dump upvar 1 v w dump {inside foo's alpha, v resolves to $w} set v "foo's method alpha" dump {foo's alpha is calling [self] bravo - v should resolve at global level} set result [uplevel 1 [list [self] bravo]] dump {exiting from foo's alpha} return $result } method bravo {} { # like connections 'foreach' method dump upvar 1 v w dump {inside foo's bravo, v resolves to $w} set v "foo's method bravo" dump {foo's bravo is calling charlie to create barney} set barney [my charlie ::barney] dump {foo's bravo is calling bravo on $barney} dump {v should resolve at global scope there} set result [uplevel 1 [list $barney bravo]] dump {exiting from foo's bravo} return $result } method charlie {name} { # like tdbc prepare dump set v "foo's method charlie" dump {tailcalling bar's constructor} tailcall ::bar create $name } } oo::class create bar { # like statement method bravo {} { # like statement foreach method dump upvar 1 v w dump {inside bar's bravo, v is resolving to $w} set v "bar's method bravo" dump {calling delta to construct betty - v should resolve global there} uplevel 1 [list [self] delta ::betty] dump {exiting from bar's bravo} return [::betty whathappened] } method delta {name} { # like statement execute method dump upvar 1 v w dump {inside bar's delta, v is resolving to $w} set v "bar's method delta" dump {tailcalling to construct $name as instance of grill} dump {v should resolve at global level in grill's constructor} dump {grill's constructor should run at level [info level]} tailcall grill create $name } } oo::class create grill { variable resolution constructor {} { dump upvar 1 v w dump "in grill's constructor, v resolves to $w" set resolution $w } method whathappened {} { return $resolution } } foo create fred } -body { set result [fred alpha] if {$result ne "global level"} { puts "v should have been found at global level but was found in $result" } } -cleanup { unset result rename fred {} rename dump {} rename foo {} rename bar {} rename grill {} } -output {1: fred alpha 1: inside foo's alpha, v resolves to global level 1: foo's alpha is calling ::fred bravo - v should resolve at global level 1: ::fred bravo 1: inside foo's bravo, v resolves to global level 1: foo's bravo is calling charlie to create barney 2: my charlie ::barney 2: tailcalling bar's constructor 1: foo's bravo is calling bravo on ::barney 1: v should resolve at global scope there 1: ::barney bravo 1: inside bar's bravo, v is resolving to global level 1: calling delta to construct betty - v should resolve global there 1: ::barney delta ::betty 1: inside bar's delta, v is resolving to global level 1: tailcalling to construct ::betty as instance of grill 1: v should resolve at global level in grill's constructor 1: grill's constructor should run at level 1 1: grill create ::betty 1: in grill's constructor, v resolves to global level 1: exiting from bar's bravo 1: exiting from foo's bravo 1: exiting from foo's alpha } test tailcall-12.3a0 {[Bug 2695587]} -body { apply {{} { catch [list tailcall foo] }} } -returnCodes 1 -result {invalid command name "foo"} test tailcall-12.3a1 {[Bug 2695587]} -body { apply {{} { catch [list tailcall foo] tailcall }} } -result {} test tailcall-12.3a2 {[Bug 2695587]} -body { apply {{} { catch [list tailcall foo] tailcall moo }} } -returnCodes 1 -result {invalid command name "moo"} test tailcall-12.3a3 {[Bug 2695587]} -body { set x 0 apply {{} { catch [list tailcall foo] tailcall lappend x 1 }} set x } -cleanup { unset x } -result {0 1} test tailcall-12.3b0 {[Bug 2695587]} -body { apply {{} { set catch catch $catch [list tailcall foo] }} } -returnCodes 1 -result {invalid command name "foo"} test tailcall-12.3b1 {[Bug 2695587]} -body { apply {{} { set catch catch $catch [list tailcall foo] tailcall }} } -result {} test tailcall-12.3b2 {[Bug 2695587]} -body { apply {{} { set catch catch $catch [list tailcall foo] tailcall moo }} } -returnCodes 1 -result {invalid command name "moo"} test tailcall-12.3b3 {[Bug 2695587]} -body { set x 0 apply {{} { set catch catch $catch [list tailcall foo] tailcall lappend x 1 }} set x } -cleanup { unset x } -result {0 1} # MORE VARIANTS MISSING: bc'ed caught script vs (bc'ed, not-bc'ed) # catch. Actually superfluous now, as tailcall just returns TCL_RETURN so that # standard catch behaviour is required. test tailcall-13.1 {directly tailcalling the tailcall command is ok} { list [catch { apply {{} { apply {{} { tailcall tailcall subst ok subst b }} subst c }} } msg opt] $msg [errorcode $opt] } {0 ok NONE} test tailcall-13.2 {indirectly tailcalling the tailcall command is ok} { list [catch { apply {{} { apply {{} { tailcall eval tailcall subst ok subst b }} subst c }} } msg opt] $msg [errorcode $opt] } {0 ok NONE} if {[testConstraint testnrelevels]} { namespace forget testnre::* namespace delete testnre } test tailcall-14.1 {in a deleted namespace} -body { namespace eval ns { proc p args { tailcall [namespace current] $args } namespace delete [namespace current] p } } -returnCodes 1 -result {namespace "::ns" not found} test tailcall-14.1-bc {{in a deleted namespace} {byte compiled}} -body { namespace eval ns { proc p args { tailcall [namespace current] {*}$args } namespace delete [namespace current] p } } -returnCodes 1 -result {namespace "::ns" not found} # cleanup ::tcltest::cleanupTests # Local Variables: # mode: tcl # End: