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/server/build.rb
CHANGED
@@ -1,29 +1,33 @@
|
|
1
|
-
|
1
|
+
module Testbot::Server
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def create_jobs!(available_runner_usage)
|
11
|
-
groups = Group.build(self[:files].split, self[:sizes].split.map { |size| size.to_i },
|
12
|
-
Runner.total_instances.to_f * (available_runner_usage.to_i / 100.0), self[:type])
|
13
|
-
groups.each do |group|
|
14
|
-
Job.create(:files => group.join(' '),
|
15
|
-
:root => self[:root],
|
16
|
-
:project => self[:project],
|
17
|
-
:type => self[:type],
|
18
|
-
:requester_mac => self[:requester_mac],
|
19
|
-
:build_id => self[:id],
|
20
|
-
:jruby => self[:jruby])
|
3
|
+
class Build < Sequel::Model
|
4
|
+
|
5
|
+
def self.create_and_build_jobs(hash)
|
6
|
+
hash["jruby"] = (hash["jruby"] == "true") ? 1 : 0
|
7
|
+
build = create(hash.reject { |k, v| k == 'available_runner_usage' })
|
8
|
+
build.create_jobs!(hash['available_runner_usage'])
|
9
|
+
build
|
21
10
|
end
|
11
|
+
|
12
|
+
def create_jobs!(available_runner_usage)
|
13
|
+
groups = Group.build(self[:files].split, self[:sizes].split.map { |size| size.to_i },
|
14
|
+
Runner.total_instances.to_f * (available_runner_usage.to_i / 100.0), self[:type])
|
15
|
+
groups.each do |group|
|
16
|
+
Job.create(:files => group.join(' '),
|
17
|
+
:root => self[:root],
|
18
|
+
:project => self[:project],
|
19
|
+
:type => self[:type],
|
20
|
+
:requester_mac => self[:requester_mac],
|
21
|
+
:build_id => self[:id],
|
22
|
+
:jruby => self[:jruby])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def destroy
|
27
|
+
Job.filter([ 'build_id = ?', self[:id] ]).each { |job| job.destroy }
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
22
31
|
end
|
23
|
-
|
24
|
-
def destroy
|
25
|
-
Job.filter([ 'build_id = ?', self[:id] ]).each { |job| job.destroy }
|
26
|
-
super
|
27
|
-
end
|
28
|
-
|
32
|
+
|
29
33
|
end
|
data/lib/server/db.rb
CHANGED
@@ -1,46 +1,49 @@
|
|
1
1
|
require 'sequel'
|
2
2
|
|
3
|
-
|
3
|
+
module Testbot::Server
|
4
4
|
|
5
|
-
DB
|
6
|
-
primary_key :id
|
7
|
-
String :files
|
8
|
-
String :sizes
|
9
|
-
String :results, :default => ''
|
10
|
-
String :root
|
11
|
-
String :project
|
12
|
-
String :type
|
13
|
-
String :requester_mac
|
14
|
-
Integer :jruby
|
15
|
-
Boolean :success, :default => true
|
16
|
-
Boolean :done, :default => false
|
17
|
-
end
|
5
|
+
DB = Sequel.sqlite
|
18
6
|
|
7
|
+
DB.create_table :builds do
|
8
|
+
primary_key :id
|
9
|
+
String :files
|
10
|
+
String :sizes
|
11
|
+
String :results, :default => ''
|
12
|
+
String :root
|
13
|
+
String :project
|
14
|
+
String :type
|
15
|
+
String :requester_mac
|
16
|
+
Integer :jruby
|
17
|
+
Boolean :success, :default => true
|
18
|
+
Boolean :done, :default => false
|
19
|
+
end
|
19
20
|
|
20
|
-
DB.create_table :jobs do
|
21
|
-
primary_key :id
|
22
|
-
String :files
|
23
|
-
String :result
|
24
|
-
String :root
|
25
|
-
String :project
|
26
|
-
String :type
|
27
|
-
String :requester_mac
|
28
|
-
Integer :jruby
|
29
|
-
Integer :build_id
|
30
|
-
Integer :taken_by_id
|
31
|
-
Boolean :success
|
32
|
-
Datetime :taken_at, :default => nil
|
33
|
-
end
|
34
21
|
|
35
|
-
DB.create_table :
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
22
|
+
DB.create_table :jobs do
|
23
|
+
primary_key :id
|
24
|
+
String :files
|
25
|
+
String :result
|
26
|
+
String :root
|
27
|
+
String :project
|
28
|
+
String :type
|
29
|
+
String :requester_mac
|
30
|
+
Integer :jruby
|
31
|
+
Integer :build_id
|
32
|
+
Integer :taken_by_id
|
33
|
+
Boolean :success
|
34
|
+
Datetime :taken_at, :default => nil
|
35
|
+
end
|
46
36
|
|
37
|
+
DB.create_table :runners do
|
38
|
+
primary_key :id
|
39
|
+
String :ip
|
40
|
+
String :hostname
|
41
|
+
String :uid
|
42
|
+
String :username
|
43
|
+
String :version
|
44
|
+
Integer :idle_instances
|
45
|
+
Integer :max_instances
|
46
|
+
Datetime :last_seen_at
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/lib/server/group.rb
CHANGED
@@ -1,44 +1,48 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
current_group
|
17
|
-
|
18
|
-
|
3
|
+
module Testbot::Server
|
4
|
+
|
5
|
+
class Group
|
6
|
+
|
7
|
+
DEFAULT = nil
|
8
|
+
|
9
|
+
def self.build(files, sizes, instance_count, type)
|
10
|
+
tests_with_sizes = slow_tests_first(map_files_and_sizes(files, sizes))
|
11
|
+
|
12
|
+
groups = []
|
13
|
+
current_group, current_size = 0, 0
|
14
|
+
tests_with_sizes.each do |test, size|
|
15
|
+
# inserts into next group if current is full and we are not in the last group
|
16
|
+
if (0.5*size + current_size) > group_size(tests_with_sizes, instance_count) and instance_count > current_group + 1
|
17
|
+
current_size = size
|
18
|
+
current_group += 1
|
19
|
+
else
|
20
|
+
current_size += size
|
21
|
+
end
|
22
|
+
groups[current_group] ||= []
|
23
|
+
groups[current_group] << test
|
19
24
|
end
|
20
|
-
|
21
|
-
groups
|
25
|
+
|
26
|
+
groups.compact
|
22
27
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
tests.sort_by { |test, time| time.to_i }.reverse
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def self.group_size(tests_with_sizes, group_count)
|
32
|
+
total = tests_with_sizes.inject(0) { |sum, test| sum += test[1] }
|
33
|
+
total / group_count.to_f
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.map_files_and_sizes(files, sizes)
|
37
|
+
list = []
|
38
|
+
files.each_with_index { |file, i| list << [ file, sizes[i] ] }
|
39
|
+
list
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.slow_tests_first(tests)
|
43
|
+
tests.sort_by { |test, time| time.to_i }.reverse
|
44
|
+
end
|
45
|
+
|
42
46
|
end
|
43
47
|
|
44
|
-
end
|
48
|
+
end
|
data/lib/server/job.rb
CHANGED
@@ -1,44 +1,48 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'db.rb')
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
build.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'db.rb'))
|
2
|
+
|
3
|
+
module Testbot::Server
|
4
|
+
|
5
|
+
class Job < Sequel::Model
|
6
|
+
def update(hash)
|
7
|
+
super(hash)
|
8
|
+
if build = Build.find([ "id = ?", self[:build_id] ])
|
9
|
+
done = Job.filter([ "result IS NULL AND build_id = ?", self[:build_id] ]).count == 0
|
10
|
+
build.update(:results => build[:results].to_s + hash[:result].to_s,
|
11
|
+
:done => done)
|
12
|
+
|
13
|
+
build_broken_by_job = (hash[:success] == "false" && build[:success])
|
14
|
+
build.update(:success => false) if build_broken_by_job
|
15
|
+
end
|
13
16
|
end
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
17
|
+
|
18
|
+
def self.next(params, remove_addr)
|
19
|
+
clean_params = params.reject { |k, v| [ "requester_mac", "no_jruby" ].include?(k) }
|
20
|
+
runner = Runner.record! clean_params.merge({ :ip => remove_addr, :last_seen_at => Time.now })
|
21
|
+
return unless Server.valid_version?(params[:version])
|
22
|
+
[ next_job_query(params["requester_mac"], params["no_jruby"]).first, runner ]
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.next_job_query(requester_mac, no_jruby)
|
28
|
+
release_jobs_taken_by_missing_runners!
|
29
|
+
query = Job.filter("taken_at IS NULL").order("Random()".lit)
|
30
|
+
filters = []
|
31
|
+
filters << "requester_mac = '#{requester_mac}'" if requester_mac
|
32
|
+
filters << "jruby != 1" if no_jruby
|
33
|
+
if filters.empty?
|
34
|
+
query
|
35
|
+
else
|
36
|
+
query.filter(filters.join(' AND '))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.release_jobs_taken_by_missing_runners!
|
41
|
+
missing_runners = Runner.filter([ "last_seen_at < ?", (Time.now - Runner.timeout) ])
|
42
|
+
missing_runners.each { |r|
|
43
|
+
Job.filter(:taken_by_id => r[:id]).update(:taken_at => nil)
|
44
|
+
}
|
35
45
|
end
|
36
46
|
end
|
37
|
-
|
38
|
-
def self.release_jobs_taken_by_missing_runners!
|
39
|
-
missing_runners = Runner.filter([ "last_seen_at < ?", (Time.now - Runner.timeout) ])
|
40
|
-
missing_runners.each { |r|
|
41
|
-
Job.filter(:taken_by_id => r[:id]).update(:taken_at => nil)
|
42
|
-
}
|
43
|
-
end
|
47
|
+
|
44
48
|
end
|
data/lib/server/runner.rb
CHANGED
@@ -1,38 +1,42 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'db.rb')
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'db.rb'))
|
2
2
|
|
3
|
-
|
3
|
+
module Testbot::Server
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def self.create_or_update_by_mac!(hash)
|
10
|
-
if (runner = find(:uid => hash[:uid]))
|
11
|
-
runner.update hash
|
12
|
-
else
|
13
|
-
Runner.create hash
|
5
|
+
class Runner < Sequel::Model
|
6
|
+
|
7
|
+
def self.record!(hash)
|
8
|
+
create_or_update_by_mac!(hash)
|
14
9
|
end
|
10
|
+
|
11
|
+
def self.create_or_update_by_mac!(hash)
|
12
|
+
if (runner = find(:uid => hash[:uid]))
|
13
|
+
runner.update hash
|
14
|
+
else
|
15
|
+
Runner.create hash
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.timeout
|
20
|
+
10
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.find_all_outdated
|
24
|
+
DB[:runners].filter("version != ? OR version IS NULL", Testbot.version)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.find_all_available
|
28
|
+
DB[:runners].filter("version = ? AND last_seen_at > ?", Testbot.version, Time.now - Runner.timeout)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.available_instances
|
32
|
+
find_all_available.inject(0) { |sum, r| r[:idle_instances] + sum }
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.total_instances
|
36
|
+
return 1 if ENV['INTEGRATION_TEST']
|
37
|
+
find_all_available.inject(0) { |sum, r| r[:max_instances] + sum }
|
38
|
+
end
|
39
|
+
|
15
40
|
end
|
16
|
-
|
17
|
-
def self.timeout
|
18
|
-
10
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.find_all_outdated
|
22
|
-
DB[:runners].filter("version != ? OR version IS NULL", Testbot.version)
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.find_all_available
|
26
|
-
DB[:runners].filter("version = ? AND last_seen_at > ?", Testbot.version, Time.now - Runner.timeout)
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.available_instances
|
30
|
-
find_all_available.inject(0) { |sum, r| r[:idle_instances] + sum }
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.total_instances
|
34
|
-
return 1 if ENV['INTEGRATION_TEST']
|
35
|
-
find_all_available.inject(0) { |sum, r| r[:max_instances] + sum }
|
36
|
-
end
|
37
|
-
|
41
|
+
|
38
42
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'yaml'
|
4
|
+
require 'json'
|
5
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '/../shared/testbot'))
|
6
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'job.rb'))
|
7
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'group.rb')) #unless defined?(Group)
|
8
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'runner.rb'))
|
9
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'build.rb'))
|
10
|
+
|
11
|
+
module Testbot::Server
|
12
|
+
|
13
|
+
if ENV['INTEGRATION_TEST']
|
14
|
+
set :port, 22880
|
15
|
+
else
|
16
|
+
set :port, Testbot::SERVER_PORT
|
17
|
+
end
|
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
|
73
|
+
|
74
|
+
end
|