mirror of
https://github.com/codeflash-ai/codeflash-agent.git
synced 2026-05-04 18:25:19 +00:00
Add unit tests for vendored _tabulate module
64 tests covering: tabulate() with pipe/simple formats, empty/None data, dict input, numeric alignment, float formatting, whitespace preservation, separating lines, firstrow headers. Internal helpers: type detection, number parsing, ANSI stripping, padding, multiline detection, pipe segment alignment. Integration test matching the _create_pr use case.
This commit is contained in:
parent
276c2f36da
commit
e2135e39b2
1 changed files with 439 additions and 0 deletions
439
packages/codeflash-python/tests/test_tabulate.py
Normal file
439
packages/codeflash-python/tests/test_tabulate.py
Normal file
|
|
@ -0,0 +1,439 @@
|
|||
"""Tests for the vendored tabulate module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from codeflash_python.ai._tabulate import (
|
||||
SEPARATING_LINE,
|
||||
_afterpoint,
|
||||
_is_multiline,
|
||||
_is_separating_line,
|
||||
_isbool,
|
||||
_isconvertible,
|
||||
_isint,
|
||||
_isnumber,
|
||||
_isnumber_with_thousands_separator,
|
||||
_padboth,
|
||||
_padleft,
|
||||
_padnone,
|
||||
_padright,
|
||||
_pipe_segment_with_colons,
|
||||
_strip_ansi,
|
||||
_type,
|
||||
_visible_width,
|
||||
tabulate,
|
||||
tabulate_formats,
|
||||
)
|
||||
|
||||
|
||||
class TestTabulate:
|
||||
"""Tests for the main tabulate function."""
|
||||
|
||||
def test_simple_table(self) -> None:
|
||||
"""Simple format produces a readable table."""
|
||||
data = [["Alice", 24], ["Bob", 30]]
|
||||
result = tabulate(data, headers=["Name", "Age"], tablefmt="simple")
|
||||
|
||||
assert "Name" in result
|
||||
assert "Age" in result
|
||||
assert "Alice" in result
|
||||
assert "Bob" in result
|
||||
|
||||
def test_pipe_format(self) -> None:
|
||||
"""Pipe format produces markdown-style table."""
|
||||
data = [["Alice", 24], ["Bob", 30]]
|
||||
result = tabulate(data, headers=["Name", "Age"], tablefmt="pipe")
|
||||
|
||||
assert result.startswith("|")
|
||||
assert "Alice" in result
|
||||
assert "|" in result
|
||||
|
||||
def test_empty_table(self) -> None:
|
||||
"""Empty data returns empty string."""
|
||||
assert "" == tabulate([], headers=[])
|
||||
|
||||
def test_none_data(self) -> None:
|
||||
"""None data is treated as empty."""
|
||||
assert "" == tabulate(None)
|
||||
|
||||
def test_headers_only(self) -> None:
|
||||
"""Table with headers but no data rows."""
|
||||
result = tabulate([], headers=["A", "B"], tablefmt="simple")
|
||||
|
||||
assert "A" in result
|
||||
assert "B" in result
|
||||
|
||||
def test_numeric_alignment(self) -> None:
|
||||
"""Numbers are right-aligned by default in simple format."""
|
||||
data = [["x", 1], ["y", 200]]
|
||||
result = tabulate(data, headers=["Name", "Val"], tablefmt="simple")
|
||||
|
||||
assert "1" in result
|
||||
assert "200" in result
|
||||
|
||||
def test_missing_values(self) -> None:
|
||||
"""None values use the missingval placeholder."""
|
||||
data = [["Alice", None]]
|
||||
result = tabulate(
|
||||
data,
|
||||
headers=["Name", "Age"],
|
||||
missingval="N/A",
|
||||
tablefmt="simple",
|
||||
)
|
||||
|
||||
assert "N/A" in result
|
||||
|
||||
def test_dict_data(self) -> None:
|
||||
"""Dict input with headers='keys' uses dict keys as headers."""
|
||||
data = {"Name": ["Alice", "Bob"], "Age": [24, 30]}
|
||||
result = tabulate(data, headers="keys", tablefmt="simple")
|
||||
|
||||
assert "Name" in result
|
||||
assert "Alice" in result
|
||||
|
||||
def test_disable_numparse(self) -> None:
|
||||
"""disable_numparse treats numbers as strings."""
|
||||
data = [["001", "002"]]
|
||||
result = tabulate(
|
||||
data,
|
||||
headers=["A", "B"],
|
||||
disable_numparse=True,
|
||||
tablefmt="simple",
|
||||
)
|
||||
|
||||
assert "001" in result
|
||||
|
||||
def test_float_format(self) -> None:
|
||||
"""Custom float format is applied."""
|
||||
data = [[3.14159]]
|
||||
result = tabulate(
|
||||
data,
|
||||
headers=["Pi"],
|
||||
floatfmt=".2f",
|
||||
tablefmt="simple",
|
||||
)
|
||||
|
||||
assert "3.14" in result
|
||||
|
||||
def test_preserve_whitespace(self) -> None:
|
||||
"""preserve_whitespace prevents stripping."""
|
||||
data = [[" padded "]]
|
||||
result = tabulate(
|
||||
data,
|
||||
headers=["Val"],
|
||||
preserve_whitespace=True,
|
||||
tablefmt="pipe",
|
||||
)
|
||||
|
||||
assert " padded " in result
|
||||
|
||||
def test_separating_line_in_rows(self) -> None:
|
||||
"""SEPARATING_LINE constant inserts a separator row."""
|
||||
data = [["a", 1], [SEPARATING_LINE], ["b", 2]]
|
||||
result = tabulate(data, headers=["X", "Y"], tablefmt="simple")
|
||||
|
||||
assert "a" in result
|
||||
assert "b" in result
|
||||
|
||||
def test_colglobalalign(self) -> None:
|
||||
"""Global column alignment is applied."""
|
||||
data = [["Alice", 24], ["Bob", 30]]
|
||||
result = tabulate(
|
||||
data,
|
||||
headers=["Name", "Age"],
|
||||
colglobalalign="center",
|
||||
tablefmt="pipe",
|
||||
)
|
||||
|
||||
assert "|" in result
|
||||
|
||||
def test_firstrow_headers(self) -> None:
|
||||
"""headers='firstrow' uses the first row as headers."""
|
||||
data = [["Name", "Age"], ["Alice", 24]]
|
||||
result = tabulate(data, headers="firstrow", tablefmt="simple")
|
||||
|
||||
assert "Name" in result
|
||||
assert "Alice" in result
|
||||
|
||||
|
||||
class TestTabulateFormats:
|
||||
"""Tests for tabulate_formats constant."""
|
||||
|
||||
def test_contains_pipe(self) -> None:
|
||||
"""pipe format is available."""
|
||||
assert "pipe" in tabulate_formats
|
||||
|
||||
def test_contains_simple(self) -> None:
|
||||
"""simple format is available."""
|
||||
assert "simple" in tabulate_formats
|
||||
|
||||
def test_is_sorted(self) -> None:
|
||||
"""Format list is sorted."""
|
||||
assert tabulate_formats == sorted(tabulate_formats)
|
||||
|
||||
|
||||
class TestTypeDetection:
|
||||
"""Tests for type detection helpers."""
|
||||
|
||||
def test_isnumber_int(self) -> None:
|
||||
"""Integers are numbers."""
|
||||
assert _isnumber(42) is True
|
||||
|
||||
def test_isnumber_float(self) -> None:
|
||||
"""Floats are numbers."""
|
||||
assert _isnumber(3.14) is True
|
||||
|
||||
def test_isnumber_string_int(self) -> None:
|
||||
"""String integers are numbers."""
|
||||
assert _isnumber("42") is True
|
||||
|
||||
def test_isnumber_string_float(self) -> None:
|
||||
"""String floats are numbers."""
|
||||
assert _isnumber("3.14") is True
|
||||
|
||||
def test_isnumber_nan(self) -> None:
|
||||
"""'NaN' string is a number."""
|
||||
assert _isnumber("NaN") is True
|
||||
|
||||
def test_isnumber_inf(self) -> None:
|
||||
"""'inf' string is a number."""
|
||||
assert _isnumber("inf") is True
|
||||
|
||||
def test_isnumber_not_a_number(self) -> None:
|
||||
"""Non-numeric string is not a number."""
|
||||
assert _isnumber("hello") is False
|
||||
|
||||
def test_isint_true(self) -> None:
|
||||
"""Actual int returns True."""
|
||||
assert _isint(42) is True
|
||||
|
||||
def test_isint_string(self) -> None:
|
||||
"""String integer returns True."""
|
||||
assert _isint("42") is True
|
||||
|
||||
def test_isint_float(self) -> None:
|
||||
"""Float is not an int."""
|
||||
assert _isint(3.14) is False
|
||||
|
||||
def test_isbool_true(self) -> None:
|
||||
"""Python bool returns True."""
|
||||
assert _isbool(True) is True
|
||||
assert _isbool(False) is True
|
||||
|
||||
def test_isbool_string(self) -> None:
|
||||
"""String booleans are detected."""
|
||||
assert _isbool("True") is True
|
||||
assert _isbool("False") is True
|
||||
|
||||
def test_isbool_other(self) -> None:
|
||||
"""Non-boolean returns False."""
|
||||
assert _isbool("yes") is False
|
||||
assert _isbool(1) is False
|
||||
|
||||
def test_isconvertible_int(self) -> None:
|
||||
"""'42' is convertible to int."""
|
||||
assert _isconvertible(int, "42") is True
|
||||
|
||||
def test_isconvertible_fails(self) -> None:
|
||||
"""'abc' is not convertible to int."""
|
||||
assert _isconvertible(int, "abc") is False
|
||||
|
||||
def test_isnumber_with_thousands_separator(self) -> None:
|
||||
"""Thousands-separated numbers are detected."""
|
||||
assert _isnumber_with_thousands_separator("1,000") is True
|
||||
assert _isnumber_with_thousands_separator("1,000.50") is True
|
||||
|
||||
def test_isnumber_with_thousands_separator_invalid(self) -> None:
|
||||
"""Non-numeric strings are rejected."""
|
||||
assert _isnumber_with_thousands_separator("abc") is False
|
||||
|
||||
def test_type_none(self) -> None:
|
||||
"""None returns NoneType."""
|
||||
assert _type(None) is type(None)
|
||||
|
||||
def test_type_empty_string(self) -> None:
|
||||
"""Empty string returns NoneType."""
|
||||
assert _type("") is type(None)
|
||||
|
||||
def test_type_bool(self) -> None:
|
||||
"""Bool values return bool type."""
|
||||
assert _type(True) is bool
|
||||
|
||||
def test_type_int(self) -> None:
|
||||
"""Int string returns int type."""
|
||||
assert _type("42") is int
|
||||
|
||||
def test_type_float(self) -> None:
|
||||
"""Float string returns float type."""
|
||||
assert _type("3.14") is float
|
||||
|
||||
def test_type_string(self) -> None:
|
||||
"""Non-numeric string returns str type."""
|
||||
assert _type("hello") is str
|
||||
|
||||
|
||||
class TestAfterpoint:
|
||||
"""Tests for _afterpoint decimal position detection."""
|
||||
|
||||
def test_integer(self) -> None:
|
||||
"""Integers have -1 afterpoint."""
|
||||
assert -1 == _afterpoint(42)
|
||||
|
||||
def test_float_string(self) -> None:
|
||||
"""Decimal digits are counted."""
|
||||
assert 2 == _afterpoint("3.14")
|
||||
|
||||
def test_no_decimal(self) -> None:
|
||||
"""String integer has -1 afterpoint."""
|
||||
assert -1 == _afterpoint("42")
|
||||
|
||||
def test_non_number(self) -> None:
|
||||
"""Non-numbers return -1."""
|
||||
assert -1 == _afterpoint("abc")
|
||||
|
||||
|
||||
class TestPadding:
|
||||
"""Tests for padding functions."""
|
||||
|
||||
def test_padleft(self) -> None:
|
||||
"""Left padding right-aligns the string."""
|
||||
assert " hi" == _padleft(5, "hi")
|
||||
|
||||
def test_padright(self) -> None:
|
||||
"""Right padding left-aligns the string."""
|
||||
assert "hi " == _padright(5, "hi")
|
||||
|
||||
def test_padboth(self) -> None:
|
||||
"""Both padding centers the string."""
|
||||
result = _padboth(6, "hi")
|
||||
assert "hi" in result
|
||||
assert 6 == len(result)
|
||||
|
||||
def test_padnone(self) -> None:
|
||||
"""No padding returns the string unchanged."""
|
||||
assert "hi" == _padnone(999, "hi")
|
||||
|
||||
|
||||
class TestAnsiHandling:
|
||||
"""Tests for ANSI escape code handling."""
|
||||
|
||||
def test_strip_ansi_plain(self) -> None:
|
||||
"""Plain string passes through unchanged."""
|
||||
assert "hello" == _strip_ansi("hello")
|
||||
|
||||
def test_strip_ansi_colored(self) -> None:
|
||||
"""ANSI color codes are removed."""
|
||||
colored = "\033[31mred\033[0m"
|
||||
assert "red" == _strip_ansi(colored)
|
||||
|
||||
def test_visible_width_plain(self) -> None:
|
||||
"""Plain string width is its length."""
|
||||
assert 5 == _visible_width("hello")
|
||||
|
||||
def test_visible_width_ansi(self) -> None:
|
||||
"""ANSI codes don't count toward visible width."""
|
||||
colored = "\033[31mred\033[0m"
|
||||
assert 3 == _visible_width(colored)
|
||||
|
||||
|
||||
class TestMultiline:
|
||||
"""Tests for multiline detection."""
|
||||
|
||||
def test_newline_detected(self) -> None:
|
||||
"""String with newline is multiline."""
|
||||
assert _is_multiline("a\nb") is True
|
||||
|
||||
def test_carriage_return_detected(self) -> None:
|
||||
"""String with carriage return is multiline."""
|
||||
assert _is_multiline("a\rb") is True
|
||||
|
||||
def test_single_line(self) -> None:
|
||||
"""String without line breaks is not multiline."""
|
||||
assert _is_multiline("hello") is False
|
||||
|
||||
|
||||
class TestSeparatingLine:
|
||||
"""Tests for separating line detection."""
|
||||
|
||||
def test_separating_line_list(self) -> None:
|
||||
"""List starting with SEPARATING_LINE is detected."""
|
||||
assert _is_separating_line([SEPARATING_LINE]) is True
|
||||
|
||||
def test_normal_row(self) -> None:
|
||||
"""Normal data row is not a separating line."""
|
||||
assert _is_separating_line(["hello", "world"]) is False
|
||||
|
||||
def test_empty_list(self) -> None:
|
||||
"""Empty list is not a separating line."""
|
||||
assert _is_separating_line([]) is False
|
||||
|
||||
|
||||
class TestPipeSegment:
|
||||
"""Tests for pipe format alignment segments."""
|
||||
|
||||
def test_right_alignment(self) -> None:
|
||||
"""Right alignment ends with colon."""
|
||||
result = _pipe_segment_with_colons("right", 5)
|
||||
assert result.endswith(":")
|
||||
assert 5 == len(result)
|
||||
|
||||
def test_left_alignment(self) -> None:
|
||||
"""Left alignment starts with colon."""
|
||||
result = _pipe_segment_with_colons("left", 5)
|
||||
assert result.startswith(":")
|
||||
assert 5 == len(result)
|
||||
|
||||
def test_center_alignment(self) -> None:
|
||||
"""Center alignment has colons on both ends."""
|
||||
result = _pipe_segment_with_colons("center", 5)
|
||||
assert result.startswith(":")
|
||||
assert result.endswith(":")
|
||||
assert 5 == len(result)
|
||||
|
||||
def test_default_alignment(self) -> None:
|
||||
"""Default alignment is all dashes."""
|
||||
result = _pipe_segment_with_colons("", 5)
|
||||
assert "-----" == result
|
||||
|
||||
|
||||
class TestIntegrationWithCreatePr:
|
||||
"""Tests verifying tabulate works as used by _create_pr."""
|
||||
|
||||
def test_pipe_format_with_performance_data(self) -> None:
|
||||
"""Pipe format table matches the PR description use case."""
|
||||
headers = [
|
||||
"Test File::Test Function",
|
||||
"Original",
|
||||
"Optimized",
|
||||
"Speedup",
|
||||
]
|
||||
rows = [
|
||||
["`test_a.py::test_func`", "100ms", "50ms", "50%"],
|
||||
["`test_b.py::test_other`", "200ms", "80ms", "60%"],
|
||||
]
|
||||
|
||||
result = tabulate(
|
||||
headers=headers,
|
||||
tabular_data=rows,
|
||||
tablefmt="pipe",
|
||||
colglobalalign=None,
|
||||
preserve_whitespace=True,
|
||||
)
|
||||
|
||||
assert "|" in result
|
||||
assert "test_a.py" in result
|
||||
assert "50%" in result
|
||||
lines = result.strip().split("\n")
|
||||
assert len(lines) >= 3
|
||||
|
||||
def test_empty_rows_returns_empty(self) -> None:
|
||||
"""Empty rows with headers still produces a header-only table."""
|
||||
headers = ["A", "B", "C", "D"]
|
||||
result = tabulate(
|
||||
headers=headers,
|
||||
tabular_data=[],
|
||||
tablefmt="pipe",
|
||||
colglobalalign=None,
|
||||
preserve_whitespace=True,
|
||||
)
|
||||
|
||||
assert "A" in result
|
||||
Loading…
Reference in a new issue