# testutils.tcl -- # # This file holds utility procs, each of which is used by several test files # in the Tk test suite. # # The procs are defined per functional area of Tk (also called "domain"), # similar to the names of test files: # - generic utility procs that don't belong to a specific functional area go # into the namespace ::tk::test. # - those that do belong to a specific functional area go into a child namespace # of ::tk::test that bears the name of that functional area. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # # DEFINITIONS OF GENERIC UTILITY PROCS # namespace eval tk { namespace eval test { namespace export loadTkCommand proc loadTkCommand {} { set tklib {} foreach pair [info loaded {}] { foreach {lib pfx} $pair break if {$pfx eq "Tk"} { set tklib $lib break } } return [list load $tklib Tk] } namespace eval bg { # Manage a background process. # Replace with child interp or thread? namespace import ::tcltest::interpreter namespace import ::tk::test::loadTkCommand namespace export setup cleanup do proc cleanup {} { variable fd # catch in case the background process has closed $fd catch {puts $fd exit} catch {close $fd} set fd "" } proc setup args { variable fd if {[info exists fd] && [string length $fd]} { cleanup } set fd [open "|[list [interpreter] \ -geometry +0+0 -name tktest] $args" r+] puts $fd "puts foo; flush stdout" flush $fd if {[gets $fd data] < 0} { error "unexpected EOF from \"[interpreter]\"" } if {$data ne "foo"} { error "unexpected output from\ background process: \"$data\"" } puts $fd [loadTkCommand] flush $fd fileevent $fd readable [namespace code Ready] } proc Ready {} { variable fd variable Data variable Done set x [gets $fd] if {[eof $fd]} { fileevent $fd readable {} set Done 1 } elseif {$x eq "**DONE**"} { set Done 1 } else { append Data $x } } proc do {cmd {block 0}} { variable fd variable Data variable Done if {$block} { fileevent $fd readable {} } puts $fd "[list catch $cmd msg]; update; puts \$msg;\ puts **DONE**; flush stdout" flush $fd set Data {} if {$block} { while {![eof $fd]} { set line [gets $fd] if {$line eq "**DONE**"} { break } append Data $line } } else { set Done 0 vwait [namespace which -variable Done] } return $Data } } proc Export {internal as external} { uplevel 1 [list namespace import $internal] uplevel 1 [list rename [namespace tail $internal] $external] uplevel 1 [list namespace export $external] } Export bg::setup as setupbg Export bg::cleanup as cleanupbg Export bg::do as dobg namespace export deleteWindows proc deleteWindows {} { destroy {*}[winfo children .] # This update is needed to avoid intermittent failures on macOS in unixEmbed.test # with the (GitHub Actions) CI runner. # Reason for the failures is unclear but could have to do with window ids being deleted # after the destroy command returns. The detailed mechanism of such delayed deletions # is not understood, but it appears that this update prevents the test failures. update } namespace export fixfocus proc fixfocus {} { catch {destroy .focus} toplevel .focus wm geometry .focus +0+0 entry .focus.e .focus.e insert 0 "fixfocus" pack .focus.e update focus -force .focus.e destroy .focus } namespace export imageInit imageFinish imageCleanup imageNames variable ImageNames proc imageInit {} { variable ImageNames if {![info exists ImageNames]} { set ImageNames [lsearch -all -inline -glob -not [lsort [image names]] ::tk::icons::indicator*] } imageCleanup if {[lsort [image names]] ne $ImageNames} { return -code error "IMAGE NAMES mismatch: [image names] != $ImageNames" } } proc imageFinish {} { variable ImageNames set imgs [lsearch -all -inline -glob -not [lsort [image names]] ::tk::icons::indicator*] if {$imgs ne $ImageNames} { return -code error "images remaining: [image names] != $ImageNames" } imageCleanup } proc imageCleanup {} { variable ImageNames foreach img [image names] { if {$img ni $ImageNames} {image delete $img} } } proc imageNames {} { variable ImageNames set r {} foreach img [image names] { if {$img ni $ImageNames} {lappend r $img} } return $r } # # CONTROL TIMING ASPECTS OF POINTER WARPING # # The proc [controlPointerWarpTiming] is intended to ensure that the (mouse) # pointer has actually been moved to its new position after a Tk test issued: # # [event generate $w $event -warp 1 ...] # # It takes care of the following timing details of pointer warping: # # a. Allow pointer warping to happen if it was scheduled for execution at # idle time. This happens synchronously if $w refers to the # whole screen or if the -when option to [event generate] is "now". # # b. Work around a race condition associated with OS notification of # mouse motion on Windows. # # When calling [event generate $w $event -warp 1 ...], the following # sequence occurs: # - At some point in the processing of this command, either via a # synchronous execution path, or asynchronously at idle time, Tk calls # an OS function* to carry out the mouse cursor motion. # - Tk has previously registered a callback function** with the OS, for # the OS to call in order to notify Tk when a mouse move is completed. # - Tk doesn't wait for the callback function to receive the notification # from the OS, but continues processing. This suits most use cases # because usually the notification arrives fast enough (within a few tens # of microseconds). However ... # - A problem arises if Tk performs some processing, immediately following # up on [event generate $w $event -warp 1 ...], and that processing # relies on the mouse pointer having actually moved. If such processing # happens just before the notification from the OS has been received, # Tk will be using not yet updated info (e.g. mouse coordinates). # # Hickup, choke etc ... ! # # * the function SendInput() of the Win32 API # ** the callback function is TkWinChildProc() # # This timing issue can be addressed by putting the Tk process on hold # (do nothing at all) for a somewhat extended amount of time, while # letting the OS complete its job in the meantime. This is what is # accomplished by calling [after ms]. # # ---- # For the history of this issue please refer to Tk ticket [69b48f427e], # specifically the comment on 2019-10-27 14:24:26. # # # Beware: there are cases, not (yet) exercised by the Tk test suite, where # [controlPointerWarpTiming] doesn't ensure the new position of the pointer. # For example, when issued under Tk8.7+, if the value for the -when option # to [event generate $w] is not "now", and $w refers to a Tk window, i.e. not # the whole screen. # proc controlPointerWarpTiming {{duration 50}} { update idletasks ;# see a. above if {[tk windowingsystem] eq "win32"} { after $duration ;# see b. above } } namespace export controlPointerWarpTiming # On macOS windows are not allowed to overlap the menubar at the top of the # screen or the dock. So tests which move a window and then check whether it # got moved to the requested location should use a y coordinate larger than the # height of the menubar (normally 23 pixels) and an x coordinate larger than the # width of the dock, if it happens to be on the left. # menubarheight deals with this issue but may not be available from the test # environment, therefore provide a fallback here if {[llength [info procs menubarheight]] == 0} { if {[tk windowingsystem] ne "aqua"} { # Windows may overlap the menubar proc menubarheight {} { return 0 } } else { # Windows may not overlap the menubar proc menubarheight {} { return 30 ; # arbitrary value known to be larger than the menubar height } } namespace export menubarheight } } } namespace import -force tk::test::* # # DEFINITIONS OF UTILITY PROCS PER FUNCTIONAL AREA # # Utility procs are defined and used per functional area of Tk as indicated by # the names of test files. The namespace names below ::tk::test correspond to # these functional areas. # namespace eval ::tk::test::scroll { # scrollInfo -- # # Used as the scrolling command for widgets, set with "-[xy]scrollcommand". # It saves the scrolling information in, or retrieves it from a namespace # variable "scrollInfo". # variable scrollInfo {} proc scrollInfo {mode args} { variable scrollInfo switch -- $mode { get { return $scrollInfo } set { set scrollInfo $args } } } namespace export * } namespace eval ::tk::test::select { proc errHandler args { error "selection handler aborted" } namespace export * } # # TODO: RELOCATE UTILITY PROCS CATEGORY B. HERE # (As indicated by the spreadsheet file "relocate.ods") # # EOF