mutant 0.9.13 → 0.10.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/mutant +14 -11
- data/lib/mutant.rb +10 -6
- data/lib/mutant/cli.rb +8 -167
- data/lib/mutant/cli/command.rb +196 -0
- data/lib/mutant/cli/command/root.rb +13 -0
- data/lib/mutant/cli/command/run.rb +161 -0
- data/lib/mutant/cli/command/subscription.rb +54 -0
- data/lib/mutant/config.rb +28 -52
- data/lib/mutant/expression.rb +0 -1
- data/lib/mutant/license.rb +9 -35
- data/lib/mutant/license/subscription.rb +31 -9
- data/lib/mutant/license/subscription/commercial.rb +2 -4
- data/lib/mutant/license/subscription/opensource.rb +8 -7
- data/lib/mutant/matcher/config.rb +13 -0
- data/lib/mutant/meta/example.rb +16 -4
- data/lib/mutant/meta/example/dsl.rb +33 -16
- data/lib/mutant/meta/example/verification.rb +70 -28
- data/lib/mutant/mutator/node.rb +2 -2
- data/lib/mutant/mutator/node/{dstr.rb → dynamic_literal.rb} +7 -5
- data/lib/mutant/mutator/node/index.rb +4 -4
- data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
- data/lib/mutant/mutator/node/op_asgn.rb +15 -1
- data/lib/mutant/mutator/node/send.rb +1 -1
- data/lib/mutant/mutator/node/send/attribute_assignment.rb +1 -0
- data/lib/mutant/reporter/cli/printer/config.rb +2 -2
- data/lib/mutant/selector/expression.rb +3 -1
- data/lib/mutant/subject/method/instance.rb +1 -1
- data/lib/mutant/test.rb +1 -1
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/world.rb +52 -0
- metadata +16 -13
- data/lib/mutant/minitest/coverage.rb +0 -53
- data/lib/mutant/mutator/node/dsym.rb +0 -22
@@ -4,8 +4,6 @@ module Mutant
|
|
4
4
|
module License
|
5
5
|
class Subscription
|
6
6
|
class Commercial < self
|
7
|
-
include Concord.new(:authors)
|
8
|
-
|
9
7
|
class Author
|
10
8
|
include Concord.new(:email)
|
11
9
|
|
@@ -20,10 +18,10 @@ module Mutant
|
|
20
18
|
def apply(world)
|
21
19
|
candidates = candidates(world)
|
22
20
|
|
23
|
-
if (
|
21
|
+
if (licensed & candidates).any?
|
24
22
|
success
|
25
23
|
else
|
26
|
-
failure(
|
24
|
+
failure(licensed, candidates)
|
27
25
|
end
|
28
26
|
end
|
29
27
|
|
@@ -4,8 +4,6 @@ module Mutant
|
|
4
4
|
module License
|
5
5
|
class Subscription
|
6
6
|
class Opensource < self
|
7
|
-
include Concord.new(:repositories)
|
8
|
-
|
9
7
|
class Repository
|
10
8
|
include Concord.new(:host, :path)
|
11
9
|
|
@@ -43,10 +41,13 @@ module Mutant
|
|
43
41
|
private_class_method :parse_url
|
44
42
|
end
|
45
43
|
|
46
|
-
private_constant(*constants(false))
|
47
|
-
|
48
44
|
def self.from_json(value)
|
49
|
-
new(
|
45
|
+
new(
|
46
|
+
value
|
47
|
+
.fetch('repositories')
|
48
|
+
.map(&Repository.public_method(:parse))
|
49
|
+
.to_set
|
50
|
+
)
|
50
51
|
end
|
51
52
|
|
52
53
|
def apply(world)
|
@@ -59,10 +60,10 @@ module Mutant
|
|
59
60
|
private
|
60
61
|
|
61
62
|
def check_subscription(actual)
|
62
|
-
if (
|
63
|
+
if (licensed & actual).any?
|
63
64
|
success
|
64
65
|
else
|
65
|
-
failure(
|
66
|
+
failure(licensed, actual)
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
@@ -44,6 +44,19 @@ module Mutant
|
|
44
44
|
with(attribute => public_send(attribute) + [value])
|
45
45
|
end
|
46
46
|
|
47
|
+
# Merge with other config
|
48
|
+
#
|
49
|
+
# @param [Config] other
|
50
|
+
#
|
51
|
+
# @return [Config]
|
52
|
+
def merge(other)
|
53
|
+
self.class.new(
|
54
|
+
to_h
|
55
|
+
.map { |name, value| [name, value + other.public_send(name)] }
|
56
|
+
.to_h
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
47
60
|
private
|
48
61
|
|
49
62
|
def present_attributes
|
data/lib/mutant/meta/example.rb
CHANGED
@@ -3,7 +3,19 @@
|
|
3
3
|
module Mutant
|
4
4
|
module Meta
|
5
5
|
class Example
|
6
|
-
include Adamantium
|
6
|
+
include Adamantium
|
7
|
+
include Anima.new(
|
8
|
+
:expected,
|
9
|
+
:file,
|
10
|
+
:lvars,
|
11
|
+
:node,
|
12
|
+
:original_source,
|
13
|
+
:types
|
14
|
+
)
|
15
|
+
|
16
|
+
class Expected
|
17
|
+
include Anima.new(:original_source, :node)
|
18
|
+
end
|
7
19
|
|
8
20
|
# Verification instance for example
|
9
21
|
#
|
@@ -13,13 +25,13 @@ module Mutant
|
|
13
25
|
end
|
14
26
|
memoize :verification
|
15
27
|
|
16
|
-
#
|
28
|
+
# Original source as generated by unparser
|
17
29
|
#
|
18
30
|
# @return [String]
|
19
|
-
def
|
31
|
+
def original_source_generated
|
20
32
|
Unparser.unparse(node)
|
21
33
|
end
|
22
|
-
memoize :
|
34
|
+
memoize :original_source_generated
|
23
35
|
|
24
36
|
# Generated mutations on example source
|
25
37
|
#
|
@@ -25,10 +25,11 @@ module Mutant
|
|
25
25
|
#
|
26
26
|
# @return [undefined]
|
27
27
|
def initialize(file, types)
|
28
|
+
@expected = []
|
28
29
|
@file = file
|
30
|
+
@lvars = []
|
31
|
+
@source = nil
|
29
32
|
@types = types
|
30
|
-
@node = nil
|
31
|
-
@expected = []
|
32
33
|
end
|
33
34
|
|
34
35
|
# Example captured by DSL
|
@@ -38,28 +39,41 @@ module Mutant
|
|
38
39
|
# @raise [RuntimeError]
|
39
40
|
# in case example cannot be build
|
40
41
|
def example
|
41
|
-
fail 'source not defined' unless @
|
42
|
+
fail 'source not defined' unless @source
|
42
43
|
Example.new(
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
expected: @expected,
|
45
|
+
file: @file,
|
46
|
+
lvars: @lvars,
|
47
|
+
node: @node,
|
48
|
+
original_source: @source,
|
49
|
+
types: @types
|
47
50
|
)
|
48
51
|
end
|
49
52
|
|
53
|
+
# Declare a local variable
|
54
|
+
#
|
55
|
+
# @param [Symbol]
|
56
|
+
def declare_lvar(name)
|
57
|
+
@lvars << name
|
58
|
+
end
|
59
|
+
|
50
60
|
private
|
51
61
|
|
52
62
|
def source(input)
|
53
|
-
fail 'source already defined' if @
|
54
|
-
|
63
|
+
fail 'source already defined' if @source
|
64
|
+
|
65
|
+
@source = input
|
66
|
+
@node = node(input)
|
55
67
|
end
|
56
68
|
|
57
69
|
def mutation(input)
|
58
|
-
|
59
|
-
|
70
|
+
expected = Expected.new(original_source: input, node: node(input))
|
71
|
+
|
72
|
+
if @expected.include?(expected)
|
60
73
|
fail "Mutation for input: #{input.inspect} is already expected"
|
61
74
|
end
|
62
|
-
|
75
|
+
|
76
|
+
@expected << expected
|
63
77
|
end
|
64
78
|
|
65
79
|
def singleton_mutations
|
@@ -70,14 +84,17 @@ module Mutant
|
|
70
84
|
def node(input)
|
71
85
|
case input
|
72
86
|
when String
|
73
|
-
|
74
|
-
when ::Parser::AST::Node
|
75
|
-
input
|
87
|
+
parser.parse(Unparser.buffer(input))
|
76
88
|
else
|
77
|
-
fail "
|
89
|
+
fail "Unsupported input: #{input.inspect}"
|
78
90
|
end
|
79
91
|
end
|
80
92
|
|
93
|
+
def parser
|
94
|
+
Unparser.parser.tap do |parser|
|
95
|
+
@lvars.each(&parser.static_env.public_method(:declare))
|
96
|
+
end
|
97
|
+
end
|
81
98
|
end # DSL
|
82
99
|
end # Example
|
83
100
|
end # Meta
|
@@ -11,54 +11,96 @@ module Mutant
|
|
11
11
|
#
|
12
12
|
# @return [Boolean]
|
13
13
|
def success?
|
14
|
-
[
|
14
|
+
[
|
15
|
+
original_verification,
|
16
|
+
invalid,
|
17
|
+
missing,
|
18
|
+
no_diffs,
|
19
|
+
unexpected
|
20
|
+
].all?(&:empty?)
|
15
21
|
end
|
16
22
|
memoize :success?
|
17
23
|
|
18
|
-
# Error report
|
19
|
-
#
|
20
|
-
# @return [String]
|
21
24
|
def error_report
|
22
|
-
|
23
|
-
|
24
|
-
YAML.dump(
|
25
|
-
'file' => example.file,
|
26
|
-
'original_ast' => example.node.inspect,
|
27
|
-
'original_source' => example.source,
|
28
|
-
'missing' => format_mutations(missing),
|
29
|
-
'unexpected' => format_mutations(unexpected),
|
30
|
-
'invalid_syntax' => format_mutations(invalid_syntax),
|
31
|
-
'no_diff' => no_diff_report
|
32
|
-
)
|
25
|
+
reports.join("\n")
|
33
26
|
end
|
34
|
-
memoize :error_report
|
35
27
|
|
36
28
|
private
|
37
29
|
|
30
|
+
def reports
|
31
|
+
reports = [example.file]
|
32
|
+
reports.concat(original)
|
33
|
+
reports.concat(original_verification)
|
34
|
+
reports.concat(make_report('Missing mutations:', missing))
|
35
|
+
reports.concat(make_report('Unexpected mutations:', unexpected))
|
36
|
+
reports.concat(make_report('No-Diff mutations:', no_diffs))
|
37
|
+
reports.concat(invalid)
|
38
|
+
end
|
39
|
+
|
40
|
+
def make_report(label, mutations)
|
41
|
+
if mutations.any?
|
42
|
+
[label, mutations.map(&method(:report_mutation))]
|
43
|
+
else
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def report_mutation(mutation)
|
49
|
+
[
|
50
|
+
mutation.node.inspect,
|
51
|
+
mutation.source
|
52
|
+
]
|
53
|
+
end
|
54
|
+
|
55
|
+
def original
|
56
|
+
[
|
57
|
+
'Original:',
|
58
|
+
example.node,
|
59
|
+
example.original_source
|
60
|
+
]
|
61
|
+
end
|
62
|
+
|
63
|
+
def original_verification
|
64
|
+
validation = Unparser::Validation.from_string(example.original_source)
|
65
|
+
if validation.success?
|
66
|
+
[]
|
67
|
+
else
|
68
|
+
[
|
69
|
+
prefix('[original]', validation.report)
|
70
|
+
]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def prefix(prefix, string)
|
75
|
+
string.each_line.map do |line|
|
76
|
+
"#{prefix} #{line}"
|
77
|
+
end.join
|
78
|
+
end
|
79
|
+
|
80
|
+
def invalid
|
81
|
+
mutations.each_with_object([]) do |mutation, aggregate|
|
82
|
+
validation = Unparser::Validation.from_node(mutation.node)
|
83
|
+
aggregate << prefix('[invalid-mutation]', validation.report) unless validation.success?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
memoize :invalid
|
87
|
+
|
38
88
|
def unexpected
|
39
89
|
mutations.reject do |mutation|
|
40
|
-
example.expected.
|
90
|
+
example.expected.any? { |expected| expected.node.eql?(mutation.node) }
|
41
91
|
end
|
42
92
|
end
|
43
93
|
memoize :unexpected
|
44
94
|
|
45
95
|
def missing
|
46
|
-
(example.expected - mutations.map(&:node)).map do |node|
|
47
|
-
Mutation::Evil.new(
|
96
|
+
(example.expected.map(&:node) - mutations.map(&:node)).map do |node|
|
97
|
+
Mutation::Evil.new(nil, node)
|
48
98
|
end
|
49
99
|
end
|
50
100
|
memoize :missing
|
51
101
|
|
52
|
-
def invalid_syntax
|
53
|
-
mutations.reject do |mutation|
|
54
|
-
::Parser::CurrentRuby.parse(mutation.source)
|
55
|
-
rescue ::Parser::SyntaxError # rubocop:disable Lint/SuppressedException
|
56
|
-
end
|
57
|
-
end
|
58
|
-
memoize :invalid_syntax
|
59
|
-
|
60
102
|
def no_diffs
|
61
|
-
mutations.select { |mutation| mutation.source.eql?(example.
|
103
|
+
mutations.select { |mutation| mutation.source.eql?(example.original_source_generated) }
|
62
104
|
end
|
63
105
|
memoize :no_diffs
|
64
106
|
|
data/lib/mutant/mutator/node.rb
CHANGED
@@ -75,7 +75,7 @@ module Mutant
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def emit_nil
|
78
|
-
emit(N_NIL) unless
|
78
|
+
emit(N_NIL) unless left_op_assignment?
|
79
79
|
end
|
80
80
|
|
81
81
|
def parent_node
|
@@ -86,7 +86,7 @@ module Mutant
|
|
86
86
|
parent_node&.type
|
87
87
|
end
|
88
88
|
|
89
|
-
def
|
89
|
+
def left_op_assignment?
|
90
90
|
AST::Types::OP_ASSIGN.include?(parent_type) && parent.node.children.first.equal?(node)
|
91
91
|
end
|
92
92
|
|
@@ -3,17 +3,19 @@
|
|
3
3
|
module Mutant
|
4
4
|
class Mutator
|
5
5
|
class Node
|
6
|
+
# Mutator for dynamic literals
|
7
|
+
class DynamicLiteral < self
|
6
8
|
|
7
|
-
|
8
|
-
class Dstr < Generic
|
9
|
-
|
10
|
-
handle(:dstr)
|
9
|
+
handle(:dstr, :dsym)
|
11
10
|
|
12
11
|
private
|
13
12
|
|
14
13
|
def dispatch
|
15
|
-
super()
|
16
14
|
emit_singletons
|
15
|
+
|
16
|
+
children.each_index do |index|
|
17
|
+
mutate_child(index, &method(:n_begin?))
|
18
|
+
end
|
17
19
|
end
|
18
20
|
|
19
21
|
end # Dstr
|
@@ -24,7 +24,7 @@ module Mutant
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def emit_send_forms
|
27
|
-
return if
|
27
|
+
return if left_op_assignment?
|
28
28
|
|
29
29
|
SEND_REPLACEMENTS.each do |selector|
|
30
30
|
emit(s(:send, receiver, selector, *indices))
|
@@ -43,7 +43,7 @@ module Mutant
|
|
43
43
|
|
44
44
|
def mutate_indices
|
45
45
|
children_indices(index_range).each do |index|
|
46
|
-
emit_propagation(children.fetch(index)) unless
|
46
|
+
emit_propagation(children.fetch(index)) unless left_op_assignment?
|
47
47
|
delete_child(index)
|
48
48
|
mutate_child(index)
|
49
49
|
end
|
@@ -77,7 +77,7 @@ module Mutant
|
|
77
77
|
def dispatch
|
78
78
|
super()
|
79
79
|
|
80
|
-
return if
|
80
|
+
return if left_op_assignment?
|
81
81
|
|
82
82
|
emit_index_read
|
83
83
|
emit(children.last)
|
@@ -89,7 +89,7 @@ module Mutant
|
|
89
89
|
end
|
90
90
|
|
91
91
|
def index_range
|
92
|
-
if
|
92
|
+
if left_op_assignment?
|
93
93
|
NO_VALUE_RANGE
|
94
94
|
else
|
95
95
|
REGULAR_RANGE
|
@@ -15,10 +15,24 @@ module Mutant
|
|
15
15
|
|
16
16
|
def dispatch
|
17
17
|
emit_singletons
|
18
|
+
|
19
|
+
left_mutations
|
20
|
+
|
21
|
+
emit_right_mutations
|
22
|
+
end
|
23
|
+
|
24
|
+
def left_mutations
|
18
25
|
emit_left_mutations do |node|
|
19
26
|
!n_self?(node)
|
20
27
|
end
|
21
|
-
|
28
|
+
|
29
|
+
emit_left_promotion if n_send?(left)
|
30
|
+
end
|
31
|
+
|
32
|
+
def emit_left_promotion
|
33
|
+
receiver = left.children.first
|
34
|
+
|
35
|
+
emit_left(s(:ivasgn, *receiver)) if n_ivar?(receiver)
|
22
36
|
end
|
23
37
|
|
24
38
|
end # OpAsgn
|