summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2024-05-23 10:44:01 -0400
committerBen Sima <ben@bsima.me>2024-05-23 10:44:01 -0400
commit93fc94363e6aeeb7bf45cdb57af3179e66933813 (patch)
tree7eb0d68eb2f4a6f71101f12189ad903716810830
parentc57a7f1cb3823e5290812b30dd22a210d15c1d97 (diff)
Add test to Biz/Repl.py
It's just a simple test, but it effectively tests that the `CustomRepl` can be instantiated for the ns and path. I also copied the unittest recipes I came up with in the other place I used Python tests so far: Biz/Llamacpp.py. Also, I'm beginning to see how a Biz/Cli.py module might work. Probably just a simple abstract base class with move, test, help, and tidy methods, pretty similar to the Haskell version.
-rw-r--r--Biz/Llamacpp.py14
-rw-r--r--Biz/Repl.py92
2 files changed, 78 insertions, 28 deletions
diff --git a/Biz/Llamacpp.py b/Biz/Llamacpp.py
index 9a2ff86..66b57d8 100644
--- a/Biz/Llamacpp.py
+++ b/Biz/Llamacpp.py
@@ -19,14 +19,22 @@ class TestLlamaCpp(unittest.TestCase):
def test_in_path(self) -> None:
"""Test that llama.cpp is in $PATH."""
- self.assertTrue("llama-cpp" in os.environ.get("PATH", ""))
+ self.assertIn("llama-cpp", os.environ.get("PATH", ""))
+
+
+def test() -> None:
+ """Run this module's test suite."""
+ suite = unittest.TestSuite()
+ suite.addTests(
+ unittest.defaultTestLoader.loadTestsFromTestCase(TestLlamaCpp),
+ )
+ unittest.TextTestRunner().run(suite)
def main() -> None:
"""Entrypoint."""
if sys.argv[1] == "test":
- sys.argv.pop()
- unittest.main()
+ test()
else:
sys.exit(0)
diff --git a/Biz/Repl.py b/Biz/Repl.py
index 595b555..cd7bad6 100644
--- a/Biz/Repl.py
+++ b/Biz/Repl.py
@@ -23,6 +23,7 @@ import inspect
import logging
import mypy.api
import os
+import pathlib
import pydoc
import string
import subprocess
@@ -30,6 +31,11 @@ import sys
import textwrap
import types
import typing
+import unittest
+
+
+class ReplError(Exception):
+ """Type for errors at the repl."""
def use(ns: str, path: str) -> None:
@@ -41,8 +47,8 @@ def use(ns: str, path: str) -> None:
logging.info("loading %s from %s", ns, path)
spec = importlib.util.spec_from_file_location(ns, path)
if spec is None or spec.loader is None:
- msg = "spec could not be loaded for %s at %s"
- raise ValueError(msg, ns, path)
+ msg = f"spec could not be loaded for {ns} at {path}"
+ raise ReplError(msg)
module = importlib.util.module_from_spec(spec)
# delete module and its imported names if its already loaded
if ns in sys.modules:
@@ -115,18 +121,20 @@ class CustomRepl:
sys.ps1 = f"{self.ns}> "
sys.ps2 = f"{self.ns}| "
- def show_help(self) -> None:
- """Print info about how to use this repl."""
- sys.stdout.write(
- textwrap.dedent(f"""
+ def help(self) -> str:
+ """Return help text."""
+ return textwrap.dedent(f"""
repl commands:
:e open {self.ns} in {self.editor}
:r reload {self.ns}
:t obj show the type of obj
obj? expands to 'help(obj)'
:? show this help
- """),
- )
+ """)
+
+ def show_help(self) -> None:
+ """Print info about how to use this repl."""
+ sys.stdout.write(self.help())
sys.stdout.flush()
def excepthook(
@@ -146,22 +154,21 @@ class CustomRepl:
if not isinstance(value, SyntaxError):
return self.default(type_, value, traceback)
if value.text is None:
- msg = "value.text is None: %s"
- raise ValueError(msg, value)
+ msg = f"value.text is None: {value}"
+ raise ReplError(msg)
stmt = value.text.rstrip()
if stmt == ":?":
- self.repl_help()
+ self.show_help()
return None
if stmt.endswith("?"):
name = stmt.rstrip("?(" + self.whitespace)
- self.wut(name)
+ self.get_help(name)
return None
if stmt == ":e":
- edit_file(self.ns, self.path, self.editor)
+ self.edit()
return None
if stmt == ":r":
- use(self.ns, self.path)
- typecheck(self.path)
+ self.reload()
return None
if stmt.startswith(":t"):
var = stmt.split()[1]
@@ -179,7 +186,7 @@ class CustomRepl:
return eval(cmd, frame.f_globals, frame.f_locals) # noqa: S307
return None
- def wut(self, name: str) -> typing.Any | None:
+ def get_help(self, name: str) -> typing.Any | None:
"""Return the documentation for `name` to the caller."""
for record in self.stack():
frame = record[0]
@@ -199,18 +206,53 @@ class CustomRepl:
edit_file(self.ns, self.path, self.editor)
+class TestCustomRepl(unittest.TestCase):
+ """Test the CustomRepl functionality."""
+
+ def setUp(self) -> None:
+ """Create a CustomRepl for testing."""
+ ns = __name__
+ path = pathlib.Path(__name__.replace(".", "/"))
+ path = path.with_suffix(".py")
+ self.repl = CustomRepl(ns, str(path), "true")
+ self.repl.setup()
+
+ def tearDown(self) -> None:
+ """Undo `self.setUp`."""
+ sys.excepthook = self.repl.default
+ del self.repl
+
+ def test_help(self) -> None:
+ """Help message should include the ns and path."""
+ self.assertIn(self.repl.ns, self.repl.help())
+
+
+def test() -> None:
+ """Run this module's test suite."""
+ suite = unittest.TestSuite()
+ suite.addTests(
+ unittest.defaultTestLoader.loadTestsFromTestCase(TestCustomRepl),
+ )
+ unittest.TextTestRunner().run(suite)
+
+
+def move() -> None:
+ """Actual entrypoint."""
+ Log.setup()
+ ns = sys.argv[1]
+ path = sys.argv[2]
+ editor = os.environ.get("EDITOR", "$EDITOR")
+ repl = CustomRepl(ns, path, editor)
+ repl.setup()
+ repl.show_help()
+
+
def main() -> None:
- """Entrypoint."""
+ """Entrypoint, should be replaced by a `Biz.Cli.main`."""
if sys.argv[1] == "test":
- pass
+ test()
else:
- Log.setup()
- ns = sys.argv[1]
- path = sys.argv[2]
- editor = os.environ.get("EDITOR", "$EDITOR")
- repl = CustomRepl(ns, path, editor)
- repl.setup()
- repl.show_help()
+ move()
if __name__ == "__main__":