Skip to content

Commit 0597a7e

Browse files
committed
test(waiter): improve test coverage for libtmux.test.waiter module
WHAT: Increased test coverage from 84% to 88%, added tests for regex matching across lines, fallback behavior, type error handling, and fixed unreliable tests WHY: Improved reliability, covered edge cases and error handling paths, validated fallback behavior, and fixed style issues for better maintainability
1 parent b846f03 commit 0597a7e

File tree

1 file changed

+226
-1
lines changed

1 file changed

+226
-1
lines changed

tests/test/test_waiter.py

+226-1
Original file line numberDiff line numberDiff line change
@@ -1403,7 +1403,7 @@ def test_wait_for_any_content_exact_match(wait_pane: Pane) -> None:
14031403

14041404
# Capture the current content to match it exactly later
14051405
content = wait_pane.capture_pane()
1406-
content_str = "\n".join(content)
1406+
content_str = "\n".join(content if isinstance(content, list) else [content])
14071407

14081408
# Run a test that won't match exactly
14091409
non_matching_result = wait_for_any_content(
@@ -1665,3 +1665,228 @@ def mock_time_time() -> float:
16651665

16661666
# We're not asserting elapsed_time anymore since we're using a direct mock
16671667
# to test the control flow, not actual timing
1668+
1669+
1670+
def test_match_regex_across_lines_with_line_numbers(wait_pane: Pane) -> None:
1671+
"""Test the _match_regex_across_lines with line numbers.
1672+
1673+
This test specifically targets the line 1169 where matches are identified
1674+
across multiple lines, including the fallback case when no specific line
1675+
was matched.
1676+
"""
1677+
# Create content with newlines that we know exactly
1678+
content_list = [
1679+
"line1",
1680+
"line2",
1681+
"line3",
1682+
"line4",
1683+
"multi",
1684+
"line",
1685+
"content",
1686+
]
1687+
1688+
# Create a pattern that will match across lines but not on a single line
1689+
pattern = re.compile(r"line2.*line3", re.DOTALL)
1690+
1691+
# Call _match_regex_across_lines directly with our controlled content
1692+
matched, matched_text, match_line = _match_regex_across_lines(content_list, pattern)
1693+
1694+
assert matched is True
1695+
assert matched_text is not None
1696+
assert "line2" in matched_text
1697+
assert "line3" in matched_text
1698+
1699+
# Now test with a pattern that matches in a specific line
1700+
pattern = re.compile(r"line3")
1701+
matched, matched_text, match_line = _match_regex_across_lines(content_list, pattern)
1702+
1703+
assert matched is True
1704+
assert matched_text == "line3"
1705+
assert match_line is not None
1706+
assert match_line == 2 # 0-indexed, so line "line3" is at index 2
1707+
1708+
# Test the fallback case - match in joined content but not individual lines
1709+
complex_pattern = re.compile(r"line1.*multi", re.DOTALL)
1710+
matched, matched_text, match_line = _match_regex_across_lines(
1711+
content_list, complex_pattern
1712+
)
1713+
1714+
assert matched is True
1715+
assert matched_text is not None
1716+
assert "line1" in matched_text
1717+
assert "multi" in matched_text
1718+
# In this case, match_line might be None since it's across multiple lines
1719+
1720+
# Test no match case
1721+
pattern = re.compile(r"not_in_content")
1722+
matched, matched_text, match_line = _match_regex_across_lines(content_list, pattern)
1723+
1724+
assert matched is False
1725+
assert matched_text is None
1726+
assert match_line is None
1727+
1728+
1729+
def test_contains_and_regex_match_fallbacks() -> None:
1730+
"""Test the fallback logic in _contains_match and _regex_match.
1731+
1732+
This test specifically targets lines 1108 and 1141 which handle the case
1733+
when a match is found in joined content but not in individual lines.
1734+
"""
1735+
# Create content with newlines inside that will create a match when joined
1736+
# but not in any individual line (notice the split between "first part" and "of")
1737+
content_with_newlines = [
1738+
"first part",
1739+
"of a sentence",
1740+
"another line",
1741+
]
1742+
1743+
# Test _contains_match where the match spans across lines
1744+
# Match "first part" + newline + "of a"
1745+
search_str = "first part\nof a"
1746+
matched, matched_text, match_line = _contains_match(
1747+
content_with_newlines, search_str
1748+
)
1749+
1750+
# The match should be found in the joined content, but not in any individual line
1751+
assert matched is True
1752+
assert matched_text == search_str
1753+
assert match_line is None # This is the fallback case we're testing
1754+
1755+
# Test _regex_match where the match spans across lines
1756+
pattern = re.compile(r"first part\nof")
1757+
matched, matched_text, match_line = _regex_match(content_with_newlines, pattern)
1758+
1759+
# The match should be found in the joined content, but not in any individual line
1760+
assert matched is True
1761+
assert matched_text is not None
1762+
assert "first part" in matched_text
1763+
assert match_line is None # This is the fallback case we're testing
1764+
1765+
# Test with a pattern that matches at the end of one line and beginning of another
1766+
pattern = re.compile(r"part\nof")
1767+
matched, matched_text, match_line = _regex_match(content_with_newlines, pattern)
1768+
1769+
assert matched is True
1770+
assert matched_text is not None
1771+
assert "part\nof" in matched_text
1772+
assert match_line is None # Fallback case since match spans multiple lines
1773+
1774+
1775+
def test_wait_for_pane_content_specific_type_errors(wait_pane: Pane) -> None:
1776+
"""Test specific type error handling in wait_for_pane_content.
1777+
1778+
This test targets lines 445-451, 461-465, 481-485 which handle
1779+
various type error conditions in different match types.
1780+
"""
1781+
# Import error message constants from the module
1782+
from libtmux.test.waiter import (
1783+
ERR_CONTAINS_TYPE,
1784+
ERR_EXACT_TYPE,
1785+
ERR_PREDICATE_TYPE,
1786+
ERR_REGEX_TYPE,
1787+
)
1788+
1789+
# Test EXACT match with non-string pattern
1790+
with pytest.raises(TypeError) as excinfo:
1791+
wait_for_pane_content(
1792+
wait_pane,
1793+
123, # type: ignore
1794+
ContentMatchType.EXACT,
1795+
timeout=0.1,
1796+
)
1797+
assert ERR_EXACT_TYPE in str(excinfo.value)
1798+
1799+
# Test CONTAINS match with non-string pattern
1800+
with pytest.raises(TypeError) as excinfo:
1801+
wait_for_pane_content(
1802+
wait_pane,
1803+
123, # type: ignore
1804+
ContentMatchType.CONTAINS,
1805+
timeout=0.1,
1806+
)
1807+
assert ERR_CONTAINS_TYPE in str(excinfo.value)
1808+
1809+
# Test REGEX match with invalid pattern type
1810+
with pytest.raises(TypeError) as excinfo:
1811+
wait_for_pane_content(
1812+
wait_pane,
1813+
123, # type: ignore
1814+
ContentMatchType.REGEX,
1815+
timeout=0.1,
1816+
)
1817+
assert ERR_REGEX_TYPE in str(excinfo.value)
1818+
1819+
# Test PREDICATE match with non-callable pattern
1820+
with pytest.raises(TypeError) as excinfo:
1821+
wait_for_pane_content(
1822+
wait_pane,
1823+
"not callable",
1824+
ContentMatchType.PREDICATE,
1825+
timeout=0.1,
1826+
)
1827+
assert ERR_PREDICATE_TYPE in str(excinfo.value)
1828+
1829+
1830+
def test_wait_for_pane_content_exact_match_detailed(wait_pane: Pane) -> None:
1831+
"""Test wait_for_pane_content with EXACT match type in detail.
1832+
1833+
This test specifically targets lines 447-451 where the exact
1834+
match type is handled, including the code path where a match
1835+
is found and validated.
1836+
"""
1837+
# Clear the pane first to have more predictable content
1838+
wait_pane.clear()
1839+
time.sleep(0.3) # Give time for clear to take effect
1840+
1841+
# Send a unique string that we can test with an exact match
1842+
wait_pane.send_keys("UNIQUE_TEST_STRING_123", literal=True)
1843+
time.sleep(0.3) # Give more time for content to appear
1844+
1845+
# Get the current content to work with
1846+
content = wait_pane.capture_pane()
1847+
content_str = "\n".join(content if isinstance(content, list) else [content])
1848+
1849+
# Verify our test string is in the content
1850+
assert "UNIQUE_TEST_STRING_123" in content_str
1851+
1852+
# Test with CONTAINS match type first (more reliable)
1853+
result = wait_for_pane_content(
1854+
wait_pane,
1855+
"UNIQUE_TEST_STRING_123",
1856+
ContentMatchType.CONTAINS,
1857+
timeout=1.0,
1858+
interval=0.1,
1859+
)
1860+
assert result.success
1861+
1862+
# Now test with EXACT match but with a simpler approach
1863+
# Find the exact line that contains our test string
1864+
for line in content:
1865+
if "UNIQUE_TEST_STRING_123" in line:
1866+
exact_line = line
1867+
break
1868+
else:
1869+
# If we can't find the line, use a fallback
1870+
exact_line = "UNIQUE_TEST_STRING_123"
1871+
1872+
# Test the EXACT match against just the line containing our test string
1873+
result = wait_for_pane_content(
1874+
wait_pane,
1875+
exact_line,
1876+
ContentMatchType.EXACT,
1877+
timeout=1.0,
1878+
interval=0.1,
1879+
)
1880+
1881+
assert result.success
1882+
assert result.matched_content == exact_line
1883+
1884+
# Test EXACT match failing case
1885+
with pytest.raises(WaitTimeout):
1886+
wait_for_pane_content(
1887+
wait_pane,
1888+
"content that definitely doesn't exist",
1889+
ContentMatchType.EXACT,
1890+
timeout=0.2,
1891+
interval=0.1,
1892+
)

0 commit comments

Comments
 (0)