Add the tools sub-project with the run_llm batch runner

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 14:03:28 +08:00
co-authored by Claude Fable 5
parent 9a42b463a3
commit 12ace6f45a
24 changed files with 1947 additions and 17 deletions
+130
View File
@@ -0,0 +1,130 @@
# Tools for A Feminist Audit of Pop Music.
# Copyright 2026 imacat. All rights reserved.
# Authors:
# imacat@mail.imacat.idv.tw (imacat), 2026/7/31
# AI assistance: Claude Code (Anthropic)
"""Unit tests for the package-level CLI dispatcher."""
import argparse
import io
import sys
import unittest
from contextlib import redirect_stderr, redirect_stdout
from unittest import mock
from pop_fem_audit_tools import __main__, run_llm
class TestDispatcher(unittest.TestCase):
"""Test cases for the package-level CLI dispatcher."""
@staticmethod
def __run_main(argv: list[str]) -> tuple[int, str, str]:
"""Run the dispatcher with captured output.
:param argv: The command-line arguments.
:return: A tuple of the exit status, the standard output,
and the standard error.
"""
stdout: io.StringIO = io.StringIO()
stderr: io.StringIO = io.StringIO()
with redirect_stdout(stdout), redirect_stderr(stderr):
status: int = __main__.main(argv)
return status, stdout.getvalue(), stderr.getvalue()
def test_run_llm_registered(self) -> None:
"""Test that run-llm is bound to the run_llm tool main."""
self.assertIs(__main__.SUBCOMMANDS["run-llm"], run_llm.main)
def test_run_llm_dispatch(self) -> None:
"""Test that run-llm forwards the arguments and the status."""
tool: mock.Mock = mock.Mock(return_value=7)
with mock.patch.dict(__main__.SUBCOMMANDS,
{"run-llm": tool}):
status: int = self.__run_main(
["run-llm", "--input", "items.jsonl"])[0]
self.assertEqual(status, 7)
tool.assert_called_once_with(["--input", "items.jsonl"])
def test_help_exits_zero_and_lists_subcommands(self) -> None:
"""Test that --help lists the subcommands and exits 0."""
status: int
stdout: str
stderr: str
status, stdout, stderr = self.__run_main(["--help"])
self.assertEqual(status, 0)
self.assertIn("run-llm", stdout)
def test_no_arguments_exits_non_zero(self) -> None:
"""Test that no arguments yields the usage and non-zero."""
status: int
stdout: str
stderr: str
status, stdout, stderr = self.__run_main([])
self.assertNotEqual(status, 0)
self.assertIn("run-llm", stderr)
def test_usage_prog_module_run(self) -> None:
"""Test the usage program name when run with python -m."""
argv: list[str] = ["/x/pop_fem_audit_tools/__main__.py"]
stdout: str
with mock.patch.object(sys, "argv", argv):
stdout = self.__run_main(["--help"])[1]
self.assertIn(
"usage: python -m pop_fem_audit_tools ", stdout)
def test_usage_prog_console_script(self) -> None:
"""Test the usage program name when run as a script."""
argv: list[str] = ["/x/bin/pop-fem-audit-tools"]
stdout: str
with mock.patch.object(sys, "argv", argv):
stdout = self.__run_main(["--help"])[1]
self.assertIn("usage: pop-fem-audit-tools ", stdout)
def test_subcommand_prog_module_run(self) -> None:
"""Test the subcommand program name with python -m."""
argv: list[str] = ["/x/pop_fem_audit_tools/__main__.py"]
self.assertEqual(
self.__dispatched_prog(argv),
"python -m pop_fem_audit_tools run-llm")
def test_subcommand_prog_console_script(self) -> None:
"""Test the subcommand program name as a script."""
argv: list[str] = ["/x/bin/pop-fem-audit-tools"]
self.assertEqual(
self.__dispatched_prog(argv),
"pop-fem-audit-tools run-llm")
def __dispatched_prog(self, argv: list[str]) -> str:
"""Run a stub run-llm tool and return the prog it sees.
:param argv: The value to patch ``sys.argv`` with.
:return: The default argparse program name seen by the
dispatched tool.
"""
seen: list[str] = []
def tool(tool_argv: list[str] | None) -> int:
"""Record the argparse program name and succeed.
:param tool_argv: The tool command-line arguments.
:return: Always 0.
"""
parser: argparse.ArgumentParser = \
argparse.ArgumentParser()
seen.append(parser.prog)
return 0
with mock.patch.object(sys, "argv", argv), \
mock.patch.dict(__main__.SUBCOMMANDS,
{"run-llm": tool}):
self.__run_main(["run-llm"])
return seen[0]
def test_unknown_subcommand_exits_non_zero(self) -> None:
"""Test that an unknown subcommand errors to standard error."""
status: int
stdout: str
stderr: str
status, stdout, stderr = self.__run_main(["nonsense"])
self.assertNotEqual(status, 0)
self.assertIn("nonsense", stderr)