variable 0.0.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.
@@ -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