parallel_tests 3.2.0 → 3.5.2
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 +26 -8
 - data/bin/parallel_cucumber +2 -1
 - data/bin/parallel_rspec +2 -1
 - data/bin/parallel_spinach +2 -1
 - data/bin/parallel_test +2 -1
 - data/lib/parallel_tests.rb +4 -2
 - data/lib/parallel_tests/cli.rb +90 -47
 - data/lib/parallel_tests/cucumber/failures_logger.rb +1 -1
 - data/lib/parallel_tests/cucumber/features_with_steps.rb +4 -3
 - data/lib/parallel_tests/cucumber/runner.rb +4 -5
 - data/lib/parallel_tests/cucumber/scenario_line_logger.rb +3 -3
 - data/lib/parallel_tests/cucumber/scenarios.rb +5 -5
 - data/lib/parallel_tests/gherkin/io.rb +2 -3
 - data/lib/parallel_tests/gherkin/listener.rb +9 -10
 - data/lib/parallel_tests/gherkin/runner.rb +16 -21
 - data/lib/parallel_tests/gherkin/runtime_logger.rb +2 -1
 - data/lib/parallel_tests/grouper.rb +88 -10
 - data/lib/parallel_tests/pids.rb +3 -2
 - data/lib/parallel_tests/railtie.rb +1 -0
 - data/lib/parallel_tests/rspec/failures_logger.rb +2 -2
 - data/lib/parallel_tests/rspec/logger_base.rb +9 -7
 - data/lib/parallel_tests/rspec/runner.rb +21 -5
 - data/lib/parallel_tests/rspec/runtime_logger.rb +12 -10
 - data/lib/parallel_tests/rspec/summary_logger.rb +2 -3
 - data/lib/parallel_tests/spinach/runner.rb +2 -2
 - data/lib/parallel_tests/tasks.rb +44 -30
 - data/lib/parallel_tests/test/runner.rb +32 -27
 - data/lib/parallel_tests/test/runtime_logger.rb +19 -14
 - data/lib/parallel_tests/version.rb +2 -1
 - metadata +4 -4
 
| 
         @@ -1,10 +1,11 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            require "parallel_tests/gherkin/runner"
         
     | 
| 
       2 
3 
     | 
    
         | 
| 
       3 
4 
     | 
    
         
             
            module ParallelTests
         
     | 
| 
       4 
5 
     | 
    
         
             
              module Cucumber
         
     | 
| 
       5 
6 
     | 
    
         
             
                class Runner < ParallelTests::Gherkin::Runner
         
     | 
| 
       6 
     | 
    
         
            -
                  SCENARIOS_RESULTS_BOUNDARY_REGEX = /^(Failing|Flaky) Scenarios 
     | 
| 
       7 
     | 
    
         
            -
                  SCENARIO_REGEX =  
     | 
| 
      
 7 
     | 
    
         
            +
                  SCENARIOS_RESULTS_BOUNDARY_REGEX = /^(Failing|Flaky) Scenarios:$/.freeze
         
     | 
| 
      
 8 
     | 
    
         
            +
                  SCENARIO_REGEX = %r{^cucumber features/.+:\d+}.freeze
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
       9 
10 
     | 
    
         
             
                  class << self
         
     | 
| 
       10 
11 
     | 
    
         
             
                    def name
         
     | 
| 
         @@ -21,9 +22,7 @@ module ParallelTests 
     | 
|
| 
       21 
22 
     | 
    
         
             
                      scenario_groups = results.slice_before(SCENARIOS_RESULTS_BOUNDARY_REGEX).group_by(&:first)
         
     | 
| 
       22 
23 
     | 
    
         
             
                      scenario_groups.each do |header, group|
         
     | 
| 
       23 
24 
     | 
    
         
             
                        scenarios = group.flatten.grep(SCENARIO_REGEX)
         
     | 
| 
       24 
     | 
    
         
            -
                        if scenarios.any?
         
     | 
| 
       25 
     | 
    
         
            -
                          output << ([header] + scenarios).join("\n")
         
     | 
| 
       26 
     | 
    
         
            -
                        end
         
     | 
| 
      
 25 
     | 
    
         
            +
                        output << ([header] + scenarios).join("\n") if scenarios.any?
         
     | 
| 
       27 
26 
     | 
    
         
             
                      end
         
     | 
| 
       28 
27 
     | 
    
         | 
| 
       29 
28 
     | 
    
         
             
                      output << super
         
     | 
| 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module ParallelTests
         
     | 
| 
       2 
3 
     | 
    
         
             
              module Cucumber
         
     | 
| 
       3 
4 
     | 
    
         
             
                module Formatters
         
     | 
| 
         @@ -10,7 +11,7 @@ module ParallelTests 
     | 
|
| 
       10 
11 
     | 
    
         
             
                    end
         
     | 
| 
       11 
12 
     | 
    
         | 
| 
       12 
13 
     | 
    
         
             
                    def visit_feature_element(uri, feature_element, feature_tags, line_numbers: [])
         
     | 
| 
       13 
     | 
    
         
            -
                      scenario_tags = feature_element.tags.map 
     | 
| 
      
 14 
     | 
    
         
            +
                      scenario_tags = feature_element.tags.map(&:name)
         
     | 
| 
       14 
15 
     | 
    
         
             
                      scenario_tags = feature_tags + scenario_tags
         
     | 
| 
       15 
16 
     | 
    
         
             
                      if feature_element.is_a?(CukeModeler::Scenario) # :Scenario
         
     | 
| 
       16 
17 
     | 
    
         
             
                        test_line = feature_element.source_line
         
     | 
| 
         @@ -36,8 +37,7 @@ module ParallelTests 
     | 
|
| 
       36 
37 
     | 
    
         
             
                      end
         
     | 
| 
       37 
38 
     | 
    
         
             
                    end
         
     | 
| 
       38 
39 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
                    def method_missing(* 
     | 
| 
       40 
     | 
    
         
            -
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    def method_missing(*); end # # rubocop:disable Style/MissingRespondToMissing
         
     | 
| 
       41 
41 
     | 
    
         | 
| 
       42 
42 
     | 
    
         
             
                    private
         
     | 
| 
       43 
43 
     | 
    
         | 
| 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            require 'cucumber/tag_expressions/parser'
         
     | 
| 
       2 
3 
     | 
    
         
             
            require 'cucumber/runtime'
         
     | 
| 
       3 
4 
     | 
    
         
             
            require 'cucumber'
         
     | 
| 
         @@ -16,11 +17,11 @@ module ParallelTests 
     | 
|
| 
       16 
17 
     | 
    
         
             
              module Cucumber
         
     | 
| 
       17 
18 
     | 
    
         
             
                class Scenarios
         
     | 
| 
       18 
19 
     | 
    
         
             
                  class << self
         
     | 
| 
       19 
     | 
    
         
            -
                    def all(files, options={})
         
     | 
| 
      
 20 
     | 
    
         
            +
                    def all(files, options = {})
         
     | 
| 
       20 
21 
     | 
    
         
             
                      # Parse tag expression from given test options and ignore tag pattern. Refer here to understand how new tag expression syntax works - https://github.com/cucumber/cucumber/tree/master/tag-expressions
         
     | 
| 
       21 
22 
     | 
    
         
             
                      tags = []
         
     | 
| 
       22 
23 
     | 
    
         
             
                      words = options[:test_options].to_s.shellsplit
         
     | 
| 
       23 
     | 
    
         
            -
                      words.each_with_index { |w,i| tags << words[i+1] if ["-t", "--tags"].include?(w) }
         
     | 
| 
      
 24 
     | 
    
         
            +
                      words.each_with_index { |w, i| tags << words[i + 1] if ["-t", "--tags"].include?(w) }
         
     | 
| 
       24 
25 
     | 
    
         
             
                      if ignore = options[:ignore_tag_pattern]
         
     | 
| 
       25 
26 
     | 
    
         
             
                        tags << "not (#{ignore})"
         
     | 
| 
       26 
27 
     | 
    
         
             
                      end
         
     | 
| 
         @@ -31,8 +32,7 @@ module ParallelTests 
     | 
|
| 
       31 
32 
     | 
    
         | 
| 
       32 
33 
     | 
    
         
             
                    private
         
     | 
| 
       33 
34 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
                    def split_into_scenarios(files, tags='')
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
      
 35 
     | 
    
         
            +
                    def split_into_scenarios(files, tags = '')
         
     | 
| 
       36 
36 
     | 
    
         
             
                      # Create the tag expression instance from cucumber tag expressions parser, this is needed to know if the scenario matches with the tags invoked by the request
         
     | 
| 
       37 
37 
     | 
    
         
             
                      # Create the ScenarioLineLogger which will filter the scenario we want
         
     | 
| 
       38 
38 
     | 
    
         
             
                      args = []
         
     | 
| 
         @@ -40,7 +40,7 @@ module ParallelTests 
     | 
|
| 
       40 
40 
     | 
    
         
             
                      scenario_line_logger = ParallelTests::Cucumber::Formatters::ScenarioLineLogger.new(*args)
         
     | 
| 
       41 
41 
     | 
    
         | 
| 
       42 
42 
     | 
    
         
             
                      # here we loop on the files map, each file will contain one or more scenario
         
     | 
| 
       43 
     | 
    
         
            -
                       
     | 
| 
      
 43 
     | 
    
         
            +
                      files.each do |path|
         
     | 
| 
       44 
44 
     | 
    
         
             
                        # Gather up any line numbers attached to the file path
         
     | 
| 
       45 
45 
     | 
    
         
             
                        path, *test_lines = path.split(/:(?=\d+)/)
         
     | 
| 
       46 
46 
     | 
    
         
             
                        test_lines.map!(&:to_i)
         
     | 
| 
         @@ -1,9 +1,9 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            require 'parallel_tests'
         
     | 
| 
       2 
3 
     | 
    
         | 
| 
       3 
4 
     | 
    
         
             
            module ParallelTests
         
     | 
| 
       4 
5 
     | 
    
         
             
              module Gherkin
         
     | 
| 
       5 
6 
     | 
    
         
             
                module Io
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
7 
     | 
    
         
             
                  def prepare_io(path_or_io)
         
     | 
| 
       8 
8 
     | 
    
         
             
                    if path_or_io.respond_to?(:write)
         
     | 
| 
       9 
9 
     | 
    
         
             
                      path_or_io
         
     | 
| 
         @@ -24,7 +24,7 @@ module ParallelTests 
     | 
|
| 
       24 
24 
     | 
    
         | 
| 
       25 
25 
     | 
    
         
             
                  # do not let multiple processes get in each others way
         
     | 
| 
       26 
26 
     | 
    
         
             
                  def lock_output
         
     | 
| 
       27 
     | 
    
         
            -
                    if  
     | 
| 
      
 27 
     | 
    
         
            +
                    if @io.is_a?(File)
         
     | 
| 
       28 
28 
     | 
    
         
             
                      begin
         
     | 
| 
       29 
29 
     | 
    
         
             
                        @io.flock File::LOCK_EX
         
     | 
| 
       30 
30 
     | 
    
         
             
                        yield
         
     | 
| 
         @@ -35,7 +35,6 @@ module ParallelTests 
     | 
|
| 
       35 
35 
     | 
    
         
             
                      yield
         
     | 
| 
       36 
36 
     | 
    
         
             
                    end
         
     | 
| 
       37 
37 
     | 
    
         
             
                  end
         
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
38 
     | 
    
         
             
                end
         
     | 
| 
       40 
39 
     | 
    
         
             
              end
         
     | 
| 
       41 
40 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module ParallelTests
         
     | 
| 
       2 
3 
     | 
    
         
             
              module Gherkin
         
     | 
| 
       3 
4 
     | 
    
         
             
                class Listener
         
     | 
| 
         @@ -6,7 +7,8 @@ module ParallelTests 
     | 
|
| 
       6 
7 
     | 
    
         
             
                  attr_writer :ignore_tag_pattern
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
       8 
9 
     | 
    
         
             
                  def initialize
         
     | 
| 
       9 
     | 
    
         
            -
                    @steps 
     | 
| 
      
 10 
     | 
    
         
            +
                    @steps = []
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @uris = []
         
     | 
| 
       10 
12 
     | 
    
         
             
                    @collect = {}
         
     | 
| 
       11 
13 
     | 
    
         
             
                    @feature, @ignore_tag_pattern = nil
         
     | 
| 
       12 
14 
     | 
    
         
             
                    reset_counters!
         
     | 
| 
         @@ -16,7 +18,7 @@ module ParallelTests 
     | 
|
| 
       16 
18 
     | 
    
         
             
                    @feature = feature
         
     | 
| 
       17 
19 
     | 
    
         
             
                  end
         
     | 
| 
       18 
20 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
                  def background(* 
     | 
| 
      
 21 
     | 
    
         
            +
                  def background(*)
         
     | 
| 
       20 
22 
     | 
    
         
             
                    @background = 1
         
     | 
| 
       21 
23 
     | 
    
         
             
                  end
         
     | 
| 
       22 
24 
     | 
    
         | 
| 
         @@ -31,7 +33,7 @@ module ParallelTests 
     | 
|
| 
       31 
33 
     | 
    
         
             
                    @outline = 1
         
     | 
| 
       32 
34 
     | 
    
         
             
                  end
         
     | 
| 
       33 
35 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
                  def step(* 
     | 
| 
      
 36 
     | 
    
         
            +
                  def step(*)
         
     | 
| 
       35 
37 
     | 
    
         
             
                    return if @ignoring
         
     | 
| 
       36 
38 
     | 
    
         
             
                    if @background == 1
         
     | 
| 
       37 
39 
     | 
    
         
             
                      @background_steps += 1
         
     | 
| 
         @@ -51,12 +53,10 @@ module ParallelTests 
     | 
|
| 
       51 
53 
     | 
    
         
             
                  # @param  [Gherkin::Formatter::Model::Examples]  examples
         
     | 
| 
       52 
54 
     | 
    
         
             
                  #
         
     | 
| 
       53 
55 
     | 
    
         
             
                  def examples(examples)
         
     | 
| 
       54 
     | 
    
         
            -
                     
     | 
| 
       55 
     | 
    
         
            -
                      @collect[@uri] += (@outline_steps * examples.rows.size)
         
     | 
| 
       56 
     | 
    
         
            -
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                    @collect[@uri] += (@outline_steps * examples.rows.size) unless examples.rows.empty?
         
     | 
| 
       57 
57 
     | 
    
         
             
                  end
         
     | 
| 
       58 
58 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                  def eof(* 
     | 
| 
      
 59 
     | 
    
         
            +
                  def eof(*)
         
     | 
| 
       60 
60 
     | 
    
         
             
                    @collect[@uri] += (@background_steps * @scenarios)
         
     | 
| 
       61 
61 
     | 
    
         
             
                    reset_counters!
         
     | 
| 
       62 
62 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -67,8 +67,7 @@ module ParallelTests 
     | 
|
| 
       67 
67 
     | 
    
         
             
                  end
         
     | 
| 
       68 
68 
     | 
    
         | 
| 
       69 
69 
     | 
    
         
             
                  # ignore lots of other possible callbacks ...
         
     | 
| 
       70 
     | 
    
         
            -
                  def method_missing(* 
     | 
| 
       71 
     | 
    
         
            -
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                  def method_missing(*); end # rubocop:disable Style/MissingRespondToMissing
         
     | 
| 
       72 
71 
     | 
    
         | 
| 
       73 
72 
     | 
    
         
             
                  private
         
     | 
| 
       74 
73 
     | 
    
         | 
| 
         @@ -79,7 +78,7 @@ module ParallelTests 
     | 
|
| 
       79 
78 
     | 
    
         | 
| 
       80 
79 
     | 
    
         
             
                  # Set @ignoring if we should ignore this scenario/outline based on its tags
         
     | 
| 
       81 
80 
     | 
    
         
             
                  def should_ignore(scenario)
         
     | 
| 
       82 
     | 
    
         
            -
                    @ignoring = @ignore_tag_pattern && all_tags(scenario).find{ |tag| @ignore_tag_pattern === tag.name }
         
     | 
| 
      
 81 
     | 
    
         
            +
                    @ignoring = @ignore_tag_pattern && all_tags(scenario).find { |tag| @ignore_tag_pattern === tag.name }
         
     | 
| 
       83 
82 
     | 
    
         
             
                  end
         
     | 
| 
       84 
83 
     | 
    
         
             
                end
         
     | 
| 
       85 
84 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,23 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            require "parallel_tests/test/runner"
         
     | 
| 
       2 
3 
     | 
    
         
             
            require 'shellwords'
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
5 
     | 
    
         
             
            module ParallelTests
         
     | 
| 
       5 
6 
     | 
    
         
             
              module Gherkin
         
     | 
| 
       6 
7 
     | 
    
         
             
                class Runner < ParallelTests::Test::Runner
         
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
8 
     | 
    
         
             
                  class << self
         
     | 
| 
       9 
9 
     | 
    
         
             
                    def run_tests(test_files, process_number, num_processes, options)
         
     | 
| 
       10 
10 
     | 
    
         
             
                      combined_scenarios = test_files
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
                      if options[:group_by] == :scenarios
         
     | 
| 
       13 
13 
     | 
    
         
             
                        grouped = test_files.map { |t| t.split(':') }.group_by(&:first)
         
     | 
| 
       14 
     | 
    
         
            -
                        combined_scenarios = grouped.map  
     | 
| 
      
 14 
     | 
    
         
            +
                        combined_scenarios = grouped.map do |file, files_and_lines|
         
     | 
| 
      
 15 
     | 
    
         
            +
                          "#{file}:#{files_and_lines.map(&:last).join(':')}"
         
     | 
| 
      
 16 
     | 
    
         
            +
                        end
         
     | 
| 
       15 
17 
     | 
    
         
             
                      end
         
     | 
| 
       16 
18 
     | 
    
         | 
| 
       17 
19 
     | 
    
         
             
                      sanitized_test_files = combined_scenarios.map { |val| WINDOWS ? "\"#{val}\"" : Shellwords.escape(val) }
         
     | 
| 
       18 
20 
     | 
    
         | 
| 
       19 
21 
     | 
    
         
             
                      options[:env] ||= {}
         
     | 
| 
       20 
     | 
    
         
            -
                      options[:env] = options[:env].merge({'AUTOTEST' => '1'}) if $stdout.tty? 
     | 
| 
      
 22 
     | 
    
         
            +
                      options[:env] = options[:env].merge({ 'AUTOTEST' => '1' }) if $stdout.tty?
         
     | 
| 
       21 
23 
     | 
    
         | 
| 
       22 
24 
     | 
    
         
             
                      cmd = [
         
     | 
| 
       23 
25 
     | 
    
         
             
                        executable,
         
     | 
| 
         @@ -44,16 +46,16 @@ module ParallelTests 
     | 
|
| 
       44 
46 
     | 
    
         
             
                    # 1 scenario (1 failed)
         
     | 
| 
       45 
47 
     | 
    
         
             
                    # 1 step (1 failed)
         
     | 
| 
       46 
48 
     | 
    
         
             
                    def summarize_results(results)
         
     | 
| 
       47 
     | 
    
         
            -
                      sort_order =  
     | 
| 
      
 49 
     | 
    
         
            +
                      sort_order = ['scenario', 'step', 'failed', 'flaky', 'undefined', 'skipped', 'pending', 'passed']
         
     | 
| 
       48 
50 
     | 
    
         | 
| 
       49 
     | 
    
         
            -
                       
     | 
| 
      
 51 
     | 
    
         
            +
                      ['scenario', 'step'].map do |group|
         
     | 
| 
       50 
52 
     | 
    
         
             
                        group_results = results.grep(/^\d+ #{group}/)
         
     | 
| 
       51 
53 
     | 
    
         
             
                        next if group_results.empty?
         
     | 
| 
       52 
54 
     | 
    
         | 
| 
       53 
55 
     | 
    
         
             
                        sums = sum_up_results(group_results)
         
     | 
| 
       54 
56 
     | 
    
         
             
                        sums = sums.sort_by { |word, _| sort_order.index(word) || 999 }
         
     | 
| 
       55 
57 
     | 
    
         
             
                        sums.map! do |word, number|
         
     | 
| 
       56 
     | 
    
         
            -
                          plural = "s" if word == group  
     | 
| 
      
 58 
     | 
    
         
            +
                          plural = "s" if (word == group) && (number != 1)
         
     | 
| 
       57 
59 
     | 
    
         
             
                          "#{number} #{word}#{plural}"
         
     | 
| 
       58 
60 
     | 
    
         
             
                        end
         
     | 
| 
       59 
61 
     | 
    
         
             
                        "#{sums[0]} (#{sums[1..-1].join(", ")})"
         
     | 
| 
         @@ -61,7 +63,7 @@ module ParallelTests 
     | 
|
| 
       61 
63 
     | 
    
         
             
                    end
         
     | 
| 
       62 
64 
     | 
    
         | 
| 
       63 
65 
     | 
    
         
             
                    def cucumber_opts(given)
         
     | 
| 
       64 
     | 
    
         
            -
                      if given =~ /--profile/  
     | 
| 
      
 66 
     | 
    
         
            +
                      if given =~ (/--profile/) || given =~ (/(^|\s)-p /)
         
     | 
| 
       65 
67 
     | 
    
         
             
                        given
         
     | 
| 
       66 
68 
     | 
    
         
             
                      else
         
     | 
| 
       67 
69 
     | 
    
         
             
                        [given, profile_from_config].compact.join(" ")
         
     | 
| 
         @@ -71,15 +73,11 @@ module ParallelTests 
     | 
|
| 
       71 
73 
     | 
    
         
             
                    def profile_from_config
         
     | 
| 
       72 
74 
     | 
    
         
             
                      # copied from https://github.com/cucumber/cucumber/blob/master/lib/cucumber/cli/profile_loader.rb#L85
         
     | 
| 
       73 
75 
     | 
    
         
             
                      config = Dir.glob("{,.config/,config/}#{name}{.yml,.yaml}").first
         
     | 
| 
       74 
     | 
    
         
            -
                      if config && File.read(config) =~ /^parallel:/
         
     | 
| 
       75 
     | 
    
         
            -
                        "--profile parallel"
         
     | 
| 
       76 
     | 
    
         
            -
                      end
         
     | 
| 
      
 76 
     | 
    
         
            +
                      "--profile parallel" if config && File.read(config) =~ /^parallel:/
         
     | 
| 
       77 
77 
     | 
    
         
             
                    end
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
     | 
    
         
            -
                    def tests_in_groups(tests, num_groups, options={})
         
     | 
| 
       80 
     | 
    
         
            -
                      if options[:group_by] == :scenarios
         
     | 
| 
       81 
     | 
    
         
            -
                        @test_file_name = "scenario"
         
     | 
| 
       82 
     | 
    
         
            -
                      end
         
     | 
| 
      
 79 
     | 
    
         
            +
                    def tests_in_groups(tests, num_groups, options = {})
         
     | 
| 
      
 80 
     | 
    
         
            +
                      @test_file_name = "scenario" if options[:group_by] == :scenarios
         
     | 
| 
       83 
81 
     | 
    
         
             
                      method = "by_#{options[:group_by]}"
         
     | 
| 
       84 
82 
     | 
    
         
             
                      if Grouper.respond_to?(method)
         
     | 
| 
       85 
83 
     | 
    
         
             
                        Grouper.send(method, find_tests(tests, options), num_groups, options)
         
     | 
| 
         @@ -88,7 +86,6 @@ module ParallelTests 
     | 
|
| 
       88 
86 
     | 
    
         
             
                      end
         
     | 
| 
       89 
87 
     | 
    
         
             
                    end
         
     | 
| 
       90 
88 
     | 
    
         | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
89 
     | 
    
         
             
                    def runtime_logging
         
     | 
| 
       93 
90 
     | 
    
         
             
                      "--format ParallelTests::Gherkin::RuntimeLogger --out #{runtime_log}"
         
     | 
| 
       94 
91 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -98,18 +95,16 @@ module ParallelTests 
     | 
|
| 
       98 
95 
     | 
    
         
             
                    end
         
     | 
| 
       99 
96 
     | 
    
         | 
| 
       100 
97 
     | 
    
         
             
                    def determine_executable
         
     | 
| 
       101 
     | 
    
         
            -
                       
     | 
| 
       102 
     | 
    
         
            -
                      when File.exist?("bin/#{name}")
         
     | 
| 
      
 98 
     | 
    
         
            +
                      if File.exist?("bin/#{name}")
         
     | 
| 
       103 
99 
     | 
    
         
             
                        ParallelTests.with_ruby_binary("bin/#{name}")
         
     | 
| 
       104 
     | 
    
         
            -
                       
     | 
| 
      
 100 
     | 
    
         
            +
                      elsif ParallelTests.bundler_enabled?
         
     | 
| 
       105 
101 
     | 
    
         
             
                        "bundle exec #{name}"
         
     | 
| 
       106 
     | 
    
         
            -
                       
     | 
| 
      
 102 
     | 
    
         
            +
                      elsif File.file?("script/#{name}")
         
     | 
| 
       107 
103 
     | 
    
         
             
                        ParallelTests.with_ruby_binary("script/#{name}")
         
     | 
| 
       108 
104 
     | 
    
         
             
                      else
         
     | 
| 
       109 
     | 
    
         
            -
                         
     | 
| 
      
 105 
     | 
    
         
            +
                        name.to_s
         
     | 
| 
       110 
106 
     | 
    
         
             
                      end
         
     | 
| 
       111 
107 
     | 
    
         
             
                    end
         
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
108 
     | 
    
         
             
                  end
         
     | 
| 
       114 
109 
     | 
    
         
             
                end
         
     | 
| 
       115 
110 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            require 'parallel_tests/gherkin/io'
         
     | 
| 
       2 
3 
     | 
    
         | 
| 
       3 
4 
     | 
    
         
             
            module ParallelTests
         
     | 
| 
         @@ -19,7 +20,7 @@ module ParallelTests 
     | 
|
| 
       19 
20 
     | 
    
         | 
| 
       20 
21 
     | 
    
         
             
                    config.on_event :test_run_finished do |_|
         
     | 
| 
       21 
22 
     | 
    
         
             
                      lock_output do
         
     | 
| 
       22 
     | 
    
         
            -
                        @io.puts 
     | 
| 
      
 23 
     | 
    
         
            +
                        @io.puts(@example_times.map { |file, time| "#{file}:#{time}" })
         
     | 
| 
       23 
24 
     | 
    
         
             
                      end
         
     | 
| 
       24 
25 
     | 
    
         
             
                    end
         
     | 
| 
       25 
26 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module ParallelTests
         
     | 
| 
       2 
3 
     | 
    
         
             
              class Grouper
         
     | 
| 
       3 
4 
     | 
    
         
             
                class << self
         
     | 
| 
         @@ -6,30 +7,107 @@ module ParallelTests 
     | 
|
| 
       6 
7 
     | 
    
         
             
                    in_even_groups_by_size(features_with_steps, num_groups)
         
     | 
| 
       7 
8 
     | 
    
         
             
                  end
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
                  def by_scenarios(tests, num_groups, options={})
         
     | 
| 
      
 10 
     | 
    
         
            +
                  def by_scenarios(tests, num_groups, options = {})
         
     | 
| 
       10 
11 
     | 
    
         
             
                    scenarios = group_by_scenarios(tests, options)
         
     | 
| 
       11 
12 
     | 
    
         
             
                    in_even_groups_by_size(scenarios, num_groups)
         
     | 
| 
       12 
13 
     | 
    
         
             
                  end
         
     | 
| 
       13 
14 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                  def in_even_groups_by_size(items, num_groups, options= {})
         
     | 
| 
       15 
     | 
    
         
            -
                    groups = Array.new(num_groups) { {: 
     | 
| 
      
 15 
     | 
    
         
            +
                  def in_even_groups_by_size(items, num_groups, options = {})
         
     | 
| 
      
 16 
     | 
    
         
            +
                    groups = Array.new(num_groups) { { items: [], size: 0 } }
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    return specify_groups(items, num_groups, options, groups) if options[:specify_groups]
         
     | 
| 
       16 
19 
     | 
    
         | 
| 
       17 
20 
     | 
    
         
             
                    # add all files that should run in a single process to one group
         
     | 
| 
       18 
     | 
    
         
            -
                     
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
      
 21 
     | 
    
         
            +
                    single_process_patterns = options[:single_process] || []
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    single_items, items = items.partition do |item, _size|
         
     | 
| 
      
 24 
     | 
    
         
            +
                      single_process_patterns.any? { |pattern| item =~ pattern }
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    isolate_count = isolate_count(options)
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    if isolate_count >= num_groups
         
     | 
| 
      
 30 
     | 
    
         
            +
                      raise 'Number of isolated processes must be less than total the number of processes'
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    if isolate_count >= num_groups
         
     | 
| 
      
 34 
     | 
    
         
            +
                      raise 'Number of isolated processes must be >= total number of processes'
         
     | 
| 
       21 
35 
     | 
    
         
             
                    end
         
     | 
| 
       22 
36 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
                     
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
      
 37 
     | 
    
         
            +
                    if isolate_count >= 1
         
     | 
| 
      
 38 
     | 
    
         
            +
                      # add all files that should run in a multiple isolated processes to their own groups
         
     | 
| 
      
 39 
     | 
    
         
            +
                      group_features_by_size(items_to_group(single_items), groups[0..(isolate_count - 1)])
         
     | 
| 
      
 40 
     | 
    
         
            +
                      # group the non-isolated by size
         
     | 
| 
      
 41 
     | 
    
         
            +
                      group_features_by_size(items_to_group(items), groups[isolate_count..-1])
         
     | 
| 
      
 42 
     | 
    
         
            +
                    else
         
     | 
| 
      
 43 
     | 
    
         
            +
                      # add all files that should run in a single non-isolated process to first group
         
     | 
| 
      
 44 
     | 
    
         
            +
                      single_items.each { |item, size| add_to_group(groups.first, item, size) }
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                      # group all by size
         
     | 
| 
      
 47 
     | 
    
         
            +
                      group_features_by_size(items_to_group(items), groups)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
       25 
49 
     | 
    
         | 
| 
       26 
50 
     | 
    
         
             
                    groups.map! { |g| g[:items].sort }
         
     | 
| 
       27 
51 
     | 
    
         
             
                  end
         
     | 
| 
       28 
52 
     | 
    
         | 
| 
       29 
53 
     | 
    
         
             
                  private
         
     | 
| 
       30 
54 
     | 
    
         | 
| 
      
 55 
     | 
    
         
            +
                  def specify_groups(items, num_groups, options, groups)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    specify_test_process_groups = options[:specify_groups].split('|')
         
     | 
| 
      
 57 
     | 
    
         
            +
                    if specify_test_process_groups.count > num_groups
         
     | 
| 
      
 58 
     | 
    
         
            +
                      raise 'Number of processes separated by pipe must be less than or equal to the total number of processes'
         
     | 
| 
      
 59 
     | 
    
         
            +
                    end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                    all_specified_tests = specify_test_process_groups.map { |group| group.split(',') }.flatten
         
     | 
| 
      
 62 
     | 
    
         
            +
                    specified_items_found, items = items.partition { |item, _size| all_specified_tests.include?(item) }
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    specified_specs_not_found = all_specified_tests - specified_items_found.map(&:first)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    if specified_specs_not_found.any?
         
     | 
| 
      
 66 
     | 
    
         
            +
                      raise "Could not find #{specified_specs_not_found} from --specify-groups in the selected files & folders"
         
     | 
| 
      
 67 
     | 
    
         
            +
                    end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                    if specify_test_process_groups.count == num_groups && items.flatten.any?
         
     | 
| 
      
 70 
     | 
    
         
            +
                      raise(
         
     | 
| 
      
 71 
     | 
    
         
            +
                        <<~ERROR
         
     | 
| 
      
 72 
     | 
    
         
            +
                          The number of groups in --specify-groups matches the number of groups from -n but there were other specs
         
     | 
| 
      
 73 
     | 
    
         
            +
                          found in the selected files & folders not specified in --specify-groups. Make sure -n is larger than the
         
     | 
| 
      
 74 
     | 
    
         
            +
                          number of processes in --specify-groups if there are other specs that need to be run. The specs that aren't run:
         
     | 
| 
      
 75 
     | 
    
         
            +
                          #{items.map(&:first)}
         
     | 
| 
      
 76 
     | 
    
         
            +
                        ERROR
         
     | 
| 
      
 77 
     | 
    
         
            +
                      )
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    # First order the specify_groups into the main groups array
         
     | 
| 
      
 81 
     | 
    
         
            +
                    specify_test_process_groups.each_with_index do |specify_test_process, i|
         
     | 
| 
      
 82 
     | 
    
         
            +
                      groups[i] = specify_test_process.split(',')
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                    # Return early when processed specify_groups tests exactly match the items passed in
         
     | 
| 
      
 86 
     | 
    
         
            +
                    return groups if specify_test_process_groups.count == num_groups
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                    # Now sort the rest of the items into the main groups array
         
     | 
| 
      
 89 
     | 
    
         
            +
                    specified_range = specify_test_process_groups.count..-1
         
     | 
| 
      
 90 
     | 
    
         
            +
                    remaining_groups = groups[specified_range]
         
     | 
| 
      
 91 
     | 
    
         
            +
                    group_features_by_size(items_to_group(items), remaining_groups)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    # Don't sort all the groups, only sort the ones not specified in specify_groups
         
     | 
| 
      
 93 
     | 
    
         
            +
                    sorted_groups = remaining_groups.map { |g| g[:items].sort }
         
     | 
| 
      
 94 
     | 
    
         
            +
                    groups[specified_range] = sorted_groups
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    groups
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                  def isolate_count(options)
         
     | 
| 
      
 100 
     | 
    
         
            +
                    if options[:isolate_count] && options[:isolate_count] > 1
         
     | 
| 
      
 101 
     | 
    
         
            +
                      options[:isolate_count]
         
     | 
| 
      
 102 
     | 
    
         
            +
                    elsif options[:isolate]
         
     | 
| 
      
 103 
     | 
    
         
            +
                      1
         
     | 
| 
      
 104 
     | 
    
         
            +
                    else
         
     | 
| 
      
 105 
     | 
    
         
            +
                      0
         
     | 
| 
      
 106 
     | 
    
         
            +
                    end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
       31 
109 
     | 
    
         
             
                  def largest_first(files)
         
     | 
| 
       32 
     | 
    
         
            -
                    files.sort_by{|_item, size| size }.reverse
         
     | 
| 
      
 110 
     | 
    
         
            +
                    files.sort_by { |_item, size| size }.reverse
         
     | 
| 
       33 
111 
     | 
    
         
             
                  end
         
     | 
| 
       34 
112 
     | 
    
         | 
| 
       35 
113 
     | 
    
         
             
                  def smallest_group(groups)
         
     | 
| 
         @@ -46,7 +124,7 @@ module ParallelTests 
     | 
|
| 
       46 
124 
     | 
    
         
             
                    ParallelTests::Cucumber::FeaturesWithSteps.all(tests, options)
         
     | 
| 
       47 
125 
     | 
    
         
             
                  end
         
     | 
| 
       48 
126 
     | 
    
         | 
| 
       49 
     | 
    
         
            -
                  def group_by_scenarios(tests, options={})
         
     | 
| 
      
 127 
     | 
    
         
            +
                  def group_by_scenarios(tests, options = {})
         
     | 
| 
       50 
128 
     | 
    
         
             
                    require 'parallel_tests/cucumber/scenarios'
         
     | 
| 
       51 
129 
     | 
    
         
             
                    ParallelTests::Cucumber::Scenarios.all(tests, options)
         
     | 
| 
       52 
130 
     | 
    
         
             
                  end
         
     | 
    
        data/lib/parallel_tests/pids.rb
    CHANGED
    
    | 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            require 'json'
         
     | 
| 
       2 
3 
     | 
    
         | 
| 
       3 
4 
     | 
    
         
             
            module ParallelTests
         
     | 
| 
         @@ -52,8 +53,8 @@ module ParallelTests 
     | 
|
| 
       52 
53 
     | 
    
         
             
                  sync { IO.write(file_path, pids.to_json) }
         
     | 
| 
       53 
54 
     | 
    
         
             
                end
         
     | 
| 
       54 
55 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
                def sync
         
     | 
| 
       56 
     | 
    
         
            -
                  mutex.synchronize 
     | 
| 
      
 56 
     | 
    
         
            +
                def sync(&block)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  mutex.synchronize(&block)
         
     | 
| 
       57 
58 
     | 
    
         
             
                end
         
     | 
| 
       58 
59 
     | 
    
         
             
              end
         
     | 
| 
       59 
60 
     | 
    
         
             
            end
         
     |