rspec-queue 0.0.1 → 0.0.2.pre
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 +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
|