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="-&lt;lambda&gt;"><strong>'
1317db96d56Sopenharmony_ci                       b'&lt;lambda&gt;</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&nbsp;two&nbsp;instances&nbsp;together.&nbsp;This&nbsp;'
1497db96d56Sopenharmony_ci             b'follows&nbsp;<a href="https://peps.python.org/pep-0008/">'
1507db96d56Sopenharmony_ci             b'PEP008</a>,&nbsp;but&nbsp;has&nbsp;nothing<br>\nto&nbsp;do&nbsp;'
1517db96d56Sopenharmony_ci             b'with&nbsp;<a href="https://www.rfc-editor.org/rfc/rfc1952.txt">'
1527db96d56Sopenharmony_ci             b'RFC1952</a>.&nbsp;Case&nbsp;should&nbsp;matter:&nbsp;pEp008&nbsp;'
1537db96d56Sopenharmony_ci             b'and&nbsp;rFC1952.&nbsp;&nbsp;Things<br>\nthat&nbsp;start&nbsp;'
1547db96d56Sopenharmony_ci             b'with&nbsp;http&nbsp;and&nbsp;ftp&nbsp;should&nbsp;be&nbsp;'
1557db96d56Sopenharmony_ci             b'auto-linked,&nbsp;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\')&nbsp;=&gt;&nbsp;"Adds&nbsp;'
1737db96d56Sopenharmony_ci             b'two&nbsp;integers&nbsp;together"<br>\n&nbsp;<br>\nReturns&nbsp;a'
1747db96d56Sopenharmony_ci             b'&nbsp;string&nbsp;containing&nbsp;documentation&nbsp;for&nbsp;'
1757db96d56Sopenharmony_ci             b'the&nbsp;specified&nbsp;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\')&nbsp;=&gt;&nbsp;[double,&nbsp;'
1797db96d56Sopenharmony_ci             b'int,&nbsp;int]<br>\n&nbsp;<br>\nReturns&nbsp;a&nbsp;list&nbsp;'
1807db96d56Sopenharmony_ci             b'describing&nbsp;the&nbsp;signature&nbsp;of&nbsp;the&nbsp;method.'
1817db96d56Sopenharmony_ci             b'&nbsp;In&nbsp;the<br>\nabove&nbsp;example,&nbsp;the&nbsp;add&nbsp;'
1827db96d56Sopenharmony_ci             b'method&nbsp;takes&nbsp;two&nbsp;integers&nbsp;as&nbsp;arguments'
1837db96d56Sopenharmony_ci             b'<br>\nand&nbsp;returns&nbsp;a&nbsp;double&nbsp;result.<br>\n&nbsp;'
1847db96d56Sopenharmony_ci             b'<br>\nThis&nbsp;server&nbsp;does&nbsp;NOT&nbsp;support&nbsp;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&nbsp;self.<strong>add</strong>,&nbsp;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&nbsp;function&nbsp;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&lt;script&gt;</title>', title)
2227db96d56Sopenharmony_ci        self.assertEqual('<p><tt>test_documentation&lt;script&gt;</tt></p>', documentation)
2237db96d56Sopenharmony_ci
2247db96d56Sopenharmony_ci
2257db96d56Sopenharmony_ciif __name__ == '__main__':
2267db96d56Sopenharmony_ci    unittest.main()
227