Skip to content

Commit 52d9e93

Browse files
committed
Created shared specs for Concurrent::Observable and updated tests for observable classes.
1 parent f63950f commit 52d9e93

File tree

12 files changed

+257
-4
lines changed

12 files changed

+257
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ ibm.value #=> 187.57
128128
* [Michele Della Torre](https://github.com/mighe)
129129
* [Chris Seaton](https://github.com/chrisseaton)
130130
* [Lucas Allan](https://github.com/lucasallan)
131+
* [Ravil Bayramgalin](https://github.com/brainopia)
131132
* [Giuseppe Capizzi](https://github.com/gcapizzi)
132133
* [Brian Shirai](https://github.com/brixen)
133134
* [Chip Miller](https://github.com/chip-miller)

lib/concurrent/atomic/copy_on_notify_observer_set.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ def initialize
1616
# @param [Symbol] func the function to call on the observer during notification. Default is :update
1717
# @return [Symbol] the added function
1818
def add_observer(observer=nil, func=:update, &block)
19-
unless !!observer ^ block # xor
19+
if observer.nil? && block.nil?
2020
raise ArgumentError, 'should pass observer as a first argument or block'
21+
elsif observer && block
22+
raise ArgumentError.new('cannot provide both an observer and a block')
2123
end
2224

2325
if block

lib/concurrent/atomic/copy_on_write_observer_set.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ def initialize
1515
# @param [Symbol] func the function to call on the observer during notification. Default is :update
1616
# @return [Symbol] the added function
1717
def add_observer(observer=nil, func=:update, &block)
18-
unless !!observer ^ block # xor
18+
if observer.nil? && block.nil?
1919
raise ArgumentError, 'should pass observer as a first argument or block'
20+
elsif observer && block
21+
raise ArgumentError.new('cannot provide both an observer and a block')
2022
end
2123

2224
if block

lib/concurrent/ivar.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def initialize(value = NO_VALUE, opts = {})
4242
#
4343
# @param [Object] observer the object that will be notified of changes
4444
# @param [Symbol] func symbol naming the method to call when this `Observable` has changes`
45-
def add_observer(observer, func = :update, &block)
45+
def add_observer(observer = nil, func = :update, &block)
46+
raise ArgumentError.new('cannot provide both an observer and a block') if observer && block
4647
direct_notification = false
4748

4849
if block

lib/concurrent/observable.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def delete_observer(*args)
1515

1616
def delete_observers
1717
observers.delete_observers
18+
self
1819
end
1920

2021
def count_observers

spec/concurrent/agent_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'spec_helper'
22
require_relative 'dereferenceable_shared'
3+
require_relative 'observable_shared'
34

45
module Concurrent
56

@@ -38,6 +39,17 @@ def execute_dereferenceable(subject)
3839
end
3940

4041
it_should_behave_like :dereferenceable
42+
43+
# observable
44+
45+
subject{ Agent.new(0) }
46+
47+
def trigger_observable(observable)
48+
observable.post{ nil }
49+
sleep(0.1)
50+
end
51+
52+
it_should_behave_like :observable
4153
end
4254

4355
context '#initialize' do

spec/concurrent/channel/probe_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'spec_helper'
2+
require_relative '../observable_shared'
23

34
module Concurrent
45

@@ -7,6 +8,19 @@ module Concurrent
78
let(:channel) { Object.new }
89
let(:probe) { Channel::Probe.new }
910

11+
describe 'behavior' do
12+
13+
# observable
14+
15+
subject{ Channel::Probe.new }
16+
17+
def trigger_observable(observable)
18+
observable.set('value')
19+
end
20+
21+
it_should_behave_like :observable
22+
end
23+
1024
describe '#set_unless_assigned' do
1125
context 'empty probe' do
1226
it 'assigns the value' do

spec/concurrent/future_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'spec_helper'
22
require_relative 'dereferenceable_shared'
33
require_relative 'obligation_shared'
4+
require_relative 'observable_shared'
45

56
module Concurrent
67

@@ -54,6 +55,17 @@ def execute_dereferenceable(subject)
5455
end
5556

5657
it_should_behave_like :dereferenceable
58+
59+
# observable
60+
61+
subject{ Future.new{ nil } }
62+
63+
def trigger_observable(observable)
64+
observable.execute
65+
sleep(0.1)
66+
end
67+
68+
it_should_behave_like :observable
5769
end
5870

5971
context 'subclassing' do

spec/concurrent/ivar_spec.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'spec_helper'
22
require_relative 'dereferenceable_shared'
33
require_relative 'obligation_shared'
4+
require_relative 'observable_shared'
45

56
module Concurrent
67

@@ -56,10 +57,19 @@ def dereferenceable_observable(opts = {})
5657

5758
def execute_dereferenceable(subject)
5859
subject.set('value')
59-
sleep(0.1)
6060
end
6161

6262
it_should_behave_like :dereferenceable
63+
64+
# observable
65+
66+
subject{ IVar.new }
67+
68+
def trigger_observable(observable)
69+
observable.set('value')
70+
end
71+
72+
it_should_behave_like :observable
6373
end
6474

6575
context '#initialize' do

spec/concurrent/observable_shared.rb

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
require 'spec_helper'
2+
3+
share_examples_for :observable do
4+
5+
let(:observer_set) do
6+
subject.instance_variable_get(:@observers)
7+
end
8+
9+
let(:observer_class) do
10+
Class.new do
11+
def initialize(&block)
12+
@block = block
13+
end
14+
def update(*args)
15+
@block.call(*args) if @block
16+
end
17+
end
18+
end
19+
20+
let(:observer){ observer_class.new }
21+
22+
let!(:observer_func){ :notify }
23+
24+
let(:observer_with_func_class) do
25+
Class.new do
26+
def initialize(&block)
27+
@block = block
28+
end
29+
def notify(*args)
30+
@block.call(*args) if @block
31+
end
32+
end
33+
end
34+
35+
let(:observer_with_func){ observer_with_func_class.new }
36+
37+
context '#add_observer' do
38+
39+
it 'adds an observer if called before first notification' do
40+
observer_set.should_receive(:add_observer).with(any_args)
41+
subject.add_observer(observer)
42+
end
43+
44+
it 'adds an observer with :func if called before first notification' do
45+
observer_set.should_receive(:add_observer).with(observer_with_func, :notify)
46+
subject.add_observer(observer_with_func, observer_func)
47+
end
48+
49+
it 'creates an observer from a block if called before first notification' do
50+
block = proc{ nil }
51+
observer_set.should_receive(:add_observer).with(any_args)
52+
subject.add_observer(&block)
53+
end
54+
55+
it 'raises an exception if not given an observer or a block' do
56+
expect {
57+
subject.add_observer
58+
}.to raise_error(ArgumentError)
59+
end
60+
61+
it 'raises an exception when given both an observer and a block' do
62+
expect {
63+
subject.add_observer(observer){ nil }
64+
}.to raise_error(ArgumentError)
65+
end
66+
end
67+
68+
context '#delete_observer' do
69+
70+
it 'deletes the given observer if called before first notification' do
71+
subject.count_observers.should eq 0
72+
subject.add_observer(observer)
73+
subject.count_observers.should eq 1
74+
subject.delete_observer(observer)
75+
subject.count_observers.should eq 0
76+
end
77+
78+
it 'returns the removed observer if found in the observer set' do
79+
subject.add_observer(observer)
80+
subject.delete_observer(observer).should eq observer
81+
end
82+
83+
it 'returns the given observer even when not found in the observer set' do
84+
subject.delete_observer(observer).should eq observer
85+
end
86+
end
87+
88+
context '#delete_observers' do
89+
90+
it 'deletes all observers when called before first notification' do
91+
5.times{ subject.add_observer(observer_class.new) }
92+
subject.count_observers.should eq 5
93+
subject.delete_observers
94+
subject.count_observers.should eq 0
95+
end
96+
97+
it 'returns self' do
98+
subject.delete_observers.should eq subject
99+
end
100+
end
101+
102+
context '#count_observers' do
103+
104+
it 'returns zero for a new observable object' do
105+
subject.count_observers.should eq 0
106+
end
107+
108+
it 'returns a count of registered observers if called before first notification' do
109+
5.times{ subject.add_observer(observer_class.new) }
110+
subject.count_observers.should eq 5
111+
end
112+
113+
it 'returns zero after #delete_observers has been called' do
114+
5.times{ subject.add_observer(observer_class.new) }
115+
subject.delete_observers
116+
subject.count_observers.should eq 0
117+
end
118+
end
119+
120+
context 'first notification' do
121+
122+
it 'calls the #update method on all observers without a specified :func' do
123+
latch = Concurrent::CountDownLatch.new(5)
124+
5.times do
125+
subject.add_observer(observer_class.new{ latch.count_down })
126+
end
127+
trigger_observable(subject)
128+
latch.count.should eq 0
129+
end
130+
131+
it 'calls the appropriate function on all observers which specified a :func' do
132+
latch = Concurrent::CountDownLatch.new(5)
133+
5.times do
134+
obs = observer_with_func_class.new{ latch.count_down }
135+
subject.add_observer(obs, observer_func)
136+
end
137+
trigger_observable(subject)
138+
latch.count.should eq 0
139+
end
140+
141+
it 'calls the proc for all observers added as a block' do
142+
latch = Concurrent::CountDownLatch.new(5)
143+
5.times do
144+
subject.add_observer{ latch.count_down }
145+
end
146+
trigger_observable(subject)
147+
latch.count.should eq 0
148+
end
149+
150+
it 'does not notify any observers removed with #delete_observer' do
151+
latch = Concurrent::CountDownLatch.new(5)
152+
153+
obs = observer_class.new{ latch.count_down }
154+
subject.add_observer(obs)
155+
subject.delete_observer(obs)
156+
157+
trigger_observable(subject)
158+
latch.count.should eq 5
159+
end
160+
161+
it 'does not notify any observers after #delete_observers called' do
162+
latch = Concurrent::CountDownLatch.new(5)
163+
5.times do
164+
subject.add_observer(observer_class.new{ latch.count_down })
165+
end
166+
167+
subject.delete_observers
168+
169+
trigger_observable(subject)
170+
latch.count.should eq 5
171+
end
172+
end
173+
end

0 commit comments

Comments
 (0)