17db96d56Sopenharmony_cifrom xmlrpc.server import DocXMLRPCServer 27db96d56Sopenharmony_ciimport http.client 37db96d56Sopenharmony_ciimport re 47db96d56Sopenharmony_ciimport sys 57db96d56Sopenharmony_ciimport threading 67db96d56Sopenharmony_ciimport unittest 77db96d56Sopenharmony_cifrom test import support 87db96d56Sopenharmony_ci 97db96d56Sopenharmony_cisupport.requires_working_socket(module=True) 107db96d56Sopenharmony_ci 117db96d56Sopenharmony_cidef make_request_and_skipIf(condition, reason): 127db96d56Sopenharmony_ci # If we skip the test, we have to make a request because 137db96d56Sopenharmony_ci # the server created in setUp blocks expecting one to come in. 147db96d56Sopenharmony_ci if not condition: 157db96d56Sopenharmony_ci return lambda func: func 167db96d56Sopenharmony_ci def decorator(func): 177db96d56Sopenharmony_ci def make_request_and_skip(self): 187db96d56Sopenharmony_ci self.client.request("GET", "/") 197db96d56Sopenharmony_ci self.client.getresponse() 207db96d56Sopenharmony_ci raise unittest.SkipTest(reason) 217db96d56Sopenharmony_ci return make_request_and_skip 227db96d56Sopenharmony_ci return decorator 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_ci 257db96d56Sopenharmony_cidef make_server(): 267db96d56Sopenharmony_ci serv = DocXMLRPCServer(("localhost", 0), logRequests=False) 277db96d56Sopenharmony_ci 287db96d56Sopenharmony_ci try: 297db96d56Sopenharmony_ci # Add some documentation 307db96d56Sopenharmony_ci serv.set_server_title("DocXMLRPCServer Test Documentation") 317db96d56Sopenharmony_ci serv.set_server_name("DocXMLRPCServer Test Docs") 327db96d56Sopenharmony_ci serv.set_server_documentation( 337db96d56Sopenharmony_ci "This is an XML-RPC server's documentation, but the server " 347db96d56Sopenharmony_ci "can be used by POSTing to /RPC2. Try self.add, too.") 357db96d56Sopenharmony_ci 367db96d56Sopenharmony_ci # Create and register classes and functions 377db96d56Sopenharmony_ci class TestClass(object): 387db96d56Sopenharmony_ci def test_method(self, arg): 397db96d56Sopenharmony_ci """Test method's docs. This method truly does very little.""" 407db96d56Sopenharmony_ci self.arg = arg 417db96d56Sopenharmony_ci 427db96d56Sopenharmony_ci serv.register_introspection_functions() 437db96d56Sopenharmony_ci serv.register_instance(TestClass()) 447db96d56Sopenharmony_ci 457db96d56Sopenharmony_ci def add(x, y): 467db96d56Sopenharmony_ci """Add two instances together. This follows PEP008, but has nothing 477db96d56Sopenharmony_ci to do with RFC1952. Case should matter: pEp008 and rFC1952. Things 487db96d56Sopenharmony_ci that start with http and ftp should be auto-linked, too: 497db96d56Sopenharmony_ci http://google.com. 507db96d56Sopenharmony_ci """ 517db96d56Sopenharmony_ci return x + y 527db96d56Sopenharmony_ci 537db96d56Sopenharmony_ci def annotation(x: int): 547db96d56Sopenharmony_ci """ Use function annotations. """ 557db96d56Sopenharmony_ci return x 567db96d56Sopenharmony_ci 577db96d56Sopenharmony_ci class ClassWithAnnotation: 587db96d56Sopenharmony_ci def method_annotation(self, x: bytes): 597db96d56Sopenharmony_ci return x.decode() 607db96d56Sopenharmony_ci 617db96d56Sopenharmony_ci serv.register_function(add) 627db96d56Sopenharmony_ci serv.register_function(lambda x, y: x-y) 637db96d56Sopenharmony_ci serv.register_function(annotation) 647db96d56Sopenharmony_ci serv.register_instance(ClassWithAnnotation()) 657db96d56Sopenharmony_ci return serv 667db96d56Sopenharmony_ci except: 677db96d56Sopenharmony_ci serv.server_close() 687db96d56Sopenharmony_ci raise 697db96d56Sopenharmony_ci 707db96d56Sopenharmony_ciclass DocXMLRPCHTTPGETServer(unittest.TestCase): 717db96d56Sopenharmony_ci def setUp(self): 727db96d56Sopenharmony_ci # Enable server feedback 737db96d56Sopenharmony_ci DocXMLRPCServer._send_traceback_header = True 747db96d56Sopenharmony_ci 757db96d56Sopenharmony_ci self.serv = make_server() 767db96d56Sopenharmony_ci self.thread = threading.Thread(target=self.serv.serve_forever) 777db96d56Sopenharmony_ci self.thread.start() 787db96d56Sopenharmony_ci 797db96d56Sopenharmony_ci PORT = self.serv.server_address[1] 807db96d56Sopenharmony_ci self.client = http.client.HTTPConnection("localhost:%d" % PORT) 817db96d56Sopenharmony_ci 827db96d56Sopenharmony_ci def tearDown(self): 837db96d56Sopenharmony_ci self.client.close() 847db96d56Sopenharmony_ci 857db96d56Sopenharmony_ci # Disable server feedback 867db96d56Sopenharmony_ci DocXMLRPCServer._send_traceback_header = False 877db96d56Sopenharmony_ci self.serv.shutdown() 887db96d56Sopenharmony_ci self.thread.join() 897db96d56Sopenharmony_ci self.serv.server_close() 907db96d56Sopenharmony_ci 917db96d56Sopenharmony_ci def test_valid_get_response(self): 927db96d56Sopenharmony_ci self.client.request("GET", "/") 937db96d56Sopenharmony_ci response = self.client.getresponse() 947db96d56Sopenharmony_ci 957db96d56Sopenharmony_ci self.assertEqual(response.status, 200) 967db96d56Sopenharmony_ci self.assertEqual(response.getheader("Content-type"), "text/html; charset=UTF-8") 977db96d56Sopenharmony_ci 987db96d56Sopenharmony_ci # Server raises an exception if we don't start to read the data 997db96d56Sopenharmony_ci response.read() 1007db96d56Sopenharmony_ci 1017db96d56Sopenharmony_ci def test_get_css(self): 1027db96d56Sopenharmony_ci self.client.request("GET", "/pydoc.css") 1037db96d56Sopenharmony_ci response = self.client.getresponse() 1047db96d56Sopenharmony_ci 1057db96d56Sopenharmony_ci self.assertEqual(response.status, 200) 1067db96d56Sopenharmony_ci self.assertEqual(response.getheader("Content-type"), "text/css; charset=UTF-8") 1077db96d56Sopenharmony_ci 1087db96d56Sopenharmony_ci # Server raises an exception if we don't start to read the data 1097db96d56Sopenharmony_ci response.read() 1107db96d56Sopenharmony_ci 1117db96d56Sopenharmony_ci def test_invalid_get_response(self): 1127db96d56Sopenharmony_ci self.client.request("GET", "/spam") 1137db96d56Sopenharmony_ci response = self.client.getresponse() 1147db96d56Sopenharmony_ci 1157db96d56Sopenharmony_ci self.assertEqual(response.status, 404) 1167db96d56Sopenharmony_ci self.assertEqual(response.getheader("Content-type"), "text/plain") 1177db96d56Sopenharmony_ci 1187db96d56Sopenharmony_ci response.read() 1197db96d56Sopenharmony_ci 1207db96d56Sopenharmony_ci def test_lambda(self): 1217db96d56Sopenharmony_ci """Test that lambda functionality stays the same. The output produced 1227db96d56Sopenharmony_ci currently is, I suspect invalid because of the unencoded brackets in the 1237db96d56Sopenharmony_ci HTML, "<lambda>". 1247db96d56Sopenharmony_ci 1257db96d56Sopenharmony_ci The subtraction lambda method is tested. 1267db96d56Sopenharmony_ci """ 1277db96d56Sopenharmony_ci self.client.request("GET", "/") 1287db96d56Sopenharmony_ci response = self.client.getresponse() 1297db96d56Sopenharmony_ci 1307db96d56Sopenharmony_ci self.assertIn((b'<dl><dt><a name="-<lambda>"><strong>' 1317db96d56Sopenharmony_ci b'<lambda></strong></a>(x, y)</dt></dl>'), 1327db96d56Sopenharmony_ci response.read()) 1337db96d56Sopenharmony_ci 1347db96d56Sopenharmony_ci @make_request_and_skipIf(sys.flags.optimize >= 2, 1357db96d56Sopenharmony_ci "Docstrings are omitted with -O2 and above") 1367db96d56Sopenharmony_ci def test_autolinking(self): 1377db96d56Sopenharmony_ci """Test that the server correctly automatically wraps references to 1387db96d56Sopenharmony_ci PEPS and RFCs with links, and that it linkifies text starting with 1397db96d56Sopenharmony_ci http or ftp protocol prefixes. 1407db96d56Sopenharmony_ci 1417db96d56Sopenharmony_ci The documentation for the "add" method contains the test material. 1427db96d56Sopenharmony_ci """ 1437db96d56Sopenharmony_ci self.client.request("GET", "/") 1447db96d56Sopenharmony_ci response = self.client.getresponse().read() 1457db96d56Sopenharmony_ci 1467db96d56Sopenharmony_ci self.assertIn( 1477db96d56Sopenharmony_ci (b'<dl><dt><a name="-add"><strong>add</strong></a>(x, y)</dt><dd>' 1487db96d56Sopenharmony_ci b'<tt>Add two instances together. This ' 1497db96d56Sopenharmony_ci b'follows <a href="https://peps.python.org/pep-0008/">' 1507db96d56Sopenharmony_ci b'PEP008</a>, but has nothing<br>\nto do ' 1517db96d56Sopenharmony_ci b'with <a href="https://www.rfc-editor.org/rfc/rfc1952.txt">' 1527db96d56Sopenharmony_ci b'RFC1952</a>. Case should matter: pEp008 ' 1537db96d56Sopenharmony_ci b'and rFC1952. Things<br>\nthat start ' 1547db96d56Sopenharmony_ci b'with http and ftp should be ' 1557db96d56Sopenharmony_ci b'auto-linked, too:<br>\n<a href="http://google.com">' 1567db96d56Sopenharmony_ci b'http://google.com</a>.</tt></dd></dl>'), response) 1577db96d56Sopenharmony_ci 1587db96d56Sopenharmony_ci @make_request_and_skipIf(sys.flags.optimize >= 2, 1597db96d56Sopenharmony_ci "Docstrings are omitted with -O2 and above") 1607db96d56Sopenharmony_ci def test_system_methods(self): 1617db96d56Sopenharmony_ci """Test the presence of three consecutive system.* methods. 1627db96d56Sopenharmony_ci 1637db96d56Sopenharmony_ci This also tests their use of parameter type recognition and the 1647db96d56Sopenharmony_ci systems related to that process. 1657db96d56Sopenharmony_ci """ 1667db96d56Sopenharmony_ci self.client.request("GET", "/") 1677db96d56Sopenharmony_ci response = self.client.getresponse().read() 1687db96d56Sopenharmony_ci 1697db96d56Sopenharmony_ci self.assertIn( 1707db96d56Sopenharmony_ci (b'<dl><dt><a name="-system.methodHelp"><strong>system.methodHelp' 1717db96d56Sopenharmony_ci b'</strong></a>(method_name)</dt><dd><tt><a href="#-system.method' 1727db96d56Sopenharmony_ci b'Help">system.methodHelp</a>(\'add\') => "Adds ' 1737db96d56Sopenharmony_ci b'two integers together"<br>\n <br>\nReturns a' 1747db96d56Sopenharmony_ci b' string containing documentation for ' 1757db96d56Sopenharmony_ci b'the specified method.</tt></dd></dl>\n<dl><dt><a name' 1767db96d56Sopenharmony_ci b'="-system.methodSignature"><strong>system.methodSignature</strong>' 1777db96d56Sopenharmony_ci b'</a>(method_name)</dt><dd><tt><a href="#-system.methodSignature">' 1787db96d56Sopenharmony_ci b'system.methodSignature</a>(\'add\') => [double, ' 1797db96d56Sopenharmony_ci b'int, int]<br>\n <br>\nReturns a list ' 1807db96d56Sopenharmony_ci b'describing the signature of the method.' 1817db96d56Sopenharmony_ci b' In the<br>\nabove example, the add ' 1827db96d56Sopenharmony_ci b'method takes two integers as arguments' 1837db96d56Sopenharmony_ci b'<br>\nand returns a double result.<br>\n ' 1847db96d56Sopenharmony_ci b'<br>\nThis server does NOT support system' 1857db96d56Sopenharmony_ci b'.methodSignature.</tt></dd></dl>'), response) 1867db96d56Sopenharmony_ci 1877db96d56Sopenharmony_ci def test_autolink_dotted_methods(self): 1887db96d56Sopenharmony_ci """Test that selfdot values are made strong automatically in the 1897db96d56Sopenharmony_ci documentation.""" 1907db96d56Sopenharmony_ci self.client.request("GET", "/") 1917db96d56Sopenharmony_ci response = self.client.getresponse() 1927db96d56Sopenharmony_ci 1937db96d56Sopenharmony_ci self.assertIn(b"""Try self.<strong>add</strong>, too.""", 1947db96d56Sopenharmony_ci response.read()) 1957db96d56Sopenharmony_ci 1967db96d56Sopenharmony_ci def test_annotations(self): 1977db96d56Sopenharmony_ci """ Test that annotations works as expected """ 1987db96d56Sopenharmony_ci self.client.request("GET", "/") 1997db96d56Sopenharmony_ci response = self.client.getresponse() 2007db96d56Sopenharmony_ci docstring = (b'' if sys.flags.optimize >= 2 else 2017db96d56Sopenharmony_ci b'<dd><tt>Use function annotations.</tt></dd>') 2027db96d56Sopenharmony_ci self.assertIn( 2037db96d56Sopenharmony_ci (b'<dl><dt><a name="-annotation"><strong>annotation</strong></a>' 2047db96d56Sopenharmony_ci b'(x: int)</dt>' + docstring + b'</dl>\n' 2057db96d56Sopenharmony_ci b'<dl><dt><a name="-method_annotation"><strong>' 2067db96d56Sopenharmony_ci b'method_annotation</strong></a>(x: bytes)</dt></dl>'), 2077db96d56Sopenharmony_ci response.read()) 2087db96d56Sopenharmony_ci 2097db96d56Sopenharmony_ci def test_server_title_escape(self): 2107db96d56Sopenharmony_ci # bpo-38243: Ensure that the server title and documentation 2117db96d56Sopenharmony_ci # are escaped for HTML. 2127db96d56Sopenharmony_ci self.serv.set_server_title('test_title<script>') 2137db96d56Sopenharmony_ci self.serv.set_server_documentation('test_documentation<script>') 2147db96d56Sopenharmony_ci self.assertEqual('test_title<script>', self.serv.server_title) 2157db96d56Sopenharmony_ci self.assertEqual('test_documentation<script>', 2167db96d56Sopenharmony_ci self.serv.server_documentation) 2177db96d56Sopenharmony_ci 2187db96d56Sopenharmony_ci generated = self.serv.generate_html_documentation() 2197db96d56Sopenharmony_ci title = re.search(r'<title>(.+?)</title>', generated).group() 2207db96d56Sopenharmony_ci documentation = re.search(r'<p><tt>(.+?)</tt></p>', generated).group() 2217db96d56Sopenharmony_ci self.assertEqual('<title>Python: test_title<script></title>', title) 2227db96d56Sopenharmony_ci self.assertEqual('<p><tt>test_documentation<script></tt></p>', documentation) 2237db96d56Sopenharmony_ci 2247db96d56Sopenharmony_ci 2257db96d56Sopenharmony_ciif __name__ == '__main__': 2267db96d56Sopenharmony_ci unittest.main() 227