mutant 0.5.19 → 0.5.20

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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -4
  3. data/Changelog.md +9 -0
  4. data/Gemfile.devtools +15 -11
  5. data/README.md +10 -6
  6. data/config/flay.yml +1 -1
  7. data/config/reek.yml +3 -1
  8. data/lib/mutant.rb +41 -41
  9. data/lib/mutant/cache.rb +0 -2
  10. data/lib/mutant/cli.rb +6 -8
  11. data/lib/mutant/color.rb +0 -2
  12. data/lib/mutant/config.rb +0 -2
  13. data/lib/mutant/context.rb +0 -2
  14. data/lib/mutant/context/scope.rb +0 -2
  15. data/lib/mutant/delegator.rb +2 -0
  16. data/lib/mutant/diff.rb +4 -16
  17. data/lib/mutant/expression.rb +8 -7
  18. data/lib/mutant/expression/method.rb +3 -9
  19. data/lib/mutant/isolation.rb +65 -0
  20. data/lib/mutant/killer.rb +1 -3
  21. data/lib/mutant/loader.rb +0 -2
  22. data/lib/mutant/matcher.rb +0 -2
  23. data/lib/mutant/matcher/chain.rb +0 -2
  24. data/lib/mutant/matcher/filter.rb +0 -2
  25. data/lib/mutant/matcher/method.rb +1 -2
  26. data/lib/mutant/matcher/method/finder.rb +0 -2
  27. data/lib/mutant/matcher/method/instance.rb +1 -3
  28. data/lib/mutant/matcher/method/singleton.rb +0 -2
  29. data/lib/mutant/matcher/methods.rb +0 -2
  30. data/lib/mutant/matcher/namespace.rb +2 -6
  31. data/lib/mutant/matcher/null.rb +0 -2
  32. data/lib/mutant/matcher/scope.rb +0 -2
  33. data/lib/mutant/meta/example.rb +57 -18
  34. data/lib/mutant/meta/example/dsl.rb +10 -13
  35. data/lib/mutant/mutation.rb +0 -2
  36. data/lib/mutant/mutation/evil.rb +0 -2
  37. data/lib/mutant/mutation/neutral.rb +0 -2
  38. data/lib/mutant/mutator.rb +4 -31
  39. data/lib/mutant/mutator/node.rb +19 -18
  40. data/lib/mutant/mutator/node/and_asgn.rb +3 -6
  41. data/lib/mutant/mutator/node/argument.rb +0 -2
  42. data/lib/mutant/mutator/node/arguments.rb +47 -4
  43. data/lib/mutant/mutator/node/begin.rb +3 -10
  44. data/lib/mutant/mutator/node/binary.rb +0 -2
  45. data/lib/mutant/mutator/node/block.rb +0 -2
  46. data/lib/mutant/mutator/node/blockarg.rb +0 -2
  47. data/lib/mutant/mutator/node/break.rb +0 -2
  48. data/lib/mutant/mutator/node/case.rb +3 -6
  49. data/lib/mutant/mutator/node/conditional_loop.rb +0 -2
  50. data/lib/mutant/mutator/node/const.rb +1 -3
  51. data/lib/mutant/mutator/node/define.rb +0 -2
  52. data/lib/mutant/mutator/node/defined.rb +1 -3
  53. data/lib/mutant/mutator/node/dstr.rb +0 -2
  54. data/lib/mutant/mutator/node/dsym.rb +0 -2
  55. data/lib/mutant/mutator/node/generic.rb +0 -2
  56. data/lib/mutant/mutator/node/if.rb +8 -12
  57. data/lib/mutant/mutator/node/kwbegin.rb +0 -2
  58. data/lib/mutant/mutator/node/literal.rb +0 -2
  59. data/lib/mutant/mutator/node/literal/array.rb +2 -5
  60. data/lib/mutant/mutator/node/literal/boolean.rb +0 -2
  61. data/lib/mutant/mutator/node/literal/fixnum.rb +0 -2
  62. data/lib/mutant/mutator/node/literal/float.rb +0 -2
  63. data/lib/mutant/mutator/node/literal/hash.rb +0 -2
  64. data/lib/mutant/mutator/node/literal/nil.rb +0 -2
  65. data/lib/mutant/mutator/node/literal/range.rb +0 -2
  66. data/lib/mutant/mutator/node/literal/regex.rb +2 -4
  67. data/lib/mutant/mutator/node/literal/string.rb +0 -2
  68. data/lib/mutant/mutator/node/literal/symbol.rb +0 -2
  69. data/lib/mutant/mutator/node/masgn.rb +0 -2
  70. data/lib/mutant/mutator/node/mlhs.rb +0 -2
  71. data/lib/mutant/mutator/node/named_value/access.rb +0 -2
  72. data/lib/mutant/mutator/node/named_value/constant_assignment.rb +0 -2
  73. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +0 -2
  74. data/lib/mutant/mutator/node/next.rb +0 -3
  75. data/lib/mutant/mutator/node/noop.rb +0 -2
  76. data/lib/mutant/mutator/node/nthref.rb +0 -2
  77. data/lib/mutant/mutator/node/op_asgn.rb +2 -4
  78. data/lib/mutant/mutator/node/or_asgn.rb +4 -7
  79. data/lib/mutant/mutator/node/resbody.rb +1 -1
  80. data/lib/mutant/mutator/node/rescue.rb +0 -2
  81. data/lib/mutant/mutator/node/restarg.rb +0 -2
  82. data/lib/mutant/mutator/node/return.rb +3 -6
  83. data/lib/mutant/mutator/node/send.rb +14 -8
  84. data/lib/mutant/mutator/node/send/attribute_assignment.rb +0 -2
  85. data/lib/mutant/mutator/node/send/binary.rb +1 -3
  86. data/lib/mutant/mutator/node/splat.rb +0 -2
  87. data/lib/mutant/mutator/node/super.rb +0 -2
  88. data/lib/mutant/mutator/node/when.rb +0 -2
  89. data/lib/mutant/mutator/node/yield.rb +0 -2
  90. data/lib/mutant/mutator/node/zsuper.rb +0 -2
  91. data/lib/mutant/mutator/registry.rb +0 -2
  92. data/lib/mutant/mutator/util.rb +0 -2
  93. data/lib/mutant/mutator/util/array.rb +0 -2
  94. data/lib/mutant/mutator/util/symbol.rb +0 -2
  95. data/lib/mutant/node_helpers.rb +11 -2
  96. data/lib/mutant/reporter.rb +0 -2
  97. data/lib/mutant/reporter/cli.rb +0 -2
  98. data/lib/mutant/reporter/cli/printer.rb +0 -2
  99. data/lib/mutant/reporter/cli/progress.rb +0 -2
  100. data/lib/mutant/reporter/cli/report.rb +0 -2
  101. data/lib/mutant/reporter/cli/report/config.rb +0 -2
  102. data/lib/mutant/reporter/cli/report/mutation.rb +5 -5
  103. data/lib/mutant/reporter/cli/report/subject.rb +0 -2
  104. data/lib/mutant/reporter/null.rb +0 -2
  105. data/lib/mutant/runner.rb +0 -2
  106. data/lib/mutant/runner/config.rb +0 -2
  107. data/lib/mutant/runner/mutation.rb +0 -2
  108. data/lib/mutant/runner/subject.rb +0 -2
  109. data/lib/mutant/strategy.rb +0 -2
  110. data/lib/mutant/subject.rb +0 -2
  111. data/lib/mutant/subject/method.rb +0 -2
  112. data/lib/mutant/subject/method/instance.rb +1 -3
  113. data/lib/mutant/subject/method/singleton.rb +0 -2
  114. data/lib/mutant/version.rb +1 -3
  115. data/lib/mutant/zombifier.rb +0 -2
  116. data/meta/begin.rb +10 -0
  117. data/meta/case.rb +0 -2
  118. data/meta/def.rb +19 -0
  119. data/mutant-rspec.gemspec +1 -1
  120. data/mutant.gemspec +1 -1
  121. data/spec/integration/mutant/corpus_spec.rb +81 -22
  122. data/spec/integration/mutant/null_spec.rb +1 -3
  123. data/spec/integration/mutant/rspec_spec.rb +6 -8
  124. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +0 -2
  125. data/spec/integration/mutant/zombie_spec.rb +0 -2
  126. data/spec/integrations.yml +12 -1
  127. data/spec/shared/method_matcher_behavior.rb +0 -2
  128. data/spec/spec_helper.rb +0 -2
  129. data/spec/support/compress_helper.rb +0 -2
  130. data/spec/support/ice_nine_config.rb +0 -2
  131. data/spec/support/rspec.rb +0 -2
  132. data/spec/support/test_app.rb +0 -2
  133. data/spec/unit/mutant/cli_new_spec.rb +0 -2
  134. data/spec/unit/mutant/cli_run_spec.rb +0 -2
  135. data/spec/unit/mutant/context/root_spec.rb +0 -2
  136. data/spec/unit/mutant/context/scope/root_spec.rb +0 -2
  137. data/spec/unit/mutant/context/scope/unqualified_name_spec.rb +0 -2
  138. data/spec/unit/mutant/diff_spec.rb +0 -2
  139. data/spec/unit/mutant/expression/method_spec.rb +2 -4
  140. data/spec/unit/mutant/expression/namespace/flat_spec.rb +1 -3
  141. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +10 -6
  142. data/spec/unit/mutant/isolation_spec.rb +61 -0
  143. data/spec/unit/mutant/loader/eval_spec.rb +0 -2
  144. data/spec/unit/mutant/matcher/chain_spec.rb +4 -15
  145. data/spec/unit/mutant/matcher/method/instance_spec.rb +0 -2
  146. data/spec/unit/mutant/matcher/method/singleton_spec.rb +0 -2
  147. data/spec/unit/mutant/matcher/methods/instance_spec.rb +0 -2
  148. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +0 -2
  149. data/spec/unit/mutant/matcher/namespace_spec.rb +33 -31
  150. data/spec/unit/mutant/mutation_spec.rb +0 -2
  151. data/spec/unit/mutant/runner/config_spec.rb +0 -2
  152. data/spec/unit/mutant/runner/mutation_spec.rb +0 -2
  153. data/spec/unit/mutant/runner/subject_spec.rb +0 -2
  154. data/spec/unit/mutant/strategy_spec.rb +0 -2
  155. data/spec/unit/mutant/subject/context_spec.rb +0 -2
  156. data/spec/unit/mutant/subject/mutations_spec.rb +0 -2
  157. data/spec/unit/mutant/subject/node_spec.rb +0 -2
  158. data/spec/unit/mutant/subject_spec.rb +0 -2
  159. data/spec/unit/mutant/warning_filter_spec.rb +6 -0
  160. data/spec/unit/mutant_spec.rb +0 -55
  161. data/test_app/Gemfile.devtools +15 -11
  162. data/test_app/Gemfile.rspec3 +2 -2
  163. metadata +7 -7
  164. data/lib/mutant/constants.rb +0 -45
  165. data/spec/unit/mutant/mutator_spec.rb +0 -29
@@ -0,0 +1,65 @@
1
+ module Mutant
2
+ # Module providing isolationg
3
+ module Isolation
4
+ Error = Class.new(RuntimeError)
5
+
6
+ # Call block in isolation
7
+ #
8
+ # This isolation implements the fork strategy.
9
+ # Future strategies will probably use a process pool that can
10
+ # handle multiple mutation kills, in-isolation at once.
11
+ #
12
+ # @return [Object]
13
+ #
14
+ # @raise [Error]
15
+ #
16
+ # @api private
17
+ #
18
+ def self.call(&block)
19
+ reader, writer = IO.pipe.each(&:binmode)
20
+
21
+ pid = fork
22
+
23
+ if pid.nil?
24
+ begin
25
+ reader.close
26
+ writer.write(Marshal.dump(block.call))
27
+ Kernel.exit!(0)
28
+ ensure
29
+ Kernel.exit!(1)
30
+ end
31
+ end
32
+
33
+ writer.close
34
+
35
+ read_result(reader, pid)
36
+ end
37
+
38
+ # Read result from child process
39
+ #
40
+ # @param [IO] reader
41
+ # @param [Fixnum] pid
42
+ #
43
+ # @return [Object]
44
+ #
45
+ # @raise [Error]
46
+ #
47
+ def self.read_result(reader, pid)
48
+ begin
49
+ data = Marshal.load(reader.read)
50
+ rescue ArgumentError, TypeError
51
+ raise Error, 'Childprocess wrote un-unmarshallable data'
52
+ end
53
+
54
+ status = Process.waitpid2(pid).last
55
+
56
+ unless status.exitstatus.zero?
57
+ raise Error, "Childprocess exited with nonzero exit status: #{status.exitstatus}"
58
+ end
59
+
60
+ data
61
+ end
62
+ private_class_method :read_result
63
+
64
+ end # Isolator
65
+ end # Mutant
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  # Mutation killer
5
3
  class Killer
@@ -31,7 +29,7 @@ module Mutant
31
29
  # @api private
32
30
  #
33
31
  def run
34
- test_report = Mutant.isolate do
32
+ test_report = Isolation.call do
35
33
  mutation.insert
36
34
  test.run
37
35
  end
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  # Base class for code loaders
5
3
  class Loader
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  # Abstract matcher to find subjects to mutate
5
3
  class Matcher
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
  # A chain of matchers
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
  # Matcher filter
@@ -1,10 +1,9 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
  # Matcher for subjects that are a specific method
6
4
  class Method < self
7
5
  include Adamantium::Flat, Concord::Public.new(:cache, :scope, :method)
6
+ include Equalizer.new(:identification)
8
7
 
9
8
  # Methods within rbx kernel directory are precompiled and their source
10
9
  # cannot be accessed via reading source location
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
  class Method
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
  class Method
@@ -19,7 +17,7 @@ module Mutant
19
17
  #
20
18
  def self.build(cache, scope, method)
21
19
  name = method.name
22
- if scope.ancestors.include?(::Memoizable) and scope.memoized?(name)
20
+ if scope.ancestors.include?(::Memoizable) && scope.memoized?(name)
23
21
  return Memoized.new(cache, scope, method)
24
22
  end
25
23
  super
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
  class Method
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
  # Abstract base class for matcher that returns method subjects from scope
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
 
@@ -36,7 +34,7 @@ module Mutant
36
34
  # @api private
37
35
  #
38
36
  def pattern
39
- /\A#{Regexp.escape(namespace)}(?:::)?/
37
+ /\A#{Regexp.escape(namespace)}(?:\z|::)/
40
38
  end
41
39
  memoize :pattern
42
40
 
@@ -95,9 +93,7 @@ Fix your lib to support normal ruby semantics!
95
93
  MESSAGE
96
94
  return
97
95
  end
98
- if pattern =~ name
99
- yield scope
100
- end
96
+ yield scope if pattern =~ name
101
97
  end
102
98
 
103
99
  end # Namespace
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
  # A null matcher, that does not match any subjects
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Matcher
5
3
  # Matcher for specific namespace
@@ -15,20 +15,33 @@ module Mutant
15
15
  Verification.new(self, generated)
16
16
  end
17
17
 
18
+ # Return source
19
+ #
20
+ # @return [String]
21
+ #
22
+ # @api private
23
+ #
24
+ def source
25
+ Unparser.unparse(node)
26
+ end
27
+ memoize :source
28
+
18
29
  # Return generated mutations
19
30
  #
20
- # @return [Emumerable<Parser::AST::Node>]
31
+ # @return [Emumerable<Mutant::Mutation>]
21
32
  #
22
33
  # @api private
23
34
  #
24
35
  def generated
25
- Mutant::Mutator.each(node).to_a
36
+ Mutant::Mutator.each(node).map do |node|
37
+ Mutant::Mutation::Evil.new(self, node)
38
+ end
26
39
  end
27
40
  memoize :generated
28
41
 
29
42
  # Example verification
30
43
  class Verification
31
- include Adamantium::Flat, Concord.new(:example, :generated)
44
+ include Adamantium::Flat, Concord.new(:example, :mutations)
32
45
 
33
46
  # Test if mutation was verified successfully
34
47
  #
@@ -37,7 +50,7 @@ module Mutant
37
50
  # @api private
38
51
  #
39
52
  def success?
40
- unparser.success? && missing.empty? && unexpected.empty?
53
+ unparser.success? && missing.empty? && unexpected.empty? && no_diffs.empty?
41
54
  end
42
55
 
43
56
  # Return error report
@@ -62,10 +75,21 @@ module Mutant
62
75
  # @api private
63
76
  #
64
77
  def unexpected
65
- generated - example.mutations
78
+ mutations.map(&:node) - example.mutations
66
79
  end
67
80
  memoize :unexpected
68
81
 
82
+ # Return mutations with no diff to original
83
+ #
84
+ # @return [Enumerable<Mutation>]
85
+ #
86
+ # @api private
87
+ #
88
+ def no_diffs
89
+ mutations.select { |mutation| mutation.source.eql?(example.source) }
90
+ end
91
+ memoize :no_diffs
92
+
69
93
  # Return mutation report
70
94
  #
71
95
  # @return [String]
@@ -78,9 +102,10 @@ module Mutant
78
102
  'Original-AST:',
79
103
  original_node.inspect,
80
104
  'Original-Source:',
81
- Unparser.unparse(original_node),
105
+ example.source,
82
106
  *missing_report,
83
- *unexpected_report
107
+ *unexpected_report,
108
+ *no_diff_report
84
109
  ].join("\n======\n")
85
110
  end
86
111
 
@@ -91,9 +116,25 @@ module Mutant
91
116
  # @api private
92
117
  #
93
118
  def missing_report
94
- if missing.any?
95
- ['Missing mutations:', missing.map(&method(:format_mutation)).join("\n-----\n")]
96
- end
119
+ [
120
+ 'Missing mutations:',
121
+ missing.map(&method(:format_mutation)).join("\n-----\n")
122
+ ] if missing.any?
123
+ end
124
+
125
+ # Return no diff report
126
+ #
127
+ # @return [Array, nil]
128
+ #
129
+ # @api private
130
+ #
131
+ def no_diff_report
132
+ [
133
+ 'No source diffs to original:',
134
+ no_diffs.map do |mutation|
135
+ "#{mutation.node.inspect}\n#{mutation.source}"
136
+ end
137
+ ] if no_diffs.any?
97
138
  end
98
139
 
99
140
  # Return unexpected report
@@ -103,12 +144,10 @@ module Mutant
103
144
  # @api private
104
145
  #
105
146
  def unexpected_report
106
- if unexpected.any?
107
- [
108
- 'Unexpected mutations:',
109
- unexpected.map(&method(:format_mutation)).join("\n-----\n")
110
- ]
111
- end
147
+ [
148
+ 'Unexpected mutations:',
149
+ unexpected.map(&method(:format_mutation)).join("\n-----\n")
150
+ ] if unexpected.any?
112
151
  end
113
152
 
114
153
  # Format mutation
@@ -124,14 +163,14 @@ module Mutant
124
163
  ].join("\n")
125
164
  end
126
165
 
127
- # Return missing mutationso
166
+ # Return missing mutations
128
167
  #
129
168
  # @return [Array<Parser::AST::Node>]
130
169
  #
131
170
  # @api private
132
171
  #
133
172
  def missing
134
- example.mutations - generated
173
+ example.mutations - mutations.map(&:node)
135
174
  end
136
175
  memoize :missing
137
176
 
@@ -81,8 +81,8 @@ module Mutant
81
81
  # @api private
82
82
  #
83
83
  def singleton_mutations
84
- mutation 'nil'
85
- mutation 'self'
84
+ mutation('nil')
85
+ mutation('self')
86
86
  end
87
87
 
88
88
  # Helper method to coerce input to node
@@ -97,17 +97,14 @@ module Mutant
97
97
  # @api private
98
98
  #
99
99
  def node(input)
100
- node =
101
- case input
102
- when String
103
- Parser::CurrentRuby.parse(input)
104
- when Parser::AST::Node
105
- input
106
- else
107
- raise "Cannot coerce to node: #{source.inspect}"
108
- end
109
-
110
- Unparser::Preprocessor.run(node)
100
+ case input
101
+ when String
102
+ Unparser::Preprocessor.run(Parser::CurrentRuby.parse(input))
103
+ when Parser::AST::Node
104
+ input
105
+ else
106
+ raise "Cannot coerce to node: #{source.inspect}"
107
+ end
111
108
  end
112
109
 
113
110
  end # DSL
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  # Represent a mutated node with its subject
5
3
  class Mutation
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Mutation
5
3
  # Evul mutation
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  class Mutation
5
3
  # Neutral mutation
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Mutant
4
2
  # Generator for mutations
5
3
  class Mutator
@@ -7,7 +5,10 @@ module Mutant
7
5
 
8
6
  # Run mutator on input
9
7
  #
10
- # @param [Parser::AST::Node] node
8
+ # @param [Object] input
9
+ # the input to mutate
10
+ #
11
+ # @param [Mutator] parent
11
12
  #
12
13
  # @return [self]
13
14
  #
@@ -119,34 +120,6 @@ module Mutant
119
120
  emit!(object)
120
121
  end
121
122
 
122
- # Maximum amount of tries to generate a new object
123
- MAX_TRIES = 3
124
-
125
- # Call block until it generates a mutation
126
- #
127
- # @yield
128
- # Execute block until object is generated where new?(object) returns true
129
- #
130
- # @return [self]
131
- #
132
- # @raise [RuntimeError]
133
- # raises RuntimeError when no new node can be generated after MAX_TRIES.
134
- #
135
- # @api private
136
- #
137
- def emit_new
138
- MAX_TRIES.times do
139
- object = yield
140
-
141
- if new?(object)
142
- emit!(object)
143
- return
144
- end
145
- end
146
-
147
- raise "New AST could not be generated after #{MAX_TRIES} attempts"
148
- end
149
-
150
123
  # Call block with node
151
124
  #
152
125
  # @param [Parser::AST::Node] node