fraggle 0.1.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.
- data/LICENSE +22 -0
- data/README.md +68 -0
- data/lib/fraggle.rb +322 -0
- data/lib/fraggle/proto.rb +75 -0
- data/test/core_test.rb +215 -0
- data/test/live_test.rb +197 -0
- data/test/reconnect_test.rb +175 -0
- metadata +89 -0
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2011 Blake Mizerany, Keith Rarick, Chris Moos
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Permission is hereby granted, free of charge, to any person
         | 
| 4 | 
            +
            obtaining a copy of this software and associated documentation
         | 
| 5 | 
            +
            files (the "Software"), to deal in the Software without
         | 
| 6 | 
            +
            restriction, including without limitation the rights to use,
         | 
| 7 | 
            +
            copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 8 | 
            +
            copies of the Software, and to permit persons to whom the
         | 
| 9 | 
            +
            Software is furnished to do so, subject to the following
         | 
| 10 | 
            +
            conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 13 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 16 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
         | 
| 17 | 
            +
            OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 18 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
         | 
| 19 | 
            +
            HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
         | 
| 20 | 
            +
            WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
         | 
| 21 | 
            +
            FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
         | 
| 22 | 
            +
            OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,68 @@ | |
| 1 | 
            +
            # Fraggle
         | 
| 2 | 
            +
            **An EventMachine based Doozer client**
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            ## Install
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                $ gem install fraggle
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ## Use
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                require 'rubygems'
         | 
| 11 | 
            +
                require 'eventmachine'
         | 
| 12 | 
            +
                require 'fraggle'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                EM.start do
         | 
| 15 | 
            +
                  c = Fraggle.connect "127.0.0.1", 8046
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  ## Setting a key
         | 
| 18 | 
            +
                  c.set "/foo", "bar", :missing do |e|
         | 
| 19 | 
            +
                    if ! e.err
         | 
| 20 | 
            +
                      e.cas # => "123"
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  c.get "/foo" do |e|
         | 
| 25 | 
            +
                    if err != nil
         | 
| 26 | 
            +
                      e.body     # => "bar"
         | 
| 27 | 
            +
                      e.cas      # => "123"
         | 
| 28 | 
            +
                      e.dir? # => false
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  watch = c.watch "/foo" do |e|
         | 
| 33 | 
            +
                    # The event has:
         | 
| 34 | 
            +
                    # ------------------------
         | 
| 35 | 
            +
                    # NOTE:  `err` will be set iff the glob is bad
         | 
| 36 | 
            +
                    # e.err       # => nil
         | 
| 37 | 
            +
                    # e.path      # => "/foo"
         | 
| 38 | 
            +
                    # e.body      # => "bar"
         | 
| 39 | 
            +
                    # e.cas       # => "123"
         | 
| 40 | 
            +
                    # e.set?  # => true
         | 
| 41 | 
            +
                    # e.del?  # => false
         | 
| 42 | 
            +
                    # e.done? # => true
         | 
| 43 | 
            +
                    # ------------------------
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    if e.done?
         | 
| 46 | 
            +
                      # This watch was closed, do something if you wish.
         | 
| 47 | 
            +
                    else
         | 
| 48 | 
            +
                      done_something_with(e)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      # Phoney check for example
         | 
| 51 | 
            +
                      if can_stop_watching?(path)
         | 
| 52 | 
            +
                        c.close(watch)
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
             | 
| 61 | 
            +
            ## Dev
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            **Clone**
         | 
| 64 | 
            +
                $ git clone http://github.com/bmizerany/fraggle.git
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            **Test**
         | 
| 67 | 
            +
                $ gem install turn
         | 
| 68 | 
            +
                $ turn
         | 
    
        data/lib/fraggle.rb
    ADDED
    
    | @@ -0,0 +1,322 @@ | |
| 1 | 
            +
            require 'beefcake'
         | 
| 2 | 
            +
            require 'eventmachine'
         | 
| 3 | 
            +
            require 'fraggle/proto'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Fraggle
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              MaxInt32 = (1<<31)-1
         | 
| 8 | 
            +
              MinInt32 = -(1<<31)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              ##
         | 
| 11 | 
            +
              # Response extensions
         | 
| 12 | 
            +
              class Response
         | 
| 13 | 
            +
                module Flag
         | 
| 14 | 
            +
                  VALID = 1
         | 
| 15 | 
            +
                  DONE  = 2
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def valid?
         | 
| 19 | 
            +
                  (flags & Flag::VALID) > 0
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def done?
         | 
| 23 | 
            +
                  (flags & Flag::DONE) > 0
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                # Err sugar
         | 
| 27 | 
            +
                def ok?           ; err_code == nil               ; end
         | 
| 28 | 
            +
                def other?        ; err_code == Err::OTHER        ; end
         | 
| 29 | 
            +
                def tag_in_use?   ; err_code == Err::TAG_IN_USE   ; end
         | 
| 30 | 
            +
                def unknown_verb? ; err_code == Err::UNKNOWN_VERB ; end
         | 
| 31 | 
            +
                def redirect?     ; err_code == Err::REDIRECT     ; end
         | 
| 32 | 
            +
                def invalid_snap? ; err_code == Err::INVALID_SNAP ; end
         | 
| 33 | 
            +
                def mismatch?     ; err_code == Err::CAS_MISMATCH ; end
         | 
| 34 | 
            +
                def notdir?       ; err_code == Err::NOTDIR       ; end
         | 
| 35 | 
            +
                def dir?          ; err_code == Err::ISDIR        ; end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                # CAS sugar
         | 
| 38 | 
            +
                def missing?  ; cas ==  0 ; end
         | 
| 39 | 
            +
                def clobber?  ; cas == -1 ; end
         | 
| 40 | 
            +
                def dir?      ; cas == -2 ; end
         | 
| 41 | 
            +
                def dummy?    ; cas == -3 ; end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
             | 
| 45 | 
            +
              class AssemblyError < StandardError
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
             | 
| 49 | 
            +
              def self.connect(addr="127.0.0.1:8046", opts={})
         | 
| 50 | 
            +
                # TODO: take a magnet link instead
         | 
| 51 | 
            +
                host, port = addr.split(":")
         | 
| 52 | 
            +
                EM.connect(host, port, self, addr, opts)
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              attr_reader :doozers, :addr, :opts
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              def initialize(addr, opts)
         | 
| 58 | 
            +
                opts[:assemble] = opts.fetch(:assemble, true)
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                # TODO: take a magnet link and load into @doozers
         | 
| 61 | 
            +
                @addr    = addr
         | 
| 62 | 
            +
                @opts    = opts
         | 
| 63 | 
            +
                @doozers = {}
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              ##
         | 
| 67 | 
            +
              # Collect all cluster information for the event of a disconnect from the
         | 
| 68 | 
            +
              # server; At which point we will want to attempt a connecting to them one by
         | 
| 69 | 
            +
              # one until we have a connection or run out of options.
         | 
| 70 | 
            +
              def assemble
         | 
| 71 | 
            +
                return if ! opts[:assemble]
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                blk = Proc.new do |we|
         | 
| 74 | 
            +
                  if ! we.ok?
         | 
| 75 | 
            +
                    raise AssemblyError, we.err_detail
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  if we.value == ""
         | 
| 79 | 
            +
                    doozers.delete(we.path)
         | 
| 80 | 
            +
                  else
         | 
| 81 | 
            +
                    get "/doozer/info/#{we.value}/public-addr" do |e|
         | 
| 82 | 
            +
                      next if e.value == addr
         | 
| 83 | 
            +
                      doozers[we.path] = e.value
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                watch "/doozer/slot/*", &blk
         | 
| 89 | 
            +
                walk  "/doozer/slot/*", &blk
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              ##
         | 
| 93 | 
            +
              # Attempts to connect to another doozer when a connection is lost
         | 
| 94 | 
            +
              def unbind
         | 
| 95 | 
            +
                return if ! opts[:assemble]
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                _, @addr = doozers.shift
         | 
| 98 | 
            +
                if ! @addr
         | 
| 99 | 
            +
                  raise AssemblyError, "All known doozers are down"
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                host, port = @addr.split(":")
         | 
| 103 | 
            +
                reconnect(host, port)
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
             | 
| 107 | 
            +
             | 
| 108 | 
            +
             | 
| 109 | 
            +
              ##
         | 
| 110 | 
            +
              # Session generation
         | 
| 111 | 
            +
              def gen_key(name, size=16)
         | 
| 112 | 
            +
                nibbles = "0123456789abcdef"
         | 
| 113 | 
            +
                "#{name}." + (0...size).map { nibbles[rand(nibbles.length)].chr }.join
         | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
              def session(name="fraggle", &blk)
         | 
| 117 | 
            +
                raise ArgumentError, "no block given" if ! blk
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                id = gen_key(name)
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                fun = lambda do |e|
         | 
| 122 | 
            +
                  raise e.err_detail if ! e.ok?
         | 
| 123 | 
            +
                  checkin(e.cas, id, &fun)
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                established = lambda do |e|
         | 
| 127 | 
            +
                  case true
         | 
| 128 | 
            +
                  when e.mismatch?
         | 
| 129 | 
            +
                    id = gen_key(name)
         | 
| 130 | 
            +
                    checkin(0, id, &established)
         | 
| 131 | 
            +
                  when ! e.ok?
         | 
| 132 | 
            +
                    raise e.err_detail
         | 
| 133 | 
            +
                  else
         | 
| 134 | 
            +
                    blk.call
         | 
| 135 | 
            +
                    checkin(e.cas, id, &fun)
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                checkin(0, id, &established)
         | 
| 140 | 
            +
              end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
              def checkin(cas, id, &blk)
         | 
| 143 | 
            +
                call(
         | 
| 144 | 
            +
                  Request::Verb::CHECKIN,
         | 
| 145 | 
            +
                  :cas => casify(cas),
         | 
| 146 | 
            +
                  :path => id.to_s,
         | 
| 147 | 
            +
                  &blk
         | 
| 148 | 
            +
                )
         | 
| 149 | 
            +
              end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
              def post_init
         | 
| 152 | 
            +
                @buf = ""
         | 
| 153 | 
            +
                @tag = 0
         | 
| 154 | 
            +
                @cbx = {}
         | 
| 155 | 
            +
                @len = nil
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                assemble
         | 
| 158 | 
            +
              end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
              def receive_data(data)
         | 
| 161 | 
            +
                @buf << data
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                got = true
         | 
| 164 | 
            +
                while got
         | 
| 165 | 
            +
                  got = false
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  if @len.nil? && @buf.length >= 4
         | 
| 168 | 
            +
                    @len = @buf.slice!(0, 4).unpack("N").first
         | 
| 169 | 
            +
                  end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  if @len && @buf.length >= @len
         | 
| 172 | 
            +
                    bytes = @buf.slice!(0, @len)
         | 
| 173 | 
            +
                    res   = Response.decode(bytes)
         | 
| 174 | 
            +
                    receive_response(res)
         | 
| 175 | 
            +
                    @len = nil
         | 
| 176 | 
            +
                    got = true
         | 
| 177 | 
            +
                  end
         | 
| 178 | 
            +
                end
         | 
| 179 | 
            +
              end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
              def receive_response(res)
         | 
| 182 | 
            +
                blk = @cbx[res.tag]
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                if blk && res.valid?
         | 
| 185 | 
            +
                  if blk.arity == 2
         | 
| 186 | 
            +
                    blk.call(res, false)
         | 
| 187 | 
            +
                  else
         | 
| 188 | 
            +
                    blk.call(res)
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
                end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                if res.done?
         | 
| 193 | 
            +
                  if blk && blk.arity == 2
         | 
| 194 | 
            +
                    blk.call(nil, true)
         | 
| 195 | 
            +
                  end
         | 
| 196 | 
            +
                  @cbx.delete(res.tag)
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
              end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
              def call(verb, attrs={}, &blk)
         | 
| 201 | 
            +
                if @tag == MaxInt32
         | 
| 202 | 
            +
                  @tag = MinInt32
         | 
| 203 | 
            +
                end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                while true
         | 
| 206 | 
            +
                  break if ! @cbx.has_key?(@tag)
         | 
| 207 | 
            +
                  @tag += 1
         | 
| 208 | 
            +
                end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                attrs[:verb] = verb
         | 
| 211 | 
            +
                attrs[:tag]  = @tag
         | 
| 212 | 
            +
                @cbx[@tag]   = blk
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                send_request(Request.new(attrs))
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                @tag
         | 
| 217 | 
            +
              end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
              def send_request(req)
         | 
| 220 | 
            +
                buf = req.encode
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                send_data([buf.length].pack("N"))
         | 
| 223 | 
            +
                send_data(buf)
         | 
| 224 | 
            +
              end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
             | 
| 227 | 
            +
             | 
| 228 | 
            +
              ##
         | 
| 229 | 
            +
              # Sugar
         | 
| 230 | 
            +
              def get(path, sid=0, &blk)
         | 
| 231 | 
            +
                call(
         | 
| 232 | 
            +
                  Request::Verb::GET,
         | 
| 233 | 
            +
                  :path => path,
         | 
| 234 | 
            +
                  :id => sid,
         | 
| 235 | 
            +
                  &blk
         | 
| 236 | 
            +
                )
         | 
| 237 | 
            +
              end
         | 
| 238 | 
            +
             | 
| 239 | 
            +
              def set(path, body, cas, &blk)
         | 
| 240 | 
            +
                call(
         | 
| 241 | 
            +
                  Request::Verb::SET,
         | 
| 242 | 
            +
                  :path => path,
         | 
| 243 | 
            +
                  :value => body,
         | 
| 244 | 
            +
                  :cas => casify(cas),
         | 
| 245 | 
            +
                  &blk
         | 
| 246 | 
            +
                )
         | 
| 247 | 
            +
              end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
              def del(path, cas, &blk)
         | 
| 250 | 
            +
                call(
         | 
| 251 | 
            +
                  Request::Verb::DEL,
         | 
| 252 | 
            +
                  :path => path,
         | 
| 253 | 
            +
                  :cas  => casify(cas),
         | 
| 254 | 
            +
                  &blk
         | 
| 255 | 
            +
                )
         | 
| 256 | 
            +
              end
         | 
| 257 | 
            +
             | 
| 258 | 
            +
              def watch(glob, &blk)
         | 
| 259 | 
            +
                call(
         | 
| 260 | 
            +
                  Request::Verb::WATCH,
         | 
| 261 | 
            +
                  :path => glob,
         | 
| 262 | 
            +
                  &blk
         | 
| 263 | 
            +
                )
         | 
| 264 | 
            +
              end
         | 
| 265 | 
            +
             | 
| 266 | 
            +
              def walk(glob, &blk)
         | 
| 267 | 
            +
                call(
         | 
| 268 | 
            +
                  Request::Verb::WALK,
         | 
| 269 | 
            +
                  :path => glob,
         | 
| 270 | 
            +
                  &blk
         | 
| 271 | 
            +
                )
         | 
| 272 | 
            +
              end
         | 
| 273 | 
            +
             | 
| 274 | 
            +
              def snap(&blk)
         | 
| 275 | 
            +
                call(
         | 
| 276 | 
            +
                  Request::Verb::SNAP,
         | 
| 277 | 
            +
                  &blk
         | 
| 278 | 
            +
                )
         | 
| 279 | 
            +
              end
         | 
| 280 | 
            +
             | 
| 281 | 
            +
              def delsnap(id, &blk)
         | 
| 282 | 
            +
                call(
         | 
| 283 | 
            +
                  Request::Verb::DELSNAP,
         | 
| 284 | 
            +
                  :id => id,
         | 
| 285 | 
            +
                  &blk
         | 
| 286 | 
            +
                )
         | 
| 287 | 
            +
              end
         | 
| 288 | 
            +
             | 
| 289 | 
            +
              def noop(&blk)
         | 
| 290 | 
            +
                call(
         | 
| 291 | 
            +
                  Request::Verb::NOOP,
         | 
| 292 | 
            +
                  &blk
         | 
| 293 | 
            +
                )
         | 
| 294 | 
            +
              end
         | 
| 295 | 
            +
             | 
| 296 | 
            +
              def cancel(tag)
         | 
| 297 | 
            +
                blk = lambda do |e|
         | 
| 298 | 
            +
                  if e.ok?
         | 
| 299 | 
            +
                    if blk = @cbx.delete(tag)
         | 
| 300 | 
            +
                      blk.call(nil, true)
         | 
| 301 | 
            +
                    end
         | 
| 302 | 
            +
                  end
         | 
| 303 | 
            +
                end
         | 
| 304 | 
            +
             | 
| 305 | 
            +
                call(
         | 
| 306 | 
            +
                  Request::Verb::CANCEL,
         | 
| 307 | 
            +
                  :id => tag,
         | 
| 308 | 
            +
                  &blk
         | 
| 309 | 
            +
                )
         | 
| 310 | 
            +
              end
         | 
| 311 | 
            +
             | 
| 312 | 
            +
              private
         | 
| 313 | 
            +
             | 
| 314 | 
            +
              def casify(cas)
         | 
| 315 | 
            +
                case cas
         | 
| 316 | 
            +
                when :missing then 0
         | 
| 317 | 
            +
                when :clobber then -1
         | 
| 318 | 
            +
                else cas
         | 
| 319 | 
            +
                end
         | 
| 320 | 
            +
              end
         | 
| 321 | 
            +
             | 
| 322 | 
            +
            end
         | 
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            require 'beefcake'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Fraggle
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              class Request
         | 
| 6 | 
            +
                include Beefcake::Message
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                required :tag, :int32, 1
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                module Verb
         | 
| 11 | 
            +
                  CHECKIN  = 0;  # cas, id          => cas
         | 
| 12 | 
            +
                  GET      = 1;  # path, id         => cas, value
         | 
| 13 | 
            +
                  SET      = 2;  # cas, path, value => cas
         | 
| 14 | 
            +
                  DEL      = 3;  # cas, path        => {}
         | 
| 15 | 
            +
                  ESET     = 4;  # cas, path        => {}
         | 
| 16 | 
            +
                  SNAP     = 5;  # {}               => seqn, id
         | 
| 17 | 
            +
                  DELSNAP  = 6;  # id               => {}
         | 
| 18 | 
            +
                  NOOP     = 7;  # {}               => {}
         | 
| 19 | 
            +
                  WATCH    = 8;  # path             => {cas, path, value}+
         | 
| 20 | 
            +
                  CANCEL   = 10; # id               => {}
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  # future
         | 
| 23 | 
            +
                  GETDIR   = 14; # path             => {cas, value}+
         | 
| 24 | 
            +
                  MONITOR  = 11; # path             => {cas, path, value}+
         | 
| 25 | 
            +
                  SYNCPATH = 12; # path             => cas, value
         | 
| 26 | 
            +
                  WALK     = 9;  # path, id         => {cas, path, value}+
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # deprecated
         | 
| 29 | 
            +
                  JOIN     = 13;
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                required :verb, Verb, 2
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                optional :cas,   :int64,  3
         | 
| 35 | 
            +
                optional :path,  :string, 4
         | 
| 36 | 
            +
                optional :value, :bytes,  5
         | 
| 37 | 
            +
                optional :id,    :int32,  6
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                optional :offset, :int32, 7
         | 
| 40 | 
            +
                optional :limit,  :int32, 8
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
             | 
| 45 | 
            +
              class Response
         | 
| 46 | 
            +
                include Beefcake::Message
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                required :tag,   :int32, 1
         | 
| 49 | 
            +
                required :flags, :int32, 2
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                optional :seqn,  :int64,  3
         | 
| 52 | 
            +
                optional :cas,   :int64,  4
         | 
| 53 | 
            +
                optional :path,  :string, 5
         | 
| 54 | 
            +
                optional :value, :bytes,  6
         | 
| 55 | 
            +
                optional :id,    :int32,  7
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                module Err
         | 
| 58 | 
            +
                  # don't use value 0
         | 
| 59 | 
            +
                  OTHER        = 127
         | 
| 60 | 
            +
                  TAG_IN_USE   = 1
         | 
| 61 | 
            +
                  UNKNOWN_VERB = 2
         | 
| 62 | 
            +
                  REDIRECT     = 3
         | 
| 63 | 
            +
                  INVALID_SNAP = 4
         | 
| 64 | 
            +
                  CAS_MISMATCH = 5
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  # match unix errno
         | 
| 67 | 
            +
                  NOTDIR       = 20
         | 
| 68 | 
            +
                  ISDIR        = 21
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                optional :err_code,   Err,     100
         | 
| 72 | 
            +
                optional :err_detail, :string, 101
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            end
         | 
    
        data/test/core_test.rb
    ADDED
    
    | @@ -0,0 +1,215 @@ | |
| 1 | 
            +
            require 'fraggle'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ##
         | 
| 4 | 
            +
            # This is used to test core functionality that the live integration tests will
         | 
| 5 | 
            +
            # rely on.
         | 
| 6 | 
            +
            class FakeConn
         | 
| 7 | 
            +
              include Fraggle
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              attr_reader   :sent, :cbx
         | 
| 10 | 
            +
              attr_accessor :tag
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def initialize
         | 
| 13 | 
            +
                @sent = ""
         | 
| 14 | 
            +
                super("127.0.0.1", :assemble => false)
         | 
| 15 | 
            +
                post_init
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def send_data(data)
         | 
| 19 | 
            +
                @sent << data
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            class CoreTest < Test::Unit::TestCase
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              attr_reader :c
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              V = Fraggle::Request::Verb
         | 
| 28 | 
            +
              F = Fraggle::Response::Flag
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def setup
         | 
| 31 | 
            +
                @c = FakeConn.new
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def reply(attrs={})
         | 
| 35 | 
            +
                attrs[:tag] = c.tag
         | 
| 36 | 
            +
                attrs[:flags] ||= 0
         | 
| 37 | 
            +
                attrs[:flags] |= F::VALID
         | 
| 38 | 
            +
                res = Fraggle::Response.new(attrs)
         | 
| 39 | 
            +
                c.receive_response(res)
         | 
| 40 | 
            +
                res
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              def reply!(attrs={})
         | 
| 44 | 
            +
                attrs[:flags] = F::DONE
         | 
| 45 | 
            +
                reply(attrs)
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              def test_sending_data
         | 
| 49 | 
            +
                c.call(V::NOOP)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                req = Fraggle::Request.new(
         | 
| 52 | 
            +
                  :tag   => c.tag,
         | 
| 53 | 
            +
                  :verb  => V::NOOP
         | 
| 54 | 
            +
                )
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                buf = req.encode
         | 
| 57 | 
            +
                pre = [buf.length].pack("N")
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                assert_equal pre+buf, c.sent
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
              def test_receive_small_buffered_data
         | 
| 63 | 
            +
                count = 0
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                tag = c.call(V::WATCH, :path => "**") do |e|
         | 
| 66 | 
            +
                  count += 1
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                res = Fraggle::Response.new(
         | 
| 70 | 
            +
                  :tag   => tag,
         | 
| 71 | 
            +
                  :flags => F::VALID
         | 
| 72 | 
            +
                )
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                exp   = 10
         | 
| 75 | 
            +
                buf   = res.encode
         | 
| 76 | 
            +
                pre   = [buf.length].pack("N")
         | 
| 77 | 
            +
                bytes = (pre+buf)*exp
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                # Chunk bytes to receive_data in some arbitrary size
         | 
| 80 | 
            +
                0.step(bytes.length, 3) do |n|
         | 
| 81 | 
            +
                  c.receive_data(bytes.slice!(0, n))
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                assert_equal 10, count
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              def test_receive_large_buffered_data
         | 
| 88 | 
            +
                count = 0
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                tag = c.call(V::WATCH, :path => "**") do |e|
         | 
| 91 | 
            +
                  count += 1
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                res = Fraggle::Response.new(
         | 
| 95 | 
            +
                  :tag   => tag,
         | 
| 96 | 
            +
                  :flags => F::VALID
         | 
| 97 | 
            +
                )
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                exp   = 10
         | 
| 100 | 
            +
                buf   = res.encode
         | 
| 101 | 
            +
                pre   = [buf.length].pack("N")
         | 
| 102 | 
            +
                bytes = (pre+buf)*exp
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                c.receive_data(bytes)
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                assert_equal 10, count
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
              def test_callback_without_done
         | 
| 110 | 
            +
                valid = lambda do |e|
         | 
| 111 | 
            +
                  assert_kind_of Fraggle::Response, e
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                done = lambda do |e|
         | 
| 115 | 
            +
                  assert false, "Unreachable"
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                tests = [valid, done]
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                c.call(V::NOOP) do |e|
         | 
| 121 | 
            +
                  tests.shift.call(e)
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
                reply!
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                assert_equal 1, tests.length
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
              def test_callback_with_done
         | 
| 129 | 
            +
                valid = lambda do |e, done|
         | 
| 130 | 
            +
                  assert_kind_of Fraggle::Response, e
         | 
| 131 | 
            +
                  assert_equal false, done
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                done = lambda do |e, done|
         | 
| 135 | 
            +
                  assert_nil e
         | 
| 136 | 
            +
                  assert_equal true, done
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                tests = [valid, done]
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                c.call(V::NOOP) do |e, done|
         | 
| 142 | 
            +
                  tests.shift.call(e, done)
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                reply!
         | 
| 146 | 
            +
                assert tests.empty?
         | 
| 147 | 
            +
              end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
              def test_no_callback
         | 
| 150 | 
            +
                c.call(V::NOOP)
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                assert_nothing_raised do
         | 
| 153 | 
            +
                  reply!
         | 
| 154 | 
            +
                end
         | 
| 155 | 
            +
              end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
              def test_no_callback_gc
         | 
| 158 | 
            +
                c.call(V::NOOP)
         | 
| 159 | 
            +
                reply!
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                assert ! c.cbx.has_key?(1)
         | 
| 162 | 
            +
              end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
              def test_callback_gc
         | 
| 165 | 
            +
                c.call(V::NOOP) {}
         | 
| 166 | 
            +
                reply
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                assert c.cbx.has_key?(c.tag)
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                reply!
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                assert ! c.cbx.has_key?(c.tag)
         | 
| 173 | 
            +
              end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
              def test_call_returns_tag
         | 
| 176 | 
            +
                assert_equal 0, c.call(V::NOOP)
         | 
| 177 | 
            +
                assert_equal 1, c.call(V::NOOP)
         | 
| 178 | 
            +
              end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
              def test_call_increments_tag
         | 
| 181 | 
            +
                c.call(V::NOOP)
         | 
| 182 | 
            +
                assert_equal 0, c.tag
         | 
| 183 | 
            +
                c.call(V::NOOP)
         | 
| 184 | 
            +
                assert_equal 1, c.tag
         | 
| 185 | 
            +
                c.call(V::NOOP)
         | 
| 186 | 
            +
                assert_equal 2, c.tag
         | 
| 187 | 
            +
                c.call(V::NOOP)
         | 
| 188 | 
            +
                assert_equal 3, c.tag
         | 
| 189 | 
            +
                c.call(V::NOOP)
         | 
| 190 | 
            +
                assert_equal 4, c.tag
         | 
| 191 | 
            +
                c.call(V::NOOP)
         | 
| 192 | 
            +
                assert_equal 5, c.tag
         | 
| 193 | 
            +
                c.call(V::NOOP)
         | 
| 194 | 
            +
                assert_equal 6, c.tag
         | 
| 195 | 
            +
                c.call(V::NOOP)
         | 
| 196 | 
            +
                assert_equal 7, c.tag
         | 
| 197 | 
            +
                c.call(V::NOOP)
         | 
| 198 | 
            +
                assert_equal 8, c.tag
         | 
| 199 | 
            +
                c.call(V::NOOP)
         | 
| 200 | 
            +
                assert_equal 9, c.tag
         | 
| 201 | 
            +
              end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
              def test_no_overlap_in_tags
         | 
| 204 | 
            +
                c.cbx[0] = Proc.new {}
         | 
| 205 | 
            +
                assert_equal 1, c.call(V::NOOP)
         | 
| 206 | 
            +
              end
         | 
| 207 | 
            +
             | 
| 208 | 
            +
              def test_rollover_tag_when_maxed_out
         | 
| 209 | 
            +
                c.tag = Fraggle::MaxInt32
         | 
| 210 | 
            +
                c.call(V::NOOP)
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                assert_equal  Fraggle::MinInt32, c.tag
         | 
| 213 | 
            +
              end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
            end
         | 
    
        data/test/live_test.rb
    ADDED
    
    | @@ -0,0 +1,197 @@ | |
| 1 | 
            +
            require 'fraggle'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class LiveTest < Test::Unit::TestCase
         | 
| 4 | 
            +
              def start(timeout=1, &blk)
         | 
| 5 | 
            +
                EM.run do
         | 
| 6 | 
            +
                  if timeout > 0
         | 
| 7 | 
            +
                    EM.add_timer(timeout) { fail "Test timeout!" }
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  c = Fraggle.connect(
         | 
| 11 | 
            +
                    "127.0.0.1:8046",
         | 
| 12 | 
            +
                    :assemble => false
         | 
| 13 | 
            +
                  )
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  blk.call(c)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def stop
         | 
| 20 | 
            +
                EM.stop
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def test_get
         | 
| 24 | 
            +
                start do |c|
         | 
| 25 | 
            +
                  c.get "/ping" do |e|
         | 
| 26 | 
            +
                    assert e.ok?, e.err_detail
         | 
| 27 | 
            +
                    assert e.cas > 0
         | 
| 28 | 
            +
                    assert_equal "pong", e.value
         | 
| 29 | 
            +
                    stop
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def test_set
         | 
| 35 | 
            +
                start do |c|
         | 
| 36 | 
            +
                  c.set "/test-set", "a", :clobber do |ea|
         | 
| 37 | 
            +
                    assert ea.ok?, ea.err_detail
         | 
| 38 | 
            +
                    assert ea.cas > 0
         | 
| 39 | 
            +
                    assert_nil ea.value
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    c.get "/test-set" do |eb|
         | 
| 42 | 
            +
                      assert eb.ok?, eb.err_detail
         | 
| 43 | 
            +
                      assert_equal "a", eb.value
         | 
| 44 | 
            +
                      stop
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def test_del
         | 
| 51 | 
            +
                start do |c|
         | 
| 52 | 
            +
                  c.set "/test-del", "a", :clobber do |e|
         | 
| 53 | 
            +
                    assert e.ok?, e.err_detail
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    c.del("/test-del", e.cas) do |de|
         | 
| 56 | 
            +
                      assert de.ok?, de.err_detail
         | 
| 57 | 
            +
                      stop
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              def test_error
         | 
| 64 | 
            +
                start do |c|
         | 
| 65 | 
            +
                  c.set "/test-error", "a", :clobber do |ea|
         | 
| 66 | 
            +
                    assert ! ea.mismatch?
         | 
| 67 | 
            +
                    assert ea.ok?, ea.err_detail
         | 
| 68 | 
            +
                    c.set "/test-error", "b", :missing do |eb|
         | 
| 69 | 
            +
                      assert eb.mismatch?, eb.err_detail
         | 
| 70 | 
            +
                      stop
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              def test_watch
         | 
| 77 | 
            +
                start do |c|
         | 
| 78 | 
            +
                  count = 0
         | 
| 79 | 
            +
                  c.watch("/**") do |e|
         | 
| 80 | 
            +
                    assert e.ok?, e.err_detail
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    count += 1
         | 
| 83 | 
            +
                    if count == 9
         | 
| 84 | 
            +
                      stop
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  10.times do
         | 
| 89 | 
            +
                    EM.next_tick { c.set("/test-watch", "something", :clobber) }
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              def test_snap
         | 
| 95 | 
            +
                start do |c|
         | 
| 96 | 
            +
                  c.set "/test-snap", "a", :clobber do |e|
         | 
| 97 | 
            +
                    assert e.ok?, e.err_detail
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    c.snap do |se|
         | 
| 100 | 
            +
                      assert se.ok?, se.err_detail
         | 
| 101 | 
            +
                      assert_not_equal 0, se.id
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                      c.set "/test-snap", "b", :clobber do |e|
         | 
| 104 | 
            +
                        assert e.ok?, e.err_detail
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                        c.get "/test-snap", se.id do |ge|
         | 
| 107 | 
            +
                          assert ge.ok?, ge.err_detail
         | 
| 108 | 
            +
                          assert_equal "a", ge.value
         | 
| 109 | 
            +
                          stop
         | 
| 110 | 
            +
                        end
         | 
| 111 | 
            +
                      end
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
              # TODO:  ???  Shouldn't a deleted snapid produce an error on read?
         | 
| 118 | 
            +
              def test_delsnap
         | 
| 119 | 
            +
                start do |c|
         | 
| 120 | 
            +
                  c.snap do |se|
         | 
| 121 | 
            +
                    assert se.ok?, se.err_detail
         | 
| 122 | 
            +
                    assert_not_equal 0, se.id
         | 
| 123 | 
            +
             | 
| 124 | 
            +
             | 
| 125 | 
            +
                    c.delsnap se.id do |de|
         | 
| 126 | 
            +
                      assert de.ok?, de.err_detail
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                      c.get "/ping", se.id do |ge|
         | 
| 129 | 
            +
                        assert ! ge.ok?, ge.err_detail
         | 
| 130 | 
            +
                        stop
         | 
| 131 | 
            +
                      end
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
              end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
              def test_noop
         | 
| 138 | 
            +
                start do |c|
         | 
| 139 | 
            +
                  c.noop do |e|
         | 
| 140 | 
            +
                    assert e.ok?, e.err_detail
         | 
| 141 | 
            +
                    stop
         | 
| 142 | 
            +
                  end
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              def test_cancel
         | 
| 147 | 
            +
                start do |c|
         | 
| 148 | 
            +
                  tag = c.watch("/test-cancel") do |e, done|
         | 
| 149 | 
            +
                    if ! done
         | 
| 150 | 
            +
                      assert e.ok?, e.err_detail
         | 
| 151 | 
            +
                    end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    if done
         | 
| 154 | 
            +
                      stop
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    c.cancel(tag)
         | 
| 158 | 
            +
                  end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                  c.set("/test-cancel", "a", :clobber)
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
              end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
              def test_walk
         | 
| 165 | 
            +
                start do |c|
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  exp = [
         | 
| 168 | 
            +
                    ["/test-walk/1", "a"],
         | 
| 169 | 
            +
                    ["/test-walk/2", "b"],
         | 
| 170 | 
            +
                    ["/test-walk/3", "c"]
         | 
| 171 | 
            +
                  ]
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                  n = exp.length
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  exp.each do |path, val|
         | 
| 176 | 
            +
                    c.set path, val, :clobber do |e|
         | 
| 177 | 
            +
                      assert e.ok?, e.err_detail
         | 
| 178 | 
            +
                      n -= 1
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                      if n == 0
         | 
| 181 | 
            +
                        items = []
         | 
| 182 | 
            +
                        c.walk "/test-walk/*" do |e, done|
         | 
| 183 | 
            +
                          if done
         | 
| 184 | 
            +
                            assert_equal exp, items
         | 
| 185 | 
            +
                            stop
         | 
| 186 | 
            +
                          else
         | 
| 187 | 
            +
                            items << [e.path, e.value]
         | 
| 188 | 
            +
                          end
         | 
| 189 | 
            +
                        end
         | 
| 190 | 
            +
                      end
         | 
| 191 | 
            +
                    end
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                end
         | 
| 195 | 
            +
              end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
            end
         | 
| @@ -0,0 +1,175 @@ | |
| 1 | 
            +
            require 'fraggle'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class RecConn
         | 
| 4 | 
            +
              include Fraggle
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              attr_reader :store, :recs
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def initialize
         | 
| 9 | 
            +
                super("1:1", :assemble => true)
         | 
| 10 | 
            +
                post_init
         | 
| 11 | 
            +
                @store = {}
         | 
| 12 | 
            +
                @recs  = []
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def get(path, sid=0, &blk)
         | 
| 16 | 
            +
                res = store.fetch(path) { fail("testing: no slot for #{path}") }
         | 
| 17 | 
            +
                blk.call(res)
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def reconnect(host, port)
         | 
| 21 | 
            +
                @recs << [host, port]
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def send_data(data)
         | 
| 25 | 
            +
                # do nothing
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            class ReconnectTest < Test::Unit::TestCase
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              Walk  = 0
         | 
| 32 | 
            +
              Watch = 1
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              attr_reader :c
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              def setup
         | 
| 37 | 
            +
                @c = RecConn.new
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def reply(to, path, value)
         | 
| 41 | 
            +
                res = Fraggle::Response.new
         | 
| 42 | 
            +
                res.tag   = to
         | 
| 43 | 
            +
                res.flags = Fraggle::Response::Flag::VALID
         | 
| 44 | 
            +
                res.path  = path
         | 
| 45 | 
            +
                res.value = value
         | 
| 46 | 
            +
                res.validate!
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                c.receive_response(res)
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              def set(path, value)
         | 
| 52 | 
            +
                res = Fraggle::Response.new
         | 
| 53 | 
            +
                res.tag   = 123
         | 
| 54 | 
            +
                res.flags = Fraggle::Response::Flag::VALID
         | 
| 55 | 
            +
                res.value = value
         | 
| 56 | 
            +
                res.validate!
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                c.store[path] = res
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              def test_ignore_current
         | 
| 62 | 
            +
                assert_equal Hash.new, c.doozers
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                set "/doozer/info/ABC/public-addr", "1:1"
         | 
| 65 | 
            +
                reply(Walk, "/doozer/slot/1", "ABC")
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                assert_equal Hash.new, c.doozers
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              def test_add_other_slots_at_start
         | 
| 71 | 
            +
                set "/doozer/info/DEF/public-addr", "2:2"
         | 
| 72 | 
            +
                set "/doozer/info/GHI/public-addr", "3:3"
         | 
| 73 | 
            +
                reply(Walk, "/doozer/slot/2", "DEF")
         | 
| 74 | 
            +
                reply(Walk, "/doozer/slot/3", "GHI")
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                exp = {
         | 
| 77 | 
            +
                  "/doozer/slot/2" => "2:2",
         | 
| 78 | 
            +
                  "/doozer/slot/3" => "3:3"
         | 
| 79 | 
            +
                }
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                assert_equal exp, c.doozers
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              def test_add_new_slots_as_they_come
         | 
| 85 | 
            +
                set "/doozer/info/DEF/public-addr", "2:2"
         | 
| 86 | 
            +
                set "/doozer/info/GHI/public-addr", "3:3"
         | 
| 87 | 
            +
                reply(Watch, "/doozer/slot/2", "DEF")
         | 
| 88 | 
            +
                reply(Watch, "/doozer/slot/3", "GHI")
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                exp = {
         | 
| 91 | 
            +
                  "/doozer/slot/2" => "2:2",
         | 
| 92 | 
            +
                  "/doozer/slot/3" => "3:3"
         | 
| 93 | 
            +
                }
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                assert_equal exp, c.doozers
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
              def test_del_slots_if_they_emptied
         | 
| 99 | 
            +
                set "/doozer/info/DEF/public-addr", "2:2"
         | 
| 100 | 
            +
                set "/doozer/info/GHI/public-addr", "3:3"
         | 
| 101 | 
            +
                reply(Walk, "/doozer/slot/2", "DEF")
         | 
| 102 | 
            +
                reply(Walk, "/doozer/slot/3", "GHI")
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                # Del
         | 
| 105 | 
            +
                reply(Watch, "/doozer/slot/3", "")
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                exp = {
         | 
| 108 | 
            +
                  "/doozer/slot/2" => "2:2"
         | 
| 109 | 
            +
                }
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                assert_equal exp, c.doozers
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              def test_raise_error_if_given_by_server
         | 
| 115 | 
            +
                res = Fraggle::Response.new
         | 
| 116 | 
            +
                res.tag        = Walk
         | 
| 117 | 
            +
                res.flags      = Fraggle::Response::Flag::VALID
         | 
| 118 | 
            +
                res.err_code   = Fraggle::Response::Err::OTHER
         | 
| 119 | 
            +
                res.err_detail = "invalid glob"
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                assert_raises Fraggle::AssemblyError do
         | 
| 122 | 
            +
                  c.receive_response(res)
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
              end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
              def test_out_of_doozers
         | 
| 127 | 
            +
                assert_raises Fraggle::AssemblyError do
         | 
| 128 | 
            +
                  c.unbind
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
              end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
              def test_first_reconnect_success
         | 
| 133 | 
            +
                set "/doozer/info/DEF/public-addr", "2:2"
         | 
| 134 | 
            +
                set "/doozer/info/GHI/public-addr", "3:3"
         | 
| 135 | 
            +
                reply(Walk, "/doozer/slot/2", "DEF")
         | 
| 136 | 
            +
                reply(Walk, "/doozer/slot/3", "GHI")
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                c.unbind
         | 
| 139 | 
            +
                assert_equal 1, c.recs.length
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                # The order in which the client try is non-detrministic because we're
         | 
| 142 | 
            +
                # shifting off a Hash.
         | 
| 143 | 
            +
                assert ["2:2", "3:3"].include?(c.addr)
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              def test_second_reconnect_success
         | 
| 147 | 
            +
                set "/doozer/info/DEF/public-addr", "2:2"
         | 
| 148 | 
            +
                set "/doozer/info/GHI/public-addr", "3:3"
         | 
| 149 | 
            +
                reply(Walk, "/doozer/slot/2", "DEF")
         | 
| 150 | 
            +
                reply(Walk, "/doozer/slot/3", "GHI")
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                c.unbind
         | 
| 153 | 
            +
                c.unbind
         | 
| 154 | 
            +
                assert_equal 2, c.recs.length
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                # The order in which the client try is non-detrministic because we're
         | 
| 157 | 
            +
                # shifting off a Hash.
         | 
| 158 | 
            +
                assert ["2:2", "3:3"].include?(c.addr)
         | 
| 159 | 
            +
              end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
              def test_all_recconcts_fail
         | 
| 162 | 
            +
                set "/doozer/info/DEF/public-addr", "2:2"
         | 
| 163 | 
            +
                set "/doozer/info/GHI/public-addr", "3:3"
         | 
| 164 | 
            +
                reply(Walk, "/doozer/slot/2", "DEF")
         | 
| 165 | 
            +
                reply(Walk, "/doozer/slot/3", "GHI")
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                c.unbind
         | 
| 168 | 
            +
                c.unbind
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                assert_raises Fraggle::AssemblyError do
         | 
| 171 | 
            +
                  c.unbind
         | 
| 172 | 
            +
                end
         | 
| 173 | 
            +
              end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,89 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            name: fraggle
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            +
              prerelease: false
         | 
| 5 | 
            +
              segments: 
         | 
| 6 | 
            +
              - 0
         | 
| 7 | 
            +
              - 1
         | 
| 8 | 
            +
              - 0
         | 
| 9 | 
            +
              version: 0.1.0
         | 
| 10 | 
            +
            platform: ruby
         | 
| 11 | 
            +
            authors: 
         | 
| 12 | 
            +
            - Blake Mizerany
         | 
| 13 | 
            +
            autorequire: 
         | 
| 14 | 
            +
            bindir: bin
         | 
| 15 | 
            +
            cert_chain: []
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            date: 2011-01-25 00:00:00 -08:00
         | 
| 18 | 
            +
            default_executable: 
         | 
| 19 | 
            +
            dependencies: 
         | 
| 20 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 21 | 
            +
              name: beefcake
         | 
| 22 | 
            +
              prerelease: false
         | 
| 23 | 
            +
              requirement: &id001 !ruby/object:Gem::Requirement 
         | 
| 24 | 
            +
                requirements: 
         | 
| 25 | 
            +
                - - ">="
         | 
| 26 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 27 | 
            +
                    segments: 
         | 
| 28 | 
            +
                    - 0
         | 
| 29 | 
            +
                    - 1
         | 
| 30 | 
            +
                    - 1
         | 
| 31 | 
            +
                    version: 0.1.1
         | 
| 32 | 
            +
              type: :runtime
         | 
| 33 | 
            +
              version_requirements: *id001
         | 
| 34 | 
            +
            description: A Ruby/EventMachine Client for Doozer
         | 
| 35 | 
            +
            email: 
         | 
| 36 | 
            +
            executables: []
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            extensions: []
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            extra_rdoc_files: 
         | 
| 41 | 
            +
            - README.md
         | 
| 42 | 
            +
            - LICENSE
         | 
| 43 | 
            +
            files: 
         | 
| 44 | 
            +
            - LICENSE
         | 
| 45 | 
            +
            - README.md
         | 
| 46 | 
            +
            - lib/fraggle/proto.rb
         | 
| 47 | 
            +
            - lib/fraggle.rb
         | 
| 48 | 
            +
            - test/core_test.rb
         | 
| 49 | 
            +
            - test/live_test.rb
         | 
| 50 | 
            +
            - test/reconnect_test.rb
         | 
| 51 | 
            +
            has_rdoc: true
         | 
| 52 | 
            +
            homepage: http://github.com/bmizerany/fraggle
         | 
| 53 | 
            +
            licenses: []
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            post_install_message: 
         | 
| 56 | 
            +
            rdoc_options: 
         | 
| 57 | 
            +
            - --line-numbers
         | 
| 58 | 
            +
            - --inline-source
         | 
| 59 | 
            +
            - --title
         | 
| 60 | 
            +
            - Sinatra
         | 
| 61 | 
            +
            - --main
         | 
| 62 | 
            +
            - README.rdoc
         | 
| 63 | 
            +
            require_paths: 
         | 
| 64 | 
            +
            - lib
         | 
| 65 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         | 
| 66 | 
            +
              requirements: 
         | 
| 67 | 
            +
              - - ">="
         | 
| 68 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 69 | 
            +
                  segments: 
         | 
| 70 | 
            +
                  - 0
         | 
| 71 | 
            +
                  version: "0"
         | 
| 72 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         | 
| 73 | 
            +
              requirements: 
         | 
| 74 | 
            +
              - - ">="
         | 
| 75 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 76 | 
            +
                  segments: 
         | 
| 77 | 
            +
                  - 0
         | 
| 78 | 
            +
                  version: "0"
         | 
| 79 | 
            +
            requirements: []
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            rubyforge_project: fraggle
         | 
| 82 | 
            +
            rubygems_version: 1.3.6
         | 
| 83 | 
            +
            signing_key: 
         | 
| 84 | 
            +
            specification_version: 2
         | 
| 85 | 
            +
            summary: A Ruby/EventMachine Client for Doozer
         | 
| 86 | 
            +
            test_files: 
         | 
| 87 | 
            +
            - test/core_test.rb
         | 
| 88 | 
            +
            - test/live_test.rb
         | 
| 89 | 
            +
            - test/reconnect_test.rb
         |