diff --git a/matemat/webserver/config.py b/matemat/webserver/config.py index 90593ac..6c2ac4d 100644 --- a/matemat/webserver/config.py +++ b/matemat/webserver/config.py @@ -2,23 +2,19 @@ from typing import Any, Dict, Iterable, List, Tuple, Union import os -import io import sys import logging from configparser import ConfigParser def parse_logging(symbolic_level: str, symbolic_target: str) -> Tuple[int, logging.Handler]: - level: int = logging.NOTSET try: - level = int(symbolic_level) + level: int = int(symbolic_level) except ValueError: try: level = int(logging.getLevelName(symbolic_level)) - except KeyError: - pass except ValueError: - pass + raise ValueError(f'Unknown log level: {symbolic_level}') if symbolic_target == 'stderr': target: logging.Handler = logging.StreamHandler(sys.stderr) elif symbolic_target == 'stdout': @@ -50,7 +46,7 @@ def parse_config_file(paths: Union[str, Iterable[str]]) -> Dict[str, Any]: # Log level 'log_level': logging.INFO, # Log target: An IO stream (stderr, stdout, ...) or a filename - 'log_handler': 'stderr', + 'log_handler': logging.StreamHandler(), # Variables passed to pagelets 'pagelet_variables': dict() } @@ -78,7 +74,7 @@ def parse_config_file(paths: Union[str, Iterable[str]]) -> Dict[str, Any]: config['staticroot'] = parser['Matemat'].get('StaticPath', os.path.expanduser(config['staticroot'])) config['log_level'], config['log_handler'] =\ parse_logging(parser['Matemat'].get('LogLevel', config['log_level']), - parser['Matemat'].get('LogTarget', config['log_handler'])) + parser['Matemat'].get('LogTarget', 'stderr')) config['templateroot'] = parser['Matemat'].get('TemplatePath', os.path.expanduser(config['templateroot'])) # Read all values from the [Pagelets] section, if present. These values are passed to pagelet functions diff --git a/matemat/webserver/test/test_config.py b/matemat/webserver/test/test_config.py index 2d37635..517bc29 100644 --- a/matemat/webserver/test/test_config.py +++ b/matemat/webserver/test/test_config.py @@ -5,6 +5,8 @@ from unittest import TestCase from unittest.mock import patch from io import StringIO +import logging +import sys from matemat.webserver import parse_config_file @@ -19,6 +21,9 @@ Port = 8080 StaticPath =/var/test/static TemplatePath= /var/test/templates +LogLevel = CRITICAL +LogTarget = /tmp/log/matemat_test.log + [Pagelets] Name=Matemat (Unit Test) @@ -34,6 +39,29 @@ Port=443 Name=Matemat (Unit Test 2) ''' +_LOG_NONE_CONFIG = ''' +[Matemat] +LogTarget=none +''' + +_LOG_STDOUT_CONFIG = ''' +[Matemat] +LogTarget=stdout +''' + +_LOG_STDERR_CONFIG = ''' +[Matemat] +LogTarget=stderr +''' + +_LOG_GARBAGE_PORT = ''' +[Matemat] +Port=iwanttobeavalidporttoo''' + +_LOG_GARBAGE_LOGLEVEL = ''' +[Matemat] +LogLevel=thisisnotaloglevel''' + class IterOpenMock: """ @@ -59,7 +87,7 @@ class IterOpenMock: class TestConfig(TestCase): - def test_parse_config_empty_defualt_values(self): + def test_parse_config_empty_default_values(self): """ Test that default values are set when reading an empty config file. """ @@ -72,12 +100,17 @@ class TestConfig(TestCase): self.assertIn('port', config) self.assertIn('staticroot', config) self.assertIn('templateroot', config) + self.assertIn('log_level', config) + self.assertIn('log_handler', config) self.assertIn('pagelet_variables', config) # Make sure all mandatory values are set to their default self.assertEqual('::', config['listen']) self.assertEqual(80, config['port']) self.assertEqual('/var/matemat/static', config['staticroot']) self.assertEqual('/var/matemat/templates', config['templateroot']) + self.assertEqual(logging.INFO, config['log_level']) + self.assertIsInstance(config['log_handler'], logging.StreamHandler) + self.assertEqual(sys.stderr, config['log_handler'].stream) self.assertIsInstance(config['pagelet_variables'], dict) self.assertEqual(0, len(config['pagelet_variables'])) @@ -94,6 +127,8 @@ class TestConfig(TestCase): self.assertIn('port', config) self.assertIn('staticroot', config) self.assertIn('templateroot', config) + self.assertIn('log_level', config) + self.assertIn('log_handler', config) self.assertIn('pagelet_variables', config) self.assertIn('Name', config['pagelet_variables']) self.assertIn('UploadDir', config['pagelet_variables']) @@ -103,6 +138,9 @@ class TestConfig(TestCase): self.assertEqual(8080, config['port']) self.assertEqual('/var/test/static', config['staticroot']) self.assertEqual('/var/test/templates', config['templateroot']) + self.assertEqual(logging.CRITICAL, config['log_level']) + self.assertIsInstance(config['log_handler'], logging.FileHandler) + self.assertEqual('/tmp/log/matemat_test.log', config['log_handler'].baseFilename) self.assertEqual('Matemat\n(Unit Test)', config['pagelet_variables']['Name']) self.assertEqual('/var/test/static/upload', config['pagelet_variables']['UploadDir']) self.assertEqual('/var/test/db/test.db', config['pagelet_variables']['DatabaseFile']) @@ -134,3 +172,55 @@ class TestConfig(TestCase): self.assertEqual('Matemat (Unit Test 2)', config['pagelet_variables']['Name']) self.assertEqual('/var/test/static/upload', config['pagelet_variables']['UploadDir']) self.assertEqual('/var/test/db/test.db', config['pagelet_variables']['DatabaseFile']) + + def test_parse_config_logging_none(self): + """ + Test that "LogTaget=none" disables logging. + """ + # Mock the open() function to return a config file example with disabled logging + with patch('builtins.open', return_value=StringIO(_LOG_NONE_CONFIG)): + # The filename is only a placeholder, file content is determined by mocking open + config = parse_config_file('test') + # Make sure the returned log handler is a null handler + self.assertIsInstance(config['log_handler'], logging.NullHandler) + + def test_parse_config_logging_stdout(self): + """ + Test that "LogTaget=stdout" logs to sys.stdout. + """ + # Mock the open() function to return a config file example with stdout logging + with patch('builtins.open', return_value=StringIO(_LOG_STDOUT_CONFIG)): + # The filename is only a placeholder, file content is determined by mocking open + config = parse_config_file('test') + # Make sure the returned log handler is a stdout handler + self.assertIsInstance(config['log_handler'], logging.StreamHandler) + self.assertEqual(sys.stdout, config['log_handler'].stream) + + def test_parse_config_logging_stderr(self): + """ + Test that "LogTaget=stderr" logs to sys.stderr. + """ + # Mock the open() function to return a config file example with stdout logging + with patch('builtins.open', return_value=StringIO(_LOG_STDERR_CONFIG)): + # The filename is only a placeholder, file content is determined by mocking open + config = parse_config_file('test') + # Make sure the returned log handler is a stdout handler + self.assertIsInstance(config['log_handler'], logging.StreamHandler) + self.assertEqual(sys.stderr, config['log_handler'].stream) + + def test_parse_config_garbage(self): + """ + Test that garbage config raises ValueErrors. + """ + # Mock the open() function to return a config file example with a non-numeric port + with patch('builtins.open', return_value=StringIO(_LOG_GARBAGE_PORT)): + # Make sure a ValueError is raised + with self.assertRaises(ValueError): + # The filename is only a placeholder, file content is determined by mocking open + parse_config_file('test') + # Mock the open() function to return a config file example with a non-numeric log level with invalid symbol + with patch('builtins.open', return_value=StringIO(_LOG_GARBAGE_LOGLEVEL)): + # Make sure a ValueError is raised + with self.assertRaises(ValueError): + # The filename is only a placeholder, file content is determined by mocking open + parse_config_file('test')