1 # -*- encoding: binary -*-
7 require 'mogilefs/backend'
9 class MogileFS::Backend
12 attr_reader :timeout, :dead
13 attr_writer :lasterr, :lasterrstr, :socket
17 class TestBackend < Test::Unit::TestCase
20 @backend = MogileFS::Backend.new :hosts => ['localhost:1']
24 assert_raises ArgumentError do MogileFS::Backend.new end
25 assert_raises ArgumentError do MogileFS::Backend.new :hosts => [] end
26 assert_raises ArgumentError do MogileFS::Backend.new :hosts => [''] end
28 assert_equal ['localhost:1'], @backend.hosts
29 assert_equal 3, @backend.timeout
30 assert_equal nil, @backend.lasterr
31 assert_equal nil, @backend.lasterrstr
32 assert_equal({}, @backend.dead)
34 @backend = MogileFS::Backend.new :hosts => ['localhost:6001'], :timeout => 1
35 assert_equal 1, @backend.timeout
39 received = Tempfile.new('received')
40 tmp = TempServer.new(Proc.new do |serv, port|
41 client, client_addr = serv.accept
43 received.syswrite(client.recv(4096))
44 client.send "OK 1 you=win\r\n", 0
47 @backend.hosts = [ "127.0.0.1:#{tmp.port}" ]
49 assert_equal({'you' => 'win'},
50 @backend.do_request('go!', { 'fight' => 'team fight!' }))
52 assert_equal "go! fight=team+fight%21\r\n", received.sysread(4096)
54 TempServer.destroy_all!
57 def test_do_request_send_error
60 def socket.closed?() false end
61 def socket.write(request) raise SystemCallError, 'dummy' end
63 @backend.instance_variable_set '@socket', socket
65 assert_raises MogileFS::UnreachableBackendError do
66 @backend.do_request 'go!', { 'fight' => 'team fight!' }
69 assert_equal nil, @backend.instance_variable_get('@socket')
72 def test_automatic_exception
73 assert ! MogileFS::Backend.const_defined?('PebkacError')
74 assert @backend.error('pebkac')
75 assert_equal MogileFS::Error, @backend.error('PebkacError').superclass
76 assert MogileFS::Backend.const_defined?('PebkacError')
78 assert ! MogileFS::Backend.const_defined?('PebKacError')
79 assert @backend.error('peb_kac')
80 assert_equal MogileFS::Error, @backend.error('PebKacError').superclass
81 assert MogileFS::Backend.const_defined?('PebKacError')
84 def test_size_verify_error_defined
85 # "ErrorError" just looks dumb, but we used to get it
86 # since mogilefs would send us "size_verify_error" and we'd
87 # blindly append "Error" to the exception
88 assert ! MogileFS::Backend.const_defined?('SizeVerifyErrorError')
89 assert MogileFS::Backend.const_defined?('SizeVerifyError')
92 def test_do_request_truncated
95 def socket.closed?() false end
96 def socket.write(request) return request.length - 1 end
98 @backend.instance_variable_set '@socket', socket
100 assert_raises MogileFS::RequestTruncatedError do
101 @backend.do_request 'go!', { 'fight' => 'team fight!' }
105 def test_make_request
106 assert_equal "go! fight=team+fight%21\r\n",
107 @backend.make_request('go!', { 'fight' => 'team fight!' })
110 def test_parse_response
111 assert_equal({'foo' => 'bar', 'baz' => 'hoge'},
112 @backend.parse_response('OK 1 foo=bar&baz=hoge'))
116 @backend.parse_response('ERR you totally suck')
117 rescue MogileFS::Error => err
118 assert_equal 'MogileFS::Backend::YouError', err.class.to_s
119 assert_equal 'totally suck', err.message
121 assert_equal 'MogileFS::Backend::YouError', err.class.to_s
123 assert_equal 'you', @backend.lasterr
124 assert_equal 'totally suck', @backend.lasterrstr
126 assert_raises MogileFS::InvalidResponseError do
127 @backend.parse_response 'garbage'
131 def test_parse_response_newline
133 @backend.parse_response("ERR you totally suck\r\n")
134 rescue MogileFS::Error => err
135 assert_equal 'MogileFS::Backend::YouError', err.class.to_s
136 assert_equal 'totally suck', err.message
139 assert_equal 'you', @backend.lasterr
140 assert_equal 'totally suck', @backend.lasterrstr
143 def test_readable_eh_not_readable
144 tmp = TempServer.new(Proc.new { |serv,port| serv.accept; sleep })
145 @backend = MogileFS::Backend.new(:hosts => [ "127.0.0.1:#{tmp.port}" ],
148 @backend.do_request 'foo', {}
149 rescue MogileFS::UnreadableSocketError => e
150 assert_equal "127.0.0.1:#{tmp.port} never became readable", e.message
151 rescue Exception => err
152 flunk "MogileFS::UnreadableSocketError not raised #{err} #{err.backtrace}"
154 flunk "MogileFS::UnreadableSocketError not raised"
156 TempServer.destroy_all!
161 assert_equal({}, @backend.dead)
162 assert_raises MogileFS::UnreachableBackendError do @backend.socket end
163 assert_equal(['localhost:1'], @backend.dead.keys)
166 def test_socket_robust
167 bad_accept = Tempfile.new('bad_accept')
168 accept = Tempfile.new('accept')
169 bad = Proc.new do |serv,port|
170 client, client_addr = serv.accept
171 bad_accept.syswrite('!')
173 good = Proc.new do |serv,port|
174 client, client_addr = serv.accept
184 t1 = TempServer.new(bad, ENV['TEST_DEAD_PORT'])
185 t2 = TempServer.new(good)
186 hosts = ["127.0.0.1:#{t1.port}", "127.0.0.1:#{t2.port}"]
187 @backend = MogileFS::Backend.new(:hosts => hosts.dup)
188 assert_equal({}, @backend.dead)
189 old_chld_handler = trap('CHLD', 'DEFAULT')
191 Process.waitpid(t1.pid)
192 trap('CHLD', old_chld_handler)
193 sock = @backend.socket
194 assert_equal Socket, sock.class
195 port = Socket.unpack_sockaddr_in(sock.getpeername).first
196 # p [ 'ports', "port=#{port}", "t1=#{t1.port}", "t2=#{t2.port}" ]
197 assert_equal t2.port, port
199 assert_equal '.', sock.sysread(1)
201 TempServer.destroy_all!
204 assert_equal 0, bad_accept.stat.size
205 assert_equal nr, accept.stat.size
210 tmp = TempServer.new(Proc.new do |serv,port|
211 client, client_addr = serv.accept
213 r = IO.select([client], [client])
214 client.syswrite(accept_nr.to_s)
217 @backend = MogileFS::Backend.new :hosts => [ "127.0.0.1:#{tmp.port}" ]
218 assert @backend.socket
219 assert ! @backend.socket.closed?
220 IO.select([@backend.socket])
221 resp = @backend.socket.sysread(4096)
223 assert_equal nil, @backend.instance_variable_get(:@socket)
224 assert_equal 1, resp.to_i
226 TempServer.destroy_all!
230 assert_equal({"\272z" => "\360opy", "f\000" => "\272r"},
231 @backend.url_decode("%baz=%f0opy&f%00=%bar"))
232 assert_equal({}, @backend.url_decode(''))
236 params = [["f\000", "\272r"], ["\272z", "\360opy"]]
237 assert_equal "f%00=%bar&%baz=%f0opy", @backend.url_encode(params)
240 def test_url_escape # \n for unit_diff
241 actual = (0..255).map { |c| @backend.url_escape c.chr }.join "\n"
244 expected.push(*(0..0x1f).map { |c| "%%%0.2x" % c })
246 expected.push(*(0x21..0x2b).map { |c| "%%%0.2x" % c })
247 expected.push(*%w[, - . /])
248 expected.push(*('0'..'9'))
249 expected.push(*%w[: %3b %3c %3d %3e %3f %40])
250 expected.push(*('A'..'Z'))
251 expected.push(*%w[%5b \\ %5d %5e _ %60])
252 expected.push(*('a'..'z'))
253 expected.push(*(0x7b..0xff).map { |c| "%%%0.2x" % c })
255 expected = expected.join "\n"
257 assert_equal expected, actual
260 def test_url_unescape
262 input.push(*(0..0x1f).map { |c| "%%%0.2x" % c })
264 input.push(*(0x21..0x2b).map { |c| "%%%0.2x" % c })
265 input.push(*%w[, - . /])
266 input.push(*('0'..'9'))
267 input.push(*%w[: %3b %3c %3d %3e %3f %40])
268 input.push(*('A'..'Z'))
269 input.push(*%w[%5b \\ %5d %5e _ %60])
270 input.push(*('a'..'z'))
271 input.push(*(0x7b..0xff).map { |c| "%%%0.2x" % c })
273 actual = input.map { |c| @backend.url_unescape c }.join "\n"
275 expected = (0..255).map { |c| c.chr }.join "\n"
276 expected.sub! '+', ' '
278 assert_equal expected, actual