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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Readme.md +7 -3
  3. data/bin/parallel_cucumber +5 -1
  4. data/bin/parallel_rspec +5 -1
  5. data/bin/parallel_spinach +5 -1
  6. data/bin/parallel_test +5 -2
  7. data/lib/parallel_tests.rb +0 -1
  8. metadata +3 -51
  9. data/.gitignore +0 -4
  10. data/.rspec +0 -2
  11. data/.travis.yml +0 -10
  12. data/Gemfile +0 -9
  13. data/Gemfile.lock +0 -53
  14. data/Rakefile +0 -10
  15. data/ReadmeRails2.md +0 -48
  16. data/lib/parallel_tests/cli.rb +0 -206
  17. data/lib/parallel_tests/cucumber/failures_logger.rb +0 -25
  18. data/lib/parallel_tests/cucumber/runner.rb +0 -37
  19. data/lib/parallel_tests/cucumber/scenario_line_logger.rb +0 -51
  20. data/lib/parallel_tests/cucumber/scenarios.rb +0 -34
  21. data/lib/parallel_tests/gherkin/io.rb +0 -41
  22. data/lib/parallel_tests/gherkin/listener.rb +0 -87
  23. data/lib/parallel_tests/gherkin/runner.rb +0 -116
  24. data/lib/parallel_tests/gherkin/runtime_logger.rb +0 -28
  25. data/lib/parallel_tests/grouper.rb +0 -73
  26. data/lib/parallel_tests/railtie.rb +0 -8
  27. data/lib/parallel_tests/rspec/failures_logger.rb +0 -54
  28. data/lib/parallel_tests/rspec/logger_base.rb +0 -55
  29. data/lib/parallel_tests/rspec/runner.rb +0 -73
  30. data/lib/parallel_tests/rspec/runtime_logger.rb +0 -59
  31. data/lib/parallel_tests/rspec/summary_logger.rb +0 -19
  32. data/lib/parallel_tests/spinach/runner.rb +0 -19
  33. data/lib/parallel_tests/tasks.rb +0 -157
  34. data/lib/parallel_tests/test/runner.rb +0 -186
  35. data/lib/parallel_tests/test/runtime_logger.rb +0 -98
  36. data/lib/parallel_tests/version.rb +0 -3
  37. data/parallel_tests.gemspec +0 -14
  38. data/spec/integration_spec.rb +0 -437
  39. data/spec/parallel_tests/cli_spec.rb +0 -149
  40. data/spec/parallel_tests/cucumber/failure_logger_spec.rb +0 -43
  41. data/spec/parallel_tests/cucumber/runner_spec.rb +0 -25
  42. data/spec/parallel_tests/cucumber/scenarios_spec.rb +0 -69
  43. data/spec/parallel_tests/gherkin/listener_spec.rb +0 -96
  44. data/spec/parallel_tests/gherkin/runner_behaviour.rb +0 -216
  45. data/spec/parallel_tests/grouper_spec.rb +0 -61
  46. data/spec/parallel_tests/rspec/failures_logger_spec.rb +0 -82
  47. data/spec/parallel_tests/rspec/logger_base_spec.rb +0 -35
  48. data/spec/parallel_tests/rspec/runner_spec.rb +0 -201
  49. data/spec/parallel_tests/rspec/runtime_logger_spec.rb +0 -131
  50. data/spec/parallel_tests/rspec/summary_logger_spec.rb +0 -37
  51. data/spec/parallel_tests/spinach/runner_spec.rb +0 -12
  52. data/spec/parallel_tests/tasks_spec.rb +0 -178
  53. data/spec/parallel_tests/test/runner_spec.rb +0 -407
  54. data/spec/parallel_tests/test/runtime_logger_spec.rb +0 -112
  55. data/spec/parallel_tests_spec.rb +0 -137
  56. 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