rspec-core 2.7.1 → 2.8.0.rc1

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.
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