diff --git a/docs/tools.rst b/docs/tools.rst index 8388ad560..afa5ae730 100644 --- a/docs/tools.rst +++ b/docs/tools.rst @@ -20,6 +20,7 @@ arguments * (optional) ``-v[v]``, verbosity flag, up to two - increments ``fre``'s verbosity from the default ``logging.WARNING`` to ``logging.INFO`` with one ``-v``, and again to ``logging.DEBUG`` with ``-vv`` + - at default verbosity or ``-v``, unhandled exceptions print only the error message; use ``-vv`` to show the full traceback * (optional) ``-q``, quiet flag, up to one diff --git a/fre/fre.py b/fre/fre.py index 25b717504..334258b85 100644 --- a/fre/fre.py +++ b/fre/fre.py @@ -5,6 +5,7 @@ """ import logging +import sys import click @@ -38,7 +39,8 @@ ) @click.option( '-v', '--verbose', default = 0, required = False, count = True, type = int, help = "Increment logging verbosity from default (logging.WARNING) to logging.INFO. " + \ - "use -vv for logging.DEBUG. will be overridden by -q/--quiet" ) + "use -vv for logging.DEBUG and to show full tracebacks on errors. " + \ + "will be overridden by -q/--quiet" ) @click.option( '-q', '--quiet', default = False, required = False, is_flag = True, type = bool, help = "Set logging verbosity from default (logging.WARNING) to logging.ERROR, printing " + \ "less output to screen. overrides -v[v]/--verbose" ) @@ -79,3 +81,10 @@ def fre(verbose = 0, quiet = False, log_file = None): fre_logger.info('fre_file_handler added to base_fre_logger') fre_logger.debug('click entry-point function call done.') + + # install custom exception hook to suppress tracebacks unless -vv is used + if log_level > logging.DEBUG: + def _brief_excepthook(exc_type, exc_value, exc_tb): + click.echo(f"{exc_type.__name__}: {exc_value}", err=True) + click.echo("(use 'fre -vv ...' for the full traceback)", err=True) + sys.excepthook = _brief_excepthook diff --git a/fre/tests/test_fre_cli.py b/fre/tests/test_fre_cli.py index f7fa283e8..1d4131e6a 100644 --- a/fre/tests/test_fre_cli.py +++ b/fre/tests/test_fre_cli.py @@ -10,6 +10,7 @@ right and thinks the tool has a --optionDNE option) - fre --version, checking for version GTE current version (fails if version isn't defined) """ +import sys import subprocess from click.testing import CliRunner @@ -50,3 +51,45 @@ def test_cli_fre_version(): # latest_testing_tag = result.stdout.split('\n')[0] # # assert '2026.01.alpha2' == latest_testing_tag + +# ---- traceback suppression tests ---- +# These tests verify that unhandled exceptions print a clean error message +# at default verbosity, and the full traceback only at -vv. + +_CLI_SCRIPT = """\ +import sys +sys.argv = {argv!r} +from fre.fre import fre +fre(standalone_mode=True) +""" + +def _run_fre_subprocess(argv): + """Helper: run fre via subprocess so sys.excepthook is exercised.""" + return subprocess.run( + [sys.executable, "-c", _CLI_SCRIPT.format(argv=argv)], + capture_output=True, text=True + ) + +def test_traceback_suppressed_by_default(): + '''fre run function - default verbosity should suppress traceback''' + result = _run_fre_subprocess(["fre", "run", "function"]) + assert result.returncode != 0 + assert "NotImplementedError" in result.stderr + assert "Traceback" not in result.stderr + assert "fre -vv" in result.stderr + +def test_traceback_suppressed_with_single_v(): + '''fre -v run function - single -v should still suppress traceback''' + result = _run_fre_subprocess(["fre", "-v", "run", "function"]) + assert result.returncode != 0 + assert "NotImplementedError" in result.stderr + assert "Traceback" not in result.stderr + assert "fre -vv" in result.stderr + +def test_traceback_shown_with_vv(): + '''fre -vv run function - double -v should show full traceback''' + result = _run_fre_subprocess(["fre", "-vv", "run", "function"]) + assert result.returncode != 0 + assert "NotImplementedError" in result.stderr + assert "Traceback" in result.stderr + assert "fre -vv" not in result.stderr