parallel_tests 0.7.4 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|