DTR 0.0.1 → 0.0.2
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 +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
|