functional-ruby 0.5.0 → 0.6.0
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 +4 -4
- data/README.md +154 -562
- data/lib/functional/agent.rb +130 -0
- data/lib/functional/all.rb +9 -1
- data/lib/functional/behavior.rb +72 -39
- data/lib/functional/cached_thread_pool.rb +122 -0
- data/lib/functional/concurrency.rb +32 -24
- data/lib/functional/core.rb +2 -62
- data/lib/functional/event.rb +53 -0
- data/lib/functional/event_machine_defer_proxy.rb +23 -0
- data/lib/functional/fixed_thread_pool.rb +89 -0
- data/lib/functional/future.rb +42 -0
- data/lib/functional/global_thread_pool.rb +3 -0
- data/lib/functional/obligation.rb +121 -0
- data/lib/functional/promise.rb +194 -0
- data/lib/functional/thread_pool.rb +61 -0
- data/lib/functional/utilities.rb +114 -0
- data/lib/functional/version.rb +1 -1
- data/lib/functional.rb +1 -0
- data/lib/functional_ruby.rb +1 -0
- data/md/behavior.md +147 -0
- data/md/concurrency.md +465 -0
- data/md/future.md +32 -0
- data/md/obligation.md +32 -0
- data/md/pattern_matching.md +512 -0
- data/md/promise.md +220 -0
- data/md/utilities.md +53 -0
- data/spec/functional/agent_spec.rb +405 -0
- data/spec/functional/behavior_spec.rb +12 -33
- data/spec/functional/cached_thread_pool_spec.rb +112 -0
- data/spec/functional/concurrency_spec.rb +55 -0
- data/spec/functional/event_machine_defer_proxy_spec.rb +246 -0
- data/spec/functional/event_spec.rb +114 -0
- data/spec/functional/fixed_thread_pool_spec.rb +84 -0
- data/spec/functional/future_spec.rb +115 -0
- data/spec/functional/obligation_shared.rb +121 -0
- data/spec/functional/pattern_matching_spec.rb +10 -8
- data/spec/functional/promise_spec.rb +310 -0
- data/spec/functional/thread_pool_shared.rb +209 -0
- data/spec/functional/utilities_spec.rb +149 -0
- data/spec/spec_helper.rb +2 -0
- metadata +55 -5
data/md/promise.md
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
# Promises, promises...
|
2
|
+
|
3
|
+
> A promise represents the eventual value returned from the single completion of an operation.
|
4
|
+
|
5
|
+
Promises have become an extremely important async technique in JavaScript. A promise is an
|
6
|
+
an operation that is performed asynchronously and is guaranteed to either succeed and return
|
7
|
+
a value or fail with a reason. What makes promises distinctly different from futures is
|
8
|
+
that promises can be chained such that the result of onw promise is passed to zero or
|
9
|
+
more children. Order of execution is guaranteed based on the order the promises are
|
10
|
+
created and parent promises are guaranteed to be complete before their children. Once a
|
11
|
+
promise has been fulfilled or rejected the corresponding value/reason can be retrieved.
|
12
|
+
|
13
|
+
## The shoulders of giants
|
14
|
+
|
15
|
+
Inspiration for this implementation came from the CommonJS
|
16
|
+
[Promises/A](http://wiki.commonjs.org/wiki/Promises/A) proposal and the
|
17
|
+
[Promises/A+](http://promises-aplus.github.io/promises-spec/) specification.
|
18
|
+
This implementation is specifically tailored to the idioms and practices of
|
19
|
+
Ruby. It is not 100% compliant with either of the aforementioned specifications.
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
|
24
|
+
Start by requiring promises
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'functional/promise'
|
28
|
+
```
|
29
|
+
|
30
|
+
Then create one
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
p = Promise.new("Jerry", "D'Antonio") do |first, last|
|
34
|
+
"#{last}, #{first}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# -or-
|
38
|
+
|
39
|
+
p = promise(10){|x| x * x * x }
|
40
|
+
```
|
41
|
+
|
42
|
+
Promises can be chained using the `then` method. The `then` method
|
43
|
+
accepts a block but no arguments. The result of the each promise is
|
44
|
+
passed as the block argument to chained promises
|
45
|
+
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
p = promise(10){|x| x * 2}.then{|result| result - 10 }
|
49
|
+
```
|
50
|
+
|
51
|
+
And so on, and so on, and so on...
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
p = promise(10){|x| x * 2}.
|
55
|
+
then{|result| result - 10 }.
|
56
|
+
then{|result| result * 3 }.
|
57
|
+
then{|result| result % 5 }
|
58
|
+
```
|
59
|
+
|
60
|
+
Promises are executed asynchronously so a newly-created promise
|
61
|
+
*should* always be in the pending state
|
62
|
+
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
p = promise{ "Hello, world!" }
|
66
|
+
p.state #=> :pending
|
67
|
+
p.pending? #=> true
|
68
|
+
```
|
69
|
+
|
70
|
+
Wait a little bit, and the promise will resolve and provide a value
|
71
|
+
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
p = promise{ "Hello, world!" }
|
75
|
+
sleep(0.1)
|
76
|
+
|
77
|
+
p.state #=> :fulfilled
|
78
|
+
p.fulfilled? #=> true
|
79
|
+
|
80
|
+
p.value #=> "Hello, world!"
|
81
|
+
|
82
|
+
```
|
83
|
+
|
84
|
+
If an exception occurs, the promise will be rejected and will provide
|
85
|
+
a reason for the rejection
|
86
|
+
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
p = promise{ raise StandardError.new("Here comes the Boom!") }
|
90
|
+
sleep(0.1)
|
91
|
+
|
92
|
+
p.state #=> :rejected
|
93
|
+
p.rejected? #=> true
|
94
|
+
|
95
|
+
p.reason=> #=> "#<StandardError: Here comes the Boom!>"
|
96
|
+
```
|
97
|
+
|
98
|
+
### Rejection
|
99
|
+
|
100
|
+
Much like the economy, rejection exhibits a trickle-down effect. When
|
101
|
+
a promise is rejected all its children will be rejected
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
p = [ promise{ Thread.pass; raise StandardError } ]
|
105
|
+
|
106
|
+
10.times{|i| p << p.first.then{ i } }
|
107
|
+
sleep(0.1)
|
108
|
+
|
109
|
+
p.length #=> 11
|
110
|
+
p.first.state #=> :rejected
|
111
|
+
p.last.state #=> :rejected
|
112
|
+
```
|
113
|
+
|
114
|
+
Once a promise is rejected it will not accept any children. Calls
|
115
|
+
to `then` will continually return `self`
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
p = promise{ raise StandardError }
|
119
|
+
sleep(0.1)
|
120
|
+
|
121
|
+
p.object_id #=> 32960556
|
122
|
+
p.then{}.object_id #=> 32960556
|
123
|
+
p.then{}.object_id #=> 32960556
|
124
|
+
```
|
125
|
+
|
126
|
+
### Error Handling
|
127
|
+
|
128
|
+
Promises support error handling callbacks is a style mimicing Ruby's
|
129
|
+
own exception handling mechanism, namely `rescue`
|
130
|
+
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
promise{ "dangerous operation..." }.rescue{|ex| puts "Bam!" }
|
134
|
+
|
135
|
+
# -or- (for the Java/C# crowd)
|
136
|
+
promise{ "dangerous operation..." }.catch{|ex| puts "Boom!" }
|
137
|
+
|
138
|
+
# -or- (for the hipsters)
|
139
|
+
promise{ "dangerous operation..." }.on_error{|ex| puts "Pow!" }
|
140
|
+
```
|
141
|
+
|
142
|
+
As with Ruby's `rescue` mechanism, a promise's `rescue` method can
|
143
|
+
accept an optional Exception class argument (defaults to `Exception`
|
144
|
+
when not specified)
|
145
|
+
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
promise{ "dangerous operation..." }.rescue(ArgumentError){|ex| puts "Bam!" }
|
149
|
+
```
|
150
|
+
|
151
|
+
Calls to `rescue` can also be chained
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
promise{ "dangerous operation..." }.
|
155
|
+
rescue(ArgumentError){|ex| puts "Bam!" }.
|
156
|
+
rescue(NoMethodError){|ex| puts "Boom!" }.
|
157
|
+
rescue(StandardError){|ex| puts "Pow!" }
|
158
|
+
```
|
159
|
+
|
160
|
+
When there are multiple `rescue` handlers the first one to match the thrown
|
161
|
+
exception will be triggered
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
promise{ raise NoMethodError }.
|
165
|
+
rescue(ArgumentError){|ex| puts "Bam!" }.
|
166
|
+
rescue(NoMethodError){|ex| puts "Boom!" }.
|
167
|
+
rescue(StandardError){|ex| puts "Pow!" }
|
168
|
+
|
169
|
+
sleep(0.1)
|
170
|
+
|
171
|
+
#=> Boom!
|
172
|
+
```
|
173
|
+
|
174
|
+
Trickle-down rejection also applies to rescue handlers. When a promise is rejected,
|
175
|
+
for any reason, its rescue handlers will be triggered. Rejection of the parent counts.
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
promise{ Thread.pass; raise StandardError }.
|
179
|
+
then{ true }.rescue{ puts 'Boom!' }.
|
180
|
+
then{ true }.rescue{ puts 'Boom!' }.
|
181
|
+
then{ true }.rescue{ puts 'Boom!' }.
|
182
|
+
then{ true }.rescue{ puts 'Boom!' }.
|
183
|
+
then{ true }.rescue{ puts 'Boom!' }
|
184
|
+
sleep(0.1)
|
185
|
+
|
186
|
+
#=> Boom!
|
187
|
+
#=> Boom!
|
188
|
+
#=> Boom!
|
189
|
+
#=> Boom!
|
190
|
+
#=> Boom!
|
191
|
+
```
|
192
|
+
|
193
|
+
## Copyright
|
194
|
+
|
195
|
+
*Functional Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
|
196
|
+
It is free software and may be redistributed under the terms specified in the LICENSE file.
|
197
|
+
|
198
|
+
## License
|
199
|
+
|
200
|
+
Released under the MIT license.
|
201
|
+
|
202
|
+
http://www.opensource.org/licenses/mit-license.php
|
203
|
+
|
204
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
205
|
+
> of this software and associated documentation files (the "Software"), to deal
|
206
|
+
> in the Software without restriction, including without limitation the rights
|
207
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
208
|
+
> copies of the Software, and to permit persons to whom the Software is
|
209
|
+
> furnished to do so, subject to the following conditions:
|
210
|
+
>
|
211
|
+
> The above copyright notice and this permission notice shall be included in
|
212
|
+
> all copies or substantial portions of the Software.
|
213
|
+
>
|
214
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
215
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
216
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
217
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
218
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
219
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
220
|
+
> THE SOFTWARE.
|
data/md/utilities.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Utility Functions
|
2
|
+
|
3
|
+
Convenience functions are not imported by default. They need a separate `require` statement:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
require 'functional/utilities'
|
7
|
+
```
|
8
|
+
|
9
|
+
This gives you access to a few constants and functions:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Infinity #=> Infinity
|
13
|
+
NaN #=> NaN
|
14
|
+
|
15
|
+
repl? #=> true when called under irb, pry, bundle console, or rails console
|
16
|
+
|
17
|
+
safe(1, 2){|a, b| a + b} #=> 3
|
18
|
+
safe{ eval 'puts "Hello World!"' } #=> SecurityError: Insecure operation
|
19
|
+
|
20
|
+
pp_s [1,2,3,4] #=> "[1, 2, 3, 4]\n" props to Rha7
|
21
|
+
|
22
|
+
delta(-1, 1) #=> 2
|
23
|
+
delta({count: -1}, {count: 1}){|item| item[:count]} #=> 2
|
24
|
+
```
|
25
|
+
|
26
|
+
## Copyright
|
27
|
+
|
28
|
+
*Functional Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
|
29
|
+
It is free software and may be redistributed under the terms specified in the LICENSE file.
|
30
|
+
|
31
|
+
## License
|
32
|
+
|
33
|
+
Released under the MIT license.
|
34
|
+
|
35
|
+
http://www.opensource.org/licenses/mit-license.php
|
36
|
+
|
37
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
38
|
+
> of this software and associated documentation files (the "Software"), to deal
|
39
|
+
> in the Software without restriction, including without limitation the rights
|
40
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
41
|
+
> copies of the Software, and to permit persons to whom the Software is
|
42
|
+
> furnished to do so, subject to the following conditions:
|
43
|
+
>
|
44
|
+
> The above copyright notice and this permission notice shall be included in
|
45
|
+
> all copies or substantial portions of the Software.
|
46
|
+
>
|
47
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
48
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
49
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
50
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
51
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
52
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
53
|
+
> THE SOFTWARE.
|
@@ -0,0 +1,405 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Functional
|
4
|
+
|
5
|
+
describe Agent do
|
6
|
+
|
7
|
+
subject { Agent.new(0) }
|
8
|
+
|
9
|
+
let(:observer) do
|
10
|
+
Class.new do
|
11
|
+
attr_reader :value
|
12
|
+
define_method(:update) do |time, value|
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
end.new
|
16
|
+
end
|
17
|
+
|
18
|
+
before(:each) do
|
19
|
+
$GLOBAL_THREAD_POOL = CachedThreadPool.new
|
20
|
+
end
|
21
|
+
|
22
|
+
context '#initialize' do
|
23
|
+
|
24
|
+
it 'sets the value to the given initial state' do
|
25
|
+
Agent.new(10).value.should eq 10
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets the timeout to the given value' do
|
29
|
+
Agent.new(0, 5).timeout.should eq 5
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'sets the timeout to the default when nil' do
|
33
|
+
Agent.new(0).timeout.should eq Agent::TIMEOUT
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'sets the length to zero' do
|
37
|
+
Agent.new(10).length.should eq 0
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'spawns the worker thread' do
|
41
|
+
$GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
|
42
|
+
Agent.new(0)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context '#rescue' do
|
47
|
+
|
48
|
+
it 'returns self when a block is given' do
|
49
|
+
a1 = subject
|
50
|
+
a2 = a1.rescue{}
|
51
|
+
a1.object_id.should eq a2.object_id
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'returns self when no block is given' do
|
55
|
+
a1 = subject
|
56
|
+
a2 = a1.rescue
|
57
|
+
a1.object_id.should eq a2.object_id
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'accepts an exception class as the first parameter' do
|
61
|
+
lambda {
|
62
|
+
subject.rescue(StandardError){}
|
63
|
+
}.should_not raise_error(ArgumentError)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'ignores rescuers without a block' do
|
67
|
+
subject.rescue
|
68
|
+
subject.instance_variable_get(:@rescuers).should be_empty
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context '#validate' do
|
73
|
+
|
74
|
+
it 'returns self when a block is given' do
|
75
|
+
a1 = subject
|
76
|
+
a2 = a1.validate{}
|
77
|
+
a1.object_id.should eq a2.object_id
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'returns self when no block is given' do
|
81
|
+
a1 = subject
|
82
|
+
a2 = a1.validate
|
83
|
+
a1.object_id.should eq a2.object_id
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'ignores validators without a block' do
|
87
|
+
subject.validate
|
88
|
+
subject.instance_variable_get(:@validator).should be_nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context '#post' do
|
93
|
+
|
94
|
+
it 'adds the given block to the queue' do
|
95
|
+
before = subject.length
|
96
|
+
subject.post{ nil }
|
97
|
+
subject.post{ nil }
|
98
|
+
subject.length.should eq before+2
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'does not add to the queue when no block is given' do
|
102
|
+
before = subject.length
|
103
|
+
subject.post
|
104
|
+
subject.post{ nil }
|
105
|
+
subject.length.should eq before+1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context '#length' do
|
110
|
+
|
111
|
+
it 'should be zero for a new agent' do
|
112
|
+
subject.length.should eq 0
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should increase by one for each #post' do
|
116
|
+
subject.post{ sleep }
|
117
|
+
subject.post{ sleep }
|
118
|
+
subject.post{ sleep }
|
119
|
+
subject.length.should eq 3
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should decrease by one each time a handler is run' do
|
123
|
+
subject.post{ nil }
|
124
|
+
subject.post{ sleep }
|
125
|
+
subject.post{ sleep }
|
126
|
+
sleep(0.1)
|
127
|
+
subject.length.should eq 1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'fulfillment' do
|
132
|
+
|
133
|
+
it 'process each block in the queue' do
|
134
|
+
@expected = []
|
135
|
+
subject.post{ @expected << 1 }
|
136
|
+
subject.post{ @expected << 2 }
|
137
|
+
subject.post{ @expected << 3 }
|
138
|
+
sleep(0.1)
|
139
|
+
@expected.should eq [1,2,3]
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'passes the current value to the handler' do
|
143
|
+
@expected = nil
|
144
|
+
Agent.new(10).post{|i| @expected = i }
|
145
|
+
sleep(0.1)
|
146
|
+
@expected.should eq 10
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'sets the value to the handler return value on success' do
|
150
|
+
subject.post{ 100 }
|
151
|
+
sleep(0.1)
|
152
|
+
subject.value.should eq 100
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'rejects the handler after timeout reached' do
|
156
|
+
agent = Agent.new(0, 0.1)
|
157
|
+
agent.post{ sleep(1); 10 }
|
158
|
+
agent.value.should eq 0
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'validation' do
|
163
|
+
|
164
|
+
it 'processes the validator when present' do
|
165
|
+
@expected = nil
|
166
|
+
subject.validate{ @expected = 10; true }
|
167
|
+
subject.post{ nil }
|
168
|
+
sleep(0.1)
|
169
|
+
@expected.should eq 10
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'passes the new value to the validator' do
|
173
|
+
@expected = nil
|
174
|
+
subject.validate{|v| @expected = v; true }
|
175
|
+
subject.post{ 10 }
|
176
|
+
sleep(0.1)
|
177
|
+
@expected.should eq 10
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'sets the new value when the validator returns true' do
|
181
|
+
agent = Agent.new(0).validate{ true }
|
182
|
+
agent.post{ 10 }
|
183
|
+
sleep(0.1)
|
184
|
+
agent.value.should eq 10
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'does not change the value when the validator returns false' do
|
188
|
+
agent = Agent.new(0).validate{ false }
|
189
|
+
agent.post{ 10 }
|
190
|
+
sleep(0.1)
|
191
|
+
agent.value.should eq 0
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'does not change the value when the validator raises an exception' do
|
195
|
+
agent = Agent.new(0).validate{ raise StandardError }
|
196
|
+
agent.post{ 10 }
|
197
|
+
sleep(0.1)
|
198
|
+
agent.value.should eq 0
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'rejection' do
|
203
|
+
|
204
|
+
it 'calls the first exception block with a matching class' do
|
205
|
+
@expected = nil
|
206
|
+
subject.
|
207
|
+
rescue(StandardError){|ex| @expected = 1 }.
|
208
|
+
rescue(StandardError){|ex| @expected = 2 }.
|
209
|
+
rescue(StandardError){|ex| @expected = 3 }
|
210
|
+
subject.post{ raise StandardError }
|
211
|
+
sleep(0.1)
|
212
|
+
@expected.should eq 1
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'matches all with a rescue with no class given' do
|
216
|
+
@expected = nil
|
217
|
+
subject.
|
218
|
+
rescue(LoadError){|ex| @expected = 1 }.
|
219
|
+
rescue{|ex| @expected = 2 }.
|
220
|
+
rescue(StandardError){|ex| @expected = 3 }
|
221
|
+
subject.post{ raise NoMethodError }
|
222
|
+
sleep(0.1)
|
223
|
+
@expected.should eq 2
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'searches associated rescue handlers in order' do
|
227
|
+
@expected = nil
|
228
|
+
subject.
|
229
|
+
rescue(ArgumentError){|ex| @expected = 1 }.
|
230
|
+
rescue(LoadError){|ex| @expected = 2 }.
|
231
|
+
rescue(Exception){|ex| @expected = 3 }
|
232
|
+
subject.post{ raise ArgumentError }
|
233
|
+
sleep(0.1)
|
234
|
+
@expected.should eq 1
|
235
|
+
|
236
|
+
@expected = nil
|
237
|
+
subject.
|
238
|
+
rescue(ArgumentError){|ex| @expected = 1 }.
|
239
|
+
rescue(LoadError){|ex| @expected = 2 }.
|
240
|
+
rescue(Exception){|ex| @expected = 3 }
|
241
|
+
subject.post{ raise LoadError }
|
242
|
+
sleep(0.1)
|
243
|
+
@expected.should eq 2
|
244
|
+
|
245
|
+
@expected = nil
|
246
|
+
subject.
|
247
|
+
rescue(ArgumentError){|ex| @expected = 1 }.
|
248
|
+
rescue(LoadError){|ex| @expected = 2 }.
|
249
|
+
rescue(Exception){|ex| @expected = 3 }
|
250
|
+
subject.post{ raise StandardError }
|
251
|
+
sleep(0.1)
|
252
|
+
@expected.should eq 3
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'passes the exception object to the matched block' do
|
256
|
+
@expected = nil
|
257
|
+
subject.
|
258
|
+
rescue(ArgumentError){|ex| @expected = ex }.
|
259
|
+
rescue(LoadError){|ex| @expected = ex }.
|
260
|
+
rescue(Exception){|ex| @expected = ex }
|
261
|
+
subject.post{ raise StandardError }
|
262
|
+
sleep(0.1)
|
263
|
+
@expected.should be_a(StandardError)
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'ignores rescuers without a block' do
|
267
|
+
@expected = nil
|
268
|
+
subject.
|
269
|
+
rescue(StandardError).
|
270
|
+
rescue(StandardError){|ex| @expected = ex }.
|
271
|
+
rescue(Exception){|ex| @expected = ex }
|
272
|
+
subject.post{ raise StandardError }
|
273
|
+
sleep(0.1)
|
274
|
+
@expected.should be_a(StandardError)
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'supresses the exception if no rescue matches' do
|
278
|
+
lambda {
|
279
|
+
subject.
|
280
|
+
rescue(ArgumentError){|ex| @expected = ex }.
|
281
|
+
rescue(StandardError){|ex| @expected = ex }.
|
282
|
+
rescue(Exception){|ex| @expected = ex }
|
283
|
+
subject.post{ raise StandardError }
|
284
|
+
sleep(0.1)
|
285
|
+
}.should_not raise_error
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'supresses exceptions thrown from rescue handlers' do
|
289
|
+
lambda {
|
290
|
+
subject.rescue(Exception){ raise StandardError }
|
291
|
+
subject.post{ raise ArgumentError }
|
292
|
+
sleep(0.1)
|
293
|
+
}.should_not raise_error(StandardError)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
context 'observation' do
|
298
|
+
|
299
|
+
it 'notifies all observers when the value changes' do
|
300
|
+
agent = Agent.new(0)
|
301
|
+
agent.add_observer(observer)
|
302
|
+
agent.post{ 10 }
|
303
|
+
sleep(0.1)
|
304
|
+
observer.value.should eq 10
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'does not notify observers when validation fails' do
|
308
|
+
agent = Agent.new(0)
|
309
|
+
agent.validate{ false }
|
310
|
+
agent.add_observer(observer)
|
311
|
+
agent.post{ 10 }
|
312
|
+
sleep(0.1)
|
313
|
+
observer.value.should be_nil
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'does not notify observers when the handler raises an exception' do
|
317
|
+
agent = Agent.new(0)
|
318
|
+
agent.add_observer(observer)
|
319
|
+
agent.post{ raise StandardError }
|
320
|
+
sleep(0.1)
|
321
|
+
observer.value.should be_nil
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'aliases' do
|
326
|
+
|
327
|
+
it 'aliases #deref for #value' do
|
328
|
+
Agent.new(10).deref.should eq 10
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'aliases #validates for :validate' do
|
332
|
+
@expected = nil
|
333
|
+
subject.validates{|v| @expected = v }
|
334
|
+
subject.post{ 10 }
|
335
|
+
sleep(0.1)
|
336
|
+
@expected.should eq 10
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'aliases #validate_with for :validate' do
|
340
|
+
@expected = nil
|
341
|
+
subject.validate_with{|v| @expected = v }
|
342
|
+
subject.post{ 10 }
|
343
|
+
sleep(0.1)
|
344
|
+
@expected.should eq 10
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'aliases #validates_with for :validate' do
|
348
|
+
@expected = nil
|
349
|
+
subject.validates_with{|v| @expected = v }
|
350
|
+
subject.post{ 10 }
|
351
|
+
sleep(0.1)
|
352
|
+
@expected.should eq 10
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'aliases #catch for #rescue' do
|
356
|
+
@expected = nil
|
357
|
+
subject.catch{ @expected = true }
|
358
|
+
subject.post{ raise StandardError }
|
359
|
+
sleep(0.1)
|
360
|
+
@expected.should be_true
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'aliases #on_error for #rescue' do
|
364
|
+
@expected = nil
|
365
|
+
subject.on_error{ @expected = true }
|
366
|
+
subject.post{ raise StandardError }
|
367
|
+
sleep(0.1)
|
368
|
+
@expected.should be_true
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'aliases #add_watch for #add_observer' do
|
372
|
+
agent = Agent.new(0)
|
373
|
+
agent.add_watch(observer)
|
374
|
+
agent.post{ 10 }
|
375
|
+
sleep(0.1)
|
376
|
+
observer.value.should eq 10
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'aliases #<< for Agent#post' do
|
380
|
+
subject << proc{ 100 }
|
381
|
+
sleep(0.1)
|
382
|
+
subject.value.should eq 100
|
383
|
+
|
384
|
+
subject << lambda{ 100 }
|
385
|
+
sleep(0.1)
|
386
|
+
subject.value.should eq 100
|
387
|
+
end
|
388
|
+
|
389
|
+
it 'aliases Kernel#agent for Agent.new' do
|
390
|
+
agent(10).should be_a(Agent)
|
391
|
+
end
|
392
|
+
|
393
|
+
it 'aliases Kernel#deref for #deref' do
|
394
|
+
deref(Agent.new(10)).should eq 10
|
395
|
+
deref(Agent.new(10), 10).should eq 10
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'aliases Kernel:post for Agent#post' do
|
399
|
+
post(subject){ 100 }
|
400
|
+
sleep(0.1)
|
401
|
+
subject.value.should eq 100
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|