mutant 0.10.15 → 0.10.20

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbc0ad994f56a3ad82ac1c775f483e698d901344fc53d9ec09d0e921d9a241fb
4
- data.tar.gz: fd281190d497ed1c6d3231d170d44d3ec6ccf620cfed3d6f2f215b65d646d9a6
3
+ metadata.gz: 7a3308efe39e70e35d8c0fc11294280713c3c69f402e3dc59a30e3a3789a5744
4
+ data.tar.gz: ce590187601f50a475271571b7da3bd744d1b2196fc15f249de3fbec62913de5
5
5
  SHA512:
6
- metadata.gz: 9170426e14e26e3fb038fcb5902c9439a5901be11a54e9b78a59daab4baf3bdff8b9cba1f3af49415e9154ee88b500cfb751627f424bd15ed6a9adff317e41c0
7
- data.tar.gz: 170b80b8b311c695b1cab0f28cd3cd51369ac0576f9b764660784e6090cc7c73eeb14fd0fd9ef914bab7f0434f7e426caed4afb137228ac9bfcc8519e6e68fb3
6
+ metadata.gz: adc7dd97643cd658857712d4ae220fa167777145e9cae93db73a1c43be9db63a98b03fe549c0b7eac5c595b4b8f80892e8ca1c12fcfcf8385ffdc360d4b95f65
7
+ data.tar.gz: 51e49cfc6d35f60b7b9e5f3bd2c4991ceba3e383a60192a57920aab16860fbebda0f560ec39cf552994c3a1f6319793a0483f9fc3d2b4669ec556b7526df8b8d
@@ -171,6 +171,7 @@ require 'mutant/cli/command/subscription'
171
171
  require 'mutant/cli/command/environment'
172
172
  require 'mutant/cli/command/environment/run'
173
173
  require 'mutant/cli/command/environment/show'
174
+ require 'mutant/cli/command/environment/subject'
174
175
  require 'mutant/cli/command/root'
175
176
  require 'mutant/runner'
176
177
  require 'mutant/runner/sink'
@@ -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
@@ -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}*"
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
 
@@ -64,6 +64,7 @@ module Mutant
64
64
  end
65
65
  end # Pipe
66
66
 
67
+ # rubocop:disable Metrics/ClassLength
67
68
  class Parent
68
69
  include(
69
70
  Anima.new(*ATTRIBUTES),
@@ -149,17 +150,26 @@ module Mutant
149
150
 
150
151
  break unless ready
151
152
 
152
- ready.each do |fd|
153
- if fd.eof?
154
- targets.delete(fd)
153
+ ready.each do |target|
154
+ if target.eof?
155
+ targets.delete(target)
155
156
  else
156
- targets.fetch(fd) << fd.read_nonblock(READ_SIZE)
157
+ read_fragment(target, targets.fetch(target))
157
158
  end
158
159
  end
159
160
  end
160
161
  end
161
162
  # rubocop:enable Metrics/MethodLength
162
163
 
164
+ def read_fragment(target, fragments)
165
+ loop do
166
+ result = target.read_nonblock(READ_SIZE, exception: false)
167
+ break unless result.instance_of?(String)
168
+ fragments << result
169
+ break if result.bytesize < READ_SIZE
170
+ end
171
+ end
172
+
163
173
  # rubocop:disable Metrics/MethodLength
164
174
  def terminate_graceful
165
175
  status = nil
@@ -199,6 +209,7 @@ module Mutant
199
209
  @result = defined?(@result) ? @result.add_error(result) : result
200
210
  end
201
211
  end # Parent
212
+ # rubocop:enable Metrics/ClassLength
202
213
 
203
214
  class Child
204
215
  include(
@@ -12,7 +12,7 @@ module Mutant
12
12
  # @return [Either<String,Subscription>]
13
13
  #
14
14
  # @api private
15
- def self.apply(world)
15
+ def self.call(world)
16
16
  load_mutant_license(world)
17
17
  .fmap { license_path(world) }
18
18
  .bind { |path| Subscription.load(world, world.json.load(path)) }
@@ -30,7 +30,7 @@ module Mutant
30
30
  'oss' => Opensource
31
31
  }.fetch(value.fetch('type'))
32
32
  .from_json(value.fetch('contents'))
33
- .apply(world)
33
+ .call(world)
34
34
  end
35
35
 
36
36
  # Subscription self description
@@ -15,7 +15,7 @@ module Mutant
15
15
  new(value.fetch('authors').map(&Author.public_method(:new)).to_set)
16
16
  end
17
17
 
18
- def apply(world)
18
+ def call(world)
19
19
  candidates = candidates(world)
20
20
 
21
21
  if (licensed & candidates).any?
@@ -50,7 +50,7 @@ module Mutant
50
50
  )
51
51
  end
52
52
 
53
- def apply(world)
53
+ def call(world)
54
54
  world
55
55
  .capture_stdout(%w[git remote --verbose])
56
56
  .fmap(&method(:parse_remotes))
@@ -9,34 +9,37 @@ module Mutant
9
9
  CODE_DELIMITER = "\0"
10
10
  CODE_RANGE = (0..4).freeze
11
11
 
12
- def initialize(subject, node)
13
- super(subject, node)
14
-
15
- @source = Unparser.unparse(node)
16
- @code = sha1[CODE_RANGE]
17
- @identification = "#{self.class::SYMBOL}:#{subject.identification}:#{code}"
18
- @monkeypatch = Unparser.unparse(subject.context.root(node))
19
- end
20
-
21
12
  # Mutation identification code
22
13
  #
23
14
  # @return [String]
24
- attr_reader :code
15
+ def code
16
+ sha1[CODE_RANGE]
17
+ end
18
+ memoize :code
25
19
 
26
20
  # Normalized mutation source
27
21
  #
28
22
  # @return [String]
29
- attr_reader :source
23
+ def source
24
+ Unparser.unparse(node)
25
+ end
26
+ memoize :source
30
27
 
31
28
  # Identification string
32
29
  #
33
30
  # @return [String]
34
- attr_reader :identification
31
+ def identification
32
+ "#{self.class::SYMBOL}:#{subject.identification}:#{code}"
33
+ end
34
+ memoize :identification
35
35
 
36
36
  # The monkeypatch to insert the mutation
37
37
  #
38
38
  # @return [String]
39
- attr_reader :monkeypatch
39
+ def monkeypatch
40
+ Unparser.unparse(subject.context.root(node))
41
+ end
42
+ memoize :monkeypatch
40
43
 
41
44
  # Normalized original source
42
45
  #
@@ -13,10 +13,19 @@ module Mutant
13
13
  private
14
14
 
15
15
  def dispatch
16
- emit_singletons
17
- emit(N_TRUE)
16
+ emit(N_NIL)
17
+ emit_instance_variable_mutation
18
+ end
19
+
20
+ def emit_instance_variable_mutation
21
+ return unless n_ivar?(expression)
22
+
23
+ instance_variable_name = Mutant::Util.one(expression.children)
18
24
 
19
- emit_expression_mutations { |node| !n_self?(node) }
25
+ emit(
26
+ s(:send, nil, :instance_variable_defined?,
27
+ s(:sym, instance_variable_name))
28
+ )
20
29
  end
21
30
 
22
31
  end # Defined
@@ -4,12 +4,6 @@ module Mutant
4
4
  class Mutator
5
5
  class Node
6
6
  class ProcargZero < self
7
- MAP = {
8
- ::Parser::AST::Node => :emit_argument_node_mutations,
9
- Symbol => :emit_argument_symbol_mutations
10
- }.freeze
11
-
12
- private_constant(*constants(false))
13
7
 
14
8
  handle :procarg0
15
9
 
@@ -121,11 +121,7 @@ module Mutant
121
121
 
122
122
  # Test result
123
123
  class Test
124
- include Result, Anima.new(
125
- :passed,
126
- :runtime,
127
- :tests
128
- )
124
+ include Anima.new(:passed, :runtime)
129
125
 
130
126
  class VoidValue < self
131
127
  include Singleton
@@ -137,7 +133,6 @@ module Mutant
137
133
  super(
138
134
  passed: false,
139
135
  runtime: 0.0,
140
- tests: []
141
136
  )
142
137
  end
143
138
  end # VoidValue
@@ -6,7 +6,7 @@ module Mutant
6
6
  # Run against env
7
7
  #
8
8
  # @return [Either<String, Result>]
9
- def self.apply(env)
9
+ def self.call(env)
10
10
  reporter(env).start(env)
11
11
 
12
12
  Either::Right.new(run_mutation_analysis(env))
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # Abstract base class for test that might kill a mutation
5
5
  class Test
6
- include Adamantium::Flat, Anima.new(
6
+ include Anima.new(
7
7
  :expressions,
8
8
  :id
9
9
  )
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.10.15'
5
+ VERSION = '0.10.20'
6
6
  end # Mutant
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.15
4
+ version: 0.10.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Schirp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-07 00:00:00.000000000 Z
11
+ date: 2020-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: abstract_type
@@ -307,6 +307,7 @@ files:
307
307
  - lib/mutant/cli/command/environment.rb
308
308
  - lib/mutant/cli/command/environment/run.rb
309
309
  - lib/mutant/cli/command/environment/show.rb
310
+ - lib/mutant/cli/command/environment/subject.rb
310
311
  - lib/mutant/cli/command/root.rb
311
312
  - lib/mutant/cli/command/subscription.rb
312
313
  - lib/mutant/config.rb