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,88 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'ftools'
4
+
5
+ include Config
6
+
7
+ $ruby = CONFIG['ruby_install_name']
8
+
9
+ ##
10
+ # Install a binary file. We patch in on the way through to
11
+ # insert a #! line. If this is a Unix install, we name
12
+ # the command (for example) 'dtr' and let the shebang line
13
+ # handle running it. Under windows, we add a '.rb' extension
14
+ # and let file associations to their stuff
15
+ #
16
+
17
+ def installBIN(from, opfile)
18
+
19
+ tmp_dir = nil
20
+ for t in [".", "/tmp", "c:/temp", $bindir]
21
+ stat = File.stat(t) rescue next
22
+ if stat.directory? and stat.writable?
23
+ tmp_dir = t
24
+ break
25
+ end
26
+ end
27
+
28
+ fail "Cannot find a temporary directory" unless tmp_dir
29
+ tmp_file = File.join(tmp_dir, "_tmp")
30
+
31
+ File.open(from) do |ip|
32
+ File.open(tmp_file, "w") do |op|
33
+ ruby = File.join($realbindir, $ruby)
34
+ op.puts "#!#{ruby}"
35
+ op.write ip.read
36
+ end
37
+ end
38
+
39
+ opfile += ".rb" if CONFIG["target_os"] =~ /mswin/i
40
+ File::install(tmp_file, File.join($bindir, opfile), 0755, true)
41
+ File::unlink(tmp_file)
42
+ end
43
+
44
+ $sitedir = CONFIG["sitelibdir"]
45
+ unless $sitedir
46
+ version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
47
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
48
+ $sitedir = $:.find {|x| x =~ /site_ruby/}
49
+ if !$sitedir
50
+ $sitedir = File.join($libdir, "site_ruby")
51
+ elsif $sitedir !~ Regexp.quote(version)
52
+ $sitedir = File.join($sitedir, version)
53
+ end
54
+ end
55
+
56
+ $bindir = CONFIG["bindir"]
57
+
58
+ $realbindir = $bindir
59
+
60
+ bindir = CONFIG["bindir"]
61
+ if (destdir = ENV['DESTDIR'])
62
+ $bindir = destdir + $bindir
63
+ $sitedir = destdir + $sitedir
64
+
65
+ File::makedirs($bindir)
66
+ File::makedirs($sitedir)
67
+ end
68
+
69
+ dtr_dest = File.join($sitedir, "dtr")
70
+ File::makedirs(dtr_dest, true)
71
+ File::chmod(0755, dtr_dest)
72
+
73
+ # The library files
74
+
75
+ files = Dir.chdir('lib') { Dir['**/*.rb', '**/*.rake'] }
76
+
77
+ for fn in files
78
+ fn_dir = File.dirname(fn)
79
+ target_dir = File.join($sitedir, fn_dir)
80
+ if ! File.exist?(target_dir)
81
+ File.makedirs(target_dir)
82
+ end
83
+ File::install(File.join('lib', fn), File.join($sitedir, fn), 0644, true)
84
+ end
85
+
86
+ # and the executable
87
+
88
+ installBIN("bin/dtr", "dtr")
@@ -0,0 +1,111 @@
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
+ DTRVERSION="0.0.3"
16
+ DTROPTIONS = {} unless defined?(DTROPTIONS)
17
+
18
+ require 'fileutils'
19
+
20
+ module DTR
21
+
22
+ def start_server
23
+ require 'dtr/service_provider'
24
+ ServiceProvider.new.start
25
+ end
26
+
27
+ def start_runners
28
+ launch_runners(DTROPTIONS[:names], DTROPTIONS[:setup])
29
+ end
30
+
31
+ def launch_runners(names, setup=nil)
32
+ require 'dtr/runner'
33
+ names = names || "DTR(#{Time.now})"
34
+ DTR::RunnerAgent.start(names, setup)
35
+ end
36
+
37
+ def lib_path
38
+ File.expand_path(File.dirname(__FILE__))
39
+ end
40
+
41
+ def broadcast_list=(list)
42
+ require 'dtr/service_provider'
43
+ ServiceProvider.broadcast_list = list
44
+ end
45
+
46
+ def port=(port)
47
+ require 'dtr/service_provider'
48
+ ServiceProvider.port = port
49
+ end
50
+
51
+ def runners
52
+ require 'dtr/service_provider'
53
+ ServiceProvider.new.runners
54
+ end
55
+
56
+ def monitor
57
+ require 'dtr/service_provider'
58
+ DTROPTIONS[:log_file] = 'dtr_monitor.log'
59
+ ServiceProvider.new.monitor
60
+ end
61
+
62
+ def stop_runners_daemon_mode
63
+ with_daemon_gem do
64
+ Daemons.run_proc "dtr_runners", :ARGV => ['stop']
65
+ end
66
+ end
67
+
68
+ def start_runners_daemon_mode
69
+ with_daemon_gem do
70
+ FileUtils.rm_rf('dtr_runners.output')
71
+ pwd = Dir.pwd
72
+ Daemons.run_proc "dtr_runners", :ARGV => ['start'], :backtrace => true do
73
+ Dir.chdir(pwd) do
74
+ start_runners
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def start_server_daemon_mode
81
+ with_daemon_gem do
82
+ Daemons.run_proc "dtr_server", :ARGV => ['start'] do
83
+ start_server
84
+ end
85
+ end
86
+ sleep(2)
87
+ end
88
+
89
+ def stop_server_daemon_mode
90
+ with_daemon_gem do
91
+ Daemons.run_proc "dtr_server", :ARGV => ['stop']
92
+ end
93
+ end
94
+
95
+ def with_daemon_gem
96
+ require "rubygems"
97
+ begin
98
+ require "daemons"
99
+ rescue LoadError
100
+ raise "The daemons gem must be installed"
101
+ end
102
+ yield
103
+ end
104
+
105
+ def clean_server
106
+ require 'dtr/service_provider'
107
+ ServiceProvider.new.clear_workspace
108
+ end
109
+
110
+ module_function :start_server, :start_runners, :launch_runners, :lib_path, :broadcast_list=, :runners, :with_daemon_gem, :start_runners_daemon_mode, :stop_runners_daemon_mode, :start_server_daemon_mode, :stop_server_daemon_mode, :monitor, :port=, :clean_server
111
+ 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 'pstore'
16
+
17
+ require 'logger'
18
+
19
+ module DTR
20
+
21
+ MESSAGE_KEY = :message
22
+
23
+ def logger
24
+ return DTROPTIONS[:logger] if DTROPTIONS[:logger]
25
+ DTROPTIONS[:logger] = if DTROPTIONS[:log_level] == Logger::DEBUG
26
+ Logger.new(STDOUT)
27
+ else
28
+ log_file = if File.exist?('log')
29
+ File.join("log", DTROPTIONS[:log_file] || 'dtr.log')
30
+ else
31
+ DTROPTIONS[:log_file] || 'dtr.log'
32
+ end
33
+ Logger.new(log_file, 1, 5*1024*1024)
34
+ end
35
+ DTROPTIONS[:logger].datetime_format = "%m-%d %H:%M:%S"
36
+ DTROPTIONS[:logger].level = DTROPTIONS[:log_level] || Logger::INFO
37
+ DTROPTIONS[:logger]
38
+ end
39
+
40
+ def debug(message=nil, &block)
41
+ output(:debug, message, &block)
42
+ end
43
+
44
+ def info(message=nil, &block)
45
+ output(:info, message, &block)
46
+ end
47
+
48
+ def error(message=nil, &block)
49
+ output(:error, message, &block)
50
+ end
51
+
52
+ def output(level, msg=nil, &block)
53
+ logger.send(level) do
54
+ message = block_given? ? block.call : msg.to_s
55
+ EnvStore.new << [MESSAGE_KEY, "[#{Process.pid}-#{level.to_s.upcase}] #{message}"] if DTROPTIONS[:run_with_monitor]
56
+ message
57
+ end
58
+ end
59
+
60
+ def silent?
61
+ logger.level == Logger::ERROR
62
+ end
63
+
64
+ def with_monitor
65
+ DTROPTIONS[:run_with_monitor] = true
66
+ EnvStore.new[MESSAGE_KEY] = []
67
+ yield
68
+ rescue Exception => e
69
+ info {"stopping by Exception => #{e.class.name}, message => #{e.message}"}
70
+ wait_times = 0
71
+ until EnvStore.new[MESSAGE_KEY].empty? || wait_times > 14
72
+ wait_times += 1
73
+ sleep(1)
74
+ end
75
+ raise e
76
+ end
77
+
78
+ module_function :debug, :info, :error, :output, :with_monitor, :logger, :silent?
79
+
80
+ class CmdInterrupt < StandardError; end
81
+
82
+ class Cmd
83
+ def self.execute(cmd)
84
+ return true if cmd.nil? || cmd.empty?
85
+ DTR.info {"Executing: #{cmd.inspect}"}
86
+ output = %x[#{cmd} 2>&1]
87
+ DTR.info {"Execution is done, status: #{$?.exitstatus}"}
88
+ DTR.error {"#{cmd.inspect} output:\n#{output}"} if $?.exitstatus != 0
89
+ $?.exitstatus == 0
90
+ end
91
+ end
92
+
93
+ class EnvStore
94
+
95
+ FILE_NAME = '.dtr_env_pstore'
96
+
97
+ def self.destroy
98
+ File.delete(FILE_NAME) if File.exist?(FILE_NAME)
99
+ end
100
+
101
+ def [](key)
102
+ return nil unless File.exist?(FILE_NAME)
103
+
104
+ repository = PStore.new(FILE_NAME)
105
+ repository.transaction(true) do
106
+ repository[key]
107
+ end
108
+ end
109
+
110
+ def []=(key, value)
111
+ repository = PStore.new(FILE_NAME)
112
+ repository.transaction do
113
+ repository[key] = value
114
+ end
115
+ end
116
+
117
+ def <<(key_value)
118
+ key, value = key_value
119
+ repository = PStore.new(FILE_NAME)
120
+ repository.transaction do
121
+ repository[key] = (repository[key] || []) << value
122
+ end
123
+ end
124
+
125
+ def shift(key)
126
+ repository = PStore.new(FILE_NAME)
127
+ repository.transaction do
128
+ if array = repository[key]
129
+ array.shift
130
+ repository[key] = array
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ class WorkingEnv
137
+
138
+ @@current = nil
139
+ def self.refresh
140
+ @@current = self.new
141
+ end
142
+
143
+ def self.current
144
+ @@current
145
+ end
146
+
147
+ def initialize
148
+ files = (defined?($argv_dup) ? $argv_dup : []).dup
149
+ @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}"}
150
+ end
151
+
152
+ def [](key)
153
+ @env[key]
154
+ end
155
+
156
+ def to_s
157
+ @env.inspect
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,68 @@
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
+ runner_names = []
64
+ self.processes.to_i.times {|i| runner_names << "runner#{i}"}
65
+ %x[dtr -r #{runner_names.join(',')} -D #{runner_options}]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,265 @@
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.empty?
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].empty?
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].empty?
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 shutdown
257
+ DTR.info "#{self} is shutting down. Ran #{@started.size} tests, finished #{@run_finished.size}."
258
+ @provider.stop_service rescue exit!
259
+ end
260
+
261
+ def to_s
262
+ "Runner #{@name}"
263
+ end
264
+ end
265
+ end