mutant 0.5.24 → 0.5.25
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +8 -0
- data/config/flay.yml +1 -1
- data/config/flog.yml +1 -1
- data/config/reek.yml +15 -13
- data/lib/mutant.rb +28 -12
- data/lib/mutant/ast/meta.rb +0 -10
- data/lib/mutant/ast/named_children.rb +1 -0
- data/lib/mutant/ast/types.rb +5 -5
- data/lib/mutant/cli.rb +84 -64
- data/lib/mutant/config.rb +7 -39
- data/lib/mutant/delegator.rb +2 -0
- data/lib/mutant/env.rb +119 -16
- data/lib/mutant/expression.rb +8 -2
- data/lib/mutant/expression/method.rb +6 -16
- data/lib/mutant/expression/methods.rb +5 -5
- data/lib/mutant/expression/namespace.rb +7 -7
- data/lib/mutant/integration.rb +0 -10
- data/lib/mutant/isolation.rb +41 -15
- data/lib/mutant/matcher/chain.rb +1 -17
- data/lib/mutant/matcher/compiler.rb +108 -0
- data/lib/mutant/matcher/config.rb +28 -0
- data/lib/mutant/matcher/method.rb +1 -1
- data/lib/mutant/matcher/namespace.rb +5 -52
- data/lib/mutant/matcher/null.rb +1 -1
- data/lib/mutant/matcher/scope.rb +1 -1
- data/lib/mutant/mutation.rb +29 -13
- data/lib/mutant/mutator/node.rb +2 -12
- data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
- data/lib/mutant/reporter/cli.rb +0 -2
- data/lib/mutant/reporter/cli/printer.rb +14 -0
- data/lib/mutant/reporter/cli/progress.rb +1 -3
- data/lib/mutant/reporter/cli/progress/config.rb +5 -9
- data/lib/mutant/reporter/cli/progress/env.rb +30 -0
- data/lib/mutant/reporter/cli/progress/noop.rb +4 -1
- data/lib/mutant/reporter/cli/progress/result.rb +12 -0
- data/lib/mutant/reporter/cli/progress/result/mutation.rb +45 -0
- data/lib/mutant/reporter/cli/progress/result/subject.rb +54 -0
- data/lib/mutant/reporter/cli/progress/subject.rb +7 -90
- data/lib/mutant/reporter/cli/registry.rb +2 -0
- data/lib/mutant/reporter/cli/report/env.rb +92 -0
- data/lib/mutant/reporter/cli/report/mutation.rb +58 -77
- data/lib/mutant/reporter/cli/report/subject.rb +4 -3
- data/lib/mutant/reporter/cli/report/test.rb +28 -0
- data/lib/mutant/reporter/null.rb +1 -1
- data/lib/mutant/reporter/trace.rb +16 -3
- data/lib/mutant/result.rb +302 -0
- data/lib/mutant/runner.rb +77 -123
- data/lib/mutant/subject.rb +32 -16
- data/lib/mutant/subject/method.rb +0 -15
- data/lib/mutant/subject/method/instance.rb +3 -3
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/warning_expectation.rb +12 -5
- data/spec/integration/mutant/corpus_spec.rb +1 -1
- data/spec/spec_helper.rb +5 -1
- data/spec/unit/mutant/cli_spec.rb +248 -0
- data/spec/unit/mutant/expression/namespace/flat_spec.rb +1 -1
- data/spec/unit/mutant/expression_spec.rb +55 -0
- data/spec/unit/mutant/integration_spec.rb +0 -5
- data/spec/unit/mutant/isolation_spec.rb +36 -5
- data/spec/unit/mutant/matcher/chain_spec.rb +1 -13
- data/spec/unit/mutant/matcher/compiler_spec.rb +95 -0
- data/spec/unit/mutant/matcher/filter_spec.rb +31 -0
- data/spec/unit/mutant/matcher/method/instance_spec.rb +33 -2
- data/spec/unit/mutant/matcher/method/singleton_spec.rb +1 -1
- data/spec/unit/mutant/matcher/methods/instance_spec.rb +1 -1
- data/spec/unit/mutant/matcher/methods/singleton_spec.rb +1 -1
- data/spec/unit/mutant/matcher/namespace_spec.rb +10 -6
- data/spec/unit/mutant/matcher/null_spec.rb +26 -0
- data/spec/unit/mutant/reporter/cli_spec.rb +337 -0
- data/spec/unit/mutant/reporter/null_spec.rb +12 -0
- data/spec/unit/mutant/runner_spec.rb +130 -0
- data/spec/unit/mutant/subject/context_spec.rb +4 -3
- data/spec/unit/mutant/subject/method/instance_spec.rb +5 -3
- data/spec/unit/mutant/subject/method/singleton_spec.rb +3 -2
- data/spec/unit/mutant/subject_spec.rb +36 -1
- data/spec/unit/mutant/test_spec.rb +25 -0
- data/spec/unit/mutant/warning_expectation.rb +11 -8
- data/spec/unit/mutant_spec.rb +11 -2
- metadata +27 -28
- data/lib/mutant/killer.rb +0 -44
- data/lib/mutant/matcher/builder.rb +0 -142
- data/lib/mutant/mutation/evil.rb +0 -23
- data/lib/mutant/mutation/neutral.rb +0 -18
- data/lib/mutant/reporter/cli/progress/mutation.rb +0 -46
- data/lib/mutant/reporter/cli/report/config.rb +0 -116
- data/lib/mutant/rspec.rb +0 -0
- data/lib/mutant/runner/config.rb +0 -138
- data/lib/mutant/runner/killer.rb +0 -75
- data/lib/mutant/runner/mutation.rb +0 -78
- data/lib/mutant/runner/subject.rb +0 -85
- data/lib/mutant/test/report.rb +0 -59
- data/spec/unit/mutant/cli_new_spec.rb +0 -147
- data/spec/unit/mutant/cli_run_spec.rb +0 -46
- data/spec/unit/mutant/runner/config_spec.rb +0 -157
- data/spec/unit/mutant/runner/mutation_spec.rb +0 -101
- data/spec/unit/mutant/runner/subject_spec.rb +0 -59
- data/spec/unit/mutant/subject/mutations_spec.rb +0 -23
- data/spec/unit/mutant/subject/node_spec.rb +0 -17
data/lib/mutant/config.rb
CHANGED
@@ -1,53 +1,21 @@
|
|
1
1
|
module Mutant
|
2
2
|
# The configuration of a mutator run
|
3
3
|
class Config
|
4
|
-
include Adamantium::Flat, Anima.new(
|
4
|
+
include Adamantium::Flat, Anima::Update, Anima.new(
|
5
5
|
:debug,
|
6
6
|
:integration,
|
7
|
-
:
|
7
|
+
:matcher_config,
|
8
|
+
:includes,
|
9
|
+
:requires,
|
8
10
|
:reporter,
|
11
|
+
:isolation,
|
9
12
|
:fail_fast,
|
10
13
|
:zombie,
|
11
14
|
:expected_coverage
|
12
15
|
)
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
# @api private
|
17
|
-
#
|
18
|
-
# @return [self]
|
19
|
-
# if block given
|
20
|
-
#
|
21
|
-
# @return [Enumerator<Subject>]
|
22
|
-
# otherwise
|
23
|
-
#
|
24
|
-
# @api private
|
25
|
-
#
|
26
|
-
def subjects(&block)
|
27
|
-
return to_enum(__method__) unless block_given?
|
28
|
-
matcher.each(&block)
|
29
|
-
self
|
30
|
-
end
|
31
|
-
|
32
|
-
# Return tests for mutation
|
33
|
-
#
|
34
|
-
# TODO: This logic is now centralized but still fucked.
|
35
|
-
#
|
36
|
-
# @param [Mutation] mutation
|
37
|
-
#
|
38
|
-
# @return [Enumerable<Test>]
|
39
|
-
#
|
40
|
-
# @api private
|
41
|
-
#
|
42
|
-
def tests(subject)
|
43
|
-
subject.match_expressions.each do |match_expression|
|
44
|
-
tests = integration.all_tests.select do |test|
|
45
|
-
match_expression.prefix?(test.expression)
|
46
|
-
end
|
47
|
-
return tests if tests.any?
|
48
|
-
end
|
49
|
-
|
50
|
-
EMPTY_ARRAY
|
17
|
+
[:fail_fast, :zombie, :debug].each do |name|
|
18
|
+
define_method(:"#{name}?") { public_send(name) }
|
51
19
|
end
|
52
20
|
|
53
21
|
end # Config
|
data/lib/mutant/delegator.rb
CHANGED
@@ -25,6 +25,7 @@ module Mutant
|
|
25
25
|
# @api private
|
26
26
|
#
|
27
27
|
def define_delegator(name)
|
28
|
+
fail "method #{name} already defined" if instance_methods.include?(name)
|
28
29
|
define_method(name) do
|
29
30
|
object.public_send(name)
|
30
31
|
end
|
@@ -43,6 +44,7 @@ module Mutant
|
|
43
44
|
#
|
44
45
|
def self.included(host)
|
45
46
|
super
|
47
|
+
|
46
48
|
host.extend(ClassMethods)
|
47
49
|
end
|
48
50
|
|
data/lib/mutant/env.rb
CHANGED
@@ -1,31 +1,43 @@
|
|
1
1
|
module Mutant
|
2
2
|
# Abstract base class for mutant environments
|
3
3
|
class Env
|
4
|
-
include
|
4
|
+
include Adamantium, Concord::Public.new(:config, :cache), Procto.call(:run)
|
5
5
|
|
6
|
-
# Return
|
6
|
+
# Return new env
|
7
7
|
#
|
8
|
-
# @
|
8
|
+
# @param [Config] config
|
9
|
+
#
|
10
|
+
# @return [Env]
|
9
11
|
#
|
10
12
|
# @api private
|
11
13
|
#
|
12
|
-
|
14
|
+
def self.new(config, cache = Cache.new)
|
15
|
+
super(config, cache)
|
16
|
+
end
|
13
17
|
|
14
|
-
#
|
18
|
+
# Initialize env
|
15
19
|
#
|
16
|
-
# @return [
|
20
|
+
# @return [undefined]
|
17
21
|
#
|
18
22
|
# @api private
|
19
23
|
#
|
20
|
-
|
24
|
+
def initialize(*)
|
25
|
+
super
|
26
|
+
|
27
|
+
infect
|
28
|
+
initialize_matchable_scopes
|
29
|
+
initialize_subjects
|
30
|
+
end
|
21
31
|
|
22
|
-
#
|
32
|
+
# Run mutant producing a report on configured env
|
23
33
|
#
|
24
|
-
# @return [
|
34
|
+
# @return [Report]
|
25
35
|
#
|
26
36
|
# @api private
|
27
37
|
#
|
28
|
-
|
38
|
+
def run
|
39
|
+
Runner.call(self)
|
40
|
+
end
|
29
41
|
|
30
42
|
# Print warning message
|
31
43
|
#
|
@@ -36,14 +48,105 @@ module Mutant
|
|
36
48
|
# @api private
|
37
49
|
#
|
38
50
|
def warn(message)
|
39
|
-
reporter.warn(message)
|
51
|
+
config.reporter.warn(message)
|
40
52
|
self
|
41
53
|
end
|
42
54
|
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
55
|
+
# Return subjects
|
56
|
+
#
|
57
|
+
# @return [Array<Subject>]
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
#
|
61
|
+
attr_reader :subjects
|
62
|
+
|
63
|
+
# Return all usable match scopes
|
64
|
+
#
|
65
|
+
# @return [Array<Matcher::Scope>]
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
attr_reader :matchable_scopes
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Return scope name
|
74
|
+
#
|
75
|
+
# @param [Class, Module] scope
|
76
|
+
#
|
77
|
+
# @return [String]
|
78
|
+
# if scope has a name and does not raise exceptions optaining it
|
79
|
+
#
|
80
|
+
# @return [nil]
|
81
|
+
# otherwise
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
#
|
85
|
+
# rubocop:disable LineLength
|
86
|
+
#
|
87
|
+
def scope_name(scope)
|
88
|
+
scope.name
|
89
|
+
rescue => exception
|
90
|
+
warn("While optaining #{scope.class}#name from: #{scope.inspect} It raised an error: #{exception.inspect} fix your lib!")
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
# Try to turn scope into expression
|
95
|
+
#
|
96
|
+
# @param [Class, Module] scope
|
97
|
+
#
|
98
|
+
# @return [Expression]
|
99
|
+
# if scope can be represented in an expression
|
100
|
+
#
|
101
|
+
# @return [nil]
|
102
|
+
# otherwise
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
#
|
106
|
+
def expression(scope)
|
107
|
+
name = scope_name(scope) or return
|
108
|
+
|
109
|
+
unless name.kind_of?(String)
|
110
|
+
warn("#{scope.class}#name from: #{scope.inspect} did not return a String or nil. Fix your lib to support normal ruby semantics!")
|
111
|
+
return
|
112
|
+
end
|
113
|
+
|
114
|
+
Expression.try_parse(name)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Initialize subjects
|
118
|
+
#
|
119
|
+
# @return [undefined]
|
120
|
+
#
|
121
|
+
# @api private
|
122
|
+
#
|
123
|
+
def initialize_subjects
|
124
|
+
@subjects = Matcher::Compiler.call(self, config.matcher_config).to_a
|
125
|
+
end
|
126
|
+
|
127
|
+
# Infect environment
|
128
|
+
#
|
129
|
+
# @return [undefined]
|
130
|
+
#
|
131
|
+
# @api private
|
132
|
+
#
|
133
|
+
def infect
|
134
|
+
config.includes.each(&$LOAD_PATH.method(:<<))
|
135
|
+
config.requires.each(&method(:require))
|
136
|
+
end
|
137
|
+
|
138
|
+
# Initialize matchable scopes
|
139
|
+
#
|
140
|
+
# @return [undefined]
|
141
|
+
#
|
142
|
+
# @api private
|
143
|
+
#
|
144
|
+
def initialize_matchable_scopes
|
145
|
+
@matchable_scopes = ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
|
146
|
+
expression = expression(scope)
|
147
|
+
aggregate << Matcher::Scope.new(self, scope, expression) if expression
|
148
|
+
end.sort_by(&:identification)
|
149
|
+
end
|
47
150
|
|
48
|
-
end #
|
151
|
+
end # Env
|
49
152
|
end # Mutant
|
data/lib/mutant/expression.rb
CHANGED
@@ -17,6 +17,12 @@ module Mutant
|
|
17
17
|
|
18
18
|
REGISTRY = {}
|
19
19
|
|
20
|
+
# Error raised on invalid expressions
|
21
|
+
class InvalidExpressionError < RuntimeError; end
|
22
|
+
|
23
|
+
# Error raised on ambigous expressions
|
24
|
+
class AmbigousExpressionError < RuntimeError; end
|
25
|
+
|
20
26
|
# Initialize expression
|
21
27
|
#
|
22
28
|
# @param [MatchData] match
|
@@ -88,7 +94,7 @@ module Mutant
|
|
88
94
|
# @api private
|
89
95
|
#
|
90
96
|
def self.parse(input)
|
91
|
-
try_parse(input) or
|
97
|
+
try_parse(input) or fail InvalidExpressionError, "Expression: #{input.inspect} is not valid"
|
92
98
|
end
|
93
99
|
|
94
100
|
# Parse input into expression
|
@@ -110,7 +116,7 @@ module Mutant
|
|
110
116
|
when 1
|
111
117
|
expressions.first
|
112
118
|
else
|
113
|
-
fail "Ambigous expression: #{input.inspect}"
|
119
|
+
fail AmbigousExpressionError, "Ambigous expression: #{input.inspect}"
|
114
120
|
end
|
115
121
|
end
|
116
122
|
|
@@ -4,10 +4,10 @@ module Mutant
|
|
4
4
|
# Explicit method expression
|
5
5
|
class Method < self
|
6
6
|
|
7
|
-
MATCHERS =
|
7
|
+
MATCHERS = IceNine.deep_freeze(
|
8
8
|
'.' => Matcher::Methods::Singleton,
|
9
9
|
'#' => Matcher::Methods::Instance
|
10
|
-
|
10
|
+
)
|
11
11
|
|
12
12
|
register(
|
13
13
|
/\A(?<scope_name>#{SCOPE_PATTERN})(?<scope_symbol>[.#])(?<method_name>#{METHOD_NAME_PATTERN})\z/
|
@@ -15,18 +15,18 @@ module Mutant
|
|
15
15
|
|
16
16
|
# Return method matcher
|
17
17
|
#
|
18
|
-
# @param [
|
18
|
+
# @param [Env] env
|
19
19
|
#
|
20
20
|
# @return [Matcher::Method]
|
21
21
|
#
|
22
22
|
# @api private
|
23
23
|
#
|
24
|
-
def matcher(
|
25
|
-
methods_matcher = MATCHERS.fetch(scope_symbol).new(
|
24
|
+
def matcher(env)
|
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
28
|
end or raise NameError, "Cannot find method #{identifier}"
|
29
|
-
methods_matcher.matcher.build(
|
29
|
+
methods_matcher.matcher.build(env, scope, method)
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
@@ -71,16 +71,6 @@ module Mutant
|
|
71
71
|
match[__method__]
|
72
72
|
end
|
73
73
|
|
74
|
-
# Return matcher class
|
75
|
-
#
|
76
|
-
# @return [Class:Mutant::Matcher]
|
77
|
-
#
|
78
|
-
# @api private
|
79
|
-
#
|
80
|
-
def methods_matcher(cache)
|
81
|
-
MATCHERS.fetch(scope_symbol).new(cache, scope)
|
82
|
-
end
|
83
|
-
|
84
74
|
end # Method
|
85
75
|
end # Expression
|
86
76
|
end # Mutant
|
@@ -4,10 +4,10 @@ module Mutant
|
|
4
4
|
# Abstrat base class for methods expression
|
5
5
|
class Methods < self
|
6
6
|
|
7
|
-
MATCHERS =
|
7
|
+
MATCHERS = IceNine.deep_freeze(
|
8
8
|
'.' => Matcher::Methods::Singleton,
|
9
9
|
'#' => Matcher::Methods::Instance
|
10
|
-
|
10
|
+
)
|
11
11
|
|
12
12
|
register(
|
13
13
|
/\A(?<scope_name>#{SCOPE_PATTERN})(?<scope_symbol>[.#])\z/
|
@@ -15,14 +15,14 @@ module Mutant
|
|
15
15
|
|
16
16
|
# Return method matcher
|
17
17
|
#
|
18
|
-
# @param [
|
18
|
+
# @param [Env] env
|
19
19
|
#
|
20
20
|
# @return [Matcher::Method]
|
21
21
|
#
|
22
22
|
# @api private
|
23
23
|
#
|
24
|
-
def matcher(
|
25
|
-
MATCHERS.fetch(scope_symbol).new(
|
24
|
+
def matcher(env)
|
25
|
+
MATCHERS.fetch(scope_symbol).new(env, scope)
|
26
26
|
end
|
27
27
|
|
28
28
|
private
|
@@ -26,14 +26,14 @@ module Mutant
|
|
26
26
|
|
27
27
|
# Return matcher
|
28
28
|
#
|
29
|
-
# @param [
|
29
|
+
# @param [Env] env
|
30
30
|
#
|
31
31
|
# @return [Matcher]
|
32
32
|
#
|
33
33
|
# @api private
|
34
34
|
#
|
35
|
-
def matcher(
|
36
|
-
Matcher::Namespace.new(
|
35
|
+
def matcher(env)
|
36
|
+
Matcher::Namespace.new(env, self)
|
37
37
|
end
|
38
38
|
|
39
39
|
# Return length of match
|
@@ -61,7 +61,7 @@ module Mutant
|
|
61
61
|
# @api private
|
62
62
|
#
|
63
63
|
def namespace
|
64
|
-
match[__method__] ||
|
64
|
+
match[__method__] || EMPTY_STRING
|
65
65
|
end
|
66
66
|
|
67
67
|
end # Recursive
|
@@ -75,14 +75,14 @@ module Mutant
|
|
75
75
|
|
76
76
|
# Return matcher
|
77
77
|
#
|
78
|
-
# @param [Cache]
|
78
|
+
# @param [Cache] env
|
79
79
|
#
|
80
80
|
# @return [Matcher]
|
81
81
|
#
|
82
82
|
# @api private
|
83
83
|
#
|
84
|
-
def matcher(
|
85
|
-
Matcher::Scope.new(
|
84
|
+
def matcher(env)
|
85
|
+
Matcher::Scope.new(env, Mutant.constant_lookup(namespace), self)
|
86
86
|
end
|
87
87
|
|
88
88
|
private
|
data/lib/mutant/integration.rb
CHANGED
data/lib/mutant/isolation.rb
CHANGED
@@ -3,23 +3,49 @@ module Mutant
|
|
3
3
|
module Isolation
|
4
4
|
Error = Class.new(RuntimeError)
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
def self.call(&block)
|
19
|
-
Parallel.map([block], in_processes: 1) do
|
6
|
+
module None
|
7
|
+
|
8
|
+
# Call block in isolation
|
9
|
+
#
|
10
|
+
# @return [Object]
|
11
|
+
#
|
12
|
+
# @raise [Error]
|
13
|
+
# if block terminates abnormal
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
#
|
17
|
+
def self.call(&block)
|
20
18
|
block.call
|
21
|
-
|
19
|
+
rescue => exception
|
20
|
+
fail Error, exception
|
21
|
+
end
|
22
22
|
end
|
23
23
|
|
24
|
+
module Fork
|
25
|
+
|
26
|
+
# Call block in isolation
|
27
|
+
#
|
28
|
+
# This isolation implements the fork strategy.
|
29
|
+
# Future strategies will probably use a process pool that can
|
30
|
+
# handle multiple mutation kills, in-isolation at once.
|
31
|
+
#
|
32
|
+
# @return [Object]
|
33
|
+
# returns block execution result
|
34
|
+
#
|
35
|
+
# @raise [Error]
|
36
|
+
# if block terminates abnormal
|
37
|
+
#
|
38
|
+
# @api private
|
39
|
+
#
|
40
|
+
def self.call(&block)
|
41
|
+
Parallel.map([block], in_processes: 1) do
|
42
|
+
block.call
|
43
|
+
end.first
|
44
|
+
rescue Parallel::DeadWorker => exception
|
45
|
+
fail Error, exception
|
46
|
+
end
|
47
|
+
|
48
|
+
end # Fork
|
49
|
+
|
24
50
|
end # Isolator
|
25
51
|
end # Mutant
|