From 4bb10395d8154d537a8ff4d5e7b366e5556ff7a6 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 14 Sep 2020 19:03:07 -0700 Subject: [PATCH] working on adding tests for write_budget_markdown --- src/budget_sync/test/mock_path.py | 135 ++++++++++++++++++ src/budget_sync/test/test_mock_path.py | 33 +++++ .../test/test_write_budget_markdown.py | 45 ++++++ src/budget_sync/write_budget_markdown.py | 4 +- 4 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 src/budget_sync/test/mock_path.py create mode 100644 src/budget_sync/test/test_mock_path.py create mode 100644 src/budget_sync/test/test_write_budget_markdown.py diff --git a/src/budget_sync/test/mock_path.py b/src/budget_sync/test/mock_path.py new file mode 100644 index 0000000..5e72e38 --- /dev/null +++ b/src/budget_sync/test/mock_path.py @@ -0,0 +1,135 @@ +from typing import Optional, Dict, Iterator, Tuple, Set, Union +from pathlib import PurePosixPath +from os import PathLike +from posixpath import normpath +from enum import Enum +import errno +from functools import cached_property + + +class MockDir(Enum): + DIR = "" + + +DIR = MockDir.DIR + + +class MockFilesystem: + files: Dict[str, Union[bytes, MockDir]] + current_dir: PurePosixPath + + def __init__(self, + files: Optional[Dict[str, Union[bytes, MockDir]]] = None, + current_dir: Optional[PathLike] = None): + if files is None: + files = {"/": DIR} + self.files = files + if current_dir is None: + current_dir = PurePosixPath("/") + self.current_dir = current_dir + + def normalize_path(self, path: PathLike) -> PurePosixPath: + abs_path = self.current_dir.joinpath(path) + return PurePosixPath(normpath(abs_path)) + + def check_parent(self, normalized_path: PurePosixPath): + parent = self.files.get(str(normalized_path.parent)) + if parent is None: + raise FileNotFoundError(normalized_path) + if parent is not DIR: + raise NotADirectoryError(normalized_path) + + def create(self, path: PathLike, contents: Union[bytes, MockDir]): + normalized_path = self.normalize_path(path) + self.check_parent(normalized_path) + if str(normalized_path) in self.files: + raise FileExistsError(normalized_path) + self.files[str(normalized_path)] = contents + + def create_or_write_file(self, path: PathLike, contents: bytes): + normalized_path = self.normalize_path(path) + self.check_parent(normalized_path) + if self.files.get(str(normalized_path)) is DIR: + raise IsADirectoryError(normalized_path) + self.files[str(normalized_path)] = contents + + def write_existing_file(self, path: PathLike, contents: bytes): + normalized_path = self.normalize_path(path) + self.check_parent(normalized_path) + old_file = self.files.get(str(normalized_path)) + if old_file is None: + raise FileNotFoundError(normalized_path) + if old_file is DIR: + raise IsADirectoryError(normalized_path) + self.files[str(normalized_path)] = contents + + def is_dir(self, path: PathLike) -> bool: + normalized_path = self.normalize_path(path) + return self.files.get(str(normalized_path)) is DIR + + def change_dir(self, path: PathLike): + normalized_path = self.normalize_path(path) + f = self.files.get(str(normalized_path)) + if f is None: + raise FileNotFoundError(normalized_path) + if f is not DIR: + raise NotADirectoryError(normalized_path) + self.current_dir = normalized_path + + def __repr__(self) -> str: + return f"MockFilesystem(files={self.files!r}, " \ + f"current_dir={self.current_dir!r})" + + +class MockPath: + filesystem: MockFilesystem + path: PurePosixPath + + def __init__(self, path: PathLike, filesystem: MockFilesystem): + self.path = PurePosixPath(path) + self.filesystem = filesystem + + @cached_property + def parent(self) -> "MockPath": + return MockPath(self.path.parent, self.filesystem) + + def __eq__(self, other) -> bool: + return self.path == other.path + + def is_dir(self) -> bool: + self.filesystem.is_dir(self.path) + + def mkdir(self, parents: bool = False, exist_ok: bool = False): + # derived from Python's Path.mkdir + try: + self.filesystem.create(self.path, DIR) + except FileNotFoundError: + if not parents or self.parent == self: + raise + self.parent.mkdir(parents=True, exist_ok=True) + self.mkdir(parents=False, exist_ok=exist_ok) + except OSError: + if not exist_ok or not self.is_dir(): + raise + + def joinpath(self, *args) -> "MockPath": + return MockPath(self.path.joinpath(*args), self.filesystem) + + def write_bytes(self, data): + self.filesystem.create_or_write_file(self.path, + bytes(memoryview(data))) + + def write_text(self, data, encoding=None, errors=None): + if not isinstance(data, str): + raise TypeError() + if encoding is None: + encoding = "utf-8" + if errors is None: + errors = "strict" + self.write_bytes(data.encode(encoding=encoding, errors=errors)) + + def __str__(self) -> str: + return str(self.path) + + def __repr__(self) -> str: + return repr(self.path) diff --git a/src/budget_sync/test/test_mock_path.py b/src/budget_sync/test/test_mock_path.py new file mode 100644 index 0000000..6ddcd4c --- /dev/null +++ b/src/budget_sync/test/test_mock_path.py @@ -0,0 +1,33 @@ +from contextlib import contextmanager +import unittest +from budget_sync.test.mock_path import MockPath, MockFilesystem, DIR + + +@contextmanager +def make_filesystem_and_report_if_error(test_case: unittest.TestCase): + filesystem = MockFilesystem() + try: + yield filesystem + except Exception as e: + if isinstance(e, AssertionError): + raise + with test_case.subTest(filesystem=filesystem): + raise + + +class TestMockPath(unittest.TestCase): + # TODO: add more test cases + + def test_mkdir(self): + with make_filesystem_and_report_if_error(self) as filesystem: + MockPath("/dir/", filesystem).mkdir() + self.assertEqual(filesystem.files, + { + "/": DIR, + "/dir": DIR, + }) + # TODO: add more test cases + + +if __name__ == "__main__": + unittest.main() diff --git a/src/budget_sync/test/test_write_budget_markdown.py b/src/budget_sync/test/test_write_budget_markdown.py new file mode 100644 index 0000000..8ff5956 --- /dev/null +++ b/src/budget_sync/test/test_write_budget_markdown.py @@ -0,0 +1,45 @@ +import unittest +from budget_sync.config import Config +from budget_sync.test.mock_bug import MockBug +from budget_sync.test.mock_path import MockPath, DIR +from budget_sync.test.test_mock_path import make_filesystem_and_report_if_error +from budget_sync.budget_graph import BudgetGraph +from budget_sync.write_budget_markdown import write_budget_markdown + + +class TestWriteBudgetMarkdown(unittest.TestCase): + def test(self): + config = Config.from_str( + """ + bugzilla_url = "https://bugzilla.example.com/" + [milestones] + [people."person1"] + output_markdown_file = "person1.mdwn" + [people."person2"] + output_markdown_file = "person2.mdwn" + """) + budget_graph = BudgetGraph([ + MockBug(bug_id=1, + cf_budget_parent=None, + cf_budget="0", + cf_total_budget="0", + cf_nlnet_milestone=None, + cf_payees_list="", + summary=""), + ], config) + self.assertEqual([], budget_graph.get_errors()) + with make_filesystem_and_report_if_error(self) as filesystem: + output_dir = MockPath("/output_dir/", filesystem=filesystem) + write_budget_markdown(budget_graph, output_dir) + self.assertEqual({ + "/": DIR, + "/output_dir": DIR, + '/output_dir/person1.mdwn': b'\n# person1\n\n# Status Tracking\n', + '/output_dir/person2.mdwn': b'\n# person2\n\n# Status Tracking\n', + }, filesystem.files) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/budget_sync/write_budget_markdown.py b/src/budget_sync/write_budget_markdown.py index efe3598..6780a10 100644 --- a/src/budget_sync/write_budget_markdown.py +++ b/src/budget_sync/write_budget_markdown.py @@ -33,8 +33,8 @@ def _markdown_for_person(person: Person, for payment in payments_list: # TODO: finish summary = _markdown_escape(payment.node.bug.summary) - print(f"* [Bug #{payment.node.bug.id}: " - f"{summary}]({payment.node.bug_url})", + print(f"* [Bug #{payment.node.bug.id}]({payment.node.bug_url}): " + f"{summary}", file=buffer) return buffer.getvalue() -- 2.30.2