rspec-queue 0.0.1 → 0.0.2.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rspec_queue/configuration.rb +36 -0
- data/lib/rspec_queue/formatter.rb +80 -0
- data/lib/rspec_queue/server.rb +81 -0
- data/lib/rspec_queue/server_runner.rb +42 -0
- data/lib/rspec_queue/worker.rb +60 -0
- data/lib/rspec_queue/worker_runner.rb +32 -0
- metadata +9 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 485ab2b5b93e916e15048e8111a2b4a21d0eb155
|
4
|
+
data.tar.gz: ac930cd0eee512063ac081ce9838215222fe950c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ff29ed27220bbb918cd53d4854406e753b6e0c73c1f841ddf0a48f506e84b3b651837cd846df2d142c2316f3e28f5d4a1e3555ee7f0cd25315df3ebc5e0cecb
|
7
|
+
data.tar.gz: c3c971f13c03a963aad682c988f104a7c5a84b5cbeef93661bf0f0286691ac8bd97c442ac535f269ecd08291fb69b92f9a7a24af86d517c1a29f75194fc79bc8
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module RSpecQueue
|
4
|
+
class Configuration
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_accessor :after_worker_spawn_block
|
8
|
+
attr_accessor :server_socket
|
9
|
+
|
10
|
+
def self.after_worker_spawn(&block)
|
11
|
+
self.instance.after_worker_spawn_block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.call_after_worker_spawn_hooks(index)
|
15
|
+
self.instance.after_worker_spawn_block.call(index) if self.instance.after_worker_spawn_block
|
16
|
+
end
|
17
|
+
|
18
|
+
def worker_count
|
19
|
+
@worker_count ||= [env_queue_workers || cpu_count - 1, 1].max
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def env_queue_workers
|
25
|
+
ENV['RSPEC_QUEUE_WORKERS'].to_i if ENV['RSPEC_QUEUE_WORKERS']
|
26
|
+
end
|
27
|
+
|
28
|
+
def cpu_count
|
29
|
+
num_cpus = if `uname`.chomp == "Darwin"
|
30
|
+
`/usr/sbin/sysctl -n hw.ncpu`.to_i
|
31
|
+
else
|
32
|
+
`grep processor /proc/cpuinfo | wc -l`.to_i
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rspec/core/formatters'
|
2
|
+
require 'rspec_queue/configuration'
|
3
|
+
|
4
|
+
# A custom formatter used for our parallel test suite
|
5
|
+
module RSpecQueue
|
6
|
+
class Formatter < RSpec::Core::Formatters::ProgressFormatter
|
7
|
+
RSpec::Core::Formatters.register self, :example_failed, :example_pending, :dump_summary, :dump_pending, :dump_failures
|
8
|
+
|
9
|
+
def initialize(output)
|
10
|
+
super
|
11
|
+
@output = output
|
12
|
+
@failed_examples = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def example_failed(failure)
|
16
|
+
@failed_examples << failure.example
|
17
|
+
@output.puts failure.fully_formatted(@failed_examples.size)
|
18
|
+
end
|
19
|
+
|
20
|
+
def example_pending(pending)
|
21
|
+
@output.puts "\nPending: #{RSpec::Core::Formatters::ConsoleCodes.wrap(pending.example.metadata[:execution_result].pending_message, :yellow)}"
|
22
|
+
@output.puts " #{RSpec::Core::Formatters::ConsoleCodes.wrap(pending.example.metadata[:location], :cyan)}\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
def dump_summary(summary)
|
26
|
+
colorizer = RSpec::Core::Formatters::ConsoleCodes
|
27
|
+
|
28
|
+
results_output = [
|
29
|
+
"Finished in #{summary.formatted_duration}",
|
30
|
+
"(files took #{summary.formatted_load_time} to load)",
|
31
|
+
"#{summary.colorized_totals_line}"
|
32
|
+
].join("\n")
|
33
|
+
|
34
|
+
slowest_examples = summary.examples.sort_by { |e| e[:run_time] }.reverse[0..4]
|
35
|
+
slowest_example_output = formatted_slowest_examples(slowest_examples, summary.duration, colorizer)
|
36
|
+
|
37
|
+
summary_output = [
|
38
|
+
results_output,
|
39
|
+
"Top 5 slowest examples:",
|
40
|
+
slowest_example_output
|
41
|
+
].join("\n")
|
42
|
+
|
43
|
+
@output.puts summary_output
|
44
|
+
end
|
45
|
+
|
46
|
+
def dump_failures(_summary)
|
47
|
+
# no-op because we already printed failures once
|
48
|
+
end
|
49
|
+
|
50
|
+
def dump_pending(_notification)
|
51
|
+
# no-op because we already printed failures once
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def formatted_slowest_examples(slowest_examples, total_duration, colorizer)
|
57
|
+
slowest_examples.map { |e|
|
58
|
+
location = colorizer.wrap(e[:location], colorizer.console_code_for(:yellow))
|
59
|
+
impact_on_build = run_time_impact(e[:run_time], total_duration, colorizer)
|
60
|
+
example_information = colorizer.wrap("took #{e[:run_time].round(2)}s, impact on build time is", colorizer.console_code_for(:cyan))
|
61
|
+
"#{location} #{example_information} #{impact_on_build}"
|
62
|
+
}.join("\n")
|
63
|
+
end
|
64
|
+
|
65
|
+
def cpu_count
|
66
|
+
RSpecQueue::Configuration.instance.worker_count
|
67
|
+
end
|
68
|
+
|
69
|
+
def run_time_impact(example_run_time, total_duration, colorizer)
|
70
|
+
overall_impact_in_seconds = (example_run_time / cpu_count).round(2)
|
71
|
+
percentage_of_run_time = (example_run_time / (total_duration * cpu_count) * 100).round(1)
|
72
|
+
|
73
|
+
if overall_impact_in_seconds == Float::INFINITY
|
74
|
+
"negligible"
|
75
|
+
else
|
76
|
+
colorizer.wrap("#{overall_impact_in_seconds}s (#{percentage_of_run_time}%)", colorizer.console_code_for(:white))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module RSpecQueue
|
2
|
+
class Server
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@server = UNIXServer.open(socket_path)
|
6
|
+
end
|
7
|
+
|
8
|
+
def dispatch(example_group_hash, report)
|
9
|
+
example_group_keys = example_group_hash.keys
|
10
|
+
|
11
|
+
while (example_group_keys.count > 0 || worker_uuids.count > 0) do
|
12
|
+
begin
|
13
|
+
socket = @server.accept
|
14
|
+
message = socket.gets.to_s.strip
|
15
|
+
|
16
|
+
case message
|
17
|
+
when "REGISTER"
|
18
|
+
worker_uid = generate_worker_uid
|
19
|
+
register_worker(worker_uid)
|
20
|
+
socket.puts worker_uid
|
21
|
+
|
22
|
+
when "GET_WORK"
|
23
|
+
if example_group_keys.count > 0
|
24
|
+
socket.puts example_group_keys.shift
|
25
|
+
else
|
26
|
+
socket.puts "SHUT_DOWN"
|
27
|
+
end
|
28
|
+
|
29
|
+
when "FINISH"
|
30
|
+
socket.puts "GET_UUID"
|
31
|
+
uuid = socket.gets.to_s.strip
|
32
|
+
|
33
|
+
worker_uuids.delete(uuid)
|
34
|
+
|
35
|
+
socket.puts "GET_RESULTS"
|
36
|
+
results = socket.gets.to_s.strip
|
37
|
+
|
38
|
+
json_results = JSON.parse(results, symbolize_names: true)
|
39
|
+
|
40
|
+
examples = json_results.select { |e| e[:status] == "passed" }
|
41
|
+
failed_examples = json_results.select { |e| e[:status] == "failed" }
|
42
|
+
pending_examples = json_results.select { |e| e[:status] == "pending" }
|
43
|
+
|
44
|
+
report.examples.push(*examples)
|
45
|
+
report.failed_examples.push(*failed_examples)
|
46
|
+
report.pending_examples.push(*pending_examples)
|
47
|
+
|
48
|
+
else
|
49
|
+
puts("unsupported: #{message}")
|
50
|
+
end
|
51
|
+
ensure
|
52
|
+
socket.close
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def close
|
58
|
+
@server.close
|
59
|
+
FileUtils.rm socket_path
|
60
|
+
end
|
61
|
+
|
62
|
+
def register_worker(uid)
|
63
|
+
worker_uuids << uid
|
64
|
+
end
|
65
|
+
|
66
|
+
def worker_uuids
|
67
|
+
@worker_uuids ||= []
|
68
|
+
end
|
69
|
+
|
70
|
+
def socket_path
|
71
|
+
"/tmp/rspec-queue-server-#{Process.pid}.sock"
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def generate_worker_uid
|
77
|
+
@uid_index ||= 0
|
78
|
+
"#{SecureRandom.uuid}/#{Process.pid}/worker/#{@uid_index += 1}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rspec/core'
|
2
|
+
require 'rspec_queue/configuration'
|
3
|
+
require 'rspec_queue/server'
|
4
|
+
require 'rspec_queue/worker'
|
5
|
+
|
6
|
+
module RSpecQueue
|
7
|
+
class ServerRunner < RSpec::Core::Runner
|
8
|
+
def run_specs(example_groups)
|
9
|
+
example_group_hash = example_groups.map { |example_group|
|
10
|
+
[example_group.id, example_group]
|
11
|
+
}.to_h
|
12
|
+
|
13
|
+
# start the server, so we are ready to accept connections from workers
|
14
|
+
server = RSpecQueue::Server.new
|
15
|
+
worker_pids = []
|
16
|
+
|
17
|
+
RSpecQueue::Configuration.instance.worker_count.times do |i|
|
18
|
+
env = {
|
19
|
+
"RSPEC_QUEUE_WORKER_ID" => i.to_s,
|
20
|
+
"RSPEC_QUEUE_SERVER_ADDRESS" => server.socket_path,
|
21
|
+
}
|
22
|
+
|
23
|
+
worker_pids << spawn(env, "rspec-queue-worker", *ARGV)
|
24
|
+
end
|
25
|
+
|
26
|
+
reporter = @configuration.reporter
|
27
|
+
|
28
|
+
reporter.report(0) do |report|
|
29
|
+
@configuration.with_suite_hooks do
|
30
|
+
server.dispatch(example_group_hash, report)
|
31
|
+
[report.failed_examples.count, 1].min # exit status
|
32
|
+
end
|
33
|
+
end
|
34
|
+
ensure
|
35
|
+
server.close
|
36
|
+
|
37
|
+
worker_pids.each do |pid|
|
38
|
+
Process.kill("TERM", pid)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module RSpecQueue
|
4
|
+
class Worker
|
5
|
+
def initialize
|
6
|
+
@server_socket = ENV["RSPEC_QUEUE_SERVER_ADDRESS"]
|
7
|
+
|
8
|
+
socket = UNIXSocket.open(@server_socket)
|
9
|
+
socket.puts "REGISTER"
|
10
|
+
|
11
|
+
@uuid = socket.gets.to_s.strip
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_work?
|
15
|
+
socket = UNIXSocket.open(@server_socket)
|
16
|
+
socket.puts "GET_WORK"
|
17
|
+
message = socket.gets.to_s.strip
|
18
|
+
|
19
|
+
if message == "SHUT_DOWN"
|
20
|
+
false
|
21
|
+
else
|
22
|
+
@example_group_key = message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def current_example
|
27
|
+
@example_group_key
|
28
|
+
end
|
29
|
+
|
30
|
+
def finish(reporter)
|
31
|
+
socket = UNIXSocket.open(@server_socket)
|
32
|
+
socket.puts "FINISH"
|
33
|
+
|
34
|
+
message = socket.gets.to_s.strip
|
35
|
+
|
36
|
+
if (message == "GET_UUID")
|
37
|
+
socket.puts @uuid
|
38
|
+
else
|
39
|
+
puts "warn"
|
40
|
+
end
|
41
|
+
|
42
|
+
message = socket.gets.to_s.strip
|
43
|
+
|
44
|
+
# serialize the rspec reporter results back to the server
|
45
|
+
if (message == "GET_RESULTS")
|
46
|
+
results = reporter.examples.map { |e|
|
47
|
+
{
|
48
|
+
location: e.metadata[:location],
|
49
|
+
status: e.metadata[:execution_result].status,
|
50
|
+
run_time: e.metadata[:execution_result].run_time
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
socket.puts results.to_json
|
55
|
+
else
|
56
|
+
puts "warn"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rspec/core'
|
2
|
+
require 'rspec_queue/configuration'
|
3
|
+
require 'rspec_queue/worker'
|
4
|
+
|
5
|
+
module RSpecQueue
|
6
|
+
class WorkerRunner < RSpec::Core::Runner
|
7
|
+
def run_specs(example_groups)
|
8
|
+
example_group_hash = example_groups.map { |example_group|
|
9
|
+
[example_group.id, example_group]
|
10
|
+
}.to_h
|
11
|
+
|
12
|
+
RSpecQueue::Configuration.instance.server_socket = ENV["RSPEC_QUEUE_SERVER_ADDRESS"]
|
13
|
+
RSpecQueue::Configuration.call_after_worker_spawn_hooks(ENV["RSPEC_QUEUE_WORKER_ID"])
|
14
|
+
|
15
|
+
worker = RSpecQueue::Worker.new
|
16
|
+
|
17
|
+
reporter = @configuration.reporter
|
18
|
+
|
19
|
+
while(worker.has_work?)
|
20
|
+
# can we pass in a custom reporter which instantly reports back
|
21
|
+
# to the server?
|
22
|
+
example_group_hash[worker.current_example].run(reporter)
|
23
|
+
end
|
24
|
+
|
25
|
+
# report the results of the examples run to the master process
|
26
|
+
worker.finish(reporter)
|
27
|
+
|
28
|
+
ensure
|
29
|
+
Process.exit
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ''
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: pry-nav
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
27
|
description:
|
42
28
|
email:
|
43
29
|
executables:
|
@@ -48,6 +34,12 @@ extra_rdoc_files: []
|
|
48
34
|
files:
|
49
35
|
- bin/rspec-queue
|
50
36
|
- bin/rspec-queue-worker
|
37
|
+
- lib/rspec_queue/configuration.rb
|
38
|
+
- lib/rspec_queue/formatter.rb
|
39
|
+
- lib/rspec_queue/server.rb
|
40
|
+
- lib/rspec_queue/server_runner.rb
|
41
|
+
- lib/rspec_queue/worker.rb
|
42
|
+
- lib/rspec_queue/worker_runner.rb
|
51
43
|
homepage:
|
52
44
|
licenses: []
|
53
45
|
metadata: {}
|
@@ -62,9 +54,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
62
54
|
version: '0'
|
63
55
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
56
|
requirements:
|
65
|
-
- - "
|
57
|
+
- - ">"
|
66
58
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
59
|
+
version: 1.3.1
|
68
60
|
requirements: []
|
69
61
|
rubyforge_project:
|
70
62
|
rubygems_version: 2.4.5
|