add debug output for unit tests

This uses the debug output facility meant for pretty HTML output
to give us debugging output for the unit tests.
This commit is contained in:
Sarah Hoffmann
2023-02-14 11:05:37 +01:00
parent 24e7ffb289
commit 8557105c40
2 changed files with 56 additions and 13 deletions

View File

@@ -7,8 +7,9 @@
""" """
Functions for specialised logging with HTML output. Functions for specialised logging with HTML output.
""" """
from typing import Any, Optional, cast from typing import Any, cast
from contextvars import ContextVar from contextvars import ContextVar
import textwrap
import io import io
import sqlalchemy as sa import sqlalchemy as sa
@@ -30,6 +31,8 @@ class BaseLogger:
in derived classes which implement logging functionality. in derived classes which implement logging functionality.
""" """
def get_buffer(self) -> str: def get_buffer(self) -> str:
""" Return the current content of the log buffer.
"""
return '' return ''
def function(self, func: str, **kwargs: Any) -> None: def function(self, func: str, **kwargs: Any) -> None:
@@ -61,16 +64,15 @@ class BaseLogger:
class HTMLLogger(BaseLogger): class HTMLLogger(BaseLogger):
""" Logger that formats messages in HTML. """ Logger that formats messages in HTML.
""" """
def __init__(self): def __init__(self) -> None:
self.buffer = io.StringIO() self.buffer = io.StringIO()
def get_buffer(self) -> str: def get_buffer(self) -> str:
return HTML_HEADER + self.buffer.getvalue() + HTML_FOOTER return HTML_HEADER + self.buffer.getvalue() + HTML_FOOTER
def function(self, func: str, **kwargs: Any) -> None: def function(self, func: str, **kwargs: Any) -> None:
""" Start a new debug chapter for the given function and its parameters.
"""
self._write(f"<h1>Debug output for {func}()</h1>\n<p>Parameters:<dl>") self._write(f"<h1>Debug output for {func}()</h1>\n<p>Parameters:<dl>")
for name, value in kwargs.items(): for name, value in kwargs.items():
self._write(f'<dt>{name}</dt><dd>{self._python_var(value)}</dd>') self._write(f'<dt>{name}</dt><dd>{self._python_var(value)}</dd>')
@@ -78,25 +80,18 @@ class HTMLLogger(BaseLogger):
def section(self, heading: str) -> None: def section(self, heading: str) -> None:
""" Start a new section with the given title.
"""
self._write(f"<h2>{heading}</h2>") self._write(f"<h2>{heading}</h2>")
def comment(self, text: str) -> None: def comment(self, text: str) -> None:
""" Add a simple comment to the debug output.
"""
self._write(f"<p>{text}</p>") self._write(f"<p>{text}</p>")
def var_dump(self, heading: str, var: Any) -> None: def var_dump(self, heading: str, var: Any) -> None:
""" Print the content of the variable to the debug output prefixed by
the given heading.
"""
self._write(f'<h5>{heading}</h5>{self._python_var(var)}') self._write(f'<h5>{heading}</h5>{self._python_var(var)}')
def sql(self, conn: AsyncConnection, statement: 'sa.Executable') -> None: def sql(self, conn: AsyncConnection, statement: 'sa.Executable') -> None:
""" Dump the SQL statement to debug output.
"""
sqlstr = str(cast('sa.ClauseElement', statement) sqlstr = str(cast('sa.ClauseElement', statement)
.compile(conn.sync_engine, compile_kwargs={"literal_binds": True})) .compile(conn.sync_engine, compile_kwargs={"literal_binds": True}))
if CODE_HIGHLIGHT: if CODE_HIGHLIGHT:
@@ -121,6 +116,51 @@ class HTMLLogger(BaseLogger):
self.buffer.write(text) self.buffer.write(text)
class TextLogger(BaseLogger):
""" Logger creating output suitable for the console.
"""
def __init__(self) -> None:
self.buffer = io.StringIO()
def get_buffer(self) -> str:
return self.buffer.getvalue()
def function(self, func: str, **kwargs: Any) -> None:
self._write(f"#### Debug output for {func}()\n\nParameters:\n")
for name, value in kwargs.items():
self._write(f' {name}: {self._python_var(value)}\n')
self._write('\n')
def section(self, heading: str) -> None:
self._write(f"\n# {heading}\n\n")
def comment(self, text: str) -> None:
self._write(f"{text}\n")
def var_dump(self, heading: str, var: Any) -> None:
self._write(f'{heading}:\n {self._python_var(var)}\n\n')
def sql(self, conn: AsyncConnection, statement: 'sa.Executable') -> None:
sqlstr = str(cast('sa.ClauseElement', statement)
.compile(conn.sync_engine, compile_kwargs={"literal_binds": True}))
sqlstr = '\n| '.join(textwrap.wrap(sqlstr, width=78))
self._write(f"| {sqlstr}\n\n")
def _python_var(self, var: Any) -> str:
return str(var)
def _write(self, text: str) -> None:
self.buffer.write(text)
logger: ContextVar[BaseLogger] = ContextVar('logger', default=BaseLogger()) logger: ContextVar[BaseLogger] = ContextVar('logger', default=BaseLogger())

View File

@@ -14,6 +14,7 @@ import datetime as dt
import nominatim.api as napi import nominatim.api as napi
from nominatim.db.sql_preprocessor import SQLPreprocessor from nominatim.db.sql_preprocessor import SQLPreprocessor
import nominatim.api.logging as loglib
class APITester: class APITester:
@@ -138,6 +139,8 @@ def apiobj(temp_db_with_extensions, temp_db_conn, monkeypatch):
SQLPreprocessor(temp_db_conn, testapi.api.config)\ SQLPreprocessor(temp_db_conn, testapi.api.config)\
.run_sql_file(temp_db_conn, 'functions/address_lookup.sql') .run_sql_file(temp_db_conn, 'functions/address_lookup.sql')
loglib.set_log_output('text')
yield testapi yield testapi
print(loglib.get_and_disable())
testapi.api.close() testapi.api.close()