fraggle 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ module Fraggle
2
+
3
+ module Emitter
4
+
5
+ def callbacks
6
+ @callbacks ||= Hash.new(lambda {})
7
+ end
8
+
9
+ def emit(name, *args)
10
+ callbacks[name].call(*args)
11
+ end
12
+
13
+ def valid(&blk) ; must_callback!(:valid, blk) ; end
14
+ def done(&blk) ; must_callback!(:done, blk) ; end
15
+ def error(&blk) ; must_callback!(:error, blk) ; end
16
+
17
+ def must_callback!(name, blk)
18
+ if ! blk
19
+ raise ArgumentError, "no block given to `#{name}`"
20
+ end
21
+ callbacks[name] = blk
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,26 @@
1
+ module Fraggle
2
+ module Logger
3
+
4
+ DEBUG = 0
5
+ INFO = 1
6
+ ERROR = 2
7
+
8
+ attr_accessor :writer, :level
9
+
10
+ def log(lv, msg)
11
+ label = case lv
12
+ when DEBUG then "debug "
13
+ when INFO then "info "
14
+ when ERROR then "error "
15
+ end
16
+
17
+ if lv >= level
18
+ writer.puts "#{label}: #{msg}"
19
+ end
20
+ end
21
+
22
+ def debug(msg) ; log(DEBUG, msg) ; end
23
+ def info(msg) ; log(INFO, msg) ; end
24
+ def error(msg) ; log(ERROR, msg) ; end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ class Object
2
+ def metaclass
3
+ (class << self ; self ; end)
4
+ end
5
+
6
+ def metadef(name, &blk)
7
+ metaclass.__send__(:define_method, name, &blk)
8
+ end
9
+ end
@@ -18,6 +18,7 @@ module Fraggle
18
18
  NOOP = 7; # {} => {}
19
19
  WATCH = 8; # path => {cas, path, value}+
20
20
  CANCEL = 10; # id => {}
21
+ STAT = 16; # path, id => cas, len
21
22
 
22
23
  # future
23
24
  GETDIR = 14; # path => {cas, value}+
@@ -45,14 +46,20 @@ module Fraggle
45
46
  class Response
46
47
  include Beefcake::Message
47
48
 
48
- required :tag, :int32, 1
49
+ required :tag, :uint32, 1
49
50
  required :flags, :int32, 2
50
51
 
51
- optional :seqn, :int64, 3
52
+ optional :rev, :int64, 3
52
53
  optional :cas, :int64, 4
53
54
  optional :path, :string, 5
54
55
  optional :value, :bytes, 6
55
56
  optional :id, :int32, 7
57
+ optional :len, :int32, 8
58
+
59
+ module Flag
60
+ VALID = 1
61
+ DONE = 2
62
+ end
56
63
 
57
64
  module Err
58
65
  # don't use value 0
@@ -66,6 +73,7 @@ module Fraggle
66
73
  # match unix errno
67
74
  NOTDIR = 20
68
75
  ISDIR = 21
76
+ NOINT = 22
69
77
  end
70
78
 
71
79
  optional :err_code, Err, 100
@@ -0,0 +1,47 @@
1
+ require 'fraggle/response'
2
+
3
+ module Fraggle
4
+
5
+ module Protocol
6
+
7
+ attr_reader :last_received
8
+
9
+ def receive_data(data)
10
+ @last_received = Time.now
11
+
12
+ (@buf ||= "") << data
13
+
14
+ while @buf.length > 0
15
+ if @len && @buf.length >= @len
16
+ bytes = @buf.slice!(0, @len)
17
+ @len = nil
18
+ res = Response.decode(bytes)
19
+ receive_response(res)
20
+ elsif @buf.length >= 4
21
+ bytes = @buf.slice!(0, 4)
22
+ @len = bytes.unpack("N")[0]
23
+ else
24
+ break
25
+ end
26
+ end
27
+ end
28
+
29
+ # The default receive_response
30
+ def receive_response(res)
31
+ p res
32
+ end
33
+
34
+ def send_request(req)
35
+ data = req.encode
36
+ head = [data.length].pack("N")
37
+
38
+ send_data("#{head}#{data}")
39
+ end
40
+
41
+ def send_data(data)
42
+ super(data)
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,15 @@
1
+ require 'fraggle/msg'
2
+ require 'fraggle/emitter'
3
+
4
+ ##
5
+ # An extension to Request in msg.rb. I want to keep these seperated so when
6
+ # future versions of Beefcake can generate code, we don't have to manually add
7
+ # this back in for each generation.
8
+
9
+ module Fraggle
10
+
11
+ class Request
12
+ include Emitter
13
+ end
14
+
15
+ end
@@ -0,0 +1,33 @@
1
+ require 'fraggle/msg'
2
+
3
+ ##
4
+ # An extension to Response in msg.rb. I want to keep these seperated so when
5
+ # future versions of Beefcake can generate code, we don't have to manually add
6
+ # this back in for each generation.
7
+
8
+ module Fraggle
9
+
10
+ class Response
11
+
12
+ Missing = 0
13
+ Clobber = -1
14
+ Dir = -2
15
+ Dummy = -3
16
+
17
+ # CAS
18
+ def missing? ; cas == Missing ; end
19
+ def dir? ; cas == Dir ; end
20
+ def dummy? ; cas == Dummy ; end
21
+
22
+ # ERR
23
+ def ok? ; error_code != 0 ; end
24
+ def other? ; error_code == Err::OTHER ; end
25
+ def unknown_verb? ; error_code == Err::UNKNOWN_VERB ; end
26
+ def redirect? ; error_code == Err::REDIRECT ; end
27
+ def invalid_snap? ; error_code == Err::INVALID_SNAP ; end
28
+ def mismatch? ; error_code == Err::CAS_MISMATCH ; end
29
+ def not_dir? ; error_code == Err::NOT_DIR ; end
30
+ def is_dir? ; error_code == Err::ISDIR ; end
31
+ end
32
+
33
+ end
@@ -0,0 +1,43 @@
1
+ require 'fraggle/client'
2
+
3
+ module Fraggle
4
+
5
+ class Snap
6
+
7
+ attr_reader :id
8
+
9
+ def initialize(id, c)
10
+ @id = id
11
+ @c = c
12
+ end
13
+
14
+ def get(path, &blk)
15
+ @c.get(@id, path, &blk)
16
+ end
17
+
18
+ def walk(glob, &blk)
19
+ @c.walk(@id, glob, &blk)
20
+ end
21
+
22
+ def stat(path, &blk)
23
+ @c.stat(@id, path, &blk)
24
+ end
25
+
26
+ def getdir(path, offset=0, limit=0, &blk)
27
+ @c.getdir(@id, path, offset, limit, &blk)
28
+ end
29
+
30
+ def snap(&blk)
31
+ @c.snap do |res|
32
+ sn = Snap.new(res.id, @c)
33
+ blk.call(sn)
34
+ end
35
+ end
36
+
37
+ def method_missing(*args, &blk)
38
+ @c.__send__(*args, &blk)
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,72 @@
1
+ require 'fraggle/request'
2
+ require 'fraggle/response'
3
+
4
+ module Fraggle
5
+
6
+ ##
7
+ # I want this to be a great starting point for testing fraggle applications.
8
+ # It's currently a work in progress. Think Rack::Test for fraggle.
9
+ #
10
+ module Test
11
+
12
+ V = Fraggle::Request::Verb
13
+ F = Fraggle::Response::Flag
14
+ E = Fraggle::Response::Err
15
+
16
+
17
+ class TestClient < Array
18
+ include Fraggle::Client
19
+ alias :send_request :<<
20
+ end
21
+
22
+
23
+ # This is handy for testing callbacks
24
+ class Blk < Array
25
+ def to_proc
26
+ Proc.new {|res| self << res }
27
+ end
28
+ end
29
+
30
+
31
+ def assert_sent(tag, attrs={})
32
+ req = Fraggle::Request.new(attrs)
33
+ req.tag = tag
34
+
35
+ msg = "This was not sent:\n"
36
+ msg << " #{req.inspect}\n"
37
+ msg << "Sent:\n "
38
+ msg << c.map {|r| r.inspect }.join("\n ")
39
+ msg << "\n"
40
+
41
+ assert_block(msg) { c.include?(req) }
42
+ end
43
+
44
+ def assert_recv(attrs)
45
+ req = Fraggle::Response.new(attrs)
46
+ msg = "This was not recieved:\n"
47
+ msg << " #{req.inspect}\n"
48
+ msg << "Received:\n "
49
+ msg << blk.map {|r| r.inspect }.join("\n ")
50
+ msg << "\n"
51
+
52
+ assert_block(msg) { blk.include?(attrs) }
53
+ end
54
+
55
+ # Replies with a valid response
56
+ def reply(tag, attrs={})
57
+ res = Fraggle::Response.new(attrs)
58
+ res.tag = tag
59
+ res.flags ||= 0
60
+ res.flags |= Fraggle::Response::Flag::VALID
61
+ c.receive_response(res)
62
+ res
63
+ end
64
+
65
+ # Replies with a valid + done response
66
+ def reply!(tag, attrs={})
67
+ reply(tag, attrs.merge(:flags => Fraggle::Response::Flag::DONE))
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,261 @@
1
+ require 'fraggle/client'
2
+ require 'fraggle/response'
3
+ require 'fraggle/test'
4
+
5
+ class FraggleClientTest < Test::Unit::TestCase
6
+ include Fraggle::Test
7
+
8
+ attr_reader :c, :blk
9
+
10
+ def setup
11
+ @c = TestClient.new("doozer://127.0.0.1:8046")
12
+ @blk = Blk.new
13
+ end
14
+
15
+ def test_send_recv
16
+ req = c.send(Fraggle::Request.new, &blk)
17
+
18
+ assert_sent req.tag
19
+ assert_recv reply(req.tag)
20
+ end
21
+
22
+ def test_valid
23
+ req = c.send(Fraggle::Request.new, &blk)
24
+
25
+ reply(req.tag)
26
+ reply(req.tag)
27
+ reply(req.tag)
28
+
29
+ assert_equal 3, blk.length
30
+ end
31
+
32
+ def test_done
33
+ req = c.send(Fraggle::Request.new)
34
+ req.done(&blk)
35
+
36
+ reply!(req.tag)
37
+
38
+ assert_equal 1, blk.length
39
+
40
+ req.valid(&blk)
41
+
42
+ reply(req.tag)
43
+ reply(req.tag)
44
+ reply(req.tag)
45
+
46
+ assert_equal 1, blk.length
47
+ end
48
+
49
+ def test_default_error
50
+ req = c.send(Fraggle::Request.new)
51
+
52
+ assert_raises Fraggle::Client::Error do
53
+ reply(req.tag, :err_code => E::OTHER, :err_detail => "boom!")
54
+ end
55
+
56
+ assert_nothing_raised do
57
+ reply(req.tag, :err_code => E::OTHER, :err_detail => "boom!")
58
+ end
59
+ end
60
+
61
+ def test_error
62
+ req = c.send(Fraggle::Request.new)
63
+ req.error(&blk)
64
+
65
+ reply(req.tag, :err_code => E::OTHER, :err_detail => "boom!")
66
+ reply(req.tag, :err_code => E::OTHER, :err_detail => "boom!")
67
+
68
+ assert_equal 1, blk.length
69
+ end
70
+
71
+ def test_tagging
72
+ t = Fraggle::Client::MinTag
73
+
74
+ assert_equal t+0, c.noop.tag
75
+ assert_equal t+1, c.noop.tag
76
+ assert_equal t+2, c.noop.tag
77
+ assert_equal t+3, c.noop.tag
78
+ assert_equal t+4, c.noop.tag
79
+ end
80
+
81
+ # CHECKIN cas, path => cas
82
+ def test_checkin
83
+ req = c.checkin("abc123", 123, &blk)
84
+
85
+ assert_sent(req.tag, :verb => V::CHECKIN, :path => "abc123", :cas => 123)
86
+ assert_recv(reply(req.tag, :cas => 123))
87
+ end
88
+
89
+ # GET path, id => cas, value
90
+ def test_get
91
+ req = c.get(0, "/ping", &blk)
92
+
93
+ assert_sent req.tag, :verb => V::GET, :path => "/ping"
94
+ assert_recv reply(req.tag, :cas => 123, :value => "pong")
95
+ end
96
+
97
+ # STAT path, id => cas, len
98
+ def test_stat
99
+ req = c.stat(0, "/ping", &blk)
100
+
101
+ assert_sent req.tag, :verb => V::STAT, :path => "/ping"
102
+ assert_recv reply(req.tag, :cas => 123, :len => 4)
103
+ end
104
+
105
+ # GETDIR id, path, offset, limit => {cas, value}+
106
+ def test_getdir
107
+ req = c.getdir(0, "/test", 0, 0, &blk)
108
+
109
+ assert_sent req.tag, :verb => V::GETDIR, :path => "/test"
110
+ assert_recv reply(req.tag, :cas => 123, :value => "a")
111
+
112
+ req = c.getdir(0, "/test", 1, 2, &blk)
113
+
114
+ assert_sent req.tag, :verb => V::GETDIR, :path => "/test", :offset => 1, :limit => 2
115
+ assert_recv reply(req.tag, :cas => 123, :value => "b")
116
+ end
117
+
118
+ # SET cas, path, value => cas
119
+ def test_set
120
+ req = c.set("/foo", "bar", 123, &blk)
121
+
122
+ assert_sent(req.tag, :verb => V::SET, :cas => 123, :path => "/foo", :value => "bar")
123
+ assert_recv(reply(req.tag, :cas => 123))
124
+ end
125
+
126
+ # DEL cas, path => {}
127
+ def test_del
128
+ req = c.del("/foo", 123, &blk)
129
+
130
+ assert_sent(req.tag, :verb => V::DEL, :cas => 123, :path => "/foo")
131
+ assert_recv(reply(req.tag))
132
+ end
133
+
134
+ # WALK path, id => {cas, path, value}+
135
+ def test_walk
136
+ req = c.walk(0, "/foo/*", &blk)
137
+
138
+ assert_respond_to req, :cancel
139
+
140
+ assert_sent(req.tag, :verb => V::WALK, :path => "/foo/*")
141
+ assert_recv(reply(req.tag, :cas => 123, :path => "/foo/a", :value => "1"))
142
+ assert_recv(reply(req.tag, :cas => 456, :path => "/foo/b", :value => "2"))
143
+ assert_recv(reply(req.tag, :cas => 789, :path => "/foo/c", :value => "3"))
144
+ end
145
+
146
+ # WATCH path => {cas, path, value}+
147
+ def test_watch
148
+ req = c.watch("/foo/*", &blk)
149
+
150
+ assert_respond_to req, :cancel
151
+
152
+ assert_sent(req.tag, :verb => V::WATCH, :path => "/foo/*")
153
+ assert_recv(reply(req.tag, :cas => 123, :path => "/foo/a", :value => "1"))
154
+ assert_recv(reply(req.tag, :cas => 456, :path => "/foo/b", :value => "2"))
155
+ assert_recv(reply(req.tag, :cas => 789, :path => "/foo/c", :value => "3"))
156
+ end
157
+
158
+ # SNAP {} => id
159
+ def test_snap
160
+ req = c.snap(&blk)
161
+
162
+ assert_sent(req.tag, :verb => V::SNAP)
163
+ assert_recv(reply(req.tag, :id => 1))
164
+ end
165
+
166
+ # DELSNAP id => {}
167
+ def test_delsnap
168
+ req = c.delsnap(1, &blk)
169
+
170
+ assert_sent(req.tag, :verb => V::DELSNAP, :id => 1)
171
+ assert_recv(reply(req.tag))
172
+ end
173
+
174
+ # NOOP {} => {}
175
+ def test_noop
176
+ req = c.noop(&blk)
177
+
178
+ assert_sent(req.tag, :verb => V::NOOP)
179
+ assert_recv(reply(req.tag))
180
+ end
181
+
182
+ # CANCEL id => {}
183
+ def test_cancel
184
+ nop = c.noop(&blk)
185
+ req = c.__cancel__(nop, &blk)
186
+
187
+ assert_sent(req.tag, :verb => V::CANCEL, :id => nop.tag)
188
+ assert_recv(reply(req.tag))
189
+ end
190
+
191
+ def test_cancelable
192
+ can = c.cancelable(Fraggle::Request.new(:tag => 123))
193
+
194
+ assert ! can.canceled?
195
+
196
+ req = can.cancel
197
+
198
+ assert can.canceled?
199
+ assert_equal 1, c.length
200
+ assert_sent req.tag, :verb => V::CANCEL, :id => can.tag
201
+
202
+ # A few more for good measure
203
+ can.cancel
204
+ can.cancel
205
+
206
+ # Ensure we haven't called cancel on the same tag more than once.
207
+ assert_equal 1, c.length
208
+ end
209
+
210
+ def test_cancel_does_not_prematurely_remove_callback
211
+ x = c.watch("/foo/*", &blk)
212
+ y = x.cancel
213
+
214
+ assert_not_equal x.object_id, y.object_id
215
+ assert_not_equal x.tag, y.tag
216
+ end
217
+
218
+ def test_cancel_discards_further_replies
219
+ x = c.watch("/foo/*", &blk)
220
+ x.cancel
221
+
222
+ reply!(x.tag)
223
+
224
+ # The cancel happened before the reply was received by the reactor. Any
225
+ # remaining data from the server should be discarded.
226
+ assert_equal 0, blk.length
227
+ end
228
+
229
+ def test_tag_pending_cancel_is_not_useable
230
+ x = c.watch("/foo/*", &blk)
231
+ y = x.cancel
232
+
233
+ # Force a reset of tag so that `send` will attempt
234
+ # to reuse the pending cancels tag.
235
+ c.instance_eval { @tag = x.tag }
236
+
237
+ z = c.noop
238
+
239
+ assert_not_equal x.tag, z.tag
240
+ assert_not_equal y.tag, z.tag
241
+ end
242
+
243
+ def test_reuse_canceled_tag
244
+ x = c.watch("/foo/*", &blk)
245
+ y = x.cancel
246
+
247
+ reply!(y.tag)
248
+
249
+ z = c.noop
250
+
251
+ assert_equal x.tag, z.tag
252
+ end
253
+
254
+ # These are planned for future doozer versions
255
+ #
256
+ # ESET cas, path => {}
257
+ # GETDIR path => {cas, value}+
258
+ # MONITOR path => {cas, path, value}+
259
+ # SYNCPATH path => cas, value
260
+
261
+ end