parallel_rspec 0.2.0 → 0.3.1

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: 1df19064c0de30bc25fe56463553c51354d38319
4
- data.tar.gz: 41552dac37c0fff6e8f04612493d4593ea68aa25
3
+ metadata.gz: cd859eb40b317c48862eb67b4f24f3fa50e4bea8
4
+ data.tar.gz: 6c04d37e7c0828a2b3bfa7112e9819a28b814c91
5
5
  SHA512:
6
- metadata.gz: 7458d557acec03dfce6c25e3908d363b30c69d3bb896c1fd633471c25ce32f862d8b3b23ab4419247cd9730def619578fee671a1fa797881fc72372dacdd39d1
7
- data.tar.gz: b7d6e86fe55ddc5e926c5452243e9e035c0dc6024f8570d6d4bb306b32a1dbf42cc0dd19fad10ac25ec47133dbe3c7dd7c96f6afff8a4e8e7008ebc672633cf5
6
+ metadata.gz: 10d65506bd40f980d529362af9b5e95e3da72d0be1eda7bdb86ec52f16ac903b38fb9a717c2ca63da7c2533e639b7a1368aa067a7623287af42337a4aa7ca076
7
+ data.tar.gz: ef27474707581225d8514111a55c8859459979932730dade8872f62a70781dd4318ae51db90c33405aaac2674ffb067ef9fc71b39376030084824fd4dd514414
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015 Will Bryant
3
+ Copyright (c) 2015 Powershop New Zealand Ltd
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -12,13 +12,17 @@ group :development, :test do
12
12
  end
13
13
  ```
14
14
 
15
- And then execute:
15
+ Or if you use Spring:
16
16
 
17
- $ bundle
17
+ ```ruby
18
+ group :development, :test do
19
+ gem 'spring-prspec'
20
+ end
21
+ ```
18
22
 
19
- Or install it yourself as:
23
+ And then execute:
20
24
 
21
- $ gem install parallel_rspec
25
+ $ bundle
22
26
 
23
27
  This version of ParallelRSpec has been tested with RSpec 3.3.
24
28
 
@@ -38,6 +42,17 @@ You're then ready to run specs in parallel:
38
42
 
39
43
  $ bundle exec prspec spec/my_spec.rb spec/another_spec.rb
40
44
 
45
+ Or if you use Spring:
46
+
47
+ $ bundle exec spring prspec spec/my_spec.rb spec/another_spec.rb
48
+
49
+ You may like to make an alias:
50
+
51
+ $ alias prspec='bundle exec spring prspec'
52
+ $ prspec spec/my_spec.rb spec/another_spec.rb
53
+
54
+ When you change WORKERS, don't forget to restart Spring and re-run the create and populate steps above if necessary.
55
+
41
56
  ## Contributing
42
57
 
43
58
  Bug reports and pull requests are welcome on GitHub at https://github.com/willbryant/parallel_rspec.
@@ -1,4 +1,8 @@
1
1
  require "parallel_rspec/version"
2
+ require "parallel_rspec/channel"
2
3
  require "parallel_rspec/workers"
4
+ require "parallel_rspec/example"
5
+ require "parallel_rspec/server"
6
+ require "parallel_rspec/client"
3
7
  require "parallel_rspec/runner"
4
8
  require "parallel_rspec/railtie"
@@ -0,0 +1,54 @@
1
+ module ParallelRSpec
2
+ # Adapted from nitra.
3
+ # Copyright 2012-2013 Roger Nesbitt, Powershop Limited, YouDo Limited. MIT licence.
4
+ class Channel
5
+ ProtocolInvalidError = Class.new(StandardError)
6
+
7
+ attr_reader :rd, :wr
8
+ attr_accessor :raise_epipe_on_write_error
9
+
10
+ def initialize(rd, wr)
11
+ @rd = rd
12
+ @wr = wr
13
+ @rd.binmode
14
+ @wr.binmode
15
+ end
16
+
17
+ def self.pipe
18
+ c_rd, s_wr = IO.pipe("ASCII-8BIT", "ASCII-8BIT")
19
+ s_rd, c_wr = IO.pipe("ASCII-8BIT", "ASCII-8BIT")
20
+ [new(c_rd, c_wr), new(s_rd, s_wr)]
21
+ end
22
+
23
+ def self.read_select(channels)
24
+ fds = IO.select(channels.collect(&:rd))
25
+ fds.first.collect do |fd|
26
+ channels.detect {|c| c.rd == fd}
27
+ end
28
+ end
29
+
30
+ def close
31
+ rd.close
32
+ wr.close
33
+ end
34
+
35
+ def read
36
+ return unless line = rd.gets
37
+ if result = line.strip.match(/\ACOMMAND,(\d+)\z/)
38
+ data = rd.read(result[1].to_i)
39
+ Marshal.load(data)
40
+ else
41
+ raise ProtocolInvalidError, "Expected command length line, got #{line.inspect}"
42
+ end
43
+ end
44
+
45
+ def write(data)
46
+ encoded = Marshal.dump(data)
47
+ wr.write("COMMAND,#{encoded.bytesize}\n")
48
+ wr.write(encoded)
49
+ wr.flush
50
+ rescue Errno::EPIPE
51
+ raise if raise_epipe_on_write_error
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,61 @@
1
+ module ParallelRSpec
2
+ class Client
3
+ attr_reader :channel_to_server
4
+
5
+ def initialize(channel_to_server)
6
+ @channel_to_server = channel_to_server
7
+ end
8
+
9
+ def example_group_started(group)
10
+ # not implemented yet - would need the same extraction/simplification for serialization as Example below
11
+ end
12
+
13
+ def example_group_finished(group)
14
+ # ditto
15
+ end
16
+
17
+ def example_started(example)
18
+ channel_to_server.write([:example_started, serialize_example(example)])
19
+ end
20
+
21
+ def example_passed(example)
22
+ channel_to_server.write([:example_passed, serialize_example(example)])
23
+ end
24
+
25
+ def example_failed(example)
26
+ channel_to_server.write([:example_failed, serialize_example(example)])
27
+ end
28
+
29
+ def example_pending(example)
30
+ channel_to_server.write([:example_pending, serialize_example(example)])
31
+ end
32
+
33
+ def deprecation(hash)
34
+ channel_to_server.write([:deprecation, hash])
35
+ end
36
+
37
+ def serialize_example(example)
38
+ Example.new(
39
+ example.id,
40
+ example.description,
41
+ example.exception,
42
+ example.location_rerun_argument,
43
+ example.metadata.slice(
44
+ :absolute_file_path,
45
+ :described_class,
46
+ :description,
47
+ :description_args,
48
+ :execution_result,
49
+ :full_description,
50
+ :file_path,
51
+ :last_run_status,
52
+ :line_number,
53
+ :location,
54
+ :pending,
55
+ :rerun_file_path,
56
+ :scoped_id,
57
+ :shared_group_inclusion_backtrace,
58
+ :type))
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,16 @@
1
+ module ParallelRSpec
2
+ # only the good bits of RSpec's Example class, those needed by the reporters and formatters and
3
+ # marshallable.
4
+ Example = Struct.new(:id, :description, :exception, :location_rerun_argument, :metadata) do
5
+ def self.delegate_to_metadata(key)
6
+ define_method(key) { metadata[key] }
7
+ end
8
+
9
+ delegate_to_metadata :execution_result
10
+ delegate_to_metadata :file_path
11
+ delegate_to_metadata :full_description
12
+ delegate_to_metadata :location
13
+ delegate_to_metadata :pending
14
+ delegate_to_metadata :skip
15
+ end
16
+ end
@@ -62,8 +62,10 @@ module ParallelRSpec
62
62
  end
63
63
 
64
64
  def run_in_parallel(example_groups, reporter)
65
+ server = Server.new(reporter)
65
66
  workers = Workers.new
66
- workers.run_test_workers do |worker|
67
+ workers.run_test_workers_with_server(server) do |worker, channel_to_server|
68
+ client = Client.new(channel_to_server)
67
69
  index = 0
68
70
  RSpec.world.filtered_examples.each do |group, examples|
69
71
  examples.reject! do |example|
@@ -71,10 +73,10 @@ module ParallelRSpec
71
73
  (index % workers.number_of_workers) != (worker % workers.number_of_workers)
72
74
  end
73
75
  end
74
- success = example_groups.map { |g| g.run(reporter) }.all?
75
- reporter.finish
76
- success
76
+ success = example_groups.map { |g| g.run(client) }.all?
77
+ channel_to_server.write([:result, success])
77
78
  end
79
+ server.success?
78
80
  end
79
81
  end
80
82
  end
@@ -0,0 +1,38 @@
1
+ module ParallelRSpec
2
+ class Server
3
+ attr_reader :reporter
4
+
5
+ def initialize(reporter)
6
+ @reporter = reporter
7
+ @success = true
8
+ end
9
+
10
+ def example_started(example)
11
+ reporter.example_started(example)
12
+ end
13
+
14
+ def example_passed(example)
15
+ reporter.example_passed(example)
16
+ end
17
+
18
+ def example_failed(example)
19
+ reporter.example_failed(example)
20
+ end
21
+
22
+ def example_pending(example)
23
+ reporter.example_pending(example)
24
+ end
25
+
26
+ def deprecation(hash)
27
+ reporter.deprecation(hash)
28
+ end
29
+
30
+ def result(result)
31
+ @success &&= result
32
+ end
33
+
34
+ def success?
35
+ @success
36
+ end
37
+ end
38
+ end
@@ -1,17 +1,17 @@
1
- require 'lib/parallel_workers.rb'
1
+ require 'parallel_rspec/workers.rb'
2
2
 
3
3
  db_namespace = namespace :db do
4
4
  namespace :parallel do
5
5
  # desc "Creates the test database"
6
6
  task :create => [:load_config] do
7
- ParallelWorkers.new.run_test_workers do |worker|
7
+ ParallelRSpec::Workers.new.run_test_workers do |worker|
8
8
  ActiveRecord::Tasks::DatabaseTasks.create ActiveRecord::Base.configurations['test']
9
9
  end
10
10
  end
11
11
 
12
12
  # desc "Empty the test database"
13
13
  task :purge => %w(environment load_config) do
14
- ParallelWorkers.new.run_test_workers do |worker|
14
+ ParallelRSpec::Workers.new.run_test_workers do |worker|
15
15
  ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
16
16
  end
17
17
  end
@@ -20,7 +20,7 @@ db_namespace = namespace :db do
20
20
  task :load_schema => %w(db:parallel:purge) do
21
21
  should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
22
22
  begin
23
- ParallelWorkers.new.run_test_workers do |worker|
23
+ ParallelRSpec::Workers.new.run_test_workers do |worker|
24
24
  ActiveRecord::Schema.verbose = false
25
25
  ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA']
26
26
  end
@@ -33,7 +33,7 @@ db_namespace = namespace :db do
33
33
 
34
34
  # desc "Recreate the test database from an existent structure.sql file"
35
35
  task :load_structure => %w(db:parallel:purge) do
36
- ParallelWorkers.new.run_test_workers do |worker|
36
+ ParallelRSpec::Workers.new.run_test_workers do |worker|
37
37
  ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA']
38
38
  end
39
39
  end
@@ -1,3 +1,3 @@
1
1
  module ParallelRSpec
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -13,24 +13,55 @@ module ParallelRSpec
13
13
  end
14
14
 
15
15
  def run_test_workers
16
- children = (1..number_of_workers).collect do |worker|
16
+ child_pids = (1..number_of_workers).collect do |worker|
17
17
  fork do
18
18
  establish_test_database_connection(worker)
19
19
  yield worker
20
20
  end
21
21
  end
22
22
 
23
- verify_children(children)
23
+ verify_children(child_pids)
24
+ end
25
+
26
+ def run_test_workers_with_server(server)
27
+ child_pids, channels = (1..number_of_workers).collect do |worker|
28
+ channel_to_client, channel_to_server = ParallelRSpec::Channel.pipe
29
+
30
+ pid = fork do
31
+ channel_to_client.close
32
+ establish_test_database_connection(worker)
33
+ yield worker, channel_to_server
34
+ end
35
+
36
+ channel_to_server.close
37
+ [pid, channel_to_client]
38
+ end.transpose
39
+
40
+ invoke_server_for_channels(server, channels)
41
+
42
+ verify_children(child_pids)
43
+ end
44
+
45
+ def invoke_server_for_channels(server, channels)
46
+ while !channels.empty?
47
+ Channel.read_select(channels).each do |channel|
48
+ if command = channel.read
49
+ server.send(*command)
50
+ else
51
+ channels.delete(channel)
52
+ end
53
+ end
54
+ end
24
55
  end
25
56
 
26
57
  def establish_test_database_connection(worker)
27
58
  ENV['TEST_ENV_NUMBER'] = worker.to_s
28
- ActiveRecord::Base.configurations['test']['database'] << worker.to_s unless worker.zero?
59
+ ActiveRecord::Base.configurations['test']['database'] << worker.to_s unless worker == 1
29
60
  ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
30
61
  end
31
62
 
32
- def verify_children(children)
33
- results = children.collect { |pid| Process.wait2(pid).last }.reject(&:success?)
63
+ def verify_children(child_pids)
64
+ results = child_pids.collect { |pid| Process.wait2(pid).last }.reject(&:success?)
34
65
 
35
66
  unless results.empty?
36
67
  STDERR.puts "\n#{results.size} worker#{'s' unless results.size == 1} failed"
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Will Bryant, Powershop New Zealand Ltd"]
10
10
  spec.email = ["will.bryant@gmail.com"]
11
11
 
12
- spec.summary = %q{This gem lets you run your RSpec examples in parallel across across your CPUs..}
13
- spec.description = %q{This gem lets you run your RSpec examples in parallel across across your CPUs. Each worker automatically gets its own database to avoid conflicts.}
12
+ spec.summary = %q{This gem lets you run your RSpec examples in parallel across across your CPUs.}
13
+ spec.description = %q{This gem lets you run your RSpec examples in parallel across across your CPUs. Each worker automatically gets its own database to avoid conflicts. The optional spring-prspec gem adds support for running under Spring.}
14
14
  spec.homepage = "https://github.com/willbryant/parallel_rspec"
15
15
  spec.license = "MIT"
16
16
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel_rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Will Bryant, Powershop New Zealand Ltd
@@ -39,7 +39,8 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
41
  description: This gem lets you run your RSpec examples in parallel across across your
42
- CPUs. Each worker automatically gets its own database to avoid conflicts.
42
+ CPUs. Each worker automatically gets its own database to avoid conflicts. The
43
+ optional spring-prspec gem adds support for running under Spring.
43
44
  email:
44
45
  - will.bryant@gmail.com
45
46
  executables:
@@ -55,8 +56,12 @@ files:
55
56
  - Rakefile
56
57
  - exe/prspec
57
58
  - lib/parallel_rspec.rb
59
+ - lib/parallel_rspec/channel.rb
60
+ - lib/parallel_rspec/client.rb
61
+ - lib/parallel_rspec/example.rb
58
62
  - lib/parallel_rspec/railtie.rb
59
63
  - lib/parallel_rspec/runner.rb
64
+ - lib/parallel_rspec/server.rb
60
65
  - lib/parallel_rspec/tasks.rake
61
66
  - lib/parallel_rspec/version.rb
62
67
  - lib/parallel_rspec/workers.rb
@@ -85,5 +90,5 @@ rubygems_version: 2.2.2
85
90
  signing_key:
86
91
  specification_version: 4
87
92
  summary: This gem lets you run your RSpec examples in parallel across across your
88
- CPUs..
93
+ CPUs.
89
94
  test_files: []