fraggle 0.1.1 → 0.2.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.
@@ -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