dtr 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +10 -0
- data/LICENSE.TXT +203 -0
- data/LICENSE.txt +203 -0
- data/README +302 -0
- data/Rakefile +425 -0
- data/TODO +8 -0
- data/bin/dtr +124 -0
- data/doc/jamis.rb +591 -0
- data/install.rb +88 -0
- data/lib/dtr.rb +111 -0
- data/lib/dtr/base.rb +160 -0
- data/lib/dtr/raketasks.rb +68 -0
- data/lib/dtr/runner.rb +265 -0
- data/lib/dtr/service_provider.rb +160 -0
- data/lib/dtr/test_unit.rb +278 -0
- data/lib/dtr/test_unit_injection.rb +19 -0
- data/test/base_test.rb +31 -0
- data/test/logger_test.rb +54 -0
- data/test/scenario_tests.rb +166 -0
- data/test/test_helper.rb +17 -0
- data/test/test_unit_test.rb +25 -0
- metadata +89 -0
@@ -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
|
@@ -0,0 +1,278 @@
|
|
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/testcase'
|
18
|
+
require 'test/unit/util/observable'
|
19
|
+
require 'monitor'
|
20
|
+
require 'drb'
|
21
|
+
|
22
|
+
DTROPTIONS = {} unless defined?(DTROPTIONS)
|
23
|
+
|
24
|
+
module DTR
|
25
|
+
def reject
|
26
|
+
return unless Test::Unit::TestSuite.method_defined?(:dtr_injected?)
|
27
|
+
Test::Unit::TestCase.send(:include, Rejection)
|
28
|
+
Test::Unit::TestSuite.send(:include, Rejection)
|
29
|
+
end
|
30
|
+
|
31
|
+
def inject
|
32
|
+
return if Test::Unit::TestSuite.method_defined?(:dtr_injected?)
|
33
|
+
Test::Unit::TestCase.send(:include, TestCaseInjection)
|
34
|
+
Test::Unit::TestSuite.send(:include, TestSuiteInjection)
|
35
|
+
end
|
36
|
+
|
37
|
+
def service_provider
|
38
|
+
$dtr_service_provider ||= DTR::ServiceProvider.new
|
39
|
+
end
|
40
|
+
|
41
|
+
module_function :reject, :inject, :service_provider
|
42
|
+
|
43
|
+
class Counter
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
extend MonitorMixin
|
47
|
+
@start_count, @finish_count = 0, 0
|
48
|
+
@complete_cond = new_cond
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_start_count
|
52
|
+
synchronize do
|
53
|
+
@start_count += 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_finish_count
|
58
|
+
synchronize do
|
59
|
+
@finish_count += 1
|
60
|
+
@complete_cond.signal if complete?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
synchronize do
|
66
|
+
status
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def wait_until_complete
|
71
|
+
synchronize do
|
72
|
+
@complete_cond.wait_until {complete?} unless complete?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def complete?
|
78
|
+
DTR.debug {"Counter status: #{status}"}
|
79
|
+
@finish_count >= @start_count
|
80
|
+
end
|
81
|
+
|
82
|
+
def status
|
83
|
+
"finish_count: #{@finish_count}; start_count: #{@start_count}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class ThreadSafeTestResult
|
88
|
+
include Test::Unit::Util::Observable
|
89
|
+
include DRbUndumped
|
90
|
+
|
91
|
+
def initialize(rs)
|
92
|
+
extend MonitorMixin
|
93
|
+
@rs = rs
|
94
|
+
@channels = @rs.send(:channels).dup
|
95
|
+
@rs.send(:channels).clear
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_run
|
99
|
+
synchronize do
|
100
|
+
@rs.add_run
|
101
|
+
end
|
102
|
+
notify_listeners(Test::Unit::TestResult::CHANGED, self)
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_failure(failure)
|
106
|
+
synchronize do
|
107
|
+
@rs.add_failure(failure)
|
108
|
+
end
|
109
|
+
notify_listeners(Test::Unit::TestResult::FAULT, failure)
|
110
|
+
notify_listeners(Test::Unit::TestResult::CHANGED, self)
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_error(error)
|
114
|
+
synchronize do
|
115
|
+
@rs.add_error(error)
|
116
|
+
end
|
117
|
+
notify_listeners(Test::Unit::TestResult::FAULT, error)
|
118
|
+
notify_listeners(Test::Unit::TestResult::CHANGED, self)
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_assertion
|
122
|
+
synchronize do
|
123
|
+
@rs.add_assertion
|
124
|
+
end
|
125
|
+
notify_listeners(Test::Unit::TestResult::CHANGED, self)
|
126
|
+
end
|
127
|
+
|
128
|
+
def to_s
|
129
|
+
synchronize do
|
130
|
+
@rs.to_s
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def passed?
|
135
|
+
synchronize do
|
136
|
+
@rs.passed?
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def failure_count
|
141
|
+
synchronize do
|
142
|
+
@rs.failure_count
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def error_count
|
147
|
+
synchronize do
|
148
|
+
@rs.error_count
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class DRbTestRunner
|
154
|
+
|
155
|
+
# because some test case will rewrite TestCase#run to ignore some tests, which
|
156
|
+
# makes TestResult#run_count different with TestSuite#size, so we need to count
|
157
|
+
# by ourselves.(for example: ActionController::IntegrationTest)
|
158
|
+
class << self
|
159
|
+
def counter
|
160
|
+
@counter ||= Counter.new
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
RUN_TEST_FINISHED = "::DRbTestRunner::RUN_TEST_FINISHED"
|
165
|
+
|
166
|
+
def initialize(test, result, &progress_block)
|
167
|
+
@test = test
|
168
|
+
@result = result
|
169
|
+
@progress_block = progress_block
|
170
|
+
|
171
|
+
DRbTestRunner.counter.add_start_count
|
172
|
+
end
|
173
|
+
|
174
|
+
def run
|
175
|
+
if runner = lookup_runner
|
176
|
+
run_test_on(runner)
|
177
|
+
else
|
178
|
+
self.run
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def run_test_on(runner)
|
183
|
+
Thread.start do
|
184
|
+
begin
|
185
|
+
runner.run(@test, @result, &@progress_block)
|
186
|
+
@progress_block.call(RUN_TEST_FINISHED, @test.name)
|
187
|
+
rescue DRb::DRbConnError => e
|
188
|
+
DTR.info{ "DRb::DRbConnError(#{e.message}), rerun test: #{@test.name}" }
|
189
|
+
self.run
|
190
|
+
rescue Exception => e
|
191
|
+
DTR.info{ "#{test.name}, rescue an exception: #{e.message}, add error into result." }
|
192
|
+
@result.add_error(Test::Unit::Error.new(@test.name, e))
|
193
|
+
@result.add_run
|
194
|
+
@progress_block.call(Test::Unit::TestCase::FINISHED, @test.name)
|
195
|
+
@progress_block.call(RUN_TEST_FINISHED, @test.name)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def lookup_runner
|
201
|
+
runner = DTR.service_provider.lookup_runner
|
202
|
+
begin
|
203
|
+
DTR.debug {"#{runner.name}.env ==?: #{WorkingEnv.current[:identifier] == runner.identifier}"}
|
204
|
+
return runner if runner.identifier == WorkingEnv.current[:identifier]
|
205
|
+
runner.shutdown
|
206
|
+
rescue DRb::DRbConnError => e
|
207
|
+
DTR.debug {"DRb::DRbConnError(#{e.message})"}
|
208
|
+
end
|
209
|
+
nil
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
module TestCaseInjection
|
214
|
+
|
215
|
+
def self.included(base)
|
216
|
+
base.class_eval do
|
217
|
+
alias_method :__run__, :run
|
218
|
+
|
219
|
+
def run(result, &progress_block)
|
220
|
+
DTR.debug {"start of run TestCase(#{name})"}
|
221
|
+
DRbTestRunner.new(self, result, &progress_block).run
|
222
|
+
DTR.debug {"end of run TestCase(#{name})"}
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
module TestSuiteInjection
|
229
|
+
def self.included(base)
|
230
|
+
base.class_eval do
|
231
|
+
def dtr_injected?
|
232
|
+
true
|
233
|
+
end
|
234
|
+
|
235
|
+
alias_method :__run__, :run
|
236
|
+
|
237
|
+
def run(result, &progress_block)
|
238
|
+
DTR.debug { "start of run suite(#{name}), size: #{size};"}
|
239
|
+
|
240
|
+
if result.kind_of?(ThreadSafeTestResult)
|
241
|
+
__run__(result, &progress_block)
|
242
|
+
else
|
243
|
+
if defined?(ActiveRecord::Base)
|
244
|
+
ActiveRecord::Base.clear_active_connections! rescue nil
|
245
|
+
end
|
246
|
+
|
247
|
+
DTR.service_provider.setup_working_env WorkingEnv.refresh
|
248
|
+
|
249
|
+
puts 'Refreshed dtr working environment, looking for runner service...' unless DTR.silent?
|
250
|
+
result = ThreadSafeTestResult.new(result)
|
251
|
+
__run__(result) do |channel, value|
|
252
|
+
DTR.debug { "=> channel: #{channel}, value: #{value}" }
|
253
|
+
progress_block.call(channel, value)
|
254
|
+
if channel == DTR::DRbTestRunner::RUN_TEST_FINISHED
|
255
|
+
DRbTestRunner.counter.add_finish_count
|
256
|
+
end
|
257
|
+
end
|
258
|
+
DRbTestRunner.counter.wait_until_complete
|
259
|
+
DTR.debug { "==> teardown" }
|
260
|
+
DTR.service_provider.teardown_working_env
|
261
|
+
end
|
262
|
+
DTR.debug { "end of run suite(#{name}), test result status: #{result}, counter status: #{DRbTestRunner.counter}"}
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
module Rejection
|
269
|
+
def self.included(base)
|
270
|
+
base.class_eval do
|
271
|
+
remove_method :dtr_injected? if base.method_defined?(:dtr_injected?)
|
272
|
+
remove_method :run
|
273
|
+
alias_method :run, :__run__
|
274
|
+
remove_method :__run__
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,19 @@
|
|
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/service_provider'
|
16
|
+
require 'dtr/test_unit'
|
17
|
+
$argv_dup = ARGV.dup.delete_if{|f| f.include?('dtr/test_unit_injection')}
|
18
|
+
|
19
|
+
DTR.inject
|