parallel_tests 0.7.4 → 0.8.0
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.
- data/Gemfile.lock +1 -1
- data/Readme.md +9 -5
- data/bin/parallel_test +1 -0
- data/lib/parallel_tests/cli.rb +8 -2
- data/lib/parallel_tests/cucumber/gherkin_listener.rb +60 -0
- data/lib/parallel_tests/cucumber/runner.rb +8 -0
- data/lib/parallel_tests/grouper.rb +18 -1
- data/lib/parallel_tests/test/runner.rb +1 -1
- data/lib/parallel_tests/version.rb +1 -1
- data/spec/integration_spec.rb +6 -0
- data/spec/parallel_tests/cucumber/gherkin_listener_spec.rb +48 -0
- data/spec/parallel_tests/grouper_spec.rb +61 -0
- data/spec/parallel_tests/test/runner_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -2
- metadata +15 -7
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
@@ -119,15 +119,19 @@ Setup for non-rails
|
|
119
119
|
parallel_test test/bar test/baz/foo_text.rb
|
120
120
|
|
121
121
|
Options are:
|
122
|
-
|
123
122
|
-n [PROCESSES] How many processes to use, default: available CPUs
|
124
|
-
-p, --
|
125
|
-
--
|
123
|
+
-p, --pattern [PATTERN] run tests matching this pattern
|
124
|
+
--group-by group tests by:
|
125
|
+
found - order of finding files
|
126
|
+
steps - number of cucumber steps
|
127
|
+
default - runtime or filesize
|
126
128
|
-m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
|
129
|
+
-s, --single [PATTERN] Run all matching files in only one process
|
127
130
|
-e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUM']
|
128
131
|
-o, --test-options '[OPTIONS]' execute test commands with those options
|
129
|
-
-t, --type [TYPE] test(default) /
|
132
|
+
-t, --type [TYPE] test(default) / rspec / cucumber
|
130
133
|
--non-parallel execute same commands but do not in parallel, needs --exec
|
134
|
+
--chunk-timeout [TIMEOUT] timeout before re-printing the output of a child-process
|
131
135
|
-v, --version Show Version
|
132
136
|
-h, --help Show this.
|
133
137
|
|
@@ -151,7 +155,7 @@ TIPS
|
|
151
155
|
- [Capybara + Selenium] add to env.rb: `Capybara.server_port = 8888 + ENV['TEST_ENV_NUMBER'].to_i`
|
152
156
|
- [RSpec] add a `.rspec_parallel` to use different options, e.g. **no --drb**
|
153
157
|
- [RSpec] delete `script/spec`
|
154
|
-
- [
|
158
|
+
- [[Spork](https://github.com/sporkrb/spork)] does not work with parallel_tests
|
155
159
|
- [RSpec] remove --loadby from you spec/*.opts
|
156
160
|
- [RSpec] Instantly see failures (instead of just a red F) with [rspec-instafail](https://github.com/grosser/rspec-instafail)
|
157
161
|
- [Bundler] if you have a `Gemfile` then `bundle exec` will be used to run tests
|
data/bin/parallel_test
CHANGED
data/lib/parallel_tests/cli.rb
CHANGED
@@ -81,7 +81,13 @@ Options are:
|
|
81
81
|
BANNER
|
82
82
|
opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs") { |n| options[:count] = n }
|
83
83
|
opts.on("-p", '--pattern [PATTERN]', "run tests matching this pattern") { |pattern| options[:pattern] = /#{pattern}/ }
|
84
|
-
opts.on("--
|
84
|
+
opts.on("--group-by [TYPE]", <<-TEXT
|
85
|
+
group tests by:
|
86
|
+
found - order of finding files
|
87
|
+
steps - number of cucumber steps
|
88
|
+
default - runtime or filesize
|
89
|
+
TEXT
|
90
|
+
) { |type| options[:group_by] = type.to_sym }
|
85
91
|
opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run") { |multiply| options[:multiply] = multiply }
|
86
92
|
opts.on("-s [PATTERN]", "--single [PATTERN]", "Run all matching files in only one process") do |pattern|
|
87
93
|
options[:single_process] ||= []
|
@@ -96,7 +102,7 @@ BANNER
|
|
96
102
|
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
97
103
|
end.parse!(argv)
|
98
104
|
|
99
|
-
raise "--
|
105
|
+
raise "--group-by found and --single-process are not supported" if options[:group_by] == :found and options[:single_process]
|
100
106
|
|
101
107
|
options[:files] = argv
|
102
108
|
options
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'gherkin'
|
2
|
+
|
3
|
+
module ParallelTests
|
4
|
+
module Cucumber
|
5
|
+
class GherkinListener
|
6
|
+
attr_reader :collect
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@steps, @uris = [], []
|
10
|
+
@collect = {}
|
11
|
+
reset_counters!
|
12
|
+
end
|
13
|
+
|
14
|
+
def background(*args)
|
15
|
+
@background = 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def scenario(*args)
|
19
|
+
@scenarios += 1
|
20
|
+
@outline = @background = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def scenario_outline(*args)
|
24
|
+
@outline = 1
|
25
|
+
end
|
26
|
+
|
27
|
+
def step(*args)
|
28
|
+
if @background == 1
|
29
|
+
@background_steps += 1
|
30
|
+
elsif @outline > 0
|
31
|
+
@outline_steps += 1
|
32
|
+
else
|
33
|
+
@collect[@uri] += 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def uri(path)
|
38
|
+
@uri = path
|
39
|
+
@collect[@uri] = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def examples(*args)
|
43
|
+
@examples += 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def eof(*args)
|
47
|
+
@collect[@uri] += (@background_steps * @scenarios) + (@outline_steps * @examples)
|
48
|
+
reset_counters!
|
49
|
+
end
|
50
|
+
|
51
|
+
def reset_counters!
|
52
|
+
@examples = @outline = @outline_steps = @background = @background_steps = @scenarios = 0
|
53
|
+
end
|
54
|
+
|
55
|
+
# ignore lots of other possible callbacks ...
|
56
|
+
def method_missing(*args)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -49,6 +49,14 @@ module ParallelTests
|
|
49
49
|
"--profile parallel"
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
def self.tests_in_groups(tests, num_groups, options={})
|
54
|
+
if options[:group_by] == :steps
|
55
|
+
Grouper.by_steps(find_tests(tests, options), num_groups)
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
52
60
|
end
|
53
61
|
end
|
54
62
|
end
|
@@ -5,7 +5,9 @@ module ParallelTests
|
|
5
5
|
|
6
6
|
until items.empty?
|
7
7
|
num_groups.times do |group_number|
|
8
|
-
|
8
|
+
if item = items.shift
|
9
|
+
groups[group_number] << item
|
10
|
+
end
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
@@ -45,5 +47,20 @@ module ParallelTests
|
|
45
47
|
group[:items] << item
|
46
48
|
group[:size] += size
|
47
49
|
end
|
50
|
+
|
51
|
+
def self.by_steps(tests, num_groups)
|
52
|
+
features_with_steps = build_features_with_steps(tests)
|
53
|
+
in_even_groups_by_size(features_with_steps, num_groups)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.build_features_with_steps(tests)
|
57
|
+
require 'parallel_tests/cucumber/gherkin_listener'
|
58
|
+
listener = Cucumber::GherkinListener.new
|
59
|
+
parser = Gherkin::Parser::Parser.new(listener, true, 'root')
|
60
|
+
tests.each{|file|
|
61
|
+
parser.parse(File.read(file), file, 0)
|
62
|
+
}
|
63
|
+
listener.collect.sort_by{|_,value| -value }
|
64
|
+
end
|
48
65
|
end
|
49
66
|
end
|
@@ -31,7 +31,7 @@ module ParallelTests
|
|
31
31
|
def self.tests_in_groups(tests, num_groups, options={})
|
32
32
|
tests = find_tests(tests, options)
|
33
33
|
|
34
|
-
if options[:
|
34
|
+
if options[:group_by] == :found
|
35
35
|
Grouper.in_groups(tests, num_groups)
|
36
36
|
else
|
37
37
|
tests = with_runtime_info(tests)
|
data/spec/integration_spec.rb
CHANGED
@@ -108,6 +108,12 @@ describe 'CLI' do
|
|
108
108
|
`#{bin_folder}/parallel_cucumber -v`.should == version
|
109
109
|
end
|
110
110
|
|
111
|
+
it "runs with --group-by found" do
|
112
|
+
# it only tests that it does not blow up, as it did before fixing...
|
113
|
+
write "spec/x1_spec.rb", "puts '111'"
|
114
|
+
run_tests "spec", :type => 'rspec', :add => '--group-by found'
|
115
|
+
end
|
116
|
+
|
111
117
|
it "runs faster with more processes" do
|
112
118
|
2.times{|i|
|
113
119
|
write "spec/xxx#{i}_spec.rb", 'describe("it"){it("should"){sleep 5}}; $stderr.puts ENV["TEST_ENV_NUMBER"]'
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'parallel_tests/cucumber/gherkin_listener'
|
2
|
+
|
3
|
+
describe ParallelTests::Cucumber::GherkinListener do
|
4
|
+
describe :collect do
|
5
|
+
before(:each) do
|
6
|
+
@listener = ParallelTests::Cucumber::GherkinListener.new
|
7
|
+
@listener.uri("feature_file")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "returns steps count" do
|
11
|
+
3.times {@listener.step(nil)}
|
12
|
+
@listener.collect.should == {"feature_file" => 3}
|
13
|
+
end
|
14
|
+
|
15
|
+
it "counts background steps separately" do
|
16
|
+
@listener.background("background")
|
17
|
+
5.times {@listener.step(nil)}
|
18
|
+
@listener.collect.should == {"feature_file" => 0}
|
19
|
+
|
20
|
+
@listener.scenario("scenario")
|
21
|
+
2.times {@listener.step(nil)}
|
22
|
+
@listener.collect.should == {"feature_file" => 2}
|
23
|
+
|
24
|
+
@listener.scenario("scenario")
|
25
|
+
@listener.collect.should == {"feature_file" => 2}
|
26
|
+
|
27
|
+
@listener.eof
|
28
|
+
@listener.collect.should == {"feature_file" => 12}
|
29
|
+
end
|
30
|
+
|
31
|
+
it "counts scenario outlines steps separately" do
|
32
|
+
@listener.scenario_outline("outline")
|
33
|
+
5.times {@listener.step(nil)}
|
34
|
+
@listener.collect.should == {"feature_file" => 0}
|
35
|
+
|
36
|
+
@listener.scenario("scenario")
|
37
|
+
2.times {@listener.step(nil)}
|
38
|
+
@listener.collect.should == {"feature_file" => 2}
|
39
|
+
|
40
|
+
@listener.scenario("scenario")
|
41
|
+
@listener.collect.should == {"feature_file" => 2}
|
42
|
+
|
43
|
+
3.times {@listener.examples}
|
44
|
+
@listener.eof
|
45
|
+
@listener.collect.should == {"feature_file" => 17}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'parallel_tests/grouper'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
describe ParallelTests::Grouper do
|
5
|
+
describe :by_steps do
|
6
|
+
def write(file, content)
|
7
|
+
File.open(file,'w'){|f| f.write content }
|
8
|
+
end
|
9
|
+
|
10
|
+
it "sorts features by steps" do
|
11
|
+
tmpdir = nil
|
12
|
+
result = Dir.mktmpdir do |dir|
|
13
|
+
tmpdir = dir
|
14
|
+
write("#{dir}/a.feature", "Feature: xxx\n Scenario: xxx\n Given something")
|
15
|
+
write("#{dir}/b.feature", "Feature: xxx\n Scenario: xxx\n Given something\n Scenario: yyy\n Given something")
|
16
|
+
write("#{dir}/c.feature", "Feature: xxx\n Scenario: xxx\n Given something")
|
17
|
+
ParallelTests::Grouper.by_steps(["#{dir}/a.feature", "#{dir}/b.feature", "#{dir}/c.feature"],2)
|
18
|
+
end
|
19
|
+
|
20
|
+
# testing inside mktmpdir is always green
|
21
|
+
result.should =~ [
|
22
|
+
["#{tmpdir}/a.feature", "#{tmpdir}/c.feature"],
|
23
|
+
["#{tmpdir}/b.feature"]
|
24
|
+
]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe :in_even_groups_by_size do
|
29
|
+
let(:files_with_size){ {"1" => 1, "2" => 2, "3" => 3, "4" => 4, "5" => 5} }
|
30
|
+
|
31
|
+
def call(num_groups)
|
32
|
+
ParallelTests::Grouper.in_even_groups_by_size(files_with_size, num_groups)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "groups 1 by 1 for same groups as size" do
|
36
|
+
call(5).should == [["5"], ["4"], ["3"], ["2"], ["1"]]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "groups into even groups" do
|
40
|
+
call(2).should == [["1", "2", "5"], ["3", "4"]]
|
41
|
+
end
|
42
|
+
|
43
|
+
it "groups into a single group" do
|
44
|
+
call(1).should == [["1", "2", "3", "4", "5"]]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "adds empty groups if there are more groups than feature files" do
|
48
|
+
call(6).should == [["5"], ["4"], ["3"], ["2"], ["1"], []]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe :in_groups do
|
53
|
+
it "groups" do
|
54
|
+
ParallelTests::Grouper.in_groups([1,2,3],2).should == [[1,3],[2]]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "keeps groups sorted" do
|
58
|
+
ParallelTests::Grouper.in_groups([3,2,1],2).should == [[1,3],[2]]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -38,7 +38,7 @@ describe ParallelTests::Test::Runner do
|
|
38
38
|
|
39
39
|
it "does not sort when passed false do_sort option" do
|
40
40
|
ParallelTests::Test::Runner.should_not_receive(:smallest_first)
|
41
|
-
call [], 1, :
|
41
|
+
call [], 1, :group_by => :found
|
42
42
|
end
|
43
43
|
|
44
44
|
it "does sort when not passed do_sort option" do
|
data/spec/spec_helper.rb
CHANGED
@@ -135,7 +135,7 @@ def test_tests_in_groups(klass, folder, suffix)
|
|
135
135
|
it "partitions by round-robin when not sorting" do
|
136
136
|
files = ["file1.rb", "file2.rb", "file3.rb", "file4.rb"]
|
137
137
|
klass.should_receive(:find_tests).and_return(files)
|
138
|
-
groups = klass.tests_in_groups(files, 2, :
|
138
|
+
groups = klass.tests_in_groups(files, 2, :group_by => :found)
|
139
139
|
groups[0].should == ["file1.rb", "file3.rb"]
|
140
140
|
groups[1].should == ["file2.rb", "file4.rb"]
|
141
141
|
end
|
@@ -143,7 +143,7 @@ def test_tests_in_groups(klass, folder, suffix)
|
|
143
143
|
it "alpha-sorts partitions when not sorting by runtime" do
|
144
144
|
files = %w[q w e r t y u i o p a s d f g h j k l z x c v b n m]
|
145
145
|
klass.should_receive(:find_tests).and_return(files)
|
146
|
-
groups = klass.tests_in_groups(files, 2, :
|
146
|
+
groups = klass.tests_in_groups(files, 2, :group_by => :found)
|
147
147
|
groups[0].should == groups[0].sort
|
148
148
|
groups[1].should == groups[1].sort
|
149
149
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parallel_tests
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: parallel
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,12 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
description:
|
26
31
|
email: michael@grosser.it
|
27
32
|
executables:
|
@@ -42,6 +47,7 @@ files:
|
|
42
47
|
- bin/parallel_test
|
43
48
|
- lib/parallel_tests.rb
|
44
49
|
- lib/parallel_tests/cli.rb
|
50
|
+
- lib/parallel_tests/cucumber/gherkin_listener.rb
|
45
51
|
- lib/parallel_tests/cucumber/runner.rb
|
46
52
|
- lib/parallel_tests/cucumber/runtime_logger.rb
|
47
53
|
- lib/parallel_tests/grouper.rb
|
@@ -57,7 +63,9 @@ files:
|
|
57
63
|
- lib/parallel_tests/version.rb
|
58
64
|
- parallel_tests.gemspec
|
59
65
|
- spec/integration_spec.rb
|
66
|
+
- spec/parallel_tests/cucumber/gherkin_listener_spec.rb
|
60
67
|
- spec/parallel_tests/cucumber/runner_spec.rb
|
68
|
+
- spec/parallel_tests/grouper_spec.rb
|
61
69
|
- spec/parallel_tests/rspec/failure_logger_spec.rb
|
62
70
|
- spec/parallel_tests/rspec/runner_spec.rb
|
63
71
|
- spec/parallel_tests/rspec/runtime_logger_spec.rb
|
@@ -81,7 +89,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
89
|
version: '0'
|
82
90
|
segments:
|
83
91
|
- 0
|
84
|
-
hash:
|
92
|
+
hash: 1985471141696551729
|
85
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
94
|
none: false
|
87
95
|
requirements:
|
@@ -90,10 +98,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
98
|
version: '0'
|
91
99
|
segments:
|
92
100
|
- 0
|
93
|
-
hash:
|
101
|
+
hash: 1985471141696551729
|
94
102
|
requirements: []
|
95
103
|
rubyforge_project:
|
96
|
-
rubygems_version: 1.8.
|
104
|
+
rubygems_version: 1.8.24
|
97
105
|
signing_key:
|
98
106
|
specification_version: 3
|
99
107
|
summary: Run Test::Unit / RSpec / Cucumber in parallel
|