parallel_tests 1.0.9 → 1.1.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.
- checksums.yaml +4 -4
- data/Readme.md +7 -3
- data/bin/parallel_cucumber +5 -1
- data/bin/parallel_rspec +5 -1
- data/bin/parallel_spinach +5 -1
- data/bin/parallel_test +5 -2
- data/lib/parallel_tests.rb +0 -1
- metadata +3 -51
- data/.gitignore +0 -4
- data/.rspec +0 -2
- data/.travis.yml +0 -10
- data/Gemfile +0 -9
- data/Gemfile.lock +0 -53
- data/Rakefile +0 -10
- data/ReadmeRails2.md +0 -48
- data/lib/parallel_tests/cli.rb +0 -206
- data/lib/parallel_tests/cucumber/failures_logger.rb +0 -25
- data/lib/parallel_tests/cucumber/runner.rb +0 -37
- data/lib/parallel_tests/cucumber/scenario_line_logger.rb +0 -51
- data/lib/parallel_tests/cucumber/scenarios.rb +0 -34
- data/lib/parallel_tests/gherkin/io.rb +0 -41
- data/lib/parallel_tests/gherkin/listener.rb +0 -87
- data/lib/parallel_tests/gherkin/runner.rb +0 -116
- data/lib/parallel_tests/gherkin/runtime_logger.rb +0 -28
- data/lib/parallel_tests/grouper.rb +0 -73
- data/lib/parallel_tests/railtie.rb +0 -8
- data/lib/parallel_tests/rspec/failures_logger.rb +0 -54
- data/lib/parallel_tests/rspec/logger_base.rb +0 -55
- data/lib/parallel_tests/rspec/runner.rb +0 -73
- data/lib/parallel_tests/rspec/runtime_logger.rb +0 -59
- data/lib/parallel_tests/rspec/summary_logger.rb +0 -19
- data/lib/parallel_tests/spinach/runner.rb +0 -19
- data/lib/parallel_tests/tasks.rb +0 -157
- data/lib/parallel_tests/test/runner.rb +0 -186
- data/lib/parallel_tests/test/runtime_logger.rb +0 -98
- data/lib/parallel_tests/version.rb +0 -3
- data/parallel_tests.gemspec +0 -14
- data/spec/integration_spec.rb +0 -437
- data/spec/parallel_tests/cli_spec.rb +0 -149
- data/spec/parallel_tests/cucumber/failure_logger_spec.rb +0 -43
- data/spec/parallel_tests/cucumber/runner_spec.rb +0 -25
- data/spec/parallel_tests/cucumber/scenarios_spec.rb +0 -69
- data/spec/parallel_tests/gherkin/listener_spec.rb +0 -96
- data/spec/parallel_tests/gherkin/runner_behaviour.rb +0 -216
- data/spec/parallel_tests/grouper_spec.rb +0 -61
- data/spec/parallel_tests/rspec/failures_logger_spec.rb +0 -82
- data/spec/parallel_tests/rspec/logger_base_spec.rb +0 -35
- data/spec/parallel_tests/rspec/runner_spec.rb +0 -201
- data/spec/parallel_tests/rspec/runtime_logger_spec.rb +0 -131
- data/spec/parallel_tests/rspec/summary_logger_spec.rb +0 -37
- data/spec/parallel_tests/spinach/runner_spec.rb +0 -12
- data/spec/parallel_tests/tasks_spec.rb +0 -178
- data/spec/parallel_tests/test/runner_spec.rb +0 -407
- data/spec/parallel_tests/test/runtime_logger_spec.rb +0 -112
- data/spec/parallel_tests_spec.rb +0 -137
- data/spec/spec_helper.rb +0 -182
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'cucumber/formatter/rerun'
|
2
|
-
require 'parallel_tests/gherkin/io'
|
3
|
-
|
4
|
-
module ParallelTests
|
5
|
-
module Cucumber
|
6
|
-
class FailuresLogger < ::Cucumber::Formatter::Rerun
|
7
|
-
include ParallelTests::Gherkin::Io
|
8
|
-
|
9
|
-
def initialize(runtime, path_or_io, options)
|
10
|
-
@io = prepare_io(path_or_io)
|
11
|
-
end
|
12
|
-
|
13
|
-
def after_feature(feature)
|
14
|
-
unless @lines.empty?
|
15
|
-
lock_output do
|
16
|
-
@lines.each do |line|
|
17
|
-
@io.puts "#{feature.file}:#{line}"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
require "parallel_tests/gherkin/runner"
|
2
|
-
|
3
|
-
module ParallelTests
|
4
|
-
module Cucumber
|
5
|
-
class Runner < ParallelTests::Gherkin::Runner
|
6
|
-
class << self
|
7
|
-
def name
|
8
|
-
'cucumber'
|
9
|
-
end
|
10
|
-
|
11
|
-
def line_is_result?(line)
|
12
|
-
super or line =~ failing_scenario_regex
|
13
|
-
end
|
14
|
-
|
15
|
-
def summarize_results(results)
|
16
|
-
output = []
|
17
|
-
|
18
|
-
failing_scenarios = results.grep(failing_scenario_regex)
|
19
|
-
if failing_scenarios.any?
|
20
|
-
failing_scenarios.unshift("Failing Scenarios:")
|
21
|
-
output << failing_scenarios.join("\n")
|
22
|
-
end
|
23
|
-
|
24
|
-
output << super
|
25
|
-
|
26
|
-
output.join("\n\n")
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def failing_scenario_regex
|
32
|
-
/^cucumber features\/.+:\d+/
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,51 +0,0 @@
|
|
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
|
@@ -1,34 +0,0 @@
|
|
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
|
-
tags = []
|
13
|
-
tags.concat options[:ignore_tag_pattern].to_s.split(/\s*,\s*/).map {|tag| "~#{tag}" }
|
14
|
-
tags.concat options[:test_options].to_s.scan(/(?:-t|--tags) (~?@[\w,~@]+)/).flatten
|
15
|
-
split_into_scenarios files, tags.uniq
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def split_into_scenarios(files, tags=[])
|
21
|
-
tag_expression = ::Gherkin::TagExpression.new(tags)
|
22
|
-
scenario_line_logger = ParallelTests::Cucumber::Formatters::ScenarioLineLogger.new(tag_expression)
|
23
|
-
loader = ::Cucumber::Runtime::FeaturesLoader.new(files, [], tag_expression)
|
24
|
-
|
25
|
-
loader.features.each do |feature|
|
26
|
-
feature.accept(scenario_line_logger)
|
27
|
-
end
|
28
|
-
|
29
|
-
scenario_line_logger.scenarios
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'parallel_tests'
|
2
|
-
|
3
|
-
module ParallelTests
|
4
|
-
module Gherkin
|
5
|
-
module Io
|
6
|
-
|
7
|
-
def prepare_io(path_or_io)
|
8
|
-
if path_or_io.respond_to?(:write)
|
9
|
-
path_or_io
|
10
|
-
else # its a path
|
11
|
-
File.open(path_or_io, 'w').close # clean out the file
|
12
|
-
file = File.open(path_or_io, 'a')
|
13
|
-
|
14
|
-
at_exit do
|
15
|
-
unless file.closed?
|
16
|
-
file.flush
|
17
|
-
file.close
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
file
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
# do not let multiple processes get in each others way
|
26
|
-
def lock_output
|
27
|
-
if File === @io
|
28
|
-
begin
|
29
|
-
@io.flock File::LOCK_EX
|
30
|
-
yield
|
31
|
-
ensure
|
32
|
-
@io.flock File::LOCK_UN
|
33
|
-
end
|
34
|
-
else
|
35
|
-
yield
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
require 'gherkin'
|
2
|
-
|
3
|
-
module ParallelTests
|
4
|
-
module Gherkin
|
5
|
-
class Listener
|
6
|
-
attr_reader :collect
|
7
|
-
|
8
|
-
attr_writer :ignore_tag_pattern
|
9
|
-
|
10
|
-
def initialize
|
11
|
-
@steps, @uris = [], []
|
12
|
-
@collect = {}
|
13
|
-
reset_counters!
|
14
|
-
end
|
15
|
-
|
16
|
-
def feature(feature)
|
17
|
-
@feature = feature
|
18
|
-
end
|
19
|
-
|
20
|
-
def background(*args)
|
21
|
-
@background = 1
|
22
|
-
end
|
23
|
-
|
24
|
-
def scenario(scenario)
|
25
|
-
@outline = @background = 0
|
26
|
-
return if should_ignore(scenario)
|
27
|
-
@scenarios += 1
|
28
|
-
end
|
29
|
-
|
30
|
-
def scenario_outline(outline)
|
31
|
-
return if should_ignore(outline)
|
32
|
-
@outline = 1
|
33
|
-
end
|
34
|
-
|
35
|
-
def step(*args)
|
36
|
-
return if @ignoring
|
37
|
-
if @background == 1
|
38
|
-
@background_steps += 1
|
39
|
-
elsif @outline > 0
|
40
|
-
@outline_steps += 1
|
41
|
-
else
|
42
|
-
@collect[@uri] += 1
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def uri(path)
|
47
|
-
@uri = path
|
48
|
-
@collect[@uri] = 0
|
49
|
-
end
|
50
|
-
|
51
|
-
#
|
52
|
-
# @param [Gherkin::Formatter::Model::Examples] examples
|
53
|
-
#
|
54
|
-
def examples(examples)
|
55
|
-
if examples.rows.size > 0
|
56
|
-
@collect[@uri] += (@outline_steps * examples.rows.size)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def eof(*args)
|
61
|
-
@collect[@uri] += (@background_steps * @scenarios)
|
62
|
-
reset_counters!
|
63
|
-
end
|
64
|
-
|
65
|
-
def reset_counters!
|
66
|
-
@outline = @outline_steps = @background = @background_steps = @scenarios = 0
|
67
|
-
@ignoring = nil
|
68
|
-
end
|
69
|
-
|
70
|
-
# ignore lots of other possible callbacks ...
|
71
|
-
def method_missing(*args)
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
# Return a combination of tags declared on this scenario/outline and the feature it belongs to
|
77
|
-
def all_tags(scenario)
|
78
|
-
(scenario.tags || []) + ((@feature && @feature.tags) || [])
|
79
|
-
end
|
80
|
-
|
81
|
-
# Set @ignoring if we should ignore this scenario/outline based on its tags
|
82
|
-
def should_ignore(scenario)
|
83
|
-
@ignoring = @ignore_tag_pattern && all_tags(scenario).find{ |tag| @ignore_tag_pattern === tag.name }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
@@ -1,116 +0,0 @@
|
|
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
|
-
combined_scenarios = test_files
|
11
|
-
|
12
|
-
if options[:group_by] == :scenarios
|
13
|
-
grouped = test_files.map { |t| t.split(':') }.group_by(&:first)
|
14
|
-
combined_scenarios = grouped.map {|file,files_and_lines| "#{file}:#{files_and_lines.map(&:last).join(':')}" }
|
15
|
-
end
|
16
|
-
|
17
|
-
sanitized_test_files = combined_scenarios.map { |val| WINDOWS ? "\"#{val}\"" : Shellwords.escape(val) }
|
18
|
-
|
19
|
-
options[:env] ||= {}
|
20
|
-
options[:env] = options[:env].merge({'AUTOTEST' => '1'}) if $stdout.tty? # display color when we are in a terminal
|
21
|
-
|
22
|
-
cmd = [
|
23
|
-
executable,
|
24
|
-
(runtime_logging if File.directory?(File.dirname(runtime_log))),
|
25
|
-
cucumber_opts(options[:test_options]),
|
26
|
-
*sanitized_test_files
|
27
|
-
].compact.join(' ')
|
28
|
-
execute_command(cmd, process_number, num_processes, options)
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_file_name
|
32
|
-
@test_file_name || 'feature'
|
33
|
-
end
|
34
|
-
|
35
|
-
def test_suffix
|
36
|
-
/\.feature$/
|
37
|
-
end
|
38
|
-
|
39
|
-
def line_is_result?(line)
|
40
|
-
line =~ /^\d+ (steps?|scenarios?)/
|
41
|
-
end
|
42
|
-
|
43
|
-
# cucumber has 2 result lines per test run, that cannot be added
|
44
|
-
# 1 scenario (1 failed)
|
45
|
-
# 1 step (1 failed)
|
46
|
-
def summarize_results(results)
|
47
|
-
sort_order = %w[scenario step failed undefined skipped pending passed]
|
48
|
-
|
49
|
-
%w[scenario step].map do |group|
|
50
|
-
group_results = results.grep(/^\d+ #{group}/)
|
51
|
-
next if group_results.empty?
|
52
|
-
|
53
|
-
sums = sum_up_results(group_results)
|
54
|
-
sums = sums.sort_by { |word, _| sort_order.index(word) || 999 }
|
55
|
-
sums.map! do |word, number|
|
56
|
-
plural = "s" if word == group and number != 1
|
57
|
-
"#{number} #{word}#{plural}"
|
58
|
-
end
|
59
|
-
"#{sums[0]} (#{sums[1..-1].join(", ")})"
|
60
|
-
end.compact.join("\n")
|
61
|
-
end
|
62
|
-
|
63
|
-
def cucumber_opts(given)
|
64
|
-
if given =~ /--profile/ or given =~ /(^|\s)-p /
|
65
|
-
given
|
66
|
-
else
|
67
|
-
[given, profile_from_config].compact.join(" ")
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def profile_from_config
|
72
|
-
# copied from https://github.com/cucumber/cucumber/blob/master/lib/cucumber/cli/profile_loader.rb#L85
|
73
|
-
config = Dir.glob("{,.config/,config/}#{name}{.yml,.yaml}").first
|
74
|
-
if config && File.read(config) =~ /^parallel:/
|
75
|
-
"--profile parallel"
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def tests_in_groups(tests, num_groups, options={})
|
80
|
-
if options[:group_by] == :scenarios
|
81
|
-
@test_file_name = "scenario"
|
82
|
-
end
|
83
|
-
method = "by_#{options[:group_by]}"
|
84
|
-
if Grouper.respond_to?(method)
|
85
|
-
Grouper.send(method, find_tests(tests, options), num_groups, options)
|
86
|
-
else
|
87
|
-
super
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
def runtime_logging
|
93
|
-
" --format ParallelTests::Gherkin::RuntimeLogger --out #{runtime_log}"
|
94
|
-
end
|
95
|
-
|
96
|
-
def runtime_log
|
97
|
-
"tmp/parallel_runtime_#{name}.log"
|
98
|
-
end
|
99
|
-
|
100
|
-
def determine_executable
|
101
|
-
case
|
102
|
-
when File.exists?("bin/#{name}")
|
103
|
-
"bin/#{name}"
|
104
|
-
when ParallelTests.bundler_enabled?
|
105
|
-
"bundle exec #{name}"
|
106
|
-
when File.file?("script/#{name}")
|
107
|
-
"script/#{name}"
|
108
|
-
else
|
109
|
-
"#{name}"
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require 'parallel_tests/gherkin/io'
|
2
|
-
|
3
|
-
module ParallelTests
|
4
|
-
module Gherkin
|
5
|
-
class RuntimeLogger
|
6
|
-
include Io
|
7
|
-
|
8
|
-
def initialize(step_mother, path_or_io, options=nil)
|
9
|
-
@io = prepare_io(path_or_io)
|
10
|
-
@example_times = Hash.new(0)
|
11
|
-
end
|
12
|
-
|
13
|
-
def before_feature(_)
|
14
|
-
@start_at = ParallelTests.now.to_f
|
15
|
-
end
|
16
|
-
|
17
|
-
def after_feature(feature)
|
18
|
-
@example_times[feature.file] += ParallelTests.now.to_f - @start_at
|
19
|
-
end
|
20
|
-
|
21
|
-
def after_features(*args)
|
22
|
-
lock_output do
|
23
|
-
@io.puts @example_times.map { |file, time| "#{file}:#{time}" }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
module ParallelTests
|
2
|
-
class Grouper
|
3
|
-
class << self
|
4
|
-
def by_steps(tests, num_groups, options)
|
5
|
-
features_with_steps = build_features_with_steps(tests, options)
|
6
|
-
in_even_groups_by_size(features_with_steps, num_groups)
|
7
|
-
end
|
8
|
-
|
9
|
-
def by_scenarios(tests, num_groups, options={})
|
10
|
-
scenarios = group_by_scenarios(tests, options)
|
11
|
-
in_even_groups_by_size(scenarios, num_groups)
|
12
|
-
end
|
13
|
-
|
14
|
-
def in_even_groups_by_size(items, num_groups, options= {})
|
15
|
-
groups = Array.new(num_groups) { {:items => [], :size => 0} }
|
16
|
-
|
17
|
-
# add all files that should run in a single process to one group
|
18
|
-
(options[:single_process] || []).each do |pattern|
|
19
|
-
matched, items = items.partition { |item, size| item =~ pattern }
|
20
|
-
matched.each { |item, size| add_to_group(groups.first, item, size) }
|
21
|
-
end
|
22
|
-
|
23
|
-
groups_to_fill = (options[:isolate] ? groups[1..-1] : groups)
|
24
|
-
group_features_by_size(items_to_group(items), groups_to_fill)
|
25
|
-
|
26
|
-
groups.map!{|g| g[:items].sort }
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def largest_first(files)
|
32
|
-
files.sort_by{|item, size| size }.reverse
|
33
|
-
end
|
34
|
-
|
35
|
-
def smallest_group(groups)
|
36
|
-
groups.min_by{|g| g[:size] }
|
37
|
-
end
|
38
|
-
|
39
|
-
def add_to_group(group, item, size)
|
40
|
-
group[:items] << item
|
41
|
-
group[:size] += size
|
42
|
-
end
|
43
|
-
|
44
|
-
def build_features_with_steps(tests, options)
|
45
|
-
require 'parallel_tests/gherkin/listener'
|
46
|
-
listener = ParallelTests::Gherkin::Listener.new
|
47
|
-
listener.ignore_tag_pattern = Regexp.compile(options[:ignore_tag_pattern]) if options[:ignore_tag_pattern]
|
48
|
-
parser = ::Gherkin::Parser::Parser.new(listener, true, 'root')
|
49
|
-
tests.each{|file|
|
50
|
-
parser.parse(File.read(file), file, 0)
|
51
|
-
}
|
52
|
-
listener.collect.sort_by{|_,value| -value }
|
53
|
-
end
|
54
|
-
|
55
|
-
def group_by_scenarios(tests, options={})
|
56
|
-
require 'parallel_tests/cucumber/scenarios'
|
57
|
-
ParallelTests::Cucumber::Scenarios.all(tests, options)
|
58
|
-
end
|
59
|
-
|
60
|
-
def group_features_by_size(items, groups_to_fill)
|
61
|
-
items.each do |item, size|
|
62
|
-
size ||= 1
|
63
|
-
smallest = smallest_group(groups_to_fill)
|
64
|
-
add_to_group(smallest, item, size)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def items_to_group(items)
|
69
|
-
items.first && items.first.size == 2 ? largest_first(items) : items
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|