mutant 0.5.10 → 0.5.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/Changelog.md +12 -1
  4. data/README.md +3 -0
  5. data/Rakefile +0 -9
  6. data/bin/mutant +1 -1
  7. data/config/flay.yml +1 -1
  8. data/config/mutant.yml +13 -0
  9. data/config/reek.yml +6 -2
  10. data/lib/mutant/constants.rb +0 -13
  11. data/lib/mutant/mutator/node/conditional_loop.rb +2 -1
  12. data/lib/mutant/mutator/node/generic.rb +1 -1
  13. data/lib/mutant/mutator/node/if.rb +1 -1
  14. data/lib/mutant/mutator/node/literal/regex.rb +2 -2
  15. data/lib/mutant/mutator/node/match_current_line.rb +27 -0
  16. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
  17. data/lib/mutant/mutator/node/nthref.rb +3 -1
  18. data/lib/mutant/mutator/node/rescue.rb +0 -12
  19. data/lib/mutant/mutator/node/send/attribute_assignment.rb +51 -0
  20. data/lib/mutant/mutator/node/send/index.rb +43 -0
  21. data/lib/mutant/mutator/node/send.rb +7 -37
  22. data/lib/mutant/mutator/node.rb +4 -12
  23. data/lib/mutant/mutator.rb +2 -26
  24. data/lib/mutant/node_helpers.rb +3 -3
  25. data/lib/mutant/require_highjack.rb +64 -0
  26. data/lib/mutant/subject/method/instance.rb +9 -1
  27. data/lib/mutant/version.rb +1 -1
  28. data/lib/mutant/warning_expectation.rb +40 -0
  29. data/lib/mutant/warning_filter.rb +74 -0
  30. data/lib/mutant/zombifier/file.rb +81 -0
  31. data/lib/mutant/zombifier.rb +61 -240
  32. data/lib/mutant.rb +57 -1
  33. data/lib/parser_extensions.rb +25 -0
  34. data/mutant.gemspec +4 -3
  35. data/spec/integration/mutant/corpus_spec.rb +121 -0
  36. data/spec/integration/mutant/rspec_spec.rb +1 -1
  37. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -1
  38. data/spec/integration/mutant/zombie_spec.rb +3 -3
  39. data/spec/integrations.yml +23 -0
  40. data/spec/shared/mutator_behavior.rb +22 -72
  41. data/spec/spec_helper.rb +4 -2
  42. data/spec/support/mutation_verifier.rb +95 -0
  43. data/spec/unit/mutant/mutator/node/conditional_loop_spec.rb +16 -0
  44. data/spec/unit/mutant/mutator/node/match_current_line_spec.rb +4 -4
  45. data/spec/unit/mutant/mutator/node/named_value/access_spec.rb +6 -3
  46. data/spec/unit/mutant/mutator/node/nthref_spec.rb +2 -2
  47. data/spec/unit/mutant/mutator/node/op_assgn_spec.rb +3 -1
  48. data/spec/unit/mutant/mutator/node/send_spec.rb +0 -1
  49. data/spec/unit/mutant/require_highjack_spec.rb +54 -0
  50. data/spec/unit/mutant/subject/method/instance_spec.rb +34 -3
  51. data/spec/unit/mutant/warning_expectation.rb +71 -0
  52. data/spec/unit/mutant/warning_filter_spec.rb +94 -0
  53. metadata +40 -9
  54. data/lib/mutant/singleton_methods.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 84a8405d55414b2cd802d72e2a8fab5a3a2e9383
4
- data.tar.gz: e4acca9abe75f892f73c1b830473d17630ec1001
3
+ metadata.gz: bff75074f1898f689c4215c2a19f14d02863ccc4
4
+ data.tar.gz: 576dbee5c9743707641a1f3fac543c0c76d85832
5
5
  SHA512:
6
- metadata.gz: 1ca5f7c4ff72450792d49df2c40d10a45335442d095b54564c8f650bf9218fed4b9821994fa17162d27e4b1e2c1087cfc2f4bf8661e3adf63e73c187e57475d1
7
- data.tar.gz: 8a0154fce8d6a39824108c609c2a76f170f5fb855fa8b5cfb4224b91a5528106b41d83500e5af0b67b2d7775ca4a00b24e5ffbe090369efa94c4e9525e0fccc4
6
+ metadata.gz: 47b0085d022c5e09f1013c824989f2449838bea7b9681b257e9e8ef7641a39fd522dacdf643e20e177422ff8c8a944b9d34a20c5c0f464f68f4458c02b2f1702
7
+ data.tar.gz: 47f0863f96176f3c90200a4a6ed46c86e31a39de77a8d35a66d7728446b38422288d162e5a0f1a4e54bc2a750ee34f869958e7019048652522d5e15c346e6cf7
data/.rubocop.yml CHANGED
@@ -4,4 +4,5 @@ AllCops:
4
4
  Excludes:
5
5
  - 'Gemfile.devtools'
6
6
  - 'vendor/**'
7
+ - 'tmp/**'
7
8
  - 'benchmarks/**'
data/Changelog.md CHANGED
@@ -1,8 +1,19 @@
1
+ # v0.5.11 2014-04-21
2
+
3
+ Changes:
4
+
5
+ * Fix crash on while and until without body
6
+ * Better require highjack based zombifier
7
+ * Do not mutate nthref $1 to gvar $0
8
+ * Use faster duplicate guarding hashing AST::Node intances
9
+ * Fix lots of shadowed invalid ASTs
10
+ * Fix undefine initialize warnings, Closes #175
11
+
1
12
  # v0.5.10 2014-04-06
2
13
 
3
14
  Changes:
4
15
 
5
- * Fix crash without conditionals
16
+ * Fix crash on case without conditional
6
17
  * Remove dependency to descendants tracker
7
18
  * Add mutation #== => #eql?, #equal?
8
19
  * Add mutation #eql? => #equal?
data/README.md CHANGED
@@ -17,6 +17,9 @@ Mutant supports MRI and RBX 1.9 and 2.0, while support for jruby is planned.
17
17
  It should also work under any ruby engine that supports POSIX-fork(2) semantics.
18
18
  Support for MRI 2.1 is unstable, because this MRI release segfaults on basic metaprogramming mutants dependencies do.
19
19
 
20
+ Mutant uses a pure ruby [parser](https://github.com/whitequark/parser) and an [unparser](https://github.com/mbj/unparser)
21
+ to do its magic.
22
+
20
23
  Integrations
21
24
  ------------
22
25
 
data/Rakefile CHANGED
@@ -3,12 +3,3 @@
3
3
  require 'devtools'
4
4
 
5
5
  Devtools.init_rake_tasks
6
-
7
- Rake.application.load_imports
8
- task('metrics:mutant').clear
9
-
10
- namespace :metrics do
11
- task :mutant => :coverage do
12
- $stderr.puts 'Mutant self mutation is disable till mutant is fast enough for travis'
13
- end
14
- end
data/bin/mutant CHANGED
@@ -10,7 +10,7 @@ require 'mutant'
10
10
  namespace =
11
11
  if ARGV.include?('--zombie')
12
12
  $stderr.puts('Running mutant zombified!')
13
- Mutant::Zombifier.zombify
13
+ Mutant.zombify
14
14
  Zombie::Mutant
15
15
  else
16
16
  Mutant
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 18
3
- total_score: 804
3
+ total_score: 836
data/config/mutant.yml CHANGED
@@ -1,2 +1,15 @@
1
1
  name: mutant
2
2
  namespace: Mutant
3
+ zombify: true
4
+ expect_coverage: 64.99
5
+ ignore_subjects:
6
+ # Mutation causes infinite runtime
7
+ - Mutant::Runner.lookup
8
+ # Suboptimal test selection stragegy (will be fixed soon) causes timeouts on CI
9
+ - Mutant::Zombifier*
10
+ - Mutant::Reporter*
11
+ - Mutant::CLI*
12
+ - Mutant.singleton_subclass_instance
13
+ - Mutant.symbolset
14
+ # Executing this has undefined behavior with the zombifier
15
+ - Mutant.zombify
data/config/reek.yml CHANGED
@@ -52,6 +52,9 @@ NestedIterators:
52
52
  - Mutant::CLI#parse
53
53
  - Mutant::Mutator::Util::Array::Element#dispatch
54
54
  - Mutant::Reporter::CLI::Printer::Config::Runner#generic_stats
55
+ - Mutant::RequireHighjack#infect
56
+ - Mutant::RequireHighjack#desinfect
57
+ - Parser::Lexer#self.new
55
58
  max_allowed_nesting: 1
56
59
  ignore_iterators: []
57
60
  NilCheck:
@@ -85,7 +88,8 @@ TooManyStatements:
85
88
  - Mutant::Reporter::CLI#colorized_diff
86
89
  - Mutant::Reporter::CLI::Printer::Config::Runner#run
87
90
  - Mutant::Runner#dispatch
88
- - Mutant::Zombifier::File#self.find_uncached
91
+ - Mutant::Zombifier::File#self.find
92
+ - Mutant::RequireHighjack#infect
89
93
  # How mutant does CLI parsing is shit
90
94
  - Mutant::CLI#parse
91
95
  - Mutant::CLI#initialize
@@ -123,7 +127,7 @@ UncommunicativeVariableName:
123
127
  - !ruby/regexp /^.$/
124
128
  - !ruby/regexp /[0-9]$/
125
129
  - !ruby/regexp /[A-Z]/
126
- accept: []
130
+ accept: ['force_utf32']
127
131
  UnusedParameters:
128
132
  enabled: true
129
133
  exclude: []
@@ -2,19 +2,6 @@
2
2
 
3
3
  module Mutant
4
4
 
5
- # Return a frozen set of symbols from string enumerable
6
- #
7
- # @param [Enumerable<String>]
8
- #
9
- # @return [Set<Symbol>]
10
- #
11
- # @api private
12
- #
13
- def self.symbolset(strings)
14
- strings.map(&:to_sym).to_set.freeze
15
- end
16
- private_class_method :symbolset
17
-
18
5
  # Set of nodes that cannot be on the LHS of an assignment
19
6
  NOT_ASSIGNABLE = symbolset %w(
20
7
  int float str dstr class module self
@@ -21,8 +21,9 @@ module Mutant
21
21
  #
22
22
  def dispatch
23
23
  emit_condition_mutations
24
- emit_body_mutations
24
+ emit_body_mutations if body
25
25
  emit_body(nil)
26
+ emit_body(RAISE)
26
27
  emit_nil
27
28
  end
28
29
 
@@ -13,7 +13,7 @@ module Mutant
13
13
  :ensure, :redo, :defined?, :regopt, :retry, :arg_expr,
14
14
  :kwrestarg, :kwoptarg, :kwarg, :undef, :module, :empty,
15
15
  :alias, :for, :xstr, :back_ref, :class,
16
- :sclass, :match_with_lvasgn, :match_current_line, :while_post,
16
+ :sclass, :match_with_lvasgn, :while_post,
17
17
  :until_post, :preexe, :postexe, :iflipflop, :eflipflop, :kwsplat,
18
18
  :shadowarg
19
19
  )
@@ -33,7 +33,7 @@ module Mutant
33
33
  #
34
34
  def mutate_condition
35
35
  emit_condition_mutations
36
- emit_self(n_not(condition), if_branch, else_branch)
36
+ emit_self(n_not(condition), if_branch, else_branch) unless condition.type == :match_current_line
37
37
  emit_self(N_TRUE, if_branch, else_branch)
38
38
  emit_self(N_FALSE, if_branch, else_branch)
39
39
  end
@@ -31,11 +31,11 @@ module Mutant
31
31
  # @api private
32
32
  #
33
33
  def dispatch
34
- emit_nil
34
+ emit_nil unless parent_type == :match_current_line
35
35
  children.each_with_index do |child, index|
36
36
  mutate_child(index) unless child.type == :str
37
37
  end
38
- emit_self(s(:str, EMPTY_STRING), options)
38
+ emit_self(options)
39
39
  emit_self(s(:str, NULL_REGEXP_SOURCE), options)
40
40
  end
41
41
 
@@ -0,0 +1,27 @@
1
+ module Mutant
2
+ class Mutator
3
+ class Node
4
+ # Emitter for perl style match current line node
5
+ class MatchCurrentLine < self
6
+
7
+ handle :match_current_line
8
+
9
+ children :regexp
10
+
11
+ private
12
+
13
+ # Emit mutants
14
+ #
15
+ # @return [undefined]
16
+ #
17
+ # @api private
18
+ #
19
+ def dispatch
20
+ emit_nil
21
+ emit_regexp_mutations
22
+ end
23
+
24
+ end # MatchCurrentLine
25
+ end # Node
26
+ end # Mutator
27
+ end # Mutant
@@ -42,7 +42,7 @@ module Mutant
42
42
  def mutate_name
43
43
  prefix = MAP.fetch(node.type)
44
44
  Mutator::Util::Symbol.each(name, self) do |name|
45
- emit_name(prefix + name.to_s)
45
+ emit_name(:"#{prefix}#{name}")
46
46
  end
47
47
  end
48
48
 
@@ -19,7 +19,9 @@ module Mutant
19
19
  # @api private
20
20
  #
21
21
  def dispatch
22
- emit_number(number - 1)
22
+ unless number.equal?(1)
23
+ emit_number(number - 1)
24
+ end
23
25
  emit_number(number + 1)
24
26
  end
25
27
 
@@ -8,18 +8,6 @@ module Mutant
8
8
 
9
9
  handle :rescue
10
10
 
11
- # Return identity
12
- #
13
- # @param [Parser::AST::Node] node
14
- #
15
- # @return [String]
16
- #
17
- # @api private
18
- #
19
- def self.identity(node)
20
- super(NodeHelpers.s(:kwbegin, node))
21
- end
22
-
23
11
  end # Rescue
24
12
  end # Node
25
13
  end # Mutator
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ class Send
7
+
8
+ # Mutator for sends that correspond to an attribute assignment
9
+ class AttributeAssignment < self
10
+
11
+ private
12
+
13
+ # Emit mutations
14
+ #
15
+ # @return [undefined]
16
+ #
17
+ # @api private
18
+ #
19
+ def dispatch
20
+ normal_dispatch
21
+ emit_attribute_read
22
+ end
23
+
24
+ # Mutate arguments
25
+ #
26
+ # @return [undefined]
27
+ #
28
+ # @api private
29
+ #
30
+ def mutate_arguments
31
+ remaining_children_indices.each do |index|
32
+ mutate_child(index)
33
+ end
34
+ end
35
+
36
+ # Emit attribute read
37
+ #
38
+ # @return [undefined]
39
+ #
40
+ # @api private
41
+ #
42
+ def emit_attribute_read
43
+ emit_self(receiver, selector.to_s[0..-2].to_sym)
44
+ end
45
+
46
+ end # AttributeAssignment
47
+
48
+ end # Send
49
+ end # Node
50
+ end # Mutator
51
+ end # Mutant
@@ -0,0 +1,43 @@
1
+ # encoding: UTF-8
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ class Send
7
+ # Base mutator for index operations
8
+ class Index < self
9
+
10
+ # Mutator for index references
11
+ class Reference < self
12
+
13
+ # Perform dispatch
14
+ #
15
+ # @return [undefined]
16
+ #
17
+ # @api private
18
+ #
19
+ def dispatch
20
+ emit(receiver)
21
+ end
22
+
23
+ end # Reference
24
+
25
+ # Mutator for index assignments
26
+ class Assign < self
27
+
28
+ # Perform dispatch
29
+ #
30
+ # @return [undefined]
31
+ #
32
+ # @api private
33
+ #
34
+ def dispatch
35
+ emit(receiver)
36
+ end
37
+
38
+ end # Assign
39
+ end # Index
40
+ end # Send
41
+ end # Node
42
+ end # Mutator
43
+ end # Mutant
@@ -22,40 +22,6 @@ module Mutant
22
22
  INDEX_ASSIGN = :[]=
23
23
  ASSIGN_SUFFIX = '='.freeze
24
24
 
25
- # Base mutator for index operations
26
- class Index < self
27
-
28
- # Mutator for index references
29
- class Reference < self
30
-
31
- # Perform dispatch
32
- #
33
- # @return [undefined]
34
- #
35
- # @api private
36
- #
37
- def dispatch
38
- emit(receiver)
39
- end
40
-
41
- end # Reference
42
-
43
- # Mutator for index assignments
44
- class Assign < self
45
-
46
- # Perform dispatch
47
- #
48
- # @return [undefined]
49
- #
50
- # @api private
51
- #
52
- def dispatch
53
- emit(receiver)
54
- end
55
-
56
- end # Assign
57
- end # Index
58
-
59
25
  private
60
26
 
61
27
  # Perform dispatch
@@ -65,6 +31,7 @@ module Mutant
65
31
  # @api private
66
32
  #
67
33
  def dispatch
34
+ emit_nil
68
35
  case selector
69
36
  when INDEX_REFERENCE
70
37
  run(Index::Reference)
@@ -73,7 +40,6 @@ module Mutant
73
40
  else
74
41
  non_index_dispatch
75
42
  end
76
- emit_nil
77
43
  end
78
44
 
79
45
  # Perform non index dispatch
@@ -86,6 +52,8 @@ module Mutant
86
52
  case
87
53
  when binary_operator?
88
54
  run(Binary)
55
+ when attribute_assignment?
56
+ run(AttributeAssignment)
89
57
  else
90
58
  normal_dispatch
91
59
  end
@@ -173,7 +141,6 @@ module Mutant
173
141
  # @api private
174
142
  #
175
143
  def mutate_arguments
176
- return if arguments.empty?
177
144
  emit_self(receiver, selector)
178
145
  remaining_children_with_index.each do |node, index|
179
146
  mutate_child(index)
@@ -213,7 +180,10 @@ module Mutant
213
180
  # @api private
214
181
  #
215
182
  def emit_implicit_self
216
- if receiver.type == :self && !KEYWORDS.include?(selector) && !attribute_assignment?
183
+ if receiver.type == :self &&
184
+ !KEYWORDS.include?(selector) &&
185
+ !attribute_assignment? &&
186
+ !OP_ASSIGN.include?(parent_type)
217
187
  emit_receiver(nil)
218
188
  end
219
189
  end
@@ -9,18 +9,6 @@ module Mutant
9
9
  class Node < self
10
10
  include AbstractType, NodeHelpers, Unparser::Constants
11
11
 
12
- # Return identity of node
13
- #
14
- # @param [Parser::AST::Node] node
15
- #
16
- # @return [String]
17
- #
18
- # @api private
19
- #
20
- def self.identity(node)
21
- Unparser.unparse(node)
22
- end
23
-
24
12
  # Define named child
25
13
  #
26
14
  # @param [Symbol] name
@@ -58,6 +46,10 @@ module Mutant
58
46
  children.each_with_index.drop(names.length)
59
47
  end
60
48
 
49
+ define_method(:remaining_children_indices) do
50
+ children.each_index.drop(names.length)
51
+ end
52
+
61
53
  define_method(:remaining_children) do
62
54
  children.drop(names.length)
63
55
  end
@@ -33,18 +33,6 @@ module Mutant
33
33
  end
34
34
  private_class_method :handle
35
35
 
36
- # Return identity of object (for deduplication)
37
- #
38
- # @param [Object] object
39
- #
40
- # @return [Object]
41
- #
42
- # @api private
43
- #
44
- def self.identity(object)
45
- object
46
- end
47
-
48
36
  # Return input
49
37
  #
50
38
  # @return [Object]
@@ -92,7 +80,7 @@ module Mutant
92
80
  # @api private
93
81
  #
94
82
  def new?(object)
95
- !@seen.include?(identity(object))
83
+ !@seen.include?(object)
96
84
  end
97
85
 
98
86
  # Add object to guarded values
@@ -104,19 +92,7 @@ module Mutant
104
92
  # @api private
105
93
  #
106
94
  def guard(object)
107
- @seen << identity(object)
108
- end
109
-
110
- # Return identity for input
111
- #
112
- # @param [Object] input
113
- #
114
- # @return [Object]
115
- #
116
- # @api private
117
- #
118
- def identity(input)
119
- self.class.identity(input)
95
+ @seen << object
120
96
  end
121
97
 
122
98
  # Dispatch node generations
@@ -18,13 +18,13 @@ module Mutant
18
18
  module_function :s
19
19
 
20
20
  NAN =
21
- s(:send, s(:float, 0.0), :/, s(:args, s(:float, 0.0)))
21
+ s(:send, s(:float, 0.0), :/, s(:float, 0.0))
22
22
  INFINITY =
23
- s(:send, s(:float, 1.0), :/, s(:args, s(:float, 0.0)))
23
+ s(:send, s(:float, 1.0), :/, s(:float, 0.0))
24
24
  NEW_OBJECT =
25
25
  s(:send, s(:const, s(:cbase), :Object), :new)
26
26
  NEGATIVE_INFINITY =
27
- s(:send, s(:float, -1.0), :/, s(:args, s(:float, 0.0)))
27
+ s(:send, s(:float, -1.0), :/, s(:float, 0.0))
28
28
 
29
29
  RAISE = s(:send, nil, :raise)
30
30
 
@@ -0,0 +1,64 @@
1
+ module Mutant
2
+ # Require highjack
3
+ class RequireHighjack
4
+ include Concord.new(:target, :callback)
5
+
6
+ # Return original method
7
+ #
8
+ # @return [#call]
9
+ #
10
+ # @api private
11
+ #
12
+ attr_reader :original
13
+
14
+ # Run block with highjacked require
15
+ #
16
+ # @return [self]
17
+ #
18
+ # @api pivate
19
+ #
20
+ def run
21
+ infect
22
+ yield
23
+ self
24
+ ensure
25
+ desinfect
26
+ end
27
+
28
+ # Infect kernel with highjack
29
+ #
30
+ # @return [self]
31
+ #
32
+ # @api pivate
33
+ #
34
+ def infect
35
+ callback = @callback
36
+ @original = target.method(:require)
37
+ target.module_eval do
38
+ undef :require
39
+ define_method(:require) do |logical_name|
40
+ callback.call(logical_name)
41
+ end
42
+ module_function :require
43
+ end
44
+ end
45
+
46
+ # Imperfectly desinfect kernel from highjack
47
+ #
48
+ # @return [self]
49
+ #
50
+ # @api private
51
+ #
52
+ def desinfect
53
+ original = @original
54
+ target.module_eval do
55
+ undef :require
56
+ define_method(:require) do |logical_name|
57
+ original.call(logical_name)
58
+ end
59
+ module_function :require
60
+ end
61
+ end
62
+
63
+ end # RequireHighjack
64
+ end # Mutant
@@ -31,7 +31,15 @@ module Mutant
31
31
  # @api private
32
32
  #
33
33
  def prepare
34
- scope.send(:undef_method, name)
34
+ expected_warnings =
35
+ if name.equal?(:initialize)
36
+ ["#{__FILE__}:#{__LINE__ + 5}: warning: undefining `initialize' may cause serious problems\n"]
37
+ else
38
+ []
39
+ end
40
+ WarningExpectation.new(expected_warnings).execute do
41
+ scope.send(:undef_method, name)
42
+ end
35
43
  self
36
44
  end
37
45
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # The current mutant version
5
- VERSION = '0.5.10'.freeze
5
+ VERSION = '0.5.11'.freeze
6
6
  end # Mutant
@@ -0,0 +1,40 @@
1
+ module Mutant
2
+ # A class to expect some warning message raising on absence of unexpected warnings
3
+ class WarningExpectation
4
+ include Adamantium::Flat, Concord.new(:expected)
5
+
6
+ # Error raised on expectation miss
7
+ class ExpectationError < RuntimeError
8
+ include Concord.new(:unexpected, :missing)
9
+
10
+ # Return exception message
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ #
16
+ def message
17
+ "Unexpected warnings: #{unexpected.inspect} missing warnigns: #{missing.inspect}"
18
+ end
19
+ end
20
+
21
+ # Execute blocks with warning expectations
22
+ #
23
+ # @return [self]
24
+ #
25
+ # @api private
26
+ #
27
+ def execute(&block)
28
+ warnings = WarningFilter.use do
29
+ block.call
30
+ end
31
+ missing = expected - warnings
32
+ unexpected = warnings - expected
33
+ if missing.any? or unexpected.any?
34
+ fail ExpectationError.new(unexpected, missing)
35
+ end
36
+ self
37
+ end
38
+
39
+ end # WarningExpectation
40
+ end # Mutant