summaryrefslogtreecommitdiffstats
path: root/tests/tailcall.test
diff options
context:
space:
mode:
Diffstat (limited to 'tests/tailcall.test')
-rw-r--r--tests/tailcall.test696
1 files changed, 696 insertions, 0 deletions
diff --git a/tests/tailcall.test b/tests/tailcall.test
new file mode 100644
index 0000000..26f3cbf
--- /dev/null
+++ b/tests/tailcall.test
@@ -0,0 +1,696 @@
+# 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 (c) 2008 by Miguel Sofer.
+#
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+
+if {[lsearch [namespace children] ::tcltest] == -1} {
+ package require tcltest
+ 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
+}
+
+# cleanup
+::tcltest::cleanupTests
+
+# Local Variables:
+# mode: tcl
+# End: