xli-dtr 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +16 -0
- data/LICENSE.TXT +203 -0
- data/README +291 -0
- data/Rakefile +373 -0
- data/TODO +8 -0
- data/bin/dtr +124 -0
- data/dtr.gemspec +33 -0
- data/install.rb +88 -0
- data/lib/dtr/base.rb +172 -0
- data/lib/dtr/raketasks.rb +69 -0
- data/lib/dtr/runner.rb +270 -0
- data/lib/dtr/service_provider.rb +160 -0
- data/lib/dtr/test_unit.rb +289 -0
- data/lib/dtr/test_unit_injection.rb +19 -0
- data/lib/dtr.rb +111 -0
- metadata +78 -0
data/lib/dtr/base.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
# Copyright (c) 2007-2008 Li Xiao
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'pstore'
|
16
|
+
|
17
|
+
require 'logger'
|
18
|
+
|
19
|
+
class Array
|
20
|
+
def blank?
|
21
|
+
empty?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class NilClass
|
26
|
+
def blank?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module DTR
|
32
|
+
|
33
|
+
MESSAGE_KEY = :message
|
34
|
+
|
35
|
+
def logger
|
36
|
+
return DTROPTIONS[:logger] if DTROPTIONS[:logger]
|
37
|
+
DTROPTIONS[:logger] = if DTROPTIONS[:log_level] == Logger::DEBUG
|
38
|
+
Logger.new(STDOUT)
|
39
|
+
else
|
40
|
+
log_file = if File.exist?('log')
|
41
|
+
File.join("log", DTROPTIONS[:log_file] || 'dtr.log')
|
42
|
+
else
|
43
|
+
DTROPTIONS[:log_file] || 'dtr.log'
|
44
|
+
end
|
45
|
+
Logger.new("./#{log_file}", 1, 5*1024*1024)
|
46
|
+
end
|
47
|
+
DTROPTIONS[:logger].datetime_format = "%m-%d %H:%M:%S"
|
48
|
+
DTROPTIONS[:logger].level = DTROPTIONS[:log_level] || Logger::INFO
|
49
|
+
DTROPTIONS[:logger]
|
50
|
+
end
|
51
|
+
|
52
|
+
def debug(message=nil, &block)
|
53
|
+
output(:debug, message, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def info(message=nil, &block)
|
57
|
+
output(:info, message, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def error(message=nil, &block)
|
61
|
+
output(:error, message, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def output(level, msg=nil, &block)
|
65
|
+
logger.send(level) do
|
66
|
+
message = block_given? ? block.call : msg.to_s
|
67
|
+
EnvStore.new << [MESSAGE_KEY, "[#{Process.pid}-#{level.to_s.upcase}] #{message}"] if DTROPTIONS[:run_with_monitor]
|
68
|
+
message
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def silent?
|
73
|
+
logger.level == Logger::ERROR
|
74
|
+
end
|
75
|
+
|
76
|
+
def with_monitor
|
77
|
+
DTROPTIONS[:run_with_monitor] = true
|
78
|
+
EnvStore.new[MESSAGE_KEY] = []
|
79
|
+
yield
|
80
|
+
rescue Exception => e
|
81
|
+
info {"stopping by Exception => #{e.class.name}, message => #{e.message}"}
|
82
|
+
wait_times = 0
|
83
|
+
until EnvStore.new[MESSAGE_KEY].empty? || wait_times > 14
|
84
|
+
wait_times += 1
|
85
|
+
sleep(1)
|
86
|
+
end
|
87
|
+
raise e
|
88
|
+
end
|
89
|
+
|
90
|
+
module_function :debug, :info, :error, :output, :with_monitor, :logger, :silent?
|
91
|
+
|
92
|
+
class CmdInterrupt < StandardError; end
|
93
|
+
|
94
|
+
class Cmd
|
95
|
+
def self.execute(cmd)
|
96
|
+
return true if cmd.nil? || cmd.empty?
|
97
|
+
DTR.info {"Executing: #{cmd.inspect}"}
|
98
|
+
output = %x[#{cmd} 2>&1]
|
99
|
+
DTR.info {"Execution is done, status: #{$?.exitstatus}"}
|
100
|
+
DTR.error {"#{cmd.inspect} output:\n#{output}"} if $?.exitstatus != 0
|
101
|
+
$?.exitstatus == 0
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class EnvStore
|
106
|
+
|
107
|
+
FILE_NAME = '.dtr_env_pstore'
|
108
|
+
|
109
|
+
def self.destroy
|
110
|
+
File.delete(FILE_NAME) if File.exist?(FILE_NAME)
|
111
|
+
end
|
112
|
+
|
113
|
+
def [](key)
|
114
|
+
return nil unless File.exist?(FILE_NAME)
|
115
|
+
|
116
|
+
repository = PStore.new(FILE_NAME)
|
117
|
+
repository.transaction(true) do
|
118
|
+
repository[key]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def []=(key, value)
|
123
|
+
repository = PStore.new(FILE_NAME)
|
124
|
+
repository.transaction do
|
125
|
+
repository[key] = value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def <<(key_value)
|
130
|
+
key, value = key_value
|
131
|
+
repository = PStore.new(FILE_NAME)
|
132
|
+
repository.transaction do
|
133
|
+
repository[key] = (repository[key] || []) << value
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def shift(key)
|
138
|
+
repository = PStore.new(FILE_NAME)
|
139
|
+
repository.transaction do
|
140
|
+
if array = repository[key]
|
141
|
+
array.shift
|
142
|
+
repository[key] = array
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class WorkingEnv
|
149
|
+
|
150
|
+
@@current = nil
|
151
|
+
def self.refresh
|
152
|
+
@@current = self.new
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.current
|
156
|
+
@@current
|
157
|
+
end
|
158
|
+
|
159
|
+
def initialize
|
160
|
+
files = (defined?($argv_dup) ? $argv_dup : []).dup
|
161
|
+
@env = {:libs => $LOAD_PATH.dup, :files => files, :created_at => Time.now.to_s, :dtr_master_env => ENV['DTR_MASTER_ENV'], :identifier => "#{Time.now.to_s}:#{rand}:#{object_id}"}
|
162
|
+
end
|
163
|
+
|
164
|
+
def [](key)
|
165
|
+
@env[key]
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_s
|
169
|
+
@env.inspect
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Copyright (c) 2007-2008 Li Xiao
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "rubygems"
|
16
|
+
require 'dtr'
|
17
|
+
require 'rake/testtask'
|
18
|
+
|
19
|
+
module DTR
|
20
|
+
class MPTask < Rake::TestTask
|
21
|
+
attr_accessor :processes, :runner_options, :start_server
|
22
|
+
|
23
|
+
def define
|
24
|
+
@libs.unshift DTR.lib_path
|
25
|
+
lib_path = @libs.join(File::PATH_SEPARATOR)
|
26
|
+
|
27
|
+
desc "Run tests" + (@name==:test ? "" : " for #{@name}")
|
28
|
+
task @name do
|
29
|
+
DTR.start_server_daemon_mode if start_server?
|
30
|
+
start_runners
|
31
|
+
run_code = ''
|
32
|
+
begin
|
33
|
+
RakeFileUtils.verbose(@verbose) do
|
34
|
+
run_code = rake_loader
|
35
|
+
@ruby_opts.unshift( "-I#{lib_path}" )
|
36
|
+
@ruby_opts.unshift( "-w" ) if @warning
|
37
|
+
|
38
|
+
ruby @ruby_opts.join(" ") +
|
39
|
+
" \"#{run_code}\" " +
|
40
|
+
file_list.unshift('dtr/test_unit_injection.rb').collect { |fn| "\"#{fn}\"" }.join(' ') +
|
41
|
+
" #{option_list}"
|
42
|
+
end
|
43
|
+
ensure
|
44
|
+
DTR.stop_runners_daemon_mode rescue nil
|
45
|
+
if start_server?
|
46
|
+
DTR.stop_server_daemon_mode rescue nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def processes
|
54
|
+
@processes ? @processes.to_i : 2
|
55
|
+
end
|
56
|
+
|
57
|
+
def start_server?
|
58
|
+
defined?(@start_server) ? @start_server : true
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def start_runners
|
63
|
+
return if self.processes.to_i <= 0
|
64
|
+
runner_names = []
|
65
|
+
self.processes.to_i.times {|i| runner_names << "runner#{i}"}
|
66
|
+
%x[dtr -r #{runner_names.join(',')} -D #{runner_options}]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/dtr/runner.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
# Copyright (c) 2007-2008 Li Xiao
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'dtr/base'
|
16
|
+
require 'dtr/service_provider'
|
17
|
+
require 'test/unit'
|
18
|
+
require 'drb'
|
19
|
+
|
20
|
+
class Test::Unit::TestCase
|
21
|
+
alias_method :add_error_without_hack, :add_error
|
22
|
+
def add_error(exception)
|
23
|
+
add_error_without_hack(DTR::RunnerRuntimeException.new(exception))
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :add_failure_without_hack, :add_failure
|
27
|
+
def add_failure(message, all_locations=caller())
|
28
|
+
add_failure_without_hack(DTR.decorate_error_message(message, 'Assertion failure'), all_locations)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module DTR
|
33
|
+
|
34
|
+
def service_provider
|
35
|
+
ServiceProvider.new
|
36
|
+
end
|
37
|
+
|
38
|
+
module_function :service_provider
|
39
|
+
|
40
|
+
class RunnerAgent
|
41
|
+
|
42
|
+
def self.start(runner_names=["Distributed Test Runner"], setup_cmd=nil)
|
43
|
+
DTR.with_monitor do
|
44
|
+
new(runner_names, setup_cmd).launch
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(runner_names, setup_cmd)
|
49
|
+
@runner_names = runner_names.is_a?(Array) ? runner_names : [runner_names.to_s]
|
50
|
+
@setup_cmd = setup_cmd || ""
|
51
|
+
@runner_pids = []
|
52
|
+
@herald = nil
|
53
|
+
@working_env_key = :working_env
|
54
|
+
@env_store = EnvStore.new
|
55
|
+
@agent_pid = Process.pid
|
56
|
+
at_exit {
|
57
|
+
if Process.pid == @agent_pid
|
58
|
+
DTR.info "*** Runner agent is stopping ***"
|
59
|
+
kill_all_runners
|
60
|
+
if @herald
|
61
|
+
Process.kill 'KILL', @herald rescue nil
|
62
|
+
DTR.info "=> Herald is killed."
|
63
|
+
end
|
64
|
+
if @heart
|
65
|
+
Process.kill 'KILL', @heart rescue nil
|
66
|
+
DTR.info "=> Heartbeat is stopped."
|
67
|
+
end
|
68
|
+
DTR.info "*** Runner agent stopped ***"
|
69
|
+
end
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def launch
|
74
|
+
DTR.info "=> Runner agent started at: #{Dir.pwd}, pid: #{Process.pid}"
|
75
|
+
@heart = drb_fork { Heart.new }
|
76
|
+
@herald = drb_fork { Herald.new @working_env_key }
|
77
|
+
working_env = {}
|
78
|
+
@env_store[@working_env_key] = nil
|
79
|
+
loop do
|
80
|
+
if @env_store[@working_env_key] && working_env[:identifier] != @env_store[@working_env_key][:identifier]
|
81
|
+
working_env = @env_store[@working_env_key]
|
82
|
+
|
83
|
+
DTR.info "=> Got new working environment created at #{working_env[:created_at]}"
|
84
|
+
|
85
|
+
kill_all_runners
|
86
|
+
ENV['DTR_MASTER_ENV'] = working_env[:dtr_master_env]
|
87
|
+
|
88
|
+
if Cmd.execute(@setup_cmd)
|
89
|
+
@runner_names.each do |name|
|
90
|
+
@runner_pids << drb_fork { Runner.start name, working_env }
|
91
|
+
end
|
92
|
+
else
|
93
|
+
DTR.info {'No runners started.'}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
sleep(2)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def kill_all_runners
|
103
|
+
unless @runner_pids.blank?
|
104
|
+
@runner_pids.each{ |pid| Process.kill 'KILL', pid rescue nil }
|
105
|
+
DTR.info "=> All runners(#{@runner_pids.join(", ")}) were killed."
|
106
|
+
@runner_pids = []
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def drb_fork
|
111
|
+
Process.fork do
|
112
|
+
at_exit {
|
113
|
+
DRb.stop_service
|
114
|
+
exit!
|
115
|
+
}
|
116
|
+
begin
|
117
|
+
yield
|
118
|
+
rescue Interrupt => e
|
119
|
+
raise e
|
120
|
+
rescue SystemExit => e
|
121
|
+
raise e
|
122
|
+
rescue Exception => e
|
123
|
+
DTR.error "Got an Exception #{e.message}:"
|
124
|
+
DTR.error e.backtrace.join("\n")
|
125
|
+
raise e
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Heart
|
132
|
+
def initialize(key=MESSAGE_KEY)
|
133
|
+
@key = key
|
134
|
+
@env_store = EnvStore.new
|
135
|
+
@provider = DTR.service_provider
|
136
|
+
beat
|
137
|
+
end
|
138
|
+
|
139
|
+
def beat
|
140
|
+
loop do
|
141
|
+
begin
|
142
|
+
if @env_store[@key].blank?
|
143
|
+
@provider.send_message('---/V---')
|
144
|
+
else
|
145
|
+
while message = @env_store[@key].first
|
146
|
+
@provider.send_message(message)
|
147
|
+
@env_store.shift(@key)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
sleep_any_way
|
151
|
+
rescue => e
|
152
|
+
DTR.info "Heart lost DTR Server(#{e.message}), going to sleep 10 sec..."
|
153
|
+
@env_store[@key] = []
|
154
|
+
sleep_any_way
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
def sleep_any_way
|
161
|
+
sleep(10)
|
162
|
+
rescue Exception
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Herald
|
167
|
+
|
168
|
+
def initialize(key)
|
169
|
+
@key = key
|
170
|
+
@env_store = EnvStore.new
|
171
|
+
@env_store[@key] = nil
|
172
|
+
@provider = DTR.service_provider
|
173
|
+
start_off
|
174
|
+
end
|
175
|
+
|
176
|
+
def start_off
|
177
|
+
loop do
|
178
|
+
DTR.info "=> Herald starts off..."
|
179
|
+
begin
|
180
|
+
working_env = @provider.working_env
|
181
|
+
DTR.debug { "working env: #{working_env.inspect}" }
|
182
|
+
if working_env[:files].blank?
|
183
|
+
DTR.error "No test files need to load?(working env: #{working_env.inspect})"
|
184
|
+
else
|
185
|
+
@env_store[@key] = working_env if @env_store[@key].nil? || @env_store[@key][:identifier] != working_env[:identifier]
|
186
|
+
@provider.wait_until_teardown
|
187
|
+
end
|
188
|
+
|
189
|
+
sleep(2)
|
190
|
+
rescue => e
|
191
|
+
DTR.info "Herald lost DTR Server(#{e.message}), going to sleep 5 sec..."
|
192
|
+
sleep(5)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class Runner
|
199
|
+
include DRbUndumped
|
200
|
+
|
201
|
+
def self.start(name, env)
|
202
|
+
DTR.info "#{name}: Initialize working environment..."
|
203
|
+
env[:libs].select{ |lib| !$LOAD_PATH.include?(lib) && File.exists?(lib) }.each do |lib|
|
204
|
+
$LOAD_PATH << lib
|
205
|
+
DTR.debug {"#{name}: appended lib: #{lib}"}
|
206
|
+
end
|
207
|
+
DTR.info "#{name}: libs loaded"
|
208
|
+
DTR.debug {"#{name}: $LOAD_PATH: #{$LOAD_PATH.inspect}"}
|
209
|
+
|
210
|
+
env[:files].each do |f|
|
211
|
+
begin
|
212
|
+
load f unless f =~ /^-/
|
213
|
+
DTR.debug {"#{name}: loaded #{f}"}
|
214
|
+
rescue LoadError => e
|
215
|
+
DTR.error "#{name}: No such file to load -- #{f} (Environment: #{env.inspect})"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
DTR.info "#{name}: test files loaded"
|
219
|
+
|
220
|
+
@provider = DTR.service_provider
|
221
|
+
|
222
|
+
@provider.provide(self.new(@provider, name, env[:identifier]))
|
223
|
+
DTR.info "=> Runner #{name} provided"
|
224
|
+
DRb.thread.join if DRb.thread
|
225
|
+
end
|
226
|
+
|
227
|
+
attr_reader :name, :identifier
|
228
|
+
|
229
|
+
def initialize(provider, name, identifier)
|
230
|
+
Test::Unit.run = true
|
231
|
+
@name = name
|
232
|
+
@provider = provider
|
233
|
+
@identifier = identifier
|
234
|
+
@started = []
|
235
|
+
@run_finished = []
|
236
|
+
end
|
237
|
+
|
238
|
+
def run(test, result, &progress_block)
|
239
|
+
DTR.debug {"#{name}: running #{test}..."}
|
240
|
+
@started << test.name
|
241
|
+
test.run(result, &progress_block)
|
242
|
+
rescue DRb::DRbConnError => e
|
243
|
+
DTR.info{ "Rescued DRb::DRbConnError(#{e.message}), while running test: #{test.name}. The master process may be stopped." }
|
244
|
+
rescue Exception => e
|
245
|
+
DTR.error {"Unexpected exception: #{e.message}"}
|
246
|
+
DTR.error {e.backtrace.join("\n")}
|
247
|
+
result.add_error(Test::Unit::Error.new(test.name, e))
|
248
|
+
result.add_run
|
249
|
+
progress_block.call(Test::Unit::TestCase::FINISHED, test.name)
|
250
|
+
ensure
|
251
|
+
DTR.debug {"#{name}: done #{test}"}
|
252
|
+
@run_finished << test.name
|
253
|
+
@provider.provide(self)
|
254
|
+
end
|
255
|
+
|
256
|
+
def reboot
|
257
|
+
DTR.info "#{self} is rebooting. Ran #{@started.size} tests, finished #{@run_finished.size}."
|
258
|
+
@provider.provide(self)
|
259
|
+
end
|
260
|
+
|
261
|
+
def shutdown
|
262
|
+
DTR.info "#{self} is shutting down. Ran #{@started.size} tests, finished #{@run_finished.size}."
|
263
|
+
@provider.stop_service rescue exit!
|
264
|
+
end
|
265
|
+
|
266
|
+
def to_s
|
267
|
+
"Runner #{@name}"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# Copyright (c) 2007-2008 Li Xiao
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'dtr/base'
|
16
|
+
require 'drb'
|
17
|
+
require 'rinda/ring'
|
18
|
+
require 'rinda/tuplespace'
|
19
|
+
require 'socket'
|
20
|
+
|
21
|
+
module DTR
|
22
|
+
def decorate_error_message(msg, source=nil)
|
23
|
+
source ? "#{source} from #{Socket.gethostname}: #{msg}" : "From #{Socket.gethostname}: #{msg}"
|
24
|
+
end
|
25
|
+
|
26
|
+
module_function :decorate_error_message
|
27
|
+
|
28
|
+
class RunnerRuntimeException < StandardError
|
29
|
+
def initialize(e)
|
30
|
+
super(DTR.decorate_error_message(e.message, e.class.name))
|
31
|
+
set_backtrace(e.backtrace)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ServiceProvider
|
36
|
+
|
37
|
+
def self.broadcast_list=(list)
|
38
|
+
EnvStore.new[:broadcast_list] = list
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.port=(port)
|
42
|
+
EnvStore.new[:port] = port
|
43
|
+
end
|
44
|
+
|
45
|
+
PORT = 3344
|
46
|
+
BROADCAST_LIST = []
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
DTR.info "-- Initializing drb service..."
|
50
|
+
env_store = EnvStore.new
|
51
|
+
(env_store[:broadcast_list] || ['localhost']).each do |broadcast|
|
52
|
+
BROADCAST_LIST << broadcast.untaint
|
53
|
+
DTR.info "-- Added broadcast: #{broadcast}"
|
54
|
+
end
|
55
|
+
DTR.info "-- Server port: #{server_port}"
|
56
|
+
DRb.start_service
|
57
|
+
end
|
58
|
+
|
59
|
+
# start DTR server
|
60
|
+
def start
|
61
|
+
env_store = EnvStore.new
|
62
|
+
DTR.info '-- Booting DTR server...'
|
63
|
+
Rinda::RingServer.new Rinda::TupleSpace.new, server_port
|
64
|
+
DTR.info "-- DTR server started on port #{server_port}"
|
65
|
+
#set safe level to 1 here, now, runner can't set to 1, cause test should can do anything
|
66
|
+
#......
|
67
|
+
$SAFE = 1 unless $DEBUG # disable eval() and friends
|
68
|
+
# Wait until the user explicitly kills the server.
|
69
|
+
DRb.thread.join
|
70
|
+
end
|
71
|
+
|
72
|
+
def provide(runner)
|
73
|
+
renewer = Rinda::SimpleRenewer.new
|
74
|
+
tuple = [:name, 'DTR::Runner'.to_sym, runner.freeze, "DTR remote runner #{Process.pid}-#{runner.name}"]
|
75
|
+
lookup_ring.write(tuple, renewer)
|
76
|
+
end
|
77
|
+
|
78
|
+
def send_message(message)
|
79
|
+
lookup_ring.write [:agent_heartbeat, Socket.gethostname, message, Time.now], 2
|
80
|
+
end
|
81
|
+
|
82
|
+
def lookup_runner
|
83
|
+
lookup_ring.take([:name, 'DTR::Runner'.to_sym, nil, nil])[2]
|
84
|
+
end
|
85
|
+
|
86
|
+
def runners
|
87
|
+
lookup_ring.read_all([:name, 'DTR::Runner'.to_sym, nil, nil]).collect {|rt| rt[2]}
|
88
|
+
end
|
89
|
+
|
90
|
+
def monitor
|
91
|
+
working_env_monitor = lookup_ring.notify(nil, [:working_env, nil])
|
92
|
+
Thread.start do
|
93
|
+
DTR.info("Current work environment: #{working_env.inspect}")
|
94
|
+
working_env_monitor.each { |t| DTR.info t.inspect }
|
95
|
+
end
|
96
|
+
if DTROPTIONS[:log_level] == Logger::DEBUG
|
97
|
+
runner_monitor = lookup_ring.notify(nil, [:name, 'DTR::Runner'.to_sym, nil, nil])
|
98
|
+
Thread.start do
|
99
|
+
runner_monitor.each { |t| DTR.debug t.inspect }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
agent_heartbeat_monitor = lookup_ring.notify("write", [:agent_heartbeat, nil, nil, nil])
|
103
|
+
Thread.start do
|
104
|
+
colors = {}
|
105
|
+
base = 30
|
106
|
+
agent_heartbeat_monitor.each do |t|
|
107
|
+
host, message, time = t[1][1..3]
|
108
|
+
colors[host] = base+=1 unless colors[host]
|
109
|
+
message = "\e[1;31m#{message}\e[0m" if message =~ /-ERROR\]/
|
110
|
+
DTR.info "#{time.strftime("[%I:%M:%S%p]")} \e[1;#{colors[host]};1m#{host}\e[0m: #{message}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
DRb.thread.join
|
114
|
+
end
|
115
|
+
|
116
|
+
def wait_until_teardown
|
117
|
+
lookup_ring.notify(nil, [:working_env, nil]).pop
|
118
|
+
end
|
119
|
+
|
120
|
+
def working_env
|
121
|
+
lookup_ring.read([:working_env, nil])[1]
|
122
|
+
end
|
123
|
+
|
124
|
+
def setup_working_env(env)
|
125
|
+
clear_workspace
|
126
|
+
lookup_ring.write [:working_env, env]
|
127
|
+
end
|
128
|
+
|
129
|
+
def teardown_working_env
|
130
|
+
clear_workspace
|
131
|
+
end
|
132
|
+
|
133
|
+
def start_service
|
134
|
+
DRb.start_service
|
135
|
+
end
|
136
|
+
|
137
|
+
def stop_service
|
138
|
+
DRb.stop_service
|
139
|
+
end
|
140
|
+
|
141
|
+
def clear_workspace
|
142
|
+
lookup_ring.read_all([:working_env, nil]).size.times do
|
143
|
+
lookup_ring.take [:working_env, nil] rescue nil
|
144
|
+
end rescue nil
|
145
|
+
runners.size.times do
|
146
|
+
lookup_runner.shutdown rescue nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
def server_port
|
152
|
+
env_store = EnvStore.new
|
153
|
+
env_store[:port].to_i > 0 ? env_store[:port].to_i : PORT
|
154
|
+
end
|
155
|
+
|
156
|
+
def lookup_ring
|
157
|
+
Rinda::TupleSpaceProxy.new(Rinda::RingFinger.new(BROADCAST_LIST, server_port).lookup_ring_any)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|