testbot 0.4.6 → 0.4.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/README.markdown +3 -1
- data/bin/testbot +1 -1
- data/lib/generators/testbot/testbot_generator.rb +1 -1
- data/lib/railtie.rb +0 -2
- data/lib/requester/requester.rb +134 -0
- data/lib/runner/job.rb +52 -33
- data/lib/runner/runner.rb +215 -0
- data/lib/server/build.rb +29 -25
- data/lib/server/db.rb +42 -39
- data/lib/server/group.rb +42 -38
- data/lib/server/job.rb +44 -40
- data/lib/server/runner.rb +37 -33
- data/lib/server/server.rb +74 -0
- data/lib/{adapters → shared/adapters}/adapter.rb +0 -0
- data/lib/{adapters → shared/adapters}/cucumber_adapter.rb +0 -0
- data/lib/{adapters → shared/adapters}/helpers/ruby_env.rb +0 -0
- data/lib/{adapters → shared/adapters}/rspec_adapter.rb +0 -0
- data/lib/{adapters → shared/adapters}/test_unit_adapter.rb +0 -0
- data/lib/shared/testbot.rb +141 -0
- data/lib/tasks/testbot.rake +3 -3
- data/lib/testbot.rb +2 -146
- data/testbot.gemspec +3 -7
- metadata +13 -30
- data/lib/new_runner.rb +0 -30
- data/lib/requester.rb +0 -130
- data/lib/runner.rb +0 -213
- data/lib/server.rb +0 -72
data/lib/requester.rb
DELETED
@@ -1,130 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'httparty'
|
3
|
-
require 'macaddr'
|
4
|
-
require 'ostruct'
|
5
|
-
require File.dirname(__FILE__) + '/shared/ssh_tunnel'
|
6
|
-
require File.dirname(__FILE__) + '/adapters/adapter'
|
7
|
-
require File.expand_path(File.dirname(__FILE__) + '/testbot')
|
8
|
-
|
9
|
-
class Hash
|
10
|
-
def symbolize_keys_without_active_support
|
11
|
-
inject({}) do |options, (key, value)|
|
12
|
-
options[(key.to_sym rescue key) || key] = value
|
13
|
-
options
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class Requester
|
19
|
-
|
20
|
-
attr_reader :config
|
21
|
-
|
22
|
-
def initialize(config = {})
|
23
|
-
config = config.symbolize_keys_without_active_support
|
24
|
-
config[:rsync_path] ||= Testbot::DEFAULT_SERVER_PATH
|
25
|
-
config[:project] ||= Testbot::DEFAULT_PROJECT
|
26
|
-
config[:server_user] ||= Testbot::DEFAULT_USER
|
27
|
-
config[:available_runner_usage] ||= Testbot::DEFAULT_RUNNER_USAGE
|
28
|
-
@config = OpenStruct.new(config)
|
29
|
-
end
|
30
|
-
|
31
|
-
def run_tests(adapter, dir)
|
32
|
-
puts if config.simple_output
|
33
|
-
|
34
|
-
if config.ssh_tunnel
|
35
|
-
SSHTunnel.new(config.server_host, config.server_user, adapter.requester_port).open
|
36
|
-
server_uri = "http://127.0.0.1:#{adapter.requester_port}"
|
37
|
-
else
|
38
|
-
server_uri = "http://#{config.server_host}:#{Testbot::SERVER_PORT}"
|
39
|
-
end
|
40
|
-
|
41
|
-
rsync_ignores = config.rsync_ignores.to_s.split.map { |pattern| "--exclude='#{pattern}'" }.join(' ')
|
42
|
-
system "rsync -az --delete -e ssh #{rsync_ignores} . #{rsync_uri}"
|
43
|
-
|
44
|
-
files = adapter.test_files(dir)
|
45
|
-
sizes = adapter.get_sizes(files)
|
46
|
-
|
47
|
-
build_id = HTTParty.post("#{server_uri}/builds", :body => { :root => root,
|
48
|
-
:type => adapter.type.to_s,
|
49
|
-
:project => config.project,
|
50
|
-
:requester_mac => Mac.addr,
|
51
|
-
:available_runner_usage => config.available_runner_usage,
|
52
|
-
:files => files.join(' '),
|
53
|
-
:sizes => sizes.join(' '),
|
54
|
-
:jruby => jruby? })
|
55
|
-
|
56
|
-
|
57
|
-
last_results_size = 0
|
58
|
-
success = true
|
59
|
-
error_count = 0
|
60
|
-
while true
|
61
|
-
sleep 1
|
62
|
-
|
63
|
-
begin
|
64
|
-
@build = HTTParty.get("#{server_uri}/builds/#{build_id}", :format => :json)
|
65
|
-
next unless @build
|
66
|
-
rescue Exception => ex
|
67
|
-
error_count += 1
|
68
|
-
if error_count > 4
|
69
|
-
puts "Failed to get status: #{ex.message}"
|
70
|
-
error_count = 0
|
71
|
-
end
|
72
|
-
next
|
73
|
-
end
|
74
|
-
|
75
|
-
results = @build['results'][last_results_size..-1]
|
76
|
-
unless results == ''
|
77
|
-
if config.simple_output
|
78
|
-
print results.gsub(/[^\.F]|Finished/, '')
|
79
|
-
STDOUT.flush
|
80
|
-
else
|
81
|
-
puts results
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
last_results_size = @build['results'].size
|
86
|
-
|
87
|
-
break if @build['done']
|
88
|
-
end
|
89
|
-
|
90
|
-
puts if config.simple_output
|
91
|
-
|
92
|
-
@build["success"]
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.create_by_config(path)
|
96
|
-
config = YAML.load_file(path)
|
97
|
-
Requester.new(config)
|
98
|
-
end
|
99
|
-
|
100
|
-
def result_lines
|
101
|
-
@build['results'].split("\n").find_all { |line| line_is_result?(line) }.map { |line| line.chomp }
|
102
|
-
end
|
103
|
-
|
104
|
-
private
|
105
|
-
|
106
|
-
def root
|
107
|
-
if localhost?
|
108
|
-
config.rsync_path
|
109
|
-
else
|
110
|
-
"#{config.server_user}@#{config.server_host}:#{config.rsync_path}"
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def rsync_uri
|
115
|
-
localhost? ? config.rsync_path : "#{config.server_user}@#{config.server_host}:#{config.rsync_path}"
|
116
|
-
end
|
117
|
-
|
118
|
-
def localhost?
|
119
|
-
[ '0.0.0.0', 'localhost', '127.0.0.1' ].include?(config.server_host)
|
120
|
-
end
|
121
|
-
|
122
|
-
def line_is_result?(line)
|
123
|
-
line =~ /\d+ fail/
|
124
|
-
end
|
125
|
-
|
126
|
-
def jruby?
|
127
|
-
RUBY_PLATFORM =~ /java/ || !!ENV['USE_JRUBY']
|
128
|
-
end
|
129
|
-
|
130
|
-
end
|
data/lib/runner.rb
DELETED
@@ -1,213 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'httparty'
|
3
|
-
require 'macaddr'
|
4
|
-
require 'ostruct'
|
5
|
-
require File.dirname(__FILE__) + '/shared/ssh_tunnel'
|
6
|
-
require File.dirname(__FILE__) + '/adapters/adapter'
|
7
|
-
require File.dirname(__FILE__) + '/runner/job'
|
8
|
-
|
9
|
-
TIME_BETWEEN_NORMAL_POLLS = 1
|
10
|
-
TIME_BETWEEN_QUICK_POLLS = 0.1
|
11
|
-
TIME_BETWEEN_PINGS = 5
|
12
|
-
TIME_BETWEEN_VERSION_CHECKS = Testbot.version.include?('.DEV.') ? 10 : 60
|
13
|
-
MAX_CPU_USAGE_WHEN_IDLE = 50
|
14
|
-
|
15
|
-
class CPU
|
16
|
-
|
17
|
-
def self.current_usage
|
18
|
-
process_usages = `ps -eo pcpu`
|
19
|
-
total_usage = process_usages.split("\n").inject(0) { |sum, usage| sum += usage.strip.to_f }
|
20
|
-
(total_usage / count).to_i
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.count
|
24
|
-
case RUBY_PLATFORM
|
25
|
-
when /darwin/
|
26
|
-
`hwprefs cpu_count`.to_i
|
27
|
-
when /linux/
|
28
|
-
`cat /proc/cpuinfo | grep processor | wc -l`.to_i
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
class Server
|
35
|
-
include HTTParty
|
36
|
-
end
|
37
|
-
|
38
|
-
class Runner
|
39
|
-
|
40
|
-
def initialize(config)
|
41
|
-
@instances = []
|
42
|
-
@last_requester_mac = nil
|
43
|
-
@last_version_check = Time.now - TIME_BETWEEN_VERSION_CHECKS - 1
|
44
|
-
@config = OpenStruct.new(config)
|
45
|
-
@config.max_instances = @config.max_instances ? @config.max_instances.to_i : CPU.count
|
46
|
-
|
47
|
-
if @config.ssh_tunnel
|
48
|
-
server_uri = "http://127.0.0.1:#{Testbot::SERVER_PORT}"
|
49
|
-
else
|
50
|
-
server_uri = "http://#{@config.server_host}:#{Testbot::SERVER_PORT}"
|
51
|
-
end
|
52
|
-
|
53
|
-
Server.base_uri(server_uri)
|
54
|
-
end
|
55
|
-
|
56
|
-
attr_reader :config
|
57
|
-
|
58
|
-
def run!
|
59
|
-
# Remove legacy instance* and *_rsync|git style folders
|
60
|
-
Dir.entries(".").find_all { |name| name.include?('instance') || name.include?('_rsync') ||
|
61
|
-
name.include?('_git') }.each { |folder|
|
62
|
-
system "rm -rf #{folder}"
|
63
|
-
}
|
64
|
-
|
65
|
-
SSHTunnel.new(@config.server_host, @config.server_user || Testbot::DEFAULT_USER).open if @config.ssh_tunnel
|
66
|
-
while true
|
67
|
-
begin
|
68
|
-
update_uid!
|
69
|
-
start_ping
|
70
|
-
wait_for_jobs
|
71
|
-
rescue Exception => ex
|
72
|
-
break if [ 'SignalException', 'Interrupt' ].include?(ex.class.to_s)
|
73
|
-
puts "The runner crashed, restarting. Error: #{ex.inspect} #{ex.class}"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def update_uid!
|
81
|
-
# When a runner crashes or is restarted it might loose current job info. Because
|
82
|
-
# of this we provide a new unique ID to the server so that it does not wait for
|
83
|
-
# lost jobs to complete.
|
84
|
-
@uid = "#{Time.now.to_i}@#{Mac.addr}"
|
85
|
-
end
|
86
|
-
|
87
|
-
def wait_for_jobs
|
88
|
-
last_check_found_a_job = false
|
89
|
-
loop do
|
90
|
-
sleep (last_check_found_a_job ? TIME_BETWEEN_QUICK_POLLS : TIME_BETWEEN_NORMAL_POLLS)
|
91
|
-
|
92
|
-
check_for_update if !last_check_found_a_job && time_for_update?
|
93
|
-
|
94
|
-
# Only get jobs from one requester at a time
|
95
|
-
next_params = base_params
|
96
|
-
if @instances.size > 0
|
97
|
-
next_params.merge!({ :requester_mac => @last_requester_mac })
|
98
|
-
next_params.merge!({ :no_jruby => true }) if max_jruby_instances?
|
99
|
-
else
|
100
|
-
@last_requester_mac = nil
|
101
|
-
end
|
102
|
-
|
103
|
-
# Makes sure all instances are listed as available after a run
|
104
|
-
clear_completed_instances
|
105
|
-
next unless cpu_available?
|
106
|
-
|
107
|
-
next_job = Server.get("/jobs/next", :query => next_params) rescue nil
|
108
|
-
last_check_found_a_job = (next_job != nil)
|
109
|
-
next unless last_check_found_a_job
|
110
|
-
|
111
|
-
job = Job.new(*([ self, next_job.split(',') ].flatten))
|
112
|
-
if first_job_from_requester?
|
113
|
-
fetch_code(job)
|
114
|
-
before_run(job) if File.exists?("#{job.project}/lib/tasks/testbot.rake")
|
115
|
-
end
|
116
|
-
|
117
|
-
@instances << [ Thread.new { job.run(free_instance_number) },
|
118
|
-
free_instance_number, job ]
|
119
|
-
@last_requester_mac = job.requester_mac
|
120
|
-
loop do
|
121
|
-
clear_completed_instances
|
122
|
-
break unless max_instances_running?
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def max_jruby_instances?
|
128
|
-
return unless @config.max_jruby_instances
|
129
|
-
@instances.find_all { |thread, n, job| job.jruby? }.size >= @config.max_jruby_instances
|
130
|
-
end
|
131
|
-
|
132
|
-
def fetch_code(job)
|
133
|
-
system "rsync -az --delete -e ssh #{job.root}/ #{job.project}"
|
134
|
-
end
|
135
|
-
|
136
|
-
def before_run(job)
|
137
|
-
bundler_cmd = RubyEnv.bundler?(job.project) ? "bundle; " : ""
|
138
|
-
system "export RAILS_ENV=test; export TEST_INSTANCES=#{@config.max_instances}; cd #{job.project}; #{bundler_cmd} rake testbot:before_run"
|
139
|
-
end
|
140
|
-
|
141
|
-
def first_job_from_requester?
|
142
|
-
@last_requester_mac == nil
|
143
|
-
end
|
144
|
-
|
145
|
-
def cpu_available?
|
146
|
-
@instances.size > 0 || CPU.current_usage < MAX_CPU_USAGE_WHEN_IDLE
|
147
|
-
end
|
148
|
-
|
149
|
-
def time_for_update?
|
150
|
-
time_for_update = ((Time.now - @last_version_check) >= TIME_BETWEEN_VERSION_CHECKS)
|
151
|
-
@last_version_check = Time.now if time_for_update
|
152
|
-
time_for_update
|
153
|
-
end
|
154
|
-
|
155
|
-
def check_for_update
|
156
|
-
return unless @config.auto_update
|
157
|
-
version = Server.get('/version') rescue Testbot.version
|
158
|
-
return unless version != Testbot.version
|
159
|
-
|
160
|
-
# In a PXE cluster with a shared gem folder we only want one of them to do the update
|
161
|
-
if @config.wait_for_updated_gem
|
162
|
-
# Gem.available? is cached so it won't detect new gems.
|
163
|
-
gem = Gem::Dependency.new("testbot", version)
|
164
|
-
successful_install = !Gem::SourceIndex.from_installed_gems.search(gem).empty?
|
165
|
-
else
|
166
|
-
if version.include?(".DEV.")
|
167
|
-
successful_install = system("wget #{@config.dev_gem_root}/testbot-#{version}.gem && gem install testbot-#{version}.gem --no-ri --no-rdoc && rm testbot-#{version}.gem")
|
168
|
-
else
|
169
|
-
successful_install = system "gem install testbot -v #{version} --no-ri --no-rdoc"
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
system "testbot #{ARGV.join(' ')}" if successful_install
|
174
|
-
end
|
175
|
-
|
176
|
-
def ping_params
|
177
|
-
{ :hostname => (@hostname ||= `hostname`.chomp), :max_instances => @config.max_instances,
|
178
|
-
:idle_instances => (@config.max_instances - @instances.size), :username => ENV['USER'] }.merge(base_params)
|
179
|
-
end
|
180
|
-
|
181
|
-
def base_params
|
182
|
-
{ :version => Testbot.version, :uid => @uid }
|
183
|
-
end
|
184
|
-
|
185
|
-
def max_instances_running?
|
186
|
-
@instances.size == @config.max_instances
|
187
|
-
end
|
188
|
-
|
189
|
-
def clear_completed_instances
|
190
|
-
@instances.each_with_index do |data, index|
|
191
|
-
@instances.delete_at(index) if data.first.join(0.25)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def free_instance_number
|
196
|
-
0.upto(@config.max_instances - 1) do |number|
|
197
|
-
return number unless @instances.find { |instance, n, job| n == number }
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def start_ping
|
202
|
-
Thread.new do
|
203
|
-
while true
|
204
|
-
begin
|
205
|
-
Server.get("/runners/ping", :body => ping_params)
|
206
|
-
rescue
|
207
|
-
end
|
208
|
-
sleep TIME_BETWEEN_PINGS
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
end
|
data/lib/server.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'sinatra'
|
3
|
-
require 'yaml'
|
4
|
-
require 'json'
|
5
|
-
require File.join(File.dirname(__FILE__), 'server/job.rb')
|
6
|
-
require File.join(File.dirname(__FILE__), 'server/group.rb') unless defined?(Group)
|
7
|
-
require File.join(File.dirname(__FILE__), 'server/runner.rb')
|
8
|
-
require File.join(File.dirname(__FILE__), 'server/build.rb')
|
9
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'testbot'))
|
10
|
-
|
11
|
-
if ENV['INTEGRATION_TEST']
|
12
|
-
set :port, 22880
|
13
|
-
else
|
14
|
-
set :port, Testbot::SERVER_PORT
|
15
|
-
end
|
16
|
-
|
17
|
-
disable :logging if ENV['DISABLE_LOGGING']
|
18
|
-
|
19
|
-
class Server
|
20
|
-
def self.valid_version?(runner_version)
|
21
|
-
Testbot.version == runner_version
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
post '/builds' do
|
26
|
-
build = Build.create_and_build_jobs(params)[:id].to_s
|
27
|
-
end
|
28
|
-
|
29
|
-
get '/builds/:id' do
|
30
|
-
build = Build.find(:id => params[:id].to_i)
|
31
|
-
build.destroy if build[:done]
|
32
|
-
{ "done" => build[:done], "results" => build[:results], "success" => build[:success] }.to_json
|
33
|
-
end
|
34
|
-
|
35
|
-
get '/jobs/next' do
|
36
|
-
next_job, runner = Job.next(params, @env['REMOTE_ADDR'])
|
37
|
-
if next_job
|
38
|
-
next_job.update(:taken_at => Time.now, :taken_by_id => runner.id)
|
39
|
-
[ next_job[:id], next_job[:requester_mac], next_job[:project], next_job[:root], next_job[:type], (next_job[:jruby] == 1 ? 'jruby' : 'ruby'), next_job[:files] ].join(',')
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
put '/jobs/:id' do
|
44
|
-
Job.find(:id => params[:id].to_i).update(:result => params[:result], :success => params[:success]); nil
|
45
|
-
end
|
46
|
-
|
47
|
-
get '/runners/ping' do
|
48
|
-
return unless Server.valid_version?(params[:version])
|
49
|
-
runner = Runner.find(:uid => params[:uid])
|
50
|
-
runner.update(params.merge({ :last_seen_at => Time.now })) if runner
|
51
|
-
nil
|
52
|
-
end
|
53
|
-
|
54
|
-
get '/runners/outdated' do
|
55
|
-
Runner.find_all_outdated.map { |runner| [ runner[:ip], runner[:hostname], runner[:uid] ].join(' ') }.join("\n").strip
|
56
|
-
end
|
57
|
-
|
58
|
-
get '/runners/available_instances' do
|
59
|
-
Runner.available_instances.to_s
|
60
|
-
end
|
61
|
-
|
62
|
-
get '/runners/total_instances' do
|
63
|
-
Runner.total_instances.to_s
|
64
|
-
end
|
65
|
-
|
66
|
-
get '/runners/available' do
|
67
|
-
Runner.find_all_available.map { |runner| [ runner[:ip], runner[:hostname], runner[:uid], runner[:username], runner[:idle_instances] ].join(' ') }.join("\n").strip
|
68
|
-
end
|
69
|
-
|
70
|
-
get '/version' do
|
71
|
-
Testbot.version
|
72
|
-
end
|