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
@@ -1,6 +1,4 @@
1
- require 'spec_helper'
2
-
3
- describe 'null integration' do
1
+ RSpec.describe 'null integration' do
4
2
 
5
3
  let(:base_cmd) { 'bundle exec mutant -I lib --require test_app "TestApp*"' }
6
4
 
@@ -1,6 +1,4 @@
1
- require 'spec_helper'
2
-
3
- describe 'rspec integration' do
1
+ RSpec.describe 'rspec integration' do
4
2
 
5
3
  let(:base_cmd) { 'bundle exec mutant -I lib --require test_app --use rspec' }
6
4
 
@@ -1,6 +1,4 @@
1
- require 'spec_helper'
2
-
3
- describe do
1
+ RSpec.describe do
4
2
 
5
3
  specify 'mutant should not crash for any node parser can generate' do
6
4
  Mutant::AST::Types::ALL.each do |type|
@@ -1,6 +1,4 @@
1
- require 'spec_helper'
2
-
3
- describe 'as a zombie' do
1
+ RSpec.describe 'as a zombie' do
4
2
  specify 'it allows to create zombie from mutant' do
5
3
  expect { Mutant.zombify }.to change { defined?(Zombie) }.from(nil).to('constant')
6
4
  expect(Zombie.constants).to include(:Mutant)
@@ -32,3 +32,10 @@
32
32
  mutation_generation: true
33
33
  exclude: []
34
34
  expect_coverage: 100.0
35
+ - name: axiom
36
+ namespace: Axiom
37
+ repo_uri: 'https://github.com/dkubb/axiom.git'
38
+ mutation_coverage: false
39
+ mutation_generation: true
40
+ exclude: []
41
+ expect_coverage: 100.0
@@ -1,4 +1,4 @@
1
- shared_examples_for 'a method matcher' do
1
+ RSpec.shared_examples_for 'a method matcher' do
2
2
 
3
3
  before { subject }
4
4
 
@@ -26,6 +26,7 @@ require 'devtools/spec_helper'
26
26
  require 'unparser/cli'
27
27
  require 'mutant'
28
28
  require 'mutant/meta'
29
+ Devtools.init_spec_helper
29
30
 
30
31
  $LOAD_PATH << File.join(TestApp.root, 'lib')
31
32
 
@@ -2,6 +2,7 @@ module CompressHelper
2
2
  def strip_indent(string)
3
3
  lines = string.lines
4
4
  match = /\A( *)/.match(lines.first)
5
+ return string unless match
5
6
  whitespaces = match[1].to_s.length
6
7
  lines.map { |line| line[whitespaces..-1] }.join
7
8
  end
@@ -0,0 +1,239 @@
1
+ require 'morpher'
2
+ require 'anima'
3
+
4
+ # Namespace module for corpus testing
5
+ module Corpus
6
+ # Project under corpus test
7
+ # rubocop:disable ClassLength
8
+ ROOT = Pathname.new(__FILE__).parent.parent.parent
9
+ TMP = ROOT.join('tmp').freeze
10
+
11
+ class Project
12
+ MUTEX = Mutex.new
13
+ include Adamantium, Anima.new(
14
+ :name,
15
+ :repo_uri,
16
+ :exclude,
17
+ :mutation_coverage,
18
+ :mutation_generation,
19
+ :namespace,
20
+ :expect_coverage
21
+ )
22
+
23
+ # Verify mutation coverage
24
+ #
25
+ # @return [self]
26
+ # if successful
27
+ #
28
+ # @raise [Exception]
29
+ #
30
+ def verify_mutation_coverage
31
+ checkout
32
+ Dir.chdir(repo_path) do
33
+ Bundler.with_clean_env do
34
+ install_mutant
35
+ system(%W[bundle exec mutant --use rspec -I lib -r #{name} --score #{expect_coverage} #{namespace}*])
36
+ end
37
+ end
38
+ end
39
+
40
+ # Verify mutation generation
41
+ #
42
+ # @return [self]
43
+ # if successful
44
+ #
45
+ # @raise [Exception]
46
+ # otherwise
47
+ #
48
+ # rubocop:disable MethodLength
49
+ def verify_mutation_generation
50
+ checkout
51
+ start = Time.now
52
+ paths = Pathname.glob(repo_path.join('**/*.rb')).sort_by(&:size).reverse
53
+ options = {
54
+ finish: method(:finish),
55
+ start: method(:start),
56
+ in_processes: parallel_processes
57
+ }
58
+ total = Parallel.map(paths, options) do |path|
59
+ count = 0
60
+ node =
61
+ begin
62
+ Parser::CurrentRuby.parse(path.read)
63
+ rescue EncodingError, ArgumentError
64
+ nil # Make rubocop happy
65
+ end
66
+ if node
67
+ Mutant::Mutator::Node.each(node) do
68
+ count += 1
69
+ end
70
+ end
71
+ count
72
+ end.inject(0, :+)
73
+ took = Time.now - start
74
+ puts format(
75
+ 'Total Mutations/Time/Parse-Errors: %s/%0.2fs - %0.2f/s',
76
+ total,
77
+ took,
78
+ total / took
79
+ )
80
+ self
81
+ end
82
+
83
+ # Checkout repository
84
+ #
85
+ # @return [self]
86
+ #
87
+ # @api private
88
+ #
89
+ def checkout
90
+ return self if noinstall?
91
+ TMP.mkdir unless TMP.directory?
92
+ if repo_path.exist?
93
+ Dir.chdir(repo_path) do
94
+ system(%w[git fetch])
95
+ system(%w[git reset --hard])
96
+ system(%w[git clean -f -d -x])
97
+ system(%w[git checkout origin/master])
98
+ system(%w[git reset --hard])
99
+ system(%w[git clean -f -d -x])
100
+ end
101
+ else
102
+ system(%W[git clone #{repo_uri} #{repo_path}])
103
+ end
104
+ self
105
+ end
106
+ memoize :checkout
107
+
108
+ private
109
+
110
+ # Install mutant
111
+ #
112
+ # @return [undefined]
113
+ #
114
+ # @api private
115
+ #
116
+ def install_mutant
117
+ return if noinstall?
118
+ relative = ROOT.relative_path_from(repo_path)
119
+ devtools = ROOT.join('Gemfile.devtools').read
120
+ devtools << "gem 'mutant', path: '#{relative}'\n"
121
+ devtools << "gem 'mutant-rspec', path: '#{relative}'\n"
122
+ File.write(repo_path.join('Gemfile.devtools'), devtools)
123
+ lockfile = repo_path.join('Gemfile.lock')
124
+ lockfile.delete if lockfile.exist?
125
+ system('bundle install')
126
+ end
127
+
128
+ # Not in the docs. Number from chatting with their support.
129
+ CIRCLE_CI_CONTAINER_PROCESSES = 2
130
+
131
+ # Return number of parallel processes to use
132
+ #
133
+ # @return [Fixnum]
134
+ #
135
+ # @api private
136
+ #
137
+ def parallel_processes
138
+ case
139
+ when Devtools.circle_ci?
140
+ CIRCLE_CI_CONTAINER_PROCESSES
141
+ else
142
+ Parallel.processor_count
143
+ end
144
+ end
145
+
146
+ # Return repository path
147
+ #
148
+ # @return [Pathname]
149
+ #
150
+ # @api private
151
+ #
152
+ def repo_path
153
+ TMP.join(name)
154
+ end
155
+
156
+ # Test if installation should be skipped
157
+ #
158
+ # @return [Boolean]
159
+ #
160
+ # @api private
161
+ #
162
+ def noinstall?
163
+ ENV.key?('NOINSTALL')
164
+ end
165
+
166
+ # Print start progress
167
+ #
168
+ # @param [Pathname] path
169
+ # @param [Fixnum] _index
170
+ #
171
+ # @return [undefined]
172
+ #
173
+ def start(path, _index)
174
+ MUTEX.synchronize do
175
+ puts format('Starting - %s', path)
176
+ end
177
+ end
178
+
179
+ # Print finish progress
180
+ #
181
+ # @param [Pathname] path
182
+ # @param [Fixnum] _index
183
+ # @param [Fixnum] count
184
+ #
185
+ # @return [undefined]
186
+ #
187
+ def finish(path, _index, count)
188
+ MUTEX.synchronize do
189
+ puts format('Mutations - %4i - %s', count, path)
190
+ end
191
+ end
192
+
193
+ # Helper method to execute system commands
194
+ #
195
+ # @param [Array<String>] arguments
196
+ #
197
+ # @api private
198
+ #
199
+ def system(arguments)
200
+ return if Kernel.system(*arguments)
201
+ if block_given?
202
+ yield
203
+ else
204
+ fail 'System command failed!'
205
+ end
206
+ end
207
+
208
+ LOADER = Morpher.build do
209
+ s(:block,
210
+ s(:guard, s(:primitive, Array)),
211
+ s(:map,
212
+ s(:block,
213
+ s(:guard, s(:primitive, Hash)),
214
+ s(:hash_transform,
215
+ s(:key_symbolize, :repo_uri, s(:guard, s(:primitive, String))),
216
+ s(:key_symbolize, :name, s(:guard, s(:primitive, String))),
217
+ s(:key_symbolize, :namespace, s(:guard, s(:primitive, String))),
218
+ s(:key_symbolize, :expect_coverage, s(:guard, s(:primitive, Float))),
219
+ s(:key_symbolize, :mutation_coverage,
220
+ s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
221
+ s(:key_symbolize, :mutation_generation,
222
+ s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
223
+ s(:key_symbolize, :exclude, s(:map, s(:guard, s(:primitive, String))))
224
+ ),
225
+ s(:load_attribute_hash,
226
+ # NOTE: The domain param has no DSL currently!
227
+ Morpher::Evaluator::Transformer::Domain::Param.new(
228
+ Project,
229
+ [:repo_uri, :name, :exclude, :mutation_coverage, :mutation_generation]
230
+ )
231
+ )
232
+ )
233
+ )
234
+ )
235
+ end
236
+
237
+ ALL = LOADER.call(YAML.load_file(ROOT.join('spec', 'integrations.yml')))
238
+ end # Project
239
+ end # Corpus
@@ -1,5 +1,3 @@
1
- # encoding: UTF-8
2
-
3
1
  class MutationVerifier
4
2
  include Adamantium::Flat, Concord.new(:original_node, :expected, :generated)
5
3
 
@@ -28,7 +26,7 @@ class MutationVerifier
28
26
 
29
27
  private
30
28
 
31
- # Return unexpected mutationso
29
+ # Return unexpected mutations
32
30
  #
33
31
  # @return [Array<Parser::AST::Node>]
34
32
  #
@@ -71,7 +69,7 @@ private
71
69
  ].join("\n")
72
70
  end
73
71
 
74
- # Return missing mutationso
72
+ # Return missing mutations
75
73
  #
76
74
  # @return [Array<Parser::AST::Node>]
77
75
  #
@@ -1,6 +1,4 @@
1
- require 'spec_helper'
2
-
3
- shared_examples_for 'an invalid cli run' do
1
+ RSpec.shared_examples_for 'an invalid cli run' do
4
2
  it 'raises error' do
5
3
  expect do
6
4
  subject
@@ -8,13 +6,13 @@ shared_examples_for 'an invalid cli run' do
8
6
  end
9
7
  end
10
8
 
11
- shared_examples_for 'a cli parser' do
9
+ RSpec.shared_examples_for 'a cli parser' do
12
10
  it { expect(subject.config.integration).to eql(expected_integration) }
13
11
  it { expect(subject.config.reporter).to eql(expected_reporter) }
14
12
  it { expect(subject.config.matcher_config).to eql(expected_matcher_config) }
15
13
  end
16
14
 
17
- describe Mutant::CLI do
15
+ RSpec.describe Mutant::CLI do
18
16
  let(:object) { described_class }
19
17
 
20
18
  describe '.run' do
@@ -38,7 +36,7 @@ describe Mutant::CLI do
38
36
  end
39
37
  end
40
38
 
41
- context 'when report signalls error' do
39
+ context 'when report signals error' do
42
40
  let(:report_success) { false }
43
41
 
44
42
  it 'exits failure' do
@@ -67,18 +65,16 @@ describe Mutant::CLI do
67
65
  subject { object.new(arguments) }
68
66
 
69
67
  # 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 }
68
+ let(:expected_filter) { Morpher.evaluator(s(:true)) }
69
+ let(:expected_integration) { Mutant::Integration::Null.new }
70
+ let(:expected_reporter) { Mutant::Config::DEFAULT.reporter }
71
+ let(:expected_matcher_config) { default_matcher_config }
74
72
 
75
73
  let(:default_matcher_config) do
76
74
  Mutant::Matcher::Config::DEFAULT
77
75
  .update(match_expressions: expressions.map(&Mutant::Expression.method(:parse)))
78
76
  end
79
77
 
80
- let(:ns) { Mutant::Matcher }
81
-
82
78
  let(:flags) { [] }
83
79
  let(:expressions) { %w[TestApp*] }
84
80
 
@@ -132,6 +128,7 @@ Environment:
132
128
  --zombie Run mutant zombified
133
129
  -I, --include DIRECTORY Add DIRECTORY to $LOAD_PATH
134
130
  -r, --require NAME Require file with NAME
131
+ -j, --jobs NUMBER Number of kill processes. Defaults to number of processors.
135
132
 
136
133
  Options:
137
134
  --score COVERAGE Fail unless COVERAGE is not reached exactly
@@ -161,7 +158,7 @@ Options:
161
158
 
162
159
  it_should_behave_like 'a cli parser'
163
160
 
164
- let(:expected_integration) { Mutant::Integration::Rspec2.new }
161
+ let(:expected_integration) { Mutant::Integration::Rspec.build }
165
162
  end
166
163
 
167
164
  context 'with version flag' do
@@ -175,6 +172,16 @@ Options:
175
172
  it_should_behave_like 'a cli parser'
176
173
  end
177
174
 
175
+ context 'with jobs flag' do
176
+ let(:flags) { %w[--jobs 0] }
177
+
178
+ it_should_behave_like 'a cli parser'
179
+
180
+ it 'configures expected coverage' do
181
+ expect(subject.config.processes).to eql(0)
182
+ end
183
+ end
184
+
178
185
  context 'with score flag' do
179
186
  let(:flags) { %w[--score 99.5] }
180
187
 
@@ -1,6 +1,4 @@
1
- require 'spec_helper'
2
-
3
- describe Mutant::Context, '#root' do
1
+ RSpec.describe Mutant::Context, '#root' do
4
2
  subject { object.root }
5
3
 
6
4
  let(:object) { described_class.allocate }
@@ -1,6 +1,4 @@
1
- require 'spec_helper'
2
-
3
- describe Mutant::Context::Scope, '#root' do
1
+ RSpec.describe Mutant::Context::Scope, '#root' do
4
2
  subject { object.root(node) }
5
3
 
6
4
  let(:object) { described_class.new(TestApp::Literal, path) }
@@ -1,6 +1,4 @@
1
- require 'spec_helper'
2
-
3
- describe Mutant::Context::Scope, '#unqualified_name' do
1
+ RSpec.describe Mutant::Context::Scope, '#unqualified_name' do
4
2
  subject { object.unqualified_name }
5
3
 
6
4
  let(:path) { double('Path') }