parallel_tests 2.32.0 → 3.4.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 +13 -8
- data/lib/parallel_tests/cli.rb +10 -3
- data/lib/parallel_tests/cucumber/features_with_steps.rb +31 -0
- data/lib/parallel_tests/cucumber/scenario_line_logger.rb +17 -15
- data/lib/parallel_tests/cucumber/scenarios.rb +15 -25
- data/lib/parallel_tests/gherkin/listener.rb +0 -2
- data/lib/parallel_tests/gherkin/runtime_logger.rb +1 -1
- data/lib/parallel_tests/grouper.rb +36 -23
- data/lib/parallel_tests/rspec/runner.rb +16 -2
- data/lib/parallel_tests/tasks.rb +1 -1
- data/lib/parallel_tests/test/runner.rb +1 -7
- data/lib/parallel_tests/version.rb +1 -1
- metadata +7 -6
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: fd8878310c757d2a906bbf5ca62b68e21941cc79472a9833944f88087449c229
         | 
| 4 | 
            +
              data.tar.gz: d55a5ac186f1c16d9568b8f6bb145e0016bafc1c2fb9748593e96997eaf2034c
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: f01821e38506525feaafb8a9ff5a480ab1f6f2b0bed849088d72682a699f82ae50bb2a78b94526f243a65c2474ff8f2d3a06c204b44431a7687d877cbbc91dd8
         | 
| 7 | 
            +
              data.tar.gz: 2823702fc7e5d047a69c57d3ee4c0b72afbb51603d58e34a3656899d0026a4b749e94494b9edf234fdcc5fd81bae59dfba55f920d777f5f7d1f7c21fb5219552
         | 
    
        data/Readme.md
    CHANGED
    
    | @@ -82,6 +82,8 @@ Running things once | |
| 82 82 | 
             
            ===================
         | 
| 83 83 |  | 
| 84 84 | 
             
            ```Ruby
         | 
| 85 | 
            +
            require "parallel_tests"
         | 
| 86 | 
            +
             | 
| 85 87 | 
             
            # preparation:
         | 
| 86 88 | 
             
            # affected by race-condition: first process may boot slower than the second
         | 
| 87 89 | 
             
            # either sleep a bit or use a lock for example File.lock
         | 
| @@ -97,7 +99,6 @@ at_exit do | |
| 97 99 | 
             
                undo_something
         | 
| 98 100 | 
             
              end
         | 
| 99 101 | 
             
            end
         | 
| 100 | 
            -
             | 
| 101 102 | 
             
            ```
         | 
| 102 103 |  | 
| 103 104 | 
             
            Even test group run-times
         | 
| @@ -192,7 +193,6 @@ Setup for non-rails | |
| 192 193 |  | 
| 193 194 | 
             
            Options are:
         | 
| 194 195 | 
             
            <!-- copy output from bundle exec ./bin/parallel_test -h -->
         | 
| 195 | 
            -
             | 
| 196 196 | 
             
                -n [PROCESSES]                   How many processes to use, default: available CPUs
         | 
| 197 197 | 
             
                -p, --pattern [PATTERN]          run tests matching this regex pattern
         | 
| 198 198 | 
             
                    --exclude-pattern [PATTERN]  exclude tests matching this regex pattern
         | 
| @@ -205,7 +205,9 @@ Options are: | |
| 205 205 | 
             
                      default - runtime when runtime log is filled otherwise filesize
         | 
| 206 206 | 
             
                -m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
         | 
| 207 207 | 
             
                -s, --single [PATTERN]           Run all matching files in the same process
         | 
| 208 | 
            -
                -i, --isolate                    Do not run any other tests in the group used by --single(-s)
         | 
| 208 | 
            +
                -i, --isolate                    Do not run any other tests in the group used by --single(-s).
         | 
| 209 | 
            +
                                                 Automatically turned on if --isolate-n is set above 0.
         | 
| 210 | 
            +
                    --isolate-n                  Number of processes for isolated groups. Default to 1 when --isolate is on.
         | 
| 209 211 | 
             
                    --only-group INT[, INT]
         | 
| 210 212 | 
             
                -e, --exec [COMMAND]             execute this code parallel and with ENV['TEST_ENV_NUMBER']
         | 
| 211 213 | 
             
                -o, --test-options '[OPTIONS]'   execute test commands with those options
         | 
| @@ -222,12 +224,14 @@ Options are: | |
| 222 224 | 
             
                    --ignore-tags [PATTERN]      When counting steps ignore scenarios with tags that match this pattern
         | 
| 223 225 | 
             
                    --nice                       execute test commands with low priority.
         | 
| 224 226 | 
             
                    --runtime-log [PATH]         Location of previously recorded test runtimes
         | 
| 225 | 
            -
                    --allowed-missing | 
| 227 | 
            +
                    --allowed-missing [INT]      Allowed percentage of missing runtimes (default = 50)
         | 
| 226 228 | 
             
                    --unknown-runtime [FLOAT]    Use given number as unknown runtime (otherwise use average time)
         | 
| 229 | 
            +
                    --first-is-1                 Use "1" as TEST_ENV_NUMBER to not reuse the default test environment
         | 
| 230 | 
            +
                    --fail-fast                  Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported
         | 
| 227 231 | 
             
                    --verbose                    Print debug output
         | 
| 228 | 
            -
                    --verbose-process-command     | 
| 229 | 
            -
                    --verbose-rerun-command       | 
| 230 | 
            -
                    --quiet                      Print only  | 
| 232 | 
            +
                    --verbose-process-command    Displays only the command that will be executed by each process
         | 
| 233 | 
            +
                    --verbose-rerun-command      When there are failures, displays the command executed by each process that failed
         | 
| 234 | 
            +
                    --quiet                      Print only tests output
         | 
| 231 235 | 
             
                -v, --version                    Show Version
         | 
| 232 236 | 
             
                -h, --help                       Show this.
         | 
| 233 237 |  | 
| @@ -257,7 +261,7 @@ TIPS | |
| 257 261 | 
             
             - Use [rspec-retry](https://github.com/NoRedInk/rspec-retry) (not rspec-rerun) to rerun failed tests.
         | 
| 258 262 | 
             
             - [JUnit formatter configuration](https://github.com/grosser/parallel_tests/wiki#with-rspec_junit_formatter----by-jgarber)
         | 
| 259 263 | 
             
             - Use [parallel_split_test](https://github.com/grosser/parallel_split_test) to run multiple scenarios in a single spec file, concurrently. (`parallel_tests` [works at the file-level and intends to stay that way](https://github.com/grosser/parallel_tests/issues/747#issuecomment-580216980))
         | 
| 260 | 
            -
             | 
| 264 | 
            +
             | 
| 261 265 | 
             
            ### Cucumber
         | 
| 262 266 |  | 
| 263 267 | 
             
             - Add a `parallel: foo` profile to your `config/cucumber.yml` and it will be used to run parallel tests
         | 
| @@ -374,6 +378,7 @@ inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rs | |
| 374 378 | 
             
             - [Calaway](https://github.com/calaway)
         | 
| 375 379 | 
             
             - [alboyadjian](https://github.com/alboyadjian)
         | 
| 376 380 | 
             
             - [Nathan Broadbent](https://github.com/ndbroadbent)
         | 
| 381 | 
            +
             - [Vikram B Kumar](https://github.com/v-kumar)
         | 
| 377 382 |  | 
| 378 383 | 
             
            [Michael Grosser](http://grosser.it)<br/>
         | 
| 379 384 | 
             
            michael@grosser.it<br/>
         | 
    
        data/lib/parallel_tests/cli.rb
    CHANGED
    
    | @@ -42,9 +42,10 @@ module ParallelTests | |
| 42 42 | 
             
                  Tempfile.open 'parallel_tests-lock' do |lock|
         | 
| 43 43 | 
             
                    ParallelTests.with_pid_file do
         | 
| 44 44 | 
             
                      simulate_output_for_ci options[:serialize_stdout] do
         | 
| 45 | 
            -
                        Parallel.map(items, : | 
| 45 | 
            +
                        Parallel.map(items, in_threads: num_processes) do |item|
         | 
| 46 46 | 
             
                          result = yield(item)
         | 
| 47 47 | 
             
                          reprint_output(result, lock.path) if options[:serialize_stdout]
         | 
| 48 | 
            +
                          ParallelTests.stop_all_processes if options[:fail_fast] && result[:exit_status] != 0
         | 
| 48 49 | 
             
                          result
         | 
| 49 50 | 
             
                        end
         | 
| 50 51 | 
             
                      end
         | 
| @@ -191,6 +192,12 @@ module ParallelTests | |
| 191 192 | 
             
                      options[:isolate] = true
         | 
| 192 193 | 
             
                    end
         | 
| 193 194 |  | 
| 195 | 
            +
                    opts.on("--isolate-n [PROCESSES]",
         | 
| 196 | 
            +
                      Integer,
         | 
| 197 | 
            +
                      "Use 'isolate'  singles with number of processes, default: 1.") do |n|
         | 
| 198 | 
            +
                      options[:isolate_count] = n
         | 
| 199 | 
            +
                    end
         | 
| 200 | 
            +
             | 
| 194 201 | 
             
                    opts.on("--only-group INT[, INT]", Array) { |groups| options[:only_group] = groups.map(&:to_i) }
         | 
| 195 202 |  | 
| 196 203 | 
             
                    opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUMBER']") { |path| options[:execute] = path }
         | 
| @@ -220,6 +227,7 @@ module ParallelTests | |
| 220 227 | 
             
                    opts.on("--allowed-missing [INT]", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
         | 
| 221 228 | 
             
                    opts.on("--unknown-runtime [FLOAT]", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
         | 
| 222 229 | 
             
                    opts.on("--first-is-1", "Use \"1\" as TEST_ENV_NUMBER to not reuse the default test environment") { options[:first_is_1] = true }
         | 
| 230 | 
            +
                    opts.on("--fail-fast", "Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported") { options[:fail_fast] = true }
         | 
| 223 231 | 
             
                    opts.on("--verbose", "Print debug output") { options[:verbose] = true }
         | 
| 224 232 | 
             
                    opts.on("--verbose-process-command", "Displays only the command that will be executed by each process") { options[:verbose_process_command] = true }
         | 
| 225 233 | 
             
                    opts.on("--verbose-rerun-command", "When there are failures, displays the command executed by each process that failed") { options[:verbose_rerun_command] = true }
         | 
| @@ -316,9 +324,8 @@ module ParallelTests | |
| 316 324 | 
             
                end
         | 
| 317 325 |  | 
| 318 326 | 
             
                def final_fail_message
         | 
| 319 | 
            -
                  fail_message = " | 
| 327 | 
            +
                  fail_message = "Tests Failed"
         | 
| 320 328 | 
             
                  fail_message = "\e[31m#{fail_message}\e[0m" if use_colors?
         | 
| 321 | 
            -
             | 
| 322 329 | 
             
                  fail_message
         | 
| 323 330 | 
             
                end
         | 
| 324 331 |  | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            begin
         | 
| 2 | 
            +
              gem "cuke_modeler", "~> 3.0"
         | 
| 3 | 
            +
              require 'cuke_modeler'
         | 
| 4 | 
            +
            rescue LoadError
         | 
| 5 | 
            +
              raise 'Grouping by number of cucumber steps requires the `cuke_modeler` modeler gem with requirement `~> 3.0`. Add `gem "cuke_modeler", "~> 3.0"` to your `Gemfile`, run `bundle install` and try again.'
         | 
| 6 | 
            +
            end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module ParallelTests
         | 
| 9 | 
            +
              module Cucumber
         | 
| 10 | 
            +
                class FeaturesWithSteps
         | 
| 11 | 
            +
                  class << self
         | 
| 12 | 
            +
                    def all(tests, options)
         | 
| 13 | 
            +
                      ignore_tag_pattern = options[:ignore_tag_pattern].nil? ? nil : Regexp.compile(options[:ignore_tag_pattern])
         | 
| 14 | 
            +
                      # format of hash will be FILENAME => NUM_STEPS
         | 
| 15 | 
            +
                      steps_per_file = tests.each_with_object({}) do |file,steps|
         | 
| 16 | 
            +
                        feature = ::CukeModeler::FeatureFile.new(file).feature
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        # skip feature if it matches tag regex
         | 
| 19 | 
            +
                        next if feature.tags.grep(ignore_tag_pattern).any?
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                        # count the number of steps in the file
         | 
| 22 | 
            +
                        # will only include a feature if the regex does not match
         | 
| 23 | 
            +
                        all_steps = feature.scenarios.map{|a| a.steps.count if a.tags.grep(ignore_tag_pattern).empty? }.compact
         | 
| 24 | 
            +
                        steps[file] = all_steps.inject(0,:+)
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                      steps_per_file.sort_by { |_, value| -value }
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| @@ -1,37 +1,33 @@ | |
| 1 | 
            -
            require 'cucumber/tag_expressions/parser'
         | 
| 2 | 
            -
            require 'cucumber/core/gherkin/tag_expression'
         | 
| 3 | 
            -
             | 
| 4 1 | 
             
            module ParallelTests
         | 
| 5 2 | 
             
              module Cucumber
         | 
| 6 3 | 
             
                module Formatters
         | 
| 7 4 | 
             
                  class ScenarioLineLogger
         | 
| 8 5 | 
             
                    attr_reader :scenarios
         | 
| 9 6 |  | 
| 10 | 
            -
                    def initialize(tag_expression =  | 
| 7 | 
            +
                    def initialize(tag_expression = nil)
         | 
| 11 8 | 
             
                      @scenarios = []
         | 
| 12 9 | 
             
                      @tag_expression = tag_expression
         | 
| 13 10 | 
             
                    end
         | 
| 14 11 |  | 
| 15 12 | 
             
                    def visit_feature_element(uri, feature_element, feature_tags, line_numbers: [])
         | 
| 16 | 
            -
                      scenario_tags = feature_element | 
| 13 | 
            +
                      scenario_tags = feature_element.tags.map { |tag| tag.name }
         | 
| 17 14 | 
             
                      scenario_tags = feature_tags + scenario_tags
         | 
| 18 | 
            -
                      if feature_element | 
| 19 | 
            -
                        test_line = feature_element | 
| 15 | 
            +
                      if feature_element.is_a?(CukeModeler::Scenario) # :Scenario
         | 
| 16 | 
            +
                        test_line = feature_element.source_line
         | 
| 20 17 |  | 
| 21 18 | 
             
                        # We don't accept the feature_element if the current tags are not valid
         | 
| 22 | 
            -
                        return unless  | 
| 19 | 
            +
                        return unless matches_tags?(scenario_tags)
         | 
| 23 20 | 
             
                        # or if it is not at the correct location
         | 
| 24 21 | 
             
                        return if line_numbers.any? && !line_numbers.include?(test_line)
         | 
| 25 22 |  | 
| 26 | 
            -
                        @scenarios << [uri, feature_element | 
| 23 | 
            +
                        @scenarios << [uri, feature_element.source_line].join(":")
         | 
| 27 24 | 
             
                      else # :ScenarioOutline
         | 
| 28 | 
            -
                        feature_element | 
| 29 | 
            -
                          example_tags = example | 
| 25 | 
            +
                        feature_element.examples.each do |example|
         | 
| 26 | 
            +
                          example_tags = example.tags.map(&:name)
         | 
| 30 27 | 
             
                          example_tags = scenario_tags + example_tags
         | 
| 31 | 
            -
                          next unless  | 
| 32 | 
            -
                          rows | 
| 33 | 
            -
             | 
| 34 | 
            -
                            test_line = row[:location][:line]
         | 
| 28 | 
            +
                          next unless matches_tags?(example_tags)
         | 
| 29 | 
            +
                          example.rows[1..-1].each do |row|
         | 
| 30 | 
            +
                            test_line = row.source_line
         | 
| 35 31 | 
             
                            next if line_numbers.any? && !line_numbers.include?(test_line)
         | 
| 36 32 |  | 
| 37 33 | 
             
                            @scenarios << [uri, test_line].join(':')
         | 
| @@ -42,6 +38,12 @@ module ParallelTests | |
| 42 38 |  | 
| 43 39 | 
             
                    def method_missing(*args)
         | 
| 44 40 | 
             
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    private
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    def matches_tags?(tags)
         | 
| 45 | 
            +
                      @tag_expression.nil? || @tag_expression.evaluate(tags)
         | 
| 46 | 
            +
                    end
         | 
| 45 47 | 
             
                  end
         | 
| 46 48 | 
             
                end
         | 
| 47 49 | 
             
              end
         | 
| @@ -1,12 +1,17 @@ | |
| 1 1 | 
             
            require 'cucumber/tag_expressions/parser'
         | 
| 2 | 
            -
            require 'cucumber/core/gherkin/tag_expression'
         | 
| 3 2 | 
             
            require 'cucumber/runtime'
         | 
| 4 3 | 
             
            require 'cucumber'
         | 
| 5 4 | 
             
            require 'parallel_tests/cucumber/scenario_line_logger'
         | 
| 6 5 | 
             
            require 'parallel_tests/gherkin/listener'
         | 
| 7 | 
            -
            require 'gherkin/errors'
         | 
| 8 6 | 
             
            require 'shellwords'
         | 
| 9 7 |  | 
| 8 | 
            +
            begin
         | 
| 9 | 
            +
              gem "cuke_modeler", "~> 3.0"
         | 
| 10 | 
            +
              require 'cuke_modeler'
         | 
| 11 | 
            +
            rescue LoadError
         | 
| 12 | 
            +
              raise 'Grouping by individual cucumber scenarios requires the `cuke_modeler` modeler gem with requirement `~> 3.0`.  Add `gem "cuke_modeler", "~> 3.0"` to your `Gemfile`, run `bundle install` and try again.'
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 10 15 | 
             
            module ParallelTests
         | 
| 11 16 | 
             
              module Cucumber
         | 
| 12 17 | 
             
                class Scenarios
         | 
| @@ -40,32 +45,17 @@ module ParallelTests | |
| 40 45 | 
             
                        path, *test_lines = path.split(/:(?=\d+)/)
         | 
| 41 46 | 
             
                        test_lines.map!(&:to_i)
         | 
| 42 47 |  | 
| 43 | 
            -
                        # We encode the file and get the content of it
         | 
| 44 | 
            -
                        source = ::Cucumber::Runtime::NormalisedEncodingFile.read(path)
         | 
| 45 48 | 
             
                        # We create a Gherkin document, this will be used to decode the details of each scenario
         | 
| 46 | 
            -
                        document = :: | 
| 47 | 
            -
             | 
| 48 | 
            -
                        # We create a parser for the gherkin document
         | 
| 49 | 
            -
                        parser  = ::Gherkin::Parser.new()
         | 
| 50 | 
            -
                        scanner = ::Gherkin::TokenScanner.new(document.body)
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                        begin
         | 
| 53 | 
            -
                          # We make an attempt to parse the gherkin document, this could be failed if the document is not well formatted
         | 
| 54 | 
            -
                          result = parser.parse(scanner)
         | 
| 55 | 
            -
                          feature_tags = result[:feature][:tags].map { |tag| tag[:name] }
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                          # We loop on each children of the feature
         | 
| 58 | 
            -
                          result[:feature][:children].each do |feature_element|
         | 
| 59 | 
            -
                            # If the type of the child is not a scenario or scenario outline, we continue, we are only interested by the name of the scenario here
         | 
| 60 | 
            -
                            next unless /Scenario/.match(feature_element[:type])
         | 
| 49 | 
            +
                        document = ::CukeModeler::FeatureFile.new(path)
         | 
| 50 | 
            +
                        feature = document.feature
         | 
| 61 51 |  | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
                          end
         | 
| 52 | 
            +
                        # We make an attempt to parse the gherkin document, this could be failed if the document is not well formatted
         | 
| 53 | 
            +
                        feature_tags = feature.tags.map(&:name)
         | 
| 65 54 |  | 
| 66 | 
            -
                         | 
| 67 | 
            -
             | 
| 68 | 
            -
                           | 
| 55 | 
            +
                        # We loop on each children of the feature
         | 
| 56 | 
            +
                        feature.tests.each do |test|
         | 
| 57 | 
            +
                          # It's a scenario, we add it to the scenario_line_logger
         | 
| 58 | 
            +
                          scenario_line_logger.visit_feature_element(document.path, test, feature_tags, line_numbers: test_lines)
         | 
| 69 59 | 
             
                        end
         | 
| 70 60 | 
             
                      end
         | 
| 71 61 |  | 
| @@ -14,7 +14,7 @@ module ParallelTests | |
| 14 14 | 
             
                    end
         | 
| 15 15 |  | 
| 16 16 | 
             
                    config.on_event :test_case_finished do |event|
         | 
| 17 | 
            -
                      @example_times[event.test_case. | 
| 17 | 
            +
                      @example_times[event.test_case.location.file] += ParallelTests.now.to_f - @start_at
         | 
| 18 18 | 
             
                    end
         | 
| 19 19 |  | 
| 20 20 | 
             
                    config.on_event :test_run_finished do |_|
         | 
| @@ -2,7 +2,7 @@ module ParallelTests | |
| 2 2 | 
             
              class Grouper
         | 
| 3 3 | 
             
                class << self
         | 
| 4 4 | 
             
                  def by_steps(tests, num_groups, options)
         | 
| 5 | 
            -
                    features_with_steps =  | 
| 5 | 
            +
                    features_with_steps = group_by_features_with_steps(tests, options)
         | 
| 6 6 | 
             
                    in_even_groups_by_size(features_with_steps, num_groups)
         | 
| 7 7 | 
             
                  end
         | 
| 8 8 |  | 
| @@ -15,19 +15,46 @@ module ParallelTests | |
| 15 15 | 
             
                    groups = Array.new(num_groups) { {:items => [], :size => 0} }
         | 
| 16 16 |  | 
| 17 17 | 
             
                    # add all files that should run in a single process to one group
         | 
| 18 | 
            -
                     | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 18 | 
            +
                    single_process_patterns = options[:single_process] || []
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    single_items, items = items.partition do |item, _size|
         | 
| 21 | 
            +
                      single_process_patterns.any? { |pattern| item =~ pattern }
         | 
| 21 22 | 
             
                    end
         | 
| 22 23 |  | 
| 23 | 
            -
                     | 
| 24 | 
            -
             | 
| 24 | 
            +
                    isolate_count = isolate_count(options)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    if isolate_count >= num_groups
         | 
| 27 | 
            +
                      raise 'Number of isolated processes must be less than total the number of processes'
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    if isolate_count >= 1
         | 
| 31 | 
            +
                      # add all files that should run in a multiple isolated processes to their own groups
         | 
| 32 | 
            +
                      group_features_by_size(items_to_group(single_items), groups[0..(isolate_count - 1)])
         | 
| 33 | 
            +
                      # group the non-isolated by size
         | 
| 34 | 
            +
                      group_features_by_size(items_to_group(items), groups[isolate_count..-1])
         | 
| 35 | 
            +
                    else
         | 
| 36 | 
            +
                      # add all files that should run in a single non-isolated process to first group
         | 
| 37 | 
            +
                      single_items.each { |item, size| add_to_group(groups.first, item, size) }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      # group all by size
         | 
| 40 | 
            +
                      group_features_by_size(items_to_group(items), groups)
         | 
| 41 | 
            +
                    end
         | 
| 25 42 |  | 
| 26 43 | 
             
                    groups.map! { |g| g[:items].sort }
         | 
| 27 44 | 
             
                  end
         | 
| 28 45 |  | 
| 29 46 | 
             
                  private
         | 
| 30 47 |  | 
| 48 | 
            +
                  def isolate_count(options)
         | 
| 49 | 
            +
                    if options[:isolate_count] && options[:isolate_count] > 1
         | 
| 50 | 
            +
                      options[:isolate_count]
         | 
| 51 | 
            +
                    elsif options[:isolate]
         | 
| 52 | 
            +
                      1
         | 
| 53 | 
            +
                    else
         | 
| 54 | 
            +
                      0
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 31 58 | 
             
                  def largest_first(files)
         | 
| 32 59 | 
             
                    files.sort_by{|_item, size| size }.reverse
         | 
| 33 60 | 
             
                  end
         | 
| @@ -41,23 +68,9 @@ module ParallelTests | |
| 41 68 | 
             
                    group[:size] += size
         | 
| 42 69 | 
             
                  end
         | 
| 43 70 |  | 
| 44 | 
            -
                  def  | 
| 45 | 
            -
                    require ' | 
| 46 | 
            -
                     | 
| 47 | 
            -
                    parser = ::Gherkin::Parser.new
         | 
| 48 | 
            -
                    # format of hash will be FILENAME => NUM_STEPS
         | 
| 49 | 
            -
                    steps_per_file = tests.each_with_object({}) do |file,steps|
         | 
| 50 | 
            -
                      feature = parser.parse(File.read(file)).fetch(:feature)
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                      # skip feature if it matches tag regex
         | 
| 53 | 
            -
                      next if feature[:tags].grep(ignore_tag_pattern).any?
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                      # count the number of steps in the file
         | 
| 56 | 
            -
                      # will only include a feature if the regex does not match
         | 
| 57 | 
            -
                      all_steps = feature[:children].map{|a| a[:steps].count if a[:tags].grep(ignore_tag_pattern).empty? }.compact
         | 
| 58 | 
            -
                      steps[file] = all_steps.inject(0,:+)
         | 
| 59 | 
            -
                    end
         | 
| 60 | 
            -
                    steps_per_file.sort_by { |_, value| -value }
         | 
| 71 | 
            +
                  def group_by_features_with_steps(tests, options)
         | 
| 72 | 
            +
                    require 'parallel_tests/cucumber/features_with_steps'
         | 
| 73 | 
            +
                    ParallelTests::Cucumber::FeaturesWithSteps.all(tests, options)
         | 
| 61 74 | 
             
                  end
         | 
| 62 75 |  | 
| 63 76 | 
             
                  def group_by_scenarios(tests, options={})
         | 
| @@ -4,8 +4,6 @@ module ParallelTests | |
| 4 4 | 
             
              module RSpec
         | 
| 5 5 | 
             
                class Runner < ParallelTests::Test::Runner
         | 
| 6 6 | 
             
                  DEV_NULL = (WINDOWS ? "NUL" : "/dev/null")
         | 
| 7 | 
            -
                  NAME = 'RSpec'
         | 
| 8 | 
            -
             | 
| 9 7 | 
             
                  class << self
         | 
| 10 8 | 
             
                    def run_tests(test_files, process_number, num_processes, options)
         | 
| 11 9 | 
             
                      exe = executable # expensive, so we cache
         | 
| @@ -50,6 +48,22 @@ module ParallelTests | |
| 50 48 | 
             
                      "#{clean} --seed #{seed}"
         | 
| 51 49 | 
             
                    end
         | 
| 52 50 |  | 
| 51 | 
            +
                    # Summarize results from threads and colorize results based on failure and pending counts.
         | 
| 52 | 
            +
                    #
         | 
| 53 | 
            +
                    def summarize_results(results)
         | 
| 54 | 
            +
                      text = super
         | 
| 55 | 
            +
                      return text unless $stdout.tty?
         | 
| 56 | 
            +
                      sums = sum_up_results(results)
         | 
| 57 | 
            +
                      color =
         | 
| 58 | 
            +
                        if sums['failure'].positive?
         | 
| 59 | 
            +
                          31 # red
         | 
| 60 | 
            +
                        elsif sums['pending'].positive?
         | 
| 61 | 
            +
                          33 # yellow
         | 
| 62 | 
            +
                        else
         | 
| 63 | 
            +
                          32 # green
         | 
| 64 | 
            +
                        end
         | 
| 65 | 
            +
                      "\e[#{color}m#{text}\e[0m"
         | 
| 66 | 
            +
                    end
         | 
| 53 67 |  | 
| 54 68 | 
             
                    private
         | 
| 55 69 |  | 
    
        data/lib/parallel_tests/tasks.rb
    CHANGED
    
    
| @@ -3,15 +3,9 @@ require 'parallel_tests' | |
| 3 3 | 
             
            module ParallelTests
         | 
| 4 4 | 
             
              module Test
         | 
| 5 5 | 
             
                class Runner
         | 
| 6 | 
            -
                  NAME = 'Test'
         | 
| 7 | 
            -
             | 
| 8 6 | 
             
                  class << self
         | 
| 9 7 | 
             
                    # --- usually overwritten by other runners
         | 
| 10 8 |  | 
| 11 | 
            -
                    def name
         | 
| 12 | 
            -
                      NAME
         | 
| 13 | 
            -
                    end
         | 
| 14 | 
            -
             | 
| 15 9 | 
             
                    def runtime_log
         | 
| 16 10 | 
             
                      'tmp/parallel_runtime_test.log'
         | 
| 17 11 | 
             
                    end
         | 
| @@ -179,7 +173,7 @@ module ParallelTests | |
| 179 173 | 
             
                        allowed_missing -= 1 unless time = runtimes[test]
         | 
| 180 174 | 
             
                        if allowed_missing < 0
         | 
| 181 175 | 
             
                          log = options[:runtime_log] || runtime_log
         | 
| 182 | 
            -
                          raise "Runtime log file '#{log}' does not contain sufficient data to sort #{tests.size} test files, please update it."
         | 
| 176 | 
            +
                          raise "Runtime log file '#{log}' does not contain sufficient data to sort #{tests.size} test files, please update or remove it."
         | 
| 183 177 | 
             
                        end
         | 
| 184 178 | 
             
                        [test, time]
         | 
| 185 179 | 
             
                      end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: parallel_tests
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 3.4.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Michael Grosser
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2020- | 
| 11 | 
            +
            date: 2020-11-23 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: parallel
         | 
| @@ -42,6 +42,7 @@ files: | |
| 42 42 | 
             
            - lib/parallel_tests.rb
         | 
| 43 43 | 
             
            - lib/parallel_tests/cli.rb
         | 
| 44 44 | 
             
            - lib/parallel_tests/cucumber/failures_logger.rb
         | 
| 45 | 
            +
            - lib/parallel_tests/cucumber/features_with_steps.rb
         | 
| 45 46 | 
             
            - lib/parallel_tests/cucumber/runner.rb
         | 
| 46 47 | 
             
            - lib/parallel_tests/cucumber/scenario_line_logger.rb
         | 
| 47 48 | 
             
            - lib/parallel_tests/cucumber/scenarios.rb
         | 
| @@ -67,8 +68,8 @@ licenses: | |
| 67 68 | 
             
            - MIT
         | 
| 68 69 | 
             
            metadata:
         | 
| 69 70 | 
             
              bug_tracker_uri: https://github.com/grosser/parallel_tests/issues
         | 
| 70 | 
            -
              documentation_uri: https://github.com/grosser/parallel_tests/blob/ | 
| 71 | 
            -
              source_code_uri: https://github.com/grosser/parallel_tests/tree/ | 
| 71 | 
            +
              documentation_uri: https://github.com/grosser/parallel_tests/blob/v3.4.0/Readme.md
         | 
| 72 | 
            +
              source_code_uri: https://github.com/grosser/parallel_tests/tree/v3.4.0
         | 
| 72 73 | 
             
              wiki_uri: https://github.com/grosser/parallel_tests/wiki
         | 
| 73 74 | 
             
            post_install_message: 
         | 
| 74 75 | 
             
            rdoc_options: []
         | 
| @@ -78,14 +79,14 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 78 79 | 
             
              requirements:
         | 
| 79 80 | 
             
              - - ">="
         | 
| 80 81 | 
             
                - !ruby/object:Gem::Version
         | 
| 81 | 
            -
                  version: 2. | 
| 82 | 
            +
                  version: 2.4.0
         | 
| 82 83 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 83 84 | 
             
              requirements:
         | 
| 84 85 | 
             
              - - ">="
         | 
| 85 86 | 
             
                - !ruby/object:Gem::Version
         | 
| 86 87 | 
             
                  version: '0'
         | 
| 87 88 | 
             
            requirements: []
         | 
| 88 | 
            -
            rubygems_version: 3. | 
| 89 | 
            +
            rubygems_version: 3.1.3
         | 
| 89 90 | 
             
            signing_key: 
         | 
| 90 91 | 
             
            specification_version: 4
         | 
| 91 92 | 
             
            summary: Run Test::Unit / RSpec / Cucumber / Spinach in parallel
         |