dtr 0.0.3
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/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
|