Skip to content

Commit 156dad1

Browse files
committed
test(multixact): add RDS path mock tests
Add comprehensive tests for the RDS fallback path (rds_tools.pg_ls_multixactdir): Unit tests (6 new): - RDS probe structure validation for v11 and v19 - RDS exclusion logic (only when native/Aurora unavailable) - File path filtering (members vs offsets) - Sum aggregation verification - Priority ordering (Aurora > RDS > local) Mock tests (6 new): - RDS function output parsing simulation - Empty directory handling - Large multixact usage scenarios - Path filtering edge cases - COALESCE null handling - status_code logic verification Total: 36 unit tests, 2 integration tests, 4 TODO tests (PG19)
1 parent 5cb499a commit 156dad1

1 file changed

Lines changed: 279 additions & 0 deletions

File tree

tests/multixact/test_multixact_size_metric.py

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,92 @@ def test_multixact_size_v19_aurora_excludes_native(metrics_config: dict) -> None
270270
assert "has_native_fn" in sql, "Aurora probe should check native function availability"
271271

272272

273+
# =============================================================================
274+
# Unit Tests: RDS Path (rds_tools.pg_ls_multixactdir)
275+
# =============================================================================
276+
277+
278+
@pytest.mark.unit
279+
def test_multixact_size_v11_rds_probe_structure(metrics_config: dict) -> None:
280+
"""Test that v11 SQL has proper RDS probe structure."""
281+
sql = metrics_config["metrics"]["multixact_size"]["sqls"][11]
282+
283+
# Should check for RDS function
284+
assert "rds_tools" in sql, "v11 SQL should reference rds_tools schema"
285+
assert "pg_ls_multixactdir" in sql, "v11 SQL should reference pg_ls_multixactdir function"
286+
assert "has_rds_fn" in sql, "v11 SQL should have has_rds_fn flag"
287+
288+
289+
@pytest.mark.unit
290+
def test_multixact_size_v19_rds_probe_structure(metrics_config: dict) -> None:
291+
"""Test that v19 SQL has proper RDS probe structure."""
292+
sql = metrics_config["metrics"]["multixact_size"]["sqls"][19]
293+
294+
# Should have RDS fallback
295+
assert "rds_tools" in sql, "v19 SQL should reference rds_tools schema"
296+
assert "pg_ls_multixactdir" in sql, "v19 SQL should reference pg_ls_multixactdir function"
297+
assert "rds_probe_xml" in sql, "v19 SQL should have rds_probe_xml CTE"
298+
299+
300+
@pytest.mark.unit
301+
def test_multixact_size_v19_rds_excludes_native_and_aurora(metrics_config: dict) -> None:
302+
"""Test that RDS fallback only triggers when native and Aurora unavailable."""
303+
sql = metrics_config["metrics"]["multixact_size"]["sqls"][19]
304+
305+
# Find the rds_probe_xml WHERE clause
306+
# It should have: WHERE has_rds_fn AND NOT has_aurora_fn AND NOT has_native_fn
307+
rds_section_start = sql.find("rds_probe_xml as")
308+
assert rds_section_start > 0, "rds_probe_xml CTE should exist"
309+
310+
# Get the section after rds_probe_xml definition
311+
rds_section = sql[rds_section_start:rds_section_start + 2000]
312+
313+
# Should exclude both native and aurora
314+
assert "has_rds_fn" in rds_section, "RDS probe should check has_rds_fn"
315+
assert "has_aurora_fn" in rds_section, "RDS probe should check has_aurora_fn"
316+
assert "has_native_fn" in rds_section, "RDS probe should check has_native_fn"
317+
318+
319+
@pytest.mark.unit
320+
def test_multixact_size_rds_probe_parses_members_and_offsets(metrics_config: dict) -> None:
321+
"""Test that RDS probe correctly separates members and offsets files."""
322+
sql = metrics_config["metrics"]["multixact_size"]["sqls"][11]
323+
324+
# RDS probe should filter files by path pattern
325+
assert "pg_multixact/members" in sql, "RDS probe should filter members files"
326+
assert "pg_multixact/offsets" in sql, "RDS probe should filter offsets files"
327+
328+
329+
@pytest.mark.unit
330+
def test_multixact_size_rds_probe_sums_file_sizes(metrics_config: dict) -> None:
331+
"""Test that RDS probe sums file sizes correctly."""
332+
sql = metrics_config["metrics"]["multixact_size"]["sqls"][11]
333+
334+
# Should use SUM to aggregate file sizes
335+
# The RDS function returns 'size' column
336+
assert "sum(size)" in sql.lower() or "sum(used_bytes)" in sql.lower(), \
337+
"RDS probe should sum file sizes"
338+
339+
340+
@pytest.mark.unit
341+
def test_multixact_size_rds_priority_between_aurora_and_local(metrics_config: dict) -> None:
342+
"""Test that RDS probe is checked after Aurora but before local filesystem."""
343+
sql = metrics_config["metrics"]["multixact_size"]["sqls"][19]
344+
345+
# Find positions in the picked CTE
346+
picked_section_start = sql.find("picked as")
347+
assert picked_section_start > 0, "picked CTE should exist"
348+
picked_section = sql[picked_section_start:]
349+
350+
aurora_pos = picked_section.find("aurora_probe_xml")
351+
rds_pos = picked_section.find("rds_probe_xml")
352+
local_pos = picked_section.find("local_probe_xml")
353+
354+
# Order should be: aurora < rds < local (in the UNION ALL)
355+
assert aurora_pos < rds_pos, "Aurora should come before RDS in picked CTE"
356+
assert rds_pos < local_pos, "RDS should come before local in picked CTE"
357+
358+
273359
@pytest.mark.unit
274360
def test_multixact_size_gauges_defined(metrics_config: dict) -> None:
275361
"""Test that the metric has proper gauge definitions."""
@@ -490,6 +576,199 @@ def test_mock_pg19_fallback_when_function_missing() -> None:
490576
assert "expected_probe" in scenario
491577

492578

579+
# =============================================================================
580+
# Mock Tests: RDS Path (rds_tools.pg_ls_multixactdir)
581+
# =============================================================================
582+
583+
584+
@pytest.mark.unit
585+
def test_mock_rds_function_output_parsing() -> None:
586+
"""
587+
Mock test: simulate rds_tools.pg_ls_multixactdir() output parsing.
588+
589+
The RDS function returns rows with columns: name, size
590+
where name is like 'pg_multixact/members/0000' or 'pg_multixact/offsets/0000'
591+
"""
592+
# Simulate what rds_tools.pg_ls_multixactdir() returns
593+
mock_rds_output = [
594+
{"name": "pg_multixact/members/0000", "size": 262144},
595+
{"name": "pg_multixact/members/0001", "size": 262144},
596+
{"name": "pg_multixact/members/0002", "size": 131072}, # partial segment
597+
{"name": "pg_multixact/offsets/0000", "size": 262144},
598+
{"name": "pg_multixact/offsets/0001", "size": 8192}, # partial segment
599+
]
600+
601+
# Calculate expected sums (simulating the SQL logic)
602+
members_bytes = sum(
603+
row["size"] for row in mock_rds_output
604+
if "pg_multixact/members" in row["name"]
605+
)
606+
offsets_bytes = sum(
607+
row["size"] for row in mock_rds_output
608+
if "pg_multixact/offsets" in row["name"]
609+
)
610+
611+
assert members_bytes == 262144 + 262144 + 131072 # 655360
612+
assert offsets_bytes == 262144 + 8192 # 270336
613+
614+
615+
@pytest.mark.unit
616+
def test_mock_rds_empty_directory() -> None:
617+
"""
618+
Mock test: simulate empty pg_multixact directories on RDS.
619+
620+
When directories are empty, the function should return 0 bytes.
621+
"""
622+
mock_rds_output = [] # No files
623+
624+
members_bytes = sum(
625+
row["size"] for row in mock_rds_output
626+
if "pg_multixact/members" in row["name"]
627+
)
628+
offsets_bytes = sum(
629+
row["size"] for row in mock_rds_output
630+
if "pg_multixact/offsets" in row["name"]
631+
)
632+
633+
assert members_bytes == 0
634+
assert offsets_bytes == 0
635+
636+
637+
@pytest.mark.unit
638+
def test_mock_rds_large_multixact_usage() -> None:
639+
"""
640+
Mock test: simulate large multixact usage on RDS.
641+
642+
Verify calculations work correctly with many files and large sizes.
643+
"""
644+
# Simulate a busy system with many multixact segments
645+
# 100 member segments + 10 offset segments
646+
mock_rds_output = []
647+
648+
# Add 100 member files (each 256KB = 262144 bytes)
649+
for i in range(100):
650+
mock_rds_output.append({
651+
"name": f"pg_multixact/members/{i:04X}",
652+
"size": 262144
653+
})
654+
655+
# Add 10 offset files
656+
for i in range(10):
657+
mock_rds_output.append({
658+
"name": f"pg_multixact/offsets/{i:04X}",
659+
"size": 262144
660+
})
661+
662+
members_bytes = sum(
663+
row["size"] for row in mock_rds_output
664+
if "pg_multixact/members" in row["name"]
665+
)
666+
offsets_bytes = sum(
667+
row["size"] for row in mock_rds_output
668+
if "pg_multixact/offsets" in row["name"]
669+
)
670+
671+
# 100 * 256KB = 25.6MB
672+
assert members_bytes == 100 * 262144 # 26214400 bytes
673+
# 10 * 256KB = 2.56MB
674+
assert offsets_bytes == 10 * 262144 # 2621440 bytes
675+
676+
677+
@pytest.mark.unit
678+
def test_mock_rds_path_filtering() -> None:
679+
"""
680+
Mock test: verify path filtering correctly separates members from offsets.
681+
682+
The SQL uses LIKE patterns to filter files.
683+
"""
684+
mock_rds_output = [
685+
# Members files (should match 'pg_multixact/members%')
686+
{"name": "pg_multixact/members/0000", "size": 1000},
687+
{"name": "pg_multixact/members/0001", "size": 2000},
688+
# Offsets files (should match 'pg_multixact/offsets%')
689+
{"name": "pg_multixact/offsets/0000", "size": 3000},
690+
# Other files that should NOT be counted
691+
{"name": "pg_multixact/other_file", "size": 9999},
692+
{"name": "base/1234/5678", "size": 9999},
693+
]
694+
695+
# SQL uses: WHERE name LIKE 'pg_multixact/members%'
696+
members_bytes = sum(
697+
row["size"] for row in mock_rds_output
698+
if row["name"].startswith("pg_multixact/members")
699+
)
700+
701+
# SQL uses: WHERE name LIKE 'pg_multixact/offsets%'
702+
offsets_bytes = sum(
703+
row["size"] for row in mock_rds_output
704+
if row["name"].startswith("pg_multixact/offsets")
705+
)
706+
707+
assert members_bytes == 1000 + 2000 # 3000
708+
assert offsets_bytes == 3000
709+
# Other files should not be included
710+
assert members_bytes + offsets_bytes == 6000 # Not 6000 + 9999 + 9999
711+
712+
713+
@pytest.mark.unit
714+
def test_mock_rds_coalesce_null_handling() -> None:
715+
"""
716+
Mock test: verify COALESCE handles NULL when no files exist for a category.
717+
718+
If only members files exist (no offsets), offsets_bytes should be 0, not NULL.
719+
"""
720+
mock_rds_output = [
721+
{"name": "pg_multixact/members/0000", "size": 262144},
722+
# No offsets files
723+
]
724+
725+
members_bytes = sum(
726+
row["size"] for row in mock_rds_output
727+
if row["name"].startswith("pg_multixact/members")
728+
)
729+
730+
offsets_files = [
731+
row for row in mock_rds_output
732+
if row["name"].startswith("pg_multixact/offsets")
733+
]
734+
735+
# In SQL: COALESCE((SELECT sz FROM offsets), 0)
736+
offsets_bytes = sum(row["size"] for row in offsets_files) if offsets_files else 0
737+
738+
assert members_bytes == 262144
739+
assert offsets_bytes == 0 # Should be 0, not None/NULL
740+
741+
742+
@pytest.mark.unit
743+
def test_mock_rds_status_code_logic() -> None:
744+
"""
745+
Mock test: verify status_code logic for RDS probe.
746+
747+
status_code should be:
748+
- 0: Data found (files exist)
749+
- 1: No data (empty directories but probe worked)
750+
"""
751+
# Scenario 1: Files exist
752+
mock_rds_output_with_files = [
753+
{"name": "pg_multixact/members/0000", "size": 262144},
754+
]
755+
has_rows = len([
756+
r for r in mock_rds_output_with_files
757+
if "pg_multixact/" in r["name"]
758+
]) > 0
759+
status_code = 0 if has_rows else 1
760+
assert status_code == 0
761+
762+
# Scenario 2: No files
763+
mock_rds_output_empty = []
764+
has_rows = len([
765+
r for r in mock_rds_output_empty
766+
if "pg_multixact/" in r["name"]
767+
]) > 0
768+
status_code = 0 if has_rows else 1
769+
assert status_code == 1
770+
771+
493772
# =============================================================================
494773
# TODO: PG19 Image Tests (Pending PG19 Release)
495774
# =============================================================================

0 commit comments

Comments
 (0)