mutant 0.11.16 → 0.11.18

Sign up to get free protection for your applications and to get access to all the features.
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