parallel_tests 0.16.3 → 0.16.4
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/Gemfile.lock +14 -15
- data/Readme.md +1 -1
- data/lib/parallel_tests/cli.rb +1 -0
- data/lib/parallel_tests/cucumber/scenario_line_logger.rb +51 -0
- data/lib/parallel_tests/cucumber/scenarios.rb +36 -0
- data/lib/parallel_tests/gherkin/runner.rb +16 -12
- data/lib/parallel_tests/grouper.rb +26 -8
- data/lib/parallel_tests/version.rb +1 -1
- data/spec/integration_spec.rb +30 -0
- data/spec/parallel_tests/cucumber/scenarios_spec.rb +59 -0
- data/spec/parallel_tests/grouper_spec.rb +9 -0
- data/spec/parallel_tests/tasks_spec.rb +9 -0
- data/spec/parallel_tests/test/runner_spec.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1e3fb69bc5868e4a5321edbbc4f4527aaa7d277
|
4
|
+
data.tar.gz: f6dc34a502ef7a65f565ecff44bf0990154d2ed3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92e953b15c06a785571f9c1aafc3fcdfb4ae6adf03cd9810215079485a9780a6cc280e2052aa857ee211697930c3b0530f1c1366ac5e560052a87c7f6ab008e8
|
7
|
+
data.tar.gz: df03a89e62a972b117a4e8b2ac9be3ebdd296ae6031c9ff8b392d8743a95ee96eb06e001e2da41b00e58cc2d7b6c1f53367fe41ce359840fefe2d450d18ea0ee
|
data/Gemfile.lock
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
parallel_tests (0.16.
|
4
|
+
parallel_tests (0.16.4)
|
5
5
|
parallel
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
builder (3.
|
10
|
+
builder (3.2.2)
|
11
11
|
bump (0.3.8)
|
12
12
|
colorize (0.5.8)
|
13
|
-
cucumber (1.
|
13
|
+
cucumber (1.3.10)
|
14
14
|
builder (>= 2.1.2)
|
15
|
-
diff-lcs (>= 1.1.
|
16
|
-
gherkin (~> 2.
|
17
|
-
|
18
|
-
|
19
|
-
diff-lcs (1.2.
|
20
|
-
gherkin (2.
|
21
|
-
|
22
|
-
gherkin (2.
|
23
|
-
|
15
|
+
diff-lcs (>= 1.1.3)
|
16
|
+
gherkin (~> 2.12)
|
17
|
+
multi_json (>= 1.7.5, < 2.0)
|
18
|
+
multi_test (>= 0.0.2)
|
19
|
+
diff-lcs (1.2.5)
|
20
|
+
gherkin (2.12.2)
|
21
|
+
multi_json (~> 1.3)
|
22
|
+
gherkin (2.12.2-java)
|
23
|
+
multi_json (~> 1.3)
|
24
24
|
gherkin-ruby (0.3.0)
|
25
|
-
|
26
|
-
|
25
|
+
multi_json (1.8.2)
|
26
|
+
multi_test (0.0.2)
|
27
27
|
parallel (0.9.0)
|
28
28
|
rake (10.0.3)
|
29
29
|
rspec (2.13.0)
|
@@ -37,7 +37,6 @@ GEM
|
|
37
37
|
spinach (0.8.3)
|
38
38
|
colorize (= 0.5.8)
|
39
39
|
gherkin-ruby (~> 0.3.0)
|
40
|
-
term-ansicolor (1.0.7)
|
41
40
|
test-unit (2.4.4)
|
42
41
|
|
43
42
|
PLATFORMS
|
data/Readme.md
CHANGED
@@ -213,7 +213,7 @@ TIPS
|
|
213
213
|
- [RSpec] Instantly see failures (instead of just a red F) with [rspec-instafail](https://github.com/grosser/rspec-instafail)
|
214
214
|
- [Bundler] if you have a `Gemfile` then `bundle exec` will be used to run tests
|
215
215
|
- [Cucumber] add a `parallel: foo` profile to your `config/cucumber.yml` and it will be used to run parallel tests
|
216
|
-
- [Cucumber] Pass in cucumber options by not giving the options an identifier ex: parallel:features[
|
216
|
+
- [Cucumber] Pass in cucumber options by not giving the options an identifier ex: `rake parallel:features[,,'cucumber_opts']`
|
217
217
|
- [Capybara setup](https://github.com/grosser/parallel_tests/wiki)
|
218
218
|
- [Sphinx setup](https://github.com/grosser/parallel_tests/wiki)
|
219
219
|
- [Capistrano setup](https://github.com/grosser/parallel_tests/wiki/Remotely-with-capistrano) let your tests run on a big box instead of your laptop
|
data/lib/parallel_tests/cli.rb
CHANGED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'gherkin/tag_expression'
|
2
|
+
|
3
|
+
module ParallelTests
|
4
|
+
module Cucumber
|
5
|
+
module Formatters
|
6
|
+
class ScenarioLineLogger
|
7
|
+
attr_reader :scenarios
|
8
|
+
|
9
|
+
def initialize(tag_expression = ::Gherkin::TagExpression.new([]))
|
10
|
+
@scenarios = []
|
11
|
+
@tag_expression = tag_expression
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_feature_element(feature_element)
|
15
|
+
return unless @tag_expression.evaluate(feature_element.source_tags)
|
16
|
+
|
17
|
+
case feature_element
|
18
|
+
when ::Cucumber::Ast::Scenario
|
19
|
+
line = if feature_element.respond_to?(:line)
|
20
|
+
feature_element.line
|
21
|
+
else
|
22
|
+
feature_element.instance_variable_get(:@line)
|
23
|
+
end
|
24
|
+
@scenarios << [feature_element.feature.file, line].join(":")
|
25
|
+
when ::Cucumber::Ast::ScenarioOutline
|
26
|
+
sections = feature_element.instance_variable_get(:@example_sections)
|
27
|
+
sections.each { |section|
|
28
|
+
rows = if section[1].respond_to?(:rows)
|
29
|
+
section[1].rows
|
30
|
+
else
|
31
|
+
section[1].instance_variable_get(:@rows)
|
32
|
+
end
|
33
|
+
rows.each_with_index { |row, index|
|
34
|
+
next if index == 0 # slices didn't work with jruby data structure
|
35
|
+
line = if row.respond_to?(:line)
|
36
|
+
row.line
|
37
|
+
else
|
38
|
+
row.instance_variable_get(:@line)
|
39
|
+
end
|
40
|
+
@scenarios << [feature_element.feature.file, line].join(":")
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(*args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'gherkin/tag_expression'
|
2
|
+
require 'cucumber/runtime'
|
3
|
+
require 'cucumber'
|
4
|
+
require 'parallel_tests/cucumber/scenario_line_logger'
|
5
|
+
require 'parallel_tests/gherkin/listener'
|
6
|
+
|
7
|
+
module ParallelTests
|
8
|
+
module Cucumber
|
9
|
+
class Scenarios
|
10
|
+
class << self
|
11
|
+
def all(files, options={})
|
12
|
+
tag_expressions = if options[:ignore_tag_pattern]
|
13
|
+
options[:ignore_tag_pattern].split(/\s*,\s*/).map {|tag| "~#{tag}" }
|
14
|
+
else
|
15
|
+
[]
|
16
|
+
end
|
17
|
+
split_into_scenarios files, tag_expressions
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def split_into_scenarios(files, tags=[])
|
23
|
+
tag_expression = ::Gherkin::TagExpression.new(tags)
|
24
|
+
scenario_line_logger = ParallelTests::Cucumber::Formatters::ScenarioLineLogger.new(tag_expression)
|
25
|
+
loader = ::Cucumber::Runtime::FeaturesLoader.new(files, [], tag_expression)
|
26
|
+
|
27
|
+
loader.features.each do |feature|
|
28
|
+
feature.accept(scenario_line_logger)
|
29
|
+
end
|
30
|
+
|
31
|
+
scenario_line_logger.scenarios
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -19,7 +19,7 @@ module ParallelTests
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_file_name
|
22
|
-
"feature"
|
22
|
+
@test_file_name || "feature"
|
23
23
|
end
|
24
24
|
|
25
25
|
def test_suffix
|
@@ -37,7 +37,7 @@ module ParallelTests
|
|
37
37
|
sort_order = %w[scenario step failed undefined skipped pending passed]
|
38
38
|
|
39
39
|
%w[scenario step].map do |group|
|
40
|
-
group_results = results.grep
|
40
|
+
group_results = results.grep(/^\d+ #{group}/)
|
41
41
|
next if group_results.empty?
|
42
42
|
|
43
43
|
sums = sum_up_results(group_results)
|
@@ -67,8 +67,12 @@ module ParallelTests
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def tests_in_groups(tests, num_groups, options={})
|
70
|
-
if options[:group_by] == :
|
71
|
-
|
70
|
+
if options[:group_by] == :scenarios
|
71
|
+
@test_file_name = "scenario"
|
72
|
+
end
|
73
|
+
method = "by_#{options[:group_by]}"
|
74
|
+
if Grouper.respond_to?(method)
|
75
|
+
Grouper.send(method, find_tests(tests, options), num_groups, options)
|
72
76
|
else
|
73
77
|
super
|
74
78
|
end
|
@@ -85,14 +89,14 @@ module ParallelTests
|
|
85
89
|
|
86
90
|
def determine_executable
|
87
91
|
case
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
when File.exists?("bin/#{name}")
|
93
|
+
"bin/#{name}"
|
94
|
+
when ParallelTests.bundler_enabled?
|
95
|
+
"bundle exec #{name}"
|
96
|
+
when File.file?("script/#{name}")
|
97
|
+
"script/#{name}"
|
98
|
+
else
|
99
|
+
"#{name}"
|
96
100
|
end
|
97
101
|
end
|
98
102
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'parallel_tests/cucumber/scenarios'
|
2
|
+
|
1
3
|
module ParallelTests
|
2
4
|
class Grouper
|
3
5
|
class << self
|
@@ -6,22 +8,22 @@ module ParallelTests
|
|
6
8
|
in_even_groups_by_size(features_with_steps, num_groups)
|
7
9
|
end
|
8
10
|
|
9
|
-
def
|
11
|
+
def by_scenarios(tests, num_groups, options={})
|
12
|
+
scenarios = group_by_scenarios(tests, options)
|
13
|
+
in_even_groups_by_size(scenarios, num_groups)
|
14
|
+
end
|
15
|
+
|
16
|
+
def in_even_groups_by_size(items, num_groups, options= {})
|
10
17
|
groups = Array.new(num_groups) { {:items => [], :size => 0} }
|
11
18
|
|
12
19
|
# add all files that should run in a single process to one group
|
13
20
|
(options[:single_process] || []).each do |pattern|
|
14
|
-
matched,
|
21
|
+
matched, items = items.partition { |item, size| item =~ pattern }
|
15
22
|
matched.each { |item, size| add_to_group(groups.first, item, size) }
|
16
23
|
end
|
17
24
|
|
18
25
|
groups_to_fill = (options[:isolate] ? groups[1..-1] : groups)
|
19
|
-
|
20
|
-
# add all other files
|
21
|
-
largest_first(items_with_sizes).each do |item, size|
|
22
|
-
smallest = smallest_group(groups_to_fill)
|
23
|
-
add_to_group(smallest, item, size)
|
24
|
-
end
|
26
|
+
group_features_by_size(items_to_group(items), groups_to_fill)
|
25
27
|
|
26
28
|
groups.map!{|g| g[:items].sort }
|
27
29
|
end
|
@@ -51,6 +53,22 @@ module ParallelTests
|
|
51
53
|
}
|
52
54
|
listener.collect.sort_by{|_,value| -value }
|
53
55
|
end
|
56
|
+
|
57
|
+
def group_by_scenarios(tests, options={})
|
58
|
+
ParallelTests::Cucumber::Scenarios.all(tests, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def group_features_by_size(items, groups_to_fill)
|
62
|
+
items.each do |item, size|
|
63
|
+
size ||= 1
|
64
|
+
smallest = smallest_group(groups_to_fill)
|
65
|
+
add_to_group(smallest, item, size)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def items_to_group(items)
|
70
|
+
items.first && items.first.size == 2 ? largest_first(items) : items
|
71
|
+
end
|
54
72
|
end
|
55
73
|
end
|
56
74
|
end
|
data/spec/integration_spec.rb
CHANGED
@@ -304,6 +304,36 @@ cucumber features/fail1.feature:2 # Scenario: xxx
|
|
304
304
|
3 steps (2 failed, 1 passed)
|
305
305
|
"""
|
306
306
|
end
|
307
|
+
|
308
|
+
it "groups by scenario" do
|
309
|
+
write "features/long.feature", <<-EOS
|
310
|
+
Feature: xxx
|
311
|
+
Scenario: xxx
|
312
|
+
Given I print TEST_ENV_NUMBER
|
313
|
+
|
314
|
+
Scenario: xxx
|
315
|
+
Given I print TEST_ENV_NUMBER
|
316
|
+
|
317
|
+
Scenario Outline: xxx
|
318
|
+
Given I print TEST_ENV_NUMBER
|
319
|
+
|
320
|
+
Examples:
|
321
|
+
| num |
|
322
|
+
| one |
|
323
|
+
| two |
|
324
|
+
EOS
|
325
|
+
result = run_tests "features", :type => "cucumber", :add => "--group-by scenarios"
|
326
|
+
result.should include("2 processes for 4 scenarios")
|
327
|
+
end
|
328
|
+
|
329
|
+
it "groups by step" do
|
330
|
+
write "features/good1.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
|
331
|
+
write "features/good2.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
|
332
|
+
|
333
|
+
result = run_tests "features", :type => "cucumber", :add => '--group-by steps'
|
334
|
+
|
335
|
+
result.should include("2 processes for 2 features")
|
336
|
+
end
|
307
337
|
end
|
308
338
|
|
309
339
|
context "Spinach", :fails_on_ruby_187 => true do
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'parallel_tests/cucumber/scenarios'
|
3
|
+
|
4
|
+
module ParallelTests
|
5
|
+
module Cucumber
|
6
|
+
describe Scenarios do
|
7
|
+
describe '.all' do
|
8
|
+
context 'by default' do
|
9
|
+
let(:feature_file) do
|
10
|
+
Tempfile.new('grouper.feature').tap do |feature|
|
11
|
+
feature.write <<-EOS
|
12
|
+
Feature: Grouping by scenario
|
13
|
+
|
14
|
+
Scenario: First
|
15
|
+
Given I do nothing
|
16
|
+
|
17
|
+
Scenario: Second
|
18
|
+
Given I don't do anything
|
19
|
+
EOS
|
20
|
+
feature.rewind
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns all the scenarios' do
|
25
|
+
scenarios = Scenarios.all([feature_file.path])
|
26
|
+
scenarios.should eq %W(#{feature_file.path}:3 #{feature_file.path}:6)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with tags' do
|
31
|
+
let(:feature_file) do
|
32
|
+
Tempfile.new('grouper.feature').tap do |feature|
|
33
|
+
feature.write <<-EOS
|
34
|
+
Feature: Grouping by scenario
|
35
|
+
|
36
|
+
@wip
|
37
|
+
Scenario: First
|
38
|
+
Given I do nothing
|
39
|
+
|
40
|
+
Scenario: Second
|
41
|
+
Given I don't do anything
|
42
|
+
|
43
|
+
@ignore
|
44
|
+
Scenario: Third
|
45
|
+
Given I am ignored
|
46
|
+
EOS
|
47
|
+
feature.rewind
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'ignores those scenarios' do
|
52
|
+
scenarios = Scenarios.all([feature_file.path], :ignore_tag_pattern => '@ignore, @wip')
|
53
|
+
scenarios.should eq %W(#{feature_file.path}:7)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -49,4 +49,13 @@ describe ParallelTests::Grouper do
|
|
49
49
|
call(6).should == [["5"], ["4"], ["3"], ["2"], ["1"], []]
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
describe :by_scenarios do
|
54
|
+
let(:feature_file) { double 'file' }
|
55
|
+
|
56
|
+
it 'splits a feature into individual scenarios' do
|
57
|
+
ParallelTests::Cucumber::Scenarios.should_receive(:all).and_return({ 'feature_file:3' => 1 })
|
58
|
+
ParallelTests::Grouper.by_scenarios([feature_file], 1)
|
59
|
+
end
|
60
|
+
end
|
52
61
|
end
|
@@ -22,6 +22,15 @@ describe ParallelTests::Tasks do
|
|
22
22
|
args = {:count => 2, :pattern => "plain", :options => "-p default"}
|
23
23
|
ParallelTests::Tasks.parse_args(args).should == [2, "plain", "-p default"]
|
24
24
|
end
|
25
|
+
|
26
|
+
it "should return the count, pattern, and options" do
|
27
|
+
args = {
|
28
|
+
:count => 2,
|
29
|
+
:pattern => "plain",
|
30
|
+
:options => "-p default --group-by steps",
|
31
|
+
}
|
32
|
+
ParallelTests::Tasks.parse_args(args).should == [2, "plain", "-p default --group-by steps"]
|
33
|
+
end
|
25
34
|
end
|
26
35
|
|
27
36
|
describe ".rails_env" do
|
@@ -43,7 +43,7 @@ describe ParallelTests::Test::Runner do
|
|
43
43
|
|
44
44
|
it "does sort when not passed do_sort option" do
|
45
45
|
ParallelTests::Test::Runner.stub!(:tests_with_runtime).and_return([])
|
46
|
-
ParallelTests::Grouper.should_receive(:
|
46
|
+
ParallelTests::Grouper.should_receive(:group_features_by_size).and_return([])
|
47
47
|
call([], 1)
|
48
48
|
end
|
49
49
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parallel_tests
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.16.
|
4
|
+
version: 0.16.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Grosser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-11-
|
11
|
+
date: 2013-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parallel
|
@@ -50,6 +50,8 @@ files:
|
|
50
50
|
- lib/parallel_tests/cli.rb
|
51
51
|
- lib/parallel_tests/cucumber/failures_logger.rb
|
52
52
|
- lib/parallel_tests/cucumber/runner.rb
|
53
|
+
- lib/parallel_tests/cucumber/scenario_line_logger.rb
|
54
|
+
- lib/parallel_tests/cucumber/scenarios.rb
|
53
55
|
- lib/parallel_tests/gherkin/io.rb
|
54
56
|
- lib/parallel_tests/gherkin/listener.rb
|
55
57
|
- lib/parallel_tests/gherkin/runner.rb
|
@@ -71,6 +73,7 @@ files:
|
|
71
73
|
- spec/parallel_tests/cli_spec.rb
|
72
74
|
- spec/parallel_tests/cucumber/failure_logger_spec.rb
|
73
75
|
- spec/parallel_tests/cucumber/runner_spec.rb
|
76
|
+
- spec/parallel_tests/cucumber/scenarios_spec.rb
|
74
77
|
- spec/parallel_tests/gherkin/listener_spec.rb
|
75
78
|
- spec/parallel_tests/gherkin/runner_behaviour.rb
|
76
79
|
- spec/parallel_tests/grouper_spec.rb
|
@@ -104,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
107
|
version: '0'
|
105
108
|
requirements: []
|
106
109
|
rubyforge_project:
|
107
|
-
rubygems_version: 2.0.
|
110
|
+
rubygems_version: 2.0.3
|
108
111
|
signing_key:
|
109
112
|
specification_version: 4
|
110
113
|
summary: Run Test::Unit / RSpec / Cucumber / Spinach in parallel
|