From 79db2f2216f347c2ad19ef2c2c188a9155906b5c Mon Sep 17 00:00:00 2001 From: ggqlq Date: Tue, 15 Apr 2025 02:35:59 +0800 Subject: [PATCH 01/61] Add tests for http.server command-line interface --- Lib/http/server.py | 29 +++++-- Lib/test/test_httpservers.py | 157 ++++++++++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 7 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index a2aad4c9be3c51..d0a7777f67c76a 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -106,6 +106,8 @@ import sys import time import urllib.parse +import argparse +import contextlib from http import HTTPStatus @@ -150,6 +152,20 @@ def server_bind(self): class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer): daemon_threads = True +class CommandLineServerClass(ThreadingHTTPServer): + def __init__(self, server_address, RequestHandlerClass, directory=None): + super().__init__(server_address, RequestHandlerClass) + self.directory = directory + def server_bind(self): + # suppress exception when protocol is IPv4 + with contextlib.suppress(Exception): + self.socket.setsockopt( + socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + return super().server_bind() + + def finish_request(self, request, client_address): + self.RequestHandlerClass(request, client_address, self, + directory=self.directory) class HTTPSServer(HTTPServer): def __init__(self, server_address, RequestHandlerClass, @@ -1336,10 +1352,7 @@ def test(HandlerClass=BaseHTTPRequestHandler, print("\nKeyboard interrupt received, exiting.") sys.exit(0) -if __name__ == '__main__': - import argparse - import contextlib - +def _main(args=None): parser = argparse.ArgumentParser() parser.add_argument('--cgi', action='store_true', help='run as CGI server') @@ -1362,7 +1375,7 @@ def test(HandlerClass=BaseHTTPRequestHandler, parser.add_argument('port', default=8000, type=int, nargs='?', help='bind to this port ' '(default: %(default)s)') - args = parser.parse_args() + args = parser.parse_args(args=args) if not args.tls_cert and args.tls_key: parser.error("--tls-key requires --tls-cert to be set") @@ -1399,7 +1412,7 @@ def finish_request(self, request, client_address): test( HandlerClass=handler_class, - ServerClass=DualStackServer, + ServerClass=CommandLineServerClass, port=args.port, bind=args.bind, protocol=args.protocol, @@ -1407,3 +1420,7 @@ def finish_request(self, request, client_address): tls_key=args.tls_key, tls_password=tls_key_password, ) + + +if __name__ == '__main__': + _main() \ No newline at end of file diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 2cafa4e45a1313..51a63715a3a4a8 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -8,6 +8,7 @@ SimpleHTTPRequestHandler, CGIHTTPRequestHandler from http import server, HTTPStatus +import http.server import os import socket import sys @@ -27,6 +28,8 @@ import threading from unittest import mock from io import BytesIO, StringIO +import textwrap +import contextlib import unittest from test import support @@ -1466,7 +1469,7 @@ def test_windows_colon(self): class MiscTestCase(unittest.TestCase): def test_all(self): expected = [] - denylist = {'executable', 'nobody_uid', 'test'} + denylist = {'executable', 'nobody_uid', 'test', 'CommandLineServerClass', 'CommandLineServerClass'} for name in dir(server): if name.startswith('_') or name in denylist: continue @@ -1535,6 +1538,158 @@ def test_server_test_ipv4(self, _): server.test(ServerClass=mock_server, bind=bind) self.assertEqual(mock_server.address_family, socket.AF_INET) +class CommandLineTestCase(unittest.TestCase): + def setUp(self): + self.default_port = 8000 + self.default_bind = None + self.default_protocol = 'HTTP/1.0' + self.default_handler = SimpleHTTPRequestHandler + self.default_server = http.server.CommandLineServerClass + self.tls_cert = '-----BEGIN CERTIFICATE-----\n' + '-----END CERTIFICATE-----\n' + self.tls_key = '-----BEGIN RSA PRIVATE KEY-----\n' + '-----END RSA PRIVATE KEY-----\n' + + self.tls_password = '' + tls_password_file_object = tempfile.NamedTemporaryFile(mode='w+', delete=False) + tls_password_file_object.write(self.tls_password) + self.tls_password_file = tls_password_file_object.name + tls_password_file_object.close() + return super().setUp() + + def tearDown(self): + if os.path.exists(self.tls_password_file): + os.remove(self.tls_password_file) + return super().tearDown() + + def text_normalizer(self, string): + return textwrap.dedent(string).strip() + + def invoke_httpd(self, args=[]): + output = StringIO() + with contextlib.redirect_stdout(output): + server._main(args) + return self.text_normalizer(output.getvalue()) + + @mock.patch('http.server.test') + def test_port_flag(self, mock_func): + ports = [8000, 65535,] + for port in ports: + with self.subTest(port=port): + self.invoke_httpd([str(port)]) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, + protocol=self.default_protocol, port=port, bind=self.default_bind, tls_cert=None, + tls_key=None, tls_password=None) + mock_func.reset_mock() + + @mock.patch('http.server.test') + def test_directory_flag(self, mock_func): + options = ['-d', '--directory'] + directories = ['.', '/foo', '\\bar', '/', 'C:\\', 'C:\\foo', 'C:\\bar',] + for flag in options: + for directory in directories: + with self.subTest(flag=flag, directory=directory): + self.invoke_httpd([flag, directory]) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, + tls_cert=None, tls_key=None, tls_password=None) + mock_func.reset_mock() + + @mock.patch('http.server.test') + def test_bind_flag(self, mock_func): + options = ['-b', '--bind'] + bind_addresses = ['localhost', '127.0.0.1', '::1', '0.0.0.0', '8.8.8.8',] + for flag in options: + for bind_address in bind_addresses: + with self.subTest(flag=flag, bind_address=bind_address): + self.invoke_httpd([flag, bind_address]) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, + protocol=self.default_protocol, port=self.default_port, bind=bind_address, + tls_cert=None, tls_key=None, tls_password=None) + mock_func.reset_mock() + + @mock.patch('http.server.test') + def test_protocol_flag(self, mock_func): + options = ['-p', '--protocol'] + protocols = ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2.0', 'HTTP/3.0',] + for flag in options: + for protocol in protocols: + with self.subTest(flag=flag, protocol=protocol): + self.invoke_httpd([flag, protocol]) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, + protocol=protocol, port=self.default_port, bind=self.default_bind, + tls_cert=None, tls_key=None, tls_password=None) + mock_func.reset_mock() + + @mock.patch('http.server.test') + def test_cgi_flag(self, mock_func): + self.invoke_httpd(['--cgi']) + mock_func.assert_called_once_with(HandlerClass=CGIHTTPRequestHandler, ServerClass=self.default_server, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, + tls_cert=None, tls_key=None, tls_password=None) + + @mock.patch('http.server.test') + def test_tls_flag(self, mock_func): + tls_cert_options = ['--tls-cert', ] + tls_key_options = ['--tls-key', ] + tls_password_options = ['--tls-password-file', ] + # Normal: --tls-cert and --tls-key + + for tls_cert_option in tls_cert_options: + for tls_key_option in tls_key_options: + self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key]) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, + tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=None) + mock_func.reset_mock() + + # Normal: --tls-cert, --tls-key and --tls-password-file + + for tls_cert_option in tls_cert_options: + for tls_key_option in tls_key_options: + for tls_password_option in tls_password_options: + self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key, tls_password_option, self.tls_password_file]) + + mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, + tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=self.tls_password) + mock_func.reset_mock() + + # Abnormal: --tls-key without --tls-cert + + for tls_key_option in tls_key_options: + for tls_cert_option in tls_cert_options: + with self.assertRaises(SystemExit): + self.invoke_httpd([tls_key_option, self.tls_key]) + mock_func.reset_mock() + + # Abnormal: --tls-password-file without --tls-cert + + for tls_password_option in tls_password_options: + with self.assertRaises(SystemExit): + self.invoke_httpd([tls_password_option, self.tls_password_file]) + mock_func.reset_mock() + + # Abnormal: --tls-password-file cannot be opened + + non_existent_file = os.path.join(tempfile.gettempdir(), os.urandom(16).hex()) + retry_count = 0 + while os.path.exists(non_existent_file) and retry_count < 10: + non_existent_file = os.path.join(tempfile.gettempdir(), os.urandom(16).hex()) + if not os.path.exists(non_existent_file): + for tls_password_option in tls_password_options: + for tls_cert_option in tls_cert_options: + with self.assertRaises(SystemExit): + self.invoke_httpd([tls_cert_option, self.tls_cert, tls_password_option, non_existent_file]) + + def test_help_flag(self): + options = ['-h', '--help'] + for option in options: + with self.assertRaises(SystemExit): + output = self.invoke_httpd([option]) + self.assertIn('usage:', output) + + def test_unknown_flag(self): + with self.assertRaises(SystemExit): + self.invoke_httpd(['--unknown-flag']) def setUpModule(): unittest.addModuleCleanup(os.chdir, os.getcwd()) From 2b589bf54be4054385e15e9de9ee04a5f706efad Mon Sep 17 00:00:00 2001 From: ggqlq Date: Tue, 15 Apr 2025 12:50:01 +0800 Subject: [PATCH 02/61] add news --- ...-04-15-12-47-09.gh-issue-131178.Td8j5x.rst | 1 + confdefs.h | 139 ++++++++++++ conftest.c | 200 ++++++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst create mode 100644 confdefs.h create mode 100644 conftest.c diff --git a/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst b/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst new file mode 100644 index 00000000000000..9742c7791995c7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst @@ -0,0 +1 @@ +Add tests for the command line interface of the ``http.server`` module. \ No newline at end of file diff --git a/confdefs.h b/confdefs.h new file mode 100644 index 00000000000000..8a4fb56e9fa822 --- /dev/null +++ b/confdefs.h @@ -0,0 +1,139 @@ +/* confdefs.h */ +#define _NETBSD_SOURCE 1 +#define __BSD_VISIBLE 1 +#define _DARWIN_C_SOURCE 1 +#define _PYTHONFRAMEWORK "" +#define _XOPEN_SOURCE 700 +#define _XOPEN_SOURCE_EXTENDED 1 +#define _POSIX_C_SOURCE 200809L +#define HAVE_STDIO_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_WCHAR_H 1 +#define STDC_HEADERS 1 +#define _ALL_SOURCE 1 +#define _DARWIN_C_SOURCE 1 +#define _GNU_SOURCE 1 +#define _HPUX_ALT_XOPEN_SOCKET_API 1 +#define _NETBSD_SOURCE 1 +#define _OPENBSD_SOURCE 1 +#define _POSIX_PTHREAD_SEMANTICS 1 +#define __STDC_WANT_IEC_60559_ATTRIBS_EXT__ 1 +#define __STDC_WANT_IEC_60559_BFP_EXT__ 1 +#define __STDC_WANT_IEC_60559_DFP_EXT__ 1 +#define __STDC_WANT_IEC_60559_EXT__ 1 +#define __STDC_WANT_IEC_60559_FUNCS_EXT__ 1 +#define __STDC_WANT_IEC_60559_TYPES_EXT__ 1 +#define __STDC_WANT_LIB_EXT2__ 1 +#define __STDC_WANT_MATH_SPEC_FUNCS__ 1 +#define _TANDEM_SOURCE 1 +#define __EXTENSIONS__ 1 +#define PY_SUPPORT_TIER 1 +#define STDC_HEADERS 1 +#define HAVE_ALLOCA_H 1 +#define HAVE_ASM_TYPES_H 1 +#define HAVE_DLFCN_H 1 +#define HAVE_ENDIAN_H 1 +#define HAVE_ERRNO_H 1 +#define HAVE_FCNTL_H 1 +#define HAVE_GRP_H 1 +#define HAVE_LANGINFO_H 1 +#define HAVE_LIBINTL_H 1 +#define HAVE_LINUX_AUXVEC_H 1 +#define HAVE_SYS_AUXV_H 1 +#define HAVE_LINUX_FS_H 1 +#define HAVE_LINUX_LIMITS_H 1 +#define HAVE_LINUX_MEMFD_H 1 +#define HAVE_LINUX_NETFILTER_IPV4_H 1 +#define HAVE_LINUX_RANDOM_H 1 +#define HAVE_LINUX_SOUNDCARD_H 1 +#define HAVE_LINUX_SCHED_H 1 +#define HAVE_LINUX_TIPC_H 1 +#define HAVE_LINUX_WAIT_H 1 +#define HAVE_NETDB_H 1 +#define HAVE_NET_ETHERNET_H 1 +#define HAVE_NETINET_IN_H 1 +#define HAVE_NETPACKET_PACKET_H 1 +#define HAVE_POLL_H 1 +#define HAVE_PTHREAD_H 1 +#define HAVE_PTY_H 1 +#define HAVE_SCHED_H 1 +#define HAVE_SETJMP_H 1 +#define HAVE_SHADOW_H 1 +#define HAVE_SIGNAL_H 1 +#define HAVE_SPAWN_H 1 +#define HAVE_SYS_EPOLL_H 1 +#define HAVE_SYS_EVENTFD_H 1 +#define HAVE_SYS_FILE_H 1 +#define HAVE_SYS_IOCTL_H 1 +#define HAVE_SYS_MMAN_H 1 +#define HAVE_SYS_PARAM_H 1 +#define HAVE_SYS_PIDFD_H 1 +#define HAVE_SYS_POLL_H 1 +#define HAVE_SYS_RANDOM_H 1 +#define HAVE_SYS_RESOURCE_H 1 +#define HAVE_SYS_SELECT_H 1 +#define HAVE_SYS_SENDFILE_H 1 +#define HAVE_SYS_SOCKET_H 1 +#define HAVE_SYS_SOUNDCARD_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_STATVFS_H 1 +#define HAVE_SYS_SYSCALL_H 1 +#define HAVE_SYS_SYSMACROS_H 1 +#define HAVE_SYS_TIME_H 1 +#define HAVE_SYS_TIMES_H 1 +#define HAVE_SYS_TIMERFD_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_UIO_H 1 +#define HAVE_SYS_UN_H 1 +#define HAVE_SYS_UTSNAME_H 1 +#define HAVE_SYS_WAIT_H 1 +#define HAVE_SYS_XATTR_H 1 +#define HAVE_SYSEXITS_H 1 +#define HAVE_SYSLOG_H 1 +#define HAVE_TERMIOS_H 1 +#define HAVE_UTIME_H 1 +#define HAVE_UTMP_H 1 +#define HAVE_DIRENT_H 1 +#define MAJOR_IN_SYSMACROS 1 +#define HAVE_NET_IF_H 1 +#define HAVE_LINUX_NETLINK_H 1 +#define HAVE_LINUX_QRTR_H 1 +#define HAVE_LINUX_VM_SOCKETS_H 1 +#define HAVE_LINUX_CAN_H 1 +#define HAVE_LINUX_CAN_BCM_H 1 +#define HAVE_LINUX_CAN_J1939_H 1 +#define HAVE_LINUX_CAN_RAW_H 1 +#define HAVE_CLOCK_T 1 +#define HAVE_MAKEDEV 1 +#define HAVE_HTOLE64 1 +#define _LARGEFILE_SOURCE 1 +#define _FILE_OFFSET_BITS 64 +#if defined(SCO_DS) +#undef _OFF_T +#endif +#define RETSIGTYPE void +#define HAVE_SSIZE_T 1 +#define HAVE___UINT128_T 1 +#define HAVE_GCC_UINT128_T 1 +#define SIZEOF_INT 4 +#define SIZEOF_LONG 8 +#define ALIGNOF_LONG 8 +#define SIZEOF_LONG_LONG 8 +#define SIZEOF_VOID_P 8 +#define SIZEOF_SHORT 2 +#define SIZEOF_FLOAT 4 +#define SIZEOF_DOUBLE 8 +#define SIZEOF_FPOS_T 16 +#define SIZEOF_SIZE_T 8 +#define ALIGNOF_SIZE_T 8 +#define SIZEOF_PID_T 4 +#define SIZEOF_UINTPTR_T 8 +#define ALIGNOF_MAX_ALIGN_T 16 +#define HAVE_LONG_DOUBLE 1 diff --git a/conftest.c b/conftest.c new file mode 100644 index 00000000000000..eccb334eb63ef1 --- /dev/null +++ b/conftest.c @@ -0,0 +1,200 @@ +/* confdefs.h */ +#define _NETBSD_SOURCE 1 +#define __BSD_VISIBLE 1 +#define _DARWIN_C_SOURCE 1 +#define _PYTHONFRAMEWORK "" +#define _XOPEN_SOURCE 700 +#define _XOPEN_SOURCE_EXTENDED 1 +#define _POSIX_C_SOURCE 200809L +#define HAVE_STDIO_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_WCHAR_H 1 +#define STDC_HEADERS 1 +#define _ALL_SOURCE 1 +#define _DARWIN_C_SOURCE 1 +#define _GNU_SOURCE 1 +#define _HPUX_ALT_XOPEN_SOCKET_API 1 +#define _NETBSD_SOURCE 1 +#define _OPENBSD_SOURCE 1 +#define _POSIX_PTHREAD_SEMANTICS 1 +#define __STDC_WANT_IEC_60559_ATTRIBS_EXT__ 1 +#define __STDC_WANT_IEC_60559_BFP_EXT__ 1 +#define __STDC_WANT_IEC_60559_DFP_EXT__ 1 +#define __STDC_WANT_IEC_60559_EXT__ 1 +#define __STDC_WANT_IEC_60559_FUNCS_EXT__ 1 +#define __STDC_WANT_IEC_60559_TYPES_EXT__ 1 +#define __STDC_WANT_LIB_EXT2__ 1 +#define __STDC_WANT_MATH_SPEC_FUNCS__ 1 +#define _TANDEM_SOURCE 1 +#define __EXTENSIONS__ 1 +#define PY_SUPPORT_TIER 1 +#define STDC_HEADERS 1 +#define HAVE_ALLOCA_H 1 +#define HAVE_ASM_TYPES_H 1 +#define HAVE_DLFCN_H 1 +#define HAVE_ENDIAN_H 1 +#define HAVE_ERRNO_H 1 +#define HAVE_FCNTL_H 1 +#define HAVE_GRP_H 1 +#define HAVE_LANGINFO_H 1 +#define HAVE_LIBINTL_H 1 +#define HAVE_LINUX_AUXVEC_H 1 +#define HAVE_SYS_AUXV_H 1 +#define HAVE_LINUX_FS_H 1 +#define HAVE_LINUX_LIMITS_H 1 +#define HAVE_LINUX_MEMFD_H 1 +#define HAVE_LINUX_NETFILTER_IPV4_H 1 +#define HAVE_LINUX_RANDOM_H 1 +#define HAVE_LINUX_SOUNDCARD_H 1 +#define HAVE_LINUX_SCHED_H 1 +#define HAVE_LINUX_TIPC_H 1 +#define HAVE_LINUX_WAIT_H 1 +#define HAVE_NETDB_H 1 +#define HAVE_NET_ETHERNET_H 1 +#define HAVE_NETINET_IN_H 1 +#define HAVE_NETPACKET_PACKET_H 1 +#define HAVE_POLL_H 1 +#define HAVE_PTHREAD_H 1 +#define HAVE_PTY_H 1 +#define HAVE_SCHED_H 1 +#define HAVE_SETJMP_H 1 +#define HAVE_SHADOW_H 1 +#define HAVE_SIGNAL_H 1 +#define HAVE_SPAWN_H 1 +#define HAVE_SYS_EPOLL_H 1 +#define HAVE_SYS_EVENTFD_H 1 +#define HAVE_SYS_FILE_H 1 +#define HAVE_SYS_IOCTL_H 1 +#define HAVE_SYS_MMAN_H 1 +#define HAVE_SYS_PARAM_H 1 +#define HAVE_SYS_PIDFD_H 1 +#define HAVE_SYS_POLL_H 1 +#define HAVE_SYS_RANDOM_H 1 +#define HAVE_SYS_RESOURCE_H 1 +#define HAVE_SYS_SELECT_H 1 +#define HAVE_SYS_SENDFILE_H 1 +#define HAVE_SYS_SOCKET_H 1 +#define HAVE_SYS_SOUNDCARD_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_STATVFS_H 1 +#define HAVE_SYS_SYSCALL_H 1 +#define HAVE_SYS_SYSMACROS_H 1 +#define HAVE_SYS_TIME_H 1 +#define HAVE_SYS_TIMES_H 1 +#define HAVE_SYS_TIMERFD_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_UIO_H 1 +#define HAVE_SYS_UN_H 1 +#define HAVE_SYS_UTSNAME_H 1 +#define HAVE_SYS_WAIT_H 1 +#define HAVE_SYS_XATTR_H 1 +#define HAVE_SYSEXITS_H 1 +#define HAVE_SYSLOG_H 1 +#define HAVE_TERMIOS_H 1 +#define HAVE_UTIME_H 1 +#define HAVE_UTMP_H 1 +#define HAVE_DIRENT_H 1 +#define MAJOR_IN_SYSMACROS 1 +#define HAVE_NET_IF_H 1 +#define HAVE_LINUX_NETLINK_H 1 +#define HAVE_LINUX_QRTR_H 1 +#define HAVE_LINUX_VM_SOCKETS_H 1 +#define HAVE_LINUX_CAN_H 1 +#define HAVE_LINUX_CAN_BCM_H 1 +#define HAVE_LINUX_CAN_J1939_H 1 +#define HAVE_LINUX_CAN_RAW_H 1 +#define HAVE_CLOCK_T 1 +#define HAVE_MAKEDEV 1 +#define HAVE_HTOLE64 1 +#define _LARGEFILE_SOURCE 1 +#define _FILE_OFFSET_BITS 64 +#if defined(SCO_DS) +#undef _OFF_T +#endif +#define RETSIGTYPE void +#define HAVE_SSIZE_T 1 +#define HAVE___UINT128_T 1 +#define HAVE_GCC_UINT128_T 1 +#define SIZEOF_INT 4 +#define SIZEOF_LONG 8 +#define ALIGNOF_LONG 8 +#define SIZEOF_LONG_LONG 8 +#define SIZEOF_VOID_P 8 +#define SIZEOF_SHORT 2 +#define SIZEOF_FLOAT 4 +#define SIZEOF_DOUBLE 8 +#define SIZEOF_FPOS_T 16 +#define SIZEOF_SIZE_T 8 +#define ALIGNOF_SIZE_T 8 +#define SIZEOF_PID_T 4 +#define SIZEOF_UINTPTR_T 8 +#define ALIGNOF_MAX_ALIGN_T 16 +#define HAVE_LONG_DOUBLE 1 +/* end confdefs.h. */ +#include +#ifdef HAVE_STDIO_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_STRING_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +static long int longval (void) { return (long int) (sizeof (long double)); } +static unsigned long int ulongval (void) { return (long int) (sizeof (long double)); } +#include +#include +int +main (void) +{ + + FILE *f = fopen ("conftest.val", "w"); + if (! f) + return 1; + if (((long int) (sizeof (long double))) < 0) + { + long int i = longval (); + if (i != ((long int) (sizeof (long double)))) + return 1; + fprintf (f, "%ld", i); + } + else + { + unsigned long int i = ulongval (); + if (i != ((long int) (sizeof (long double)))) + return 1; + fprintf (f, "%lu", i); + } + /* Do not output a trailing newline, as this causes \r\n confusion + on some platforms. */ + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} From e11f8fe53fcc932b6d24667d95310568b2192ce3 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Tue, 15 Apr 2025 12:50:29 +0800 Subject: [PATCH 03/61] add news --- confdefs.h | 139 ------------------------------------- conftest.c | 200 ----------------------------------------------------- 2 files changed, 339 deletions(-) delete mode 100644 confdefs.h delete mode 100644 conftest.c diff --git a/confdefs.h b/confdefs.h deleted file mode 100644 index 8a4fb56e9fa822..00000000000000 --- a/confdefs.h +++ /dev/null @@ -1,139 +0,0 @@ -/* confdefs.h */ -#define _NETBSD_SOURCE 1 -#define __BSD_VISIBLE 1 -#define _DARWIN_C_SOURCE 1 -#define _PYTHONFRAMEWORK "" -#define _XOPEN_SOURCE 700 -#define _XOPEN_SOURCE_EXTENDED 1 -#define _POSIX_C_SOURCE 200809L -#define HAVE_STDIO_H 1 -#define HAVE_STDLIB_H 1 -#define HAVE_STRING_H 1 -#define HAVE_INTTYPES_H 1 -#define HAVE_STDINT_H 1 -#define HAVE_STRINGS_H 1 -#define HAVE_SYS_STAT_H 1 -#define HAVE_SYS_TYPES_H 1 -#define HAVE_UNISTD_H 1 -#define HAVE_WCHAR_H 1 -#define STDC_HEADERS 1 -#define _ALL_SOURCE 1 -#define _DARWIN_C_SOURCE 1 -#define _GNU_SOURCE 1 -#define _HPUX_ALT_XOPEN_SOCKET_API 1 -#define _NETBSD_SOURCE 1 -#define _OPENBSD_SOURCE 1 -#define _POSIX_PTHREAD_SEMANTICS 1 -#define __STDC_WANT_IEC_60559_ATTRIBS_EXT__ 1 -#define __STDC_WANT_IEC_60559_BFP_EXT__ 1 -#define __STDC_WANT_IEC_60559_DFP_EXT__ 1 -#define __STDC_WANT_IEC_60559_EXT__ 1 -#define __STDC_WANT_IEC_60559_FUNCS_EXT__ 1 -#define __STDC_WANT_IEC_60559_TYPES_EXT__ 1 -#define __STDC_WANT_LIB_EXT2__ 1 -#define __STDC_WANT_MATH_SPEC_FUNCS__ 1 -#define _TANDEM_SOURCE 1 -#define __EXTENSIONS__ 1 -#define PY_SUPPORT_TIER 1 -#define STDC_HEADERS 1 -#define HAVE_ALLOCA_H 1 -#define HAVE_ASM_TYPES_H 1 -#define HAVE_DLFCN_H 1 -#define HAVE_ENDIAN_H 1 -#define HAVE_ERRNO_H 1 -#define HAVE_FCNTL_H 1 -#define HAVE_GRP_H 1 -#define HAVE_LANGINFO_H 1 -#define HAVE_LIBINTL_H 1 -#define HAVE_LINUX_AUXVEC_H 1 -#define HAVE_SYS_AUXV_H 1 -#define HAVE_LINUX_FS_H 1 -#define HAVE_LINUX_LIMITS_H 1 -#define HAVE_LINUX_MEMFD_H 1 -#define HAVE_LINUX_NETFILTER_IPV4_H 1 -#define HAVE_LINUX_RANDOM_H 1 -#define HAVE_LINUX_SOUNDCARD_H 1 -#define HAVE_LINUX_SCHED_H 1 -#define HAVE_LINUX_TIPC_H 1 -#define HAVE_LINUX_WAIT_H 1 -#define HAVE_NETDB_H 1 -#define HAVE_NET_ETHERNET_H 1 -#define HAVE_NETINET_IN_H 1 -#define HAVE_NETPACKET_PACKET_H 1 -#define HAVE_POLL_H 1 -#define HAVE_PTHREAD_H 1 -#define HAVE_PTY_H 1 -#define HAVE_SCHED_H 1 -#define HAVE_SETJMP_H 1 -#define HAVE_SHADOW_H 1 -#define HAVE_SIGNAL_H 1 -#define HAVE_SPAWN_H 1 -#define HAVE_SYS_EPOLL_H 1 -#define HAVE_SYS_EVENTFD_H 1 -#define HAVE_SYS_FILE_H 1 -#define HAVE_SYS_IOCTL_H 1 -#define HAVE_SYS_MMAN_H 1 -#define HAVE_SYS_PARAM_H 1 -#define HAVE_SYS_PIDFD_H 1 -#define HAVE_SYS_POLL_H 1 -#define HAVE_SYS_RANDOM_H 1 -#define HAVE_SYS_RESOURCE_H 1 -#define HAVE_SYS_SELECT_H 1 -#define HAVE_SYS_SENDFILE_H 1 -#define HAVE_SYS_SOCKET_H 1 -#define HAVE_SYS_SOUNDCARD_H 1 -#define HAVE_SYS_STAT_H 1 -#define HAVE_SYS_STATVFS_H 1 -#define HAVE_SYS_SYSCALL_H 1 -#define HAVE_SYS_SYSMACROS_H 1 -#define HAVE_SYS_TIME_H 1 -#define HAVE_SYS_TIMES_H 1 -#define HAVE_SYS_TIMERFD_H 1 -#define HAVE_SYS_TYPES_H 1 -#define HAVE_SYS_UIO_H 1 -#define HAVE_SYS_UN_H 1 -#define HAVE_SYS_UTSNAME_H 1 -#define HAVE_SYS_WAIT_H 1 -#define HAVE_SYS_XATTR_H 1 -#define HAVE_SYSEXITS_H 1 -#define HAVE_SYSLOG_H 1 -#define HAVE_TERMIOS_H 1 -#define HAVE_UTIME_H 1 -#define HAVE_UTMP_H 1 -#define HAVE_DIRENT_H 1 -#define MAJOR_IN_SYSMACROS 1 -#define HAVE_NET_IF_H 1 -#define HAVE_LINUX_NETLINK_H 1 -#define HAVE_LINUX_QRTR_H 1 -#define HAVE_LINUX_VM_SOCKETS_H 1 -#define HAVE_LINUX_CAN_H 1 -#define HAVE_LINUX_CAN_BCM_H 1 -#define HAVE_LINUX_CAN_J1939_H 1 -#define HAVE_LINUX_CAN_RAW_H 1 -#define HAVE_CLOCK_T 1 -#define HAVE_MAKEDEV 1 -#define HAVE_HTOLE64 1 -#define _LARGEFILE_SOURCE 1 -#define _FILE_OFFSET_BITS 64 -#if defined(SCO_DS) -#undef _OFF_T -#endif -#define RETSIGTYPE void -#define HAVE_SSIZE_T 1 -#define HAVE___UINT128_T 1 -#define HAVE_GCC_UINT128_T 1 -#define SIZEOF_INT 4 -#define SIZEOF_LONG 8 -#define ALIGNOF_LONG 8 -#define SIZEOF_LONG_LONG 8 -#define SIZEOF_VOID_P 8 -#define SIZEOF_SHORT 2 -#define SIZEOF_FLOAT 4 -#define SIZEOF_DOUBLE 8 -#define SIZEOF_FPOS_T 16 -#define SIZEOF_SIZE_T 8 -#define ALIGNOF_SIZE_T 8 -#define SIZEOF_PID_T 4 -#define SIZEOF_UINTPTR_T 8 -#define ALIGNOF_MAX_ALIGN_T 16 -#define HAVE_LONG_DOUBLE 1 diff --git a/conftest.c b/conftest.c deleted file mode 100644 index eccb334eb63ef1..00000000000000 --- a/conftest.c +++ /dev/null @@ -1,200 +0,0 @@ -/* confdefs.h */ -#define _NETBSD_SOURCE 1 -#define __BSD_VISIBLE 1 -#define _DARWIN_C_SOURCE 1 -#define _PYTHONFRAMEWORK "" -#define _XOPEN_SOURCE 700 -#define _XOPEN_SOURCE_EXTENDED 1 -#define _POSIX_C_SOURCE 200809L -#define HAVE_STDIO_H 1 -#define HAVE_STDLIB_H 1 -#define HAVE_STRING_H 1 -#define HAVE_INTTYPES_H 1 -#define HAVE_STDINT_H 1 -#define HAVE_STRINGS_H 1 -#define HAVE_SYS_STAT_H 1 -#define HAVE_SYS_TYPES_H 1 -#define HAVE_UNISTD_H 1 -#define HAVE_WCHAR_H 1 -#define STDC_HEADERS 1 -#define _ALL_SOURCE 1 -#define _DARWIN_C_SOURCE 1 -#define _GNU_SOURCE 1 -#define _HPUX_ALT_XOPEN_SOCKET_API 1 -#define _NETBSD_SOURCE 1 -#define _OPENBSD_SOURCE 1 -#define _POSIX_PTHREAD_SEMANTICS 1 -#define __STDC_WANT_IEC_60559_ATTRIBS_EXT__ 1 -#define __STDC_WANT_IEC_60559_BFP_EXT__ 1 -#define __STDC_WANT_IEC_60559_DFP_EXT__ 1 -#define __STDC_WANT_IEC_60559_EXT__ 1 -#define __STDC_WANT_IEC_60559_FUNCS_EXT__ 1 -#define __STDC_WANT_IEC_60559_TYPES_EXT__ 1 -#define __STDC_WANT_LIB_EXT2__ 1 -#define __STDC_WANT_MATH_SPEC_FUNCS__ 1 -#define _TANDEM_SOURCE 1 -#define __EXTENSIONS__ 1 -#define PY_SUPPORT_TIER 1 -#define STDC_HEADERS 1 -#define HAVE_ALLOCA_H 1 -#define HAVE_ASM_TYPES_H 1 -#define HAVE_DLFCN_H 1 -#define HAVE_ENDIAN_H 1 -#define HAVE_ERRNO_H 1 -#define HAVE_FCNTL_H 1 -#define HAVE_GRP_H 1 -#define HAVE_LANGINFO_H 1 -#define HAVE_LIBINTL_H 1 -#define HAVE_LINUX_AUXVEC_H 1 -#define HAVE_SYS_AUXV_H 1 -#define HAVE_LINUX_FS_H 1 -#define HAVE_LINUX_LIMITS_H 1 -#define HAVE_LINUX_MEMFD_H 1 -#define HAVE_LINUX_NETFILTER_IPV4_H 1 -#define HAVE_LINUX_RANDOM_H 1 -#define HAVE_LINUX_SOUNDCARD_H 1 -#define HAVE_LINUX_SCHED_H 1 -#define HAVE_LINUX_TIPC_H 1 -#define HAVE_LINUX_WAIT_H 1 -#define HAVE_NETDB_H 1 -#define HAVE_NET_ETHERNET_H 1 -#define HAVE_NETINET_IN_H 1 -#define HAVE_NETPACKET_PACKET_H 1 -#define HAVE_POLL_H 1 -#define HAVE_PTHREAD_H 1 -#define HAVE_PTY_H 1 -#define HAVE_SCHED_H 1 -#define HAVE_SETJMP_H 1 -#define HAVE_SHADOW_H 1 -#define HAVE_SIGNAL_H 1 -#define HAVE_SPAWN_H 1 -#define HAVE_SYS_EPOLL_H 1 -#define HAVE_SYS_EVENTFD_H 1 -#define HAVE_SYS_FILE_H 1 -#define HAVE_SYS_IOCTL_H 1 -#define HAVE_SYS_MMAN_H 1 -#define HAVE_SYS_PARAM_H 1 -#define HAVE_SYS_PIDFD_H 1 -#define HAVE_SYS_POLL_H 1 -#define HAVE_SYS_RANDOM_H 1 -#define HAVE_SYS_RESOURCE_H 1 -#define HAVE_SYS_SELECT_H 1 -#define HAVE_SYS_SENDFILE_H 1 -#define HAVE_SYS_SOCKET_H 1 -#define HAVE_SYS_SOUNDCARD_H 1 -#define HAVE_SYS_STAT_H 1 -#define HAVE_SYS_STATVFS_H 1 -#define HAVE_SYS_SYSCALL_H 1 -#define HAVE_SYS_SYSMACROS_H 1 -#define HAVE_SYS_TIME_H 1 -#define HAVE_SYS_TIMES_H 1 -#define HAVE_SYS_TIMERFD_H 1 -#define HAVE_SYS_TYPES_H 1 -#define HAVE_SYS_UIO_H 1 -#define HAVE_SYS_UN_H 1 -#define HAVE_SYS_UTSNAME_H 1 -#define HAVE_SYS_WAIT_H 1 -#define HAVE_SYS_XATTR_H 1 -#define HAVE_SYSEXITS_H 1 -#define HAVE_SYSLOG_H 1 -#define HAVE_TERMIOS_H 1 -#define HAVE_UTIME_H 1 -#define HAVE_UTMP_H 1 -#define HAVE_DIRENT_H 1 -#define MAJOR_IN_SYSMACROS 1 -#define HAVE_NET_IF_H 1 -#define HAVE_LINUX_NETLINK_H 1 -#define HAVE_LINUX_QRTR_H 1 -#define HAVE_LINUX_VM_SOCKETS_H 1 -#define HAVE_LINUX_CAN_H 1 -#define HAVE_LINUX_CAN_BCM_H 1 -#define HAVE_LINUX_CAN_J1939_H 1 -#define HAVE_LINUX_CAN_RAW_H 1 -#define HAVE_CLOCK_T 1 -#define HAVE_MAKEDEV 1 -#define HAVE_HTOLE64 1 -#define _LARGEFILE_SOURCE 1 -#define _FILE_OFFSET_BITS 64 -#if defined(SCO_DS) -#undef _OFF_T -#endif -#define RETSIGTYPE void -#define HAVE_SSIZE_T 1 -#define HAVE___UINT128_T 1 -#define HAVE_GCC_UINT128_T 1 -#define SIZEOF_INT 4 -#define SIZEOF_LONG 8 -#define ALIGNOF_LONG 8 -#define SIZEOF_LONG_LONG 8 -#define SIZEOF_VOID_P 8 -#define SIZEOF_SHORT 2 -#define SIZEOF_FLOAT 4 -#define SIZEOF_DOUBLE 8 -#define SIZEOF_FPOS_T 16 -#define SIZEOF_SIZE_T 8 -#define ALIGNOF_SIZE_T 8 -#define SIZEOF_PID_T 4 -#define SIZEOF_UINTPTR_T 8 -#define ALIGNOF_MAX_ALIGN_T 16 -#define HAVE_LONG_DOUBLE 1 -/* end confdefs.h. */ -#include -#ifdef HAVE_STDIO_H -# include -#endif -#ifdef HAVE_STDLIB_H -# include -#endif -#ifdef HAVE_STRING_H -# include -#endif -#ifdef HAVE_INTTYPES_H -# include -#endif -#ifdef HAVE_STDINT_H -# include -#endif -#ifdef HAVE_STRINGS_H -# include -#endif -#ifdef HAVE_SYS_TYPES_H -# include -#endif -#ifdef HAVE_SYS_STAT_H -# include -#endif -#ifdef HAVE_UNISTD_H -# include -#endif -static long int longval (void) { return (long int) (sizeof (long double)); } -static unsigned long int ulongval (void) { return (long int) (sizeof (long double)); } -#include -#include -int -main (void) -{ - - FILE *f = fopen ("conftest.val", "w"); - if (! f) - return 1; - if (((long int) (sizeof (long double))) < 0) - { - long int i = longval (); - if (i != ((long int) (sizeof (long double)))) - return 1; - fprintf (f, "%ld", i); - } - else - { - unsigned long int i = ulongval (); - if (i != ((long int) (sizeof (long double)))) - return 1; - fprintf (f, "%lu", i); - } - /* Do not output a trailing newline, as this causes \r\n confusion - on some platforms. */ - return ferror (f) || fclose (f) != 0; - - ; - return 0; -} From 4e008fd710406d74f0008391757bed6c7923afa1 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Tue, 15 Apr 2025 13:31:59 +0800 Subject: [PATCH 04/61] lint --- Lib/http/server.py | 2 +- Lib/test/test_httpservers.py | 24 +++++++++---------- ...-04-15-12-47-09.gh-issue-131178.Td8j5x.rst | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index d0a7777f67c76a..28f431a64c211a 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1423,4 +1423,4 @@ def finish_request(self, request, client_address): if __name__ == '__main__': - _main() \ No newline at end of file + _main() diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 51a63715a3a4a8..fa4523b34f329a 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1547,7 +1547,7 @@ def setUp(self): self.default_server = http.server.CommandLineServerClass self.tls_cert = '-----BEGIN CERTIFICATE-----\n' + '-----END CERTIFICATE-----\n' self.tls_key = '-----BEGIN RSA PRIVATE KEY-----\n' + '-----END RSA PRIVATE KEY-----\n' - + self.tls_password = '' tls_password_file_object = tempfile.NamedTemporaryFile(mode='w+', delete=False) tls_password_file_object.write(self.tls_password) @@ -1624,7 +1624,7 @@ def test_cgi_flag(self, mock_func): self.invoke_httpd(['--cgi']) mock_func.assert_called_once_with(HandlerClass=CGIHTTPRequestHandler, ServerClass=self.default_server, protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, - tls_cert=None, tls_key=None, tls_password=None) + tls_cert=None, tls_key=None, tls_password=None) @mock.patch('http.server.test') def test_tls_flag(self, mock_func): @@ -1632,7 +1632,7 @@ def test_tls_flag(self, mock_func): tls_key_options = ['--tls-key', ] tls_password_options = ['--tls-password-file', ] # Normal: --tls-cert and --tls-key - + for tls_cert_option in tls_cert_options: for tls_key_option in tls_key_options: self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key]) @@ -1640,36 +1640,36 @@ def test_tls_flag(self, mock_func): protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=None) mock_func.reset_mock() - + # Normal: --tls-cert, --tls-key and --tls-password-file - + for tls_cert_option in tls_cert_options: for tls_key_option in tls_key_options: for tls_password_option in tls_password_options: self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key, tls_password_option, self.tls_password_file]) - + mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=self.tls_password) mock_func.reset_mock() - + # Abnormal: --tls-key without --tls-cert - + for tls_key_option in tls_key_options: for tls_cert_option in tls_cert_options: with self.assertRaises(SystemExit): self.invoke_httpd([tls_key_option, self.tls_key]) mock_func.reset_mock() - + # Abnormal: --tls-password-file without --tls-cert - + for tls_password_option in tls_password_options: with self.assertRaises(SystemExit): self.invoke_httpd([tls_password_option, self.tls_password_file]) mock_func.reset_mock() - + # Abnormal: --tls-password-file cannot be opened - + non_existent_file = os.path.join(tempfile.gettempdir(), os.urandom(16).hex()) retry_count = 0 while os.path.exists(non_existent_file) and retry_count < 10: diff --git a/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst b/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst index 9742c7791995c7..1fdd23447a5086 100644 --- a/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst +++ b/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst @@ -1 +1 @@ -Add tests for the command line interface of the ``http.server`` module. \ No newline at end of file +Add tests for the command line interface of the ``http.server`` module. From ad76ab1906b8067882eded2d872c559f40bcd4e1 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Wed, 16 Apr 2025 16:02:25 +0800 Subject: [PATCH 05/61] move a new class into test --- Lib/http/server.py | 52 +++++++++++++++--------------------- Lib/test/test_httpservers.py | 28 +++++++++---------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index 28f431a64c211a..c7a92f6d4f8b2d 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -152,21 +152,6 @@ def server_bind(self): class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer): daemon_threads = True -class CommandLineServerClass(ThreadingHTTPServer): - def __init__(self, server_address, RequestHandlerClass, directory=None): - super().__init__(server_address, RequestHandlerClass) - self.directory = directory - def server_bind(self): - # suppress exception when protocol is IPv4 - with contextlib.suppress(Exception): - self.socket.setsockopt( - socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - return super().server_bind() - - def finish_request(self, request, client_address): - self.RequestHandlerClass(request, client_address, self, - directory=self.directory) - class HTTPSServer(HTTPServer): def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True, *, certfile, keyfile=None, @@ -1322,7 +1307,7 @@ def _get_best_family(*address): def test(HandlerClass=BaseHTTPRequestHandler, ServerClass=ThreadingHTTPServer, - protocol="HTTP/1.0", port=8000, bind=None, + protocol="HTTP/1.0", port=8000, bind=None, directory=None, tls_cert=None, tls_key=None, tls_password=None): """Test the HTTP request handler class. @@ -1335,6 +1320,24 @@ def test(HandlerClass=BaseHTTPRequestHandler, if tls_cert: server = ThreadingHTTPSServer(addr, HandlerClass, certfile=tls_cert, keyfile=tls_key, password=tls_password) + elif ServerClass is ThreadingHTTPServer: + # ensure dual-stack is not disabled; ref #38907 + class DualStackServer(ThreadingHTTPServer): + def __init__(self, server_address, RequestHandlerClass, directory=None): + super().__init__(server_address, RequestHandlerClass) + self.directory = directory + + def server_bind(self): + # suppress exception when protocol is IPv4 + with contextlib.suppress(Exception): + self.socket.setsockopt( + socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + return super().server_bind() + + def finish_request(self, request, client_address): + self.RequestHandlerClass(request, client_address, self, + directory=self.directory) + server = DualStackServer(addr, HandlerClass, directory=directory) else: server = ServerClass(addr, HandlerClass) @@ -1396,26 +1399,13 @@ def _main(args=None): else: handler_class = SimpleHTTPRequestHandler - # ensure dual-stack is not disabled; ref #38907 - class DualStackServer(ThreadingHTTPServer): - - def server_bind(self): - # suppress exception when protocol is IPv4 - with contextlib.suppress(Exception): - self.socket.setsockopt( - socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - return super().server_bind() - - def finish_request(self, request, client_address): - self.RequestHandlerClass(request, client_address, self, - directory=args.directory) - test( HandlerClass=handler_class, - ServerClass=CommandLineServerClass, + ServerClass=ThreadingHTTPServer, port=args.port, bind=args.bind, protocol=args.protocol, + directory=args.directory, tls_cert=args.tls_cert, tls_key=args.tls_key, tls_password=tls_key_password, diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index fa4523b34f329a..a07b3ff37141d0 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1469,7 +1469,7 @@ def test_windows_colon(self): class MiscTestCase(unittest.TestCase): def test_all(self): expected = [] - denylist = {'executable', 'nobody_uid', 'test', 'CommandLineServerClass', 'CommandLineServerClass'} + denylist = {'executable', 'nobody_uid', 'test', 'CommandLineServerClass'} for name in dir(server): if name.startswith('_') or name in denylist: continue @@ -1543,12 +1543,12 @@ def setUp(self): self.default_port = 8000 self.default_bind = None self.default_protocol = 'HTTP/1.0' + self.default_directory = os.getcwd() self.default_handler = SimpleHTTPRequestHandler - self.default_server = http.server.CommandLineServerClass - self.tls_cert = '-----BEGIN CERTIFICATE-----\n' + '-----END CERTIFICATE-----\n' - self.tls_key = '-----BEGIN RSA PRIVATE KEY-----\n' + '-----END RSA PRIVATE KEY-----\n' - - self.tls_password = '' + self.default_server = http.server.ThreadingHTTPServer + self.tls_cert = certdata_file('ssl_cert.pem') + self.tls_key = certdata_file('ssl_key.pem') + self.tls_password = 'somepass' tls_password_file_object = tempfile.NamedTemporaryFile(mode='w+', delete=False) tls_password_file_object.write(self.tls_password) self.tls_password_file = tls_password_file_object.name @@ -1576,8 +1576,8 @@ def test_port_flag(self, mock_func): with self.subTest(port=port): self.invoke_httpd([str(port)]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=port, bind=self.default_bind, tls_cert=None, - tls_key=None, tls_password=None) + protocol=self.default_protocol, port=port, bind=self.default_bind, directory=self.default_directory, + tls_cert=None, tls_key=None, tls_password=None) mock_func.reset_mock() @mock.patch('http.server.test') @@ -1589,7 +1589,7 @@ def test_directory_flag(self, mock_func): with self.subTest(flag=flag, directory=directory): self.invoke_httpd([flag, directory]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, directory=directory, tls_cert=None, tls_key=None, tls_password=None) mock_func.reset_mock() @@ -1602,7 +1602,7 @@ def test_bind_flag(self, mock_func): with self.subTest(flag=flag, bind_address=bind_address): self.invoke_httpd([flag, bind_address]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=bind_address, + protocol=self.default_protocol, port=self.default_port, bind=bind_address, directory=self.default_directory, tls_cert=None, tls_key=None, tls_password=None) mock_func.reset_mock() @@ -1615,7 +1615,7 @@ def test_protocol_flag(self, mock_func): with self.subTest(flag=flag, protocol=protocol): self.invoke_httpd([flag, protocol]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=protocol, port=self.default_port, bind=self.default_bind, + protocol=protocol, port=self.default_port, bind=self.default_bind, directory=self.default_directory, tls_cert=None, tls_key=None, tls_password=None) mock_func.reset_mock() @@ -1623,7 +1623,7 @@ def test_protocol_flag(self, mock_func): def test_cgi_flag(self, mock_func): self.invoke_httpd(['--cgi']) mock_func.assert_called_once_with(HandlerClass=CGIHTTPRequestHandler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, directory=self.default_directory, tls_cert=None, tls_key=None, tls_password=None) @mock.patch('http.server.test') @@ -1637,7 +1637,7 @@ def test_tls_flag(self, mock_func): for tls_key_option in tls_key_options: self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, directory=self.default_directory, tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=None) mock_func.reset_mock() @@ -1649,7 +1649,7 @@ def test_tls_flag(self, mock_func): self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key, tls_password_option, self.tls_password_file]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, directory=self.default_directory, tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=self.tls_password) mock_func.reset_mock() From 5e563d3c5e10c6e82d81dfe6656454fe407968ce Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:31:43 +0800 Subject: [PATCH 06/61] Update Lib/http/server.py Co-authored-by: Semyon Moroz --- Lib/http/server.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index c7a92f6d4f8b2d..53912cb6178cd7 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -106,8 +106,6 @@ import sys import time import urllib.parse -import argparse -import contextlib from http import HTTPStatus From 86b856e00c341271711bb5cce8ec3f83a5a0337e Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:32:00 +0800 Subject: [PATCH 07/61] Update Lib/http/server.py Co-authored-by: Semyon Moroz --- Lib/http/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index 53912cb6178cd7..ef19d25573306e 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1305,7 +1305,7 @@ def _get_best_family(*address): def test(HandlerClass=BaseHTTPRequestHandler, ServerClass=ThreadingHTTPServer, - protocol="HTTP/1.0", port=8000, bind=None, directory=None, + protocol="HTTP/1.0", port=8000, bind=None, tls_cert=None, tls_key=None, tls_password=None): """Test the HTTP request handler class. From 4d5c2b5bb2a3be0ff287ec18fedb5f7571a0a473 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:32:14 +0800 Subject: [PATCH 08/61] Update Lib/http/server.py Co-authored-by: Semyon Moroz --- Lib/http/server.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index ef19d25573306e..ea23e2b1fc9b13 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1318,24 +1318,6 @@ def test(HandlerClass=BaseHTTPRequestHandler, if tls_cert: server = ThreadingHTTPSServer(addr, HandlerClass, certfile=tls_cert, keyfile=tls_key, password=tls_password) - elif ServerClass is ThreadingHTTPServer: - # ensure dual-stack is not disabled; ref #38907 - class DualStackServer(ThreadingHTTPServer): - def __init__(self, server_address, RequestHandlerClass, directory=None): - super().__init__(server_address, RequestHandlerClass) - self.directory = directory - - def server_bind(self): - # suppress exception when protocol is IPv4 - with contextlib.suppress(Exception): - self.socket.setsockopt( - socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - return super().server_bind() - - def finish_request(self, request, client_address): - self.RequestHandlerClass(request, client_address, self, - directory=self.directory) - server = DualStackServer(addr, HandlerClass, directory=directory) else: server = ServerClass(addr, HandlerClass) From 574d6beed6c7025a5fdc6230644218f7883ee482 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:32:25 +0800 Subject: [PATCH 09/61] Update Lib/http/server.py Co-authored-by: Semyon Moroz --- Lib/http/server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/http/server.py b/Lib/http/server.py index ea23e2b1fc9b13..37f275c8179fcf 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1336,6 +1336,9 @@ def test(HandlerClass=BaseHTTPRequestHandler, sys.exit(0) def _main(args=None): + import argparse + import contextlib + parser = argparse.ArgumentParser() parser.add_argument('--cgi', action='store_true', help='run as CGI server') From c1f3358da768b5d27003e4ee7739c6a84a2777a4 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:32:33 +0800 Subject: [PATCH 10/61] Update Lib/http/server.py Co-authored-by: Semyon Moroz --- Lib/http/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/http/server.py b/Lib/http/server.py index 37f275c8179fcf..d78e3218333eae 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -150,6 +150,7 @@ def server_bind(self): class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer): daemon_threads = True + class HTTPSServer(HTTPServer): def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True, *, certfile, keyfile=None, From 01d5fb85de67944fdfedd82ca8f60f30aa332c0c Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:32:46 +0800 Subject: [PATCH 11/61] Update Lib/http/server.py Co-authored-by: Semyon Moroz --- Lib/http/server.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Lib/http/server.py b/Lib/http/server.py index d78e3218333eae..fe347eb03686d4 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1383,6 +1383,20 @@ def _main(args=None): else: handler_class = SimpleHTTPRequestHandler + # ensure dual-stack is not disabled; ref #38907 + class DualStackServer(ThreadingHTTPServer): + + def server_bind(self): + # suppress exception when protocol is IPv4 + with contextlib.suppress(Exception): + self.socket.setsockopt( + socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + return super().server_bind() + + def finish_request(self, request, client_address): + self.RequestHandlerClass(request, client_address, self, + directory=args.directory) + test( HandlerClass=handler_class, ServerClass=ThreadingHTTPServer, From 540700f4dd73f5ee9de24bff85b7dbc123fe3ce7 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:32:54 +0800 Subject: [PATCH 12/61] Update Lib/http/server.py Co-authored-by: Semyon Moroz --- Lib/http/server.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index fe347eb03686d4..5df23c7ca20c97 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1399,11 +1399,10 @@ def finish_request(self, request, client_address): test( HandlerClass=handler_class, - ServerClass=ThreadingHTTPServer, + ServerClass=DualStackServer, port=args.port, bind=args.bind, protocol=args.protocol, - directory=args.directory, tls_cert=args.tls_cert, tls_key=args.tls_key, tls_password=tls_key_password, From 1bdb0ec603220ef0f8b2c25efab95291c8d36fad Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:33:03 +0800 Subject: [PATCH 13/61] Update Lib/test/test_httpservers.py Co-authored-by: Semyon Moroz --- Lib/test/test_httpservers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index a07b3ff37141d0..7a03fb7b690458 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -8,7 +8,6 @@ SimpleHTTPRequestHandler, CGIHTTPRequestHandler from http import server, HTTPStatus -import http.server import os import socket import sys From 38aea9ef833f8d56d5dd174baff508aa0ad111d5 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:34:18 +0800 Subject: [PATCH 14/61] Update Lib/test/test_httpservers.py Co-authored-by: Semyon Moroz --- Lib/test/test_httpservers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 7a03fb7b690458..1bf2897bdb54d9 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -27,8 +27,6 @@ import threading from unittest import mock from io import BytesIO, StringIO -import textwrap -import contextlib import unittest from test import support From 3277327db79d7f912ae0edd6b89576637cc1a583 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Wed, 16 Apr 2025 22:09:33 +0800 Subject: [PATCH 15/61] update --- Lib/test/test_httpservers.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 1bf2897bdb54d9..fef52831fd756a 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -8,6 +8,7 @@ SimpleHTTPRequestHandler, CGIHTTPRequestHandler from http import server, HTTPStatus +import contextlib import os import socket import sys @@ -22,6 +23,7 @@ import http, http.client import urllib.parse import tempfile +import textwrap import time import datetime import threading @@ -1466,7 +1468,7 @@ def test_windows_colon(self): class MiscTestCase(unittest.TestCase): def test_all(self): expected = [] - denylist = {'executable', 'nobody_uid', 'test', 'CommandLineServerClass'} + denylist = {'executable', 'nobody_uid', 'test'} for name in dir(server): if name.startswith('_') or name in denylist: continue @@ -1542,7 +1544,7 @@ def setUp(self): self.default_protocol = 'HTTP/1.0' self.default_directory = os.getcwd() self.default_handler = SimpleHTTPRequestHandler - self.default_server = http.server.ThreadingHTTPServer + self.default_server = unittest.mock.ANY self.tls_cert = certdata_file('ssl_cert.pem') self.tls_key = certdata_file('ssl_key.pem') self.tls_password = 'somepass' @@ -1573,7 +1575,7 @@ def test_port_flag(self, mock_func): with self.subTest(port=port): self.invoke_httpd([str(port)]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=port, bind=self.default_bind, directory=self.default_directory, + protocol=self.default_protocol, port=port, bind=self.default_bind, tls_cert=None, tls_key=None, tls_password=None) mock_func.reset_mock() @@ -1586,7 +1588,7 @@ def test_directory_flag(self, mock_func): with self.subTest(flag=flag, directory=directory): self.invoke_httpd([flag, directory]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, directory=directory, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, tls_cert=None, tls_key=None, tls_password=None) mock_func.reset_mock() @@ -1599,7 +1601,7 @@ def test_bind_flag(self, mock_func): with self.subTest(flag=flag, bind_address=bind_address): self.invoke_httpd([flag, bind_address]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=bind_address, directory=self.default_directory, + protocol=self.default_protocol, port=self.default_port, bind=bind_address, tls_cert=None, tls_key=None, tls_password=None) mock_func.reset_mock() @@ -1612,7 +1614,7 @@ def test_protocol_flag(self, mock_func): with self.subTest(flag=flag, protocol=protocol): self.invoke_httpd([flag, protocol]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=protocol, port=self.default_port, bind=self.default_bind, directory=self.default_directory, + protocol=protocol, port=self.default_port, bind=self.default_bind, tls_cert=None, tls_key=None, tls_password=None) mock_func.reset_mock() @@ -1620,9 +1622,10 @@ def test_protocol_flag(self, mock_func): def test_cgi_flag(self, mock_func): self.invoke_httpd(['--cgi']) mock_func.assert_called_once_with(HandlerClass=CGIHTTPRequestHandler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, directory=self.default_directory, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, tls_cert=None, tls_key=None, tls_password=None) + @unittest.skipIf(ssl is None, "requires ssl") @mock.patch('http.server.test') def test_tls_flag(self, mock_func): tls_cert_options = ['--tls-cert', ] @@ -1634,7 +1637,7 @@ def test_tls_flag(self, mock_func): for tls_key_option in tls_key_options: self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, directory=self.default_directory, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=None) mock_func.reset_mock() @@ -1646,7 +1649,7 @@ def test_tls_flag(self, mock_func): self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key, tls_password_option, self.tls_password_file]) mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, directory=self.default_directory, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=self.tls_password) mock_func.reset_mock() From 771263d6c6482980c026d3aaa57fdeeddce060d3 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Wed, 16 Apr 2025 22:44:57 +0800 Subject: [PATCH 16/61] Update Lib/http/server.py Co-authored-by: Semyon Moroz --- Lib/http/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/http/server.py b/Lib/http/server.py index 5df23c7ca20c97..7537b7f3df464a 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -1362,7 +1362,7 @@ def _main(args=None): parser.add_argument('port', default=8000, type=int, nargs='?', help='bind to this port ' '(default: %(default)s)') - args = parser.parse_args(args=args) + args = parser.parse_args(args) if not args.tls_cert and args.tls_key: parser.error("--tls-key requires --tls-cert to be set") From 3679a76b076e9f9b2ae0bcd568c96f1460636e23 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Wed, 16 Apr 2025 23:08:21 +0800 Subject: [PATCH 17/61] remove news --- .../next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst diff --git a/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst b/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst deleted file mode 100644 index 1fdd23447a5086..00000000000000 --- a/Misc/NEWS.d/next/Library/2025-04-15-12-47-09.gh-issue-131178.Td8j5x.rst +++ /dev/null @@ -1 +0,0 @@ -Add tests for the command line interface of the ``http.server`` module. From 6c58710d37b73881a96cfbef81400c7e3f4e360f Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:50:20 +0800 Subject: [PATCH 18/61] Update Lib/test/test_httpservers.py Co-authored-by: Semyon Moroz --- Lib/test/test_httpservers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index fef52831fd756a..35132b046d1b22 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1542,7 +1542,6 @@ def setUp(self): self.default_port = 8000 self.default_bind = None self.default_protocol = 'HTTP/1.0' - self.default_directory = os.getcwd() self.default_handler = SimpleHTTPRequestHandler self.default_server = unittest.mock.ANY self.tls_cert = certdata_file('ssl_cert.pem') From 3e4a6aab3f2afea21641dd01fee46b76c5ce8e5f Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:51:12 +0800 Subject: [PATCH 19/61] Update Lib/test/test_httpservers.py Co-authored-by: Semyon Moroz --- Lib/test/test_httpservers.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 35132b046d1b22..ad9c21d62698bc 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1558,14 +1558,11 @@ def tearDown(self): os.remove(self.tls_password_file) return super().tearDown() - def text_normalizer(self, string): - return textwrap.dedent(string).strip() - - def invoke_httpd(self, args=[]): + def invoke_httpd(self, *args): output = StringIO() with contextlib.redirect_stdout(output): server._main(args) - return self.text_normalizer(output.getvalue()) + return textwrap.dedent(output.getvalue()).strip() @mock.patch('http.server.test') def test_port_flag(self, mock_func): From 8e93c5d40256bf2e424b4328c4af284e8e9c97cc Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:51:37 +0800 Subject: [PATCH 20/61] Update Lib/test/test_httpservers.py Co-authored-by: Semyon Moroz --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index ad9c21d62698bc..72200e2ff504b0 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1681,7 +1681,7 @@ def test_help_flag(self): for option in options: with self.assertRaises(SystemExit): output = self.invoke_httpd([option]) - self.assertIn('usage:', output) + self.assertStartsWith(output, 'usage: ') def test_unknown_flag(self): with self.assertRaises(SystemExit): From 439c36df843a8c41a6bc6b0b80281ad6bd7a3b95 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Thu, 17 Apr 2025 15:49:43 +0800 Subject: [PATCH 21/61] add no argument test and redirect stderr --- Lib/test/test_httpservers.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 72200e2ff504b0..680232294d8670 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1561,8 +1561,14 @@ def tearDown(self): def invoke_httpd(self, *args): output = StringIO() with contextlib.redirect_stdout(output): +<<<<<<< HEAD server._main(args) return textwrap.dedent(output.getvalue()).strip() +======= + with contextlib.redirect_stderr(output): + server._main(args) + return self.text_normalizer(output.getvalue()) +>>>>>>> 26e6b963f04 (add no argument test and redirect stderr) @mock.patch('http.server.test') def test_port_flag(self, mock_func): @@ -1676,6 +1682,15 @@ def test_tls_flag(self, mock_func): with self.assertRaises(SystemExit): self.invoke_httpd([tls_cert_option, self.tls_cert, tls_password_option, non_existent_file]) + @mock.patch('http.server.test') + def test_no_arguments(self, mock_func): + self.invoke_httpd() + mock_func.assert_called_once_with(HandlerClass=self.default_handler, + ServerClass=self.default_server, + protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, + tls_cert=None, tls_key=None, tls_password=None) + mock_func.reset_mock() + def test_help_flag(self): options = ['-h', '--help'] for option in options: From 7e8aedc18f770a37be23835f6824df16773a04f5 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Thu, 17 Apr 2025 16:16:42 +0800 Subject: [PATCH 22/61] wrap some lines to fit into 79 characters --- Lib/test/test_httpservers.py | 117 ++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 42 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 680232294d8670..39137f44bba379 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1560,25 +1560,24 @@ def tearDown(self): def invoke_httpd(self, *args): output = StringIO() - with contextlib.redirect_stdout(output): -<<<<<<< HEAD + with contextlib.redirect_stdout(output), contextlib.redirect_stderr(output): server._main(args) return textwrap.dedent(output.getvalue()).strip() -======= - with contextlib.redirect_stderr(output): - server._main(args) - return self.text_normalizer(output.getvalue()) ->>>>>>> 26e6b963f04 (add no argument test and redirect stderr) @mock.patch('http.server.test') def test_port_flag(self, mock_func): ports = [8000, 65535,] for port in ports: with self.subTest(port=port): - self.invoke_httpd([str(port)]) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=port, bind=self.default_bind, - tls_cert=None, tls_key=None, tls_password=None) + self.invoke_httpd(str(port)) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, + ServerClass=self.default_server, + protocol=self.default_protocol, + port=port, + bind=self.default_bind, + tls_cert=None, + tls_key=None, + tls_password=None) mock_func.reset_mock() @mock.patch('http.server.test') @@ -1588,10 +1587,15 @@ def test_directory_flag(self, mock_func): for flag in options: for directory in directories: with self.subTest(flag=flag, directory=directory): - self.invoke_httpd([flag, directory]) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, - tls_cert=None, tls_key=None, tls_password=None) + self.invoke_httpd(flag, directory) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, + ServerClass=self.default_server, + protocol=self.default_protocol, + port=self.default_port, + bind=self.default_bind, + tls_cert=None, + tls_key=None, + tls_password=None) mock_func.reset_mock() @mock.patch('http.server.test') @@ -1601,10 +1605,15 @@ def test_bind_flag(self, mock_func): for flag in options: for bind_address in bind_addresses: with self.subTest(flag=flag, bind_address=bind_address): - self.invoke_httpd([flag, bind_address]) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=bind_address, - tls_cert=None, tls_key=None, tls_password=None) + self.invoke_httpd(flag, bind_address) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, + ServerClass=self.default_server, + protocol=self.default_protocol, + port=self.default_port, + bind=bind_address, + tls_cert=None, + tls_key=None, + tls_password=None) mock_func.reset_mock() @mock.patch('http.server.test') @@ -1614,18 +1623,28 @@ def test_protocol_flag(self, mock_func): for flag in options: for protocol in protocols: with self.subTest(flag=flag, protocol=protocol): - self.invoke_httpd([flag, protocol]) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=protocol, port=self.default_port, bind=self.default_bind, - tls_cert=None, tls_key=None, tls_password=None) + self.invoke_httpd(flag, protocol) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, + ServerClass=self.default_server, + protocol=protocol, + port=self.default_port, + bind=self.default_bind, + tls_cert=None, + tls_key=None, + tls_password=None) mock_func.reset_mock() @mock.patch('http.server.test') def test_cgi_flag(self, mock_func): - self.invoke_httpd(['--cgi']) - mock_func.assert_called_once_with(HandlerClass=CGIHTTPRequestHandler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, - tls_cert=None, tls_key=None, tls_password=None) + self.invoke_httpd('--cgi') + mock_func.assert_called_once_with(HandlerClass=CGIHTTPRequestHandler, + ServerClass=self.default_server, + protocol=self.default_protocol, + port=self.default_port, + bind=self.default_bind, + tls_cert=None, + tls_key=None, + tls_password=None) @unittest.skipIf(ssl is None, "requires ssl") @mock.patch('http.server.test') @@ -1637,10 +1656,15 @@ def test_tls_flag(self, mock_func): for tls_cert_option in tls_cert_options: for tls_key_option in tls_key_options: - self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key]) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, - tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=None) + self.invoke_httpd(tls_cert_option, self.tls_cert, tls_key_option, self.tls_key) + mock_func.assert_called_once_with(HandlerClass=self.default_handler, + ServerClass=self.default_server, + protocol=self.default_protocol, + port=self.default_port, + bind=self.default_bind, + tls_cert=self.tls_cert, + tls_key=self.tls_key, + tls_password=None) mock_func.reset_mock() # Normal: --tls-cert, --tls-key and --tls-password-file @@ -1648,11 +1672,16 @@ def test_tls_flag(self, mock_func): for tls_cert_option in tls_cert_options: for tls_key_option in tls_key_options: for tls_password_option in tls_password_options: - self.invoke_httpd([tls_cert_option, self.tls_cert, tls_key_option, self.tls_key, tls_password_option, self.tls_password_file]) - - mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, - tls_cert=self.tls_cert, tls_key=self.tls_key, tls_password=self.tls_password) + self.invoke_httpd(tls_cert_option, self.tls_cert, tls_key_option, self.tls_key, tls_password_option, self.tls_password_file) + + mock_func.assert_called_once_with(HandlerClass=self.default_handler, + ServerClass=self.default_server, + protocol=self.default_protocol, + port=self.default_port, + bind=self.default_bind, + tls_cert=self.tls_cert, + tls_key=self.tls_key, + tls_password=self.tls_password) mock_func.reset_mock() # Abnormal: --tls-key without --tls-cert @@ -1660,14 +1689,14 @@ def test_tls_flag(self, mock_func): for tls_key_option in tls_key_options: for tls_cert_option in tls_cert_options: with self.assertRaises(SystemExit): - self.invoke_httpd([tls_key_option, self.tls_key]) + self.invoke_httpd(tls_key_option, self.tls_key) mock_func.reset_mock() # Abnormal: --tls-password-file without --tls-cert for tls_password_option in tls_password_options: with self.assertRaises(SystemExit): - self.invoke_httpd([tls_password_option, self.tls_password_file]) + self.invoke_httpd(tls_password_option, self.tls_password_file) mock_func.reset_mock() # Abnormal: --tls-password-file cannot be opened @@ -1687,20 +1716,24 @@ def test_no_arguments(self, mock_func): self.invoke_httpd() mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, - protocol=self.default_protocol, port=self.default_port, bind=self.default_bind, - tls_cert=None, tls_key=None, tls_password=None) + protocol=self.default_protocol, + port=self.default_port, + bind=self.default_bind, + tls_cert=None, + tls_key=None, + tls_password=None) mock_func.reset_mock() def test_help_flag(self): options = ['-h', '--help'] for option in options: with self.assertRaises(SystemExit): - output = self.invoke_httpd([option]) + output = self.invoke_httpd(option) self.assertStartsWith(output, 'usage: ') def test_unknown_flag(self): with self.assertRaises(SystemExit): - self.invoke_httpd(['--unknown-flag']) + self.invoke_httpd('--unknown-flag') def setUpModule(): unittest.addModuleCleanup(os.chdir, os.getcwd()) From 8f3e7adb1c408461d3833098843502e2b561d3ff Mon Sep 17 00:00:00 2001 From: ggqlq Date: Thu, 17 Apr 2025 18:18:11 +0800 Subject: [PATCH 23/61] wrap some lines to fit into 79 characters(2) --- Lib/test/test_httpservers.py | 128 +++++++++++++++++------------------ 1 file changed, 61 insertions(+), 67 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 39137f44bba379..cbe757cfb2940e 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1537,6 +1537,7 @@ def test_server_test_ipv4(self, _): server.test(ServerClass=mock_server, bind=bind) self.assertEqual(mock_server.address_family, socket.AF_INET) +@mock.patch('http.server.test') class CommandLineTestCase(unittest.TestCase): def setUp(self): self.default_port = 8000 @@ -1547,10 +1548,21 @@ def setUp(self): self.tls_cert = certdata_file('ssl_cert.pem') self.tls_key = certdata_file('ssl_key.pem') self.tls_password = 'somepass' - tls_password_file_object = tempfile.NamedTemporaryFile(mode='w+', delete=False) + tls_password_file_object = \ + tempfile.NamedTemporaryFile(mode='w+', delete=False) tls_password_file_object.write(self.tls_password) self.tls_password_file = tls_password_file_object.name tls_password_file_object.close() + self.args = { + 'HandlerClass': self.default_handler, + 'ServerClass': self.default_server, + 'protocol': self.default_protocol, + 'port': self.default_port, + 'bind': self.default_bind, + 'tls_cert': None, + 'tls_key': None, + 'tls_password': None, + } return super().setUp() def tearDown(self): @@ -1558,65 +1570,51 @@ def tearDown(self): os.remove(self.tls_password_file) return super().tearDown() + def get_random_temporary_file_name(self): + return os.path.join(tempfile.gettempdir(), os.urandom(16).hex()) + def invoke_httpd(self, *args): output = StringIO() - with contextlib.redirect_stdout(output), contextlib.redirect_stderr(output): + with contextlib.redirect_stdout(output), \ + contextlib.redirect_stderr(output): server._main(args) return textwrap.dedent(output.getvalue()).strip() - @mock.patch('http.server.test') + def test_port_flag(self, mock_func): ports = [8000, 65535,] for port in ports: with self.subTest(port=port): self.invoke_httpd(str(port)) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, - ServerClass=self.default_server, - protocol=self.default_protocol, - port=port, - bind=self.default_bind, - tls_cert=None, - tls_key=None, - tls_password=None) + self.args['port'] = port + mock_func.assert_called_once_with(**self.args) + self.args['port'] = self.default_port mock_func.reset_mock() - @mock.patch('http.server.test') def test_directory_flag(self, mock_func): options = ['-d', '--directory'] - directories = ['.', '/foo', '\\bar', '/', 'C:\\', 'C:\\foo', 'C:\\bar',] + directories = ['.', '/foo', '\\bar', '/', + 'C:\\', 'C:\\foo', 'C:\\bar',] for flag in options: for directory in directories: with self.subTest(flag=flag, directory=directory): self.invoke_httpd(flag, directory) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, - ServerClass=self.default_server, - protocol=self.default_protocol, - port=self.default_port, - bind=self.default_bind, - tls_cert=None, - tls_key=None, - tls_password=None) + mock_func.assert_called_once_with(**self.args) mock_func.reset_mock() - @mock.patch('http.server.test') def test_bind_flag(self, mock_func): options = ['-b', '--bind'] - bind_addresses = ['localhost', '127.0.0.1', '::1', '0.0.0.0', '8.8.8.8',] + bind_addresses = ['localhost', '127.0.0.1', '::1', + '0.0.0.0', '8.8.8.8',] for flag in options: for bind_address in bind_addresses: with self.subTest(flag=flag, bind_address=bind_address): self.invoke_httpd(flag, bind_address) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, - ServerClass=self.default_server, - protocol=self.default_protocol, - port=self.default_port, - bind=bind_address, - tls_cert=None, - tls_key=None, - tls_password=None) + self.args['bind'] = bind_address + mock_func.assert_called_once_with(**self.args) + self.args['bind'] = self.default_bind mock_func.reset_mock() - @mock.patch('http.server.test') def test_protocol_flag(self, mock_func): options = ['-p', '--protocol'] protocols = ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2.0', 'HTTP/3.0',] @@ -1624,17 +1622,11 @@ def test_protocol_flag(self, mock_func): for protocol in protocols: with self.subTest(flag=flag, protocol=protocol): self.invoke_httpd(flag, protocol) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, - ServerClass=self.default_server, - protocol=protocol, - port=self.default_port, - bind=self.default_bind, - tls_cert=None, - tls_key=None, - tls_password=None) + self.args['protocol'] = protocol + mock_func.assert_called_once_with(**self.args) + self.args['protocol'] = self.default_protocol mock_func.reset_mock() - @mock.patch('http.server.test') def test_cgi_flag(self, mock_func): self.invoke_httpd('--cgi') mock_func.assert_called_once_with(HandlerClass=CGIHTTPRequestHandler, @@ -1647,7 +1639,6 @@ def test_cgi_flag(self, mock_func): tls_password=None) @unittest.skipIf(ssl is None, "requires ssl") - @mock.patch('http.server.test') def test_tls_flag(self, mock_func): tls_cert_options = ['--tls-cert', ] tls_key_options = ['--tls-key', ] @@ -1656,15 +1647,13 @@ def test_tls_flag(self, mock_func): for tls_cert_option in tls_cert_options: for tls_key_option in tls_key_options: - self.invoke_httpd(tls_cert_option, self.tls_cert, tls_key_option, self.tls_key) - mock_func.assert_called_once_with(HandlerClass=self.default_handler, - ServerClass=self.default_server, - protocol=self.default_protocol, - port=self.default_port, - bind=self.default_bind, - tls_cert=self.tls_cert, - tls_key=self.tls_key, - tls_password=None) + self.invoke_httpd(tls_cert_option, self.tls_cert, + tls_key_option, self.tls_key) + self.args['tls_cert'] = self.tls_cert + self.args['tls_key'] = self.tls_key + mock_func.assert_called_once_with(**self.args) + self.args['tls_cert'] = None + self.args['tls_key'] = None mock_func.reset_mock() # Normal: --tls-cert, --tls-key and --tls-password-file @@ -1672,16 +1661,19 @@ def test_tls_flag(self, mock_func): for tls_cert_option in tls_cert_options: for tls_key_option in tls_key_options: for tls_password_option in tls_password_options: - self.invoke_httpd(tls_cert_option, self.tls_cert, tls_key_option, self.tls_key, tls_password_option, self.tls_password_file) - - mock_func.assert_called_once_with(HandlerClass=self.default_handler, - ServerClass=self.default_server, - protocol=self.default_protocol, - port=self.default_port, - bind=self.default_bind, - tls_cert=self.tls_cert, - tls_key=self.tls_key, - tls_password=self.tls_password) + self.invoke_httpd(tls_cert_option, + self.tls_cert, + tls_key_option, + self.tls_key, + tls_password_option, + self.tls_password_file) + self.args['tls_cert'] = self.tls_cert + self.args['tls_key'] = self.tls_key + self.args['tls_password'] = self.tls_password + mock_func.assert_called_once_with(**self.args) + self.args['tls_cert'] = None + self.args['tls_key'] = None + self.args['tls_password'] = None mock_func.reset_mock() # Abnormal: --tls-key without --tls-cert @@ -1701,17 +1693,19 @@ def test_tls_flag(self, mock_func): # Abnormal: --tls-password-file cannot be opened - non_existent_file = os.path.join(tempfile.gettempdir(), os.urandom(16).hex()) + non_existent_file = self.get_random_temporary_file_name() retry_count = 0 while os.path.exists(non_existent_file) and retry_count < 10: - non_existent_file = os.path.join(tempfile.gettempdir(), os.urandom(16).hex()) + non_existent_file = self.get_random_temporary_file_name() if not os.path.exists(non_existent_file): for tls_password_option in tls_password_options: for tls_cert_option in tls_cert_options: with self.assertRaises(SystemExit): - self.invoke_httpd([tls_cert_option, self.tls_cert, tls_password_option, non_existent_file]) + self.invoke_httpd(tls_cert_option, + self.tls_cert, + tls_password_option, + non_existent_file) - @mock.patch('http.server.test') def test_no_arguments(self, mock_func): self.invoke_httpd() mock_func.assert_called_once_with(HandlerClass=self.default_handler, @@ -1724,14 +1718,14 @@ def test_no_arguments(self, mock_func): tls_password=None) mock_func.reset_mock() - def test_help_flag(self): + def test_help_flag(self, _): options = ['-h', '--help'] for option in options: with self.assertRaises(SystemExit): output = self.invoke_httpd(option) self.assertStartsWith(output, 'usage: ') - def test_unknown_flag(self): + def test_unknown_flag(self, _): with self.assertRaises(SystemExit): self.invoke_httpd('--unknown-flag') From 85cb0994c45a3eb5a2c926cfb94232a97e27cc95 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:43:09 +0800 Subject: [PATCH 24/61] Update Lib/test/test_httpservers.py Co-authored-by: Semyon Moroz --- Lib/test/test_httpservers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index cbe757cfb2940e..4edc56335c41f1 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -23,7 +23,6 @@ import http, http.client import urllib.parse import tempfile -import textwrap import time import datetime import threading From 9417864891efd44fce3698e6b6baa8f12b53c615 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:43:20 +0800 Subject: [PATCH 25/61] Update Lib/test/test_httpservers.py Co-authored-by: Semyon Moroz --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 4edc56335c41f1..7f4fed5ada062f 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1577,7 +1577,7 @@ def invoke_httpd(self, *args): with contextlib.redirect_stdout(output), \ contextlib.redirect_stderr(output): server._main(args) - return textwrap.dedent(output.getvalue()).strip() + return output.getvalue() def test_port_flag(self, mock_func): From a5a7d8ce25ebd593af134ae83abf46f3fcd1b982 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:43:43 +0800 Subject: [PATCH 26/61] Update Lib/test/test_httpservers.py Co-authored-by: Semyon Moroz --- Lib/test/test_httpservers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 7f4fed5ada062f..5d71287be42155 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1726,7 +1726,8 @@ def test_help_flag(self, _): def test_unknown_flag(self, _): with self.assertRaises(SystemExit): - self.invoke_httpd('--unknown-flag') + output = self.invoke_httpd('--unknown-flag') + self.assertStartsWith(output, 'usage: ') def setUpModule(): unittest.addModuleCleanup(os.chdir, os.getcwd()) From a35e0d60b0410accf3c36c8928c15f09602194fe Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:44:14 +0800 Subject: [PATCH 27/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 5d71287be42155..5bf3725c3ef08b 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1570,7 +1570,7 @@ def tearDown(self): return super().tearDown() def get_random_temporary_file_name(self): - return os.path.join(tempfile.gettempdir(), os.urandom(16).hex()) + return os.path.join(tempfile.gettempdir(), os.urandom(16).hex()) def invoke_httpd(self, *args): output = StringIO() From e2266c0313226a506ed494b876f14ec4c4604ed5 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:44:27 +0800 Subject: [PATCH 28/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 5bf3725c3ef08b..f9dd0170bdc0dd 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1579,7 +1579,6 @@ def invoke_httpd(self, *args): server._main(args) return output.getvalue() - def test_port_flag(self, mock_func): ports = [8000, 65535,] for port in ports: From b4f9e726fe701907e69bae8ca539f07388cfebb6 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:44:55 +0800 Subject: [PATCH 29/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index f9dd0170bdc0dd..2fdccff9e48654 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1580,7 +1580,7 @@ def invoke_httpd(self, *args): return output.getvalue() def test_port_flag(self, mock_func): - ports = [8000, 65535,] + ports = [8000, 65535] for port in ports: with self.subTest(port=port): self.invoke_httpd(str(port)) From a61b5b127cbfa38fd16bf32439182283719dd384 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:45:13 +0800 Subject: [PATCH 30/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 2fdccff9e48654..baae42e2bf13a6 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1615,7 +1615,7 @@ def test_bind_flag(self, mock_func): def test_protocol_flag(self, mock_func): options = ['-p', '--protocol'] - protocols = ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2.0', 'HTTP/3.0',] + protocols = ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2.0', 'HTTP/3.0'] for flag in options: for protocol in protocols: with self.subTest(flag=flag, protocol=protocol): From fd7093221c2a4df50e38089a46cf3535fac5e2f4 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Fri, 18 Apr 2025 21:52:03 +0800 Subject: [PATCH 31/61] update --- Lib/test/test_httpservers.py | 40 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index baae42e2bf13a6..09fd5b27295752 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1536,9 +1536,9 @@ def test_server_test_ipv4(self, _): server.test(ServerClass=mock_server, bind=bind) self.assertEqual(mock_server.address_family, socket.AF_INET) -@mock.patch('http.server.test') class CommandLineTestCase(unittest.TestCase): def setUp(self): + super().setUp() self.default_port = 8000 self.default_bind = None self.default_protocol = 'HTTP/1.0' @@ -1562,16 +1562,12 @@ def setUp(self): 'tls_key': None, 'tls_password': None, } - return super().setUp() def tearDown(self): if os.path.exists(self.tls_password_file): os.remove(self.tls_password_file) return super().tearDown() - def get_random_temporary_file_name(self): - return os.path.join(tempfile.gettempdir(), os.urandom(16).hex()) - def invoke_httpd(self, *args): output = StringIO() with contextlib.redirect_stdout(output), \ @@ -1579,6 +1575,7 @@ def invoke_httpd(self, *args): server._main(args) return output.getvalue() + @mock.patch('http.server.test') def test_port_flag(self, mock_func): ports = [8000, 65535] for port in ports: @@ -1589,10 +1586,12 @@ def test_port_flag(self, mock_func): self.args['port'] = self.default_port mock_func.reset_mock() + @mock.patch('http.server.test') def test_directory_flag(self, mock_func): options = ['-d', '--directory'] directories = ['.', '/foo', '\\bar', '/', - 'C:\\', 'C:\\foo', 'C:\\bar',] + 'C:\\', 'C:\\foo', 'C:\\bar', + '/home/user', './foo/foo2', 'D:\\foo\\bar'] for flag in options: for directory in directories: with self.subTest(flag=flag, directory=directory): @@ -1600,6 +1599,7 @@ def test_directory_flag(self, mock_func): mock_func.assert_called_once_with(**self.args) mock_func.reset_mock() + @mock.patch('http.server.test') def test_bind_flag(self, mock_func): options = ['-b', '--bind'] bind_addresses = ['localhost', '127.0.0.1', '::1', @@ -1613,6 +1613,7 @@ def test_bind_flag(self, mock_func): self.args['bind'] = self.default_bind mock_func.reset_mock() + @mock.patch('http.server.test') def test_protocol_flag(self, mock_func): options = ['-p', '--protocol'] protocols = ['HTTP/1.0', 'HTTP/1.1', 'HTTP/2.0', 'HTTP/3.0'] @@ -1625,6 +1626,8 @@ def test_protocol_flag(self, mock_func): self.args['protocol'] = self.default_protocol mock_func.reset_mock() + # TODO: This test should be removed once the CGI component is removed(3.15) + @mock.patch('http.server.test') def test_cgi_flag(self, mock_func): self.invoke_httpd('--cgi') mock_func.assert_called_once_with(HandlerClass=CGIHTTPRequestHandler, @@ -1637,6 +1640,7 @@ def test_cgi_flag(self, mock_func): tls_password=None) @unittest.skipIf(ssl is None, "requires ssl") + @mock.patch('http.server.test') def test_tls_flag(self, mock_func): tls_cert_options = ['--tls-cert', ] tls_key_options = ['--tls-key', ] @@ -1691,19 +1695,16 @@ def test_tls_flag(self, mock_func): # Abnormal: --tls-password-file cannot be opened - non_existent_file = self.get_random_temporary_file_name() - retry_count = 0 - while os.path.exists(non_existent_file) and retry_count < 10: - non_existent_file = self.get_random_temporary_file_name() - if not os.path.exists(non_existent_file): - for tls_password_option in tls_password_options: - for tls_cert_option in tls_cert_options: - with self.assertRaises(SystemExit): - self.invoke_httpd(tls_cert_option, - self.tls_cert, - tls_password_option, - non_existent_file) + non_existent_file = 'non_existent_file' + for tls_password_option in tls_password_options: + for tls_cert_option in tls_cert_options: + with self.assertRaises(SystemExit): + self.invoke_httpd(tls_cert_option, + self.tls_cert, + tls_password_option, + non_existent_file) + @mock.patch('http.server.test') def test_no_arguments(self, mock_func): self.invoke_httpd() mock_func.assert_called_once_with(HandlerClass=self.default_handler, @@ -1716,6 +1717,7 @@ def test_no_arguments(self, mock_func): tls_password=None) mock_func.reset_mock() + @mock.patch('http.server.test') def test_help_flag(self, _): options = ['-h', '--help'] for option in options: @@ -1723,11 +1725,13 @@ def test_help_flag(self, _): output = self.invoke_httpd(option) self.assertStartsWith(output, 'usage: ') + @mock.patch('http.server.test') def test_unknown_flag(self, _): with self.assertRaises(SystemExit): output = self.invoke_httpd('--unknown-flag') self.assertStartsWith(output, 'usage: ') + def setUpModule(): unittest.addModuleCleanup(os.chdir, os.getcwd()) From 348e256b6cf7206ca73a97bb330ac08167b31b37 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 19 Apr 2025 18:33:04 +0800 Subject: [PATCH 32/61] update --- Lib/test/test_httpservers.py | 46 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 09fd5b27295752..f1757ca59bd295 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1537,31 +1537,31 @@ def test_server_test_ipv4(self, _): self.assertEqual(mock_server.address_family, socket.AF_INET) class CommandLineTestCase(unittest.TestCase): + default_port = 8000 + default_bind = None + default_protocol = 'HTTP/1.0' + default_handler = SimpleHTTPRequestHandler + default_server = unittest.mock.ANY + tls_cert = certdata_file('ssl_cert.pem') + tls_key = certdata_file('ssl_key.pem') + tls_password = 'somepass' + args = { + 'HandlerClass': default_handler, + 'ServerClass': default_server, + 'protocol': default_protocol, + 'port': default_port, + 'bind': default_bind, + 'tls_cert': None, + 'tls_key': None, + 'tls_password': None, + } + def setUp(self): super().setUp() - self.default_port = 8000 - self.default_bind = None - self.default_protocol = 'HTTP/1.0' - self.default_handler = SimpleHTTPRequestHandler - self.default_server = unittest.mock.ANY - self.tls_cert = certdata_file('ssl_cert.pem') - self.tls_key = certdata_file('ssl_key.pem') - self.tls_password = 'somepass' - tls_password_file_object = \ - tempfile.NamedTemporaryFile(mode='w+', delete=False) - tls_password_file_object.write(self.tls_password) - self.tls_password_file = tls_password_file_object.name - tls_password_file_object.close() - self.args = { - 'HandlerClass': self.default_handler, - 'ServerClass': self.default_server, - 'protocol': self.default_protocol, - 'port': self.default_port, - 'bind': self.default_bind, - 'tls_cert': None, - 'tls_key': None, - 'tls_password': None, - } + self.tls_password_file = tempfile.mktemp() + with open(self.tls_password_file, 'wb') as f: + f.write(self.tls_password.encode()) + self.addCleanup(os_helper.unlink, self.tls_password_file) def tearDown(self): if os.path.exists(self.tls_password_file): From 4c315b081d89bfe279c0749b063e2273bd4ad91b Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 19 Apr 2025 18:41:38 +0800 Subject: [PATCH 33/61] update(2) --- Lib/test/test_httpservers.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index f1757ca59bd295..04f2e4b04d374e 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1563,11 +1563,6 @@ def setUp(self): f.write(self.tls_password.encode()) self.addCleanup(os_helper.unlink, self.tls_password_file) - def tearDown(self): - if os.path.exists(self.tls_password_file): - os.remove(self.tls_password_file) - return super().tearDown() - def invoke_httpd(self, *args): output = StringIO() with contextlib.redirect_stdout(output), \ From a57b95980c66c026b341a6cb3cb0304e6ab13e79 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Mon, 5 May 2025 11:15:10 +0800 Subject: [PATCH 34/61] add cli test --- Lib/test/test_httpservers.py | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 04f2e4b04d374e..91fc0768101949 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -11,6 +11,7 @@ import contextlib import os import socket +import subprocess import sys import re import base64 @@ -22,6 +23,7 @@ import html import http, http.client import urllib.parse +import urllib.request import tempfile import time import datetime @@ -34,6 +36,7 @@ from test.support import ( is_apple, import_helper, os_helper, requires_subprocess, threading_helper ) +from test.support.socket_helper import find_unused_port try: import ssl @@ -1555,6 +1558,8 @@ class CommandLineTestCase(unittest.TestCase): 'tls_key': None, 'tls_password': None, } + random_data = os.urandom(1024) + random_file_name = 'random.bin' def setUp(self): super().setUp() @@ -1562,6 +1567,9 @@ def setUp(self): with open(self.tls_password_file, 'wb') as f: f.write(self.tls_password.encode()) self.addCleanup(os_helper.unlink, self.tls_password_file) + with open(self.random_file_name, 'wb') as f: + f.write(self.random_data) + self.addCleanup(os_helper.unlink, self.random_file_name) def invoke_httpd(self, *args): output = StringIO() @@ -1726,6 +1734,44 @@ def test_unknown_flag(self, _): output = self.invoke_httpd('--unknown-flag') self.assertStartsWith(output, 'usage: ') + def fetch_file(self, path, allow_self_signed_cert=True) -> bytes: + context = ssl.create_default_context() + if allow_self_signed_cert: + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + req = urllib.request.Request(path, method='GET') + res = urllib.request.urlopen(req, context=context) + return res.read() + + def test_http_client(self): + port = find_unused_port() + bind = '127.0.0.1' + proc = subprocess.Popen([sys.executable, '-m', 'http.server', + str(port), '-b', bind], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + time.sleep(0.5) # Wait for the server to start. + # TODO: Find a better way to wait for the server to start. + res = self.fetch_file(f'http://{bind}:{port}/{self.random_file_name}') + self.assertEqual(res, self.random_data) + proc.kill() + proc.wait() + + def test_https_client(self): + port = find_unused_port() + bind = '127.0.0.1' + proc = subprocess.Popen([sys.executable, '-m', 'http.server', + str(port), '-b', bind, + '--tls-cert', self.tls_cert, + '--tls-key', self.tls_key, + '--tls-password-file', self.tls_password_file], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + time.sleep(0.5) + res = self.fetch_file(f'https://{bind}:{port}/{self.random_file_name}') + self.assertEqual(res, self.random_data) + proc.kill() + proc.wait() def setUpModule(): unittest.addModuleCleanup(os.chdir, os.getcwd()) From b627e02b25e3d105fc5c848981e6ebb3b51a4ec3 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Mon, 5 May 2025 14:34:20 +0800 Subject: [PATCH 35/61] add cli test(1) --- Lib/test/test_httpservers.py | 66 +++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 91fc0768101949..a467622edcf9e2 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1743,33 +1743,81 @@ def fetch_file(self, path, allow_self_signed_cert=True) -> bytes: res = urllib.request.urlopen(req, context=context) return res.read() + def parse_cli_output(self, output: str) -> tuple[str, str, int]: + matches = re.search(r'\((https?)://([^/:]+):(\d+)/?\)', output) + return matches.group(1), matches.group(2), int(matches.group(3)) + def test_http_client(self): port = find_unused_port() bind = '127.0.0.1' - proc = subprocess.Popen([sys.executable, '-m', 'http.server', + proc = subprocess.Popen([sys.executable, '-u', '-m', 'http.server', str(port), '-b', bind], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) - time.sleep(0.5) # Wait for the server to start. - # TODO: Find a better way to wait for the server to start. + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + bufsize=1, + text=True) + max_tries = 50 + while True: + # Wait for the server to start. + if max_tries <= 0: + self.fail('Server did not start') + line = proc.stdout.readline() + if not line: + if proc.poll() is not None: + break + time.sleep(0.1) + max_tries -= 1 + continue + _protocol, _host, _port = self.parse_cli_output(line) + if not _protocol or not _host or not _port: + time.sleep(0.1) + max_tries -= 1 + continue + self.assertEqual(_protocol, 'http') + self.assertEqual(_host, bind) + self.assertEqual(_port, port) + break res = self.fetch_file(f'http://{bind}:{port}/{self.random_file_name}') self.assertEqual(res, self.random_data) + proc.stdout.close() proc.kill() proc.wait() def test_https_client(self): port = find_unused_port() bind = '127.0.0.1' - proc = subprocess.Popen([sys.executable, '-m', 'http.server', + proc = subprocess.Popen([sys.executable, '-u', '-m', 'http.server', str(port), '-b', bind, '--tls-cert', self.tls_cert, '--tls-key', self.tls_key, '--tls-password-file', self.tls_password_file], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) - time.sleep(0.5) + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + bufsize=1, + text=True) + max_tries = 50 + while True: + if max_tries <= 0: + self.fail('Server did not start') + line = proc.stdout.readline() + if not line: + if proc.poll() is not None: + break + time.sleep(0.1) + max_tries -= 1 + continue + _protocol, _host, _port = self.parse_cli_output(line) + if not _protocol or not _host or not _port: + time.sleep(0.1) + max_tries -= 1 + continue + self.assertEqual(_protocol, 'https') + self.assertEqual(_host, bind) + self.assertEqual(_port, port) + break res = self.fetch_file(f'https://{bind}:{port}/{self.random_file_name}') self.assertEqual(res, self.random_data) + proc.stdout.close() proc.kill() proc.wait() From 41065c2103291b4a7ac63dcc98b02515b3c0be0e Mon Sep 17 00:00:00 2001 From: ggqlq Date: Wed, 7 May 2025 15:35:29 +0800 Subject: [PATCH 36/61] add cli test(2) --- Lib/test/test_httpservers.py | 68 +++++++++++++----------------------- 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index a467622edcf9e2..67cff531d1fade 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1744,8 +1744,29 @@ def fetch_file(self, path, allow_self_signed_cert=True) -> bytes: return res.read() def parse_cli_output(self, output: str) -> tuple[str, str, int]: - matches = re.search(r'\((https?)://([^/:]+):(\d+)/?\)', output) - return matches.group(1), matches.group(2), int(matches.group(3)) + try: + matches = re.search(r'\((https?)://([^/:]+):(\d+)/?\)', output) + return matches.group(1), matches.group(2), int(matches.group(3)) + except: + return None, None, None + + def wait_for_server(self, proc, protocol, port, bind, timeout=50) -> bool: + while timeout > 0: + line = proc.stdout.readline() + if not line: + time.sleep(0.1) + timeout -= 1 + continue + _protocol, _host, _port = self.parse_cli_output(line) + if not _protocol or not _host or not _port: + time.sleep(0.1) + timeout -= 1 + continue + if _protocol == protocol and _host == bind and _port == port: + return True + else: + break + return False def test_http_client(self): port = find_unused_port() @@ -1756,27 +1777,7 @@ def test_http_client(self): stderr=subprocess.STDOUT, bufsize=1, text=True) - max_tries = 50 - while True: - # Wait for the server to start. - if max_tries <= 0: - self.fail('Server did not start') - line = proc.stdout.readline() - if not line: - if proc.poll() is not None: - break - time.sleep(0.1) - max_tries -= 1 - continue - _protocol, _host, _port = self.parse_cli_output(line) - if not _protocol or not _host or not _port: - time.sleep(0.1) - max_tries -= 1 - continue - self.assertEqual(_protocol, 'http') - self.assertEqual(_host, bind) - self.assertEqual(_port, port) - break + self.assertTrue(self.wait_for_server(proc, 'http', port, bind)) res = self.fetch_file(f'http://{bind}:{port}/{self.random_file_name}') self.assertEqual(res, self.random_data) proc.stdout.close() @@ -1795,26 +1796,7 @@ def test_https_client(self): stderr=subprocess.STDOUT, bufsize=1, text=True) - max_tries = 50 - while True: - if max_tries <= 0: - self.fail('Server did not start') - line = proc.stdout.readline() - if not line: - if proc.poll() is not None: - break - time.sleep(0.1) - max_tries -= 1 - continue - _protocol, _host, _port = self.parse_cli_output(line) - if not _protocol or not _host or not _port: - time.sleep(0.1) - max_tries -= 1 - continue - self.assertEqual(_protocol, 'https') - self.assertEqual(_host, bind) - self.assertEqual(_port, port) - break + self.assertTrue(self.wait_for_server(proc, 'https', port, bind)) res = self.fetch_file(f'https://{bind}:{port}/{self.random_file_name}') self.assertEqual(res, self.random_data) proc.stdout.close() From cbda832f1f699eb8fc34af3d6874fe804fa4590e Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 10 May 2025 03:39:03 +0800 Subject: [PATCH 37/61] move runtime tests into a new class --- Lib/test/test_httpservers.py | 53 ++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 67cff531d1fade..c7a73c7c888e99 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -34,8 +34,9 @@ import unittest from test import support from test.support import ( - is_apple, import_helper, os_helper, requires_subprocess, threading_helper + is_apple, import_helper, os_helper, requires_subprocess, threading_helper, ) +from test.support.script_helper import spawn_python, kill_python from test.support.socket_helper import find_unused_port try: @@ -1539,6 +1540,7 @@ def test_server_test_ipv4(self, _): server.test(ServerClass=mock_server, bind=bind) self.assertEqual(mock_server.address_family, socket.AF_INET) + class CommandLineTestCase(unittest.TestCase): default_port = 8000 default_bind = None @@ -1558,8 +1560,6 @@ class CommandLineTestCase(unittest.TestCase): 'tls_key': None, 'tls_password': None, } - random_data = os.urandom(1024) - random_file_name = 'random.bin' def setUp(self): super().setUp() @@ -1567,9 +1567,6 @@ def setUp(self): with open(self.tls_password_file, 'wb') as f: f.write(self.tls_password.encode()) self.addCleanup(os_helper.unlink, self.tls_password_file) - with open(self.random_file_name, 'wb') as f: - f.write(self.random_data) - self.addCleanup(os_helper.unlink, self.random_file_name) def invoke_httpd(self, *args): output = StringIO() @@ -1734,6 +1731,17 @@ def test_unknown_flag(self, _): output = self.invoke_httpd('--unknown-flag') self.assertStartsWith(output, 'usage: ') + +class CommandLineRunTimeTestCase(CommandLineTestCase): + random_data = os.urandom(1024) + random_file_name = 'random.bin' + + def setUp(self): + super().setUp() + with open(self.random_file_name, 'wb') as f: + f.write(self.random_data) + self.addCleanup(os_helper.unlink, self.random_file_name) + def fetch_file(self, path, allow_self_signed_cert=True) -> bytes: context = ssl.create_default_context() if allow_self_signed_cert: @@ -1771,37 +1779,28 @@ def wait_for_server(self, proc, protocol, port, bind, timeout=50) -> bool: def test_http_client(self): port = find_unused_port() bind = '127.0.0.1' - proc = subprocess.Popen([sys.executable, '-u', '-m', 'http.server', - str(port), '-b', bind], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - text=True) + proc = spawn_python('-u', '-m', 'http.server', str(port), '-b', bind, + bufsize=1, text=True) self.assertTrue(self.wait_for_server(proc, 'http', port, bind)) res = self.fetch_file(f'http://{bind}:{port}/{self.random_file_name}') self.assertEqual(res, self.random_data) - proc.stdout.close() - proc.kill() - proc.wait() + proc.terminate() + kill_python(proc) def test_https_client(self): port = find_unused_port() bind = '127.0.0.1' - proc = subprocess.Popen([sys.executable, '-u', '-m', 'http.server', - str(port), '-b', bind, - '--tls-cert', self.tls_cert, - '--tls-key', self.tls_key, - '--tls-password-file', self.tls_password_file], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - bufsize=1, - text=True) + proc = spawn_python('-u', '-m', 'http.server', str(port), '-b', bind, + '--tls-cert', self.tls_cert, + '--tls-key', self.tls_key, + '--tls-password-file', self.tls_password_file, + bufsize=1, text=True) self.assertTrue(self.wait_for_server(proc, 'https', port, bind)) res = self.fetch_file(f'https://{bind}:{port}/{self.random_file_name}') self.assertEqual(res, self.random_data) - proc.stdout.close() - proc.kill() - proc.wait() + proc.terminate() + kill_python(proc) + def setUpModule(): unittest.addModuleCleanup(os.chdir, os.getcwd()) From 86847e8efba9a780ce1eccea2bb1e37dcc5c17fd Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 10 May 2025 11:40:19 +0800 Subject: [PATCH 38/61] update --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index c7a73c7c888e99..59692698370143 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -34,7 +34,7 @@ import unittest from test import support from test.support import ( - is_apple, import_helper, os_helper, requires_subprocess, threading_helper, + is_apple, import_helper, os_helper, requires_subprocess, threading_helper ) from test.support.script_helper import spawn_python, kill_python from test.support.socket_helper import find_unused_port From d5047608ccea7170859f7cca1fed23b8405316d4 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 10 May 2025 13:34:43 +0800 Subject: [PATCH 39/61] update --- Lib/test/test_httpservers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 59692698370143..1f1a571f097ba9 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1732,15 +1732,22 @@ def test_unknown_flag(self, _): self.assertStartsWith(output, 'usage: ') -class CommandLineRunTimeTestCase(CommandLineTestCase): +class CommandLineRunTimeTestCase(unittest.TestCase): random_data = os.urandom(1024) random_file_name = 'random.bin' + tls_cert = certdata_file('ssl_cert.pem') + tls_key = certdata_file('ssl_key.pem') + tls_password = 'somepass' def setUp(self): super().setUp() with open(self.random_file_name, 'wb') as f: f.write(self.random_data) self.addCleanup(os_helper.unlink, self.random_file_name) + self.tls_password_file = tempfile.mktemp() + with open(self.tls_password_file, 'wb') as f: + f.write(self.tls_password.encode()) + self.addCleanup(os_helper.unlink, self.tls_password_file) def fetch_file(self, path, allow_self_signed_cert=True) -> bytes: context = ssl.create_default_context() From 811d86dcd845549b51849791aeb50635142699b7 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Fri, 16 May 2025 16:11:02 +0800 Subject: [PATCH 40/61] remove test_cli_flag function --- Lib/test/test_httpservers.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 1f1a571f097ba9..e2c9bd5a94b54d 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1626,19 +1626,6 @@ def test_protocol_flag(self, mock_func): self.args['protocol'] = self.default_protocol mock_func.reset_mock() - # TODO: This test should be removed once the CGI component is removed(3.15) - @mock.patch('http.server.test') - def test_cgi_flag(self, mock_func): - self.invoke_httpd('--cgi') - mock_func.assert_called_once_with(HandlerClass=CGIHTTPRequestHandler, - ServerClass=self.default_server, - protocol=self.default_protocol, - port=self.default_port, - bind=self.default_bind, - tls_cert=None, - tls_key=None, - tls_password=None) - @unittest.skipIf(ssl is None, "requires ssl") @mock.patch('http.server.test') def test_tls_flag(self, mock_func): From e5df251016b8efda147c14eb89da5b86711b5604 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 16 May 2025 16:47:54 +0800 Subject: [PATCH 41/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index e2c9bd5a94b54d..06379e5cfe9292 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -36,7 +36,7 @@ from test.support import ( is_apple, import_helper, os_helper, requires_subprocess, threading_helper ) -from test.support.script_helper import spawn_python, kill_python +from test.support.script_helper import kill_python, spawn_python from test.support.socket_helper import find_unused_port try: From 1843c804d98aee39b083c0cb25b09b479ced6efb Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 16 May 2025 16:48:07 +0800 Subject: [PATCH 42/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 06379e5cfe9292..931f1d734dc19b 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1745,7 +1745,7 @@ def fetch_file(self, path, allow_self_signed_cert=True) -> bytes: res = urllib.request.urlopen(req, context=context) return res.read() - def parse_cli_output(self, output: str) -> tuple[str, str, int]: + def parse_cli_output(self, output): try: matches = re.search(r'\((https?)://([^/:]+):(\d+)/?\)', output) return matches.group(1), matches.group(2), int(matches.group(3)) From 8e8b755a59421bade7132cc7784e688e7053ff75 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 16 May 2025 16:48:18 +0800 Subject: [PATCH 43/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 931f1d734dc19b..81db8623c3db07 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1736,7 +1736,7 @@ def setUp(self): f.write(self.tls_password.encode()) self.addCleanup(os_helper.unlink, self.tls_password_file) - def fetch_file(self, path, allow_self_signed_cert=True) -> bytes: + def fetch_file(self, path, allow_self_signed_cert=True): context = ssl.create_default_context() if allow_self_signed_cert: context.check_hostname = False From 2f742d9c487017e091b135c3b6caee8122e73886 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 16 May 2025 16:48:57 +0800 Subject: [PATCH 44/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 81db8623c3db07..822221e5859a81 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1716,7 +1716,7 @@ def test_help_flag(self, _): def test_unknown_flag(self, _): with self.assertRaises(SystemExit): output = self.invoke_httpd('--unknown-flag') - self.assertStartsWith(output, 'usage: ') + self.assertStartsWith(output, 'usage: ') class CommandLineRunTimeTestCase(unittest.TestCase): From b5c5ab0d27201ca420f7016531a95a20364a9869 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 16 May 2025 16:49:39 +0800 Subject: [PATCH 45/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 822221e5859a81..6b95d0c898becc 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1694,14 +1694,7 @@ def test_tls_flag(self, mock_func): @mock.patch('http.server.test') def test_no_arguments(self, mock_func): self.invoke_httpd() - mock_func.assert_called_once_with(HandlerClass=self.default_handler, - ServerClass=self.default_server, - protocol=self.default_protocol, - port=self.default_port, - bind=self.default_bind, - tls_cert=None, - tls_key=None, - tls_password=None) + mock_func.assert_called_once_with(**self.args) mock_func.reset_mock() @mock.patch('http.server.test') From 05aea0616e7c3467ecab5aa26f37edc32cef186f Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Fri, 16 May 2025 16:50:08 +0800 Subject: [PATCH 46/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 6b95d0c898becc..ebc42e150cf3b0 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1703,7 +1703,7 @@ def test_help_flag(self, _): for option in options: with self.assertRaises(SystemExit): output = self.invoke_httpd(option) - self.assertStartsWith(output, 'usage: ') + self.assertStartsWith(output, 'usage: ') @mock.patch('http.server.test') def test_unknown_flag(self, _): From 333a761a4e8bac96a865c99f6dcd6682a04de26f Mon Sep 17 00:00:00 2001 From: ggqlq Date: Fri, 16 May 2025 17:24:12 +0800 Subject: [PATCH 47/61] split stdout and stderr, remove output check after self.assertRaises(SystemExit) --- Lib/test/test_httpservers.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index ebc42e150cf3b0..83fee58ff5b852 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1569,11 +1569,12 @@ def setUp(self): self.addCleanup(os_helper.unlink, self.tls_password_file) def invoke_httpd(self, *args): - output = StringIO() - with contextlib.redirect_stdout(output), \ - contextlib.redirect_stderr(output): + stdout = StringIO() + stderr = StringIO() + with contextlib.redirect_stdout(stdout), \ + contextlib.redirect_stderr(stderr): server._main(args) - return output.getvalue() + return {'stdout': stdout.getvalue(), 'stderr': stderr.getvalue()} @mock.patch('http.server.test') def test_port_flag(self, mock_func): @@ -1703,14 +1704,11 @@ def test_help_flag(self, _): for option in options: with self.assertRaises(SystemExit): output = self.invoke_httpd(option) - self.assertStartsWith(output, 'usage: ') @mock.patch('http.server.test') def test_unknown_flag(self, _): with self.assertRaises(SystemExit): output = self.invoke_httpd('--unknown-flag') - self.assertStartsWith(output, 'usage: ') - class CommandLineRunTimeTestCase(unittest.TestCase): random_data = os.urandom(1024) From 4304354b4db2baeb007b59d6ac267f2db402fa48 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Fri, 16 May 2025 17:45:46 +0800 Subject: [PATCH 48/61] split tls tests --- Lib/test/test_httpservers.py | 75 +++++++++++++++++------------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 83fee58ff5b852..05ddc5bedb1dac 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1550,6 +1550,9 @@ class CommandLineTestCase(unittest.TestCase): tls_cert = certdata_file('ssl_cert.pem') tls_key = certdata_file('ssl_key.pem') tls_password = 'somepass' + tls_cert_options = ['--tls-cert'] + tls_key_options = ['--tls-key'] + tls_password_options = ['--tls-password-file'] args = { 'HandlerClass': default_handler, 'ServerClass': default_server, @@ -1629,63 +1632,55 @@ def test_protocol_flag(self, mock_func): @unittest.skipIf(ssl is None, "requires ssl") @mock.patch('http.server.test') - def test_tls_flag(self, mock_func): - tls_cert_options = ['--tls-cert', ] - tls_key_options = ['--tls-key', ] - tls_password_options = ['--tls-password-file', ] - # Normal: --tls-cert and --tls-key - - for tls_cert_option in tls_cert_options: - for tls_key_option in tls_key_options: + def test_tls_cert_and_key_flags(self, mock_func): + for tls_cert_option in self.tls_cert_options: + for tls_key_option in self.tls_key_options: self.invoke_httpd(tls_cert_option, self.tls_cert, tls_key_option, self.tls_key) - self.args['tls_cert'] = self.tls_cert - self.args['tls_key'] = self.tls_key - mock_func.assert_called_once_with(**self.args) - self.args['tls_cert'] = None - self.args['tls_key'] = None + call_args = { + 'tls_cert': self.tls_cert, + 'tls_key': self.tls_key, + } + call_args = self.args | call_args + mock_func.assert_called_once_with(**call_args) mock_func.reset_mock() - # Normal: --tls-cert, --tls-key and --tls-password-file - - for tls_cert_option in tls_cert_options: - for tls_key_option in tls_key_options: - for tls_password_option in tls_password_options: + @unittest.skipIf(ssl is None, "requires ssl") + @mock.patch('http.server.test') + def test_tls_cert_and_key_and_password_flags(self, mock_func): + for tls_cert_option in self.tls_cert_options: + for tls_key_option in self.tls_key_options: + for tls_password_option in self.tls_password_options: self.invoke_httpd(tls_cert_option, self.tls_cert, tls_key_option, self.tls_key, tls_password_option, self.tls_password_file) - self.args['tls_cert'] = self.tls_cert - self.args['tls_key'] = self.tls_key - self.args['tls_password'] = self.tls_password - mock_func.assert_called_once_with(**self.args) - self.args['tls_cert'] = None - self.args['tls_key'] = None - self.args['tls_password'] = None + call_args = { + 'tls_cert': self.tls_cert, + 'tls_key': self.tls_key, + 'tls_password': self.tls_password, + } + call_args = self.args | call_args + mock_func.assert_called_once_with(**call_args) mock_func.reset_mock() - # Abnormal: --tls-key without --tls-cert - - for tls_key_option in tls_key_options: - for tls_cert_option in tls_cert_options: + @unittest.skipIf(ssl is None, "requires ssl") + @mock.patch('http.server.test') + def test_missing_tls_cert_flag(self, mock_func): + for tls_key_option in self.tls_key_options: + for tls_cert_option in self.tls_cert_options: with self.assertRaises(SystemExit): self.invoke_httpd(tls_key_option, self.tls_key) mock_func.reset_mock() - # Abnormal: --tls-password-file without --tls-cert - - for tls_password_option in tls_password_options: - with self.assertRaises(SystemExit): - self.invoke_httpd(tls_password_option, self.tls_password_file) - mock_func.reset_mock() - - # Abnormal: --tls-password-file cannot be opened - + @unittest.skipIf(ssl is None, "requires ssl") + @mock.patch('http.server.test') + def test_invalid_password_file(self, mock_func): non_existent_file = 'non_existent_file' - for tls_password_option in tls_password_options: - for tls_cert_option in tls_cert_options: + for tls_password_option in self.tls_password_options: + for tls_cert_option in self.tls_cert_options: with self.assertRaises(SystemExit): self.invoke_httpd(tls_cert_option, self.tls_cert, From 6c4c13402e136ccbabd752892683a9183a8de1eb Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Sat, 17 May 2025 02:47:26 +0800 Subject: [PATCH 49/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 05ddc5bedb1dac..31ca49ffbac5f4 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1673,7 +1673,7 @@ def test_missing_tls_cert_flag(self, mock_func): for tls_cert_option in self.tls_cert_options: with self.assertRaises(SystemExit): self.invoke_httpd(tls_key_option, self.tls_key) - mock_func.reset_mock() + mock_func.reset_mock() @unittest.skipIf(ssl is None, "requires ssl") @mock.patch('http.server.test') From 7b3bb1d7eb88ab78890619d0bd40c332a5056388 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Sat, 17 May 2025 02:47:43 +0800 Subject: [PATCH 50/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 31ca49ffbac5f4..9b6daf9510a3e5 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1698,7 +1698,7 @@ def test_help_flag(self, _): options = ['-h', '--help'] for option in options: with self.assertRaises(SystemExit): - output = self.invoke_httpd(option) + _ = self.invoke_httpd(option) @mock.patch('http.server.test') def test_unknown_flag(self, _): From eed42289b13a6e072f18e4798f9a6893bafe3814 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Sat, 17 May 2025 02:47:56 +0800 Subject: [PATCH 51/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 9b6daf9510a3e5..fa292be0482240 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1703,7 +1703,7 @@ def test_help_flag(self, _): @mock.patch('http.server.test') def test_unknown_flag(self, _): with self.assertRaises(SystemExit): - output = self.invoke_httpd('--unknown-flag') + _ = self.invoke_httpd('--unknown-flag') class CommandLineRunTimeTestCase(unittest.TestCase): random_data = os.urandom(1024) From 7c1713e8f31bfa2318acd2271db712f41dced3d5 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Sat, 17 May 2025 02:48:07 +0800 Subject: [PATCH 52/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index fa292be0482240..04a3a209180457 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1732,11 +1732,10 @@ def fetch_file(self, path, allow_self_signed_cert=True): return res.read() def parse_cli_output(self, output): - try: - matches = re.search(r'\((https?)://([^/:]+):(\d+)/?\)', output) - return matches.group(1), matches.group(2), int(matches.group(3)) - except: + matches = re.search(r'\((https?)://([^/:]+):(\d+)/?\)', output) + if matches is None: return None, None, None + return matches.group(1), matches.group(2), int(matches.group(3)) def wait_for_server(self, proc, protocol, port, bind, timeout=50) -> bool: while timeout > 0: From a34fa51ab8ef86fcfb7bc75219e417e1a7f20238 Mon Sep 17 00:00:00 2001 From: ggqlq <124190229+ggqlq@users.noreply.github.com> Date: Sat, 17 May 2025 18:45:35 +0800 Subject: [PATCH 53/61] Update Lib/test/test_httpservers.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 04a3a209180457..ce9a112458777d 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1706,7 +1706,7 @@ def test_unknown_flag(self, _): _ = self.invoke_httpd('--unknown-flag') class CommandLineRunTimeTestCase(unittest.TestCase): - random_data = os.urandom(1024) + random_data = os.urandom(32) random_file_name = 'random.bin' tls_cert = certdata_file('ssl_cert.pem') tls_key = certdata_file('ssl_key.pem') From 8f73c224d00fd8d00c3bb444283877b7bdbd00a8 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 17 May 2025 03:11:37 +0800 Subject: [PATCH 54/61] make invoke_httpd function return a pair --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index ce9a112458777d..7895b07e6b9e9f 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1577,7 +1577,7 @@ def invoke_httpd(self, *args): with contextlib.redirect_stdout(stdout), \ contextlib.redirect_stderr(stderr): server._main(args) - return {'stdout': stdout.getvalue(), 'stderr': stderr.getvalue()} + return stdout.getvalue(), stderr.getvalue() @mock.patch('http.server.test') def test_port_flag(self, mock_func): From 2daf3f8351466bbc9fd870059cb0abec184a2d7b Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 17 May 2025 17:34:17 +0800 Subject: [PATCH 55/61] rename vars in wait_for_server function --- Lib/test/test_httpservers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 7895b07e6b9e9f..a9ad05a340d60e 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1744,12 +1744,12 @@ def wait_for_server(self, proc, protocol, port, bind, timeout=50) -> bool: time.sleep(0.1) timeout -= 1 continue - _protocol, _host, _port = self.parse_cli_output(line) - if not _protocol or not _host or not _port: + protocol_, host_, port_ = self.parse_cli_output(line) + if not protocol_ or not host_ or not port_: time.sleep(0.1) timeout -= 1 continue - if _protocol == protocol and _host == bind and _port == port: + if protocol_ == protocol and host_ == bind and port_ == port: return True else: break From 1c67654e7218c39bda57821b4bcd9d7e0cd696bb Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 17 May 2025 17:51:30 +0800 Subject: [PATCH 56/61] use call_args = self.args | dict(...) --- Lib/test/test_httpservers.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index a9ad05a340d60e..d190b1b65f7386 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1585,9 +1585,8 @@ def test_port_flag(self, mock_func): for port in ports: with self.subTest(port=port): self.invoke_httpd(str(port)) - self.args['port'] = port - mock_func.assert_called_once_with(**self.args) - self.args['port'] = self.default_port + call_args = self.args | dict(port=port) + mock_func.assert_called_once_with(**call_args) mock_func.reset_mock() @mock.patch('http.server.test') @@ -1612,9 +1611,8 @@ def test_bind_flag(self, mock_func): for bind_address in bind_addresses: with self.subTest(flag=flag, bind_address=bind_address): self.invoke_httpd(flag, bind_address) - self.args['bind'] = bind_address - mock_func.assert_called_once_with(**self.args) - self.args['bind'] = self.default_bind + call_args = self.args | dict(bind=bind_address) + mock_func.assert_called_once_with(**call_args) mock_func.reset_mock() @mock.patch('http.server.test') @@ -1625,9 +1623,8 @@ def test_protocol_flag(self, mock_func): for protocol in protocols: with self.subTest(flag=flag, protocol=protocol): self.invoke_httpd(flag, protocol) - self.args['protocol'] = protocol - mock_func.assert_called_once_with(**self.args) - self.args['protocol'] = self.default_protocol + call_args = self.args | dict(protocol=protocol) + mock_func.assert_called_once_with(**call_args) mock_func.reset_mock() @unittest.skipIf(ssl is None, "requires ssl") @@ -1728,8 +1725,8 @@ def fetch_file(self, path, allow_self_signed_cert=True): context.check_hostname = False context.verify_mode = ssl.CERT_NONE req = urllib.request.Request(path, method='GET') - res = urllib.request.urlopen(req, context=context) - return res.read() + with urllib.request.urlopen(req, context=context) as res: + return res.read() def parse_cli_output(self, output): matches = re.search(r'\((https?)://([^/:]+):(\d+)/?\)', output) From 9639219d468c7d949394d7664fe79c005f1d075d Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 17 May 2025 17:53:24 +0800 Subject: [PATCH 57/61] rename 'random.bin' as 'served_filename' --- Lib/test/test_httpservers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index d190b1b65f7386..4bd1f7cf01ac15 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1704,7 +1704,7 @@ def test_unknown_flag(self, _): class CommandLineRunTimeTestCase(unittest.TestCase): random_data = os.urandom(32) - random_file_name = 'random.bin' + random_file_name = 'served_filename' tls_cert = certdata_file('ssl_cert.pem') tls_key = certdata_file('ssl_key.pem') tls_password = 'somepass' From 4156e78c72049a33cae7d01f55164bb2a30b6c19 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 17 May 2025 18:26:53 +0800 Subject: [PATCH 58/61] fix indentation in test_missing_tls_cert_flag --- Lib/test/test_httpservers.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 4bd1f7cf01ac15..377e5f72206ab5 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1667,10 +1667,14 @@ def test_tls_cert_and_key_and_password_flags(self, mock_func): @mock.patch('http.server.test') def test_missing_tls_cert_flag(self, mock_func): for tls_key_option in self.tls_key_options: - for tls_cert_option in self.tls_cert_options: - with self.assertRaises(SystemExit): - self.invoke_httpd(tls_key_option, self.tls_key) - mock_func.reset_mock() + with self.assertRaises(SystemExit): + self.invoke_httpd(tls_key_option, self.tls_key) + mock_func.reset_mock() + + for tls_password_option in self.tls_password_options: + with self.assertRaises(SystemExit): + self.invoke_httpd(tls_password_option, self.tls_password) + mock_func.reset_mock() @unittest.skipIf(ssl is None, "requires ssl") @mock.patch('http.server.test') From 0522a17ec1afe7f4c638cee54d67299243f661ac Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 17 May 2025 18:50:32 +0800 Subject: [PATCH 59/61] add docstring for wait_for_server --- Lib/test/test_httpservers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 377e5f72206ab5..73ba72a2e5c069 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1738,7 +1738,9 @@ def parse_cli_output(self, output): return None, None, None return matches.group(1), matches.group(2), int(matches.group(3)) - def wait_for_server(self, proc, protocol, port, bind, timeout=50) -> bool: + def wait_for_server(self, proc, protocol, port, bind, timeout=50): + """Parses the output of the server process by lines and returns True if + the server is listening on the given port and bind address.""" while timeout > 0: line = proc.stdout.readline() if not line: From 1b5b3f80c1a5b46c706c077a28a3091a78c595a3 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 17 May 2025 19:19:32 +0800 Subject: [PATCH 60/61] capture the output outside invoke_httpd --- Lib/test/test_httpservers.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 62a914a4e6de42..7edb685e2c78c5 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1315,9 +1315,9 @@ def setUp(self): f.write(self.tls_password.encode()) self.addCleanup(os_helper.unlink, self.tls_password_file) - def invoke_httpd(self, *args): - stdout = StringIO() - stderr = StringIO() + def invoke_httpd(self, *args, stdout=None, stderr=None): + stdout = StringIO() if stdout is None else stdout + stderr = StringIO() if stderr is None else stderr with contextlib.redirect_stdout(stdout), \ contextlib.redirect_stderr(stderr): server._main(args) @@ -1441,14 +1441,20 @@ def test_no_arguments(self, mock_func): @mock.patch('http.server.test') def test_help_flag(self, _): options = ['-h', '--help'] + stdout, stderr = StringIO(), StringIO() for option in options: with self.assertRaises(SystemExit): - _ = self.invoke_httpd(option) + self.invoke_httpd(option, stdout=stdout, stderr=stderr) + self.assertIn('usage', stdout.getvalue()) + self.assertEqual('', stderr.getvalue()) @mock.patch('http.server.test') def test_unknown_flag(self, _): + stdout, stderr = StringIO(), StringIO() with self.assertRaises(SystemExit): - _ = self.invoke_httpd('--unknown-flag') + self.invoke_httpd('--unknown-flag', stdout=stdout, stderr=stderr) + self.assertEqual('', stdout.getvalue()) + self.assertIn('error', stderr.getvalue()) class CommandLineRunTimeTestCase(unittest.TestCase): random_data = os.urandom(32) From 5637928b61a11414329b9f8f5980977bfcf75863 Mon Sep 17 00:00:00 2001 From: ggqlq Date: Sat, 17 May 2025 19:35:35 +0800 Subject: [PATCH 61/61] update terminate processes in test_http_client and test_https_client --- Lib/test/test_httpservers.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 7edb685e2c78c5..7437b1a4e2c205 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -1513,11 +1513,11 @@ def test_http_client(self): bind = '127.0.0.1' proc = spawn_python('-u', '-m', 'http.server', str(port), '-b', bind, bufsize=1, text=True) + self.addCleanup(kill_python, proc) + self.addCleanup(proc.terminate) self.assertTrue(self.wait_for_server(proc, 'http', port, bind)) res = self.fetch_file(f'http://{bind}:{port}/{self.random_file_name}') self.assertEqual(res, self.random_data) - proc.terminate() - kill_python(proc) def test_https_client(self): port = find_unused_port() @@ -1527,12 +1527,11 @@ def test_https_client(self): '--tls-key', self.tls_key, '--tls-password-file', self.tls_password_file, bufsize=1, text=True) + self.addCleanup(kill_python, proc) + self.addCleanup(proc.terminate) self.assertTrue(self.wait_for_server(proc, 'https', port, bind)) res = self.fetch_file(f'https://{bind}:{port}/{self.random_file_name}') self.assertEqual(res, self.random_data) - proc.terminate() - kill_python(proc) - def setUpModule(): unittest.addModuleCleanup(os.chdir, os.getcwd())