home · contact · privacy
Add basic testing infrastructure. master
authorChristian Heller <c.heller@plomlompom.de>
Wed, 7 Jan 2026 18:41:40 +0000 (19:41 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Wed, 7 Jan 2026 18:41:40 +0000 (19:41 +0100)
src/ledgplom/http.py
src/ledgplom/testing.py [new file with mode: 0644]
src/run.py
src/tests/test.dat [new file with mode: 0644]
src/tests/test.html [new file with mode: 0644]

index 4efc05aabc2202cff16d08208a23d1249610fe6b..85d4aa3cca655edb1d30daa81ccfe9e7ae49eaa6 100644 (file)
@@ -1,13 +1,14 @@
 'Collect directly HTTP-related elements.'
 'Collect directly HTTP-related elements.'
-# standard libs
+# built-ins
 from pathlib import Path
 from typing import Any
 from pathlib import Path
 from typing import Any
-# non-standard libs
+# plomlib
 from plomlib.web import PlomHttpHandler, PlomHttpServer, PlomQueryMap
 from plomlib.web import PlomHttpHandler, PlomHttpServer, PlomQueryMap
+# ourselves
 from ledgplom.ledger import Account, DatBlock, DEFAULT_INDENT, Ledger
 
 
 from ledgplom.ledger import Account, DatBlock, DEFAULT_INDENT, Ledger
 
 
-_PATH_TEMPLATES = Path('templates')
+PATH_TEMPLATES = Path('templates')
 _PREFIX_EDIT = 'edit_'
 _PREFIX_FILE = 'file_'
 _PREFIX_LEDGER = 'ledger_'
 _PREFIX_EDIT = 'edit_'
 _PREFIX_FILE = 'file_'
 _PREFIX_LEDGER = 'ledger_'
@@ -18,14 +19,14 @@ _TOK_STRUCTURED = 'structured'
 _PAGENAME_EDIT_RAW = f'{_PREFIX_EDIT}{_TOK_RAW}'
 _PAGENAME_EDIT_STRUCTURED = f'{_PREFIX_EDIT}{_TOK_STRUCTURED}'
 _PAGENAME_LEDGER_RAW = f'{_PREFIX_LEDGER}{_TOK_RAW}'
 _PAGENAME_EDIT_RAW = f'{_PREFIX_EDIT}{_TOK_RAW}'
 _PAGENAME_EDIT_STRUCTURED = f'{_PREFIX_EDIT}{_TOK_STRUCTURED}'
 _PAGENAME_LEDGER_RAW = f'{_PREFIX_LEDGER}{_TOK_RAW}'
-_PAGENAME_LEDGER_STRUCTURED = f'{_PREFIX_LEDGER}{_TOK_STRUCTURED}'
+PAGENAME_LEDGER_STRUCTURED = f'{_PREFIX_LEDGER}{_TOK_STRUCTURED}'
 
 
 class Server(PlomHttpServer):
     'Extends parent by loading .dat file into database for Handler.'
 
     def __init__(self, path_dat: Path, *args, **kwargs) -> None:
 
 
 class Server(PlomHttpServer):
     'Extends parent by loading .dat file into database for Handler.'
 
     def __init__(self, path_dat: Path, *args, **kwargs) -> None:
-        super().__init__(_PATH_TEMPLATES,
+        super().__init__(PATH_TEMPLATES,
                          (_SERVER_HOST, _SERVER_PORT),
                          _Handler)
         self.ledger = Ledger(path_dat)
                          (_SERVER_HOST, _SERVER_PORT),
                          _Handler)
         self.ledger = Ledger(path_dat)
@@ -106,6 +107,8 @@ class _Handler(PlomHttpHandler):
             self.redirect(
                 Path('/', _PAGENAME_EDIT_STRUCTURED, self.path_toks[2]))
             return
             self.redirect(
                 Path('/', _PAGENAME_EDIT_STRUCTURED, self.path_toks[2]))
             return
+        ### from time import time_ns
+        ### start = time_ns()
         ctx = {'unsaved_changes': self.server.ledger.tainted,
                'path': self.path}
         if self.pagename == 'balance':
         ctx = {'unsaved_changes': self.server.ledger.tainted,
                'path': self.path}
         if self.pagename == 'balance':
@@ -116,6 +119,9 @@ class _Handler(PlomHttpHandler):
             self.get_ledger(ctx, self.pagename == _PAGENAME_LEDGER_RAW)
         else:
             self.get_ledger(ctx, False)
             self.get_ledger(ctx, self.pagename == _PAGENAME_LEDGER_RAW)
         else:
             self.get_ledger(ctx, False)
+        ### end = time_ns()
+        ### duration = (end - start) / (10 ** 9)
+        ### print("DEBUG GET", self.pagename,  f'{duration:8.5f}')
 
     def get_balance(self, ctx) -> None:
         'Display tree of calculated Accounts over blocks up_incl+1.'
 
     def get_balance(self, ctx) -> None:
         'Display tree of calculated Accounts over blocks up_incl+1.'
@@ -187,4 +193,4 @@ class _Handler(PlomHttpHandler):
         ctx['has_redundant_empty_lines'] =\
             self.server.ledger.has_redundant_empty_lines
         self._send_rendered(
         ctx['has_redundant_empty_lines'] =\
             self.server.ledger.has_redundant_empty_lines
         self._send_rendered(
-            _PAGENAME_LEDGER_RAW if raw else _PAGENAME_LEDGER_STRUCTURED, ctx)
+            _PAGENAME_LEDGER_RAW if raw else PAGENAME_LEDGER_STRUCTURED, ctx)
diff --git a/src/ledgplom/testing.py b/src/ledgplom/testing.py
new file mode 100644 (file)
index 0000000..4bc4ba9
--- /dev/null
@@ -0,0 +1,47 @@
+'To run tests.'
+# built-ins
+from sys import exit as sys_exit
+from pathlib import Path
+from typing import Optional
+# requirements.txt
+from jinja2 import (Environment as JinjaEnv,
+                    FileSystemLoader as JinjaFSLoader)
+# ourselves
+from ledgplom.http import PAGENAME_LEDGER_STRUCTURED, PATH_TEMPLATES
+from ledgplom.ledger import Ledger
+
+
+_EXT_DAT = '.dat'
+_EXT_HTML = '.html'
+_PATH_TESTS = Path('tests')
+
+
+def run_tests() -> None:
+    'Run tests from tests directory.'
+    def fail(abort_msg: str, msg_prefix: str, idx: Optional[int]) -> None:
+        for jdx, line in enumerate(lines_rendered[:idx], start=1):
+            print(f'{jdx}: [{line}]')
+        print(f'{msg_prefix} FAILED – {abort_msg}')
+        sys_exit(1)
+
+    jinja = JinjaEnv(loader=JinjaFSLoader(PATH_TEMPLATES), autoescape=True)
+    tmpl = jinja.get_template(f'{PAGENAME_LEDGER_STRUCTURED}.tmpl')
+    for path in [p for p in _PATH_TESTS.iterdir()
+                 if p.parts[-1].endswith(_EXT_DAT)]:
+        with Path(str(path)[:-len(_EXT_DAT)] + _EXT_HTML
+                  ).open('r', encoding='utf8') as f:
+            lines_expected = [line.rstrip('\n') for line in f.readlines()]
+        lines_rendered = tmpl.render(blocks=Ledger(path).blocks).split('\n')
+        msg_prefix = f'test for {path}:'
+        for idx0, line in enumerate(lines_rendered):
+            idx1 = idx0 + 1
+            abort_msg = ''
+            if idx1 > len(lines_expected):
+                abort_msg = f'only {idx0} lines expected'
+            elif lines_expected[idx0] != line:
+                abort_msg = f'line differs, expected: [{lines_expected[idx0]}]'
+            if abort_msg:
+                fail(abort_msg, msg_prefix, idx1)
+        if len(lines_expected) > idx1:
+            fail(f'more lines expected line {idx1}', msg_prefix, None)
+        print(f'{msg_prefix} passed')
index 077e62a93766fcf986a2226f305e9a8a4017929a..c6546d0d6829e47e256c711bc6158165ab93aeff 100755 (executable)
@@ -1,22 +1,35 @@
 #!/usr/bin/env python3
 'Viewer and editor for ledger .dat files.'
 #!/usr/bin/env python3
 'Viewer and editor for ledger .dat files.'
-# standard libs
+# built-ins
 from os import environ
 from pathlib import Path
 from os import environ
 from pathlib import Path
-from sys import exit as sys_exit
-# non-standard libs
+from sys import argv, exit as sys_exit
+# plomlib
 from plomlib.setup import dependency_hint
 from plomlib.setup import dependency_hint
+# ourselves
 try:
     from ledgplom.http import Server
 try:
     from ledgplom.http import Server
+    from ledgplom.testing import run_tests
 except ModuleNotFoundError as e:
     dependency_hint(e)
 
 
 except ModuleNotFoundError as e:
     dependency_hint(e)
 
 
-LEDGER_DAT = environ.get('LEDGER_DAT')
+_ARG_TEST = 'test'
+_NAME_ENV_LEDGER_DAT = 'LEDGER_DAT'
+_LEDGER_DAT = environ.get(_NAME_ENV_LEDGER_DAT)
 
 
 if __name__ == '__main__':
 
 
 if __name__ == '__main__':
-    if not LEDGER_DAT:
-        print('LEDGER_DAT environment variable not set.')
+    if len(argv) == 1:
+        if not _LEDGER_DAT:
+            print(f'{_NAME_ENV_LEDGER_DAT} environment variable not set.')
+            sys_exit(1)
+        Server(Path(_LEDGER_DAT)).serve()
+    elif len(argv) == 2:
+        if argv[1] != _ARG_TEST:
+            print('fail: unknown arg argument')
+            sys_exit(1)
+        run_tests()
+    else:
+        print('fail: expected zero or one arguments')
         sys_exit(1)
         sys_exit(1)
-    Server(Path(LEDGER_DAT)).serve()
diff --git a/src/tests/test.dat b/src/tests/test.dat
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/tests/test.html b/src/tests/test.html
new file mode 100644 (file)
index 0000000..fd0e2f5
--- /dev/null
@@ -0,0 +1,119 @@
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+</script>
+<style>
+html {
+    scroll-padding-top: 2em;
+}
+body {
+    background: #ffffff;
+    font-family: sans-serif;
+    text-align: left;
+    margin: 0;
+    padding: 0;
+}
+#header {
+    background: #ffffff;
+    position: sticky;
+    top: 0;
+    padding-left: 0.5em;
+    padding-bottom: 0.25em;
+    border-bottom: 1px solid black;
+}
+table.alternating > tbody > tr:nth-child(odd) {
+    background-color: #dcdcdc;
+}
+table.alternating > tbody > tr:nth-child(even) {
+    background: #ffffff;;
+}
+td {
+    vertical-align: top;
+}
+div.critical, td.critical, tr.critical, span.critical, input[type="submit"].critical {
+    background: #ff6666;
+}
+
+
+td.block_column {
+    background: #ffffff;
+}
+td.block_column.critical {
+    background: #ff6666;
+}
+
+
+td.amount {
+    text-align: right;
+}
+td.amount, td.currency {
+    
+    font-family: monospace;
+    font-size: 1.25em;
+
+}
+
+td.amount {
+    text-align: right;
+}
+td.amount, td.currency {
+    vertical-align: bottom;
+}
+
+</style>
+</head>
+<body>
+
+<div id="header">
+<form action="" method="POST">
+<span class="disable_on_change">
+ledger <a href="/ledger_structured">structured</a>
+/ <a href="/ledger_raw">raw</a>
+· <a href="/balance">balance</a>
+
+<input type="submit"name="file_load" value="reload" />
+
+</span>
+</form>
+</div>
+
+
+<form action="/ledger_structured" method="POST">
+
+
+
+<table class="alternating">
+
+    
+<tr></tr><!-- just to keep the background-color alternation in proper order -->
+<tr id="block_0">
+<td class="block_column " rowspan=2>
+<input type="submit" name="ledger_moveup_0" value="^" disabled/><br />
+<input type="submit" name="ledger_movedown_0" value="v" disabled/><br />
+<input type="submit" name="ledger_copy_0" value="C" />
+</td>
+<td class="block_column " rowspan=2>
+[<a href="#block_0">#</a>]<br />
+[<a href="/balance?up_incl=0">b</a>]<br />
+[<a href="/edit_structured/0">e</a>]
+</td>
+</tr >
+
+    
+    
+        <tr>
+        <td colspan=4>&nbsp;</td>
+        </tr>
+    
+
+</table>
+<input type="submit" name="add_booking" value="add booking" />
+</form>
+
+</body>
+</html>