1 from xmlrpc.server import DocXMLRPCServer
2 import http.client
3 import re
4 import sys
5 import threading
6 import unittest
7 from test import support
8
9 support.requires_working_socket(module=True)
10
11 def make_request_and_skipIf(condition, reason):
12 # If we skip the test, we have to make a request because
13 # the server created in setUp blocks expecting one to come in.
14 if not condition:
15 return lambda func: func
16 def decorator(func):
17 def make_request_and_skip(self):
18 self.client.request("GET", "/")
19 self.client.getresponse()
20 raise unittest.SkipTest(reason)
21 return make_request_and_skip
22 return decorator
23
24
25 def make_server():
26 serv = DocXMLRPCServer(("localhost", 0), logRequests=False)
27
28 try:
29 # Add some documentation
30 serv.set_server_title("DocXMLRPCServer Test Documentation")
31 serv.set_server_name("DocXMLRPCServer Test Docs")
32 serv.set_server_documentation(
33 "This is an XML-RPC server's documentation, but the server "
34 "can be used by POSTing to /RPC2. Try self.add, too.")
35
36 # Create and register classes and functions
37 class ESC[4;38;5;81mTestClass(ESC[4;38;5;149mobject):
38 def test_method(self, arg):
39 """Test method's docs. This method truly does very little."""
40 self.arg = arg
41
42 serv.register_introspection_functions()
43 serv.register_instance(TestClass())
44
45 def add(x, y):
46 """Add two instances together. This follows PEP008, but has nothing
47 to do with RFC1952. Case should matter: pEp008 and rFC1952. Things
48 that start with http and ftp should be auto-linked, too:
49 http://google.com.
50 """
51 return x + y
52
53 def annotation(x: int):
54 """ Use function annotations. """
55 return x
56
57 class ESC[4;38;5;81mClassWithAnnotation:
58 def method_annotation(self, x: bytes):
59 return x.decode()
60
61 serv.register_function(add)
62 serv.register_function(lambda x, y: x-y)
63 serv.register_function(annotation)
64 serv.register_instance(ClassWithAnnotation())
65 return serv
66 except:
67 serv.server_close()
68 raise
69
70 class ESC[4;38;5;81mDocXMLRPCHTTPGETServer(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
71 def setUp(self):
72 # Enable server feedback
73 DocXMLRPCServer._send_traceback_header = True
74
75 self.serv = make_server()
76 self.thread = threading.Thread(target=self.serv.serve_forever)
77 self.thread.start()
78
79 PORT = self.serv.server_address[1]
80 self.client = http.client.HTTPConnection("localhost:%d" % PORT)
81
82 def tearDown(self):
83 self.client.close()
84
85 # Disable server feedback
86 DocXMLRPCServer._send_traceback_header = False
87 self.serv.shutdown()
88 self.thread.join()
89 self.serv.server_close()
90
91 def test_valid_get_response(self):
92 self.client.request("GET", "/")
93 response = self.client.getresponse()
94
95 self.assertEqual(response.status, 200)
96 self.assertEqual(response.getheader("Content-type"), "text/html; charset=UTF-8")
97
98 # Server raises an exception if we don't start to read the data
99 response.read()
100
101 def test_get_css(self):
102 self.client.request("GET", "/pydoc.css")
103 response = self.client.getresponse()
104
105 self.assertEqual(response.status, 200)
106 self.assertEqual(response.getheader("Content-type"), "text/css; charset=UTF-8")
107
108 # Server raises an exception if we don't start to read the data
109 response.read()
110
111 def test_invalid_get_response(self):
112 self.client.request("GET", "/spam")
113 response = self.client.getresponse()
114
115 self.assertEqual(response.status, 404)
116 self.assertEqual(response.getheader("Content-type"), "text/plain")
117
118 response.read()
119
120 def test_lambda(self):
121 """Test that lambda functionality stays the same. The output produced
122 currently is, I suspect invalid because of the unencoded brackets in the
123 HTML, "<lambda>".
124
125 The subtraction lambda method is tested.
126 """
127 self.client.request("GET", "/")
128 response = self.client.getresponse()
129
130 self.assertIn((b'<dl><dt><a name="-<lambda>"><strong>'
131 b'<lambda></strong></a>(x, y)</dt></dl>'),
132 response.read())
133
134 @make_request_and_skipIf(sys.flags.optimize >= 2,
135 "Docstrings are omitted with -O2 and above")
136 def test_autolinking(self):
137 """Test that the server correctly automatically wraps references to
138 PEPS and RFCs with links, and that it linkifies text starting with
139 http or ftp protocol prefixes.
140
141 The documentation for the "add" method contains the test material.
142 """
143 self.client.request("GET", "/")
144 response = self.client.getresponse().read()
145
146 self.assertIn(
147 (b'<dl><dt><a name="-add"><strong>add</strong></a>(x, y)</dt><dd>'
148 b'<tt>Add two instances together. This '
149 b'follows <a href="https://peps.python.org/pep-0008/">'
150 b'PEP008</a>, but has nothing<br>\nto do '
151 b'with <a href="https://www.rfc-editor.org/rfc/rfc1952.txt">'
152 b'RFC1952</a>. Case should matter: pEp008 '
153 b'and rFC1952. Things<br>\nthat start '
154 b'with http and ftp should be '
155 b'auto-linked, too:<br>\n<a href="http://google.com">'
156 b'http://google.com</a>.</tt></dd></dl>'), response)
157
158 @make_request_and_skipIf(sys.flags.optimize >= 2,
159 "Docstrings are omitted with -O2 and above")
160 def test_system_methods(self):
161 """Test the presence of three consecutive system.* methods.
162
163 This also tests their use of parameter type recognition and the
164 systems related to that process.
165 """
166 self.client.request("GET", "/")
167 response = self.client.getresponse().read()
168
169 self.assertIn(
170 (b'<dl><dt><a name="-system.methodHelp"><strong>system.methodHelp'
171 b'</strong></a>(method_name)</dt><dd><tt><a href="#-system.method'
172 b'Help">system.methodHelp</a>(\'add\') => "Adds '
173 b'two integers together"<br>\n <br>\nReturns a'
174 b' string containing documentation for '
175 b'the specified method.</tt></dd></dl>\n<dl><dt><a name'
176 b'="-system.methodSignature"><strong>system.methodSignature</strong>'
177 b'</a>(method_name)</dt><dd><tt><a href="#-system.methodSignature">'
178 b'system.methodSignature</a>(\'add\') => [double, '
179 b'int, int]<br>\n <br>\nReturns a list '
180 b'describing the signature of the method.'
181 b' In the<br>\nabove example, the add '
182 b'method takes two integers as arguments'
183 b'<br>\nand returns a double result.<br>\n '
184 b'<br>\nThis server does NOT support system'
185 b'.methodSignature.</tt></dd></dl>'), response)
186
187 def test_autolink_dotted_methods(self):
188 """Test that selfdot values are made strong automatically in the
189 documentation."""
190 self.client.request("GET", "/")
191 response = self.client.getresponse()
192
193 self.assertIn(b"""Try self.<strong>add</strong>, too.""",
194 response.read())
195
196 def test_annotations(self):
197 """ Test that annotations works as expected """
198 self.client.request("GET", "/")
199 response = self.client.getresponse()
200 docstring = (b'' if sys.flags.optimize >= 2 else
201 b'<dd><tt>Use function annotations.</tt></dd>')
202 self.assertIn(
203 (b'<dl><dt><a name="-annotation"><strong>annotation</strong></a>'
204 b'(x: int)</dt>' + docstring + b'</dl>\n'
205 b'<dl><dt><a name="-method_annotation"><strong>'
206 b'method_annotation</strong></a>(x: bytes)</dt></dl>'),
207 response.read())
208
209 def test_server_title_escape(self):
210 # bpo-38243: Ensure that the server title and documentation
211 # are escaped for HTML.
212 self.serv.set_server_title('test_title<script>')
213 self.serv.set_server_documentation('test_documentation<script>')
214 self.assertEqual('test_title<script>', self.serv.server_title)
215 self.assertEqual('test_documentation<script>',
216 self.serv.server_documentation)
217
218 generated = self.serv.generate_html_documentation()
219 title = re.search(r'<title>(.+?)</title>', generated).group()
220 documentation = re.search(r'<p><tt>(.+?)</tt></p>', generated).group()
221 self.assertEqual('<title>Python: test_title<script></title>', title)
222 self.assertEqual('<p><tt>test_documentation<script></tt></p>', documentation)
223
224
225 if __name__ == '__main__':
226 unittest.main()