object-channel 0.1.3
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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE.md +18 -0
- data/README.md +105 -0
- data/Rakefile +7 -0
- data/lib/object-channel.rb +228 -0
- data/lib/object-channel/version.rb +3 -0
- data/object-channel.gemspec +24 -0
- data/spec/object-channel_spec.rb +165 -0
- data/spec/spec_helper.rb +6 -0
- metadata +117 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2012 [Simply Measured](http://simplymeasured.com/)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
17
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
Object Channel
|
2
|
+
==============
|
3
|
+
|
4
|
+
In some testing scenarios (such as signal handling), it is helpful to be able to test
|
5
|
+
from outside a process; common methods for communicating between two ends of a fork
|
6
|
+
include pushing data to an intermediary, such as a database or filesystem, but both
|
7
|
+
have side-effects: additional dependencies (many of which have their own dependencies)
|
8
|
+
and/or the need for post-run cleanup.
|
9
|
+
|
10
|
+
To avoid this, I often use IO.pipe:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
reader,writer = IO.pipe
|
14
|
+
|
15
|
+
child_pid = fork do
|
16
|
+
reader.close
|
17
|
+
writer.write( "Hello, I'm #{Process.pid}" )
|
18
|
+
writer.close
|
19
|
+
end
|
20
|
+
writer.close
|
21
|
+
|
22
|
+
message_from_child = reader.read
|
23
|
+
reader.close
|
24
|
+
|
25
|
+
puts Process.pid # => 12338
|
26
|
+
puts child_pid # => 12345
|
27
|
+
puts message_from_child # => "Hello, I'm 12345"
|
28
|
+
```
|
29
|
+
|
30
|
+
But that has several drawbacks:
|
31
|
+
|
32
|
+
- `read` only works if all `write` ends of the pipe are
|
33
|
+
closed; keeping track of which is open and which is closed
|
34
|
+
is a pain, especially if you have to fork several layers deep.
|
35
|
+
- only a single message can be passed; multiple messages
|
36
|
+
adds complexity, and complicated test cases aren't a good
|
37
|
+
idea
|
38
|
+
- only strings can be passed; sure Objects can be serialized, but...
|
39
|
+
|
40
|
+
|
41
|
+
A Simpler Way
|
42
|
+
------------
|
43
|
+
|
44
|
+
Adding this kind of logic directly into your tests is a bad idea; it makes them
|
45
|
+
unnecessarily complicated, hard to read, and easy to get wrong. `ObjectChannel`
|
46
|
+
abstracts all that away so you can just test. Here's the same simple example, but
|
47
|
+
this time without all the pipe-closing:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
require 'object-channel'
|
51
|
+
|
52
|
+
p2c = ObjectChannel.fork do |c2p|
|
53
|
+
c2p.transmit( "Hello, I'm #{Process.pid}" )
|
54
|
+
end
|
55
|
+
|
56
|
+
puts Process.pid # => 12338
|
57
|
+
puts p2c.pid # => 12345
|
58
|
+
puts p2c.receive! # => "Hello, I'm 12345"
|
59
|
+
```
|
60
|
+
|
61
|
+
Features
|
62
|
+
--------
|
63
|
+
|
64
|
+
`ObjectChannel` provides the following features:
|
65
|
+
|
66
|
+
- Full-fledged objects, not just strings
|
67
|
+
- Bi-directional channel
|
68
|
+
- Blocking and non-blocking access
|
69
|
+
- block-invoke supported and fully optional
|
70
|
+
- No additional dependencies
|
71
|
+
- No cleanup necessary
|
72
|
+
|
73
|
+
Check out the [specs][] for examples.
|
74
|
+
|
75
|
+
License & Contributing
|
76
|
+
======================
|
77
|
+
|
78
|
+
Project is (c) 2012 [Simply Measured][simply-measured] and released under an
|
79
|
+
[MIT-style license][license]
|
80
|
+
|
81
|
+
* This project uses Vincent Driessen's [Git-Flow][git-flow] branching model.
|
82
|
+
* Check out the latest develop to make sure the feature hasn't been implemented
|
83
|
+
or the bug hasn't been fixed yet
|
84
|
+
* Check out the issue tracker to make sure someone already hasn't requested it
|
85
|
+
and/or contributed it
|
86
|
+
* Fork the project
|
87
|
+
* Start a feature/bugfix branch
|
88
|
+
* Commit and push until you are happy with your contribution
|
89
|
+
* Make sure to add tests for it. This is important so I don't break it in a
|
90
|
+
future version unintentionally.
|
91
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to
|
92
|
+
have your own version, or is otherwise necessary, that is fine, but please
|
93
|
+
isolate to its own commit so I can cherry-pick around it.
|
94
|
+
|
95
|
+
|
96
|
+
<!-- INTERNAL LINKS -->
|
97
|
+
|
98
|
+
[license]: https://github.com/simplymeasured/object-channel/blob/master/LICENSE.md
|
99
|
+
[specs]: https://github.com/simplymeasured/object-channel/blob/master/spec/
|
100
|
+
|
101
|
+
<!-- EXTERNAL LINKS -->
|
102
|
+
|
103
|
+
[simply-measured]: http://simplymeasured.com/
|
104
|
+
[git-flow]: http://nvie.com/posts/a-successful-git-branching-model/
|
105
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
# require "object-channel/version"
|
2
|
+
|
3
|
+
class ObjectChannel
|
4
|
+
class << self
|
5
|
+
##
|
6
|
+
# Create object channels endpoints, each connected by an object channel.
|
7
|
+
#
|
8
|
+
def create(endpoints=2)
|
9
|
+
endpoints.times.to_a.map do |index|
|
10
|
+
pipe = IO.pipe
|
11
|
+
{:index=>index, :pipe=>pipe}
|
12
|
+
end.permutation(2).to_a.inject(Hash.new{|h,k|h[k]=Hash.new}) do |channels,pipes|
|
13
|
+
from = pipes[0]
|
14
|
+
to = pipes[1]
|
15
|
+
channels[from[:index]][to[:index]] = ObjectChannel.new( from[:pipe], to[:pipe] )
|
16
|
+
channels
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# :nodoc:
|
21
|
+
def new(receive_pipe,transmit_pipe)
|
22
|
+
object_channel = allocate
|
23
|
+
[
|
24
|
+
:@receive_reader,
|
25
|
+
:@receive_writer,
|
26
|
+
:@transmit_reader,
|
27
|
+
:@transmit_writer
|
28
|
+
].zip([receive_pipe,transmit_pipe].flatten(1)) do |name,ios|
|
29
|
+
object_channel.instance_variable_set(name,ios)
|
30
|
+
end
|
31
|
+
object_channel
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Creates a subprocess with a bi-directional ObjectChannel
|
36
|
+
# available to both processes, behaving otherwise like Process.fork.
|
37
|
+
#
|
38
|
+
# If a block is specified, the block is run in the subprocess with
|
39
|
+
# the ObjectChannel::Pair to its parent as its only argument. Like
|
40
|
+
# Process.fork(), the subprocess terminates with a status of zero.
|
41
|
+
#
|
42
|
+
# Otherwise, the fork call returns twice, once in the parent, returning
|
43
|
+
# the ObjectChannel to its child whose pid method is the process ID
|
44
|
+
# of that child, and once in the child, returning the ObjectChannel to
|
45
|
+
# the parent, whose pid method returns nil.
|
46
|
+
#
|
47
|
+
# :call-seq:
|
48
|
+
# ObjectChannel.fork -> ObjectChannel x 2
|
49
|
+
# ObjectChannel.fork{|channel|} -> ObjectChannel
|
50
|
+
#
|
51
|
+
# = Example (Block)
|
52
|
+
#
|
53
|
+
# p2c = ObjectChannel.fork do |c2p|
|
54
|
+
# c2p.transmit :foo => :bar
|
55
|
+
# puts "child received <#{p2c.receive!.inspect}>" #=> [1, 2, 3]
|
56
|
+
# end
|
57
|
+
# puts "parent received <#{p2c.receive!.inspect}>" #=> {:foo=>:bar}
|
58
|
+
# channel.transmit [1,2,3]
|
59
|
+
#
|
60
|
+
# = Example (No Block)
|
61
|
+
#
|
62
|
+
# if (channel = ObjectChannel.fork).pid
|
63
|
+
# channel.transmit :foo => :bar
|
64
|
+
# puts "child received <#{channel.receive!.inspect}>" #=> [1, 2, 3]
|
65
|
+
# else
|
66
|
+
# puts "received <#{channel.receive!.inspect}>" #=> {:foo=>:bar}
|
67
|
+
# channel.transmit [1,2,3]
|
68
|
+
# exit
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
def fork name=nil, &block
|
72
|
+
channels = create(2)
|
73
|
+
channel_a = channels[0][1]
|
74
|
+
channel_b = channels[1][0]
|
75
|
+
|
76
|
+
if block_given?
|
77
|
+
ret = fork name
|
78
|
+
if ret.nil? or (ret.respond_to?(:pid) and ret.pid.nil?)
|
79
|
+
begin
|
80
|
+
block.call(*(ret.respond_to?(:pid) ? [ret] : [] ) )
|
81
|
+
ensure
|
82
|
+
exit!
|
83
|
+
end
|
84
|
+
end
|
85
|
+
return ret
|
86
|
+
else
|
87
|
+
pid = forker.fork
|
88
|
+
channel = ( pid.nil? ? channel_a : channel_b)
|
89
|
+
if name.nil?
|
90
|
+
channel.instance_variable_set(:@pid, pid)
|
91
|
+
channel
|
92
|
+
else
|
93
|
+
Object.const_set name, channel
|
94
|
+
pid
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Allows the object that forks to be replaced, helpful for adding additional
|
101
|
+
# functionality in something of a daisy-chain.
|
102
|
+
#
|
103
|
+
# The forker's fork method *must* be API-compatible with Kernel.fork:
|
104
|
+
# - It *must* work with and without blocks provided
|
105
|
+
# - It *must* return an integer pid to the parent and nil to the child
|
106
|
+
# - It *must not* require any arguments, as args are swallowed in ObjectChannel.fork()
|
107
|
+
#
|
108
|
+
attr_writer :forker
|
109
|
+
def forker
|
110
|
+
(@forker || ::Kernel)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
attr_reader :pid
|
115
|
+
|
116
|
+
##
|
117
|
+
# Transmit an object
|
118
|
+
#
|
119
|
+
def transmit(object)
|
120
|
+
close(:@transmit_reader)
|
121
|
+
@transmit_writer.write [Marshal.dump(object)].pack('m').gsub("\n",'') + "\n"
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Receive the next object in a blocking manner
|
126
|
+
#
|
127
|
+
def receive!
|
128
|
+
close(:@receive_writer)
|
129
|
+
begin
|
130
|
+
r = @receive_reader.readline
|
131
|
+
Marshal.load(r.unpack("m")[0])
|
132
|
+
rescue EOFError
|
133
|
+
return nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Receive the next object in a non-blocking manner
|
139
|
+
# Returns nil if no object was present
|
140
|
+
def receive
|
141
|
+
receive! if ready?
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Test to see if there is anything to receive
|
146
|
+
#
|
147
|
+
def ready?
|
148
|
+
c = Thread.start{@receive_reader.getc}
|
149
|
+
Thread.pass
|
150
|
+
if c.status == false and c.value
|
151
|
+
@receive_reader.ungetc(c.value)
|
152
|
+
return true
|
153
|
+
else
|
154
|
+
c.kill
|
155
|
+
return false
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Test to see if the receive_reader is at eof in a non-blocking manner
|
161
|
+
#
|
162
|
+
def closed?
|
163
|
+
c = Thread.start{@receive_reader.eof?}
|
164
|
+
Thread.pass
|
165
|
+
if c.status == false and c.value
|
166
|
+
return true
|
167
|
+
else
|
168
|
+
c.kill
|
169
|
+
return false
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Close all ends of all IOS.
|
175
|
+
#
|
176
|
+
def close!
|
177
|
+
[
|
178
|
+
:@receive_reader,
|
179
|
+
:@receive_writer,
|
180
|
+
:@transmit_reader,
|
181
|
+
:@transmit_writer
|
182
|
+
].each &method(:close)
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Close an IO by name
|
187
|
+
#
|
188
|
+
def close(ios_name)
|
189
|
+
ios = instance_variable_get(ios_name)
|
190
|
+
ios.close unless ios.closed?
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# A Bonus Example:
|
196
|
+
#
|
197
|
+
# a proc that both call
|
198
|
+
# pingpong = Proc.new do |channel, action|
|
199
|
+
# srand(Process.pid)
|
200
|
+
# def report(message) puts %Q{#{Process.pid}: #{message}}; end
|
201
|
+
# begin
|
202
|
+
# break (report 'VICTORY!') if channel.closed? or !(received = channel.receive!)
|
203
|
+
# report "got #{received}"
|
204
|
+
|
205
|
+
# break (report 'aw, man!') if Kernel.rand(99) > 61
|
206
|
+
# report "returning <#{action}>"
|
207
|
+
# channel.transmit(action)
|
208
|
+
# end while true
|
209
|
+
# channel.close!
|
210
|
+
# end
|
211
|
+
|
212
|
+
# # without a block
|
213
|
+
# if (channel = ObjectChannel.fork).pid
|
214
|
+
# pingpong.call(channel, 'ping!')
|
215
|
+
# Process.wait(channel.pid)
|
216
|
+
# else
|
217
|
+
# channel.transmit('serve!')
|
218
|
+
# pingpong.call(channel, 'pong.')
|
219
|
+
# exit
|
220
|
+
# end
|
221
|
+
|
222
|
+
# # with a block
|
223
|
+
# channel = ObjectChannel.fork do |channel|
|
224
|
+
# pingpong.call(channel,'ping!')
|
225
|
+
# end
|
226
|
+
# channel.transmit 'SERVICE!'
|
227
|
+
# pingpong.call(channel,'pong.')
|
228
|
+
# Process.wait(channel.pid)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "object-channel/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "object-channel"
|
7
|
+
s.version = ObjectChannel::VERSION
|
8
|
+
s.authors = ["Ryan Biesemeyer"]
|
9
|
+
s.email = ["ryan@yaauie.com"]
|
10
|
+
s.homepage = "https://github.com/simplymeasured/object-channel"
|
11
|
+
s.summary = %q{A library for sending objects over pipes, with convenience methods for forking}
|
12
|
+
s.description = %q{Wraps around Process.fork(), providing parent and child each an ObjectChannel to transmit objects to and receive objects from the other.}
|
13
|
+
|
14
|
+
#s.rubyforge_project = "object-channel"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency 'rspec'
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'bundler'
|
24
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe 'ObjectChannel' do
|
4
|
+
module CustomForker
|
5
|
+
extend self
|
6
|
+
def fork(*a,&b)
|
7
|
+
$FORKER = "#{self.to_s}.fork"
|
8
|
+
::Kernel.fork(*a,&b)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
[nil,CustomForker].each do |forker|
|
13
|
+
before(:each) do
|
14
|
+
ObjectChannel.forker = forker
|
15
|
+
end
|
16
|
+
after(:each) do
|
17
|
+
ObjectChannel.forker = forker
|
18
|
+
$FORKER = nil
|
19
|
+
end
|
20
|
+
describe '#fork' do
|
21
|
+
it 'should be able to send an object for processing' do
|
22
|
+
channel = ObjectChannel.fork do |channel|
|
23
|
+
received = channel.receive!
|
24
|
+
channel.transmit( received + 1 )
|
25
|
+
end
|
26
|
+
channel.transmit 100
|
27
|
+
channel.receive!.should == 101
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should actually happen on a separate process' do
|
31
|
+
def prepend_pid(s); "#{Process.pid}: #{s}"; end
|
32
|
+
channel = ObjectChannel.fork do |channel|
|
33
|
+
received = channel.receive!
|
34
|
+
channel.transmit prepend_pid(received)
|
35
|
+
end
|
36
|
+
channel.transmit 'foobar'
|
37
|
+
channel.receive!.should_not == prepend_pid('foobar')
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'with constantized channels' do
|
41
|
+
after(:each){ Object.send(:remove_const, :NamedChannel) }
|
42
|
+
it 'should be able to use the constantized channels' do
|
43
|
+
ObjectChannel.fork :NamedChannel do
|
44
|
+
received = NamedChannel.receive!
|
45
|
+
NamedChannel.transmit received.reverse
|
46
|
+
end
|
47
|
+
NamedChannel.transmit 'foobar'
|
48
|
+
NamedChannel.receive!.should == 'raboof'
|
49
|
+
NamedChannel.close!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'block-invoke' do
|
54
|
+
after(:each){ Object.send(:remove_const, :OuterChannel) }
|
55
|
+
# OK, I have to do some tricky magic here.
|
56
|
+
# If you know of a better way, by all means, reimplement.
|
57
|
+
it 'should invoke exit! when done' do
|
58
|
+
if (ObjectChannel.fork :OuterChannel)
|
59
|
+
begin
|
60
|
+
ObjectChannel.fork :InnerChannel do
|
61
|
+
InnerChannel.receive!
|
62
|
+
end
|
63
|
+
InnerChannel.transmit 'foo' #get the party started
|
64
|
+
rescue SystemExit => exception
|
65
|
+
OuterChannel.transmit exception
|
66
|
+
raise exception
|
67
|
+
end
|
68
|
+
exit! 0
|
69
|
+
else
|
70
|
+
output = OuterChannel.receive!
|
71
|
+
output.should be_nil
|
72
|
+
output.inspect.should_not == '#<SystemExit: exit>'
|
73
|
+
OuterChannel.receive!.should be_nil # it shouldn't invoke exit twice!
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should run the block in the child' do
|
78
|
+
cached_pid = Process.pid
|
79
|
+
ObjectChannel.fork(:OuterChannel){exit!}
|
80
|
+
Process.pid.should == cached_pid
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
unless forker.nil?
|
85
|
+
context 'with a custom forker' do
|
86
|
+
context 'ObjectChannel.forker' do
|
87
|
+
subject{ ObjectChannel.forker }
|
88
|
+
it{ should_not be_nil }
|
89
|
+
end
|
90
|
+
context 'when run' do
|
91
|
+
before(:each) do
|
92
|
+
ObjectChannel.fork(:NamedChannel){ $RAN=true }
|
93
|
+
end
|
94
|
+
after(:each){ Object.send(:remove_const, :NamedChannel) }
|
95
|
+
subject{ $FORKER }
|
96
|
+
it 'should have run in the custom forker' do
|
97
|
+
should == "#{forker.to_s}.fork"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# Bonus example:
|
108
|
+
# An Isolater. It'll run the supplied block in a child process,
|
109
|
+
# then appropriately return or raise the result in the parent process.
|
110
|
+
# Note: It does not currently support catch/throw, so it's possible to bypass.
|
111
|
+
describe 'isolate' do
|
112
|
+
module Isolater
|
113
|
+
extend self
|
114
|
+
def isolate( &block )
|
115
|
+
p2c = ObjectChannel.fork do |c2p|
|
116
|
+
begin
|
117
|
+
c2p.transmit( [:returned, block.call] )
|
118
|
+
rescue Object => obj_raised
|
119
|
+
c2p.transmit( [:raised, obj_raised] )
|
120
|
+
end
|
121
|
+
end
|
122
|
+
verb, value = p2c.receive!
|
123
|
+
|
124
|
+
raise value if verb == :raised
|
125
|
+
return value if verb == :returned
|
126
|
+
|
127
|
+
raise RuntimeError, 'expected something to be returned or raised; got <#{verb},#{value}>'
|
128
|
+
end
|
129
|
+
def dirty?
|
130
|
+
!!(@dirty)
|
131
|
+
end
|
132
|
+
attr_writer :dirty
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should be isolated' do
|
136
|
+
Isolater.dirty = false
|
137
|
+
Isolater.isolate do
|
138
|
+
Isolater.dirty = true
|
139
|
+
end
|
140
|
+
Isolater.dirty?.should be_false
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when block returns' do
|
144
|
+
subject do
|
145
|
+
lambda{ return { :k=>'value', :int=>4 } }
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should not raise' do
|
149
|
+
expect{ Isolater.isolate{ subject.call } }.to_not raise_exception
|
150
|
+
end
|
151
|
+
it 'should return the correct value' do
|
152
|
+
subject.call.should == { :k=>'value', :int=>4 }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'when block raises' do
|
157
|
+
subject do
|
158
|
+
lambda{ $GLOBAL_VALUE = true; 5 / 0 }
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should raise' do
|
162
|
+
expect{ Isolater.isolate{ subject.call } }.to raise_exception(ZeroDivisionError)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: object-channel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 3
|
10
|
+
version: 0.1.3
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ryan Biesemeyer
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-03-19 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rake
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: bundler
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
description: Wraps around Process.fork(), providing parent and child each an ObjectChannel to transmit objects to and receive objects from the other.
|
63
|
+
email:
|
64
|
+
- ryan@yaauie.com
|
65
|
+
executables: []
|
66
|
+
|
67
|
+
extensions: []
|
68
|
+
|
69
|
+
extra_rdoc_files: []
|
70
|
+
|
71
|
+
files:
|
72
|
+
- .gitignore
|
73
|
+
- Gemfile
|
74
|
+
- LICENSE.md
|
75
|
+
- README.md
|
76
|
+
- Rakefile
|
77
|
+
- lib/object-channel.rb
|
78
|
+
- lib/object-channel/version.rb
|
79
|
+
- object-channel.gemspec
|
80
|
+
- spec/object-channel_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
homepage: https://github.com/simplymeasured/object-channel
|
83
|
+
licenses: []
|
84
|
+
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
hash: 3
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
requirements: []
|
109
|
+
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.8.10
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: A library for sending objects over pipes, with convenience methods for forking
|
115
|
+
test_files:
|
116
|
+
- spec/object-channel_spec.rb
|
117
|
+
- spec/spec_helper.rb
|