mutant 0.7.4 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
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