mutant 0.10.35 → 0.11.3

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: b90188339d2ae50f1bc48723f56dc2740b6932a99cd8aae263543734aa66c494
4
- data.tar.gz: dcd1e40f9dc9b37467ec067142da4f2c1c6351191de235b2a88237c16b6b936c
3
+ metadata.gz: 4fd616c16c6eb981572f300d63760de29c418e0c8c72b29b0462565aeec59271
4
+ data.tar.gz: cff1c3128b75347267692919f598642dc6906faff02253b8bb5cf0a738b7488e
5
5
  SHA512:
6
- metadata.gz: 323cc89a39fb5163c7778d5a9ae7e989e207c974436207803d91118c502e17219b47514573d24d2e9003043bb170744110e68c9fa8dc2201236566d1a2f701c7
7
- data.tar.gz: 4f27f289f727c157237f65a15241b8adce8f91027894690994908eb698895a4c81f57344138e7c1a010454aee06776a41460f1ed37a4f164871b58e71291c1ca
6
+ metadata.gz: 0a1fe601d15da1235e4384c15346f3080021b8f08dfdd2313f60882f722d45bc824d8099fd13cebe77ea97edd06ecba0bbd2ff64ede9b7f5765e2115652f4202
7
+ data.tar.gz: b61a27b8b5119e489908bb43aa4aec0a348e552b443fa3b937ed20718f436e77740b0b891fc731fd1126eb5170525ffa7388d4ab555f9a7ef7e33812141b4ec7
data/LICENSE CHANGED
@@ -5,8 +5,8 @@ END-USER LICENSE AGREEMENT
5
5
  IMPORTANT: THIS SOFTWARE END-USER LICENSE AGREEMENT ("EULA") IS A LEGAL
6
6
  AGREEMENT (“Agreement”) BETWEEN YOU (THE CUSTOMER, EITHER AS AN INDIVIDUAL OR,
7
7
  IF PURCHASED OR OTHERWISE ACQUIRED BY OR FOR AN ENTITY, AS AN ENTITY) AND
8
- CONTRIBUTED SYSTEMS. READ IT CAREFULLY BEFORE COMPLETING THE INSTALLATION
9
- PROCESS AND USING SIDEKIQ PRO AND RELATED SOFTWARE COMPONENTS (“SOFTWARE”).
8
+ SCHIRP DSO LTD. READ IT CAREFULLY BEFORE COMPLETING THE INSTALLATION
9
+ PROCESS AND USING MUTANT AND RELATED SOFTWARE COMPONENTS (“SOFTWARE”).
10
10
 
11
11
  IT PROVIDES A LICENSE TO USE THE SOFTWARE AND CONTAINS WARRANTY INFORMATION
12
12
  AND LIABILITY DISCLAIMERS. BY INSTALLING AND USING THE SOFTWARE, YOU ARE
@@ -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
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Expression
5
+ class Descendants < self
6
+ include Anima.new(:const_name)
7
+
8
+ REGEXP = /\Adescendants:(?<const_name>.+)\z/.freeze
9
+
10
+ def syntax
11
+ "descendants:#{const_name}"
12
+ end
13
+
14
+ def matcher
15
+ Matcher::Descendants.new(const_name: const_name)
16
+ end
17
+ end # Descendants
18
+ end # Expression
19
+ end # Mutant
@@ -14,7 +14,7 @@ module Mutant
14
14
  def call(env)
15
15
  matchers.flat_map do |matcher|
16
16
  matcher.call(env)
17
- end
17
+ end.uniq
18
18
  end
19
19
 
20
20
  end # Chain
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Matcher
5
+ # Matcher for all descendants by constant name
6
+ class Descendants < self
7
+ include Anima.new(:const_name)
8
+
9
+ def call(env)
10
+ const = env.world.try_const_get(const_name) or return EMPTY_ARRAY
11
+
12
+ Chain.new(
13
+ matched_scopes(env, const).map { |scope| Scope.new(scope.raw) }
14
+ ).call(env)
15
+ end
16
+
17
+ private
18
+
19
+ def matched_scopes(env, const)
20
+ env.matchable_scopes.select do |scope|
21
+ scope.raw.equal?(const) || const > scope.raw
22
+ end
23
+ end
24
+ end # Descendant
25
+ end # Matcher
26
+ end # Mutant
@@ -82,7 +82,17 @@ module Mutant
82
82
  end
83
83
 
84
84
  def source_location
85
- target_method.source_location
85
+ signature = sorbet_signature
86
+
87
+ if signature
88
+ signature.method.source_location
89
+ else
90
+ target_method.source_location
91
+ end
92
+ end
93
+
94
+ def sorbet_signature
95
+ T::Private::Methods.signature_for_method(target_method)
86
96
  end
87
97
 
88
98
  def subject
@@ -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.10.35'
5
+ VERSION = '0.11.3'
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,
@@ -49,6 +50,19 @@ module Mutant
49
50
  end
50
51
  end
51
52
 
53
+ # Try const get
54
+ #
55
+ # @param [String]
56
+ #
57
+ # @return [Class|Module|nil]
58
+ #
59
+ # rubocop:disable Lint/SuppressedException
60
+ def try_const_get(name)
61
+ kernel.const_get(name)
62
+ rescue NameError
63
+ end
64
+ # rubocop:enable Lint/SuppressedException
65
+
52
66
  # Deadline
53
67
  #
54
68
  # @param [Float, nil] allowed_time
data/lib/mutant.rb CHANGED
@@ -14,6 +14,7 @@ require 'pathname'
14
14
  require 'regexp_parser'
15
15
  require 'set'
16
16
  require 'singleton'
17
+ require 'sorbet-runtime'
17
18
  require 'stringio'
18
19
  require 'unparser'
19
20
  require 'yaml'
@@ -37,6 +38,11 @@ module Mutant
37
38
  EMPTY_ARRAY = [].freeze
38
39
  EMPTY_HASH = {}.freeze
39
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
40
46
  end # Mutant
41
47
 
42
48
  require 'mutant/procto'
@@ -163,23 +169,25 @@ require 'mutant/subject/method/instance'
163
169
  require 'mutant/subject/method/singleton'
164
170
  require 'mutant/subject/method/metaclass'
165
171
  require 'mutant/matcher'
166
- require 'mutant/matcher/config'
167
172
  require 'mutant/matcher/chain'
173
+ require 'mutant/matcher/config'
174
+ require 'mutant/matcher/descendants'
175
+ require 'mutant/matcher/filter'
168
176
  require 'mutant/matcher/method'
169
- require 'mutant/matcher/method/singleton'
170
- require 'mutant/matcher/method/metaclass'
171
177
  require 'mutant/matcher/method/instance'
178
+ require 'mutant/matcher/method/metaclass'
179
+ require 'mutant/matcher/method/singleton'
172
180
  require 'mutant/matcher/methods'
173
181
  require 'mutant/matcher/namespace'
174
- require 'mutant/matcher/scope'
175
- require 'mutant/matcher/filter'
176
182
  require 'mutant/matcher/null'
183
+ require 'mutant/matcher/scope'
177
184
  require 'mutant/matcher/static'
178
185
  require 'mutant/expression'
179
- require 'mutant/expression/parser'
186
+ require 'mutant/expression/descendants'
180
187
  require 'mutant/expression/method'
181
188
  require 'mutant/expression/methods'
182
189
  require 'mutant/expression/namespace'
190
+ require 'mutant/expression/parser'
183
191
  require 'mutant/test'
184
192
  require 'mutant/timer'
185
193
  require 'mutant/integration'
@@ -233,46 +241,49 @@ require 'mutant/license/subscription/commercial'
233
241
 
234
242
  module Mutant
235
243
  WORLD = World.new(
236
- condition_variable: ConditionVariable,
237
- gem: Gem,
238
- gem_method: method(:gem),
239
- io: IO,
240
- json: JSON,
241
- kernel: Kernel,
242
- load_path: $LOAD_PATH,
243
- marshal: Marshal,
244
- mutex: Mutex,
245
- object_space: ObjectSpace,
246
- open3: Open3,
247
- pathname: Pathname,
248
- process: Process,
249
- stderr: $stderr,
250
- stdout: $stdout,
251
- thread: Thread,
252
- timer: Timer.new(Process)
244
+ condition_variable: ConditionVariable,
245
+ environment_variables: ENV,
246
+ gem: Gem,
247
+ gem_method: method(:gem),
248
+ io: IO,
249
+ json: JSON,
250
+ kernel: Kernel,
251
+ load_path: $LOAD_PATH,
252
+ marshal: Marshal,
253
+ mutex: Mutex,
254
+ object_space: ObjectSpace,
255
+ open3: Open3,
256
+ pathname: Pathname,
257
+ process: Process,
258
+ stderr: $stderr,
259
+ stdout: $stdout,
260
+ thread: Thread,
261
+ timer: Timer.new(Process)
253
262
  )
254
263
 
255
264
  # Reopen class to initialize constant to avoid dep circle
256
265
  class Config
257
266
  DEFAULT = new(
258
- coverage_criteria: Config::CoverageCriteria::EMPTY,
259
- expression_parser: Expression::Parser.new([
267
+ coverage_criteria: Config::CoverageCriteria::EMPTY,
268
+ expression_parser: Expression::Parser.new([
269
+ Expression::Descendants,
260
270
  Expression::Method,
261
271
  Expression::Methods,
262
272
  Expression::Namespace::Exact,
263
273
  Expression::Namespace::Recursive
264
274
  ]),
265
- fail_fast: false,
266
- hooks: EMPTY_ARRAY,
267
- includes: EMPTY_ARRAY,
268
- integration: nil,
269
- isolation: Mutant::Isolation::Fork.new(WORLD),
270
- jobs: nil,
271
- matcher: Matcher::Config::DEFAULT,
272
- mutation_timeout: nil,
273
- reporter: Reporter::CLI.build(WORLD.stdout),
274
- requires: EMPTY_ARRAY,
275
- zombie: false
275
+ fail_fast: false,
276
+ environment_variables: EMPTY_HASH,
277
+ hooks: EMPTY_ARRAY,
278
+ includes: EMPTY_ARRAY,
279
+ integration: nil,
280
+ isolation: Mutant::Isolation::Fork.new(WORLD),
281
+ jobs: nil,
282
+ matcher: Matcher::Config::DEFAULT,
283
+ mutation_timeout: nil,
284
+ reporter: Reporter::CLI.build(WORLD.stdout),
285
+ requires: EMPTY_ARRAY,
286
+ zombie: false
276
287
  )
277
288
  end # Config
278
289
 
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.35
4
+ version: 0.11.3
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-17 00:00:00.000000000 Z
11
+ date: 2022-02-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 3.0.0
33
+ version: 3.1.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 3.0.0
40
+ version: 3.1.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: regexp_parser
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,20 +58,34 @@ dependencies:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: 2.0.3
61
+ - !ruby/object:Gem::Dependency
62
+ name: sorbet-runtime
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.5.0
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.5.0
61
75
  - !ruby/object:Gem::Dependency
62
76
  name: unparser
63
77
  requirement: !ruby/object:Gem::Requirement
64
78
  requirements:
65
79
  - - "~>"
66
80
  - !ruby/object:Gem::Version
67
- version: 0.6.0
81
+ version: 0.6.4
68
82
  type: :runtime
69
83
  prerelease: false
70
84
  version_requirements: !ruby/object:Gem::Requirement
71
85
  requirements:
72
86
  - - "~>"
73
87
  - !ruby/object:Gem::Version
74
- version: 0.6.0
88
+ version: 0.6.4
75
89
  - !ruby/object:Gem::Dependency
76
90
  name: parallel
77
91
  requirement: !ruby/object:Gem::Requirement
@@ -193,6 +207,7 @@ files:
193
207
  - lib/mutant/context.rb
194
208
  - lib/mutant/env.rb
195
209
  - lib/mutant/expression.rb
210
+ - lib/mutant/expression/descendants.rb
196
211
  - lib/mutant/expression/method.rb
197
212
  - lib/mutant/expression/methods.rb
198
213
  - lib/mutant/expression/namespace.rb
@@ -212,6 +227,7 @@ files:
212
227
  - lib/mutant/matcher.rb
213
228
  - lib/mutant/matcher/chain.rb
214
229
  - lib/mutant/matcher/config.rb
230
+ - lib/mutant/matcher/descendants.rb
215
231
  - lib/mutant/matcher/filter.rb
216
232
  - lib/mutant/matcher/method.rb
217
233
  - lib/mutant/matcher/method/instance.rb
@@ -368,7 +384,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
368
384
  - !ruby/object:Gem::Version
369
385
  version: '0'
370
386
  requirements: []
371
- rubygems_version: 3.1.6
387
+ rubygems_version: 3.3.3
372
388
  signing_key:
373
389
  specification_version: 4
374
390
  summary: ''