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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yaml +68 -0
- data/.rspec +5 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +179 -0
- data/LICENSE +20 -0
- data/README.md +5 -0
- data/config/devtools.yml +2 -0
- data/lib/variable.rb +322 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/xspec.rb +192 -0
- data/spec/unit/variable_spec.rb +772 -0
- data/variable.gemspec +20 -0
- metadata +71 -0
data/spec/spec_helper.rb
ADDED
@@ -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
|