Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32.3k
gh-131178: Add tests for http.server command-line interface#132540
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes from6 commits
79db2f2
a5e5220
2b589bf
e11f8fe
4e008fd
ad76ab1
5e563d3
86b856e
4d5c2b5
574d6be
c1f3358
01d5fb8
540700f
1bdb0ec
38aea9e
3277327
771263d
3679a76
6c58710
3e4a6aa
8e93c5d
439c36d
7e8aedc
8f3e7ad
85cb099
9417864
a5a7d8c
a35e0d6
e2266c0
b4f9e72
a61b5b1
fd70932
348e256
4c315b0
a57b959
b627e02
41065c2
e7b7dff
cbda832
86847e8
7eb1572
d504760
811d86d
e5df251
1843c80
8e8b755
2f742d9
b5c5ab0
05aea06
333a761
4304354
6c4c134
7b3bb1d
eed4228
7c1713e
a34fa51
8f73c22
2daf3f8
1c67654
9639219
4156e78
0522a17
2c9612b
1b5b3f8
5637928
4a6b779
6beb7f1
4c25dcd
d67ee16
d5914dc
4d0154e
2ba7001
0c37216
6938dd9
59be989
7a8d6f1
921739c
6d9981f
1d42f2c
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -106,6 +106,8 @@ | ||
import sys | ||
import time | ||
import urllib.parse | ||
import argparse | ||
import contextlib | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
from http import HTTPStatus | ||
@@ -150,7 +152,6 @@ def server_bind(self): | ||
class ThreadingHTTPServer(socketserver.ThreadingMixIn, HTTPServer): | ||
daemon_threads = True | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
class HTTPSServer(HTTPServer): | ||
def __init__(self, server_address, RequestHandlerClass, | ||
bind_and_activate=True, *, certfile, keyfile=None, | ||
@@ -1306,7 +1307,7 @@ def _get_best_family(*address): | ||
def test(HandlerClass=BaseHTTPRequestHandler, | ||
ServerClass=ThreadingHTTPServer, | ||
protocol="HTTP/1.0", port=8000, bind=None, directory=None, | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
tls_cert=None, tls_key=None, tls_password=None): | ||
"""Test the HTTP request handler class. | ||
@@ -1319,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) | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
else: | ||
server = ServerClass(addr, HandlerClass) | ||
@@ -1336,10 +1355,7 @@ def test(HandlerClass=BaseHTTPRequestHandler, | ||
print("\nKeyboard interrupt received, exiting.") | ||
sys.exit(0) | ||
def _main(args=None): | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument('--cgi', action='store_true', | ||
help='run as CGI server') | ||
@@ -1362,7 +1378,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=args) | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
if not args.tls_cert and args.tls_key: | ||
parser.error("--tls-key requires --tls-cert to be set") | ||
@@ -1383,27 +1399,18 @@ def test(HandlerClass=BaseHTTPRequestHandler, | ||
else: | ||
handler_class = SimpleHTTPRequestHandler | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
test( | ||
HandlerClass=handler_class, | ||
ServerClass=ThreadingHTTPServer, | ||
port=args.port, | ||
bind=args.bind, | ||
protocol=args.protocol, | ||
directory=args.directory, | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
tls_cert=args.tls_cert, | ||
tls_key=args.tls_key, | ||
tls_password=tls_key_password, | ||
) | ||
if __name__ == '__main__': | ||
_main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -8,6 +8,7 @@ | ||
SimpleHTTPRequestHandler, CGIHTTPRequestHandler | ||
from http import server, HTTPStatus | ||
import http.server | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
import os | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
import socket | ||
import sys | ||
@@ -27,6 +28,8 @@ | ||
import threading | ||
from unittest import mock | ||
from io import BytesIO, StringIO | ||
import textwrap | ||
import contextlib | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
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', 'CommandLineServerClass'} | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page.
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
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): | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
def setUp(self): | ||
self.default_port = 8000 | ||
self.default_bind = None | ||
self.default_protocol = 'HTTP/1.0' | ||
self.default_directory = os.getcwd() | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
self.default_handler = SimpleHTTPRequestHandler | ||
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 | ||
tls_password_file_object.close() | ||
return super().setUp() | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
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() | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
with contextlib.redirect_stdout(output): | ||
server._main(args) | ||
return self.text_normalizer(output.getvalue()) | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
@mock.patch('http.server.test') | ||
def test_port_flag(self, mock_func): | ||
ports = [8000, 65535,] | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
for port in ports: | ||
with self.subTest(port=port): | ||
self.invoke_httpd([str(port)]) | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
mock_func.assert_called_once_with(HandlerClass=self.default_handler, ServerClass=self.default_server, | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
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') | ||
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, directory=directory, | ||
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, directory=self.default_directory, | ||
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',] | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
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, directory=self.default_directory, | ||
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): | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
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, | ||
tls_cert=None, tls_key=None, tls_password=None) | ||
@mock.patch('http.server.test') | ||
def test_tls_flag(self, mock_func): | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page.
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
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, directory=self.default_directory, | ||
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, directory=self.default_directory, | ||
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) | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
def test_unknown_flag(self): | ||
with self.assertRaises(SystemExit): | ||
self.invoke_httpd(['--unknown-flag']) | ||
def setUpModule(): | ||
unittest.addModuleCleanup(os.chdir, os.getcwd()) | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add tests for the command line interface of the ``http.server`` module. | ||
ggqlq marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Uh oh!
There was an error while loading.Please reload this page.