1# hook.test -*- tcl -*- 2# 3# This file contains the test suite for hook.tcl. 4# 5# Copyright (C) 2010 by Will Duquette 6# Copyright (c) 2019 by Andreas Kupries 7# 8# See the file "license.terms" for information on usage and 9# redistribution of this file, and for a DISCLAIMER OF ALL 10# WARRANTIES. 11 12#----------------------------------------------------------------------- 13 14source [file join \ 15 [file dirname [file dirname [file join [pwd] [info script]]]] \ 16 devtools testutilities.tcl] 17 18testsNeedTcl 8.5 19testsNeedTcltest 2.1 20 21support { 22} 23testing { 24 useLocal hook.tcl hook 25} 26 27#----------------------------------------------------------------------- 28# Helper procs 29 30variable info 31array set info { 32 callList {} 33 traceList {} 34 errorList {} 35} 36 37proc cleanup {} { 38 variable info 39 array set info { 40 callList {} 41 traceList {} 42 errorList {} 43 } 44 45 foreach subject [hook bind] { 46 hook forget $subject 47 } 48 49 hook configure -errorcommand {} -tracecommand {} 50 51 # Ensure that auto-generated observers are repeatable. 52 set ::hook::observerCounter 0 53} 54 55proc TestBinding {subject hook observer args} { 56 variable info 57 58 lappend info(callList) [list $subject $hook $observer $args] 59 60 return 61} 62 63proc GetCalls {} { 64 variable info 65 66 return $info(callList) 67} 68 69proc TraceCommand {subject hook args observers} { 70 variable info 71 72 lappend info(traceList) [list $subject $hook $args $observers] 73} 74 75proc GetTrace {} { 76 variable info 77 78 return $info(traceList) 79} 80 81proc TestBind {subject hook observer} { 82 hook bind $subject $hook $observer \ 83 [list TestBinding $subject $hook $observer] 84} 85 86proc ErrorCommand {call result opts} { 87 variable info 88 89 set opts [dict remove $opts -errorinfo -errorline] 90 91 lappend info(errorList) [list $call $result $opts] 92} 93 94proc GetError {} { 95 variable info 96 97 return $info(errorList) 98} 99 100#----------------------------------------------------------------------- 101# cget 102 103test cget-1.1 {unknown option name} -body { 104 hook cget -nonesuch 105} -returnCodes { 106 error 107} -result {unknown option "-nonesuch"} 108 109test cget-1.2 {retrieve option value} -body { 110 hook cget -errorcommand 111} -result {} 112 113#----------------------------------------------------------------------- 114# configure 115 116test configure-1.1 {unknown option name} -body { 117 hook configure -nonesuch 118} -returnCodes { 119 error 120} -result {unknown option "-nonesuch"} 121 122test configure-1.2 {missing option value} -body { 123 hook configure -errorcommand 124} -returnCodes { 125 error 126} -result {value for "-errorcommand" missing} 127 128test configure-2.1 {set values} -body { 129 hook configure -errorcommand foo -tracecommand bar 130 131 list [hook cget -errorcommand] [hook cget -tracecommand] 132} -cleanup { 133 hook configure -errorcommand {} -tracecommand {} 134} -result {foo bar} 135 136#----------------------------------------------------------------------- 137# bind 138 139test bind-1.1 {too many arguments} -body { 140 hook bind a b c d e 141} -returnCodes { 142 error 143} -result "wrong # args: should be \"hook bind ?subject? ?hook? ?observer? ?binding?\"" 144 145test bind-2.1 {bindings can be made} -body { 146 hook bind S1 <H1> O1 {B1 arg1 arg2} 147 hook bind S1 <H1> O1 148} -cleanup { 149 cleanup 150} -result {B1 arg1 arg2} 151 152test bind-2.2 {bindings can be deleted} -body { 153 hook bind S1 <H1> O1 {B1 arg1 arg2} 154 hook bind S1 <H1> O1 {} 155 hook bind S1 <H1> O1 156} -cleanup { 157 cleanup 158} -result {} 159 160test bind-3.1 {bound observers can be queried} -body { 161 hook bind S1 <H1> O1 B1 162 hook bind S1 <H1> O2 B2 163 hook bind S2 <H1> O2 B3 164 165 set a [hook bind S1 <H1>] 166 set b [hook bind S2 <H1>] 167 set c [hook bind S2 <H2>] 168 169 list $a $b $c 170} -cleanup { 171 cleanup 172} -result {{O1 O2} O2 {}} 173 174test bind-3.2 {bound hooks can be queried} -body { 175 hook bind S1 <H1> O1 B1 176 hook bind S1 <H2> O2 B2 177 hook bind S2 <H3> O2 B3 178 179 set a [hook bind S1] 180 set b [hook bind S2] 181 set c [hook bind S3] 182 183 list $a $b $c 184} -cleanup { 185 cleanup 186} -result {{<H1> <H2>} <H3> {}} 187 188test bind-3.3 {bound subjects can be queried} -body { 189 hook bind S1 <H1> O1 B1 190 hook bind S1 <H2> O2 B2 191 hook bind S2 <H3> O2 B3 192 193 hook bind 194} -cleanup { 195 cleanup 196} -result {S1 S2} 197 198test bind-3.4 {deleted bindings can no longer be queried} -body { 199 hook bind S1 <H1> O1 B1 200 hook bind S1 <H1> O2 B2 201 hook bind S2 <H1> O2 B3 202 203 hook bind S1 <H1> O2 {} 204 205 set a [hook bind S1 <H1>] 206 set b [hook bind S2 <H1>] 207 set c [hook bind S2 <H2>] 208 209 list $a $b $c 210} -cleanup { 211 cleanup 212} -result {O1 O2 {}} 213 214 215test bind-4.1 {auto-generated observer is returned} -body { 216 hook bind S1 <H1> "" {B1 arg1 arg2} 217} -cleanup { 218 cleanup 219} -result {::hook::ob1} 220 221test bind-4.2 {auto-generated observer is a real observer} -body { 222 set ob [hook bind S1 <H1> "" {B1 arg1 arg2}] 223 hook bind S1 <H1> $ob 224} -cleanup { 225 cleanup 226} -result {B1 arg1 arg2} 227 228test bind-4.3 {successive calls get distinct observers} -body { 229 set a [hook bind S1 <H1> "" {B1 arg1 arg2}] 230 set b [hook bind S1 <H2> "" {B2 arg1 arg2}] 231 list $a $b 232} -cleanup { 233 cleanup 234} -result {::hook::ob1 ::hook::ob2} 235 236test bind-5.1 {binding deleted during hook call is not called} -body { 237 # If a subject/hook is called, and if a binding deletes some 238 # other binding to that same subject/hook, and if the second binding 239 # has not yet been called, it should not be called. 240 241 hook bind S1 <H1> O1 {hook bind S1 <H1> O2 ""} 242 TestBind S1 <H1> O2 243 TestBind S1 <H1> O3 244 hook call S1 <H1> 245 246 # Should see O3 but not O2. 247 GetCalls 248} -cleanup { 249 cleanup 250} -result {{S1 <H1> O3 {}}} 251 252test bind-5.2 {binding revised during hook call is called} -body { 253 # If a subject/hook is called, and if a binding changes some 254 # other observer's binding to that same subject/hook, and if the 255 # other observer's binding has not yet been called, it is the 256 # changed binding that will be called. 257 258 hook bind S1 <H1> O1 {TestBind S1 <H1> O2} 259 hook bind S1 <H1> O2 {error "Rebind Failed"} 260 261 hook call S1 <H1> 262 263 # Should see O2 in result, instead of getting "Rebind Failed" error. 264 GetCalls 265} -cleanup { 266 cleanup 267} -result {{S1 <H1> O2 {}}} 268 269test bind-5.3 {binding added during hook call is not called} -body { 270 # If a subject/hook is called, and a binding adds a new binding 271 # for a new observer for this same subject/hook, the new binding 272 # will not be called this time around. 273 274 hook bind S1 <H1> O1 {TestBind S1 <H1> O3} 275 TestBind S1 <H1> O2 276 277 hook call S1 <H1> 278 279 # Should see O2 in result, but not O3 280 GetCalls 281} -cleanup { 282 cleanup 283} -result {{S1 <H1> O2 {}}} 284 285 286#----------------------------------------------------------------------- 287# forget 288 289test forget-1.1 {can forget safely when not yet initialized} -body { 290 hook forget NONESUCH 291} -result {} 292 293test forget-1.2 {can forget unbound entity safely} -body { 294 hook bind S1 <H1> O1 B1 295 hook forget NONESUCH 296 hook bind S1 <H1> O1 297} -cleanup { 298 cleanup 299} -result {B1} 300 301test forget-1.3 {can forget subject} -body { 302 hook bind S1 <H1> O1 B1 303 hook bind S2 <H2> O2 B2 304 hook bind S3 <H3> O3 B3 305 306 hook forget S2 307 hook bind 308} -cleanup { 309 cleanup 310} -result {S1 S3} 311 312test forget-1.4 {can forget subject} -body { 313 hook bind S1 <H1> O1 B1 314 hook bind S2 <H2> O2 B2 315 hook bind S3 <H3> O3 B3 316 317 hook forget O2 318 hook bind S2 <H2> 319} -cleanup { 320 cleanup 321} -result {} 322 323test forget-2.1 {observer forgotten during hook call is not called} -body { 324 # If an observer has a binding to a particular subject/hook, and if 325 # in a call to that subject/hook the observer is forgotten, and 326 # if that observer's binding has not yet been called, it should not 327 # be called. 328 329 hook bind S1 <H1> O1 {hook forget O2} 330 TestBind S1 <H1> O2 331 TestBind S1 <H1> O3 332 333 hook call S1 <H1> 334 335 # Should get O3 but not O2 336 GetCalls 337} -cleanup { 338 cleanup 339} -result {{S1 <H1> O3 {}}} 340 341test forget-2.2 {subject forgotten during hook call, no more calls} -body { 342 # If a subject/hook is called, and some binding forgets the subject, 343 # no uncalled bindings for that subject/hook should be called. 344 345 TestBind S1 <H1> O1 346 hook bind S1 <H1> O2 {hook forget S1} 347 TestBind S1 <H1> O3 348 349 hook call S1 <H1> 350 351 # Should get O1 but not O3 352 GetCalls 353} -cleanup { 354 cleanup 355} -result {{S1 <H1> O1 {}}} 356 357 358 359#----------------------------------------------------------------------- 360# call 361 362test call-1.1 {can call safely before anything is bound} -body { 363 hook call S1 <H1> 364} -result {} 365 366test call-1.2 {can call safely when hook isn't bound} -body { 367 hook bind S1 <H1> O1 B1 368 hook call S2 <H2> 369} -cleanup { 370 cleanup 371} -result {} 372 373test call-1.3 {bindings are executed} -body { 374 TestBind S1 <H1> O1 375 hook call S1 <H1> 376 GetCalls 377} -cleanup { 378 cleanup 379} -result {{S1 <H1> O1 {}}} 380 381test call-1.4 {multiple bindings are executed} -body { 382 TestBind S1 <H1> O1 383 TestBind S1 <H1> O2 384 hook call S1 <H1> 385 GetCalls 386} -cleanup { 387 cleanup 388} -result {{S1 <H1> O1 {}} {S1 <H1> O2 {}}} 389 390test call-1.5 {only relevant bindings are executed} -body { 391 TestBind S1 <H1> O1 392 TestBind S2 <H1> O2 393 hook call S1 <H1> 394 GetCalls 395} -cleanup { 396 cleanup 397} -result {{S1 <H1> O1 {}}} 398 399test call-2.1 {errors propagate normally} -body { 400 hook bind S1 <H1> O1 {error "Simulated Error"} 401 hook call S1 <H1> 402} -returnCodes { 403 error 404} -cleanup { 405 cleanup 406} -result {Simulated Error} 407 408test call-2.2 {other exceptions propagate normally} -body { 409 hook bind S1 <H1> O1 {return -code break "Simulated Break"} 410 hook call S1 <H1> 411} -returnCodes { 412 break 413} -cleanup { 414 cleanup 415} -result {Simulated Break} 416 417 418#----------------------------------------------------------------------- 419# -errorcommand 420 421test errorerror-1.1 {error with -errorcommand {}} -body { 422 hook bind S1 <H1> O1 {error "simulated error"} 423 hook call S1 <H1> 424} -returnCodes { 425 error 426} -cleanup { 427 cleanup 428} -result {simulated error} 429 430test errorcommand-1.2 {error with -errorcommand set} -body { 431 hook configure -errorcommand ErrorCommand 432 433 hook bind S1 <H1> O1 {error "simulated error"} 434 hook call S1 <H1> 435 GetError 436} -cleanup { 437 cleanup 438} -result [tcltest::byConstraint { 439 tcl8.6.10plus {{{S1 <H1> {} O1} {simulated error} {-code 1 -level 0 -errorstack {INNER {error {simulated error}} UP 1 CALL {::hook::call S1 <H1>}} -errorcode NONE}}} 440 tcl8.6not10 {{{S1 <H1> {} O1} {simulated error} {-code 1 -level 0 -errorstack {INNER {error {simulated error}} UP 1 CALL {call S1 <H1>}} -errorcode NONE}}} 441 tcl8.5minus {{{S1 <H1> {} O1} {simulated error} {-code 1 -level 0 -errorcode NONE}}} 442}] 443 444test errorcommand-1.3 {handled errors don't break sequence of calls} -body { 445 hook configure -errorcommand ErrorCommand 446 447 TestBind S1 <H1> O1 448 hook bind S1 <H1> O2 {error "simulated error"} 449 TestBind S1 <H1> O3 450 hook call S1 <H1> 451 list [GetCalls] [GetError] 452} -cleanup { 453 cleanup 454} -result [tcltest::byConstraint { 455 tcl8.6.10plus {{{S1 <H1> O1 {}} {S1 <H1> O3 {}}} {{{S1 <H1> {} O2} {simulated error} {-code 1 -level 0 -errorstack {INNER {error {simulated error}} UP 1 CALL {::hook::call S1 <H1>}} -errorcode NONE}}}} 456 tcl8.6not10 {{{S1 <H1> O1 {}} {S1 <H1> O3 {}}} {{{S1 <H1> {} O2} {simulated error} {-code 1 -level 0 -errorstack {INNER {error {simulated error}} UP 1 CALL {call S1 <H1>}} -errorcode NONE}}}} 457 tcl8.5minus {{{S1 <H1> O1 {}} {S1 <H1> O3 {}}} {{{S1 <H1> {} O2} {simulated error} {-code 1 -level 0 -errorcode NONE}}}} 458}] 459 460test errorcommand-1.4 {-errorcommand handles other exceptions} -body { 461 hook configure -errorcommand ErrorCommand 462 463 hook bind S1 <H1> O1 {return -code break "simulated break"} 464 hook call S1 <H1> 465 GetError 466} -cleanup { 467 cleanup 468} -result {{{S1 <H1> {} O1} {simulated break} {-code 3 -level 1}}} 469 470#----------------------------------------------------------------------- 471# -tracecommand 472 473test tracecommand-1.1 {-tracecommand is called} -body { 474 TestBind S1 <H1> O1 475 TestBind S1 <H1> O2 476 TestBind S2 <H2> O2 477 478 hook configure -tracecommand TraceCommand 479 hook call S1 <H1> 480 hook call S2 <H2> 481 hook call S3 <H3> 482 GetTrace 483} -cleanup { 484 cleanup 485} -result {{S1 <H1> {} {O1 O2}} {S2 <H2> {} O2} {S3 <H3> {} {}}} 486 487#----------------------------------------------------------------------- 488# Clean up and finish 489 490::tcltest::cleanupTests 491return 492