mutant 0.10.24 → 0.10.29

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +15 -11
  3. data/lib/mutant/ast/find_metaclass_containing.rb +1 -1
  4. data/lib/mutant/ast/regexp.rb +17 -0
  5. data/lib/mutant/ast/regexp/transformer.rb +2 -2
  6. data/lib/mutant/ast/regexp/transformer/quantifier.rb +4 -2
  7. data/lib/mutant/bootstrap.rb +1 -1
  8. data/lib/mutant/cli/command.rb +4 -0
  9. data/lib/mutant/cli/command/environment/subject.rb +0 -4
  10. data/lib/mutant/cli/command/environment/test.rb +36 -0
  11. data/lib/mutant/cli/command/root.rb +1 -1
  12. data/lib/mutant/config.rb +1 -1
  13. data/lib/mutant/context.rb +1 -1
  14. data/lib/mutant/env.rb +2 -2
  15. data/lib/mutant/expression/method.rb +4 -4
  16. data/lib/mutant/expression/methods.rb +5 -4
  17. data/lib/mutant/integration.rb +8 -2
  18. data/lib/mutant/isolation/exception.rb +22 -0
  19. data/lib/mutant/isolation/fork.rb +9 -12
  20. data/lib/mutant/loader.rb +1 -1
  21. data/lib/mutant/matcher.rb +1 -1
  22. data/lib/mutant/matcher/config.rb +6 -3
  23. data/lib/mutant/matcher/method.rb +12 -10
  24. data/lib/mutant/matcher/method/instance.rb +6 -2
  25. data/lib/mutant/matcher/method/metaclass.rb +1 -1
  26. data/lib/mutant/matcher/methods.rb +2 -4
  27. data/lib/mutant/meta/example.rb +1 -1
  28. data/lib/mutant/meta/example/dsl.rb +0 -1
  29. data/lib/mutant/meta/example/verification.rb +1 -1
  30. data/lib/mutant/mutation.rb +1 -1
  31. data/lib/mutant/mutator.rb +8 -1
  32. data/lib/mutant/mutator/node.rb +0 -5
  33. data/lib/mutant/mutator/node/argument.rb +2 -2
  34. data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
  35. data/lib/mutant/mutator/node/index.rb +1 -0
  36. data/lib/mutant/mutator/node/literal/float.rb +1 -3
  37. data/lib/mutant/mutator/node/literal/integer.rb +3 -6
  38. data/lib/mutant/mutator/node/literal/range.rb +1 -1
  39. data/lib/mutant/mutator/node/literal/regex.rb +3 -17
  40. data/lib/mutant/mutator/node/module.rb +19 -0
  41. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +3 -3
  42. data/lib/mutant/mutator/node/regexp.rb +0 -11
  43. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
  44. data/lib/mutant/mutator/node/regexp/character_type.rb +1 -1
  45. data/lib/mutant/mutator/node/regexp/named_group.rb +39 -0
  46. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
  47. data/lib/mutant/mutator/node/regopt.rb +1 -1
  48. data/lib/mutant/mutator/node/sclass.rb +1 -1
  49. data/lib/mutant/mutator/node/send.rb +73 -6
  50. data/lib/mutant/parallel.rb +2 -2
  51. data/lib/mutant/parallel/driver.rb +1 -1
  52. data/lib/mutant/parallel/worker.rb +1 -1
  53. data/lib/mutant/parser.rb +1 -1
  54. data/lib/mutant/pipe.rb +1 -1
  55. data/lib/mutant/procto.rb +23 -0
  56. data/lib/mutant/reporter/cli/printer.rb +10 -4
  57. data/lib/mutant/reporter/cli/printer/env.rb +3 -3
  58. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -2
  59. data/lib/mutant/reporter/cli/printer/isolation_result.rb +3 -1
  60. data/lib/mutant/selector.rb +1 -1
  61. data/lib/mutant/subject.rb +1 -1
  62. data/lib/mutant/subject/method/instance.rb +5 -42
  63. data/lib/mutant/transform.rb +25 -0
  64. data/lib/mutant/variable.rb +322 -0
  65. data/lib/mutant/version.rb +1 -1
  66. data/lib/mutant/world.rb +1 -1
  67. metadata +13 -160
  68. data/lib/mutant/mutator/node/regexp/greedy_zero_or_more.rb +0 -24
@@ -73,6 +73,31 @@ module Mutant
73
73
  end
74
74
  end # Named
75
75
 
76
+ class Block < self
77
+ include Anima.new(:block, :name)
78
+
79
+ def self.capture(name, &block)
80
+ new(block: block, name: name)
81
+ end
82
+
83
+ def call(input)
84
+ block
85
+ .call(input)
86
+ .lmap do |message|
87
+ Error.new(
88
+ cause: nil,
89
+ input: input,
90
+ message: message,
91
+ transform: self
92
+ )
93
+ end
94
+ end
95
+
96
+ def slug
97
+ name
98
+ end
99
+ end
100
+
76
101
  private
77
102
 
78
103
  def error(cause: nil, input:, message: nil)
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ # Lightweight concurrency variables
5
+ #
6
+ # These are inspired by Haskells MVar and IVar types.
7
+ class Variable
8
+ EMPTY = Class.new do
9
+ const_set(:INSPECT, 'Variable::EMPTY')
10
+ end.new.freeze
11
+
12
+ TIMEOUT = Class.new do
13
+ const_set(:INSPECT, 'Variable::TIMEOUT')
14
+ end.new.freeze
15
+
16
+ # Result of operation that may time out
17
+ class Result
18
+ include Equalizer.new(:value)
19
+ attr_reader :value
20
+
21
+ # Initialize result
22
+ #
23
+ # @return [undefined]
24
+ def initialize(value)
25
+ @value = value
26
+ freeze
27
+ end
28
+
29
+ # Test if take resulted in a timeout
30
+ #
31
+ # @return [Boolean]
32
+ #
33
+ # @api private
34
+ def timeout?
35
+ instance_of?(Timeout)
36
+ end
37
+
38
+ # Instance returned on timeouts
39
+ class Timeout < self
40
+ INSTANCE = new(nil)
41
+
42
+ # Construct new object
43
+ #
44
+ # @return [Timeout]
45
+ def self.new
46
+ INSTANCE
47
+ end
48
+ end # Timeout
49
+
50
+ # Instance returned without timeouts
51
+ class Value < self
52
+ end # Value
53
+ end # Result
54
+
55
+ private_constant(*constants(false))
56
+
57
+ module Timer
58
+ # Monotonic elapsed time of block execution
59
+ #
60
+ # @return [Float]
61
+ def self.elapsed
62
+ start = now
63
+ yield
64
+ now - start
65
+ end
66
+
67
+ # The now monotonic time
68
+ #
69
+ # @return [Float]
70
+ def self.now
71
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
72
+ end
73
+ private_class_method :now
74
+ end # Timer
75
+
76
+ # Initialize object
77
+ #
78
+ # @param [Object] value
79
+ # the initial value
80
+ #
81
+ # @return [undefined]
82
+ def initialize(condition_variable:, mutex:, value: EMPTY)
83
+ @full = condition_variable.new
84
+ @mutex = mutex.new
85
+ @value = value
86
+ end
87
+
88
+ # Take value, block on empty
89
+ #
90
+ # @return [Object]
91
+ def take
92
+ synchronize do
93
+ wait_full
94
+ perform_take
95
+ end
96
+ end
97
+
98
+ # Take value, with timeout
99
+ #
100
+ # @param [Float] Timeout
101
+ #
102
+ # @return [Result::Timeout]
103
+ # in case take resulted in a timeout
104
+ #
105
+ # @return [Result::Value]
106
+ # in case take resulted in a value
107
+ def take_timeout(timeout)
108
+ synchronize do
109
+ if wait_timeout(@full, timeout, &method(:full?))
110
+ Result::Timeout.new
111
+ else
112
+ Result::Value.new(perform_take)
113
+ end
114
+ end
115
+ end
116
+
117
+ # Read value, block on empty
118
+ #
119
+ # @return [Object]
120
+ # the variable value
121
+ def read
122
+ synchronize do
123
+ wait_full
124
+ @value
125
+ end
126
+ end
127
+
128
+ # Try put value into the variable, non blocking
129
+ #
130
+ # @param [Object] value
131
+ #
132
+ # @return [self]
133
+ def try_put(value)
134
+ synchronize do
135
+ perform_put(value) if empty?
136
+ end
137
+
138
+ self
139
+ end
140
+
141
+ # Execute block with value, blocking
142
+ #
143
+ # @yield [Object]
144
+ #
145
+ # @return [Object]
146
+ # the blocks return value
147
+ def with
148
+ synchronize do
149
+ wait_full
150
+ yield @value
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ # Perform the put
157
+ #
158
+ # @param [Object] value
159
+ def perform_put(value)
160
+ (@value = value).tap { @full.signal }
161
+ end
162
+
163
+ # Execute block under mutex
164
+ #
165
+ # @return [self]
166
+ def synchronize(&block)
167
+ @mutex.synchronize(&block)
168
+ end
169
+
170
+ # Wait for block predicate
171
+ #
172
+ # @param [ConditionVariable] event
173
+ #
174
+ # @return [undefined]
175
+ def wait(event)
176
+ event.wait(@mutex) until yield
177
+ end
178
+
179
+ # Wait with timeout for block predicate
180
+ #
181
+ # @param [ConditionVariable] event
182
+ #
183
+ # @return [Boolean]
184
+ # if wait was terminated due a timeout
185
+ #
186
+ # @return [undefined]
187
+ # otherwise
188
+ def wait_timeout(event, timeout)
189
+ loop do
190
+ break true if timeout <= 0
191
+ break if yield
192
+ timeout -= Timer.elapsed { event.wait(@mutex, timeout) }
193
+ end
194
+ end
195
+
196
+ # Wait till mvar is full
197
+ #
198
+ # @return [undefined]
199
+ def wait_full
200
+ wait(@full, &method(:full?))
201
+ end
202
+
203
+ # Test if state is full
204
+ #
205
+ # @return [Boolean]
206
+ def full?
207
+ !empty?
208
+ end
209
+
210
+ # Test if state is empty
211
+ #
212
+ # @return [Boolean]
213
+ def empty?
214
+ @value.equal?(EMPTY)
215
+ end
216
+
217
+ # Shared variable that can be written at most once
218
+ #
219
+ # ignore :reek:InstanceVariableAssumption
220
+ class IVar < self
221
+
222
+ # Exception raised on ivar errors
223
+ class Error < RuntimeError; end
224
+
225
+ # Put value, raises if already full
226
+ #
227
+ # @param [Object] value
228
+ #
229
+ # @return [self]
230
+ #
231
+ # @raise Error
232
+ # if already full
233
+ def put(value)
234
+ synchronize do
235
+ fail Error, 'is immutable' if full?
236
+ perform_put(value)
237
+ end
238
+
239
+ self
240
+ end
241
+
242
+ # Populate and return value, use block to compute value if empty
243
+ #
244
+ # The block is guaranteed to be executed at max once.
245
+ #
246
+ # Subsequent reads are guaranteed to return the block value.
247
+ #
248
+ # @return [Object]
249
+ def populate_with
250
+ return @value if full?
251
+
252
+ synchronize do
253
+ perform_put(yield) if empty?
254
+ end
255
+
256
+ @value
257
+ end
258
+
259
+ private
260
+
261
+ # Perform take operation
262
+ #
263
+ # @return [Object]
264
+ def perform_take
265
+ @value
266
+ end
267
+ end # IVar
268
+
269
+ # Shared variable that can be written multiple times
270
+ #
271
+ # ignore :reek:InstanceVariableAssumption
272
+ class MVar < self
273
+
274
+ # Initialize object
275
+ #
276
+ # @param [Object] value
277
+ # the initial value
278
+ #
279
+ # @return [undefined]
280
+ def initialize(condition_variable:, mutex:, value: EMPTY)
281
+ super
282
+ @empty = condition_variable.new
283
+ end
284
+
285
+ # Put value, block on full
286
+ #
287
+ # @param [Object] value
288
+ #
289
+ # @return [self]
290
+ def put(value)
291
+ synchronize do
292
+ wait(@empty, &method(:empty?))
293
+ perform_put(value)
294
+ end
295
+
296
+ self
297
+ end
298
+
299
+ # Modify value, blocks if empty
300
+ #
301
+ # @return [Object]
302
+ def modify
303
+ synchronize do
304
+ wait_full
305
+ perform_put(yield(@value))
306
+ end
307
+ end
308
+
309
+ private
310
+
311
+ # Empty the variable
312
+ #
313
+ # @return [Object]
314
+ def perform_take
315
+ @value.tap do
316
+ @value = EMPTY
317
+ @empty.signal
318
+ end
319
+ end
320
+ end # MVar
321
+ end # Variable
322
+ end # Mutant
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.10.24'
5
+ VERSION = '0.10.29'
6
6
  end # Mutant
data/lib/mutant/world.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # The outer world IO objects mutant does interact with
5
5
  class World
6
- include Adamantium::Flat, Anima.new(
6
+ include Adamantium, Anima.new(
7
7
  :condition_variable,
8
8
  :gem,
9
9
  :gem_method,
metadata CHANGED
@@ -1,85 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.24
4
+ version: 0.10.29
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-01-01 00:00:00.000000000 Z
11
+ date: 2021-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: abstract_type
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 0.0.7
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 0.0.7
27
- - !ruby/object:Gem::Dependency
28
- name: adamantium
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 0.2.0
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 0.2.0
41
- - !ruby/object:Gem::Dependency
42
- name: anima
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 0.3.1
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 0.3.1
55
- - !ruby/object:Gem::Dependency
56
- name: ast
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '2.2'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '2.2'
69
- - !ruby/object:Gem::Dependency
70
- name: concord
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 0.1.5
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 0.1.5
83
13
  - !ruby/object:Gem::Dependency
84
14
  name: diff-lcs
85
15
  requirement: !ruby/object:Gem::Requirement
@@ -94,62 +24,6 @@ dependencies:
94
24
  - - "~>"
95
25
  - !ruby/object:Gem::Version
96
26
  version: '1.3'
97
- - !ruby/object:Gem::Dependency
98
- name: equalizer
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: 0.0.9
104
- type: :runtime
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: 0.0.9
111
- - !ruby/object:Gem::Dependency
112
- name: ice_nine
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: 0.11.1
118
- type: :runtime
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: 0.11.1
125
- - !ruby/object:Gem::Dependency
126
- name: memoizable
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: 0.4.2
132
- type: :runtime
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: 0.4.2
139
- - !ruby/object:Gem::Dependency
140
- name: mprelude
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: 0.1.0
146
- type: :runtime
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: 0.1.0
153
27
  - !ruby/object:Gem::Dependency
154
28
  name: parser
155
29
  requirement: !ruby/object:Gem::Requirement
@@ -164,20 +38,6 @@ dependencies:
164
38
  - - "~>"
165
39
  - !ruby/object:Gem::Version
166
40
  version: 3.0.0
167
- - !ruby/object:Gem::Dependency
168
- name: procto
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: 0.0.2
174
- type: :runtime
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: 0.0.2
181
41
  - !ruby/object:Gem::Dependency
182
42
  name: regexp_parser
183
43
  requirement: !ruby/object:Gem::Requirement
@@ -204,28 +64,14 @@ dependencies:
204
64
  requirements:
205
65
  - - "~>"
206
66
  - !ruby/object:Gem::Version
207
- version: 0.5.6
208
- type: :runtime
209
- prerelease: false
210
- version_requirements: !ruby/object:Gem::Requirement
211
- requirements:
212
- - - "~>"
213
- - !ruby/object:Gem::Version
214
- version: 0.5.6
215
- - !ruby/object:Gem::Dependency
216
- name: variable
217
- requirement: !ruby/object:Gem::Requirement
218
- requirements:
219
- - - "~>"
220
- - !ruby/object:Gem::Version
221
- version: 0.0.1
67
+ version: 0.6.0
222
68
  type: :runtime
223
69
  prerelease: false
224
70
  version_requirements: !ruby/object:Gem::Requirement
225
71
  requirements:
226
72
  - - "~>"
227
73
  - !ruby/object:Gem::Version
228
- version: 0.0.1
74
+ version: 0.6.0
229
75
  - !ruby/object:Gem::Dependency
230
76
  name: parallel
231
77
  requirement: !ruby/object:Gem::Requirement
@@ -337,6 +183,7 @@ files:
337
183
  - lib/mutant/cli/command/environment/run.rb
338
184
  - lib/mutant/cli/command/environment/show.rb
339
185
  - lib/mutant/cli/command/environment/subject.rb
186
+ - lib/mutant/cli/command/environment/test.rb
340
187
  - lib/mutant/cli/command/root.rb
341
188
  - lib/mutant/cli/command/subscription.rb
342
189
  - lib/mutant/config.rb
@@ -351,6 +198,7 @@ files:
351
198
  - lib/mutant/integration.rb
352
199
  - lib/mutant/integration/null.rb
353
200
  - lib/mutant/isolation.rb
201
+ - lib/mutant/isolation/exception.rb
354
202
  - lib/mutant/isolation/fork.rb
355
203
  - lib/mutant/isolation/none.rb
356
204
  - lib/mutant/license.rb
@@ -412,6 +260,7 @@ files:
412
260
  - lib/mutant/mutator/node/masgn.rb
413
261
  - lib/mutant/mutator/node/match_current_line.rb
414
262
  - lib/mutant/mutator/node/mlhs.rb
263
+ - lib/mutant/mutator/node/module.rb
415
264
  - lib/mutant/mutator/node/named_value/access.rb
416
265
  - lib/mutant/mutator/node/named_value/constant_assignment.rb
417
266
  - lib/mutant/mutator/node/named_value/variable_assignment.rb
@@ -423,11 +272,13 @@ files:
423
272
  - lib/mutant/mutator/node/procarg_zero.rb
424
273
  - lib/mutant/mutator/node/regexp.rb
425
274
  - lib/mutant/mutator/node/regexp/alternation_meta.rb
275
+ - lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb
426
276
  - lib/mutant/mutator/node/regexp/capture_group.rb
427
277
  - lib/mutant/mutator/node/regexp/character_type.rb
428
278
  - lib/mutant/mutator/node/regexp/end_of_line_anchor.rb
429
279
  - lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb
430
- - lib/mutant/mutator/node/regexp/greedy_zero_or_more.rb
280
+ - lib/mutant/mutator/node/regexp/named_group.rb
281
+ - lib/mutant/mutator/node/regexp/zero_or_more.rb
431
282
  - lib/mutant/mutator/node/regopt.rb
432
283
  - lib/mutant/mutator/node/resbody.rb
433
284
  - lib/mutant/mutator/node/rescue.rb
@@ -451,6 +302,7 @@ files:
451
302
  - lib/mutant/parallel/worker.rb
452
303
  - lib/mutant/parser.rb
453
304
  - lib/mutant/pipe.rb
305
+ - lib/mutant/procto.rb
454
306
  - lib/mutant/range.rb
455
307
  - lib/mutant/registry.rb
456
308
  - lib/mutant/reporter.rb
@@ -488,6 +340,7 @@ files:
488
340
  - lib/mutant/timer.rb
489
341
  - lib/mutant/transform.rb
490
342
  - lib/mutant/util.rb
343
+ - lib/mutant/variable.rb
491
344
  - lib/mutant/version.rb
492
345
  - lib/mutant/world.rb
493
346
  - lib/mutant/zombifier.rb
@@ -510,7 +363,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
510
363
  - !ruby/object:Gem::Version
511
364
  version: '0'
512
365
  requirements: []
513
- rubygems_version: 3.2.3
366
+ rubygems_version: 3.1.4
514
367
  signing_key:
515
368
  specification_version: 4
516
369
  summary: ''