X-Git-Url: https://git.madduck.net/etc/taskwarrior.git/blobdiff_plain/23116bed0b21d2e72a304fa9f653474c3679a1a8..1225677a596f1c36eb54ed15459ac9a40f0233e2:/tasklib/tests.py?ds=inline diff --git a/tasklib/tests.py b/tasklib/tests.py index adcb3c8..0d4b50e 100644 --- a/tasklib/tests.py +++ b/tasklib/tests.py @@ -11,7 +11,10 @@ import sys import tempfile import unittest -from .task import TaskWarrior, Task, ReadOnlyDictView, local_zone, DATE_FORMAT +from .backends import TaskWarrior +from .task import Task, ReadOnlyDictView +from .lazy import LazyUUIDTask, LazyUUIDTaskSet +from .serializing import DATE_FORMAT, local_zone # http://taskwarrior.org/docs/design/task.html , Section: The Attributes TASK_STANDARD_ATTRS = ( @@ -37,7 +40,9 @@ TASK_STANDARD_ATTRS = ( 'annotations', ) -total_seconds_2_6 = lambda x: x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600 + +def total_seconds_2_6(x): + return x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600 class TasklibTest(unittest.TestCase): @@ -78,6 +83,39 @@ class TaskFilterTest(TasklibTest): self.tw.tasks.all()[0].done() self.assertEqual(len(self.tw.tasks.completed()), 1) + def test_deleted_empty(self): + Task(self.tw, description="test task").save() + self.assertEqual(len(self.tw.tasks.deleted()), 0) + + def test_deleted_non_empty(self): + Task(self.tw, description="test task").save() + self.assertEqual(len(self.tw.tasks.deleted()), 0) + self.tw.tasks.all()[0].delete() + self.assertEqual(len(self.tw.tasks.deleted()), 1) + + def test_waiting_empty(self): + Task(self.tw, description="test task").save() + self.assertEqual(len(self.tw.tasks.waiting()), 0) + + def test_waiting_non_empty(self): + Task(self.tw, description="test task").save() + self.assertEqual(len(self.tw.tasks.waiting()), 0) + + t = self.tw.tasks.all()[0] + t['wait'] = datetime.datetime.now() + datetime.timedelta(days=1) + t.save() + + self.assertEqual(len(self.tw.tasks.waiting()), 1) + + def test_recurring_empty(self): + Task(self.tw, description="test task").save() + self.assertEqual(len(self.tw.tasks.recurring()), 0) + + def test_recurring_non_empty(self): + Task(self.tw, description="test task", recur="daily", + due=datetime.datetime.now()).save() + self.assertEqual(len(self.tw.tasks.recurring()), 1) + def test_filtering_by_attribute(self): Task(self.tw, description="no priority task").save() Task(self.tw, priority="H", description="high priority task").save() @@ -88,7 +126,8 @@ class TaskFilterTest(TasklibTest): # Assert that the correct tasks are returned high_priority_task = self.tw.tasks.get(priority="H") - self.assertEqual(high_priority_task['description'], "high priority task") + self.assertEqual(high_priority_task['description'], + "high priority task") def test_filtering_by_empty_attribute(self): Task(self.tw, description="no priority task").save() @@ -241,7 +280,7 @@ class TaskTest(TasklibTest): def test_create_unsaved_task(self): # Make sure a new task is not saved unless explicitly called for - t = Task(self.tw, description="test task") + Task(self.tw, description="test task") self.assertEqual(len(self.tw.tasks.all()), 0) # TODO: once python 2.6 compatiblity is over, use context managers here @@ -284,6 +323,19 @@ class TaskTest(TasklibTest): self.assertRaises(Task.CompletedTask, t.start) + def test_add_completed_task(self): + t = Task(self.tw, description="test", status="completed", + end=datetime.datetime.now()) + t.save() + + def test_add_multiple_completed_tasks(self): + t1 = Task(self.tw, description="test1", status="completed", + end=datetime.datetime.now()) + t2 = Task(self.tw, description="test2", status="completed", + end=datetime.datetime.now()) + t1.save() + t2.save() + def test_complete_deleted_task(self): t = Task(self.tw, description="test task") t.save() @@ -372,7 +424,7 @@ class TaskTest(TasklibTest): def test_stopping_task(self): t = Task(self.tw, description="test task") - now = t.datetime_normalizer(datetime.datetime.now()) + t.datetime_normalizer(datetime.datetime.now()) t.save() t.start() t.stop() @@ -565,6 +617,23 @@ class TaskTest(TasklibTest): t2 = self.tw.tasks.get(uuid=t1['uuid']) self.assertEqual(t1.__hash__(), t2.__hash__()) + def test_hash_unequal_unsaved_tasks(self): + # Compare the hash of the task using two different objects + t1 = Task(self.tw, description="test task 1") + t2 = Task(self.tw, description="test task 2") + + self.assertNotEqual(t1.__hash__(), t2.__hash__()) + + def test_hash_unequal_saved_tasks(self): + # Compare the hash of the task using two different objects + t1 = Task(self.tw, description="test task 1") + t2 = Task(self.tw, description="test task 2") + + t1.save() + t2.save() + + self.assertNotEqual(t1.__hash__(), t2.__hash__()) + def test_adding_task_with_priority(self): t = Task(self.tw, description="test task", priority="M") t.save() @@ -640,7 +709,7 @@ class TaskTest(TasklibTest): self.assertEqual(set(t._modified_fields), set()) # Get the task by using a filter by UUID - t2 = self.tw.tasks.get(uuid=t['uuid']) + self.tw.tasks.get(uuid=t['uuid']) # Reassigning the fields with the same values now should not produce # modified fields @@ -654,7 +723,7 @@ class TaskTest(TasklibTest): t = Task(self.tw) for field in TASK_STANDARD_ATTRS: - value = t[field] + t[field] self.assertEqual(set(t._modified_fields), set()) @@ -680,16 +749,24 @@ class TaskTest(TasklibTest): def test_adding_tag_by_appending(self): t = Task(self.tw, description="test task", tags=['test1']) t.save() - t['tags'].append('test2') + t['tags'].add('test2') + t.save() + self.assertEqual(t['tags'], set(['test1', 'test2'])) + + def test_adding_tag_twice(self): + t = Task(self.tw, description="test task", tags=['test1']) + t.save() + t['tags'].add('test2') + t['tags'].add('test2') t.save() - self.assertEqual(t['tags'], ['test1', 'test2']) + self.assertEqual(t['tags'], set(['test1', 'test2'])) def test_adding_tag_by_appending_empty(self): t = Task(self.tw, description="test task") t.save() - t['tags'].append('test') + t['tags'].add('test') t.save() - self.assertEqual(t['tags'], ['test']) + self.assertEqual(t['tags'], set(['test'])) def test_serializers_returning_empty_string_for_none(self): # Test that any serializer returns '' when passed None @@ -703,7 +780,7 @@ class TaskTest(TasklibTest): # Test that any deserializer returns empty value when passed '' t = Task(self.tw) deserializers = [getattr(t, deserializer_name) for deserializer_name in - filter(lambda x: x.startswith('deserialize_'), dir(t))] + filter(lambda x: x.startswith('deserialize_'), dir(t))] for deserializer in deserializers: self.assertTrue(deserializer('') in (None, [], set())) @@ -721,6 +798,15 @@ class TaskTest(TasklibTest): t.save() self.assertEqual(len(self.tw.tasks.pending()), 2) + def test_spawned_task_parent(self): + today = datetime.date.today() + t = Task(self.tw, description="brush teeth", + due=today, recur="daily") + t.save() + + spawned = self.tw.tasks.pending().get(due=today) + assert spawned['parent'] == t + def test_modify_number_of_tasks_at_once(self): for i in range(1, 100): Task(self.tw, description="test task %d" % i, tags=['test']).save() @@ -735,7 +821,8 @@ class TaskTest(TasklibTest): def test_return_all_from_failed_executed_command(self): Task(self.tw, description="test task", tags=['test']).save() out, err, rc = self.tw.execute_command(['countinvalid'], - return_all=True, allow_failure=False) + return_all=True, + allow_failure=False) self.assertNotEqual(rc, 0) @@ -748,7 +835,16 @@ class TaskFromHookTest(TasklibTest): '"start":"20141119T152233Z",' '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}') - input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' + + input_add_data_recurring = six.StringIO( + '{"description":"Mow the lawn",' + '"entry":"20160210T224304Z",' + '"parent":"62da6227-519c-42c2-915d-dccada926ad7",' + '"recur":"weekly",' + '"status":"pending",' + '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}') + + input_modify_data = six.StringIO( + input_add_data.getvalue() + '\n' + '{"description":"Buy some milk finally",' '"entry":"20141118T050231Z",' '"status":"completed",' @@ -756,17 +852,23 @@ class TaskFromHookTest(TasklibTest): exported_raw_data = ( '{"project":"Home",' - '"due":"20150101T232323Z",' - '"description":"test task"}') + '"due":"20150101T232323Z",' + '"description":"test task"}') def test_setting_up_from_add_hook_input(self): - t = Task.from_input(input_file=self.input_add_data, warrior=self.tw) + t = Task.from_input(input_file=self.input_add_data, backend=self.tw) self.assertEqual(t['description'], "Buy some milk") self.assertEqual(t.pending, True) + def test_setting_up_from_add_hook_input_recurring(self): + t = Task.from_input(input_file=self.input_add_data_recurring, + backend=self.tw) + self.assertEqual(t['description'], "Mow the lawn") + self.assertEqual(t.pending, True) + def test_setting_up_from_modified_hook_input(self): t = Task.from_input(input_file=self.input_modify_data, modify=True, - warrior=self.tw) + backend=self.tw) self.assertEqual(t['description'], "Buy some milk finally") self.assertEqual(t.pending, False) self.assertEqual(t.completed, True) @@ -778,8 +880,9 @@ class TaskFromHookTest(TasklibTest): def test_export_data(self): t = Task(self.tw, description="test task", - project="Home", - due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23))) + project="Home", + due=pytz.utc.localize(datetime.datetime(2015, 1, 1, + 23, 23, 23))) # Check that the output is a permutation of: # {"project":"Home","description":"test task","due":"20150101232323Z"} @@ -792,13 +895,14 @@ class TaskFromHookTest(TasklibTest): self.assertTrue(any(t.export_data() == expected for expected in allowed_output)) + class TimezoneAwareDatetimeTest(TasklibTest): def setUp(self): super(TimezoneAwareDatetimeTest, self).setUp() self.zone = local_zone - self.localdate_naive = datetime.datetime(2015,2,2) - self.localtime_naive = datetime.datetime(2015,2,2,0,0,0) + self.localdate_naive = datetime.datetime(2015, 2, 2) + self.localtime_naive = datetime.datetime(2015, 2, 2, 0, 0, 0) self.localtime_aware = self.zone.localize(self.localtime_naive) self.utctime_aware = self.localtime_aware.astimezone(pytz.utc) @@ -862,6 +966,7 @@ class TimezoneAwareDatetimeTest(TasklibTest): self.assertEqual(json.loads(t.export_data())['due'], self.utctime_aware.strftime(DATE_FORMAT)) + class DatetimeStringTest(TasklibTest): def test_simple_now_conversion(self): @@ -877,7 +982,7 @@ class DatetimeStringTest(TasklibTest): now = local_zone.localize(datetime.datetime.now()) # Assert that both times are not more than 5 seconds apart - if sys.version_info < (2,7): + if sys.version_info < (2, 7): self.assertTrue(total_seconds_2_6(now - t['due']) < 5) self.assertTrue(total_seconds_2_6(t['due'] - now) < 5) else: @@ -923,7 +1028,7 @@ class DatetimeStringTest(TasklibTest): hour=23, minute=59, second=59 - )) - datetime.timedelta(0,4 * 30 * 86400) + )) - datetime.timedelta(0, 4 * 30 * 86400) self.assertEqual(due_date, t['due']) def test_filtering_with_string_datetime(self): @@ -936,10 +1041,11 @@ class DatetimeStringTest(TasklibTest): return t = Task(self.tw, description="test task", - due=datetime.datetime.now() - datetime.timedelta(0,2)) + due=datetime.datetime.now() - datetime.timedelta(0, 2)) t.save() self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1) + class AnnotationTest(TasklibTest): def setUp(self): @@ -974,12 +1080,12 @@ class AnnotationTest(TasklibTest): self.assertEqual(len(task['annotations']), 0) def test_annotation_after_modification(self): - task = self.tw.tasks.get() - task['project'] = 'test' - task.add_annotation('I should really do this task') - self.assertEqual(task['project'], 'test') - task.save() - self.assertEqual(task['project'], 'test') + task = self.tw.tasks.get() + task['project'] = 'test' + task.add_annotation('I should really do this task') + self.assertEqual(task['project'], 'test') + task.save() + self.assertEqual(task['project'], 'test') def test_serialize_annotations(self): # Test that serializing annotations is possible @@ -1014,40 +1120,43 @@ class UnicodeTest(TasklibTest): Task(self.tw, description="test task").save() self.tw.tasks.get() + class ReadOnlyDictViewTest(unittest.TestCase): def setUp(self): - self.sample = dict(l=[1,2,3], d={'k':'v'}) + self.sample = dict(sample_list=[1, 2, 3], sample_dict={'key': 'value'}) self.original_sample = copy.deepcopy(self.sample) self.view = ReadOnlyDictView(self.sample) def test_readonlydictview_getitem(self): - l = self.view['l'] - self.assertEqual(l, self.sample['l']) + sample_list = self.view['sample_list'] + self.assertEqual(sample_list, self.sample['sample_list']) # Assert that modification changed only copied value - l.append(4) - self.assertNotEqual(l, self.sample['l']) + sample_list.append(4) + self.assertNotEqual(sample_list, self.sample['sample_list']) # Assert that viewed dict is not changed self.assertEqual(self.sample, self.original_sample) def test_readonlydictview_contains(self): - self.assertEqual('l' in self.view, 'l' in self.sample) - self.assertEqual('d' in self.view, 'd' in self.sample) - self.assertEqual('k' in self.view, 'k' in self.sample) + self.assertEqual('sample_list' in self.view, + 'sample_list' in self.sample) + self.assertEqual('sample_dict' in self.view, + 'sample_dict' in self.sample) + self.assertEqual('key' in self.view, 'key' in self.sample) # Assert that viewed dict is not changed self.assertEqual(self.sample, self.original_sample) def test_readonlydictview_iter(self): - self.assertEqual(list(k for k in self.view), - list(k for k in self.sample)) + self.assertEqual(list(key for key in self.view), + list(key for key in self.sample)) # Assert the view is correct after modification self.sample['new'] = 'value' - self.assertEqual(list(k for k in self.view), - list(k for k in self.sample)) + self.assertEqual(list(key for key in self.view), + list(key for key in self.sample)) def test_readonlydictview_len(self): self.assertEqual(len(self.view), len(self.sample)) @@ -1057,12 +1166,12 @@ class ReadOnlyDictViewTest(unittest.TestCase): self.assertEqual(len(self.view), len(self.sample)) def test_readonlydictview_get(self): - l = self.view.get('l') - self.assertEqual(l, self.sample.get('l')) + sample_list = self.view.get('sample_list') + self.assertEqual(sample_list, self.sample.get('sample_list')) # Assert that modification changed only copied value - l.append(4) - self.assertNotEqual(l, self.sample.get('l')) + sample_list.append(4) + self.assertNotEqual(sample_list, self.sample.get('sample_list')) # Assert that viewed dict is not changed self.assertEqual(self.sample, self.original_sample) @@ -1086,3 +1195,200 @@ class ReadOnlyDictViewTest(unittest.TestCase): view_list_item.append(4) self.assertNotEqual(view_values, sample_values) self.assertEqual(self.sample, self.original_sample) + + +class LazyUUIDTaskTest(TasklibTest): + + def setUp(self): + super(LazyUUIDTaskTest, self).setUp() + + self.stored = Task(self.tw, description="this is test task") + self.stored.save() + + self.lazy = LazyUUIDTask(self.tw, self.stored['uuid']) + + def test_uuid_non_conversion(self): + assert self.stored['uuid'] == self.lazy['uuid'] + assert type(self.lazy) is LazyUUIDTask + + def test_lazy_explicit_conversion(self): + assert type(self.lazy) is LazyUUIDTask + self.lazy.replace() + assert type(self.lazy) is Task + + def test_conversion_key(self): + assert self.stored['description'] == self.lazy['description'] + assert type(self.lazy) is Task + + def test_conversion_attribute(self): + assert type(self.lazy) is LazyUUIDTask + assert self.lazy.completed is False + assert type(self.lazy) is Task + + def test_normal_to_lazy_equality(self): + assert self.stored == self.lazy + assert not self.stored != self.lazy + assert type(self.lazy) is LazyUUIDTask + + def test_lazy_to_lazy_equality(self): + lazy1 = LazyUUIDTask(self.tw, self.stored['uuid']) + lazy2 = LazyUUIDTask(self.tw, self.stored['uuid']) + + assert lazy1 == lazy2 + assert not lazy1 != lazy2 + assert type(lazy1) is LazyUUIDTask + assert type(lazy2) is LazyUUIDTask + + def test_normal_to_lazy_inequality(self): + # Create a different UUID by changing the last letter + wrong_uuid = self.stored['uuid'] + wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b') + + wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid) + + assert not self.stored == wrong_lazy + assert self.stored != wrong_lazy + assert type(wrong_lazy) is LazyUUIDTask + + def test_lazy_to_lazy_inequality(self): + # Create a different UUID by changing the last letter + wrong_uuid = self.stored['uuid'] + wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b') + + lazy1 = LazyUUIDTask(self.tw, self.stored['uuid']) + lazy2 = LazyUUIDTask(self.tw, wrong_uuid) + + assert not lazy1 == lazy2 + assert lazy1 != lazy2 + assert type(lazy1) is LazyUUIDTask + assert type(lazy2) is LazyUUIDTask + + def test_lazy_in_queryset(self): + tasks = self.tw.tasks.filter(uuid=self.stored['uuid']) + + assert self.lazy in tasks + assert type(self.lazy) is LazyUUIDTask + + def test_lazy_saved(self): + assert self.lazy.saved is True + + def test_lazy_modified(self): + assert self.lazy.modified is False + + def test_lazy_modified_fields(self): + assert self.lazy._modified_fields == set() + + +class LazyUUIDTaskSetTest(TasklibTest): + + def setUp(self): + super(LazyUUIDTaskSetTest, self).setUp() + + self.task1 = Task(self.tw, description="task 1") + self.task2 = Task(self.tw, description="task 2") + self.task3 = Task(self.tw, description="task 3") + + self.task1.save() + self.task2.save() + self.task3.save() + + self.uuids = ( + self.task1['uuid'], + self.task2['uuid'], + self.task3['uuid'] + ) + + self.lazy = LazyUUIDTaskSet(self.tw, self.uuids) + + def test_length(self): + assert len(self.lazy) == 3 + assert type(self.lazy) is LazyUUIDTaskSet + + def test_contains(self): + assert self.task1 in self.lazy + assert self.task2 in self.lazy + assert self.task3 in self.lazy + assert type(self.lazy) is LazyUUIDTaskSet + + def test_eq_lazy(self): + new_lazy = LazyUUIDTaskSet(self.tw, self.uuids) + assert self.lazy == new_lazy + assert not self.lazy != new_lazy + assert type(self.lazy) is LazyUUIDTaskSet + + def test_eq_real(self): + assert self.lazy == self.tw.tasks.all() + assert self.tw.tasks.all() == self.lazy + assert not self.lazy != self.tw.tasks.all() + + assert type(self.lazy) is LazyUUIDTaskSet + + def test_union(self): + taskset = set([self.task1]) + lazyset = LazyUUIDTaskSet( + self.tw, + (self.task2['uuid'], self.task3['uuid']) + ) + + assert taskset | lazyset == self.lazy + assert lazyset | taskset == self.lazy + assert taskset.union(lazyset) == self.lazy + assert lazyset.union(taskset) == self.lazy + + lazyset |= taskset + assert lazyset == self.lazy + + def test_difference(self): + taskset = set([self.task1, self.task2]) + lazyset = LazyUUIDTaskSet( + self.tw, + (self.task2['uuid'], self.task3['uuid']) + ) + + assert taskset - lazyset == set([self.task1]) + assert lazyset - taskset == set([self.task3]) + assert taskset.difference(lazyset) == set([self.task1]) + assert lazyset.difference(taskset) == set([self.task3]) + + lazyset -= taskset + assert lazyset == set([self.task3]) + + def test_symmetric_difference(self): + taskset = set([self.task1, self.task2]) + lazyset = LazyUUIDTaskSet( + self.tw, + (self.task2['uuid'], self.task3['uuid']) + ) + + assert taskset ^ lazyset == set([self.task1, self.task3]) + assert lazyset ^ taskset == set([self.task1, self.task3]) + assert taskset.symmetric_difference(lazyset) == set([self.task1, + self.task3]) + assert lazyset.symmetric_difference(taskset) == set([self.task1, + self.task3]) + + lazyset ^= taskset + assert lazyset == set([self.task1, self.task3]) + + def test_intersection(self): + taskset = set([self.task1, self.task2]) + lazyset = LazyUUIDTaskSet( + self.tw, + (self.task2['uuid'], self.task3['uuid']) + ) + + assert taskset & lazyset == set([self.task2]) + assert lazyset & taskset == set([self.task2]) + assert taskset.intersection(lazyset) == set([self.task2]) + assert lazyset.intersection(taskset) == set([self.task2]) + + lazyset &= taskset + assert lazyset == set([self.task2]) + + +class TaskWarriorBackendTest(TasklibTest): + + def test_config(self): + assert self.tw.config['nag'] == "You have more urgent tasks." + assert self.tw.config['default.command'] == "next" + assert self.tw.config['dependency.indicator'] == "D"