diff --git a/testing/test_common.py b/testing/test_common.py new file mode 100644 index 0000000..7239d23 --- /dev/null +++ b/testing/test_common.py @@ -0,0 +1,48 @@ +# flake8: noqa +import os + +import pytest + +from clog.utils.common import ( + is_path_spec, + LogLevel, +) + + +class TestCommon: + @pytest.mark.parametrize( + "value, expected", [ + ("log.txt", True), + (".log", True), + (".a_file", True), + ("a file.txt", False), + ("log123", True), + ("a^bad,spec", False), + ("may\\not\\exist\\but_valid" if os.name == 'nt'\ + else "may/not/exist/but_valid", True), + (os.path.realpath("log.txt"), True) + ] + ) + def test_is_path_spec(self, value, expected): + assert is_path_spec(value) is expected + + @pytest.mark.parametrize( + "value, expected", [ + (LogLevel.NORMAL, 0), + (LogLevel.PASS, 1), + (LogLevel.DEBUG, 2), + (LogLevel.WARN, 3), + (LogLevel.ERROR, 4), + ] + ) + def test_log_level_values(self, value, expected): + assert value == expected + + @pytest.mark.parametrize( + "value, expected", [ + *zip([eval(f"LogLevel.{_}") for _ in dir(LogLevel) if _.isupper()], + [eval(f"LogLevel.{_}") for _ in dir(LogLevel) if _.isupper()]) + ] + ) + def test_log_level_through_identity(self, value, expected): + assert value is expected \ No newline at end of file diff --git a/testing/test_logger.py b/testing/test_logger.py new file mode 100644 index 0000000..a5f2cc5 --- /dev/null +++ b/testing/test_logger.py @@ -0,0 +1,158 @@ +# flake8: noqa +import os +import re + +import pytest + +from clog import Logger +from utils.common import LogLevel + + +logger = Logger() + +class TestLoggingBasics: + @pytest.mark.parametrize( + "value, expected", [ + (Logger(), Logger()), + (logger, logger), + (Logger(), logger), + (logger, Logger()) + ] + ) + def test_new_logger_instance(self, value, expected): + assert value is expected + + def test_logger_file_default(self): + assert logger.log == os.path.realpath("dump.log") + + def test_creation_of_new_logger(self): + assert logger is not Logger.new() + + @pytest.mark.parametrize( + "value, expected", + [(".log", '.log'), + (".dump.log", ".dump.log"), + ("./non-dir/log", "dump.log"),] + ) + def test_using_different_log_file(self, value, expected): + global logger + + logger = logger.new(out_f=value) + os.remove(logger.log) + + assert Logger().log == os.path.realpath(expected).strip('"') + + +class TestLoggingSTD: + global logger + logger = logger.new() + + @pytest.mark.parametrize( + "value, expected, s, e", [ + (*("Normal text to STDOUT",) * 2, None, None), + (*("Normal test with a newline\nto STDOUT",) * 2, None, None), + (("Text", "with", "hyphens"), "Text-with-hyphens", "-", None), + ("Text here...", "Text here...\r", None, '\r') + ] + ) + def test_message_written_to_stdout(self, value, expected, s, e, capture_stdpipe): + if isinstance(value, tuple): + logger.printLog(*value, level=LogLevel.NORMAL, sep=s, end=e) + else: + logger.printLog(value, level=LogLevel.NORMAL, sep=s, end=e) + + assert capture_stdpipe['stdout'].rstrip('\n') == expected and capture_stdpipe['stderr'] == "" + + @pytest.mark.parametrize( + "value, expected, s, e", [ + (*("Normal text to STDOUT",) * 2, None, None), + (*("Normal test with a newline\nto STDOUT",) * 2, None, None), + (("Text", "with", "hyphens"), "Text-with-hyphens", "-", None), + ("Text here...", "Text here...\r", None, '\r') + ] + ) + def test_message_written_to_stderr(self, value, expected, s, e, capture_stdpipe): + if isinstance(value, tuple): + logger.printLog(*value, level=LogLevel.DEBUG, sep=s, end=e) + else: + logger.printLog(value, level=LogLevel.DEBUG, sep=s, end=e) + + assert capture_stdpipe['stderr'].rstrip('\n') == expected and capture_stdpipe['stdout'] == "" + + def test_message_not_on_std_pipe(self, capture_stdpipe): + with open(os.path.devnull, 'w') as devnull: + Logger.printLog("Message isn't sent to STDOUT or STDERR", file=devnull) + + assert capture_stdpipe['stdout'] == "" and capture_stdpipe['stderr'] == "" + + @pytest.mark.skip + def test_when_file_descriptor_redirect_is_given(self): + ### :@Ethan: OK this test requires a little explaining as to what's going on + # and why it's happening. Since this project was pulled out of another and + # became standalone, the original program which made use of this system was + # designed to prevent writes with ANSI escape sequences on PIPE when a file + # descriptor redirect was given from the command-line. That logic still exists + # and here we invoke that behavour for testing by running a bit of Python script + # from the terminal. + # + # + # :@UPDATE: if anyone knows how to test this behaviour by invoking that a TTY + # is present in a subprocess call, create an Issue or a PR with the appropriate + # code to get something like that working. I've spent way too much time on trying + # to prove the system works and behaves how it should, but you can confirm this + # happens by calling the `Logger.printLog()` method from an external script + # and performing a file descriptor redirect to a file. If you don't see ASNI + # escape sequences, the system works correctly, and you can call the module + # again without redirect to ensure the terminal gets coloured output on log levels + # `LogLevel.DEBUG` or higher. + ... + + def test_print_log_does_not_handle_string_paths(self, capture_stdpipe): + Logger.printLog("This fails correctly", file="bad_file.txt") + assert capture_stdpipe['stderr'] != "" + + +class TestLogging2File: + def test_print_log_2_file_handles_string_paths(self, capture_stdpipe): + Logger.printLog2File("Hello, World!", file=logger.log, mode='w') + assert capture_stdpipe['stdout'] == "" and capture_stdpipe['stderr'] == "" + + def test_print_log_2_file_writes_to_file(self): + Logger.printLog2File("Message to log", file=logger.log, mode='w') + with open(logger.log, 'r') as f: + assert re.match('^.*Message to log$', f.read()) + + +class TestPseudoLogs: + global logger + logger = logger.new() + + @pytest.mark.parametrize( + "value", [Logger().debug, Logger().warn, Logger().error] + ) + def test_pseudologs_are_instance_bound(self, value): + assert hasattr(value, '__self__') + + @pytest.mark.parametrize( + "value, ln, lv", [ + (logger.debug, 1, "DEBUG"), + (logger.warn, 2, "WARN"), + (logger.error, 3, "ERROR"), + ] + ) + def test_pseudologs_write_to_file(self, value, ln, lv): + value("Pseudolog to file") + with open(logger.log, 'r') as f: + assert re.match(f'^.*{lv}\\s+Pseudolog to file$', f.readlines()[ln]) + + @pytest.mark.parametrize( + "value, ln", [ + (logger.debug, 4), + (logger.warn, 5), + (logger.error, 6), + ] + ) + def test_pseudologs_write_to_file_and_console(self, value, ln, capture_stdpipe): + value("Pseudolog to file and console", strace=False).withConsole() + with open(logger.log, 'r') as f: + assert re.match(f'^.*{capture_stdpipe["stderr"]}$', f.readlines()[ln]) diff --git a/testing/test_printfmt.py b/testing/test_printfmt.py new file mode 100644 index 0000000..2252ca3 --- /dev/null +++ b/testing/test_printfmt.py @@ -0,0 +1,94 @@ +# flake8: noqa +import re + +import pytest +from utils.common import LogLevel + +from clog.utils.printfmt import ( + Colours, + gen_log_header, + log_as_col, + loglevel_as_str, + wrap, +) + + +class TestPrintFMT: + @pytest.mark.parametrize( + "seq", [ + eval(f"Colours.{_}") for _ in dir(Colours) if _.isupper() + ] + ) + def test_colour_ansi_format(self, seq): + assert re.match('\\033\\[[0-9]+m', seq) + + @pytest.mark.parametrize( + "value, expected", [ + (Colours.NORMAL, '\033[0m'), + (Colours.RED, '\033[91m'), + (Colours.GREEN, '\033[92m'), + (Colours.YELLOW, '\033[93m'), + (Colours.BLUE, '\033[94m'), + ] + ) + def test_colour_ansi_match_codes(self, value, expected): + assert value == expected + + def test_log_header_time_format_iso_8601(self): + assert re.match( + '^\\[\\d{4}(-\\d{2}){2}T\\d{2}(:\\d{2}){2}\\+\\d{4}\\].*$', + gen_log_header('') + ) + + def test_log_header_component_length(self): + assert len(gen_log_header('TEST').split(' ')) >= 4 + + def test_log_header_is_formattable(self): + assert len(re.findall(r'{(.*?)}', gen_log_header(LogLevel.DEBUG))) > 0 + + @pytest.mark.parametrize( + "value, expected", [ + (LogLevel.NORMAL, Colours.NORMAL), + (LogLevel.PASS, Colours.GREEN), + (LogLevel.DEBUG, Colours.BLUE), + (LogLevel.WARN, Colours.YELLOW), + (LogLevel.ERROR, Colours.RED), + (-1, Colours.NORMAL), + (1000, Colours.NORMAL), + (2, '\033[94m'), + (LogLevel.WARN, '\033[93m'), + ("a pesky string", Colours.NORMAL), + ] + ) + def test_log_as_colour(self, value, expected): + assert log_as_col(value) == expected + + @pytest.mark.parametrize( + "value, expected", [ + *zip([eval(f"LogLevel.{_}") for _ in dir(LogLevel) if _.isupper()], + ["{0: <5}".format(_) for _ in dir(LogLevel) if _.isupper()]) + ] + ) + def test_log_level_in_string_form(self, value, expected): + assert loglevel_as_str(value) == expected + + @pytest.mark.parametrize( + "value, expected", [ + ("This won't be too long. So no wrap", False), + ("This will be long enough" * 10, True), + (gen_log_header(LogLevel.DEBUG).format( + "Not long enough in file", CALLER=0), False), + (gen_log_header(LogLevel.DEBUG).format( + "Long enough to break many times" * 20, CALLER=0), True) + ] + ) + def test_wrap_has_newlines(self, value, expected): + assert (wrap(value).find('\n') != -1) == expected + + + + + + + +