mutant 0.8.10 → 0.8.11

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