mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
linkcheck: add a distinct 'timeout' reporting status (#11876)
This commit is contained in:
parent
85400a0430
commit
9e198c70fd
@ -57,6 +57,8 @@ Bugs fixed
|
|||||||
Set this option to ``False`` to report HTTP 401 (unauthorized) server
|
Set this option to ``False`` to report HTTP 401 (unauthorized) server
|
||||||
responses as broken.
|
responses as broken.
|
||||||
Patch by James Addison.
|
Patch by James Addison.
|
||||||
|
* #11868: linkcheck: added a distinct ``timeout`` reporting status code.
|
||||||
|
Patch by James Addison.
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
-------
|
-------
|
||||||
|
@ -17,6 +17,7 @@ from urllib.parse import unquote, urlparse, urlsplit, urlunparse
|
|||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from requests.exceptions import ConnectionError, HTTPError, SSLError, TooManyRedirects
|
from requests.exceptions import ConnectionError, HTTPError, SSLError, TooManyRedirects
|
||||||
|
from requests.exceptions import Timeout as RequestTimeout
|
||||||
|
|
||||||
from sphinx.builders.dummy import DummyBuilder
|
from sphinx.builders.dummy import DummyBuilder
|
||||||
from sphinx.deprecation import RemovedInSphinx80Warning
|
from sphinx.deprecation import RemovedInSphinx80Warning
|
||||||
@ -64,6 +65,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
|
|
||||||
def init(self) -> None:
|
def init(self) -> None:
|
||||||
self.broken_hyperlinks = 0
|
self.broken_hyperlinks = 0
|
||||||
|
self.timed_out_hyperlinks = 0
|
||||||
self.hyperlinks: dict[str, Hyperlink] = {}
|
self.hyperlinks: dict[str, Hyperlink] = {}
|
||||||
# set a timeout for non-responding servers
|
# set a timeout for non-responding servers
|
||||||
socket.setdefaulttimeout(5.0)
|
socket.setdefaulttimeout(5.0)
|
||||||
@ -88,7 +90,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
for result in checker.check(self.hyperlinks):
|
for result in checker.check(self.hyperlinks):
|
||||||
self.process_result(result)
|
self.process_result(result)
|
||||||
|
|
||||||
if self.broken_hyperlinks:
|
if self.broken_hyperlinks or self.timed_out_hyperlinks:
|
||||||
self.app.statuscode = 1
|
self.app.statuscode = 1
|
||||||
|
|
||||||
def process_result(self, result: CheckResult) -> None:
|
def process_result(self, result: CheckResult) -> None:
|
||||||
@ -115,6 +117,15 @@ class CheckExternalLinksBuilder(DummyBuilder):
|
|||||||
self.write_entry('local', result.docname, filename, result.lineno, result.uri)
|
self.write_entry('local', result.docname, filename, result.lineno, result.uri)
|
||||||
elif result.status == 'working':
|
elif result.status == 'working':
|
||||||
logger.info(darkgreen('ok ') + result.uri + result.message)
|
logger.info(darkgreen('ok ') + result.uri + result.message)
|
||||||
|
elif result.status == 'timeout':
|
||||||
|
if self.app.quiet or self.app.warningiserror:
|
||||||
|
logger.warning('timeout ' + result.uri + result.message,
|
||||||
|
location=(result.docname, result.lineno))
|
||||||
|
else:
|
||||||
|
logger.info(red('timeout ') + result.uri + red(' - ' + result.message))
|
||||||
|
self.write_entry('timeout', result.docname, filename, result.lineno,
|
||||||
|
result.uri + ': ' + result.message)
|
||||||
|
self.timed_out_hyperlinks += 1
|
||||||
elif result.status == 'broken':
|
elif result.status == 'broken':
|
||||||
if self.app.quiet or self.app.warningiserror:
|
if self.app.quiet or self.app.warningiserror:
|
||||||
logger.warning(__('broken link: %s (%s)'), result.uri, result.message,
|
logger.warning(__('broken link: %s (%s)'), result.uri, result.message,
|
||||||
@ -436,6 +447,9 @@ class HyperlinkAvailabilityCheckWorker(Thread):
|
|||||||
del response
|
del response
|
||||||
break
|
break
|
||||||
|
|
||||||
|
except RequestTimeout as err:
|
||||||
|
return 'timeout', str(err), 0
|
||||||
|
|
||||||
except SSLError as err:
|
except SSLError as err:
|
||||||
# SSL failure; report that the link is broken.
|
# SSL failure; report that the link is broken.
|
||||||
return 'broken', str(err), 0
|
return 'broken', str(err), 0
|
||||||
|
@ -854,6 +854,27 @@ def test_too_many_requests_retry_after_without_header(app, capsys):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
||||||
|
def test_requests_timeout(app):
|
||||||
|
class DelayedResponseHandler(http.server.BaseHTTPRequestHandler):
|
||||||
|
protocol_version = "HTTP/1.1"
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
time.sleep(0.2) # wait before sending any response data
|
||||||
|
self.send_response(200, "OK")
|
||||||
|
self.send_header("Content-Length", "0")
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
app.config.linkcheck_timeout = 0.01
|
||||||
|
with http_server(DelayedResponseHandler):
|
||||||
|
app.build()
|
||||||
|
|
||||||
|
with open(app.outdir / "output.json", encoding="utf-8") as fp:
|
||||||
|
content = json.load(fp)
|
||||||
|
|
||||||
|
assert content["status"] == "timeout"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
||||||
def test_too_many_requests_user_timeout(app):
|
def test_too_many_requests_user_timeout(app):
|
||||||
app.config.linkcheck_rate_limit_timeout = 0.0
|
app.config.linkcheck_rate_limit_timeout = 0.0
|
||||||
|
Loading…
Reference in New Issue
Block a user