ls4 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +1 -0
- data/COPYING +661 -0
- data/ChangeLog +9 -0
- data/NOTICE +8 -0
- data/README.rdoc +61 -0
- data/bin/ls4-cs +3 -0
- data/bin/ls4-ds +3 -0
- data/bin/ls4-gw +3 -0
- data/bin/ls4-standalone +3 -0
- data/bin/ls4cmd +3 -0
- data/bin/ls4ctl +3 -0
- data/bin/ls4rpc +3 -0
- data/bin/ls4stat +3 -0
- data/bin/ls4top +3 -0
- data/lib/ls4/command/cmd.rb +241 -0
- data/lib/ls4/command/cs.rb +190 -0
- data/lib/ls4/command/ctl.rb +278 -0
- data/lib/ls4/command/ds.rb +335 -0
- data/lib/ls4/command/gw.rb +256 -0
- data/lib/ls4/command/rpc.rb +172 -0
- data/lib/ls4/command/standalone.rb +318 -0
- data/lib/ls4/command/stat.rb +244 -0
- data/lib/ls4/command/top.rb +291 -0
- data/lib/ls4/default.rb +26 -0
- data/lib/ls4/lib/cclog.rb +220 -0
- data/lib/ls4/lib/ebus.rb +553 -0
- data/lib/ls4/lib/vbcode.rb +228 -0
- data/lib/ls4/logic/fault_detector.rb +212 -0
- data/lib/ls4/logic/membership.rb +253 -0
- data/lib/ls4/logic/node.rb +66 -0
- data/lib/ls4/logic/okey.rb +45 -0
- data/lib/ls4/logic/tsv_data.rb +81 -0
- data/lib/ls4/logic/weight.rb +166 -0
- data/lib/ls4/service/balance.rb +62 -0
- data/lib/ls4/service/base.rb +29 -0
- data/lib/ls4/service/bus.rb +37 -0
- data/lib/ls4/service/config.rb +63 -0
- data/lib/ls4/service/config_cs.rb +33 -0
- data/lib/ls4/service/config_ds.rb +56 -0
- data/lib/ls4/service/config_gw.rb +42 -0
- data/lib/ls4/service/data_client.rb +122 -0
- data/lib/ls4/service/data_server.rb +168 -0
- data/lib/ls4/service/data_server_url.rb +83 -0
- data/lib/ls4/service/gateway.rb +375 -0
- data/lib/ls4/service/gateway_ro.rb +91 -0
- data/lib/ls4/service/gw_http.rb +821 -0
- data/lib/ls4/service/heartbeat.rb +182 -0
- data/lib/ls4/service/log.rb +81 -0
- data/lib/ls4/service/master_select.rb +148 -0
- data/lib/ls4/service/mds.rb +292 -0
- data/lib/ls4/service/mds_cache.rb +294 -0
- data/lib/ls4/service/mds_cache_mem.rb +63 -0
- data/lib/ls4/service/mds_cache_memcached.rb +65 -0
- data/lib/ls4/service/mds_ha.rb +176 -0
- data/lib/ls4/service/mds_memcache.rb +209 -0
- data/lib/ls4/service/mds_tc.rb +508 -0
- data/lib/ls4/service/mds_tt.rb +472 -0
- data/lib/ls4/service/membership.rb +331 -0
- data/lib/ls4/service/process.rb +90 -0
- data/lib/ls4/service/rpc.rb +50 -0
- data/lib/ls4/service/rpc_cs.rb +101 -0
- data/lib/ls4/service/rpc_ds.rb +96 -0
- data/lib/ls4/service/rpc_gw.rb +255 -0
- data/lib/ls4/service/rts.rb +94 -0
- data/lib/ls4/service/rts_file.rb +76 -0
- data/lib/ls4/service/rts_memory.rb +55 -0
- data/lib/ls4/service/slave.rb +132 -0
- data/lib/ls4/service/stat.rb +91 -0
- data/lib/ls4/service/stat_cs.rb +25 -0
- data/lib/ls4/service/stat_ds.rb +40 -0
- data/lib/ls4/service/stat_gw.rb +25 -0
- data/lib/ls4/service/storage.rb +116 -0
- data/lib/ls4/service/storage_dir.rb +201 -0
- data/lib/ls4/service/sync.rb +206 -0
- data/lib/ls4/service/time_check.rb +80 -0
- data/lib/ls4/service/ulog.rb +159 -0
- data/lib/ls4/service/ulog_file.rb +398 -0
- data/lib/ls4/service/ulog_memory.rb +53 -0
- data/lib/ls4/service/weight.rb +134 -0
- data/lib/ls4/version.rb +5 -0
- data/test/01_add_get_remove.rt +84 -0
- data/test/02_read.rt +61 -0
- data/test/03_getd_readd.rt +69 -0
- data/test/04_version_time.rt +170 -0
- data/test/05_version_name.rt +161 -0
- data/test/06_http_get_set_remove_1.rt +119 -0
- data/test/07_http_get_set_remove_2.rt +116 -0
- data/test/08_read_only_time.rt +177 -0
- data/test/09_read_only_name.rt +173 -0
- data/test/10_http_get_set_remove_3.rt +73 -0
- data/test/11_mds_cache_memcached.rt +88 -0
- data/test/12_mds_cache_local_memory.rt +86 -0
- data/test/13_memcache_mds.rt +84 -0
- data/test/14_delete.rt +63 -0
- data/test/15_standalone.rt +71 -0
- data/test/chukan.rb +516 -0
- data/test/common.rb +250 -0
- data/test/load_test.rb +79 -0
- data/test/load_test_offload.rb +86 -0
- metadata +295 -0
data/test/14_delete.rt
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
3
|
+
require 'common'
|
4
|
+
|
5
|
+
LOOP = (ARGV[0] || ENV["LOOP"] || (ENV["HEAVY"] ? 20 : 3)).to_i
|
6
|
+
SIZE = (ARGV[1] || 10).to_i
|
7
|
+
NUM = (ARGV[2] || 50).to_i
|
8
|
+
|
9
|
+
mds = start_mds
|
10
|
+
cs = start_cs
|
11
|
+
ds0 = start_ds(0, 0)
|
12
|
+
ds1 = start_ds(1, 0)
|
13
|
+
ds2 = start_ds(2, 1)
|
14
|
+
ds3 = start_ds(3, 1)
|
15
|
+
|
16
|
+
cs.show_nodes
|
17
|
+
cs.show_version
|
18
|
+
|
19
|
+
gw = start_gw
|
20
|
+
|
21
|
+
pid = Process.pid
|
22
|
+
keyf = "#{pid}-key%d"
|
23
|
+
_data = "@"*SIZE
|
24
|
+
|
25
|
+
test "run normally" do
|
26
|
+
c = gw.client
|
27
|
+
|
28
|
+
LOOP.times {|o|
|
29
|
+
NUM.times do |i|
|
30
|
+
key = keyf % i
|
31
|
+
_attrs = {"loop"=>o.to_s, "attr#{i}"=>i.to_s}
|
32
|
+
|
33
|
+
test 'add' do
|
34
|
+
c.call(:add, key, _data, _attrs)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
NUM.times do |i|
|
39
|
+
key = keyf % i
|
40
|
+
|
41
|
+
test "delete" do
|
42
|
+
deleted = c.call(:delete, key)
|
43
|
+
test_equals true, deleted, 'deleted == true'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
NUM.times do |i|
|
48
|
+
key = keyf % i
|
49
|
+
|
50
|
+
test 'deleted get' do
|
51
|
+
data, attrs = c.call(:get, key)
|
52
|
+
test_equals nil, data, 'get_data _data is deleted'
|
53
|
+
test_equals nil, attrs, 'get_attrs _attrs is deleted'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
cs.show_items
|
60
|
+
cs.show_stat
|
61
|
+
|
62
|
+
term_all(ds0, ds1, ds2, ds3, gw, mds, cs)
|
63
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
3
|
+
require 'common'
|
4
|
+
|
5
|
+
LOOP = (ARGV[0] || ENV["LOOP"] || (ENV["HEAVY"] ? 20 : 3)).to_i
|
6
|
+
SIZE = (ARGV[1] || 10).to_i
|
7
|
+
NUM = (ARGV[2] || 50).to_i
|
8
|
+
|
9
|
+
standalone = start_standalone
|
10
|
+
|
11
|
+
pid = Process.pid
|
12
|
+
keyf = "#{pid}-key%d"
|
13
|
+
_data = "@"*SIZE
|
14
|
+
|
15
|
+
test "run normally" do
|
16
|
+
c = standalone.client
|
17
|
+
|
18
|
+
LOOP.times {|o|
|
19
|
+
NUM.times do |i|
|
20
|
+
key = keyf % i
|
21
|
+
_attrs = {"loop"=>o.to_s, "attr#{i}"=>i.to_s}
|
22
|
+
|
23
|
+
test 'add' do
|
24
|
+
c.call(:add, key, _data, _attrs)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
NUM.times do |i|
|
29
|
+
key = keyf % i
|
30
|
+
_attrs = {"loop"=>o.to_s, "attr#{i}"=>i.to_s}
|
31
|
+
|
32
|
+
test 'get' do
|
33
|
+
data, attrs = c.call(:get, key)
|
34
|
+
test_equals _data, data, 'get _data == data'
|
35
|
+
test_equals _attrs, attrs, 'get _attrs == attrs'
|
36
|
+
end
|
37
|
+
|
38
|
+
test 'get_data' do
|
39
|
+
data = c.call(:get_data, key)
|
40
|
+
test_equals _data, data, 'get_data _data == data'
|
41
|
+
end
|
42
|
+
|
43
|
+
test 'get_attrs' do
|
44
|
+
attrs = c.call(:get_attrs, key)
|
45
|
+
test_equals _attrs, attrs, 'get_attrs _attrs == attrs'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
NUM.times do |i|
|
50
|
+
key = keyf % i
|
51
|
+
|
52
|
+
test "remove" do
|
53
|
+
removed = c.call(:remove, key)
|
54
|
+
test_equals true, removed, 'removed == true'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
NUM.times do |i|
|
59
|
+
key = keyf % i
|
60
|
+
|
61
|
+
test 'removed get' do
|
62
|
+
data, attrs = c.call(:get, key)
|
63
|
+
test_equals nil, data, 'get_data _data is removed'
|
64
|
+
test_equals nil, attrs, 'get_attrs _attrs is removed'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
term_all(standalone)
|
71
|
+
|
data/test/chukan.rb
ADDED
@@ -0,0 +1,516 @@
|
|
1
|
+
#
|
2
|
+
# Chukan automation library for distributed systems
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
#
|
24
|
+
|
25
|
+
|
26
|
+
####
|
27
|
+
## Basic usage
|
28
|
+
##
|
29
|
+
=begin
|
30
|
+
#!/usr/bin/env ruby
|
31
|
+
require 'chukan'
|
32
|
+
include Chukan # include Chukan
|
33
|
+
|
34
|
+
srv = spawn("server -arg1 -arg2") # run 'server' command
|
35
|
+
# with '-arg1 -arg2' arguments
|
36
|
+
srv.stdout_join("started") # wait until the server outputs "started"
|
37
|
+
|
38
|
+
cli = spawn("client -arg1 -arg2") # run 'client' command with some arguments
|
39
|
+
srv.stdout_join("connected") # wait until the server outputs "connected"
|
40
|
+
|
41
|
+
cli.kill # send SIGKILL signal to the client
|
42
|
+
cli.join # wait until the client is really dead
|
43
|
+
srv.stderr_join(/disconnected/) # stderr and regexp are also available
|
44
|
+
|
45
|
+
srv.stdin.write "status\n" # input "status\n" to the server
|
46
|
+
srv.stdout_join("done") # wait until the server outputs "done"
|
47
|
+
|
48
|
+
if srv.stdout.read =~ /^client:/ # read output of the server
|
49
|
+
puts "** TEST FAILED **" # this library is usable for tests
|
50
|
+
# see also "Unit test" example below
|
51
|
+
end
|
52
|
+
=end
|
53
|
+
|
54
|
+
|
55
|
+
####
|
56
|
+
## Remote process execution
|
57
|
+
##
|
58
|
+
=begin
|
59
|
+
#!/usr/bin/env ruby
|
60
|
+
require 'chukan'
|
61
|
+
include Chukan # include Chukan
|
62
|
+
|
63
|
+
mac = remote("mymac.local") # login to the remote host using ssh and run
|
64
|
+
# commands on the host
|
65
|
+
# use ssh-agent if your key is encrypted
|
66
|
+
mac.cd("work/myproject") # run on "work/myproject" directory
|
67
|
+
|
68
|
+
linux = remote("192.168.10.2", "myname", ".id_rsa_linux")
|
69
|
+
# user name and path of the key is optional
|
70
|
+
|
71
|
+
cli_on_mac = mac.spawn("client -arg1") # run 'client' on the remote host
|
72
|
+
cli_on_linux = linux.spawn("client -arg1")
|
73
|
+
|
74
|
+
cli_on_mac.stdout_join("started") # signals and I/Os are also available
|
75
|
+
=end
|
76
|
+
|
77
|
+
|
78
|
+
####
|
79
|
+
## Unit test
|
80
|
+
##
|
81
|
+
=begin
|
82
|
+
#!/usr/bin/env ruby
|
83
|
+
require 'chukan'
|
84
|
+
include Chukan::Test # include Chukan::Test
|
85
|
+
|
86
|
+
test "load mylibrary" do # Chukan::Test provides 'test' and 'run' methods
|
87
|
+
require "mylibrary" # test will fail if the block returns nil or false,
|
88
|
+
# or an exception is raised
|
89
|
+
end
|
90
|
+
|
91
|
+
run {|b| # 'run' iterates YAML documents written after
|
92
|
+
# __END__ line
|
93
|
+
test "score <= 100", :TODO do # second argument of 'test' is :TODO or :SKIP
|
94
|
+
b.score <= 100 # which is useful for Test Anything Protocol
|
95
|
+
end # (TAP) processor like 'prove'
|
96
|
+
}
|
97
|
+
|
98
|
+
__END__
|
99
|
+
--- # YAML documents are here
|
100
|
+
name: test A
|
101
|
+
user: a-san
|
102
|
+
score: 10
|
103
|
+
---
|
104
|
+
name: test B
|
105
|
+
user: b-san
|
106
|
+
score: 100
|
107
|
+
=end
|
108
|
+
|
109
|
+
|
110
|
+
require 'stringio'
|
111
|
+
require 'strscan'
|
112
|
+
require 'monitor'
|
113
|
+
require 'fcntl'
|
114
|
+
|
115
|
+
|
116
|
+
module Chukan
|
117
|
+
IO_BUFFER_LIMIT = 1024*1024
|
118
|
+
|
119
|
+
class LocalProcess
|
120
|
+
def initialize(*cmdline)
|
121
|
+
@cmdline = cmdline.map {|x| x.to_s }
|
122
|
+
@status = nil
|
123
|
+
@shortname = cmdline.join(' ').split(/\s/)
|
124
|
+
@shortname[0] = File.basename(@shortname[0])
|
125
|
+
@shortname = @shortname.join(' ')[0, 12]
|
126
|
+
start
|
127
|
+
end
|
128
|
+
|
129
|
+
attr_reader :cmdline
|
130
|
+
attr_reader :stdin, :stdout, :stderr
|
131
|
+
attr_reader :pid
|
132
|
+
attr_reader :status
|
133
|
+
|
134
|
+
def join
|
135
|
+
@status = Process.waitpid2(@pid)[1]
|
136
|
+
@stdout_reader.join
|
137
|
+
@stderr_reader.join
|
138
|
+
@killer.killed
|
139
|
+
reason = @status.inspect
|
140
|
+
if m = reason.match(/\,([^\>]*)\>/)
|
141
|
+
reason = m[1]
|
142
|
+
end
|
143
|
+
$stderr.puts "#{@msg_prefix}#{reason}"
|
144
|
+
@status
|
145
|
+
end
|
146
|
+
|
147
|
+
def stdout_join(pattern, &block)
|
148
|
+
io_join(@stdout, pattern, &block)
|
149
|
+
end
|
150
|
+
|
151
|
+
def stderr_join(pattern, &block)
|
152
|
+
io_join(@stderr, pattern, &block)
|
153
|
+
end
|
154
|
+
|
155
|
+
def signal(sig)
|
156
|
+
Process.kill(sig, @pid) rescue nil
|
157
|
+
self
|
158
|
+
end
|
159
|
+
|
160
|
+
def kill
|
161
|
+
signal(:SIGKILL)
|
162
|
+
end
|
163
|
+
|
164
|
+
def term
|
165
|
+
signal(:SIGTERM)
|
166
|
+
end
|
167
|
+
|
168
|
+
def hup
|
169
|
+
signal(:SIGHUP)
|
170
|
+
end
|
171
|
+
|
172
|
+
def set_display(shortname)
|
173
|
+
if shortname.length <= 12
|
174
|
+
@msg_prefix[0..-1] = "[%-12s %6d] " % [shortname, @pid]
|
175
|
+
elsif shortname.length < 19
|
176
|
+
@msg_prefix[0..-1] = "[%-19s] " % [shortname]
|
177
|
+
else
|
178
|
+
@msg_prefix[0..-1] = "[#{shortname}] "
|
179
|
+
end
|
180
|
+
self
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
def io_join(io, pattern, &block)
|
185
|
+
if pattern.is_a?(String)
|
186
|
+
pattern = Regexp.new(Regexp.escape(pattern))
|
187
|
+
end
|
188
|
+
if block
|
189
|
+
io.synchronize {
|
190
|
+
io.read
|
191
|
+
}
|
192
|
+
yield
|
193
|
+
end
|
194
|
+
match = nil
|
195
|
+
io.synchronize {
|
196
|
+
until match = io.scanner.scan_until(pattern)
|
197
|
+
if io.closed_write?
|
198
|
+
raise EOFError.new("io closed: #{pattern.inspect}")
|
199
|
+
end
|
200
|
+
io.cond.wait
|
201
|
+
end
|
202
|
+
}
|
203
|
+
match
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
def start
|
208
|
+
stdin, @stdin = IO.pipe
|
209
|
+
@pout, pout = IO.pipe
|
210
|
+
@perr, perr = IO.pipe
|
211
|
+
@pid = fork
|
212
|
+
unless @pid
|
213
|
+
@stdin.close
|
214
|
+
@pout.close
|
215
|
+
@perr.close
|
216
|
+
$stdin.reopen(stdin) rescue nil
|
217
|
+
$stdout.reopen(pout) rescue nil
|
218
|
+
$stderr.reopen(perr) rescue nil
|
219
|
+
exec *cmdline
|
220
|
+
exit 127
|
221
|
+
end
|
222
|
+
stdin.close
|
223
|
+
pout.close
|
224
|
+
perr.close
|
225
|
+
@stdin.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
226
|
+
@pout.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
227
|
+
@perr.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
228
|
+
|
229
|
+
@msg_prefix = "[%-12s %6d] " % [@shortname, @pid]
|
230
|
+
$stdout.puts "#{@msg_prefix}#{@cmdline.join(' ')}"
|
231
|
+
|
232
|
+
@stdout, @stdout_reader = self.class.start_scan(@pout, $stdout, @msg_prefix)
|
233
|
+
@stderr, @stderr_reader = self.class.start_scan(@perr, $stderr, @msg_prefix)
|
234
|
+
|
235
|
+
@killer = ZombieKiller.define_finalizer(self, @pid)
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.start_scan(pipe, out, msg_prefix)
|
239
|
+
io = StringIO.new
|
240
|
+
io.extend(MonitorMixin)
|
241
|
+
cond = io.new_cond
|
242
|
+
scanner = StringScanner.new(io.string)
|
243
|
+
(class<<io; self; end).instance_eval do
|
244
|
+
define_method(:cond) { cond }
|
245
|
+
define_method(:scanner) { scanner }
|
246
|
+
end
|
247
|
+
|
248
|
+
reader = Thread.start(pipe, io,
|
249
|
+
out, msg_prefix,
|
250
|
+
&method(:reader_thread))
|
251
|
+
|
252
|
+
return io, reader
|
253
|
+
end
|
254
|
+
|
255
|
+
def self.reader_thread(src, dst, msgout, msg_prefix)
|
256
|
+
buf = ""
|
257
|
+
line = ''
|
258
|
+
begin
|
259
|
+
while src.sysread(1024, buf)
|
260
|
+
dst.synchronize {
|
261
|
+
dst.string << buf
|
262
|
+
if dst.string.size > IO_BUFFER_LIMIT
|
263
|
+
cut = dst.string.size - IO_BUFFER_LIMIT
|
264
|
+
dst.string.slice!(0, cut)
|
265
|
+
dst.pos = (dst.pos > cut) ?
|
266
|
+
dst.pos - cut : 0
|
267
|
+
dst.scanner.pos = (dst.scanner.pos > cut) ?
|
268
|
+
dst.scanner.pos - cut : 0
|
269
|
+
end
|
270
|
+
dst.cond.signal
|
271
|
+
}
|
272
|
+
line << buf
|
273
|
+
line.gsub!(/.*\n/) {|l|
|
274
|
+
msgout.puts "#{msg_prefix}#{l}"
|
275
|
+
msgout.flush
|
276
|
+
""
|
277
|
+
}
|
278
|
+
end
|
279
|
+
rescue
|
280
|
+
nil
|
281
|
+
ensure
|
282
|
+
src.close
|
283
|
+
dst.synchronize {
|
284
|
+
dst.close_write
|
285
|
+
dst.cond.signal
|
286
|
+
}
|
287
|
+
unless line.empty?
|
288
|
+
msgout.puts "#{msg_prefix}#{line}"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
class ZombieKiller
|
296
|
+
def initialize(pid)
|
297
|
+
@pid = pid
|
298
|
+
end
|
299
|
+
def killed
|
300
|
+
@pid = nil
|
301
|
+
end
|
302
|
+
attr_reader :pid
|
303
|
+
|
304
|
+
def self.define_finalizer(obj, pid)
|
305
|
+
killer = self.new(pid)
|
306
|
+
ObjectSpace.define_finalizer(obj, self.finalizer(killer))
|
307
|
+
killer
|
308
|
+
end
|
309
|
+
|
310
|
+
def self.finalizer(killer)
|
311
|
+
proc {
|
312
|
+
if pid = killer.pid
|
313
|
+
[:SIGTERM, :SIGKILL].each {|sig|
|
314
|
+
Process.kill(sig, pid)
|
315
|
+
break if 10.times {
|
316
|
+
begin
|
317
|
+
if Process.waitpid(pid, Process::WNOHANG)
|
318
|
+
break true
|
319
|
+
end
|
320
|
+
sleep 0.1
|
321
|
+
rescue
|
322
|
+
break true
|
323
|
+
end
|
324
|
+
nil
|
325
|
+
}
|
326
|
+
}
|
327
|
+
end
|
328
|
+
}
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
class RemoteProcess < LocalProcess
|
334
|
+
def initialize(remote, *cmdline, &block)
|
335
|
+
@remote = remote
|
336
|
+
|
337
|
+
cmdline_real = ["echo","$$","&&","exec"] + cmdline
|
338
|
+
super(*remote.command(*cmdline_real), &block)
|
339
|
+
|
340
|
+
@shortname = File.basename(cmdline.first.split(/\s/,2).first)[0, 7] +
|
341
|
+
"@"+remote.host[0,5]
|
342
|
+
|
343
|
+
stdout_join("\n")
|
344
|
+
@rpid = stdout.gets.to_i
|
345
|
+
end
|
346
|
+
attr_reader :rpid
|
347
|
+
|
348
|
+
def signal(sig)
|
349
|
+
system(*@remote.command("kill -#{sig} #{@rpid}"))
|
350
|
+
self
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
class Remote
|
356
|
+
def initialize(host, user = nil, key = nil)
|
357
|
+
@host = host
|
358
|
+
@user = user
|
359
|
+
@key = key
|
360
|
+
@dir = nil
|
361
|
+
end
|
362
|
+
attr_reader :host
|
363
|
+
|
364
|
+
def cd(dir = nil)
|
365
|
+
@dir = dir
|
366
|
+
self
|
367
|
+
end
|
368
|
+
|
369
|
+
def command(*cmdline)
|
370
|
+
ssh = ENV["SSH"] || "ssh"
|
371
|
+
cmd = [ssh, "-o", "Batchmode yes"]
|
372
|
+
cmd += ["-i", @key] if @key
|
373
|
+
if @user
|
374
|
+
cmd.push "#{@user}:#{@host}"
|
375
|
+
else
|
376
|
+
cmd.push @host
|
377
|
+
end
|
378
|
+
if @dir
|
379
|
+
cmd += ["cd", @dir, "&&"]
|
380
|
+
end
|
381
|
+
cmd + cmdline.map {|x| x.to_s }
|
382
|
+
end
|
383
|
+
|
384
|
+
def spawn(*cmdline, &block)
|
385
|
+
RemoteProcess.new(self, *cmdline, &block)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
|
390
|
+
def spawn(*cmdline, &block)
|
391
|
+
LocalProcess.new(*cmdline, &block)
|
392
|
+
end
|
393
|
+
|
394
|
+
def remote(host, user = nil, key = nil)
|
395
|
+
Remote.new(host, user, key)
|
396
|
+
end
|
397
|
+
|
398
|
+
|
399
|
+
module Test
|
400
|
+
@@start = nil
|
401
|
+
@@cases = Hash.new {|hash,key| hash[key] = [0,0,0] }
|
402
|
+
@@count = 0
|
403
|
+
@@data = nil
|
404
|
+
|
405
|
+
if ENV["TERM"] =~ /color/i && $stdout.stat.chardev?
|
406
|
+
SEPARATOR = ""
|
407
|
+
module Color
|
408
|
+
SUCCESS = "\e[0;32m"
|
409
|
+
FAIL = "\e[1;33m"
|
410
|
+
ERROR = "\e[0;31m"
|
411
|
+
NORMAL = "\e[00m"
|
412
|
+
end
|
413
|
+
else
|
414
|
+
SEPARATOR = "\n"
|
415
|
+
module Color
|
416
|
+
SUCCESS = ""
|
417
|
+
FAIL = ""
|
418
|
+
ERROR = ""
|
419
|
+
NORMAL = ""
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
def self.report
|
424
|
+
proc {
|
425
|
+
finish = Time.now
|
426
|
+
puts "\n1..#{@@count}"
|
427
|
+
$stderr.puts "Finished in #{finish - @@start} seconds."
|
428
|
+
$stderr.puts ""
|
429
|
+
succes = @@cases.to_a.inject(0) {|r,(n,c)| r + c[0] }
|
430
|
+
failure = @@cases.to_a.inject(0) {|r,(n,c)| r + c[1] }
|
431
|
+
error = @@cases.to_a.inject(0) {|r,(n,c)| r + c[2] }
|
432
|
+
$stderr.puts "#{@@cases.size} tests, " +
|
433
|
+
"#{succes+failure+error} assertions, " +
|
434
|
+
"#{Color::FAIL}#{failure} failures, " +
|
435
|
+
"#{Color::ERROR}#{error} errors" +
|
436
|
+
"#{Color::NORMAL}"
|
437
|
+
}
|
438
|
+
end
|
439
|
+
|
440
|
+
def self.included(mod)
|
441
|
+
unless @@start
|
442
|
+
ObjectSpace.define_finalizer(mod, report)
|
443
|
+
@@start = Time.now
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
def test(name = nil, directive = nil, &block)
|
448
|
+
yield
|
449
|
+
tap(Color::SUCCESS, "ok", @@count+=1, name, directive)
|
450
|
+
@@cases[name][0] += 1
|
451
|
+
#if yield
|
452
|
+
# tap(Color::SUCCESS, "ok", @@count+=1, name, directive)
|
453
|
+
# @@cases[name][0] += 1
|
454
|
+
#else
|
455
|
+
# tap(Color::FAIL, "not ok", @@count+=1, name, directive)
|
456
|
+
# print_backtrace(caller, "test failed")
|
457
|
+
# @@cases[name][1] += 1
|
458
|
+
#end
|
459
|
+
rescue Exception
|
460
|
+
tap(Color::ERROR, "not ok", @@count+=1, name, directive)
|
461
|
+
print_backtrace($!.backtrace, "#{$!} (#{$!.class})")
|
462
|
+
@@cases[name][2] += 1
|
463
|
+
ensure
|
464
|
+
print Color::NORMAL
|
465
|
+
end
|
466
|
+
|
467
|
+
def test_equals(should, actual, name = nil, directive = nil)
|
468
|
+
if should == actual
|
469
|
+
tap(Color::SUCCESS, "ok", @@count+=1, name, directive)
|
470
|
+
@@cases[name][0] += 1
|
471
|
+
else
|
472
|
+
tap(Color::FAIL, "not ok", @@count+=1, name, directive)
|
473
|
+
@@cases[name][1] += 1
|
474
|
+
print_backtrace(caller, "test failed: expected #{should.inspect} but got #{actual.inspect}")
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def data
|
479
|
+
require 'yaml'
|
480
|
+
@@data ||= YAML.load_stream(DATA.read.gsub(/(^\t+)/) {
|
481
|
+
' ' * $+.length
|
482
|
+
}).documents.map {|obj|
|
483
|
+
obj.each_pair {|k,v|
|
484
|
+
(class<<obj; self; end).instance_eval do
|
485
|
+
define_method(k) { v }
|
486
|
+
end rescue nil
|
487
|
+
} rescue obj
|
488
|
+
}
|
489
|
+
end
|
490
|
+
|
491
|
+
def run(&block)
|
492
|
+
data.each {|d|
|
493
|
+
yield d
|
494
|
+
}
|
495
|
+
end
|
496
|
+
|
497
|
+
private
|
498
|
+
def tap(color, stat, count, name, directive = nil)
|
499
|
+
if directive
|
500
|
+
directive = " # #{directive.to_s.upcase}"
|
501
|
+
end
|
502
|
+
puts "#{color}#{SEPARATOR}#{stat} #{count} - #{name}#{directive}"
|
503
|
+
end
|
504
|
+
|
505
|
+
def print_backtrace(trace, msg)
|
506
|
+
$stderr.puts "#{trace.shift}: #{msg}"
|
507
|
+
trace.each {|c|
|
508
|
+
unless c.to_s.include?(__FILE__)
|
509
|
+
$stderr.puts "\tfrom #{c}"
|
510
|
+
end
|
511
|
+
}
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
end
|
516
|
+
|