From bbfcee4fffc553b5dd08f37a79dd6ccddbf340f8 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Thu, 8 Dec 2022 16:32:52 +0100 Subject: uri and some utils --- test/utils/__init__.py | 0 test/utils/test_commons.py | 31 ++++++++ test/utils/test_uri.py | 171 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 test/utils/__init__.py create mode 100644 test/utils/test_commons.py create mode 100644 test/utils/test_uri.py (limited to 'test/utils') diff --git a/test/utils/__init__.py b/test/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/utils/test_commons.py b/test/utils/test_commons.py new file mode 100644 index 0000000..ce73788 --- /dev/null +++ b/test/utils/test_commons.py @@ -0,0 +1,31 @@ +""" + +Part of the tagit test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import unittest + +# objects to test +from bsfs.utils.commons import typename + + +## code ## + +class TestCommons(unittest.TestCase): + def test_typename(self): + class Foo(): pass + self.assertEqual(typename(Foo()), 'Foo') + self.assertEqual(typename('hello'), 'str') + self.assertEqual(typename(123), 'int') + self.assertEqual(typename(None), 'NoneType') + + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## diff --git a/test/utils/test_uri.py b/test/utils/test_uri.py new file mode 100644 index 0000000..976e75d --- /dev/null +++ b/test/utils/test_uri.py @@ -0,0 +1,171 @@ +""" + +Part of the tagit test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import unittest + +# objects to test +from bsfs.utils.uri import URI + + +## code ## + +class TestURI(unittest.TestCase): + + def test_new(self): + # cannot create an unparseable URI + self.assertRaises(ValueError, URI, 'http://') + # returns URI otherwise + self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment'), URI) + + def test_is_parseable(self): + # empty string is a parseable uri + self.assertTrue(URI.is_parseable('')) + # examples from the RFC are parseable + self.assertTrue(URI.is_parseable('foo://example.com:8042/over/there?name=ferret#nose')) + self.assertTrue(URI.is_parseable('urn:example:animal:ferret:nose')) + self.assertTrue(URI.is_parseable('mailto:fred@xample.com')) + self.assertTrue(URI.is_parseable('www.w3.org/Addressing/')) + self.assertTrue(URI.is_parseable('ftp://cnn.example.com&store=breaking_news@10.0.0.1/top_story.htm')) + self.assertTrue(URI.is_parseable('ftp://ftp.is.co.za/rfc/rfc1808.txt')) + self.assertTrue(URI.is_parseable('http://www.ietf.org/rfc/rfc2396.txt')) + self.assertTrue(URI.is_parseable('ldap://[2001:db8::7]/c=GB?objectClass?one')) + self.assertTrue(URI.is_parseable('mailto:John.Doe@example.com')) + self.assertTrue(URI.is_parseable('news:comp.infosystems.www.servers.unix')) + self.assertTrue(URI.is_parseable('tel:+1-816-555-1212')) + self.assertTrue(URI.is_parseable('telnet://192.0.2.16:80/')) + self.assertTrue(URI.is_parseable('urn:oasis:names:specification:docbook:dtd:xml:4.1.2')) + + # uri cannot end with a scheme delimiter + self.assertFalse(URI.is_parseable('http://')) + # port must be a number + self.assertFalse(URI.is_parseable('http://example.com:foo/')) + # the double slash (//) implies a authority + self.assertFalse(URI.is_parseable('http:///path0/path1?query#fragment')) + + def test_compose(self): + self.assertEqual(URI.compose('path'), '/path') + self.assertEqual(URI.compose('/path'), '/path') # leading slash is not repeated + self.assertEqual(URI.compose('path', scheme='scheme'), 'scheme:/path') + self.assertEqual(URI.compose('path', authority='authority'), '//authority/path') + self.assertEqual(URI.compose('path', host='host'), '//host/path') + self.assertEqual(URI.compose('path', user='user'), '/path') # user w/o host is ignored + self.assertEqual(URI.compose('path', host='host', user='user'), '//user@host/path') + self.assertEqual(URI.compose('path', port='port'), '/path') # port w/o host is ignored + self.assertEqual(URI.compose('path', host='host', port=1234), '//host:1234/path') + self.assertEqual(URI.compose('path', host='host', port='1234'), '//host:1234/path') + self.assertRaises(ValueError, URI.compose, 'path', host='host', port='foo') # port must be a number + self.assertEqual(URI.compose('path', host='host', user='foo', port='1234'), '//foo@host:1234/path') + self.assertEqual(URI.compose('path', query='query'), '/path?query') + self.assertEqual(URI.compose('path', fragment='fragment'), '/path#fragment') + self.assertEqual(URI.compose('path', 'scheme', 'authority', 'user', 'host', 1234, 'query', 'fragment'), + 'scheme://user@host:1234/path?query#fragment') + + def test_get(self): + # get returns the respective component + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('scheme'), 'http') + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('authority'), 'user@www.example.com:1234') + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('userinfo'), 'user') + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('host'), 'www.example.com') + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('port'), 1234) + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('path'), '/path0/path1') + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('query'), 'query') + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').get('fragment'), 'fragment') + # get returns a default value if the component is missing + class Foo(): pass + foo = Foo() + self.assertEqual(URI('//user@www.example.com:1234/path0/path1?query#fragment').get('scheme', foo), foo) + self.assertEqual(URI('/path0/path1?query#fragment').get('authority', foo), foo) + self.assertEqual(URI('http://www.example.com:1234/path0/path1?query#fragment').get('userinfo', foo), foo) + self.assertEqual(URI('/path0/path1?query#fragment').get('host', foo), foo) + self.assertEqual(URI('http://user@www.example.com/path0/path1?query#fragment').get('port', foo), foo) + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1#fragment').get('query', foo), foo) + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query').get('fragment', foo), foo) + # can only get components + self.assertRaises(ValueError, URI('').get, 'foobar') + + def test_scheme(self): + # full URI + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').scheme, 'http') + self.assertEqual(URI('ftp://user@www.example.com:1234/path0/path1?query#fragment').scheme, 'ftp') + self.assertEqual(URI('myown://user@www.example.com:1234/path0/path1?query#fragment').scheme, 'myown') + # empty scheme + self.assertRaises(ValueError, getattr, URI('www.example.com/path0/path1?query#fragment'), 'scheme') + # empty URI + self.assertRaises(ValueError, getattr, URI(''), 'scheme') + + def test_authority(self): + # full URI + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').authority, 'user@www.example.com:1234') + # empty authority + self.assertRaises(ValueError, getattr, URI('http/path0/path1?query#fragment'), 'authority') + # empty URI + self.assertRaises(ValueError, getattr, URI(''), 'authority') + + def test_userinfo(self): + # full URI + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').scheme, 'http') + # empty authority + self.assertRaises(ValueError, getattr, URI('http/path0/path1?query#fragment'), 'userinfo') + # empty userinfo + self.assertRaises(ValueError, getattr, URI('http://www.example.com:1234/path0/path1?query#fragment'), 'userinfo') + # empty URI + self.assertRaises(ValueError, getattr, URI(''), 'userinfo') + + def test_host(self): + # full URI + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').host, 'www.example.com') + # IPv4 host + self.assertEqual(URI('http://user@10.0.0.1:1234/path0/path1?query#fragment').host, '10.0.0.1') + # IPv6 host + self.assertEqual(URI('http://user@[::64]:1234/path0/path1?query#fragment').host, '[::64]') + # empty authority + self.assertRaises(ValueError, getattr, URI('http/path0/path1?query#fragment'), 'host') + # empty URI + self.assertRaises(ValueError, getattr, URI(''), 'host') + + def test_port(self): + # full URI + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').port, 1234) + # empty authority + self.assertRaises(ValueError, getattr, URI('http/path0/path1?query#fragment'), 'port') + # empty port + self.assertRaises(ValueError, getattr, URI('http://user@www.example.com/path0/path1?query#fragment'), 'port') + # empty URI + self.assertRaises(ValueError, getattr, URI(''), 'port') + + def test_path(self): + # full URI + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').path, '/path0/path1') + # empty path + self.assertEqual(URI('http://user@www.example.com:1234?query#fragment').path, '') + # empty URI + self.assertEqual(URI('').path, '') + + def test_query(self): + # full URI + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').query, 'query') + # empty query + self.assertRaises(ValueError, getattr, URI('http://user@www.example.com:1234/path0/path1#fragment'), 'query') + # empty URI + self.assertRaises(ValueError, getattr, URI(''), 'query') + + def test_fragment(self): + # full URI + self.assertEqual(URI('http://user@www.example.com:1234/path0/path1?query#fragment').fragment, 'fragment') + # empty fragment + self.assertRaises(ValueError, getattr, URI('http://user@www.example.com:1234/path0/path1?query'), 'fragment') + # empty URI + self.assertRaises(ValueError, getattr, URI(''), 'fragment') + + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## -- cgit v1.2.3 From 547aa08b1f05ec0cdf725c34a7b1d1512b694063 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Thu, 8 Dec 2022 16:35:20 +0100 Subject: remaining essentials: uuid, errors --- test/utils/test_uuid.py | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ test/utils/testfile.t | 1 + 2 files changed, 93 insertions(+) create mode 100644 test/utils/test_uuid.py create mode 100644 test/utils/testfile.t (limited to 'test/utils') diff --git a/test/utils/test_uuid.py b/test/utils/test_uuid.py new file mode 100644 index 0000000..49176d4 --- /dev/null +++ b/test/utils/test_uuid.py @@ -0,0 +1,92 @@ +""" + +Part of the tagit test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import os +import re +import unittest + +# objects to test +from bsfs.utils.uuid import UUID, UCID + + +## code ## + +class TestUUID(unittest.TestCase): + """Test the UUID generator. + + The UUID is expected to generate random strings of 64 characters(0-9, A-F, case insensitive). + Due to the random nature of UUIDs, we cannot actually check if an uid is 'valid' besides + matching the expected format. + + At best, we can check if the number of collisions (values generated repeatedly) is below some + threshold. One would expect the number of collisions to increase with the number of generated uids. + Hence, we only perform an empirical test, whereas the exact test parameters (NUM_SAMPLES, + COLLISIONS_THRESHOLD) are subject to the application requirements. Note that this simple test + cannot replace a thorough statistical analysis. + + """ + + # expected uuid string format + _RX_FORMAT = re.compile('[0-9A-Fa-f]{64}') + + # number of uuids to generate for collisions test + _NUM_SAMPLES = 100_000 + + # number of permitted collisions (less-than test; exclusive) + _COLLISIONS_THRESHOLD = 2 # zero or one collisions to pass the test + + def _test_format(self, uid): + self.assertIsInstance(uid, str) + self.assertTrue(self._RX_FORMAT.fullmatch(uid) is not None) + + def test_call(self): + gen = UUID() + # w/o content + self._test_format(gen()) + # with content + self._test_format(gen('hello world')) + + def test_iter(self): + for _, uid in zip(range(1_000), iter(UUID())): + self._test_format(uid) + + def test_next(self): + gen = UUID() + for _ in range(1_000): + uid = next(gen) + self._test_format(uid) + + def test_collisions(self): + # generated uuids are reasonably unique. + # Note that we cannot guarantee no collisions. + uids = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID())} + self.assertGreater(len(uids), self._NUM_SAMPLES - self._COLLISIONS_THRESHOLD) + # uuids are reasonably unique across instances + uidA = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID())} + uidB = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID())} + self.assertLess(len(uidA & uidB), self._COLLISIONS_THRESHOLD) + # uuids are reasonably unique despite identical seeds. + uidA = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID(seed=123))} + uidB = {uid for _, uid in zip(range(self._NUM_SAMPLES), UUID(seed=123))} + self.assertLess(len(uidA & uidB), self._COLLISIONS_THRESHOLD) + + +class TestUCID(unittest.TestCase): + def setUp(self): + self._checksum = 'a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447' # sha256 + self._path = os.path.join(os.path.dirname(__file__), 'testfile.t') + + def test_from_path(self): + self.assertEqual(UCID.from_path(self._path), self._checksum) + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## diff --git a/test/utils/testfile.t b/test/utils/testfile.t new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/test/utils/testfile.t @@ -0,0 +1 @@ +hello world -- cgit v1.2.3 From 0e52514639b043454425a9cc2317d27e628a1027 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Sun, 18 Dec 2022 13:42:34 +0100 Subject: namespace and uri extensions --- test/utils/test_uri.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'test/utils') diff --git a/test/utils/test_uri.py b/test/utils/test_uri.py index 976e75d..770e65a 100644 --- a/test/utils/test_uri.py +++ b/test/utils/test_uri.py @@ -5,6 +5,7 @@ A copy of the license is provided with the project. Author: Matthias Baumgartner, 2022 """ # imports +import operator import unittest # objects to test @@ -161,6 +162,23 @@ class TestURI(unittest.TestCase): # empty URI self.assertRaises(ValueError, getattr, URI(''), 'fragment') + def test_overloaded(self): + # composition + self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment') + 'hello', URI) + self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment') * 2, URI) + self.assertIsInstance(2 * URI('http://user@www.example.com:1234/{}/path1?{}#fragment'), URI) # rmul + self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment').join(['hello', 'world']) , URI) + # stripping + self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').strip(), URI) + self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').lstrip(), URI) + self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').rstrip(), URI) + # case fold + self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').lower(), URI) + self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').upper(), URI) + # formatting + self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment').format('hello', 'world'), URI) + self.assertIsInstance(URI('http://user@www.example.com:1234/%s/path1?%s#fragment') % ('hello', 'world'), URI) + self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').replace('path0', 'pathX'), URI) ## main ## -- cgit v1.2.3