DTR 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,8 @@
1
+ require 'dtr/service_provider'
2
+ require 'dtr/svn'
3
+ require 'dtr/inject'
4
+
5
+ $repository = DTR::SvnRepository.new
6
+ $argv_dup = ARGV.dup.delete_if{|f| f.include?('dtr/inject_with_svn.rb')}
7
+
8
+ DTR.inject
@@ -0,0 +1,6 @@
1
+ require 'dtr/service_provider'
2
+ require 'dtr/inject'
3
+ $repository = NullObject.new
4
+ $argv_dup = ARGV.dup.delete_if{|f| f.include?('dtr/inject_without_updating_codebase.rb')}
5
+
6
+ DTR.inject
@@ -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
- class Array
3
- DEFAULT_ACL = %w(deny all)
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