1 # Commands covered: coroutine, yield, yieldto, [info coroutine]
3 # This file contains a collection of tests for experimental commands that are
4 # found in ::tcl::unsupported. The tests will migrate to normal test files
5 # if/when the commands find their way into the core.
7 # Copyright (c) 2008 by Miguel Sofer.
9 # See the file "license.terms" for information on usage and redistribution
10 # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 if {"::tcltest" ni [namespace children]} {
13 package require tcltest 2.5
14 namespace import -force ::tcltest::*
17 ::tcltest::loadTestedCommands
18 catch [list package require -exact Tcltest [info patchlevel]]
20 testConstraint testnrelevels [llength [info commands testnrelevels]]
21 testConstraint memory [llength [info commands memory]]
23 set lambda [list {{start 0} {stop 10}} {
29 yield [expr {$i*$stop}]
34 test coroutine-1.1 {coroutine basic} -setup {
35 coroutine foo ::apply $lambda
38 for {set k 1} {$k < 4} {incr k} {
46 test coroutine-1.2 {coroutine basic} -setup {
47 coroutine foo ::apply $lambda 2 8
50 for {set k 1} {$k < 4} {incr k} {
58 test coroutine-1.3 {yield returns new arg} -setup {
65 set stop [yield [expr {$i*$stop}]]
69 coroutine foo ::apply [list {{start 2} {stop 10}} $body]
72 for {set k 1} {$k < 4} {incr k} {
80 test coroutine-1.4 {yield in nested proc} -setup {
83 yield [expr {$i*$stop}]
95 coroutine foo ::apply [list {{start 0} {stop 10}} $body]
98 for {set k 1} {$k < 4} {incr k} {
107 test coroutine-1.5 {just yield} -body {
109 list [foo] [catch foo msg] $msg
112 } -result {{} 1 {invalid command name "foo"}}
113 test coroutine-1.6 {just yield} -body {
114 coroutine foo [list yield]
115 list [foo] [catch foo msg] $msg
118 } -result {{} 1 {invalid command name "foo"}}
119 test coroutine-1.7 {yield in nested uplevel} -setup {
126 uplevel 0 [list yield [expr {$i*$stop}]]
130 coroutine foo ::apply [list {{start 0} {stop 10}} $body]
133 for {set k 1} {$k < 4} {incr k} {
134 lappend res [eval foo $k]
141 test coroutine-1.8 {yield in nested uplevel} -setup {
148 uplevel 0 yield [expr {$i*$stop}]
152 coroutine foo ::apply [list {{start 0} {stop 10}} $body]
155 for {set k 1} {$k < 4} {incr k} {
156 lappend res [eval foo $k]
163 test coroutine-1.9 {yield in nested eval} -setup {
165 upvar 1 i i stop stop
166 yield [expr {$i*$stop}]
178 coroutine foo ::apply [list {{start 0} {stop 10}} $body]
181 for {set k 1} {$k < 4} {incr k} {
189 test coroutine-1.10 {yield in nested eval} -setup {
196 eval yield [expr {$i*$stop}]
200 coroutine foo ::apply [list {{start 0} {stop 10}} $body]
203 for {set k 1} {$k < 4} {incr k} {
204 lappend res [eval foo $k]
210 test coroutine-1.11 {yield outside coroutine} -setup {
212 upvar 1 i i stop stop
213 yield [expr {$i*$stop}]
221 } -returnCodes error -result {yield can only be called in a coroutine}
222 test coroutine-1.12 {proc as coroutine} -setup {
229 uplevel 0 [list yield [expr {$i*$stop}]]
233 proc moo {{start 0} {stop 10}} $body
234 coroutine foo moo 2 8
242 test coroutine-1.13 {subst as coroutine: literal} {
243 list [coroutine foo eval {subst {>>[yield a],[yield b]<<}}] [foo x] [foo y]
245 test coroutine-1.14 {subst as coroutine: in variable} {
246 set pattern {>>[yield c],[yield d]<<}
247 list [coroutine foo eval {subst $pattern}] [foo p] [foo q]
250 test coroutine-2.1 {self deletion on return} -body {
251 coroutine foo set x 3
253 } -returnCodes error -result {invalid command name "foo"}
254 test coroutine-2.2 {self deletion on return} -body {
255 coroutine foo ::apply [list {} {yield; yield 1; return 2}]
256 list [foo] [foo] [catch foo msg] $msg
257 } -result {1 2 1 {invalid command name "foo"}}
258 test coroutine-2.3 {self deletion on error return} -body {
259 coroutine foo ::apply [list {} {yield;yield 1; error ouch!}]
260 list [foo] [catch foo msg] $msg [catch foo msg] $msg
261 } -result {1 1 ouch! 1 {invalid command name "foo"}}
262 test coroutine-2.4 {self deletion on other return} -body {
263 coroutine foo ::apply [list {} {yield;yield 1; return -code 100 ouch!}]
264 list [foo] [catch foo msg] $msg [catch foo msg] $msg
265 } -result {1 100 ouch! 1 {invalid command name "foo"}}
266 test coroutine-2.5 {deletion of suspended coroutine} -body {
267 coroutine foo ::apply [list {} {yield; yield 1; return 2}]
268 list [foo] [rename foo {}] [catch foo msg] $msg
269 } -result {1 {} 1 {invalid command name "foo"}}
270 test coroutine-2.6 {deletion of running coroutine} -body {
271 coroutine foo ::apply [list {} {yield; rename foo {}; yield 1; return 2}]
272 list [foo] [catch foo msg] $msg
273 } -result {1 1 {invalid command name "foo"}}
275 test coroutine-3.1 {info level computation} -setup {
276 proc a {} {while 1 {yield [info level]}}
279 # note that coroutines execute in uplevel #0
280 set l0 [coroutine foo a]
288 test coroutine-3.2 {info frame computation} -setup {
289 proc a {} {while 1 {yield [info frame]}}
292 set l0 [coroutine foo a]
300 test coroutine-3.3 {info coroutine} -setup {
301 proc a {} {info coroutine}
309 test coroutine-3.4 {info coroutine} -setup {
310 proc a {} {info coroutine}
318 test coroutine-3.5 {info coroutine} -setup {
319 proc a {} {info coroutine}
320 proc b {} {rename [info coroutine] {}; a}
327 test coroutine-3.6 {info frame, bug #2910094} -setup {
329 set res [list "LEVEL:[set lev [info frame]]"]
330 for {set i 1} {$i < $lev} {incr i} {
331 lappend res [info frame $i]
334 # the precise command depends on line numbers and such, is likely not
335 # to be stable: just check that the test completes!
345 test coroutine-3.7 {bug 0b874c344d} {
346 dict get [coroutine X coroutine Y info frame 0] cmd
347 } {coroutine X coroutine Y info frame 0}
349 test coroutine-4.1 {bug #2093188} -setup {
352 trace add variable v {write unset} bar
358 proc bar args {lappend ::res $args}
366 } -result {{} 3 {{v {} write} {v {} write} {v {} unset}}}
367 test coroutine-4.2 {bug #2093188} -setup {
370 trace add variable v {read unset} bar
377 proc bar args {lappend ::res $args}
385 } -result {{} 3 {{v {} read} {v {} unset}}}
387 test coroutine-4.3 {bug #2093947} -setup {
390 trace add variable v {write unset} bar
396 proc bar args {lappend ::res $args}
409 } -result {{v {} write} {v {} write} {v {} unset} {v {} write} {v {} unset}}
411 test coroutine-4.4 {bug #2917627: cmd resolution} -setup {
412 proc a {} {return global}
413 namespace eval b {proc a {} {return local}}
415 namespace eval b {coroutine foo a}
421 test coroutine-4.5 {bug #2724403} -constraints {memory} \
424 set lines [split [memory info] "\n"]
429 for {set i 0} {$i < 5} {incr i} {
431 namespace eval $ns {}
432 proc ${ns}::start {} {yield; puts hello}
433 coroutine ${ns}::run ${ns}::start
438 set leakedBytes [expr {$end - $start}]
444 test coroutine-4.6 {compile context, bug #3282869} -setup {
445 unset -nocomplain ::x
447 coroutine D eval {yield X$x;yield Y}
453 } -returnCodes error -match glob -result {can't read *}
455 test coroutine-4.7 {compile context, bug #3282869} -setup {
457 coroutine D eval {yield X$x;yield Y$x}
469 test coroutine-5.1 {right numLevels on coro return} -constraints {testnrelevels} \
471 proc nestedYield {{val {}}} {
474 proc getNumLevel {} {
475 # remove the level for this proc's call
476 expr {[lindex [testnrelevels] 1] - 1}
478 proc relativeLevel base {
479 # remove the level for this proc's call
480 expr {[getNumLevel] - $base - 1}
489 set base [getNumLevel]
490 lappend res [relativeLevel $base]
491 eval {coroutine a foo}
493 lappend res [relativeLevel $base]
495 lappend res [relativeLevel $base]
497 lappend res [relativeLevel $base]
499 lappend res [relativeLevel $base]
501 lappend res [relativeLevel $base]
505 rename nestedYield {}
506 rename getNumLevel {}
507 rename relativeLevel {}
509 } -result {0 0 0 0 0 0}
510 test coroutine-5.2 {right numLevels within coro} -constraints {testnrelevels} \
512 proc nestedYield {{val {}}} {
515 proc getNumLevel {} {
516 # remove the level for this proc's call
517 expr {[lindex [testnrelevels] 1] - 1}
519 proc relativeLevel base {
520 # remove the level for this proc's call
521 expr {[getNumLevel] - $base - 1}
525 set base [nestedYield [relativeLevel $base]]
530 lappend res [eval {coroutine a foo [getNumLevel]}]
531 lappend res [a [getNumLevel]]
532 lappend res [eval {a [getNumLevel]}]
533 lappend res [eval {eval {a [getNumLevel]}}]
534 set base [lindex $res 0]
535 foreach x $res[set res {}] {
536 lappend res [expr {$x-$base}]
542 rename nestedYield {}
543 rename getNumLevel {}
544 rename relativeLevel {}
548 test coroutine-6.1 {coroutine nargs} -body {
549 coroutine a ::apply $lambda
554 test coroutine-6.2 {coroutine nargs} -body {
555 coroutine a ::apply $lambda
560 test coroutine-6.3 {coroutine nargs} -body {
561 coroutine a ::apply $lambda
565 } -returnCodes error -result {wrong # args: should be "a ?arg?"}
567 test coroutine-7.1 {yieldto} -body {
568 coroutine c apply {{} {
570 yieldto return -level 0 -code 1 quux
573 set res [list [catch c msg] $msg]
574 lappend res [catch c msg] $msg
575 lappend res [catch c msg] $msg
578 } -result [list 1 quux 0 quuy 1 {invalid command name "c"}]
579 test coroutine-7.2 {multi-argument yielding with yieldto} -body {
584 set a [yieldto return -level 0 $a]
585 lappend a [llength $a]
590 list [a x] [a y z] [a \{p] [a \{q r] [a] [a] [rename a {}] \
594 } -result {x {y z 2} \{p {\{q r 2} {} 0 {} ok {}}
595 test coroutine-7.3 {yielding between coroutines} -body {
596 proc juggler {target {value ""}} {
598 set value [yield [info coroutine]]
600 while {[llength $value]} {
601 lappend ::result $value [info coroutine]
602 set value [lrange $value 0 end-1]
603 lassign [yieldto $target $value] value
605 # Clear nested collection of coroutines
609 coroutine j1 juggler [coroutine j2 juggler [coroutine j3 juggler j1]]\
611 list $result [info command j1] [info command j2] [info command j3]
613 catch {rename juggler ""}
614 } -result {{{a b c d e} ::j1 {a b c d} ::j2 {a b c} ::j3 {a b} ::j1 a ::j2} {} {} {}}
615 test coroutine-7.4 {Bug 8ff0cb9fe1} -setup {
616 proc foo {a b} {catch yield; return 1}
620 coroutine demo lsort -command foo {a b}
622 test coroutine-7.5 {return codes} {
624 foreach code {0 1 2 3 4 5} {
625 lappend result [catch {coroutine demo return -level 0 -code $code}]
629 test coroutine-7.6 {Early yield crashes} -setup {
630 set i [interp create]
632 # Force into a child interpreter [bug 60559fd4a6]
635 trace add execution foo enter {catch yield}
643 test coroutine-7.7 {Bug 2486550} -setup {
644 set i [interp create]
647 # Force into a child interpreter [bug 60559fd4a6]
649 coroutine demo interp invokehidden {} yield ok
655 test coroutine-7.8 {yieldto context nuke: Bug a90d9331bc} -setup {
656 namespace eval cotest {}
659 proc cotest::body {} {
663 yieldto ::return -level 0 123
667 lappend ::result [coroutine cotest cotest::body]
668 namespace delete cotest
669 namespace eval cotest {}
670 lappend ::result [cotest]
673 } -returnCodes error -cleanup {
674 catch {namespace delete ::cotest}
675 catch {rename cotest ""}
676 } -result {yieldto called in deleted namespace}
677 test coroutine-7.9 {yieldto context nuke: Bug a90d9331bc} -setup {
678 namespace eval cotest {}
681 proc cotest::body {} {
686 $y ::return -level 0 123
690 lappend ::result [coroutine cotest cotest::body]
691 namespace delete cotest
692 namespace eval cotest {}
693 lappend ::result [cotest]
696 } -returnCodes error -cleanup {
697 catch {namespace delete ::cotest}
698 catch {rename cotest ""}
699 } -result {yieldto called in deleted namespace}
700 test coroutine-7.10 {yieldto context nuke: Bug a90d9331bc} -setup {
701 namespace eval cotest {}
704 proc cotest::body {} {
708 yieldto ::return -level 0 -cotest [namespace delete ::cotest] 123
712 lappend ::result [coroutine cotest cotest::body]
713 lappend ::result [cotest]
716 } -returnCodes error -cleanup {
717 catch {namespace delete ::cotest}
718 catch {rename cotest ""}
719 } -result {yieldto called in deleted namespace}
720 test coroutine-7.11 {yieldto context nuke: Bug a90d9331bc} -setup {
721 namespace eval cotest {}
724 proc cotest::body {} {
729 $y ::return -level 0 -cotest [namespace delete ::cotest] 123
733 lappend ::result [coroutine cotest cotest::body]
734 lappend ::result [cotest]
737 } -returnCodes error -cleanup {
738 catch {namespace delete ::cotest}
739 catch {rename cotest ""}
740 } -result {yieldto called in deleted namespace}
741 test coroutine-7.12 {coro floor above street level #3008307} -body {
749 cc ; # coro created at level 2
750 C ; # and called at level 1
752 boom ; # does not crash: the coro floor is a good insulator
755 rename boom {}; rename cc {}; rename c {}
758 test coroutine-8.0.0 {coro inject executed} -body {
759 coroutine demo apply {{} { foreach i {1 2} yield }}
762 tcl::unsupported::inject demo set ::result inject-executed
765 } -result {inject-executed}
766 test coroutine-8.0.1 {coro inject after error} -body {
767 coroutine demo apply {{} { foreach i {1 2} yield; error test }}
770 tcl::unsupported::inject demo set ::result inject-executed
771 lappend ::result [catch {demo} err] $err
772 } -result {inject-executed 1 test}
773 test coroutine-8.1.1 {coro inject, ticket 42202ba1e5ff566e} -body {
776 coroutine demo apply {{} { while {1} yield }}
778 tcl::unsupported::inject demo set ::result inject-executed
782 test coroutine-8.1.2 {coro inject with result, ticket 42202ba1e5ff566e} -body {
785 coroutine demo apply {{} { while {1} yield }}
787 tcl::unsupported::inject demo set ::result inject-executed
790 set result [child eval {set ::result}]
794 } -result {inject-executed}
796 test coroutine-9.1 {coro type} {
797 coroutine demo eval {
800 yieldto string cat "PHASE 2"
801 ::tcl::unsupported::corotype [info coroutine]
803 list [demo] [::tcl::unsupported::corotype demo] \
804 [demo] [::tcl::unsupported::corotype demo] [demo]
805 } {{PHASE 1} yield {PHASE 2} yieldto active}
806 test coroutine-9.2 {coro type} -setup {
807 catch {rename nosuchcommand ""}
808 } -returnCodes error -body {
809 ::tcl::unsupported::corotype nosuchcommand
810 } -result {can only get coroutine type of a coroutine}
811 test coroutine-9.3 {coro type} -returnCodes error -body {
812 proc notacoroutine {} {}
813 ::tcl::unsupported::corotype notacoroutine
814 } -returnCodes error -cleanup {
815 rename notacoroutine {}
816 } -result {can only get coroutine type of a coroutine}
818 test coroutine-10.1 {coroutine general introspection} -setup {
819 set i [interp create]
822 # Make the introspection code
823 namespace path tcl::unsupported
824 proc probe {type var} {
828 set result [list $v [dict get [info frame $f] proc]]
829 if {$type eq "yield"} {
830 tailcall yield $result
832 tailcall yieldto string cat $result
835 proc pokecoro {c var} {
836 inject $c probe [corotype $c] $var
840 # Coroutine implementations
842 set val [info coroutine]
844 while {[set val [yield $val]] ne ""} {
851 set val [info coroutine]
853 while {[llength [set val [yieldto string cat $val]]]} {
854 lappend accum {*}$val
860 # Make the coroutines
863 list [c1 abc] [c2 1 2 3] [pokecoro c1 accum] [pokecoro c2 accum] \
864 [c1 def] [c2 4 5 6] [pokecoro c1 accum] [pokecoro c2 accum] \
869 } -result {ok ok {abc ::cbody1} {{1 2 3} ::cbody2} ok ok {{abc def} ::cbody1} {{1 2 3 4 5 6} ::cbody2} {abc def} {1 2 3 4 5 6}}
873 ::tcltest::cleanupTests