rrrspec-server 0.2.0
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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/bin/rrrspec-server +4 -0
- data/db/migrate/20131105050718_create_tables.rb +77 -0
- data/db/schema.rb +94 -0
- data/lib/rrrspec/server/arbiter.rb +186 -0
- data/lib/rrrspec/server/cli.rb +107 -0
- data/lib/rrrspec/server/configuration.rb +54 -0
- data/lib/rrrspec/server/dispatcher.rb +53 -0
- data/lib/rrrspec/server/persistent_models.rb +111 -0
- data/lib/rrrspec/server/persister.rb +147 -0
- data/lib/rrrspec/server/version.rb +5 -0
- data/lib/rrrspec/server/worker_runner.rb +245 -0
- data/lib/rrrspec/server.rb +9 -0
- data/rrrspec-server.gemspec +39 -0
- data/spec/fixture.rb +32 -0
- data/spec/rrrspec/server/arbiter_spec.rb +497 -0
- data/spec/rrrspec/server/dispatcher_spec.rb +53 -0
- data/spec/rrrspec/server/persistent_models_spec.rb +54 -0
- data/spec/rrrspec/server/persister_spec.rb +121 -0
- data/spec/spec_helper.rb +75 -0
- data/tasks/db.rake +62 -0
- metadata +256 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
module RRRSpec
|
2
|
+
module Server
|
3
|
+
module Persistence
|
4
|
+
class Taskset < ActiveRecord::Base
|
5
|
+
include ActiveModel::Serializers::JSON
|
6
|
+
|
7
|
+
has_many :worker_logs
|
8
|
+
has_many :slaves
|
9
|
+
has_many :tasks
|
10
|
+
|
11
|
+
scope :full, includes(
|
12
|
+
:tasks => [{:trials => [:task, :slave]}, :taskset],
|
13
|
+
:slaves => [:trials],
|
14
|
+
:worker_logs => [:taskset]
|
15
|
+
)
|
16
|
+
|
17
|
+
def as_nodetail_json
|
18
|
+
as_json(except: :id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def as_short_json
|
22
|
+
h = as_json(except: :id)
|
23
|
+
h['slaves'] = slaves.map { |slave| slave.as_json(only: :key) }
|
24
|
+
h['tasks'] = tasks.map { |task| task.as_json(only: :key) }
|
25
|
+
h['worker_logs'] = worker_logs.map { |worker_log| worker_log.as_json(only: :key) }
|
26
|
+
h
|
27
|
+
end
|
28
|
+
|
29
|
+
def as_full_json
|
30
|
+
h = as_json(except: :id)
|
31
|
+
h['slaves'] = slaves.map(&:as_full_json)
|
32
|
+
h['tasks'] = tasks.map(&:as_full_json)
|
33
|
+
h['worker_logs'] = worker_logs.map(&:as_full_json)
|
34
|
+
h
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Task < ActiveRecord::Base
|
39
|
+
include ActiveModel::Serializers::JSON
|
40
|
+
|
41
|
+
belongs_to :taskset
|
42
|
+
has_many :trials
|
43
|
+
|
44
|
+
def as_short_json
|
45
|
+
h = as_json(except: [:id, :taskset_id, :trials],
|
46
|
+
include: { 'taskset' => { only: :key } })
|
47
|
+
h['trials'] = trials.map { |trial| trial.as_json(only: :key) }
|
48
|
+
h
|
49
|
+
end
|
50
|
+
|
51
|
+
def as_full_json
|
52
|
+
h = as_json(except: [:id, :taskset_id, :trials],
|
53
|
+
include: { 'taskset' => { only: :key } })
|
54
|
+
h['trials'] = trials.map(&:as_full_json)
|
55
|
+
h
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Trial < ActiveRecord::Base
|
60
|
+
include ActiveModel::Serializers::JSON
|
61
|
+
|
62
|
+
belongs_to :task
|
63
|
+
belongs_to :slave
|
64
|
+
|
65
|
+
def as_full_json
|
66
|
+
as_json(except: [:id, :task_id, :slave_id],
|
67
|
+
include: { 'slave' => { only: :key }, 'task' => { only: :key } })
|
68
|
+
end
|
69
|
+
|
70
|
+
def as_short_json
|
71
|
+
as_full_json
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class WorkerLog < ActiveRecord::Base
|
76
|
+
include ActiveModel::Serializers::JSON
|
77
|
+
|
78
|
+
belongs_to :taskset
|
79
|
+
|
80
|
+
def as_full_json
|
81
|
+
as_json(except: [:id, :taskset_id, :worker_key],
|
82
|
+
include: { 'taskset' => { only: :key } },
|
83
|
+
methods: :worker)
|
84
|
+
end
|
85
|
+
|
86
|
+
def as_short_json
|
87
|
+
as_full_json
|
88
|
+
end
|
89
|
+
|
90
|
+
def worker
|
91
|
+
{ 'key' => worker_key }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Slave < ActiveRecord::Base
|
96
|
+
include ActiveModel::Serializers::JSON
|
97
|
+
|
98
|
+
has_many :trials
|
99
|
+
|
100
|
+
def as_full_json
|
101
|
+
as_json(except: [:id, :taskset_id],
|
102
|
+
include: { 'trials' => { only: :key } })
|
103
|
+
end
|
104
|
+
|
105
|
+
def as_short_json
|
106
|
+
as_full_json
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'activerecord-import'
|
3
|
+
require 'active_support/inflector'
|
4
|
+
ActiveSupport::Inflector::Inflections.instance.singular('Slaves', 'Slave')
|
5
|
+
ActiveSupport::Inflector::Inflections.instance.singular('slaves', 'slave')
|
6
|
+
ActiveRecord::Base.include_root_in_json = false
|
7
|
+
ActiveRecord::Base.default_timezone = :utc
|
8
|
+
|
9
|
+
module RRRSpec
|
10
|
+
module Server
|
11
|
+
module Persister
|
12
|
+
SLAVE_EXIT_WAIT_TIME = 15
|
13
|
+
PERSISTED_RESIDUE_SEC = 60
|
14
|
+
|
15
|
+
module_function
|
16
|
+
|
17
|
+
def work_loop
|
18
|
+
loop { work }
|
19
|
+
end
|
20
|
+
|
21
|
+
def work
|
22
|
+
taskset = PersisterQueue.dequeue
|
23
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
24
|
+
return if Persistence::Taskset.where(key: taskset.key).exists?
|
25
|
+
end
|
26
|
+
|
27
|
+
sleep SLAVE_EXIT_WAIT_TIME
|
28
|
+
|
29
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
30
|
+
persist(taskset)
|
31
|
+
if RRRSpec.configuration.json_cache_path
|
32
|
+
create_api_cache(taskset, RRRSpec.configuration.json_cache_path)
|
33
|
+
end
|
34
|
+
taskset.expire(PERSISTED_RESIDUE_SEC)
|
35
|
+
update_estimate_sec(taskset)
|
36
|
+
end
|
37
|
+
rescue
|
38
|
+
RRRSpec.logger.error($!)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
module_function
|
43
|
+
|
44
|
+
def persist(taskset)
|
45
|
+
taskset_finished_at = taskset.finished_at
|
46
|
+
return if taskset_finished_at.blank?
|
47
|
+
|
48
|
+
p_taskset = ActiveRecord::Base.transaction do
|
49
|
+
h = taskset.to_h
|
50
|
+
h.delete('tasks')
|
51
|
+
h.delete('slaves')
|
52
|
+
h.delete('worker_logs')
|
53
|
+
Persistence::Taskset.create(h)
|
54
|
+
end
|
55
|
+
|
56
|
+
ActiveRecord::Base.transaction do
|
57
|
+
p_slaves = taskset.slaves.map do |slave|
|
58
|
+
h = slave.to_h
|
59
|
+
h.delete('trials')
|
60
|
+
p_slave = Persistence::Slave.new(h)
|
61
|
+
p_slave.taskset_id = p_taskset.id
|
62
|
+
p_slave
|
63
|
+
end
|
64
|
+
Persistence::Slave.import(p_slaves)
|
65
|
+
end
|
66
|
+
|
67
|
+
ActiveRecord::Base.transaction do
|
68
|
+
Persistence::Task.import(taskset.tasks.map do |task|
|
69
|
+
h = task.to_h
|
70
|
+
h.delete('taskset')
|
71
|
+
h.delete('trials')
|
72
|
+
p_task = Persistence::Task.new(h)
|
73
|
+
p_task.taskset_id = p_taskset
|
74
|
+
p_task
|
75
|
+
end)
|
76
|
+
end
|
77
|
+
|
78
|
+
p_slaves = {}
|
79
|
+
p_taskset.slaves.each do |p_slave|
|
80
|
+
p_slaves[p_slave.key] = p_slave
|
81
|
+
end
|
82
|
+
|
83
|
+
ActiveRecord::Base.transaction do
|
84
|
+
p_trials = []
|
85
|
+
p_taskset.tasks.each do |p_task|
|
86
|
+
Task.new(p_task.key).trials.each do |trial|
|
87
|
+
h = trial.to_h
|
88
|
+
next if h['finished_at'].blank? || h['finished_at'] > taskset_finished_at
|
89
|
+
slave_key = h.delete('slave')['key']
|
90
|
+
h.delete('task')
|
91
|
+
p_trial = Persistence::Trial.new(h)
|
92
|
+
p_trial.task_id = p_task
|
93
|
+
p_trial.slave_id = p_slaves[slave_key]
|
94
|
+
p_trials << p_trial
|
95
|
+
end
|
96
|
+
end
|
97
|
+
Persistence::Trial.import(p_trials)
|
98
|
+
end
|
99
|
+
|
100
|
+
ActiveRecord::Base.transaction do
|
101
|
+
Persistence::WorkerLog.import(taskset.worker_logs.map do |worker_log|
|
102
|
+
h = worker_log.to_h
|
103
|
+
h['worker_key'] = h['worker']['key']
|
104
|
+
h.delete('worker')
|
105
|
+
h.delete('taskset')
|
106
|
+
p_worker_log = Persistence::WorkerLog.new(h)
|
107
|
+
p_worker_log.taskset_id = p_taskset
|
108
|
+
p_worker_log
|
109
|
+
end)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def create_api_cache(taskset, path)
|
114
|
+
p_obj = Persistence::Taskset.where(key: taskset.key).full.first
|
115
|
+
json = JSON.generate(p_obj.as_full_json.update('is_full' => true))
|
116
|
+
|
117
|
+
FileUtils.mkdir_p(File.join(path, 'v1', 'tasksets'))
|
118
|
+
json_path = File.join(path, 'v1', 'tasksets', taskset.key.gsub(':', '-'))
|
119
|
+
IO.write(json_path, json)
|
120
|
+
Zlib::GzipWriter.open(json_path + ".gz") { |gz| gz.write(json) }
|
121
|
+
end
|
122
|
+
|
123
|
+
ESTIMATION_FIELDS = [
|
124
|
+
"`spec_file`",
|
125
|
+
"avg(UNIX_TIMESTAMP(`trials`.`finished_at`)-UNIX_TIMESTAMP(`trials`.`started_at`)) as `avg`",
|
126
|
+
# "avg(`trials`.`finished_at`-`trials`.`started_at`) as `avg`",
|
127
|
+
]
|
128
|
+
|
129
|
+
def update_estimate_sec(taskset)
|
130
|
+
p_obj = Persistence::Taskset.where(key: taskset.key).first
|
131
|
+
taskset_class = p_obj.taskset_class
|
132
|
+
query = Persistence::Task.joins(:trials).joins(:taskset).
|
133
|
+
select(ESTIMATION_FIELDS).
|
134
|
+
where('tasksets.taskset_class' => taskset_class).
|
135
|
+
where('trials.status' => ["passed", "pending"]).
|
136
|
+
group('spec_file')
|
137
|
+
estimation = {}
|
138
|
+
query.each do |row|
|
139
|
+
estimation[row.spec_file] = row.avg.to_i
|
140
|
+
end
|
141
|
+
unless estimation.empty?
|
142
|
+
TasksetEstimation.update_estimate_secs(taskset_class, estimation)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
module RRRSpec
|
2
|
+
module Server
|
3
|
+
class WorkerRunner
|
4
|
+
CANCEL_POLLING = 10
|
5
|
+
TIMEOUT_EXITCODE = 42
|
6
|
+
|
7
|
+
attr_reader :internal_status, :current_taskset
|
8
|
+
|
9
|
+
def initialize(worker)
|
10
|
+
@worker = worker
|
11
|
+
end
|
12
|
+
|
13
|
+
def work_loop
|
14
|
+
loop do
|
15
|
+
DispatcherQueue.notify
|
16
|
+
work
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def rsync(logger, taskset)
|
23
|
+
logger.write("Start RSync")
|
24
|
+
|
25
|
+
working_path = File.join(RRRSpec.configuration.working_dir, taskset.rsync_name)
|
26
|
+
FileUtils.mkdir_p(working_path) unless Dir.exists?(working_path)
|
27
|
+
remote_path = "#{RSyncInfo.rsync_server}:#{File.join(RSyncInfo.rsync_dir, taskset.rsync_name)}"
|
28
|
+
command = "rsync #{RSyncInfo.rsync_options} #{remote_path}/ #{working_path}"
|
29
|
+
|
30
|
+
pid, out_rd, err_rd = execute_with_logs(working_path, command, {})
|
31
|
+
log_to_logger(logger, out_rd, err_rd)
|
32
|
+
pid, status = Process.waitpid2(pid)
|
33
|
+
if status.success?
|
34
|
+
logger.write("RSync finished")
|
35
|
+
return true
|
36
|
+
else
|
37
|
+
logger.write("RSync failed")
|
38
|
+
ArbiterQueue.fail(taskset)
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def setup(logger, taskset)
|
44
|
+
logger.write("Start setup")
|
45
|
+
env = {
|
46
|
+
'NUM_SLAVES' => RRRSpec.configuration.slave_processes.to_s
|
47
|
+
}
|
48
|
+
|
49
|
+
working_path = File.join(RRRSpec.configuration.working_dir, taskset.rsync_name)
|
50
|
+
pid, out_rd, err_rd = execute_with_logs(working_path, '/bin/bash -ex', env,
|
51
|
+
taskset.setup_command)
|
52
|
+
log_to_logger(logger, out_rd, err_rd)
|
53
|
+
pid, status = Process.waitpid2(pid)
|
54
|
+
if status.success?
|
55
|
+
logger.write("Setup finished")
|
56
|
+
return true
|
57
|
+
else
|
58
|
+
logger.write("Setup failed")
|
59
|
+
ArbiterQueue.fail(taskset)
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def rspec(taskset)
|
65
|
+
working_path = File.join(RRRSpec.configuration.working_dir, taskset.rsync_name)
|
66
|
+
|
67
|
+
num_slaves = RRRSpec.configuration.slave_processes
|
68
|
+
env = {}
|
69
|
+
env["NUM_SLAVES"] = num_slaves.to_s
|
70
|
+
env["RRRSPEC_CONFIG_FILES"] = RRRSpec.configuration.loaded.join(':')
|
71
|
+
env["RRRSPEC_WORKING_DIR"] = RRRSpec.configuration.working_dir
|
72
|
+
env["RRRSPEC_TASKSET_KEY"] = taskset.key
|
73
|
+
|
74
|
+
pid_to_slave_number = {}
|
75
|
+
slave_command = taskset.slave_command
|
76
|
+
spawner = proc do |slave_number|
|
77
|
+
pid, out_rd, err_rd = execute_with_logs(
|
78
|
+
working_path, '/bin/bash -ex',
|
79
|
+
env.merge({"SLAVE_NUMBER" => slave_number.to_s}),
|
80
|
+
slave_command
|
81
|
+
)
|
82
|
+
slave = Slave.build_from_pid(pid)
|
83
|
+
taskset.add_slave(slave)
|
84
|
+
Thread.fork { log_to_logger(TimedLogger.new(slave), out_rd, err_rd) }
|
85
|
+
|
86
|
+
pid_to_slave_number[pid] = slave_number
|
87
|
+
end
|
88
|
+
|
89
|
+
num_slaves.times { |i| spawner.call(i) }
|
90
|
+
|
91
|
+
cancel_watcher_pid = Process.fork do
|
92
|
+
$0 = 'rrrspec cancel watcher'
|
93
|
+
loop do
|
94
|
+
break unless taskset.status == 'running'
|
95
|
+
sleep CANCEL_POLLING
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
trials = 1
|
100
|
+
max_trials = taskset.max_trials
|
101
|
+
loop do
|
102
|
+
break if pid_to_slave_number.empty?
|
103
|
+
begin
|
104
|
+
pid, status = Process.wait2
|
105
|
+
break if pid == cancel_watcher_pid
|
106
|
+
break unless taskset.status == 'running'
|
107
|
+
|
108
|
+
slave = Slave.build_from_pid(pid)
|
109
|
+
if status.success?
|
110
|
+
slave.update_status('normal_exit')
|
111
|
+
pid_to_slave_number.delete(pid)
|
112
|
+
else
|
113
|
+
exit_code = (status.to_i >> 8)
|
114
|
+
if exit_code == TIMEOUT_EXITCODE
|
115
|
+
slave_log = slave.log
|
116
|
+
slave.trials.each do |trial|
|
117
|
+
if trial.status == nil
|
118
|
+
trial.finish('timeout', slave_log, '', nil, nil, nil)
|
119
|
+
ArbiterQueue.trial(trial)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
slave.update_status('timeout_exit')
|
123
|
+
else
|
124
|
+
slave.trials.each do |trial|
|
125
|
+
if trial.status == nil
|
126
|
+
trial.finish('error', '', '', nil, nil, nil)
|
127
|
+
ArbiterQueue.trial(trial)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
slave.update_status('failure_exit')
|
131
|
+
trials += 1
|
132
|
+
if trials > max_trials
|
133
|
+
ArbiterQueue.fail(taskset)
|
134
|
+
break
|
135
|
+
end
|
136
|
+
end
|
137
|
+
slave_number = pid_to_slave_number[pid]
|
138
|
+
pid_to_slave_number.delete(pid)
|
139
|
+
spawner.call(slave_number)
|
140
|
+
end
|
141
|
+
rescue Errno::ECHILD
|
142
|
+
break
|
143
|
+
end
|
144
|
+
end
|
145
|
+
return cancel_watcher_pid, pid_to_slave_number
|
146
|
+
end
|
147
|
+
|
148
|
+
def cleaning_process(logger, taskset, cancel_watcher_pid, pid_to_slave_number)
|
149
|
+
logger.write("Send TERM signal to the children")
|
150
|
+
(pid_to_slave_number.keys + [cancel_watcher_pid]).each do |pid|
|
151
|
+
begin
|
152
|
+
Process.kill("-TERM", pid)
|
153
|
+
rescue Errno::ESRCH
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
logger.write("Wait for the children")
|
158
|
+
begin
|
159
|
+
loop do
|
160
|
+
pid, status = Process.wait2
|
161
|
+
if pid != cancel_watcher_pid
|
162
|
+
slave = Slave.build_from_pid(pid)
|
163
|
+
slave.update_status('normal_exit')
|
164
|
+
end
|
165
|
+
end
|
166
|
+
rescue Errno::ECHILD
|
167
|
+
end
|
168
|
+
logger.write("Finished the task")
|
169
|
+
|
170
|
+
# Some slaves are failed to exit with SIGTERM. Kill -9 them by name.
|
171
|
+
`ps aux | grep "rrrspec slave" | grep -v grep | awk '{print $2}'`.split("\n").map(&:to_i).each do |pid|
|
172
|
+
begin
|
173
|
+
Process.kill("KILL", pid)
|
174
|
+
rescue Errno::ESRCH
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def work
|
180
|
+
@worker.update_current_taskset(nil)
|
181
|
+
taskset = @worker.dequeue_taskset
|
182
|
+
worker_log = WorkerLog.create(@worker, taskset)
|
183
|
+
logger = TimedLogger.new(worker_log)
|
184
|
+
|
185
|
+
check = proc do
|
186
|
+
unless taskset.status == 'running'
|
187
|
+
logger.write("The taskset(#{taskset.key}) is not running but #{taskset.status}")
|
188
|
+
return
|
189
|
+
end
|
190
|
+
end
|
191
|
+
check.call
|
192
|
+
@worker.update_current_taskset(taskset)
|
193
|
+
|
194
|
+
rsync(logger, taskset)
|
195
|
+
worker_log.set_rsync_finished_time
|
196
|
+
check.call
|
197
|
+
|
198
|
+
setup(logger, taskset)
|
199
|
+
worker_log.set_setup_finished_time
|
200
|
+
check.call
|
201
|
+
|
202
|
+
cancel_watcher_pid, pid_to_slave_number = rspec(taskset)
|
203
|
+
cleaning_process(logger, taskset, cancel_watcher_pid, pid_to_slave_number)
|
204
|
+
ensure
|
205
|
+
worker_log.set_finished_time if worker_log
|
206
|
+
@worker.update_current_taskset(nil)
|
207
|
+
end
|
208
|
+
|
209
|
+
def execute_with_logs(chdir, command, env, input=nil)
|
210
|
+
Bundler.with_clean_env do
|
211
|
+
in_rd, in_wt = IO.pipe
|
212
|
+
out_rd, out_wt = IO.pipe
|
213
|
+
err_rd, err_wt = IO.pipe
|
214
|
+
pid = spawn(env, command, { chdir: chdir, pgroup: true,
|
215
|
+
in: in_rd, out: out_wt, err: err_wt })
|
216
|
+
out_wt.close_write
|
217
|
+
err_wt.close_write
|
218
|
+
in_wt.write(input) if input
|
219
|
+
in_wt.close_write
|
220
|
+
|
221
|
+
return pid, out_rd, err_rd
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def log_to_logger(logger, out_rd, err_rd)
|
226
|
+
rds = [out_rd, err_rd]
|
227
|
+
while !rds.empty?
|
228
|
+
IO.select(rds)[0].each do |r|
|
229
|
+
line = r.gets
|
230
|
+
if line
|
231
|
+
line = line.strip
|
232
|
+
if r == out_rd
|
233
|
+
logger.write("OUT " + line)
|
234
|
+
else
|
235
|
+
logger.write("ERR " + line)
|
236
|
+
end
|
237
|
+
else
|
238
|
+
rds.delete(r)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
require 'rrrspec'
|
4
|
+
require 'rrrspec/server/arbiter'
|
5
|
+
require 'rrrspec/server/configuration'
|
6
|
+
require 'rrrspec/server/dispatcher'
|
7
|
+
require 'rrrspec/server/persistent_models'
|
8
|
+
require 'rrrspec/server/persister'
|
9
|
+
require 'rrrspec/server/worker_runner'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rrrspec/server/version'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "rrrspec-server"
|
9
|
+
spec.version = RRRSpec::Server::VERSION
|
10
|
+
spec.authors = ["Masaya Suzuki"]
|
11
|
+
spec.email = ["draftcode@gmail.com"]
|
12
|
+
spec.description = "Execute RSpec in a distributed manner"
|
13
|
+
spec.summary = "Execute RSpec in a distributed manner"
|
14
|
+
spec.homepage = ""
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
gemspec_dir = File.expand_path('..', __FILE__)
|
18
|
+
spec.files = `git ls-files`.split($/).
|
19
|
+
map { |f| File.absolute_path(f) }.
|
20
|
+
select { |f| f.start_with?(gemspec_dir) }.
|
21
|
+
map { |f| Pathname(f).relative_path_from(Pathname(gemspec_dir)).to_s }
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
24
|
+
spec.require_paths = ["lib"]
|
25
|
+
|
26
|
+
spec.add_development_dependency "database_cleaner", "~> 1.2.0"
|
27
|
+
spec.add_development_dependency "mysql2"
|
28
|
+
spec.add_development_dependency "rake"
|
29
|
+
spec.add_development_dependency "rspec"
|
30
|
+
spec.add_development_dependency "sqlite3"
|
31
|
+
spec.add_development_dependency "timecop"
|
32
|
+
spec.add_dependency "activerecord", "~> 3.0"
|
33
|
+
spec.add_dependency "activerecord-import", "~> 0.3.1"
|
34
|
+
spec.add_dependency "activesupport"
|
35
|
+
spec.add_dependency "bundler"
|
36
|
+
spec.add_dependency "redis"
|
37
|
+
spec.add_dependency "rrrspec-client"
|
38
|
+
spec.add_dependency "thor"
|
39
|
+
end
|
data/spec/fixture.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module RRRSpec
|
2
|
+
def self.finished_fullset
|
3
|
+
worker = Worker.create('default')
|
4
|
+
taskset = Taskset.create(
|
5
|
+
'testuser', 'echo 1', 'echo 2', 'default', 'default', 3, 3, 5, 5
|
6
|
+
)
|
7
|
+
task = Task.create(taskset, 10, 'spec/test_spec.rb')
|
8
|
+
taskset.add_task(task)
|
9
|
+
taskset.enqueue_task(task)
|
10
|
+
ActiveTaskset.add(taskset)
|
11
|
+
worker_log = WorkerLog.create(worker, taskset)
|
12
|
+
worker_log.set_rsync_finished_time
|
13
|
+
worker_log.append_log('worker_log log body')
|
14
|
+
worker_log.set_setup_finished_time
|
15
|
+
slave = Slave.create
|
16
|
+
taskset.add_slave(slave)
|
17
|
+
slave.append_log('slave log body')
|
18
|
+
trial = Trial.create(task, slave)
|
19
|
+
trial.start
|
20
|
+
trial.finish('pending', 'stdout body', 'stderr body', 10, 2, 0)
|
21
|
+
task.update_status('pending')
|
22
|
+
taskset.incr_succeeded_count
|
23
|
+
taskset.finish_task(task)
|
24
|
+
taskset.update_status('succeeded')
|
25
|
+
taskset.set_finished_time
|
26
|
+
ActiveTaskset.remove(taskset)
|
27
|
+
slave.update_status('normal_exit')
|
28
|
+
worker_log.set_finished_time
|
29
|
+
|
30
|
+
return worker, taskset, task, worker_log, slave, trial
|
31
|
+
end
|
32
|
+
end
|