mutant 0.5.26 → 0.6.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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +1 -0
  4. data/Changelog.md +16 -3
  5. data/Gemfile +0 -2
  6. data/Gemfile.devtools +2 -2
  7. data/README.md +9 -15
  8. data/bin/mutant +0 -1
  9. data/config/flay.yml +1 -1
  10. data/config/flog.yml +1 -1
  11. data/config/mutant.yml +1 -1
  12. data/config/reek.yml +14 -11
  13. data/config/rubocop.yml +1 -1
  14. data/lib/mutant.rb +22 -21
  15. data/lib/mutant/ast.rb +47 -0
  16. data/lib/mutant/cli.rb +7 -4
  17. data/lib/mutant/config.rb +1 -0
  18. data/lib/mutant/context.rb +1 -1
  19. data/lib/mutant/diff.rb +38 -7
  20. data/lib/mutant/env.rb +22 -3
  21. data/lib/mutant/expression.rb +15 -4
  22. data/lib/mutant/integration.rb +1 -1
  23. data/lib/mutant/isolation.rb +2 -4
  24. data/lib/mutant/matcher.rb +1 -1
  25. data/lib/mutant/matcher/method.rb +1 -1
  26. data/lib/mutant/matcher/method/singleton.rb +1 -1
  27. data/lib/mutant/matcher/methods.rb +0 -2
  28. data/lib/mutant/meta/example.rb +0 -2
  29. data/lib/mutant/meta/example/dsl.rb +1 -1
  30. data/lib/mutant/mutator.rb +1 -1
  31. data/lib/mutant/mutator/node.rb +3 -3
  32. data/lib/mutant/mutator/node/begin.rb +1 -1
  33. data/lib/mutant/mutator/node/block.rb +16 -3
  34. data/lib/mutant/mutator/node/if.rb +1 -1
  35. data/lib/mutant/mutator/node/literal/fixnum.rb +1 -1
  36. data/lib/mutant/mutator/node/resbody.rb +0 -2
  37. data/lib/mutant/mutator/node/send.rb +17 -7
  38. data/lib/mutant/mutator/node/send/index.rb +0 -2
  39. data/lib/mutant/mutator/registry.rb +1 -1
  40. data/lib/mutant/mutator/util.rb +1 -1
  41. data/lib/mutant/mutator/util/array.rb +1 -1
  42. data/lib/mutant/reporter.rb +13 -3
  43. data/lib/mutant/reporter/cli.rb +54 -8
  44. data/lib/mutant/reporter/cli/format.rb +197 -0
  45. data/lib/mutant/reporter/cli/printer.rb +402 -22
  46. data/lib/mutant/reporter/cli/tput.rb +27 -0
  47. data/lib/mutant/reporter/null.rb +4 -34
  48. data/lib/mutant/reporter/trace.rb +6 -38
  49. data/lib/mutant/result.rb +44 -56
  50. data/lib/mutant/runner.rb +99 -52
  51. data/lib/mutant/runner/collector.rb +134 -0
  52. data/lib/mutant/subject/method/instance.rb +12 -4
  53. data/lib/mutant/version.rb +1 -1
  54. data/lib/mutant/warning_filter.rb +0 -2
  55. data/lib/mutant/zombifier/file.rb +1 -1
  56. data/meta/block.rb +17 -1
  57. data/meta/send.rb +123 -1
  58. data/mutant-rspec.gemspec +3 -3
  59. data/mutant.gemspec +1 -1
  60. data/spec/integration/mutant/corpus_spec.rb +4 -195
  61. data/spec/integration/mutant/null_spec.rb +1 -3
  62. data/spec/integration/mutant/rspec_spec.rb +1 -3
  63. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -3
  64. data/spec/integration/mutant/zombie_spec.rb +1 -3
  65. data/spec/integrations.yml +7 -0
  66. data/spec/shared/method_matcher_behavior.rb +1 -1
  67. data/spec/spec_helper.rb +1 -0
  68. data/spec/support/compress_helper.rb +1 -0
  69. data/spec/support/corpus.rb +239 -0
  70. data/spec/support/mutation_verifier.rb +2 -4
  71. data/spec/unit/mutant/cli_spec.rb +20 -13
  72. data/spec/unit/mutant/context/root_spec.rb +1 -3
  73. data/spec/unit/mutant/context/scope/root_spec.rb +1 -3
  74. data/spec/unit/mutant/context/scope/unqualified_name_spec.rb +1 -3
  75. data/spec/unit/mutant/diff_spec.rb +37 -19
  76. data/spec/unit/mutant/expression/method_spec.rb +5 -7
  77. data/spec/unit/mutant/expression/methods_spec.rb +5 -7
  78. data/spec/unit/mutant/expression/namespace/flat_spec.rb +6 -8
  79. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +6 -7
  80. data/spec/unit/mutant/expression_spec.rb +14 -5
  81. data/spec/unit/mutant/integration_spec.rb +14 -3
  82. data/spec/unit/mutant/isolation_spec.rb +2 -4
  83. data/spec/unit/mutant/loader/eval_spec.rb +1 -3
  84. data/spec/unit/mutant/matcher/chain_spec.rb +1 -3
  85. data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +21 -0
  86. data/spec/unit/mutant/matcher/compiler_spec.rb +28 -3
  87. data/spec/unit/mutant/matcher/filter_spec.rb +1 -3
  88. data/spec/unit/mutant/matcher/method/instance_spec.rb +3 -5
  89. data/spec/unit/mutant/matcher/method/singleton_spec.rb +22 -4
  90. data/spec/unit/mutant/matcher/methods/instance_spec.rb +7 -6
  91. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +4 -6
  92. data/spec/unit/mutant/matcher/namespace_spec.rb +1 -3
  93. data/spec/unit/mutant/matcher/null_spec.rb +1 -3
  94. data/spec/unit/mutant/mutation_spec.rb +1 -3
  95. data/spec/unit/mutant/mutator/node_spec.rb +1 -3
  96. data/spec/unit/mutant/reporter/cli_spec.rb +444 -206
  97. data/spec/unit/mutant/reporter/null_spec.rb +1 -3
  98. data/spec/unit/mutant/require_highjack_spec.rb +1 -3
  99. data/spec/unit/mutant/runner_spec.rb +42 -28
  100. data/spec/unit/mutant/subject/context_spec.rb +1 -3
  101. data/spec/unit/mutant/subject/method/instance_spec.rb +27 -19
  102. data/spec/unit/mutant/subject/method/singleton_spec.rb +49 -17
  103. data/spec/unit/mutant/subject_spec.rb +1 -3
  104. data/spec/unit/mutant/test_spec.rb +1 -3
  105. data/spec/unit/mutant/warning_expectation.rb +1 -3
  106. data/spec/unit/mutant/warning_filter_spec.rb +1 -3
  107. data/spec/unit/mutant_spec.rb +13 -3
  108. data/test_app/Gemfile.devtools +2 -2
  109. data/test_app/spec/unit/test_app/literal/string_spec.rb +1 -1
  110. metadata +10 -21
  111. data/lib/mutant/matcher/method/finder.rb +0 -72
  112. data/lib/mutant/reporter/cli/progress.rb +0 -10
  113. data/lib/mutant/reporter/cli/progress/config.rb +0 -30
  114. data/lib/mutant/reporter/cli/progress/env.rb +0 -30
  115. data/lib/mutant/reporter/cli/progress/noop.rb +0 -27
  116. data/lib/mutant/reporter/cli/progress/result.rb +0 -12
  117. data/lib/mutant/reporter/cli/progress/result/mutation.rb +0 -45
  118. data/lib/mutant/reporter/cli/progress/result/subject.rb +0 -54
  119. data/lib/mutant/reporter/cli/progress/subject.rb +0 -27
  120. data/lib/mutant/reporter/cli/registry.rb +0 -81
  121. data/lib/mutant/reporter/cli/report.rb +0 -10
  122. data/lib/mutant/reporter/cli/report/env.rb +0 -92
  123. data/lib/mutant/reporter/cli/report/mutation.rb +0 -103
  124. data/lib/mutant/reporter/cli/report/subject.rb +0 -32
  125. data/lib/mutant/reporter/cli/report/test.rb +0 -28
  126. data/lib/mutant/walker.rb +0 -53
  127. data/spec/shared/mutator_behavior.rb +0 -55
@@ -62,7 +62,7 @@ module Mutant
62
62
  def dispatch
63
63
  run(Element)
64
64
  run(Presence)
65
- emit([])
65
+ emit(EMPTY_ARRAY)
66
66
  end
67
67
 
68
68
  end # Array
@@ -1,7 +1,7 @@
1
1
  module Mutant
2
2
  # Abstract base class for reporters
3
3
  class Reporter
4
- include Adamantium::Flat, AbstractType
4
+ include AbstractType
5
5
 
6
6
  # Write warning message
7
7
  #
@@ -13,9 +13,19 @@ module Mutant
13
13
  #
14
14
  abstract_method :warn
15
15
 
16
- # Report object
16
+ # Report start
17
17
  #
18
- # @param [Object] object
18
+ # @param [Env] env
19
+ #
20
+ # @return [self]
21
+ #
22
+ # @api private
23
+ #
24
+ abstract_method :start
25
+
26
+ # Report collector state
27
+ #
28
+ # @param [Runner::Collector] collector
19
29
  #
20
30
  # @return [self]
21
31
  #
@@ -2,18 +2,50 @@ module Mutant
2
2
  class Reporter
3
3
  # Reporter that reports in human readable format
4
4
  class CLI < self
5
- include Concord.new(:output)
5
+ include Concord.new(:output, :format)
6
+
7
+ # Build reporter
8
+ #
9
+ # @param [IO] output
10
+ #
11
+ # @return [Reporter::CLI]
12
+ #
13
+ # @api private
14
+ #
15
+ def self.build(output)
16
+ tty = output.respond_to?(:tty?) && output.tty?
17
+ format =
18
+ if !Mutant.ci? && tty && Tput::INSTANCE.available
19
+ Format::Framed.new(tty: tty, tput: Tput::INSTANCE)
20
+ else
21
+ Format::Progressive.new(tty: tty)
22
+ end
23
+
24
+ new(output, format)
25
+ end
26
+
27
+ # Report start
28
+ #
29
+ # @param [Env] env
30
+ #
31
+ # @api private
32
+ #
33
+ def start(env)
34
+ write(format.start(env))
35
+ self
36
+ end
6
37
 
7
38
  # Report progress object
8
39
  #
9
- # @param [Object] object
40
+ # @param [Runner::Collector] collector
10
41
  #
11
42
  # @return [self]
12
43
  #
13
44
  # @api private
14
45
  #
15
- def progress(object)
16
- Progress.run(output, object)
46
+ def progress(collector)
47
+ write(format.progress(collector))
48
+
17
49
  self
18
50
  end
19
51
 
@@ -30,19 +62,33 @@ module Mutant
30
62
  self
31
63
  end
32
64
 
33
- # Report object
65
+ # Report env
34
66
  #
35
- # @param [Object] object
67
+ # @param [Result::Env] env
36
68
  #
37
69
  # @return [self]
38
70
  #
39
71
  # @api private
40
72
  #
41
- def report(object)
42
- Report.run(output, object)
73
+ def report(env)
74
+ write(format.report(env))
43
75
  self
44
76
  end
45
77
 
78
+ private
79
+
80
+ # Write output frame
81
+ #
82
+ # @param [String] frame
83
+ #
84
+ # @return [undefined]
85
+ #
86
+ # @api private
87
+ #
88
+ def write(frame)
89
+ output.write(frame)
90
+ end
91
+
46
92
  end # CLI
47
93
  end # Reporter
48
94
  end # Mutant
@@ -0,0 +1,197 @@
1
+ module Mutant
2
+ class Reporter
3
+ class CLI
4
+ # CLI output format
5
+ class Format
6
+ include AbstractType, Anima.new(:tty)
7
+
8
+ # Return start representation
9
+ #
10
+ # @param [Env] env
11
+ #
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ #
16
+ abstract_method :start
17
+
18
+ # Return progress representation
19
+ #
20
+ # @param [Runner::Collector] collector
21
+ #
22
+ # @return [String]
23
+ #
24
+ # @api private
25
+ #
26
+ abstract_method :progress
27
+
28
+ # Format result
29
+ #
30
+ # @param [Result::Env] env
31
+ #
32
+ # @return [String]
33
+ #
34
+ # @api private
35
+ #
36
+ def report(env)
37
+ format(Printer::EnvResult, env)
38
+ end
39
+
40
+ # Output abstraction to decouple tty? from buffer
41
+ class Output
42
+ include Concord.new(:tty, :buffer)
43
+
44
+ # Test if output is a tty
45
+ #
46
+ # @return [Boolean]
47
+ #
48
+ # @api private
49
+ #
50
+ def tty?
51
+ @tty
52
+ end
53
+
54
+ [:puts, :write].each do |name|
55
+ define_method(name) do |*args, &block|
56
+ buffer.public_send(name, *args, &block)
57
+ end
58
+ end
59
+ end # Output
60
+
61
+ private
62
+
63
+ # Format object with printer
64
+ #
65
+ # @param [Class:Printer] printer
66
+ # @param [Object] object
67
+ #
68
+ # @return [String]
69
+ #
70
+ # @api private
71
+ #
72
+ def format(printer, object)
73
+ buffer = new_buffer
74
+ printer.run(Output.new(tty, buffer), object)
75
+ buffer.rewind
76
+ buffer.read
77
+ end
78
+
79
+ # Format for progressive non rewindable output
80
+ class Progressive < self
81
+
82
+ # Return start representation
83
+ #
84
+ # @return [String]
85
+ #
86
+ # @api private
87
+ #
88
+ def start(env)
89
+ format(Printer::Config, env.config)
90
+ end
91
+
92
+ # Return progress representation
93
+ #
94
+ # @return [String]
95
+ #
96
+ # @api private
97
+ #
98
+ def progress(collector)
99
+ last_mutation_result = collector.last_mutation_result
100
+ return EMPTY_STRING unless last_mutation_result
101
+ format(Printer::MutationProgressResult, last_mutation_result)
102
+ end
103
+
104
+ private
105
+
106
+ # Return new buffer
107
+ #
108
+ # @return [StringIO]
109
+ #
110
+ # @api private
111
+ #
112
+ def new_buffer
113
+ StringIO.new
114
+ end
115
+
116
+ end # Progressive
117
+
118
+ # Format for framed rewindable output
119
+ class Framed < self
120
+ include anima.add(:tput)
121
+
122
+ BUFFER_FLAGS = 'a+'.freeze
123
+
124
+ # Rate per second progress report fires
125
+ OUTPUT_RATE = 1.0 / 20
126
+
127
+ # Initialize object
128
+ #
129
+ # @return [undefined]
130
+ #
131
+ # @api private
132
+ #
133
+ def initialize(*)
134
+ super
135
+ @last_frame = nil
136
+ end
137
+
138
+ # Format start
139
+ #
140
+ # @param [Env] env
141
+ #
142
+ # @return [String]
143
+ #
144
+ # @api private
145
+ #
146
+ def start(_env)
147
+ tput.prepare
148
+ end
149
+
150
+ # Format progress
151
+ #
152
+ # @param [Runner::Collector] collector
153
+ #
154
+ # @return [String]
155
+ #
156
+ # @api private
157
+ #
158
+ def progress(collector)
159
+ throttle do
160
+ format(Printer::Collector, collector)
161
+ end.to_s
162
+ end
163
+
164
+ private
165
+
166
+ # Return new buffer
167
+ #
168
+ # @return [StringIO]
169
+ #
170
+ # @api private
171
+ #
172
+ def new_buffer
173
+ # For some reason this raises an Ernno::EACCESS errror:
174
+ #
175
+ # StringIO.new(Tput::INSTANCE.restore, BUFFER_FLAGS)
176
+ #
177
+ buffer = StringIO.new
178
+ buffer << tput.restore
179
+ end
180
+
181
+ # Call block throttled
182
+ #
183
+ # @return [self]
184
+ #
185
+ # @api private
186
+ #
187
+ def throttle
188
+ now = Time.now
189
+ return if @last_frame && (now - @last_frame) < OUTPUT_RATE
190
+ yield.tap { @last_frame = now }
191
+ end
192
+
193
+ end # Framed
194
+ end # Format
195
+ end # CLI
196
+ end # Reporter
197
+ end # Mutant
@@ -1,7 +1,6 @@
1
1
  module Mutant
2
2
  class Reporter
3
3
  class CLI
4
-
5
4
  # CLI runner status printer base class
6
5
  class Printer
7
6
  include AbstractType, Delegator, Adamantium::Flat, Concord.new(:output, :object)
@@ -18,9 +17,7 @@ module Mutant
18
17
  # @api private
19
18
  #
20
19
  def self.run(output, object)
21
- handler = lookup(object.class)
22
- handler.new(output, object).run
23
- self
20
+ new(output, object).run
24
21
  end
25
22
 
26
23
  # Run printer
@@ -45,26 +42,30 @@ module Mutant
45
42
 
46
43
  # Visit a collection of objects
47
44
  #
45
+ # @return [Class::Printer] printer
48
46
  # @return [Enumerable<Object>] collection
49
47
  #
50
48
  # @return [undefined]
51
49
  #
52
50
  # @api private
53
51
  #
54
- def visit_collection(collection)
55
- collection.each(&method(:visit))
52
+ def visit_collection(printer, collection)
53
+ collection.each do |object|
54
+ visit(printer, object)
55
+ end
56
56
  end
57
57
 
58
58
  # Visit object
59
59
  #
60
+ # @param [Class::Printer] printer
60
61
  # @param [Object] object
61
62
  #
62
63
  # @return [undefined]
63
64
  #
64
65
  # @api private
65
66
  #
66
- def visit(object)
67
- self.class.run(output, object)
67
+ def visit(printer, object)
68
+ printer.run(output, object)
68
69
  end
69
70
 
70
71
  # Print an info line to output
@@ -93,7 +94,7 @@ module Mutant
93
94
  #
94
95
  # @api private
95
96
  #
96
- def puts(string = NL)
97
+ def puts(string)
97
98
  output.puts(string)
98
99
  end
99
100
 
@@ -107,16 +108,6 @@ module Mutant
107
108
  object.success?
108
109
  end
109
110
 
110
- # Test if output can be colored
111
- #
112
- # @return [Boolean]
113
- #
114
- # @api private
115
- #
116
- def color?
117
- tty?
118
- end
119
-
120
111
  # Colorize message
121
112
  #
122
113
  # @param [Color] color
@@ -133,17 +124,406 @@ module Mutant
133
124
  color.format(message)
134
125
  end
135
126
 
136
- # Test for output to tty
127
+ # Test if output is a tty
137
128
  #
138
129
  # @return [Boolean]
139
130
  #
140
131
  # @api private
141
132
  #
142
133
  def tty?
143
- output.respond_to?(:tty?) && output.tty?
134
+ output.tty?
144
135
  end
145
- memoize :tty?
146
136
 
137
+ # Test if output can be colored
138
+ #
139
+ # @return [Boolean]
140
+ #
141
+ # @api private
142
+ #
143
+ alias_method :color?, :tty?
144
+
145
+ # Printer for run collector
146
+ class Collector < self
147
+
148
+ # Print progress for collector
149
+ #
150
+ # @return [self]
151
+ #
152
+ # @api private
153
+ #
154
+ def run
155
+ visit(EnvProgress, object.result)
156
+ active_subject_results = object.active_subject_results
157
+ info('Active subjects: %d', active_subject_results.length)
158
+ visit_collection(SubjectProgress, active_subject_results)
159
+ self
160
+ end
161
+
162
+ end # Collector
163
+
164
+ # Progress printer for configuration
165
+ class Config < self
166
+
167
+ # Report configuration
168
+ #
169
+ # @param [Mutant::Config] config
170
+ #
171
+ # @return [self]
172
+ #
173
+ # @api private
174
+ #
175
+ def run
176
+ info 'Mutant configuration:'
177
+ info 'Matcher: %s', object.matcher_config.inspect
178
+ info 'Integration: %s', object.integration.name
179
+ info 'Expect Coverage: %0.2f%%', object.expected_coverage.inspect
180
+ info 'Processes: %d', object.processes
181
+ info 'Includes: %s', object.includes.inspect
182
+ info 'Requires: %s', object.requires.inspect
183
+ self
184
+ end
185
+
186
+ end # Config
187
+
188
+ # Env progress printer
189
+ class EnvProgress < self
190
+
191
+ delegate(
192
+ :coverage,
193
+ :amount_subjects,
194
+ :amount_mutations,
195
+ :amount_mutations_alive,
196
+ :amount_mutations_killed,
197
+ :runtime,
198
+ :killtime,
199
+ :overhead,
200
+ :env
201
+ )
202
+
203
+ # Run printer
204
+ #
205
+ # @return [self]
206
+ #
207
+ # @api private
208
+ #
209
+ def run
210
+ visit(Config, env.config)
211
+ info 'Available Subjects: %s', amount_subjects
212
+ info 'Subjects: %s', amount_subjects
213
+ info 'Mutations: %s', amount_mutations
214
+ info 'Kills: %s', amount_mutations_killed
215
+ info 'Alive: %s', amount_mutations_alive
216
+ info 'Runtime: %0.2fs', runtime
217
+ info 'Killtime: %0.2fs', killtime
218
+ info 'Overhead: %0.2f%%', overhead_percent
219
+ status 'Coverage: %0.2f%%', coverage_percent
220
+ status 'Expected: %0.2f%%', env.config.expected_coverage
221
+ self
222
+ end
223
+
224
+ private
225
+
226
+ # Return coverage percent
227
+ #
228
+ # @return [Float]
229
+ #
230
+ # @api private
231
+ #
232
+ def coverage_percent
233
+ coverage * 100
234
+ end
235
+
236
+ # Return overhead percent
237
+ #
238
+ # @return [Float]
239
+ #
240
+ # @api private
241
+ #
242
+ def overhead_percent
243
+ (overhead / killtime) * 100
244
+ end
245
+
246
+ end # EnvProgress
247
+
248
+ # Full env result reporter
249
+ class EnvResult < self
250
+
251
+ delegate(:failed_subject_results)
252
+
253
+ # Run printer
254
+ #
255
+ # @return [self]
256
+ #
257
+ # @api private
258
+ #
259
+ def run
260
+ visit_collection(SubjectResult, failed_subject_results)
261
+ visit(EnvProgress, object)
262
+ self
263
+ end
264
+
265
+ end # EnvResult
266
+
267
+ # Subject report printer
268
+ class SubjectResult < self
269
+
270
+ delegate :subject, :failed_mutations
271
+
272
+ # Run report printer
273
+ #
274
+ # @return [self]
275
+ #
276
+ # @api private
277
+ #
278
+ def run
279
+ status(subject.identification)
280
+ subject.tests.each do |test|
281
+ puts("- #{test.identification}")
282
+ end
283
+ visit_collection(MutationResult, object.alive_mutation_results)
284
+ self
285
+ end
286
+
287
+ end # Subject
288
+
289
+ # Printer for mutation progress results
290
+ class MutationProgressResult < self
291
+
292
+ SUCCESS = '.'.freeze
293
+ FAILURE = 'F'.freeze
294
+
295
+ # Run printer
296
+ #
297
+ # @return [self]
298
+ #
299
+ # @api private
300
+ #
301
+ def run
302
+ char(success? ? SUCCESS : FAILURE)
303
+ end
304
+
305
+ private
306
+
307
+ # Write colorized char
308
+ #
309
+ # @param [String] char
310
+ #
311
+ # @return [undefined]
312
+ #
313
+ # @api private
314
+ #
315
+ def char(char)
316
+ output.write(colorize(status_color, char))
317
+ end
318
+
319
+ end # MutationProgressResult
320
+
321
+ # Reporter for subject progress
322
+ class SubjectProgress < self
323
+
324
+ FORMAT = '(%02d/%02d) %3d%% - killtime: %0.02fs runtime: %0.02fs overhead: %0.02fs'.freeze
325
+
326
+ delegate(
327
+ :subject,
328
+ :coverage,
329
+ :runtime,
330
+ :amount_mutations_killed,
331
+ :amount_mutations,
332
+ :amount_mutation_results,
333
+ :killtime,
334
+ :overhead
335
+ )
336
+
337
+ # Run printer
338
+ #
339
+ # @return [self]
340
+ #
341
+ # @api private
342
+ #
343
+ def run
344
+ puts("#{subject.identification} mutations: #{amount_mutations}")
345
+ print_tests
346
+ print_mutation_results
347
+ print_progress_bar_finish
348
+ print_stats
349
+ self
350
+ end
351
+
352
+ private
353
+
354
+ # Print stats
355
+ #
356
+ # @return [undefined]
357
+ #
358
+ # @api private
359
+ #
360
+ def print_stats
361
+ status(
362
+ FORMAT,
363
+ amount_mutations_killed,
364
+ amount_mutations,
365
+ coverage * 100,
366
+ killtime,
367
+ runtime,
368
+ overhead
369
+ )
370
+ end
371
+
372
+ # Print tests
373
+ #
374
+ # @return [undefined]
375
+ #
376
+ # @api private
377
+ #
378
+ def print_tests
379
+ subject.tests.each do |test|
380
+ puts "- #{test.identification}"
381
+ end
382
+ end
383
+
384
+ # Print progress bar finish
385
+ #
386
+ # @return [undefined]
387
+ #
388
+ # @api private
389
+ #
390
+ def print_progress_bar_finish
391
+ puts(NL) unless amount_mutation_results.zero?
392
+ end
393
+
394
+ # Print mutation results
395
+ #
396
+ # @return [undefined]
397
+ #
398
+ # @api private
399
+ #
400
+ def print_mutation_results
401
+ visit_collection(MutationProgressResult, object.mutation_results)
402
+ end
403
+
404
+ end # Subject
405
+
406
+ # Reporter for mutation results
407
+ class MutationResult < self
408
+
409
+ delegate :mutation, :failed_test_results
410
+
411
+ DIFF_ERROR_MESSAGE = 'BUG: Mutation NOT resulted in exactly one diff. Please report a reproduction!'.freeze
412
+
413
+ MAP = {
414
+ Mutant::Mutation::Evil => :evil_details,
415
+ Mutant::Mutation::Neutral => :neutral_details,
416
+ Mutant::Mutation::Noop => :noop_details
417
+ }.freeze
418
+
419
+ NEUTRAL_MESSAGE =
420
+ "--- Neutral failure ---\n" \
421
+ "Original code was inserted unmutated. And the test did NOT PASS.\n" \
422
+ "Your tests do not pass initially or you found a bug in mutant / unparser.\n" \
423
+ "Subject AST:\n" \
424
+ "%s\n" \
425
+ "Unparsed Source:\n" \
426
+ "%s\n" \
427
+ "Test Reports: %d\n"
428
+
429
+ NOOP_MESSAGE =
430
+ "---- Noop failure -----\n" \
431
+ "No code was inserted. And the test did NOT PASS.\n" \
432
+ "This is typically a problem of your specs not passing unmutated.\n" \
433
+ "Test Reports: %d\n"
434
+
435
+ FOOTER = '-----------------------'.freeze
436
+
437
+ # Run report printer
438
+ #
439
+ # @return [self]
440
+ #
441
+ # @api private
442
+ #
443
+ def run
444
+ puts(mutation.identification)
445
+ print_details
446
+ puts(FOOTER)
447
+ self
448
+ end
449
+
450
+ private
451
+
452
+ # Return details
453
+ #
454
+ # @return [undefined]
455
+ #
456
+ # @api private
457
+ #
458
+ def print_details
459
+ send(MAP.fetch(mutation.class))
460
+ end
461
+
462
+ # Return evil details
463
+ #
464
+ # @return [String]
465
+ #
466
+ # @api private
467
+ #
468
+ def evil_details
469
+ original, current = mutation.original_source, mutation.source
470
+ diff = Mutant::Diff.build(original, current)
471
+ diff = color? ? diff.colorized_diff : diff.diff
472
+ puts(diff || ['Original source:', original, 'Mutated Source:', current, DIFF_ERROR_MESSAGE])
473
+ end
474
+
475
+ # Noop details
476
+ #
477
+ # @return [String]
478
+ #
479
+ # @api private
480
+ #
481
+ def noop_details
482
+ info(NOOP_MESSAGE, failed_test_results.length)
483
+ visit_failed_test_results
484
+ end
485
+
486
+ # Neutral details
487
+ #
488
+ # @return [String]
489
+ #
490
+ # @api private
491
+ #
492
+ def neutral_details
493
+ info(NEUTRAL_MESSAGE, mutation.subject.node.inspect, mutation.source, failed_test_results.length)
494
+ visit_failed_test_results
495
+ end
496
+
497
+ # Visit failed test results
498
+ #
499
+ # @return [undefined]
500
+ #
501
+ # @api private
502
+ #
503
+ def visit_failed_test_results
504
+ visit_collection(TestResult, failed_test_results)
505
+ end
506
+
507
+ end # MutationResult
508
+
509
+ # Test result reporter
510
+ class TestResult < self
511
+
512
+ delegate :test, :runtime
513
+
514
+ # Run test result reporter
515
+ #
516
+ # @return [self]
517
+ #
518
+ # @api private
519
+ #
520
+ def run
521
+ status('- %s / runtime: %s', test.identification, object.runtime)
522
+ puts('Test Output:')
523
+ puts(object.output)
524
+ end
525
+
526
+ end # TestResult
147
527
  end # Printer
148
528
  end # CLI
149
529
  end # Reporter