rrrspec-client 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 187ea5b96b3437a7c839c23b3e5f1aa3df3b5716
4
+ data.tar.gz: d83a9df93ffa5ae672b5909e887a52173f6941ff
5
+ SHA512:
6
+ metadata.gz: c6583325da9e6865baca29415e524f662ecfcdf1a7d2be524b3769748855a20f6629cfe9dae6102bf035534fd4a314a4271e5ace84e421780e56ed5121d435af
7
+ data.tar.gz: b1f67aba5e30a24d1ee5dad72ae4dc15587d5b25470546c29ebc290be512c3b63743ae43a1b3e630619abb1b9d1308563ad2d0347d8ed7c9cc004431e611a93e
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p247
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/rrrspec ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ case ARGV[0]
4
+ when "server", "worker"
5
+ require 'rrrspec/server/cli'
6
+ RRRSpec::Server::CLI.start
7
+ when "scaler", "scalerspy"
8
+ require 'rrrspec/scaler/cli'
9
+ RRRSpec::Scaler::CLI.start
10
+ else
11
+ require 'rrrspec/client/cli'
12
+ RRRSpec::Client::CLI.start
13
+ end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rrrspec/client/cli'
4
+ RRRSpec::Client::CLI.start
@@ -0,0 +1,131 @@
1
+ require 'rrrspec/client'
2
+ require 'launchy'
3
+ require 'thor'
4
+
5
+ module RRRSpec
6
+ module Client
7
+ class CLI < Thor
8
+ WAIT_POLLING_SEC = 10
9
+
10
+ package_name 'RRRSpec'
11
+ default_command 'help'
12
+ class_option :config, aliases: '-c', type: :string, default: ''
13
+
14
+ no_commands do
15
+ def setup(conf)
16
+ RRRSpec.setup(conf, options[:config].split(':'))
17
+ end
18
+
19
+ def log_exception
20
+ yield
21
+ rescue
22
+ RRRSpec.logger.error($!)
23
+ raise
24
+ end
25
+ end
26
+
27
+ option :'key-only', type: :boolean
28
+ option :'rsync-name', type: :string, default: ENV['USER']
29
+ option :'worker-type', type: :string
30
+ desc 'start', 'start RRRSpec'
31
+ def start
32
+ setup(ClientConfiguration.new)
33
+ if options[:'worker-type']
34
+ RRRSpec.configuration.worker_type = options[:'worker-type']
35
+ end
36
+ taskset = Support.start_taskset(RRRSpec.configuration, options[:'rsync-name'])
37
+ puts taskset.key
38
+
39
+ if RRRSpec.configuration.rrrspec_web_base && !options[:'key-only']
40
+ url = "#{RRRSpec.configuration.rrrspec_web_base}/tasksets/#{taskset.key}"
41
+ Launchy.open(url)
42
+ end
43
+ end
44
+
45
+ desc 'cancel', 'cancel the taskset'
46
+ def cancel(taskset_id)
47
+ setup(Configuration.new)
48
+ taskset = Taskset.new(taskset_id)
49
+ exit(1) unless taskset.exist?
50
+ taskset.cancel
51
+ end
52
+
53
+ desc 'cancelall', 'cancel all tasksets whose rsync name is specified name'
54
+ def cancelall(rsync_name)
55
+ setup(Configuration.new)
56
+ ActiveTaskset.all_tasksets_of(rsync_name).each do |taskset|
57
+ taskset.cancel
58
+ end
59
+ end
60
+
61
+ desc 'actives', 'list up the active tasksets'
62
+ def actives
63
+ setup(Configuration.new)
64
+ ActiveTaskset.list.each { |taskset| puts taskset.key }
65
+ end
66
+
67
+ desc 'nodes', 'list up the active nodes'
68
+ def nodes
69
+ setup(Configuration.new)
70
+ puts "Server:"
71
+ puts "\t#{RSyncInfo.exist? ? "Yes" : "No"}"
72
+ puts "Workers:"
73
+ Worker.list.each { |worker| puts "\t#{worker.key}" }
74
+ end
75
+
76
+ option :pollsec, type: :numeric, default: WAIT_POLLING_SEC
77
+ desc 'waitfor', 'wait for the taskset'
78
+ def waitfor(taskset_id)
79
+ setup(Configuration.new)
80
+ taskset = Taskset.new(taskset_id)
81
+ exit(1) unless taskset.exist?
82
+
83
+ rd, wt = IO.pipe
84
+ Signal.trap(:TERM) { wt.write("1") }
85
+ Signal.trap(:INT) { wt.write("1") }
86
+
87
+ loop do
88
+ rs, ws, = IO.select([rd], [], [], options[:pollsec])
89
+ if rs == nil
90
+ break if taskset.persisted?
91
+ elsif rs.size != 0
92
+ rs[0].getc
93
+ taskset.cancel
94
+ end
95
+ end
96
+ end
97
+
98
+ option :'failure-exit-code', type: :numeric, default: 1
99
+ option :verbose, type: :boolean, default: false
100
+ desc 'show', 'show the result of the taskset'
101
+ def show(taskset_id)
102
+ setup(Configuration.new)
103
+ taskset = Taskset.new(taskset_id)
104
+ exit 1 unless taskset.exist?
105
+ Support.show_result(taskset, options[:verbose])
106
+
107
+ if taskset.status != 'succeeded'
108
+ exit options[:'failure-exit-code']
109
+ end
110
+ end
111
+
112
+ desc 'slave', 'run RRRSpec as a slave'
113
+ def slave(working_dir=nil, taskset_key=nil)
114
+ $0 = 'rrrspec slave'
115
+ working_dir ||= ENV['RRRSPEC_WORKING_DIR']
116
+ taskset_key ||= ENV['RRRSPEC_TASKSET_KEY']
117
+ exit 1 unless taskset_key && working_dir
118
+
119
+ setup(Configuration.new)
120
+ log_exception do
121
+ slave = Slave.create
122
+ slave_runner = SlaveRunner.new(slave, working_dir, taskset_key)
123
+ Thread.abort_on_exception = true
124
+ Thread.fork { RRRSpec.pacemaker(slave, 60, 5) }
125
+ Thread.fork { slave_runner.work_loop }
126
+ Kernel.sleep
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,82 @@
1
+ module RRRSpec
2
+ module Client
3
+ class ClientConfiguration < Configuration
4
+ attr_accessor :packaging_dir
5
+ attr_accessor :packaging_rsync_options
6
+ attr_writer :spec_files
7
+ attr_accessor :setup_command, :slave_command
8
+ attr_accessor :taskset_class, :worker_type
9
+ attr_accessor :max_workers, :max_trials
10
+ attr_accessor :rrrspec_web_base
11
+ attr_accessor :unknown_spec_timeout_sec, :least_timeout_sec
12
+
13
+ def spec_files
14
+ case @spec_files
15
+ when Proc then @spec_files.call
16
+ when String then [@spec_files]
17
+ else @spec_files
18
+ end
19
+ end
20
+
21
+ def initialize
22
+ super()
23
+ @type = :client
24
+ @unknown_spec_timeout_sec = 5 * 60
25
+ @least_timeout_sec = 30
26
+ end
27
+
28
+ def check_validity
29
+ validity = super
30
+
31
+ unless Dir.exists?(packaging_dir)
32
+ $stderr.puts("The packaging_dir does not exists: '#{packaging_dir}'")
33
+ validity = false
34
+ end
35
+
36
+ unless spec_files.is_a?(Array)
37
+ $stderr.puts("The spec_files should be an Array: '#{spec_files}'")
38
+ validity = false
39
+ else
40
+ spec_files.each do |filepath|
41
+ unless File.exists?(File.join(packaging_dir, filepath))
42
+ $stderr.puts("One of the spec_files does not exists '#{filepath}'")
43
+ validity = false
44
+ end
45
+ end
46
+ end
47
+
48
+ unless max_workers.is_a?(Integer)
49
+ $stderr.puts("The max_workers should be an Integer: '#{max_workers}'")
50
+ validity = false
51
+ else
52
+ unless max_workers >= 1
53
+ $stderr.puts("The max_workers should not be less than 1: #{max_workers}")
54
+ validity = false
55
+ end
56
+ end
57
+
58
+ unless max_trials.is_a?(Integer)
59
+ $stderr.puts("The max_trials should be an Integer: '#{max_trials}'")
60
+ validity = false
61
+ end
62
+
63
+ unless taskset_class.is_a?(String)
64
+ $stderr.puts("The taskset_class should be a String: '#{taskset_class}'")
65
+ validity = false
66
+ end
67
+
68
+ unless unknown_spec_timeout_sec.is_a?(Integer)
69
+ $stderr.puts("The unknown_spec_timeout_sec should be an Integer: '#{unknown_spec_timeout_sec}'")
70
+ validity = false
71
+ end
72
+
73
+ unless least_timeout_sec.is_a?(Integer)
74
+ $stderr.puts("The least_timeout_sec should be an Integer: '#{least_timeout_sec}'")
75
+ validity = false
76
+ end
77
+
78
+ validity
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,83 @@
1
+ require 'rspec'
2
+ require 'rspec/core/formatters/base_text_formatter'
3
+
4
+ module RRRSpec
5
+ module Client
6
+ class RSpecRunner
7
+ def initialize
8
+ @options = RSpec::Core::ConfigurationOptions.new([])
9
+ @options.parse_options
10
+ @configuration = RSpec.configuration
11
+ @configuration.setup_load_path_and_require([])
12
+ @world = RSpec.world
13
+ @before_suite_run = false
14
+ end
15
+
16
+ def exc_safe_replace_stdouts
17
+ outbuf = ''
18
+ errbuf = ''
19
+ $stdout = StringIO.new(outbuf)
20
+ $stderr = StringIO.new(errbuf)
21
+ begin
22
+ yield
23
+ rescue Exception
24
+ $stdout.puts $!
25
+ $stdout.puts $!.backtrace.join("\n")
26
+ end
27
+ [outbuf, errbuf]
28
+ ensure
29
+ $stdout = STDOUT
30
+ $stderr = STDERR
31
+ end
32
+
33
+ def setup(filepath)
34
+ status = false
35
+ outbuf, errbuf = exc_safe_replace_stdouts do
36
+ begin
37
+ @options.configure(@configuration)
38
+ @configuration.files_to_run = [filepath]
39
+ @configuration.load_spec_files
40
+ @world.announce_filters
41
+ unless @before_suite_run
42
+ @configuration.run_hook(:before, :suite)
43
+ @before_suite_run = true
44
+ end
45
+ status = true
46
+ rescue Exception
47
+ $stdout.puts $!
48
+ $stdout.puts $!.backtrace.join("\n")
49
+ status = false
50
+ end
51
+ end
52
+
53
+ [status, outbuf, errbuf]
54
+ end
55
+
56
+ def run(*formatters)
57
+ status = false
58
+ outbuf, errbuf = exc_safe_replace_stdouts do
59
+ @configuration.formatters << RSpec::Core::Formatters::BaseTextFormatter.new($stdout)
60
+ formatters.each do |formatter|
61
+ @configuration.formatters << formatter
62
+ end
63
+ @configuration.reporter.report(
64
+ @world.example_count,
65
+ @configuration.randomize? ? @configuration.seed : nil
66
+ ) do |reporter|
67
+ @world.example_groups.ordered.each do |example_group|
68
+ example_group.run(reporter)
69
+ end
70
+ end
71
+ status = true
72
+ end
73
+
74
+ [status, outbuf, errbuf]
75
+ end
76
+
77
+ def reset
78
+ @world.example_groups.clear
79
+ @configuration.reset
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,128 @@
1
+ require 'set'
2
+ require 'extreme_timeout'
3
+ require 'timeout'
4
+
5
+ module RRRSpec
6
+ module Client
7
+ class SlaveRunner
8
+ attr_reader :key
9
+
10
+ TASKQUEUE_TASK_TIMEOUT = -1
11
+ TASKQUEUE_ARBITER_TIMEOUT = 20
12
+ TIMEOUT_EXITCODE = 42
13
+ class SoftTimeoutException < Exception; end
14
+
15
+ def initialize(slave, working_dir, taskset_key)
16
+ @slave = slave
17
+ @taskset = Taskset.new(taskset_key)
18
+ @timeout = TASKQUEUE_TASK_TIMEOUT
19
+ @rspec_runner = RSpecRunner.new
20
+ @working_path = File.join(working_dir, @taskset.rsync_name)
21
+ @unknown_spec_timeout_sec = @taskset.unknown_spec_timeout_sec
22
+ @least_timeout_sec = @taskset.least_timeout_sec
23
+ @worked_task_keys = Set.new
24
+ end
25
+
26
+ def work_loop
27
+ loop { work }
28
+ end
29
+
30
+ def spec_timeout_sec(task)
31
+ if task.estimate_sec == nil
32
+ soft_timeout_sec = @unknown_spec_timeout_sec
33
+ hard_timeout_sec = @unknown_spec_timeout_sec + 30
34
+ else
35
+ estimate_sec = task.estimate_sec
36
+ soft_timeout_sec = estimate_sec * 2
37
+ hard_timeout_sec = estimate_sec * 3
38
+ end
39
+ return [soft_timeout_sec, @least_timeout_sec].max, [hard_timeout_sec, @least_timeout_sec].max
40
+ end
41
+
42
+ def work
43
+ task = @taskset.dequeue_task(@timeout)
44
+ unless task
45
+ @timeout = TASKQUEUE_ARBITER_TIMEOUT
46
+ ArbiterQueue.check(@taskset)
47
+ else
48
+ @timeout = TASKQUEUE_TASK_TIMEOUT
49
+ if @worked_task_keys.include?(task.key)
50
+ @taskset.reversed_enqueue_task(task)
51
+ return
52
+ else
53
+ @worked_task_keys << task.key
54
+ end
55
+
56
+ return if task.status.present?
57
+ trial = Trial.create(task, @slave)
58
+
59
+ @rspec_runner.reset
60
+ status, outbuf, errbuf = @rspec_runner.setup(File.join(@working_path, task.spec_file))
61
+ unless status
62
+ trial.finish('error', outbuf, errbuf, nil, nil, nil)
63
+ ArbiterQueue.trial(trial)
64
+ return
65
+ end
66
+
67
+ soft_timeout_sec, hard_timeout_sec = spec_timeout_sec(task)
68
+
69
+ formatter = RedisReportingFormatter.new
70
+ trial.start
71
+ status, outbuf, errbuf = ExtremeTimeout::timeout(
72
+ hard_timeout_sec, TIMEOUT_EXITCODE
73
+ ) do
74
+ Timeout::timeout(soft_timeout_sec, SoftTimeoutException) do
75
+ @rspec_runner.run(formatter)
76
+ end
77
+ end
78
+ if status
79
+ trial.finish(formatter.status, outbuf, errbuf,
80
+ formatter.passed, formatter.pending, formatter.failed)
81
+ else
82
+ trial.finish('error', outbuf, errbuf, nil, nil, nil)
83
+ end
84
+
85
+ ArbiterQueue.trial(trial)
86
+ end
87
+ end
88
+
89
+ class RedisReportingFormatter
90
+ attr_reader :passed, :pending, :failed
91
+
92
+ def initialize
93
+ @passed = 0
94
+ @pending = 0
95
+ @failed = 0
96
+ @timeout = false
97
+ end
98
+
99
+ def example_passed(example)
100
+ @passed += 1
101
+ end
102
+
103
+ def example_pending(example)
104
+ @pending += 1
105
+ end
106
+
107
+ def example_failed(example)
108
+ @failed += 1
109
+ if example.exception.is_a?(SoftTimeoutException)
110
+ @timeout = true
111
+ end
112
+ end
113
+
114
+ def status
115
+ if @timeout
116
+ 'timeout'
117
+ elsif @failed != 0
118
+ 'failed'
119
+ elsif @pending != 0
120
+ 'pending'
121
+ else
122
+ 'passed'
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,147 @@
1
+ module RRRSpec
2
+ module Client
3
+ module Support
4
+ module_function
5
+
6
+ def start_taskset(conf, rsync_name)
7
+ $stderr.puts '1/3) Making a package...'
8
+ if is_using_rsync?(rsync_name)
9
+ $stderr.puts 'It seems you are running rrrspec already'
10
+ $stderr.puts 'Please wait until the previous run finishes'
11
+ exit 1
12
+ end
13
+ unless run_rsync_package(rsync_name)
14
+ $stderr.puts 'rsync failed.'
15
+ exit 1
16
+ end
17
+
18
+ $stderr.puts '2/3) Uploading the package...'
19
+ taskset = Taskset.create(
20
+ rsync_name,
21
+ conf.setup_command,
22
+ conf.slave_command,
23
+ conf.worker_type,
24
+ conf.taskset_class,
25
+ conf.max_workers,
26
+ conf.max_trials,
27
+ conf.unknown_spec_timeout_sec,
28
+ conf.least_timeout_sec
29
+ )
30
+ estimate_sec_sorted(conf.taskset_class, conf.spec_files.uniq).reverse_each do |spec_file, estimate_sec|
31
+ task = Task.create(taskset, estimate_sec, spec_file)
32
+ taskset.add_task(task)
33
+ taskset.enqueue_task(task)
34
+ end
35
+
36
+ $stderr.puts '3/3) Enqueue the taskset...'
37
+ ActiveTaskset.add(taskset)
38
+ DispatcherQueue.notify
39
+ $stderr.puts 'Your request is successfully enqueued!'
40
+ return taskset
41
+ end
42
+
43
+ def show_result(taskset, verbose=false)
44
+ puts "Status: #{taskset.status}"
45
+ puts "Created: #{taskset.created_at}"
46
+ puts "Finished: #{taskset.finished_at}"
47
+ puts "Tasks: #{taskset.task_size}"
48
+ puts "Succeeded: #{taskset.succeeded_count}"
49
+ puts "Failed: #{taskset.failed_count}"
50
+
51
+ if verbose
52
+ puts
53
+
54
+ puts "Log:"
55
+ taskset.log.split("\n").each { |line| puts "\t#{line}" }
56
+ puts
57
+
58
+ puts "Workers:"
59
+ taskset.worker_logs.each do |worker_log|
60
+ puts "\tKey: #{worker_log.key}"
61
+ puts "\tStarted: #{worker_log.started_at}"
62
+ puts "\tRSync Finished: #{worker_log.rsync_finished_at}"
63
+ puts "\tSetup Finished: #{worker_log.setup_finished_at}"
64
+ puts "\tFinished: #{worker_log.finished_at}"
65
+ puts "\tLog:"
66
+ worker_log.log.split("\n").each { |line| puts "\t\t#{line}" }
67
+ end
68
+ puts
69
+
70
+ puts "Slaves:"
71
+ taskset.slaves.each do |slave|
72
+ puts "\tKey: #{slave.key}"
73
+ puts "\tStatus: #{slave.status}"
74
+ puts "\tTrials:"
75
+ slave.trials.each do |trial|
76
+ puts "\t\t#{trial.key}"
77
+ end
78
+ puts "\tLog:"
79
+ slave.log.split("\n").each { |line| puts "\t\t#{line}" }
80
+ end
81
+ puts
82
+
83
+ puts "Tasks:"
84
+ taskset.tasks.each do |task|
85
+ puts "\tKey: #{task.key}"
86
+ puts "\tSpec: #{task.spec_file}"
87
+ puts "\tStatus: #{task.status}"
88
+ puts "\tTrials:"
89
+ task.trials.each do |trial|
90
+ puts "\t\tKey: #{trial.key}"
91
+ puts "\t\tSlave: #{trial.slave.key}"
92
+ puts "\t\tStatus: #{trial.status}"
93
+ puts "\t\tStarted: #{trial.started_at}"
94
+ puts "\t\tFinished: #{trial.finished_at}"
95
+ puts "\t\tPassed: #{trial.passed}"
96
+ puts "\t\tPending: #{trial.pending}"
97
+ puts "\t\tFailed: #{trial.failed}"
98
+ stdout = trial.stdout
99
+ if stdout
100
+ puts "\t\tSTDOUT:"
101
+ stdout.split("\n").each { |line| puts "\t\t\t#{line}" }
102
+ end
103
+ stderr = trial.stderr
104
+ if stderr
105
+ puts "\t\tSTDERR:"
106
+ stderr.split("\n").each { |line| puts "\t\t\t#{line}" }
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def run_rsync_package(rsync_name)
114
+ conf = RRRSpec.configuration
115
+ remote_dir = File.join(RSyncInfo.rsync_dir, rsync_name)
116
+ remote_path = "#{RSyncInfo.rsync_server}:#{remote_dir}"
117
+ command = "rsync #{conf.packaging_rsync_options} #{conf.packaging_dir}/ #{remote_path}"
118
+ $stderr.puts command
119
+ system(command)
120
+ $?.success?
121
+ end
122
+
123
+ def is_using_rsync?(rsync_name)
124
+ ActiveTaskset.list.any? do |taskset|
125
+ taskset.rsync_name == rsync_name
126
+ end
127
+ end
128
+
129
+ # Public: Sort the spec filepaths by their estiamted spec execution times.
130
+ #
131
+ # Returns an array of [spec_file, estiamte_sec]
132
+ def estimate_sec_sorted(taskset_class, spec_files)
133
+ estimate_secs = TasksetEstimation.estimate_secs(taskset_class)
134
+ spec_files.map do |spec_file|
135
+ [spec_file, estimate_secs[spec_file]]
136
+ end.sort do |a, b|
137
+ case
138
+ when a[1] == nil && b[1] == nil then 0
139
+ when a[1] == nil && b[1] != nil then 1
140
+ when a[1] != nil && b[1] == nil then -1
141
+ else a[1] - b[1]
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,5 @@
1
+ module RRRSpec
2
+ module Client
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'rrrspec'
2
+ require 'rrrspec/client/configuration'
3
+ require 'rrrspec/client/rspec_runner'
4
+ require 'rrrspec/client/slave_runner'
5
+ require 'rrrspec/client/support'
@@ -0,0 +1,42 @@
1
+ module RRRSpec
2
+ class Configuration
3
+ attr_accessor :loaded
4
+ attr_reader :type
5
+
6
+ def redis=(arg)
7
+ @redis_value = arg
8
+ end
9
+
10
+ def redis
11
+ case @redis_value
12
+ when Hash then Redis.new(@redis_value)
13
+ when Proc then @redis_value.call
14
+ else @redis_value
15
+ end
16
+ end
17
+
18
+ def check_validity
19
+ validity = true
20
+
21
+ unless @redis_value
22
+ $stderr.puts("Redis configuration is empty")
23
+ validity = false
24
+ end
25
+
26
+ validity
27
+ end
28
+
29
+ def load_files(files)
30
+ loaded = []
31
+ files.each do |filepath|
32
+ filepath = File.absolute_path(filepath)
33
+ next unless File.exists?(filepath)
34
+ $stderr.puts("Loading: #{filepath}")
35
+ load filepath
36
+ loaded << filepath
37
+ end
38
+
39
+ RRRSpec.configuration.loaded = loaded
40
+ end
41
+ end
42
+ end