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
@@ -2,16 +2,17 @@ module Mutant
2
2
  # Subject of a mutation
3
3
  class Subject
4
4
  include AbstractType, Adamantium::Flat, Enumerable
5
- include Concord::Public.new(:context, :node)
5
+ include Concord::Public.new(:config, :context, :node)
6
6
 
7
7
  # Return mutations
8
8
  #
9
9
  # @return [Enumerable<Mutation>]
10
+ # @return [undefined]
10
11
  #
11
12
  # @api private
12
13
  #
13
14
  def mutations
14
- mutations = []
15
+ mutations = [neutral_mutation]
15
16
  generate_mutations(mutations)
16
17
  mutations
17
18
  end
@@ -27,6 +28,28 @@ module Mutant
27
28
  context.source_path
28
29
  end
29
30
 
31
+ # Return tests for mutation
32
+ #
33
+ # TODO: This logic is now centralized but still fucked.
34
+ #
35
+ # @param [Mutation] mutation
36
+ #
37
+ # @return [Array<Test>]
38
+ #
39
+ # @api private
40
+ #
41
+ def tests
42
+ match_expressions.each do |match_expression|
43
+ tests = config.integration.all_tests.select do |test|
44
+ match_expression.prefix?(test.expression)
45
+ end
46
+ return tests if tests.any?
47
+ end
48
+
49
+ EMPTY_ARRAY
50
+ end
51
+ memoize :tests
52
+
30
53
  # Prepare the subject for the insertion of mutation
31
54
  #
32
55
  # @return [self]
@@ -81,17 +104,6 @@ module Mutant
81
104
  context.root(node)
82
105
  end
83
106
 
84
- # Return root AST node for original AST ndoe
85
- #
86
- # @return [Parser::AST::Node]
87
- #
88
- # @api private
89
- #
90
- def original_root
91
- root(node)
92
- end
93
- memoize :original_root
94
-
95
107
  # Return match expression
96
108
  #
97
109
  # @return [Expression]
@@ -119,8 +131,8 @@ module Mutant
119
131
  #
120
132
  # @api private
121
133
  #
122
- def noop_mutation
123
- Mutation::Neutral::Noop.new(self, node)
134
+ def neutral_mutation
135
+ Mutation::Neutral.new(self, node)
124
136
  end
125
137
 
126
138
  # Generate mutations
@@ -131,7 +143,11 @@ module Mutant
131
143
  #
132
144
  # @api private
133
145
  #
134
- abstract_method :generate_mutations
146
+ def generate_mutations(emitter)
147
+ Mutator.each(node) do |mutant|
148
+ emitter << Mutation::Evil.new(self, mutant)
149
+ end
150
+ end
135
151
 
136
152
  end # Subject
137
153
  end # Mutant
@@ -34,21 +34,6 @@ module Mutant
34
34
 
35
35
  private
36
36
 
37
- # Return mutations
38
- #
39
- # @param [#<<] emitter
40
- #
41
- # @return [undefined]
42
- #
43
- # @api private
44
- #
45
- def generate_mutations(emitter)
46
- emitter << noop_mutation
47
- Mutator.each(node) do |mutant|
48
- emitter << Mutation::Evil.new(self, mutant)
49
- end
50
- end
51
-
52
37
  # Return scope
53
38
  #
54
39
  # @return [Class, Module]
@@ -75,7 +75,7 @@ module Mutant
75
75
  # @api private
76
76
  #
77
77
  def generate_mutations(emitter)
78
- emitter << noop_mutation
78
+ emitter << neutral_mutation
79
79
  Mutator.each(node) do |mutant|
80
80
  emitter << Mutation::Evil.new(self, memoizer_node(mutant))
81
81
  end
@@ -87,8 +87,8 @@ module Mutant
87
87
  #
88
88
  # @api private
89
89
  #
90
- def noop_mutation
91
- Mutation::Neutral::Noop.new(self, memoizer_node(node))
90
+ def neutral_mutation
91
+ Mutation::Neutral.new(self, memoizer_node(node))
92
92
  end
93
93
 
94
94
  # Return memoizer node for mutant
@@ -1,4 +1,4 @@
1
1
  module Mutant
2
2
  # The current mutant version
3
- VERSION = '0.5.24'.freeze
3
+ VERSION = '0.5.25'.freeze
4
4
  end # Mutant
@@ -5,7 +5,7 @@ module Mutant
5
5
 
6
6
  # Error raised on expectation miss
7
7
  class ExpectationError < RuntimeError
8
- include Concord.new(:unexpected, :missing)
8
+ include Concord.new(:unexpected)
9
9
 
10
10
  # Return exception message
11
11
  #
@@ -14,7 +14,7 @@ module Mutant
14
14
  # @api private
15
15
  #
16
16
  def message
17
- "Unexpected warnings: #{unexpected.inspect} missing warnigns: #{missing.inspect}"
17
+ "Unexpected warnings: #{unexpected.inspect}"
18
18
  end
19
19
  end
20
20
 
@@ -28,11 +28,18 @@ module Mutant
28
28
  warnings = WarningFilter.use do
29
29
  block.call
30
30
  end
31
- missing = expected - warnings
31
+
32
+ missing = expected - warnings
32
33
  unexpected = warnings - expected
33
- if missing.any? or unexpected.any?
34
- fail ExpectationError.new(unexpected, missing)
34
+
35
+ if unexpected.any?
36
+ fail ExpectationError, unexpected
35
37
  end
38
+
39
+ if missing.any?
40
+ $stderr.puts("Expected but missing warnings: #{missing}")
41
+ end
42
+
36
43
  self
37
44
  end
38
45
 
@@ -160,7 +160,7 @@ describe 'Mutant on ruby corpus' do
160
160
  if block_given?
161
161
  yield
162
162
  else
163
- raise 'System command failed!'
163
+ fail 'System command failed!'
164
164
  end
165
165
  end
166
166
 
@@ -13,6 +13,8 @@ if ENV['COVERAGE'] == 'true'
13
13
  add_filter 'vendor'
14
14
  add_filter 'test_app'
15
15
  add_filter 'lib/mutant/meta/*'
16
+ add_filter 'lib/mutant/zombifier'
17
+ add_filter 'lib/mutant/zombifier/*'
16
18
 
17
19
  minimum_coverage 89.77 # TODO: raise this to 100, then mutation test
18
20
  end
@@ -30,7 +32,9 @@ $LOAD_PATH << File.join(TestApp.root, 'lib')
30
32
  require 'test_app'
31
33
 
32
34
  module Fixtures
33
- BOOT_ENV = Mutant::Env::Boot.new(Mutant::Reporter::CLI.new(STDERR), Mutant::Cache.new)
35
+ TEST_CONFIG = Mutant::Config::DEFAULT.update(reporter: Mutant::Reporter::Trace.new)
36
+ TEST_CACHE = Mutant::Cache.new
37
+ TEST_ENV = Mutant::Env.new(TEST_CONFIG, TEST_CACHE)
34
38
  end # Fixtures
35
39
 
36
40
  module ParserHelper
@@ -0,0 +1,248 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for 'an invalid cli run' do
4
+ it 'raises error' do
5
+ expect do
6
+ subject
7
+ end.to raise_error(Mutant::CLI::Error, expected_message)
8
+ end
9
+ end
10
+
11
+ shared_examples_for 'a cli parser' do
12
+ it { expect(subject.config.integration).to eql(expected_integration) }
13
+ it { expect(subject.config.reporter).to eql(expected_reporter) }
14
+ it { expect(subject.config.matcher_config).to eql(expected_matcher_config) }
15
+ end
16
+
17
+ describe Mutant::CLI do
18
+ let(:object) { described_class }
19
+
20
+ describe '.run' do
21
+ subject { object.run(arguments) }
22
+
23
+ let(:arguments) { double('arguments') }
24
+
25
+ let(:report) { double('Report', success?: report_success) }
26
+ let(:config) { double('Config') }
27
+
28
+ before do
29
+ expect(Mutant::CLI).to receive(:call).with(arguments).and_return(config)
30
+ expect(Mutant::Env).to receive(:call).with(config).and_return(report)
31
+ end
32
+
33
+ context 'when report signalls success' do
34
+ let(:report_success) { true }
35
+
36
+ it 'exits failure' do
37
+ expect(subject).to be(0)
38
+ end
39
+ end
40
+
41
+ context 'when report signalls error' do
42
+ let(:report_success) { false }
43
+
44
+ it 'exits failure' do
45
+ expect(subject).to be(1)
46
+ end
47
+ end
48
+
49
+ context 'when execution raises an Mutant::CLI::Error' do
50
+ let(:exception) { Mutant::CLI::Error.new('test-error') }
51
+ let(:report_success) { nil }
52
+
53
+ before do
54
+ expect(report).to receive(:success?).and_raise(exception)
55
+ end
56
+
57
+ it 'exits failure' do
58
+ expect($stderr).to receive(:puts).with('test-error')
59
+ expect(subject).to be(1)
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '.new' do
65
+ let(:object) { described_class }
66
+
67
+ subject { object.new(arguments) }
68
+
69
+ # Defaults
70
+ let(:expected_filter) { Morpher.evaluator(s(:true)) }
71
+ let(:expected_integration) { Mutant::Integration::Null.new }
72
+ let(:expected_reporter) { Mutant::Reporter::CLI.new($stdout) }
73
+ let(:expected_matcher_config) { default_matcher_config }
74
+
75
+ let(:default_matcher_config) do
76
+ Mutant::Matcher::Config::DEFAULT
77
+ .update(match_expressions: expressions.map(&Mutant::Expression.method(:parse)))
78
+ end
79
+
80
+ let(:ns) { Mutant::Matcher }
81
+
82
+ let(:flags) { [] }
83
+ let(:expressions) { %w[TestApp*] }
84
+
85
+ let(:arguments) { flags + expressions }
86
+
87
+ context 'with unknown flag' do
88
+ let(:flags) { %w[--invalid] }
89
+
90
+ let(:expected_message) { 'invalid option: --invalid' }
91
+
92
+ it_should_behave_like 'an invalid cli run'
93
+ end
94
+
95
+ context 'with unknown option' do
96
+ let(:flags) { %w[--invalid Foo] }
97
+
98
+ let(:expected_message) { 'invalid option: --invalid' }
99
+
100
+ it_should_behave_like 'an invalid cli run'
101
+ end
102
+
103
+ context 'without expressions' do
104
+ let(:expressions) { [] }
105
+
106
+ let(:expected_message) { 'No expressions given' }
107
+
108
+ it_should_behave_like 'an invalid cli run'
109
+ end
110
+
111
+ context 'with code filter and missing argument' do
112
+ let(:arguments) { %w[--code] }
113
+ let(:expected_message) { 'missing argument: --code' }
114
+
115
+ it_should_behave_like 'an invalid cli run'
116
+ end
117
+
118
+ context 'with include help flag' do
119
+ let(:flags) { %w[--help] }
120
+
121
+ before do
122
+ expect($stdout).to receive(:puts).with(expected_message)
123
+ expect(Kernel).to receive(:exit).with(0)
124
+ end
125
+
126
+ it_should_behave_like 'a cli parser'
127
+
128
+ let(:expected_message) do
129
+ strip_indent(<<-MESSAGE)
130
+ usage: mutant [options] MATCH_EXPRESSION ...
131
+ Environment:
132
+ --zombie Run mutant zombified
133
+ -I, --include DIRECTORY Add DIRECTORY to $LOAD_PATH
134
+ -r, --require NAME Require file with NAME
135
+
136
+ Options:
137
+ --score COVERAGE Fail unless COVERAGE is not reached exactly
138
+ --use STRATEGY Use STRATEGY for killing mutations
139
+ --ignore-subject PATTERN Ignore subjects that match PATTERN
140
+ --code CODE Scope execution to subjects with CODE
141
+ --fail-fast Fail fast
142
+ --version Print mutants version
143
+ -d, --debug Enable debugging output
144
+ -h, --help Show this message
145
+ MESSAGE
146
+ end
147
+ end
148
+
149
+ context 'with include flag' do
150
+ let(:flags) { %w[--include foo] }
151
+
152
+ it_should_behave_like 'a cli parser'
153
+
154
+ it 'configures includes' do
155
+ expect(subject.config.includes).to eql(%w[foo])
156
+ end
157
+ end
158
+
159
+ context 'with use flag' do
160
+ let(:flags) { %w[--use rspec] }
161
+
162
+ it_should_behave_like 'a cli parser'
163
+
164
+ let(:expected_integration) { Mutant::Integration::Rspec2.new }
165
+ end
166
+
167
+ context 'with version flag' do
168
+ let(:flags) { %w[--version] }
169
+
170
+ before do
171
+ expect(Kernel).to receive(:exit).with(0)
172
+ expect($stdout).to receive(:puts).with("mutant-#{Mutant::VERSION}")
173
+ end
174
+
175
+ it_should_behave_like 'a cli parser'
176
+ end
177
+
178
+ context 'with score flag' do
179
+ let(:flags) { %w[--score 99.5] }
180
+
181
+ it_should_behave_like 'a cli parser'
182
+
183
+ it 'configures expected coverage' do
184
+ expect(subject.config.expected_coverage).to eql(99.5)
185
+ end
186
+ end
187
+
188
+ context 'with require flag' do
189
+ let(:flags) { %w[--require foo] }
190
+
191
+ it_should_behave_like 'a cli parser'
192
+
193
+ it 'configures requires' do
194
+ expect(subject.config.requires).to eql(%w[foo])
195
+ end
196
+ end
197
+
198
+ context 'with subject-ignore flag' do
199
+ let(:flags) { %w[--ignore-subject Foo::Bar] }
200
+
201
+ let(:expected_matcher_config) do
202
+ default_matcher_config.update(subject_ignores: [Mutant::Expression.parse('Foo::Bar')])
203
+ end
204
+
205
+ it_should_behave_like 'a cli parser'
206
+ end
207
+
208
+ context 'with fail-fast flag' do
209
+ let(:flags) { %w[--fail-fast] }
210
+
211
+ it_should_behave_like 'a cli parser'
212
+
213
+ it 'sets the fail fast option' do
214
+ expect(subject.config.fail_fast).to be(true)
215
+ end
216
+ end
217
+
218
+ context 'with debug flag' do
219
+ let(:flags) { %w[--debug] }
220
+
221
+ it_should_behave_like 'a cli parser'
222
+
223
+ it 'sets the debug option' do
224
+ expect(subject.config.debug).to be(true)
225
+ end
226
+ end
227
+
228
+ context 'with zombie flag' do
229
+ let(:flags) { %w[--zombie] }
230
+
231
+ it_should_behave_like 'a cli parser'
232
+
233
+ it 'sets the zombie option' do
234
+ expect(subject.config.zombie).to be(true)
235
+ end
236
+ end
237
+
238
+ context 'with subject code filter' do
239
+ let(:flags) { %w[--code faa --code bbb] }
240
+
241
+ let(:expected_matcher_config) do
242
+ default_matcher_config.update(subject_selects: [[:code, 'faa'], [:code, 'bbb']])
243
+ end
244
+
245
+ it_should_behave_like 'a cli parser'
246
+ end
247
+ end
248
+ end