mutant 0.10.21 → 0.10.26
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/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
|
#
|