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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 319bf2ca5d1e26a5b75074f4e9594952039902eb
4
- data.tar.gz: 612368ada57548012cc91b984213b3e4efc03663
3
+ metadata.gz: 485ab2b5b93e916e15048e8111a2b4a21d0eb155
4
+ data.tar.gz: ac930cd0eee512063ac081ce9838215222fe950c
5
5
  SHA512:
6
- metadata.gz: ece6e605b3422b756212204d680ff0c7230dcd1e30eacfb15fc230cbd3515e181da24241109b7daf31538bbc720cf798c1ac2dda8da5f1981bbb08db2f0e0d15
7
- data.tar.gz: ebfd73bc88ab2ae4f0df7ce770c5f492140a0b9f2480eb89c62b3f0c69fc7fcd79de0f485d730bd8e846a94364c5cda01d1e61e3cf101109e530325c3381d0ed
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.1
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: '0'
59
+ version: 1.3.1
68
60
  requirements: []
69
61
  rubyforge_project:
70
62
  rubygems_version: 2.4.5