Source code for click_extra.tests.test_logging

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

from __future__ import annotations

import logging
import random
import re

import click
import pytest
from pytest_cases import parametrize

from click_extra import echo
from click_extra.decorators import extra_command, verbosity_option
from click_extra.logging import DEFAULT_LEVEL, LOG_LEVELS

from .conftest import (
    command_decorators,
    default_debug_colored_log_end,
    default_debug_colored_log_start,
    default_debug_colored_logging,
    default_debug_uncolored_log_end,
    default_debug_uncolored_logging,
    skip_windows_colors,
)


[docs]def test_level_default_order(): assert tuple(LOG_LEVELS) == ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG")
[docs]def test_root_logger_defaults(): """Check our internal default is aligned to Python's root logger.""" # Check the root logger is the default logger, and that getLogger is # properly patched on Python 3.8. assert logging.getLogger() is logging.getLogger("root") assert logging.getLogger() is logging.root # Check root logger's level. assert logging.root.getEffectiveLevel() == logging.WARNING assert logging._levelToName[logging.root.level] == "WARNING" assert logging.root.level == DEFAULT_LEVEL
[docs]@pytest.mark.parametrize( ("cmd_decorator", "cmd_type"), command_decorators(with_types=True), ) def test_unrecognized_verbosity(invoke, cmd_decorator, cmd_type): @cmd_decorator @verbosity_option def logging_cli1(): echo("It works!") # Remove colors to simplify output comparison. result = invoke(logging_cli1, "--verbosity", "random", color=False) assert result.exit_code == 2 assert not result.stdout group_help = " COMMAND [ARGS]..." if "group" in cmd_type else "" extra_suggest = ( "Try 'logging-cli1 --help' for help.\n" if "extra" not in cmd_type else "" ) assert result.stderr == ( f"Usage: logging-cli1 [OPTIONS]{group_help}\n" f"{extra_suggest}\n" "Error: Invalid value for '--verbosity' / '-v': " "'random' is not one of 'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'.\n" )
[docs]@skip_windows_colors @pytest.mark.parametrize( "cmd_decorator", # Skip click extra's commands, as verbosity option is already part of the default. command_decorators(no_groups=True, no_extra=True), ) @parametrize("option_decorator", (verbosity_option, verbosity_option())) @pytest.mark.parametrize("level", LOG_LEVELS.keys()) def test_default_root_logger(invoke, cmd_decorator, option_decorator, level): """Checks: - the default logger is ``<root>`` - the default logger message format - level names are colored - log level is propagated to all other loggers. """ @cmd_decorator @option_decorator def logging_cli2(): echo("It works!") random_logger = logging.getLogger( f"random_logger_{random.randrange(10000, 99999)}", ) random_logger.debug("my random message.") logging.debug("my debug message.") logging.info("my info message.") logging.warning("my warning message.") logging.error("my error message.") logging.critical("my critical message.") result = invoke(logging_cli2, "--verbosity", level, color=True) assert result.exit_code == 0 assert result.stdout == "It works!\n" messages = ( ( rf"{default_debug_colored_logging}" r"\x1b\[34mdebug\x1b\[0m: my random message.\n" r"\x1b\[34mdebug\x1b\[0m: my debug message.\n" ), r"info: my info message.\n", r"\x1b\[33mwarning\x1b\[0m: my warning message.\n", r"\x1b\[31merror\x1b\[0m: my error message.\n", r"\x1b\[31m\x1b\[1mcritical\x1b\[0m: my critical message.\n", ) level_index = {index: level for level, index in enumerate(LOG_LEVELS)}[level] log_records = r"".join(messages[-level_index - 1 :]) if level == "DEBUG": log_records += default_debug_colored_log_end assert re.fullmatch(log_records, result.stderr)
[docs]@skip_windows_colors @pytest.mark.parametrize("level", LOG_LEVELS.keys()) # TODO: test extra_group def test_integrated_verbosity_option(invoke, level): @extra_command def logging_cli3(): echo("It works!") result = invoke(logging_cli3, "--verbosity", level, color=True) assert result.exit_code == 0 assert result.stdout == "It works!\n" if level == "DEBUG": assert re.fullmatch( default_debug_colored_log_start + default_debug_colored_log_end, result.stderr, ) else: assert not result.stderr
[docs]@pytest.mark.parametrize( "logger_param", (logging.getLogger("awesome_app"), "awesome_app"), ) @pytest.mark.parametrize("params", (("--verbosity", "DEBUG"), None)) def test_custom_logger_param(invoke, logger_param, params): """Passing a logger instance or name to the ``default_logger`` parameter works.""" @click.command @verbosity_option(default_logger=logger_param) def awesome_app(): echo("Starting Awesome App...") logging.getLogger("awesome_app").debug("Awesome App has started.") result = invoke(awesome_app, params, color=False) assert result.exit_code == 0 assert result.stdout == "Starting Awesome App...\n" if params: assert re.fullmatch( ( r"debug: Set <Logger click_extra \(DEBUG\)> to DEBUG.\n" r"debug: Set <Logger awesome_app \(DEBUG\)> to DEBUG.\n" r"debug: Awesome App has started\.\n" r"debug: Reset <Logger awesome_app \(DEBUG\)> to WARNING.\n" r"debug: Reset <Logger click_extra \(DEBUG\)> to WARNING.\n" ), result.stderr, ) else: assert not result.stderr
[docs]def test_custom_option_name(invoke): param_names = ("--blah", "-B") @click.command @verbosity_option(*param_names) def awesome_app(): root_logger = logging.getLogger() root_logger.debug("my debug message.") for name in param_names: result = invoke(awesome_app, name, "DEBUG", color=False) assert result.exit_code == 0 assert not result.stdout assert re.fullmatch( ( rf"{default_debug_uncolored_logging}" r"debug: my debug message\.\n" rf"{default_debug_uncolored_log_end}" ), result.stderr, )