mutant 0.10.21 → 0.10.26
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mutant.rb +32 -13
- data/lib/mutant/ast/find_metaclass_containing.rb +1 -1
- data/lib/mutant/ast/regexp.rb +54 -0
- data/lib/mutant/ast/regexp/transformer.rb +150 -0
- data/lib/mutant/ast/regexp/transformer/direct.rb +121 -0
- data/lib/mutant/ast/regexp/transformer/named_group.rb +50 -0
- data/lib/mutant/ast/regexp/transformer/options_group.rb +68 -0
- data/lib/mutant/ast/regexp/transformer/quantifier.rb +92 -0
- data/lib/mutant/ast/regexp/transformer/recursive.rb +56 -0
- data/lib/mutant/ast/regexp/transformer/root.rb +28 -0
- data/lib/mutant/ast/regexp/transformer/text.rb +58 -0
- data/lib/mutant/ast/types.rb +115 -2
- data/lib/mutant/bootstrap.rb +1 -1
- data/lib/mutant/cli/command.rb +4 -0
- data/lib/mutant/cli/command/environment.rb +9 -3
- data/lib/mutant/cli/command/environment/subject.rb +0 -4
- data/lib/mutant/cli/command/environment/test.rb +36 -0
- data/lib/mutant/cli/command/root.rb +1 -1
- data/lib/mutant/config.rb +9 -55
- data/lib/mutant/config/coverage_criteria.rb +61 -0
- data/lib/mutant/context.rb +1 -1
- data/lib/mutant/env.rb +2 -2
- data/lib/mutant/expression.rb +0 -12
- data/lib/mutant/expression/method.rb +4 -4
- data/lib/mutant/expression/methods.rb +5 -4
- data/lib/mutant/expression/namespace.rb +1 -1
- data/lib/mutant/integration.rb +8 -2
- data/lib/mutant/isolation/fork.rb +4 -11
- data/lib/mutant/loader.rb +1 -1
- data/lib/mutant/matcher.rb +3 -3
- data/lib/mutant/matcher/config.rb +30 -8
- data/lib/mutant/matcher/method.rb +10 -9
- data/lib/mutant/matcher/method/instance.rb +6 -2
- data/lib/mutant/matcher/method/metaclass.rb +1 -1
- data/lib/mutant/matcher/methods.rb +2 -4
- data/lib/mutant/meta/example.rb +1 -1
- data/lib/mutant/meta/example/dsl.rb +6 -1
- data/lib/mutant/meta/example/verification.rb +1 -1
- data/lib/mutant/mutation.rb +1 -1
- data/lib/mutant/mutator.rb +8 -1
- data/lib/mutant/mutator/node/argument.rb +2 -2
- data/lib/mutant/mutator/node/block.rb +5 -1
- data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
- data/lib/mutant/mutator/node/kwargs.rb +44 -0
- data/lib/mutant/mutator/node/literal/regex.rb +12 -0
- data/lib/mutant/mutator/node/named_value/variable_assignment.rb +3 -3
- data/lib/mutant/mutator/node/regexp.rb +20 -0
- data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
- data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
- data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
- data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
- data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
- data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
- data/lib/mutant/mutator/node/regexp/named_group.rb +39 -0
- data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
- data/lib/mutant/mutator/node/regopt.rb +1 -1
- data/lib/mutant/mutator/node/sclass.rb +1 -1
- data/lib/mutant/mutator/node/send.rb +55 -6
- data/lib/mutant/parallel.rb +2 -2
- data/lib/mutant/parallel/driver.rb +1 -1
- data/lib/mutant/parallel/worker.rb +1 -1
- data/lib/mutant/parser.rb +1 -1
- data/lib/mutant/pipe.rb +1 -1
- data/lib/mutant/procto.rb +23 -0
- data/lib/mutant/reporter/cli/printer.rb +10 -4
- data/lib/mutant/reporter/cli/printer/env.rb +3 -3
- data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -2
- data/lib/mutant/selector.rb +1 -1
- data/lib/mutant/subject.rb +2 -4
- data/lib/mutant/subject/method/instance.rb +6 -45
- data/lib/mutant/timer.rb +2 -2
- data/lib/mutant/transform.rb +25 -0
- data/lib/mutant/variable.rb +322 -0
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/world.rb +2 -3
- metadata +39 -151
- data/lib/mutant/warnings.rb +0 -106
data/lib/mutant/bootstrap.rb
CHANGED
@@ -8,7 +8,7 @@ module Mutant
|
|
8
8
|
#
|
9
9
|
# env = config interpreted against the world
|
10
10
|
module Bootstrap
|
11
|
-
include Adamantium
|
11
|
+
include Adamantium, Anima.new(:config, :parser, :world)
|
12
12
|
|
13
13
|
SEMANTICS_MESSAGE_FORMAT =
|
14
14
|
"%<message>s. Fix your lib to follow normal ruby semantics!\n" \
|
data/lib/mutant/cli/command.rb
CHANGED
@@ -29,13 +29,19 @@ module Mutant
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def expand(file_config)
|
32
|
+
if @config.matcher.subjects.any?
|
33
|
+
file_config = file_config.with(
|
34
|
+
matcher: file_config.matcher.with(subjects: [])
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
32
38
|
@config = Config.env.merge(file_config).merge(@config).expand_defaults
|
33
39
|
end
|
34
40
|
|
35
41
|
def parse_remaining_arguments(arguments)
|
36
42
|
Mutant.traverse(@config.expression_parser, arguments)
|
37
|
-
.fmap do |
|
38
|
-
matcher(
|
43
|
+
.fmap do |expressions|
|
44
|
+
matcher(subjects: expressions)
|
39
45
|
self
|
40
46
|
end
|
41
47
|
end
|
@@ -82,7 +88,7 @@ module Mutant
|
|
82
88
|
parser.separator('Matcher:')
|
83
89
|
|
84
90
|
parser.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
|
85
|
-
add_matcher(:
|
91
|
+
add_matcher(:ignore, @config.expression_parser.call(pattern).from_right)
|
86
92
|
end
|
87
93
|
parser.on('--start-subject EXPRESSION', 'Start mutation testing at a specific subject') do |pattern|
|
88
94
|
add_matcher(:start_expressions, @config.expression_parser.call(pattern).from_right)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
module CLI
|
5
|
+
class Command
|
6
|
+
class Environment
|
7
|
+
class Test < self
|
8
|
+
NAME = 'test'
|
9
|
+
SHORT_DESCRIPTION = 'test subcommands'
|
10
|
+
|
11
|
+
class List < self
|
12
|
+
NAME = 'list'
|
13
|
+
SHORT_DESCRIPTION = 'List tests detected in the environment'
|
14
|
+
SUBCOMMANDS = EMPTY_ARRAY
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def action
|
19
|
+
bootstrap.fmap(&method(:list_tests))
|
20
|
+
end
|
21
|
+
|
22
|
+
def list_tests(env)
|
23
|
+
tests = env.integration.all_tests
|
24
|
+
print('Tests in environment: %d' % tests.length)
|
25
|
+
tests.each do |test|
|
26
|
+
print(test.identification)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
SUBCOMMANDS = [List].freeze
|
32
|
+
end # Test
|
33
|
+
end # Environment
|
34
|
+
end # Command
|
35
|
+
end # CLI
|
36
|
+
end # Mutant
|
data/lib/mutant/config.rb
CHANGED
@@ -6,7 +6,7 @@ module Mutant
|
|
6
6
|
# Does not reference any "external" volatile state. The configuration applied
|
7
7
|
# to current environment is being represented by the Mutant::Env object.
|
8
8
|
class Config
|
9
|
-
include Adamantium
|
9
|
+
include Adamantium, Anima.new(
|
10
10
|
:coverage_criteria,
|
11
11
|
:expression_parser,
|
12
12
|
:fail_fast,
|
@@ -37,54 +37,6 @@ module Mutant
|
|
37
37
|
|
38
38
|
private_constant(*constants(false))
|
39
39
|
|
40
|
-
class CoverageCriteria
|
41
|
-
include Anima.new(:process_abort, :test_result, :timeout)
|
42
|
-
|
43
|
-
EMPTY = new(
|
44
|
-
process_abort: nil,
|
45
|
-
test_result: nil,
|
46
|
-
timeout: nil
|
47
|
-
)
|
48
|
-
|
49
|
-
DEFAULT = new(
|
50
|
-
process_abort: false,
|
51
|
-
test_result: true,
|
52
|
-
timeout: false
|
53
|
-
)
|
54
|
-
|
55
|
-
TRANSFORM =
|
56
|
-
Transform::Sequence.new(
|
57
|
-
[
|
58
|
-
Transform::Hash.new(
|
59
|
-
optional: [
|
60
|
-
Transform::Hash::Key.new('process_abort', Transform::BOOLEAN),
|
61
|
-
Transform::Hash::Key.new('test_result', Transform::BOOLEAN),
|
62
|
-
Transform::Hash::Key.new('timeout', Transform::BOOLEAN)
|
63
|
-
],
|
64
|
-
required: []
|
65
|
-
),
|
66
|
-
Transform::Hash::Symbolize.new,
|
67
|
-
->(value) { Either::Right.new(DEFAULT.with(**value)) }
|
68
|
-
]
|
69
|
-
)
|
70
|
-
|
71
|
-
def merge(other)
|
72
|
-
self.class.new(
|
73
|
-
process_abort: overwrite(other, :process_abort),
|
74
|
-
test_result: overwrite(other, :test_result),
|
75
|
-
timeout: overwrite(other, :timeout)
|
76
|
-
)
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def overwrite(other, attribute_name)
|
82
|
-
other_value = other.public_send(attribute_name)
|
83
|
-
|
84
|
-
other_value.nil? ? public_send(attribute_name) : other_value
|
85
|
-
end
|
86
|
-
end # CoverageCriteria
|
87
|
-
|
88
40
|
# Merge with other config
|
89
41
|
#
|
90
42
|
# @param [Config] other
|
@@ -116,13 +68,14 @@ module Mutant
|
|
116
68
|
#
|
117
69
|
# @return [Either<String,Config>]
|
118
70
|
def self.load_config_file(world)
|
119
|
-
|
120
|
-
|
71
|
+
files = CANDIDATES
|
72
|
+
.map(&world.pathname.public_method(:new))
|
73
|
+
.select(&:readable?)
|
121
74
|
|
122
75
|
if files.one?
|
123
|
-
load_contents(files.first).fmap(&
|
76
|
+
load_contents(files.first).fmap(&DEFAULT.public_method(:with))
|
124
77
|
elsif files.empty?
|
125
|
-
Either::Right.new(
|
78
|
+
Either::Right.new(DEFAULT)
|
126
79
|
else
|
127
80
|
Either::Left.new(MORE_THAN_ONE_CONFIG_FILE % files.join(', '))
|
128
81
|
end
|
@@ -159,13 +112,14 @@ module Mutant
|
|
159
112
|
Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
|
160
113
|
Transform::Hash.new(
|
161
114
|
optional: [
|
162
|
-
Transform::Hash::Key.new('coverage_criteria', CoverageCriteria::TRANSFORM),
|
115
|
+
Transform::Hash::Key.new('coverage_criteria', ->(value) { CoverageCriteria::TRANSFORM.call(value) }),
|
163
116
|
Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
|
164
117
|
Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
|
165
118
|
Transform::Hash::Key.new('integration', Transform::STRING),
|
166
119
|
Transform::Hash::Key.new('jobs', Transform::INTEGER),
|
167
120
|
Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
|
168
|
-
Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
|
121
|
+
Transform::Hash::Key.new('requires', Transform::STRING_ARRAY),
|
122
|
+
Transform::Hash::Key.new('matcher', Matcher::Config::LOADER)
|
169
123
|
],
|
170
124
|
required: []
|
171
125
|
),
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Config
|
5
|
+
# Configuration of coverge conditions
|
6
|
+
class CoverageCriteria
|
7
|
+
include Anima.new(:process_abort, :test_result, :timeout)
|
8
|
+
|
9
|
+
EMPTY = new(
|
10
|
+
process_abort: nil,
|
11
|
+
test_result: nil,
|
12
|
+
timeout: nil
|
13
|
+
)
|
14
|
+
|
15
|
+
DEFAULT = new(
|
16
|
+
process_abort: false,
|
17
|
+
test_result: true,
|
18
|
+
timeout: false
|
19
|
+
)
|
20
|
+
|
21
|
+
TRANSFORM =
|
22
|
+
Transform::Sequence.new(
|
23
|
+
[
|
24
|
+
Transform::Hash.new(
|
25
|
+
optional: [
|
26
|
+
Transform::Hash::Key.new('process_abort', Transform::BOOLEAN),
|
27
|
+
Transform::Hash::Key.new('test_result', Transform::BOOLEAN),
|
28
|
+
Transform::Hash::Key.new('timeout', Transform::BOOLEAN)
|
29
|
+
],
|
30
|
+
required: []
|
31
|
+
),
|
32
|
+
Transform::Hash::Symbolize.new,
|
33
|
+
->(value) { Either::Right.new(DEFAULT.with(**value)) }
|
34
|
+
]
|
35
|
+
)
|
36
|
+
|
37
|
+
# Merge coverage criteria with other instance
|
38
|
+
#
|
39
|
+
# Values from the other instance have precedence.
|
40
|
+
#
|
41
|
+
# @param [CoverageCriteria] other
|
42
|
+
#
|
43
|
+
# @return [CoverageCriteria]
|
44
|
+
def merge(other)
|
45
|
+
self.class.new(
|
46
|
+
process_abort: overwrite(other, :process_abort),
|
47
|
+
test_result: overwrite(other, :test_result),
|
48
|
+
timeout: overwrite(other, :timeout)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def overwrite(other, attribute_name)
|
55
|
+
other_value = other.public_send(attribute_name)
|
56
|
+
|
57
|
+
other_value.nil? ? public_send(attribute_name) : other_value
|
58
|
+
end
|
59
|
+
end # CoverageCriteria
|
60
|
+
end # Config
|
61
|
+
end # Mutant
|
data/lib/mutant/context.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Mutant
|
4
4
|
# An abstract context where mutations can be applied to.
|
5
5
|
class Context
|
6
|
-
include Adamantium
|
6
|
+
include Adamantium, Concord::Public.new(:scope, :source_path)
|
7
7
|
extend AST::Sexp
|
8
8
|
|
9
9
|
NAMESPACE_DELIMITER = '::'
|
data/lib/mutant/env.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Mutant
|
4
4
|
# Abstract base class for mutant environments
|
5
5
|
class Env
|
6
|
-
include Adamantium
|
6
|
+
include Adamantium, Anima.new(
|
7
7
|
:config,
|
8
8
|
:integration,
|
9
9
|
:matchable_scopes,
|
@@ -31,7 +31,7 @@ module Mutant
|
|
31
31
|
config: config,
|
32
32
|
integration: Integration::Null.new(
|
33
33
|
expression_parser: config.expression_parser,
|
34
|
-
|
34
|
+
world: world
|
35
35
|
),
|
36
36
|
matchable_scopes: EMPTY_ARRAY,
|
37
37
|
mutations: EMPTY_ARRAY,
|
data/lib/mutant/expression.rb
CHANGED
@@ -4,8 +4,6 @@ module Mutant
|
|
4
4
|
|
5
5
|
# Abstract base class for match expression
|
6
6
|
class Expression
|
7
|
-
include AbstractType
|
8
|
-
|
9
7
|
fragment = /[A-Za-z][A-Za-z\d_]*/.freeze
|
10
8
|
SCOPE_NAME_PATTERN = /(?<scope_name>#{fragment}(?:#{SCOPE_OPERATOR}#{fragment})*)/.freeze
|
11
9
|
SCOPE_SYMBOL_PATTERN = '(?<scope_symbol>[.#])'
|
@@ -16,16 +14,6 @@ module Mutant
|
|
16
14
|
super.freeze
|
17
15
|
end
|
18
16
|
|
19
|
-
# Syntax of expression
|
20
|
-
#
|
21
|
-
# @return [Matcher]
|
22
|
-
abstract_method :matcher
|
23
|
-
|
24
|
-
# Syntax of expression
|
25
|
-
#
|
26
|
-
# @return [String]
|
27
|
-
abstract_method :syntax
|
28
|
-
|
29
17
|
# Match length with other expression
|
30
18
|
#
|
31
19
|
# @param [Expression] other
|
@@ -15,10 +15,10 @@ module Mutant
|
|
15
15
|
|
16
16
|
private(*anima.attribute_names)
|
17
17
|
|
18
|
-
MATCHERS =
|
19
|
-
'.' => [Matcher::Methods::Singleton, Matcher::Methods::Metaclass],
|
20
|
-
'#' => [Matcher::Methods::Instance]
|
21
|
-
|
18
|
+
MATCHERS = {
|
19
|
+
'.' => [Matcher::Methods::Singleton, Matcher::Methods::Metaclass].freeze,
|
20
|
+
'#' => [Matcher::Methods::Instance].freeze
|
21
|
+
}.freeze
|
22
22
|
|
23
23
|
METHOD_NAME_PATTERN = /(?<method_name>.+)/.freeze
|
24
24
|
|
@@ -12,10 +12,11 @@ module Mutant
|
|
12
12
|
|
13
13
|
private(*anima.attribute_names)
|
14
14
|
|
15
|
-
MATCHERS =
|
16
|
-
'.' => [Matcher::Methods::Singleton, Matcher::Methods::Metaclass],
|
17
|
-
'#' => [Matcher::Methods::Instance]
|
18
|
-
|
15
|
+
MATCHERS = {
|
16
|
+
'.' => [Matcher::Methods::Singleton, Matcher::Methods::Metaclass].freeze,
|
17
|
+
'#' => [Matcher::Methods::Instance].freeze
|
18
|
+
}.freeze
|
19
|
+
|
19
20
|
private_constant(*constants(false))
|
20
21
|
|
21
22
|
REGEXP = /\A#{SCOPE_NAME_PATTERN}#{SCOPE_SYMBOL_PATTERN}\z/.freeze
|
data/lib/mutant/integration.rb
CHANGED
@@ -4,7 +4,7 @@ module Mutant
|
|
4
4
|
|
5
5
|
# Abstract base class mutant test framework integrations
|
6
6
|
class Integration
|
7
|
-
include AbstractType, Adamantium
|
7
|
+
include AbstractType, Adamantium, Anima.new(:expression_parser, :world)
|
8
8
|
|
9
9
|
LOAD_MESSAGE = <<~'MESSAGE'
|
10
10
|
Unable to load integration mutant-%<integration_name>s:
|
@@ -39,7 +39,7 @@ module Mutant
|
|
39
39
|
attempt_require(env).bind { attempt_const_get(env) }.fmap do |klass|
|
40
40
|
klass.new(
|
41
41
|
expression_parser: env.config.expression_parser,
|
42
|
-
|
42
|
+
world: env.world
|
43
43
|
).setup
|
44
44
|
end
|
45
45
|
end
|
@@ -92,5 +92,11 @@ module Mutant
|
|
92
92
|
#
|
93
93
|
# @return [Enumerable<Test>]
|
94
94
|
abstract_method :all_tests
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def timer
|
99
|
+
world.timer
|
100
|
+
end
|
95
101
|
end # Integration
|
96
102
|
end # Mutant
|
@@ -28,7 +28,7 @@ module Mutant
|
|
28
28
|
# * Child process freezing after closing the pipes needs to be
|
29
29
|
# detected by parent process.
|
30
30
|
class Fork < self
|
31
|
-
include(Adamantium
|
31
|
+
include(Adamantium, Concord.new(:world))
|
32
32
|
|
33
33
|
READ_SIZE = 4096
|
34
34
|
|
@@ -36,7 +36,7 @@ module Mutant
|
|
36
36
|
|
37
37
|
# Pipe abstraction
|
38
38
|
class Pipe
|
39
|
-
include Adamantium
|
39
|
+
include Adamantium, Anima.new(:reader, :writer)
|
40
40
|
|
41
41
|
# Run block with pipe in binmode
|
42
42
|
#
|
@@ -66,10 +66,7 @@ module Mutant
|
|
66
66
|
|
67
67
|
# rubocop:disable Metrics/ClassLength
|
68
68
|
class Parent
|
69
|
-
include(
|
70
|
-
Anima.new(*ATTRIBUTES),
|
71
|
-
Procto.call
|
72
|
-
)
|
69
|
+
include(Anima.new(*ATTRIBUTES), Procto)
|
73
70
|
|
74
71
|
# Prevent mutation from `process.fork` to `fork` to call Kernel#fork
|
75
72
|
undef_method :fork
|
@@ -212,11 +209,7 @@ module Mutant
|
|
212
209
|
# rubocop:enable Metrics/ClassLength
|
213
210
|
|
214
211
|
class Child
|
215
|
-
include(
|
216
|
-
Adamantium::Flat,
|
217
|
-
Anima.new(*ATTRIBUTES),
|
218
|
-
Procto.call
|
219
|
-
)
|
212
|
+
include(Adamantium, Anima.new(*ATTRIBUTES), Procto)
|
220
213
|
|
221
214
|
# Handle child process
|
222
215
|
#
|