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 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