blob: 116e626740df8545ee6af2e980f006da44f77077 [file] [log] [blame]
Georg Brandl38eceaa2008-05-26 11:14:17 +00001from xmlrpc.server import DocXMLRPCServer
Georg Brandl24420152008-05-26 16:32:26 +00002import http.client
R. David Murray378c0cf2010-02-24 01:46:21 +00003import sys
Antoine Pitroua6a4dc82017-09-07 18:56:24 +02004import threading
Christian Heimes2f1019e2007-12-10 16:18:49 +00005import unittest
Christian Heimes2f1019e2007-12-10 16:18:49 +00006
R. David Murray378c0cf2010-02-24 01:46:21 +00007def make_request_and_skipIf(condition, reason):
Serhiy Storchaka56a6d852014-12-01 18:28:43 +02008 # If we skip the test, we have to make a request because
R. David Murray378c0cf2010-02-24 01:46:21 +00009 # the server created in setUp blocks expecting one to come in.
10 if not condition:
11 return lambda func: func
12 def decorator(func):
13 def make_request_and_skip(self):
14 self.client.request("GET", "/")
15 self.client.getresponse()
16 raise unittest.SkipTest(reason)
17 return make_request_and_skip
18 return decorator
19
20
Martin Panterd874c052016-08-20 06:50:58 +000021def make_server():
Benjamin Petersonf10a79a2008-10-11 00:49:57 +000022 serv = DocXMLRPCServer(("localhost", 0), logRequests=False)
Christian Heimes2f1019e2007-12-10 16:18:49 +000023
Benjamin Petersonf10a79a2008-10-11 00:49:57 +000024 try:
Christian Heimes2f1019e2007-12-10 16:18:49 +000025 # Add some documentation
26 serv.set_server_title("DocXMLRPCServer Test Documentation")
27 serv.set_server_name("DocXMLRPCServer Test Docs")
28 serv.set_server_documentation(
Ezio Melottib58e0bd2010-01-23 15:40:09 +000029 "This is an XML-RPC server's documentation, but the server "
30 "can be used by POSTing to /RPC2. Try self.add, too.")
Christian Heimes2f1019e2007-12-10 16:18:49 +000031
32 # Create and register classes and functions
33 class TestClass(object):
34 def test_method(self, arg):
35 """Test method's docs. This method truly does very little."""
36 self.arg = arg
37
38 serv.register_introspection_functions()
39 serv.register_instance(TestClass())
40
41 def add(x, y):
42 """Add two instances together. This follows PEP008, but has nothing
43 to do with RFC1952. Case should matter: pEp008 and rFC1952. Things
44 that start with http and ftp should be auto-linked, too:
45 http://google.com.
46 """
47 return x + y
48
R David Murrayf22b62e2013-08-10 12:01:47 -040049 def annotation(x: int):
50 """ Use function annotations. """
51 return x
52
53 class ClassWithAnnotation:
54 def method_annotation(self, x: bytes):
55 return x.decode()
56
Christian Heimes2f1019e2007-12-10 16:18:49 +000057 serv.register_function(add)
58 serv.register_function(lambda x, y: x-y)
R David Murrayf22b62e2013-08-10 12:01:47 -040059 serv.register_function(annotation)
60 serv.register_instance(ClassWithAnnotation())
Martin Panterd874c052016-08-20 06:50:58 +000061 return serv
62 except:
Christian Heimes2f1019e2007-12-10 16:18:49 +000063 serv.server_close()
Martin Panterd874c052016-08-20 06:50:58 +000064 raise
Christian Heimes2f1019e2007-12-10 16:18:49 +000065
66class DocXMLRPCHTTPGETServer(unittest.TestCase):
67 def setUp(self):
68 # Enable server feedback
69 DocXMLRPCServer._send_traceback_header = True
70
Martin Panterd874c052016-08-20 06:50:58 +000071 self.serv = make_server()
72 self.thread = threading.Thread(target=self.serv.serve_forever)
73 self.thread.start()
Christian Heimes2f1019e2007-12-10 16:18:49 +000074
Martin Panterd874c052016-08-20 06:50:58 +000075 PORT = self.serv.server_address[1]
Georg Brandl24420152008-05-26 16:32:26 +000076 self.client = http.client.HTTPConnection("localhost:%d" % PORT)
Christian Heimes2f1019e2007-12-10 16:18:49 +000077
78 def tearDown(self):
79 self.client.close()
80
Christian Heimes2f1019e2007-12-10 16:18:49 +000081 # Disable server feedback
82 DocXMLRPCServer._send_traceback_header = False
Martin Panterd874c052016-08-20 06:50:58 +000083 self.serv.shutdown()
84 self.thread.join()
85 self.serv.server_close()
Christian Heimes2f1019e2007-12-10 16:18:49 +000086
87 def test_valid_get_response(self):
88 self.client.request("GET", "/")
89 response = self.client.getresponse()
90
91 self.assertEqual(response.status, 200)
92 self.assertEqual(response.getheader("Content-type"), "text/html")
93
Andrew Svetlov737fb892012-12-18 21:14:22 +020094 # Server raises an exception if we don't start to read the data
Christian Heimes2f1019e2007-12-10 16:18:49 +000095 response.read()
96
97 def test_invalid_get_response(self):
98 self.client.request("GET", "/spam")
99 response = self.client.getresponse()
100
101 self.assertEqual(response.status, 404)
102 self.assertEqual(response.getheader("Content-type"), "text/plain")
103
104 response.read()
105
106 def test_lambda(self):
107 """Test that lambda functionality stays the same. The output produced
108 currently is, I suspect invalid because of the unencoded brackets in the
109 HTML, "<lambda>".
110
111 The subtraction lambda method is tested.
112 """
113 self.client.request("GET", "/")
114 response = self.client.getresponse()
115
Ezio Melottib58e0bd2010-01-23 15:40:09 +0000116 self.assertIn((b'<dl><dt><a name="-&lt;lambda&gt;"><strong>'
117 b'&lt;lambda&gt;</strong></a>(x, y)</dt></dl>'),
118 response.read())
Christian Heimes2f1019e2007-12-10 16:18:49 +0000119
R. David Murray378c0cf2010-02-24 01:46:21 +0000120 @make_request_and_skipIf(sys.flags.optimize >= 2,
121 "Docstrings are omitted with -O2 and above")
Christian Heimes2f1019e2007-12-10 16:18:49 +0000122 def test_autolinking(self):
R. David Murray378c0cf2010-02-24 01:46:21 +0000123 """Test that the server correctly automatically wraps references to
124 PEPS and RFCs with links, and that it linkifies text starting with
125 http or ftp protocol prefixes.
Christian Heimes2f1019e2007-12-10 16:18:49 +0000126
127 The documentation for the "add" method contains the test material.
128 """
129 self.client.request("GET", "/")
Christian Heimes2202f872008-02-06 14:31:34 +0000130 response = self.client.getresponse().read()
Christian Heimes2f1019e2007-12-10 16:18:49 +0000131
Ezio Melottib58e0bd2010-01-23 15:40:09 +0000132 self.assertIn(
133 (b'<dl><dt><a name="-add"><strong>add</strong></a>(x, y)</dt><dd>'
134 b'<tt>Add&nbsp;two&nbsp;instances&nbsp;together.&nbsp;This&nbsp;'
135 b'follows&nbsp;<a href="http://www.python.org/dev/peps/pep-0008/">'
136 b'PEP008</a>,&nbsp;but&nbsp;has&nbsp;nothing<br>\nto&nbsp;do&nbsp;'
137 b'with&nbsp;<a href="http://www.rfc-editor.org/rfc/rfc1952.txt">'
138 b'RFC1952</a>.&nbsp;Case&nbsp;should&nbsp;matter:&nbsp;pEp008&nbsp;'
139 b'and&nbsp;rFC1952.&nbsp;&nbsp;Things<br>\nthat&nbsp;start&nbsp;'
140 b'with&nbsp;http&nbsp;and&nbsp;ftp&nbsp;should&nbsp;be&nbsp;'
141 b'auto-linked,&nbsp;too:<br>\n<a href="http://google.com">'
142 b'http://google.com</a>.</tt></dd></dl>'), response)
Christian Heimes2f1019e2007-12-10 16:18:49 +0000143
R. David Murray378c0cf2010-02-24 01:46:21 +0000144 @make_request_and_skipIf(sys.flags.optimize >= 2,
145 "Docstrings are omitted with -O2 and above")
Christian Heimes2f1019e2007-12-10 16:18:49 +0000146 def test_system_methods(self):
Martin Panter69332c12016-08-04 13:07:31 +0000147 """Test the presence of three consecutive system.* methods.
Christian Heimes2f1019e2007-12-10 16:18:49 +0000148
R. David Murray378c0cf2010-02-24 01:46:21 +0000149 This also tests their use of parameter type recognition and the
150 systems related to that process.
Christian Heimes2f1019e2007-12-10 16:18:49 +0000151 """
152 self.client.request("GET", "/")
Christian Heimesa5535f22007-12-10 20:18:07 +0000153 response = self.client.getresponse().read()
Christian Heimes2f1019e2007-12-10 16:18:49 +0000154
Ezio Melottib58e0bd2010-01-23 15:40:09 +0000155 self.assertIn(
156 (b'<dl><dt><a name="-system.methodHelp"><strong>system.methodHelp'
157 b'</strong></a>(method_name)</dt><dd><tt><a href="#-system.method'
158 b'Help">system.methodHelp</a>(\'add\')&nbsp;=&gt;&nbsp;"Adds&nbsp;'
159 b'two&nbsp;integers&nbsp;together"<br>\n&nbsp;<br>\nReturns&nbsp;a'
160 b'&nbsp;string&nbsp;containing&nbsp;documentation&nbsp;for&nbsp;'
161 b'the&nbsp;specified&nbsp;method.</tt></dd></dl>\n<dl><dt><a name'
162 b'="-system.methodSignature"><strong>system.methodSignature</strong>'
163 b'</a>(method_name)</dt><dd><tt><a href="#-system.methodSignature">'
164 b'system.methodSignature</a>(\'add\')&nbsp;=&gt;&nbsp;[double,&nbsp;'
165 b'int,&nbsp;int]<br>\n&nbsp;<br>\nReturns&nbsp;a&nbsp;list&nbsp;'
166 b'describing&nbsp;the&nbsp;signature&nbsp;of&nbsp;the&nbsp;method.'
167 b'&nbsp;In&nbsp;the<br>\nabove&nbsp;example,&nbsp;the&nbsp;add&nbsp;'
168 b'method&nbsp;takes&nbsp;two&nbsp;integers&nbsp;as&nbsp;arguments'
169 b'<br>\nand&nbsp;returns&nbsp;a&nbsp;double&nbsp;result.<br>\n&nbsp;'
170 b'<br>\nThis&nbsp;server&nbsp;does&nbsp;NOT&nbsp;support&nbsp;system'
R David Murrayf22b62e2013-08-10 12:01:47 -0400171 b'.methodSignature.</tt></dd></dl>'), response)
Christian Heimes2f1019e2007-12-10 16:18:49 +0000172
173 def test_autolink_dotted_methods(self):
174 """Test that selfdot values are made strong automatically in the
175 documentation."""
176 self.client.request("GET", "/")
177 response = self.client.getresponse()
178
Ezio Melottib58e0bd2010-01-23 15:40:09 +0000179 self.assertIn(b"""Try&nbsp;self.<strong>add</strong>,&nbsp;too.""",
180 response.read())
Christian Heimes2f1019e2007-12-10 16:18:49 +0000181
R David Murrayf22b62e2013-08-10 12:01:47 -0400182 def test_annotations(self):
183 """ Test that annotations works as expected """
184 self.client.request("GET", "/")
185 response = self.client.getresponse()
Serhiy Storchaka3e60a9d2013-12-08 18:14:49 +0200186 docstring = (b'' if sys.flags.optimize >= 2 else
187 b'<dd><tt>Use&nbsp;function&nbsp;annotations.</tt></dd>')
R David Murrayf22b62e2013-08-10 12:01:47 -0400188 self.assertIn(
189 (b'<dl><dt><a name="-annotation"><strong>annotation</strong></a>'
Serhiy Storchaka3e60a9d2013-12-08 18:14:49 +0200190 b'(x: int)</dt>' + docstring + b'</dl>\n'
191 b'<dl><dt><a name="-method_annotation"><strong>'
R David Murrayf22b62e2013-08-10 12:01:47 -0400192 b'method_annotation</strong></a>(x: bytes)</dt></dl>'),
193 response.read())
194
195
Christian Heimes2f1019e2007-12-10 16:18:49 +0000196if __name__ == '__main__':
Zachary Ware38c707e2015-04-13 15:00:43 -0500197 unittest.main()