mutant 0.9.13 → 0.10.5
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/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
|