DTR 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,275 @@
1
+ require 'drb/acl'
2
+ require 'drb'
3
+ require 'dtr/base'
4
+ require 'dtr/ruby_ext'
5
+
6
+ module DTR
7
+ class DRbObjectWrapper
8
+ def initialize(uri)
9
+ @uri = uri
10
+ end
11
+
12
+ def method_missing(method, *args, &block)
13
+ (@drbo ||= DRbObject.new_with_uri(@uri)).send(method, *args, &block)
14
+ rescue DRb::DRbConnError => e
15
+ logger.info { "DRb #{@uri} is not alive!" }
16
+ nil
17
+ end
18
+ end
19
+
20
+ class OriginalTestReports
21
+ def summary_of(report)
22
+ reports << report
23
+ convert_to_str(report.successes, '.') + convert_to_str(report.failures, 'F') + convert_to_str(report.errors, 'E')
24
+ end
25
+
26
+ def reports
27
+ @reports ||= []
28
+ end
29
+
30
+ def failures_and_errors
31
+ collect(reports.collect{|r|r.stdout})
32
+ end
33
+
34
+ private
35
+ def convert_to_str(size, status_char)
36
+ return '' if size == 0
37
+ Array.new(size).fill(status_char).join
38
+ end
39
+
40
+ def collect(stdouts)
41
+ test_errors = []
42
+ stdouts.each do |stdout|
43
+ stdout.gsub(/^\s+\d+\) Error:\n(.*:\n.*\n[\s\S]*?\n)\n/) do
44
+ test_errors << " #{test_errors.size + 1}) Error:\n" + $1
45
+ end
46
+ end
47
+
48
+ test_failures = []
49
+ stdouts.each do |stdout|
50
+ stdout.gsub(/^\s+\d+\) Failure:\n([\S\s]*?\n)\n/) do
51
+ test_failures << " #{test_errors.size + test_failures.size + 1}) Failure:\n" + $1
52
+ end
53
+ end
54
+
55
+ if test_errors.empty?
56
+ test_failures.join("\n")
57
+ else
58
+ test_errors.join("\n") + "\n" + test_failures.join("\n")
59
+ end
60
+ end
61
+ end
62
+
63
+ class DrbTestRunnerServer
64
+
65
+ def initialize(my_uri, test_files, clients)
66
+ logger.info {'Initializing DTR server...'}
67
+ @server = Server.new(test_files)
68
+
69
+ clients.each do |uri|
70
+ @server.add_client DRbObjectWrapper.new(uri)
71
+ logger.info {"Added DTR client at #{uri}"}
72
+ end
73
+
74
+ DRb.install_acl clients.to_acl
75
+ logger.info { "Installed acl: #{clients.to_acl_list.join(', ')}" }
76
+
77
+ @my_uri = my_uri
78
+ @original_output_model = DTROPTIONS[:output_model] == :original
79
+
80
+ @progress_bar = unless @original_output_model
81
+ begin
82
+ require 'rubygems'
83
+ require 'progressbar'
84
+ ProgressBar.new("Completed", test_files.size)
85
+ rescue Exception => e
86
+ logger.info { "Progressbar gem is not installed or could not be loaded. " }
87
+ logger.info { e }
88
+ @original_output_model = true
89
+ original_output { "Progressbar gem is not installed or could not be loaded(got error: #{e.message}).\nTurn on original output model." }
90
+ NullObj.new
91
+ end
92
+ else
93
+ NullObj.new
94
+ end
95
+
96
+ @original_reports = @original_output_model ? OriginalTestReports.new : NullObj.new
97
+ end
98
+
99
+ def start(run_setup=false)
100
+ $SAFE = 1 # disable eval() and friends
101
+ logger.info {'Starting server...'}
102
+ DRb.start_service(@my_uri, self)
103
+ logger.info { "=> DTR server started on #{@my_uri}"}
104
+ original_output { "Loaded suites: #{@server.test_files.join(', ')}\n" }
105
+ output "Started\n"
106
+ begin
107
+ begin
108
+ @server.run :setup => run_setup
109
+ rescue Server::NoAliveClientsError => e
110
+ logger.info { e }
111
+ end
112
+
113
+ logger.debug { reports.to_yaml }
114
+ logger.info { '' }
115
+ @server.latest_build_summary.split("\n").each do |msg|
116
+ logger.info { msg }
117
+ end
118
+
119
+ logger.info { "Run tests completed #{succeeded? ? 'SUCCEEDED' : 'FAILED'}" }
120
+ original_output { "\n" }
121
+ original_output { "\n#{@original_reports.failures_and_errors}" }
122
+ output "\n#{@server.latest_build_summary}\n"
123
+ ensure
124
+ DRb.stop_service
125
+ end
126
+ end
127
+
128
+ def succeeded?
129
+ @server.succeeded?
130
+ end
131
+
132
+ def reports
133
+ @server.reports
134
+ end
135
+
136
+ def server_signature
137
+ @server.server_signature
138
+ end
139
+
140
+ def update_setup_log(signature, report)
141
+ if report[:exit_code] == 0
142
+ logger.debug {"updating setup report: #{report.to_yaml}"}
143
+ else
144
+ logger.info { report.to_yaml }
145
+ end
146
+ @server.update_setup_log(signature, report)
147
+ end
148
+
149
+ def update(signature, report)
150
+ original_output {@original_reports.summary_of report}
151
+ if report.succeeded?
152
+ logger.debug {"updating report: #{report.to_yaml}"}
153
+ else
154
+ logger.info { report.to_yaml }
155
+
156
+ output report.stdout unless @original_output_model
157
+ end
158
+
159
+ result = @server.update(signature, report)
160
+ @progress_bar.set reports.size
161
+ result
162
+ end
163
+
164
+ private
165
+ def original_output
166
+ if @original_output_model
167
+ output yield
168
+ end
169
+ end
170
+
171
+ def output(msg)
172
+ $stdout.print msg
173
+ $stdout.flush
174
+ end
175
+ end
176
+
177
+ class DrbTestRunnerClientServer
178
+
179
+ def initialize(my_uri, server_uri)
180
+ @my_uri = my_uri
181
+ @server_uri = server_uri
182
+ @run_setup_signature = nil
183
+ @client_name = DTROPTIONS[:client_name]
184
+ end
185
+
186
+ def name
187
+ @client_name
188
+ end
189
+
190
+ def queue
191
+ @queue ||= []
192
+ end
193
+
194
+ def idle?
195
+ queue.empty?
196
+ end
197
+
198
+ def setup(signature)
199
+ logger.info {"request setup"}
200
+ @run_setup_signature = signature
201
+ end
202
+
203
+ def run(runner_name, test_files, signature)
204
+ unless test_files.nil? || test_files.empty?
205
+ queue << [runner_name, test_files, signature]
206
+ else
207
+ logger.info { "Error: argument test_files is #{test_files.nil? ? 'nil' : 'empty'}!"}
208
+ end
209
+ end
210
+
211
+ def server_signature
212
+ drbServer.server_signature
213
+ end
214
+
215
+ def start
216
+ $SAFE = 1 # disable eval() and friends
217
+ access_uris = [@server_uri]
218
+ DRb.install_acl access_uris.to_acl
219
+ logger.info { "Installed acl: #{access_uris.to_acl_list.join(', ')}" }
220
+ DRb.start_service(@my_uri, self)
221
+
222
+ begin
223
+ logger.info { "=> DTR client started on #{@my_uri}"}
224
+ logger.info { "=> OPTIONS: #{DTROPTIONS.to_yaml}"}
225
+ logger.info { "=> Ctrl-C to shutdown; call with --help for options" }
226
+
227
+ loop do
228
+ if @run_setup_signature
229
+ logger.info {"Setup"}
230
+ begin
231
+ result = dtrClient.setup(@run_setup_signature)
232
+ if result[:exit_code] != 0
233
+ logger.info { "SETUP FAILED: #{result.inspect}" }
234
+ puts "SETUP FAILED: #{result.inspect}"
235
+ break
236
+ end
237
+ rescue Exception => e
238
+ logger.error { e }
239
+ end
240
+ @run_setup_signature = nil
241
+ logger.info {"Sleeping..."}
242
+ end
243
+ unless queue.empty?
244
+ logger.info {"Working..."}
245
+ logger.debug {queue.first.to_yaml}
246
+ begin
247
+ dtrClient.run(*queue.first.dup)
248
+ rescue Exception => e
249
+ logger.error { e }
250
+ end
251
+ queue.pop
252
+ logger.info {"Sleeping..."}
253
+ end
254
+
255
+ begin
256
+ sleep(1)
257
+ rescue Interrupt => e
258
+ break # Ctrl-C to shutdown
259
+ end
260
+ end
261
+ ensure
262
+ DRb.stop_service
263
+ end
264
+ end
265
+
266
+ private
267
+ def dtrClient
268
+ Client.new(drbServer, @client_name)
269
+ end
270
+
271
+ def drbServer
272
+ @drbServer ||= DRbObjectWrapper.new(@server_uri)
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,27 @@
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)
26
+ end
27
+ end
data/lib/dtr.rb ADDED
@@ -0,0 +1,88 @@
1
+ require "dtr/drb_dtr"
2
+ require "fileutils"
3
+ require "logger"
4
+
5
+ DTRVERSION="0.0.1"
6
+ DTROPTIONS = {
7
+ #dtr client
8
+ :client_name => "client(#{Time.now})",
9
+ :setup => "rake db:test:prepare",
10
+ :port => "3344",
11
+ :server_uri => "druby://localhost:1314",
12
+ #dtr server
13
+ :server_port => "1314",
14
+ :wait_a_moment => 5,
15
+ :output_model => :log,
16
+ :raise_on_no_alive_client => false,
17
+ #both
18
+ :tmp_dir => "tmp/dtr",
19
+ :log_level => Logger::INFO
20
+ }
21
+
22
+ unless defined?(logger)
23
+ def logger
24
+ return $logger if defined?($logger) && $logger
25
+ DTR.ensure_temp_dir_exist
26
+ $logger = DTROPTIONS[:output_model] == :stdout ? Logger.new(STDOUT) : Logger.new("#{DTROPTIONS[:tmp_dir]}/runtime.log", 2, 1024000)
27
+ $logger.level = DTROPTIONS[:log_level]
28
+ $logger
29
+ end
30
+ end
31
+
32
+ module DTR
33
+
34
+ def start_client_server(my_port=DTROPTIONS[:port], server_uri=DTROPTIONS[:server_uri])
35
+ my_uri = "druby://:" << my_port
36
+
37
+ DTROPTIONS[:tmp_dir] = DTROPTIONS[:tmp_dir] + '/' + DTROPTIONS[:client_name].gsub(/[^\d\w]+/, '_')
38
+ logger.info { "Initializing client server at '#{my_uri}'..." }
39
+ logger.info { "Working dir: #{Dir.pwd}" }
40
+ logger.info { "Listening server at '#{server_uri}'..." }
41
+ ensure_temp_dir_exist
42
+ server = DrbTestRunnerClientServer.new my_uri, server_uri
43
+ server.start
44
+ end
45
+
46
+ def run(tests, clients, opts={})
47
+ opts = {:my_uri => "druby://:#{DTROPTIONS[:server_port]}", :run_setup_before_start => false}.merge(opts)
48
+ server = DrbTestRunnerServer.new opts[:my_uri], tests, clients
49
+ server.start opts[:run_setup_before_start]
50
+ server.succeeded?
51
+ end
52
+
53
+ def ensure_temp_dir_exist
54
+ raise "Tmp dir(#{DTROPTIONS[:tmp_dir]}) is a file!" if File.file?(DTROPTIONS[:tmp_dir])
55
+ unless File.exist?(DTROPTIONS[:tmp_dir])
56
+ FileUtils.makedirs(DTROPTIONS[:tmp_dir])
57
+ end
58
+ end
59
+
60
+ module_function :start_client_server, :run, :ensure_temp_dir_exist
61
+
62
+ class ClientServer
63
+ @@servers = []
64
+
65
+ def self.stop_all
66
+ logger.info { "client servers: #{@@servers.join(", ")}" }
67
+ @@servers.each do |server|
68
+ Process.kill "TERM", server
69
+ logger.info { "Killed #{server}" }
70
+ end
71
+ end
72
+
73
+ def initialize(opts={})
74
+ @opts = DTROPTIONS.merge opts
75
+ end
76
+
77
+ def start
78
+ @@servers << Process.fork do
79
+ DTROPTIONS.merge! @opts
80
+ trap("TERM") do
81
+ exit!
82
+ end
83
+ DTR.start_client_server
84
+ end
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,5 @@
1
+ call rake run_dtr_s
2
+ echo "prog exit code [%errorlevel%]"
3
+
4
+ call rake run_dtr_f
5
+ echo "prog exit code [%errorlevel%]"
@@ -0,0 +1,32 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/test_helper'
3
+
4
+ class AveragePackerTest < Test::Unit::TestCase
5
+ def setup
6
+ @packer = DTR::AveragePacker.new
7
+ end
8
+ def test_pack
9
+ assert_raise RuntimeError do
10
+ @packer.pack([], 2)
11
+ end
12
+
13
+ assert_raise RuntimeError do
14
+ @packer.pack(nil, 2)
15
+ end
16
+
17
+ assert_equal [[1]], @packer.pack([1], 1)
18
+
19
+ assert_equal [[1, 2]], @packer.pack([1, 2], 1)
20
+
21
+ assert_equal [[1], []], @packer.pack([1], 2)
22
+
23
+ assert_equal [['1'], []], @packer.pack(['1'], 2)
24
+
25
+ assert_equal [[1], [2]], @packer.pack([1, 2], 2)
26
+
27
+ assert_equal [[1, 3], [2]], @packer.pack([1, 2, 3], 2)
28
+ assert_equal [[1, 4], [2], [3]], @packer.pack([1, 2, 3, 4], 3)
29
+
30
+ assert_equal [[1, 4, 7], [2, 5, 8], [3, 6]], @packer.pack([1, 2, 3, 4, 5, 6, 7, 8], 3)
31
+ end
32
+ end
data/test/base_test.rb ADDED
@@ -0,0 +1,236 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ DTROPTIONS[:packing_once] = false
7
+ @server = DTR::Server.new
8
+ @client_num = 0
9
+
10
+ NoResponseClient.server = @server
11
+ end
12
+
13
+ def test_run_test_successfully
14
+ @server.test_files = [new_successful_test_file]
15
+ @server.add_client new_client
16
+
17
+ assert_false @server.complete?
18
+ assert_false @server.succeeded?
19
+ @server.run
20
+ assert @server.complete?
21
+ assert @server.succeeded?
22
+
23
+ test_report = @server.reports[@server.test_files[0]]
24
+ assert_equal @server.clients.first.name, test_report.client_name
25
+ assert test_report.succeeded?
26
+ assert test_report.stdout.length > 0
27
+ assert test_report.stderr.length > 0
28
+ end
29
+
30
+ def test_run_test_failed
31
+ @server.test_files = [new_failed_test_file]
32
+ @server.add_client new_client
33
+
34
+ assert_false @server.complete?
35
+ assert_false @server.succeeded?
36
+ @server.run
37
+ assert @server.complete?
38
+ assert_false @server.succeeded?
39
+
40
+ test_report = @server.reports[@server.test_files[0]]
41
+ assert_false test_report.succeeded?
42
+ end
43
+
44
+ def test_run_on_multi_clients_with_average_strategy
45
+ @server.test_files = [new_successful_test_file, new_successful_test_file]
46
+ @server.add_client new_client
47
+ @server.add_client new_client
48
+
49
+ @server.run
50
+
51
+ assert @server.complete?
52
+ assert @server.succeeded?
53
+ assert_equal 1, @server.clients[0].completed_cmds.size
54
+ assert_equal 1, @server.clients[1].completed_cmds.size
55
+ end
56
+
57
+ def test_should_dispatch_tests_which_is_waiting_for_report_to_idle_client
58
+ @server.test_files = [new_successful_test_file, new_successful_test_file]
59
+ @server.add_client NoResponseClient.new
60
+ client = new_client
61
+ @server.add_client client
62
+
63
+ @server.run
64
+
65
+ assert @server.complete?
66
+ assert @server.succeeded?
67
+ assert_equal 2, client.completed_cmds.size
68
+ end
69
+
70
+ def test_update_report_by_signature
71
+ @server.test_files = [new_successful_test_file]
72
+ @server.add_client NoResponseClient.new
73
+ @server.run(:wait_for_reports => false)
74
+
75
+ assert_false @server.complete?
76
+ report = new_report
77
+ assert @server.update(@server.clients.first.report_signature, report)
78
+ assert @server.complete?
79
+ assert_equal({report.test => report}, @server.reports)
80
+ end
81
+
82
+ def test_should_not_be_able_to_update_report_after_completed
83
+ @server.test_files = [new_successful_test_file]
84
+ @server.add_client NoResponseClient.new
85
+ @server.run(:wait_for_reports => false)
86
+ report = new_report
87
+ @server.update @server.clients.first.report_signature, report
88
+ assert @server.complete?
89
+
90
+ @server.reports.clear
91
+ assert_false @server.update(@server.clients.first.report_signature, report)
92
+ assert_equal({}, @server.reports)
93
+ end
94
+
95
+ def test_should_not_update_reports_if_server_never_ran
96
+ @server.test_files = [new_successful_test_file]
97
+ assert_false @server.update(nil, DTR::TestReport.new('test', {:name => 'client name', :exit_code => 0}, *success_test_summary))
98
+ assert_equal({}, @server.reports)
99
+
100
+ @server.update(Time.now.to_s, new_report)
101
+ assert_equal({}, @server.reports)
102
+ end
103
+
104
+ def test_should_not_update_reports_if_signature_is_incorrect
105
+ @server.test_files = [new_successful_test_file]
106
+ @server.add_client NoResponseClient.new
107
+ @server.run(:wait_for_reports => false)
108
+
109
+ assert_false @server.complete?
110
+ assert_false @server.update('incorrect signature', new_report)
111
+ assert_equal({}, @server.reports)
112
+ end
113
+
114
+ def test_summary_after_ran_tests
115
+ @server.test_files = [new_successful_test_file, new_failed_test_file, new_error_test_file]
116
+ @server.add_client new_client
117
+
118
+ assert_raise RuntimeError do
119
+ @server.latest_build_summary
120
+ end
121
+ @server.run
122
+ assert(/Finished in \d\.\d seconds./ =~ @server.latest_build_summary)
123
+ assert(/4 tests, 2 assertions, 1 failures, 2 errors/ =~ @server.latest_build_summary)
124
+ end
125
+
126
+ def test_should_be_able_to_setup_test_env_on_client_side
127
+ @server.test_files = [new_successful_test_file]
128
+ client = NoResponseClient.new
129
+ @server.add_client client
130
+ @server.run :setup => true, :wait_for_reports => false
131
+ assert_equal "setup #{client.report_signature}", client.log
132
+ end
133
+
134
+ def test_should_have_client_setup_log
135
+ @server.test_files = [new_successful_test_file]
136
+ client = new_client
137
+ @server.add_client client
138
+ @server.run :setup => true, :wait_for_reports => false
139
+ assert @server.client_setup_logs[client.name]
140
+ assert(/echo "setup_dtr_client"/ =~ @server.client_setup_logs[client.name][:stdout])
141
+ assert_equal client.name, @server.client_setup_logs[client.name][:client_name]
142
+ assert_equal 0, @server.client_setup_logs[client.name][:exit_code]
143
+ end
144
+
145
+ def test_should_ignore_out_of_date_report_of_client_setup
146
+ @server.test_files = [new_successful_test_file]
147
+ client = NoResponseClient.new
148
+ @server.add_client client
149
+ @server.run :setup => true, :wait_for_reports => false
150
+ out_of_date = client.report_signature
151
+ sleep(1)
152
+ @server.run :setup => true, :wait_for_reports => false
153
+ assert_false @server.update_setup_log(out_of_date, {:client_name => client.name})
154
+ end
155
+
156
+ def test_should_update_as_0_tests_when_test_file_execution_result_has_no_summary_line
157
+ empty_test_file = new_test_file {|file, num|}
158
+ @server.test_files = [empty_test_file]
159
+ @server.add_client new_client
160
+
161
+ @server.run
162
+
163
+ assert(/Finished in \d\.\d seconds./ =~ @server.latest_build_summary)
164
+ assert(/0 tests, 0 assertions, 0 failures, 0 errors/ =~ @server.latest_build_summary)
165
+ end
166
+
167
+ def test_should_be_failed_when_no_test_files_specified
168
+ @server.test_files = []
169
+ @server.add_client new_client
170
+
171
+ @server.run
172
+
173
+ assert @server.complete?
174
+ assert_false @server.succeeded?
175
+ assert(/Finished in \d\.\d seconds./ =~ @server.latest_build_summary)
176
+ assert(/0 tests, 0 assertions, 0 failures, 0 errors/ =~ @server.latest_build_summary)
177
+ end
178
+
179
+ def test_should_remove_client_when_client_setup_failed
180
+ @server.test_files = [new_successful_test_file]
181
+ @server.add_client DTR::Client.new(@server, "client name", 'error cmd')
182
+
183
+ @server.run :setup => true
184
+
185
+ assert @server.clients.empty?
186
+ assert @server.client_setup_logs["client name"]
187
+ end
188
+
189
+ def test_should_work_with_client_which_is_added_after_ran_tests
190
+ @server.test_files = [new_successful_test_file]
191
+ @server.add_client NoResponseClient.new
192
+
193
+ @server.send(:run_start, :setup => true)
194
+ @server.send(:run_once)
195
+ assert_false @server.complete?
196
+
197
+ client = new_client
198
+ @server.add_client client
199
+ @server.send(:run_once)
200
+ assert @server.complete?
201
+ assert @server.client_setup_logs[client.name]
202
+ end
203
+
204
+ def test_should_only_ask_client_to_setup_once
205
+ @server.test_files = [new_successful_test_file]
206
+ client = NoResponseClient.new
207
+ @server.add_client client
208
+
209
+ @server.send(:run_start, :setup => true)
210
+ @server.send(:run_once)
211
+ @server.send(:run_once)
212
+ @server.send(:run_once)
213
+ assert_equal "setup #{client.report_signature}", client.log
214
+ end
215
+
216
+ def xtest_pack_once
217
+ DTROPTIONS[:packing_once] = true
218
+ @server.test_files = [new_successful_test_file, new_successful_test_file]
219
+ @server.add_client NoResponseClient.new
220
+ @server.add_client new_client
221
+ @server.run
222
+ end
223
+
224
+ def success_test_summary
225
+ [1, 0, 0, 0]
226
+ end
227
+
228
+ def new_client
229
+ DTR::Client.new(@server, "client#{@client_num += 1}")
230
+ end
231
+
232
+ def new_report(test=@server.test_files.first)
233
+ DTR::TestReport.new(test, {:name => 'client name', :exit_code => 0}, *success_test_summary)
234
+ end
235
+
236
+ end