diff --git a/covimerage/cli.py b/covimerage/cli.py index 65ab12b0..90584d7e 100644 --- a/covimerage/cli.py +++ b/covimerage/cli.py @@ -1,3 +1,4 @@ +import logging import os import click @@ -9,12 +10,22 @@ from .utils import build_vim_profile_args, join_argv +def default_loglevel(): + return logging.getLevelName(LOGGER.level).lower() + + @click.group(context_settings={'help_option_names': ['-h', '--help']}) @click.version_option(__version__, '-V', '--version', prog_name='covimerage') @click.option('-v', '--verbose', count=True, help='Increase verbosity.') @click.option('-q', '--quiet', count=True, help='Decrease verbosity.') -def main(verbose, quiet): - if verbose - quiet: +@click.option('-l', '--loglevel', show_default=True, + help=('Set logging level explicitly (overrides -v/-q). ' + '[default: %s]' % (default_loglevel(),)), + type=click.Choice(('error', 'warning', 'info', 'debug'))) +def main(verbose, quiet, loglevel): + if loglevel: + LOGGER.setLevel(loglevel.upper()) + elif verbose - quiet: LOGGER.setLevel(max(10, LOGGER.level - (verbose - quiet) * 10)) diff --git a/tests/test_logging.py b/tests/test_logging.py index 06535638..4e6a0a05 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,3 +1,6 @@ +from imp import reload +import logging + import pytest @@ -17,3 +20,67 @@ def test_logging_error_causes_exception(capfd): "Message: 'Wrong:'", "Arguments: ('no %s',)"] assert 'TypeError: not all arguments converted during string formatting' in lines # noqa: E501 + + +def test_loglevel(mocker, runner, devnull): + from covimerage import cli + + logger = cli.LOGGER + + m = mocker.patch.object(logger, 'setLevel') + + for level in ['error', 'warning', 'info', 'debug']: + result = runner.invoke(cli.main, [ + '--loglevel', level, + 'report', '--nonexistingoption']) + assert result.output.splitlines() == [ + 'Error: no such option: --nonexistingoption'] + assert result.exit_code == 2 + + level_name = level.upper() + assert m.call_args_list[-1] == mocker.call(level_name) + + # -v should not override -l. + m.reset_mock() + result = runner.invoke(cli.main, [ + '-l', 'warning', '-vvv', + 'report', '--nonexistingoption']) + assert result.output.splitlines() == [ + 'Error: no such option: --nonexistingoption'] + assert result.exit_code == 2 + assert m.call_args_list == [mocker.call('WARNING')] + + # -q should not override -l. + m.reset_mock() + result = runner.invoke(cli.main, [ + '-l', 'warning', '-qqq', + 'report', '--nonexistingoption']) + assert result.output.splitlines() == [ + 'Error: no such option: --nonexistingoption'] + assert result.exit_code == 2 + assert m.call_args_list == [mocker.call('WARNING')] + + +@pytest.mark.parametrize('default', (None, 'INFO', 'WARNING')) +def test_loglevel_default(default, mocker, runner): + from covimerage import cli + from covimerage.logger import LOGGER as logger + + if default: + mocker.patch.object(logger, 'level', getattr(logging, default)) + else: + default = 'INFO' + reload(cli) + + result = runner.invoke(cli.main, ['-h']) + + assert logging.getLevelName(logger.level) == default + lines = result.output.splitlines() + idx = lines.index(' -l, --loglevel [error|warning|info|debug]') + assert idx + indent = ' ' * 34 + assert lines[idx+1:idx+3] == [ + indent + 'Set logging level explicitly (overrides', + indent + '-v/-q). [default: %s]' % (default.lower(),), + ] + assert result.exit_code == 0