diff options
-rw-r--r-- | test/utils/__init__.py | 0 | ||||
-rw-r--r-- | test/utils/test_builder.py | 173 | ||||
-rw-r--r-- | test/utils/test_frame.py | 168 | ||||
-rw-r--r-- | test/utils/test_time.py | 159 |
4 files changed, 500 insertions, 0 deletions
diff --git a/test/utils/__init__.py b/test/utils/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/utils/__init__.py diff --git a/test/utils/test_builder.py b/test/utils/test_builder.py new file mode 100644 index 0000000..53a22d0 --- /dev/null +++ b/test/utils/test_builder.py @@ -0,0 +1,173 @@ +""" + +Part of the tagit test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +from functools import partial +import unittest + +# objects to test +from tagit.utils.builder import BuilderBase, InvalidFactoryName + + +## code ## + +class Foo(object): + """Description Foo""" + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + def __eq__(self, other): + return self.args == other.args and self.kwargs == other.kwargs + +def bar(*args, **kwargs): + """Description bar""" + return args, kwargs + +class BuilderStub(BuilderBase): + _factories = { + 'foo': Foo, + 'bar': bar, + } + +class TestBuilderBase(unittest.TestCase): + def test_magics(self): + builder = BuilderStub() + # contains + self.assertIn('foo', builder) + self.assertIn('bar', builder) + self.assertNotIn('foobar', builder) + # iter + self.assertEqual(set(builder), {'foo', 'bar'}) + self.assertCountEqual(list(builder), ['foo', 'bar']) + self.assertEqual(set(BuilderBase()), set()) + # len + self.assertEqual(len(builder), 2) + self.assertEqual(len(BuilderBase()), 0) + # eq + self.assertEqual(BuilderStub(), BuilderStub()) + self.assertNotEqual(BuilderBase(), BuilderStub()) + # hash + self.assertEqual(hash(BuilderStub()), hash(BuilderStub())) + self.assertEqual(hash(BuilderBase()), hash(BuilderBase())) + + def test_get(self): + builder = BuilderStub() + # get + self.assertEqual(builder.get('foo'), Foo) + self.assertEqual(builder.get('bar'), bar) + self.assertRaises(InvalidFactoryName, builder.get, 'foobar') + # getitem + self.assertEqual(builder['foo'], Foo) + self.assertEqual(builder['bar'], bar) + self.assertRaises(InvalidFactoryName, builder.__getitem__, 'foobar') + + def test_keys(self): + self.assertEqual(set(BuilderStub().keys()), {'foo', 'bar'}) + self.assertEqual(set(BuilderBase().keys()), set()) + + def test_describe(self): + builder = BuilderStub() + self.assertEqual(builder.describe('foo'), 'Description Foo') + self.assertEqual(builder.describe('bar'), 'Description bar') + self.assertRaises(InvalidFactoryName, builder.describe, 'foobar') + + def test_prepare(self): + builder = BuilderStub() + # empty args + # foo + part = builder.prepare('foo') + self.assertIsInstance(part, partial) + self.assertEqual((part.func, part.args, part.keywords), (Foo, (), {})) + # bar + part = builder.prepare('bar') + self.assertIsInstance(part, partial) + self.assertEqual((part.func, part.args, part.keywords), (bar, (), {})) + # foobar + self.assertRaises(InvalidFactoryName, builder.prepare, 'foobar') + + # args + # foo + part = builder.prepare('foo', 1, 2, 3) + self.assertIsInstance(part, partial) + self.assertEqual((part.func, part.args, part.keywords), (Foo, (1, 2, 3), {})) + # bar + part = builder.prepare('bar', 1, 2, 3) + self.assertIsInstance(part, partial) + self.assertEqual((part.func, part.args, part.keywords), (bar, (1, 2, 3), {})) + # foobar + self.assertRaises(InvalidFactoryName, builder.prepare, 'foobar', 1, 2, 3) + + # kwargs + # foo + part = builder.prepare('foo', arg1='hello', arg2='world') + self.assertIsInstance(part, partial) + self.assertEqual((part.func, part.args, part.keywords), + (Foo, (), {'arg1': 'hello', 'arg2': 'world'})) + # bar + part = builder.prepare('bar', arg1='hello', arg2='world') + self.assertIsInstance(part, partial) + self.assertEqual((part.func, part.args, part.keywords), + (bar, (), {'arg1': 'hello', 'arg2': 'world'})) + # foobar + self.assertRaises(InvalidFactoryName, builder.prepare, 'foobar', + arg1='hello', arg2='world') + + # mixed + # foo + part = builder.prepare('foo', 1, 2, 3, arg1='hello', arg2='world') + self.assertIsInstance(part, partial) + self.assertEqual((part.func, part.args, part.keywords), + (Foo, (1, 2, 3), {'arg1': 'hello', 'arg2': 'world'})) + # bar + part = builder.prepare('bar', 1, 2, 3, arg1='hello', arg2='world') + self.assertIsInstance(part, partial) + self.assertEqual((part.func, part.args, part.keywords), + (bar, (1, 2, 3), {'arg1': 'hello', 'arg2': 'world'})) + # foobar + self.assertRaises(InvalidFactoryName, builder.prepare, 'foobar', + 1, 2, 3, arg1='hello', arg2='world') + + def test_build(self): + builder = BuilderStub() + # empty args + self.assertEqual(builder.build('foo'), Foo()) + self.assertEqual(builder.build('bar'), bar()) + self.assertRaises(InvalidFactoryName, builder.build, 'foobar') + # args + self.assertEqual(builder.build('foo', 1, 2, 3), Foo(1, 2, 3)) + self.assertEqual(builder.build('bar', 1, 2, 3), bar(1, 2, 3)) + self.assertRaises(InvalidFactoryName, builder.build, 'foobar', 1, 2, 3) + # kwargs + self.assertEqual(builder.build('foo', arg1='hello', arg2='world'), + Foo(arg1='hello', arg2='world')) + self.assertEqual(builder.build('bar', arg1='hello', arg2='world'), + bar(arg1='hello', arg2='world')) + self.assertRaises(InvalidFactoryName, builder.build, 'foobar', + arg1='hello', arg2='world') + # mixed + self.assertEqual( + builder.build('foo', 1, 2, 3, arg1='hello', arg2='world'), + Foo(1, 2, 3, arg1='hello', arg2='world')) + self.assertEqual( + builder.build('bar', 1, 2, 3, arg1='hello', arg2='world'), + bar(1, 2, 3, arg1='hello', arg2='world')) + self.assertRaises(InvalidFactoryName, builder.build, 'foobar', + 1, 2, 3, arg1='hello', arg2='world') + + def test_key_from_instance(self): + builder = BuilderStub() + self.assertEqual(builder.key_from_instance(Foo()), 'foo') + self.assertEqual(builder.key_from_instance(bar), 'bar') + self.assertRaises(KeyError, builder.key_from_instance, 'foobar') + self.assertRaises(KeyError, builder.key_from_instance, BuilderStub()) + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## diff --git a/test/utils/test_frame.py b/test/utils/test_frame.py new file mode 100644 index 0000000..79bfa3b --- /dev/null +++ b/test/utils/test_frame.py @@ -0,0 +1,168 @@ +""" + +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 tagit.utils.frame import Frame + + +## code ## + +class EntityStub(object): + def __init__(self, guid): + self.guid = guid + def __eq__(self, other): + return self.guid == other.guid + +class LibraryStub(object): + def __init__(self, guids): + self.guids = guids + def entity(self, guid): + if guid in self.guids: + return EntityStub(guid) + else: + raise KeyError(guid) + +class TestFrame(unittest.TestCase): + def setUp(self): + self.ent0 = EntityStub('ent0') + self.ent1 = EntityStub('ent1') + self.ent2 = EntityStub('ent2') + self.ent3 = EntityStub('ent3') + self.ent4 = EntityStub('ent4') + self.ent5 = EntityStub('ent5') + self.lib = LibraryStub(['ent0', 'ent1', 'ent2', 'ent3']) + + def test_properties(self): + # plain test + frame = Frame(self.ent0, [self.ent1, self.ent2], 123) + self.assertEqual(frame, { + 'cursor': self.ent0, + 'selection': [self.ent1, self.ent2], + 'offset': 123, + }) + self.assertEqual(frame.cursor, self.ent0) + self.assertEqual(frame.selection, [self.ent1, self.ent2]) + self.assertEqual(frame.offset, 123) + + # empty selection + frame = Frame(self.ent0, [], 123) + self.assertEqual(frame, { + 'cursor': self.ent0, + 'selection': [], + 'offset': 123, + }) + self.assertEqual(frame.cursor, self.ent0) + self.assertEqual(frame.selection, []) + self.assertEqual(frame.offset, 123) + + # no cursor + frame = Frame(None, [self.ent0], 123) + self.assertEqual(frame, { + 'cursor': None, + 'selection': [self.ent0], + 'offset': 123, + }) + self.assertEqual(frame.cursor, None) + self.assertEqual(frame.selection, [self.ent0]) + self.assertEqual(frame.offset, 123) + + # no selection + frame = Frame(self.ent0, None, 123) + self.assertEqual(frame, { + 'cursor': self.ent0, + 'selection': [], + 'offset': 123 + }) + self.assertEqual(frame.cursor, self.ent0) + self.assertEqual(frame.selection, []) + self.assertEqual(frame.offset, 123) + + # Not tested: different list-like selection formats (tuple, list) + # This seems ok since such formats would be admissible. + + def test_copy(self): + frameA = Frame(self.ent0, [self.ent1, self.ent2], 123) + self.assertEqual(frameA, { + 'cursor': self.ent0, + 'selection': [self.ent1, self.ent2], + 'offset': 123 + }) + + frameB = frameA.copy() + self.assertEqual(frameB, { + 'cursor': self.ent0, + 'selection': [self.ent1, self.ent2], + 'offset': 123 + }) + + # robust against frame changes + frameA['cursor'] = self.ent3 + self.assertEqual(frameB.cursor, self.ent0) + frameA['selection'].append(self.ent3) + self.assertEqual(frameB.selection, [self.ent1, self.ent2, self.ent3]) + frameA['selection'] = [self.ent4, self.ent5] + self.assertEqual(frameB.selection, [self.ent1, self.ent2, self.ent3]) + frameA['offset'] = 321 + self.assertEqual(frameB.offset, 123) + + # ignorant to object changes + self.ent0.guid = 'abc' + self.assertEqual(frameA.cursor.guid, 'ent3') + self.assertEqual(frameB.cursor.guid, 'abc') + + def test_serialization(self): + # empty frame + frame = Frame() + self.assertEqual(frame, + Frame.from_serialized(self.lib, frame.serialize(), ignore_errors=False)) + # with cursor + frame = Frame(self.ent1) + self.assertEqual(frame, + Frame.from_serialized(self.lib, frame.serialize(), ignore_errors=False)) + + # with selection + frame = Frame(selection=[self.ent0, self.ent3]) + self.assertEqual(frame, + Frame.from_serialized(self.lib, frame.serialize(), ignore_errors=False)) + + # with offset + frame = Frame(offset=554) + self.assertEqual(frame, + Frame.from_serialized(self.lib, frame.serialize(), ignore_errors=False)) + + # full frame + frame = Frame(self.ent1, [self.ent0, self.ent2], 482) + self.assertEqual(frame, + Frame.from_serialized(self.lib, frame.serialize(), ignore_errors=False)) + + # with invalid values + frame = Frame(self.ent5, [self.ent0, self.ent2], 482) + self.assertRaises(KeyError, + Frame.from_serialized, self.lib, frame.serialize(), ignore_errors=False) + frame = Frame(self.ent1, [self.ent5, self.ent2], 482) + self.assertRaises(KeyError, + Frame.from_serialized, self.lib, frame.serialize(), ignore_errors=False) + + # ignoring invalid values + frame = Frame(self.ent5, [self.ent0, self.ent2], 482) + self.assertEqual( + Frame(None, [self.ent0, self.ent2], 482), + Frame.from_serialized(self.lib, frame.serialize(), ignore_errors=True)) + frame = Frame(self.ent1, [self.ent5, self.ent2], 482) + self.assertEqual( + Frame(self.ent1, [self.ent2], 482), + Frame.from_serialized(self.lib, frame.serialize(), ignore_errors=True)) + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## diff --git a/test/utils/test_time.py b/test/utils/test_time.py new file mode 100644 index 0000000..a4ffeac --- /dev/null +++ b/test/utils/test_time.py @@ -0,0 +1,159 @@ +""" + +Part of the tagit test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +from datetime import datetime, timezone +import math +import os +import shutil +import sys +import tempfile +import unittest + +# external imports +import pyexiv2 + +# inner-module imports +from tagit.parsing import parse_datetime + +# objects to test +from tagit.shared import time as ttime + +# constants +sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')) +from testdata import IMAGE_VALID + + +## code ## + +class TestTTime(unittest.TestCase): + def setUp(self): + self.img = tempfile.mkstemp(prefix='tagit_')[1] + shutil.copy(IMAGE_VALID['path'], self.img) + self.empty = tempfile.mkstemp(prefix='tagit_')[1] + # ensure correct file date (fixed, in utc) + #os.system('touch -d "29 Oct 2015 14:20:56" {}'.format(self.empty)) + os.utime(self.empty, (1446124856, 1446124856)) + + def tearDown(self): + if os.path.exists(self.img): os.unlink(self.img) + if os.path.exists(self.empty): os.unlink(self.empty) + + def test_conversions(self): + """ + Files, via os.stat (Timestamp in UTC, Local timezone) + Images, from Exif (Timestamp in camera local time, No timezone) + Images, from Xmp (Timestamp in camera local time, Timezone from Xmp) + now (Timestamp in UTC, Local timezone) + Database (timestamp in UTC, timestamp in local time) + """ + # prepare + stat = os.stat(self.empty) + meta = pyexiv2.ImageMetadata(self.img) + meta.read() + # Create time objects in the proper format + + # Manually defined + dt_manual = datetime(2015, 10, 29, 14, 20, 56) + + # os.stat + dt_stat = datetime.fromtimestamp(stat.st_mtime) + + # exif + dt_exif = meta['Exif.Photo.DateTimeOriginal'].value.replace(tzinfo=ttime.NoTimeZone) + + # xmp + dt_xmp = meta['Xmp.exif.DateTimeOriginal'].value + + # user-specified + dt_user = parse_datetime("29.10.2015, 14:20:56") + + # now + dt_now = datetime.now() + + # UTC offset + offset_ref = dt_manual.replace(tzinfo=timezone.utc).timestamp() - dt_manual.timestamp() + offset_ref /= 3600 + offset_now = dt_now.replace(tzinfo=timezone.utc).timestamp() - dt_now.timestamp() + offset_now /= 3600 + + # Conversions + # Comparable local time + self.assertEqual(math.floor(ttime.timestamp_loc(dt_manual) % (3600 * 24) / 3600), 14 ) + self.assertEqual(math.ceil( ttime.timestamp_loc(dt_manual) % (3600 * 24) / 3600), 15 ) + self.assertEqual(ttime.timestamp_loc(dt_manual) % (3600 * 24), 51656 ) + self.assertEqual(ttime.timestamp_loc(dt_manual), ttime.timestamp_loc(dt_stat) ) + self.assertEqual(ttime.timestamp_loc(dt_manual), ttime.timestamp_loc(dt_exif) ) + self.assertEqual(ttime.timestamp_loc(dt_manual), ttime.timestamp_loc(dt_xmp) ) + self.assertEqual(ttime.timestamp_loc(dt_manual), ttime.timestamp_loc(dt_user) ) + + # UTC offset + self.assertEqual(ttime.utcoffset(dt_manual), offset_ref) + self.assertEqual(ttime.utcoffset(dt_stat), offset_ref) + self.assertEqual(ttime.utcoffset(dt_exif), None) + self.assertEqual(ttime.utcoffset(dt_xmp), 11) + self.assertEqual(ttime.utcoffset(dt_user), offset_ref) + self.assertEqual(ttime.utcoffset(dt_now), offset_now) + + # Comparable UTC + self.assertEqual(ttime.timestamp_utc(dt_now), + ttime.timestamp_loc(dt_now) - 3600 * offset_now) + self.assertEqual(ttime.timestamp_utc(dt_manual), + ttime.timestamp_loc(dt_manual) - 3600 * offset_ref ) + self.assertEqual(ttime.timestamp_utc(dt_stat), + ttime.timestamp_loc(dt_stat) - 3600 * offset_ref) + self.assertEqual(ttime.timestamp_utc(dt_user), + ttime.timestamp_loc(dt_user) - 3600 * offset_ref) + self.assertEqual(ttime.timestamp_utc(dt_exif), + ttime.timestamp_loc(dt_exif)) + self.assertEqual(ttime.timestamp_utc(dt_xmp), + ttime.timestamp_loc(dt_xmp) - 3600 * ttime.utcoffset(dt_xmp)) + + # Conversion back + self.assertEqual(ttime.from_timestamp_utc( + ttime.timestamp_utc(dt_now)).timestamp(), dt_now.timestamp()) + self.assertEqual(ttime.from_timestamp_utc( + ttime.timestamp_utc(dt_manual)).timestamp(), dt_manual.timestamp()) + self.assertEqual(ttime.from_timestamp_utc( + ttime.timestamp_utc(dt_stat)).timestamp(), dt_stat.timestamp()) + self.assertEqual(ttime.from_timestamp_utc( + ttime.timestamp_utc(dt_user)).timestamp(), dt_user.timestamp()) + self.assertEqual(ttime.from_timestamp_utc( + ttime.timestamp_utc(dt_exif)).timestamp(), dt_exif.timestamp()) + self.assertEqual(ttime.from_timestamp_utc( + ttime.timestamp_utc(dt_xmp)).timestamp(), dt_xmp.timestamp()) + + self.assertEqual(ttime.timestamp_loc(ttime.from_timestamp_loc( + ttime.timestamp_loc(dt_now))), ttime.timestamp_loc(dt_now)) + self.assertEqual(ttime.timestamp_loc(ttime.from_timestamp_loc( + ttime.timestamp_loc(dt_manual))), ttime.timestamp_loc(dt_manual)) + self.assertEqual(ttime.timestamp_loc(ttime.from_timestamp_loc( + ttime.timestamp_loc(dt_stat))), ttime.timestamp_loc(dt_stat)) + self.assertEqual(ttime.timestamp_loc(ttime.from_timestamp_loc( + ttime.timestamp_loc(dt_user))), ttime.timestamp_loc(dt_user)) + self.assertEqual(ttime.timestamp_loc(ttime.from_timestamp_loc( + ttime.timestamp_loc(dt_exif))), ttime.timestamp_loc(dt_exif)) + self.assertEqual(ttime.timestamp_loc(ttime.from_timestamp_loc( + ttime.timestamp_loc(dt_xmp))), ttime.timestamp_loc(dt_xmp)) + + # Conversion to local time + TZO for storage.library + # This is used to store file infos + + # Retrieval in local time + # This is used to search for files in a given time range + + # Retrieval in local time + TZO from storage.library + + # Retrieve in UTC from storage.library + # This is used to compare files (e.g. syncing) across platforms + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## |