blob: 7d3e30cbee964a17ed4d5cf03788993deeb4ff35 [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
Miss Islington (bot)6447b9f2019-09-27 13:19:41 -07003import re
R. David Murray378c0cf2010-02-24 01:46:21 +00004import sys
Antoine Pitroua6a4dc82017-09-07 18:56:24 +02005import threading
Christian Heimes2f1019e2007-12-10 16:18:49 +00006import unittest
Christian Heimes2f1019e2007-12-10 16:18:49 +00007
R. David Murray378c0cf2010-02-24 01:46:21 +00008def make_request_and_skipIf(condition, reason):
Serhiy Storchaka56a6d852014-12-01 18:28:43 +02009 # If we skip the test, we have to make a request because
R. David Murray378c0cf2010-02-24 01:46:21 +000010 # the server created in setUp blocks expecting one to come in.
11 if not condition:
12 return lambda func: func
13 def decorator(func):
14 def make_request_and_skip(self):
15 self.client.request("GET", "/")
16 self.client.getresponse()
17 raise unittest.SkipTest(reason)
18 return make_request_and_skip
19 return decorator
20
21
Martin Panterd874c052016-08-20 06:50:58 +000022def make_server():
Benjamin Petersonf10a79a2008-10-11 00:49:57 +000023 serv = DocXMLRPCServer(("localhost", 0), logRequests=False)
Christian Heimes2f1019e2007-12-10 16:18:49 +000024
Benjamin Petersonf10a79a2008-10-11 00:49:57 +000025 try:
Christian Heimes2f1019e2007-12-10 16:18:49 +000026 # Add some documentation
27 serv.set_server_title("DocXMLRPCServer Test Documentation")
28 serv.set_server_name("DocXMLRPCServer Test Docs")
29 serv.set_server_documentation(
Ezio Melottib58e0bd2010-01-23 15:40:09 +000030 "This is an XML-RPC server's documentation, but the server "
31 "can be used by POSTing to /RPC2. Try self.add, too.")
Christian Heimes2f1019e2007-12-10 16:18:49 +000032
33 # Create and register classes and functions
34 class TestClass(object):
35 def test_method(self, arg):
36 """Test method's docs. This method truly does very little."""
37 self.arg = arg
38
39 serv.register_introspection_functions()
40 serv.register_instance(TestClass())
41
42 def add(x, y):
43 """Add two instances together. This follows PEP008, but has nothing
44 to do with RFC1952. Case should matter: pEp008 and rFC1952. Things
45 that start with http and ftp should be auto-linked, too:
46 http://google.com.
47 """
48 return x + y
49
R David Murrayf22b62e2013-08-10 12:01:47 -040050 def annotation(x: int):
51 """ Use function annotations. """
52 return x
53
54 class ClassWithAnnotation:
55 def method_annotation(self, x: bytes):
56 return x.decode()
57
Christian Heimes2f1019e2007-12-10 16:18:49 +000058 serv.register_function(add)
59 serv.register_function(lambda x, y: x-y)
R David Murrayf22b62e2013-08-10 12:01:47 -040060 serv.register_function(annotation)
61 serv.register_instance(ClassWithAnnotation())
Martin Panterd874c052016-08-20 06:50:58 +000062 return serv
63 except:
Christian Heimes2f1019e2007-12-10 16:18:49 +000064 serv.server_close()
Martin Panterd874c052016-08-20 06:50:58 +000065 raise
Christian Heimes2f1019e2007-12-10 16:18:49 +000066
67class DocXMLRPCHTTPGETServer(unittest.TestCase):
68 def setUp(self):
69 # Enable server feedback
70 DocXMLRPCServer._send_traceback_header = True
71
Martin Panterd874c052016-08-20 06:50:58 +000072 self.serv = make_server()
73 self.thread = threading.Thread(target=self.serv.serve_forever)
74 self.thread.start()
Christian Heimes2f1019e2007-12-10 16:18:49 +000075
Martin Panterd874c052016-08-20 06:50:58 +000076 PORT = self.serv.server_address[1]
Georg Brandl24420152008-05-26 16:32:26 +000077 self.client = http.client.HTTPConnection("localhost:%d" % PORT)
Christian Heimes2f1019e2007-12-10 16:18:49 +000078
79 def tearDown(self):
80 self.client.close()
81
Christian Heimes2f1019e2007-12-10 16:18:49 +000082 # Disable server feedback
83 DocXMLRPCServer._send_traceback_header = False
Martin Panterd874c052016-08-20 06:50:58 +000084 self.serv.shutdown()
85 self.thread.join()
86 self.serv.server_close()
Christian Heimes2f1019e2007-12-10 16:18:49 +000087
88 def test_valid_get_response(self):
89 self.client.request("GET", "/")
90 response = self.client.getresponse()
91
92 self.assertEqual(response.status, 200)
93 self.assertEqual(response.getheader("Content-type"), "text/html")
94
Andrew Svetlov737fb892012-12-18 21:14:22 +020095 # Server raises an exception if we don't start to read the data
Christian Heimes2f1019e2007-12-10 16:18:49 +000096 response.read()
97
98 def test_invalid_get_response(self):
99 self.client.request("GET", "/spam")
100 response = self.client.getresponse()
101
102 self.assertEqual(response.status, 404)
103 self.assertEqual(response.getheader("Content-type"), "text/plain")
104
105 response.read()
106
107 def test_lambda(self):
108 """Test that lambda functionality stays the same. The output produced
109 currently is, I suspect invalid because of the unencoded brackets in the
110 HTML, "<lambda>".
111
112 The subtraction lambda method is tested.
113 """
114 self.client.request("GET", "/")
115 response = self.client.getresponse()
116
Ezio Melottib58e0bd2010-01-23 15:40:09 +0000117 self.assertIn((b'<dl><dt><a name="-&lt;lambda&gt;"><strong>'
118 b'&lt;lambda&gt;</strong></a>(x, y)</dt></dl>'),
119 response.read())
Christian Heimes2f1019e2007-12-10 16:18:49 +0000120
R. David Murray378c0cf2010-02-24 01:46:21 +0000121 @make_request_and_skipIf(sys.flags.optimize >= 2,
122 "Docstrings are omitted with -O2 and above")
Christian Heimes2f1019e2007-12-10 16:18:49 +0000123 def test_autolinking(self):
R. David Murray378c0cf2010-02-24 01:46:21 +0000124 """Test that the server correctly automatically wraps references to
125 PEPS and RFCs with links, and that it linkifies text starting with
126 http or ftp protocol prefixes.
Christian Heimes2f1019e2007-12-10 16:18:49 +0000127
128 The documentation for the "add" method contains the test material.
129 """
130 self.client.request("GET", "/")
Christian Heimes2202f872008-02-06 14:31:34 +0000131 response = self.client.getresponse().read()
Christian Heimes2f1019e2007-12-10 16:18:49 +0000132
Ezio Melottib58e0bd2010-01-23 15:40:09 +0000133 self.assertIn(
134 (b'<dl><dt><a name="-add"><strong>add</strong></a>(x, y)</dt><dd>'
135 b'<tt>Add&nbsp;two&nbsp;instances&nbsp;together.&nbsp;This&nbsp;'
136 b'follows&nbsp;<a href="http://www.python.org/dev/peps/pep-0008/">'
137 b'PEP008</a>,&nbsp;but&nbsp;has&nbsp;nothing<br>\nto&nbsp;do&nbsp;'
138 b'with&nbsp;<a href="http://www.rfc-editor.org/rfc/rfc1952.txt">'
139 b'RFC1952</a>.&nbsp;Case&nbsp;should&nbsp;matter:&nbsp;pEp008&nbsp;'
140 b'and&nbsp;rFC1952.&nbsp;&nbsp;Things<br>\nthat&nbsp;start&nbsp;'
141 b'with&nbsp;http&nbsp;and&nbsp;ftp&nbsp;should&nbsp;be&nbsp;'
142 b'auto-linked,&nbsp;too:<br>\n<a href="http://google.com">'
143 b'http://google.com</a>.</tt></dd></dl>'), response)
Christian Heimes2f1019e2007-12-10 16:18:49 +0000144
R. David Murray378c0cf2010-02-24 01:46:21 +0000145 @make_request_and_skipIf(sys.flags.optimize >= 2,
146 "Docstrings are omitted with -O2 and above")
Christian Heimes2f1019e2007-12-10 16:18:49 +0000147 def test_system_methods(self):
Martin Panter69332c12016-08-04 13:07:31 +0000148 """Test the presence of three consecutive system.* methods.
Christian Heimes2f1019e2007-12-10 16:18:49 +0000149
R. David Murray378c0cf2010-02-24 01:46:21 +0000150 This also tests their use of parameter type recognition and the
151 systems related to that process.
Christian Heimes2f1019e2007-12-10 16:18:49 +0000152 """
153 self.client.request("GET", "/")
Christian Heimesa5535f22007-12-10 20:18:07 +0000154 response = self.client.getresponse().read()
Christian Heimes2f1019e2007-12-10 16:18:49 +0000155
Ezio Melottib58e0bd2010-01-23 15:40:09 +0000156 self.assertIn(
157 (b'<dl><dt><a name="-system.methodHelp"><strong>system.methodHelp'
158 b'</strong></a>(method_name)</dt><dd><tt><a href="#-system.method'
159 b'Help">system.methodHelp</a>(\'add\')&nbsp;=&gt;&nbsp;"Adds&nbsp;'
160 b'two&nbsp;integers&nbsp;together"<br>\n&nbsp;<br>\nReturns&nbsp;a'
161 b'&nbsp;string&nbsp;containing&nbsp;documentation&nbsp;for&nbsp;'
162 b'the&nbsp;specified&nbsp;method.</tt></dd></dl>\n<dl><dt><a name'
163 b'="-system.methodSignature"><strong>system.methodSignature</strong>'
164 b'</a>(method_name)</dt><dd><tt><a href="#-system.methodSignature">'
165 b'system.methodSignature</a>(\'add\')&nbsp;=&gt;&nbsp;[double,&nbsp;'
166 b'int,&nbsp;int]<br>\n&nbsp;<br>\nReturns&nbsp;a&nbsp;list&nbsp;'
167 b'describing&nbsp;the&nbsp;signature&nbsp;of&nbsp;the&nbsp;method.'
168 b'&nbsp;In&nbsp;the<br>\nabove&nbsp;example,&nbsp;the&nbsp;add&nbsp;'
169 b'method&nbsp;takes&nbsp;two&nbsp;integers&nbsp;as&nbsp;arguments'
170 b'<br>\nand&nbsp;returns&nbsp;a&nbsp;double&nbsp;result.<br>\n&nbsp;'
171 b'<br>\nThis&nbsp;server&nbsp;does&nbsp;NOT&nbsp;support&nbsp;system'
R David Murrayf22b62e2013-08-10 12:01:47 -0400172 b'.methodSignature.</tt></dd></dl>'), response)
Christian Heimes2f1019e2007-12-10 16:18:49 +0000173
174 def test_autolink_dotted_methods(self):
175 """Test that selfdot values are made strong automatically in the
176 documentation."""
177 self.client.request("GET", "/")
178 response = self.client.getresponse()
179
Ezio Melottib58e0bd2010-01-23 15:40:09 +0000180 self.assertIn(b"""Try&nbsp;self.<strong>add</strong>,&nbsp;too.""",
181 response.read())
Christian Heimes2f1019e2007-12-10 16:18:49 +0000182
R David Murrayf22b62e2013-08-10 12:01:47 -0400183 def test_annotations(self):
184 """ Test that annotations works as expected """
185 self.client.request("GET", "/")
186 response = self.client.getresponse()
Serhiy Storchaka3e60a9d2013-12-08 18:14:49 +0200187 docstring = (b'' if sys.flags.optimize >= 2 else
188 b'<dd><tt>Use&nbsp;function&nbsp;annotations.</tt></dd>')
R David Murrayf22b62e2013-08-10 12:01:47 -0400189 self.assertIn(
190 (b'<dl><dt><a name="-annotation"><strong>annotation</strong></a>'
Serhiy Storchaka3e60a9d2013-12-08 18:14:49 +0200191 b'(x: int)</dt>' + docstring + b'</dl>\n'
192 b'<dl><dt><a name="-method_annotation"><strong>'
R David Murrayf22b62e2013-08-10 12:01:47 -0400193 b'method_annotation</strong></a>(x: bytes)</dt></dl>'),
194 response.read())
195
Miss Islington (bot)6447b9f2019-09-27 13:19:41 -0700196 def test_server_title_escape(self):
197 # bpo-38243: Ensure that the server title and documentation
198 # are escaped for HTML.
199 self.serv.set_server_title('test_title<script>')
200 self.serv.set_server_documentation('test_documentation<script>')
201 self.assertEqual('test_title<script>', self.serv.server_title)
202 self.assertEqual('test_documentation<script>',
203 self.serv.server_documentation)
204
205 generated = self.serv.generate_html_documentation()
206 title = re.search(r'<title>(.+?)</title>', generated).group()
207 documentation = re.search(r'<p><tt>(.+?)</tt></p>', generated).group()
208 self.assertEqual('<title>Python: test_title&lt;script&gt;</title>', title)
209 self.assertEqual('<p><tt>test_documentation&lt;script&gt;</tt></p>', documentation)
210
R David Murrayf22b62e2013-08-10 12:01:47 -0400211
Christian Heimes2f1019e2007-12-10 16:18:49 +0000212if __name__ == '__main__':
Zachary Ware38c707e2015-04-13 15:00:43 -0500213 unittest.main()