testbot 0.4.6 → 0.4.7
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/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
|