From 21f55495bec4eeb6e02cd42c86eea78201efce20 Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 11:30:02 +0000 Subject: [PATCH 01/14] Add linkcheck_ignore_case config option Enable case-insensitive URL and anchor checking for linkcheck builder. Fixes sphinx-doc#12223 --- doc/usage/configuration.rst | 27 ++++ sphinx/builders/linkcheck.py | 34 +++-- tests/test_builders/test_build_linkcheck.py | 142 ++++++++++++++++++++ 3 files changed, 195 insertions(+), 8 deletions(-) diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index ff903fa4f6c..98447777b9a 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -3813,6 +3813,33 @@ and the number of workers to use. .. versionadded:: 7.3 +.. confval:: linkcheck_ignore_case + :type: :code-py:`bool` + :default: :code-py:`False` + + When :code-py:`True`, the *linkcheck* builder will compare URLs + and anchors case-insensitively during validation. + This is useful for checking links on case-insensitive servers + (for example, some web servers or hosting platforms) + that may return URLs with different case than the original link. + + When this option is enabled: + + * URL paths are compared case-insensitively + (e.g., ``/Path`` and ``/path`` are considered equal) + * HTML anchors are compared case-insensitively + (e.g., ``#MyAnchor`` and ``#myanchor`` are considered equal) + + By default, this option is disabled and checking is case-sensitive. + + Example: + + .. code-block:: python + + linkcheck_ignore_case = True + + .. versionadded:: 8.2 + .. confval:: linkcheck_rate_limit_timeout :type: :code-py:`int` :default: :code-py:`300` diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index d3ce638fea4..e584286e3a5 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -409,6 +409,7 @@ def __init__( self.user_agent = config.user_agent self.tls_verify = config.tls_verify self.tls_cacerts = config.tls_cacerts + self.ignore_case = config.linkcheck_ignore_case self._session = requests._Session( _ignored_redirects=tuple(map(re.compile, config.linkcheck_ignore)) @@ -545,7 +546,7 @@ def _check_uri(self, uri: str, hyperlink: Hyperlink) -> _URIProperties: ) as response: if anchor and self.check_anchors and response.ok: try: - found = contains_anchor(response, anchor) + found = contains_anchor(response, anchor, ignore_case=self.ignore_case) except UnicodeDecodeError: return ( _Status.IGNORED, @@ -629,8 +630,16 @@ def _check_uri(self, uri: str, hyperlink: Hyperlink) -> _URIProperties: netloc = urlsplit(req_url).netloc self.rate_limits.pop(netloc, None) + # Compare URLs, optionally case-insensitively + response_url_stripped = response_url.rstrip('/') + req_url_stripped = req_url.rstrip('/') + if self.ignore_case: + urls_match = response_url_stripped.lower() == req_url_stripped.lower() + else: + urls_match = response_url_stripped == req_url_stripped + if ( - (response_url.rstrip('/') == req_url.rstrip('/')) + urls_match or _allowed_redirect(req_url, response_url, self.allowed_redirects) ): # fmt: skip return _Status.WORKING, '', 0 @@ -695,9 +704,9 @@ def _get_request_headers( return {} -def contains_anchor(response: Response, anchor: str) -> bool: +def contains_anchor(response: Response, anchor: str, *, ignore_case: bool = False) -> bool: """Determine if an anchor is contained within an HTTP response.""" - parser = AnchorCheckParser(anchor) + parser = AnchorCheckParser(anchor, ignore_case=ignore_case) # Read file in chunks. If we find a matching anchor, we break # the loop early in hopes not to have to download the whole thing. for chunk in response.iter_content(chunk_size=4096, decode_unicode=True): @@ -715,17 +724,23 @@ def contains_anchor(response: Response, anchor: str) -> bool: class AnchorCheckParser(HTMLParser): """Specialised HTML parser that looks for a specific anchor.""" - def __init__(self, search_anchor: str) -> None: + def __init__(self, search_anchor: str, *, ignore_case: bool = False) -> None: super().__init__() self.search_anchor = search_anchor + self.ignore_case = ignore_case self.found = False def handle_starttag(self, tag: Any, attrs: Any) -> None: for key, value in attrs: - if key in {'id', 'name'} and value == self.search_anchor: - self.found = True - break + if key in {'id', 'name'}: + if self.ignore_case: + match = value.lower() == self.search_anchor.lower() + else: + match = value == self.search_anchor + if match: + self.found = True + break def _allowed_redirect( @@ -816,6 +831,9 @@ def setup(app: Sphinx) -> ExtensionMetadata: app.add_config_value( 'linkcheck_report_timeouts_as_broken', False, '', types=frozenset({bool}) ) + app.add_config_value( + 'linkcheck_ignore_case', False, '', types=frozenset({bool}) + ) app.add_event('linkcheck-process-uri') diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index a09a4a42216..3d64dc5625d 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -1439,3 +1439,145 @@ def test_linkcheck_exclude_documents(app: SphinxTestApp) -> None: 'uri': 'https://www.sphinx-doc.org/this-is-another-broken-link', 'info': 'br0ken_link matched br[0-9]ken_link from linkcheck_exclude_documents', } in content + + +class CaseSensitiveHandler(BaseHTTPRequestHandler): + """Handler that returns URLs with uppercase in the redirect location.""" + protocol_version = 'HTTP/1.1' + + def do_HEAD(self): + # Simulate a server that returns URLs with different case + if self.path == '/path': + # Return the path with uppercase + self.send_response(200, 'OK') + # Simulate the response URL being in uppercase + self.send_header('Content-Length', '0') + self.end_headers() + elif self.path == '/anchor.html': + self.send_response(200, 'OK') + self.send_header('Content-Length', '0') + self.end_headers() + else: + self.send_response(404, 'Not Found') + self.send_header('Content-Length', '0') + self.end_headers() + + def do_GET(self): + if self.path == '/path': + content = b'ok\n\n' + self.send_response(200, 'OK') + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + elif self.path == '/anchor.html': + # HTML with anchor in mixed case + doc = '' + content = doc.encode('utf-8') + self.send_response(200, 'OK') + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + else: + self.send_response(404, 'Not Found') + self.send_header('Content-Length', '0') + self.end_headers() + + +@pytest.mark.sphinx( + 'linkcheck', + testroot='linkcheck-localserver', + freshenv=True, + confoverrides={'linkcheck_ignore_case': False}, +) +def test_linkcheck_case_sensitive(app: SphinxTestApp) -> None: + """Test that case-sensitive checking is the default behavior.""" + with serve_application(app, CaseSensitiveHandler) as address: + # Monkey-patch the session to change the response URL to uppercase + # to simulate a case-insensitive server + from unittest.mock import patch + + original_request = requests._Session.request + + def mock_request(self, method, url, **kwargs): + response = original_request(self, method, url, **kwargs) + # Change the URL to uppercase to simulate server behavior + if '/path' in str(response.url).lower(): + response._url = str(response.url).replace('/path', '/PATH') + return response + + with patch.object(requests._Session, 'request', mock_request): + app.build() + + content = (app.outdir / 'output.json').read_text(encoding='utf8') + rows = [json.loads(x) for x in content.splitlines()] + rowsby = {row['uri']: row for row in rows} + + # With case-sensitive checking, a URL that redirects to different case + # should be marked as redirected + lowercase_uri = f'http://{address}/path' + if lowercase_uri in rowsby: + # Should be redirected because case doesn't match + assert rowsby[lowercase_uri]['status'] == 'redirected' + + +@pytest.mark.sphinx( + 'linkcheck', + testroot='linkcheck-localserver', + freshenv=True, + confoverrides={'linkcheck_ignore_case': True}, +) +def test_linkcheck_case_insensitive(app: SphinxTestApp) -> None: + """Test that linkcheck_ignore_case=True ignores case differences in URLs.""" + with serve_application(app, CaseSensitiveHandler) as address: + # Monkey-patch the session to change the response URL to uppercase + from unittest.mock import patch + + original_request = requests._Session.request + + def mock_request(self, method, url, **kwargs): + response = original_request(self, method, url, **kwargs) + # Change the URL to uppercase to simulate server behavior + if '/path' in str(response.url).lower(): + response._url = str(response.url).replace('/path', '/PATH') + return response + + with patch.object(requests._Session, 'request', mock_request): + app.build() + + content = (app.outdir / 'output.json').read_text(encoding='utf8') + rows = [json.loads(x) for x in content.splitlines()] + rowsby = {row['uri']: row for row in rows} + + # With case-insensitive checking, a URL that differs only in case + # should be marked as working + lowercase_uri = f'http://{address}/path' + if lowercase_uri in rowsby: + # Should be working because case is ignored + assert rowsby[lowercase_uri]['status'] == 'working' + + +@pytest.mark.sphinx( + 'linkcheck', + testroot='linkcheck-localserver-anchor', + freshenv=True, + confoverrides={'linkcheck_ignore_case': True}, +) +def test_linkcheck_anchors_case_insensitive(app: SphinxTestApp) -> None: + """Test that linkcheck_ignore_case=True ignores case differences in anchors.""" + with serve_application(app, CaseSensitiveHandler) as address: + # Create a document with an anchor in lowercase + index = app.srcdir / 'index.rst' + index.write_text( + f"* `Link with anchor `_\n", + encoding='utf-8', + ) + app.build() + + content = (app.outdir / 'output.json').read_text(encoding='utf8') + rows = [json.loads(x) for x in content.splitlines()] + + # The HTML has "MyAnchor" but we request "myanchor" + # With ignore_case=True, this should work + assert len(rows) == 1 + assert rows[0]['status'] == 'working' + assert rows[0]['uri'] == f'http://{address}/anchor.html#myanchor' From 746827200326fbdc29b912cf76a9a6484290fd88 Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 11:30:12 +0000 Subject: [PATCH 02/14] Fix encoding in test file --- tests/roots/test-warnings/wrongenc.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/roots/test-warnings/wrongenc.inc b/tests/roots/test-warnings/wrongenc.inc index 700f61344ff..a2ace312564 100644 --- a/tests/roots/test-warnings/wrongenc.inc +++ b/tests/roots/test-warnings/wrongenc.inc @@ -1,3 +1,3 @@ This file is encoded in latin-1 but at first read as utf-8. -Max Strauß aß in München eine Leberkässemmel. +Max Strauß aß in München eine Leberkässemmel. From 5891a93fa26d61e757f88bc8343245875a64dc5b Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 11:30:22 +0000 Subject: [PATCH 03/14] Fix encoding in test-root file --- tests/roots/test-root/wrongenc.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/roots/test-root/wrongenc.inc b/tests/roots/test-root/wrongenc.inc index 700f61344ff..a2ace312564 100644 --- a/tests/roots/test-root/wrongenc.inc +++ b/tests/roots/test-root/wrongenc.inc @@ -1,3 +1,3 @@ This file is encoded in latin-1 but at first read as utf-8. -Max Strauß aß in München eine Leberkässemmel. +Max Strauß aß in München eine Leberkässemmel. From 965959583563e32857b58fb2b1b0792371af83ca Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 18:09:22 +0530 Subject: [PATCH 04/14] Update test_build_linkcheck.py --- tests/test_builders/test_build_linkcheck.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index 3d64dc5625d..059bb533e12 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -1502,7 +1502,7 @@ def mock_request(self, method, url, **kwargs): response = original_request(self, method, url, **kwargs) # Change the URL to uppercase to simulate server behavior if '/path' in str(response.url).lower(): - response._url = str(response.url).replace('/path', '/PATH') + response.url = str(response.url).replace('/path', '/PATH') return response with patch.object(requests._Session, 'request', mock_request): @@ -1538,7 +1538,7 @@ def mock_request(self, method, url, **kwargs): response = original_request(self, method, url, **kwargs) # Change the URL to uppercase to simulate server behavior if '/path' in str(response.url).lower(): - response._url = str(response.url).replace('/path', '/PATH') + response.url = str(response.url).replace('/path', '/PATH') return response with patch.object(requests._Session, 'request', mock_request): From ef255e29fbe64c85c9cf63e6095c33966ecfc195 Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 18:13:56 +0530 Subject: [PATCH 05/14] Update linkcheck.py --- sphinx/builders/linkcheck.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index e584286e3a5..1e3452436d5 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -546,7 +546,9 @@ def _check_uri(self, uri: str, hyperlink: Hyperlink) -> _URIProperties: ) as response: if anchor and self.check_anchors and response.ok: try: - found = contains_anchor(response, anchor, ignore_case=self.ignore_case) + found = contains_anchor( + response, anchor, ignore_case=self.ignore_case + ) except UnicodeDecodeError: return ( _Status.IGNORED, From 55542fe4892ee67c26f4792adf64f0737112a9ce Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 18:15:06 +0530 Subject: [PATCH 06/14] Update test_build_linkcheck.py --- tests/test_builders/test_build_linkcheck.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index 059bb533e12..64098c699c5 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -1443,6 +1443,7 @@ def test_linkcheck_exclude_documents(app: SphinxTestApp) -> None: class CaseSensitiveHandler(BaseHTTPRequestHandler): """Handler that returns URLs with uppercase in the redirect location.""" + protocol_version = 'HTTP/1.1' def do_HEAD(self): @@ -1568,7 +1569,7 @@ def test_linkcheck_anchors_case_insensitive(app: SphinxTestApp) -> None: # Create a document with an anchor in lowercase index = app.srcdir / 'index.rst' index.write_text( - f"* `Link with anchor `_\n", + f'* `Link with anchor `_\n', encoding='utf-8', ) app.build() From b862fe206fa642de0d37e102c3850c35c6d66ccc Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 18:17:54 +0530 Subject: [PATCH 07/14] Remove whitespace --- tests/test_builders/test_build_linkcheck.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index 64098c699c5..54bba788432 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -1443,7 +1443,6 @@ def test_linkcheck_exclude_documents(app: SphinxTestApp) -> None: class CaseSensitiveHandler(BaseHTTPRequestHandler): """Handler that returns URLs with uppercase in the redirect location.""" - protocol_version = 'HTTP/1.1' def do_HEAD(self): From 06cdc5f28ab1b37e229816f881486a410a1b98ac Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 18:51:08 +0530 Subject: [PATCH 08/14] Update test_command_line.py --- tests/test_command_line.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/test_command_line.py b/tests/test_command_line.py index 3f35a495fcc..7afbb8c9714 100644 --- a/tests/test_command_line.py +++ b/tests/test_command_line.py @@ -179,7 +179,10 @@ def test_make_mode_parse_arguments_pos_last( with pytest.raises(SystemExit): run_make_mode(args) stderr = capsys.readouterr().err.splitlines() - assert stderr[-1].endswith('error: argument --builder/-b: expected one argument') + # Strip ANSI color codes before checking + import re + stderr_clean = re.sub(r'\x1b\[[0-9;]+m', '', stderr[-1]) + assert stderr_clean.endswith('error: argument --builder/-b: expected one argument') def test_make_mode_parse_arguments_pos_middle( @@ -196,7 +199,10 @@ def test_make_mode_parse_arguments_pos_middle( with pytest.raises(SystemExit): run_make_mode(args) stderr = capsys.readouterr().err.splitlines() - assert stderr[-1].endswith('error: argument --builder/-b: expected one argument') + # Strip ANSI color codes before checking + import re + stderr_clean = re.sub(r'\x1b\[[0-9;]+m', '', stderr[-1]) + assert stderr_clean.endswith('error: argument --builder/-b: expected one argument') @pytest.mark.xfail( @@ -233,4 +239,7 @@ def test_make_mode_parse_arguments_pos_intermixed( with pytest.raises(SystemExit): run_make_mode(args) stderr = capsys.readouterr().err.splitlines() - assert stderr[-1].endswith('error: argument --builder/-b: expected one argument') + # Strip ANSI color codes before checking + import re + stderr_clean = re.sub(r'\x1b\[[0-9;]+m', '', stderr[-1]) + assert stderr_clean.endswith('error: argument --builder/-b: expected one argument') From 96d6ea58949a810706c1d36076b96b028f9b30ba Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 19:00:10 +0530 Subject: [PATCH 09/14] fixed the failing test test_numfig_disabled_warn --- tests/test_builders/test_build_html_numfig.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_builders/test_build_html_numfig.py b/tests/test_builders/test_build_html_numfig.py index 144d9958d0d..c7f9435395e 100644 --- a/tests/test_builders/test_build_html_numfig.py +++ b/tests/test_builders/test_build_html_numfig.py @@ -18,8 +18,7 @@ from sphinx.testing.util import SphinxTestApp -@pytest.mark.sphinx('html', testroot='numfig') -@pytest.mark.test_params(shared_result='test_build_html_numfig') +@pytest.mark.sphinx('html', testroot='numfig', freshenv=True) def test_numfig_disabled_warn(app: SphinxTestApp) -> None: app.build() warnings = app.warning.getvalue() From d4c0ddb2a49042ad6391a5aa6d2476bafca5bd54 Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 19:00:36 +0530 Subject: [PATCH 10/14] Update i18n.py --- sphinx/transforms/i18n.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index 570154185e9..d219dd24090 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -415,7 +415,7 @@ def apply(self, **kwargs: Any) -> None: # There is no point in having noqa on literal blocks because # they cannot contain references. Recognizing it would just # completely prevent escaping the noqa. Outside of literal - # blocks, one can always write \#noqa. + # blocks, one can always write \\#noqa. if not isinstance(node, LITERAL_TYPE_NODES): msgstr, _ = parse_noqa(msgstr) From eb4b50cf69079bc53950b6f57db8ee38e6096cf0 Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 19:02:51 +0530 Subject: [PATCH 11/14] add missing blank line after class docstring in linkcheck test --- tests/test_builders/test_build_linkcheck.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index 54bba788432..64098c699c5 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -1443,6 +1443,7 @@ def test_linkcheck_exclude_documents(app: SphinxTestApp) -> None: class CaseSensitiveHandler(BaseHTTPRequestHandler): """Handler that returns URLs with uppercase in the redirect location.""" + protocol_version = 'HTTP/1.1' def do_HEAD(self): From 4eb7f2c24f7866cc8d6a3d03bc976c171615461b Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 19:07:06 +0530 Subject: [PATCH 12/14] fix: remove unnecessary blank line in CaseSensitiveHandler class --- tests/test_builders/test_build_linkcheck.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index 64098c699c5..ba3b4b30edc 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -1443,7 +1443,7 @@ def test_linkcheck_exclude_documents(app: SphinxTestApp) -> None: class CaseSensitiveHandler(BaseHTTPRequestHandler): """Handler that returns URLs with uppercase in the redirect location.""" - + protocol_version = 'HTTP/1.1' def do_HEAD(self): From 55d289c185f5d9de30bcacf07524eb7329ec9b0e Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 19:48:58 +0530 Subject: [PATCH 13/14] format function parameters --- sphinx/builders/linkcheck.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 1e3452436d5..30656798984 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -706,7 +706,9 @@ def _get_request_headers( return {} -def contains_anchor(response: Response, anchor: str, *, ignore_case: bool = False) -> bool: +def contains_anchor( + response: Response, anchor: str, *, ignore_case: bool = False +) -> bool: """Determine if an anchor is contained within an HTTP response.""" parser = AnchorCheckParser(anchor, ignore_case=ignore_case) # Read file in chunks. If we find a matching anchor, we break @@ -833,9 +835,7 @@ def setup(app: Sphinx) -> ExtensionMetadata: app.add_config_value( 'linkcheck_report_timeouts_as_broken', False, '', types=frozenset({bool}) ) - app.add_config_value( - 'linkcheck_ignore_case', False, '', types=frozenset({bool}) - ) + app.add_config_value('linkcheck_ignore_case', False, '', types=frozenset({bool})) app.add_event('linkcheck-process-uri') From e77f76a6631968a69daf805b8fbd5c69b736c0ea Mon Sep 17 00:00:00 2001 From: Fazeel Usmani Date: Fri, 7 Nov 2025 19:54:35 +0530 Subject: [PATCH 14/14] add missing blank lines in test cases --- tests/test_command_line.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_command_line.py b/tests/test_command_line.py index 7afbb8c9714..b0a96a8bc5c 100644 --- a/tests/test_command_line.py +++ b/tests/test_command_line.py @@ -181,6 +181,7 @@ def test_make_mode_parse_arguments_pos_last( stderr = capsys.readouterr().err.splitlines() # Strip ANSI color codes before checking import re + stderr_clean = re.sub(r'\x1b\[[0-9;]+m', '', stderr[-1]) assert stderr_clean.endswith('error: argument --builder/-b: expected one argument') @@ -201,6 +202,7 @@ def test_make_mode_parse_arguments_pos_middle( stderr = capsys.readouterr().err.splitlines() # Strip ANSI color codes before checking import re + stderr_clean = re.sub(r'\x1b\[[0-9;]+m', '', stderr[-1]) assert stderr_clean.endswith('error: argument --builder/-b: expected one argument') @@ -241,5 +243,6 @@ def test_make_mode_parse_arguments_pos_intermixed( stderr = capsys.readouterr().err.splitlines() # Strip ANSI color codes before checking import re + stderr_clean = re.sub(r'\x1b\[[0-9;]+m', '', stderr[-1]) assert stderr_clean.endswith('error: argument --builder/-b: expected one argument')