oleganza-emrpc 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README +129 -0
  3. data/Rakefile +156 -0
  4. data/TODO +47 -0
  5. data/bin/emrpc +4 -0
  6. data/lib/emrpc.rb +15 -0
  7. data/lib/emrpc/archive/reference_savior.rb +48 -0
  8. data/lib/emrpc/archive/ring.rb +44 -0
  9. data/lib/emrpc/blocking_api.rb +3 -0
  10. data/lib/emrpc/blocking_api/method_proxy.rb +57 -0
  11. data/lib/emrpc/blocking_api/multithreaded_client.rb +52 -0
  12. data/lib/emrpc/blocking_api/singlethreaded_client.rb +68 -0
  13. data/lib/emrpc/client.rb +28 -0
  14. data/lib/emrpc/console.rb +32 -0
  15. data/lib/emrpc/evented_api.rb +14 -0
  16. data/lib/emrpc/evented_api/connection_mixin.rb +14 -0
  17. data/lib/emrpc/evented_api/debug_connection.rb +52 -0
  18. data/lib/emrpc/evented_api/debug_pid_callbacks.rb +39 -0
  19. data/lib/emrpc/evented_api/default_callbacks.rb +40 -0
  20. data/lib/emrpc/evented_api/evented_wrapper.rb +28 -0
  21. data/lib/emrpc/evented_api/local_connection.rb +48 -0
  22. data/lib/emrpc/evented_api/pid.rb +198 -0
  23. data/lib/emrpc/evented_api/protocol_mapper.rb +57 -0
  24. data/lib/emrpc/evented_api/reconnecting_pid.rb +105 -0
  25. data/lib/emrpc/evented_api/remote_connection.rb +73 -0
  26. data/lib/emrpc/evented_api/remote_pid.rb +38 -0
  27. data/lib/emrpc/evented_api/subscribable.rb +56 -0
  28. data/lib/emrpc/evented_api/timer.rb +23 -0
  29. data/lib/emrpc/protocols.rb +2 -0
  30. data/lib/emrpc/protocols/fast_message_protocol.rb +99 -0
  31. data/lib/emrpc/protocols/marshal_protocol.rb +33 -0
  32. data/lib/emrpc/server.rb +17 -0
  33. data/lib/emrpc/util.rb +7 -0
  34. data/lib/emrpc/util/blank_slate.rb +25 -0
  35. data/lib/emrpc/util/codec.rb +114 -0
  36. data/lib/emrpc/util/combine_modules.rb +11 -0
  37. data/lib/emrpc/util/em2rev.rb +48 -0
  38. data/lib/emrpc/util/em_start_stop_timeouts.rb +62 -0
  39. data/lib/emrpc/util/parsed_uri.rb +15 -0
  40. data/lib/emrpc/util/safe_run.rb +23 -0
  41. data/lib/emrpc/util/timers.rb +17 -0
  42. data/lib/emrpc/version.rb +3 -0
  43. data/spec/blocking_api/method_proxy_spec.rb +33 -0
  44. data/spec/blocking_api/multithreaded_client_spec.rb +52 -0
  45. data/spec/blocking_api/scenario_spec.rb +35 -0
  46. data/spec/blocking_api/singlethreaded_client_spec.rb +63 -0
  47. data/spec/blocking_api/spec_helper.rb +1 -0
  48. data/spec/blocking_api_test.rb +98 -0
  49. data/spec/evented_api/connection_mixin_spec.rb +34 -0
  50. data/spec/evented_api/default_callbacks_spec.rb +26 -0
  51. data/spec/evented_api/evented_wrapper_spec.rb +50 -0
  52. data/spec/evented_api/pid_spec.rb +194 -0
  53. data/spec/evented_api/reconnecting_pid_spec.rb +76 -0
  54. data/spec/evented_api/remote_connection_spec.rb +147 -0
  55. data/spec/evented_api/remote_pid_spec.rb +84 -0
  56. data/spec/evented_api/scenario_spec.rb +138 -0
  57. data/spec/evented_api/spec_helper.rb +10 -0
  58. data/spec/evented_api/subscribable_spec.rb +53 -0
  59. data/spec/server_spec.rb +7 -0
  60. data/spec/spec_helper.rb +96 -0
  61. data/spec/util/blank_slate_spec.rb +7 -0
  62. data/spec/util/codec_spec.rb +183 -0
  63. data/spec/util/fast_message_protocol_spec.rb +60 -0
  64. data/spec/util/marshal_protocol_spec.rb +50 -0
  65. data/spec/util/parsed_uri_spec.rb +19 -0
  66. data/spec/util/spec_helper.rb +1 -0
  67. metadata +164 -0
@@ -0,0 +1,84 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe RemotePid do
4
+
5
+ before(:each) do
6
+ @uuid = "my-uuid"
7
+ @local_pid = mock('pid', :uuid => "local-uuid", :_uid => "short")
8
+ @connection = mock('Protocol', :send_raw_message => nil,
9
+ :address => ::URI.parse(em_addr),
10
+ :local_pid => @local_pid)
11
+
12
+ @options = {:uuid => @uuid}
13
+ @cls = RemotePid
14
+ @rpid = @cls.new(@connection, @options)
15
+ end
16
+
17
+ describe "inspectable", :shared => true do
18
+ it "should be inspectable" do
19
+ lambda{ @rpid.inspect }.should_not raise_error
20
+ end
21
+ end
22
+
23
+ describe "#initialize" do
24
+ it "should set connection" do
25
+ @rpid._connection.should == @connection
26
+ end
27
+
28
+ it "should set uuid" do
29
+ @rpid.uuid.should == @uuid
30
+ end
31
+
32
+ it "should not be killed" do
33
+ @rpid.should_not be_killed
34
+ end
35
+
36
+ it_should_behave_like "inspectable"
37
+
38
+ it "should have connection description in inspect" do
39
+ @rpid.inspect.should =~ /on \S+ connected with local pid \S+/
40
+ end
41
+
42
+ end
43
+
44
+
45
+ describe "#kill" do
46
+ before(:each) do
47
+ @connection.should_receive(:send_raw_message).once.with([:kill])
48
+ @connection.should_receive(:close_connection_after_writing).once
49
+ @rpid.kill
50
+ end
51
+
52
+ it "should be killed" do
53
+ @rpid.should be_killed
54
+ end
55
+
56
+ it "should ignore more kill calls" do
57
+ @rpid.kill
58
+ @rpid.kill
59
+ end
60
+
61
+ it_should_behave_like "inspectable"
62
+
63
+ it "should have 'KILLED' in inspect" do
64
+ @rpid.inspect.should =~ /KILLED/
65
+ end
66
+
67
+ end
68
+
69
+
70
+ describe "method call proxying" do
71
+ before(:each) do
72
+ @connection.should_receive(:send_raw_message).once.with([:meth, :arg1, :arg2])
73
+ end
74
+
75
+ it "should allow direct call" do
76
+ @rpid.meth(:arg1, :arg2)
77
+ end
78
+
79
+ it "should allow indirect call with send()" do
80
+ @rpid.send(:meth, :arg1, :arg2)
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,138 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Chat" do
4
+
5
+ before(:all) do
6
+
7
+ class Chatterer < Fixtures::Person
8
+ include Pid
9
+ attr_accessor :chat, :notices, :log
10
+ def initialize(*args)
11
+ super(*args)
12
+ @notices = []
13
+ @log = []
14
+ end
15
+ def connected(pid)
16
+ @chat = pid
17
+ end
18
+ def notice(msg)
19
+ @notices << msg
20
+ end
21
+ def receive(from, msg)
22
+ @log << [from, msg]
23
+ end
24
+ def write(msg)
25
+ @log << [@name, msg]
26
+ @chat.write(self, @name, msg)
27
+ end
28
+ def pid_class_name
29
+ self.class.name
30
+ end
31
+ end
32
+
33
+ class Chat
34
+ include Pid
35
+ def initialize
36
+ super
37
+ @chatterers = []
38
+ end
39
+ def connected(pid)
40
+ @chatterers << pid
41
+ end
42
+ def disconnected(pid)
43
+ @chatterers.delete(pid)
44
+ broadcast(:notice, self, "Chatterer #{pid.uuid} disconnected!")
45
+ end
46
+ def write(from, name, text)
47
+ broadcast(:receive, name, text) {|c| c != from }
48
+ end
49
+ def broadcast(msg, *args, &blk)
50
+ cs = @chatterers
51
+ cs = cs.select(&blk) if blk
52
+ cs.each do |c|
53
+ c.send(msg, *args)
54
+ end
55
+ end
56
+ def pid_class_name
57
+ self.class.name
58
+ end
59
+ end
60
+
61
+ class Oleg < Chatterer
62
+ def initialize
63
+ super(:name => "oleg")
64
+ end
65
+ def receive(from, text)
66
+ super
67
+ case text
68
+ when /hello!/i
69
+ write "How are you?"
70
+ when /fine/i
71
+ write "Ok then! Cya!"
72
+ when /ciao/i
73
+ else
74
+ raise "I don't understand you"
75
+ end
76
+ end
77
+ def connected(pid)
78
+ super
79
+ write("Hi!")
80
+ end
81
+ end
82
+
83
+ class Olga < Chatterer
84
+ def initialize
85
+ super(:name => "olga")
86
+ end
87
+ def receive(from, text)
88
+ super
89
+ case text
90
+ when /hi!/i
91
+ write "Hello!"
92
+ when /how are you/i
93
+ write "Fine, thanks!"
94
+ when /cya!/i
95
+ write "Ciao!"
96
+ else
97
+ raise "I don't understand you"
98
+ end
99
+ end
100
+ end
101
+
102
+ @oleg = Oleg.new
103
+ @olga = Olga.new
104
+
105
+ @oleg.method(:marshal_dump)
106
+
107
+ @chat_addr = em_addr
108
+ @chat = Chat.new
109
+
110
+ @chat.bind(@chat_addr)
111
+
112
+ lambda { Marshal.dump(@oleg) }.should_not raise_error
113
+ lambda { Marshal.dump(@olga) }.should_not raise_error
114
+
115
+ Thread.new do
116
+ @oleg.connect(@chat_addr)
117
+ @olga.connect(@chat_addr)
118
+ end
119
+ end
120
+
121
+ it "should produce a conversation" do
122
+ sleep 0.5
123
+
124
+ @oleg.log.should == [
125
+ ["oleg", "Hi!"],
126
+ ["olga", "Hello!"],
127
+ ["oleg", "How are you?"],
128
+ ["olga", "Fine, thanks!"],
129
+ ["oleg", "Ok then! Cya!"],
130
+ ["olga", "Ciao!"]
131
+ ]
132
+
133
+ @oleg.log.should == @olga.log
134
+ end
135
+
136
+
137
+
138
+ end
@@ -0,0 +1,10 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ module MockDescribe
4
+ def mock_describe(title, scope = :each, &blk)
5
+ describe(title) do
6
+ before(scope, &blk)
7
+ it("should verify mocks expectations") { }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Subscribable do
4
+ extend MockDescribe
5
+
6
+ before(:all) do
7
+ @cls = Class.new { include Subscribable }
8
+ @cafe = @cls.new
9
+ end
10
+
11
+ def client_mock(name)
12
+ m = mock(name)
13
+ m.stub!(:send)
14
+ m
15
+ end
16
+
17
+ mock_describe "subscribed client" do
18
+ oleg = client_mock("Oleg in cafe")
19
+ oleg.should_receive(:send).once.with(:on_open, :arg).ordered
20
+ oleg.should_receive(:send).once.with(:on_close).ordered
21
+ andrey = client_mock("Andrey in cafe")
22
+ andrey.should_receive(:send).once.with(:on_close).ordered
23
+
24
+ @cafe.subscribe(:open, oleg, :on_open)
25
+ @cafe.subscribe(:close, oleg, :on_close)
26
+ @cafe.subscribe(:close, andrey, :on_close)
27
+
28
+ @cafe.notify_subscribers(:open, :arg)
29
+ @cafe.notify_subscribers(:close)
30
+ end
31
+
32
+ mock_describe "unsubscribed client from event" do
33
+ oleg = client_mock("Oleg in cafe")
34
+ oleg.should_not_receive(:send).with(:on_open, :arg)
35
+ oleg.should_receive(:send).with(:on_close).ordered
36
+ andrey = client_mock("Andrey in cafe")
37
+ andrey.should_receive(:send).with(:on_close).ordered
38
+
39
+ @cafe.subscribe(:open, oleg, :on_open)
40
+ @cafe.subscribe(:close, oleg, :on_close)
41
+ @cafe.subscribe(:close, andrey, :on_close)
42
+
43
+ @cafe.unsubscribe(:open, oleg)
44
+
45
+ @cafe.notify_subscribers(:open, :arg)
46
+ @cafe.notify_subscribers(:close)
47
+ end
48
+
49
+
50
+ %{
51
+ TODO: more scenarios with special DSL
52
+ }
53
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Server do
4
+ it "should be covered with some scenario-like specs" do
5
+ pending
6
+ end
7
+ end
@@ -0,0 +1,96 @@
1
+ $LOAD_PATH.unshift( File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) )
2
+
3
+ require 'emrpc'
4
+ include EMRPC
5
+
6
+ require 'socket'
7
+
8
+ module AddressHelpers
9
+ def em_host(host = ENV['EM_HOST'])
10
+ host || '127.0.0.1'
11
+ end
12
+
13
+ def em_port(port = ENV['EM_PORT'])
14
+ (port || 45_678).to_i
15
+ end
16
+
17
+ def em_random_port
18
+ rand(10_000) + 40_000
19
+ end
20
+
21
+ def em_proto(proto = ENV['EM_PROTO'])
22
+ proto || 'emrpc'
23
+ end
24
+
25
+ def em_addr(proto = em_proto, host = em_host, port = em_port)
26
+ "#{proto}://#{host}:#{port}"
27
+ end
28
+ end
29
+
30
+ class Object
31
+ include AddressHelpers
32
+ end
33
+
34
+ module Fixtures
35
+ class Person
36
+ attr_accessor :name
37
+ def initialize(options = {})
38
+ @name = options[:name]
39
+ end
40
+ end
41
+
42
+ class Paris
43
+ attr_accessor :people, :options
44
+
45
+ def initialize(ppl = 2, options = {})
46
+ @options = options
47
+ @people = Array.new(ppl){ Person.new }
48
+ @name = "Paris"
49
+ end
50
+
51
+ def translate(english_word)
52
+ "le #{english_word}" # :-)
53
+ end
54
+
55
+ def visit(person)
56
+ people << person
57
+ people.size
58
+ end
59
+
60
+ def run_slow(time = 2)
61
+ sleep(time)
62
+ end
63
+
64
+ def run_exception
65
+ raise SomeException, "paris message"
66
+ end
67
+ class SomeException < Exception; end
68
+ end
69
+ end
70
+
71
+ module ThreadHelpers
72
+ def create_threads(n, abort_on_exception = false, &blk)
73
+ Array.new(n) do
74
+ t = Thread.new(&blk)
75
+ t.abort_on_exception = abort_on_exception
76
+ t
77
+ end
78
+ end
79
+ end
80
+
81
+ # Runs eventmachine reactor in a child thread,
82
+ # waits 0.5 sec. in a current thread.
83
+ # Returns child thread.
84
+ #
85
+ module EventMachine
86
+ def self.run_in_thread(delay = 0.5, &blk)
87
+ blk = proc{} unless blk
88
+ t = Thread.new do
89
+ EventMachine.run(&blk)
90
+ end
91
+ sleep delay
92
+ t
93
+ end
94
+ end
95
+
96
+ $em_thread ||= EventMachine.run_in_thread
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe EMRPC::BlankSlate do
4
+ it "should have only __id__ and __send__ instance methods" do
5
+ EMRPC::BlankSlate.instance_methods.sort.should == %w[__id__ __send__]
6
+ end
7
+ end
@@ -0,0 +1,183 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Codec with" do
4
+
5
+ before(:each) do
6
+ @encode_method = :encode_b381b571_1ab2_5889_8221_855dbbc76242
7
+ @decode_method = :decode_b381b571_1ab2_5889_8221_855dbbc76242
8
+ @dummy_pid = mock("dummy pid", :uuid => "dummy-uuid")
9
+ @host_pid = mock("host pid", :find_pid => @dummy_pid)
10
+ end
11
+
12
+ describe Object do
13
+ before(:each) do
14
+ @obj = Object.new
15
+ end
16
+
17
+ it "should return self" do
18
+ @obj.encode_b381b571_1ab2_5889_8221_855dbbc76242(@host_pid).should eql(@obj)
19
+ @obj.decode_b381b571_1ab2_5889_8221_855dbbc76242(@host_pid).should eql(@obj)
20
+ end
21
+ end
22
+
23
+ describe PidVariables do
24
+ before(:each) do
25
+ ::MyClass = Class.new do
26
+ include PidVariables
27
+ attr_accessor :name, :pid
28
+ end
29
+ @pid = Pid.new
30
+ @obj = ::MyClass.new
31
+ @obj.name = "Oleg"
32
+ @obj.pid = @pid
33
+ @encoded = @obj.encode_b381b571_1ab2_5889_8221_855dbbc76242(@host_pid)
34
+ end
35
+
36
+ after(:each) do
37
+ Object.send(:remove_const, :MyClass) if defined?(::MyClass)
38
+ end
39
+
40
+ it "should encode ivars" do
41
+ encoded = @encoded
42
+ encoded.should be_kind_of(PidVariables::Container)
43
+ encoded.cls.should == @obj.class
44
+ name_pair, pid_pair = encoded.ivars.sort_by{|a|a[0]}
45
+ name_pair.should == ["@name", "Oleg"]
46
+ pid_pair[0].should == "@pid"
47
+ pid_pair[1].should be_kind_of(Pid::Marshallable)
48
+ pid_pair[1].uuid.should == @obj.pid.uuid
49
+ end
50
+
51
+ describe PidVariables::Container do
52
+ before(:each) do
53
+ @host_pid.should_receive(:find_pid).once.with(@pid.uuid).and_return do |uuid|
54
+ @pid
55
+ end
56
+ @decoded = @encoded.decode_b381b571_1ab2_5889_8221_855dbbc76242(@host_pid)
57
+ end
58
+
59
+ it "should decode ivars" do
60
+ @decoded.class.should == @obj.class
61
+ @decoded.name.should == @obj.name
62
+ @decoded.pid.should == @obj.pid
63
+ end
64
+ end
65
+ end
66
+
67
+ describe "primitive", :shared => true do
68
+ it "should return itself" do
69
+ @obj.encode_b381b571_1ab2_5889_8221_855dbbc76242(@host_pid).should eql(@obj)
70
+ end
71
+ it "should return itself" do
72
+ @obj.decode_b381b571_1ab2_5889_8221_855dbbc76242(@host_pid).should eql(@obj)
73
+ end
74
+ end
75
+
76
+ [nil, true, false, "string", 123, 3.725, :symbol].each do |obj|
77
+ describe obj.inspect do
78
+ before(:each) do
79
+ @obj = obj
80
+ end
81
+ it_should_behave_like "primitive"
82
+ end
83
+ end
84
+
85
+ def codec_mock(method, name, n)
86
+ enc = name.upcase.to_sym
87
+ item = mock(name)
88
+ item.stub!(method).and_return(enc)
89
+ item.should_receive(method).exactly(n).times.with(@host_pid).and_return do |hpid|
90
+ hpid.should == @host_pid
91
+ enc
92
+ end
93
+ item
94
+ end
95
+
96
+ describe Array do
97
+ describe "array codec", :shared => true do
98
+ it "should encode/decode each item recusively" do
99
+ item = codec_mock(@method, "item", 6)
100
+ @arr = [item, item, [item, item, [item, [[item]] ]]]
101
+ @coded = @arr.__send__(@method, @host_pid)
102
+ item = :ITEM
103
+ @coded.should == [item, item, [item, item, [item, [[item]] ]]]
104
+ end
105
+ end
106
+ describe "encoding" do
107
+ before(:each) do
108
+ @method = @encode_method
109
+ end
110
+ it_should_behave_like "array codec"
111
+ end
112
+ describe "decoding" do
113
+ before(:each) do
114
+ @method = @decode_method
115
+ end
116
+ it_should_behave_like "array codec"
117
+ end
118
+ end
119
+
120
+
121
+ describe Hash do
122
+ describe "hash codec", :shared => true do
123
+ it "should encode/decode each key and value recusively" do
124
+ key = codec_mock(@method, "key", 3)
125
+ value = codec_mock(@method, "value", 3)
126
+ @hash = { key => value, :key => { key => { "str" => value, key => {"str" => 123, :sym => value } } } }
127
+ @coded = @hash.__send__(@method, @host_pid)
128
+ key = :KEY
129
+ value = :VALUE
130
+ @coded.should == { key => value, :key => { key => { "str" => value, key => {"str" => 123, :sym => value } } } }
131
+ end
132
+ end
133
+ describe "encoding" do
134
+ before(:each) do
135
+ @method = @encode_method
136
+ end
137
+ it_should_behave_like "hash codec"
138
+ end
139
+ describe "decoding" do
140
+ before(:each) do
141
+ @method = @decode_method
142
+ end
143
+ it_should_behave_like "hash codec"
144
+ end
145
+ end
146
+
147
+ describe Pid do
148
+ before(:each) do
149
+ @pid = Pid.new
150
+ @pid.uuid = @dummy_pid.uuid
151
+ @epid = @pid.encode_b381b571_1ab2_5889_8221_855dbbc76242(@host_pid)
152
+ end
153
+
154
+ it "should be encoded into Pid::Marshallable" do
155
+ @epid.should be_kind_of(Pid::Marshallable)
156
+ @epid.uuid.should_not be_nil
157
+ @epid.uuid.should == @pid.uuid
158
+ end
159
+
160
+ describe "decoded" do
161
+ before(:each) do
162
+ @host_pid.should_receive(:find_pid).once.with(@dummy_pid.uuid).and_return do |uuid|
163
+ @dummy_pid
164
+ end
165
+ @depid = @epid.decode_b381b571_1ab2_5889_8221_855dbbc76242(@host_pid)
166
+ end
167
+ it "should be decoded into real pid" do
168
+ @depid.should == @dummy_pid
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+
175
+ describe Pid::Marshallable do
176
+ before(:each) do
177
+ @cls = Pid::Marshallable
178
+ @obj = @cls.new("myuuid")
179
+ end
180
+ it "should be marshallable" do
181
+ Marshal.load(Marshal.dump(@obj)).uuid.should == @obj.uuid
182
+ end
183
+ end