rspec-core 3.8.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 +7 -0
 - checksums.yaml.gz.sig +0 -0
 - data.tar.gz.sig +0 -0
 - data/.document +5 -0
 - data/.yardopts +8 -0
 - data/Changelog.md +2243 -0
 - data/LICENSE.md +26 -0
 - data/README.md +384 -0
 - data/exe/rspec +4 -0
 - data/lib/rspec/autorun.rb +3 -0
 - data/lib/rspec/core.rb +185 -0
 - data/lib/rspec/core/backtrace_formatter.rb +65 -0
 - data/lib/rspec/core/bisect/coordinator.rb +62 -0
 - data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
 - data/lib/rspec/core/bisect/fork_runner.rb +134 -0
 - data/lib/rspec/core/bisect/server.rb +61 -0
 - data/lib/rspec/core/bisect/shell_command.rb +126 -0
 - data/lib/rspec/core/bisect/shell_runner.rb +73 -0
 - data/lib/rspec/core/bisect/utilities.rb +58 -0
 - data/lib/rspec/core/configuration.rb +2308 -0
 - data/lib/rspec/core/configuration_options.rb +233 -0
 - data/lib/rspec/core/drb.rb +113 -0
 - data/lib/rspec/core/dsl.rb +98 -0
 - data/lib/rspec/core/example.rb +656 -0
 - data/lib/rspec/core/example_group.rb +889 -0
 - data/lib/rspec/core/example_status_persister.rb +235 -0
 - data/lib/rspec/core/filter_manager.rb +231 -0
 - data/lib/rspec/core/flat_map.rb +20 -0
 - data/lib/rspec/core/formatters.rb +269 -0
 - data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
 - data/lib/rspec/core/formatters/base_formatter.rb +70 -0
 - data/lib/rspec/core/formatters/base_text_formatter.rb +75 -0
 - data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
 - data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
 - data/lib/rspec/core/formatters/console_codes.rb +68 -0
 - data/lib/rspec/core/formatters/deprecation_formatter.rb +223 -0
 - data/lib/rspec/core/formatters/documentation_formatter.rb +70 -0
 - data/lib/rspec/core/formatters/exception_presenter.rb +508 -0
 - data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
 - data/lib/rspec/core/formatters/helpers.rb +110 -0
 - data/lib/rspec/core/formatters/html_formatter.rb +153 -0
 - data/lib/rspec/core/formatters/html_printer.rb +414 -0
 - data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
 - data/lib/rspec/core/formatters/json_formatter.rb +102 -0
 - data/lib/rspec/core/formatters/profile_formatter.rb +68 -0
 - data/lib/rspec/core/formatters/progress_formatter.rb +29 -0
 - data/lib/rspec/core/formatters/protocol.rb +182 -0
 - data/lib/rspec/core/formatters/snippet_extractor.rb +134 -0
 - data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
 - data/lib/rspec/core/hooks.rb +624 -0
 - data/lib/rspec/core/invocations.rb +87 -0
 - data/lib/rspec/core/memoized_helpers.rb +554 -0
 - data/lib/rspec/core/metadata.rb +498 -0
 - data/lib/rspec/core/metadata_filter.rb +255 -0
 - data/lib/rspec/core/minitest_assertions_adapter.rb +31 -0
 - data/lib/rspec/core/mocking_adapters/flexmock.rb +31 -0
 - data/lib/rspec/core/mocking_adapters/mocha.rb +57 -0
 - data/lib/rspec/core/mocking_adapters/null.rb +14 -0
 - data/lib/rspec/core/mocking_adapters/rr.rb +31 -0
 - data/lib/rspec/core/mocking_adapters/rspec.rb +32 -0
 - data/lib/rspec/core/notifications.rb +521 -0
 - data/lib/rspec/core/option_parser.rb +309 -0
 - data/lib/rspec/core/ordering.rb +158 -0
 - data/lib/rspec/core/output_wrapper.rb +29 -0
 - data/lib/rspec/core/pending.rb +165 -0
 - data/lib/rspec/core/profiler.rb +34 -0
 - data/lib/rspec/core/project_initializer.rb +48 -0
 - data/lib/rspec/core/project_initializer/.rspec +1 -0
 - data/lib/rspec/core/project_initializer/spec/spec_helper.rb +100 -0
 - data/lib/rspec/core/rake_task.rb +168 -0
 - data/lib/rspec/core/reporter.rb +257 -0
 - data/lib/rspec/core/ruby_project.rb +53 -0
 - data/lib/rspec/core/runner.rb +199 -0
 - data/lib/rspec/core/sandbox.rb +37 -0
 - data/lib/rspec/core/set.rb +54 -0
 - data/lib/rspec/core/shared_context.rb +55 -0
 - data/lib/rspec/core/shared_example_group.rb +269 -0
 - data/lib/rspec/core/shell_escape.rb +49 -0
 - data/lib/rspec/core/test_unit_assertions_adapter.rb +30 -0
 - data/lib/rspec/core/version.rb +9 -0
 - data/lib/rspec/core/warnings.rb +40 -0
 - data/lib/rspec/core/world.rb +275 -0
 - metadata +292 -0
 - metadata.gz.sig +0 -0
 
| 
         @@ -0,0 +1,235 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            RSpec::Support.require_rspec_support "directory_maker"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module RSpec
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Core
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Persists example ids and their statuses so that we can filter
         
     | 
| 
      
 6 
     | 
    
         
            +
                # to just the ones that failed the last time they ran.
         
     | 
| 
      
 7 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 8 
     | 
    
         
            +
                class ExampleStatusPersister
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def self.load_from(file_name)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    return [] unless File.exist?(file_name)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    ExampleStatusParser.parse(File.read(file_name))
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def self.persist(examples, file_name)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    new(examples, file_name).persist
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def initialize(examples, file_name)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @examples  = examples
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @file_name = file_name
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def persist
         
     | 
| 
      
 24 
     | 
    
         
            +
                    RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(@file_name))
         
     | 
| 
      
 25 
     | 
    
         
            +
                    File.open(@file_name, File::RDWR | File::CREAT) do |f|
         
     | 
| 
      
 26 
     | 
    
         
            +
                      # lock the file while reading / persisting to avoid a race
         
     | 
| 
      
 27 
     | 
    
         
            +
                      # condition where parallel or unrelated spec runs race to
         
     | 
| 
      
 28 
     | 
    
         
            +
                      # update the same file
         
     | 
| 
      
 29 
     | 
    
         
            +
                      f.flock(File::LOCK_EX)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      unparsed_previous_runs = f.read
         
     | 
| 
      
 31 
     | 
    
         
            +
                      f.rewind
         
     | 
| 
      
 32 
     | 
    
         
            +
                      f.write(dump_statuses(unparsed_previous_runs))
         
     | 
| 
      
 33 
     | 
    
         
            +
                      f.flush
         
     | 
| 
      
 34 
     | 
    
         
            +
                      f.truncate(f.pos)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                private
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  def dump_statuses(unparsed_previous_runs)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    statuses_from_previous_runs = ExampleStatusParser.parse(unparsed_previous_runs)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    merged_statuses = ExampleStatusMerger.merge(statuses_from_this_run, statuses_from_previous_runs)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    ExampleStatusDumper.dump(merged_statuses)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  def statuses_from_this_run
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @examples.map do |ex|
         
     | 
| 
      
 48 
     | 
    
         
            +
                      result = ex.execution_result
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                      {
         
     | 
| 
      
 51 
     | 
    
         
            +
                        :example_id => ex.id,
         
     | 
| 
      
 52 
     | 
    
         
            +
                        :status     => result.status ? result.status.to_s : Configuration::UNKNOWN_STATUS,
         
     | 
| 
      
 53 
     | 
    
         
            +
                        :run_time   => result.run_time ? Formatters::Helpers.format_duration(result.run_time) : ""
         
     | 
| 
      
 54 
     | 
    
         
            +
                      }
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                # Merges together a list of example statuses from this run
         
     | 
| 
      
 60 
     | 
    
         
            +
                # and a list from previous runs (presumably loaded from disk).
         
     | 
| 
      
 61 
     | 
    
         
            +
                # Each example status object is expected to be a hash with
         
     | 
| 
      
 62 
     | 
    
         
            +
                # at least an `:example_id` and a `:status` key. Examples that
         
     | 
| 
      
 63 
     | 
    
         
            +
                # were loaded but not executed (due to filtering, `--fail-fast`
         
     | 
| 
      
 64 
     | 
    
         
            +
                # or whatever) should have a `:status` of `UNKNOWN_STATUS`.
         
     | 
| 
      
 65 
     | 
    
         
            +
                #
         
     | 
| 
      
 66 
     | 
    
         
            +
                # This willl produce a new list that:
         
     | 
| 
      
 67 
     | 
    
         
            +
                #   - Will be missing examples from previous runs that we know for sure
         
     | 
| 
      
 68 
     | 
    
         
            +
                #     no longer exist.
         
     | 
| 
      
 69 
     | 
    
         
            +
                #   - Will have the latest known status for any examples that either
         
     | 
| 
      
 70 
     | 
    
         
            +
                #     definitively do exist or may still exist.
         
     | 
| 
      
 71 
     | 
    
         
            +
                #   - Is sorted by file name and example definition order, so that
         
     | 
| 
      
 72 
     | 
    
         
            +
                #     the saved file is easily scannable if users want to inspect it.
         
     | 
| 
      
 73 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 74 
     | 
    
         
            +
                class ExampleStatusMerger
         
     | 
| 
      
 75 
     | 
    
         
            +
                  def self.merge(this_run, from_previous_runs)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    new(this_run, from_previous_runs).merge
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  def initialize(this_run, from_previous_runs)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    @this_run           = hash_from(this_run)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    @from_previous_runs = hash_from(from_previous_runs)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    @file_exists_cache  = Hash.new { |hash, file| hash[file] = File.exist?(file) }
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  def merge
         
     | 
| 
      
 86 
     | 
    
         
            +
                    delete_previous_examples_that_no_longer_exist
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                    @this_run.merge(@from_previous_runs) do |_ex_id, new, old|
         
     | 
| 
      
 89 
     | 
    
         
            +
                      new.fetch(:status) == Configuration::UNKNOWN_STATUS ? old : new
         
     | 
| 
      
 90 
     | 
    
         
            +
                    end.values.sort_by(&method(:sort_value_from))
         
     | 
| 
      
 91 
     | 
    
         
            +
                  end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                private
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  def hash_from(example_list)
         
     | 
| 
      
 96 
     | 
    
         
            +
                    example_list.inject({}) do |hash, example|
         
     | 
| 
      
 97 
     | 
    
         
            +
                      hash[example.fetch(:example_id)] = example
         
     | 
| 
      
 98 
     | 
    
         
            +
                      hash
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  def delete_previous_examples_that_no_longer_exist
         
     | 
| 
      
 103 
     | 
    
         
            +
                    @from_previous_runs.delete_if do |ex_id, _|
         
     | 
| 
      
 104 
     | 
    
         
            +
                      example_must_no_longer_exist?(ex_id)
         
     | 
| 
      
 105 
     | 
    
         
            +
                    end
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                  def example_must_no_longer_exist?(ex_id)
         
     | 
| 
      
 109 
     | 
    
         
            +
                    # Obviously, it exists if it was loaded for this spec run...
         
     | 
| 
      
 110 
     | 
    
         
            +
                    return false if @this_run.key?(ex_id)
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    spec_file = spec_file_from(ex_id)
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                    # `this_run` includes examples that were loaded but not executed.
         
     | 
| 
      
 115 
     | 
    
         
            +
                    # Given that, if the spec file for this example was loaded,
         
     | 
| 
      
 116 
     | 
    
         
            +
                    # but the id does not still exist, it's safe to assume that
         
     | 
| 
      
 117 
     | 
    
         
            +
                    # the example must no longer exist.
         
     | 
| 
      
 118 
     | 
    
         
            +
                    return true if loaded_spec_files.include?(spec_file)
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    # The example may still exist as long as the file exists...
         
     | 
| 
      
 121 
     | 
    
         
            +
                    !@file_exists_cache[spec_file]
         
     | 
| 
      
 122 
     | 
    
         
            +
                  end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                  def loaded_spec_files
         
     | 
| 
      
 125 
     | 
    
         
            +
                    @loaded_spec_files ||= Set.new(@this_run.keys.map(&method(:spec_file_from)))
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                  def spec_file_from(ex_id)
         
     | 
| 
      
 129 
     | 
    
         
            +
                    ex_id.split("[").first
         
     | 
| 
      
 130 
     | 
    
         
            +
                  end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                  def sort_value_from(example)
         
     | 
| 
      
 133 
     | 
    
         
            +
                    file, scoped_id = Example.parse_id(example.fetch(:example_id))
         
     | 
| 
      
 134 
     | 
    
         
            +
                    [file, *scoped_id.split(":").map(&method(:Integer))]
         
     | 
| 
      
 135 
     | 
    
         
            +
                  end
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                # Dumps a list of hashes in a pretty, human readable format
         
     | 
| 
      
 139 
     | 
    
         
            +
                # for later parsing. The hashes are expected to have symbol
         
     | 
| 
      
 140 
     | 
    
         
            +
                # keys and string values, and each hash should have the same
         
     | 
| 
      
 141 
     | 
    
         
            +
                # set of keys.
         
     | 
| 
      
 142 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 143 
     | 
    
         
            +
                class ExampleStatusDumper
         
     | 
| 
      
 144 
     | 
    
         
            +
                  def self.dump(examples)
         
     | 
| 
      
 145 
     | 
    
         
            +
                    new(examples).dump
         
     | 
| 
      
 146 
     | 
    
         
            +
                  end
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                  def initialize(examples)
         
     | 
| 
      
 149 
     | 
    
         
            +
                    @examples = examples
         
     | 
| 
      
 150 
     | 
    
         
            +
                  end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                  def dump
         
     | 
| 
      
 153 
     | 
    
         
            +
                    return nil if @examples.empty?
         
     | 
| 
      
 154 
     | 
    
         
            +
                    (formatted_header_rows + formatted_value_rows).join("\n") << "\n"
         
     | 
| 
      
 155 
     | 
    
         
            +
                  end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                private
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                  def formatted_header_rows
         
     | 
| 
      
 160 
     | 
    
         
            +
                    @formatted_header_rows ||= begin
         
     | 
| 
      
 161 
     | 
    
         
            +
                      dividers = column_widths.map { |w| "-" * w }
         
     | 
| 
      
 162 
     | 
    
         
            +
                      [formatted_row_from(headers.map(&:to_s)), formatted_row_from(dividers)]
         
     | 
| 
      
 163 
     | 
    
         
            +
                    end
         
     | 
| 
      
 164 
     | 
    
         
            +
                  end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                  def formatted_value_rows
         
     | 
| 
      
 167 
     | 
    
         
            +
                    @foramtted_value_rows ||= rows.map do |row|
         
     | 
| 
      
 168 
     | 
    
         
            +
                      formatted_row_from(row)
         
     | 
| 
      
 169 
     | 
    
         
            +
                    end
         
     | 
| 
      
 170 
     | 
    
         
            +
                  end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                  def rows
         
     | 
| 
      
 173 
     | 
    
         
            +
                    @rows ||= @examples.map { |ex| ex.values_at(*headers) }
         
     | 
| 
      
 174 
     | 
    
         
            +
                  end
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                  def formatted_row_from(row_values)
         
     | 
| 
      
 177 
     | 
    
         
            +
                    padded_values = row_values.each_with_index.map do |value, index|
         
     | 
| 
      
 178 
     | 
    
         
            +
                      value.ljust(column_widths[index])
         
     | 
| 
      
 179 
     | 
    
         
            +
                    end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                    padded_values.join(" | ") << " |"
         
     | 
| 
      
 182 
     | 
    
         
            +
                  end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                  def headers
         
     | 
| 
      
 185 
     | 
    
         
            +
                    @headers ||= @examples.first.keys
         
     | 
| 
      
 186 
     | 
    
         
            +
                  end
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
                  def column_widths
         
     | 
| 
      
 189 
     | 
    
         
            +
                    @column_widths ||= begin
         
     | 
| 
      
 190 
     | 
    
         
            +
                      value_sets = rows.transpose
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
                      headers.each_with_index.map do |header, index|
         
     | 
| 
      
 193 
     | 
    
         
            +
                        values = value_sets[index] << header.to_s
         
     | 
| 
      
 194 
     | 
    
         
            +
                        values.map(&:length).max
         
     | 
| 
      
 195 
     | 
    
         
            +
                      end
         
     | 
| 
      
 196 
     | 
    
         
            +
                    end
         
     | 
| 
      
 197 
     | 
    
         
            +
                  end
         
     | 
| 
      
 198 
     | 
    
         
            +
                end
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                # Parses a string that has been previously dumped by ExampleStatusDumper.
         
     | 
| 
      
 201 
     | 
    
         
            +
                # Note that this parser is a bit naive in that it does a simple split on
         
     | 
| 
      
 202 
     | 
    
         
            +
                # "\n" and " | ", with no concern for handling escaping. For now, that's
         
     | 
| 
      
 203 
     | 
    
         
            +
                # OK because the values we plan to persist (example id, status, and perhaps
         
     | 
| 
      
 204 
     | 
    
         
            +
                # example duration) are highly unlikely to contain "\n" or " | " -- after
         
     | 
| 
      
 205 
     | 
    
         
            +
                # all, who puts those in file names?
         
     | 
| 
      
 206 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 207 
     | 
    
         
            +
                class ExampleStatusParser
         
     | 
| 
      
 208 
     | 
    
         
            +
                  def self.parse(string)
         
     | 
| 
      
 209 
     | 
    
         
            +
                    new(string).parse
         
     | 
| 
      
 210 
     | 
    
         
            +
                  end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                  def initialize(string)
         
     | 
| 
      
 213 
     | 
    
         
            +
                    @header_line, _, *@row_lines = string.lines.to_a
         
     | 
| 
      
 214 
     | 
    
         
            +
                  end
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
                  def parse
         
     | 
| 
      
 217 
     | 
    
         
            +
                    @row_lines.map { |line| parse_row(line) }
         
     | 
| 
      
 218 
     | 
    
         
            +
                  end
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
                private
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                  def parse_row(line)
         
     | 
| 
      
 223 
     | 
    
         
            +
                    Hash[headers.zip(split_line(line))]
         
     | 
| 
      
 224 
     | 
    
         
            +
                  end
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                  def headers
         
     | 
| 
      
 227 
     | 
    
         
            +
                    @headers ||= split_line(@header_line).grep(/\S/).map(&:to_sym)
         
     | 
| 
      
 228 
     | 
    
         
            +
                  end
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
                  def split_line(line)
         
     | 
| 
      
 231 
     | 
    
         
            +
                    line.split(/\s+\|\s+?/, -1)
         
     | 
| 
      
 232 
     | 
    
         
            +
                  end
         
     | 
| 
      
 233 
     | 
    
         
            +
                end
         
     | 
| 
      
 234 
     | 
    
         
            +
              end
         
     | 
| 
      
 235 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,231 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module RSpec
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Core
         
     | 
| 
      
 3 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 4 
     | 
    
         
            +
                class FilterManager
         
     | 
| 
      
 5 
     | 
    
         
            +
                  attr_reader :exclusions, :inclusions
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @exclusions, @inclusions = FilterRules.build
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 12 
     | 
    
         
            +
                  #
         
     | 
| 
      
 13 
     | 
    
         
            +
                  # @param file_path [String]
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # @param line_numbers [Array]
         
     | 
| 
      
 15 
     | 
    
         
            +
                  def add_location(file_path, line_numbers)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    # locations is a hash of expanded paths to arrays of line
         
     | 
| 
      
 17 
     | 
    
         
            +
                    # numbers to match against. e.g.
         
     | 
| 
      
 18 
     | 
    
         
            +
                    #   { "path/to/file.rb" => [37, 42] }
         
     | 
| 
      
 19 
     | 
    
         
            +
                    add_path_to_arrays_filter(:locations, File.expand_path(file_path), line_numbers)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def add_ids(rerun_path, scoped_ids)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    # ids is a hash of relative paths to arrays of ids
         
     | 
| 
      
 24 
     | 
    
         
            +
                    # to match against. e.g.
         
     | 
| 
      
 25 
     | 
    
         
            +
                    #   { "./path/to/file.rb" => ["1:1", "2:4"] }
         
     | 
| 
      
 26 
     | 
    
         
            +
                    rerun_path = Metadata.relative_path(File.expand_path rerun_path)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    add_path_to_arrays_filter(:ids, rerun_path, scoped_ids)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def empty?
         
     | 
| 
      
 31 
     | 
    
         
            +
                    inclusions.empty? && exclusions.empty?
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def prune(examples)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    # Semantically, this is unnecessary (the filtering below will return the empty
         
     | 
| 
      
 36 
     | 
    
         
            +
                    # array unmodified), but for perf reasons it's worth exiting early here. Users
         
     | 
| 
      
 37 
     | 
    
         
            +
                    # commonly have top-level examples groups that do not have any direct examples
         
     | 
| 
      
 38 
     | 
    
         
            +
                    # and instead have nested groups with examples. In that kind of situation,
         
     | 
| 
      
 39 
     | 
    
         
            +
                    # `examples` will be empty.
         
     | 
| 
      
 40 
     | 
    
         
            +
                    return examples if examples.empty?
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    examples = prune_conditionally_filtered_examples(examples)
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    if inclusions.standalone?
         
     | 
| 
      
 45 
     | 
    
         
            +
                      examples.select { |e| inclusions.include_example?(e) }
         
     | 
| 
      
 46 
     | 
    
         
            +
                    else
         
     | 
| 
      
 47 
     | 
    
         
            +
                      locations, ids, non_scoped_inclusions = inclusions.split_file_scoped_rules
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                      examples.select do |ex|
         
     | 
| 
      
 50 
     | 
    
         
            +
                        file_scoped_include?(ex.metadata, ids, locations) do
         
     | 
| 
      
 51 
     | 
    
         
            +
                          !exclusions.include_example?(ex) && non_scoped_inclusions.include_example?(ex)
         
     | 
| 
      
 52 
     | 
    
         
            +
                        end
         
     | 
| 
      
 53 
     | 
    
         
            +
                      end
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  def exclude(*args)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    exclusions.add(args.last)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def exclude_only(*args)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    exclusions.use_only(args.last)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  def exclude_with_low_priority(*args)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    exclusions.add_with_low_priority(args.last)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  def include(*args)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    inclusions.add(args.last)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  def include_only(*args)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    inclusions.use_only(args.last)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  def include_with_low_priority(*args)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    inclusions.add_with_low_priority(args.last)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                private
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  def add_path_to_arrays_filter(filter_key, path, values)
         
     | 
| 
      
 84 
     | 
    
         
            +
                    filter = inclusions.delete(filter_key) || Hash.new { |h, k| h[k] = [] }
         
     | 
| 
      
 85 
     | 
    
         
            +
                    filter[path].concat(values)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    inclusions.add(filter_key => filter)
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  def prune_conditionally_filtered_examples(examples)
         
     | 
| 
      
 90 
     | 
    
         
            +
                    examples.reject do |ex|
         
     | 
| 
      
 91 
     | 
    
         
            +
                      meta = ex.metadata
         
     | 
| 
      
 92 
     | 
    
         
            +
                      !meta.fetch(:if, true) || meta[:unless]
         
     | 
| 
      
 93 
     | 
    
         
            +
                    end
         
     | 
| 
      
 94 
     | 
    
         
            +
                  end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                  # When a user specifies a particular spec location, that takes priority
         
     | 
| 
      
 97 
     | 
    
         
            +
                  # over any exclusion filters (such as if the spec is tagged with `:slow`
         
     | 
| 
      
 98 
     | 
    
         
            +
                  # and there is a `:slow => true` exclusion filter), but only for specs
         
     | 
| 
      
 99 
     | 
    
         
            +
                  # defined in the same file as the location filters. Excluded specs in
         
     | 
| 
      
 100 
     | 
    
         
            +
                  # other files should still be excluded.
         
     | 
| 
      
 101 
     | 
    
         
            +
                  def file_scoped_include?(ex_metadata, ids, locations)
         
     | 
| 
      
 102 
     | 
    
         
            +
                    no_id_filters = ids[ex_metadata[:rerun_file_path]].empty?
         
     | 
| 
      
 103 
     | 
    
         
            +
                    no_location_filters = locations[
         
     | 
| 
      
 104 
     | 
    
         
            +
                      File.expand_path(ex_metadata[:rerun_file_path])
         
     | 
| 
      
 105 
     | 
    
         
            +
                    ].empty?
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                    return yield if no_location_filters && no_id_filters
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    MetadataFilter.filter_applies?(:ids, ids, ex_metadata) ||
         
     | 
| 
      
 110 
     | 
    
         
            +
                    MetadataFilter.filter_applies?(:locations, locations, ex_metadata)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  end
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 115 
     | 
    
         
            +
                class FilterRules
         
     | 
| 
      
 116 
     | 
    
         
            +
                  PROC_HEX_NUMBER = /0x[0-9a-f]+@/
         
     | 
| 
      
 117 
     | 
    
         
            +
                  PROJECT_DIR = File.expand_path('.')
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                  attr_accessor :opposite
         
     | 
| 
      
 120 
     | 
    
         
            +
                  attr_reader :rules
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                  def self.build
         
     | 
| 
      
 123 
     | 
    
         
            +
                    exclusions = ExclusionRules.new
         
     | 
| 
      
 124 
     | 
    
         
            +
                    inclusions = InclusionRules.new
         
     | 
| 
      
 125 
     | 
    
         
            +
                    exclusions.opposite = inclusions
         
     | 
| 
      
 126 
     | 
    
         
            +
                    inclusions.opposite = exclusions
         
     | 
| 
      
 127 
     | 
    
         
            +
                    [exclusions, inclusions]
         
     | 
| 
      
 128 
     | 
    
         
            +
                  end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                  def initialize(rules={})
         
     | 
| 
      
 131 
     | 
    
         
            +
                    @rules = rules
         
     | 
| 
      
 132 
     | 
    
         
            +
                  end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                  def add(updated)
         
     | 
| 
      
 135 
     | 
    
         
            +
                    @rules.merge!(updated).each_key { |k| opposite.delete(k) }
         
     | 
| 
      
 136 
     | 
    
         
            +
                  end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                  def add_with_low_priority(updated)
         
     | 
| 
      
 139 
     | 
    
         
            +
                    updated = updated.merge(@rules)
         
     | 
| 
      
 140 
     | 
    
         
            +
                    opposite.each_pair { |k, v| updated.delete(k) if updated[k] == v }
         
     | 
| 
      
 141 
     | 
    
         
            +
                    @rules.replace(updated)
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                  def use_only(updated)
         
     | 
| 
      
 145 
     | 
    
         
            +
                    updated.each_key { |k| opposite.delete(k) }
         
     | 
| 
      
 146 
     | 
    
         
            +
                    @rules.replace(updated)
         
     | 
| 
      
 147 
     | 
    
         
            +
                  end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  def clear
         
     | 
| 
      
 150 
     | 
    
         
            +
                    @rules.clear
         
     | 
| 
      
 151 
     | 
    
         
            +
                  end
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                  def delete(key)
         
     | 
| 
      
 154 
     | 
    
         
            +
                    @rules.delete(key)
         
     | 
| 
      
 155 
     | 
    
         
            +
                  end
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                  def fetch(*args, &block)
         
     | 
| 
      
 158 
     | 
    
         
            +
                    @rules.fetch(*args, &block)
         
     | 
| 
      
 159 
     | 
    
         
            +
                  end
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                  def [](key)
         
     | 
| 
      
 162 
     | 
    
         
            +
                    @rules[key]
         
     | 
| 
      
 163 
     | 
    
         
            +
                  end
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                  def empty?
         
     | 
| 
      
 166 
     | 
    
         
            +
                    rules.empty?
         
     | 
| 
      
 167 
     | 
    
         
            +
                  end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                  def each_pair(&block)
         
     | 
| 
      
 170 
     | 
    
         
            +
                    @rules.each_pair(&block)
         
     | 
| 
      
 171 
     | 
    
         
            +
                  end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                  def description
         
     | 
| 
      
 174 
     | 
    
         
            +
                    rules.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)', '')
         
     | 
| 
      
 175 
     | 
    
         
            +
                  end
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                  def include_example?(example)
         
     | 
| 
      
 178 
     | 
    
         
            +
                    MetadataFilter.apply?(:any?, @rules, example.metadata)
         
     | 
| 
      
 179 
     | 
    
         
            +
                  end
         
     | 
| 
      
 180 
     | 
    
         
            +
                end
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 183 
     | 
    
         
            +
                ExclusionRules = FilterRules
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                # @private
         
     | 
| 
      
 186 
     | 
    
         
            +
                class InclusionRules < FilterRules
         
     | 
| 
      
 187 
     | 
    
         
            +
                  def add(*args)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    apply_standalone_filter(*args) || super
         
     | 
| 
      
 189 
     | 
    
         
            +
                  end
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                  def add_with_low_priority(*args)
         
     | 
| 
      
 192 
     | 
    
         
            +
                    apply_standalone_filter(*args) || super
         
     | 
| 
      
 193 
     | 
    
         
            +
                  end
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
                  def include_example?(example)
         
     | 
| 
      
 196 
     | 
    
         
            +
                    @rules.empty? || super
         
     | 
| 
      
 197 
     | 
    
         
            +
                  end
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                  def standalone?
         
     | 
| 
      
 200 
     | 
    
         
            +
                    is_standalone_filter?(@rules)
         
     | 
| 
      
 201 
     | 
    
         
            +
                  end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                  def split_file_scoped_rules
         
     | 
| 
      
 204 
     | 
    
         
            +
                    rules_dup = @rules.dup
         
     | 
| 
      
 205 
     | 
    
         
            +
                    locations = rules_dup.delete(:locations) { Hash.new([]) }
         
     | 
| 
      
 206 
     | 
    
         
            +
                    ids       = rules_dup.delete(:ids)       { Hash.new([]) }
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
                    return locations, ids, self.class.new(rules_dup)
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                private
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                  def apply_standalone_filter(updated)
         
     | 
| 
      
 214 
     | 
    
         
            +
                    return true if standalone?
         
     | 
| 
      
 215 
     | 
    
         
            +
                    return nil unless is_standalone_filter?(updated)
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                    replace_filters(updated)
         
     | 
| 
      
 218 
     | 
    
         
            +
                    true
         
     | 
| 
      
 219 
     | 
    
         
            +
                  end
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
                  def replace_filters(new_rules)
         
     | 
| 
      
 222 
     | 
    
         
            +
                    @rules.replace(new_rules)
         
     | 
| 
      
 223 
     | 
    
         
            +
                    opposite.clear
         
     | 
| 
      
 224 
     | 
    
         
            +
                  end
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                  def is_standalone_filter?(rules)
         
     | 
| 
      
 227 
     | 
    
         
            +
                    rules.key?(:full_description)
         
     | 
| 
      
 228 
     | 
    
         
            +
                  end
         
     | 
| 
      
 229 
     | 
    
         
            +
                end
         
     | 
| 
      
 230 
     | 
    
         
            +
              end
         
     | 
| 
      
 231 
     | 
    
         
            +
            end
         
     |