Changeset - 0a34ed6798bb
[Not reviewed]
0 2 0
Brett Smith - 4 years ago 2021-02-24 18:15:33
brettcsmith@brettcsmith.org
cliutil: ExceptHook catches RewriteRuleError.

Now that we have richer exceptions, this is the easiest way to refactor out
rewrite rule error handling from the various main functions where it
currenly lives.
2 files changed with 28 insertions and 2 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/cliutil.py
Show inline comments
...
 
@@ -19,26 +19,28 @@ import operator
 
import os
 
import pkg_resources
 
import re
 
import signal
 
import subprocess
 
import sys
 
import traceback
 
import types
 

	
 
from pathlib import Path
 

	
 
import rt.exceptions as rt_error
 
import yaml
 

	
 
from . import data
 
from . import errors
 
from . import filters
 
from . import rtutil
 

	
 
from typing import (
 
    cast,
 
    Any,
 
    BinaryIO,
 
    Callable,
 
    Container,
 
    Generic,
 
    Hashable,
 
    IO,
...
 
@@ -139,24 +141,29 @@ class ExceptHook:
 
        elif isinstance(exc_value, (
 
                rt_error.AuthorizationError,
 
                rt_error.NotAllowed,
 
        )):
 
            exitcode = os.EX_NOPERM
 
            error_type = "RT access denied"
 
        elif isinstance(exc_value, rt_error.ConnectionError):
 
            exitcode = os.EX_TEMPFAIL
 
            error_type = "RT connection error"
 
        elif isinstance(exc_value, rt_error.RtError):
 
            exitcode = os.EX_UNAVAILABLE
 
            error_type = f"RT {error_type}"
 
        elif isinstance(exc_value, errors.RewriteRuleError):
 
            exitcode = ExitCode.RewriteRulesError
 
            msg = str(exc_value)
 
            if exc_value.source is not None:
 
                msg += f"\n\n source: {yaml.safe_dump(exc_value.source)}"
 
        elif isinstance(exc_value, OSError):
 
            if exc_value.filename is None:
 
                exitcode = os.EX_OSERR
 
                error_type = "OS error"
 
                msg = exc_value.strerror
 
            else:
 
                # There are more specific exit codes for input problems vs.
 
                # output problems, but without knowing how the file was
 
                # intended to be used, we can't use them.
 
                exitcode = os.EX_IOERR
 
                error_type = "I/O error"
 
                msg = f"{exc_value.filename}: {exc_value.strerror}"
tests/test_cliutil.py
Show inline comments
 
"""Test CLI utilities"""
 
# Copyright © 2020  Brett Smith
 
# Copyright © 2020, 2021  Brett Smith
 
# License: AGPLv3-or-later WITH Beancount-Plugin-Additional-Permission-1.0
 
#
 
# Full copyright and licensing details can be found at toplevel file
 
# LICENSE.txt in the repository.
 

	
 
import argparse
 
import datetime
 
import enum
 
import errno
 
import io
 
import inspect
 
import logging
 
import os
 
import re
 
import sys
 
import traceback
 

	
 
import pytest
 

	
 
from pathlib import Path
 

	
 
from . import testutil
 

	
 
from conservancy_beancount import cliutil
 
from conservancy_beancount import cliutil, errors
 

	
 
FILE_NAMES = ['-foobar', '-foo.bin']
 
STREAM_PATHS = [None, Path('-')]
 

	
 
class ArgChoices(enum.Enum):
 
    AA = 'aa'
 
    AB = 'ab'
 
    BB = 'bb'
 

	
 

	
 
class MockTraceback:
 
    def __init__(self, stack=None, index=0):
...
 
@@ -154,24 +154,43 @@ def test_text_output_stream(path):
 
    errno.ENOENT,
 
])
 
def test_excepthook_oserror(errnum, caplog):
 
    error = OSError(errnum, os.strerror(errnum), 'TestFilename')
 
    with pytest.raises(SystemExit) as exc_check:
 
        cliutil.ExceptHook()(type(error), error, None)
 
    assert exc_check.value.args[0] == os.EX_IOERR
 
    assert caplog.records
 
    for log in caplog.records:
 
        assert log.levelname == 'CRITICAL'
 
        assert log.message == f"I/O error: {error.filename}: {error.strerror}"
 

	
 
@pytest.mark.parametrize('errcls', [
 
    errors.RewriteRuleActionError,
 
    errors.RewriteRuleConditionError,
 
    errors.RewriteRuleLoadError,
 
    errors.RewriteRuleValidationError,
 
])
 
def test_excepthook_rewrite_rule_error(errcls, caplog):
 
    name = errcls.__name__
 
    error = errcls("bad rewrite rule", f"{name}.yml", 170, [name])
 
    with pytest.raises(SystemExit) as exc_check:
 
        cliutil.ExceptHook()(type(error), error, None)
 
    assert exc_check.value.args[0] == cliutil.ExitCode.RewriteRulesError
 
    assert caplog.records
 
    for log in caplog.records:
 
        assert log.levelname == 'CRITICAL'
 
        lines = log.message.splitlines()
 
        assert lines[0].startswith(f"{name}: bad rewrite rule in {name}.yml rule #170")
 
        assert re.match(rf' source:\W+{name}\b', lines[-1])
 

	
 
@pytest.mark.parametrize('exc_type', [
 
    AttributeError,
 
    RuntimeError,
 
    ValueError,
 
])
 
def test_excepthook_bug(exc_type, caplog):
 
    error = exc_type("test message")
 
    with pytest.raises(SystemExit) as exc_check:
 
        cliutil.ExceptHook()(exc_type, error, None)
 
    assert exc_check.value.args[0] == os.EX_SOFTWARE
 
    assert caplog.records
 
    for log in caplog.records:
0 comments (0 inline, 0 general)