variable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'variable'
4
+
5
+ require 'devtools/spec_helper'
6
+
7
+ module VariableSpec
8
+ def verify_events
9
+ expectations = raw_expectations
10
+ .map { |expectation| XSpec::MessageExpectation.parse(**expectation) }
11
+
12
+ XSpec::ExpectationVerifier.verify(self, expectations) do
13
+ yield
14
+ end
15
+ end
16
+
17
+ def undefined
18
+ double('undefined')
19
+ end
20
+ end # XSpecHelper
21
+
22
+ RSpec.configure do |config|
23
+ config.include(VariableSpec)
24
+ end
@@ -0,0 +1,192 @@
1
+ module XSpec
2
+ class MessageReaction
3
+ include Concord.new(:event_list)
4
+
5
+ TERMINATE_EVENTS = IceNine.deep_freeze(%i[return exception].to_set)
6
+ VALID_EVENTS = IceNine.deep_freeze(%i[return execute exception yields].to_set)
7
+
8
+ private_constant(*constants(false))
9
+
10
+ def call(observation)
11
+ event_list.map do |event, object|
12
+ __send__(event, observation, object)
13
+ end.last
14
+ end
15
+
16
+ # Parse events into reaction
17
+ #
18
+ # @param [Array{Symbol,Object}, Hash{Symbol,Object}]
19
+ #
20
+ # @return [MessageReaction]
21
+ def self.parse(events)
22
+ event_list = events.to_a
23
+ assert_valid(event_list)
24
+ new(event_list)
25
+ end
26
+
27
+ private
28
+
29
+ def return(_event, value)
30
+ value
31
+ end
32
+
33
+ def execute(_event, block)
34
+ block.call
35
+ end
36
+
37
+ def exception(_event, exception)
38
+ fail exception
39
+ end
40
+
41
+ def yields(observation, yields)
42
+ block = observation.block or fail 'No block passed where expected'
43
+
44
+ validate_block_arity(observation, yields)
45
+
46
+ block.call(*yields)
47
+ end
48
+
49
+ def validate_block_arity(observation, yields)
50
+ expected, observed = yields.length, observation.block.arity
51
+
52
+ # block allows anything we can skip the check
53
+ return if observed.equal?(-1)
54
+ fail 'Optargs currently not supported' if observed < -1
55
+ block_arity_mismatch(observation, expected, observed) unless expected.equal?(observed)
56
+ end
57
+
58
+ def block_arity_mismatch(observation, expected, observed)
59
+ fail <<~MESSAGE
60
+ block arity mismatch, expected #{expected} observed #{observed}
61
+ bservation: observation.inspect
62
+ MESSAGE
63
+ end
64
+
65
+ alias_method :yields_return, :yields
66
+
67
+ def self.assert_valid(event_list)
68
+ assert_not_empty(event_list)
69
+ assert_valid_events(event_list)
70
+ assert_total(event_list)
71
+ end
72
+ private_class_method :assert_valid
73
+
74
+ def self.assert_valid_events(event_list)
75
+ event_list.map(&:first).each do |event|
76
+ fail "Invalid event: #{event}" unless VALID_EVENTS.include?(event)
77
+ end
78
+ end
79
+ private_class_method :assert_valid_events
80
+
81
+ def self.assert_not_empty(event_list)
82
+ fail 'no events' if event_list.empty?
83
+ end
84
+ private_class_method :assert_not_empty
85
+
86
+ def self.assert_total(event_list)
87
+ return unless event_list[0..-2].map(&:first).any?(&TERMINATE_EVENTS.method(:include?))
88
+
89
+ fail "Reaction not total: #{event_list}"
90
+ end
91
+ private_class_method :assert_total
92
+ end # MessageReaction
93
+
94
+ class MessageExpectation
95
+ include Anima.new(:receiver, :selector, :arguments, :reaction, :pre_action)
96
+
97
+ # rubocop:disable Metrics/ParameterLists
98
+ def self.parse(receiver:, selector:, arguments: [], reaction: nil, pre_action: nil)
99
+ new(
100
+ receiver: receiver,
101
+ selector: selector,
102
+ arguments: arguments,
103
+ pre_action: pre_action,
104
+ reaction: MessageReaction.parse(reaction || { return: nil })
105
+ )
106
+ end
107
+
108
+ def call(observation)
109
+ Verifier.new(self, observation).call
110
+ end
111
+
112
+ class Verifier
113
+ include Concord.new(:expectation, :observation)
114
+
115
+ VERIFIED_ATTRIBUTES = IceNine.deep_freeze(%i[receiver selector arguments])
116
+
117
+ def call
118
+ VERIFIED_ATTRIBUTES.each(&method(:assert_expected_attribute))
119
+
120
+ expectation.pre_action&.call
121
+ expectation.reaction.call(observation)
122
+ end
123
+
124
+ private
125
+
126
+ def assert_expected_attribute(name)
127
+ error("#{name} mismatch") unless observation.public_send(name).eql?(expectation.public_send(name))
128
+ end
129
+
130
+ def error(message)
131
+ fail <<~MESSAGE
132
+ "#{message},
133
+ observation:
134
+ #{observation.inspect}
135
+ expectation: #{expectation.inspect}"
136
+ MESSAGE
137
+ end
138
+ end # Verifier
139
+ end # MessageExpectation
140
+
141
+ class MessageObservation
142
+ include Anima.new(:receiver, :selector, :arguments, :block)
143
+ end # MessageObservation
144
+
145
+ class ExpectationVerifier
146
+ include Concord.new(:expectations)
147
+
148
+ def call(observation)
149
+ expectation = expectations.shift or fail "No expected message but observed #{observation.inspect}"
150
+ expectation.call(observation)
151
+ end
152
+
153
+ def assert_done
154
+ expectations.empty? or fail_unconsumed
155
+ end
156
+
157
+ def fail_unconsumed
158
+ fail <<-MESSAGE
159
+ unconsumed expectations:
160
+ #{expectations.map(&:inspect).join("\n")}
161
+ MESSAGE
162
+ end
163
+
164
+ # rubocop:disable Metrics/MethodLength
165
+ def self.verify(rspec_context, expectations)
166
+ verifier = new(expectations)
167
+
168
+ hooks = expectations
169
+ .map { |expectation| [expectation.receiver, expectation.selector] }
170
+ .to_set
171
+
172
+ hooks.each do |receiver, selector|
173
+ rspec_context.instance_eval do
174
+ allow(receiver).to receive(selector) do |*arguments, &block|
175
+ verifier.call(
176
+ MessageObservation.new(
177
+ receiver: receiver,
178
+ selector: selector,
179
+ arguments: arguments,
180
+ block: block
181
+ )
182
+ )
183
+ end
184
+ end
185
+ end
186
+
187
+ yield
188
+
189
+ verifier.assert_done
190
+ end
191
+ end # ExpectationVerifier
192
+ end # XSpec
@@ -0,0 +1,772 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VariableSpec
4
+ module VariableHelper
5
+ def empty
6
+ described_class.new(
7
+ condition_variable: condition_variable_class,
8
+ mutex: mutex_class
9
+ )
10
+ end
11
+
12
+ def full(value)
13
+ described_class.new(
14
+ condition_variable: condition_variable_class,
15
+ mutex: mutex_class,
16
+ value: value
17
+ )
18
+ end
19
+
20
+ def self.shared_setup
21
+ lambda do |_host|
22
+ let(:condition_variable_class) { class_double(ConditionVariable) }
23
+ let(:expected_result) { value }
24
+ let(:full_condition) { instance_double(ConditionVariable, 'full') }
25
+ let(:mutex) { instance_double(Mutex) }
26
+ let(:mutex_class) { class_double(Mutex) }
27
+ let(:value) { instance_double(Object, 'value') }
28
+
29
+ let(:synchronize) do
30
+ {
31
+ receiver: mutex,
32
+ selector: :synchronize,
33
+ reaction: { yields: [] }
34
+ }
35
+ end
36
+
37
+ let(:signal_full) do
38
+ {
39
+ receiver: full_condition,
40
+ selector: :signal
41
+ }
42
+ end
43
+
44
+ let(:put) do
45
+ {
46
+ receiver: full_condition,
47
+ selector: :wait,
48
+ arguments: [mutex],
49
+ reaction: { execute: -> { subject.put(value) } }
50
+ }
51
+ end
52
+
53
+ let(:wait_empty) do
54
+ {
55
+ receiver: empty_condition,
56
+ selector: :wait,
57
+ arguments: [mutex]
58
+ }
59
+ end
60
+
61
+ let(:wait_full) do
62
+ {
63
+ receiver: full_condition,
64
+ selector: :wait,
65
+ arguments: [mutex]
66
+ }
67
+ end
68
+
69
+ let(:signal_empty) do
70
+ {
71
+ receiver: empty_condition,
72
+ selector: :signal
73
+ }
74
+ end
75
+
76
+ shared_examples 'consumes events' do
77
+ specify do
78
+ verify_events do
79
+ expect(apply).to eql(expected_result)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ RSpec.describe Variable::IVar do
89
+ include VariableSpec::VariableHelper
90
+
91
+ class_eval(&VariableSpec::VariableHelper.shared_setup)
92
+
93
+ subject { empty }
94
+
95
+ let(:setup) do
96
+ [
97
+ {
98
+ receiver: condition_variable_class,
99
+ selector: :new,
100
+ reaction: { return: full_condition }
101
+ },
102
+ {
103
+ receiver: mutex_class,
104
+ selector: :new,
105
+ reaction: { return: mutex }
106
+ },
107
+ ]
108
+ end
109
+
110
+ describe '#take' do
111
+ def apply
112
+ subject.take
113
+ end
114
+
115
+ context 'when is initially full' do
116
+ subject { full(value) }
117
+
118
+ let(:raw_expectations) { [*setup, synchronize] }
119
+
120
+ include_examples 'consumes events'
121
+ end
122
+
123
+ context 'when is initially empty' do
124
+ let(:raw_expectations) do
125
+ [
126
+ *setup,
127
+ synchronize,
128
+ put,
129
+ synchronize,
130
+ signal_full
131
+ ]
132
+ end
133
+
134
+ include_examples 'consumes events'
135
+ end
136
+ end
137
+
138
+ describe '#take_timeout' do
139
+ def apply
140
+ subject.take_timeout(1.0)
141
+ end
142
+
143
+ context 'when is initially full' do
144
+ subject { full(value) }
145
+
146
+ let(:raw_expectations) { [*setup, synchronize] }
147
+
148
+ let(:expected_result) do
149
+ Variable.const_get(:Result)::Value.new(value)
150
+ end
151
+
152
+ include_examples 'consumes events'
153
+ end
154
+
155
+ context 'when is initially empty' do
156
+ def wait(time)
157
+ {
158
+ receiver: full_condition,
159
+ selector: :wait,
160
+ arguments: [mutex, time]
161
+ }
162
+ end
163
+
164
+ def elapsed(time)
165
+ {
166
+ receiver: Variable::Timer,
167
+ selector: :elapsed,
168
+ reaction: { yields: [], return: time }
169
+ }
170
+ end
171
+
172
+ context 'and timeout occurs before value is put' do
173
+ let(:expected_result) do
174
+ Variable.const_get(:Result)::Timeout.new
175
+ end
176
+
177
+ context 'wait exactly runs to zero left time on the clock' do
178
+ let(:raw_expectations) do
179
+ [
180
+ *setup,
181
+ synchronize,
182
+ elapsed(0.5),
183
+ wait(1.0),
184
+ elapsed(0.5),
185
+ wait(0.5)
186
+ ]
187
+ end
188
+
189
+ include_examples 'consumes events'
190
+ end
191
+
192
+ context 'wait overruns timeout' do
193
+ let(:raw_expectations) do
194
+ [
195
+ *setup,
196
+ synchronize,
197
+ elapsed(1.5),
198
+ wait(1.0)
199
+ ]
200
+ end
201
+
202
+ include_examples 'consumes events'
203
+ end
204
+ end
205
+
206
+ context 'and put occurs before timeout' do
207
+ let(:expected_result) do
208
+ Variable.const_get(:Result)::Value.new(value)
209
+ end
210
+
211
+ let(:raw_expectations) do
212
+ [
213
+ *setup,
214
+ synchronize,
215
+ elapsed(0.5),
216
+ wait(1.0).merge(reaction: { execute: -> { subject.put(value) } }),
217
+ synchronize,
218
+ signal_full
219
+ ]
220
+ end
221
+
222
+ include_examples 'consumes events'
223
+ end
224
+ end
225
+ end
226
+
227
+ describe '#put' do
228
+ def apply
229
+ subject.put(value)
230
+ end
231
+
232
+ context 'when is initially empty' do
233
+ context 'when not reading result' do
234
+ let(:expected_result) { subject }
235
+
236
+ let(:raw_expectations) do
237
+ [
238
+ *setup,
239
+ synchronize,
240
+ signal_full
241
+ ]
242
+ end
243
+
244
+ include_examples 'consumes events'
245
+ end
246
+
247
+ context 'when reading result back' do
248
+ let(:expected_result) { value }
249
+
250
+ def apply
251
+ super
252
+ subject.read
253
+ end
254
+
255
+ let(:raw_expectations) do
256
+ [
257
+ *setup,
258
+ synchronize,
259
+ signal_full,
260
+ synchronize
261
+ ]
262
+ end
263
+
264
+ include_examples 'consumes events'
265
+ end
266
+ end
267
+
268
+ context 'when is initially full' do
269
+ subject { full(value) }
270
+
271
+ let(:raw_expectations) { [*setup, synchronize] }
272
+
273
+ it 'raises expected exception' do
274
+ verify_events do
275
+ expect { apply }.to raise_error(Variable::IVar::Error, 'is immutable')
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ describe '#try_put' do
282
+ def apply
283
+ subject.try_put(value)
284
+ end
285
+
286
+ let(:expected_result) { subject }
287
+
288
+ context 'when is initially empty' do
289
+ let(:raw_expectations) do
290
+ [
291
+ *setup,
292
+ synchronize,
293
+ signal_full
294
+ ]
295
+ end
296
+
297
+ include_examples 'consumes events'
298
+
299
+ context 'reading the put value' do
300
+ let(:expected_result) { value }
301
+
302
+ let(:raw_expectations) do
303
+ [
304
+ *super(),
305
+ synchronize
306
+ ]
307
+ end
308
+
309
+ def apply
310
+ super
311
+ subject.read
312
+ end
313
+
314
+ include_examples 'consumes events'
315
+ end
316
+ end
317
+
318
+ context 'when is initially full' do
319
+ subject { full(value) }
320
+
321
+ let(:raw_expectations) { [*setup, synchronize] }
322
+
323
+ include_examples 'consumes events'
324
+ end
325
+ end
326
+
327
+ describe '#read' do
328
+ def apply
329
+ subject.read
330
+ end
331
+
332
+ context 'when is initially empty' do
333
+ let(:raw_expectations) do
334
+ [
335
+ *setup,
336
+ synchronize,
337
+ wait_full.merge(reaction: { execute: -> { subject.put(value) } }),
338
+ synchronize,
339
+ signal_full
340
+ ]
341
+ end
342
+
343
+ include_examples 'consumes events'
344
+ end
345
+
346
+ context 'when is initially full' do
347
+ subject { full(value) }
348
+
349
+ let(:raw_expectations) { [*setup, synchronize] }
350
+
351
+ include_examples 'consumes events'
352
+ end
353
+ end
354
+
355
+ describe '#with' do
356
+ def apply
357
+ subject.with do |value|
358
+ @value = value
359
+ end
360
+ end
361
+
362
+ before { @value = nil }
363
+
364
+ context 'when is initially full' do
365
+ subject { full(value) }
366
+
367
+ let(:raw_expectations) { [*setup, synchronize] }
368
+
369
+ include_examples 'consumes events'
370
+
371
+ it 'should yield value' do
372
+ verify_events do
373
+ expect { apply }.to change { @value }.from(nil).to(value)
374
+ end
375
+ end
376
+ end
377
+
378
+ context 'when is initially empty' do
379
+ subject { empty }
380
+
381
+ let(:raw_expectations) do
382
+ [
383
+ *setup,
384
+ synchronize,
385
+ put,
386
+ synchronize,
387
+ signal_full
388
+ ]
389
+ end
390
+
391
+ include_examples 'consumes events'
392
+
393
+ it 'should yield value' do
394
+ verify_events do
395
+ expect { apply }.to change { @value }.from(nil).to(value)
396
+ end
397
+ end
398
+ end
399
+ end
400
+ describe '#populate_with' do
401
+ def apply
402
+ subject.populate_with { @counter += 1 }
403
+ end
404
+
405
+ before do
406
+ @counter = 0
407
+ end
408
+
409
+ context 'when is initially full' do
410
+ subject { full(value) }
411
+
412
+ let(:raw_expectations) { setup }
413
+
414
+ include_examples 'consumes events'
415
+
416
+ it 'does not not execute block' do
417
+ verify_events do
418
+ expect { apply }.to_not change { @counter }.from(0)
419
+ end
420
+ end
421
+ end
422
+
423
+ context 'when is initially empty' do
424
+ subject { empty }
425
+
426
+ context 'without contention' do
427
+ let(:expected_result) { 1 }
428
+
429
+ let(:raw_expectations) do
430
+ [
431
+ *setup,
432
+ synchronize,
433
+ signal_full
434
+ ]
435
+ end
436
+
437
+ include_examples 'consumes events'
438
+
439
+ it 'does execute block' do
440
+ verify_events do
441
+ expect { apply }.to change { @counter }.from(0).to(1)
442
+ end
443
+ end
444
+ end
445
+
446
+ context 'with contention' do
447
+ let(:raw_expectations) do
448
+ [
449
+ *setup,
450
+ synchronize.merge(pre_action: -> { subject.put(value) }),
451
+ synchronize,
452
+ signal_full
453
+ ]
454
+ end
455
+
456
+ include_examples 'consumes events'
457
+
458
+ it 'does not execute block' do
459
+ verify_events do
460
+ expect { apply }.to_not change { @counter }.from(0)
461
+ end
462
+ end
463
+ end
464
+ end
465
+ end
466
+ end
467
+
468
+ describe Variable::MVar do
469
+ include VariableSpec::VariableHelper
470
+
471
+ class_eval(&VariableSpec::VariableHelper.shared_setup)
472
+
473
+ subject { empty }
474
+
475
+ let(:empty_condition) { instance_double(ConditionVariable, 'empty') }
476
+
477
+ let(:setup) do
478
+ [
479
+ {
480
+ receiver: condition_variable_class,
481
+ selector: :new,
482
+ reaction: { return: full_condition }
483
+ },
484
+ {
485
+ receiver: mutex_class,
486
+ selector: :new,
487
+ reaction: { return: mutex }
488
+ },
489
+ {
490
+ receiver: condition_variable_class,
491
+ selector: :new,
492
+ reaction: { return: empty_condition }
493
+ },
494
+ synchronize
495
+ ]
496
+ end
497
+
498
+ describe '#put' do
499
+ def apply
500
+ subject.put(value)
501
+ end
502
+
503
+ context 'when is initially empty' do
504
+ context 'when not reading result' do
505
+ let(:expected_result) { subject }
506
+
507
+ let(:raw_expectations) do
508
+ [
509
+ *setup,
510
+ signal_full
511
+ ]
512
+ end
513
+
514
+ include_examples 'consumes events'
515
+ end
516
+
517
+ context 'when reading result back' do
518
+ let(:expected_result) { value }
519
+
520
+ def apply
521
+ super
522
+ subject.read
523
+ end
524
+
525
+ let(:raw_expectations) do
526
+ [
527
+ *setup,
528
+ signal_full,
529
+ synchronize
530
+ ]
531
+ end
532
+
533
+ include_examples 'consumes events'
534
+ end
535
+ end
536
+
537
+ context 'when is initially full' do
538
+ context 'when not reading result' do
539
+ subject { full(value) }
540
+
541
+ let(:expected_result) { subject }
542
+
543
+ let(:raw_expectations) do
544
+ [
545
+ *setup,
546
+ wait_empty.merge(reaction: { execute: -> { subject.take } }),
547
+ synchronize,
548
+ signal_empty,
549
+ signal_full
550
+ ]
551
+ end
552
+
553
+ include_examples 'consumes events'
554
+ end
555
+
556
+ context 'when reading result back' do
557
+ subject { full(value) }
558
+
559
+ def apply
560
+ super
561
+ subject.read
562
+ end
563
+
564
+ let(:expected_result) { value }
565
+
566
+ let(:raw_expectations) do
567
+ [
568
+ *setup,
569
+ wait_empty.merge(reaction: { execute: -> { subject.take } }),
570
+ synchronize,
571
+ signal_empty,
572
+ signal_full,
573
+ synchronize
574
+ ]
575
+ end
576
+
577
+ include_examples 'consumes events'
578
+ end
579
+ end
580
+ end
581
+
582
+ describe '#modify' do
583
+ let(:expected_result) { 1 }
584
+ let(:value) { 0 }
585
+
586
+ def apply
587
+ subject.modify(&:succ)
588
+ end
589
+
590
+ context 'when is initially empty' do
591
+ let(:raw_expectations) do
592
+ [
593
+ *setup,
594
+ wait_full.merge(reaction: { execute: -> { subject.put(value) } }),
595
+ synchronize,
596
+ signal_full,
597
+ signal_full
598
+ ]
599
+ end
600
+
601
+ include_examples 'consumes events'
602
+ end
603
+
604
+ context 'when is initially full' do
605
+ subject { full(value) }
606
+
607
+ let(:raw_expectations) do
608
+ [
609
+ *setup,
610
+ signal_full
611
+ ]
612
+ end
613
+
614
+ include_examples 'consumes events'
615
+ end
616
+ end
617
+
618
+ describe '#take' do
619
+ def apply
620
+ subject.take
621
+ end
622
+
623
+ context 'when is initially empty' do
624
+ let(:expected_result) { value }
625
+
626
+ let(:raw_expectations) do
627
+ [
628
+ *setup,
629
+ wait_full.merge(reaction: { execute: -> { subject.put(value) } }),
630
+ synchronize,
631
+ signal_full,
632
+ signal_empty
633
+ ]
634
+ end
635
+
636
+ include_examples 'consumes events'
637
+ end
638
+
639
+ context 'when is initially full' do
640
+ subject { full(value) }
641
+
642
+ let(:expected_result) { value }
643
+
644
+ let(:raw_expectations) do
645
+ [
646
+ *setup,
647
+ signal_empty
648
+ ]
649
+ end
650
+
651
+ include_examples 'consumes events'
652
+ end
653
+ end
654
+ end
655
+
656
+ describe Variable.const_get(:Result)::Value do
657
+ subject { described_class.new(object) }
658
+
659
+ let(:object) { Object.new }
660
+
661
+ describe '#frozen?' do
662
+ def apply
663
+ subject.frozen?
664
+ end
665
+
666
+ it 'returns true' do
667
+ expect(apply).to be(true)
668
+ end
669
+ end
670
+
671
+ describe '#timeout?' do
672
+ def apply
673
+ subject.timeout?
674
+ end
675
+
676
+ it 'returns false' do
677
+ expect(apply).to be(false)
678
+ end
679
+ end
680
+
681
+ describe '#value' do
682
+ def apply
683
+ subject.value
684
+ end
685
+
686
+ it 'returns value' do
687
+ expect(apply).to be(object)
688
+ end
689
+ end
690
+ end
691
+
692
+ describe Variable.const_get(:Result)::Timeout do
693
+ describe '.new' do
694
+ it 'is instance of timeout' do
695
+ expect(described_class.new.instance_of?(described_class)).to be(true)
696
+ end
697
+
698
+ it 'is idempotent' do
699
+ expect(described_class.new).to be(described_class.new)
700
+ end
701
+ end
702
+
703
+ describe '#frozen?' do
704
+ def apply
705
+ subject.frozen?
706
+ end
707
+
708
+ it 'returns true' do
709
+ expect(apply).to be(true)
710
+ end
711
+ end
712
+
713
+ describe '#timeout?' do
714
+ def apply
715
+ subject.timeout?
716
+ end
717
+
718
+ it 'returns true' do
719
+ expect(apply).to be(true)
720
+ end
721
+ end
722
+
723
+ describe '#value' do
724
+ def apply
725
+ subject.value
726
+ end
727
+
728
+ it 'returns nil' do
729
+ expect(apply).to be(nil)
730
+ end
731
+ end
732
+ end
733
+
734
+ describe Variable::Timer do
735
+ describe '.elapsed' do
736
+ let(:raw_expectations) do
737
+ [
738
+ {
739
+ receiver: Process,
740
+ selector: :clock_gettime,
741
+ arguments: [Process::CLOCK_MONOTONIC],
742
+ reaction: { return: 1 }
743
+ },
744
+ {
745
+ receiver: object,
746
+ selector: :to_s,
747
+ arguments: [],
748
+ reaction: { return: '' }
749
+ },
750
+ {
751
+ receiver: Process,
752
+ selector: :clock_gettime,
753
+ arguments: [Process::CLOCK_MONOTONIC],
754
+ reaction: { return: 3 }
755
+ },
756
+ ]
757
+ end
758
+
759
+ let(:object) { Object.new }
760
+ let(:block) { lambda { object.to_s } }
761
+
762
+ def apply
763
+ described_class.elapsed(&block)
764
+ end
765
+
766
+ it 'returns elapsed time' do
767
+ verify_events do
768
+ expect(apply).to eql(2)
769
+ end
770
+ end
771
+ end
772
+ end