main fixed and tested
This commit is contained in:
parent
7b433ea1b1
commit
8a95ca9ff0
6 changed files with 117 additions and 20 deletions
12
Makefile
12
Makefile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
0
tests/unit/main/__init__.py
Normal file
0
tests/unit/main/__init__.py
Normal file
63
tests/unit/main/test_main.py
Normal file
63
tests/unit/main/test_main.py
Normal 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()
|
||||
|
|
@ -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",
|
||||
[
|
||||
|
|
|
|||
Loading…
Reference in a new issue