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