mutant 0.11.16 → 0.11.18

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mutant +55 -46
  3. data/lib/mutant/bootstrap.rb +66 -35
  4. data/lib/mutant/cli/command/environment/run.rb +1 -1
  5. data/lib/mutant/cli/command/environment.rb +2 -2
  6. data/lib/mutant/cli/command.rb +24 -11
  7. data/lib/mutant/env.rb +9 -0
  8. data/lib/mutant/mutator/node/literal/regex.rb +8 -8
  9. data/lib/mutant/mutator/node/send.rb +27 -17
  10. data/lib/mutant/mutator/regexp.rb +211 -0
  11. data/lib/mutant/parallel/driver.rb +1 -0
  12. data/lib/mutant/parallel/worker.rb +5 -1
  13. data/lib/mutant/runner.rb +8 -6
  14. data/lib/mutant/segment/recorder.rb +124 -0
  15. data/lib/mutant/segment.rb +25 -0
  16. data/lib/mutant/version.rb +1 -1
  17. data/lib/mutant/world.rb +5 -0
  18. data/lib/mutant.rb +288 -228
  19. metadata +12 -33
  20. data/lib/mutant/ast/regexp/transformer/direct.rb +0 -145
  21. data/lib/mutant/ast/regexp/transformer/named_group.rb +0 -50
  22. data/lib/mutant/ast/regexp/transformer/options_group.rb +0 -68
  23. data/lib/mutant/ast/regexp/transformer/quantifier.rb +0 -92
  24. data/lib/mutant/ast/regexp/transformer/recursive.rb +0 -56
  25. data/lib/mutant/ast/regexp/transformer/root.rb +0 -28
  26. data/lib/mutant/ast/regexp/transformer/text.rb +0 -58
  27. data/lib/mutant/ast/regexp/transformer.rb +0 -152
  28. data/lib/mutant/ast/regexp.rb +0 -54
  29. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +0 -20
  30. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +0 -20
  31. data/lib/mutant/mutator/node/regexp/capture_group.rb +0 -23
  32. data/lib/mutant/mutator/node/regexp/character_type.rb +0 -31
  33. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +0 -20
  34. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +0 -20
  35. data/lib/mutant/mutator/node/regexp/named_group.rb +0 -39
  36. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +0 -34
  37. data/lib/mutant/mutator/node/regexp.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebfdb4dba80abb7c2b06b6b057cebf6db686b598219979ed3d0730b6e5a9ed82
4
- data.tar.gz: ee03173b094cf29a9d21ad30b383a0b01b8cac7b7ba1132f24f36c25e9e4fee3
3
+ metadata.gz: 327c0520e73601c447d4081b428de0111e59627961c82a955e86b2015fef7ce7
4
+ data.tar.gz: 93dd7338a29a416bce5025d8c95a39cb88cc5e96932435797b60102689726e3d
5
5
  SHA512:
6
- metadata.gz: 90d9c06d5a2900441b49b3bc0fa039b11bb296e389af41927f711431855a28369a8e31faf0b1cc5ba74ab09c6300c21002ac30c0d6eae6b3d63eac3129a692e8
7
- data.tar.gz: 725ecfb369b3c28133d898f3405bb5cec8f780027c8e7c05de59c225719924fde51e192186d2f7a2199a57352b056401fa562a67d75409a637d182bbb50d9390
6
+ metadata.gz: 433b57a38e8b5d68f579d26cb5352cabc222137eb875183364dcd1f39ff8852c8a7f2a95b7ee64a3258ed87f9620670160d8d98df6b0a4513e174ff4b8775a6d
7
+ data.tar.gz: 84b5b86c80958651592da077cfbd5e965587ce02b05c9b27fc9cf86506a80f963351011cf4ebfe23e084ab211a2f9ea2ac6e89f0efcfeb90cc019a28ab42a0b1
data/bin/mutant CHANGED
@@ -1,55 +1,64 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- trap('INT') do |status|
5
- effective_status = status ? status + 128 : 128
6
- exit! effective_status
7
- end
4
+ module Mutant
5
+ # Record executable timestamp
6
+ @executable_timestamp = Process.clock_gettime(Process::CLOCK_MONOTONIC)
7
+
8
+ trap('INT') do |status|
9
+ effective_status = status ? status + 128 : 128
10
+ exit! effective_status
11
+ end
8
12
 
9
- require 'mutant'
13
+ require 'mutant'
10
14
 
11
- Mutant::CLI.parse(
12
- arguments: ARGV,
13
- world: Mutant::WORLD
14
- ).either(
15
- ->(message) { Mutant::WORLD.stderr.puts(message); Kernel.exit(false) },
16
- # rubocop:disable Metrics/BlockLength
17
- lambda do |command|
18
- status =
15
+ WORLD.record(:cli_parse) do
16
+ CLI.parse(
17
+ arguments: ARGV,
18
+ world: Mutant::WORLD
19
+ )
20
+ end.either(
21
+ ->(message) { Mutant::WORLD.stderr.puts(message); Kernel.exit(false) },
22
+ # rubocop:disable Metrics/BlockLength
23
+ lambda do |command|
19
24
  if command.zombie?
20
- $stderr.puts('Running mutant zombified!')
21
- Mutant::Zombifier.call(
22
- namespace: :Zombie,
23
- load_path: $LOAD_PATH,
24
- kernel: Kernel,
25
- pathname: Pathname,
26
- require_highjack: Mutant::RequireHighjack
27
- .public_method(:call)
28
- .to_proc
29
- .curry
30
- .call(Kernel),
31
- root_require: 'mutant',
32
- includes: %w[
33
- adamantium
34
- anima
35
- concord
36
- equalizer
37
- mprelude
38
- mutant
39
- unparser
40
- variable
41
- ]
42
- )
25
+ command = WORLD.record(:zombify) do
26
+ $stderr.puts('Running mutant zombified!')
27
+ Zombifier.call(
28
+ namespace: :Zombie,
29
+ load_path: $LOAD_PATH,
30
+ kernel: Kernel,
31
+ pathname: Pathname,
32
+ require_highjack: RequireHighjack
33
+ .public_method(:call)
34
+ .to_proc
35
+ .curry
36
+ .call(Kernel),
37
+ root_require: 'mutant',
38
+ includes: %w[
39
+ adamantium
40
+ anima
41
+ concord
42
+ equalizer
43
+ mprelude
44
+ mutant
45
+ unparser
46
+ variable
47
+ ]
48
+ )
43
49
 
44
- Zombie::Mutant::CLI.parse(
45
- arguments: ARGV,
46
- world: Zombie::Mutant::WORLD
47
- ).from_right.call
48
- else
49
- command.call
50
+ Zombie::Mutant::CLI.parse(
51
+ arguments: ARGV,
52
+ world: Mutant::WORLD
53
+ ).from_right
54
+ end
50
55
  end
51
56
 
52
- Kernel.exit(status)
53
- end
54
- # rubocop:enable Metrics/BlockLength
55
- )
57
+ WORLD.record(:execute) { command.call }.tap do |status|
58
+ WORLD.recorder.print_profile(WORLD.stderr) if command.print_profile?
59
+ WORLD.kernel.exit(status)
60
+ end
61
+ end
62
+ # rubocop:enable Metrics/BlockLength
63
+ )
64
+ end
@@ -7,6 +7,8 @@ module Mutant
7
7
  # the impure world to produce an environment.
8
8
  #
9
9
  # env = config interpreted against the world
10
+ #
11
+ # rubocop:disable Metrics/ModuleLength
10
12
  module Bootstrap
11
13
  include Adamantium, Anima.new(:config, :parser, :world)
12
14
 
@@ -30,66 +32,94 @@ module Mutant
30
32
  #
31
33
  # rubocop:disable Metrics/MethodLength
32
34
  def self.call(env)
33
- env = load_hooks(env)
34
- .tap(&method(:infect))
35
- .with(matchable_scopes: matchable_scopes(env))
36
-
37
- subjects = start_subject(env, Matcher.from_config(env.config.matcher).call(env))
38
-
39
- Integration.setup(env).fmap do |integration|
40
- env.with(
41
- integration: integration,
42
- mutations: subjects.flat_map(&:mutations),
43
- selector: Selector::Expression.new(integration),
44
- subjects: subjects
45
- )
35
+ env.record(:bootstrap) do
36
+ env = load_hooks(env)
37
+ .tap(&method(:infect))
38
+ .with(matchable_scopes: matchable_scopes(env))
39
+
40
+ matched_subjects = env.record(:subject_match) do
41
+ Matcher.from_config(env.config.matcher).call(env)
42
+ end
43
+
44
+ selected_subjects = subject_select(env, matched_subjects)
45
+
46
+ mutations = env.record(:mutation_generate) do
47
+ selected_subjects.flat_map(&:mutations)
48
+ end
49
+
50
+ Integration.setup(env).fmap do |integration|
51
+ env.with(
52
+ integration: integration,
53
+ mutations: mutations,
54
+ selector: Selector::Expression.new(integration),
55
+ subjects: selected_subjects
56
+ )
57
+ end
46
58
  end
47
59
  end
48
60
  # rubocop:enable Metrics/MethodLength
49
61
 
50
62
  def self.load_hooks(env)
51
- env.with(hooks: Hooks.load_config(env.config))
63
+ env.record(__method__) do
64
+ env.with(hooks: Hooks.load_config(env.config))
65
+ end
52
66
  end
53
67
  private_class_method :load_hooks
54
68
 
55
- def self.start_subject(env, subjects)
56
- start_expressions = env.config.matcher.start_expressions
69
+ def self.subject_select(env, subjects)
70
+ env.record(__method__) do
71
+ start_expressions = env.config.matcher.start_expressions
57
72
 
58
- return subjects if start_expressions.empty?
73
+ return subjects if start_expressions.empty?
59
74
 
60
- subjects.drop_while do |subject|
61
- start_expressions.none? do |expression|
62
- expression.prefix?(subject.expression)
75
+ subjects.drop_while do |subject|
76
+ start_expressions.none? do |expression|
77
+ expression.prefix?(subject.expression)
78
+ end
63
79
  end
64
80
  end
65
81
  end
66
- private_class_method :start_subject
82
+ private_class_method :subject_select
67
83
 
84
+ # rubocop:disable Metrics/AbcSize
85
+ # rubocop:disable Metrics/MethodLength
68
86
  def self.infect(env)
69
- config, hooks, world = env.config, env.hooks, env.world
87
+ env.record(__method__) do
88
+ config, hooks, world = env.config, env.hooks, env.world
70
89
 
71
- hooks.run(:env_infection_pre, env)
90
+ env.record(:hooks_env_infection_pre) do
91
+ hooks.run(:env_infection_pre, env)
92
+ end
72
93
 
73
- config.environment_variables.each do |key, value|
74
- world.environment_variables[key] = value
75
- end
94
+ env.record(:require_target) do
95
+ config.environment_variables.each do |key, value|
96
+ world.environment_variables[key] = value
97
+ end
76
98
 
77
- config.includes.each(&world.load_path.public_method(:<<))
78
- config.requires.each(&world.kernel.public_method(:require))
99
+ config.includes.each(&world.load_path.public_method(:<<))
100
+ config.requires.each(&world.kernel.public_method(:require))
101
+ end
79
102
 
80
- hooks.run(:env_infection_post, env)
103
+ env.record(:hooks_env_infection_post) do
104
+ hooks.run(:env_infection_post, env)
105
+ end
106
+ end
81
107
  end
82
108
  private_class_method :infect
109
+ # rubocop:enable Metrics/AbcSize
110
+ # rubocop:enable Metrics/MethodLength
83
111
 
84
112
  def self.matchable_scopes(env)
85
- config = env.config
113
+ env.record(__method__) do
114
+ config = env.config
86
115
 
87
- scopes = env.world.object_space.each_object(Module).each_with_object([]) do |scope, aggregate|
88
- expression = expression(config.reporter, config.expression_parser, scope) || next
89
- aggregate << Scope.new(scope, expression)
90
- end
116
+ scopes = env.world.object_space.each_object(Module).with_object([]) do |scope, aggregate|
117
+ expression = expression(config.reporter, config.expression_parser, scope) || next
118
+ aggregate << Scope.new(scope, expression)
119
+ end
91
120
 
92
- scopes.sort_by { |scope| scope.expression.syntax }
121
+ scopes.sort_by { |scope| scope.expression.syntax }
122
+ end
93
123
  end
94
124
  private_class_method :matchable_scopes
95
125
 
@@ -132,4 +162,5 @@ module Mutant
132
162
  end
133
163
  private_class_method :semantics_warning
134
164
  end # Bootstrap
165
+ # rubocop:enable Metrics/ModuleLength
135
166
  end # Mutant
@@ -41,7 +41,7 @@ module Mutant
41
41
  end
42
42
 
43
43
  def validate_tests(environment)
44
- if environment.integration.all_tests.length.zero?
44
+ if environment.integration.all_tests.empty?
45
45
  Either::Left.new(NO_TESTS_MESSAGE)
46
46
  else
47
47
  Either::Right.new(environment)
@@ -27,8 +27,8 @@ module Mutant
27
27
  def bootstrap
28
28
  env = Env.empty(world, @config)
29
29
 
30
- Config.load_config_file(env)
31
- .fmap(&method(:expand))
30
+ env
31
+ .record(:config) { Config.load_config_file(env).fmap(&method(:expand)) }
32
32
  .bind { Bootstrap.call(env.with(config: @config)) }
33
33
  end
34
34
 
@@ -4,7 +4,13 @@ module Mutant
4
4
  module CLI
5
5
  # rubocop:disable Metrics/ClassLength
6
6
  class Command
7
- include AbstractType, Anima.new(:world, :main, :parent, :zombie)
7
+ include AbstractType, Anima.new(
8
+ :main,
9
+ :parent,
10
+ :print_profile,
11
+ :world,
12
+ :zombie
13
+ )
8
14
 
9
15
  OPTIONS = [].freeze
10
16
  SUBCOMMANDS = [].freeze
@@ -21,12 +27,13 @@ module Mutant
21
27
  # @return [Command]
22
28
  #
23
29
  # rubocop:disable Metrics/ParameterLists
24
- def self.parse(arguments:, parent: nil, world:, zombie: false)
30
+ def self.parse(arguments:, parent: nil, print_profile: false, world:, zombie: false)
25
31
  new(
26
- main: nil,
27
- parent: parent,
28
- world: world,
29
- zombie: zombie
32
+ main: nil,
33
+ parent: parent,
34
+ print_profile: print_profile,
35
+ world: world,
36
+ zombie: zombie
30
37
  ).__send__(:parse, arguments)
31
38
  end
32
39
  # rubocop:enable Metrics/ParameterLists
@@ -59,7 +66,8 @@ module Mutant
59
66
  [*parent&.full_name, self.class.command_name].join(' ')
60
67
  end
61
68
 
62
- alias_method :zombie?, :zombie
69
+ alias_method :print_profile?, :print_profile
70
+ alias_method :zombie?, :zombie
63
71
 
64
72
  abstract_method :action
65
73
 
@@ -136,6 +144,10 @@ module Mutant
136
144
  capture_main { world.stdout.puts("mutant-#{VERSION}"); true }
137
145
  end
138
146
 
147
+ parser.on('--profile', 'Profile mutant execution') do
148
+ @print_profile = true
149
+ end
150
+
139
151
  parser.on('--zombie', 'Run mutant zombified') do
140
152
  @zombie = true
141
153
  end
@@ -176,10 +188,11 @@ module Mutant
176
188
  else
177
189
  find_command(command_name).bind do |command|
178
190
  command.parse(
179
- arguments: arguments,
180
- parent: self,
181
- world: world,
182
- zombie: zombie
191
+ arguments: arguments,
192
+ parent: self,
193
+ print_profile: print_profile,
194
+ world: world,
195
+ zombie: zombie
183
196
  )
184
197
  end
185
198
  end
data/lib/mutant/env.rb CHANGED
@@ -134,6 +134,15 @@ module Mutant
134
134
  end
135
135
  memoize :test_subject_ratio
136
136
 
137
+ # Record segment
138
+ #
139
+ # @param [Symbol] name
140
+ #
141
+ # @return [self]
142
+ def record(name, &block)
143
+ world.record(name, &block)
144
+ end
145
+
137
146
  private
138
147
 
139
148
  def run_mutation_tests(mutation, tests)
@@ -29,17 +29,17 @@ module Mutant
29
29
  end
30
30
 
31
31
  def mutate_body
32
- # NOTE: will only mutate parts of regexp body if the body is composed of only strings.
33
- # Regular expressions with interpolation are skipped.
34
- return unless (body_ast = AST::Regexp.expand_regexp_ast(input))
32
+ string = Regexp.regexp_body(input) or return
35
33
 
36
- mutate(node: body_ast).each do |mutation|
37
- source = AST::Regexp.to_expression(mutation).to_s
38
- emit_type(s(:str, source), options)
39
- end
34
+ Regexp
35
+ .mutate(::Regexp::Parser.parse(string))
36
+ .each(&method(:emit_regexp_body))
40
37
  end
41
38
 
42
- end # Regex
39
+ def emit_regexp_body(expression)
40
+ emit_type(s(:str, expression.to_str), options)
41
+ end
42
+ end # Regexp
43
43
  end # Literal
44
44
  end # Node
45
45
  end # Mutator
@@ -50,9 +50,19 @@ module Mutant
50
50
  }.freeze
51
51
  }.freeze
52
52
 
53
- REGEXP_MATCH_METHODS = %i[=~ match match?].freeze
54
- REGEXP_START_WITH_NODES = %i[regexp_bos_anchor regexp_literal_literal].freeze
55
- REGEXP_END_WITH_NODES = %i[regexp_literal_literal regexp_eos_anchor].freeze
53
+ REGEXP_MATCH_METHODS = %i[=~ match match?].freeze
54
+
55
+ REGEXP_START_WITH_NODES =
56
+ [
57
+ ::Regexp::Expression::Anchor::BeginningOfString,
58
+ ::Regexp::Expression::Literal
59
+ ].freeze
60
+
61
+ REGEXP_END_WITH_NODES =
62
+ [
63
+ ::Regexp::Expression::Literal,
64
+ ::Regexp::Expression::Anchor::EndOfString
65
+ ].freeze
56
66
 
57
67
  private
58
68
 
@@ -114,33 +124,33 @@ module Mutant
114
124
  end
115
125
  end
116
126
 
117
- def emit_start_end_with_mutations # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
127
+ # rubocop:disable Metrics/CyclomaticComplexity
128
+ # rubocop:disable Metrics/MethodLength
129
+ def emit_start_end_with_mutations
118
130
  return unless REGEXP_MATCH_METHODS.include?(selector) && arguments.one?
119
131
 
120
132
  argument = Mutant::Util.one(arguments)
121
133
 
122
- return unless argument.type.equal?(:regexp) && (
123
- regexp_ast = AST::Regexp.expand_regexp_ast(argument)
124
- )
134
+ return unless argument.type.equal?(:regexp)
135
+
136
+ string = Regexp.regexp_body(argument) or return
125
137
 
126
- regexp_children = regexp_ast.children
138
+ expressions = ::Regexp::Parser.parse(string)
127
139
 
128
- case regexp_children.map(&:type)
140
+ case expressions.map(&:class)
129
141
  when REGEXP_START_WITH_NODES
130
- emit_start_with(regexp_children)
142
+ emit_start_with(expressions.last.text)
131
143
  when REGEXP_END_WITH_NODES
132
- emit_end_with(regexp_children)
144
+ emit_end_with(expressions.first.text)
133
145
  end
134
146
  end
135
147
 
136
- def emit_start_with(regexp_nodes)
137
- literal = Mutant::Util.one(regexp_nodes.last.children)
138
- emit_type(receiver, :start_with?, s(:str, literal))
148
+ def emit_start_with(string)
149
+ emit_type(receiver, :start_with?, s(:str, string))
139
150
  end
140
151
 
141
- def emit_end_with(regexp_nodes)
142
- literal = Mutant::Util.one(regexp_nodes.first.children)
143
- emit_type(receiver, :end_with?, s(:str, literal))
152
+ def emit_end_with(string)
153
+ emit_type(receiver, :end_with?, s(:str, string))
144
154
  end
145
155
 
146
156
  def emit_predicate_mutations