test_check_binding_retval.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/env python3
  2. """
  3. Test suite for check_binding_retval.py script.
  4. This test verifies that the binding return value checker correctly identifies
  5. issues in Python binding functions.
  6. """
  7. import os
  8. import sys
  9. import tempfile
  10. import subprocess
  11. from pathlib import Path
  12. # Get the repository root
  13. REPO_ROOT = Path(__file__).parent.parent
  14. CHECKER_SCRIPT = REPO_ROOT / "scripts" / "check_binding_retval.py"
  15. def run_checker(test_dir):
  16. """Run the checker on a test directory and return the exit code and output."""
  17. result = subprocess.run(
  18. [sys.executable, str(CHECKER_SCRIPT), "--dirs", test_dir],
  19. capture_output=True,
  20. text=True
  21. )
  22. return result.returncode, result.stdout, result.stderr
  23. def test_correct_binding():
  24. """Test that correct bindings pass validation."""
  25. with tempfile.TemporaryDirectory() as tmpdir:
  26. test_file = Path(tmpdir) / "test_correct.c"
  27. test_file.write_text("""
  28. #include "pocketpy/pocketpy.h"
  29. // Correct: sets py_retval() with py_newint
  30. static bool correct_function_1(int argc, py_Ref argv) {
  31. PY_CHECK_ARGC(1);
  32. py_newint(py_retval(), 42);
  33. return true;
  34. }
  35. // Correct: sets py_retval() with py_newnone
  36. static bool correct_function_2(int argc, py_Ref argv) {
  37. PY_CHECK_ARGC(0);
  38. py_newnone(py_retval());
  39. return true;
  40. }
  41. // Correct: uses py_import which sets py_retval()
  42. static bool correct_function_3(int argc, py_Ref argv) {
  43. int res = py_import("test");
  44. if(res == 1) return true;
  45. return false;
  46. }
  47. void register_correct() {
  48. py_GlobalRef mod = py_newmodule("test");
  49. py_bindfunc(mod, "f1", correct_function_1);
  50. py_bindfunc(mod, "f2", correct_function_2);
  51. py_bindfunc(mod, "f3", correct_function_3);
  52. }
  53. """)
  54. exit_code, stdout, stderr = run_checker(tmpdir)
  55. assert exit_code == 0, f"Expected exit code 0, got {exit_code}\n{stdout}\n{stderr}"
  56. assert "No issues found" in stdout, f"Expected success message\n{stdout}"
  57. print("✓ test_correct_binding passed")
  58. def test_incorrect_binding():
  59. """Test that incorrect bindings are detected."""
  60. with tempfile.TemporaryDirectory() as tmpdir:
  61. test_file = Path(tmpdir) / "test_incorrect.c"
  62. test_file.write_text("""
  63. #include "pocketpy/pocketpy.h"
  64. // Incorrect: returns true without setting py_retval()
  65. static bool incorrect_function(int argc, py_Ref argv) {
  66. PY_CHECK_ARGC(1);
  67. // Missing py_retval() assignment
  68. return true;
  69. }
  70. void register_incorrect() {
  71. py_GlobalRef mod = py_newmodule("test");
  72. py_bindfunc(mod, "bad", incorrect_function);
  73. }
  74. """)
  75. exit_code, stdout, stderr = run_checker(tmpdir)
  76. assert exit_code == 1, f"Expected exit code 1, got {exit_code}\n{stdout}\n{stderr}"
  77. assert "incorrect_function" in stdout, f"Expected to find function name\n{stdout}"
  78. assert "potential issues" in stdout, f"Expected issues message\n{stdout}"
  79. print("✓ test_incorrect_binding passed")
  80. def test_comments_ignored():
  81. """Test that comments mentioning py_retval() don't cause false negatives."""
  82. with tempfile.TemporaryDirectory() as tmpdir:
  83. test_file = Path(tmpdir) / "test_comments.c"
  84. test_file.write_text("""
  85. #include "pocketpy/pocketpy.h"
  86. // This function has comments about py_retval() but doesn't actually set it
  87. static bool buggy_function(int argc, py_Ref argv) {
  88. PY_CHECK_ARGC(1);
  89. // TODO: Should call py_retval() here
  90. /* py_newint(py_retval(), 42); */
  91. return true; // BUG: Missing actual py_retval()
  92. }
  93. void register_buggy() {
  94. py_GlobalRef mod = py_newmodule("test");
  95. py_bindfunc(mod, "bug", buggy_function);
  96. }
  97. """)
  98. exit_code, stdout, stderr = run_checker(tmpdir)
  99. assert exit_code == 1, f"Expected exit code 1, got {exit_code}\n{stdout}\n{stderr}"
  100. assert "buggy_function" in stdout, f"Expected to find function name\n{stdout}"
  101. print("✓ test_comments_ignored passed")
  102. def test_actual_codebase():
  103. """Test that the actual codebase passes validation."""
  104. src_bindings = REPO_ROOT / "src" / "bindings"
  105. src_modules = REPO_ROOT / "src" / "modules"
  106. if not src_bindings.exists() or not src_modules.exists():
  107. print("⊘ test_actual_codebase skipped (directories not found)")
  108. return
  109. exit_code, stdout, stderr = run_checker(str(REPO_ROOT / "src"))
  110. assert exit_code == 0, f"Actual codebase should pass validation\n{stdout}\n{stderr}"
  111. print("✓ test_actual_codebase passed")
  112. def main():
  113. """Run all tests."""
  114. print("Running tests for check_binding_retval.py...")
  115. print("=" * 80)
  116. tests = [
  117. test_correct_binding,
  118. test_incorrect_binding,
  119. test_comments_ignored,
  120. test_actual_codebase,
  121. ]
  122. failed = 0
  123. for test in tests:
  124. try:
  125. test()
  126. except AssertionError as e:
  127. print(f"✗ {test.__name__} failed: {e}")
  128. failed += 1
  129. except Exception as e:
  130. print(f"✗ {test.__name__} error: {e}")
  131. failed += 1
  132. print("=" * 80)
  133. if failed == 0:
  134. print(f"All {len(tests)} tests passed!")
  135. return 0
  136. else:
  137. print(f"{failed}/{len(tests)} tests failed!")
  138. return 1
  139. if __name__ == "__main__":
  140. sys.exit(main())