aboutsummaryrefslogtreecommitdiff
path: root/testsuite/runtest.libs/mockutil.tcl
blob: 20b6b9bf7710d6700af2e96d728943f07412c5e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# Copyright (C) 2019 Free Software Foundation, Inc.
#
# This file is part of DejaGnu.
#
# DejaGnu is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# DejaGnu is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with DejaGnu; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.

# This file was written by Jacob Bachmeyer.

# This library provides convenience procedures for running isolated tests
# of DejaGnu procedures in a slave interpreter.  These are designed to be
# run in the child process used by the DejaGnu library tests.

proc strip_comment_lines { text } {
    regsub -all -- {\n[[:space:]]*#[^\r\n]*[\r\n]+} $text "\n"
}

proc create_test_interpreter { name opts } {
    array set opt {
	copy_arrays {} copy_procs {} copy_vars {} attach_vfs {}
	link_channels {} link_procs {} shim_procs {} mocks {} vars {}
    }
    array set opt [strip_comment_lines $opts]

    interp create -safe -- $name
    foreach array $opt(copy_arrays) { # inlined due to upvar
	if { [llength $array] == 2 } {
	    upvar [lindex $array 1] src_array
	} elseif { [llength $array] == 1 } {
	    upvar [lindex $array 0] src_array
	} else {
	    error "bogus copy_arrays directive: $array"
	}
	$name eval array set [list [lindex $array 0] [array get src_array]]
    }
    foreach proc $opt(copy_procs) { # inlined due to uplevel
	# proc reconstruction adapted from Tcl info(n) manpage
	set argspec [list]
	foreach arg [uplevel info args $proc] {
	    if { [uplevel info default $proc $arg value] } {
		lappend argspec [list $arg $value]
	    } else {
		lappend argspec [list $arg]
	    }
	}
	$name eval proc $proc [list $argspec] [list [uplevel info body $proc]]
    }
    foreach var $opt(copy_vars) { # inlined due to upvar
	if { [llength $var] == 2 } {
	    upvar [lindex $var 1] src_var
	} elseif { [llength $var] == 1 } {
	    upvar [lindex $var 0] src_var
	} else {
	    error "bogus copy_vars directive: $var"
	}
	$name eval set [list [lindex $var 0] $src_var]
    }
    foreach {varname var} $opt(vars) {
	$name eval set [list $varname $var]
    }
    foreach {mockname arglist retexpr} $opt(mocks) {
	establish_mock $name $mockname $arglist $retexpr
    }
    foreach chan $opt(link_channels)	{ interp share {} $chan $name }
    foreach link $opt(link_procs)	{ establish_link $name $link }
    foreach shim $opt(shim_procs)	{ establish_shim $name $shim }
    if { $opt(attach_vfs) ne "" } {
	attach_mockvfs $name [lindex $opt(attach_vfs) 0]
    }
    return $name
}
proc copy_array_to_test_interpreter { sicmd dest {src {}} } {
    if { $src eq {} } { set src $dest }
    upvar $src src_array
    $sicmd eval array set [list $dest [array get src_array]]
}
proc delete_test_interpreter { name } {
    interp delete $name
}

proc reset_mock_trace {} {
    global mock_call_trace
    set mock_call_trace [list]
}
proc dump_mock_trace {} {
    global mock_call_trace
    puts "<<< mocked calls recorded"
    foreach cell $mock_call_trace {
	puts "  [lindex $cell 0]"
	if { [llength $cell] > 1 } {
	    puts "    -> [lindex $cell 1]"
	}
    }
    puts ">>> mocked calls recorded"
}
proc get_mock_trace {} {
    global mock_call_trace
    return $mock_call_trace
}
proc find_mock_calls { prefix } {
    global mock_call_trace
    set result [list]
    foreach cell $mock_call_trace {
	if { [string match "${prefix}*" [lindex $cell 0]] } {
	    lappend result $cell
	}
    }
    return $result
}

proc relay_link_call { name args } {
    eval [list $name] $args
}
proc establish_link { sicmd name } {
    $sicmd alias $name relay_link_call $name
}

proc record_mock_call { name args } {
    global mock_call_trace
    lappend mock_call_trace [list [linsert $args 0 $name]]
    return
}
proc establish_mock_log_alias { sicmd name } {
    $sicmd alias logcall_$name record_mock_call $name
}
proc establish_mock { sicmd name arglist retexpr } {
    establish_mock_log_alias $sicmd $name

    set sargl [list]
    foreach arg $arglist { lappend sargl [format {$%s} $arg] }

    if { [lindex $arglist end] eq "args" } {
	set log_call \
	    "eval \[list logcall_$name [join [lrange $sargl 0 end-1]]\] \$args"
    } else {
	set log_call \
	    "logcall_$name [join $sargl]"
    }

    $sicmd eval [subst -nocommands {
	proc $name {$arglist} {
	    $log_call
	    return $retexpr
	}
    }]
}

proc relay_shim_call { name args } {
    global mock_call_trace
    set retval [eval [list $name] $args]
    lappend mock_call_trace [list [linsert $args 0 $name] [list $retval]]
    return $retval
}
proc establish_shim { sicmd name } {
    $sicmd alias $name relay_shim_call $name
}

proc match_argpat { argpat call } {
    set result 1
    foreach {pos qre} $argpat {
	set qre [regsub -all {\M\s+(?=[^*+?\s])} $qre {\s+}]
	set qre [regsub -all {([*+?])\s+(?=[^*+?\s])} $qre {\1\s+} ]
	set out [lindex $call 0 $pos]
	verbose "matching: ^$qre$"
	verbose " against:  $out"
	if { ![regexp "^$qre$" $out] } { set result 0 }
    }
    return $result
}

# test_proc_with_mocks testName sicmd testCode {
#   check_calls {
#       prefix mode:[*U[:digit:]] { [argument pattern]... }
#       prefix mode:[!] { }
#       prefix mode:[C] [ { count } | count ]
#   }
# }
proc test_proc_with_mocks { name sicmd code args } {
    array set opt {
	check_calls {}
    }
    foreach { key value } $args {
	if { ![info exists opt($key)] } {
	    error "test_proc_with_mocks: unknown option $key"
	}
	set opt($key) [strip_comment_lines $value]
    }

    verbose "--------  begin test: $name"
    reset_mock_trace
    $sicmd eval $code
    dump_mock_trace

    set result pass
    foreach { prefix callpos argpat } $opt(check_calls) {
	set calls [find_mock_calls $prefix]

	verbose "checking: \[$callpos\] $prefix"
	if { $callpos eq "*" } {
	    # succeed if any call matches both prefix and argpat
	    set innerresult fail
	    foreach { call } $calls {
		verbose "    step: [lindex $call 0]"
		if { [match_argpat $argpat $call] } {
		    set innerresult pass
		    break
		}
	    }
	    if { $innerresult ne "pass" } {
		verbose "  failed!"
		set result fail
	    }
	} elseif { $callpos eq "!" } {
	    # succeed if no calls match prefix
	    if { [llength $calls] != 0 } {
		verbose "  failed!"
		set result fail
	    }
	} elseif { $callpos eq "C" } {
	    # succeed if exactly N calls match prefix
	    if { [llength $calls] != [lindex $argpat 0] } {
		verbose "  failed!"
		set result fail
	    }
	} elseif { $callpos eq "U" } {
	    # prefix selects one unique call
	    if { [llength $calls] != 1 } {
		error "expected unique call"
		return
	    }
	    if { ![match_argpat $argpat [lindex $calls 0]] } {
		verbose "  failed!"
		set result fail
	    }
	} elseif { [llength $calls] > $callpos } {
	    if { ![match_argpat $argpat [lindex $calls $callpos]] } {
		verbose "  failed!"
		set result fail
	    }
	} else {
	    error "failed to select trace record"
	    return
	}
    }

    $result $name
    verbose "--------    end test: $name"
}


#EOF