edq.testing.unittest

  1import datetime
  2import typing
  3import unittest
  4
  5import edq.util.dirent
  6import edq.util.json
  7import edq.util.reflection
  8import edq.util.time
  9
 10FORMAT_STR: str = "\n--- Expected ---\n%s\n--- Actual ---\n%s\n---\n"
 11
 12class BaseTest(unittest.TestCase):
 13    """
 14    A base class for unit tests.
 15    """
 16
 17    maxDiff = None
 18    """ Don't limit the size of diffs. """
 19
 20    testing_timezone: typing.Union[datetime.timezone, None] = edq.util.time.UTC
 21
 22    @classmethod
 23    def setUpClass(cls) -> None:
 24        super().setUpClass()
 25
 26        edq.util.time.set_testing_local_timezone(cls.testing_timezone)
 27
 28    @classmethod
 29    def tearDownClass(cls) -> None:
 30        super().tearDownClass()
 31
 32        edq.util.time.set_testing_local_timezone(None)
 33
 34    def assertJSONEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None:  # pylint: disable=invalid-name
 35        """
 36        Like unittest.TestCase.assertEqual(),
 37        but uses a default assertion message containing the full JSON representation of the arguments.
 38        """
 39
 40        a_json = edq.util.json.dumps(a, indent = 4)
 41        b_json = edq.util.json.dumps(b, indent = 4)
 42
 43        if (message is None):
 44            message = FORMAT_STR % (a_json, b_json)
 45
 46        super().assertEqual(a, b, msg = message)
 47
 48    def assertJSONDictEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None:  # pylint: disable=invalid-name
 49        """
 50        Like unittest.TestCase.assertDictEqual(),
 51        but will try to convert each comparison argument to a dict if it is not already,
 52        and uses a default assertion message containing the full JSON representation of the arguments.
 53        """
 54
 55        if (not isinstance(a, dict)):
 56            if (isinstance(a, edq.util.json.DictConverter)):
 57                a = a.to_dict()
 58            else:
 59                a = vars(a)
 60
 61        if (not isinstance(b, dict)):
 62            if (isinstance(b, edq.util.json.DictConverter)):
 63                b = b.to_dict()
 64            else:
 65                b = vars(b)
 66
 67        a_json = edq.util.json.dumps(a, indent = 4)
 68        b_json = edq.util.json.dumps(b, indent = 4)
 69
 70        if (message is None):
 71            message = FORMAT_STR % (a_json, b_json)
 72
 73        super().assertDictEqual(a, b, msg = message)
 74
 75    def assertJSONListEqual(self, a: typing.List[typing.Any], b: typing.List[typing.Any], message: typing.Union[str, None] = None) -> None:  # pylint: disable=invalid-name
 76        """
 77        Call assertDictEqual(), but supply a default message containing the full JSON representation of the arguments.
 78        """
 79
 80        a_json = edq.util.json.dumps(a, indent = 4)
 81        b_json = edq.util.json.dumps(b, indent = 4)
 82
 83        if (message is None):
 84            message = FORMAT_STR % (a_json, b_json)
 85
 86        super().assertListEqual(a, b, msg = message)
 87
 88    def assertFileHashEqual(self, a: str, b: str) -> None:  # pylint: disable=invalid-name
 89        """
 90        Assert that the hash of two files matches.
 91        Will fail if either path does not exist.
 92        """
 93
 94        if (not edq.util.dirent.exists(a)):
 95            self.fail(f"File does not exist: '{a}'.")
 96
 97        if (not edq.util.dirent.exists(b)):
 98            self.fail(f"File does not exist: '{b}'.")
 99
100        a_hash = edq.util.dirent.hash_file(a)
101        b_hash = edq.util.dirent.hash_file(b)
102
103        self.assertEqual(a_hash, b_hash, msg = f"Hash mismatch: '{a}' ({a_hash}) vs '{b}' ({b_hash}).")
104
105    def format_error_string(self, ex: typing.Union[BaseException, None]) -> str:
106        """
107        Format an error string from an exception so it can be checked for testing.
108        The type of the error will be included,
109        and any nested errors will be joined together.
110        """
111
112        parts = []
113
114        while (ex is not None):
115            type_name = edq.util.reflection.get_qualified_name(ex)
116            message = str(ex)
117
118            parts.append(f"{type_name}: {message}")
119
120            ex = ex.__cause__
121
122        return "; ".join(parts)
FORMAT_STR: str = '\n--- Expected ---\n%s\n--- Actual ---\n%s\n---\n'
class BaseTest(unittest.case.TestCase):
 13class BaseTest(unittest.TestCase):
 14    """
 15    A base class for unit tests.
 16    """
 17
 18    maxDiff = None
 19    """ Don't limit the size of diffs. """
 20
 21    testing_timezone: typing.Union[datetime.timezone, None] = edq.util.time.UTC
 22
 23    @classmethod
 24    def setUpClass(cls) -> None:
 25        super().setUpClass()
 26
 27        edq.util.time.set_testing_local_timezone(cls.testing_timezone)
 28
 29    @classmethod
 30    def tearDownClass(cls) -> None:
 31        super().tearDownClass()
 32
 33        edq.util.time.set_testing_local_timezone(None)
 34
 35    def assertJSONEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None:  # pylint: disable=invalid-name
 36        """
 37        Like unittest.TestCase.assertEqual(),
 38        but uses a default assertion message containing the full JSON representation of the arguments.
 39        """
 40
 41        a_json = edq.util.json.dumps(a, indent = 4)
 42        b_json = edq.util.json.dumps(b, indent = 4)
 43
 44        if (message is None):
 45            message = FORMAT_STR % (a_json, b_json)
 46
 47        super().assertEqual(a, b, msg = message)
 48
 49    def assertJSONDictEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None:  # pylint: disable=invalid-name
 50        """
 51        Like unittest.TestCase.assertDictEqual(),
 52        but will try to convert each comparison argument to a dict if it is not already,
 53        and uses a default assertion message containing the full JSON representation of the arguments.
 54        """
 55
 56        if (not isinstance(a, dict)):
 57            if (isinstance(a, edq.util.json.DictConverter)):
 58                a = a.to_dict()
 59            else:
 60                a = vars(a)
 61
 62        if (not isinstance(b, dict)):
 63            if (isinstance(b, edq.util.json.DictConverter)):
 64                b = b.to_dict()
 65            else:
 66                b = vars(b)
 67
 68        a_json = edq.util.json.dumps(a, indent = 4)
 69        b_json = edq.util.json.dumps(b, indent = 4)
 70
 71        if (message is None):
 72            message = FORMAT_STR % (a_json, b_json)
 73
 74        super().assertDictEqual(a, b, msg = message)
 75
 76    def assertJSONListEqual(self, a: typing.List[typing.Any], b: typing.List[typing.Any], message: typing.Union[str, None] = None) -> None:  # pylint: disable=invalid-name
 77        """
 78        Call assertDictEqual(), but supply a default message containing the full JSON representation of the arguments.
 79        """
 80
 81        a_json = edq.util.json.dumps(a, indent = 4)
 82        b_json = edq.util.json.dumps(b, indent = 4)
 83
 84        if (message is None):
 85            message = FORMAT_STR % (a_json, b_json)
 86
 87        super().assertListEqual(a, b, msg = message)
 88
 89    def assertFileHashEqual(self, a: str, b: str) -> None:  # pylint: disable=invalid-name
 90        """
 91        Assert that the hash of two files matches.
 92        Will fail if either path does not exist.
 93        """
 94
 95        if (not edq.util.dirent.exists(a)):
 96            self.fail(f"File does not exist: '{a}'.")
 97
 98        if (not edq.util.dirent.exists(b)):
 99            self.fail(f"File does not exist: '{b}'.")
100
101        a_hash = edq.util.dirent.hash_file(a)
102        b_hash = edq.util.dirent.hash_file(b)
103
104        self.assertEqual(a_hash, b_hash, msg = f"Hash mismatch: '{a}' ({a_hash}) vs '{b}' ({b_hash}).")
105
106    def format_error_string(self, ex: typing.Union[BaseException, None]) -> str:
107        """
108        Format an error string from an exception so it can be checked for testing.
109        The type of the error will be included,
110        and any nested errors will be joined together.
111        """
112
113        parts = []
114
115        while (ex is not None):
116            type_name = edq.util.reflection.get_qualified_name(ex)
117            message = str(ex)
118
119            parts.append(f"{type_name}: {message}")
120
121            ex = ex.__cause__
122
123        return "; ".join(parts)

A base class for unit tests.

maxDiff = None

Don't limit the size of diffs.

testing_timezone: Optional[datetime.timezone] = datetime.timezone.utc
@classmethod
def setUpClass(cls) -> None:
23    @classmethod
24    def setUpClass(cls) -> None:
25        super().setUpClass()
26
27        edq.util.time.set_testing_local_timezone(cls.testing_timezone)

Hook method for setting up class fixture before running tests in the class.

@classmethod
def tearDownClass(cls) -> None:
29    @classmethod
30    def tearDownClass(cls) -> None:
31        super().tearDownClass()
32
33        edq.util.time.set_testing_local_timezone(None)

Hook method for deconstructing the class fixture after running all tests in the class.

def assertJSONEqual(self, a: Any, b: Any, message: Optional[str] = None) -> None:
35    def assertJSONEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None:  # pylint: disable=invalid-name
36        """
37        Like unittest.TestCase.assertEqual(),
38        but uses a default assertion message containing the full JSON representation of the arguments.
39        """
40
41        a_json = edq.util.json.dumps(a, indent = 4)
42        b_json = edq.util.json.dumps(b, indent = 4)
43
44        if (message is None):
45            message = FORMAT_STR % (a_json, b_json)
46
47        super().assertEqual(a, b, msg = message)

Like unittest.TestCase.assertEqual(), but uses a default assertion message containing the full JSON representation of the arguments.

def assertJSONDictEqual(self, a: Any, b: Any, message: Optional[str] = None) -> None:
49    def assertJSONDictEqual(self, a: typing.Any, b: typing.Any, message: typing.Union[str, None] = None) -> None:  # pylint: disable=invalid-name
50        """
51        Like unittest.TestCase.assertDictEqual(),
52        but will try to convert each comparison argument to a dict if it is not already,
53        and uses a default assertion message containing the full JSON representation of the arguments.
54        """
55
56        if (not isinstance(a, dict)):
57            if (isinstance(a, edq.util.json.DictConverter)):
58                a = a.to_dict()
59            else:
60                a = vars(a)
61
62        if (not isinstance(b, dict)):
63            if (isinstance(b, edq.util.json.DictConverter)):
64                b = b.to_dict()
65            else:
66                b = vars(b)
67
68        a_json = edq.util.json.dumps(a, indent = 4)
69        b_json = edq.util.json.dumps(b, indent = 4)
70
71        if (message is None):
72            message = FORMAT_STR % (a_json, b_json)
73
74        super().assertDictEqual(a, b, msg = message)

Like unittest.TestCase.assertDictEqual(), but will try to convert each comparison argument to a dict if it is not already, and uses a default assertion message containing the full JSON representation of the arguments.

def assertJSONListEqual(self, a: List[Any], b: List[Any], message: Optional[str] = None) -> None:
76    def assertJSONListEqual(self, a: typing.List[typing.Any], b: typing.List[typing.Any], message: typing.Union[str, None] = None) -> None:  # pylint: disable=invalid-name
77        """
78        Call assertDictEqual(), but supply a default message containing the full JSON representation of the arguments.
79        """
80
81        a_json = edq.util.json.dumps(a, indent = 4)
82        b_json = edq.util.json.dumps(b, indent = 4)
83
84        if (message is None):
85            message = FORMAT_STR % (a_json, b_json)
86
87        super().assertListEqual(a, b, msg = message)

Call assertDictEqual(), but supply a default message containing the full JSON representation of the arguments.

def assertFileHashEqual(self, a: str, b: str) -> None:
 89    def assertFileHashEqual(self, a: str, b: str) -> None:  # pylint: disable=invalid-name
 90        """
 91        Assert that the hash of two files matches.
 92        Will fail if either path does not exist.
 93        """
 94
 95        if (not edq.util.dirent.exists(a)):
 96            self.fail(f"File does not exist: '{a}'.")
 97
 98        if (not edq.util.dirent.exists(b)):
 99            self.fail(f"File does not exist: '{b}'.")
100
101        a_hash = edq.util.dirent.hash_file(a)
102        b_hash = edq.util.dirent.hash_file(b)
103
104        self.assertEqual(a_hash, b_hash, msg = f"Hash mismatch: '{a}' ({a_hash}) vs '{b}' ({b_hash}).")

Assert that the hash of two files matches. Will fail if either path does not exist.

def format_error_string(self, ex: Optional[BaseException]) -> str:
106    def format_error_string(self, ex: typing.Union[BaseException, None]) -> str:
107        """
108        Format an error string from an exception so it can be checked for testing.
109        The type of the error will be included,
110        and any nested errors will be joined together.
111        """
112
113        parts = []
114
115        while (ex is not None):
116            type_name = edq.util.reflection.get_qualified_name(ex)
117            message = str(ex)
118
119            parts.append(f"{type_name}: {message}")
120
121            ex = ex.__cause__
122
123        return "; ".join(parts)

Format an error string from an exception so it can be checked for testing. The type of the error will be included, and any nested errors will be joined together.