1import unittest
2
3from supervisor.tests.base import DummySupervisor
4from supervisor.tests.base import DummyRequest
5from supervisor.tests.base import DummySupervisorRPCNamespace
6
7from supervisor.compat import xmlrpclib
8from supervisor.compat import httplib
9
10class GetFaultDescriptionTests(unittest.TestCase):
11    def test_returns_description_for_known_fault(self):
12        from supervisor import xmlrpc
13        desc = xmlrpc.getFaultDescription(xmlrpc.Faults.SHUTDOWN_STATE)
14        self.assertEqual(desc, 'SHUTDOWN_STATE')
15
16    def test_returns_unknown_for_unknown_fault(self):
17        from supervisor import xmlrpc
18        desc = xmlrpc.getFaultDescription(999999)
19        self.assertEqual(desc, 'UNKNOWN')
20
21class RPCErrorTests(unittest.TestCase):
22    def _getTargetClass(self):
23        from supervisor.xmlrpc import RPCError
24        return RPCError
25
26    def _makeOne(self, code, extra=None):
27        return self._getTargetClass()(code, extra)
28
29    def test_sets_text_with_fault_name_only(self):
30        from supervisor import xmlrpc
31        e = self._makeOne(xmlrpc.Faults.FAILED)
32        self.assertEqual(e.text, 'FAILED')
33
34    def test_sets_text_with_fault_name_and_extra(self):
35        from supervisor import xmlrpc
36        e = self._makeOne(xmlrpc.Faults.FAILED, 'oops')
37        self.assertEqual(e.text, 'FAILED: oops')
38
39    def test___str___shows_code_and_text(self):
40        from supervisor import xmlrpc
41        e = self._makeOne(xmlrpc.Faults.NO_FILE, '/nonexistent')
42        self.assertEqual(str(e),
43            "code=%r, text='NO_FILE: /nonexistent'" % xmlrpc.Faults.NO_FILE
44            )
45
46class XMLRPCMarshallingTests(unittest.TestCase):
47    def test_xmlrpc_marshal(self):
48        from supervisor import xmlrpc
49        data = xmlrpc.xmlrpc_marshal(1)
50        self.assertEqual(data, xmlrpclib.dumps((1,), methodresponse=True))
51        fault = xmlrpclib.Fault(1, 'foo')
52        data = xmlrpc.xmlrpc_marshal(fault)
53        self.assertEqual(data, xmlrpclib.dumps(fault))
54
55class XMLRPCHandlerTests(unittest.TestCase):
56    def _getTargetClass(self):
57        from supervisor.xmlrpc import supervisor_xmlrpc_handler
58        return supervisor_xmlrpc_handler
59
60    def _makeOne(self, supervisord, subinterfaces):
61        return self._getTargetClass()(supervisord, subinterfaces)
62
63    def test_ctor(self):
64        supervisor = DummySupervisor()
65        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
66        handler = self._makeOne(supervisor, subinterfaces)
67        self.assertEqual(handler.supervisord, supervisor)
68        from supervisor.xmlrpc import RootRPCInterface
69        self.assertEqual(handler.rpcinterface.__class__, RootRPCInterface)
70
71    def test_match(self):
72        class DummyRequest2:
73            def __init__(self, uri):
74                self.uri = uri
75        supervisor = DummySupervisor()
76        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
77        handler = self._makeOne(supervisor, subinterfaces)
78        self.assertEqual(handler.match(DummyRequest2('/RPC2')), True)
79        self.assertEqual(handler.match(DummyRequest2('/nope')), False)
80
81    def test_continue_request_nosuchmethod(self):
82        supervisor = DummySupervisor()
83        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
84        handler = self._makeOne(supervisor, subinterfaces)
85        data = xmlrpclib.dumps(('a', 'b'), 'supervisor.noSuchMethod')
86        request = DummyRequest('/what/ever', None, None, None)
87        handler.continue_request(data, request)
88        logdata = supervisor.options.logger.data
89        self.assertEqual(len(logdata), 2)
90        self.assertEqual(logdata[-2],
91                         'XML-RPC method called: supervisor.noSuchMethod()')
92        self.assertEqual(logdata[-1],
93           ('XML-RPC method supervisor.noSuchMethod() returned fault: '
94            '[1] UNKNOWN_METHOD'))
95        self.assertEqual(len(request.producers), 1)
96        xml_response = request.producers[0]
97        self.assertRaises(xmlrpclib.Fault, xmlrpclib.loads, xml_response)
98
99    def test_continue_request_methodsuccess(self):
100        supervisor = DummySupervisor()
101        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
102        handler = self._makeOne(supervisor, subinterfaces)
103        data = xmlrpclib.dumps((), 'supervisor.getAPIVersion')
104        request = DummyRequest('/what/ever', None, None, None)
105        handler.continue_request(data, request)
106        logdata = supervisor.options.logger.data
107        self.assertEqual(len(logdata), 2)
108        self.assertEqual(logdata[-2],
109               'XML-RPC method called: supervisor.getAPIVersion()')
110        self.assertEqual(logdata[-1],
111            'XML-RPC method supervisor.getAPIVersion() returned successfully')
112        self.assertEqual(len(request.producers), 1)
113        xml_response = request.producers[0]
114        response = xmlrpclib.loads(xml_response)
115        from supervisor.rpcinterface import API_VERSION
116        self.assertEqual(response[0][0], API_VERSION)
117        self.assertEqual(request._done, True)
118        self.assertEqual(request.headers['Content-Type'], 'text/xml')
119        self.assertEqual(request.headers['Content-Length'], len(xml_response))
120
121    def test_continue_request_no_params_in_request(self):
122        supervisor = DummySupervisor()
123        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
124        handler = self._makeOne(supervisor, subinterfaces)
125        data = '<?xml version="1.0" encoding="UTF-8"?>' \
126               '<methodCall>' \
127               '<methodName>supervisor.getAPIVersion</methodName>' \
128               '</methodCall>'
129        request = DummyRequest('/what/ever', None, None, None)
130        handler.continue_request(data, request)
131        logdata = supervisor.options.logger.data
132        self.assertEqual(len(logdata), 2)
133        self.assertEqual(logdata[-2],
134               'XML-RPC method called: supervisor.getAPIVersion()')
135        self.assertEqual(logdata[-1],
136            'XML-RPC method supervisor.getAPIVersion() returned successfully')
137        self.assertEqual(len(request.producers), 1)
138        xml_response = request.producers[0]
139        response = xmlrpclib.loads(xml_response)
140        from supervisor.rpcinterface import API_VERSION
141        self.assertEqual(response[0][0], API_VERSION)
142        self.assertEqual(request._done, True)
143        self.assertEqual(request.headers['Content-Type'], 'text/xml')
144        self.assertEqual(request.headers['Content-Length'], len(xml_response))
145
146    def test_continue_request_400_if_method_name_is_empty(self):
147        supervisor = DummySupervisor()
148        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
149        handler = self._makeOne(supervisor, subinterfaces)
150        data = '<?xml version="1.0" encoding="UTF-8"?>' \
151               '<methodCall><methodName></methodName></methodCall>'
152        request = DummyRequest('/what/ever', None, None, None)
153        handler.continue_request(data, request)
154        logdata = supervisor.options.logger.data
155        self.assertEqual(len(logdata), 1)
156        self.assertTrue(logdata[0].startswith('XML-RPC request data'))
157        self.assertTrue(repr(data) in logdata[0])
158        self.assertTrue(logdata[0].endswith('is invalid: no method name'))
159        self.assertEqual(request._error, 400)
160
161    def test_continue_request_400_if_loads_raises_not_xml(self):
162        supervisor = DummySupervisor()
163        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
164        handler = self._makeOne(supervisor, subinterfaces)
165        data = 'this is not an xml-rpc request body'
166        request = DummyRequest('/what/ever', None, None, None)
167        handler.continue_request(data, request)
168        logdata = supervisor.options.logger.data
169        self.assertEqual(len(logdata), 1)
170        self.assertTrue(logdata[0].startswith('XML-RPC request data'))
171        self.assertTrue(repr(data) in logdata[0])
172        self.assertTrue(logdata[0].endswith('is invalid: unmarshallable'))
173        self.assertEqual(request._error, 400)
174
175    def test_continue_request_400_if_loads_raises_weird_xml(self):
176        supervisor = DummySupervisor()
177        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
178        handler = self._makeOne(supervisor, subinterfaces)
179        data = '<methodName></methodName><junk></junk>'
180        request = DummyRequest('/what/ever', None, None, None)
181        handler.continue_request(data, request)
182        logdata = supervisor.options.logger.data
183        self.assertEqual(len(logdata), 1)
184        self.assertTrue(logdata[0].startswith('XML-RPC request data'))
185        self.assertTrue(repr(data) in logdata[0])
186        self.assertTrue(logdata[0].endswith('is invalid: unmarshallable'))
187        self.assertEqual(request._error, 400)
188
189    def test_continue_request_500_if_rpcinterface_method_call_raises(self):
190        supervisor = DummySupervisor()
191        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
192        handler = self._makeOne(supervisor, subinterfaces)
193        data = xmlrpclib.dumps((), 'supervisor.raiseError')
194        request = DummyRequest('/what/ever', None, None, None)
195        handler.continue_request(data, request)
196        logdata = supervisor.options.logger.data
197        self.assertEqual(len(logdata), 2)
198        self.assertEqual(logdata[0],
199               'XML-RPC method called: supervisor.raiseError()')
200        self.assertTrue("unexpected exception" in logdata[1])
201        self.assertTrue(repr(data) in logdata[1])
202        self.assertTrue("Traceback" in logdata[1])
203        self.assertTrue("ValueError: error" in logdata[1])
204        self.assertEqual(request._error, 500)
205
206    def test_continue_request_500_if_xmlrpc_dumps_raises(self):
207        supervisor = DummySupervisor()
208        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
209        handler = self._makeOne(supervisor, subinterfaces)
210        data = xmlrpclib.dumps((), 'supervisor.getXmlRpcUnmarshallable')
211        request = DummyRequest('/what/ever', None, None, None)
212        handler.continue_request(data, request)
213        logdata = supervisor.options.logger.data
214        self.assertEqual(len(logdata), 3)
215        self.assertEqual(logdata[0],
216               'XML-RPC method called: supervisor.getXmlRpcUnmarshallable()')
217        self.assertEqual(logdata[1],
218               'XML-RPC method supervisor.getXmlRpcUnmarshallable() '
219               'returned successfully')
220        self.assertTrue("unexpected exception" in logdata[2])
221        self.assertTrue(repr(data) in logdata[2])
222        self.assertTrue("Traceback" in logdata[2])
223        self.assertTrue("TypeError: cannot marshal" in logdata[2])
224        self.assertEqual(request._error, 500)
225
226    def test_continue_request_value_is_function(self):
227        class DummyRPCNamespace(object):
228            def foo(self):
229                def inner(self):
230                    return 1
231                inner.delay = .05
232                return inner
233        supervisor = DummySupervisor()
234        subinterfaces = [('supervisor', DummySupervisorRPCNamespace()),
235                          ('ns1', DummyRPCNamespace())]
236        handler = self._makeOne(supervisor, subinterfaces)
237        data = xmlrpclib.dumps((), 'ns1.foo')
238        request = DummyRequest('/what/ever', None, None, None)
239        handler.continue_request(data, request)
240        logdata = supervisor.options.logger.data
241        self.assertEqual(len(logdata), 2)
242        self.assertEqual(logdata[-2],
243               'XML-RPC method called: ns1.foo()')
244        self.assertEqual(logdata[-1],
245            'XML-RPC method ns1.foo() returned successfully')
246        self.assertEqual(len(request.producers), 0)
247        self.assertEqual(request._done, False)
248
249    def test_iterparse_loads_methodcall(self):
250        s = """<?xml version="1.0"?>
251        <methodCall>
252        <methodName>examples.getStateName</methodName>
253        <params>
254        <param>
255        <value><i4>41</i4></value>
256        </param>
257        <param>
258        <value><string>foo</string></value>
259        </param>
260        <param>
261        <value><string></string></value>
262        </param>
263        <param>
264        <!-- xml-rpc spec allows strings without <string> tag -->
265        <value>bar</value>
266        </param>
267        <param>
268        <value></value>
269        </param>
270        <param>
271        <value><boolean>1</boolean></value>
272        </param>
273        <param>
274        <value><double>-12.214</double></value>
275        </param>
276        <param>
277        <value>
278        <dateTime.iso8601>19980717T14:08:55</dateTime.iso8601>
279        </value>
280        </param>
281        <param>
282        <value><base64>eW91IGNhbid0IHJlYWQgdGhpcyE=</base64></value>
283        </param>
284        <param>
285        <struct>
286        <member><name>j</name><value><i4>5</i4></value></member>
287        <member><name>k</name><value>abc</value></member>
288        </struct>
289        </param>
290        <param>
291        <array>
292          <data>
293            <value><i4>12</i4></value>
294            <value><string>abc</string></value>
295            <value>def</value>
296            <value><i4>34</i4></value>
297          </data>
298        </array>
299        </param>
300        <param>
301        <struct>
302          <member>
303            <name>k</name>
304            <value><array><data>
305              <value><i4>1</i4></value>
306              <struct></struct>
307            </data></array></value>
308          </member>
309        </struct>
310        </param>
311        </params>
312        </methodCall>
313        """
314        supervisor = DummySupervisor()
315        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]
316        handler = self._makeOne(supervisor, subinterfaces)
317        result = handler.loads(s)
318        params, method = result
319        import datetime
320        self.assertEqual(method, 'examples.getStateName')
321        self.assertEqual(params[0], 41)
322        self.assertEqual(params[1], 'foo')
323        self.assertEqual(params[2], '')
324        self.assertEqual(params[3], 'bar')
325        self.assertEqual(params[4], '')
326        self.assertEqual(params[5], True)
327        self.assertEqual(params[6], -12.214)
328        self.assertEqual(params[7], datetime.datetime(1998, 7, 17, 14, 8, 55))
329        self.assertEqual(params[8], "you can't read this!")
330        self.assertEqual(params[9], {'j': 5, 'k': 'abc'})
331        self.assertEqual(params[10], [12, 'abc', 'def', 34])
332        self.assertEqual(params[11], {'k': [1, {}]})
333
334class TraverseTests(unittest.TestCase):
335    def test_security_disallows_underscore_methods(self):
336        from supervisor import xmlrpc
337        class Root:
338            pass
339        class A:
340            def _danger(self):
341                return True
342        root = Root()
343        root.a = A()
344        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
345            root, 'a._danger', [])
346
347    def test_security_disallows_object_traversal(self):
348        from supervisor import xmlrpc
349        class Root:
350            pass
351        class A:
352            pass
353        class B:
354            def danger(self):
355                return True
356        root = Root()
357        root.a = A()
358        root.a.b = B()
359        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
360            root, 'a.b.danger', [])
361
362    def test_namespace_name_not_found(self):
363        from supervisor import xmlrpc
364        class Root:
365            pass
366        root = Root()
367        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
368            root, 'notfound.hello', None)
369
370    def test_method_name_not_found(self):
371        from supervisor import xmlrpc
372        class Root:
373            pass
374        class A:
375            pass
376        root = Root()
377        root.a = A()
378        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
379            root, 'a.notfound', [])
380
381    def test_method_name_exists_but_is_not_a_method(self):
382        from supervisor import xmlrpc
383        class Root:
384            pass
385        class A:
386            pass
387        class B:
388            pass
389        root = Root()
390        root.a = A()
391        root.a.b = B()
392        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
393            root, 'a.b', [])  # b is not a method
394
395    def test_bad_params(self):
396        from supervisor import xmlrpc
397        class Root:
398            pass
399        class A:
400            def hello(self, name):
401                return "Hello %s" % name
402        root = Root()
403        root.a = A()
404        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
405            root, 'a.hello', ["there", "extra"])  # too many params
406
407    def test_success(self):
408        from supervisor import xmlrpc
409        class Root:
410            pass
411        class A:
412            def hello(self, name):
413                return "Hello %s" % name
414        root = Root()
415        root.a = A()
416        result = xmlrpc.traverse(root, 'a.hello', ["there"])
417        self.assertEqual(result, "Hello there")
418
419class SupervisorTransportTests(unittest.TestCase):
420    def _getTargetClass(self):
421        from supervisor.xmlrpc import SupervisorTransport
422        return SupervisorTransport
423
424    def _makeOne(self, *arg, **kw):
425        return self._getTargetClass()(*arg, **kw)
426
427    def test_ctor_unix(self):
428        from supervisor import xmlrpc
429        transport = self._makeOne('user', 'pass', 'unix:///foo/bar')
430        conn = transport._get_connection()
431        self.assertTrue(isinstance(conn, xmlrpc.UnixStreamHTTPConnection))
432        self.assertEqual(conn.host, 'localhost')
433        self.assertEqual(conn.socketfile, '/foo/bar')
434
435    def test_ctor_unknown(self):
436        self.assertRaises(ValueError,
437            self._makeOne, 'user', 'pass', 'unknown:///foo/bar'
438            )
439
440    def test__get_connection_http_9001(self):
441        transport = self._makeOne('user', 'pass', 'http://127.0.0.1:9001/')
442        conn = transport._get_connection()
443        self.assertTrue(isinstance(conn, httplib.HTTPConnection))
444        self.assertEqual(conn.host, '127.0.0.1')
445        self.assertEqual(conn.port, 9001)
446
447    def test__get_connection_http_80(self):
448        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')
449        conn = transport._get_connection()
450        self.assertTrue(isinstance(conn, httplib.HTTPConnection))
451        self.assertEqual(conn.host, '127.0.0.1')
452        self.assertEqual(conn.port, 80)
453
454    def test_request_non_200_response(self):
455        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')
456        dummy_conn = DummyConnection(400, '')
457        def getconn():
458            return dummy_conn
459        transport._get_connection = getconn
460        self.assertRaises(xmlrpclib.ProtocolError,
461                          transport.request, 'localhost', '/', '')
462        self.assertEqual(transport.connection, None)
463        self.assertEqual(dummy_conn.closed, True)
464
465    def test_request_400_response(self):
466        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')
467        dummy_conn = DummyConnection(400, '')
468        def getconn():
469            return dummy_conn
470        transport._get_connection = getconn
471        self.assertRaises(xmlrpclib.ProtocolError,
472                          transport.request, 'localhost', '/', '')
473        self.assertEqual(transport.connection, None)
474        self.assertEqual(dummy_conn.closed, True)
475        self.assertEqual(dummy_conn.requestargs[0], 'POST')
476        self.assertEqual(dummy_conn.requestargs[1], '/')
477        self.assertEqual(dummy_conn.requestargs[2], b'')
478        self.assertEqual(dummy_conn.requestargs[3]['Content-Length'], '0')
479        self.assertEqual(dummy_conn.requestargs[3]['Content-Type'], 'text/xml')
480        self.assertEqual(dummy_conn.requestargs[3]['Authorization'],
481                         'Basic dXNlcjpwYXNz')
482        self.assertEqual(dummy_conn.requestargs[3]['Accept'], 'text/xml')
483
484    def test_request_200_response(self):
485        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')
486        response = """<?xml version="1.0"?>
487        <methodResponse>
488        <params>
489        <param>
490        <value><string>South Dakota</string></value>
491        </param>
492        </params>
493        </methodResponse>"""
494        dummy_conn = DummyConnection(200, response)
495        def getconn():
496            return dummy_conn
497        transport._get_connection = getconn
498        result = transport.request('localhost', '/', '')
499        self.assertEqual(transport.connection, dummy_conn)
500        self.assertEqual(dummy_conn.closed, False)
501        self.assertEqual(dummy_conn.requestargs[0], 'POST')
502        self.assertEqual(dummy_conn.requestargs[1], '/')
503        self.assertEqual(dummy_conn.requestargs[2], b'')
504        self.assertEqual(dummy_conn.requestargs[3]['Content-Length'], '0')
505        self.assertEqual(dummy_conn.requestargs[3]['Content-Type'], 'text/xml')
506        self.assertEqual(dummy_conn.requestargs[3]['Authorization'],
507                         'Basic dXNlcjpwYXNz')
508        self.assertEqual(dummy_conn.requestargs[3]['Accept'], 'text/xml')
509        self.assertEqual(result, ('South Dakota',))
510
511    def test_close(self):
512        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')
513        dummy_conn = DummyConnection(200, '''<?xml version="1.0"?>
514        <methodResponse><params/></methodResponse>''')
515        def getconn():
516            return dummy_conn
517        transport._get_connection = getconn
518        transport.request('localhost', '/', '')
519        transport.close()
520        self.assertTrue(dummy_conn.closed)
521
522class TestDeferredXMLRPCResponse(unittest.TestCase):
523    def _getTargetClass(self):
524        from supervisor.xmlrpc import DeferredXMLRPCResponse
525        return DeferredXMLRPCResponse
526
527    def _makeOne(self, request=None, callback=None):
528        if request is None:
529            request = DummyRequest(None, None, None, None, None)
530        if callback is None:
531            callback = Dummy()
532            callback.delay = 1
533        return self._getTargetClass()(request, callback)
534
535    def test_ctor(self):
536        callback = Dummy()
537        callback.delay = 1
538        inst = self._makeOne(request='request', callback=callback)
539        self.assertEqual(inst.callback, callback)
540        self.assertEqual(inst.delay, 1.0)
541        self.assertEqual(inst.request, 'request')
542        self.assertEqual(inst.finished, False)
543
544    def test_more_finished(self):
545        inst = self._makeOne()
546        inst.finished = True
547        result = inst.more()
548        self.assertEqual(result,  '')
549
550    def test_more_callback_returns_not_done_yet(self):
551        from supervisor.http import NOT_DONE_YET
552        def callback():
553            return NOT_DONE_YET
554        callback.delay = 1
555        inst = self._makeOne(callback=callback)
556        self.assertEqual(inst.more(), NOT_DONE_YET)
557
558    def test_more_callback_raises_RPCError(self):
559        from supervisor.xmlrpc import RPCError, Faults
560        def callback():
561            raise RPCError(Faults.UNKNOWN_METHOD)
562        callback.delay = 1
563        inst = self._makeOne(callback=callback)
564        self.assertEqual(inst.more(), None)
565        self.assertEqual(len(inst.request.producers), 1)
566        self.assertTrue('UNKNOWN_METHOD' in inst.request.producers[0])
567        self.assertTrue(inst.finished)
568
569    def test_more_callback_returns_value(self):
570        def callback():
571            return 'abc'
572        callback.delay = 1
573        inst = self._makeOne(callback=callback)
574        self.assertEqual(inst.more(), None)
575        self.assertEqual(len(inst.request.producers), 1)
576        self.assertTrue('abc' in inst.request.producers[0])
577        self.assertTrue(inst.finished)
578
579    def test_more_callback_raises_unexpected_exception(self):
580        def callback():
581            raise ValueError('foo')
582        callback.delay = 1
583        inst = self._makeOne(callback=callback)
584        self.assertEqual(inst.more(), None)
585        self.assertEqual(inst.request._error, 500)
586        self.assertTrue(inst.finished)
587        logged = inst.request.channel.server.logger.logged
588        self.assertEqual(len(logged), 1)
589        src, msg = logged[0]
590        self.assertEqual(src, 'XML-RPC response callback error')
591        self.assertTrue("Traceback" in msg)
592
593    def test_getresponse_http_10_with_keepalive(self):
594        inst = self._makeOne()
595        inst.request.version = '1.0'
596        inst.request.header.append('Connection: keep-alive')
597        inst.getresponse('abc')
598        self.assertEqual(len(inst.request.producers), 1)
599        self.assertEqual(inst.request.headers['Connection'], 'Keep-Alive')
600
601    def test_getresponse_http_10_no_keepalive(self):
602        inst = self._makeOne()
603        inst.request.version = '1.0'
604        inst.getresponse('abc')
605        self.assertEqual(len(inst.request.producers), 1)
606        self.assertEqual(inst.request.headers['Connection'], 'close')
607
608    def test_getresponse_http_11_without_close(self):
609        inst = self._makeOne()
610        inst.request.version = '1.1'
611        inst.getresponse('abc')
612        self.assertEqual(len(inst.request.producers), 1)
613        self.assertTrue('Connection' not in inst.request.headers)
614
615    def test_getresponse_http_11_with_close(self):
616        inst = self._makeOne()
617        inst.request.header.append('Connection: close')
618        inst.request.version = '1.1'
619        inst.getresponse('abc')
620        self.assertEqual(len(inst.request.producers), 1)
621        self.assertEqual(inst.request.headers['Connection'], 'close')
622
623    def test_getresponse_http_unknown(self):
624        inst = self._makeOne()
625        inst.request.version = None
626        inst.getresponse('abc')
627        self.assertEqual(len(inst.request.producers), 1)
628        self.assertEqual(inst.request.headers['Connection'], 'close')
629
630class TestSystemNamespaceRPCInterface(unittest.TestCase):
631    def _makeOne(self, namespaces=()):
632        from supervisor.xmlrpc import SystemNamespaceRPCInterface
633        return SystemNamespaceRPCInterface(namespaces)
634
635    def test_listMethods_gardenpath(self):
636        inst = self._makeOne()
637        result = inst.listMethods()
638        self.assertEqual(
639            result,
640            ['system.listMethods',
641             'system.methodHelp',
642             'system.methodSignature',
643             'system.multicall',
644             ]
645            )
646
647    def test_listMethods_omits_underscore_attrs(self):
648        class DummyNamespace(object):
649            def foo(self): pass
650            def _bar(self): pass
651        ns1 = DummyNamespace()
652        inst = self._makeOne([('ns1', ns1)])
653        result = inst.listMethods()
654        self.assertEqual(
655            result,
656            ['ns1.foo',
657             'system.listMethods',
658             'system.methodHelp',
659             'system.methodSignature',
660             'system.multicall'
661             ]
662            )
663
664    def test_methodHelp_known_method(self):
665        inst = self._makeOne()
666        result = inst.methodHelp('system.listMethods')
667        self.assertTrue('array' in result)
668
669    def test_methodHelp_unknown_method(self):
670        from supervisor.xmlrpc import RPCError
671        inst = self._makeOne()
672        self.assertRaises(RPCError, inst.methodHelp, 'wont.be.found')
673
674    def test_methodSignature_known_method(self):
675        inst = self._makeOne()
676        result = inst.methodSignature('system.methodSignature')
677        self.assertEqual(result, ['array', 'string'])
678
679    def test_methodSignature_unknown_method(self):
680        from supervisor.xmlrpc import RPCError
681        inst = self._makeOne()
682        self.assertRaises(RPCError, inst.methodSignature, 'wont.be.found')
683
684    def test_methodSignature_with_bad_sig(self):
685        from supervisor.xmlrpc import RPCError
686        class DummyNamespace(object):
687            def foo(self):
688                """ @param string name The thing"""
689        ns1 = DummyNamespace()
690        inst = self._makeOne([('ns1', ns1)])
691        self.assertRaises(RPCError, inst.methodSignature, 'ns1.foo')
692
693    def test_multicall_faults_for_recursion(self):
694        from supervisor.xmlrpc import Faults
695        inst = self._makeOne()
696        calls = [{'methodName':'system.multicall'}]
697        results = inst.multicall(calls)
698        self.assertEqual(
699            results,
700            [{'faultCode': Faults.INCORRECT_PARAMETERS,
701              'faultString': ('INCORRECT_PARAMETERS: Recursive '
702                              'system.multicall forbidden')}]
703            )
704
705    def test_multicall_faults_for_missing_methodName(self):
706        from supervisor.xmlrpc import Faults
707        inst = self._makeOne()
708        calls = [{}]
709        results = inst.multicall(calls)
710        self.assertEqual(
711            results,
712            [{'faultCode': Faults.INCORRECT_PARAMETERS,
713              'faultString': 'INCORRECT_PARAMETERS: No methodName'}]
714            )
715
716    def test_multicall_faults_for_methodName_bad_namespace(self):
717        from supervisor.xmlrpc import Faults
718        inst = self._makeOne()
719        calls = [{'methodName': 'bad.stopProcess'}]
720        results = inst.multicall(calls)
721        self.assertEqual(
722            results,
723            [{'faultCode': Faults.UNKNOWN_METHOD,
724              'faultString': 'UNKNOWN_METHOD'}]
725            )
726
727    def test_multicall_faults_for_methodName_good_ns_bad_method(self):
728        from supervisor.xmlrpc import Faults
729        class DummyNamespace(object):
730            pass
731        ns1 = DummyNamespace()
732        inst = self._makeOne([('ns1', ns1)])
733        calls = [{'methodName': 'ns1.bad'}]
734        results = inst.multicall(calls)
735        self.assertEqual(
736            results,
737            [{'faultCode': Faults.UNKNOWN_METHOD,
738              'faultString': 'UNKNOWN_METHOD'}]
739            )
740
741    def test_multicall_returns_empty_results_for_empty_calls(self):
742        inst = self._makeOne()
743        calls = []
744        results = inst.multicall(calls)
745        self.assertEqual(results, [])
746
747    def test_multicall_performs_noncallback_functions_serially(self):
748        class DummyNamespace(object):
749            def say(self, name):
750                """ @param string name Process name"""
751                return name
752        ns1 = DummyNamespace()
753        inst = self._makeOne([('ns1', ns1)])
754        calls = [
755            {'methodName': 'ns1.say', 'params': ['Alvin']},
756            {'methodName': 'ns1.say', 'params': ['Simon']},
757            {'methodName': 'ns1.say', 'params': ['Theodore']}
758        ]
759        results = inst.multicall(calls)
760        self.assertEqual(results, ['Alvin', 'Simon', 'Theodore'])
761
762    def test_multicall_catches_noncallback_exceptions(self):
763        import errno
764        from supervisor.xmlrpc import RPCError, Faults
765        class DummyNamespace(object):
766            def bad_name(self):
767                raise RPCError(Faults.BAD_NAME, 'foo')
768            def os_error(self):
769                raise OSError(errno.ENOENT)
770        ns1 = DummyNamespace()
771        inst = self._makeOne([('ns1', ns1)])
772        calls = [{'methodName': 'ns1.bad_name'}, {'methodName': 'ns1.os_error'}]
773        results = inst.multicall(calls)
774
775        bad_name = {'faultCode': Faults.BAD_NAME,
776                    'faultString': 'BAD_NAME: foo'}
777        os_error = {'faultCode': Faults.FAILED,
778                    'faultString': "FAILED: %s:2" % OSError}
779        self.assertEqual(results, [bad_name, os_error])
780
781    def test_multicall_catches_callback_exceptions(self):
782        import errno
783        from supervisor.xmlrpc import RPCError, Faults
784        from supervisor.http import NOT_DONE_YET
785        class DummyNamespace(object):
786            def bad_name(self):
787                def inner():
788                    raise RPCError(Faults.BAD_NAME, 'foo')
789                return inner
790            def os_error(self):
791                def inner():
792                    raise OSError(errno.ENOENT)
793                return inner
794        ns1 = DummyNamespace()
795        inst = self._makeOne([('ns1', ns1)])
796        calls = [{'methodName': 'ns1.bad_name'}, {'methodName': 'ns1.os_error'}]
797        callback = inst.multicall(calls)
798        results = NOT_DONE_YET
799        while results is NOT_DONE_YET:
800            results = callback()
801
802        bad_name = {'faultCode': Faults.BAD_NAME,
803                    'faultString': 'BAD_NAME: foo'}
804        os_error = {'faultCode': Faults.FAILED,
805                    'faultString': "FAILED: %s:2" % OSError}
806        self.assertEqual(results, [bad_name, os_error])
807
808    def test_multicall_performs_callback_functions_serially(self):
809        from supervisor.http import NOT_DONE_YET
810        class DummyNamespace(object):
811            def __init__(self):
812                self.stop_results = [NOT_DONE_YET, NOT_DONE_YET,
813                    NOT_DONE_YET, 'stop result']
814                self.start_results = ['start result']
815            def stopProcess(self, name):
816                def inner():
817                    result = self.stop_results.pop(0)
818                    if result is not NOT_DONE_YET:
819                        self.stopped = True
820                    return result
821                return inner
822            def startProcess(self, name):
823                def inner():
824                    if not self.stopped:
825                        raise Exception("This should not raise")
826                    return self.start_results.pop(0)
827                return inner
828        ns1 = DummyNamespace()
829        inst = self._makeOne([('ns1', ns1)])
830        calls = [{'methodName': 'ns1.stopProcess',
831                  'params': {'name': 'foo'}},
832                 {'methodName': 'ns1.startProcess',
833                  'params': {'name': 'foo'}}]
834        callback = inst.multicall(calls)
835        results = NOT_DONE_YET
836        while results is NOT_DONE_YET:
837            results = callback()
838        self.assertEqual(results, ['stop result', 'start result'])
839
840class Test_gettags(unittest.TestCase):
841    def _callFUT(self, comment):
842        from supervisor.xmlrpc import gettags
843        return gettags(comment)
844
845    def test_one_atpart(self):
846        lines = '@foo'
847        result = self._callFUT(lines)
848        self.assertEqual(
849            result,
850            [(0, None, None, None, ''), (0, 'foo', '', '', '')]
851            )
852
853    def test_two_atparts(self):
854        lines = '@foo array'
855        result = self._callFUT(lines)
856        self.assertEqual(
857            result,
858            [(0, None, None, None, ''), (0, 'foo', 'array', '', '')]
859            )
860
861    def test_three_atparts(self):
862        lines = '@foo array name'
863        result = self._callFUT(lines)
864        self.assertEqual(
865            result,
866            [(0, None, None, None, ''), (0, 'foo', 'array', 'name', '')]
867            )
868
869    def test_four_atparts(self):
870        lines = '@foo array name text'
871        result = self._callFUT(lines)
872        self.assertEqual(
873            result,
874            [(0, None, None, None, ''), (0, 'foo', 'array', 'name', 'text')]
875            )
876
877class Test_capped_int(unittest.TestCase):
878    def _callFUT(self, value):
879        from supervisor.xmlrpc import capped_int
880        return capped_int(value)
881
882    def test_converts_value_to_integer(self):
883        self.assertEqual(self._callFUT('42'), 42)
884
885    def test_caps_value_below_minint(self):
886        from supervisor.compat import xmlrpclib
887        self.assertEqual(self._callFUT(xmlrpclib.MININT - 1), xmlrpclib.MININT)
888
889    def test_caps_value_above_maxint(self):
890        from supervisor.compat import xmlrpclib
891        self.assertEqual(self._callFUT(xmlrpclib.MAXINT + 1), xmlrpclib.MAXINT)
892
893
894class DummyResponse:
895    def __init__(self, status=200, body='', reason='reason'):
896        self.status = status
897        self.body = body
898        self.reason = reason
899
900    def read(self):
901        return self.body
902
903class Dummy(object):
904    pass
905
906class DummyConnection:
907    closed = False
908    def __init__(self, status=200, body='', reason='reason'):
909        self.response = DummyResponse(status, body, reason)
910
911    def getresponse(self):
912        return self.response
913
914    def request(self, *arg, **kw):
915        self.requestargs = arg
916        self.requestkw = kw
917
918    def close(self):
919        self.closed = True
920
921