mutant 0.6.6 → 0.6.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +5 -0
- data/Gemfile.devtools +2 -1
- data/config/flay.yml +1 -1
- data/lib/mutant/ast.rb +1 -1
- data/lib/mutant/cli.rb +1 -1
- data/lib/mutant/context/scope.rb +3 -13
- data/lib/mutant/env.rb +3 -13
- data/lib/mutant/expression/method.rb +1 -1
- data/lib/mutant/isolation.rb +1 -1
- data/lib/mutant/loader.rb +1 -2
- data/lib/mutant/meta/example/dsl.rb +4 -4
- data/lib/mutant/mutation.rb +11 -11
- data/lib/mutant/mutator.rb +2 -2
- data/lib/mutant/mutator/registry.rb +22 -57
- data/lib/mutant/mutator/util/array.rb +0 -2
- data/lib/mutant/mutator/util/symbol.rb +0 -2
- data/lib/mutant/subject.rb +0 -12
- data/lib/mutant/subject/method/instance.rb +1 -6
- data/lib/mutant/version.rb +1 -1
- data/spec/integration/mutant/corpus_spec.rb +1 -1
- data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -1
- data/spec/unit/mutant/cli_spec.rb +6 -5
- data/spec/unit/mutant/context_spec.rb +56 -0
- data/spec/unit/mutant/env_spec.rb +49 -0
- data/spec/unit/mutant/isolation_spec.rb +20 -0
- data/spec/unit/mutant/loader/eval_spec.rb +0 -4
- data/spec/unit/mutant/mutation_spec.rb +18 -3
- data/spec/unit/mutant/mutator/registry_spec.rb +57 -0
- data/spec/unit/mutant/subject/method/instance_spec.rb +42 -0
- data/spec/unit/mutant/subject_spec.rb +56 -2
- data/spec/unit/mutant/{warning_expectation.rb → warning_expectation_spec.rb} +10 -2
- data/test_app/Gemfile.devtools +2 -1
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11e3b674e08512b58bd258c3ab1434bdf684d71a
|
4
|
+
data.tar.gz: f1c0559335e982f5348d6fb71c4ed2f7ae01b5b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7decc3ff8888ea325265bfaf8ea098ee4656a9254978c72b0561997944cdf287d003a724ff9511ec2d15355e708b070f10c7d5561a107bfdfb5eec21bcd3263
|
7
|
+
data.tar.gz: 9e229f14f8b9b808ab72293ac15855627cd821cdf151413aa38dfd5fd3815c64c11eb37665cca847652c98db90c51f50ec53c2afa1c6e27ae2f9bf3185fd3094
|
data/Changelog.md
CHANGED
data/Gemfile.devtools
CHANGED
@@ -8,6 +8,7 @@ group :development do
|
|
8
8
|
|
9
9
|
platform :rbx do
|
10
10
|
gem 'rubysl-singleton', '~> 2.0.0'
|
11
|
+
gem 'rubinius-coverage', '~> 2.0.1'
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
@@ -38,7 +39,7 @@ group :metrics do
|
|
38
39
|
gem 'flay', '~> 2.5.0'
|
39
40
|
gem 'flog', '~> 4.2.1'
|
40
41
|
gem 'reek', '~> 1.3.7'
|
41
|
-
gem 'rubocop', '~> 0.26.
|
42
|
+
gem 'rubocop', '~> 0.26.0'
|
42
43
|
gem 'simplecov', '~> 0.7.1'
|
43
44
|
gem 'yardstick', '~> 0.9.9'
|
44
45
|
|
data/config/flay.yml
CHANGED
data/lib/mutant/ast.rb
CHANGED
@@ -44,7 +44,7 @@ module Mutant
|
|
44
44
|
# @api private
|
45
45
|
#
|
46
46
|
def self.find_last_path(node, &predicate)
|
47
|
-
|
47
|
+
fail ArgumentError, 'block expected' unless block_given?
|
48
48
|
path = []
|
49
49
|
walk(node, [node]) do |candidate, stack|
|
50
50
|
if predicate.call(candidate, &predicate)
|
data/lib/mutant/cli.rb
CHANGED
@@ -22,7 +22,7 @@ module Mutant
|
|
22
22
|
# @api private
|
23
23
|
#
|
24
24
|
def self.run(arguments)
|
25
|
-
|
25
|
+
Runner.call(Env.new(call(arguments))).success? ? EXIT_SUCCESS : EXIT_FAILURE
|
26
26
|
rescue Error => exception
|
27
27
|
$stderr.puts(exception.message)
|
28
28
|
EXIT_FAILURE
|
data/lib/mutant/context/scope.rb
CHANGED
@@ -45,12 +45,12 @@ module Mutant
|
|
45
45
|
def self.wrap(scope, node)
|
46
46
|
name = s(:const, nil, scope.name.split(NAMESPACE_DELIMITER).last.to_sym)
|
47
47
|
case scope
|
48
|
-
when
|
48
|
+
when Class
|
49
49
|
s(:class, name, nil, node)
|
50
|
-
when
|
50
|
+
when Module
|
51
51
|
s(:module, name, node)
|
52
52
|
else
|
53
|
-
|
53
|
+
fail "Cannot wrap scope: #{scope.inspect}"
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
@@ -79,16 +79,6 @@ module Mutant
|
|
79
79
|
name_nesting.last
|
80
80
|
end
|
81
81
|
|
82
|
-
# Return name
|
83
|
-
#
|
84
|
-
# @return [String]
|
85
|
-
#
|
86
|
-
# @api private
|
87
|
-
#
|
88
|
-
def name
|
89
|
-
scope.name
|
90
|
-
end
|
91
|
-
|
92
82
|
# Return match expressions
|
93
83
|
#
|
94
84
|
# @return [Enumerable<Expression>]
|
data/lib/mutant/env.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Mutant
|
2
2
|
# Abstract base class for mutant environments
|
3
3
|
class Env
|
4
|
-
include Adamantium::Flat, Concord::Public.new(:config, :cache)
|
4
|
+
include Adamantium::Flat, Concord::Public.new(:config, :cache)
|
5
5
|
|
6
6
|
# Return new env
|
7
7
|
#
|
@@ -30,16 +30,6 @@ module Mutant
|
|
30
30
|
initialize_mutations
|
31
31
|
end
|
32
32
|
|
33
|
-
# Run mutant producing a report on configured env
|
34
|
-
#
|
35
|
-
# @return [Report]
|
36
|
-
#
|
37
|
-
# @api private
|
38
|
-
#
|
39
|
-
def run
|
40
|
-
Runner.call(self)
|
41
|
-
end
|
42
|
-
|
43
33
|
# Print warning message
|
44
34
|
#
|
45
35
|
# @param [String]
|
@@ -96,7 +86,7 @@ module Mutant
|
|
96
86
|
def scope_name(scope)
|
97
87
|
scope.name
|
98
88
|
rescue => exception
|
99
|
-
warn("
|
89
|
+
warn("#{scope.class}#name from: #{scope.inspect} raised an error: #{exception.inspect} fix your lib to follow normal ruby semantics!")
|
100
90
|
nil
|
101
91
|
end
|
102
92
|
|
@@ -116,7 +106,7 @@ module Mutant
|
|
116
106
|
name = scope_name(scope) or return
|
117
107
|
|
118
108
|
unless name.is_a?(String)
|
119
|
-
warn("#{scope.class}#name from: #{scope.inspect}
|
109
|
+
warn("#{scope.class}#name from: #{scope.inspect} returned #{name.inspect} instead String or nil. Fix your lib to follow normal ruby semantics!")
|
120
110
|
return
|
121
111
|
end
|
122
112
|
|
@@ -25,7 +25,7 @@ module Mutant
|
|
25
25
|
methods_matcher = MATCHERS.fetch(scope_symbol).new(env, scope)
|
26
26
|
method = methods_matcher.methods.detect do |meth|
|
27
27
|
meth.name.equal?(method_name)
|
28
|
-
end or
|
28
|
+
end or fail NameError, "Cannot find method #{method_name}"
|
29
29
|
methods_matcher.matcher.build(env, scope, method)
|
30
30
|
end
|
31
31
|
|
data/lib/mutant/isolation.rb
CHANGED
data/lib/mutant/loader.rb
CHANGED
@@ -40,7 +40,7 @@ module Mutant
|
|
40
40
|
# @api private
|
41
41
|
#
|
42
42
|
def example
|
43
|
-
|
43
|
+
fail 'source not defined' unless @source
|
44
44
|
Example.new(@file, @source, @expected)
|
45
45
|
end
|
46
46
|
|
@@ -55,7 +55,7 @@ module Mutant
|
|
55
55
|
# @api private
|
56
56
|
#
|
57
57
|
def source(input)
|
58
|
-
|
58
|
+
fail 'source already defined' if @source
|
59
59
|
@source = node(input)
|
60
60
|
|
61
61
|
self
|
@@ -72,7 +72,7 @@ module Mutant
|
|
72
72
|
def mutation(input)
|
73
73
|
node = node(input)
|
74
74
|
if @expected.include?(node)
|
75
|
-
|
75
|
+
fail "Node for input: #{input.inspect} is already expected"
|
76
76
|
end
|
77
77
|
@expected << node
|
78
78
|
|
@@ -108,7 +108,7 @@ module Mutant
|
|
108
108
|
when Parser::AST::Node
|
109
109
|
input
|
110
110
|
else
|
111
|
-
|
111
|
+
fail "Cannot coerce to node: #{source.inspect}"
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
data/lib/mutant/mutation.rb
CHANGED
@@ -7,17 +7,6 @@ module Mutant
|
|
7
7
|
CODE_DELIMITER = "\0".freeze
|
8
8
|
CODE_RANGE = (0..4).freeze
|
9
9
|
|
10
|
-
# Return mutated root node
|
11
|
-
#
|
12
|
-
# @return [Parser::AST::Node]
|
13
|
-
#
|
14
|
-
# @api private
|
15
|
-
#
|
16
|
-
def root
|
17
|
-
subject.root(node)
|
18
|
-
end
|
19
|
-
memoize :root
|
20
|
-
|
21
10
|
# Insert mutated node
|
22
11
|
#
|
23
12
|
# FIXME: Cache subject visibility in a better way! Ideally dont mutate it
|
@@ -30,6 +19,7 @@ module Mutant
|
|
30
19
|
#
|
31
20
|
def insert
|
32
21
|
subject.public?
|
22
|
+
subject.prepare
|
33
23
|
Loader::Eval.call(root, subject)
|
34
24
|
self
|
35
25
|
end
|
@@ -102,6 +92,16 @@ module Mutant
|
|
102
92
|
end
|
103
93
|
memoize :sha1
|
104
94
|
|
95
|
+
# Return mutated root node
|
96
|
+
#
|
97
|
+
# @return [Parser::AST::Node]
|
98
|
+
#
|
99
|
+
# @api private
|
100
|
+
#
|
101
|
+
def root
|
102
|
+
subject.context.root(node)
|
103
|
+
end
|
104
|
+
|
105
105
|
# Evil mutation that should case mutations to fail tests
|
106
106
|
class Evil < self
|
107
107
|
|
data/lib/mutant/mutator.rb
CHANGED
@@ -16,7 +16,7 @@ module Mutant
|
|
16
16
|
#
|
17
17
|
def self.each(input, parent = nil, &block)
|
18
18
|
return to_enum(__method__, input, parent) unless block_given?
|
19
|
-
|
19
|
+
REGISTRY.lookup(input).new(input, parent, block)
|
20
20
|
|
21
21
|
self
|
22
22
|
end
|
@@ -29,7 +29,7 @@ module Mutant
|
|
29
29
|
#
|
30
30
|
def self.handle(*types)
|
31
31
|
types.each do |type|
|
32
|
-
|
32
|
+
REGISTRY.register(type, self)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
private_class_method :handle
|
@@ -1,27 +1,34 @@
|
|
1
1
|
module Mutant
|
2
2
|
class Mutator
|
3
3
|
# Registry for mutators
|
4
|
-
|
4
|
+
class Registry
|
5
5
|
|
6
|
-
#
|
7
|
-
|
6
|
+
# Initialize object
|
7
|
+
#
|
8
|
+
# @return [undefined]
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
#
|
12
|
+
def initialize
|
13
|
+
@registry = {}
|
14
|
+
end
|
8
15
|
|
9
|
-
# Raised when the type is
|
10
|
-
|
16
|
+
# Raised when the type is an invalid type
|
17
|
+
RegistryError = Class.new(TypeError)
|
11
18
|
|
12
19
|
# Register mutator class for AST node class
|
13
20
|
#
|
14
21
|
# @param [Symbol] type
|
15
|
-
# @param [Class]
|
22
|
+
# @param [Class:Mutator] mutator
|
16
23
|
#
|
17
24
|
# @api private
|
18
25
|
#
|
19
26
|
# @return [self]
|
20
27
|
#
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
registry[type] =
|
28
|
+
def register(type, mutator)
|
29
|
+
fail RegistryError, "Invalid type registration: #{type}" unless AST::Types::ALL.include?(type)
|
30
|
+
fail RegistryError, "Duplicate type registration: #{type}" if @registry.key?(type)
|
31
|
+
@registry[type] = mutator
|
25
32
|
self
|
26
33
|
end
|
27
34
|
|
@@ -36,58 +43,16 @@ module Mutant
|
|
36
43
|
#
|
37
44
|
# @api private
|
38
45
|
#
|
39
|
-
def
|
46
|
+
def lookup(node)
|
40
47
|
type = node.type
|
41
|
-
registry.fetch(type) do
|
42
|
-
|
48
|
+
@registry.fetch(type) do
|
49
|
+
fail RegistryError, "No mutator to handle: #{type.inspect}"
|
43
50
|
end
|
44
51
|
end
|
45
52
|
|
46
|
-
|
47
|
-
#
|
48
|
-
# @return [Hash]
|
49
|
-
#
|
50
|
-
# @api private
|
51
|
-
#
|
52
|
-
def self.registry
|
53
|
-
@registry ||= {}
|
54
|
-
end
|
55
|
-
private_class_method :registry
|
56
|
-
|
57
|
-
# Assert the node type is valid
|
58
|
-
#
|
59
|
-
# @param [Symbol] type
|
60
|
-
#
|
61
|
-
# @return [undefined]
|
62
|
-
#
|
63
|
-
# @raise [InvalidTypeError]
|
64
|
-
# raised when the node type is invalid
|
65
|
-
#
|
66
|
-
# @api private
|
67
|
-
#
|
68
|
-
def self.assert_valid_type(type)
|
69
|
-
unless AST::Types::ALL.include?(type) || type.is_a?(Class)
|
70
|
-
raise InvalidTypeError, "invalid type registration: #{type}"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
private_class_method :assert_valid_type
|
53
|
+
end # Registry
|
74
54
|
|
75
|
-
|
76
|
-
#
|
77
|
-
# @return [undefined]
|
78
|
-
#
|
79
|
-
# @raise [DuplicateTypeError]
|
80
|
-
# raised when the node type is a duplicate
|
81
|
-
#
|
82
|
-
# @api private
|
83
|
-
#
|
84
|
-
def self.assert_unique_type(type)
|
85
|
-
if registry.key?(type)
|
86
|
-
raise DuplicateTypeError, "duplicate type registration: #{type}"
|
87
|
-
end
|
88
|
-
end
|
89
|
-
private_class_method :assert_unique_type
|
55
|
+
REGISTRY = Registry.new
|
90
56
|
|
91
|
-
end # Registry
|
92
57
|
end # Mutator
|
93
58
|
end # Mutant
|
data/lib/mutant/subject.rb
CHANGED
@@ -88,18 +88,6 @@ module Mutant
|
|
88
88
|
end
|
89
89
|
memoize :source
|
90
90
|
|
91
|
-
# Return root AST for node
|
92
|
-
#
|
93
|
-
# @param [Parser::AST::Node] node
|
94
|
-
#
|
95
|
-
# @return [Parser::AST::Node]
|
96
|
-
#
|
97
|
-
# @api private
|
98
|
-
#
|
99
|
-
def root(node)
|
100
|
-
context.root(node)
|
101
|
-
end
|
102
|
-
|
103
91
|
# Return match expression
|
104
92
|
#
|
105
93
|
# @return [Expression]
|
@@ -9,11 +9,7 @@ module Mutant
|
|
9
9
|
|
10
10
|
# A list of methods that will warn when they are undefined
|
11
11
|
WARN_METHODS_UNDEFINED =
|
12
|
-
|
13
|
-
[:initialize, :__send__, :object_id].freeze
|
14
|
-
else
|
15
|
-
EMPTY_ARRAY
|
16
|
-
end
|
12
|
+
RUBY_ENGINE.eql?('ruby') ? [:initialize, :__send__, :object_id].freeze : EMPTY_ARRAY
|
17
13
|
|
18
14
|
# Test if method is public
|
19
15
|
#
|
@@ -85,7 +81,6 @@ module Mutant
|
|
85
81
|
# @api private
|
86
82
|
#
|
87
83
|
def generate_mutations(emitter)
|
88
|
-
emitter << neutral_mutation
|
89
84
|
Mutator.each(node) do |mutant|
|
90
85
|
emitter << Mutation::Evil.new(self, memoizer_node(mutant))
|
91
86
|
end
|
data/lib/mutant/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
RSpec.describe 'Mutant on ruby corpus' do
|
2
2
|
|
3
3
|
before do
|
4
|
-
skip 'Corpus test is deactivated on 1
|
4
|
+
skip 'Corpus test is deactivated on < 2.1' if RUBY_VERSION < '2.1'
|
5
5
|
skip 'Corpus test is deactivated on RBX' if RUBY_ENGINE.eql?('rbx')
|
6
6
|
end
|
7
7
|
|
@@ -18,14 +18,15 @@ RSpec.describe Mutant::CLI do
|
|
18
18
|
describe '.run' do
|
19
19
|
subject { object.run(arguments) }
|
20
20
|
|
21
|
-
let(:arguments) { double('arguments')
|
22
|
-
|
23
|
-
let(:
|
24
|
-
let(:
|
21
|
+
let(:arguments) { double('arguments') }
|
22
|
+
let(:report) { double('Report', success?: report_success) }
|
23
|
+
let(:config) { double('Config') }
|
24
|
+
let(:env) { double('env') }
|
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(:
|
28
|
+
expect(Mutant::Env).to receive(:new).with(config).and_return(env)
|
29
|
+
expect(Mutant::Runner).to receive(:call).with(env).and_return(report)
|
29
30
|
end
|
30
31
|
|
31
32
|
context 'when report signalls success' do
|
@@ -0,0 +1,56 @@
|
|
1
|
+
RSpec.describe Mutant::Context::Scope do
|
2
|
+
describe '.wrap' do
|
3
|
+
subject { described_class.wrap(scope, node) }
|
4
|
+
|
5
|
+
let(:node) { s(:str, 'test') }
|
6
|
+
|
7
|
+
context 'with Module as scope' do
|
8
|
+
let(:scope) { Mutant }
|
9
|
+
|
10
|
+
let(:expected) do
|
11
|
+
s(:module,
|
12
|
+
s(:const, nil, :Mutant),
|
13
|
+
s(:str, 'test')
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
it { should eql(expected) }
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'with Class as scope' do
|
21
|
+
let(:scope) { Mutant::Context }
|
22
|
+
|
23
|
+
let(:expected) do
|
24
|
+
s(:class,
|
25
|
+
s(:const, nil, :Context),
|
26
|
+
nil,
|
27
|
+
s(:str, 'test')
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
it { should eql(expected) }
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with Class as scope' do
|
35
|
+
let(:scope) { Mutant::Context }
|
36
|
+
|
37
|
+
let(:expected) do
|
38
|
+
s(:class,
|
39
|
+
s(:const, nil, :Context),
|
40
|
+
nil,
|
41
|
+
s(:str, 'test')
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
it { should eql(expected) }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with anything else as scope' do
|
49
|
+
let(:scope) { double(name: 'Foo') }
|
50
|
+
|
51
|
+
it 'raises exception' do
|
52
|
+
expect { subject }.to raise_error(RuntimeError, "Cannot wrap scope: #{scope.inspect}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
RSpec.describe Mutant::Env do
|
2
|
+
let(:config) { Mutant::Config::DEFAULT.update(jobs: 1, reporter: Mutant::Reporter::Trace.new) }
|
3
|
+
|
4
|
+
context '.new' do
|
5
|
+
subject { described_class.new(config) }
|
6
|
+
|
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
|
+
raise
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
expected_warnings = ["Class#name from: #{klass} raised an error: RuntimeError fix your lib to follow normal ruby semantics!"]
|
16
|
+
|
17
|
+
expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
|
18
|
+
|
19
|
+
# Fix Class#name so other specs do not see this one
|
20
|
+
class << klass
|
21
|
+
undef :name
|
22
|
+
def name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when Module#name does not return a String or nil' do
|
29
|
+
it 'warns via reporter' do
|
30
|
+
klass = Class.new do
|
31
|
+
def self.name
|
32
|
+
Object
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
expected_warnings = ["Class#name from: #{klass.inspect} returned #{Object.inspect} instead String or nil. Fix your lib to follow normal ruby semantics!"]
|
37
|
+
|
38
|
+
expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
|
39
|
+
|
40
|
+
# Fix Class#name so other specs do not see this one
|
41
|
+
class << klass
|
42
|
+
undef :name
|
43
|
+
def name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -63,5 +63,25 @@ RSpec.describe Mutant::Isolation::Fork do
|
|
63
63
|
$stderr = STDERR
|
64
64
|
end
|
65
65
|
end
|
66
|
+
|
67
|
+
# Spec stubbing out the fork to ensure all lines are covered
|
68
|
+
# with expectations
|
69
|
+
it 'covers all lines' do
|
70
|
+
reader, writer = double('reader'), double('writer')
|
71
|
+
expect(IO).to receive(:pipe).ordered.and_return([reader, writer])
|
72
|
+
pid = double('PID')
|
73
|
+
expect(Process).to receive(:fork).ordered.and_yield.and_return(pid)
|
74
|
+
file = double('file')
|
75
|
+
expect(File).to receive(:open).ordered.with('/dev/null', 'w').and_yield(file)
|
76
|
+
expect($stderr).to receive(:reopen).ordered.with(file)
|
77
|
+
expect(reader).to receive(:close).ordered
|
78
|
+
expect(writer).to receive(:write).ordered.with(Marshal.dump(:foo))
|
79
|
+
expect(writer).to receive(:close).ordered
|
80
|
+
expect(writer).to receive(:close).ordered
|
81
|
+
expect(reader).to receive(:read).ordered.and_return(Marshal.dump(:foo))
|
82
|
+
expect(Process).to receive(:waitpid).with(pid)
|
83
|
+
|
84
|
+
expect(object.call { :foo }).to be(:foo)
|
85
|
+
end
|
66
86
|
end
|
67
87
|
end
|
@@ -10,10 +10,6 @@ RSpec.describe Mutant::Loader::Eval, '.call' do
|
|
10
10
|
double('Subject', source_path: path, source_line: line)
|
11
11
|
end
|
12
12
|
|
13
|
-
before do
|
14
|
-
expect(mutation_subject).to receive(:prepare).and_return(mutation_subject)
|
15
|
-
end
|
16
|
-
|
17
13
|
let(:source) do
|
18
14
|
<<-RUBY
|
19
15
|
class SomeNamespace
|
@@ -4,9 +4,9 @@ RSpec.describe Mutant::Mutation do
|
|
4
4
|
SYMBOL = 'test'.freeze
|
5
5
|
end
|
6
6
|
|
7
|
-
let(:object) { TestMutation.new(mutation_subject, Mutant::AST::Nodes::N_NIL)
|
8
|
-
let(:mutation_subject) { double('Subject', identification: 'subject', source: 'original') }
|
9
|
-
let(:
|
7
|
+
let(:object) { TestMutation.new(mutation_subject, Mutant::AST::Nodes::N_NIL) }
|
8
|
+
let(:mutation_subject) { double('Subject', identification: 'subject', context: context, source: 'original') }
|
9
|
+
let(:context) { double('Context') }
|
10
10
|
|
11
11
|
describe '#code' do
|
12
12
|
subject { object.code }
|
@@ -24,6 +24,21 @@ RSpec.describe Mutant::Mutation do
|
|
24
24
|
it_should_behave_like 'an idempotent method'
|
25
25
|
end
|
26
26
|
|
27
|
+
describe '#insert' do
|
28
|
+
subject { object.insert }
|
29
|
+
|
30
|
+
let(:wrapped_node) { double('Wrapped Node') }
|
31
|
+
|
32
|
+
before do
|
33
|
+
expect(mutation_subject).to receive(:public?).ordered.and_return(true)
|
34
|
+
expect(mutation_subject).to receive(:prepare).ordered
|
35
|
+
expect(context).to receive(:root).ordered.with(s(:nil)).and_return(wrapped_node)
|
36
|
+
expect(Mutant::Loader::Eval).to receive(:call).ordered.with(wrapped_node, mutation_subject).and_return(nil)
|
37
|
+
end
|
38
|
+
|
39
|
+
it_should_behave_like 'a command method'
|
40
|
+
end
|
41
|
+
|
27
42
|
describe '#source' do
|
28
43
|
subject { object.source }
|
29
44
|
|
@@ -0,0 +1,57 @@
|
|
1
|
+
RSpec.describe Mutant::Mutator::Registry do
|
2
|
+
describe '#lookup' do
|
3
|
+
subject { Mutant::Mutator::REGISTRY.lookup(node) }
|
4
|
+
|
5
|
+
context 'on registred node' do
|
6
|
+
let(:node) { s(:true) }
|
7
|
+
|
8
|
+
it { should eql(Mutant::Mutator::Node::Literal::Boolean) }
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'on unknown node' do
|
12
|
+
let(:node) { s(:unknown) }
|
13
|
+
|
14
|
+
it 'raises error' do
|
15
|
+
expect { subject }.to raise_error(described_class::RegistryError, "No mutator to handle: :unknown")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#register' do
|
21
|
+
let(:object) { described_class.new }
|
22
|
+
|
23
|
+
let(:mutator) { double('Mutator') }
|
24
|
+
|
25
|
+
subject { object.register(type, mutator) }
|
26
|
+
|
27
|
+
context 'when registring an invalid node type' do
|
28
|
+
let(:type) { :invalid }
|
29
|
+
|
30
|
+
it 'raises error' do
|
31
|
+
expect { subject }.to raise_error(described_class::RegistryError, 'Invalid type registration: invalid')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when registring a valid node type' do
|
36
|
+
let(:type) { :true }
|
37
|
+
|
38
|
+
it 'allows to lookup mutator' do
|
39
|
+
subject
|
40
|
+
expect(object.lookup(s(type))).to be(mutator)
|
41
|
+
end
|
42
|
+
|
43
|
+
it_behaves_like 'a command method'
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when duplicate the registration of a valid node type' do
|
47
|
+
let(:type) { :true }
|
48
|
+
|
49
|
+
it 'allows to lookup mutator' do
|
50
|
+
object.register(type, mutator)
|
51
|
+
expect { subject }.to raise_error(described_class::RegistryError, 'Duplicate type registration: true')
|
52
|
+
end
|
53
|
+
|
54
|
+
it_behaves_like 'a command method'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -85,6 +85,34 @@ RSpec.describe Mutant::Subject::Method::Instance do
|
|
85
85
|
|
86
86
|
it { should eql("def foo\nend") }
|
87
87
|
end
|
88
|
+
|
89
|
+
describe '#public?' do
|
90
|
+
subject { object.public? }
|
91
|
+
|
92
|
+
context 'when method is public' do
|
93
|
+
it { should be(true) }
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'when method is private' do
|
97
|
+
before do
|
98
|
+
scope.class_eval do
|
99
|
+
private :foo
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it { should be(false) }
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when method is protected' do
|
107
|
+
before do
|
108
|
+
scope.class_eval do
|
109
|
+
protected :foo
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it { should be(false) }
|
114
|
+
end
|
115
|
+
end
|
88
116
|
end
|
89
117
|
|
90
118
|
RSpec.describe Mutant::Subject::Method::Instance::Memoized do
|
@@ -124,6 +152,20 @@ RSpec.describe Mutant::Subject::Method::Instance::Memoized do
|
|
124
152
|
it_should_behave_like 'a command method'
|
125
153
|
end
|
126
154
|
|
155
|
+
describe '#mutations' do
|
156
|
+
subject { object.mutations }
|
157
|
+
|
158
|
+
let(:expected) do
|
159
|
+
[
|
160
|
+
Mutant::Mutation::Neutral.new(object, s(:begin, s(:def, :foo, s(:args)), s(:send, nil, :memoize, s(:args, s(:sym, :foo))))),
|
161
|
+
Mutant::Mutation::Evil.new(object, s(:begin, s(:def, :foo, s(:args), s(:send, nil, :raise)), s(:send, nil, :memoize, s(:args, s(:sym, :foo))))),
|
162
|
+
Mutant::Mutation::Evil.new(object, s(:begin, s(:def, :foo, s(:args), nil), s(:send, nil, :memoize, s(:args, s(:sym, :foo))))),
|
163
|
+
]
|
164
|
+
end
|
165
|
+
|
166
|
+
it { should eql(expected) }
|
167
|
+
end
|
168
|
+
|
127
169
|
describe '#source' do
|
128
170
|
subject { object.source }
|
129
171
|
|
@@ -2,7 +2,11 @@ RSpec.describe Mutant::Subject do
|
|
2
2
|
let(:class_under_test) do
|
3
3
|
Class.new(described_class) do
|
4
4
|
def expression
|
5
|
-
Mutant::Expression.parse('
|
5
|
+
Mutant::Expression.parse('SubjectA')
|
6
|
+
end
|
7
|
+
|
8
|
+
def match_expressions
|
9
|
+
[expression] << Mutant::Expression.parse('SubjectB')
|
6
10
|
end
|
7
11
|
end
|
8
12
|
end
|
@@ -34,7 +38,7 @@ RSpec.describe Mutant::Subject do
|
|
34
38
|
describe '#identification' do
|
35
39
|
subject { object.identification }
|
36
40
|
|
37
|
-
it { should eql('
|
41
|
+
it { should eql('SubjectA:source_path:source_line') }
|
38
42
|
end
|
39
43
|
|
40
44
|
describe '#prepare' do
|
@@ -43,6 +47,56 @@ RSpec.describe Mutant::Subject do
|
|
43
47
|
it_should_behave_like 'a command method'
|
44
48
|
end
|
45
49
|
|
50
|
+
describe '#tests' do
|
51
|
+
let(:config) { Mutant::Config::DEFAULT.update(integration: integration) }
|
52
|
+
let(:integration) { double('Integration', all_tests: all_tests) }
|
53
|
+
let(:test_a) { double('test', expression: Mutant::Expression.parse('SubjectA')) }
|
54
|
+
let(:test_b) { double('test', expression: Mutant::Expression.parse('SubjectB')) }
|
55
|
+
let(:test_c) { double('test', expression: Mutant::Expression.parse('SubjectC')) }
|
56
|
+
|
57
|
+
subject { object.tests }
|
58
|
+
|
59
|
+
context 'without available tests' do
|
60
|
+
let(:all_tests) { [] }
|
61
|
+
|
62
|
+
it { should eql([]) }
|
63
|
+
|
64
|
+
it_should_behave_like 'an idempotent method'
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'without qualifying tests' do
|
68
|
+
let(:all_tests) { [test_c] }
|
69
|
+
|
70
|
+
it { should eql([]) }
|
71
|
+
|
72
|
+
it_should_behave_like 'an idempotent method'
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'with qualifying tests for first match expression' do
|
76
|
+
let(:all_tests) { [test_a] }
|
77
|
+
|
78
|
+
it { should eql([test_a]) }
|
79
|
+
|
80
|
+
it_should_behave_like 'an idempotent method'
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'with qualifying tests for second match expression' do
|
84
|
+
let(:all_tests) { [test_b] }
|
85
|
+
|
86
|
+
it { should eql([test_b]) }
|
87
|
+
|
88
|
+
it_should_behave_like 'an idempotent method'
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'with qualifying tests for the first and second match expression' do
|
92
|
+
let(:all_tests) { [test_a, test_b] }
|
93
|
+
|
94
|
+
it { should eql([test_a]) }
|
95
|
+
|
96
|
+
it_should_behave_like 'an idempotent method'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
46
100
|
describe '#node' do
|
47
101
|
subject { object.node }
|
48
102
|
|
@@ -49,7 +49,7 @@ RSpec.describe Mutant::WarningExpectation do
|
|
49
49
|
let(:expected_warnings) { [warning_a] }
|
50
50
|
|
51
51
|
it 'raises an expectation error' do
|
52
|
-
expect { subject }.to raise_error(Mutant::WarningExpectation::ExpectationError.new(expected_warnings))
|
52
|
+
expect { subject }.to raise_error(Mutant::WarningExpectation::ExpectationError.new([warning_b], expected_warnings))
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
@@ -64,9 +64,17 @@ RSpec.describe Mutant::WarningExpectation do
|
|
64
64
|
let(:actual_warnings) { [warning_b] }
|
65
65
|
|
66
66
|
it 'raises an expectation error' do
|
67
|
-
expect { subject }.to raise_error(Mutant::WarningExpectation::ExpectationError.new([warning_b]))
|
67
|
+
expect { subject }.to raise_error(Mutant::WarningExpectation::ExpectationError.new([warning_b], expected_warnings))
|
68
68
|
end
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
73
|
+
|
74
|
+
RSpec.describe Mutant::WarningExpectation::ExpectationError do
|
75
|
+
describe '#message' do
|
76
|
+
subject { described_class.new(['unexpected-a'], ['expected-b']).message }
|
77
|
+
|
78
|
+
it { should eql('Unexpected warnings: ["unexpected-a"], expected: ["expected-b"]') }
|
79
|
+
end
|
80
|
+
end
|
data/test_app/Gemfile.devtools
CHANGED
@@ -8,6 +8,7 @@ group :development do
|
|
8
8
|
|
9
9
|
platform :rbx do
|
10
10
|
gem 'rubysl-singleton', '~> 2.0.0'
|
11
|
+
gem 'rubinius-coverage', '~> 2.0.1'
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
@@ -38,7 +39,7 @@ group :metrics do
|
|
38
39
|
gem 'flay', '~> 2.5.0'
|
39
40
|
gem 'flog', '~> 4.2.1'
|
40
41
|
gem 'reek', '~> 1.3.7'
|
41
|
-
gem 'rubocop', '~> 0.26.
|
42
|
+
gem 'rubocop', '~> 0.26.0'
|
42
43
|
gem 'simplecov', '~> 0.7.1'
|
43
44
|
gem 'yardstick', '~> 0.9.9'
|
44
45
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mutant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Markus Schirp
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-11-
|
11
|
+
date: 2014-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|
@@ -470,7 +470,9 @@ files:
|
|
470
470
|
- spec/unit/mutant/context/root_spec.rb
|
471
471
|
- spec/unit/mutant/context/scope/root_spec.rb
|
472
472
|
- spec/unit/mutant/context/scope/unqualified_name_spec.rb
|
473
|
+
- spec/unit/mutant/context_spec.rb
|
473
474
|
- spec/unit/mutant/diff_spec.rb
|
475
|
+
- spec/unit/mutant/env_spec.rb
|
474
476
|
- spec/unit/mutant/expression/method_spec.rb
|
475
477
|
- spec/unit/mutant/expression/methods_spec.rb
|
476
478
|
- spec/unit/mutant/expression/namespace/flat_spec.rb
|
@@ -491,6 +493,7 @@ files:
|
|
491
493
|
- spec/unit/mutant/matcher/null_spec.rb
|
492
494
|
- spec/unit/mutant/mutation_spec.rb
|
493
495
|
- spec/unit/mutant/mutator/node_spec.rb
|
496
|
+
- spec/unit/mutant/mutator/registry_spec.rb
|
494
497
|
- spec/unit/mutant/reporter/cli_spec.rb
|
495
498
|
- spec/unit/mutant/reporter/null_spec.rb
|
496
499
|
- spec/unit/mutant/require_highjack_spec.rb
|
@@ -501,7 +504,7 @@ files:
|
|
501
504
|
- spec/unit/mutant/subject/method/singleton_spec.rb
|
502
505
|
- spec/unit/mutant/subject_spec.rb
|
503
506
|
- spec/unit/mutant/test_spec.rb
|
504
|
-
- spec/unit/mutant/
|
507
|
+
- spec/unit/mutant/warning_expectation_spec.rb
|
505
508
|
- spec/unit/mutant/warning_filter_spec.rb
|
506
509
|
- spec/unit/mutant_spec.rb
|
507
510
|
- test_app/.rspec
|
@@ -549,7 +552,9 @@ test_files:
|
|
549
552
|
- spec/unit/mutant/context/root_spec.rb
|
550
553
|
- spec/unit/mutant/context/scope/root_spec.rb
|
551
554
|
- spec/unit/mutant/context/scope/unqualified_name_spec.rb
|
555
|
+
- spec/unit/mutant/context_spec.rb
|
552
556
|
- spec/unit/mutant/diff_spec.rb
|
557
|
+
- spec/unit/mutant/env_spec.rb
|
553
558
|
- spec/unit/mutant/expression/method_spec.rb
|
554
559
|
- spec/unit/mutant/expression/methods_spec.rb
|
555
560
|
- spec/unit/mutant/expression/namespace/flat_spec.rb
|
@@ -570,6 +575,7 @@ test_files:
|
|
570
575
|
- spec/unit/mutant/matcher/null_spec.rb
|
571
576
|
- spec/unit/mutant/mutation_spec.rb
|
572
577
|
- spec/unit/mutant/mutator/node_spec.rb
|
578
|
+
- spec/unit/mutant/mutator/registry_spec.rb
|
573
579
|
- spec/unit/mutant/reporter/cli_spec.rb
|
574
580
|
- spec/unit/mutant/reporter/null_spec.rb
|
575
581
|
- spec/unit/mutant/require_highjack_spec.rb
|
@@ -580,7 +586,7 @@ test_files:
|
|
580
586
|
- spec/unit/mutant/subject/method/singleton_spec.rb
|
581
587
|
- spec/unit/mutant/subject_spec.rb
|
582
588
|
- spec/unit/mutant/test_spec.rb
|
583
|
-
- spec/unit/mutant/
|
589
|
+
- spec/unit/mutant/warning_expectation_spec.rb
|
584
590
|
- spec/unit/mutant/warning_filter_spec.rb
|
585
591
|
- spec/unit/mutant_spec.rb
|
586
592
|
has_rdoc:
|