mutant 0.5.24 → 0.5.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +8 -0
  3. data/config/flay.yml +1 -1
  4. data/config/flog.yml +1 -1
  5. data/config/reek.yml +15 -13
  6. data/lib/mutant.rb +28 -12
  7. data/lib/mutant/ast/meta.rb +0 -10
  8. data/lib/mutant/ast/named_children.rb +1 -0
  9. data/lib/mutant/ast/types.rb +5 -5
  10. data/lib/mutant/cli.rb +84 -64
  11. data/lib/mutant/config.rb +7 -39
  12. data/lib/mutant/delegator.rb +2 -0
  13. data/lib/mutant/env.rb +119 -16
  14. data/lib/mutant/expression.rb +8 -2
  15. data/lib/mutant/expression/method.rb +6 -16
  16. data/lib/mutant/expression/methods.rb +5 -5
  17. data/lib/mutant/expression/namespace.rb +7 -7
  18. data/lib/mutant/integration.rb +0 -10
  19. data/lib/mutant/isolation.rb +41 -15
  20. data/lib/mutant/matcher/chain.rb +1 -17
  21. data/lib/mutant/matcher/compiler.rb +108 -0
  22. data/lib/mutant/matcher/config.rb +28 -0
  23. data/lib/mutant/matcher/method.rb +1 -1
  24. data/lib/mutant/matcher/namespace.rb +5 -52
  25. data/lib/mutant/matcher/null.rb +1 -1
  26. data/lib/mutant/matcher/scope.rb +1 -1
  27. data/lib/mutant/mutation.rb +29 -13
  28. data/lib/mutant/mutator/node.rb +2 -12
  29. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
  30. data/lib/mutant/reporter/cli.rb +0 -2
  31. data/lib/mutant/reporter/cli/printer.rb +14 -0
  32. data/lib/mutant/reporter/cli/progress.rb +1 -3
  33. data/lib/mutant/reporter/cli/progress/config.rb +5 -9
  34. data/lib/mutant/reporter/cli/progress/env.rb +30 -0
  35. data/lib/mutant/reporter/cli/progress/noop.rb +4 -1
  36. data/lib/mutant/reporter/cli/progress/result.rb +12 -0
  37. data/lib/mutant/reporter/cli/progress/result/mutation.rb +45 -0
  38. data/lib/mutant/reporter/cli/progress/result/subject.rb +54 -0
  39. data/lib/mutant/reporter/cli/progress/subject.rb +7 -90
  40. data/lib/mutant/reporter/cli/registry.rb +2 -0
  41. data/lib/mutant/reporter/cli/report/env.rb +92 -0
  42. data/lib/mutant/reporter/cli/report/mutation.rb +58 -77
  43. data/lib/mutant/reporter/cli/report/subject.rb +4 -3
  44. data/lib/mutant/reporter/cli/report/test.rb +28 -0
  45. data/lib/mutant/reporter/null.rb +1 -1
  46. data/lib/mutant/reporter/trace.rb +16 -3
  47. data/lib/mutant/result.rb +302 -0
  48. data/lib/mutant/runner.rb +77 -123
  49. data/lib/mutant/subject.rb +32 -16
  50. data/lib/mutant/subject/method.rb +0 -15
  51. data/lib/mutant/subject/method/instance.rb +3 -3
  52. data/lib/mutant/version.rb +1 -1
  53. data/lib/mutant/warning_expectation.rb +12 -5
  54. data/spec/integration/mutant/corpus_spec.rb +1 -1
  55. data/spec/spec_helper.rb +5 -1
  56. data/spec/unit/mutant/cli_spec.rb +248 -0
  57. data/spec/unit/mutant/expression/namespace/flat_spec.rb +1 -1
  58. data/spec/unit/mutant/expression_spec.rb +55 -0
  59. data/spec/unit/mutant/integration_spec.rb +0 -5
  60. data/spec/unit/mutant/isolation_spec.rb +36 -5
  61. data/spec/unit/mutant/matcher/chain_spec.rb +1 -13
  62. data/spec/unit/mutant/matcher/compiler_spec.rb +95 -0
  63. data/spec/unit/mutant/matcher/filter_spec.rb +31 -0
  64. data/spec/unit/mutant/matcher/method/instance_spec.rb +33 -2
  65. data/spec/unit/mutant/matcher/method/singleton_spec.rb +1 -1
  66. data/spec/unit/mutant/matcher/methods/instance_spec.rb +1 -1
  67. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +1 -1
  68. data/spec/unit/mutant/matcher/namespace_spec.rb +10 -6
  69. data/spec/unit/mutant/matcher/null_spec.rb +26 -0
  70. data/spec/unit/mutant/reporter/cli_spec.rb +337 -0
  71. data/spec/unit/mutant/reporter/null_spec.rb +12 -0
  72. data/spec/unit/mutant/runner_spec.rb +130 -0
  73. data/spec/unit/mutant/subject/context_spec.rb +4 -3
  74. data/spec/unit/mutant/subject/method/instance_spec.rb +5 -3
  75. data/spec/unit/mutant/subject/method/singleton_spec.rb +3 -2
  76. data/spec/unit/mutant/subject_spec.rb +36 -1
  77. data/spec/unit/mutant/test_spec.rb +25 -0
  78. data/spec/unit/mutant/warning_expectation.rb +11 -8
  79. data/spec/unit/mutant_spec.rb +11 -2
  80. metadata +27 -28
  81. data/lib/mutant/killer.rb +0 -44
  82. data/lib/mutant/matcher/builder.rb +0 -142
  83. data/lib/mutant/mutation/evil.rb +0 -23
  84. data/lib/mutant/mutation/neutral.rb +0 -18
  85. data/lib/mutant/reporter/cli/progress/mutation.rb +0 -46
  86. data/lib/mutant/reporter/cli/report/config.rb +0 -116
  87. data/lib/mutant/rspec.rb +0 -0
  88. data/lib/mutant/runner/config.rb +0 -138
  89. data/lib/mutant/runner/killer.rb +0 -75
  90. data/lib/mutant/runner/mutation.rb +0 -78
  91. data/lib/mutant/runner/subject.rb +0 -85
  92. data/lib/mutant/test/report.rb +0 -59
  93. data/spec/unit/mutant/cli_new_spec.rb +0 -147
  94. data/spec/unit/mutant/cli_run_spec.rb +0 -46
  95. data/spec/unit/mutant/runner/config_spec.rb +0 -157
  96. data/spec/unit/mutant/runner/mutation_spec.rb +0 -101
  97. data/spec/unit/mutant/runner/subject_spec.rb +0 -59
  98. data/spec/unit/mutant/subject/mutations_spec.rb +0 -23
  99. data/spec/unit/mutant/subject/node_spec.rb +0 -17
@@ -5,10 +5,11 @@ module Mutant
5
5
 
6
6
  # Subject report printer
7
7
  class Subject < self
8
- handle(Mutant::Runner::Subject)
9
8
 
10
9
  delegate :subject, :failed_mutations
11
10
 
11
+ handle(Mutant::Result::Subject)
12
+
12
13
  # Run report printer
13
14
  #
14
15
  # @return [self]
@@ -17,10 +18,10 @@ module Mutant
17
18
  #
18
19
  def run
19
20
  status(subject.identification)
20
- object.tests.each do |test|
21
+ subject.tests.each do |test|
21
22
  puts("- #{test.identification}")
22
23
  end
23
- object.failed_mutations.each(&method(:visit))
24
+ visit_collection(object.alive_mutation_results)
24
25
  self
25
26
  end
26
27
 
@@ -0,0 +1,28 @@
1
+ module Mutant
2
+ class Reporter
3
+ class CLI
4
+ class Report
5
+ # Test result reporter
6
+ class Test < self
7
+
8
+ handle(Mutant::Result::Test)
9
+
10
+ delegate :test, :runtime
11
+
12
+ # Run test result reporter
13
+ #
14
+ # @return [self]
15
+ #
16
+ # @api private
17
+ #
18
+ def run
19
+ status('- %s / runtime: %s', test.identification, object.runtime)
20
+ puts('Test Output:')
21
+ puts(object.output)
22
+ end
23
+
24
+ end
25
+ end # Report
26
+ end # CLI
27
+ end # Reporter
28
+ end # Mutant
@@ -13,7 +13,7 @@ module Mutant
13
13
  #
14
14
  # @api private
15
15
  #
16
- def report(_message)
16
+ def warn(_message)
17
17
  self
18
18
  end
19
19
 
@@ -2,16 +2,29 @@ module Mutant
2
2
  class Reporter
3
3
  # Reporter to trace report calls, used as a spec adapter
4
4
  class Trace
5
- include Concord::Public.new(:progress_calls, :report_calls)
5
+ include Adamantium::Mutable, Anima.new(:progress_calls, :report_calls, :warn_calls)
6
6
 
7
7
  # Return new trace reporter
8
8
  #
9
- # @return [Tracer]
9
+ # @return [Trace]
10
10
  #
11
11
  # @api private
12
12
  #
13
13
  def self.new
14
- super([], [])
14
+ super(Hash[anima.attribute_names.map { |name| [name, []] }])
15
+ end
16
+
17
+ # Warn with message
18
+ #
19
+ # @param [String] message
20
+ #
21
+ # @return [self]
22
+ #
23
+ # @api private
24
+ #
25
+ def warn(message)
26
+ warn_calls << message
27
+ self
15
28
  end
16
29
 
17
30
  # Report object
@@ -0,0 +1,302 @@
1
+ module Mutant
2
+ # Namespace and mixon module for results
3
+ module Result
4
+
5
+ # Coverage mixin
6
+ module Coverage
7
+
8
+ # Return coverage
9
+ #
10
+ # @return [Rational]
11
+ #
12
+ # @api private
13
+ #
14
+ def coverage
15
+ return Rational(0) if amount_mutations.zero?
16
+
17
+ Rational(amount_mutations_killed, amount_mutations)
18
+ end
19
+
20
+ # Hook called when module gets included
21
+ #
22
+ # @param [Class, Module] host
23
+ #
24
+ # @return [undefined]
25
+ #
26
+ # @api private
27
+ #
28
+ def self.included(host)
29
+ super
30
+
31
+ host.memoize :coverage
32
+ end
33
+
34
+ end
35
+
36
+ # Test if operation is failing
37
+ #
38
+ # @return [Boolean]
39
+ #
40
+ # @api private
41
+ #
42
+ def fail?
43
+ !success?
44
+ end
45
+
46
+ # Class level mixin
47
+ module ClassMethods
48
+
49
+ # Generate a sum messot from name and collection
50
+ #
51
+ # @param [Symbol] name
52
+ # the attribute name on collection item and method name to use
53
+ #
54
+ # @param [Symbol] collection
55
+ # the attribute name used to receive collection
56
+ #
57
+ # @return [undefined]
58
+ #
59
+ # @api private
60
+ #
61
+ def sum(name, collection)
62
+ define_method(name) do
63
+ public_send(collection).map(&name).reduce(0, :+)
64
+ end
65
+ memoize name
66
+ end
67
+ end
68
+
69
+ # Return overhead
70
+ #
71
+ # @return [Float]
72
+ #
73
+ # @api private
74
+ #
75
+ def overhead
76
+ runtime - killtime
77
+ end
78
+
79
+ # Hook called when module gets included
80
+ #
81
+ # @param [Class, Module] hosto
82
+ #
83
+ # @return [undefined]
84
+ #
85
+ # @api private
86
+ #
87
+ def self.included(host)
88
+ host.class_eval do
89
+ include Adamantium::Flat, Anima::Update
90
+ extend ClassMethods
91
+ end
92
+ end
93
+
94
+ # Env result object
95
+ class Env
96
+ include Coverage, Result, Anima.new(:runtime, :env, :subject_results)
97
+
98
+ COVERAGE_PRECISION = 1
99
+
100
+ # Test if run was successful
101
+ #
102
+ # @return [Boolean]
103
+ #
104
+ # @api private
105
+ #
106
+ def success?
107
+ (coverage * 100).to_f.round(COVERAGE_PRECISION).eql?(env.config.expected_coverage.round(COVERAGE_PRECISION))
108
+ end
109
+ memoize :success?
110
+
111
+ # Return failed subject results
112
+ #
113
+ # @return [Array<Result::Subject>]
114
+ #
115
+ # @api private
116
+ #
117
+ def failed_subject_results
118
+ subject_results.reject(&:success?)
119
+ end
120
+
121
+ sum :amount_mutations, :subject_results
122
+ sum :amount_mutations_alive, :subject_results
123
+ sum :amount_mutations_killed, :subject_results
124
+ sum :killtime, :subject_results
125
+
126
+ # Return amount of subjects
127
+ #
128
+ # @return [Fixnum]
129
+ #
130
+ # @api private
131
+ #
132
+ def amount_subjects
133
+ env.subjects.length
134
+ end
135
+
136
+ end # Env
137
+
138
+ # Test result
139
+ class Test
140
+ include Result, Adamantium::Flat, Anima::Update, Anima.new(
141
+ :test,
142
+ :output,
143
+ :mutation,
144
+ :passed,
145
+ :runtime
146
+ )
147
+
148
+ # NOTE:
149
+ #
150
+ # The test is intentionally NOT part of the mashalled data.
151
+ # In rspec the example group cannot deterministically being marshalled, because
152
+ # they reference a crazy mix of IO objects, global objects etc.
153
+ #
154
+ MARSHALLED_IVARS = (anima.attribute_names - [:test]).map do |name|
155
+ :"@#{name}"
156
+ end
157
+
158
+ # Return killtime
159
+ #
160
+ # @return [Float]
161
+ #
162
+ # @api private
163
+ #
164
+ alias_method :killtime, :runtime
165
+
166
+ # Test if mutation test result is successful
167
+ #
168
+ # @return [Boolean]
169
+ #
170
+ # @api private
171
+ #
172
+ def success?
173
+ mutation.killed_by?(self)
174
+ end
175
+
176
+ # Return marshallable data
177
+ #
178
+ #
179
+ # @return [Array]
180
+ #
181
+ # @api private
182
+ #
183
+ def marshal_dump
184
+ MARSHALLED_IVARS.map(&method(:instance_variable_get))
185
+ end
186
+
187
+ # Load marshalled data
188
+ #
189
+ # @param [Array] array
190
+ #
191
+ # @return [undefined]
192
+ #
193
+ # @api private
194
+ #
195
+ def marshal_load(array)
196
+ MARSHALLED_IVARS.zip(array) do |instance_variable_name, value|
197
+ instance_variable_set(instance_variable_name, value)
198
+ end
199
+ end
200
+
201
+ end # Test
202
+
203
+ # Subject result
204
+ class Subject
205
+ include Coverage, Result, Anima.new(:subject, :mutation_results, :runtime)
206
+
207
+ sum :killtime, :mutation_results
208
+
209
+ # Test if subject was processed successful
210
+ #
211
+ # @return [Boolean]
212
+ #
213
+ # @api private
214
+ #
215
+ def success?
216
+ alive_mutation_results.empty?
217
+ end
218
+
219
+ # Return killed mutations
220
+ #
221
+ # @return [Array<Result::Mutation>]
222
+ #
223
+ # @api private
224
+ #
225
+ def alive_mutation_results
226
+ mutation_results.reject(&:success?)
227
+ end
228
+ memoize :alive_mutation_results
229
+
230
+ # Return amount of mutations
231
+ #
232
+ # @return [Fixnum]
233
+ #
234
+ # @api private
235
+ #
236
+ def amount_mutations
237
+ subject.mutations.length
238
+ end
239
+
240
+ # Return number of killed mutations
241
+ #
242
+ # @return [Fixnum]
243
+ #
244
+ # @api private
245
+ #
246
+ def amount_mutations_killed
247
+ killed_mutation_results.length
248
+ end
249
+
250
+ # Return number of alive mutations
251
+ #
252
+ # @return [Fixnum]
253
+ #
254
+ # @api private
255
+ #
256
+ def amount_mutations_alive
257
+ amount_mutations - amount_mutations_killed
258
+ end
259
+
260
+ # Return alive mutations
261
+ #
262
+ # @return [Array<Result::Mutation>]
263
+ #
264
+ # @api private
265
+ #
266
+ def killed_mutation_results
267
+ mutation_results.select(&:success?)
268
+ end
269
+ memoize :killed_mutation_results
270
+
271
+ end # Subject
272
+
273
+ # Mutation result
274
+ class Mutation
275
+ include Result, Anima.new(:runtime, :mutation, :test_results)
276
+
277
+ # Test if mutation was handeled successfully
278
+ #
279
+ # @return [Boolean]
280
+ #
281
+ # @api private
282
+ #
283
+ def success?
284
+ test_results.any?(&:success?)
285
+ end
286
+
287
+ # Return failed test results
288
+ #
289
+ # @return [Array]
290
+ #
291
+ # @api private
292
+ #
293
+ def failed_test_results
294
+ test_results.select(&:fail?)
295
+ end
296
+
297
+ sum :killtime, :test_results
298
+
299
+ end # Mutation
300
+
301
+ end # Result
302
+ end # Mutant
@@ -1,135 +1,108 @@
1
1
  module Mutant
2
2
  # Runner baseclass
3
3
  class Runner
4
- include Adamantium::Flat, AbstractType
4
+ include Adamantium, Concord.new(:env), Procto.call(:result)
5
5
 
6
- REGISTRY = {}
7
-
8
- # Register handler
9
- #
10
- # @param [Class] klass
6
+ # Initialize object
11
7
  #
12
8
  # @return [undefined]
13
9
  #
14
10
  # @api private
15
11
  #
16
- def self.register(klass)
17
- REGISTRY[klass] = self
18
- end
19
- private_class_method :register
12
+ def initialize(env)
13
+ super
20
14
 
21
- # Lookup runner
22
- #
23
- # @param [Class] klass
24
- #
25
- # @return [undefined]
26
- #
27
- # @api private
28
- #
29
- def self.lookup(klass)
30
- current = klass
31
- while current
32
- return REGISTRY.fetch(current) if REGISTRY.key?(current)
33
- current = current.superclass
34
- end
15
+ @stop = false
35
16
 
36
- raise ArgumentError, "No handler for: #{klass}"
37
- end
38
- private_class_method :lookup
17
+ config.integration.setup
39
18
 
40
- # Run runner for object
41
- #
42
- # @param [Config] config
43
- # @param [Object] object
44
- #
45
- # @return [Runner]
46
- #
47
- # @api private
48
- #
49
- def self.run(config, object, *arguments)
50
- handler = lookup(object.class)
51
- handler.new(config, object, *arguments)
52
- end
19
+ progress(env)
53
20
 
54
- # Test if runner is running
55
- #
56
- # Yeah this is evil. Should be refactored away
57
- #
58
- # @return [Boolean]
59
- #
60
- # @api private
61
- #
62
- def running?
63
- @running
21
+ start = Time.now
22
+
23
+ @result = Result::Env.new(
24
+ env: env,
25
+ subject_results: visit_collection(env.subjects, &method(:run_subject)),
26
+ runtime: Time.now - start
27
+ ).tap do |report|
28
+ config.reporter.report(report)
29
+ end
64
30
  end
65
31
 
66
- # Return config
32
+ # Return result
67
33
  #
68
- # @return [Mutant::Config]
34
+ # @return [Result::Env]
69
35
  #
70
36
  # @api private
71
37
  #
72
- attr_reader :config
38
+ attr_reader :result
73
39
 
74
- # Initialize object
75
- #
76
- # @param [Config] config
40
+ private
41
+
42
+ # Run subject
77
43
  #
78
- # @return [undefined]
44
+ # @return [Report::Subject]
79
45
  #
80
46
  # @api private
81
47
  #
82
- def initialize(config)
83
- @config = config
84
- @stop = false
85
- @start = Time.now
86
- @running = true
87
- progress(self)
88
- run
89
- @running = false
90
- progress(self)
91
- @end = Time.now
48
+ def run_subject(subject)
49
+ Result::Subject.new(
50
+ subject: subject,
51
+ mutation_results: visit_collection(subject.mutations, &method(:run_mutation)),
52
+ runtime: nil
53
+ )
92
54
  end
93
55
 
94
- # Test if runner should stop
56
+ # Run mutation
95
57
  #
96
- # @return [Boolean]
58
+ # @return [Report::Mutation]
97
59
  #
98
60
  # @api private
99
61
  #
100
- def stop?
101
- @stop
62
+ def run_mutation(mutation)
63
+ start = Time.now
64
+ test_results = mutation.subject.tests.each_with_object([]) do |test, results|
65
+ results << result = run_mutation_test(mutation, test).tap(&method(:progress))
66
+ break results if mutation.killed_by?(result)
67
+ end
68
+
69
+ Result::Mutation.new(
70
+ mutation: mutation,
71
+ runtime: Time.now - start,
72
+ test_results: test_results
73
+ )
102
74
  end
103
75
 
104
- # Return runtime
76
+ # Return config
105
77
  #
106
- # @return [Float]
78
+ # @return [Config]
107
79
  #
108
80
  # @api private
109
81
  #
110
- def runtime
111
- (@end || Time.now) - @start
82
+ def config
83
+ env.config
112
84
  end
113
85
 
114
- # Test if runner is successful
86
+ # Visit collection
115
87
  #
116
- # @return [Boolean]
88
+ # @return [Array<Result>]
117
89
  #
118
90
  # @api private
119
91
  #
120
- abstract_method :success?
92
+ def visit_collection(collection)
93
+ results = []
121
94
 
122
- private
95
+ collection.each do |item|
96
+ progress(item)
97
+ start = Time.now
98
+ results << result = yield(item).update(runtime: Time.now - start).tap(&method(:progress))
99
+ break if @stop ||= config.fail_fast? && result.fail?
100
+ end
123
101
 
124
- # Perform operation
125
- #
126
- # @return [undefined]
127
- #
128
- # @api private
129
- #
130
- abstract_method :run
102
+ results
103
+ end
131
104
 
132
- # Run reporter on object
105
+ # Report progress
133
106
  #
134
107
  # @param [Object] object
135
108
  #
@@ -138,48 +111,29 @@ module Mutant
138
111
  # @api private
139
112
  #
140
113
  def progress(object)
141
- reporter.progress(object)
142
- end
143
-
144
- # Return reporter
145
- #
146
- # @return [Reporter]
147
- #
148
- # @api private
149
- #
150
- def reporter
151
- config.reporter
114
+ config.reporter.progress(object)
152
115
  end
153
116
 
154
- # Perform dispatch on multiple inputs
155
- #
156
- # @param [Enumerable<Object>] input
157
- #
158
- # @return [Enumerable<Runner>]
159
- #
160
- # @api private
117
+ # Return test result
161
118
  #
162
- def visit_collection(input, *arguments)
163
- collection = []
164
- input.each do |object|
165
- runner = visit(object, *arguments)
166
- collection << runner
167
- @stop = runner.stop?
168
- break if @stop
169
- end
170
- collection
171
- end
172
-
173
- # Visit object
174
- #
175
- # @param [Object] object
176
- #
177
- # @return [undefined]
119
+ # @return [Report::Test]
178
120
  #
179
121
  # @api private
180
122
  #
181
- def visit(object, *arguments)
182
- Runner.run(config, object, *arguments)
123
+ def run_mutation_test(mutation, test)
124
+ time = Time.now
125
+ config.isolation.call do
126
+ mutation.insert
127
+ test.run
128
+ end.update(test: test, mutation: mutation)
129
+ rescue Isolation::Error => exception
130
+ Result::Test.new(
131
+ test: test,
132
+ mutation: mutation,
133
+ runtime: Time.now - time,
134
+ output: exception.message,
135
+ passed: false
136
+ )
183
137
  end
184
138
 
185
139
  end # Runner