Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linkcheck: add a distinct 'timeout' reporting status #11876

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Bugs fixed
Set this option to ``False`` to report HTTP 401 (unauthorized) server
responses as broken.
Patch by James Addison.
* #11868: linkcheck: added a distinct ``timeout`` reporting status code.
Patch by James Addison.

Testing
-------
Expand Down
16 changes: 15 additions & 1 deletion sphinx/builders/linkcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from docutils import nodes
from requests.exceptions import ConnectionError, HTTPError, SSLError, TooManyRedirects
from requests.exceptions import Timeout as RequestTimeout

from sphinx.builders.dummy import DummyBuilder
from sphinx.deprecation import RemovedInSphinx80Warning
Expand Down Expand Up @@ -64,6 +65,7 @@ class CheckExternalLinksBuilder(DummyBuilder):

def init(self) -> None:
self.broken_hyperlinks = 0
self.timed_out_hyperlinks = 0
self.hyperlinks: dict[str, Hyperlink] = {}
# set a timeout for non-responding servers
socket.setdefaulttimeout(5.0)
Expand All @@ -88,7 +90,7 @@ def finish(self) -> None:
for result in checker.check(self.hyperlinks):
self.process_result(result)

if self.broken_hyperlinks:
if self.broken_hyperlinks or self.timed_out_hyperlinks:
self.app.statuscode = 1

def process_result(self, result: CheckResult) -> None:
Expand All @@ -115,6 +117,15 @@ def process_result(self, result: CheckResult) -> None:
self.write_entry('local', result.docname, filename, result.lineno, result.uri)
elif result.status == 'working':
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':
if self.app.quiet or self.app.warningiserror:
logger.warning(__('broken link: %s (%s)'), result.uri, result.message,
Expand Down Expand Up @@ -436,6 +447,9 @@ def _check_uri(self, uri: str, hyperlink: Hyperlink) -> tuple[str, str, int]:
del response
break

except RequestTimeout as err:
return 'timeout', str(err), 0

except SSLError as err:
# SSL failure; report that the link is broken.
return 'broken', str(err), 0
Expand Down
21 changes: 21 additions & 0 deletions tests/test_build_linkcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
def test_too_many_requests_user_timeout(app):
app.config.linkcheck_rate_limit_timeout = 0.0
Expand Down
Loading