mutant 0.5.26 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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