rspec-core 2.7.1 → 2.8.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/README.md +1 -1
  2. data/features/command_line/order.feature +29 -0
  3. data/features/command_line/tag.feature +10 -9
  4. data/features/configuration/default_path.feature +2 -2
  5. data/features/filtering/exclusion_filters.feature +1 -1
  6. data/features/filtering/run_all_when_everything_filtered.feature +1 -1
  7. data/features/subject/attribute_of_subject.feature +1 -1
  8. data/lib/rspec/core.rb +148 -12
  9. data/lib/rspec/core/command_line.rb +2 -2
  10. data/lib/rspec/core/configuration.rb +300 -155
  11. data/lib/rspec/core/configuration_options.rb +34 -53
  12. data/lib/rspec/core/deprecation.rb +4 -0
  13. data/lib/rspec/core/drb_options.rb +72 -0
  14. data/lib/rspec/core/example.rb +58 -24
  15. data/lib/rspec/core/example_group.rb +10 -5
  16. data/lib/rspec/core/extensions.rb +1 -0
  17. data/lib/rspec/core/extensions/ordered.rb +16 -0
  18. data/lib/rspec/core/filter_manager.rb +170 -0
  19. data/lib/rspec/core/formatters/base_formatter.rb +3 -1
  20. data/lib/rspec/core/formatters/base_text_formatter.rb +6 -0
  21. data/lib/rspec/core/formatters/snippet_extractor.rb +1 -1
  22. data/lib/rspec/core/hooks.rb +197 -1
  23. data/lib/rspec/core/let.rb +3 -2
  24. data/lib/rspec/core/metadata.rb +25 -4
  25. data/lib/rspec/core/option_parser.rb +89 -54
  26. data/lib/rspec/core/pending.rb +41 -0
  27. data/lib/rspec/core/rake_task.rb +9 -25
  28. data/lib/rspec/core/reporter.rb +43 -19
  29. data/lib/rspec/core/shared_context.rb +35 -0
  30. data/lib/rspec/core/shared_example_group.rb +0 -1
  31. data/lib/rspec/core/subject.rb +4 -4
  32. data/lib/rspec/core/version.rb +1 -1
  33. data/lib/rspec/core/world.rb +34 -52
  34. data/spec/autotest/failed_results_re_spec.rb +2 -2
  35. data/spec/command_line/order_spec.rb +131 -0
  36. data/spec/rspec/core/command_line_spec.rb +2 -1
  37. data/spec/rspec/core/configuration_options_spec.rb +83 -163
  38. data/spec/rspec/core/configuration_spec.rb +311 -139
  39. data/spec/rspec/core/drb_options_spec.rb +131 -0
  40. data/spec/rspec/core/example_group_spec.rb +22 -11
  41. data/spec/rspec/core/example_spec.rb +1 -2
  42. data/spec/rspec/core/filter_manager_spec.rb +175 -0
  43. data/spec/rspec/core/formatters/helpers_spec.rb +1 -1
  44. data/spec/rspec/core/formatters/html_formatter_spec.rb +3 -2
  45. data/spec/rspec/core/formatters/text_mate_formatter_spec.rb +1 -1
  46. data/spec/rspec/core/metadata_spec.rb +21 -6
  47. data/spec/rspec/core/option_parser_spec.rb +74 -0
  48. data/spec/rspec/core/reporter_spec.rb +18 -1
  49. data/spec/rspec/core/shared_context_spec.rb +54 -17
  50. data/spec/rspec/core/subject_spec.rb +1 -1
  51. data/spec/rspec/core/world_spec.rb +7 -188
  52. data/spec/spec_helper.rb +47 -43
  53. data/spec/support/config_options_helper.rb +27 -0
  54. metadata +28 -12
  55. data/lib/rspec/core/expecting/with_rspec.rb +0 -9
  56. data/lib/rspec/core/expecting/with_stdlib.rb +0 -9
@@ -1,6 +1,7 @@
1
+ require 'erb'
2
+
1
3
  module RSpec
2
4
  module Core
3
-
4
5
  class ConfigurationOptions
5
6
  attr_reader :options
6
7
 
@@ -9,68 +10,39 @@ module RSpec
9
10
  end
10
11
 
11
12
  def configure(config)
12
- keys = order(options.keys, :libs, :requires, :default_path, :pattern)
13
-
14
- formatters = options[:formatters] if keys.delete(:formatters)
13
+ formatters = options.delete(:formatters)
15
14
 
16
- config.exclusion_filter.merge! options[:exclusion_filter] if keys.delete(:exclusion_filter)
15
+ config.filter_manager = filter_manager
17
16
 
18
- keys.each do |key|
19
- config.send("#{key}=", options[key]) if config.respond_to?("#{key}=")
17
+ order(options.keys, :libs, :requires, :default_path, :pattern).each do |key|
18
+ force?(key) ? config.force(key => options[key]) : config.send("#{key}=", options[key])
20
19
  end
21
20
 
22
21
  formatters.each {|pair| config.add_formatter(*pair) } if formatters
23
22
  end
24
23
 
25
- def drb_argv
26
- argv = []
27
- argv << "--color" if options[:color_enabled]
28
- argv << "--profile" if options[:profile_examples]
29
- argv << "--backtrace" if options[:full_backtrace]
30
- argv << "--tty" if options[:tty]
31
- argv << "--fail-fast" if options[:fail_fast]
32
- argv << "--options" << options[:custom_options_file] if options[:custom_options_file]
33
- if options[:full_description]
34
- # The argument to --example is regexp-escaped before being stuffed
35
- # into a regexp when received for the first time (see OptionParser).
36
- # Hence, merely grabbing the source of this regexp will retain the
37
- # backslashes, so we must remove them.
38
- argv << "--example" << options[:full_description].source.delete('\\')
39
- end
40
- if options[:line_numbers]
41
- argv += options[:line_numbers].inject([]){|a,l| a << "--line_number" << l}
42
- end
43
- if options[:filter]
44
- options[:filter].each_pair do |k, v|
45
- argv << "--tag" << k.to_s
46
- end
47
- end
48
- if options[:exclusion_filter]
49
- options[:exclusion_filter].each_pair do |k, v|
50
- argv << "--tag" << "~#{k.to_s}"
51
- end
52
- end
53
- if options[:formatters]
54
- options[:formatters].each do |pair|
55
- argv << "--format" << pair[0]
56
- argv << "--out" << pair[1] if pair[1]
57
- end
58
- end
59
- (options[:libs] || []).each do |path|
60
- argv << "-I" << path
61
- end
62
- (options[:requires] || []).each do |path|
63
- argv << "--require" << path
24
+ def parse_options
25
+ @options ||= extract_filters_from(*all_configs).inject do |merged, pending|
26
+ merged.merge(pending)
64
27
  end
65
- argv + options[:files_or_directories_to_run]
66
28
  end
67
29
 
68
- def parse_options
69
- @options ||= [file_options, command_line_options, env_options].inject {|merged, o| merged.merge o}
30
+ def drb_argv
31
+ DrbOptions.new(options, filter_manager).options
32
+ end
33
+
34
+ def filter_manager
35
+ @filter_manager ||= FilterManager.new
70
36
  end
71
37
 
72
38
  private
73
39
 
40
+ NON_FORCED_OPTIONS = [:debug, :order, :seed, :requires, :libs, :files_or_directories_to_run, :line_numbers, :full_description]
41
+
42
+ def force?(key)
43
+ !NON_FORCED_OPTIONS.include?(key)
44
+ end
45
+
74
46
  def order(keys, *ordered)
75
47
  ordered.reverse.each do |key|
76
48
  keys.unshift(key) if keys.delete(key)
@@ -78,8 +50,19 @@ module RSpec
78
50
  keys
79
51
  end
80
52
 
53
+ def extract_filters_from(*configs)
54
+ configs.compact.each do |config|
55
+ filter_manager.include config.delete(:inclusion_filter) if config.has_key?(:inclusion_filter)
56
+ filter_manager.exclude config.delete(:exclusion_filter) if config.has_key?(:exclusion_filter)
57
+ end
58
+ end
59
+
60
+ def all_configs
61
+ @all_configs ||= file_options << command_line_options << env_options
62
+ end
63
+
81
64
  def file_options
82
- custom_options_file ? custom_options : global_options.merge(local_options)
65
+ custom_options_file ? [custom_options] : [global_options, local_options]
83
66
  end
84
67
 
85
68
  def env_options
@@ -113,8 +96,7 @@ module RSpec
113
96
  end
114
97
 
115
98
  def options_file_as_erb_string(path)
116
- require 'erb'
117
- ERB.new(IO.read(path)).result(binding)
99
+ ERB.new(File.read(path)).result(binding)
118
100
  end
119
101
 
120
102
  def custom_options_file
@@ -133,7 +115,6 @@ module RSpec
133
115
  nil
134
116
  end
135
117
  end
136
-
137
118
  end
138
119
  end
139
120
  end
@@ -1,6 +1,8 @@
1
1
  module RSpec
2
2
 
3
3
  class << self
4
+ # @api private
5
+ #
4
6
  # Used internally to print deprecation warnings
5
7
  def deprecate(method, alternate_method=nil, version=nil)
6
8
  version_string = version ? "rspec-#{version}" : "a future version of RSpec"
@@ -25,6 +27,8 @@ ADDITIONAL
25
27
  warn_deprecation(message)
26
28
  end
27
29
 
30
+ # @api private
31
+ #
28
32
  # Used internally to print deprecation warnings
29
33
  def warn_deprecation(message)
30
34
  send :warn, message
@@ -0,0 +1,72 @@
1
+ # Builds command line arguments to pass to the rspec command over DRb
2
+ class RSpec::Core::DrbOptions
3
+ def initialize(submitted_options, filter_manager)
4
+ @submitted_options = submitted_options
5
+ @filter_manager = filter_manager
6
+ end
7
+
8
+ def options
9
+ argv = []
10
+ argv << "--color" if @submitted_options[:color]
11
+ argv << "--profile" if @submitted_options[:profile_examples]
12
+ argv << "--backtrace" if @submitted_options[:full_backtrace]
13
+ argv << "--tty" if @submitted_options[:tty]
14
+ argv << "--fail-fast" if @submitted_options[:fail_fast]
15
+ argv << "--options" << @submitted_options[:custom_options_file] if @submitted_options[:custom_options_file]
16
+ argv << "--order" << @submitted_options[:order] if @submitted_options[:order]
17
+
18
+ add_full_description(argv)
19
+ add_line_numbers(argv)
20
+ add_filter(argv, :inclusion, @filter_manager.inclusions)
21
+ add_filter(argv, :exclusion, @filter_manager.exclusions)
22
+ add_formatters(argv)
23
+ add_libs(argv)
24
+ add_requires(argv)
25
+
26
+ argv + @submitted_options[:files_or_directories_to_run]
27
+ end
28
+
29
+ def add_full_description(argv)
30
+ if @submitted_options[:full_description]
31
+ # The argument to --example is regexp-escaped before being stuffed
32
+ # into a regexp when received for the first time (see OptionParser).
33
+ # Hence, merely grabbing the source of this regexp will retain the
34
+ # backslashes, so we must remove them.
35
+ argv << "--example" << @submitted_options[:full_description].source.delete('\\')
36
+ end
37
+ end
38
+
39
+ def add_line_numbers(argv)
40
+ if @submitted_options[:line_numbers]
41
+ argv.push(*@submitted_options[:line_numbers].inject([]){|a,l| a << "--line_number" << l})
42
+ end
43
+ end
44
+
45
+ def add_filter(argv, name, hash)
46
+ hash.each_pair do |k, v|
47
+ next if [:if,:unless].include?(k)
48
+ tag = name == :inclusion ? k.to_s : "~#{k.to_s}"
49
+ tag << ":#{v.to_s}" if v.is_a?(String)
50
+ argv << "--tag" << tag
51
+ end unless hash.empty?
52
+ end
53
+
54
+ def add_formatters(argv)
55
+ @submitted_options[:formatters].each do |pair|
56
+ argv << "--format" << pair[0]
57
+ argv << "--out" << pair[1] if pair[1]
58
+ end if @submitted_options[:formatters]
59
+ end
60
+
61
+ def add_libs(argv)
62
+ @submitted_options[:libs].each do |path|
63
+ argv << "-I" << path
64
+ end if @submitted_options[:libs]
65
+ end
66
+
67
+ def add_requires(argv)
68
+ @submitted_options[:requires].each do |path|
69
+ argv << "--require" << path
70
+ end if @submitted_options[:requires]
71
+ end
72
+ end
@@ -2,12 +2,6 @@ module RSpec
2
2
  module Core
3
3
  class Example
4
4
 
5
- attr_reader :metadata, :options, :example_group_instance
6
-
7
- # Returns the first exception raised, if any, in the context of running
8
- # this example.
9
- attr_reader :exception
10
-
11
5
  def self.delegate_to_metadata(*keys)
12
6
  keys.each do |key|
13
7
  define_method(key) {@metadata[key]}
@@ -16,6 +10,24 @@ module RSpec
16
10
 
17
11
  delegate_to_metadata :description, :full_description, :execution_result, :file_path, :pending, :location
18
12
 
13
+ # @attr_reader
14
+ #
15
+ # Returns the first exception raised in the context of running this
16
+ # example (nil if no exception is raised)
17
+ attr_reader :exception
18
+
19
+ # @attr_reader
20
+ #
21
+ # Returns the metadata object associated with this example.
22
+ attr_reader :metadata
23
+
24
+ # @attr_reader
25
+ # @api private
26
+ #
27
+ # Returns the example_group_instance that provides the context for
28
+ # running this example.
29
+ attr_reader :example_group_instance
30
+
19
31
  def initialize(example_group_class, desc, options, example_block=nil)
20
32
  @example_group_class, @options, @example_block = example_group_class, options, example_block
21
33
  @metadata = @example_group_class.metadata.for_example(desc, options)
@@ -23,16 +35,15 @@ module RSpec
23
35
  @pending_declared_in_example = false
24
36
  end
25
37
 
26
- def example_group
27
- @example_group_class
38
+ # @deprecated access options via metadata instead
39
+ def options
40
+ @options
28
41
  end
29
42
 
30
- def around_hooks
31
- @around_hooks ||= example_group.around_hooks_for(self)
32
- end
33
-
34
- def all_apply?(filters)
35
- @metadata.all_apply?(filters) || @example_group_class.all_apply?(filters)
43
+ # Returns the example group class that provides the context for running
44
+ # this example.
45
+ def example_group
46
+ @example_group_class
36
47
  end
37
48
 
38
49
  alias_method :pending?, :pending
@@ -76,16 +87,6 @@ module RSpec
76
87
  finish(reporter)
77
88
  end
78
89
 
79
- def set_exception(exception)
80
- @exception ||= exception
81
- end
82
-
83
- def fail_fast(reporter, exception)
84
- start(reporter)
85
- set_exception(exception)
86
- finish(reporter)
87
- end
88
-
89
90
  def self.procsy(metadata, &block)
90
91
  Proc.new(&block).extend(Procsy).with(metadata)
91
92
  end
@@ -103,6 +104,39 @@ module RSpec
103
104
  end
104
105
  end
105
106
 
107
+ # @api private
108
+ def all_apply?(filters)
109
+ @metadata.all_apply?(filters) || @example_group_class.all_apply?(filters)
110
+ end
111
+
112
+ # @api private
113
+ def around_hooks
114
+ @around_hooks ||= example_group.around_hooks_for(self)
115
+ end
116
+
117
+ # @api private
118
+ #
119
+ # Used internally to set an exception in an after hook, which
120
+ # captures the exception but doesn't raise it.
121
+ def set_exception(exception)
122
+ @exception ||= exception
123
+ end
124
+
125
+ # @api private
126
+ #
127
+ # Used internally to set an exception and fail without actually executing
128
+ # the example when an exception is raised in before(:all).
129
+ def fail_with_exception(reporter, exception)
130
+ start(reporter)
131
+ set_exception(exception)
132
+ finish(reporter)
133
+ end
134
+
135
+ # @api private
136
+ def any_apply?(filters)
137
+ metadata.any_apply?(filters)
138
+ end
139
+
106
140
  private
107
141
 
108
142
  def with_around_hooks(&block)
@@ -112,7 +112,7 @@ module RSpec
112
112
  def self.examples
113
113
  @examples ||= []
114
114
  end
115
-
115
+
116
116
  def self.filtered_examples
117
117
  world.filtered_examples[self]
118
118
  end
@@ -121,10 +121,13 @@ module RSpec
121
121
  @descendant_filtered_examples ||= filtered_examples + children.inject([]){|l,c| l + c.descendant_filtered_examples}
122
122
  end
123
123
 
124
+ # @see Metadata
124
125
  def self.metadata
125
126
  @metadata if defined?(@metadata)
126
127
  end
127
128
 
129
+ # @api private
130
+ # @return [Metadata] belonging to the parent of a nested [ExampleGroup](ExampleGroup)
128
131
  def self.superclass_metadata
129
132
  @superclass_metadata ||= self.superclass.respond_to?(:metadata) ? self.superclass.metadata : nil
130
133
  end
@@ -156,7 +159,7 @@ module RSpec
156
159
  end
157
160
 
158
161
  def self.children
159
- @children ||= []
162
+ @children ||= [].extend(Extensions::Ordered)
160
163
  end
161
164
 
162
165
  def self.descendants
@@ -272,7 +275,7 @@ An error occurred in an after(:all) hook.
272
275
  begin
273
276
  run_before_all_hooks(new)
274
277
  result_for_this_group = run_examples(reporter)
275
- results_for_descendants = children.map {|child| child.run(reporter)}.all?
278
+ results_for_descendants = children.ordered.map {|child| child.run(reporter)}.all?
276
279
  result_for_this_group && results_for_descendants
277
280
  rescue Exception => ex
278
281
  fail_filtered_examples(ex, reporter)
@@ -284,7 +287,7 @@ An error occurred in an after(:all) hook.
284
287
  end
285
288
 
286
289
  def self.fail_filtered_examples(exception, reporter)
287
- filtered_examples.each { |example| example.fail_fast(reporter, exception) }
290
+ filtered_examples.each { |example| example.fail_with_exception(reporter, exception) }
288
291
 
289
292
  children.each do |child|
290
293
  reporter.example_group_started(child)
@@ -299,7 +302,7 @@ An error occurred in an after(:all) hook.
299
302
  end
300
303
 
301
304
  def self.run_examples(reporter)
302
- filtered_examples.map do |example|
305
+ filtered_examples.ordered.map do |example|
303
306
  next if RSpec.wants_to_quit
304
307
  instance = new
305
308
  set_ivars(instance, before_all_ivars)
@@ -309,10 +312,12 @@ An error occurred in an after(:all) hook.
309
312
  end.all?
310
313
  end
311
314
 
315
+ # @api private
312
316
  def self.any_apply?(filters)
313
317
  metadata.any_apply?(filters)
314
318
  end
315
319
 
320
+ # @api private
316
321
  def self.all_apply?(filters)
317
322
  metadata.all_apply?(filters)
318
323
  end
@@ -1,3 +1,4 @@
1
1
  require 'rspec/core/extensions/kernel'
2
2
  require 'rspec/core/extensions/instance_eval_with_args'
3
3
  require 'rspec/core/extensions/module_eval_with_args'
4
+ require 'rspec/core/extensions/ordered'
@@ -0,0 +1,16 @@
1
+ module RSpec
2
+ module Core
3
+ module Extensions
4
+ module Ordered
5
+ def ordered
6
+ if RSpec.configuration.randomize?
7
+ srand RSpec.configuration.seed
8
+ sort_by { rand size }
9
+ else
10
+ self
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,170 @@
1
+ module RSpec
2
+ module Core
3
+ # Manages the filtering of examples and groups by matching tags declared on
4
+ # the command line or options files, or filters declared via
5
+ # `RSpec.configure`, with hash key/values submitted within example group
6
+ # and/or example declarations. For example, given this declaration:
7
+ #
8
+ # describe Thing, :awesome => true do
9
+ # it "does something" do
10
+ # # ...
11
+ # end
12
+ # end
13
+ #
14
+ # That group (or any other with `:awesome => true`) would be filtered in
15
+ # with any of the following commands:
16
+ #
17
+ # rspec --tag awesome:true
18
+ # rspec --tag awesome
19
+ # rspec -t awesome:true
20
+ # rspec -t awesome
21
+ #
22
+ # Prefixing the tag names with `~` negates the tags, thus excluding this group with
23
+ # any of:
24
+ #
25
+ # rspec --tag ~awesome:true
26
+ # rspec --tag ~awesome
27
+ # rspec -t ~awesome:true
28
+ # rspec -t ~awesome
29
+ #
30
+ # ## Options files and command line overrides
31
+ #
32
+ # Tag declarations can be stored in `.rspec`, `~/.rspec`, or a custom
33
+ # options file. This is useful for storing defaults. For example, let's
34
+ # say you've got some slow specs that you want to suppress most of the
35
+ # time. You can tag them like this:
36
+ #
37
+ # describe Something, :slow => true do
38
+ #
39
+ # And then store this in `.rspec`:
40
+ #
41
+ # --tag ~slow:true
42
+ #
43
+ # Now when you run `rspec`, that group will be excluded.
44
+ #
45
+ # ## Overriding
46
+ #
47
+ # Of course, you probably want to run them sometimes, so you can override
48
+ # this tag on the command line like this:
49
+ #
50
+ # rspec --tag slow:true
51
+ #
52
+ # ## RSpec.configure
53
+ #
54
+ # You can also store default tags with `RSpec.configure`. We use `tag` on
55
+ # the command line (and in options files like `.rspec`), but for historical
56
+ # reasons we use the term `filter` in `RSpec.configure:
57
+ #
58
+ # RSpec.configure do |c|
59
+ # c.filter_run_including :foo => :bar
60
+ # c.filter_run_excluding :foo => :bar
61
+ # end
62
+ #
63
+ # These declarations can also be overridden from the command line.
64
+ class FilterManager
65
+ DEFAULT_EXCLUSIONS = {
66
+ :if => lambda { |value, metadata| metadata.has_key?(:if) && !value },
67
+ :unless => lambda { |value| value }
68
+ }
69
+
70
+ STANDALONE_FILTERS = [:locations, :line_numbers, :full_description]
71
+
72
+ module Describable
73
+ PROC_HEX_NUMBER = /0x[0-9a-f]+@/
74
+ PROJECT_DIR = File.expand_path('.')
75
+
76
+ def description
77
+ reject { |k, v| RSpec::Core::FilterManager::DEFAULT_EXCLUSIONS[k] == v }.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)','')
78
+ end
79
+
80
+ def empty_without_conditional_filters?
81
+ reject { |k, v| RSpec::Core::FilterManager::DEFAULT_EXCLUSIONS[k] == v }.empty?
82
+ end
83
+ end
84
+
85
+ module BackwardCompatibility
86
+ # This is to support a use case that probably doesn't exist: overriding
87
+ # the if/unless procs.
88
+ def update(orig, opposite, *updates)
89
+ if updates.last.has_key?(:unless)
90
+ RSpec.warn_deprecation("\nDEPRECATION NOTICE: FilterManager#exclude(:unless => #{updates.last[:unless].inspect}) is deprecated with no replacement, and will be removed from rspec-3.0.")
91
+ @exclusions[:unless] = updates.last.delete(:unless)
92
+ end
93
+ if updates.last.has_key?(:if)
94
+ RSpec.warn_deprecation("\nDEPRECATION NOTICE: FilterManager#exclude(:if => #{updates.last[:if].inspect}) is deprecated with no replacement, and will be removed from rspec-3.0.")
95
+ @exclusions[:if] = updates.last.delete(:if)
96
+ end
97
+
98
+ super
99
+ end
100
+ end
101
+
102
+ attr_reader :exclusions, :inclusions
103
+
104
+ def initialize
105
+ @exclusions = DEFAULT_EXCLUSIONS.dup.extend(Describable)
106
+ @inclusions = {}.extend(Describable)
107
+ extend(BackwardCompatibility)
108
+ end
109
+
110
+ def add_location(file_path, line_numbers)
111
+ # filter_locations is a hash of expanded paths to arrays of line
112
+ # numbers to match against. e.g.
113
+ # { "path/to/file.rb" => [37, 42] }
114
+ filter_locations = @inclusions[:locations] ||= Hash.new {|h,k| h[k] = []}
115
+ @exclusions.clear
116
+ @inclusions.clear
117
+ filter_locations[File.expand_path(file_path)].push(*line_numbers)
118
+ include :locations => filter_locations
119
+ end
120
+
121
+ def empty?
122
+ inclusions.empty? && exclusions.empty_without_conditional_filters?
123
+ end
124
+
125
+ def prune(examples)
126
+ examples.select {|e| !exclude?(e) && include?(e)}
127
+ end
128
+
129
+ def exclude?(example)
130
+ @exclusions.empty? ? false : example.any_apply?(@exclusions)
131
+ end
132
+
133
+ def include?(example)
134
+ @inclusions.empty? ? true : example.any_apply?(@inclusions)
135
+ end
136
+
137
+ def exclude(*args)
138
+ update(@exclusions, @inclusions, *args)
139
+ end
140
+
141
+ def include(*args)
142
+ return if already_set_standalone_filter?
143
+
144
+ is_standalone_filter?(args.last) ? @inclusions.replace(args.last) : update(@inclusions, @exclusions, *args)
145
+ end
146
+
147
+ def update(orig, opposite, *updates)
148
+ if updates.length == 2
149
+ if updates[0] == :replace
150
+ updated = updates.last
151
+ else
152
+ updated = updates.last.merge(orig)
153
+ opposite.each_key {|k| updated.delete(k)}
154
+ end
155
+ orig.replace(updated)
156
+ else
157
+ orig.merge!(updates.last).each_key {|k| opposite.delete(k)}
158
+ end
159
+ end
160
+
161
+ def already_set_standalone_filter?
162
+ is_standalone_filter?(inclusions)
163
+ end
164
+
165
+ def is_standalone_filter?(filter)
166
+ STANDALONE_FILTERS.any? {|key| filter.has_key?(key)}
167
+ end
168
+ end
169
+ end
170
+ end