mutant 0.10.17 → 0.10.22

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +3 -0
  3. data/lib/mutant/ast/meta/send.rb +0 -1
  4. data/lib/mutant/ast/types.rb +0 -9
  5. data/lib/mutant/bootstrap.rb +2 -2
  6. data/lib/mutant/cli/command/environment.rb +4 -4
  7. data/lib/mutant/cli/command/environment/run.rb +2 -2
  8. data/lib/mutant/cli/command/environment/show.rb +1 -2
  9. data/lib/mutant/cli/command/environment/subject.rb +39 -0
  10. data/lib/mutant/cli/command/root.rb +1 -1
  11. data/lib/mutant/cli/command/subscription.rb +1 -1
  12. data/lib/mutant/env.rb +8 -6
  13. data/lib/mutant/expression.rb +12 -3
  14. data/lib/mutant/expression/method.rb +33 -8
  15. data/lib/mutant/expression/methods.rb +6 -4
  16. data/lib/mutant/expression/namespace.rb +17 -6
  17. data/lib/mutant/expression/parser.rb +2 -2
  18. data/lib/mutant/integration/null.rb +2 -3
  19. data/lib/mutant/isolation/fork.rb +1 -1
  20. data/lib/mutant/license.rb +1 -1
  21. data/lib/mutant/license/subscription.rb +1 -1
  22. data/lib/mutant/license/subscription/commercial.rb +1 -1
  23. data/lib/mutant/license/subscription/opensource.rb +1 -1
  24. data/lib/mutant/mutator/node/arguments.rb +0 -2
  25. data/lib/mutant/mutator/node/block.rb +5 -1
  26. data/lib/mutant/mutator/node/defined.rb +12 -3
  27. data/lib/mutant/mutator/node/kwargs.rb +34 -0
  28. data/lib/mutant/mutator/node/literal/symbol.rb +0 -2
  29. data/lib/mutant/mutator/node/procarg_zero.rb +0 -6
  30. data/lib/mutant/mutator/node/send.rb +20 -18
  31. data/lib/mutant/parallel.rb +43 -28
  32. data/lib/mutant/parallel/driver.rb +9 -3
  33. data/lib/mutant/parallel/worker.rb +60 -2
  34. data/lib/mutant/pipe.rb +94 -0
  35. data/lib/mutant/reporter/cli/printer/isolation_result.rb +1 -6
  36. data/lib/mutant/result.rb +9 -6
  37. data/lib/mutant/runner.rb +8 -11
  38. data/lib/mutant/runner/sink.rb +12 -2
  39. data/lib/mutant/subject/method/instance.rb +1 -1
  40. data/lib/mutant/test.rb +1 -1
  41. data/lib/mutant/timer.rb +2 -4
  42. data/lib/mutant/transform.rb +0 -2
  43. data/lib/mutant/version.rb +1 -1
  44. metadata +12 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 977c03a9678ae669eaadb837b2d20402e92344cd1aa1d882c358c72819aea2f8
4
- data.tar.gz: b27367b4065c83e34423221ba99d868e0a993d35b133eee8a99b18392aaf6a85
3
+ metadata.gz: e1672efb6d075c7efa99f9f45efad0e6362f082eda55943540ab02c7a8a6dbf9
4
+ data.tar.gz: cb419c57b1f7e2955c7b80bc3c59d604d197f3bf58732da72a8442e3023a4b9e
5
5
  SHA512:
6
- metadata.gz: a238aea95f75d4bbdccf7418824b62c24c82b56c6f6d04a1e2298783a2208135bf91a2b48e224ad628c033d3f0bf893f9dd6498f3b355133c59339bf5a26964c
7
- data.tar.gz: bf517485919b231bd1994fbedab20741de57029add17a22ab15c2d2058c5c1d8453942583fc0a44d99220b09afe60ce891341d7ac42b4c5921cbdee666558503
6
+ metadata.gz: 016d92ae5cf2c2fc6c253cd7078583db2b9bee2ff01b14517f3ea12ded8a35e4cfe5df2647aa8d809144ab10c3d0d9bf56c66c1aaeeb1ee53c1e3facbcbca493
7
+ data.tar.gz: 8727a5a0520174b5c27464316eaff0130546ccabbc4dde3a0c45ca452ed0efdc46e3bccd2e52eaf01cc3fd5aacb041f3ef28783e98259d993a1f1a419a70b9eb
@@ -43,6 +43,7 @@ end # Mutant
43
43
  require 'mutant/bootstrap'
44
44
  require 'mutant/version'
45
45
  require 'mutant/env'
46
+ require 'mutant/pipe'
46
47
  require 'mutant/util'
47
48
  require 'mutant/registry'
48
49
  require 'mutant/ast'
@@ -129,6 +130,7 @@ require 'mutant/mutator/node/rescue'
129
130
  require 'mutant/mutator/node/match_current_line'
130
131
  require 'mutant/mutator/node/index'
131
132
  require 'mutant/mutator/node/procarg_zero'
133
+ require 'mutant/mutator/node/kwargs'
132
134
  require 'mutant/loader'
133
135
  require 'mutant/context'
134
136
  require 'mutant/scope'
@@ -171,6 +173,7 @@ require 'mutant/cli/command/subscription'
171
173
  require 'mutant/cli/command/environment'
172
174
  require 'mutant/cli/command/environment/run'
173
175
  require 'mutant/cli/command/environment/show'
176
+ require 'mutant/cli/command/environment/subject'
174
177
  require 'mutant/cli/command/root'
175
178
  require 'mutant/runner'
176
179
  require 'mutant/runner/sink'
@@ -13,7 +13,6 @@ module Mutant
13
13
 
14
14
  public :receiver, :selector
15
15
 
16
- INDEX_ASSIGNMENT_SELECTOR = :[]=
17
16
  ATTRIBUTE_ASSIGNMENT_SELECTOR_SUFFIX = '='
18
17
 
19
18
  # Arguments of mutated node
@@ -6,11 +6,6 @@ module Mutant
6
6
  module Types
7
7
  ASSIGNABLE_VARIABLES = Set.new(%i[ivasgn lvasgn cvasgn gvasgn]).freeze
8
8
 
9
- INDEX_ASSIGN_OPERATOR = :[]=
10
-
11
- # Set of nodes that cannot be on the LHS of an assignment
12
- NOT_ASSIGNABLE = Set.new(%i[int float str dstr class module self nil]).freeze
13
-
14
9
  # Set of op-assign types
15
10
  OP_ASSIGN = Set.new(%i[or_asgn and_asgn op_asgn]).freeze
16
11
  # Set of node types that are not valid when emitted standalone
@@ -53,10 +48,6 @@ module Mutant
53
48
  METHOD_OPERATORS - (INDEX_OPERATORS + UNARY_METHOD_OPERATORS)
54
49
  )
55
50
 
56
- OPERATOR_METHODS = Set.new(
57
- METHOD_OPERATORS + INDEX_OPERATORS + UNARY_METHOD_OPERATORS
58
- ).freeze
59
-
60
51
  # Nodes that are NOT handled by mutant.
61
52
  #
62
53
  # not - 1.8 only, mutant does not support 1.8
@@ -30,7 +30,7 @@ module Mutant
30
30
  # @return [Either<String, Env>]
31
31
  #
32
32
  # rubocop:disable Metrics/MethodLength
33
- def self.apply(world, config)
33
+ def self.call(world, config)
34
34
  env = Env
35
35
  .empty(world, config)
36
36
  .tap(&method(:infect))
@@ -109,7 +109,7 @@ module Mutant
109
109
  return
110
110
  end
111
111
 
112
- expression_parser.apply(name).from_right {}
112
+ expression_parser.call(name).from_right {}
113
113
  end
114
114
  private_class_method :expression
115
115
  # rubocop:enable Metrics/MethodLength
@@ -25,7 +25,7 @@ module Mutant
25
25
  def bootstrap
26
26
  Config.load_config_file(world)
27
27
  .fmap(&method(:expand))
28
- .bind { Bootstrap.apply(world, @config) }
28
+ .bind { Bootstrap.call(world, @config) }
29
29
  end
30
30
 
31
31
  def expand(file_config)
@@ -33,7 +33,7 @@ module Mutant
33
33
  end
34
34
 
35
35
  def parse_remaining_arguments(arguments)
36
- Mutant.traverse(@config.expression_parser.public_method(:apply), arguments)
36
+ Mutant.traverse(@config.expression_parser, arguments)
37
37
  .fmap do |match_expressions|
38
38
  matcher(match_expressions: match_expressions)
39
39
  self
@@ -82,10 +82,10 @@ module Mutant
82
82
  parser.separator('Matcher:')
83
83
 
84
84
  parser.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
85
- add_matcher(:ignore_expressions, @config.expression_parser.apply(pattern).from_right)
85
+ add_matcher(:ignore_expressions, @config.expression_parser.call(pattern).from_right)
86
86
  end
87
87
  parser.on('--start-subject EXPRESSION', 'Start mutation testing at a specific subject') do |pattern|
88
- add_matcher(:start_expressions, @config.expression_parser.apply(pattern).from_right)
88
+ add_matcher(:start_expressions, @config.expression_parser.call(pattern).from_right)
89
89
  end
90
90
  parser.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
91
91
  add_matcher(
@@ -26,9 +26,9 @@ module Mutant
26
26
  private
27
27
 
28
28
  def action
29
- soft_fail(License.apply(world))
29
+ soft_fail(License.call(world))
30
30
  .bind { bootstrap }
31
- .bind(&Runner.public_method(:apply))
31
+ .bind(&Runner.public_method(:call))
32
32
  .bind(&method(:from_result))
33
33
  end
34
34
 
@@ -12,8 +12,7 @@ module Mutant
12
12
  private
13
13
 
14
14
  def action
15
- bootstrap
16
- .fmap(&method(:report_env))
15
+ bootstrap.fmap(&method(:report_env))
17
16
  end
18
17
 
19
18
  def report_env(env)
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module CLI
5
+ class Command
6
+ class Environment
7
+ class Subject < self
8
+ NAME = 'subject'
9
+ SHORT_DESCRIPTION = 'Subject subcommands'
10
+
11
+ class List < self
12
+ NAME = 'list'
13
+ SHORT_DESCRIPTION = 'List subjects'
14
+ SUBCOMMANDS = EMPTY_ARRAY
15
+
16
+ private
17
+
18
+ def action
19
+ bootstrap.fmap(&method(:list_subjects))
20
+ end
21
+
22
+ def list_subjects(env)
23
+ print('Subjects in environment: %d' % env.subjects.length)
24
+ env.subjects.each do |subject|
25
+ print(subject.expression.syntax)
26
+ end
27
+ end
28
+
29
+ def print(message)
30
+ world.stdout.puts(message)
31
+ end
32
+ end
33
+
34
+ SUBCOMMANDS = [List].freeze
35
+ end # Subject
36
+ end # Environment
37
+ end # Command
38
+ end # CLI
39
+ end # Mutant
@@ -4,7 +4,7 @@ module Mutant
4
4
  module CLI
5
5
  class Command
6
6
  class Environment < self
7
- SUBCOMMANDS = [Environment::Show].freeze
7
+ SUBCOMMANDS = [Environment::Subject, Environment::Show].freeze
8
8
  end # Environment
9
9
 
10
10
  class Root < self
@@ -10,7 +10,7 @@ module Mutant
10
10
  private
11
11
 
12
12
  def license
13
- License.apply(world)
13
+ License.call(world)
14
14
  end
15
15
 
16
16
  class Test < self
@@ -43,19 +43,21 @@ module Mutant
43
43
  end
44
44
  # rubocop:enable Metrics/MethodLength
45
45
 
46
- # Kill mutation
46
+ # Cover mutation with specific index
47
47
  #
48
- # @param [Mutation] mutation
48
+ # @param [Integer] mutation_index
49
49
  #
50
- # @return [Result::Mutation]
51
- def kill(mutation)
50
+ # @return [Result::MutationIndex]
51
+ def cover_index(mutation_index)
52
+ mutation = mutations.fetch(mutation_index)
53
+
52
54
  start = timer.now
53
55
 
54
56
  tests = selections.fetch(mutation.subject)
55
57
 
56
- Result::Mutation.new(
58
+ Result::MutationIndex.new(
57
59
  isolation_result: run_mutation_tests(mutation, tests),
58
- mutation: mutation,
60
+ mutation_index: mutation_index,
59
61
  runtime: timer.now - start
60
62
  )
61
63
  end
@@ -4,7 +4,7 @@ module Mutant
4
4
 
5
5
  # Abstract base class for match expression
6
6
  class Expression
7
- include AbstractType, Adamantium::Flat
7
+ include AbstractType
8
8
 
9
9
  fragment = /[A-Za-z][A-Za-z\d_]*/.freeze
10
10
  SCOPE_NAME_PATTERN = /(?<scope_name>#{fragment}(?:#{SCOPE_OPERATOR}#{fragment})*)/.freeze
@@ -12,6 +12,10 @@ module Mutant
12
12
 
13
13
  private_constant(*constants(false))
14
14
 
15
+ def self.new(*)
16
+ super.freeze
17
+ end
18
+
15
19
  # Syntax of expression
16
20
  #
17
21
  # @return [Matcher]
@@ -55,9 +59,14 @@ module Mutant
55
59
  # otherwise
56
60
  def self.try_parse(input)
57
61
  match = self::REGEXP.match(input)
58
- return unless match
62
+ from_match(match) if match
63
+ end
64
+
65
+ def self.from_match(match)
59
66
  names = anima.attribute_names
60
- new(Hash[names.zip(names.map(&match.method(:[])))])
67
+ new(Hash[names.zip(names.map(&match.public_method(:[])))])
61
68
  end
69
+ private_class_method :from_match
70
+
62
71
  end # Expression
63
72
  end # Mutant
@@ -5,6 +5,8 @@ module Mutant
5
5
 
6
6
  # Explicit method expression
7
7
  class Method < self
8
+ extend AST::Sexp
9
+
8
10
  include Anima.new(
9
11
  :method_name,
10
12
  :scope_name,
@@ -18,22 +20,21 @@ module Mutant
18
20
  '#' => [Matcher::Methods::Instance]
19
21
  )
20
22
 
21
- METHOD_NAME_PATTERN = Regexp.union(
22
- /(?<method_name>[A-Za-z_][A-Za-z\d_]*[!?=]?)/,
23
- *AST::Types::OPERATOR_METHODS.map(&:to_s)
24
- ).freeze
23
+ METHOD_NAME_PATTERN = /(?<method_name>.+)/.freeze
25
24
 
26
25
  private_constant(*constants(false))
27
26
 
28
27
  REGEXP = /\A#{SCOPE_NAME_PATTERN}#{SCOPE_SYMBOL_PATTERN}#{METHOD_NAME_PATTERN}\z/.freeze
29
28
 
29
+ def initialize(*)
30
+ super
31
+ @syntax = [scope_name, scope_symbol, method_name].join.freeze
32
+ end
33
+
30
34
  # Syntax of expression
31
35
  #
32
36
  # @return [String]
33
- def syntax
34
- [scope_name, scope_symbol, method_name].join
35
- end
36
- memoize :syntax
37
+ attr_reader :syntax
37
38
 
38
39
  # Matcher for expression
39
40
  #
@@ -47,6 +48,30 @@ module Mutant
47
48
  Matcher::Filter.new(methods_matcher, ->(subject) { subject.expression.eql?(self) })
48
49
  end
49
50
 
51
+ def self.try_parse(input)
52
+ match = REGEXP.match(input) or return
53
+
54
+ from_match(match) if valid_method_name?(match[:method_name])
55
+ end
56
+
57
+ # Test if string is a valid Ruby method name
58
+ #
59
+ # Note that this crazyness is indeed the "correct" solution.
60
+ #
61
+ # See: https://github.com/whitequark/parser/issues/213
62
+ #
63
+ # @param [String]
64
+ #
65
+ # @return [Boolean]
66
+ def self.valid_method_name?(name)
67
+ buffer = ::Parser::Source::Buffer.new(nil, source: "def #{name}; end")
68
+
69
+ ::Parser::CurrentRuby
70
+ .new
71
+ .parse(buffer).eql?(s(:def, name.to_sym, s(:args), nil))
72
+ end
73
+ private_class_method :valid_method_name?
74
+
50
75
  private
51
76
 
52
77
  def scope
@@ -20,13 +20,15 @@ module Mutant
20
20
 
21
21
  REGEXP = /\A#{SCOPE_NAME_PATTERN}#{SCOPE_SYMBOL_PATTERN}\z/.freeze
22
22
 
23
+ def initialize(*)
24
+ super
25
+ @syntax = [scope_name, scope_symbol].join.freeze
26
+ end
27
+
23
28
  # Syntax of expression
24
29
  #
25
30
  # @return [String]
26
- def syntax
27
- [scope_name, scope_symbol].join
28
- end
29
- memoize :syntax
31
+ attr_reader :syntax
30
32
 
31
33
  # Matcher on expression
32
34
  #
@@ -16,6 +16,9 @@ module Mutant
16
16
  # @return [undefined]
17
17
  def initialize(*)
18
18
  super
19
+
20
+ @syntax = "#{scope_name}*".freeze # rubocop:disable Style/RedundantFreeze
21
+
19
22
  @recursion_pattern = Regexp.union(
20
23
  /\A#{scope_name}\z/,
21
24
  /\A#{scope_name}::/,
@@ -26,10 +29,7 @@ module Mutant
26
29
  # Syntax for expression
27
30
  #
28
31
  # @return [String]
29
- def syntax
30
- "#{scope_name}*"
31
- end
32
- memoize :syntax
32
+ attr_reader :syntax
33
33
 
34
34
  # Matcher for expression
35
35
  #
@@ -52,7 +52,6 @@ module Mutant
52
52
  0
53
53
  end
54
54
  end
55
-
56
55
  end # Recursive
57
56
 
58
57
  # Exact namespace expression
@@ -67,7 +66,13 @@ module Mutant
67
66
  #
68
67
  # @return [Matcher]
69
68
  def matcher
70
- Matcher::Scope.new(Object.const_get(scope_name))
69
+ scope = find_scope
70
+
71
+ if scope
72
+ Matcher::Scope.new(scope)
73
+ else
74
+ Matcher::Null.new
75
+ end
71
76
  end
72
77
 
73
78
  # Syntax for expression
@@ -76,6 +81,12 @@ module Mutant
76
81
  alias_method :syntax, :scope_name
77
82
  public :syntax
78
83
 
84
+ private
85
+
86
+ def find_scope
87
+ Object.const_get(scope_name)
88
+ rescue NameError # rubocop:disable Lint/SuppressedException
89
+ end
79
90
  end # Exact
80
91
  end # Namespace
81
92
  end # Expression
@@ -5,7 +5,7 @@ module Mutant
5
5
  class Parser
6
6
  include Concord.new(:types)
7
7
 
8
- # Apply expression parsing
8
+ # Parse expression
9
9
  #
10
10
  # @param [String] input
11
11
  #
@@ -14,7 +14,7 @@ module Mutant
14
14
  #
15
15
  # @return [nil]
16
16
  # otherwise
17
- def apply(input)
17
+ def call(input)
18
18
  expressions = expressions(input)
19
19
  case expressions.length
20
20
  when 0
@@ -16,11 +16,10 @@ module Mutant
16
16
  # @param [Enumerable<Mutant::Test>] tests
17
17
  #
18
18
  # @return [Result::Test]
19
- def call(tests)
19
+ def call(_tests)
20
20
  Result::Test.new(
21
21
  passed: true,
22
- runtime: 0.0,
23
- tests: tests
22
+ runtime: 0.0
24
23
  )
25
24
  end
26
25
 
@@ -230,7 +230,7 @@ module Mutant
230
230
 
231
231
  end # Child
232
232
 
233
- private_constant(*(constants(false) - %i[ChildError ForkError]))
233
+ private_constant(*constants(false))
234
234
 
235
235
  # Call block in isolation
236
236
  #