mutant 0.5.24 → 0.5.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +8 -0
  3. data/config/flay.yml +1 -1
  4. data/config/flog.yml +1 -1
  5. data/config/reek.yml +15 -13
  6. data/lib/mutant.rb +28 -12
  7. data/lib/mutant/ast/meta.rb +0 -10
  8. data/lib/mutant/ast/named_children.rb +1 -0
  9. data/lib/mutant/ast/types.rb +5 -5
  10. data/lib/mutant/cli.rb +84 -64
  11. data/lib/mutant/config.rb +7 -39
  12. data/lib/mutant/delegator.rb +2 -0
  13. data/lib/mutant/env.rb +119 -16
  14. data/lib/mutant/expression.rb +8 -2
  15. data/lib/mutant/expression/method.rb +6 -16
  16. data/lib/mutant/expression/methods.rb +5 -5
  17. data/lib/mutant/expression/namespace.rb +7 -7
  18. data/lib/mutant/integration.rb +0 -10
  19. data/lib/mutant/isolation.rb +41 -15
  20. data/lib/mutant/matcher/chain.rb +1 -17
  21. data/lib/mutant/matcher/compiler.rb +108 -0
  22. data/lib/mutant/matcher/config.rb +28 -0
  23. data/lib/mutant/matcher/method.rb +1 -1
  24. data/lib/mutant/matcher/namespace.rb +5 -52
  25. data/lib/mutant/matcher/null.rb +1 -1
  26. data/lib/mutant/matcher/scope.rb +1 -1
  27. data/lib/mutant/mutation.rb +29 -13
  28. data/lib/mutant/mutator/node.rb +2 -12
  29. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
  30. data/lib/mutant/reporter/cli.rb +0 -2
  31. data/lib/mutant/reporter/cli/printer.rb +14 -0
  32. data/lib/mutant/reporter/cli/progress.rb +1 -3
  33. data/lib/mutant/reporter/cli/progress/config.rb +5 -9
  34. data/lib/mutant/reporter/cli/progress/env.rb +30 -0
  35. data/lib/mutant/reporter/cli/progress/noop.rb +4 -1
  36. data/lib/mutant/reporter/cli/progress/result.rb +12 -0
  37. data/lib/mutant/reporter/cli/progress/result/mutation.rb +45 -0
  38. data/lib/mutant/reporter/cli/progress/result/subject.rb +54 -0
  39. data/lib/mutant/reporter/cli/progress/subject.rb +7 -90
  40. data/lib/mutant/reporter/cli/registry.rb +2 -0
  41. data/lib/mutant/reporter/cli/report/env.rb +92 -0
  42. data/lib/mutant/reporter/cli/report/mutation.rb +58 -77
  43. data/lib/mutant/reporter/cli/report/subject.rb +4 -3
  44. data/lib/mutant/reporter/cli/report/test.rb +28 -0
  45. data/lib/mutant/reporter/null.rb +1 -1
  46. data/lib/mutant/reporter/trace.rb +16 -3
  47. data/lib/mutant/result.rb +302 -0
  48. data/lib/mutant/runner.rb +77 -123
  49. data/lib/mutant/subject.rb +32 -16
  50. data/lib/mutant/subject/method.rb +0 -15
  51. data/lib/mutant/subject/method/instance.rb +3 -3
  52. data/lib/mutant/version.rb +1 -1
  53. data/lib/mutant/warning_expectation.rb +12 -5
  54. data/spec/integration/mutant/corpus_spec.rb +1 -1
  55. data/spec/spec_helper.rb +5 -1
  56. data/spec/unit/mutant/cli_spec.rb +248 -0
  57. data/spec/unit/mutant/expression/namespace/flat_spec.rb +1 -1
  58. data/spec/unit/mutant/expression_spec.rb +55 -0
  59. data/spec/unit/mutant/integration_spec.rb +0 -5
  60. data/spec/unit/mutant/isolation_spec.rb +36 -5
  61. data/spec/unit/mutant/matcher/chain_spec.rb +1 -13
  62. data/spec/unit/mutant/matcher/compiler_spec.rb +95 -0
  63. data/spec/unit/mutant/matcher/filter_spec.rb +31 -0
  64. data/spec/unit/mutant/matcher/method/instance_spec.rb +33 -2
  65. data/spec/unit/mutant/matcher/method/singleton_spec.rb +1 -1
  66. data/spec/unit/mutant/matcher/methods/instance_spec.rb +1 -1
  67. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +1 -1
  68. data/spec/unit/mutant/matcher/namespace_spec.rb +10 -6
  69. data/spec/unit/mutant/matcher/null_spec.rb +26 -0
  70. data/spec/unit/mutant/reporter/cli_spec.rb +337 -0
  71. data/spec/unit/mutant/reporter/null_spec.rb +12 -0
  72. data/spec/unit/mutant/runner_spec.rb +130 -0
  73. data/spec/unit/mutant/subject/context_spec.rb +4 -3
  74. data/spec/unit/mutant/subject/method/instance_spec.rb +5 -3
  75. data/spec/unit/mutant/subject/method/singleton_spec.rb +3 -2
  76. data/spec/unit/mutant/subject_spec.rb +36 -1
  77. data/spec/unit/mutant/test_spec.rb +25 -0
  78. data/spec/unit/mutant/warning_expectation.rb +11 -8
  79. data/spec/unit/mutant_spec.rb +11 -2
  80. metadata +27 -28
  81. data/lib/mutant/killer.rb +0 -44
  82. data/lib/mutant/matcher/builder.rb +0 -142
  83. data/lib/mutant/mutation/evil.rb +0 -23
  84. data/lib/mutant/mutation/neutral.rb +0 -18
  85. data/lib/mutant/reporter/cli/progress/mutation.rb +0 -46
  86. data/lib/mutant/reporter/cli/report/config.rb +0 -116
  87. data/lib/mutant/rspec.rb +0 -0
  88. data/lib/mutant/runner/config.rb +0 -138
  89. data/lib/mutant/runner/killer.rb +0 -75
  90. data/lib/mutant/runner/mutation.rb +0 -78
  91. data/lib/mutant/runner/subject.rb +0 -85
  92. data/lib/mutant/test/report.rb +0 -59
  93. data/spec/unit/mutant/cli_new_spec.rb +0 -147
  94. data/spec/unit/mutant/cli_run_spec.rb +0 -46
  95. data/spec/unit/mutant/runner/config_spec.rb +0 -157
  96. data/spec/unit/mutant/runner/mutation_spec.rb +0 -101
  97. data/spec/unit/mutant/runner/subject_spec.rb +0 -59
  98. data/spec/unit/mutant/subject/mutations_spec.rb +0 -23
  99. data/spec/unit/mutant/subject/node_spec.rb +0 -17
@@ -9,7 +9,7 @@ describe Mutant::Expression::Namespace::Exact do
9
9
  describe '#matcher' do
10
10
  subject { object.matcher(cache) }
11
11
 
12
- it { should eql(Mutant::Matcher::Scope.new(cache, TestApp::Literal)) }
12
+ it { should eql(Mutant::Matcher::Scope.new(cache, TestApp::Literal, object)) }
13
13
  end
14
14
 
15
15
  describe '#match_length' do
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mutant::Expression do
4
+ let(:object) { described_class }
5
+
6
+ describe '.try_parse' do
7
+ subject { object.try_parse(input) }
8
+
9
+ context 'on nonsense' do
10
+ let(:input) { 'foo bar' }
11
+
12
+ it { should be(nil) }
13
+ end
14
+
15
+ context 'on a valid expression' do
16
+ let(:input) { 'Foo' }
17
+
18
+ it { should eql(Mutant::Expression::Namespace::Exact.new('Foo')) }
19
+ end
20
+
21
+ context 'on ambigious expression' do
22
+ class ExpressionA < Mutant::Expression
23
+ register(/\Atest-syntax\z/)
24
+ end
25
+
26
+ class ExpressionB < Mutant::Expression
27
+ register(/^test-syntax$/)
28
+ end
29
+
30
+ let(:input) { 'test-syntax' }
31
+
32
+ it 'raises an exception' do
33
+ expect { subject }.to raise_error(Mutant::Expression::AmbigousExpressionError, 'Ambigous expression: "test-syntax"')
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '.parse' do
39
+ subject { object.parse(input) }
40
+
41
+ context 'on nonsense' do
42
+ let(:input) { 'foo bar' }
43
+
44
+ it 'raises an exception' do
45
+ expect { subject }.to raise_error(Mutant::Expression::InvalidExpressionError, 'Expression: "foo bar" is not valid')
46
+ end
47
+ end
48
+
49
+ context 'on a valid expression' do
50
+ let(:input) { 'Foo' }
51
+
52
+ it { should eql(Mutant::Expression::Namespace::Exact.new('Foo')) }
53
+ end
54
+ end
55
+ end
@@ -8,11 +8,6 @@ describe Mutant::Integration do
8
8
 
9
9
  let(:object) { class_under_test.new }
10
10
 
11
- describe '#teardown' do
12
- subject { object.teardown }
13
- it_should_behave_like 'a command method'
14
- end
15
-
16
11
  describe '#setup' do
17
12
  subject { object.setup }
18
13
  it_should_behave_like 'a command method'
@@ -1,18 +1,49 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Mutant::Isolation do
3
+ describe Mutant::Isolation::None do
4
+ before do
5
+ @initial = 1
6
+ end
7
+
8
+ describe '.run' do
9
+ let(:object) { described_class }
10
+
11
+ it 'does not isolate side effects' do
12
+ object.call { @initial = 2 }
13
+ expect(@initial).to be(2)
14
+ end
15
+
16
+ it 'return block value' do
17
+ expect(object.call { :foo }).to be(:foo)
18
+ end
19
+
20
+ it 'wraps *all* exceptions' do
21
+ expect { object.call { fail } }.to raise_error(Mutant::Isolation::Error)
22
+ end
23
+
24
+ end
25
+ end
26
+
27
+ describe Mutant::Isolation::Fork do
28
+ before do
29
+ @initial = 1
30
+ end
31
+
4
32
  describe '.run' do
5
33
  let(:object) { described_class }
6
34
 
7
- it 'isolates global effects from process' do
8
- expect(defined?(::TestConstant)).to be(nil)
9
- object.call { ::TestConstant = 1 }
10
- expect(defined?(::TestConstant)).to be(nil)
35
+ it 'does isolate side effects' do
36
+ object.call { @initial = 2 }
37
+ expect(@initial).to be(1)
11
38
  end
12
39
 
13
40
  it 'return block value' do
14
41
  expect(object.call { :foo }).to be(:foo)
15
42
  end
16
43
 
44
+ it 'wraps Parallel::DeadWorker exceptions' do
45
+ expect { object.call { fail Parallel::DeadWorker } }.to raise_error(Mutant::Isolation::Error)
46
+ end
47
+
17
48
  end
18
49
  end
@@ -5,6 +5,7 @@ describe Mutant::Matcher::Chain do
5
5
  let(:object) { described_class.new(matchers) }
6
6
 
7
7
  describe '#each' do
8
+ let(:yields) { [] }
8
9
  subject { object.each { |entry| yields << entry } }
9
10
 
10
11
  let(:matchers) { [matcher_a, matcher_b] }
@@ -26,21 +27,8 @@ describe Mutant::Matcher::Chain do
26
27
  end
27
28
  end
28
29
 
29
- let(:yields) { [] }
30
-
31
30
  it 'should yield subjects' do
32
31
  expect { subject }.to change { yields }.from([]).to([subject_a, subject_b])
33
32
  end
34
33
  end
35
-
36
- describe '#matchers' do
37
- subject { object.matchers }
38
-
39
- let(:matchers) { double('Matchers') }
40
-
41
- it { should be(matchers) }
42
-
43
- it_should_behave_like 'an idempotent method'
44
- end
45
-
46
34
  end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mutant::Matcher::Compiler do
4
+ let(:object) { described_class }
5
+
6
+ let(:env) { Fixtures::TEST_ENV }
7
+
8
+ let(:expression_a) { Mutant::Expression.parse('Foo*') }
9
+ let(:expression_b) { Mutant::Expression.parse('Bar*') }
10
+
11
+ let(:matcher_a) { expression_a.matcher(env) }
12
+ let(:matcher_b) { expression_b.matcher(env) }
13
+
14
+ let(:expected_matcher) do
15
+ Mutant::Matcher::Filter.new(expected_positive_matcher, expected_predicate)
16
+ end
17
+
18
+ let(:expected_predicate) do
19
+ Morpher.compile(s(:true))
20
+ end
21
+
22
+ describe '.call' do
23
+ subject { object.call(env, matcher_config.update(attributes)) }
24
+
25
+ let(:matcher_config) { Mutant::Matcher::Config::DEFAULT }
26
+
27
+ context 'on empty config' do
28
+ let(:attributes) { {} }
29
+
30
+ let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([]) }
31
+
32
+ it { should eql(expected_matcher) }
33
+ end
34
+
35
+ context 'on config with match expression' do
36
+ context 'and no filter' do
37
+ let(:attributes) do
38
+ { match_expressions: [expression_a] }
39
+ end
40
+
41
+ let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([matcher_a]) }
42
+
43
+ it { should eql(expected_matcher) }
44
+ end
45
+
46
+ context 'and a subject filter' do
47
+ let(:attributes) do
48
+ {
49
+ match_expressions: [expression_a],
50
+ subject_ignores: [expression_b]
51
+ }
52
+ end
53
+
54
+ let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([matcher_a]) }
55
+
56
+ let(:expected_predicate) do
57
+ Morpher::Evaluator::Predicate::Negation.new(
58
+ Morpher::Evaluator::Predicate::Boolean::Or.new([
59
+ described_class::SubjectPrefix.new(expression_b)
60
+ ])
61
+ )
62
+ end
63
+
64
+ it { should eql(expected_matcher) }
65
+ end
66
+
67
+ context 'and an attribute filter' do
68
+ let(:attributes) do
69
+ {
70
+ match_expressions: [expression_a],
71
+ subject_selects: [[:code, 'foo']]
72
+ }
73
+ end
74
+
75
+ let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([matcher_a]) }
76
+
77
+ let(:expected_predicate) do
78
+ Morpher::Evaluator::Predicate::Boolean::Or.new([
79
+ Morpher.compile(s(:eql, s(:attribute, :code), s(:static, 'foo')))
80
+ ])
81
+ end
82
+
83
+ it { should eql(expected_matcher) }
84
+ end
85
+ end
86
+
87
+ context 'on config with multiple match expressions' do
88
+ let(:attributes) do
89
+ { match_expressions: [expression_a, expression_b] }
90
+ end
91
+
92
+ let(:expected_positive_matcher) { Mutant::Matcher::Chain.new([matcher_a, matcher_b]) }
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mutant::Matcher::Filter do
4
+ let(:object) { described_class.new(matcher, predicate) }
5
+
6
+ describe '#each' do
7
+ let(:yields) { [] }
8
+ subject { object.each { |entry| yields << entry } }
9
+
10
+ let(:matcher) { [subject_a, subject_b] }
11
+ let(:predicate) { ->(subject) { subject.eql?(subject_a) } }
12
+
13
+ let(:subject_a) { double('Subject A') }
14
+ let(:subject_b) { double('Subject B') }
15
+
16
+ # it_should_behave_like 'an #each method'
17
+ context 'with no block' do
18
+ subject { object.each }
19
+
20
+ it { should be_instance_of(to_enum.class) }
21
+
22
+ it 'yields the expected values' do
23
+ expect(subject.to_a).to eql(object.to_a)
24
+ end
25
+ end
26
+
27
+ it 'should yield subjects' do
28
+ expect { subject }.to change { yields }.from([]).to([subject_a])
29
+ end
30
+ end
31
+ end
@@ -3,12 +3,13 @@ require 'spec_helper'
3
3
  # rubocop:disable ClassAndModuleChildren
4
4
  describe Mutant::Matcher::Method::Instance do
5
5
 
6
- let(:env) { Fixtures::BOOT_ENV }
6
+ let(:env) { Fixtures::TEST_ENV }
7
+ let(:reporter) { Fixtures::TEST_CONFIG.reporter }
7
8
 
8
9
  describe '#each' do
9
10
  subject { object.each { |subject| yields << subject } }
10
11
 
11
- let(:object) { described_class.new(env, scope, method) }
12
+ let(:object) { described_class.build(env, scope, method) }
12
13
  let(:method) { scope.instance_method(method_name) }
13
14
  let(:yields) { [] }
14
15
  let(:namespace) { self.class }
@@ -25,6 +26,23 @@ describe Mutant::Matcher::Method::Instance do
25
26
  node.children[1]
26
27
  end
27
28
 
29
+ context 'when method is defined without source location' do
30
+ let(:scope) { Module }
31
+ let(:method) { scope.instance_method(:object_id) }
32
+
33
+ it 'does not emit matcher' do
34
+ subject
35
+ expect(yields.length).to be(0)
36
+ end
37
+
38
+ it 'does warn' do
39
+ subject
40
+ expect(reporter.warn_calls.last).to(
41
+ eql("#{method.inspect} does not have valid source location unable to emit matcher")
42
+ )
43
+ end
44
+ end
45
+
28
46
  context 'when method is defined once' do
29
47
  let(:base) { __LINE__ }
30
48
  class self::Foo
@@ -36,6 +54,19 @@ describe Mutant::Matcher::Method::Instance do
36
54
  it_should_behave_like 'a method matcher'
37
55
  end
38
56
 
57
+ context 'when method is defined once with a memoizer' do
58
+ let(:base) { __LINE__ }
59
+ class self::Foo
60
+ def bar; end
61
+ include Adamantium
62
+ memoize :bar
63
+ end
64
+
65
+ let(:method_line) { 2 }
66
+
67
+ it_should_behave_like 'a method matcher'
68
+ end
69
+
39
70
  context 'when method is defined multiple times' do
40
71
  context 'on differend lines' do
41
72
  let(:base) { __LINE__ }
@@ -6,7 +6,7 @@ describe Mutant::Matcher::Method::Singleton, '#each' do
6
6
 
7
7
  let(:object) { described_class.new(env, scope, method) }
8
8
  let(:method) { scope.method(method_name) }
9
- let(:env) { Fixtures::BOOT_ENV }
9
+ let(:env) { Fixtures::TEST_ENV }
10
10
  let(:yields) { [] }
11
11
  let(:namespace) { self.class }
12
12
  let(:scope) { self.class::Foo }
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Mutant::Matcher::Methods::Instance, '#each' do
4
4
  let(:object) { described_class.new(env, Foo) }
5
- let(:env) { Fixtures::BOOT_ENV }
5
+ let(:env) { Fixtures::TEST_ENV }
6
6
 
7
7
  subject { object.each { |matcher| yields << matcher } }
8
8
 
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Mutant::Matcher::Methods::Singleton, '#each' do
4
4
  let(:object) { described_class.new(env, Foo) }
5
- let(:env) { Fixtures::BOOT_ENV }
5
+ let(:env) { Fixtures::TEST_ENV }
6
6
 
7
7
  subject { object.each { |matcher| yields << matcher } }
8
8
 
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Mutant::Matcher::Namespace do
4
4
  let(:object) { described_class.new(env, Mutant::Expression.parse('TestApp*')) }
5
5
  let(:yields) { [] }
6
- let(:env) { Fixtures::BOOT_ENV }
6
+ let(:env) { double('Env') }
7
7
 
8
8
  subject { object.each { |item| yields << item } }
9
9
 
@@ -16,13 +16,17 @@ describe Mutant::Matcher::Namespace do
16
16
  let(:subject_b) { double('SubjectB') }
17
17
 
18
18
  before do
19
- allow(Mutant::Matcher::Methods::Singleton).to receive(:new).with(env, singleton_a).and_return([subject_a])
20
- allow(Mutant::Matcher::Methods::Instance).to receive(:new).with(env, singleton_a).and_return([])
21
-
22
19
  allow(Mutant::Matcher::Methods::Singleton).to receive(:new).with(env, singleton_b).and_return([subject_b])
23
20
  allow(Mutant::Matcher::Methods::Instance).to receive(:new).with(env, singleton_b).and_return([])
24
21
 
25
- allow(ObjectSpace).to receive(:each_object).with(Module).and_return([singleton_a, singleton_b, singleton_c])
22
+ allow(Mutant::Matcher::Methods::Singleton).to receive(:new).with(env, singleton_a).and_return([subject_a])
23
+ allow(Mutant::Matcher::Methods::Instance).to receive(:new).with(env, singleton_a).and_return([])
24
+
25
+ allow(env).to receive(:matchable_scopes).and_return(
26
+ [singleton_a, singleton_b, singleton_c].map do |scope|
27
+ Mutant::Matcher::Scope.new(env, scope, Mutant::Expression.parse(scope.name))
28
+ end
29
+ )
26
30
  end
27
31
 
28
32
  context 'with no block' do
@@ -36,7 +40,7 @@ describe Mutant::Matcher::Namespace do
36
40
  end
37
41
 
38
42
  it 'should yield subjects' do
39
- expect { subject }.to change { yields }.from([]).to([subject_b, subject_a])
43
+ expect { subject }.to change { yields }.from([]).to([subject_a, subject_b])
40
44
  end
41
45
  end
42
46
  end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mutant::Matcher::Null do
4
+ let(:object) { described_class.new }
5
+
6
+ describe '#each' do
7
+ let(:yields) { [] }
8
+
9
+ subject { object.each { |entry| yields << entry } }
10
+
11
+ # it_should_behave_like 'an #each method'
12
+ context 'with no block' do
13
+ subject { object.each }
14
+
15
+ it { should be_instance_of(to_enum.class) }
16
+
17
+ it 'yields the expected values' do
18
+ expect(subject.to_a).to eql(object.to_a)
19
+ end
20
+ end
21
+
22
+ it 'should yield subjects' do
23
+ expect { subject }.not_to change { yields }.from([])
24
+ end
25
+ end
26
+ end