parallel_tests 1.0.9 → 1.1.0

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