mutant 0.6.6 → 0.6.7
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.
- 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:
|