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