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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
|
# See the file LICENSE for redistribution information.
#
# Copyright (c) 2012, 2012 Oracle and/or its affiliates. All rights reserved.
#
# $Id$
#
# Utility functions for multi process tests in Core and SQL
# The SQL test suite shell (testfixture) lacks some basic Tcl functions
# that are required by do_multi_proc_test and do_sync, like clock.
# So load the tcl library if it has not already been loaded.
proc load_tcl_library {} {
global tcl_platform
set nameexec [info nameofexecutable]
if { [string match *testfixture* $nameexec] } {
set loaded [info loaded]
if { [lsearch $loaded tcl*] == -1 &&
[lsearch $loaded libtcl*] == -1 } {
set isWindows 0
set os $tcl_platform(platform)
set tclversion [info tclversion]
if { [string equal -nocase "windows" $os] } {
# Get the version number and strip the . from it
for {set x 0} {$x < [string length $tclversion]} {incr x} {
set char [string index $tclversion $x]
if { [string equal \. $char] } {
set tclversion [string replace $tclversion $x $x]
}
}
load tcl$tclversion[info sharedlibextension] Tcl
} else {
load libtcl$tclversion[info sharedlibextension] Tcl
}
}
}
}
load_tcl_library
# do_multi_proc_test - Takes a list of scripts and executes them
# as separate processes, and reports any errors or test failures
# to the error log for Core tests, and the error counter for
# SQL tests. Look at db/test/sql/bdb_multi_proc.test for a
# test example. Output from the tests are redirected to
# TESTOUTPUT/err_[script number]_[testname].txt
#
# name - Name of the test.
# scripts - A list of scripts that will be writen to separate
# files then executed by tclsh for Core tests, and testfixture
# for SQL tests.
# args_lists - A list of lists containing arguments to pass
# to the test scripts.
# verbose - Print verbose output to the script error log.
proc do_multi_proc_test { name scripts args_lists {verbose 0} } {
set working_dir [pwd]
if [ catch {source ./include.tcl} ] {
eval cd ..
if [ catch {source ./include.tcl} ] {
eval cd ..
source ./include.tcl
}
}
source $test_path/testutils.tcl
set error_dir [pwd]/TESTOUTPUT
# May have to create the test directory if in the SQL suite
if { [file exists $testdir] == 0 } {
file mkdir $testdir
}
# Create the error directory if not already there.
if { [file exists $error_dir] == 0 } {
file mkdir $error_dir
}
# Write script files as $counter_$name.tcl
set counter 1
set fileNames {}
set errLogs {}
foreach script $scripts {
set fileName "${counter}_${name}.tcl"
lappend fileNames $fileName
lappend errLogs "err_${counter}_${name}.txt"
incr counter
set aFile [open $fileName w]
puts $aFile $script
flush $aFile
close $aFile
}
# Run scripts
sentinel_init
set pidlist {}
set working_dir [pwd]
# For core the executable is tclsh, for SQL it is testfixture
set exec_name [info nameofexecutable]
foreach fileName $fileNames errLog $errLogs arg_list $args_lists {
if { $verbose } {
puts "Starting script $working_dir/$fileName with arguments $arg_list, and writing to error log $error_dir/$errLog"
}
lappend pidlist [exec $exec_name $test_path/wrap.tcl \
$working_dir/$fileName \
$error_dir/$errLog $arg_list &]
}
# Wait for scripts to finish
watch_procs $pidlist 1 600 0
# Clean up old script files
foreach fileName $fileNames {
catch {file delete -force -- $fileName}
}
# Check for errors in the script logs
foreach errLog $errLogs {
set fd [open $error_dir/$errLog r]
# If this is the SQL test suite check for the success
# message, and if it is not found call fail_test,
# otherwise we are in the Core test suite so call error
set procs [info procs fail_test]
set proc_name [lindex $procs 0]
if { [string match fail_test $proc_name] } {
set success 0
while { [gets $fd str] != -1 } {
if { [string match "0 errors out of * tests" $str] } {
set success 1
break
}
}
if {!$success} {
fail_test $errLog
}
} else {
while { [gets $fd str] != -1 } {
if { [string match FAIL:* $str] ||
[string match Error:* $str] } {
close $fd
error "FAIL: found message $str"
}
}
}
close $fd
}
eval cd $working_dir
}
global ::sync_server_results
# do_sync - Synchronizes a set of processes. Works by forcing each process
# to block until it can connect to the servers of each other process,
# and receive a connection on its server from the other processes.
# Returns 0 on successful synchronization, and -1 on failure.
# For an example of how to use this, go to
# db/test/sql/bdb_multi_proc.test.
#
# myPort - Port that the other processes should connect to this process.
# clientPorts - A list of ports for all other processes to
# synchronize with.
# timeout - The number of seconds after which the function will abandon
# trying to synchronize with the other processes and will return -1.
# verbose - If set to non-0 prints verbose output.
#
# Note that this procedure is probably not thread safe.
# It is meant to be used to synchronize processes, not threads with
# shared memory. The Thread Tcl library already has functions for
# synchronizing threads.
proc do_sync { myPort clientPorts timeout {verbose 0} } {
package require Thread
#Get the number of clients
set numClients [llength $clientPorts]
unset -nocomplain ::sync_server_results
# Accept connections to the server until timeout is reached
set server_thread [thread::create {
global ::numCon
global ::numClients
global ::server_connections
# Called by the server to keep track of how many connections have
# occured.
proc my_connections {sock addr port} {
puts $sock "success"
close $sock
incr ::numCon
# Race condition does not matter here since we can set
# server_connections to 0 twice without a problem
if { $::numCon >= $::numClients } {
set ::server_connections 0
}
}
proc run_server { myPort clients timeout verbose} {
set ::numCon 0
set ::numClients $clients
# Loop until the server connects to all clients or the timeout
# is hit. This is in case one of the client sockets grabed
# the server port as its local port.
set id [after [expr {int($timeout * 1000)}] \
set ::server_connections -1]
while { [info exists ::server_connections] == 0 } {
if [catch { socket -server my_connections -myaddr 127.0.0.1 \
$myPort } server ] {
#if {$verbose} {
# puts "Could not create server at $myPort because of: $server. RETRYING"
#}
catch { close $server }
} else {
vwait ::server_connections
after cancel $id
close $server
}
}
if { $verbose } {
if { $::server_connections == -1 } {
puts "Failure, server at port $myPort reached timeout of $timeout seconds before recieving $::numClients connections."
} else {
puts "Success, server at port $myPort completed connections to $::numClients clients before timeout of $timeout seconds."
}
}
catch {close $server}
set ::sync_server_results $::server_connections
}
thread::wait
}]
#start the server thread
if { $verbose } {
puts "[timestamp]Starting server at port $myPort."
}
thread::send -async $server_thread \
"run_server $myPort $numClients $timeout $verbose" \
::sync_server_results
# Try to connect to each client. If timeout, set an error
# return value and quit trying to connect.
global ::clientconnected
unset -nocomplain ::clientconnected
# After the timeout is reached, set ::clientconnected.
set id [after [expr {int($timeout * 1000)}] \
set ::clientconnected -1]
foreach clientPort $clientPorts {
set returnVal -1
if { $verbose } {
puts "[timestamp] Attempting to contact the server at $clientPort."
}
# Loop until the client connects to the server or the timeout
# is hit.
while { $returnVal == -1 && [info exists ::clientconnected] == 0 } {
update
if [ catch { socket 127.0.0.1 $clientPort } s ] {
#if {$verbose} {
# puts "[timestamp] Could not connect to server at $clientPort because of: $s, RETRYING"
#}
catch {close $s}
} else {
if { $verbose } {
puts "[timestamp] Client connection info: [fconfigure $s -sockname]"
}
# Sometimes the client socket will pick the port it is trying
# to connect to as the port to use on its side, resulting in
# it connecting to itself
set portInfo [fconfigure $s -sockname]
set portOffset [string last " " $portInfo]
incr portOffset
set portInfo [string range $portInfo $portOffset end]
if { $portInfo == $clientPort } {
set returnVal -1
} else {
set line "Could not read server"
if { ![eof $s] } {
set line [gets $s]
}
if { $verbose } {
puts "[timestamp] Read the following from the server: $line"
}
if { [string match success* $line] } {
set returnVal 0
} else {
set returnVal -1
}
}
catch {close $s}
}
}
if { $returnVal == -1 } {
if { $verbose } {
puts "[timestamp] Failed to connect to server at port $clientPort before timeout of $timeout"
}
break
}
if { $verbose } {
puts "[timestamp] Succeeded in completing connection to server at $clientPort"
}
}
after cancel $id
if { $verbose } {
if { $returnVal == -1 } {
puts "[timestamp] Failed to connect ot all servers at ports: $clientPorts"
} else {
puts "[timestamp] Succeeded in connecting to all servers at ports: $clientPorts"
}
}
# wait on the server thread to finish if we have not already
# timed out
if { [eval info exists ::sync_server_results] == 0 } {
vwait ::sync_server_results
}
thread::release $server_thread
if { $verbose } {
if { $::sync_server_results == -1 } {
puts "[timestamp] Failed, server at port $myPort failed to connect to all clients by timeout $timeout seconds."
} else {
puts "[timestamp] Succeeded, server at port $myPort suceeded in connecting to al clients."
}
}
if { $::sync_server_results == -1 } {
set returnVal -1
}
after 500
set returnVal
}
|