mutant 0.10.17 → 0.10.22

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