mutant 0.8.24 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +3 -3
- data/Changelog.md +14 -654
- data/Gemfile +13 -0
- data/Gemfile.lock +59 -64
- data/LICENSE +271 -20
- data/README.md +73 -140
- data/Rakefile +0 -21
- data/bin/mutant +7 -2
- data/config/reek.yml +2 -1
- data/config/rubocop.yml +5 -9
- data/docs/incremental.md +76 -0
- data/docs/known-problems.md +0 -14
- data/docs/mutant-minitest.md +1 -1
- data/docs/mutant-rspec.md +2 -24
- data/lib/mutant.rb +45 -53
- data/lib/mutant/ast/nodes.rb +0 -2
- data/lib/mutant/ast/types.rb +1 -117
- data/lib/mutant/base.rb +192 -0
- data/lib/mutant/bootstrap.rb +145 -0
- data/lib/mutant/cli.rb +68 -54
- data/lib/mutant/config.rb +119 -6
- data/lib/mutant/env.rb +94 -8
- data/lib/mutant/expression.rb +6 -1
- data/lib/mutant/expression/parser.rb +9 -31
- data/lib/mutant/integration.rb +64 -36
- data/lib/mutant/isolation.rb +16 -1
- data/lib/mutant/isolation/fork.rb +105 -40
- data/lib/mutant/license.rb +34 -0
- data/lib/mutant/license/subscription.rb +47 -0
- data/lib/mutant/license/subscription/commercial.rb +57 -0
- data/lib/mutant/license/subscription/opensource.rb +77 -0
- data/lib/mutant/loader.rb +27 -4
- data/lib/mutant/matcher.rb +48 -1
- data/lib/mutant/matcher/chain.rb +1 -1
- data/lib/mutant/matcher/config.rb +0 -2
- data/lib/mutant/matcher/filter.rb +1 -1
- data/lib/mutant/matcher/method.rb +11 -7
- data/lib/mutant/matcher/methods.rb +1 -1
- data/lib/mutant/matcher/namespace.rb +1 -1
- data/lib/mutant/matcher/null.rb +1 -1
- data/lib/mutant/matcher/scope.rb +1 -1
- data/lib/mutant/meta/example/dsl.rb +0 -8
- data/lib/mutant/mutation.rb +1 -2
- data/lib/mutant/mutator/node.rb +2 -9
- data/lib/mutant/mutator/node/arguments.rb +1 -1
- data/lib/mutant/mutator/node/class.rb +0 -8
- data/lib/mutant/mutator/node/define.rb +0 -12
- data/lib/mutant/mutator/node/generic.rb +30 -44
- data/lib/mutant/mutator/node/index.rb +4 -4
- data/lib/mutant/mutator/node/literal/regex.rb +0 -39
- data/lib/mutant/mutator/node/send.rb +13 -12
- data/lib/mutant/parallel.rb +61 -40
- data/lib/mutant/parallel/driver.rb +59 -0
- data/lib/mutant/parallel/source.rb +6 -2
- data/lib/mutant/parallel/worker.rb +63 -45
- data/lib/mutant/range.rb +15 -0
- data/lib/mutant/reporter/cli.rb +5 -11
- data/lib/mutant/reporter/cli/format.rb +3 -46
- data/lib/mutant/reporter/cli/printer/config.rb +5 -6
- data/lib/mutant/reporter/cli/printer/env.rb +40 -0
- data/lib/mutant/reporter/cli/printer/env_progress.rb +13 -17
- data/lib/mutant/reporter/cli/printer/isolation_result.rb +17 -3
- data/lib/mutant/reporter/cli/printer/mutation_result.rb +2 -3
- data/lib/mutant/reporter/cli/printer/status_progressive.rb +19 -10
- data/lib/mutant/repository.rb +0 -65
- data/lib/mutant/repository/diff.rb +104 -0
- data/lib/mutant/repository/diff/ranges.rb +52 -0
- data/lib/mutant/result.rb +16 -7
- data/lib/mutant/runner.rb +38 -47
- data/lib/mutant/runner/sink.rb +1 -1
- data/lib/mutant/selector/null.rb +19 -0
- data/lib/mutant/subject.rb +3 -1
- data/lib/mutant/subject/method/instance.rb +3 -1
- data/lib/mutant/transform.rb +511 -0
- data/lib/mutant/variable.rb +282 -0
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/warnings.rb +113 -0
- data/meta/case.rb +1 -0
- data/meta/class.rb +0 -9
- data/meta/def.rb +1 -26
- data/meta/regexp.rb +10 -20
- data/meta/send.rb +14 -46
- data/mutant-minitest.gemspec +1 -1
- data/mutant-rspec.gemspec +2 -2
- data/mutant.gemspec +15 -16
- data/mutant.yml +6 -0
- data/spec/integration/mutant/isolation/fork_spec.rb +22 -5
- data/spec/integration/mutant/minitest_spec.rb +3 -2
- data/spec/integration/mutant/rspec_spec.rb +4 -3
- data/spec/integrations.yml +16 -13
- data/spec/shared/base_behavior.rb +45 -0
- data/spec/shared/framework_integration_behavior.rb +43 -14
- data/spec/spec_helper.rb +21 -17
- data/spec/support/corpus.rb +56 -95
- data/spec/support/shared_context.rb +37 -14
- data/spec/support/xspec.rb +7 -3
- data/spec/unit/mutant/bootstrap_spec.rb +216 -0
- data/spec/unit/mutant/cli_spec.rb +173 -117
- data/spec/unit/mutant/config_spec.rb +126 -0
- data/spec/unit/mutant/either_spec.rb +247 -0
- data/spec/unit/mutant/env_spec.rb +162 -40
- data/spec/unit/mutant/expression/method_spec.rb +16 -0
- data/spec/unit/mutant/expression/parser_spec.rb +29 -33
- data/spec/unit/mutant/expression_spec.rb +5 -7
- data/spec/unit/mutant/integration_spec.rb +100 -9
- data/spec/unit/mutant/isolation/fork_spec.rb +125 -67
- data/spec/unit/mutant/isolation/result_spec.rb +33 -1
- data/spec/unit/mutant/license_spec.rb +257 -0
- data/spec/unit/mutant/loader_spec.rb +50 -11
- data/spec/unit/mutant/matcher/compiler_spec.rb +0 -78
- data/spec/unit/mutant/matcher/method/instance_spec.rb +55 -11
- data/spec/unit/mutant/matcher/method/singleton_spec.rb +12 -2
- data/spec/unit/mutant/matcher_spec.rb +102 -0
- data/spec/unit/mutant/maybe_spec.rb +60 -0
- data/spec/unit/mutant/meta/example/dsl_spec.rb +1 -17
- data/spec/unit/mutant/mutation_spec.rb +13 -6
- data/spec/unit/mutant/parallel/driver_spec.rb +112 -14
- data/spec/unit/mutant/parallel/source/array_spec.rb +25 -17
- data/spec/unit/mutant/parallel/worker_spec.rb +182 -44
- data/spec/unit/mutant/parallel_spec.rb +105 -8
- data/spec/unit/mutant/range_spec.rb +141 -0
- data/spec/unit/mutant/reporter/cli/printer/config_spec.rb +7 -21
- data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +15 -6
- data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +10 -2
- data/spec/unit/mutant/reporter/cli/printer/isolation_result_spec.rb +12 -4
- data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +31 -2
- data/spec/unit/mutant/reporter/cli/printer/status_progressive_spec.rb +4 -4
- data/spec/unit/mutant/reporter/cli/printer/subject_result_spec.rb +5 -0
- data/spec/unit/mutant/reporter/cli_spec.rb +46 -123
- data/spec/unit/mutant/repository/diff/ranges_spec.rb +180 -0
- data/spec/unit/mutant/repository/diff_spec.rb +84 -71
- data/spec/unit/mutant/require_highjack_spec.rb +1 -1
- data/spec/unit/mutant/result/env_spec.rb +39 -9
- data/spec/unit/mutant/result/test_spec.rb +14 -0
- data/spec/unit/mutant/runner_spec.rb +88 -41
- data/spec/unit/mutant/selector/expression_spec.rb +11 -10
- data/spec/unit/mutant/selector/null_spec.rb +17 -0
- data/spec/unit/mutant/subject/method/instance_spec.rb +44 -5
- data/spec/unit/mutant/subject/method/singleton_spec.rb +9 -2
- data/spec/unit/mutant/subject_spec.rb +9 -1
- data/spec/unit/mutant/transform/array_spec.rb +92 -0
- data/spec/unit/mutant/transform/bool_spec.rb +63 -0
- data/spec/unit/mutant/transform/error_spec.rb +132 -0
- data/spec/unit/mutant/transform/exception_spec.rb +44 -0
- data/spec/unit/mutant/transform/hash_spec.rb +236 -0
- data/spec/unit/mutant/transform/index_spec.rb +92 -0
- data/spec/unit/mutant/transform/named_spec.rb +49 -0
- data/spec/unit/mutant/transform/primitive_spec.rb +56 -0
- data/spec/unit/mutant/transform/sequence_spec.rb +98 -0
- data/spec/unit/mutant/variable_spec.rb +618 -0
- data/spec/unit/mutant/warnings_spec.rb +89 -0
- data/spec/unit/mutant/world_spec.rb +63 -0
- data/test_app/Gemfile.minitest +0 -2
- metadata +79 -113
- data/.gitattributes +0 -1
- data/.ruby-gemset +0 -1
- data/config/triage.yml +0 -2
- data/lib/mutant/actor.rb +0 -57
- data/lib/mutant/actor/env.rb +0 -31
- data/lib/mutant/actor/mailbox.rb +0 -34
- data/lib/mutant/actor/receiver.rb +0 -42
- data/lib/mutant/actor/sender.rb +0 -26
- data/lib/mutant/ast/meta/restarg.rb +0 -19
- data/lib/mutant/ast/regexp.rb +0 -42
- data/lib/mutant/ast/regexp/transformer.rb +0 -187
- data/lib/mutant/ast/regexp/transformer/direct.rb +0 -123
- data/lib/mutant/ast/regexp/transformer/named_group.rb +0 -59
- data/lib/mutant/ast/regexp/transformer/options_group.rb +0 -83
- data/lib/mutant/ast/regexp/transformer/quantifier.rb +0 -114
- data/lib/mutant/ast/regexp/transformer/recursive.rb +0 -58
- data/lib/mutant/ast/regexp/transformer/root.rb +0 -31
- data/lib/mutant/ast/regexp/transformer/text.rb +0 -60
- data/lib/mutant/env/bootstrap.rb +0 -160
- data/lib/mutant/matcher/compiler.rb +0 -60
- data/lib/mutant/mutator/node/regexp.rb +0 -35
- data/lib/mutant/mutator/node/regexp/alternation_meta.rb +0 -23
- data/lib/mutant/mutator/node/regexp/capture_group.rb +0 -28
- data/lib/mutant/mutator/node/regexp/character_type.rb +0 -32
- data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +0 -23
- data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +0 -23
- data/lib/mutant/mutator/node/regexp/greedy_zero_or_more.rb +0 -27
- data/lib/mutant/parallel/master.rb +0 -181
- data/lib/mutant/reporter/cli/printer/status.rb +0 -53
- data/lib/mutant/reporter/cli/tput.rb +0 -46
- data/lib/mutant/warning_filter.rb +0 -61
- data/meta/regexp/character_types.rb +0 -23
- data/meta/regexp/regexp_alternation_meta.rb +0 -13
- data/meta/regexp/regexp_bol_anchor.rb +0 -10
- data/meta/regexp/regexp_bos_anchor.rb +0 -18
- data/meta/regexp/regexp_capture_group.rb +0 -19
- data/meta/regexp/regexp_eol_anchor.rb +0 -10
- data/meta/regexp/regexp_eos_anchor.rb +0 -8
- data/meta/regexp/regexp_eos_ob_eol_anchor.rb +0 -10
- data/meta/regexp/regexp_greedy_zero_or_more.rb +0 -12
- data/meta/regexp/regexp_root_expression.rb +0 -10
- data/meta/restarg.rb +0 -10
- data/spec/support/fake_actor.rb +0 -111
- data/spec/support/warning.rb +0 -66
- data/spec/unit/mutant/actor/binding_spec.rb +0 -34
- data/spec/unit/mutant/actor/env_spec.rb +0 -31
- data/spec/unit/mutant/actor/mailbox_spec.rb +0 -28
- data/spec/unit/mutant/actor/message_spec.rb +0 -25
- data/spec/unit/mutant/actor/receiver_spec.rb +0 -58
- data/spec/unit/mutant/actor/sender_spec.rb +0 -24
- data/spec/unit/mutant/ast/regexp/parse_spec.rb +0 -19
- data/spec/unit/mutant/ast/regexp/transformer/lookup_table/table_spec.rb +0 -21
- data/spec/unit/mutant/ast/regexp/transformer/lookup_table_spec.rb +0 -35
- data/spec/unit/mutant/ast/regexp/transformer_spec.rb +0 -21
- data/spec/unit/mutant/ast/regexp_spec.rb +0 -704
- data/spec/unit/mutant/env/bootstrap_spec.rb +0 -188
- data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +0 -26
- data/spec/unit/mutant/parallel/master_spec.rb +0 -338
- data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +0 -121
- data/spec/unit/mutant/reporter/cli/tput_spec.rb +0 -50
- data/spec/unit/mutant/warning_filter_spec.rb +0 -106
- data/spec/unit/mutant_spec.rb +0 -17
- data/test_app/Gemfile.rspec3.7 +0 -7
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
# Bootstrap process
|
5
|
+
#
|
6
|
+
# The role of the boostrap is to take the pure config and apply it against
|
7
|
+
# the impure world to produce an environment.
|
8
|
+
#
|
9
|
+
# env = config interpreted against the world
|
10
|
+
module Bootstrap
|
11
|
+
include Adamantium::Flat, Anima.new(:config, :parser, :world)
|
12
|
+
|
13
|
+
SEMANTICS_MESSAGE_FORMAT =
|
14
|
+
"%<message>s. Fix your lib to follow normal ruby semantics!\n" \
|
15
|
+
'{Module,Class}#name should return resolvable constant name as String or nil'
|
16
|
+
|
17
|
+
CLASS_NAME_RAISED_EXCEPTION =
|
18
|
+
'%<scope_class>s#name from: %<scope>s raised an error: %<exception>s'
|
19
|
+
|
20
|
+
CLASS_NAME_TYPE_MISMATCH_FORMAT =
|
21
|
+
'%<scope_class>s#name from: %<scope>s returned %<name>s'
|
22
|
+
|
23
|
+
private_constant(*constants(false))
|
24
|
+
|
25
|
+
# Run Bootstrap
|
26
|
+
#
|
27
|
+
# @param [World] world
|
28
|
+
# @param [Config] config
|
29
|
+
#
|
30
|
+
# @return [Either<String, Env>]
|
31
|
+
#
|
32
|
+
# rubocop:disable Metrics/MethodLength
|
33
|
+
def self.apply(world, config)
|
34
|
+
env = Env
|
35
|
+
.empty(world, config)
|
36
|
+
.tap(&method(:infect))
|
37
|
+
.with(matchable_scopes: matchable_scopes(world, config))
|
38
|
+
|
39
|
+
subjects = Matcher.from_config(env.config.matcher).call(env)
|
40
|
+
|
41
|
+
Integration.setup(env).fmap do |integration|
|
42
|
+
env.with(
|
43
|
+
integration: integration,
|
44
|
+
mutations: subjects.flat_map(&:mutations),
|
45
|
+
selector: Selector::Expression.new(integration),
|
46
|
+
subjects: subjects
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
# rubocop:enable Metrics/MethodLength
|
51
|
+
|
52
|
+
# Infect environment
|
53
|
+
#
|
54
|
+
# @return [undefined]
|
55
|
+
def self.infect(env)
|
56
|
+
config, world = env.config, env.world
|
57
|
+
|
58
|
+
config.includes.each(&world.load_path.method(:<<))
|
59
|
+
config.requires.each(&world.kernel.method(:require))
|
60
|
+
end
|
61
|
+
private_class_method :infect
|
62
|
+
|
63
|
+
# Matchable scopes
|
64
|
+
#
|
65
|
+
# @param [World] world
|
66
|
+
# @param [Config] config
|
67
|
+
#
|
68
|
+
# @return [Array<Scope>]
|
69
|
+
def self.matchable_scopes(world, config)
|
70
|
+
scopes = world.object_space.each_object(Module).each_with_object([]) do |scope, aggregate|
|
71
|
+
expression = expression(config.reporter, config.expression_parser, scope) || next
|
72
|
+
aggregate << Scope.new(scope, expression)
|
73
|
+
end
|
74
|
+
|
75
|
+
scopes.sort_by { |scope| scope.expression.syntax }
|
76
|
+
end
|
77
|
+
private_class_method :matchable_scopes
|
78
|
+
|
79
|
+
# Scope name from scoping object
|
80
|
+
#
|
81
|
+
# @param [Class, Module] scope
|
82
|
+
#
|
83
|
+
# @return [String]
|
84
|
+
# if scope has a name and does not raise exceptions obtaining it
|
85
|
+
#
|
86
|
+
# @return [nil]
|
87
|
+
# otherwise
|
88
|
+
def self.scope_name(reporter, scope)
|
89
|
+
scope.name
|
90
|
+
rescue => exception
|
91
|
+
semantics_warning(
|
92
|
+
reporter,
|
93
|
+
CLASS_NAME_RAISED_EXCEPTION,
|
94
|
+
exception: exception.inspect,
|
95
|
+
scope: scope,
|
96
|
+
scope_class: scope.class
|
97
|
+
)
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
private_class_method :scope_name
|
101
|
+
|
102
|
+
# Try to turn scope into expression
|
103
|
+
#
|
104
|
+
# @param [Expression::Parser] expression_parser
|
105
|
+
# @param [Class, Module] scope
|
106
|
+
#
|
107
|
+
# @return [Expression]
|
108
|
+
# if scope can be represented in an expression
|
109
|
+
#
|
110
|
+
# @return [nil]
|
111
|
+
# otherwise
|
112
|
+
#
|
113
|
+
# rubocop:disable Metrics/MethodLength
|
114
|
+
#
|
115
|
+
# ignore :reek:LongParameterList
|
116
|
+
def self.expression(reporter, expression_parser, scope)
|
117
|
+
name = scope_name(reporter, scope) or return
|
118
|
+
|
119
|
+
unless name.instance_of?(String)
|
120
|
+
semantics_warning(
|
121
|
+
reporter,
|
122
|
+
CLASS_NAME_TYPE_MISMATCH_FORMAT,
|
123
|
+
name: name,
|
124
|
+
scope_class: scope.class,
|
125
|
+
scope: scope
|
126
|
+
)
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
expression_parser.apply(name).from_right {}
|
131
|
+
end
|
132
|
+
private_class_method :expression
|
133
|
+
# rubocop:enable Metrics/MethodLength
|
134
|
+
|
135
|
+
# Write a semantics warning
|
136
|
+
#
|
137
|
+
# @return [undefined]
|
138
|
+
#
|
139
|
+
# ignore :reek:LongParameterList
|
140
|
+
def self.semantics_warning(reporter, format, options)
|
141
|
+
reporter.warn(SEMANTICS_MESSAGE_FORMAT % { message: format % options })
|
142
|
+
end
|
143
|
+
private_class_method :semantics_warning
|
144
|
+
end # Bootstrap
|
145
|
+
end # Mutant
|
data/lib/mutant/cli.rb
CHANGED
@@ -3,63 +3,91 @@
|
|
3
3
|
module Mutant
|
4
4
|
# Commandline parser / runner
|
5
5
|
class CLI
|
6
|
-
include
|
6
|
+
include Concord.new(:world, :config)
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
private_class_method :new
|
9
|
+
|
10
|
+
OPTIONS =
|
11
|
+
%i[
|
12
|
+
add_environment_options
|
13
|
+
add_mutation_options
|
14
|
+
add_filter_options
|
15
|
+
add_debug_options
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
private_constant(*constants(false))
|
10
19
|
|
11
20
|
# Run cli with arguments
|
12
21
|
#
|
13
|
-
# @param [
|
22
|
+
# @param [World] world
|
23
|
+
# the outside world
|
24
|
+
#
|
25
|
+
# @param [Config] default_config
|
26
|
+
# the default config
|
27
|
+
#
|
28
|
+
# @param [Array<String>]
|
29
|
+
# the user provided arguments
|
14
30
|
#
|
15
31
|
# @return [Boolean]
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
32
|
+
#
|
33
|
+
# rubocop:disable Style/Semicolon
|
34
|
+
#
|
35
|
+
# ignore :reek:LongParameterList
|
36
|
+
def self.run(world, default_config, arguments)
|
37
|
+
License
|
38
|
+
.apply(world)
|
39
|
+
.apply { Config.load_config_file(world, default_config) }
|
40
|
+
.apply { |file_config| apply(world, file_config, arguments) }
|
41
|
+
.apply { |cli_config| Bootstrap.apply(world, cli_config) }
|
42
|
+
.apply(&Runner.method(:apply))
|
43
|
+
.from_right { |error| world.stderr.puts(error); return false }
|
44
|
+
.success?
|
21
45
|
end
|
46
|
+
# rubocop:enable Style/Semicolon
|
22
47
|
|
23
|
-
#
|
48
|
+
# Parse arguments into config
|
24
49
|
#
|
25
|
-
# @param [
|
50
|
+
# @param [World] world
|
51
|
+
# @param [Config] config
|
52
|
+
# @param [Array<String>] arguments
|
26
53
|
#
|
27
|
-
# @return [
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
54
|
+
# @return [Either<OptionParser::ParseError, Config>]
|
55
|
+
#
|
56
|
+
# ignore :reek:LongParameterList
|
57
|
+
def self.apply(world, config, arguments)
|
58
|
+
Either
|
59
|
+
.wrap_error(OptionParser::ParseError) { new(world, config).parse(arguments) }
|
60
|
+
.lmap(&:message)
|
32
61
|
end
|
33
62
|
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
63
|
+
# Local opt out of option parser defaults
|
64
|
+
class OptionParser < ::OptionParser
|
65
|
+
# Kill defaults added by option parser that
|
66
|
+
# inference with ours under mutation testing.
|
67
|
+
define_method(:add_officious) {}
|
68
|
+
end # OptionParser
|
40
69
|
|
41
70
|
# Parse the command-line options
|
42
71
|
#
|
43
72
|
# @param [Array<String>] arguments
|
44
73
|
# Command-line options and arguments to be parsed.
|
45
74
|
#
|
46
|
-
# @
|
47
|
-
# An error occurred while parsing the options.
|
48
|
-
#
|
49
|
-
# @return [undefined]
|
75
|
+
# @return [Config]
|
50
76
|
def parse(arguments)
|
51
77
|
opts = OptionParser.new do |builder|
|
52
78
|
builder.banner = 'usage: mutant [options] MATCH_EXPRESSION ...'
|
53
|
-
|
79
|
+
OPTIONS.each do |name|
|
54
80
|
__send__(name, builder)
|
55
81
|
end
|
56
82
|
end
|
57
83
|
|
58
|
-
parse_match_expressions(opts.parse!(arguments))
|
59
|
-
|
60
|
-
|
84
|
+
parse_match_expressions(opts.parse!(arguments.dup))
|
85
|
+
|
86
|
+
config
|
61
87
|
end
|
62
88
|
|
89
|
+
private
|
90
|
+
|
63
91
|
# Parse matchers
|
64
92
|
#
|
65
93
|
# @param [Array<String>] expressions
|
@@ -67,7 +95,7 @@ module Mutant
|
|
67
95
|
# @return [undefined]
|
68
96
|
def parse_match_expressions(expressions)
|
69
97
|
expressions.each do |expression|
|
70
|
-
add_matcher(:match_expressions, config.expression_parser.(expression))
|
98
|
+
add_matcher(:match_expressions, config.expression_parser.apply(expression).from_right)
|
71
99
|
end
|
72
100
|
end
|
73
101
|
|
@@ -94,17 +122,6 @@ module Mutant
|
|
94
122
|
end
|
95
123
|
end
|
96
124
|
|
97
|
-
# Use integration
|
98
|
-
#
|
99
|
-
# @param [String] name
|
100
|
-
#
|
101
|
-
# @return [undefined]
|
102
|
-
def setup_integration(name)
|
103
|
-
with(integration: Integration.setup(config.kernel, name))
|
104
|
-
rescue LoadError
|
105
|
-
raise Error, "Could not load integration #{name.inspect} (you may want to try installing the gem mutant-#{name})"
|
106
|
-
end
|
107
|
-
|
108
125
|
# Add mutation options
|
109
126
|
#
|
110
127
|
# @param [OptionParser] opts
|
@@ -114,7 +131,9 @@ module Mutant
|
|
114
131
|
opts.separator(nil)
|
115
132
|
opts.separator('Options:')
|
116
133
|
|
117
|
-
opts.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations'
|
134
|
+
opts.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations') do |name|
|
135
|
+
with(integration: name)
|
136
|
+
end
|
118
137
|
end
|
119
138
|
|
120
139
|
# Add filter options
|
@@ -124,17 +143,13 @@ module Mutant
|
|
124
143
|
# @return [undefined]
|
125
144
|
def add_filter_options(opts)
|
126
145
|
opts.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
|
127
|
-
add_matcher(:ignore_expressions, config.expression_parser.(pattern))
|
146
|
+
add_matcher(:ignore_expressions, config.expression_parser.apply(pattern).from_right)
|
128
147
|
end
|
129
148
|
opts.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
|
130
149
|
add_matcher(
|
131
150
|
:subject_filters,
|
132
151
|
Repository::SubjectFilter.new(
|
133
|
-
Repository::Diff.new(
|
134
|
-
config: config,
|
135
|
-
from: Repository::Diff::HEAD,
|
136
|
-
to: revision
|
137
|
-
)
|
152
|
+
Repository::Diff.new(to: revision, world: world)
|
138
153
|
)
|
139
154
|
)
|
140
155
|
end
|
@@ -150,12 +165,12 @@ module Mutant
|
|
150
165
|
with(fail_fast: true)
|
151
166
|
end
|
152
167
|
opts.on('--version', 'Print mutants version') do
|
153
|
-
puts("mutant-#{VERSION}")
|
154
|
-
|
168
|
+
world.stdout.puts("mutant-#{VERSION}")
|
169
|
+
world.kernel.exit
|
155
170
|
end
|
156
171
|
opts.on_tail('-h', '--help', 'Show this message') do
|
157
|
-
puts(opts.to_s)
|
158
|
-
|
172
|
+
world.stdout.puts(opts.to_s)
|
173
|
+
world.kernel.exit
|
159
174
|
end
|
160
175
|
end
|
161
176
|
|
@@ -193,6 +208,5 @@ module Mutant
|
|
193
208
|
def add_matcher(attribute, value)
|
194
209
|
with(matcher: config.matcher.add(attribute, value))
|
195
210
|
end
|
196
|
-
|
197
211
|
end # CLI
|
198
212
|
end # Mutant
|
data/lib/mutant/config.rb
CHANGED
@@ -1,6 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Mutant
|
4
|
+
# The outer world IO objects mutant does interact with
|
5
|
+
class World
|
6
|
+
include Adamantium::Flat, Anima.new(
|
7
|
+
:condition_variable,
|
8
|
+
:gem,
|
9
|
+
:io,
|
10
|
+
:json,
|
11
|
+
:kernel,
|
12
|
+
:load_path,
|
13
|
+
:marshal,
|
14
|
+
:mutex,
|
15
|
+
:object_space,
|
16
|
+
:open3,
|
17
|
+
:pathname,
|
18
|
+
:process,
|
19
|
+
:stderr,
|
20
|
+
:stdout,
|
21
|
+
:thread,
|
22
|
+
:warnings
|
23
|
+
)
|
24
|
+
|
25
|
+
INSPECT = '#<Mutant::World>'
|
26
|
+
|
27
|
+
private_constant(*constants(false))
|
28
|
+
|
29
|
+
# Object inspection
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
def inspect
|
33
|
+
INSPECT
|
34
|
+
end
|
35
|
+
|
36
|
+
# Capture stdout of a command
|
37
|
+
#
|
38
|
+
# @param [Array<String>] command
|
39
|
+
#
|
40
|
+
# @return [Either<String,String>]
|
41
|
+
def capture_stdout(command)
|
42
|
+
stdout, status = open3.capture2(*command, binmode: true)
|
43
|
+
|
44
|
+
if status.success?
|
45
|
+
Either::Right.new(stdout)
|
46
|
+
else
|
47
|
+
Either::Left.new("Command #{command} failed!")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end # World
|
51
|
+
|
4
52
|
# Standalone configuration of a mutant execution.
|
5
53
|
#
|
6
54
|
# Does not reference any "external" volatile state. The configuration applied
|
@@ -9,17 +57,13 @@ module Mutant
|
|
9
57
|
include Adamantium::Flat, Anima.new(
|
10
58
|
:expression_parser,
|
11
59
|
:fail_fast,
|
12
|
-
:integration,
|
13
60
|
:includes,
|
61
|
+
:integration,
|
14
62
|
:isolation,
|
15
63
|
:jobs,
|
16
|
-
:kernel,
|
17
|
-
:load_path,
|
18
64
|
:matcher,
|
19
|
-
:open3,
|
20
|
-
:pathname,
|
21
|
-
:requires,
|
22
65
|
:reporter,
|
66
|
+
:requires,
|
23
67
|
:zombie
|
24
68
|
)
|
25
69
|
|
@@ -27,5 +71,74 @@ module Mutant
|
|
27
71
|
define_method(:"#{name}?") { public_send(name) }
|
28
72
|
end
|
29
73
|
|
74
|
+
boolean = Transform::Boolean.new
|
75
|
+
integer = Transform::Primitive.new(Integer)
|
76
|
+
string = Transform::Primitive.new(String)
|
77
|
+
|
78
|
+
string_array = Transform::Array.new(string)
|
79
|
+
|
80
|
+
TRANSFORM = Transform::Sequence.new(
|
81
|
+
[
|
82
|
+
Transform::Exception.new(SystemCallError, :read.to_proc),
|
83
|
+
Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
|
84
|
+
Transform::Hash.new(
|
85
|
+
optional: [
|
86
|
+
Transform::Hash::Key.new('fail_fast', boolean),
|
87
|
+
Transform::Hash::Key.new('includes', string_array),
|
88
|
+
Transform::Hash::Key.new('integration', string),
|
89
|
+
Transform::Hash::Key.new('jobs', integer),
|
90
|
+
Transform::Hash::Key.new('requires', string_array)
|
91
|
+
],
|
92
|
+
required: []
|
93
|
+
),
|
94
|
+
Transform::Hash::Symbolize.new
|
95
|
+
]
|
96
|
+
)
|
97
|
+
|
98
|
+
MORE_THAN_ONE_CONFIG_FILE = <<~'MESSAGE'
|
99
|
+
Found more than one candidate for use as implicit config file: %s
|
100
|
+
MESSAGE
|
101
|
+
|
102
|
+
CANDIDATES = %w[
|
103
|
+
.mutant.yml
|
104
|
+
config/mutant.yml
|
105
|
+
mutant.yml
|
106
|
+
].freeze
|
107
|
+
|
108
|
+
private_constant(*constants(false))
|
109
|
+
|
110
|
+
# Load config file
|
111
|
+
#
|
112
|
+
# @param [World] world
|
113
|
+
# @param [Config] config
|
114
|
+
#
|
115
|
+
# @return [Either<String,Config>]
|
116
|
+
def self.load_config_file(world, config)
|
117
|
+
files = CANDIDATES.map(&world.pathname.method(:new)).select(&:readable?)
|
118
|
+
|
119
|
+
if files.one?
|
120
|
+
load_contents(files.first).fmap(&config.method(:with))
|
121
|
+
elsif files.empty?
|
122
|
+
Either::Right.new(config)
|
123
|
+
else
|
124
|
+
Either::Left.new(MORE_THAN_ONE_CONFIG_FILE % files.join(', '))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Load contents of file
|
129
|
+
#
|
130
|
+
# @param [Pathname] path
|
131
|
+
#
|
132
|
+
# @return [Config]
|
133
|
+
#
|
134
|
+
# @raise [Either<String, Hash{Symbol => Object}>]
|
135
|
+
# in case of config file error
|
136
|
+
def self.load_contents(path)
|
137
|
+
Transform::Named
|
138
|
+
.new(path.to_s, TRANSFORM)
|
139
|
+
.apply(path)
|
140
|
+
.lmap(&:compact_message)
|
141
|
+
end
|
142
|
+
private_class_method :load_contents
|
30
143
|
end # Config
|
31
144
|
end # Mutant
|