diff --git a/cloud/tests/test_longfunc_inventory.py b/cloud/tests/test_longfunc_inventory.py new file mode 100644 index 0000000..45cc008 --- /dev/null +++ b/cloud/tests/test_longfunc_inventory.py @@ -0,0 +1,81 @@ +"""A13: >100-line method inventory (17 items, checkable in code). + +The split summary's section 4 listed 16 >100-line methods; the +missing 17th is ``FullAutoMixin._full_auto_execute`` +(``cloud/handler_fullauto.py:182-304``, 123 lines). This test +pins the full 17-method list by AST over ``cloud/handler_*.py`` +plus ``cloud/server.py`` -- no external summary document is the +source of truth, the code itself is. + +Counting rule, stated because a naive AST count gives 18: +the 17 are class methods. The 18th >100-line definition, +``handler_support.migrate_tasks_table`` (module-level function, +not a mixin method), is asserted separately as the ONLY +module-level >100-line definition, so the 17/18 difference is +explained, not hidden. No method is split by this batch: +internal splitting stays at 0 items. +""" + +import ast +import unittest +from pathlib import Path + +EXPECTED_METHODS = { + ("handler_admin.py", "AdminMixin._task_pr"), + ("handler_admin.py", "AdminMixin._metrics"), + ("handler_browser.py", "BrowserMixin._browser_stream"), + ("handler_core.py", "TaskCoreMixin._create_task"), + ("handler_core.py", "TaskCoreMixin._create_followup"), + ("handler_fullauto.py", "FullAutoMixin._full_auto_execute"), + ("handler_lifecycle.py", "TaskLifecycleMixin._task_thread_read"), + ("handler_lifecycle.py", "TaskLifecycleMixin._deliver_to_thread"), + ("handler_lifecycle.py", "TaskLifecycleMixin._task_wait"), + ("handler_lifecycle.py", "TaskLifecycleMixin._merge_task"), + ("handler_lifecycle.py", "TaskLifecycleMixin._loop_run"), + ("handler_lifecycle.py", "TaskLifecycleMixin._release_task"), + ("handler_lifecycle.py", "TaskLifecycleMixin._continue_task"), + ("handler_webhook.py", "WebhookMixin._handle_github_webhook"), + ("server.py", "CloudControlPlaneHandler._dispatch_task_route"), + ("server.py", "CloudControlPlaneHandler._dispatch"), + ("server.py", "CloudControlPlaneHandler._dispatch_project"), +} + + +class LongFuncInventoryTestCase(unittest.TestCase): + def test_seventeen_long_methods_including_full_auto_execute(self): + cloud_dir = Path(__file__).resolve().parents[1] + methods = set() + module_funcs = set() + for path in sorted(cloud_dir.glob("handler_*.py")) + [ + cloud_dir / "server.py"]: + tree = ast.parse(path.read_text(encoding="utf-8")) + for node in tree.body: + if isinstance(node, ast.ClassDef): + for member in node.body: + if isinstance(member, (ast.FunctionDef, + ast.AsyncFunctionDef)): + lines = (member.end_lineno + - member.lineno + 1) + if lines > 100: + methods.add( + (path.name, + "%s.%s" % (node.name, member.name))) + elif isinstance(node, (ast.FunctionDef, + ast.AsyncFunctionDef)): + lines = node.end_lineno - node.lineno + 1 + if lines > 100: + module_funcs.add((path.name, node.name)) + self.assertEqual(len(EXPECTED_METHODS), 17) + self.assertIn( + ("handler_fullauto.py", "FullAutoMixin._full_auto_execute"), + EXPECTED_METHODS) + self.assertEqual(methods, EXPECTED_METHODS) + # The single non-method >100-line definition, documented + # rather than silently dropped from the count. + self.assertEqual( + module_funcs, + {("handler_support.py", "migrate_tasks_table")}) + + +if __name__ == "__main__": + unittest.main()