diff --git a/.circleci/config.yml b/.circleci/config.yml index bb80d3b6..ac9c46c2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ common: &common - v1-deps-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }} - v1-deps- - run: pip install --user tox - - run: ~/.local/bin/tox + - run: PYTEST_ADDOPTS=-vv ~/.local/bin/tox - run: name: upload coverage results for non-checkqa builds command: | 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 diff --git a/tests/test_main.py b/tests/test_main.py index cbf57ef2..8da4f093 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -22,9 +22,9 @@ def test_profile_repr_lines(): assert repr(p.lines) == '{%r: {}}' % s assert repr(s) == "Script(path='script-path', sourced_count=None)" - l = Line('line1') - s.lines[1] = l - assert repr(p.lines) == ('{%r: {1: %r}}' % (s, l)) + line = Line('line1') + s.lines[1] = line + assert repr(p.lines) == ('{%r: {1: %r}}' % (s, line)) def test_profile_fname_or_fobj(caplog, devnull): @@ -60,8 +60,9 @@ def test_parse_count_and_times(): def test_line(): from covimerage import Line - l = Line(' 1 0.000005 Foo') - assert repr(l) == "Line(line=' 1 0.000005 Foo', count=None, total_time=None, self_time=None)" # noqa + line = ' 1 0.000005 Foo' + assert repr(Line(line)) == 'Line(line=%r, count=None, total_time=None, self_time=None)' % ( # noqa:E501 + line) def test_profile_parse():