dtr 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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