mogilefs-client 2.0.2
[ruby-mogilefs-client.git] / test / test_backend.rb
blobaedce2e23b3ad4eb2f9e57c09d94b7694c1fc438
1 require 'test/unit'
2 require 'test/setup'
4 $TESTING = true
6 require 'mogilefs/backend'
8 class MogileFS::Backend
10   attr_accessor :hosts
11   attr_reader :timeout, :dead
12   attr_writer :lasterr, :lasterrstr, :socket
14 end
16 class TestBackend < Test::Unit::TestCase
18   def setup
19     @backend = MogileFS::Backend.new :hosts => ['localhost:1']
20   end
22   def test_initialize
23     assert_raises ArgumentError do MogileFS::Backend.new end
24     assert_raises ArgumentError do MogileFS::Backend.new :hosts => [] end
25     assert_raises ArgumentError do MogileFS::Backend.new :hosts => [''] end
27     assert_equal ['localhost:1'], @backend.hosts
28     assert_equal 3, @backend.timeout
29     assert_equal nil, @backend.lasterr
30     assert_equal nil, @backend.lasterrstr
31     assert_equal({}, @backend.dead)
33     @backend = MogileFS::Backend.new :hosts => ['localhost:6001'], :timeout => 1
34     assert_equal 1, @backend.timeout
35   end
37   def test_do_request
38     received = Tempfile.new('received')
39     tmp = TempServer.new(Proc.new do |serv, port|
40       client, client_addr = serv.accept
41       client.sync = true
42       received.syswrite(client.recv(4096))
43       client.send "OK 1 you=win\r\n", 0
44     end)
46     @backend.hosts = [ "127.0.0.1:#{tmp.port}" ]
48     assert_equal({'you' => 'win'},
49                  @backend.do_request('go!', { 'fight' => 'team fight!' }))
50     received.sysseek(0)
51     assert_equal "go! fight=team+fight%21\r\n", received.sysread(4096)
52     ensure
53       TempServer.destroy_all!
54   end
56   def test_do_request_send_error
57     socket_request = ''
58     socket = Object.new
59     def socket.closed?() false end
60     def socket.send(request, flags) raise SystemCallError, 'dummy' end
62     @backend.instance_variable_set '@socket', socket
64     assert_raises MogileFS::UnreachableBackendError do
65       @backend.do_request 'go!', { 'fight' => 'team fight!' }
66     end
68     assert_equal nil, @backend.instance_variable_get('@socket')
69   end
71   def test_automatic_exception
72     assert ! MogileFS::Backend.const_defined?('PebkacError')
73     assert @backend.error('pebkac')
74     assert_equal MogileFS::Error, @backend.error('PebkacError').superclass
75     assert MogileFS::Backend.const_defined?('PebkacError')
77     assert ! MogileFS::Backend.const_defined?('PebKacError')
78     assert @backend.error('peb_kac')
79     assert_equal MogileFS::Error, @backend.error('PebKacError').superclass
80     assert MogileFS::Backend.const_defined?('PebKacError')
81   end
83   def test_size_verify_error_defined
84     # "ErrorError" just looks dumb, but we used to get it
85     # since mogilefs would send us "size_verify_error" and we'd
86     # blindly append "Error" to the exception
87     assert ! MogileFS::Backend.const_defined?('SizeVerifyErrorError')
88     assert MogileFS::Backend.const_defined?('SizeVerifyError')
89   end
91   def test_do_request_truncated
92     socket_request = ''
93     socket = Object.new
94     def socket.closed?() false end
95     def socket.send(request, flags) return request.length - 1 end
97     @backend.instance_variable_set '@socket', socket
99     assert_raises MogileFS::RequestTruncatedError do
100       @backend.do_request 'go!', { 'fight' => 'team fight!' }
101     end
102   end
104   def test_make_request
105     assert_equal "go! fight=team+fight%21\r\n",
106                  @backend.make_request('go!', { 'fight' => 'team fight!' })
107   end
109   def test_parse_response
110     assert_equal({'foo' => 'bar', 'baz' => 'hoge'},
111                  @backend.parse_response('OK 1 foo=bar&baz=hoge'))
113     err = nil
114     begin
115       @backend.parse_response('ERR you totally suck')
116     rescue MogileFS::Error => err
117       assert_equal 'MogileFS::Backend::YouError', err.class.to_s
118       assert_equal 'totally suck', err.message
119     end
120     assert_equal 'MogileFS::Backend::YouError', err.class.to_s
122     assert_equal 'you', @backend.lasterr
123     assert_equal 'totally suck', @backend.lasterrstr
125     assert_raises MogileFS::InvalidResponseError do
126       @backend.parse_response 'garbage'
127     end
128   end
130   def test_readable_eh_readable
131     accept = Tempfile.new('accept')
132     tmp = TempServer.new(Proc.new do |serv, port|
133       client, client_addr = serv.accept
134       client.sync = true
135       accept.syswrite('.')
136       client.send('.', 0)
137       sleep
138     end)
140     @backend = MogileFS::Backend.new :hosts => [ "127.0.0.1:#{tmp.port}" ]
141     assert_equal true, @backend.readable?
142     assert_equal 1, accept.stat.size
143     ensure
144       TempServer.destroy_all!
145   end
147   def test_readable_eh_not_readable
148     tmp = TempServer.new(Proc.new { |serv,port| serv.accept; sleep })
149     @backend = MogileFS::Backend.new(:hosts => [ "127.0.0.1:#{tmp.port}" ],
150                                      :timeout => 0.5)
151     begin
152       @backend.readable?
153     rescue MogileFS::UnreadableSocketError => e
154       assert_equal "127.0.0.1:#{tmp.port} never became readable", e.message
155     rescue Exception => err
156       flunk "MogileFS::UnreadableSocketError not raised #{err} #{err.backtrace}"
157     else
158       flunk "MogileFS::UnreadableSocketError not raised"
159     ensure
160       TempServer.destroy_all!
161     end
162   end
164   def test_socket
165     assert_equal({}, @backend.dead)
166     assert_raises MogileFS::UnreachableBackendError do @backend.socket end
167     assert_equal(['localhost:1'], @backend.dead.keys)
168   end
170   def test_socket_robust
171     bad_accept = Tempfile.new('bad_accept')
172     accept = Tempfile.new('accept')
173     bad = Proc.new do |serv,port|
174       client, client_addr = serv.accept
175       bad_accept.syswrite('!')
176     end
177     good = Proc.new do |serv,port|
178       client, client_addr = serv.accept
179       accept.syswrite('.')
180       client.syswrite('.')
181       client.close
182       sleep
183     end
184     nr = 10
186     nr.times do
187       begin
188         t1 = TempServer.new(bad, ENV['TEST_DEAD_PORT'])
189         t2 = TempServer.new(good)
190         hosts = ["127.0.0.1:#{t1.port}", "127.0.0.1:#{t2.port}"]
191         @backend = MogileFS::Backend.new(:hosts => hosts.dup)
192         assert_equal({}, @backend.dead)
193         old_chld_handler = trap('CHLD', 'DEFAULT')
194         t1.destroy!
195         Process.waitpid(t1.pid)
196         trap('CHLD', old_chld_handler)
197         sock = @backend.socket
198         assert_equal Socket, sock.class
199         port = Socket.unpack_sockaddr_in(sock.getpeername).first
200         # p [ 'ports', "port=#{port}", "t1=#{t1.port}", "t2=#{t2.port}" ]
201         assert_equal t2.port, port
202         IO.select([sock])
203         assert_equal '.', sock.sysread(1)
204       ensure
205         TempServer.destroy_all!
206       end
207     end # nr.times
208     assert_equal 0, bad_accept.stat.size
209     assert_equal nr, accept.stat.size
210   end
212   def test_shutdown
213     accept_nr = 0
214     tmp = TempServer.new(Proc.new do |serv,port|
215       client, client_addr = serv.accept
216       accept_nr += 1
217       r = IO.select([client], [client])
218       client.syswrite(accept_nr.to_s)
219       sleep
220     end)
221     @backend = MogileFS::Backend.new :hosts => [ "127.0.0.1:#{tmp.port}" ]
222     assert @backend.socket
223     assert ! @backend.socket.closed?
224     IO.select([@backend.socket])
225     resp = @backend.socket.sysread(4096)
226     @backend.shutdown
227     assert_equal nil, @backend.instance_variable_get(:@socket)
228     assert_equal 1, resp.to_i
229     ensure
230       TempServer.destroy_all!
231   end
233   def test_url_decode
234     assert_equal({"\272z" => "\360opy", "f\000" => "\272r"},
235                  @backend.url_decode("%baz=%f0opy&f%00=%bar"))
236     assert_equal({}, @backend.url_decode(''))
237   end
239   def test_url_encode
240     params = [["f\000", "\272r"], ["\272z", "\360opy"]]
241     assert_equal "f%00=%bar&%baz=%f0opy", @backend.url_encode(params)
242   end
244   def test_url_escape # \n for unit_diff
245     actual = (0..255).map { |c| @backend.url_escape c.chr }.join "\n"
247     expected = []
248     expected.push(*(0..0x1f).map { |c| "%%%0.2x" % c })
249     expected << '+'
250     expected.push(*(0x21..0x2b).map { |c| "%%%0.2x" % c })
251     expected.push(*%w[, - . /])
252     expected.push(*('0'..'9'))
253     expected.push(*%w[: %3b %3c %3d %3e %3f %40])
254     expected.push(*('A'..'Z'))
255     expected.push(*%w[%5b \\ %5d %5e _ %60])
256     expected.push(*('a'..'z'))
257     expected.push(*(0x7b..0xff).map { |c| "%%%0.2x" % c })
259     expected = expected.join "\n"
261     assert_equal expected, actual
262   end
264   def test_url_unescape
265     input = []
266     input.push(*(0..0x1f).map { |c| "%%%0.2x" % c })
267     input << '+'
268     input.push(*(0x21..0x2b).map { |c| "%%%0.2x" % c })
269     input.push(*%w[, - . /])
270     input.push(*('0'..'9'))
271     input.push(*%w[: %3b %3c %3d %3e %3f %40])
272     input.push(*('A'..'Z'))
273     input.push(*%w[%5b \\ %5d %5e _ %60])
274     input.push(*('a'..'z'))
275     input.push(*(0x7b..0xff).map { |c| "%%%0.2x" % c })
277     actual = input.map { |c| @backend.url_unescape c }.join "\n"
279     expected = (0..255).map { |c| c.chr }.join "\n"
280     expected.sub! '+', ' '
282     assert_equal expected, actual
283   end