diff options
author | Ben Sima <ben@bsima.me> | 2024-05-23 10:44:01 -0400 |
---|---|---|
committer | Ben Sima <ben@bsima.me> | 2024-05-23 10:44:01 -0400 |
commit | 93fc94363e6aeeb7bf45cdb57af3179e66933813 (patch) | |
tree | 7eb0d68eb2f4a6f71101f12189ad903716810830 /Biz/Repl.py | |
parent | c57a7f1cb3823e5290812b30dd22a210d15c1d97 (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.
Diffstat (limited to 'Biz/Repl.py')
-rw-r--r-- | Biz/Repl.py | 92 |
1 files changed, 67 insertions, 25 deletions
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__": |