From 87254e35892440c40026b8955ab2c6d4d7246e77 Mon Sep 17 00:00:00 2001 From: dariomesic Date: Tue, 28 Oct 2025 10:22:14 +0100 Subject: [PATCH 1/9] Tests: Refactor test_build_changes to use BeautifulSoup --- tests/test_builders/test_build_changes.py | 57 ++++++++++++++++------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index f0328e36e77..cc46c911d44 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -9,28 +9,53 @@ if TYPE_CHECKING: from sphinx.testing.util import SphinxTestApp +import re # <--- IMPORT ADDED + +from bs4 import BeautifulSoup # <--- IMPORT ADDED + @pytest.mark.sphinx('changes', testroot='changes') def test_build(app: SphinxTestApp) -> None: + """Test the 'changes' builder and resolve TODO for better HTML checking.""" app.build() - # TODO: Use better checking of html content htmltext = (app.outdir / 'changes.html').read_text(encoding='utf8') - assert 'Added in version 0.6: Some funny stuff.' in htmltext - assert 'Changed in version 0.6: Even more funny stuff.' in htmltext - assert 'Deprecated since version 0.6: Boring stuff.' in htmltext - - path_html = ( - 'Path: deprecated: Deprecated since version 0.6:' - ' So, that was a bad idea it turns out.' - ) - assert path_html in htmltext - - malloc_html = ( - 'void *Test_Malloc(size_t n): changed: Changed in version 0.6:' - ' Can now be replaced with a different allocator.' - ) - assert malloc_html in htmltext + soup = BeautifulSoup(htmltext, 'html.parser') + + def find_change_item(type_text: str, version: str, content: str) -> dict: + """Helper to find and validate change items.""" + # Use regex to find text content, ignoring surrounding whitespace/newlines + item = soup.find('li', string=re.compile(r'\s*' + re.escape(content) + r'\s*')) + assert item is not None, f"Could not find change item containing '{content}'" + + type_elem = item.find('i') + assert type_elem is not None, f"Missing type indicator for '{content}'" + assert type_text in type_elem.text, f"Expected type '{type_text}' for '{content}'" + + assert f"version {version}" in item.text, f"Version {version} not found in '{content}'" + + return {'item': item, 'type': type_elem} + + # Test simple changes + changes = [ + ('added', '0.6', 'Some funny stuff.'), + ('changed', '0.6', 'Even more funny stuff.'), + ('deprecated', '0.6', 'Boring stuff.') + ] + + for change_type, version, content in changes: + find_change_item(change_type, version, content) + + # Test Path deprecation (Search by unique text) + path_change = find_change_item('deprecated', '0.6', 'So, that was a bad idea it turns out.') + assert path_change['item'].find('b').text == 'Path' + + # Test Malloc function change (Search by unique text) + malloc_change = find_change_item('changed', '0.6', 'Can now be replaced with a different allocator.') + assert malloc_change['item'].find('b').text == 'void *Test_Malloc(size_t n)' + + # Test the other test function still works + assert len(soup.find_all('li')) >= 5 # Make sure we found all items @pytest.mark.sphinx( From bd68cd7d0799c76d6069599d39a766c498d62f14 Mon Sep 17 00:00:00 2001 From: dariomesic Date: Tue, 28 Oct 2025 10:30:16 +0100 Subject: [PATCH 2/9] Enhance test_build_changes: Update type hint for find_change_item and improve assertions --- tests/test_builders/test_build_changes.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index cc46c911d44..b567d6ae462 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -2,17 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import re +from typing import TYPE_CHECKING, Any import pytest +from bs4 import BeautifulSoup # type: ignore[import-not-found] if TYPE_CHECKING: from sphinx.testing.util import SphinxTestApp -import re # <--- IMPORT ADDED - -from bs4 import BeautifulSoup # <--- IMPORT ADDED - @pytest.mark.sphinx('changes', testroot='changes') def test_build(app: SphinxTestApp) -> None: @@ -22,7 +20,7 @@ def test_build(app: SphinxTestApp) -> None: htmltext = (app.outdir / 'changes.html').read_text(encoding='utf8') soup = BeautifulSoup(htmltext, 'html.parser') - def find_change_item(type_text: str, version: str, content: str) -> dict: + def find_change_item(type_text: str, version: str, content: str) -> dict[str, Any]: """Helper to find and validate change items.""" # Use regex to find text content, ignoring surrounding whitespace/newlines item = soup.find('li', string=re.compile(r'\s*' + re.escape(content) + r'\s*')) @@ -32,7 +30,7 @@ def find_change_item(type_text: str, version: str, content: str) -> dict: assert type_elem is not None, f"Missing type indicator for '{content}'" assert type_text in type_elem.text, f"Expected type '{type_text}' for '{content}'" - assert f"version {version}" in item.text, f"Version {version} not found in '{content}'" + assert f'version {version}' in item.text, f'Version {version} not found in {content!r}' return {'item': item, 'type': type_elem} From 801125b94355c7b79ea1c509d26f6277d4631866 Mon Sep 17 00:00:00 2001 From: dariomesic Date: Tue, 28 Oct 2025 10:38:58 +0100 Subject: [PATCH 3/9] Add BeautifulSoup dependency for HTML parsing in tests --- pyproject.toml | 1 + tests/test_builders/test_build_changes.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e3418ab98a2..15b3fe8a20a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,6 +124,7 @@ test = [ "defusedxml>=0.7.1", # for secure XML/HTML parsing "setuptools>=70.0", # for Cython compilation "typing_extensions>=4.9", # for typing_extensions.Unpack + "beautifulsoup4>=4.12", # for HTML parsing in tests ] translations = [ "babel>=2.13", diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index b567d6ae462..f9d2faff0cd 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -3,12 +3,14 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import pytest -from bs4 import BeautifulSoup # type: ignore[import-not-found] +from bs4 import BeautifulSoup if TYPE_CHECKING: + from typing import Any + from sphinx.testing.util import SphinxTestApp From 0326458f02d903a09c65fe1dfa8d06e41d9a24c6 Mon Sep 17 00:00:00 2001 From: dariomesic Date: Tue, 28 Oct 2025 10:45:12 +0100 Subject: [PATCH 4/9] Refactor test_build_changes: Improve readability of assertions and formatting in find_change_item function --- tests/test_builders/test_build_changes.py | 24 ++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index f9d2faff0cd..8ab0f6056da 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -25,14 +25,20 @@ def test_build(app: SphinxTestApp) -> None: def find_change_item(type_text: str, version: str, content: str) -> dict[str, Any]: """Helper to find and validate change items.""" # Use regex to find text content, ignoring surrounding whitespace/newlines - item = soup.find('li', string=re.compile(r'\s*' + re.escape(content) + r'\s*')) + item = soup.find( # type: ignore[call-overload] + 'li', string=re.compile(r'\s*' + re.escape(content) + r'\s*'), + ) assert item is not None, f"Could not find change item containing '{content}'" type_elem = item.find('i') assert type_elem is not None, f"Missing type indicator for '{content}'" - assert type_text in type_elem.text, f"Expected type '{type_text}' for '{content}'" + assert type_text in type_elem.text, ( + f"Expected type '{type_text}' for '{content}'" + ) - assert f'version {version}' in item.text, f'Version {version} not found in {content!r}' + assert f'version {version}' in item.text, ( + f'Version {version} not found in {content!r}' + ) return {'item': item, 'type': type_elem} @@ -40,18 +46,22 @@ def find_change_item(type_text: str, version: str, content: str) -> dict[str, An changes = [ ('added', '0.6', 'Some funny stuff.'), ('changed', '0.6', 'Even more funny stuff.'), - ('deprecated', '0.6', 'Boring stuff.') + ('deprecated', '0.6', 'Boring stuff.'), ] for change_type, version, content in changes: find_change_item(change_type, version, content) # Test Path deprecation (Search by unique text) - path_change = find_change_item('deprecated', '0.6', 'So, that was a bad idea it turns out.') + path_change = find_change_item( + 'deprecated', '0.6', 'So, that was a bad idea it turns out.', + ) assert path_change['item'].find('b').text == 'Path' # Test Malloc function change (Search by unique text) - malloc_change = find_change_item('changed', '0.6', 'Can now be replaced with a different allocator.') + malloc_change = find_change_item( + 'changed', '0.6', 'Can now be replaced with a different allocator.', + ) assert malloc_change['item'].find('b').text == 'void *Test_Malloc(size_t n)' # Test the other test function still works @@ -68,4 +78,4 @@ def test_no_changes(app: SphinxTestApp) -> None: app.build() assert 'no changes in version 0.7.' in app.status.getvalue() - assert not (app.outdir / 'changes.html').exists() + assert not (app.outdir / 'changes.html').exists() \ No newline at end of file From 181cd22666c6ef9eb80e8a454e0f84a66240a8e7 Mon Sep 17 00:00:00 2001 From: dariomesic Date: Tue, 28 Oct 2025 10:46:26 +0100 Subject: [PATCH 5/9] newline at the end ruff error --- tests/test_builders/test_build_changes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index 8ab0f6056da..3d0334c8a6b 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -78,4 +78,4 @@ def test_no_changes(app: SphinxTestApp) -> None: app.build() assert 'no changes in version 0.7.' in app.status.getvalue() - assert not (app.outdir / 'changes.html').exists() \ No newline at end of file + assert not (app.outdir / 'changes.html').exists() From a38b484e8f3323857af154afd01dc020fa7454b7 Mon Sep 17 00:00:00 2001 From: dariomesic Date: Tue, 28 Oct 2025 10:49:33 +0100 Subject: [PATCH 6/9] Refactor find_change_item function with ruff suggestions --- tests/test_builders/test_build_changes.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index 3d0334c8a6b..7891c2cde65 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -26,7 +26,8 @@ def find_change_item(type_text: str, version: str, content: str) -> dict[str, An """Helper to find and validate change items.""" # Use regex to find text content, ignoring surrounding whitespace/newlines item = soup.find( # type: ignore[call-overload] - 'li', string=re.compile(r'\s*' + re.escape(content) + r'\s*'), + 'li', + string=re.compile(r'\s*' + re.escape(content) + r'\s*'), ) assert item is not None, f"Could not find change item containing '{content}'" @@ -54,13 +55,17 @@ def find_change_item(type_text: str, version: str, content: str) -> dict[str, An # Test Path deprecation (Search by unique text) path_change = find_change_item( - 'deprecated', '0.6', 'So, that was a bad idea it turns out.', + 'deprecated', + '0.6', + 'So, that was a bad idea it turns out.', ) assert path_change['item'].find('b').text == 'Path' # Test Malloc function change (Search by unique text) malloc_change = find_change_item( - 'changed', '0.6', 'Can now be replaced with a different allocator.', + 'changed', + '0.6', + 'Can now be replaced with a different allocator.', ) assert malloc_change['item'].find('b').text == 'void *Test_Malloc(size_t n)' From d2f170e2aa38a548da8f157be72b2ad47c205e47 Mon Sep 17 00:00:00 2001 From: dariomesic Date: Tue, 28 Oct 2025 10:56:24 +0100 Subject: [PATCH 7/9] Refactor test_build_changes: Optimize change item search logic in find_change_item function --- tests/test_builders/test_build_changes.py | 33 ++++++++++++++--------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index 7891c2cde65..c84f4705dd9 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -22,26 +22,35 @@ def test_build(app: SphinxTestApp) -> None: htmltext = (app.outdir / 'changes.html').read_text(encoding='utf8') soup = BeautifulSoup(htmltext, 'html.parser') + # Get all
  • items up front + all_items = soup.find_all('li') + assert len(all_items) >= 5, "Did not find all 5 change items" + def find_change_item(type_text: str, version: str, content: str) -> dict[str, Any]: """Helper to find and validate change items.""" - # Use regex to find text content, ignoring surrounding whitespace/newlines - item = soup.find( # type: ignore[call-overload] - 'li', - string=re.compile(r'\s*' + re.escape(content) + r'\s*'), - ) - assert item is not None, f"Could not find change item containing '{content}'" - - type_elem = item.find('i') + + found_item = None + # Loop through all
  • tags + for item in all_items: + # Check if the content is in the item's *full text* + if re.search(re.escape(content), item.text): + found_item = item + break # Found it! + + # This is the assertion that was failing + assert found_item is not None, f"Could not find change item containing '{content}'" + + type_elem = found_item.find('i') assert type_elem is not None, f"Missing type indicator for '{content}'" assert type_text in type_elem.text, ( f"Expected type '{type_text}' for '{content}'" ) - assert f'version {version}' in item.text, ( + assert f'version {version}' in found_item.text, ( f'Version {version} not found in {content!r}' ) - return {'item': item, 'type': type_elem} + return {'item': found_item, 'type': type_elem} # Test simple changes changes = [ @@ -69,9 +78,6 @@ def find_change_item(type_text: str, version: str, content: str) -> dict[str, An ) assert malloc_change['item'].find('b').text == 'void *Test_Malloc(size_t n)' - # Test the other test function still works - assert len(soup.find_all('li')) >= 5 # Make sure we found all items - @pytest.mark.sphinx( 'changes', @@ -84,3 +90,4 @@ def test_no_changes(app: SphinxTestApp) -> None: assert 'no changes in version 0.7.' in app.status.getvalue() assert not (app.outdir / 'changes.html').exists() + \ No newline at end of file From 118b81524f639624d2cea3da5248a48c9e0205d7 Mon Sep 17 00:00:00 2001 From: dariomesic Date: Tue, 28 Oct 2025 11:00:36 +0100 Subject: [PATCH 8/9] Refactor test_build_changes: Update assertion formatting and improve type check in find_change_item function --- tests/test_builders/test_build_changes.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index c84f4705dd9..65a10595aa5 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -24,11 +24,10 @@ def test_build(app: SphinxTestApp) -> None: # Get all
  • items up front all_items = soup.find_all('li') - assert len(all_items) >= 5, "Did not find all 5 change items" + assert len(all_items) >= 5, 'Did not find all 5 change items' def find_change_item(type_text: str, version: str, content: str) -> dict[str, Any]: """Helper to find and validate change items.""" - found_item = None # Loop through all
  • tags for item in all_items: @@ -36,13 +35,13 @@ def find_change_item(type_text: str, version: str, content: str) -> dict[str, An if re.search(re.escape(content), item.text): found_item = item break # Found it! - + # This is the assertion that was failing assert found_item is not None, f"Could not find change item containing '{content}'" type_elem = found_item.find('i') assert type_elem is not None, f"Missing type indicator for '{content}'" - assert type_text in type_elem.text, ( + assert type_text in type_elem.text.lower(), ( f"Expected type '{type_text}' for '{content}'" ) @@ -90,4 +89,3 @@ def test_no_changes(app: SphinxTestApp) -> None: assert 'no changes in version 0.7.' in app.status.getvalue() assert not (app.outdir / 'changes.html').exists() - \ No newline at end of file From 06df8fb180a5eb5afc2153ad14b06257e02abcc1 Mon Sep 17 00:00:00 2001 From: dariomesic Date: Tue, 28 Oct 2025 11:05:44 +0100 Subject: [PATCH 9/9] final ruff format --- tests/test_builders/test_build_changes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_builders/test_build_changes.py b/tests/test_builders/test_build_changes.py index 65a10595aa5..c29cde19a1b 100644 --- a/tests/test_builders/test_build_changes.py +++ b/tests/test_builders/test_build_changes.py @@ -37,7 +37,9 @@ def find_change_item(type_text: str, version: str, content: str) -> dict[str, An break # Found it! # This is the assertion that was failing - assert found_item is not None, f"Could not find change item containing '{content}'" + assert found_item is not None, ( + f"Could not find change item containing '{content}'" + ) type_elem = found_item.find('i') assert type_elem is not None, f"Missing type indicator for '{content}'"