mutant 0.8.19 → 0.8.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +46 -8
  3. data/Changelog.md +4 -0
  4. data/Gemfile.lock +10 -12
  5. data/README.md +22 -250
  6. data/config/flay.yml +1 -1
  7. data/docs/concurrency.md +39 -0
  8. data/docs/known-problems.md +44 -0
  9. data/docs/limitations.md +50 -0
  10. data/docs/mutant-minitest.md +147 -0
  11. data/docs/mutant-rspec.md +62 -0
  12. data/docs/nomenclature.md +82 -0
  13. data/docs/reading-reports.md +74 -0
  14. data/lib/mutant.rb +4 -3
  15. data/lib/mutant/env.rb +2 -2
  16. data/lib/mutant/expression/namespace.rb +3 -1
  17. data/lib/mutant/runner/sink.rb +2 -2
  18. data/lib/mutant/timer.rb +21 -0
  19. data/lib/mutant/version.rb +1 -1
  20. data/mutant-minitest.gemspec +22 -0
  21. data/mutant.gemspec +4 -5
  22. data/spec/integration/mutant/corpus_spec.rb +1 -7
  23. data/spec/integration/mutant/minitest_spec.rb +10 -0
  24. data/spec/integration/mutant/rspec_spec.rb +1 -1
  25. data/spec/integrations.yml +14 -0
  26. data/spec/shared/framework_integration_behavior.rb +8 -5
  27. data/spec/support/corpus.rb +20 -14
  28. data/spec/unit/mutant/clock_monotonic_spec.rb +52 -0
  29. data/spec/unit/mutant/env_spec.rb +2 -2
  30. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +1 -1
  31. data/spec/unit/mutant/integration/rspec_spec.rb +1 -1
  32. data/spec/unit/mutant/reporter/cli_spec.rb +1 -1
  33. data/spec/unit/mutant/runner/sink_spec.rb +1 -1
  34. data/test_app/Gemfile.minitest +6 -0
  35. data/test_app/{Gemfile.rspec3.6 → Gemfile.rspec3.8} +2 -2
  36. data/test_app/test/unit/test_app/literal_test.rb +16 -0
  37. metadata +23 -26
  38. data/spec/rcov.opts +0 -7
  39. data/spec/support/rb_bug.rb +0 -18
  40. data/test_app/Gemfile.rspec3.4 +0 -7
  41. data/test_app/Gemfile.rspec3.5 +0 -7
@@ -4,15 +4,15 @@ require 'abstract_type'
4
4
  require 'adamantium'
5
5
  require 'anima'
6
6
  require 'concord'
7
- require 'digest/sha1'
8
7
  require 'diff/lcs'
9
8
  require 'diff/lcs/hunk'
9
+ require 'digest/sha1'
10
+ require 'etc'
10
11
  require 'equalizer'
11
12
  require 'ice_nine'
12
13
  require 'morpher'
13
14
  require 'open3'
14
15
  require 'optparse'
15
- require 'parallel'
16
16
  require 'parser'
17
17
  require 'parser/current'
18
18
  require 'pathname'
@@ -177,6 +177,7 @@ require 'mutant/expression/method'
177
177
  require 'mutant/expression/methods'
178
178
  require 'mutant/expression/namespace'
179
179
  require 'mutant/test'
180
+ require 'mutant/timer'
180
181
  require 'mutant/integration'
181
182
  require 'mutant/selector'
182
183
  require 'mutant/selector/expression'
@@ -228,7 +229,7 @@ module Mutant
228
229
  marshal: Marshal,
229
230
  process: Process
230
231
  ),
231
- jobs: ::Parallel.processor_count,
232
+ jobs: Etc.nprocessors,
232
233
  kernel: Kernel,
233
234
  load_path: $LOAD_PATH,
234
235
  matcher: Matcher::Config::DEFAULT,
@@ -42,7 +42,7 @@ module Mutant
42
42
  #
43
43
  # rubocop:disable MethodLength
44
44
  def run_mutation_tests(mutation)
45
- start = Time.now
45
+ start = Timer.now
46
46
  tests = selector.call(mutation.subject)
47
47
 
48
48
  config.isolation.call do
@@ -53,7 +53,7 @@ module Mutant
53
53
  Result::Test.new(
54
54
  output: error.message,
55
55
  passed: false,
56
- runtime: Time.now - start,
56
+ runtime: Timer.now - start,
57
57
  tests: tests
58
58
  )
59
59
  end
@@ -44,7 +44,9 @@ module Mutant
44
44
  #
45
45
  # @return [Integer]
46
46
  def match_length(expression)
47
- if @recursion_pattern.match?(expression.syntax)
47
+ if eql?(expression)
48
+ syntax.length
49
+ elsif @recursion_pattern.match?(expression.syntax)
48
50
  scope_name.length
49
51
  else
50
52
  0
@@ -10,7 +10,7 @@ module Mutant
10
10
  # @return [undefined]
11
11
  def initialize(*)
12
12
  super
13
- @start = Time.now
13
+ @start = Timer.now
14
14
  @subject_results = {}
15
15
  end
16
16
 
@@ -20,7 +20,7 @@ module Mutant
20
20
  def status
21
21
  Result::Env.new(
22
22
  env: env,
23
- runtime: Time.now - @start,
23
+ runtime: Timer.now - @start,
24
24
  subject_results: @subject_results.values
25
25
  )
26
26
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module Timer
5
+ # Monotonic elapsed time of block execution
6
+ #
7
+ # @return [Float]
8
+ def self.elapsed
9
+ start = now
10
+ yield
11
+ now - start
12
+ end
13
+
14
+ # The now monotonic time
15
+ #
16
+ # @return [Float]
17
+ def self.now
18
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
+ end
20
+ end # Timer
21
+ end # Mutant
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.8.19'.freeze
5
+ VERSION = '0.8.20'.freeze
6
6
  end # Mutant
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('lib/mutant/version', __dir__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'mutant-minitest'
7
+ gem.version = Mutant::VERSION.dup
8
+ gem.authors = ['Markus Schirp']
9
+ gem.email = %w[mbj@schirp-dso.com]
10
+ gem.description = 'Minitest integration for mutant'
11
+ gem.summary = gem.description
12
+ gem.homepage = 'https://github.com/mbj/mutant'
13
+ gem.license = 'MIT'
14
+
15
+ gem.require_paths = %w[lib]
16
+ gem.files = `git ls-files -- lib/mutant/{minitest,/integration/minitest.rb}`.split("\n")
17
+ gem.test_files = `git ls-files -- spec/integration/mutant/minitest.rb`.split("\n")
18
+ gem.extra_rdoc_files = %w[LICENSE]
19
+
20
+ gem.add_runtime_dependency('minitest', '~> 5.11')
21
+ gem.add_runtime_dependency('mutant', "~> #{gem.version}")
22
+ end
@@ -14,9 +14,9 @@ Gem::Specification.new do |gem|
14
14
 
15
15
  gem.require_paths = %w[lib]
16
16
 
17
- mutant_integration_files = `git ls-files -- lib/mutant/integration/*.rb`.split("\n")
17
+ exclusion = `git ls-files -- lib/mutant/{minitest,integration}`.split("\n")
18
18
 
19
- gem.files = `git ls-files`.split("\n") - mutant_integration_files
19
+ gem.files = `git ls-files`.split("\n") - exclusion
20
20
  gem.test_files = `git ls-files -- spec/{unit,integration}`.split("\n")
21
21
  gem.extra_rdoc_files = %w[LICENSE]
22
22
  gem.executables = %w[mutant]
@@ -33,13 +33,12 @@ Gem::Specification.new do |gem|
33
33
  gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
34
34
  gem.add_runtime_dependency('memoizable', '~> 0.4.2')
35
35
  gem.add_runtime_dependency('morpher', '~> 0.2.6')
36
- gem.add_runtime_dependency('parallel', '~> 1.3')
37
36
  gem.add_runtime_dependency('parser', '~> 2.5.1')
38
37
  gem.add_runtime_dependency('procto', '~> 0.0.2')
39
38
  gem.add_runtime_dependency('regexp_parser', '~> 1.2')
40
- gem.add_runtime_dependency('unparser', '~> 0.2.5')
39
+ gem.add_runtime_dependency('unparser', '~> 0.3.0')
41
40
 
42
41
  gem.add_development_dependency('bundler', '~> 1.10')
43
42
  gem.add_development_dependency('devtools', '~> 0.1.21')
44
- gem.add_development_dependency('ffi', '~> 1.9.6')
43
+ gem.add_development_dependency('parallel', '~> 1.3')
45
44
  end
@@ -1,12 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe 'Mutant on ruby corpus', mutant: false do
4
-
5
- before do
6
- skip 'Corpus test is deactivated on < 2.1' if RUBY_VERSION < '2.1'
7
- skip 'Corpus test is deactivated on RBX' if RUBY_ENGINE.eql?('rbx')
8
- end
9
-
10
4
  MutantSpec::Corpus::Project::ALL.select(&:mutation_generation).each do |project|
11
5
  specify "#{project.name} does not fail on mutation generation" do
12
6
  project.verify_mutation_generation
@@ -14,7 +8,7 @@ RSpec.describe 'Mutant on ruby corpus', mutant: false do
14
8
  end
15
9
 
16
10
  MutantSpec::Corpus::Project::ALL.select(&:mutation_coverage).each do |project|
17
- specify "#{project.name} does have expected mutation coverage" do
11
+ specify "#{project.name} (#{project.integration}) does have expected mutation coverage" do
18
12
  project.verify_mutation_coverage
19
13
  end
20
14
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'minitest integration', mutant: false do
4
+
5
+ let(:base_cmd) { 'bundle exec mutant -I test -I lib --require test_app --use minitest' }
6
+
7
+ let(:gemfile) { 'Gemfile.minitest' }
8
+
9
+ it_behaves_like 'framework integration'
10
+ end
@@ -4,7 +4,7 @@ RSpec.describe 'rspec integration', mutant: false do
4
4
 
5
5
  let(:base_cmd) { 'bundle exec mutant -I lib --require test_app --use rspec' }
6
6
 
7
- %w[3.4 3.5 3.6 3.7].each do |version|
7
+ %w[3.7 3.8].each do |version|
8
8
  context "RSpec #{version}" do
9
9
  let(:gemfile) { "Gemfile.rspec#{version}" }
10
10
 
@@ -3,6 +3,7 @@
3
3
  namespace: Rubyspec
4
4
  repo_uri: 'https://github.com/ruby/rubyspec.git'
5
5
  repo_ref: 'origin/master'
6
+ integration: mspec
6
7
  ruby_glob_pattern: '**/*_spec.rb'
7
8
  mutation_coverage: false
8
9
  mutation_generation: true
@@ -24,6 +25,7 @@
24
25
  namespace: Regexp
25
26
  repo_uri: 'https://github.com/ammar/regexp_parser.git'
26
27
  repo_ref: 'v1.2.0'
28
+ integration: rspec
27
29
  ruby_glob_pattern: '**/*.rb'
28
30
  mutation_coverage: false
29
31
  mutation_generation: true
@@ -34,6 +36,17 @@
34
36
  repo_uri: 'https://github.com/mbj/auom.git'
35
37
  repo_ref: 'origin/master'
36
38
  ruby_glob_pattern: '**/*.rb'
39
+ integration: rspec
40
+ mutation_coverage: true
41
+ mutation_generation: true
42
+ expected_errors: {}
43
+ exclude: []
44
+ - name: auom
45
+ namespace: AUOM
46
+ repo_uri: 'https://github.com/mbj/auom.git'
47
+ repo_ref: 'origin/master'
48
+ ruby_glob_pattern: '**/*.rb'
49
+ integration: minitest
37
50
  mutation_coverage: true
38
51
  mutation_generation: true
39
52
  expected_errors: {}
@@ -43,6 +56,7 @@
43
56
  repo_uri: 'https://github.com/dkubb/axiom.git'
44
57
  repo_ref: 'origin/master'
45
58
  ruby_glob_pattern: '**/*.rb'
59
+ integration: rspec
46
60
  mutation_coverage: false
47
61
  mutation_generation: true
48
62
  expected_errors: {}
@@ -6,22 +6,25 @@ RSpec.shared_examples_for 'framework integration' do
6
6
  end
7
7
 
8
8
  around do |example|
9
- Dir.chdir(TestApp.root) do
10
- Kernel.system('bundle', 'install', '--gemfile', gemfile) || fail('Bundle install failed!')
11
- example.run
9
+ Bundler.with_clean_env do
10
+ Dir.chdir(TestApp.root) do
11
+ Kernel.system('bundle', 'install', '--gemfile', gemfile) || fail('Bundle install failed!')
12
+ example.run
13
+ end
12
14
  end
13
15
  end
14
16
 
15
17
  specify 'it allows to kill mutations' do
16
- expect(Kernel.system("#{base_cmd} TestApp::Literal#string")).to be(true)
18
+ expect(system_with_gemfile("#{base_cmd} TestApp::Literal#string")).to be(true)
17
19
  end
18
20
 
19
21
  specify 'it allows to exclude mutations' do
20
22
  cli = <<-CMD.split("\n").join(' ')
21
23
  #{base_cmd}
24
+ --ignore-subject TestApp::Literal#uncovered_string
25
+ --
22
26
  TestApp::Literal#string
23
27
  TestApp::Literal#uncovered_string
24
- --ignore-subject TestApp::Literal#uncovered_string
25
28
  CMD
26
29
  expect(system_with_gemfile(cli)).to be(true)
27
30
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'morpher'
4
3
  require 'anima'
4
+ require 'morpher'
5
5
  require 'mutant'
6
+ require 'parallel'
6
7
 
7
8
  # @api private
8
9
  module MutantSpec
@@ -36,6 +37,7 @@ module MutantSpec
36
37
  :expected_errors,
37
38
  :mutation_coverage,
38
39
  :mutation_generation,
40
+ :integration,
39
41
  :name,
40
42
  :namespace,
41
43
  :repo_uri,
@@ -53,16 +55,18 @@ module MutantSpec
53
55
  def verify_mutation_coverage
54
56
  checkout
55
57
  Dir.chdir(repo_path) do
56
- install_mutant
57
- system(
58
- %W[
59
- bundle exec mutant
60
- --use rspec
61
- --include lib
62
- --require #{name}
63
- #{namespace}*
64
- ]
65
- )
58
+ Bundler.with_clean_env do
59
+ install_mutant
60
+ system(
61
+ %W[
62
+ bundle exec mutant
63
+ --use #{integration}
64
+ --include lib
65
+ --require #{name}
66
+ #{namespace}*
67
+ ]
68
+ )
69
+ end
66
70
  end
67
71
  end
68
72
 
@@ -75,7 +79,7 @@ module MutantSpec
75
79
  # otherwise
76
80
  def verify_mutation_generation
77
81
  checkout
78
- start = Time.now
82
+ start = Mutant::Timer.now
79
83
 
80
84
  options = {
81
85
  finish: method(:finish),
@@ -86,7 +90,7 @@ module MutantSpec
86
90
  total = Parallel.map(effective_ruby_paths, options, &method(:count_mutations_and_check_errors))
87
91
  .inject(DEFAULT_MUTATION_COUNT, :+)
88
92
 
89
- took = Time.now - start
93
+ took = Mutant::Timer.now - start
90
94
  puts MUTATION_GENERATION_MESSAGE % [total, took, total / took]
91
95
  self
92
96
  end
@@ -163,6 +167,7 @@ module MutantSpec
163
167
  repo_path.join('Gemfile').open('a') do |file|
164
168
  file << "gem 'mutant', path: '#{relative}'\n"
165
169
  file << "gem 'mutant-rspec', path: '#{relative}'\n"
170
+ file << "gem 'mutant-minitest', path: '#{relative}'\n"
166
171
  file << "eval_gemfile File.expand_path('#{relative.join('Gemfile.shared')}')\n"
167
172
  end
168
173
  lockfile = repo_path.join('Gemfile.lock')
@@ -188,7 +193,7 @@ module MutantSpec
188
193
  if ENV.key?('CI')
189
194
  CIRCLE_CI_CONTAINER_PROCESSES
190
195
  else
191
- Parallel.processor_count
196
+ Etc.nprocessors
192
197
  end
193
198
  end
194
199
 
@@ -312,6 +317,7 @@ module MutantSpec
312
317
  s(:key_symbolize, :ruby_glob_pattern, s(:guard, s(:primitive, String))),
313
318
  s(:key_symbolize, :name, s(:guard, s(:primitive, String))),
314
319
  s(:key_symbolize, :namespace, s(:guard, s(:primitive, String))),
320
+ s(:key_symbolize, :integration, s(:guard, s(:primitive, String))),
315
321
  s(:key_symbolize, :mutation_coverage,
316
322
  s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
317
323
  s(:key_symbolize, :mutation_generation,
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Mutant::Timer do
4
+ let(:events) { [] }
5
+
6
+ let(:times) { [1.0, 2.0] }
7
+
8
+ before do
9
+ allow(Process).to receive(:clock_gettime) do |argument|
10
+ expect(argument).to be(Process::CLOCK_MONOTONIC)
11
+
12
+ events << :clock_gettime
13
+
14
+ times.fetch(events.count(:clock_gettime).pred)
15
+ end
16
+ end
17
+
18
+ describe '.elapsed' do
19
+ def apply
20
+ described_class.elapsed { events << :yield }
21
+ end
22
+
23
+ it 'executes events in expected sequence' do
24
+ expect { apply }
25
+ .to change(events, :to_a)
26
+ .from([])
27
+ .to(%i[clock_gettime yield clock_gettime])
28
+ end
29
+
30
+ it 'returns elapsed time' do
31
+ expect(apply).to be(1.0)
32
+ end
33
+ end
34
+
35
+ describe '.now' do
36
+ def apply
37
+ described_class.now
38
+ end
39
+
40
+ it 'returns current monotonic time' do
41
+ expect(apply).to be(1.0)
42
+ expect(apply).to be(2.0)
43
+ end
44
+
45
+ it 'calls expected system API' do
46
+ expect { apply }
47
+ .to change(events, :to_a)
48
+ .from([])
49
+ .to(%i[clock_gettime])
50
+ end
51
+ end
52
+ end
@@ -57,7 +57,7 @@ RSpec.describe Mutant::Env do
57
57
  .with(mutation_subject)
58
58
  .and_return(tests)
59
59
 
60
- allow(Time).to receive_messages(now: Time.at(0))
60
+ allow(Mutant::Timer).to receive(:now).and_return(2.0, 3.0)
61
61
  end
62
62
 
63
63
  context 'when isolation does not raise error' do
@@ -91,7 +91,7 @@ RSpec.describe Mutant::Env do
91
91
  Mutant::Result::Test.new(
92
92
  output: 'test-error',
93
93
  passed: false,
94
- runtime: 0.0,
94
+ runtime: 1.0,
95
95
  tests: tests
96
96
  )
97
97
  end
@@ -22,7 +22,7 @@ RSpec.describe Mutant::Expression::Namespace::Recursive do
22
22
  context 'when other is an equivalent expression' do
23
23
  let(:other) { parse_expression(object.syntax) }
24
24
 
25
- it { should be(0) }
25
+ it { should be(object.syntax.length) }
26
26
  end
27
27
 
28
28
  context 'when other expression describes a shorter prefix' do