mutant 0.7.4 → 0.7.5

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -2
  3. data/Changelog.md +6 -2
  4. data/Gemfile +1 -0
  5. data/config/flay.yml +1 -1
  6. data/config/reek.yml +1 -1
  7. data/lib/mutant.rb +4 -3
  8. data/lib/mutant/cli.rb +2 -2
  9. data/lib/mutant/config.rb +2 -3
  10. data/lib/mutant/env.rb +29 -132
  11. data/lib/mutant/env/bootstrap.rb +155 -0
  12. data/lib/mutant/expression.rb +27 -0
  13. data/lib/mutant/expression/namespace.rb +2 -2
  14. data/lib/mutant/matcher/method.rb +1 -1
  15. data/lib/mutant/matcher/method/instance.rb +1 -1
  16. data/lib/mutant/mutation.rb +2 -30
  17. data/lib/mutant/mutator/node/begin.rb +1 -3
  18. data/lib/mutant/mutator/node/if.rb +2 -2
  19. data/lib/mutant/reporter/cli/printer.rb +5 -4
  20. data/lib/mutant/result.rb +1 -1
  21. data/lib/mutant/runner.rb +3 -3
  22. data/lib/mutant/runner/sink.rb +86 -53
  23. data/lib/mutant/selector.rb +17 -0
  24. data/lib/mutant/selector/expression.rb +28 -0
  25. data/lib/mutant/subject.rb +1 -19
  26. data/lib/mutant/version.rb +1 -1
  27. data/mutant.gemspec +3 -3
  28. data/spec/spec_helper.rb +1 -1
  29. data/spec/support/corpus.rb +5 -5
  30. data/spec/support/shared_context.rb +1 -6
  31. data/spec/unit/mutant/cli_spec.rb +2 -2
  32. data/spec/unit/mutant/env/boostrap_spec.rb +130 -0
  33. data/spec/unit/mutant/env_spec.rb +61 -49
  34. data/spec/unit/mutant/expression_spec.rb +13 -0
  35. data/spec/unit/mutant/matcher/filter_spec.rb +1 -1
  36. data/spec/unit/mutant/matcher/method/instance_spec.rb +1 -1
  37. data/spec/unit/mutant/matcher/method/singleton_spec.rb +1 -1
  38. data/spec/unit/mutant/mutation_spec.rb +0 -36
  39. data/spec/unit/mutant/reporter/trace_spec.rb +1 -1
  40. data/spec/unit/mutant/require_highjack_spec.rb +2 -4
  41. data/spec/unit/mutant/result/subject_spec.rb +2 -1
  42. data/spec/unit/mutant/runner/{sink_spec.rb → sink/mutation_spec.rb} +1 -3
  43. data/spec/unit/mutant/runner_spec.rb +4 -3
  44. data/spec/unit/mutant/selector/expression_spec.rb +60 -0
  45. data/spec/unit/mutant/subject/method/instance_spec.rb +3 -5
  46. data/spec/unit/mutant/subject/method/singleton_spec.rb +2 -3
  47. data/spec/unit/mutant/subject_spec.rb +1 -53
  48. data/spec/unit/mutant/warning_filter_spec.rb +2 -4
  49. metadata +18 -15
  50. data/lib/mutant/line_trace.rb +0 -34
  51. data/spec/unit/mutant/line_trace_spec.rb +0 -38
  52. data/spec/unit/mutant/subject/context_spec.rb +0 -16
@@ -1,4 +1,4 @@
1
1
  module Mutant
2
2
  # The current mutant version
3
- VERSION = '0.7.4'.freeze
3
+ VERSION = '0.7.5'.freeze
4
4
  end # Mutant
@@ -21,16 +21,16 @@ Gem::Specification.new do |gem|
21
21
  gem.extra_rdoc_files = %w[TODO LICENSE]
22
22
  gem.executables = %w[mutant]
23
23
 
24
- gem.required_ruby_version = '>= 2.0.0'
24
+ gem.required_ruby_version = '>= 1.9.3'
25
25
 
26
- gem.add_runtime_dependency('parser', '~> 2.2.pre.7')
26
+ gem.add_runtime_dependency('parser', '~> 2.2.0.2')
27
27
  gem.add_runtime_dependency('ast', '~> 2.0')
28
28
  gem.add_runtime_dependency('diff-lcs', '~> 1.2')
29
29
  gem.add_runtime_dependency('parallel', '~> 1.3')
30
30
  gem.add_runtime_dependency('morpher', '~> 0.2.3')
31
31
  gem.add_runtime_dependency('procto', '~> 0.0.2')
32
32
  gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
33
- gem.add_runtime_dependency('unparser', '~> 0.1.16')
33
+ gem.add_runtime_dependency('unparser', '~> 0.2.2')
34
34
  gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
35
35
  gem.add_runtime_dependency('adamantium', '~> 0.2.0')
36
36
  gem.add_runtime_dependency('memoizable', '~> 0.4.2')
@@ -34,7 +34,7 @@ require 'test_app'
34
34
  module Fixtures
35
35
  TEST_CONFIG = Mutant::Config::DEFAULT.update(reporter: Mutant::Reporter::Trace.new)
36
36
  TEST_CACHE = Mutant::Cache.new
37
- TEST_ENV = Mutant::Env.new(TEST_CONFIG, TEST_CACHE)
37
+ TEST_ENV = Mutant::Env::Bootstrap.call(TEST_CONFIG, TEST_CACHE)
38
38
  end # Fixtures
39
39
 
40
40
  module ParserHelper
@@ -94,7 +94,7 @@ module Corpus
94
94
  TMP.mkdir unless TMP.directory?
95
95
  if repo_path.exist?
96
96
  Dir.chdir(repo_path) do
97
- system(%w[git fetch])
97
+ system(%w[git fetch origin])
98
98
  system(%w[git reset --hard])
99
99
  system(%w[git clean -f -d -x])
100
100
  system(%w[git checkout origin/master])
@@ -119,10 +119,10 @@ module Corpus
119
119
  def install_mutant
120
120
  return if noinstall?
121
121
  relative = ROOT.relative_path_from(repo_path)
122
- devtools = ROOT.join('Gemfile.devtools').read
123
- devtools << "gem 'mutant', path: '#{relative}'\n"
124
- devtools << "gem 'mutant-rspec', path: '#{relative}'\n"
125
- File.write(repo_path.join('Gemfile.devtools'), devtools)
122
+ repo_path.join('Gemfile').open('a') do |file|
123
+ file << "gem 'mutant', path: '#{relative}'\n"
124
+ file << "gem 'mutant-rspec', path: '#{relative}'\n"
125
+ end
126
126
  lockfile = repo_path.join('Gemfile.lock')
127
127
  lockfile.delete if lockfile.exist?
128
128
  system('bundle install')
@@ -25,7 +25,6 @@ module SharedContext
25
25
  let(:matchable_scopes) { double('matchable scopes', length: 10) }
26
26
  let(:test_a) { double('test a', identification: 'test-a') }
27
27
  let(:test_b) { double('test b', identification: 'test-b') }
28
- let(:actor_names) { [] }
29
28
  let(:message_sequence) { FakeActor::MessageSequence.new }
30
29
 
31
30
  let(:status) do
@@ -38,16 +37,11 @@ module SharedContext
38
37
 
39
38
  let(:config) do
40
39
  Mutant::Config::DEFAULT.update(
41
- actor_env: actor_env,
42
40
  jobs: 1,
43
41
  reporter: Mutant::Reporter::Trace.new
44
42
  )
45
43
  end
46
44
 
47
- let(:actor_env) do
48
- FakeActor::Env.new(message_sequence, actor_names)
49
- end
50
-
51
45
  let(:subject_a) do
52
46
  double(
53
47
  'subject a',
@@ -111,6 +105,7 @@ module SharedContext
111
105
  let(:subject_a_result) do
112
106
  Mutant::Result::Subject.new(
113
107
  subject: subject_a,
108
+ tests: [test_a],
114
109
  mutation_results: [mutation_a_result, mutation_b_result]
115
110
  )
116
111
  end
@@ -9,7 +9,7 @@ end
9
9
  RSpec.shared_examples_for 'a cli parser' do
10
10
  it { expect(subject.config.integration).to eql(expected_integration) }
11
11
  it { expect(subject.config.reporter).to eql(expected_reporter) }
12
- it { expect(subject.config.matcher_config).to eql(expected_matcher_config) }
12
+ it { expect(subject.config.matcher).to eql(expected_matcher_config) }
13
13
  end
14
14
 
15
15
  RSpec.describe Mutant::CLI do
@@ -25,7 +25,7 @@ RSpec.describe Mutant::CLI do
25
25
 
26
26
  before do
27
27
  expect(Mutant::CLI).to receive(:call).with(arguments).and_return(config)
28
- expect(Mutant::Env).to receive(:new).with(config).and_return(env)
28
+ expect(Mutant::Env::Bootstrap).to receive(:call).with(config).and_return(env)
29
29
  expect(Mutant::Runner).to receive(:call).with(env).and_return(report)
30
30
  end
31
31
 
@@ -0,0 +1,130 @@
1
+ RSpec.describe Mutant::Env::Bootstrap do
2
+ let(:config) do
3
+ Mutant::Config::DEFAULT.update(
4
+ jobs: 1,
5
+ reporter: Mutant::Reporter::Trace.new,
6
+ includes: [],
7
+ requires: [],
8
+ matcher: Mutant::Matcher::Config::DEFAULT
9
+ )
10
+ end
11
+
12
+ let(:expected_env) do
13
+ Mutant::Env.new(
14
+ cache: Mutant::Cache.new,
15
+ subjects: [],
16
+ matchable_scopes: [],
17
+ mutations: [],
18
+ config: config,
19
+ selector: Mutant::Selector::Expression.new(config.integration),
20
+ actor_env: Mutant::Actor::Env.new(Thread)
21
+ )
22
+ end
23
+
24
+ shared_examples_for 'bootstrap call' do
25
+ it { should eql(expected_env) }
26
+ end
27
+
28
+ let(:object_space_modules) { [] }
29
+
30
+ before do
31
+ allow(ObjectSpace).to receive(:each_object).with(Module).and_return(object_space_modules.each)
32
+ end
33
+
34
+ describe '.call' do
35
+ subject { described_class.call(config) }
36
+
37
+ context 'when Module#name calls result in exceptions' do
38
+ let(:invalid_class) do
39
+ Class.new do
40
+ def self.name
41
+ fail
42
+ end
43
+ end
44
+ end
45
+
46
+ let(:object_space_modules) { [invalid_class] }
47
+
48
+ after do
49
+ # Fix Class#name so other specs do not see this one
50
+ class << invalid_class
51
+ undef :name
52
+ def name
53
+ end
54
+ end
55
+ end
56
+
57
+ it 'warns via reporter' do
58
+ expected_warnings = [
59
+ "Class#name from: #{invalid_class} raised an error: RuntimeError. #{Mutant::Env::SEMANTICS_MESSAGE}"
60
+ ]
61
+
62
+ expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
63
+ end
64
+
65
+ include_examples 'bootstrap call'
66
+ end
67
+
68
+ context 'when includes are present' do
69
+ let(:config) { super().update(includes: %w[foo bar]) }
70
+
71
+ before do
72
+ %w[foo bar].each do |component|
73
+ expect($LOAD_PATH).to receive(:<<).with(component).and_return($LOAD_PATH)
74
+ end
75
+ end
76
+
77
+ include_examples 'bootstrap call'
78
+ end
79
+
80
+ context 'when Module#name does not return a String or nil' do
81
+ let(:invalid_class) do
82
+ Class.new do
83
+ def self.name
84
+ Object
85
+ end
86
+ end
87
+ end
88
+
89
+ let(:object_space_modules) { [invalid_class] }
90
+
91
+ after do
92
+ # Fix Class#name so other specs do not see this one
93
+ class << invalid_class
94
+ undef :name
95
+ def name
96
+ end
97
+ end
98
+ end
99
+
100
+ it 'warns via reporter' do
101
+
102
+ expected_warnings = [
103
+ "Class#name from: #{invalid_class.inspect} returned Object. #{Mutant::Env::SEMANTICS_MESSAGE}"
104
+ ]
105
+
106
+ expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
107
+ end
108
+
109
+ include_examples 'bootstrap call'
110
+ end
111
+
112
+ context 'when scope matches expression' do
113
+ let(:mutations) { [double('Mutation')] }
114
+ let(:subjects) { [double('Subject', mutations: mutations)] }
115
+
116
+ before do
117
+ expect(Mutant::Matcher::Compiler).to receive(:call).and_return(subjects)
118
+ end
119
+
120
+ let(:expected_env) do
121
+ super().update(
122
+ subjects: subjects,
123
+ mutations: mutations
124
+ )
125
+ end
126
+
127
+ include_examples 'bootstrap call'
128
+ end
129
+ end
130
+ end
@@ -1,67 +1,79 @@
1
1
  RSpec.describe Mutant::Env do
2
- let(:config) { Mutant::Config::DEFAULT.update(jobs: 1, reporter: Mutant::Reporter::Trace.new) }
2
+ context '#kill' do
3
+ let(:object) do
4
+ described_class.new(
5
+ config: config,
6
+ actor_env: Mutant::Actor::Env.new(Thread),
7
+ cache: Mutant::Cache.new,
8
+ selector: selector,
9
+ subjects: [],
10
+ mutations: [],
11
+ matchable_scopes: []
12
+ )
13
+ end
3
14
 
4
- context '.new' do
5
- subject { described_class.new(config) }
15
+ let(:config) do
16
+ Mutant::Config::DEFAULT.update(
17
+ isolation: isolation,
18
+ integration: integration
19
+ )
20
+ end
6
21
 
7
- context 'when Module#name calls result in exceptions' do
8
- it 'warns via reporter' do
9
- klass = Class.new do
10
- def self.name
11
- fail
12
- end
13
- end
22
+ let(:isolation) { Mutant::Isolation::None }
23
+ let(:integration) { double('Integration') }
24
+ let(:isolation) { double('Isolation') }
25
+ let(:mutation) { Mutant::Mutation::Evil.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
26
+ let(:wrapped_node) { double('Wrapped Node') }
27
+ let(:context) { double('Context') }
28
+ let(:test_a) { double('Test A') }
29
+ let(:test_b) { double('Test B') }
30
+ let(:tests) { [test_a, test_b] }
31
+ let(:selector) { double('Selector') }
14
32
 
15
- expected_warnings = [
16
- "Class#name from: #{klass} raised an error: RuntimeError. #{Mutant::Env::SEMANTICS_MESSAGE}"
17
- ]
33
+ let(:mutation_subject) do
34
+ double(
35
+ 'Subject',
36
+ identification: 'subject',
37
+ context: context,
38
+ source: 'original',
39
+ tests: tests
40
+ )
41
+ end
18
42
 
19
- expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
43
+ subject { object.kill(mutation) }
20
44
 
21
- # Fix Class#name so other specs do not see this one
22
- class << klass
23
- undef :name
24
- def name
25
- end
26
- end
27
- end
45
+ shared_examples_for 'mutation kill' do
46
+ it { should eql(Mutant::Result::Mutation.new(mutation: mutation, test_result: test_result)) }
28
47
  end
29
48
 
30
- context 'when Module#name does not return a String or nil' do
31
- it 'warns via reporter' do
32
- klass = Class.new do
33
- def self.name
34
- Object
35
- end
36
- end
37
-
38
- expected_warnings = ["Class#name from: #{klass.inspect} returned Object. #{Mutant::Env::SEMANTICS_MESSAGE}"]
49
+ before do
50
+ expect(selector).to receive(:call).with(mutation_subject).and_return(tests)
51
+ allow(Time).to receive(:now).and_return(Time.at(0))
52
+ end
39
53
 
40
- expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
54
+ context 'when isolation does not raise error' do
55
+ let(:test_result) { double('Test Result A', passed: false) }
41
56
 
42
- # Fix Class#name so other specs do not see this one
43
- class << klass
44
- undef :name
45
- def name
46
- end
47
- end
57
+ before do
58
+ expect(isolation).to receive(:call).and_yield.and_return(test_result)
59
+ expect(mutation_subject).to receive(:public?).and_return(true).ordered
60
+ expect(mutation_subject).to receive(:prepare).and_return(mutation_subject).ordered
61
+ expect(context).to receive(:root).with(s(:nil)).and_return(wrapped_node).ordered
62
+ expect(Mutant::Loader::Eval).to receive(:call).with(wrapped_node, mutation_subject).and_return(nil).ordered
63
+ expect(integration).to receive(:call).with(tests).and_return(test_result).ordered
48
64
  end
49
- end
50
- end
51
65
 
52
- context '#kill_mutation' do
53
- let(:object) { described_class.new(config) }
54
- let(:result) { double('Result') }
55
- let(:mutation) { double('Mutation') }
66
+ include_examples 'mutation kill'
67
+ end
56
68
 
57
- subject { object.kill_mutation(mutation) }
69
+ context 'when isolation does raise error' do
70
+ before do
71
+ expect(isolation).to receive(:call).and_raise(Mutant::Isolation::Error, 'test-error')
72
+ end
58
73
 
59
- before do
60
- expect(mutation).to receive(:kill).with(config.isolation, config.integration).and_return(result)
61
- end
74
+ let(:test_result) { Mutant::Result::Test.new(tests: tests, output: 'test-error', passed: false, runtime: 0.0) }
62
75
 
63
- it 'uses the configured integration and isolation to kill mutation' do
64
- should eql(Mutant::Result::Mutation.new(mutation: mutation, test_result: result))
76
+ include_examples 'mutation kill'
65
77
  end
66
78
  end
67
79
  end
@@ -44,6 +44,13 @@ RSpec.describe Mutant::Expression do
44
44
  it_should_behave_like 'an idempotent method'
45
45
  end
46
46
 
47
+ describe '#_dump' do
48
+ let(:object) { described_class.parse('Foo') }
49
+ subject { object._dump(double('Level')) }
50
+
51
+ it { should eql('Foo') }
52
+ end
53
+
47
54
  describe '.parse' do
48
55
  subject { object.parse(input) }
49
56
 
@@ -64,4 +71,10 @@ RSpec.describe Mutant::Expression do
64
71
  it { should eql(Mutant::Expression::Namespace::Exact.new('Foo')) }
65
72
  end
66
73
  end
74
+
75
+ describe '._load' do
76
+ subject { described_class._load('Foo') }
77
+
78
+ it { should eql(described_class.parse('Foo')) }
79
+ end
67
80
  end
@@ -6,7 +6,7 @@ RSpec.describe Mutant::Matcher::Filter do
6
6
  subject { object.each { |entry| yields << entry } }
7
7
 
8
8
  let(:matcher) { [subject_a, subject_b] }
9
- let(:predicate) { ->(subject) { subject.eql?(subject_a) } }
9
+ let(:predicate) { ->(node) { node.eql?(subject_a) } }
10
10
 
11
11
  let(:subject_a) { double('Subject A') }
12
12
  let(:subject_b) { double('Subject B') }
@@ -5,7 +5,7 @@ RSpec.describe Mutant::Matcher::Method::Instance do
5
5
  let(:reporter) { Fixtures::TEST_CONFIG.reporter }
6
6
 
7
7
  describe '#each' do
8
- subject { object.each { |subject| yields << subject } }
8
+ subject { object.each(&yields.method(:<<)) }
9
9
 
10
10
  let(:object) { described_class.build(env, scope, method) }
11
11
  let(:method) { scope.instance_method(method_name) }
@@ -1,6 +1,6 @@
1
1
  # rubocop:disable ClassAndModuleChildren
2
2
  RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
3
- subject { object.each { |subject| yields << subject } }
3
+ subject { object.each(&yields.method(:<<)) }
4
4
 
5
5
  let(:object) { described_class.new(env, scope, method) }
6
6
  let(:method) { scope.method(method_name) }
@@ -21,42 +21,6 @@ RSpec.describe Mutant::Mutation do
21
21
  let(:test_b) { double('Test B') }
22
22
  let(:tests) { [test_a, test_b] }
23
23
 
24
- describe '#kill' do
25
- let(:isolation) { Mutant::Isolation::None }
26
- let(:integration) { double('Integration') }
27
- let(:object) { Mutant::Mutation::Evil.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
28
- let(:wrapped_node) { double('Wrapped Node') }
29
-
30
- subject { object.kill(isolation, integration) }
31
-
32
- before do
33
- allow(Time).to receive(:now).and_return(Time.at(0))
34
- end
35
-
36
- context 'when isolation does not raise error' do
37
- let(:test_result) { double('Test Result A', passed: false) }
38
-
39
- before do
40
- expect(mutation_subject).to receive(:public?).and_return(true).ordered
41
- expect(mutation_subject).to receive(:prepare).and_return(mutation_subject).ordered
42
- expect(context).to receive(:root).with(s(:nil)).and_return(wrapped_node).ordered
43
- expect(Mutant::Loader::Eval).to receive(:call).with(wrapped_node, mutation_subject).and_return(nil).ordered
44
- expect(integration).to receive(:call).with(tests).and_return(test_result).ordered
45
- expect(test_result).to receive(:update).with(tests: tests).and_return(test_result).ordered
46
- end
47
-
48
- it { should eql(test_result) }
49
- end
50
-
51
- context 'when isolation does raise error' do
52
- before do
53
- expect(isolation).to receive(:call).and_raise(Mutant::Isolation::Error, 'test-error')
54
- end
55
-
56
- it { should eql(Mutant::Result::Test.new(tests: tests, output: 'test-error', passed: false, runtime: 0.0)) }
57
- end
58
- end
59
-
60
24
  describe '#code' do
61
25
  subject { object.code }
62
26