# Commands covered:  (test)thread
#
# 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) 1996 Sun Microsystems, Inc.
# Copyright (c) 1998-1999 by Scriptics Corporation.
# Copyright (c) 2006-2008 by Joe Mistachkin.  All rights reserved.
#
# 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 2.2
    namespace import -force ::tcltest::*
}

# Some tests require the testthread command

testConstraint testthread [expr {[info commands testthread] != {}}]

# Some tests require the Thread package

testConstraint thread [expr {0 == [catch {package require Thread 2.7}]}]

testConstraint notValgrind [expr {![testConstraint valgrind]}]

proc ThreadError {id info} {
    global threadId threadError
    set threadId $id
    set threadError $info
}

if {[testConstraint thread]} {
    thread::errorproc ThreadError
}

if {[testConstraint testthread]} {
    testthread errorproc ThreadError

    set mainThread [testthread id]

    proc ThreadNullError {id info} {
	# ignore
    }

    proc threadReap {} {
	testthread errorproc ThreadNullError
	while {[llength [testthread names]] > 1} {
	    foreach tid [testthread names] {
		if {$tid != [testthread id]} {
		    catch {
			testthread send -async $tid {testthread exit}
		    }
		}
	    }
	    after 1
	}
	testthread errorproc ThreadError
	return [llength [testthread names]]
    }
}

test thread-1.3 {Tcl_ThreadObjCmd: initial thread list} {thread} {
    llength [thread::names]
} 1
test thread-1.4 {Tcl_ThreadObjCmd: thread create } {thread} {
    set serverthread [thread::create -preserved]
    set numthreads [llength [thread::names]]
    thread::release $serverthread
    set numthreads
} {2}
test thread-1.5 {Tcl_ThreadObjCmd: thread create one shot} {thread} {
    thread::create {set x 5}
    foreach try {0 1 2 4 5 6} {
	# Try various ways to yield
	update
	after 10
	set l [llength [thread::names]]
	if {$l == 1} {
	    break
	}
    }
    set l
} {1}
test thread-1.6 {Tcl_ThreadObjCmd: thread exit} {thread} {
    thread::create {{*}{}}
    update
    after 10
    llength [thread::names]
} {1}
test thread-1.13 {Tcl_ThreadObjCmd: send args} {thread} {
    set serverthread [thread::create -preserved]
    set five [thread::send $serverthread {set x 5}]
    thread::release $serverthread
    set five
} 5
test thread-1.15 {Tcl_ThreadObjCmd: wait} {thread} {
    set serverthread [thread::create -preserved {set z 5 ; thread::wait}]
    set five [thread::send $serverthread {set z}]
    thread::release $serverthread
    set five
} 5

# The tests above also cover:
# TclCreateThread, except when pthread_create fails
# NewThread, safe and regular
# ThreadErrorProc, except for printing to standard error

test thread-2.1 {ListUpdateInner and ListRemove} {thread} {
    catch {unset tid}
    foreach t {0 1 2} {
	upvar #0 t$t tid
	set tid [thread::create -preserved]
    }
    foreach t {0 1 2} {
	upvar #0 t$t tid
	thread::release $tid
    }
    llength [thread::names]
} 1

test thread-3.1 {TclThreadList} {thread} {
    catch {unset tid}
    set len [llength [thread::names]]
    set l1  {}
    foreach t {0 1 2} {
	lappend l1 [thread::create -preserved]
    }
    set l2 [thread::names]
    set c [string compare [lsort [concat [thread::id] $l1]] [lsort $l2]]
    foreach t $l1 {
	thread::release $t
    }
    list $len $c
} {1 0}

test thread-4.1 {TclThreadSend to self} {thread} {
    catch {unset x}
    thread::send [thread::id] {
	set x 4
    }
    set x
} {4}
test thread-4.2 {TclThreadSend -async} {thread} {
    set len [llength [thread::names]]
    set serverthread [thread::create -preserved]
    thread::send -async $serverthread {
	after 1 {thread::release}
    }
    set two [llength [thread::names]]
    after 100 {set done 1}
    vwait done
    list $len [llength [thread::names]] $two
} {1 1 2}
test thread-4.3 {TclThreadSend preserve errorInfo} {thread} {
    set len [llength [thread::names]]
    set serverthread [thread::create -preserved]
    set x [catch {thread::send $serverthread {set undef}} msg]
    set savedErrorInfo $::errorInfo
    thread::release $serverthread
    list $len $x $msg $savedErrorInfo
} {1 1 {can't read "undef": no such variable} {can't read "undef": no such variable
    while executing
"set undef"
    invoked from within
"thread::send $serverthread {set undef}"}}
test thread-4.4 {TclThreadSend preserve code} {thread} {
    set len [llength [thread::names]]
    set serverthread [thread::create -preserved]
    set ::errorInfo {}
    set x [catch {thread::send $serverthread {set ::errorInfo {}; break}} msg]
    set savedErrorInfo $::errorInfo
    thread::release $serverthread
    list $len $x $msg $savedErrorInfo
} {1 3 {} {}}
test thread-4.5 {TclThreadSend preserve errorCode} {thread} {
    set serverthread [thread::create]
    set x [catch {thread::send $serverthread {error ERR INFO CODE}} msg]
    set savedErrorCode $::errorCode
    thread::release $serverthread
    list $x $msg $savedErrorCode
} {1 ERR CODE}


test thread-5.0 {Joining threads} {thread} {
    set serverthread [thread::create -joinable -preserved]
    thread::send -async $serverthread {after 1000 ; thread::release}
    thread::join $serverthread
} {0}
test thread-5.1 {Joining threads after the fact} {thread} {
    set serverthread [thread::create -joinable -preserved]
    thread::send -async $serverthread {thread::release}
    after 2000
    thread::join $serverthread
} {0}
test thread-5.2 {Try to join a detached thread} {thread} {
    set serverthread [thread::create -preserved]
    thread::send -async $serverthread {after 1000 ; thread::release}
    catch {set res [thread::join $serverthread]} msg
    while {[llength [thread::names]] > 1} {
	after 20
    }
    lrange $msg 0 2
} {cannot join thread}

test thread-6.1 {freeing very large object trees in a thread} thread {
    # conceptual duplicate of obj-32.1
    set serverthread [thread::create -preserved]
    thread::send -async $serverthread {
	set x {}
	for {set i 0} {$i<100000} {incr i} {
	    set x [list $x {}]
	}
	unset x
    }
    thread::release -wait $serverthread
} 0

# TIP #285: Script cancellation support
test thread-7.4 {cancel: pure bytecode loop} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    while {1} {
		# No bytecode at all here...
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::cancel $serverthread]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval canceled}}
test thread-7.5 {cancel: pure inside-command loop} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    set while while
	    $while {1} {
		# No bytecode at all here...
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel $serverthread]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval canceled}}
test thread-7.6 {cancel: pure bytecode loop -unwind} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    while {1} {
		# No bytecode at all here...
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel -unwind $serverthread]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.7 {cancel: pure inside-command loop -unwind} -constraints thread -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    set while while
	    $while {1} {
		# No bytecode at all here...
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel -unwind $serverthread]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.8 {cancel: pure bytecode loop custom result} -constraints thread -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    while {1} {
		# No bytecode at all here...
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel $serverthread "the eval was canceled"]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {the eval was canceled}}
test thread-7.9 {cancel: pure inside-command loop custom result} -constraints {
    thread
} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    set while while
	    $while {1} {
		# No bytecode at all here...
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel $serverthread "the eval was canceled"]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {the eval was canceled}}
test thread-7.10 {cancel: pure bytecode loop custom result -unwind} -constraints {
    thread
} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    while {1} {
		# No bytecode at all here...
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel -unwind $serverthread "the eval was unwound"]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {the eval was unwound}}
test thread-7.11 {cancel: pure inside-command loop custom result -unwind} -constraints {
    thread
} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    set while while
	    $while {1} {
		# No bytecode at all here...
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel -unwind $serverthread "the eval was unwound"]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {the eval was unwound}}
test thread-7.12 {cancel: after} -constraints thread -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	if {![info exists foo]} then {
	    # signal the primary thread that we are ready
	    # to be canceled now (we are running).
	    thread::send %ID% [list set ::threadIdStarted [thread::id]]
	    set foo 1
	}
	after 30000
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel $serverthread]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval canceled}}
test thread-7.13 {cancel: after -unwind} -constraints thread -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	if {![info exists foo]} then {
	    # signal the primary thread that we are ready
	    # to be canceled now (we are running).
	    thread::send %ID% [list set ::threadIdStarted [thread::id]]
	    set foo 1
	}
	after 30000
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel -unwind $serverthread]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.14 {cancel: vwait} -constraints thread -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID [thread::id]] {
	if {![info exists foo]} then {
	    # signal the primary thread that we are ready
	    # to be canceled now (we are running).
	    thread::send %ID% [list set ::threadIdStarted [thread::id]]
	    set foo 1
	}
	vwait forever
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel $serverthread]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval canceled}}
test thread-7.15 {cancel: vwait -unwind} -constraints thread -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	if {![info exists foo]} then {
	    # signal the primary thread that we are ready
	    # to be canceled now (we are running).
	    thread::send %ID% [list set ::threadIdStarted [thread::id]]
	    set foo 1
	}
	vwait forever
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel -unwind $serverthread]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.16 {cancel: expr} -constraints thread -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID [thread::id]] {
	set i [interp create]
	$i eval "package require -exact Thread [package present Thread]"
	$i eval {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).

		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    expr {[while {1} {incr x}]}
	}
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel $serverthread]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval canceled}}
test thread-7.17 {cancel: expr -unwind} -constraints thread -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	set i [interp create]
	$i eval "package require -exact Thread [package present Thread]"
	$i eval {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    expr {[while {1} {incr x}]}
	}
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait threadIdStarted
    set res [thread::cancel -unwind $serverthread]
    vwait threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.18 {cancel: expr bignum} {thread knownBug} {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
        set i [interp create]
	$i eval "package require -exact Thread [package present Thread]"
        $i eval {
            if {![info exists foo]} then {
                # signal the primary thread that we are ready
                # to be canceled now (we are running).
                thread::send %ID% [list set ::threadIdStarted [thread::id]]
                set foo 1
            }
            #
            # BUGBUG: This will not cancel because libtommath
            #         does not check Tcl_Canceled.
            #
            expr {2**99999}
        }
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted; after 1000
    set res [thread::cancel $serverthread]
    thread::join $serverthread; # WARNING: Never returns due to bug (see above).
    list $res [expr {[info exists ::threadIdStarted] ? \
                  $::threadIdStarted == $serverthread : 0}] \
              [expr {[info exists ::threadId] ? \
                  $::threadId == $serverthread : 0}] \
              [expr {[info exists ::threadError] ? \
                  [lindex [split $::threadError \n] 0] : "" }]
} {{} 1 0 {}}
test thread-7.19 {cancel: expr bignum -unwind} {thread knownBug} {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
        set i [interp create]
	$i eval "package require -exact Thread [package present Thread]"
        $i eval {
            if {![info exists foo]} then {
                # signal the primary thread that we are ready
                # to be canceled now (we are running).
                thread::send %ID% [list set ::threadIdStarted [thread::id]]
                set foo 1
            }
            #
            # BUGBUG: This will not cancel because libtommath
            #         does not check Tcl_Canceled.
            #
            expr {2**99999}
        }
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted; after 1000
    set res [thread::cancel -unwind $serverthread]
    thread::join $serverthread; # WARNING: Never returns due to bug (see above).
    list $res [expr {[info exists ::threadIdStarted] ? \
                  $::threadIdStarted == $serverthread : 0}] \
              [expr {[info exists ::threadId] ? \
                  $::threadId == $serverthread : 0}] \
              [expr {[info exists ::threadError] ? \
                  [lindex [split $::threadError \n] 0] : "" }]
} {{} 1 0 {}}
test thread-7.20 {cancel: subst} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	set i [interp create]
	$i eval "package require -exact Thread [package present Thread]"
	$i eval {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    subst {[while {1} {incr x}]}
	}
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::cancel $serverthread]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval canceled}}
test thread-7.21 {cancel: subst -unwind} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	set i [interp create]
	$i eval "package require -exact Thread [package present Thread]"
	$i eval {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    subst {[while {1} {incr x}]}
	}
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::cancel -unwind $serverthread]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.22 {cancel: slave interp} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	set i [interp create]
	$i eval "package require -exact Thread [package present Thread]"
	$i eval {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    while {1} {}
	}
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::cancel $serverthread]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval canceled}}
test thread-7.23 {cancel: slave interp -unwind} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	set i [interp create]
	$i eval "package require -exact Thread [package present Thread]"
	$i eval {
	    if {![info exists foo]} then {
		# signal the primary thread that we are ready
		# to be canceled now (we are running).
		thread::send %ID% [list set ::threadIdStarted [thread::id]]
		set foo 1
	    }
	    set while while; $while {1} {}
	}
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::cancel -unwind $serverthread]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.24 {cancel: nested catch inside pure bytecode loop} {thread} {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		catch {
		    while {1} {
			catch {
			    while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted; after 1000
    set res [thread::cancel $serverthread]
    after 1000; # wait for ThreadErrorProc to be called.
    catch {thread::release $serverthread}
    list $res [expr {[info exists ::threadIdStarted] ? \
		  $::threadIdStarted == $serverthread : 0}] \
	      [expr {[info exists ::threadId] ? \
		  $::threadId == $serverthread : 0}] \
	      [expr {[info exists ::threadError] ? \
		  [lindex [split $::threadError \n] 0] : "" }]
} {{} 1 0 {}}
test thread-7.25 {cancel: nested catch inside pure inside-command loop} {thread} {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    set catch catch
	    set while while
	    $while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		$catch {
		    $while {1} {
			$catch {
			    $while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted; after 1000
    set res [thread::cancel $serverthread]
    after 1000; # wait for ThreadErrorProc to be called.
    catch {thread::release $serverthread}
    list $res [expr {[info exists ::threadIdStarted] ? \
		  $::threadIdStarted == $serverthread : 0}] \
	      [expr {[info exists ::threadId] ? \
		  $::threadId == $serverthread : 0}] \
	      [expr {[info exists ::threadError] ? \
		  [lindex [split $::threadError \n] 0] : "" }]
} {{} 1 0 {}}
test thread-7.26 {cancel: send async cancel bad interp path} {thread} {
    unset -nocomplain ::threadIdStarted
    set serverthread [thread::create -preserved \
	[string map [list %ID% [thread::id]] {
	proc foobar {} {
	    while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		update
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    catch {thread::send $serverthread {interp cancel -- bad}} msg
    thread::send -async $serverthread {interp cancel -unwind}
    thread::release -wait $serverthread
    list [expr {$::threadIdStarted == $serverthread}] $msg
} {1 {could not find interpreter "bad"}}
test thread-7.27 {cancel: send async cancel -- switch} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	set i [interp create -- -unwind]
	$i eval "package require -exact Thread [package present Thread]"
	$i eval {
	    proc foobar {} {
		while {1} {
		    if {![info exists foo]} then {
			# signal the primary thread that we are ready
			# to be canceled now (we are running).
			thread::send %ID% [list set ::threadIdStarted [thread::id]]
			set foo 1
		    }
		    update
		}
	    }
	    foobar
	}
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::send -async $serverthread {interp cancel -- -unwind}]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval canceled}}
test thread-7.28 {cancel: send async cancel nested catch inside pure bytecode loop} {thread} {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		catch {
		    while {1} {
			catch {
			    while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted; after 1000
    set res [thread::send -async $serverthread {interp cancel}]
    after 1000; # wait for ThreadErrorProc to be called.
    catch {thread::release $serverthread}
    list $res [expr {[info exists ::threadIdStarted] ? \
		  $::threadIdStarted == $serverthread : 0}] \
	      [expr {[info exists ::threadId] ? \
		  $::threadId == $serverthread : 0}] \
	      [expr {[info exists ::threadError] ? \
		  [lindex [split $::threadError \n] 0] : "" }]
} {{} 1 0 {}}
test thread-7.29 {cancel: send async cancel nested catch pure inside-command loop} {thread} {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    set catch catch
	    set while while
	    $while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		$catch {
		    $while {1} {
			$catch {
			    $while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted; after 1000
    set res [thread::send -async $serverthread {interp cancel}]
    after 1000; # wait for ThreadErrorProc to be called.
    catch {thread::release $serverthread}
    list $res [expr {[info exists ::threadIdStarted] ? \
		  $::threadIdStarted == $serverthread : 0}] \
	      [expr {[info exists ::threadId] ? \
		  $::threadId == $serverthread : 0}] \
	      [expr {[info exists ::threadError] ? \
		  [lindex [split $::threadError \n] 0] : "" }]
} {{} 1 0 {}}
test thread-7.30 {cancel: send async thread cancel nested catch inside pure bytecode loop} {thread} {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		catch {
		    while {1} {
			catch {
			    while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted; after 1000
    set res [thread::send -async $serverthread {thread::cancel [thread::id]}]
    after 1000; # wait for ThreadErrorProc to be called.
    catch {thread::release $serverthread}
    list $res [expr {[info exists ::threadIdStarted] ? \
		  $::threadIdStarted == $serverthread : 0}] \
	      [expr {[info exists ::threadId] ? \
		  $::threadId == $serverthread : 0}] \
	      [expr {[info exists ::threadError] ? \
		  [lindex [split $::threadError \n] 0] : "" }]
} {{} 1 0 {}}
test thread-7.31 {cancel: send async thread cancel nested catch pure inside-command loop} {thread} {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    set catch catch
	    set while while
	    $while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		$catch {
		    $while {1} {
			$catch {
			    $while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted; after 1000
    set res [thread::send -async $serverthread {thread::cancel [thread::id]}]
    after 1000; # wait for ThreadErrorProc to be called.
    catch {thread::release $serverthread}
    list $res [expr {[info exists ::threadIdStarted] ? \
		  $::threadIdStarted == $serverthread : 0}] \
	      [expr {[info exists ::threadId] ? \
		  $::threadId == $serverthread : 0}] \
	      [expr {[info exists ::threadError] ? \
		  [lindex [split $::threadError \n] 0] : "" }]
} {{} 1 0 {}}
test thread-7.32 {cancel: nested catch inside pure bytecode loop -unwind} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		catch {
		    while {1} {
			catch {
			    while {1} {
				# No bytecode at all here...
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::cancel -unwind $serverthread]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.33 {cancel: nested catch inside pure inside-command loop -unwind} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    set catch catch
	    set while while
	    $while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		$catch {
		    $while {1} {
			$catch {
			    $while {1} {
				# No bytecode at all here...
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::cancel -unwind $serverthread]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.34 {cancel: send async cancel nested catch inside pure bytecode loop -unwind} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		catch {
		    while {1} {
			catch {
			    while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::send -async $serverthread {interp cancel -unwind}]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.35 {cancel: send async cancel nested catch inside pure inside-command loop -unwind} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    set catch catch
	    set while while
	    $while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		$catch {
		    $while {1} {
			$catch {
			    $while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::send -async $serverthread {interp cancel -unwind}]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.36 {cancel: send async thread cancel nested catch inside pure bytecode loop -unwind} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID% [thread::id]] {
	proc foobar {} {
	    while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		catch {
		    while {1} {
			catch {
			    while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::send -async $serverthread {thread::cancel -unwind [thread::id]}]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}
test thread-7.37 {cancel: send async thread cancel nested catch inside pure inside-command loop -unwind} -constraints {thread} -setup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -body {
    set serverthread [thread::create -joinable \
	    [string map [list %ID [thread::id]] {
	proc foobar {} {
	    set catch catch
	    set while while
	    $while {1} {
		if {![info exists foo]} then {
		    # signal the primary thread that we are ready
		    # to be canceled now (we are running).
		    thread::send %ID% [list set ::threadIdStarted [thread::id]]
		    set foo 1
		}
		$catch {
		    $while {1} {
			$catch {
			    $while {1} {
				# we must call update here because otherwise
				# the thread cannot even be forced to exit.
				update
			    }
			}
		    }
		}
	    }
	}
	foobar
    }]]
    # wait for other thread to signal "ready to cancel"
    vwait ::threadIdStarted
    set res [thread::send -async $serverthread {thread::cancel -unwind [thread::id]}]
    vwait ::threadId
    thread::join $serverthread
    list $res [expr {$::threadIdStarted == $serverthread}] \
	      [expr {$::threadId == $serverthread}] \
	      [lindex [split $::threadError \n] 0]
} -cleanup {
    unset -nocomplain ::threadError ::threadId ::threadIdStarted
} -result {{} 1 1 {eval unwound}}

# cleanup
::tcltest::cleanupTests
return
