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
@@ -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') }