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 +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +19 -4
- data/lib/parallel_rspec.rb +4 -0
- data/lib/parallel_rspec/channel.rb +54 -0
- data/lib/parallel_rspec/client.rb +61 -0
- data/lib/parallel_rspec/example.rb +16 -0
- data/lib/parallel_rspec/runner.rb +6 -4
- data/lib/parallel_rspec/server.rb +38 -0
- data/lib/parallel_rspec/tasks.rake +5 -5
- data/lib/parallel_rspec/version.rb +1 -1
- data/lib/parallel_rspec/workers.rb +36 -5
- data/parallel_rspec.gemspec +2 -2
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd859eb40b317c48862eb67b4f24f3fa50e4bea8
|
4
|
+
data.tar.gz: 6c04d37e7c0828a2b3bfa7112e9819a28b814c91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10d65506bd40f980d529362af9b5e95e3da72d0be1eda7bdb86ec52f16ac903b38fb9a717c2ca63da7c2533e639b7a1368aa067a7623287af42337a4aa7ca076
|
7
|
+
data.tar.gz: ef27474707581225d8514111a55c8859459979932730dade8872f62a70781dd4318ae51db90c33405aaac2674ffb067ef9fc71b39376030084824fd4dd514414
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -12,13 +12,17 @@ group :development, :test do
|
|
12
12
|
end
|
13
13
|
```
|
14
14
|
|
15
|
-
|
15
|
+
Or if you use Spring:
|
16
16
|
|
17
|
-
|
17
|
+
```ruby
|
18
|
+
group :development, :test do
|
19
|
+
gem 'spring-prspec'
|
20
|
+
end
|
21
|
+
```
|
18
22
|
|
19
|
-
|
23
|
+
And then execute:
|
20
24
|
|
21
|
-
$
|
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.
|
data/lib/parallel_rspec.rb
CHANGED
@@ -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.
|
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(
|
75
|
-
|
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 '
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -13,24 +13,55 @@ module ParallelRSpec
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def run_test_workers
|
16
|
-
|
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(
|
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
|
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(
|
33
|
-
results =
|
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"
|
data/parallel_rspec.gemspec
CHANGED
@@ -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.
|
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: []
|