mutant 0.8.10 → 0.8.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (247) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -4
  3. data/Changelog.md +8 -0
  4. data/README.md +112 -43
  5. data/Rakefile +2 -16
  6. data/circle.yml +1 -1
  7. data/config/flay.yml +2 -2
  8. data/config/flog.yml +1 -1
  9. data/config/reek.yml +3 -4
  10. data/config/rubocop.yml +53 -16
  11. data/lib/mutant.rb +27 -6
  12. data/lib/mutant/ast/meta/const.rb +2 -0
  13. data/lib/mutant/ast/meta/optarg.rb +2 -0
  14. data/lib/mutant/ast/meta/resbody.rb +2 -0
  15. data/lib/mutant/ast/meta/restarg.rb +2 -0
  16. data/lib/mutant/ast/meta/send.rb +4 -0
  17. data/lib/mutant/ast/meta/symbol.rb +2 -0
  18. data/lib/mutant/ast/named_children.rb +14 -4
  19. data/lib/mutant/ast/nodes.rb +3 -1
  20. data/lib/mutant/ast/regexp.rb +53 -0
  21. data/lib/mutant/ast/regexp/transformer.rb +185 -0
  22. data/lib/mutant/ast/regexp/transformer/alternative.rb +39 -0
  23. data/lib/mutant/ast/regexp/transformer/character_set.rb +46 -0
  24. data/lib/mutant/ast/regexp/transformer/direct.rb +99 -0
  25. data/lib/mutant/ast/regexp/transformer/options_group.rb +66 -0
  26. data/lib/mutant/ast/regexp/transformer/quantifier.rb +112 -0
  27. data/lib/mutant/ast/regexp/transformer/recursive.rb +50 -0
  28. data/lib/mutant/ast/regexp/transformer/root.rb +29 -0
  29. data/lib/mutant/ast/regexp/transformer/text.rb +55 -0
  30. data/lib/mutant/ast/types.rb +92 -5
  31. data/lib/mutant/cli.rb +2 -14
  32. data/lib/mutant/color.rb +1 -1
  33. data/lib/mutant/config.rb +1 -3
  34. data/lib/mutant/expression/methods.rb +1 -1
  35. data/lib/mutant/expression/namespace.rb +2 -2
  36. data/lib/mutant/expression/parser.rb +1 -1
  37. data/lib/mutant/integration.rb +10 -28
  38. data/lib/mutant/isolation.rb +9 -60
  39. data/lib/mutant/isolation/fork.rb +72 -0
  40. data/lib/mutant/isolation/none.rb +25 -0
  41. data/lib/mutant/matcher/config.rb +2 -0
  42. data/lib/mutant/matcher/method/singleton.rb +5 -4
  43. data/lib/mutant/meta.rb +11 -4
  44. data/lib/mutant/meta/example.rb +2 -116
  45. data/lib/mutant/meta/example/dsl.rb +22 -19
  46. data/lib/mutant/meta/example/verification.rb +86 -0
  47. data/lib/mutant/mutator.rb +22 -49
  48. data/lib/mutant/mutator/node.rb +15 -19
  49. data/lib/mutant/mutator/node/and_asgn.rb +1 -1
  50. data/lib/mutant/mutator/node/argument.rb +10 -5
  51. data/lib/mutant/mutator/node/arguments.rb +5 -9
  52. data/lib/mutant/mutator/node/begin.rb +4 -17
  53. data/lib/mutant/mutator/node/block.rb +1 -1
  54. data/lib/mutant/mutator/node/break.rb +1 -1
  55. data/lib/mutant/mutator/node/class.rb +21 -0
  56. data/lib/mutant/mutator/node/conditional_loop.rb +1 -1
  57. data/lib/mutant/mutator/node/define.rb +1 -1
  58. data/lib/mutant/mutator/node/defined.rb +1 -1
  59. data/lib/mutant/mutator/node/dstr.rb +1 -1
  60. data/lib/mutant/mutator/node/dsym.rb +1 -1
  61. data/lib/mutant/mutator/node/generic.rb +3 -3
  62. data/lib/mutant/mutator/node/kwbegin.rb +1 -1
  63. data/lib/mutant/mutator/node/literal.rb +9 -0
  64. data/lib/mutant/mutator/node/literal/boolean.rb +1 -1
  65. data/lib/mutant/mutator/node/literal/fixnum.rb +2 -2
  66. data/lib/mutant/mutator/node/literal/float.rb +4 -6
  67. data/lib/mutant/mutator/node/literal/hash.rb +1 -1
  68. data/lib/mutant/mutator/node/literal/nil.rb +1 -1
  69. data/lib/mutant/mutator/node/literal/range.rb +2 -19
  70. data/lib/mutant/mutator/node/literal/regex.rb +43 -3
  71. data/lib/mutant/mutator/node/literal/string.rb +1 -1
  72. data/lib/mutant/mutator/node/literal/symbol.rb +2 -4
  73. data/lib/mutant/mutator/node/masgn.rb +1 -1
  74. data/lib/mutant/mutator/node/match_current_line.rb +1 -1
  75. data/lib/mutant/mutator/node/mlhs.rb +2 -3
  76. data/lib/mutant/mutator/node/named_value/access.rb +2 -2
  77. data/lib/mutant/mutator/node/named_value/constant_assignment.rb +4 -3
  78. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +4 -4
  79. data/lib/mutant/mutator/node/next.rb +1 -1
  80. data/lib/mutant/mutator/node/nthref.rb +1 -1
  81. data/lib/mutant/mutator/node/or_asgn.rb +1 -1
  82. data/lib/mutant/mutator/node/regexp.rb +44 -0
  83. data/lib/mutant/mutator/node/regopt.rb +31 -0
  84. data/lib/mutant/mutator/node/resbody.rb +1 -1
  85. data/lib/mutant/mutator/node/rescue.rb +1 -3
  86. data/lib/mutant/mutator/node/return.rb +1 -1
  87. data/lib/mutant/mutator/node/send.rb +43 -3
  88. data/lib/mutant/mutator/node/send/attribute_assignment.rb +4 -1
  89. data/lib/mutant/mutator/node/send/conditional.rb +23 -0
  90. data/lib/mutant/mutator/node/send/index.rb +1 -1
  91. data/lib/mutant/mutator/node/splat.rb +1 -1
  92. data/lib/mutant/mutator/node/when.rb +1 -1
  93. data/lib/mutant/mutator/node/yield.rb +1 -1
  94. data/lib/mutant/mutator/util.rb +0 -30
  95. data/lib/mutant/mutator/util/array.rb +4 -16
  96. data/lib/mutant/parallel.rb +1 -1
  97. data/lib/mutant/parallel/worker.rb +1 -1
  98. data/lib/mutant/registry.rb +44 -0
  99. data/lib/mutant/reporter/cli/format.rb +2 -0
  100. data/lib/mutant/reporter/cli/printer.rb +2 -2
  101. data/lib/mutant/reporter/cli/printer/config.rb +0 -1
  102. data/lib/mutant/reporter/cli/printer/env_progress.rb +1 -11
  103. data/lib/mutant/reporter/cli/printer/mutation_progress_result.rb +1 -1
  104. data/lib/mutant/reporter/cli/printer/mutation_result.rb +2 -0
  105. data/lib/mutant/reporter/cli/tput.rb +1 -1
  106. data/lib/mutant/reporter/sequence.rb +3 -0
  107. data/lib/mutant/require_highjack.rb +6 -2
  108. data/lib/mutant/result.rb +1 -1
  109. data/lib/mutant/subject.rb +5 -5
  110. data/lib/mutant/subject/method/instance.rb +1 -2
  111. data/lib/mutant/util.rb +18 -0
  112. data/lib/mutant/version.rb +1 -1
  113. data/lib/mutant/zombifier.rb +5 -3
  114. data/meta/and.rb +1 -1
  115. data/meta/and_asgn.rb +1 -1
  116. data/meta/array.rb +2 -2
  117. data/meta/begin.rb +2 -2
  118. data/meta/block.rb +7 -7
  119. data/meta/block_pass.rb +1 -1
  120. data/meta/blockarg.rb +1 -1
  121. data/meta/break.rb +1 -1
  122. data/meta/case.rb +2 -2
  123. data/meta/casgn.rb +11 -2
  124. data/meta/cbase.rb +1 -1
  125. data/meta/class.rb +10 -0
  126. data/meta/const.rb +9 -1
  127. data/meta/csend.rb +8 -0
  128. data/meta/cvar.rb +1 -1
  129. data/meta/cvasgn.rb +1 -1
  130. data/meta/date.rb +4 -4
  131. data/meta/def.rb +14 -14
  132. data/meta/defined.rb +1 -1
  133. data/meta/dstr.rb +1 -1
  134. data/meta/dsym.rb +1 -1
  135. data/meta/ensure.rb +1 -1
  136. data/meta/false.rb +1 -1
  137. data/meta/float.rb +3 -3
  138. data/meta/gvar.rb +1 -1
  139. data/meta/gvasgn.rb +1 -1
  140. data/meta/hash.rb +1 -1
  141. data/meta/if.rb +17 -3
  142. data/meta/int.rb +1 -1
  143. data/meta/ivar.rb +1 -1
  144. data/meta/ivasgn.rb +14 -1
  145. data/meta/kwarg.rb +8 -0
  146. data/meta/kwbegin.rb +1 -1
  147. data/meta/kwoptarg.rb +11 -0
  148. data/meta/lvar.rb +1 -1
  149. data/meta/lvasgn.rb +1 -1
  150. data/meta/masgn.rb +1 -1
  151. data/meta/match_current_line.rb +2 -2
  152. data/meta/next.rb +1 -1
  153. data/meta/nil.rb +1 -1
  154. data/meta/nthref.rb +5 -5
  155. data/meta/op_assgn.rb +1 -1
  156. data/meta/or.rb +1 -1
  157. data/meta/or_asgn.rb +5 -5
  158. data/meta/range.rb +2 -8
  159. data/meta/redo.rb +1 -1
  160. data/meta/regexp.rb +106 -0
  161. data/meta/regexp/regexp_bol_anchor.rb +13 -0
  162. data/meta/regexp/regexp_bos_anchor.rb +26 -0
  163. data/meta/regexp/regexp_root_expression.rb +13 -0
  164. data/meta/regopt.rb +8 -0
  165. data/meta/rescue.rb +49 -4
  166. data/meta/restarg.rb +6 -9
  167. data/meta/return.rb +2 -2
  168. data/meta/self.rb +1 -1
  169. data/meta/send.rb +228 -55
  170. data/meta/str.rb +1 -1
  171. data/meta/super.rb +3 -3
  172. data/meta/{symbol.rb → sym.rb} +1 -1
  173. data/meta/true.rb +1 -1
  174. data/meta/until.rb +1 -1
  175. data/meta/while.rb +2 -2
  176. data/meta/yield.rb +1 -1
  177. data/mutant-rspec.gemspec +2 -2
  178. data/mutant.gemspec +6 -5
  179. data/spec/integration/mutant/isolation/fork_spec.rb +8 -0
  180. data/spec/integration/mutant/rspec_spec.rb +1 -1
  181. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -2
  182. data/spec/integrations.yml +93 -24
  183. data/spec/spec_helper.rb +12 -7
  184. data/spec/support/compress_helper.rb +1 -1
  185. data/spec/support/corpus.rb +115 -50
  186. data/spec/support/fake_actor.rb +5 -5
  187. data/spec/support/file_system.rb +1 -1
  188. data/spec/support/ice_nine_config.rb +3 -3
  189. data/spec/support/ruby_vm.rb +11 -12
  190. data/spec/support/shared_context.rb +22 -13
  191. data/spec/support/test_app.rb +1 -1
  192. data/spec/support/warning.rb +64 -0
  193. data/spec/support/warnings.yml +4 -0
  194. data/spec/support/xspec.rb +177 -0
  195. data/spec/unit/mutant/actor/env_spec.rb +2 -2
  196. data/spec/unit/mutant/actor/sender_spec.rb +1 -1
  197. data/spec/unit/mutant/ast/meta/send_spec.rb +1 -1
  198. data/spec/unit/mutant/ast/named_children_spec.rb +26 -0
  199. data/spec/unit/mutant/ast/regexp/parse_spec.rb +7 -0
  200. data/spec/unit/mutant/ast/regexp/supported_predicate_spec.rb +14 -0
  201. data/spec/unit/mutant/ast/regexp/transformer/lookup_table/table_spec.rb +19 -0
  202. data/spec/unit/mutant/ast/regexp/transformer/lookup_table_spec.rb +33 -0
  203. data/spec/unit/mutant/ast/regexp/transformer_spec.rb +19 -0
  204. data/spec/unit/mutant/ast/regexp_spec.rb +617 -0
  205. data/spec/unit/mutant/cli_spec.rb +7 -45
  206. data/spec/unit/mutant/context_spec.rb +4 -7
  207. data/spec/unit/mutant/env/{boostrap_spec.rb → bootstrap_spec.rb} +2 -2
  208. data/spec/unit/mutant/env_spec.rb +13 -16
  209. data/spec/unit/mutant/expression/namespace/{flat_spec.rb → exact_spec.rb} +0 -0
  210. data/spec/unit/mutant/integration/rspec_spec.rb +2 -2
  211. data/spec/unit/mutant/integration_spec.rb +14 -0
  212. data/spec/unit/mutant/isolation/fork_spec.rb +155 -0
  213. data/spec/unit/mutant/isolation/none_spec.rb +16 -0
  214. data/spec/unit/mutant/loader_spec.rb +1 -1
  215. data/spec/unit/mutant/matcher/methods/instance_spec.rb +2 -4
  216. data/spec/unit/mutant/meta/example/dsl_spec.rb +106 -0
  217. data/spec/unit/mutant/meta/example/verification_spec.rb +120 -0
  218. data/spec/unit/mutant/meta/example_spec.rb +32 -0
  219. data/spec/unit/mutant/mutator/node_spec.rb +37 -4
  220. data/spec/unit/mutant/mutator_spec.rb +21 -0
  221. data/spec/unit/mutant/{runner → parallel}/driver_spec.rb +0 -0
  222. data/spec/unit/mutant/parallel/master_spec.rb +13 -13
  223. data/spec/unit/mutant/registry_spec.rb +47 -0
  224. data/spec/unit/mutant/reporter/cli/printer/config_spec.rb +0 -4
  225. data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +0 -8
  226. data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +0 -2
  227. data/spec/unit/mutant/reporter/cli/printer/status_progressive_spec.rb +0 -8
  228. data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +0 -34
  229. data/spec/unit/mutant/reporter/cli_spec.rb +0 -22
  230. data/spec/unit/mutant/repository/diff_spec.rb +6 -6
  231. data/spec/unit/mutant/require_highjack_spec.rb +38 -14
  232. data/spec/unit/mutant/result/env_spec.rb +1 -4
  233. data/spec/unit/mutant/runner_spec.rb +1 -1
  234. data/spec/unit/mutant/subject/method/instance_spec.rb +1 -1
  235. data/spec/unit/mutant/subject_spec.rb +3 -3
  236. data/spec/unit/mutant/util/one_spec.rb +20 -0
  237. data/spec/unit/mutant/zombifier_spec.rb +18 -18
  238. data/test_app/{Gemfile.rspec3.3 → Gemfile.rspec3.5} +2 -2
  239. metadata +94 -24
  240. data/TODO +0 -21
  241. data/lib/mutant/mutator/node/blockarg.rb +0 -13
  242. data/lib/mutant/mutator/node/restarg.rb +0 -13
  243. data/lib/mutant/mutator/registry.rb +0 -49
  244. data/meta/boolean.rb +0 -13
  245. data/meta/regex.rb +0 -19
  246. data/spec/unit/mutant/isolation_spec.rb +0 -104
  247. data/spec/unit/mutant/mutator/registry_spec.rb +0 -57
@@ -0,0 +1,25 @@
1
+ module Mutant
2
+ # Module providing isolation
3
+ class Isolation
4
+ Error = Class.new(RuntimeError)
5
+
6
+ # Absolutly no isolation
7
+ #
8
+ # Only useful for debugging.
9
+ class None < self
10
+
11
+ # Call block in no isolation
12
+ #
13
+ # @return [Object]
14
+ #
15
+ # @raise [Error]
16
+ # if block terminates abnormal
17
+ def call
18
+ yield
19
+ rescue => exception
20
+ raise Error, exception
21
+ end
22
+
23
+ end # None
24
+ end # Isolation
25
+ end # Mutant
@@ -1,6 +1,8 @@
1
1
  module Mutant
2
2
  class Matcher
3
3
  # Subject matcher configuration
4
+ #
5
+ # :reek:TooManyConstants
4
6
  class Config
5
7
  include Adamantium, Anima.new(
6
8
  :ignore_expressions,
@@ -16,9 +16,10 @@ module Mutant
16
16
 
17
17
  # Singleton method evaluator
18
18
  class Evaluator < Evaluator
19
- SUBJECT_CLASS = Subject::Method::Singleton
20
- RECEIVER_INDEX = 0
21
- NAME_INDEX = 1
19
+ SUBJECT_CLASS = Subject::Method::Singleton
20
+ RECEIVER_INDEX = 0
21
+ NAME_INDEX = 1
22
+ RECEIVER_WARNING = 'Can only match :defs on :self or :const got %p unable to match'.freeze
22
23
 
23
24
  private
24
25
 
@@ -65,7 +66,7 @@ module Mutant
65
66
  when :const
66
67
  receiver_name?(receiver)
67
68
  else
68
- env.warn(format('Can only match :defs on :self or :const got %s unable to match', receiver.type.inspect))
69
+ env.warn(RECEIVER_WARNING % receiver.type)
69
70
  nil
70
71
  end
71
72
  end
@@ -3,26 +3,33 @@ module Mutant
3
3
  module Meta
4
4
  require 'mutant/meta/example'
5
5
  require 'mutant/meta/example/dsl'
6
+ require 'mutant/meta/example/verification'
6
7
 
7
8
  # Mutation example
8
9
  class Example
9
10
 
11
+ # rubocop:disable MutableConstant
10
12
  ALL = []
11
13
 
12
14
  # Add example
13
15
  #
14
16
  # @return [undefined]
15
- def self.add(&block)
17
+ def self.add(type, &block)
16
18
  file = caller.first.split(':in', 2).first
17
- ALL << DSL.run(file, block)
19
+ ALL << DSL.call(file, type, block)
18
20
  end
19
21
 
20
- Pathname.glob(Pathname.new(__FILE__).parent.parent.parent.join('meta', '**/*.rb'))
22
+ Pathname.glob(Pathname.new(__dir__).parent.parent.join('meta', '*.rb'))
21
23
  .sort
22
24
  .each(&method(:require))
25
+
23
26
  ALL.freeze
24
27
 
25
- end # Example
28
+ # Remove mutation method only present for DSL executions from meta/**/*.rb
29
+ class << self
30
+ undef_method :add
31
+ end
26
32
 
33
+ end # Example
27
34
  end # Meta
28
35
  end # Mutant
@@ -1,7 +1,7 @@
1
1
  module Mutant
2
2
  module Meta
3
3
  class Example
4
- include Adamantium, Concord::Public.new(:file, :node, :mutations)
4
+ include Adamantium, Anima.new(:file, :node, :node_type, :expected)
5
5
 
6
6
  # Verification instance for example
7
7
  #
@@ -22,126 +22,12 @@ module Mutant
22
22
  #
23
23
  # @return [Enumerable<Mutant::Mutation>]
24
24
  def generated
25
- Mutator.each(node).map do |node|
25
+ Mutator.mutate(node).map do |node|
26
26
  Mutation::Evil.new(self, node)
27
27
  end
28
28
  end
29
29
  memoize :generated
30
30
 
31
- # Example verification
32
- class Verification
33
- include Adamantium::Flat, Concord.new(:example, :mutations)
34
-
35
- # Test if mutation was verified successfully
36
- #
37
- # @return [Boolean]
38
- def success?
39
- unparser.success? && missing.empty? && unexpected.empty? && no_diffs.empty?
40
- end
41
-
42
- # Error report
43
- #
44
- # @return [String]
45
- def error_report
46
- unless unparser.success?
47
- return unparser.report
48
- end
49
- mutation_report
50
- end
51
-
52
- private
53
-
54
- # Unexpected mutations
55
- #
56
- # @return [Array<Parser::AST::Node>]
57
- def unexpected
58
- mutations.map(&:node) - example.mutations
59
- end
60
- memoize :unexpected
61
-
62
- # Mutations with no diff to original
63
- #
64
- # @return [Enumerable<Mutation>]
65
- def no_diffs
66
- mutations.select { |mutation| mutation.source.eql?(example.source) }
67
- end
68
- memoize :no_diffs
69
-
70
- # Mutation report
71
- #
72
- # @return [String]
73
- def mutation_report
74
- original_node = example.node
75
- [
76
- "#{example.file}, Original-AST:",
77
- original_node.inspect,
78
- 'Original-Source:',
79
- example.source,
80
- *missing_report,
81
- *unexpected_report,
82
- *no_diff_report
83
- ].join("\n======\n")
84
- end
85
-
86
- # Missing mutation report
87
- #
88
- # @return [Array, nil]
89
- def missing_report
90
- [
91
- 'Missing mutations:',
92
- missing.map(&method(:format_mutation)).join("\n-----\n")
93
- ] if missing.any?
94
- end
95
-
96
- # No diff mutation report
97
- #
98
- # @return [Array, nil]
99
- def no_diff_report
100
- [
101
- 'No source diffs to original:',
102
- no_diffs.map do |mutation|
103
- "#{mutation.node.inspect}\n#{mutation.source}"
104
- end
105
- ] if no_diffs.any?
106
- end
107
-
108
- # Unexpected mutation report
109
- #
110
- # @return [Array, nil]
111
- def unexpected_report
112
- [
113
- 'Unexpected mutations:',
114
- unexpected.map(&method(:format_mutation)).join("\n-----\n")
115
- ] if unexpected.any?
116
- end
117
-
118
- # Format mutation
119
- #
120
- # @return [String]
121
- def format_mutation(node)
122
- [
123
- node.inspect,
124
- Unparser.unparse(node)
125
- ].join("\n")
126
- end
127
-
128
- # Missing mutations
129
- #
130
- # @return [Array<Parser::AST::Node>]
131
- def missing
132
- example.mutations - mutations.map(&:node)
133
- end
134
- memoize :missing
135
-
136
- # Unparser verifier
137
- #
138
- # @return [Unparser::CLI::Source]
139
- def unparser
140
- Unparser::CLI::Source::Node.new(Unparser::Preprocessor.run(example.node))
141
- end
142
- memoize :unparser
143
-
144
- end # Verification
145
31
  end # Example
146
32
  end # Meta
147
33
  end # Mutant
@@ -1,7 +1,6 @@
1
1
  module Mutant
2
2
  module Meta
3
3
  class Example
4
-
5
4
  # Example DSL
6
5
  class DSL
7
6
  include AST::Sexp
@@ -9,18 +8,21 @@ module Mutant
9
8
  # Run DSL on block
10
9
  #
11
10
  # @return [Example]
12
- def self.run(file, block)
13
- instance = new(file)
11
+ def self.call(file, type, block)
12
+ instance = new(file, type)
14
13
  instance.instance_eval(&block)
15
14
  instance.example
16
15
  end
17
16
 
18
- # Initialize DSL context
17
+ private_class_method :new
18
+
19
+ # Initialize object
19
20
  #
20
21
  # @return [undefined]
21
- def initialize(file)
22
- @file = file
23
- @source = nil
22
+ def initialize(file, type)
23
+ @file = file
24
+ @type = type
25
+ @node = nil
24
26
  @expected = []
25
27
  end
26
28
 
@@ -31,8 +33,13 @@ module Mutant
31
33
  # @raise [RuntimeError]
32
34
  # in case example cannot be build
33
35
  def example
34
- fail 'source not defined' unless @source
35
- Example.new(@file, @source, @expected)
36
+ fail 'source not defined' unless @node
37
+ Example.new(
38
+ file: @file,
39
+ node: @node,
40
+ node_type: @type,
41
+ expected: @expected
42
+ )
36
43
  end
37
44
 
38
45
  private
@@ -41,27 +48,23 @@ module Mutant
41
48
  #
42
49
  # @param [String,Parser::AST::Node] input
43
50
  #
44
- # @return [self]
51
+ # @return [undefined]
45
52
  def source(input)
46
- fail 'source already defined' if @source
47
- @source = node(input)
48
-
49
- self
53
+ fail 'source already defined' if @node
54
+ @node = node(input)
50
55
  end
51
56
 
52
57
  # Add expected mutation
53
58
  #
54
59
  # @param [String,Parser::AST::Node] input
55
60
  #
56
- # @return [self]
61
+ # @return [undefined]
57
62
  def mutation(input)
58
63
  node = node(input)
59
64
  if @expected.include?(node)
60
- fail "Node for input: #{input.inspect} is already expected"
65
+ fail "Mutation for input: #{input.inspect} is already expected"
61
66
  end
62
67
  @expected << node
63
-
64
- self
65
68
  end
66
69
 
67
70
  # Add singleton mutations
@@ -87,7 +90,7 @@ module Mutant
87
90
  when ::Parser::AST::Node
88
91
  input
89
92
  else
90
- fail "Cannot coerce to node: #{source.inspect}"
93
+ fail "Cannot coerce to node: #{input.inspect}"
91
94
  end
92
95
  end
93
96
 
@@ -0,0 +1,86 @@
1
+ module Mutant
2
+ module Meta
3
+ class Example
4
+ # Example verification
5
+ class Verification
6
+ include Adamantium::Flat, Concord.new(:example, :mutations)
7
+
8
+ # Test if mutation was verified successfully
9
+ #
10
+ # @return [Boolean]
11
+ def success?
12
+ missing.empty? && unexpected.empty? && no_diffs.empty?
13
+ end
14
+
15
+ # Error report
16
+ #
17
+ # @return [String]
18
+ def error_report
19
+ fail 'no error report on successful validation' if success?
20
+
21
+ YAML.dump(
22
+ 'file' => example.file,
23
+ 'original_ast' => example.node.inspect,
24
+ 'original_source' => example.source,
25
+ 'missing' => format_mutations(missing),
26
+ 'unexpected' => format_mutations(unexpected),
27
+ 'no_diff' => no_diff_report
28
+ )
29
+ end
30
+
31
+ private
32
+
33
+ # Unexpected mutations
34
+ #
35
+ # @return [Array<Parser::AST::Node>]
36
+ def unexpected
37
+ mutations.map(&:node) - example.expected
38
+ end
39
+ memoize :unexpected
40
+
41
+ # Mutations with no diff to original
42
+ #
43
+ # @return [Enumerable<Mutation>]
44
+ def no_diffs
45
+ mutations.select { |mutation| mutation.source.eql?(example.source) }
46
+ end
47
+ memoize :no_diffs
48
+
49
+ # Mutation report
50
+ #
51
+ # @param [Array<Parser::AST::Node>] nodes
52
+ #
53
+ # @return [Array<Hash>]
54
+ def format_mutations(nodes)
55
+ nodes.map do |node|
56
+ {
57
+ 'node' => node.inspect,
58
+ 'source' => Unparser.unparse(node)
59
+ }
60
+ end
61
+ end
62
+
63
+ # No diff mutation report
64
+ #
65
+ # @return [Array, nil]
66
+ def no_diff_report
67
+ no_diffs.map do |mutation|
68
+ {
69
+ 'node' => mutation.node.inspect,
70
+ 'source' => mutation.source
71
+ }
72
+ end
73
+ end
74
+
75
+ # Missing mutations
76
+ #
77
+ # @return [Array<Parser::AST::Node>]
78
+ def missing
79
+ example.expected - mutations.map(&:node)
80
+ end
81
+ memoize :missing
82
+
83
+ end # Verification
84
+ end # Example
85
+ end # Meta
86
+ end # Mutant
@@ -1,21 +1,19 @@
1
1
  module Mutant
2
2
  # Generator for mutations
3
3
  class Mutator
4
- include Adamantium::Flat, AbstractType
5
4
 
6
- # Run mutator on input
7
- #
8
- # @param [Object] input
9
- # the input to mutate
5
+ REGISTRY = Registry.new
6
+
7
+ include Adamantium::Flat, Concord.new(:input, :parent), AbstractType, Procto.call(:output)
8
+
9
+ # Lookup and invoke dedicated AST mutator
10
10
  #
11
- # @param [Mutator] parent
11
+ # @param node [Parser::AST::Node]
12
+ # @param parent [nil,Mutant::Mutator::Node]
12
13
  #
13
- # @return [self]
14
- def self.each(input, parent = nil, &block)
15
- return to_enum(__method__, input, parent) unless block_given?
16
- REGISTRY.lookup(input).new(input, parent, block)
17
-
18
- self
14
+ # @return [Set<Parser::AST::Node>]
15
+ def self.mutate(node, parent = nil)
16
+ self::REGISTRY.lookup(node.type).call(node, parent)
19
17
  end
20
18
 
21
19
  # Register node class handler
@@ -23,20 +21,15 @@ module Mutant
23
21
  # @return [undefined]
24
22
  def self.handle(*types)
25
23
  types.each do |type|
26
- REGISTRY.register(type, self)
24
+ self::REGISTRY.register(type, self)
27
25
  end
28
26
  end
29
27
  private_class_method :handle
30
28
 
31
- # Mutation input
29
+ # Return output
32
30
  #
33
- # @return [Object]
34
- attr_reader :input
35
-
36
- # Parent context of input
37
- #
38
- # @return [Object]
39
- attr_reader :parent
31
+ # @return [Set<Parser::AST::Node>]
32
+ attr_reader :output
40
33
 
41
34
  private
42
35
 
@@ -47,10 +40,11 @@ module Mutant
47
40
  # @param [#call(node)] block
48
41
  #
49
42
  # @return [undefined]
50
- def initialize(input, parent, block)
51
- @input, @parent, @block = input, parent, block
52
- @seen = Set.new
53
- guard(input)
43
+ def initialize(_input, _parent = nil)
44
+ super
45
+
46
+ @output = Set.new
47
+
54
48
  dispatch
55
49
  end
56
50
 
@@ -60,16 +54,7 @@ module Mutant
60
54
  #
61
55
  # @return [Boolean]
62
56
  def new?(object)
63
- !@seen.include?(object)
64
- end
65
-
66
- # Add object to guarded values
67
- #
68
- # @param [Object] object
69
- #
70
- # @return [undefined]
71
- def guard(object)
72
- @seen << object
57
+ !object.eql?(input)
73
58
  end
74
59
 
75
60
  # Dispatch node generations
@@ -85,26 +70,14 @@ module Mutant
85
70
  def emit(object)
86
71
  return unless new?(object)
87
72
 
88
- guard(object)
89
-
90
- emit!(object)
91
- end
92
-
93
- # Call block with node
94
- #
95
- # @param [Parser::AST::Node] node
96
- #
97
- # @return [self]
98
- def emit!(node)
99
- @block.call(node)
100
- self
73
+ output << object
101
74
  end
102
75
 
103
76
  # Run input with mutator
104
77
  #
105
78
  # @return [undefined]
106
79
  def run(mutator)
107
- mutator.new(input, self, method(:emit))
80
+ mutator.call(input).each(&method(:emit))
108
81
  end
109
82
 
110
83
  # Shortcut to create a new unfrozen duplicate of input