parallel_tests 0.13.3 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Gemfile.lock +8 -2
- data/Rakefile +5 -1
- data/Readme.md +5 -2
- data/bin/parallel_spinach +5 -0
- data/lib/parallel_tests.rb +40 -38
- data/lib/parallel_tests/cli.rb +2 -2
- data/lib/parallel_tests/cucumber/failures_logger.rb +2 -2
- data/lib/parallel_tests/cucumber/runner.rb +5 -88
- data/lib/parallel_tests/{cucumber → gherkin}/io.rb +1 -1
- data/lib/parallel_tests/{cucumber/gherkin_listener.rb → gherkin/listener.rb} +2 -2
- data/lib/parallel_tests/gherkin/runner.rb +102 -0
- data/lib/parallel_tests/{cucumber → gherkin}/runtime_logger.rb +2 -2
- data/lib/parallel_tests/grouper.rb +41 -41
- data/lib/parallel_tests/rspec/failures_logger.rb +1 -1
- data/lib/parallel_tests/rspec/runner.rb +50 -48
- data/lib/parallel_tests/spinach/runner.rb +19 -0
- data/lib/parallel_tests/tasks.rb +7 -3
- data/lib/parallel_tests/test/runner.rb +125 -123
- data/lib/parallel_tests/test/runtime_logger.rb +57 -53
- data/lib/parallel_tests/version.rb +1 -1
- data/parallel_tests.gemspec +2 -2
- data/spec/integration_spec.rb +61 -0
- data/spec/parallel_tests/cucumber/failure_logger_spec.rb +1 -1
- data/spec/parallel_tests/cucumber/runner_spec.rb +5 -172
- data/spec/parallel_tests/{cucumber/gherkin_listener_spec.rb → gherkin/listener_spec.rb} +3 -3
- data/spec/parallel_tests/gherkin/runner_behaviour.rb +177 -0
- data/spec/parallel_tests/rspec/{failure_logger_spec.rb → failures_logger_spec.rb} +0 -0
- data/spec/parallel_tests/spinach/runner_spec.rb +12 -0
- data/spec/parallel_tests/test/runtime_logger_spec.rb +1 -1
- data/spec/parallel_tests_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- metadata +16 -10
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
parallel_tests (0.
|
4
|
+
parallel_tests (0.14.0)
|
5
5
|
parallel
|
6
6
|
|
7
7
|
GEM
|
@@ -9,6 +9,7 @@ GEM
|
|
9
9
|
specs:
|
10
10
|
builder (3.0.0)
|
11
11
|
bump (0.3.8)
|
12
|
+
colorize (0.5.8)
|
12
13
|
cucumber (1.1.4)
|
13
14
|
builder (>= 2.1.2)
|
14
15
|
diff-lcs (>= 1.1.2)
|
@@ -20,9 +21,10 @@ GEM
|
|
20
21
|
json (>= 1.4.6)
|
21
22
|
gherkin (2.7.6-java)
|
22
23
|
json (>= 1.4.6)
|
24
|
+
gherkin-ruby (0.3.0)
|
23
25
|
json (1.7.5)
|
24
26
|
json (1.7.5-java)
|
25
|
-
parallel (0.
|
27
|
+
parallel (0.7.0)
|
26
28
|
rake (10.0.3)
|
27
29
|
rspec (2.13.0)
|
28
30
|
rspec-core (~> 2.13.0)
|
@@ -32,6 +34,9 @@ GEM
|
|
32
34
|
rspec-expectations (2.13.0)
|
33
35
|
diff-lcs (>= 1.1.3, < 2.0)
|
34
36
|
rspec-mocks (2.13.1)
|
37
|
+
spinach (0.8.3)
|
38
|
+
colorize (= 0.5.8)
|
39
|
+
gherkin-ruby (~> 0.3.0)
|
35
40
|
term-ansicolor (1.0.7)
|
36
41
|
test-unit (2.4.4)
|
37
42
|
|
@@ -45,4 +50,5 @@ DEPENDENCIES
|
|
45
50
|
parallel_tests!
|
46
51
|
rake
|
47
52
|
rspec (>= 2.4)
|
53
|
+
spinach
|
48
54
|
test-unit
|
data/Rakefile
CHANGED
data/Readme.md
CHANGED
@@ -9,7 +9,7 @@ Setup for Rails
|
|
9
9
|
[still using Rails 2?](https://github.com/grosser/parallel_tests/blob/master/ReadmeRails2.md)
|
10
10
|
|
11
11
|
### Install
|
12
|
-
If you use RSpec: ensure you
|
12
|
+
If you use RSpec: ensure you have >= 2.4
|
13
13
|
|
14
14
|
As gem
|
15
15
|
|
@@ -48,6 +48,7 @@ test:
|
|
48
48
|
rake parallel:test # Test::Unit
|
49
49
|
rake parallel:spec # RSpec
|
50
50
|
rake parallel:features # Cucumber
|
51
|
+
rake parallel:features-spinach # Spinach
|
51
52
|
|
52
53
|
rake parallel:test[1] --> force 1 CPU --> 86 seconds
|
53
54
|
rake parallel:test --> got 2 CPUs? --> 47 seconds
|
@@ -164,6 +165,7 @@ Setup for non-rails
|
|
164
165
|
parallel_test test/
|
165
166
|
parallel_rspec spec/
|
166
167
|
parallel_cucumber features/
|
168
|
+
parallel_spinach features/
|
167
169
|
|
168
170
|
- use ENV['TEST_ENV_NUMBER'] inside your tests to select separate db/memcache/etc.
|
169
171
|
- Only run selected files & folders:
|
@@ -183,7 +185,7 @@ Options are:
|
|
183
185
|
-i, --isolate Do not run any other tests in the group used by --single(-s)
|
184
186
|
-e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUM']
|
185
187
|
-o, --test-options '[OPTIONS]' execute test commands with those options
|
186
|
-
-t, --type [TYPE] test(default) / rspec / cucumber
|
188
|
+
-t, --type [TYPE] test(default) / rspec / cucumber / spinach
|
187
189
|
--serialize-stdout Serialize stdout output, nothing will be written until everything is done
|
188
190
|
--non-parallel execute same commands but do not in parallel, needs --exec
|
189
191
|
--no-symlinks Do not traverse symbolic links to find test files
|
@@ -286,6 +288,7 @@ inspired by [pivotal labs](http://pivotallabs.com/users/miked/blog/articles/849-
|
|
286
288
|
- [Iain Beeston](https://github.com/iainbeeston)
|
287
289
|
- [Alejandro Pulver](https://github.com/alepulver)
|
288
290
|
- [Felix Clack](https://github.com/felixclack)
|
291
|
+
- [Izaak Alpert](https://github.com/karlhungus)
|
289
292
|
|
290
293
|
[Michael Grosser](http://grosser.it)<br/>
|
291
294
|
michael@grosser.it<br/>
|
data/lib/parallel_tests.rb
CHANGED
@@ -8,52 +8,54 @@ module ParallelTests
|
|
8
8
|
autoload :VERSION, "parallel_tests/version"
|
9
9
|
autoload :Grouper, "parallel_tests/grouper"
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
class << self
|
12
|
+
def determine_number_of_processes(count)
|
13
|
+
[
|
14
|
+
count,
|
15
|
+
ENV["PARALLEL_TEST_PROCESSORS"],
|
16
|
+
Parallel.processor_count
|
17
|
+
].detect{|c| not c.to_s.strip.empty? }.to_i
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
# copied from http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile
|
21
|
+
def bundler_enabled?
|
22
|
+
return true if Object.const_defined?(:Bundler)
|
22
23
|
|
23
|
-
|
24
|
-
|
24
|
+
previous = nil
|
25
|
+
current = File.expand_path(Dir.pwd)
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
until !File.directory?(current) || current == previous
|
28
|
+
filename = File.join(current, "Gemfile")
|
29
|
+
return true if File.exists?(filename)
|
30
|
+
current, previous = File.expand_path("..", current), current
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
false
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
def first_process?
|
37
|
+
!ENV["TEST_ENV_NUMBER"] || ENV["TEST_ENV_NUMBER"].to_i == 0
|
38
|
+
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
def wait_for_other_processes_to_finish
|
41
|
+
return unless ENV["TEST_ENV_NUMBER"]
|
42
|
+
sleep 1 until number_of_running_processes <= 1
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
# Fun fact: this includes the current process if it's run via parallel_tests
|
46
|
+
def number_of_running_processes
|
47
|
+
result = `#{GREP_PROCESSES_COMMAND}`
|
48
|
+
raise "Could not grep for processes -> #{result}" if result.strip != "" && !$?.success?
|
49
|
+
result.split("\n").size
|
50
|
+
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
52
|
+
# real time even if someone messed with timecop in tests
|
53
|
+
def now
|
54
|
+
if Time.respond_to?(:now_without_mock_time) # Timecop
|
55
|
+
Time.now_without_mock_time
|
56
|
+
else
|
57
|
+
Time.now
|
58
|
+
end
|
57
59
|
end
|
58
60
|
end
|
59
61
|
end
|
data/lib/parallel_tests/cli.rb
CHANGED
@@ -96,7 +96,7 @@ BANNER
|
|
96
96
|
opts.on("--group-by [TYPE]", <<-TEXT
|
97
97
|
group tests by:
|
98
98
|
found - order of finding files
|
99
|
-
steps - number of cucumber steps
|
99
|
+
steps - number of cucumber/spinach steps
|
100
100
|
default - runtime or filesize
|
101
101
|
TEXT
|
102
102
|
) { |type| options[:group_by] = type.to_sym }
|
@@ -117,7 +117,7 @@ TEXT
|
|
117
117
|
|
118
118
|
opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUM']") { |path| options[:execute] = path }
|
119
119
|
opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = arg }
|
120
|
-
opts.on("-t", "--type [TYPE]", "test(default) / rspec / cucumber") do |type|
|
120
|
+
opts.on("-t", "--type [TYPE]", "test(default) / rspec / cucumber / spinach") do |type|
|
121
121
|
begin
|
122
122
|
@runner = load_runner(type)
|
123
123
|
rescue NameError, LoadError => e
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'cucumber/formatter/rerun'
|
2
|
-
require 'parallel_tests/
|
2
|
+
require 'parallel_tests/gherkin/io'
|
3
3
|
|
4
4
|
module ParallelTests
|
5
5
|
module Cucumber
|
6
6
|
class FailuresLogger < ::Cucumber::Formatter::Rerun
|
7
|
-
include Io
|
7
|
+
include ParallelTests::Gherkin::Io
|
8
8
|
|
9
9
|
def initialize(runtime, path_or_io, options)
|
10
10
|
@io = prepare_io(path_or_io)
|
@@ -1,94 +1,11 @@
|
|
1
|
-
require "parallel_tests/
|
2
|
-
require 'shellwords'
|
1
|
+
require "parallel_tests/gherkin/runner"
|
3
2
|
|
4
3
|
module ParallelTests
|
5
4
|
module Cucumber
|
6
|
-
class Runner < ParallelTests::
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
sanitized_test_files = test_files.map { |val| Shellwords.escape(val) }
|
11
|
-
options = options.merge(:env => {"AUTOTEST" => "1"}) if $stdout.tty? # display color when we are in a terminal
|
12
|
-
runtime_logging = " --format ParallelTests::Cucumber::RuntimeLogger --out #{runtime_log}"
|
13
|
-
cmd = [
|
14
|
-
executable,
|
15
|
-
(runtime_logging if File.directory?(File.dirname(runtime_log))),
|
16
|
-
cucumber_opts(options[:test_options]),
|
17
|
-
*sanitized_test_files
|
18
|
-
].compact.join(" ")
|
19
|
-
execute_command(cmd, process_number, num_processes, options)
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.determine_executable
|
23
|
-
case
|
24
|
-
when File.exists?("bin/cucumber")
|
25
|
-
"bin/cucumber"
|
26
|
-
when ParallelTests.bundler_enabled?
|
27
|
-
"bundle exec cucumber"
|
28
|
-
when File.file?("script/cucumber")
|
29
|
-
"script/cucumber"
|
30
|
-
else
|
31
|
-
"cucumber"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.runtime_log
|
36
|
-
'tmp/parallel_runtime_cucumber.log'
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.test_file_name
|
40
|
-
"feature"
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.test_suffix
|
44
|
-
".feature"
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.line_is_result?(line)
|
48
|
-
line =~ /^\d+ (steps?|scenarios?)/
|
49
|
-
end
|
50
|
-
|
51
|
-
# cucumber has 2 result lines per test run, that cannot be added
|
52
|
-
# 1 scenario (1 failed)
|
53
|
-
# 1 step (1 failed)
|
54
|
-
def self.summarize_results(results)
|
55
|
-
sort_order = %w[scenario step failed undefined skipped pending passed]
|
56
|
-
|
57
|
-
%w[scenario step].map do |group|
|
58
|
-
group_results = results.grep /^\d+ #{group}/
|
59
|
-
next if group_results.empty?
|
60
|
-
|
61
|
-
sums = sum_up_results(group_results)
|
62
|
-
sums = sums.sort_by { |word, _| sort_order.index(word) || 999 }
|
63
|
-
sums.map! do |word, number|
|
64
|
-
plural = "s" if word == group and number != 1
|
65
|
-
"#{number} #{word}#{plural}"
|
66
|
-
end
|
67
|
-
"#{sums[0]} (#{sums[1..-1].join(", ")})"
|
68
|
-
end.compact.join("\n")
|
69
|
-
end
|
70
|
-
|
71
|
-
def self.cucumber_opts(given)
|
72
|
-
if given =~ /--profile/ or given =~ /(^|\s)-p /
|
73
|
-
given
|
74
|
-
else
|
75
|
-
[given, profile_from_config].compact.join(" ")
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.profile_from_config
|
80
|
-
# copied from https://github.com/cucumber/cucumber/blob/master/lib/cucumber/cli/profile_loader.rb#L85
|
81
|
-
config = Dir.glob('{,.config/,config/}cucumber{.yml,.yaml}').first
|
82
|
-
if config && File.read(config) =~ /^parallel:/
|
83
|
-
"--profile parallel"
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def self.tests_in_groups(tests, num_groups, options={})
|
88
|
-
if options[:group_by] == :steps
|
89
|
-
Grouper.by_steps(find_tests(tests, options), num_groups, options)
|
90
|
-
else
|
91
|
-
super
|
5
|
+
class Runner < ParallelTests::Gherkin::Runner
|
6
|
+
class << self
|
7
|
+
def name
|
8
|
+
'cucumber'
|
92
9
|
end
|
93
10
|
end
|
94
11
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require "parallel_tests/test/runner"
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module ParallelTests
|
5
|
+
module Gherkin
|
6
|
+
class Runner < ParallelTests::Test::Runner
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def run_tests(test_files, process_number, num_processes, options)
|
10
|
+
sanitized_test_files = test_files.map { |val| Shellwords.escape(val) }
|
11
|
+
options = options.merge(:env => {"AUTOTEST" => "1"}) if $stdout.tty? # display color when we are in a terminal
|
12
|
+
cmd = [
|
13
|
+
executable,
|
14
|
+
(runtime_logging if File.directory?(File.dirname(runtime_log))),
|
15
|
+
cucumber_opts(options[:test_options]),
|
16
|
+
*sanitized_test_files
|
17
|
+
].compact.join(" ")
|
18
|
+
execute_command(cmd, process_number, num_processes, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_file_name
|
22
|
+
"feature"
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_suffix
|
26
|
+
".feature"
|
27
|
+
end
|
28
|
+
|
29
|
+
def line_is_result?(line)
|
30
|
+
line =~ /^\d+ (steps?|scenarios?)/
|
31
|
+
end
|
32
|
+
|
33
|
+
# cucumber has 2 result lines per test run, that cannot be added
|
34
|
+
# 1 scenario (1 failed)
|
35
|
+
# 1 step (1 failed)
|
36
|
+
def summarize_results(results)
|
37
|
+
sort_order = %w[scenario step failed undefined skipped pending passed]
|
38
|
+
|
39
|
+
%w[scenario step].map do |group|
|
40
|
+
group_results = results.grep /^\d+ #{group}/
|
41
|
+
next if group_results.empty?
|
42
|
+
|
43
|
+
sums = sum_up_results(group_results)
|
44
|
+
sums = sums.sort_by { |word, _| sort_order.index(word) || 999 }
|
45
|
+
sums.map! do |word, number|
|
46
|
+
plural = "s" if word == group and number != 1
|
47
|
+
"#{number} #{word}#{plural}"
|
48
|
+
end
|
49
|
+
"#{sums[0]} (#{sums[1..-1].join(", ")})"
|
50
|
+
end.compact.join("\n")
|
51
|
+
end
|
52
|
+
|
53
|
+
def cucumber_opts(given)
|
54
|
+
if given =~ /--profile/ or given =~ /(^|\s)-p /
|
55
|
+
given
|
56
|
+
else
|
57
|
+
[given, profile_from_config].compact.join(" ")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def profile_from_config
|
62
|
+
# copied from https://github.com/cucumber/cucumber/blob/master/lib/cucumber/cli/profile_loader.rb#L85
|
63
|
+
config = Dir.glob("{,.config/,config/}#{name}{.yml,.yaml}").first
|
64
|
+
if config && File.read(config) =~ /^parallel:/
|
65
|
+
"--profile parallel"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def tests_in_groups(tests, num_groups, options={})
|
70
|
+
if options[:group_by] == :steps
|
71
|
+
Grouper.by_steps(find_tests(tests, options), num_groups, options)
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def runtime_logging
|
79
|
+
" --format ParallelTests::Gherkin::RuntimeLogger --out #{runtime_log}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def runtime_log
|
83
|
+
"tmp/parallel_runtime_#{name}.log"
|
84
|
+
end
|
85
|
+
|
86
|
+
def determine_executable
|
87
|
+
case
|
88
|
+
when File.exists?("bin/#{name}")
|
89
|
+
"bin/#{name}"
|
90
|
+
when ParallelTests.bundler_enabled?
|
91
|
+
"bundle exec #{name}"
|
92
|
+
when File.file?("script/#{name}")
|
93
|
+
"script/#{name}"
|
94
|
+
else
|
95
|
+
"#{name}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|