fraggle-spanx 4.0.1.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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +223 -0
- data/Rakefile +17 -0
- data/bench/gs.rb +87 -0
- data/example/getdir.rb +29 -0
- data/example/readme.rb +82 -0
- data/example/walk.rb +33 -0
- data/example/watch.rb +16 -0
- data/fraggle.gemspec +28 -0
- data/lib/fraggle.rb +59 -0
- data/lib/fraggle/client.rb +211 -0
- data/lib/fraggle/connection.rb +124 -0
- data/lib/fraggle/msg.pb.rb +60 -0
- data/lib/fraggle/response.rb +28 -0
- data/lib/fraggle/version.rb +3 -0
- data/test/fraggle_client_test.rb +238 -0
- data/test/fraggle_protocol_test.rb +88 -0
- data/test/fraggle_test.rb +12 -0
- data/test/fraggle_transaction_test.rb +116 -0
- data/test/helper.rb +78 -0
- metadata +135 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
## Generated from msg.proto for server
|
2
|
+
require "beefcake"
|
3
|
+
|
4
|
+
module Fraggle
|
5
|
+
|
6
|
+
class Request
|
7
|
+
include Beefcake::Message
|
8
|
+
|
9
|
+
module Verb
|
10
|
+
GET = 1
|
11
|
+
SET = 2
|
12
|
+
DEL = 3
|
13
|
+
REV = 5
|
14
|
+
WAIT = 6
|
15
|
+
NOP = 7
|
16
|
+
WALK = 9
|
17
|
+
GETDIR = 14
|
18
|
+
STAT = 16
|
19
|
+
ACCESS = 99
|
20
|
+
end
|
21
|
+
|
22
|
+
optional :tag, :int32, 1
|
23
|
+
optional :verb, Request::Verb, 2
|
24
|
+
optional :path, :string, 4
|
25
|
+
optional :value, :bytes, 5
|
26
|
+
optional :other_tag, :int32, 6
|
27
|
+
optional :offset, :int32, 7
|
28
|
+
optional :rev, :int64, 9
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class Response
|
33
|
+
include Beefcake::Message
|
34
|
+
|
35
|
+
module Err
|
36
|
+
OTHER = 127
|
37
|
+
TAG_IN_USE = 1
|
38
|
+
UNKNOWN_VERB = 2
|
39
|
+
READONLY = 3
|
40
|
+
TOO_LATE = 4
|
41
|
+
REV_MISMATCH = 5
|
42
|
+
BAD_PATH = 6
|
43
|
+
MISSING_ARG = 7
|
44
|
+
RANGE = 8
|
45
|
+
NOTDIR = 20
|
46
|
+
ISDIR = 21
|
47
|
+
NOENT = 22
|
48
|
+
end
|
49
|
+
|
50
|
+
optional :tag, :int32, 1
|
51
|
+
optional :flags, :int32, 2
|
52
|
+
optional :rev, :int64, 3
|
53
|
+
optional :path, :string, 5
|
54
|
+
optional :value, :bytes, 6
|
55
|
+
optional :len, :int32, 8
|
56
|
+
optional :err_code, Response::Err, 100
|
57
|
+
optional :err_detail, :string, 101
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'fraggle/msg.pb'
|
2
|
+
|
3
|
+
module Fraggle
|
4
|
+
class Response
|
5
|
+
|
6
|
+
SET = 4
|
7
|
+
DEL = 8
|
8
|
+
|
9
|
+
def set?
|
10
|
+
return false if !flags
|
11
|
+
(flags & SET) > 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def del?
|
15
|
+
return false if !flags
|
16
|
+
(flags & DEL) > 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def missing?
|
20
|
+
rev == 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def ok?
|
24
|
+
err_code.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)+"/helper")
|
2
|
+
require 'fraggle/client'
|
3
|
+
|
4
|
+
class FraggleClientTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
attr_reader :c
|
7
|
+
|
8
|
+
def setup
|
9
|
+
addr = "127.0.0.1:0"
|
10
|
+
cn = TestConn.new(addr)
|
11
|
+
|
12
|
+
@addrs = ["1.1.1.1:1", "2.2.2.2:2", "3.3.3.3:3"]
|
13
|
+
@c = Fraggle::Client.allocate
|
14
|
+
|
15
|
+
def @c.reconnect(addr)
|
16
|
+
@cn = TestConn.new(addr)
|
17
|
+
end
|
18
|
+
|
19
|
+
def @c.monitor_addrs
|
20
|
+
# do nothing
|
21
|
+
end
|
22
|
+
|
23
|
+
@c.__send__(:initialize, cn, @addrs)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_response_error
|
27
|
+
req, log = request(V::REV)
|
28
|
+
|
29
|
+
c.send(req, &log)
|
30
|
+
|
31
|
+
res = reply(req.tag, :err_code => E::OTHER)
|
32
|
+
c.cn.receive_response(res)
|
33
|
+
|
34
|
+
assert_equal [[nil, C::ResponseError.new(res)]], log
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_reconnect_without_pending_requests
|
38
|
+
exp = @addrs.dup
|
39
|
+
|
40
|
+
# Disconnect from 127.0.0.1:0
|
41
|
+
c.cn.close_connection
|
42
|
+
|
43
|
+
# Send a request to invoke reconnect
|
44
|
+
req, log = request(V::REV)
|
45
|
+
c.send(req, &log)
|
46
|
+
|
47
|
+
# Fake reactor turn (only available in tests)
|
48
|
+
c.cn.tick!
|
49
|
+
|
50
|
+
assert exp.include?(c.cn.addr), "#{c.cn.addr.inspect} not in #{exp.inspect}"
|
51
|
+
|
52
|
+
# If the client can handle an error, it should not mention it to the user.
|
53
|
+
assert_equal [[nil, C::DisconnectedError.new("127.0.0.1:0")]], log
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_reconnect_with_pending_request
|
57
|
+
exp = @addrs.dup
|
58
|
+
|
59
|
+
# Send a request to invoke reconnect
|
60
|
+
req, log = request(V::REV)
|
61
|
+
c.send(req, &log)
|
62
|
+
|
63
|
+
# Disconnect from 127.0.0.1:0
|
64
|
+
c.cn.close_connection
|
65
|
+
|
66
|
+
# Fake reactor turn (only available in tests)
|
67
|
+
c.cn.tick!
|
68
|
+
|
69
|
+
assert exp.include?(c.cn.addr), "#{c.cn.addr.inspect} not in #{exp.inspect}"
|
70
|
+
|
71
|
+
assert_equal [[nil, C::DisconnectedError.new("127.0.0.1:0")]], log
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_reconnect_with_multiple_pending_requests
|
75
|
+
exp = @addrs.dup
|
76
|
+
|
77
|
+
# Send a request to invoke reconnect
|
78
|
+
req, loga = request(V::REV)
|
79
|
+
c.send(req, &loga)
|
80
|
+
|
81
|
+
req, logb = request(V::REV)
|
82
|
+
c.send(req, &logb)
|
83
|
+
|
84
|
+
# Disconnect from 127.0.0.1:0
|
85
|
+
c.cn.close_connection
|
86
|
+
|
87
|
+
# Fake reactor turn (only available in tests)
|
88
|
+
c.cn.tick!
|
89
|
+
|
90
|
+
assert exp.include?(c.cn.addr), "#{c.cn.addr.inspect} not in #{exp.inspect}"
|
91
|
+
|
92
|
+
# If the client can handle an error, it should not mention it to the user.
|
93
|
+
assert_equal [[nil, C::DisconnectedError.new("127.0.0.1:0")]], loga
|
94
|
+
assert_equal [[nil, C::DisconnectedError.new("127.0.0.1:0")]], logb
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_resend_pending_requests
|
98
|
+
req, log = request(V::GET, :path => "/foo")
|
99
|
+
c.resend(req, &log)
|
100
|
+
|
101
|
+
c.cn.close_connection
|
102
|
+
|
103
|
+
assert_equal [req], c.cn.sent
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_idemp_pending_requests
|
107
|
+
one, olog = request(V::SET, :rev => 1, :path => "/foo", :value => "bar")
|
108
|
+
c.idemp(one, &olog)
|
109
|
+
|
110
|
+
zero, zlog = request(V::SET, :rev => 0, :path => "/foo", :value => "bar")
|
111
|
+
c.idemp(zero, &zlog)
|
112
|
+
|
113
|
+
c.cn.close_connection
|
114
|
+
|
115
|
+
assert_equal [one], c.cn.sent
|
116
|
+
|
117
|
+
assert_equal [[nil, C::DisconnectedError.new("127.0.0.1:0")]], zlog
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_idemp_unhandled_error
|
121
|
+
req, log = request(V::SET, :path => "/foo", :value => "bar", :rev => Fraggle::Client::MaxInt64)
|
122
|
+
c.idemp(req, &log)
|
123
|
+
|
124
|
+
res = reply(req.tag, :err_code => E::OTHER)
|
125
|
+
c.cn.receive_response(res)
|
126
|
+
|
127
|
+
assert_equal [[nil, C::ResponseError.new(res)]], log
|
128
|
+
end
|
129
|
+
|
130
|
+
###
|
131
|
+
# Sugar
|
132
|
+
|
133
|
+
def last_sent
|
134
|
+
c.cn.sent.last
|
135
|
+
end
|
136
|
+
|
137
|
+
def assert_verb(exp, name, *args)
|
138
|
+
called = false
|
139
|
+
blk = Proc.new { called = true }
|
140
|
+
req = c.__send__(name, *args, &blk)
|
141
|
+
exp[:tag] = req.tag
|
142
|
+
assert_equal exp, last_sent.to_hash
|
143
|
+
|
144
|
+
c.cn.receive_response(reply(req.tag))
|
145
|
+
assert called
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_set
|
149
|
+
exp = {
|
150
|
+
:verb => V::SET,
|
151
|
+
:rev => 0,
|
152
|
+
:path => "/foo",
|
153
|
+
:value => "bar"
|
154
|
+
}
|
155
|
+
|
156
|
+
assert_verb exp, :set, 0, "/foo", "bar"
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_get
|
160
|
+
exp = {
|
161
|
+
:verb => V::GET,
|
162
|
+
:rev => 0,
|
163
|
+
:path => "/foo"
|
164
|
+
}
|
165
|
+
|
166
|
+
assert_verb exp, :get, 0, "/foo"
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_del
|
170
|
+
exp = {
|
171
|
+
:verb => V::DEL,
|
172
|
+
:rev => 0,
|
173
|
+
:path => "/foo"
|
174
|
+
}
|
175
|
+
|
176
|
+
assert_verb exp, :del, 0, "/foo"
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_getdir
|
180
|
+
exp = {
|
181
|
+
:verb => V::GETDIR,
|
182
|
+
:rev => 0,
|
183
|
+
:path => "/foo",
|
184
|
+
:offset => 0
|
185
|
+
}
|
186
|
+
|
187
|
+
assert_verb exp, :_getdir, 0, "/foo", 0
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_rev
|
191
|
+
exp = {
|
192
|
+
:verb => V::REV
|
193
|
+
}
|
194
|
+
|
195
|
+
assert_verb exp, :rev
|
196
|
+
end
|
197
|
+
|
198
|
+
def test_stat
|
199
|
+
exp = {
|
200
|
+
:rev => 0,
|
201
|
+
:verb => V::STAT,
|
202
|
+
:path => "/foo"
|
203
|
+
}
|
204
|
+
|
205
|
+
assert_verb exp, :stat, 0, "/foo"
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_walk
|
209
|
+
exp = {
|
210
|
+
:verb => V::WALK,
|
211
|
+
:rev => 0,
|
212
|
+
:path => "/foo/*",
|
213
|
+
:offset => 0
|
214
|
+
}
|
215
|
+
|
216
|
+
assert_verb exp, :_walk, 0, "/foo/*", 0
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_wait
|
220
|
+
exp = {
|
221
|
+
:verb => V::WAIT,
|
222
|
+
:rev => 0,
|
223
|
+
:path => "/foo/*"
|
224
|
+
}
|
225
|
+
|
226
|
+
assert_verb exp, :wait, 0, "/foo/*"
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_wait
|
230
|
+
exp = {
|
231
|
+
:verb => V::ACCESS,
|
232
|
+
:value => "abc"
|
233
|
+
}
|
234
|
+
|
235
|
+
assert_verb exp, :access, "abc"
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)+"/helper")
|
2
|
+
require 'fraggle/connection'
|
3
|
+
|
4
|
+
class FraggleProtocolTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
attr_reader :cn
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@cn = TestConn.new("127.0.0.1:0")
|
10
|
+
end
|
11
|
+
|
12
|
+
def encode(req)
|
13
|
+
data = req.encode
|
14
|
+
[data.length].pack("N") + data
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_simple
|
18
|
+
req = Fraggle::Response.new :tag => 0, :verb => V::NOP
|
19
|
+
cn.receive_data(encode(req))
|
20
|
+
|
21
|
+
assert_equal [req], cn.received
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_multiple_single
|
25
|
+
a = Fraggle::Response.new :tag => 0, :verb => V::NOP
|
26
|
+
b = Fraggle::Response.new :tag => 1, :verb => V::NOP
|
27
|
+
cn.receive_data(encode(a) + encode(b))
|
28
|
+
|
29
|
+
assert_equal [a, b], cn.received
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_multiple_double
|
33
|
+
a = Fraggle::Response.new :tag => 0, :verb => V::NOP
|
34
|
+
b = Fraggle::Response.new :tag => 1, :verb => V::NOP
|
35
|
+
cn.receive_data(encode(a))
|
36
|
+
cn.receive_data(encode(b))
|
37
|
+
|
38
|
+
assert_equal [a, b], cn.received
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_small_chunks
|
42
|
+
req = Fraggle::Response.new :tag => 0, :verb => V::NOP
|
43
|
+
|
44
|
+
bytes = encode(req) * 3
|
45
|
+
len = bytes.length
|
46
|
+
|
47
|
+
0.step(len, 1) do |i|
|
48
|
+
data = bytes.slice!(0, i)
|
49
|
+
cn.receive_data(data)
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_equal [req, req, req], cn.received
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_big_chunks
|
56
|
+
req = Fraggle::Response.new :tag => 0, :verb => V::NOP
|
57
|
+
|
58
|
+
bytes = encode(req) * 3
|
59
|
+
len = bytes.length
|
60
|
+
|
61
|
+
0.step(len, len/2) do |i|
|
62
|
+
data = bytes.slice!(0, i)
|
63
|
+
cn.receive_data(data)
|
64
|
+
end
|
65
|
+
|
66
|
+
assert_equal [req, req, req], cn.received
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_send_request
|
70
|
+
req = Fraggle::Request.new :tag => 0, :verb => V::NOP
|
71
|
+
bytes = req.encode
|
72
|
+
head = [bytes.length].pack("N")
|
73
|
+
exp = head+bytes
|
74
|
+
|
75
|
+
got = ""
|
76
|
+
(class << cn ; self ; end).instance_eval do
|
77
|
+
define_method(:send_data) do |data|
|
78
|
+
got << data
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
nop = Fraggle::Request.new :verb => V::NOP
|
83
|
+
cn.send_request(nop, nil)
|
84
|
+
|
85
|
+
assert_equal head+bytes, got
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)+"/helper")
|
2
|
+
require 'fraggle/connection'
|
3
|
+
|
4
|
+
class FraggleTransactionTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
attr_reader :cn
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@cn = TestConn.new("127.0.0.1:0")
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_tagging
|
13
|
+
req, _ = request(V::REV)
|
14
|
+
assert_equal 0, cn.send_request(req, _).tag
|
15
|
+
req, _ = request(V::REV)
|
16
|
+
assert_equal 1, cn.send_request(req, _).tag
|
17
|
+
req, _ = request(V::REV)
|
18
|
+
assert_equal 2, cn.send_request(req, _).tag
|
19
|
+
end
|
20
|
+
|
21
|
+
def test
|
22
|
+
req, log = request(V::REV)
|
23
|
+
|
24
|
+
cn.send_request(req, log)
|
25
|
+
|
26
|
+
res = reply(req.tag)
|
27
|
+
cn.receive_response(res)
|
28
|
+
|
29
|
+
assert_equal [[res, nil]], log
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_error
|
33
|
+
req, log = request(V::REV)
|
34
|
+
|
35
|
+
cn.send_request(req, log)
|
36
|
+
|
37
|
+
res = reply(req.tag, :err_code => E::OTHER)
|
38
|
+
cn.receive_response(res)
|
39
|
+
|
40
|
+
err = C::ResponseError.new(res)
|
41
|
+
assert_equal [[nil, err]], log
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_i_tag
|
45
|
+
res = reply(0, :err_code => E::OTHER)
|
46
|
+
|
47
|
+
assert_nothing_raised do
|
48
|
+
cn.receive_response(res)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_deletes_callback
|
53
|
+
req, log = request(V::REV)
|
54
|
+
|
55
|
+
cn.send_request(req, log)
|
56
|
+
|
57
|
+
res = reply(req.tag)
|
58
|
+
cn.receive_response(res)
|
59
|
+
|
60
|
+
# This should be ignored
|
61
|
+
cn.receive_response(res)
|
62
|
+
|
63
|
+
assert_equal [[res, nil]], log
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_error_deletes_callback
|
67
|
+
req, log = request(V::REV)
|
68
|
+
|
69
|
+
cn.send_request(req, log)
|
70
|
+
|
71
|
+
res = reply(req.tag, :err_code => E::OTHER)
|
72
|
+
cn.receive_response(res)
|
73
|
+
|
74
|
+
# This should be ignored
|
75
|
+
cn.receive_response(res)
|
76
|
+
|
77
|
+
assert_equal [[nil, C::ResponseError.new(res)]], log
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_cannot_reuse_sent_request
|
81
|
+
req, _ = request(V::REV)
|
82
|
+
cn.send_request(req, _)
|
83
|
+
|
84
|
+
assert_raises C::SendError do
|
85
|
+
cn.send_request(req, _)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_disconnect_with_pending_requests
|
90
|
+
a, al = request(V::REV)
|
91
|
+
a = cn.send_request(a, al)
|
92
|
+
b, bl = request(V::REV)
|
93
|
+
b = cn.send_request(b, bl)
|
94
|
+
c, cl = request(V::REV)
|
95
|
+
c = cn.send_request(c, cl)
|
96
|
+
|
97
|
+
cn.unbind
|
98
|
+
|
99
|
+
assert_equal [[nil, C::DisconnectedError.new("127.0.0.1:0")]], al
|
100
|
+
assert_equal [[nil, C::DisconnectedError.new("127.0.0.1:0")]], bl
|
101
|
+
assert_equal [[nil, C::DisconnectedError.new("127.0.0.1:0")]], cl
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_send_when_disconnected
|
105
|
+
cn.unbind
|
106
|
+
|
107
|
+
req, log = request(V::REV)
|
108
|
+
ret = cn.send_request(req, log)
|
109
|
+
|
110
|
+
assert_equal req, ret
|
111
|
+
|
112
|
+
cn.tick!
|
113
|
+
|
114
|
+
assert_equal [[nil, C::DisconnectedError.new("127.0.0.1:0")]], log
|
115
|
+
end
|
116
|
+
end
|