DTR 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +1 -0
- data/README +58 -41
- data/Rakefile +76 -15
- data/TODO +12 -2
- data/bin/dtr +50 -29
- data/install.rb +2 -3
- data/lib/dtr.rb +65 -71
- data/lib/dtr/env_store.rb +58 -0
- data/lib/dtr/inject.rb +155 -0
- data/lib/dtr/inject_with_svn.rb +8 -0
- data/lib/dtr/inject_without_updating_codebase.rb +6 -0
- data/lib/dtr/raketasks.rb +48 -0
- data/lib/dtr/ruby_ext.rb +3 -25
- data/lib/dtr/runner.rb +174 -0
- data/lib/dtr/service_provider.rb +91 -0
- data/lib/dtr/svn.rb +20 -0
- data/test/inject_test.rb +25 -0
- data/test/scenario_tests.rb +172 -0
- data/test/svn_test.rb +11 -0
- data/test/test_helper.rb +25 -108
- metadata +13 -20
- data/lib/dtr/base.rb +0 -281
- data/lib/dtr/command_line.rb +0 -173
- data/lib/dtr/drb_dtr.rb +0 -275
- data/show_process_exit_code.bat +0 -5
- data/test/average_packer_test.rb +0 -32
- data/test/base_test.rb +0 -236
- data/test/original_test_reports_test.rb +0 -91
- data/test/ruby_ext_test.rb +0 -13
- data/test/ruby_runner_test.rb +0 -56
- data/test/scenario_setup_and_run_tests_simply.rb +0 -51
- data/test/server_test.rb +0 -39
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'dtr/ruby_ext'
|
2
|
+
require 'pstore'
|
3
|
+
module DTR
|
4
|
+
class EnvStore
|
5
|
+
|
6
|
+
FILE_NAME = '.dtr_env_pstore'
|
7
|
+
|
8
|
+
def self.destroy
|
9
|
+
File.delete(FILE_NAME) if File.exist?(FILE_NAME)
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
return nil unless File.exist?(FILE_NAME)
|
14
|
+
|
15
|
+
repository = PStore.new(FILE_NAME)
|
16
|
+
repository.transaction(true) do
|
17
|
+
repository[key]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(key, value)
|
22
|
+
repository = PStore.new(FILE_NAME)
|
23
|
+
repository.transaction do
|
24
|
+
repository[key] = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class WorkingEnv
|
30
|
+
|
31
|
+
@@current = nil
|
32
|
+
def self.refresh
|
33
|
+
@@current = self.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.current
|
37
|
+
@@current
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@env = {:libs => $LOAD_PATH.dup, :files => $argv_dup.dup, :revision => $repository.revision, :created_at => Time.now.to_s}
|
42
|
+
end
|
43
|
+
|
44
|
+
def [](key)
|
45
|
+
@env[key]
|
46
|
+
end
|
47
|
+
|
48
|
+
def ==(another)
|
49
|
+
return false if another.nil?
|
50
|
+
return self[:libs] == another[:libs] && self[:files] == another[:files] && self[:revision] == another[:revision]
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
@env.inspect
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
data/lib/dtr/inject.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'test/unit/testcase'
|
2
|
+
require 'monitor'
|
3
|
+
require 'dtr/runner'
|
4
|
+
|
5
|
+
def service_provider
|
6
|
+
$dtr_service_provider ||= DTR::ServiceProvider.new
|
7
|
+
end
|
8
|
+
|
9
|
+
module DTR
|
10
|
+
def reject
|
11
|
+
return unless Test::Unit::TestSuite.method_defined?(:dtr_injected?)
|
12
|
+
Test::Unit::TestCase.send(:include, Rejection)
|
13
|
+
Test::Unit::TestSuite.send(:include, Rejection)
|
14
|
+
end
|
15
|
+
|
16
|
+
def inject
|
17
|
+
return if Test::Unit::TestSuite.method_defined?(:dtr_injected?)
|
18
|
+
Test::Unit::TestCase.send(:include, TestCaseInjection)
|
19
|
+
Test::Unit::TestSuite.send(:include, TestSuiteInjection)
|
20
|
+
end
|
21
|
+
|
22
|
+
module_function :reject, :inject
|
23
|
+
|
24
|
+
class DRbTestRunner
|
25
|
+
|
26
|
+
# because some test case will rewrite TestCase#run to ignore some tests, which
|
27
|
+
# makes TestResult#run_count different with TestSuite#size, so we need to count
|
28
|
+
# by ourselves.(for example: ActionController::IntegrationTest)
|
29
|
+
@@run_count = 0
|
30
|
+
|
31
|
+
RUN_TEST_FINISHED = "::DRbTestRunner::RUN_TEST_FINISHED"
|
32
|
+
|
33
|
+
def self.done?(run_count)
|
34
|
+
@@run_count == run_count
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.start
|
38
|
+
@@run_count = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(test, result, &progress_block)
|
42
|
+
@test = test
|
43
|
+
@result = result
|
44
|
+
@progress_block = progress_block
|
45
|
+
|
46
|
+
@@run_count += 1
|
47
|
+
end
|
48
|
+
|
49
|
+
def run
|
50
|
+
if runner = lookup_runner
|
51
|
+
run_test_on(runner)
|
52
|
+
else
|
53
|
+
self.run
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def run_test_on(runner)
|
58
|
+
Thread.start do
|
59
|
+
begin
|
60
|
+
runner.run(@test, @result, &@progress_block)
|
61
|
+
@progress_block.call(RUN_TEST_FINISHED, @test.name)
|
62
|
+
rescue DRb::DRbConnError => e
|
63
|
+
logger.debug {"DRb::DRbConnError(#{e.message}), rerun test: #{@test.name}"}
|
64
|
+
self.run
|
65
|
+
rescue Exception => e
|
66
|
+
logger.debug {"#{test.name}, rescue an exception: #{e.message}, add error into result."}
|
67
|
+
@result.add_error(Test::Unit::Error.new(@test.name, e))
|
68
|
+
@result.add_run
|
69
|
+
@progress_block.call(Test::Unit::TestCase::FINISHED, @test.name)
|
70
|
+
@progress_block.call(RUN_TEST_FINISHED, @test.name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def lookup_runner
|
76
|
+
runner = service_provider.lookup_runner
|
77
|
+
begin
|
78
|
+
logger.debug {"#{runner.name}.env ==?: #{WorkingEnv.current == runner.working_env}"}
|
79
|
+
return runner if runner.working_env == WorkingEnv.current
|
80
|
+
runner.shutdown
|
81
|
+
rescue DRb::DRbConnError => e
|
82
|
+
logger.debug {"DRb::DRbConnError(#{e.message})"}
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module TestCaseInjection
|
89
|
+
|
90
|
+
def self.included(base)
|
91
|
+
base.class_eval do
|
92
|
+
alias_method :__run__, :run
|
93
|
+
|
94
|
+
def run(result, &progress_block)
|
95
|
+
logger.debug {"start of run TestCase(#{name})"}
|
96
|
+
DRbTestRunner.new(self, result, &progress_block).run
|
97
|
+
logger.debug {"end of run TestCase(#{name})"}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module TestSuiteInjection
|
104
|
+
def self.included(base)
|
105
|
+
base.class_eval do
|
106
|
+
def dtr_injected?
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
alias_method :__run__, :run
|
111
|
+
|
112
|
+
def run(result, &progress_block)
|
113
|
+
logger.debug { "start of run suite(#{name}), size: #{size};"}
|
114
|
+
|
115
|
+
if result.respond_to?(:synchronize)
|
116
|
+
__run__(result, &progress_block)
|
117
|
+
else
|
118
|
+
DRbTestRunner.start
|
119
|
+
service_provider.setup_working_env WorkingEnv.refresh
|
120
|
+
|
121
|
+
@result = result.extend(MonitorMixin)
|
122
|
+
@complete_cond = @result.new_cond
|
123
|
+
__run__(result) do |channel, value|
|
124
|
+
@result.synchronize do
|
125
|
+
logger.debug { "=> channel: #{channel}, value: #{value}" }
|
126
|
+
progress_block.call(channel, value)
|
127
|
+
if channel == DTR::DRbTestRunner::RUN_TEST_FINISHED
|
128
|
+
@complete_cond.signal
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
logger.debug {"Wait until all tests finished"}
|
133
|
+
@result.synchronize do
|
134
|
+
@complete_cond.wait_until {DRbTestRunner.done?(@result.run_count)}
|
135
|
+
end
|
136
|
+
logger.debug { "==> teardown" }
|
137
|
+
service_provider.teardown_working_env
|
138
|
+
end
|
139
|
+
logger.debug { "end of run suite(#{name}), run_count: #{result.run_count}"}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
module Rejection
|
146
|
+
def self.included(base)
|
147
|
+
base.class_eval do
|
148
|
+
remove_method :dtr_injected? if base.method_defined?(:dtr_injected?)
|
149
|
+
remove_method :run
|
150
|
+
alias_method :run, :__run__
|
151
|
+
remove_method :__run__
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require 'dtr'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
module DTR
|
6
|
+
class MPTask < Rake::TestTask
|
7
|
+
attr_accessor :processes
|
8
|
+
|
9
|
+
def define
|
10
|
+
@libs.unshift DTR.lib_path
|
11
|
+
lib_path = @libs.join(File::PATH_SEPARATOR)
|
12
|
+
|
13
|
+
desc "Run tests" + (@name==:test ? "" : " for #{@name}")
|
14
|
+
task @name do
|
15
|
+
DTR.start_server_daemon_mode
|
16
|
+
start_runners
|
17
|
+
run_code = ''
|
18
|
+
begin
|
19
|
+
RakeFileUtils.verbose(@verbose) do
|
20
|
+
run_code = rake_loader
|
21
|
+
@ruby_opts.unshift( "-I#{lib_path}" )
|
22
|
+
@ruby_opts.unshift( "-w" ) if @warning
|
23
|
+
|
24
|
+
ruby @ruby_opts.join(" ") +
|
25
|
+
" \"#{run_code}\" " +
|
26
|
+
file_list.unshift('dtr/inject_without_updating_codebase.rb').collect { |fn| "\"#{fn}\"" }.join(' ') +
|
27
|
+
" #{option_list}"
|
28
|
+
end
|
29
|
+
ensure
|
30
|
+
DTR.stop_runners_daemon_mode rescue nil
|
31
|
+
DTR.stop_server_daemon_mode rescue nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def processes
|
38
|
+
@processes ? @processes.to_i : 2
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def start_runners
|
43
|
+
runner_names = []
|
44
|
+
self.processes.to_i.times {|i| runner_names << "runner#{i}"}
|
45
|
+
%x[dtr -r #{runner_names.join(',')} -D]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/dtr/ruby_ext.rb
CHANGED
@@ -1,27 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
def to_acl_list
|
5
|
-
self << "druby://localhost"
|
6
|
-
self << "druby://127.0.0.1"
|
7
|
-
addresses = self.collect do |uri|
|
8
|
-
if /^druby:\/\/([^:]+):?\d*$/ =~ uri
|
9
|
-
$1
|
10
|
-
end
|
11
|
-
end.compact.uniq
|
12
|
-
acl_list = addresses.inject([]) do |result, address|
|
13
|
-
result << 'allow'
|
14
|
-
result << address
|
15
|
-
result
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def to_acl
|
20
|
-
ACL.new(DEFAULT_ACL + to_acl_list)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class NullObj
|
25
|
-
def method_missing(*args)
|
1
|
+
class NullObject
|
2
|
+
def method_missing(method, *args, &block)
|
3
|
+
nil
|
26
4
|
end
|
27
5
|
end
|
data/lib/dtr/runner.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'dtr/svn'
|
3
|
+
require 'dtr/env_store'
|
4
|
+
require 'drb'
|
5
|
+
|
6
|
+
unless defined?(logger)
|
7
|
+
require 'logger'
|
8
|
+
def logger
|
9
|
+
return $logger if defined?($logger) && $logger
|
10
|
+
$logger = Logger.new(STDOUT)
|
11
|
+
$logger.level = $DEBUG ? Logger::DEBUG : Logger::ERROR
|
12
|
+
$logger
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module DTR
|
17
|
+
|
18
|
+
def service_provider
|
19
|
+
require 'dtr/service_provider'
|
20
|
+
ServiceProvider.new
|
21
|
+
end
|
22
|
+
|
23
|
+
module_function :service_provider
|
24
|
+
|
25
|
+
class RunnerProtectProcess
|
26
|
+
|
27
|
+
def self.start(runner_names=["Distributed Test Runner"], setup_cmd=nil)
|
28
|
+
new(runner_names, setup_cmd).launch
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(runner_names, setup_cmd)
|
32
|
+
@runner_names = runner_names.is_a?(Array) ? runner_names : [runner_names.to_s]
|
33
|
+
@setup_cmd = setup_cmd
|
34
|
+
@repository = SvnRepository.new
|
35
|
+
@runner_pids = []
|
36
|
+
@herald = nil
|
37
|
+
@working_env_key = :working_env
|
38
|
+
at_exit {
|
39
|
+
[@herald].concat(@runner_pids).each{ |pid| Process.kill 'TERM', pid rescue nil }
|
40
|
+
puts "Shut down runner protect process."
|
41
|
+
exit!
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def launch
|
46
|
+
puts "=> Runner protect process started at: #{Dir.pwd}"
|
47
|
+
loop do
|
48
|
+
@herald = drb_fork{ Herald.new @working_env_key }
|
49
|
+
Process.wait @herald
|
50
|
+
|
51
|
+
working_env = EnvStore.new[@working_env_key]
|
52
|
+
interrupt_if_exit_with_error @repository.update_to(working_env[:revision])
|
53
|
+
interrupt_if_exit_with_error @setup_cmd
|
54
|
+
|
55
|
+
@runner_pids = @runner_names.collect { |name| drb_fork {Runner.start name, working_env} }
|
56
|
+
Process.waitall
|
57
|
+
|
58
|
+
@runner_pids = []
|
59
|
+
puts "=> Runner protect process wakes up"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def interrupt_if_exit_with_error(cmd)
|
65
|
+
logger.debug { "executing: #{cmd.inspect}" }
|
66
|
+
return if cmd.nil? || cmd.empty?
|
67
|
+
output = %x[#{cmd}]
|
68
|
+
logger.debug { "output: \n#{output}" }
|
69
|
+
raise Interrupt.new(output) if $?.exitstatus != 0
|
70
|
+
end
|
71
|
+
|
72
|
+
def drb_fork
|
73
|
+
Process.fork do
|
74
|
+
Signal.trap('TERM') {
|
75
|
+
DRb.stop_service
|
76
|
+
exit
|
77
|
+
}
|
78
|
+
yield
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Herald
|
84
|
+
|
85
|
+
def initialize(key)
|
86
|
+
require 'dtr/service_provider'
|
87
|
+
@key = key
|
88
|
+
EnvStore.new[@key] = nil
|
89
|
+
@provider = DTR.service_provider
|
90
|
+
puts "=> Herald starts off..."
|
91
|
+
start_off
|
92
|
+
end
|
93
|
+
|
94
|
+
def start_off
|
95
|
+
working_env = @provider.working_env
|
96
|
+
logger.debug { "working env: #{working_env.inspect}" }
|
97
|
+
if working_env[:files].empty?
|
98
|
+
$stderr.puts "No test files need to load?(working env: #{working_env.inspect})"
|
99
|
+
sleep(2)
|
100
|
+
start_off
|
101
|
+
else
|
102
|
+
EnvStore.new[@key] = working_env
|
103
|
+
@provider.stop_service
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class Runner
|
109
|
+
include DRbUndumped
|
110
|
+
|
111
|
+
def self.start(name, env)
|
112
|
+
puts "=> Initialize working environment..."
|
113
|
+
env[:libs].select{ |lib| !$LOAD_PATH.include?(lib) && File.exists?(lib) }.each do |lib|
|
114
|
+
$LOAD_PATH << lib
|
115
|
+
logger.debug {"appended lib: #{lib}"}
|
116
|
+
end
|
117
|
+
logger.debug {"$LOAD_PATH: #{$LOAD_PATH.inspect}"}
|
118
|
+
|
119
|
+
require 'dtr/inject'
|
120
|
+
DTR.inject
|
121
|
+
|
122
|
+
env[:files].each do |f|
|
123
|
+
begin
|
124
|
+
load f unless f =~ /^-/
|
125
|
+
logger.debug {"loaded #{f}"}
|
126
|
+
rescue LoadError => e
|
127
|
+
$stderr.puts "No such file to load -- #{f} (Environment: #{env.inspect})"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
@provider = DTR.service_provider
|
132
|
+
|
133
|
+
@provider.provide(self.new(@provider, name, env))
|
134
|
+
puts "=> Runner #{name} provided"
|
135
|
+
DRb.thread.join if DRb.thread
|
136
|
+
end
|
137
|
+
|
138
|
+
attr_reader :name, :working_env
|
139
|
+
|
140
|
+
def initialize(provider, name, working_env)
|
141
|
+
Test::Unit.run = true
|
142
|
+
@name = name
|
143
|
+
@provider = provider
|
144
|
+
@working_env = working_env
|
145
|
+
end
|
146
|
+
|
147
|
+
def run(test, result, &progress_block)
|
148
|
+
print "#{name} is running #{test}..."
|
149
|
+
if test.respond_to?(:__run__)
|
150
|
+
begin
|
151
|
+
test.__run__(result, &progress_block)
|
152
|
+
return true
|
153
|
+
ensure
|
154
|
+
puts "Done"
|
155
|
+
@provider.provide(self)
|
156
|
+
end
|
157
|
+
else
|
158
|
+
msg = "#{test} is not respond_to :__run__!"
|
159
|
+
puts "Error: #{msg}"
|
160
|
+
@provider.stop_service
|
161
|
+
return false
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def shutdown
|
166
|
+
puts "#{self} is shutting down"
|
167
|
+
@provider.stop_service rescue exit!
|
168
|
+
end
|
169
|
+
|
170
|
+
def to_s
|
171
|
+
"Runner #{@name}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|