main fixed and tested

This commit is contained in:
jbannon 2022-06-07 07:13:55 +00:00
parent 7b433ea1b1
commit 8a95ca9ff0
6 changed files with 117 additions and 20 deletions

View file

@ -1,10 +1,18 @@
wheel:
wheel: clean
python setup.py bdist_wheel
docker: wheel
cp dist/*.whl docker/root/
sudo docker build --no-cache -t ytdl-sub:0.1 docker/
docs:
sphinx-build -a -b html docs docs/_html
clean:
rm -rf \
.pytest_cache/ \
build/ \
dist/ \
src/ytdl_sub.egg-info/ \
.coverage \
coverage.xml
.PHONY: wheel docker docs
.PHONY: wheel docker docs clean

View file

@ -83,6 +83,9 @@ def _main():
_download_subscription_from_cli(config=config, extra_args=extra_args)
logger.info("Download complete!")
# Ran successfully, so we can delete the debug file
Logger.cleanup(delete_debug_file=True)
def main():
"""
@ -96,9 +99,10 @@ def main():
except Exception: # pylint: disable=broad-except
logger.exception("An uncaught error occurred:")
logger.error(
"Please copy and paste the stacktrace above and make a Github "
"Please upload the error log file '%s' and make a Github "
"issue at https://github.com/jmbannon/ytdl-sub/issues with your config and "
"command/subscription yaml file to reproduce. Thanks for trying ytdl-sub!"
"command/subscription yaml file to reproduce. Thanks for trying ytdl-sub!",
Logger.debug_log_filename(),
)
sys.exit(1)

View file

@ -21,7 +21,7 @@ class LoggerLevels:
Custom log levels
"""
QUIET = LoggerLevel(name="quiet", level=0, logging_level=logging.NOTSET) # No logs whatsoever
QUIET = LoggerLevel(name="quiet", level=0, logging_level=logging.WARNING) # Only warnings
INFO = LoggerLevel(name="info", level=10, logging_level=logging.INFO) # ytdl-sub info logs
VERBOSE = LoggerLevel(name="verbose", level=20, logging_level=logging.INFO) # ytdl-sub + yt-dlp
DEBUG = LoggerLevel(
@ -65,12 +65,37 @@ class LoggerLevels:
return [logger_level.name for logger_level in cls.all()]
class StreamToLogger(io.StringIO):
def __init__(self, logger: logging.Logger, *args, **kwargs):
super().__init__(*args, **kwargs)
self._logger = logger
def write(self, __s: str) -> int:
"""
Writes to the logger and stream
"""
self._logger.info(__s.removesuffix("\n"))
return super().write(__s)
class Logger:
# The level set via CLI arguments
_LOGGER_LEVEL: LoggerLevel = LoggerLevels.DEBUG
_DEBUG_LOGGER_FILE = None
# Ignore 'using with' warning since this will be cleaned up later
# pylint: disable=R1732
_DEBUG_LOGGER_FILE = tempfile.NamedTemporaryFile(prefix="ytdl-sub.", delete=False)
# pylint: enable=R1732
@classmethod
def debug_log_filename(cls) -> str:
"""
Returns
-------
File name of the debug log file
"""
return cls._DEBUG_LOGGER_FILE.name
@classmethod
def set_log_level(cls, log_level_name: str):
@ -105,13 +130,7 @@ class Logger:
@classmethod
def _get_debug_file_handler(cls) -> logging.FileHandler:
if cls._DEBUG_LOGGER_FILE is None:
# Ignore 'using with' warning since this must be cleaned up later
# pylint: disable=R1732
cls._DEBUG_LOGGER_FILE = tempfile.NamedTemporaryFile(prefix="ytdl-sub.", delete=False)
# pylint: enable=R1732
handler = logging.FileHandler(filename=cls._DEBUG_LOGGER_FILE.name, encoding="utf-8")
handler = logging.FileHandler(filename=cls.debug_log_filename(), encoding="utf-8")
handler.setLevel(logging.DEBUG)
handler.setFormatter(cls._get_formatter())
return handler
@ -158,20 +177,17 @@ class Logger:
----------
name
Optional. Name of the logger which is included in the prefix like [ytdl-sub:<name>].
If None, the prefix is just [ytdl-sub]
If None, the prefix is just [ytdl-sub]mak
"""
logger = cls._get(
name=name, stdout=cls._LOGGER_LEVEL.level >= LoggerLevels.VERBOSE.level, debug_file=True
)
with io.StringIO() as redirect_stream:
with StreamToLogger(logger=logger) as redirect_stream:
with contextlib.redirect_stdout(new_target=redirect_stream):
with contextlib.redirect_stderr(new_target=redirect_stream):
yield
redirect_stream.flush()
logger.info(redirect_stream.getvalue())
@classmethod
def cleanup(cls, delete_debug_file: bool = True):
"""
@ -184,5 +200,5 @@ class Logger:
"""
cls._DEBUG_LOGGER_FILE.close()
if delete_debug_file and os.path.isfile(cls._DEBUG_LOGGER_FILE.name):
os.remove(cls._DEBUG_LOGGER_FILE.name)
if delete_debug_file and os.path.isfile(cls.debug_log_filename()):
os.remove(cls.debug_log_filename())

View file

View file

@ -0,0 +1,63 @@
import contextlib
import sys
from unittest.mock import patch
import pytest
from src.ytdl_sub.main import main
from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.logger import Logger
@pytest.fixture
def expected_uncaught_error_message():
return (
f"Please upload the error log file '%s' and make a "
f"Github issue at https://github.com/jmbannon/ytdl-sub/issues with your config and "
f"command/subscription yaml file to reproduce. Thanks for trying ytdl-sub!"
)
@pytest.fixture
def mock_sys_exit():
@contextlib.contextmanager
def _mock_sys_exit(expected_exit_code: int):
with patch.object(sys, "exit") as mock_exit:
yield mock_exit
assert mock_exit.called
assert mock_exit.call_args_list[0].args[0] == expected_exit_code
return _mock_sys_exit
def test_main_success(mock_sys_exit):
with mock_sys_exit(expected_exit_code=0):
with patch("src.ytdl_sub.main._main"):
main()
def test_main_validation_error(capsys, mock_sys_exit):
validation_exception = ValidationException("test")
with mock_sys_exit(expected_exit_code=1):
with patch("src.ytdl_sub.main._main", side_effect=validation_exception):
with patch("src.ytdl_sub.main.logger") as mock_logger:
main()
assert mock_logger.error.call_count == 1
assert mock_logger.error.call_args.args[0] == validation_exception
def test_main_uncaught_error(capsys, mock_sys_exit, expected_uncaught_error_message):
uncaught_error = ValueError("test")
with mock_sys_exit(expected_exit_code=1):
with patch("src.ytdl_sub.main._main", side_effect=uncaught_error):
with patch("src.ytdl_sub.main.logger") as mock_logger:
main()
assert mock_logger.exception.call_count == 1
assert mock_logger.exception.call_args.args[0] == "An uncaught error occurred:"
assert mock_logger.error.call_count == 1
assert mock_logger.error.call_args.args[0] == expected_uncaught_error_message
assert mock_logger.error.call_args.args[1] == Logger.debug_log_filename()

View file

@ -1,3 +1,5 @@
import os.path
import pytest
from ytdl_sub.utils.logger import Logger
@ -75,6 +77,10 @@ class TestLogger:
assert lines == ["[ytdl-sub:name_test] info test\n", "[ytdl-sub:name_test] debug test\n"]
# Ensure the file cleans up too
Logger.cleanup(delete_debug_file=True)
assert not os.path.isfile(Logger._DEBUG_LOGGER_FILE.name)
@pytest.mark.parametrize(
"log_level, expected_stdout",
[