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