mutant 0.11.0 → 0.11.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dfa3f4386dd2bb68e31b67124aac333cf71d13bb2fec0cf46fea56360475f3f4
4
- data.tar.gz: c1c3284ed18f7e2b268c8536a9103d11b7eb8abb0f3fc73a1890ffc8d461e9e5
3
+ metadata.gz: 3615060fe7453d95369d3a331f37af2ccaf30227b60ed88fe10cbbd647c1aef1
4
+ data.tar.gz: 26883c9dd809504502b21f95f93853144fd0c80bf4ce09b4709c9c3eb75742fe
5
5
  SHA512:
6
- metadata.gz: a20b1be07d23f2a0939bab498c500cf1707f75c647af84409d5ec42437bdd26aa6d4f89c9f70e558408e1280812da0cbc7cf7758af1c80c499289e3554b0b92d
7
- data.tar.gz: 276e169499aa4b48bc2488178de01b3d3ce1cb4647796bef023491c5d21faaed2d88e1ee61ae012c09d5c86f68ac68cda4a705174cc3cb88dab69e7cc4ea1578
6
+ metadata.gz: e99c8649c373b2f001c9e03440947ab456c1b776c0d8841316a9edcc4105ce22229b5227e72f6b1273f11879ca013b7df909286c42b3f9b8fc186827cda47938
7
+ data.tar.gz: 3a699d84bacfc16d70912534b2e68a8645d4f737bc977e0495c98b84390328f7912379bf623c0ca6a68df8884faf766e256fb3a983c0b4ea944e23c09ba5ae98
@@ -71,6 +71,10 @@ module Mutant
71
71
 
72
72
  hooks.run(:env_infection_pre, env)
73
73
 
74
+ config.environment_variables.each do |key, value|
75
+ world.environment_variables[key] = value
76
+ end
77
+
74
78
  config.includes.each(&world.load_path.public_method(:<<))
75
79
  config.requires.each(&world.kernel.public_method(:require))
76
80
 
@@ -62,6 +62,7 @@ module Mutant
62
62
  set(matcher: @config.matcher.add(attribute, value))
63
63
  end
64
64
 
65
+ # rubocop:disable Metrics/MethodLength
65
66
  def add_environment_options(parser)
66
67
  parser.separator('Environment:')
67
68
  parser.on('--zombie', 'Run mutant zombified') do
@@ -73,7 +74,14 @@ module Mutant
73
74
  parser.on('-r', '--require NAME', 'Require file with NAME') do |name|
74
75
  add(:requires, name)
75
76
  end
77
+ parser.on('--env KEY=VALUE', 'Set environment variable') do |value|
78
+ match = ENV_VARIABLE_KEY_VALUE_REGEXP.match(value) || fail("Invalid env variable: #{value.inspect}")
79
+ set(
80
+ environment_variables: @config.environment_variables.merge(match[:key] => match[:value])
81
+ )
82
+ end
76
83
  end
84
+ # rubocop:enable Metrics/MethodLength
77
85
 
78
86
  def add_integration_options(parser)
79
87
  parser.separator('Integration:')
data/lib/mutant/config.rb CHANGED
@@ -5,9 +5,12 @@ module Mutant
5
5
  #
6
6
  # Does not reference any "external" volatile state. The configuration applied
7
7
  # to current environment is being represented by the Mutant::Env object.
8
+ #
9
+ # rubocop:disable Metrics/ClassLength
8
10
  class Config
9
11
  include Adamantium, Anima.new(
10
12
  :coverage_criteria,
13
+ :environment_variables,
11
14
  :expression_parser,
12
15
  :fail_fast,
13
16
  :hooks,
@@ -48,16 +51,17 @@ module Mutant
48
51
  # rubocop:disable Metrics/MethodLength
49
52
  def merge(other)
50
53
  other.with(
51
- coverage_criteria: coverage_criteria.merge(other.coverage_criteria),
52
- fail_fast: fail_fast || other.fail_fast,
53
- hooks: hooks + other.hooks,
54
- includes: includes + other.includes,
55
- integration: other.integration || integration,
56
- jobs: other.jobs || jobs,
57
- matcher: matcher.merge(other.matcher),
58
- mutation_timeout: other.mutation_timeout || mutation_timeout,
59
- requires: requires + other.requires,
60
- zombie: zombie || other.zombie
54
+ coverage_criteria: coverage_criteria.merge(other.coverage_criteria),
55
+ environment_variables: environment_variables.merge(other.environment_variables),
56
+ fail_fast: fail_fast || other.fail_fast,
57
+ hooks: hooks + other.hooks,
58
+ includes: includes + other.includes,
59
+ integration: other.integration || integration,
60
+ jobs: other.jobs || jobs,
61
+ matcher: matcher.merge(other.matcher),
62
+ mutation_timeout: other.mutation_timeout || mutation_timeout,
63
+ requires: requires + other.requires,
64
+ zombie: zombie || other.zombie
61
65
  )
62
66
  end
63
67
  # rubocop:enable Metrics/AbcSize
@@ -117,6 +121,24 @@ module Mutant
117
121
  )
118
122
  )
119
123
 
124
+ # Parse a hash of environment variables
125
+ #
126
+ # @param [Hash<Object,Object>]
127
+ #
128
+ # @return [Either<String,Hash<String,String>]
129
+ #
130
+ def self.parse_environment_variables(hash)
131
+ invalid = hash.keys.reject { |key| key.instance_of?(String) }
132
+ return Either::Left.new("Non string keys: #{invalid}") if invalid.any?
133
+
134
+ invalid = hash.keys.grep_v(ENV_VARIABLE_KEY_REGEXP)
135
+ return Either::Left.new("Invalid keys: #{invalid}") if invalid.any?
136
+
137
+ invalid = hash.values.reject { |value| value.instance_of?(String) }
138
+ return Either::Left.new("Non string values: #{invalid}") if invalid.any?
139
+
140
+ Either::Right.new(hash)
141
+ end
120
142
  TRANSFORM = Transform::Sequence.new(
121
143
  [
122
144
  Transform::Exception.new(SystemCallError, :read.to_proc),
@@ -124,14 +146,23 @@ module Mutant
124
146
  Transform::Hash.new(
125
147
  optional: [
126
148
  Transform::Hash::Key.new('coverage_criteria', ->(value) { CoverageCriteria::TRANSFORM.call(value) }),
127
- Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
128
- Transform::Hash::Key.new('hooks', PATHNAME_ARRAY),
129
- Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
130
- Transform::Hash::Key.new('integration', Transform::STRING),
131
- Transform::Hash::Key.new('jobs', Transform::INTEGER),
132
- Transform::Hash::Key.new('matcher', Matcher::Config::LOADER),
133
- Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
134
- Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
149
+ Transform::Hash::Key.new(
150
+ 'environment_variables',
151
+ Transform::Sequence.new(
152
+ [
153
+ Transform::Primitive.new(Hash),
154
+ Transform::Block.capture(:environment_variables, &method(:parse_environment_variables))
155
+ ]
156
+ )
157
+ ),
158
+ Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
159
+ Transform::Hash::Key.new('hooks', PATHNAME_ARRAY),
160
+ Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
161
+ Transform::Hash::Key.new('integration', Transform::STRING),
162
+ Transform::Hash::Key.new('jobs', Transform::INTEGER),
163
+ Transform::Hash::Key.new('matcher', Matcher::Config::LOADER),
164
+ Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
165
+ Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
135
166
  ],
136
167
  required: []
137
168
  ),
@@ -141,4 +172,5 @@ module Mutant
141
172
 
142
173
  private_constant(:TRANSFORM)
143
174
  end # Config
175
+ # rubocop:enable Metrics/ClassLength
144
176
  end # Mutant
@@ -21,7 +21,7 @@ module Mutant
21
21
  # @return [Enumerable<Subject>]
22
22
  def call(env)
23
23
  Chain.new(
24
- methods.map { |method| matcher.new(scope, method) }
24
+ methods(env).map { |method| matcher.new(scope, method) }
25
25
  ).call(env)
26
26
  end
27
27
 
@@ -31,17 +31,16 @@ module Mutant
31
31
  self.class::MATCHER
32
32
  end
33
33
 
34
- def methods
34
+ def methods(env)
35
35
  candidate_names.each_with_object([]) do |name, methods|
36
- method = access(name)
37
- methods << method if method.owner.equal?(candidate_scope)
36
+ method = access(env, name)
37
+ methods << method if method&.owner.equal?(candidate_scope)
38
38
  end
39
39
  end
40
- memoize :methods
41
40
 
42
41
  def candidate_names
43
42
  CANDIDATE_NAMES
44
- .map(&candidate_scope.public_method(:public_send))
43
+ .map { |name| candidate_scope.public_send(name, false) }
45
44
  .reduce(:+)
46
45
  .sort
47
46
  end
@@ -55,7 +54,7 @@ module Mutant
55
54
 
56
55
  private
57
56
 
58
- def access(method_name)
57
+ def access(_env, method_name)
59
58
  scope.method(method_name)
60
59
  end
61
60
 
@@ -71,7 +70,7 @@ module Mutant
71
70
 
72
71
  private
73
72
 
74
- def access(method_name)
73
+ def access(_env, method_name)
75
74
  scope.method(method_name)
76
75
  end
77
76
 
@@ -84,11 +83,33 @@ module Mutant
84
83
  class Instance < self
85
84
  MATCHER = Matcher::Method::Instance
86
85
 
86
+ MESSAGE = <<~'MESSAGE'
87
+ Caught an exception while accessing a method with
88
+ #instance_method that is part of #{public,privat,protected}_instance_methods.
89
+
90
+ This is a bug in your ruby implementation its stdlib, libaries our your code.
91
+
92
+ Mutant will ignore this method:
93
+
94
+ Object: %<scope>s
95
+ Method: %<method_name>s
96
+ Exception: %<exception>s
97
+
98
+ See: https://github.com/mbj/mutant/issues/1273
99
+ MESSAGE
100
+
87
101
  private
88
102
 
89
- def access(method_name)
103
+ # rubocop:disable Lint/RescueException
104
+ def access(env, method_name)
90
105
  scope.instance_method(method_name)
106
+ rescue Exception => exception
107
+ env.warn(
108
+ MESSAGE % { scope: scope, method_name: method_name, exception: exception }
109
+ )
110
+ nil
91
111
  end
112
+ # rubocop:enable Lint/RescueException
92
113
 
93
114
  def candidate_scope
94
115
  scope
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.11.0'
5
+ VERSION = '0.11.1'
6
6
  end # Mutant
data/lib/mutant/world.rb CHANGED
@@ -5,6 +5,7 @@ module Mutant
5
5
  class World
6
6
  include Adamantium, Anima.new(
7
7
  :condition_variable,
8
+ :environment_variables,
8
9
  :gem,
9
10
  :gem_method,
10
11
  :io,
data/lib/mutant.rb CHANGED
@@ -38,6 +38,11 @@ module Mutant
38
38
  EMPTY_ARRAY = [].freeze
39
39
  EMPTY_HASH = {}.freeze
40
40
  SCOPE_OPERATOR = '::'
41
+
42
+ env_key = /[a-zA-Z_\d]+/
43
+
44
+ ENV_VARIABLE_KEY_VALUE_REGEXP = /\A(?<key>#{env_key}+)=(?<value>.*)\z/.freeze
45
+ ENV_VARIABLE_KEY_REGEXP = /\A#{env_key}\z/.freeze
41
46
  end # Mutant
42
47
 
43
48
  require 'mutant/procto'
@@ -234,46 +239,48 @@ require 'mutant/license/subscription/commercial'
234
239
 
235
240
  module Mutant
236
241
  WORLD = World.new(
237
- condition_variable: ConditionVariable,
238
- gem: Gem,
239
- gem_method: method(:gem),
240
- io: IO,
241
- json: JSON,
242
- kernel: Kernel,
243
- load_path: $LOAD_PATH,
244
- marshal: Marshal,
245
- mutex: Mutex,
246
- object_space: ObjectSpace,
247
- open3: Open3,
248
- pathname: Pathname,
249
- process: Process,
250
- stderr: $stderr,
251
- stdout: $stdout,
252
- thread: Thread,
253
- timer: Timer.new(Process)
242
+ condition_variable: ConditionVariable,
243
+ environment_variables: ENV,
244
+ gem: Gem,
245
+ gem_method: method(:gem),
246
+ io: IO,
247
+ json: JSON,
248
+ kernel: Kernel,
249
+ load_path: $LOAD_PATH,
250
+ marshal: Marshal,
251
+ mutex: Mutex,
252
+ object_space: ObjectSpace,
253
+ open3: Open3,
254
+ pathname: Pathname,
255
+ process: Process,
256
+ stderr: $stderr,
257
+ stdout: $stdout,
258
+ thread: Thread,
259
+ timer: Timer.new(Process)
254
260
  )
255
261
 
256
262
  # Reopen class to initialize constant to avoid dep circle
257
263
  class Config
258
264
  DEFAULT = new(
259
- coverage_criteria: Config::CoverageCriteria::EMPTY,
260
- expression_parser: Expression::Parser.new([
265
+ coverage_criteria: Config::CoverageCriteria::EMPTY,
266
+ expression_parser: Expression::Parser.new([
261
267
  Expression::Method,
262
268
  Expression::Methods,
263
269
  Expression::Namespace::Exact,
264
270
  Expression::Namespace::Recursive
265
271
  ]),
266
- fail_fast: false,
267
- hooks: EMPTY_ARRAY,
268
- includes: EMPTY_ARRAY,
269
- integration: nil,
270
- isolation: Mutant::Isolation::Fork.new(WORLD),
271
- jobs: nil,
272
- matcher: Matcher::Config::DEFAULT,
273
- mutation_timeout: nil,
274
- reporter: Reporter::CLI.build(WORLD.stdout),
275
- requires: EMPTY_ARRAY,
276
- zombie: false
272
+ fail_fast: false,
273
+ environment_variables: EMPTY_HASH,
274
+ hooks: EMPTY_ARRAY,
275
+ includes: EMPTY_ARRAY,
276
+ integration: nil,
277
+ isolation: Mutant::Isolation::Fork.new(WORLD),
278
+ jobs: nil,
279
+ matcher: Matcher::Config::DEFAULT,
280
+ mutation_timeout: nil,
281
+ reporter: Reporter::CLI.build(WORLD.stdout),
282
+ requires: EMPTY_ARRAY,
283
+ zombie: false
277
284
  )
278
285
  end # Config
279
286
 
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.11.0
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Schirp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-18 00:00:00.000000000 Z
11
+ date: 2021-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs