From c85ff19a8ffac3ae76ec48ead8bfce7647f70e54 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 11:25:47 +0200 Subject: [PATCH 01/12] Accept relative path for reader --- livelog/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livelog/__main__.py b/livelog/__main__.py index 0f2792a..560a290 100644 --- a/livelog/__main__.py +++ b/livelog/__main__.py @@ -41,7 +41,7 @@ def _parse_args(): args = parser.parse_args() if args.file is not None: - file = Path(args.file) + file = Path(args.file).resolve() else: file = ( Path("/tmp/livelog.log") From 7c7638b24b69394254899278e000e30fd9a5b0ff Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 12:21:47 +0200 Subject: [PATCH 02/12] Add logger tests --- .github/workflows/tests.yml | 27 +++++ livelog/logger.py | 11 +- tests/__init__.py | 0 tests/conftest.py | 6 + tests/test_logger.py | 233 ++++++++++++++++++++++++++++++++++++ 5 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_logger.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..543d66d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,27 @@ +name: tests + +on: [push, pull_request] + +jobs: + test: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9, 3.10] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip + pip install . + pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Run unit tests + run: | + pytest diff --git a/livelog/logger.py b/livelog/logger.py index e24594a..8642d92 100644 --- a/livelog/logger.py +++ b/livelog/logger.py @@ -95,10 +95,13 @@ def _verify_file(self): """ dir = self._file.parent.resolve() - if self._file.is_dir(): - raise LogFileIsADirectory(path=self._file) - if not dir.is_dir(): - raise LogPathDoesNotExist(path=dir) + try: + if self._file.is_dir(): + raise LogFileIsADirectory(path=self._file) + if not dir.is_dir(): + raise LogPathDoesNotExist(path=dir) + except PermissionError: + raise LogPathInsufficientPermissions(path=dir) if not access(dir, X_OK): raise LogPathInsufficientPermissions(path=dir) self._clear_file() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..9f9cb57 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture(scope="session") +def log_file(tmpdir_factory): + return tmpdir_factory.mktemp("tmp").join("test.log") diff --git a/tests/test_logger.py b/tests/test_logger.py new file mode 100644 index 0000000..a264ed3 --- /dev/null +++ b/tests/test_logger.py @@ -0,0 +1,233 @@ +from pytest import raises +from pathlib import Path +from re import findall +from livelog import Logger, LoggerSingleton, errors + + +def test_default(log_file): + logger = Logger(file=log_file) + logger.debug("0") + logger.info("1") + logger.warn("2") + logger.error("3") + with open(log_file, "r") as f: + logs = f.read() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 0)", logs)) == 1 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 1)", logs)) == 1 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 2)", logs)) == 1 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 3)", logs)) == 1 + ) + + +def test_without_erasing(log_file): + logger = Logger(file=log_file, erase=False) + logger.debug("0") + logger.info("1") + logger.warn("2") + logger.error("3") + with open(log_file, "r") as f: + logs = f.read() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 0)", logs)) == 2 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 1)", logs)) == 2 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 2)", logs)) == 2 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 3)", logs)) == 2 + ) + + +def test_with_erasing(log_file): + logger = Logger(file=log_file) + logger.debug("0") + logger.info("1") + logger.warn("2") + logger.error("3") + with open(log_file, "r") as f: + logs = f.read() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 0)", logs)) == 1 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 1)", logs)) == 1 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 2)", logs)) == 1 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 3)", logs)) == 1 + ) + + +def test_log_level_info(log_file): + logger = Logger(file=log_file, level="INFO") + logger.debug("0") + logger.info("1") + logger.warn("2") + logger.error("3") + with open(log_file, "r") as f: + logs = f.read() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 0)", logs)) == 0 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 1)", logs)) == 1 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 2)", logs)) == 1 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 3)", logs)) == 1 + ) + + +def test_log_level_warning(log_file): + logger = Logger(file=log_file, level="WARNING") + logger.debug("0") + logger.info("1") + logger.warn("2") + logger.error("3") + with open(log_file, "r") as f: + logs = f.read() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 0)", logs)) == 0 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 1)", logs)) == 0 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 2)", logs)) == 1 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 3)", logs)) == 1 + ) + + +def test_log_level_error(log_file): + logger = Logger(file=log_file, level="ERROR") + logger.debug("0") + logger.info("1") + logger.warn("2") + logger.error("3") + with open(log_file, "r") as f: + logs = f.read() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 0)", logs)) == 0 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 1)", logs)) == 0 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 2)", logs)) == 0 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 3)", logs)) == 1 + ) + + +def test_log_level_custom_case(log_file): + logger = Logger(file=log_file, level="iNfO") + logger.debug("0") + logger.info("1") + logger.warn("2") + logger.error("3") + with open(log_file, "r") as f: + logs = f.read() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 0)", logs)) == 0 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 1)", logs)) == 1 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 2)", logs)) == 1 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 3)", logs)) == 1 + ) + + +def test_log_disabled(log_file): + logger = Logger(file=log_file, enabled=False) + logger.debug("0") + logger.info("1") + logger.warn("2") + logger.error("3") + with open(log_file, "r") as f: + logs = f.read() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 0)", logs)) == 0 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 1)", logs)) == 0 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 2)", logs)) == 0 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 3)", logs)) == 0 + ) + + +def test_singleton(log_file): + logger1 = LoggerSingleton(file=log_file) + logger1.debug("0") + logger1.info("1") + logger2 = LoggerSingleton() + logger2.warn("2") + logger2.error("3") + with open(log_file, "r") as f: + logs = f.read() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 0)", logs)) == 1 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 1)", logs)) == 1 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 2)", logs)) == 1 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - 3)", logs)) == 1 + ) + + +def test_log_file_is_directory(log_file): + dir = Path(log_file).parent + with raises(errors.LogFileIsADirectory): + Logger(file=dir) + + +def test_wrong_log_path(): + with raises(errors.LogPathDoesNotExist): + Logger(file="/foo/bar/test.log") + + +def test_insufficient_permissions(): + with raises(errors.LogPathInsufficientPermissions): + Logger(file="/root/test.log") + + +def test_unknow_log_level(log_file): + with raises(errors.LogLevelDoesNotExist): + Logger(file=log_file, level="TEST") From d1a36dab23f8161bb8a10e523cda80c5ebf94021 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 12:22:37 +0200 Subject: [PATCH 03/12] Fix tests workflow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 543d66d..973f924 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9, 3.10] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 From 3647f09e4e4ca3ba96ea527ad8e69ede19aecedc Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 12:37:17 +0200 Subject: [PATCH 04/12] Add env var to avoid terminal clear during tests --- .github/workflows/tests.yml | 1 + livelog/reader.py | 9 +++++++-- tests/conftest.py | 14 ++++++++++++++ tests/test_reader.py | 11 +++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 tests/test_reader.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 973f924..e89ea50 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,4 +24,5 @@ jobs: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Run unit tests run: | + export LIVELOG_ENV=TEST pytest diff --git a/livelog/reader.py b/livelog/reader.py index 1b38e34..f44386c 100644 --- a/livelog/reader.py +++ b/livelog/reader.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from os import system, access, name, R_OK +from os import environ, system, access, name, R_OK from time import sleep from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler @@ -29,7 +29,12 @@ class Reader(FileSystemEventHandler): write to output path """ - CLEAR_CMD = "cls" if name == "nt" else "clear" + if environ.get("LIVELOG_ENV") == "TEST": + CLEAR_CMD = "" + elif name == "nt": + CLEAR_CMD = "cls" + else: + CLEAR_CMD = "clear" LEVEL_COLORS = { "ERR!": Fore.RED, "WARN": Fore.YELLOW, diff --git a/tests/conftest.py b/tests/conftest.py index 9f9cb57..dc74243 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,20 @@ import pytest +from livelog import Logger +from pathlib import Path @pytest.fixture(scope="session") def log_file(tmpdir_factory): return tmpdir_factory.mktemp("tmp").join("test.log") + + +@pytest.fixture(scope="function") +def reader_test_file(tmpdir_factory): + log_file = tmpdir_factory.mktemp("tmp").join("test.log") + logger = Logger(file=log_file) + logger.debug("0") + logger.info("1") + logger.warn("2") + logger.error("3") + + return Path(log_file) diff --git a/tests/test_reader.py b/tests/test_reader.py new file mode 100644 index 0000000..bdfa2a9 --- /dev/null +++ b/tests/test_reader.py @@ -0,0 +1,11 @@ +from livelog.reader import Reader + + +def test_default(reader_test_file, capfd): + Reader( + file=reader_test_file, + level="DEBUG", + nocolors=False, + ) + out, err = capfd.readouterr() + assert out == "Hello World!" From 627d96c948f6331452ffb9d5bfe645982d7e04b5 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 13:23:36 +0200 Subject: [PATCH 05/12] Add basic testing for reader --- tests/conftest.py | 8 ++++---- tests/test_reader.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index dc74243..10a1b4c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,9 +12,9 @@ def log_file(tmpdir_factory): def reader_test_file(tmpdir_factory): log_file = tmpdir_factory.mktemp("tmp").join("test.log") logger = Logger(file=log_file) - logger.debug("0") - logger.info("1") - logger.warn("2") - logger.error("3") + logger.debug("debug") + logger.info("info") + logger.warn("warning") + logger.error("error") return Path(log_file) diff --git a/tests/test_reader.py b/tests/test_reader.py index bdfa2a9..6f3686d 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -1,11 +1,36 @@ +from re import findall +from colorama import Style, Fore from livelog.reader import Reader +def escape(string): + return string.replace("\\", "\\\\") + + +DIM = escape(Style.DIM) +BRIGHT = escape(Style.BRIGHT) +NORMAL = escape(Style.NORMAL) +WHITE = escape(Fore.WHITE) +BLUE = escape(Fore.BLUE) +YELLOW = escape(Fore.YELLOW) +RED = escape(Fore.RED) +RESET_ALL = escape(Style.RESET_ALL) + + def test_default(reader_test_file, capfd): Reader( file=reader_test_file, level="DEBUG", nocolors=False, ) - out, err = capfd.readouterr() - assert out == "Hello World!" + out, _ = capfd.readouterr() + + debug_line = findall(r"(^.* - .*debug.*\n)", out)[0] + info_line = findall(r"(\n.* - .*info.*\n)", out)[0] + warning_line = findall(r"(\n.* - .*warning.*\n)", out)[0] + error_line = findall(r"(\n.* - .*error.*\n)", out)[0] + + assert all(x in debug_line for x in (DIM, BRIGHT, NORMAL, WHITE, RESET_ALL)) + assert all(x in info_line for x in (DIM, BRIGHT, NORMAL, BLUE, RESET_ALL)) + assert all(x in warning_line for x in (DIM, BRIGHT, NORMAL, YELLOW, RESET_ALL)) + assert all(x in error_line for x in (DIM, BRIGHT, NORMAL, RED, RESET_ALL)) From 76ae9ff6208adf70a059ae1f92b9aa75db091694 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 13:41:51 +0200 Subject: [PATCH 06/12] Fix level parsing in reader --- livelog/reader.py | 10 +++++++--- tests/test_reader.py | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/livelog/reader.py b/livelog/reader.py index f44386c..da029e0 100644 --- a/livelog/reader.py +++ b/livelog/reader.py @@ -43,7 +43,7 @@ class Reader(FileSystemEventHandler): } LONG_LEVEL_TO_SHORT = { "ERROR": "ERR!", - "WARN": "WARNING", + "WARNING": "WARN", "INFO": "INFO", "DEBUG": "DBUG", } @@ -153,10 +153,14 @@ def filter_log_level(self, lines: list): list: Filtered lines """ - for i, line in enumerate(lines): - level = line[:4] + i = 0 + for _ in range(len(lines)): + level = lines[i][:4] if self.LEVELS[self.level] > self.LEVELS[level]: del lines[i] + continue + i += 1 + return lines def color_line(self, line: str): diff --git a/tests/test_reader.py b/tests/test_reader.py index 6f3686d..58d7dfa 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -34,3 +34,49 @@ def test_default(reader_test_file, capfd): assert all(x in info_line for x in (DIM, BRIGHT, NORMAL, BLUE, RESET_ALL)) assert all(x in warning_line for x in (DIM, BRIGHT, NORMAL, YELLOW, RESET_ALL)) assert all(x in error_line for x in (DIM, BRIGHT, NORMAL, RED, RESET_ALL)) + + +def test_read_level_info(reader_test_file, capfd): + Reader( + file=reader_test_file, + level="INFO", + nocolors=False, + ) + out, _ = capfd.readouterr() + + assert len(findall(r"(.* - .*debug.*\n)", out)) == 0 + assert len(findall(r"(^.* - .*info.*\n)", out)) == 1 + assert len(findall(r"(\n.* - .*warning.*\n)", out)) == 1 + assert len(findall(r"(\n.* - .*error.*\n)", out)) == 1 + + +def test_read_level_warning(reader_test_file, capfd): + Reader( + file=reader_test_file, + level="WARNING", + nocolors=False, + ) + out, _ = capfd.readouterr() + + print(out) + + assert len(findall(r"(.* - .*debug.*\n)", out)) == 0 + assert len(findall(r"(.* - .*info.*\n)", out)) == 0 + assert len(findall(r"^(.* - .*warning.*\n)", out)) == 1 + assert len(findall(r"(\n.* - .*error.*\n)", out)) == 1 + + +def test_read_level_error(reader_test_file, capfd): + Reader( + file=reader_test_file, + level="ERROR", + nocolors=False, + ) + out, _ = capfd.readouterr() + + print(out) + + assert len(findall(r"(.* - .*debug.*\n)", out)) == 0 + assert len(findall(r"(.* - .*info.*\n)", out)) == 0 + assert len(findall(r"(.* - .*warning.*\n)", out)) == 0 + assert len(findall(r"(^.* - .*error.*\n)", out)) == 1 From 7d992e366564a9892fdf83ba8cd1974c967357c0 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 14:13:08 +0200 Subject: [PATCH 07/12] Complete tests for reader + fix reader error handling --- livelog/reader.py | 11 +++--- tests/test_reader.py | 82 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/livelog/reader.py b/livelog/reader.py index da029e0..28d0144 100644 --- a/livelog/reader.py +++ b/livelog/reader.py @@ -85,10 +85,13 @@ def verify_file(self): """Verify if provided file path is a valid log file.""" dir = self.file.parent.resolve() - if self.file.is_dir(): - raise LogFileIsADirectory(path=self.file) - if not dir.is_dir(): - raise LogPathDoesNotExist(path=dir) + try: + if self.file.is_dir(): + raise LogFileIsADirectory(path=self.file) + if not dir.is_dir(): + raise LogPathDoesNotExist(path=dir) + except PermissionError: + raise LogPathInsufficientPermissions(path=dir) if not access(dir, R_OK): raise LogPathInsufficientPermissions(path=dir) diff --git a/tests/test_reader.py b/tests/test_reader.py index 58d7dfa..ea774b7 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -1,6 +1,9 @@ from re import findall +from pytest import raises +from pathlib import Path from colorama import Style, Fore from livelog.reader import Reader +from livelog import errors def escape(string): @@ -17,7 +20,7 @@ def escape(string): RESET_ALL = escape(Style.RESET_ALL) -def test_default(reader_test_file, capfd): +def test_reader_default(reader_test_file, capfd): Reader( file=reader_test_file, level="DEBUG", @@ -36,7 +39,7 @@ def test_default(reader_test_file, capfd): assert all(x in error_line for x in (DIM, BRIGHT, NORMAL, RED, RESET_ALL)) -def test_read_level_info(reader_test_file, capfd): +def test_reader_level_info(reader_test_file, capfd): Reader( file=reader_test_file, level="INFO", @@ -50,7 +53,7 @@ def test_read_level_info(reader_test_file, capfd): assert len(findall(r"(\n.* - .*error.*\n)", out)) == 1 -def test_read_level_warning(reader_test_file, capfd): +def test_reader_level_warning(reader_test_file, capfd): Reader( file=reader_test_file, level="WARNING", @@ -66,7 +69,7 @@ def test_read_level_warning(reader_test_file, capfd): assert len(findall(r"(\n.* - .*error.*\n)", out)) == 1 -def test_read_level_error(reader_test_file, capfd): +def test_reader_level_error(reader_test_file, capfd): Reader( file=reader_test_file, level="ERROR", @@ -80,3 +83,74 @@ def test_read_level_error(reader_test_file, capfd): assert len(findall(r"(.* - .*info.*\n)", out)) == 0 assert len(findall(r"(.* - .*warning.*\n)", out)) == 0 assert len(findall(r"(^.* - .*error.*\n)", out)) == 1 + + +def test_reader_nocolors(reader_test_file, capfd): + Reader( + file=reader_test_file, + level="DEBUG", + nocolors=True, + ) + out, _ = capfd.readouterr() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - debug)", out)) + == 1 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - info)", out)) == 1 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - warning)", out)) + == 1 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - error)", out)) + == 1 + ) + + +def test_reader_level_custom_case(reader_test_file, capfd): + Reader( + file=reader_test_file, + level="iNfO", + nocolors=True, + ) + out, _ = capfd.readouterr() + + assert ( + len(findall(r"(DBUG \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - debug)", out)) + == 0 + ) + assert ( + len(findall(r"(INFO \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - info)", out)) == 1 + ) + assert ( + len(findall(r"(WARN \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - warning)", out)) + == 1 + ) + assert ( + len(findall(r"(ERR! \\| [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} - error)", out)) + == 1 + ) + + +def test_reader_log_file_is_directory(reader_test_file): + dir = Path(reader_test_file).parent + with raises(errors.LogFileIsADirectory): + Reader(file=dir, level="DEBUG", nocolors=True) + + +def test_reader_wrong_log_path(): + with raises(errors.LogPathDoesNotExist): + Reader(file=Path("/foo/bar/test.log"), level="DEBUG", nocolors=True) + + +def test_reader_insufficient_permissions(): + with raises(errors.LogPathInsufficientPermissions): + Reader(file=Path("/root/test.log"), level="DEBUG", nocolors=True) + + +def test_reader_unknow_log_level(reader_test_file): + with raises(errors.LogLevelDoesNotExist): + Reader(file=reader_test_file, level="TEST", nocolors=True) From fdea8eee6dfca7a7ee2563f7d53409fa6f076dfa Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 14:26:24 +0200 Subject: [PATCH 08/12] Add basic cli test --- tests/conftest.py | 8 +++++++- tests/test_cli.py | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/test_cli.py diff --git a/tests/conftest.py b/tests/conftest.py index 10a1b4c..ccfb6a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import pytest -from livelog import Logger from pathlib import Path +from livelog import Logger @pytest.fixture(scope="session") @@ -8,6 +8,12 @@ def log_file(tmpdir_factory): return tmpdir_factory.mktemp("tmp").join("test.log") +@pytest.fixture(scope="function") +def default_log_file(): + logger = Logger() + logger.debug("It works!") + + @pytest.fixture(scope="function") def reader_test_file(tmpdir_factory): log_file = tmpdir_factory.mktemp("tmp").join("test.log") diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..eda087f --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,7 @@ +from subprocess import Popen, PIPE + + +def test_default(default_log_file): + process = Popen("timeout 2 python3 -m livelog".split(), stdout=PIPE) + out, err = process.communicate() + assert "It works" in str(out) From d515cc432a7bed3490f35494740a8b4067e2d11d Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 14:33:19 +0200 Subject: [PATCH 09/12] Complete CLI tests --- tests/test_cli.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index eda087f..a0803ce 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,5 +3,35 @@ def test_default(default_log_file): process = Popen("timeout 2 python3 -m livelog".split(), stdout=PIPE) - out, err = process.communicate() + out, _ = process.communicate() assert "It works" in str(out) + + +def test_custom_file(reader_test_file): + process = Popen( + f"timeout 2 python3 -m livelog -f {reader_test_file}".split(), stdout=PIPE + ) + out, _ = process.communicate() + assert "debug" in str(out) + + +def test_nocolors(reader_test_file): + process = Popen( + f"timeout 2 python3 -m livelog -f {reader_test_file} --nocolors".split(), + stdout=PIPE, + ) + out, _ = process.communicate() + assert "DBUG" in str(out) + assert "INFO" in str(out) + assert "WARN" in str(out) + assert "ERR!" in str(out) + + +def test_custom_level(reader_test_file): + process = Popen( + f"timeout 2 python3 -m livelog -f {reader_test_file} --level=INFO --nocolors".split(), + stdout=PIPE, + ) + out, _ = process.communicate() + assert "INFO" in str(out) + assert "DBUG" not in str(out) From 7f20578cc597992a0297e701b6db32d39c32c5da Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 14:38:53 +0200 Subject: [PATCH 10/12] Set multi OS testing workflows --- .github/workflows/tests.yml | 51 +++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e89ea50..0be8b67 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,8 +3,7 @@ name: tests on: [push, pull_request] jobs: - test: - + linux-test: runs-on: ubuntu-latest strategy: matrix: @@ -26,3 +25,51 @@ jobs: run: | export LIVELOG_ENV=TEST pytest + + + macos-test: + runs-on: macOS-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip + pip install . + pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Run unit tests + run: | + export LIVELOG_ENV=TEST + pytest + + + windows-test: + runs-on: windows-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip + pip install . + pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Run unit tests + run: | + setx LIVELOG_ENV "TEST" + pytest From 4ec9da0969dccba78b9ba9ed40db8537f8009306 Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 17:12:30 +0200 Subject: [PATCH 11/12] Separate tests workflows + fixes --- .github/workflows/linux-tests.yml | 26 ++++++++++ .github/workflows/macos-tests.yml | 28 +++++++++++ .github/workflows/tests.yml | 75 ----------------------------- .github/workflows/windows-tests.yml | 26 ++++++++++ livelog/__main__.py | 2 +- livelog/logger.py | 2 +- livelog/reader.py | 21 ++++++-- tests/__init__.py | 3 ++ tests/conftest.py | 15 ++++++ tests/test_cli.py | 10 ++-- tests/test_logger.py | 7 ++- tests/test_reader.py | 7 ++- 12 files changed, 133 insertions(+), 89 deletions(-) create mode 100644 .github/workflows/linux-tests.yml create mode 100644 .github/workflows/macos-tests.yml delete mode 100644 .github/workflows/tests.yml create mode 100644 .github/workflows/windows-tests.yml diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml new file mode 100644 index 0000000..faf0000 --- /dev/null +++ b/.github/workflows/linux-tests.yml @@ -0,0 +1,26 @@ +name: tests + +on: [push, pull_request] + +jobs: + pytests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip + pip install . + pip install pytest + pip install -r requirements.txt + - name: Run unit tests + run: | + pytest -v diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml new file mode 100644 index 0000000..e5ce41e --- /dev/null +++ b/.github/workflows/macos-tests.yml @@ -0,0 +1,28 @@ +name: tests + +on: [push, pull_request] + +jobs: + pytests: + runs-on: macOS-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip + pip install . + pip install pytest + pip install -r requirements.txt + - name: Run unit tests + run: | + mkdir ~/testfolder + sudo chmod -R u-rwx ~/testfolder + pytest -v diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 0be8b67..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: tests - -on: [push, pull_request] - -jobs: - linux-test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.6, 3.7, 3.8, 3.9] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install --upgrade pip - pip install . - pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Run unit tests - run: | - export LIVELOG_ENV=TEST - pytest - - - macos-test: - runs-on: macOS-latest - strategy: - matrix: - python-version: [3.6, 3.7, 3.8, 3.9] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install --upgrade pip - pip install . - pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Run unit tests - run: | - export LIVELOG_ENV=TEST - pytest - - - windows-test: - runs-on: windows-latest - strategy: - matrix: - python-version: [3.6, 3.7, 3.8, 3.9] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install --upgrade pip - pip install . - pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Run unit tests - run: | - setx LIVELOG_ENV "TEST" - pytest diff --git a/.github/workflows/windows-tests.yml b/.github/workflows/windows-tests.yml new file mode 100644 index 0000000..69b4c2f --- /dev/null +++ b/.github/workflows/windows-tests.yml @@ -0,0 +1,26 @@ +name: tests + +on: [push, pull_request] + +jobs: + pytest: + runs-on: windows-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip + pip install . + pip install pytest + pip install -r requirements.txt + - name: Run unit tests + run: | + pytest -v diff --git a/livelog/__main__.py b/livelog/__main__.py index 560a290..6bf10b9 100644 --- a/livelog/__main__.py +++ b/livelog/__main__.py @@ -45,7 +45,7 @@ def _parse_args(): else: file = ( Path("/tmp/livelog.log") - if system() == "Darwin" + if "darwin" in system().lower() else Path(gettempdir()) / "livelog.log" ) diff --git a/livelog/logger.py b/livelog/logger.py index 8642d92..c15370e 100644 --- a/livelog/logger.py +++ b/livelog/logger.py @@ -55,7 +55,7 @@ def __init__( if file is None: self._file = Path( "/tmp/livelog.log" - if system() == "Darwin" + if "darwin" in system().lower() else Path(gettempdir()) / "livelog.log" ) else: diff --git a/livelog/reader.py b/livelog/reader.py index 28d0144..e105b9a 100644 --- a/livelog/reader.py +++ b/livelog/reader.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- -from os import environ, system, access, name, R_OK +from os import getenv, system, access, R_OK +from sys import exit as _exit +from platform import system as system_ from time import sleep from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler @@ -29,9 +31,9 @@ class Reader(FileSystemEventHandler): write to output path """ - if environ.get("LIVELOG_ENV") == "TEST": + if getenv("LIVELOG_ENV") == "TEST": CLEAR_CMD = "" - elif name == "nt": + elif "windows" in system_().lower(): CLEAR_CMD = "cls" else: CLEAR_CMD = "clear" @@ -74,6 +76,9 @@ def __init__(self, file: str, level: str, nocolors: bool): self.read_index = 0 system(self.CLEAR_CMD) + if not self.file_exists() and getenv("LIVELOG_ENV") == "TEST": + print("FILE NOT FOUND:", self.file) + _exit() while not self.file_exists(): print("File not found, waiting for creation.") sleep(1) @@ -192,6 +197,10 @@ def on_modified(self, *args, **kwargs): def loop_without_event(self): """If inotify instance limit reached, loop without watching file.""" + if getenv("LIVELOG_ENV") == "TEST": + self.print_output() + _exit() + while True: self.print_output() sleep(1) @@ -211,7 +220,11 @@ def start_reader(file: str, level: str, nocolors: bool): observer.schedule(event_handler, file, recursive=True) try: observer.start() - input("") + if getenv("LIVELOG_ENV") == "TEST": + sleep(2) + _exit() + else: + input("") observer.stop() observer.join() except OSError: diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..7543266 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +from os import environ + +environ["LIVELOG_ENV"] = "TEST" diff --git a/tests/conftest.py b/tests/conftest.py index ccfb6a0..c88b9cd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,21 @@ import pytest +from os import getenv from pathlib import Path from livelog import Logger +from platform import system + + +@pytest.fixture(scope="session") +def restricted_dir(): + if "darwin" in system().lower(): + return getenv("HOME") + "/testfolder/" + else: + return "/root/" + + +@pytest.fixture(scope="session") +def system_is_windows(): + return "windows" in system().lower() @pytest.fixture(scope="session") diff --git a/tests/test_cli.py b/tests/test_cli.py index a0803ce..5a687de 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,14 +2,16 @@ def test_default(default_log_file): - process = Popen("timeout 2 python3 -m livelog".split(), stdout=PIPE) + print("python3 -m livelog".split()) + process = Popen("python3 -m livelog".split(), stdout=PIPE) out, _ = process.communicate() assert "It works" in str(out) def test_custom_file(reader_test_file): process = Popen( - f"timeout 2 python3 -m livelog -f {reader_test_file}".split(), stdout=PIPE + f"python3 -m livelog -f {reader_test_file}".split(), + stdout=PIPE, ) out, _ = process.communicate() assert "debug" in str(out) @@ -17,7 +19,7 @@ def test_custom_file(reader_test_file): def test_nocolors(reader_test_file): process = Popen( - f"timeout 2 python3 -m livelog -f {reader_test_file} --nocolors".split(), + f"python3 -m livelog -f {reader_test_file} --nocolors".split(), stdout=PIPE, ) out, _ = process.communicate() @@ -29,7 +31,7 @@ def test_nocolors(reader_test_file): def test_custom_level(reader_test_file): process = Popen( - f"timeout 2 python3 -m livelog -f {reader_test_file} --level=INFO --nocolors".split(), + f"python3 -m livelog -f {reader_test_file} --level=INFO --nocolors".split(), stdout=PIPE, ) out, _ = process.communicate() diff --git a/tests/test_logger.py b/tests/test_logger.py index a264ed3..b709c19 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -223,9 +223,12 @@ def test_wrong_log_path(): Logger(file="/foo/bar/test.log") -def test_insufficient_permissions(): +def test_insufficient_permissions(restricted_dir, system_is_windows): + if system_is_windows: + return + with raises(errors.LogPathInsufficientPermissions): - Logger(file="/root/test.log") + Logger(file=restricted_dir + "test.log") def test_unknow_log_level(log_file): diff --git a/tests/test_reader.py b/tests/test_reader.py index ea774b7..da17a58 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -146,9 +146,12 @@ def test_reader_wrong_log_path(): Reader(file=Path("/foo/bar/test.log"), level="DEBUG", nocolors=True) -def test_reader_insufficient_permissions(): +def test_reader_insufficient_permissions(restricted_dir, system_is_windows): + if system_is_windows: + return + with raises(errors.LogPathInsufficientPermissions): - Reader(file=Path("/root/test.log"), level="DEBUG", nocolors=True) + Reader(file=Path(restricted_dir + "test.log"), level="DEBUG", nocolors=True) def test_reader_unknow_log_level(reader_test_file): From 6c6d4289147199630cb7e8b6e60ba4ef58d10f3d Mon Sep 17 00:00:00 2001 From: PabloLec Date: Sat, 2 Oct 2021 17:16:05 +0200 Subject: [PATCH 12/12] Update workflows name --- .github/workflows/linux-tests.yml | 2 +- .github/workflows/macos-tests.yml | 2 +- .github/workflows/windows-tests.yml | 2 +- README.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index faf0000..e34cc2d 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -1,4 +1,4 @@ -name: tests +name: Linux on: [push, pull_request] diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml index e5ce41e..e6851d0 100644 --- a/.github/workflows/macos-tests.yml +++ b/.github/workflows/macos-tests.yml @@ -1,4 +1,4 @@ -name: tests +name: macOS on: [push, pull_request] diff --git a/.github/workflows/windows-tests.yml b/.github/workflows/windows-tests.yml index 69b4c2f..28419c0 100644 --- a/.github/workflows/windows-tests.yml +++ b/.github/workflows/windows-tests.yml @@ -1,4 +1,4 @@ -name: tests +name: Windows on: [push, pull_request] diff --git a/README.md b/README.md index de90966..44ed843 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # livelog -Work in progress +[![Linux](https://github.com/PabloLec/livelog/actions/workflows/linux-tests.yml/badge.svg)](https://github.com/PabloLec/livelog/actions/workflows/linux-tests.yml)[![macOS](https://github.com/PabloLec/livelog/actions/workflows/macos-tests.yml/badge.svg)](https://github.com/PabloLec/livelog/actions/workflows/macos-tests.yml)[![Windows](https://github.com/PabloLec/livelog/actions/workflows/windows-tests.yml/badge.svg)](https://github.com/PabloLec/livelog/actions/workflows/windows-tests.yml)