mutant 0.1.1 → 0.2.0

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 (225) hide show
  1. data/.gitignore +5 -11
  2. data/.rspec +0 -1
  3. data/.travis.yml +14 -3
  4. data/Changelog.md +3 -0
  5. data/Gemfile +5 -1
  6. data/Gemfile.devtools +49 -0
  7. data/Guardfile +18 -0
  8. data/README.md +67 -0
  9. data/Rakefile +4 -1
  10. data/TODO +13 -0
  11. data/bin/mutant +14 -0
  12. data/bin/zombie +14 -0
  13. data/config/flay.yml +3 -0
  14. data/config/flog.yml +2 -0
  15. data/config/roodi.yml +26 -0
  16. data/config/site.reek +93 -0
  17. data/config/yardstick.yml +2 -0
  18. data/lib/inflector.rb +7 -0
  19. data/lib/inflector/defaults.rb +62 -0
  20. data/lib/inflector/inflections.rb +209 -0
  21. data/lib/inflector/methods.rb +149 -0
  22. data/lib/inflector/version.rb +3 -0
  23. data/lib/mutant.rb +96 -21
  24. data/lib/mutant/cli.rb +309 -0
  25. data/lib/mutant/color.rb +61 -0
  26. data/lib/mutant/context.rb +36 -0
  27. data/lib/mutant/context/scope.rb +138 -0
  28. data/lib/mutant/differ.rb +100 -0
  29. data/lib/mutant/helper.rb +38 -0
  30. data/lib/mutant/killer.rb +136 -0
  31. data/lib/mutant/killer/forking.rb +41 -0
  32. data/lib/mutant/killer/rspec.rb +49 -0
  33. data/lib/mutant/killer/static.rb +19 -0
  34. data/lib/mutant/loader.rb +129 -0
  35. data/lib/mutant/matcher.rb +55 -0
  36. data/lib/mutant/matcher/chain.rb +66 -0
  37. data/lib/mutant/matcher/method.rb +173 -0
  38. data/lib/mutant/matcher/method/classifier.rb +126 -0
  39. data/lib/mutant/matcher/method/instance.rb +67 -0
  40. data/lib/mutant/matcher/method/singleton.rb +141 -0
  41. data/lib/mutant/matcher/object_space.rb +114 -0
  42. data/lib/mutant/matcher/scope_methods.rb +127 -0
  43. data/lib/mutant/mutation.rb +101 -12
  44. data/lib/mutant/mutation/filter.rb +75 -0
  45. data/lib/mutant/mutation/filter/code.rb +68 -0
  46. data/lib/mutant/mutation/filter/regexp.rb +39 -0
  47. data/lib/mutant/mutation/filter/whitelist.rb +47 -0
  48. data/lib/mutant/mutator.rb +134 -30
  49. data/lib/mutant/mutator/node.rb +163 -0
  50. data/lib/mutant/mutator/node/arguments.rb +24 -0
  51. data/lib/mutant/mutator/node/block.rb +24 -0
  52. data/lib/mutant/mutator/node/define.rb +24 -0
  53. data/lib/mutant/mutator/node/if_statement.rb +93 -0
  54. data/lib/mutant/mutator/node/literal.rb +54 -0
  55. data/lib/mutant/mutator/node/literal/array.rb +28 -0
  56. data/lib/mutant/mutator/node/literal/boolean.rb +49 -0
  57. data/lib/mutant/mutator/node/literal/dynamic.rb +24 -0
  58. data/lib/mutant/mutator/node/literal/empty_array.rb +26 -0
  59. data/lib/mutant/mutator/node/literal/fixnum.rb +37 -0
  60. data/lib/mutant/mutator/node/literal/float.rb +48 -0
  61. data/lib/mutant/mutator/node/literal/hash.rb +89 -0
  62. data/lib/mutant/mutator/node/literal/nil.rb +25 -0
  63. data/lib/mutant/mutator/node/literal/range.rb +94 -0
  64. data/lib/mutant/mutator/node/literal/regex.rb +43 -0
  65. data/lib/mutant/mutator/node/literal/string.rb +26 -0
  66. data/lib/mutant/mutator/node/literal/symbol.rb +26 -0
  67. data/lib/mutant/mutator/node/noop.rb +55 -0
  68. data/lib/mutant/mutator/node/receiver_case.rb +140 -0
  69. data/lib/mutant/mutator/node/return.rb +31 -0
  70. data/lib/mutant/mutator/node/send.rb +112 -0
  71. data/lib/mutant/mutator/registry.rb +48 -0
  72. data/lib/mutant/mutator/util.rb +87 -0
  73. data/lib/mutant/random.rb +24 -27
  74. data/lib/mutant/reporter.rb +48 -30
  75. data/lib/mutant/reporter/cli.rb +221 -0
  76. data/lib/mutant/reporter/null.rb +42 -0
  77. data/lib/mutant/reporter/stats.rb +64 -0
  78. data/lib/mutant/runner.rb +112 -0
  79. data/lib/mutant/strategy.rb +42 -0
  80. data/lib/mutant/strategy/rspec.rb +59 -0
  81. data/lib/mutant/strategy/rspec/example_lookup.rb +122 -0
  82. data/lib/mutant/subject.rb +115 -0
  83. data/lib/mutant/support/method_object.rb +31 -0
  84. data/locator.rb +87 -0
  85. data/mutant.gemspec +21 -21
  86. data/spec/integration/mutant/differ_spec.rb +15 -0
  87. data/spec/integration/mutant/loader_spec.rb +21 -0
  88. data/spec/integration/mutant/method_matching_spec.rb +269 -0
  89. data/spec/integration/mutant/rspec_killer_spec.rb +24 -0
  90. data/spec/integration/mutant/runner_spec.rb +26 -0
  91. data/spec/integration/mutant/zombie_spec.rb +8 -0
  92. data/spec/rcov.opts +7 -0
  93. data/spec/shared/command_method_behavior.rb +7 -0
  94. data/spec/shared/each_method_behaviour.rb +15 -0
  95. data/spec/shared/hash_method_behavior.rb +17 -0
  96. data/spec/shared/idempotent_method_behavior.rb +7 -0
  97. data/spec/shared/invertible_method_behaviour.rb +9 -0
  98. data/spec/shared/method_filter_parse_behavior.rb +16 -0
  99. data/spec/shared/method_match_behavior.rb +39 -0
  100. data/spec/shared/mutator_behavior.rb +46 -0
  101. data/spec/spec_helper.rb +11 -14
  102. data/spec/support/compress_helper.rb +10 -0
  103. data/spec/support/rspec.rb +22 -0
  104. data/spec/support/test_app.rb +5 -0
  105. data/spec/support/zombie.rb +141 -0
  106. data/spec/unit/mutant/cli/class_methods/new_spec.rb +87 -0
  107. data/spec/unit/mutant/cli/class_methods/run_spec.rb +38 -0
  108. data/spec/unit/mutant/context/root_spec.rb +11 -0
  109. data/spec/unit/mutant/context/scope/class_methods/build_spec.rb +29 -0
  110. data/spec/unit/mutant/context/scope/root_spec.rb +22 -0
  111. data/spec/unit/mutant/context/scope/unqualified_name_spec.rb +27 -0
  112. data/spec/unit/mutant/killer/fail_ques_spec.rb +39 -0
  113. data/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +32 -0
  114. data/spec/unit/mutant/loader/eval/class_methods/run_spec.rb +33 -0
  115. data/spec/unit/mutant/loader/rubinius/class_methods/run_spec.rb +42 -0
  116. data/spec/unit/mutant/matcher/chain/each_spec.rb +37 -0
  117. data/spec/unit/mutant/matcher/chain/matchers_spec.rb +12 -0
  118. data/spec/unit/mutant/matcher/class_methods/from_string_spec.rb +49 -0
  119. data/spec/unit/mutant/matcher/class_methods/parse_spec.rb +12 -0
  120. data/spec/unit/mutant/matcher/each_spec.rb +14 -0
  121. data/spec/unit/mutant/matcher/method/class_methods/parse_spec.rb +21 -0
  122. data/spec/unit/mutant/matcher/method/classifier/class_methods/run_spec.rb +34 -0
  123. data/spec/unit/mutant/matcher/method/method_spec.rb +11 -0
  124. data/spec/unit/mutant/matcher/object_space/class_methods/parse_spec.rb +24 -0
  125. data/spec/unit/mutant/matcher/object_space/each_spec.rb +31 -0
  126. data/spec/unit/mutant/mutator/each_spec.rb +25 -0
  127. data/spec/unit/mutant/mutator/emit_new_spec.rb +51 -0
  128. data/spec/unit/mutant/mutator/emit_spec.rb +52 -0
  129. data/spec/unit/mutant/mutator/node/block/mutation_spec.rb +36 -0
  130. data/spec/unit/mutant/mutator/node/define/mutation_spec.rb +47 -0
  131. data/spec/unit/mutant/mutator/node/if_statement/mutation_spec.rb +30 -0
  132. data/spec/unit/mutant/mutator/node/literal/array_spec.rb +30 -0
  133. data/spec/unit/mutant/mutator/node/literal/boolean/mutation_spec.rb +23 -0
  134. data/spec/unit/mutant/mutator/node/literal/empty_array_spec.rb +17 -0
  135. data/spec/unit/mutant/mutator/node/literal/fixnum_spec.rb +17 -0
  136. data/spec/unit/mutant/mutator/node/literal/float_spec.rb +25 -0
  137. data/spec/unit/mutant/mutator/node/literal/hash_spec.rb +34 -0
  138. data/spec/unit/mutant/mutator/node/literal/nil_spec.rb +13 -0
  139. data/spec/unit/mutant/mutator/node/literal/range_spec.rb +35 -0
  140. data/spec/unit/mutant/mutator/node/literal/regex_spec.rb +23 -0
  141. data/spec/unit/mutant/mutator/node/literal/string_spec.rb +17 -0
  142. data/spec/unit/mutant/mutator/node/literal/symbol_spec.rb +17 -0
  143. data/spec/unit/mutant/mutator/node/receiver_case/mutation_spec.rb +27 -0
  144. data/spec/unit/mutant/mutator/node/return/mutation_spec.rb +21 -0
  145. data/spec/unit/mutant/mutator/node/send/mutation_spec.rb +78 -0
  146. data/spec/unit/mutant/mutator/self_spec.rb +7 -0
  147. data/spec/unit/mutant/subject/class_methods/new_spec.rb +13 -0
  148. data/spec/unit/mutant/subject/context_spec.rb +14 -0
  149. data/spec/unit/mutant/subject/each_spec.rb +35 -0
  150. data/spec/unit/mutant/subject/node_spec.rb +13 -0
  151. data/tasks/metrics/ci.rake +7 -0
  152. data/tasks/metrics/flay.rake +41 -0
  153. data/tasks/metrics/flog.rake +43 -0
  154. data/tasks/metrics/heckle.rake +216 -0
  155. data/tasks/metrics/metric_fu.rake +31 -0
  156. data/tasks/metrics/reek.rake +15 -0
  157. data/tasks/metrics/roodi.rake +15 -0
  158. data/tasks/metrics/yardstick.rake +23 -0
  159. data/tasks/spec.rake +45 -0
  160. data/tasks/yard.rake +9 -0
  161. data/test_app/.rspec +1 -0
  162. data/test_app/lib/test_app.rb +5 -0
  163. data/test_app/lib/test_app/literal.rb +32 -0
  164. data/test_app/spec/shared/command_method_behavior.rb +7 -0
  165. data/test_app/spec/shared/each_method_behaviour.rb +15 -0
  166. data/test_app/spec/shared/hash_method_behavior.rb +17 -0
  167. data/test_app/spec/shared/idempotent_method_behavior.rb +7 -0
  168. data/test_app/spec/shared/invertible_method_behaviour.rb +9 -0
  169. data/test_app/spec/shared/method_filter_parse_behavior.rb +16 -0
  170. data/test_app/spec/shared/method_match_behavior.rb +39 -0
  171. data/test_app/spec/shared/mutator_behavior.rb +44 -0
  172. data/test_app/spec/spec_helper.rb +7 -0
  173. data/test_app/spec/unit/test_app/literal/command_spec.rb +9 -0
  174. data/test_app/spec/unit/test_app/literal/string_spec.rb +9 -0
  175. metadata +346 -124
  176. data/.rvmrc +0 -1
  177. data/Readme.md +0 -13
  178. data/exe/mutate +0 -6
  179. data/lib/mutant/extensions.rb +0 -8
  180. data/lib/mutant/formatter.rb +0 -19
  181. data/lib/mutant/implementation.rb +0 -70
  182. data/lib/mutant/literal.rb +0 -147
  183. data/lib/mutant/method.rb +0 -31
  184. data/lib/mutant/mutatee.rb +0 -61
  185. data/lib/mutant/node.rb +0 -26
  186. data/lib/mutant/runners/rspec.rb +0 -34
  187. data/lib/mutant/version.rb +0 -3
  188. data/spec/functional/class_spec.rb +0 -46
  189. data/spec/functional/instance_method/array_spec.rb +0 -53
  190. data/spec/functional/instance_method/boolean_spec.rb +0 -101
  191. data/spec/functional/instance_method/call_spec.rb +0 -161
  192. data/spec/functional/instance_method/fixnum_spec.rb +0 -53
  193. data/spec/functional/instance_method/float_spec.rb +0 -53
  194. data/spec/functional/instance_method/hash_spec.rb +0 -53
  195. data/spec/functional/instance_method/if_spec.rb +0 -57
  196. data/spec/functional/instance_method/ivar_assign_spec.rb +0 -62
  197. data/spec/functional/instance_method/range_spec.rb +0 -53
  198. data/spec/functional/instance_method/regex_spec.rb +0 -55
  199. data/spec/functional/instance_method/string_spec.rb +0 -53
  200. data/spec/functional/instance_method/symbol_spec.rb +0 -53
  201. data/spec/functional/reporter/method_loaded_spec.rb +0 -62
  202. data/spec/functional/reporter/running_mutations_spec.rb +0 -60
  203. data/spec/functional/runners/rspec_spec.rb +0 -26
  204. data/spec/functional/singleton_method/array_spec.rb +0 -53
  205. data/spec/functional/singleton_method/boolean_spec.rb +0 -101
  206. data/spec/functional/singleton_method/call_spec.rb +0 -161
  207. data/spec/functional/singleton_method/fixnum_spec.rb +0 -53
  208. data/spec/functional/singleton_method/float_spec.rb +0 -53
  209. data/spec/functional/singleton_method/hash_spec.rb +0 -53
  210. data/spec/functional/singleton_method/if_spec.rb +0 -57
  211. data/spec/functional/singleton_method/ivar_assign_spec.rb +0 -60
  212. data/spec/functional/singleton_method/range_spec.rb +0 -53
  213. data/spec/functional/singleton_method/regex_spec.rb +0 -55
  214. data/spec/functional/singleton_method/string_spec.rb +0 -53
  215. data/spec/functional/singleton_method/symbol_spec.rb +0 -53
  216. data/spec/mutant/extensions_spec.rb +0 -13
  217. data/spec/mutant/implementation_spec.rb +0 -223
  218. data/spec/mutant/literal_spec.rb +0 -129
  219. data/spec/mutant/mutatee_spec.rb +0 -28
  220. data/spec/mutant/node_spec.rb +0 -41
  221. data/spec/mutant/random_spec.rb +0 -33
  222. data/spec/mutant/reporter_spec.rb +0 -17
  223. data/spec/mutant_spec.rb +0 -28
  224. data/spec/support/example_group_helpers.rb +0 -11
  225. data/spec/support/example_helpers.rb +0 -5
@@ -0,0 +1,112 @@
1
+ module Mutant
2
+ class Mutator
3
+ class Node
4
+ # Class for mutations where messages are send to objects
5
+ class Send < self
6
+
7
+ handle(Rubinius::AST::Send)
8
+
9
+ private
10
+
11
+ # Return receiver AST node
12
+ #
13
+ # @return [Rubinius::AST::Node]
14
+ #
15
+ # @api private
16
+ #
17
+ def receiver
18
+ node.receiver
19
+ end
20
+
21
+ # Return name of call
22
+ #
23
+ # @return [Symbol]
24
+ #
25
+ # @api private
26
+ #
27
+ def name
28
+ node.name
29
+ end
30
+
31
+ # Check if receiver is self
32
+ #
33
+ # @return [true]
34
+ # returns true when receiver is a Rubinius::AST::Self node
35
+ #
36
+ # @return [false]
37
+ # return false otherwise
38
+ #
39
+ # @api private
40
+ #
41
+ def self?
42
+ receiver.kind_of?(Rubinius::AST::Self)
43
+ end
44
+
45
+ # Emit mutation that replaces explicit send to self with implicit send to self
46
+ #
47
+ # @example:
48
+ #
49
+ # # This class does use Foo#a with explicitly specifing the receiver self.
50
+ # # But an implicit (privately) call should be used as there is no need for
51
+ # # specifing en explicit receiver.
52
+ #
53
+ # class Foo # Mutation
54
+ # def print_a # def print_a
55
+ # puts self.a # puts a
56
+ # end # end
57
+ #
58
+ # def a
59
+ # :bar
60
+ # end
61
+ # end
62
+ #
63
+ # There will not be any exception so the mutant is not killed and such calls where
64
+ # implicit receiver should be used will be spotted.
65
+ #
66
+ # @return [undefined]
67
+ #
68
+ # @api private
69
+ #
70
+ def emit_implicit_self_receiver
71
+ # FIXME: Edge case that is currently not very well undestood
72
+ return if name == :block_given?
73
+ return unless self?
74
+ mutant = dup_node
75
+ mutant.privately = true
76
+ # TODO: Fix rubinius to allow this as an attr_accessor
77
+ mutant.instance_variable_set(:@vcall_style, true)
78
+ emit(mutant)
79
+ end
80
+
81
+ # Emit mutations
82
+ #
83
+ # @return [undefined]
84
+ #
85
+ # @api private
86
+ #
87
+ def dispatch
88
+ emit_implicit_self_receiver
89
+ end
90
+
91
+ class SendWithArguments < self
92
+
93
+ handle(Rubinius::AST::SendWithArguments)
94
+
95
+ private
96
+
97
+ # Emit mutations
98
+ #
99
+ # @return [undefined]
100
+ #
101
+ # @api private
102
+ #
103
+ def dispatch
104
+ super
105
+ emit_attribute_mutations(:arguments)
106
+ end
107
+
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,48 @@
1
+ module Mutant
2
+ class Mutator
3
+ # Registry for mutators
4
+ module Registry
5
+ # Return registry state
6
+ #
7
+ # @return [Hash]
8
+ #
9
+ # @api private
10
+ #
11
+ def self.registry
12
+ @registry ||= {}
13
+ end
14
+ private_class_method :registry
15
+
16
+ # Register mutator class for AST node class
17
+ #
18
+ # @param [Class] ast_class
19
+ # @param [Class] mutator_class
20
+ #
21
+ # @api private
22
+ #
23
+ # @return [self]
24
+ #
25
+ def self.register(ast_class,mutator_class)
26
+ registry[ast_class]=mutator_class
27
+ self
28
+ end
29
+
30
+ # Lookup mutator class for AST node class
31
+ #
32
+ # @param [Class] ast_class
33
+ #
34
+ # @return [Class]
35
+ #
36
+ # @raise [ArgumentError]
37
+ # raises argument error when mutator class cannot be found
38
+ #
39
+ # @api private
40
+ #
41
+ def self.lookup(ast_class)
42
+ registry.fetch(ast_class) do
43
+ raise ArgumentError,"No mutator to handle: #{ast_class.inspect}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,87 @@
1
+ module Mutant
2
+ class Mutator
3
+ # Namespace for utility mutators
4
+ class Util < self
5
+
6
+ # Run ulitity mutator
7
+ #
8
+ # @param [Object]
9
+ #
10
+ # @api private
11
+ #
12
+ def self.each(object, &block)
13
+ return to_enum(__method__, object) unless block_given?
14
+
15
+ new(object, block)
16
+
17
+ self
18
+ end
19
+
20
+ # Test if mutation is new
21
+ #
22
+ # @param [Object] generated
23
+ #
24
+ # @return [true]
25
+ # if object is new
26
+ #
27
+ # @return [false]
28
+ # otherwise
29
+ #
30
+ def new?(generated)
31
+ input != generated
32
+ end
33
+
34
+ # Mutators that mutates an array of inputs
35
+ class Array < self
36
+
37
+ handle(::Array)
38
+
39
+ private
40
+
41
+ # Emit mutations
42
+ #
43
+ # @return [undefined]
44
+ #
45
+ # @api private
46
+ #
47
+ def dispatch
48
+ emit_element_presence
49
+ emit_element_mutations
50
+ emit([])
51
+ end
52
+
53
+ # Emit element mutations
54
+ #
55
+ # @return [undefined]
56
+ #
57
+ # @api private
58
+ #
59
+ def emit_element_mutations
60
+ input.each_with_index do |element, index|
61
+ dup = dup_input
62
+
63
+ Mutator.each(element).each do |mutation|
64
+ dup[index]=mutation
65
+ emit(dup)
66
+ end
67
+ end
68
+ end
69
+
70
+ # Emit element presence mutations
71
+ #
72
+ # @return [undefined]
73
+ #
74
+ # @api private
75
+ #
76
+ def emit_element_presence
77
+ input.each_index do |index|
78
+ dup = dup_input
79
+ dup.delete_at(index)
80
+ emit(dup)
81
+ end
82
+ end
83
+
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,37 +1,34 @@
1
1
  module Mutant
2
- class Random
3
- ALLOWED_SYMBOL_CHARACTERS = Array('a'..'z') + Array('A'..'Z')
4
- RANDOM_METHOD = Rubinius.ruby19? ? :sample : :choice
5
-
6
- def self.string
7
- ENV.fetch('RANDOM_STRING') {
8
- Array.new(rand(50)) { rand(126).chr }.join
9
- }
10
- end
11
-
12
- def self.symbol
13
- ENV.fetch('RANDOM_SYMBOL') {
14
- Array.new(rand(50).next) { ALLOWED_SYMBOL_CHARACTERS.send RANDOM_METHOD }.join
15
- }.to_sym
2
+ # Module for generating random values
3
+ module Random
4
+ # Return random hex string
5
+ #
6
+ # @return [String]
7
+ #
8
+ # @api private
9
+ #
10
+ def self.hex_string
11
+ SecureRandom.hex(10)
16
12
  end
17
13
 
14
+ # Return random fixnum
15
+ #
16
+ # @return [Fixnum]
17
+ #
18
+ # @api private
19
+ #
18
20
  def self.fixnum
19
- ENV.fetch('RANDOM_FIXNUM') { rand(100) }
21
+ ::Random.rand(1000)
20
22
  end
21
23
 
24
+ # Return random float
25
+ #
26
+ # @return [Float]
27
+ #
28
+ # @api private
29
+ #
22
30
  def self.float
23
- ENV.fetch('RANDOM_FLOAT') { (rand(100) + 0.5) }
24
- end
25
-
26
- def self.range
27
- if ENV['RANDOM_RANGE_MIN'] and ENV['RANDOM_RANGE_MAX']
28
- min, max = ENV['RANDOM_RANGE_MIN'].to_i, ENV['RANDOM_RANGE_MAX'].to_i
29
- else
30
- min, max = rand(50)
31
- max = min + rand(50)
32
- end
33
-
34
- Range.new(min, max)
31
+ ::Random.rand
35
32
  end
36
33
  end
37
34
  end
@@ -1,38 +1,56 @@
1
1
  module Mutant
2
+ # Abstract reporter
2
3
  class Reporter
3
- WIDTH = 70
4
+ include Adamantium::Flat, AbstractType
4
5
 
5
- def self.mutating(mutation)
6
- puts "Mutating line #{mutation.line}"
7
- puts " #{mutation.from} >>>#{mutation.to.nested? ? "\n " : " "}#{mutation.to}"
8
- puts
9
- end
6
+ # Report subject
7
+ #
8
+ # @param [Subject] subject
9
+ #
10
+ # @return [self]
11
+ #
12
+ # @api private
13
+ #
14
+ abstract_method :subject
10
15
 
11
- def self.method_loaded(mutatee)
12
- info "#{mutatee} loaded with #{mutatee.mutations.size} " \
13
- "possible #{pluralize(mutatee.mutations.size, 'mutation')}"
14
- end
16
+ # Report mutation
17
+ #
18
+ # @param [Mutation] mutation
19
+ #
20
+ # @return [self]
21
+ #
22
+ # @api private
23
+ #
24
+ abstract_method :mutation
15
25
 
16
- def self.no_mutations(mutatee)
17
- warning "#{mutatee} has no possible mutations"
18
- end
26
+ # Report notice
27
+ #
28
+ # @param [String] notice
29
+ #
30
+ # @return [self]
31
+ #
32
+ # @api private
33
+ #
34
+ abstract_method :notice
19
35
 
20
- def self.info(message)
21
- puts "*" * WIDTH
22
- puts "*** #{message}"
23
- puts "*" * WIDTH
24
- puts
25
- end
36
+ # Report killer
37
+ #
38
+ # @param [Killer] killer
39
+ #
40
+ # @return [self]
41
+ #
42
+ # @api private
43
+ #
44
+ abstract_method :killer
26
45
 
27
- def self.warning(message)
28
- puts "!" * WIDTH
29
- puts "!!! #{message}"
30
- puts "!" * WIDTH
31
- puts
32
- end
33
-
34
- def self.pluralize(count, singular, plural = nil)
35
- count == 1 ? singular : plural || singular.insert(-1, 's')
36
- end
46
+ # Report config
47
+ #
48
+ # @param [Mutant::Config] config
49
+ #
50
+ # @return [self]
51
+ #
52
+ # @api private
53
+ #
54
+ abstract_method :config
37
55
  end
38
- end
56
+ end
@@ -0,0 +1,221 @@
1
+ module Mutant
2
+ class Reporter
3
+ # Reporter that reports in human readable format
4
+ class CLI < self
5
+ include Equalizer.new(:io)
6
+
7
+ # Reporter subject
8
+ #
9
+ # @param [Subject] subject
10
+ #
11
+ # @return [self]
12
+ #
13
+ # @api private
14
+ #
15
+ def subject(subject)
16
+ stats.subject
17
+ puts("Subject: #{subject.identification}")
18
+ end
19
+
20
+ # Report mutation
21
+ #
22
+ # @param [Mutation] mutation
23
+ #
24
+ # @return [self]
25
+ #
26
+ # @api private
27
+ #
28
+ def mutation(mutation)
29
+ #colorized_diff(mutation.original_source, mutation.source)
30
+ self
31
+ end
32
+
33
+ # Report config
34
+ #
35
+ # @param [Mutant::Config] config
36
+ #
37
+ # @return [self]
38
+ #
39
+ # @api private
40
+ #
41
+ def config(config)
42
+ puts 'Mutant configuration:'
43
+ puts "Matcher: #{config.matcher.inspect}"
44
+ puts "Filter: #{config.filter.inspect}"
45
+ puts "Strategy: #{config.strategy.inspect}"
46
+ end
47
+
48
+ # Reporter killer
49
+ #
50
+ # @param [Killer] killer
51
+ #
52
+ # @return [self]
53
+ #
54
+ # @api private
55
+ #
56
+ def killer(killer)
57
+ stats.killer(killer)
58
+
59
+ color, word =
60
+ if killer.fail?
61
+ [Color::RED, 'Alive']
62
+ else
63
+ [Color::GREEN, 'Killed']
64
+ end
65
+
66
+ puts(colorize(color, "#{word}: #{killer.identification} (%02.2fs)" % killer.runtime))
67
+
68
+ if killer.fail?
69
+ mutation = killer.mutation
70
+ colorized_diff(mutation.original_source, mutation.source)
71
+ end
72
+
73
+ self
74
+ end
75
+
76
+ # Report errors
77
+ #
78
+ # @param [Enumerable<Killer>]
79
+ #
80
+ # @api private
81
+ #
82
+ # @return [self]
83
+ #
84
+ def errors(errors)
85
+ errors.each do |error|
86
+ failure(error)
87
+ end
88
+
89
+ puts
90
+ puts "subjects: #{stats.subject}"
91
+ puts "mutations: #{stats.mutation}"
92
+ puts "kills: #{stats.kill}"
93
+ puts "alive: #{stats.alive}"
94
+ puts "mtime: %02.2fs" % stats.time
95
+ puts "rtime: %02.2fs" % stats.runtime
96
+ end
97
+
98
+ # Return IO stream
99
+ #
100
+ # @return [IO]
101
+ #
102
+ # @api private
103
+ #
104
+ attr_reader :io
105
+
106
+ # Return stats
107
+ #
108
+ # @return [Stats]
109
+ #
110
+ # @api private
111
+ #
112
+ attr_reader :stats
113
+
114
+ private
115
+
116
+ # Initialize reporter
117
+ #
118
+ # @param [IO] io
119
+ #
120
+ # @return [undefined]
121
+ #
122
+ # @api private
123
+ #
124
+ def initialize(io)
125
+ @io = io
126
+ @stats = Stats.new
127
+ end
128
+
129
+ # Report failure on killer
130
+ #
131
+ # @param [Killer] killer
132
+ #
133
+ # @return [undefined]
134
+ #
135
+ # @api private
136
+ #
137
+ def failure(killer)
138
+ puts(colorize(Color::RED, "!!! Mutant alive: #{killer.identification} !!!"))
139
+ colorized_diff(killer.original_source, killer.mutation_source)
140
+ puts("Took: (%02.2fs)" % killer.runtime)
141
+ end
142
+
143
+ # Test for colored output
144
+ #
145
+ # @return [true]
146
+ # returns true if output is colored
147
+ #
148
+ # @return [false]
149
+ # returns false otherwise
150
+ #
151
+ # @api private
152
+ #
153
+ def color?
154
+ tty?
155
+ end
156
+
157
+ # Colorize message
158
+ #
159
+ # @param [Color] color
160
+ # @param [String] message
161
+ #
162
+ # @api private
163
+ #
164
+ # @return [String]
165
+ # returns colorized string if color is enabled
166
+ # returns unmodified message otherwise
167
+ #
168
+ def colorize(color, message)
169
+ color = Color::NONE unless color?
170
+ color.format(message)
171
+ end
172
+
173
+ # Write string to io
174
+ #
175
+ # @param [String] string
176
+ #
177
+ # @return [undefined]
178
+ #
179
+ # @api private
180
+ #
181
+ def puts(string="\n")
182
+ io.puts(string)
183
+ end
184
+
185
+ # Write colorized diff
186
+ #
187
+ # @param [String] original
188
+ # @param [String] current
189
+ #
190
+ # @return [self]
191
+ #
192
+ # @api private
193
+ #
194
+ def colorized_diff(original, current)
195
+ differ = Differ.new(original, current)
196
+ diff = color? ? differ.colorized_diff : differ.diff
197
+ # FIXME remove this branch before release
198
+ if diff.empty?
199
+ raise "Unable to create a diff, so ast mutation or to_source has an error!"
200
+ end
201
+ puts(diff)
202
+ self
203
+ end
204
+
205
+ # Test for output to tty
206
+ #
207
+ # @return [true]
208
+ # returns true if output is a tty
209
+ #
210
+ # @return [false]
211
+ # returns false otherwise
212
+ #
213
+ # @api private
214
+ #
215
+ def tty?
216
+ @io.respond_to?(:tty?) && @io.tty?
217
+ end
218
+ memoize :tty?
219
+ end
220
+ end
221
+ end