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.
@@ -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