All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
15 from .backends import TaskWarrior
16 from .task import Task, ReadOnlyDictView
17 from .lazy import LazyUUIDTask, LazyUUIDTaskSet
18 from .serializing import DATE_FORMAT, local_zone
20 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
21 TASK_STANDARD_ATTRS = (
45 def total_seconds_2_6(x):
46 return x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
49 class TasklibTest(unittest.TestCase):
51 def get_taskwarrior(self, **kwargs):
53 data_location=self.tmp,
56 tw_kwargs.update(kwargs)
57 return TaskWarrior(**tw_kwargs)
60 self.tmp = tempfile.mkdtemp(dir='.')
61 self.tw = self.get_taskwarrior()
64 shutil.rmtree(self.tmp)
67 class TaskWarriorTest(TasklibTest):
69 def test_custom_command(self):
70 # ensure that a custom command which contains multiple parts
71 # is properly split up
72 tw = self.get_taskwarrior(
73 task_command='wsl task',
74 # prevent `_get_version` from running as `wsl` may not exist
75 version_override=os.getenv('TASK_VERSION'),
77 self.assertEqual(tw._get_task_command(), ['wsl', 'task'])
80 class TaskFilterTest(TasklibTest):
82 def test_all_empty(self):
83 self.assertEqual(len(self.tw.tasks.all()), 0)
85 def test_all_non_empty(self):
86 Task(self.tw, description='test task').save()
87 self.assertEqual(len(self.tw.tasks.all()), 1)
88 self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
89 self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
91 def test_pending_non_empty(self):
92 Task(self.tw, description='test task').save()
93 self.assertEqual(len(self.tw.tasks.pending()), 1)
95 self.tw.tasks.pending()[0]['description'],
98 self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
100 def test_completed_empty(self):
101 Task(self.tw, description='test task').save()
102 self.assertEqual(len(self.tw.tasks.completed()), 0)
104 def test_completed_non_empty(self):
105 Task(self.tw, description='test task').save()
106 self.assertEqual(len(self.tw.tasks.completed()), 0)
107 self.tw.tasks.all()[0].done()
108 self.assertEqual(len(self.tw.tasks.completed()), 1)
110 def test_deleted_empty(self):
111 Task(self.tw, description='test task').save()
112 self.assertEqual(len(self.tw.tasks.deleted()), 0)
114 def test_deleted_non_empty(self):
115 Task(self.tw, description='test task').save()
116 self.assertEqual(len(self.tw.tasks.deleted()), 0)
117 self.tw.tasks.all()[0].delete()
118 self.assertEqual(len(self.tw.tasks.deleted()), 1)
120 def test_waiting_empty(self):
121 Task(self.tw, description='test task').save()
122 self.assertEqual(len(self.tw.tasks.waiting()), 0)
124 def test_waiting_non_empty(self):
125 Task(self.tw, description='test task').save()
126 self.assertEqual(len(self.tw.tasks.waiting()), 0)
128 t = self.tw.tasks.all()[0]
129 t['wait'] = datetime.datetime.now() + datetime.timedelta(days=1)
132 self.assertEqual(len(self.tw.tasks.waiting()), 1)
134 def test_recurring_empty(self):
135 Task(self.tw, description='test task').save()
136 self.assertEqual(len(self.tw.tasks.recurring()), 0)
138 def test_recurring_non_empty(self):
141 description='test task',
143 due=datetime.datetime.now(),
145 self.assertEqual(len(self.tw.tasks.recurring()), 1)
147 def test_filtering_by_attribute(self):
148 Task(self.tw, description='no priority task').save()
149 Task(self.tw, priority='H', description='high priority task').save()
150 self.assertEqual(len(self.tw.tasks.all()), 2)
152 # Assert that the correct number of tasks is returned
153 self.assertEqual(len(self.tw.tasks.filter(priority='H')), 1)
155 # Assert that the correct tasks are returned
156 high_priority_task = self.tw.tasks.get(priority='H')
158 high_priority_task['description'],
159 'high priority task',
162 def test_filtering_by_empty_attribute(self):
163 Task(self.tw, description='no priority task').save()
164 Task(self.tw, priority='H', description='high priority task').save()
165 self.assertEqual(len(self.tw.tasks.all()), 2)
167 # Assert that the correct number of tasks is returned
168 self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
170 # Assert that the correct tasks are returned
171 no_priority_task = self.tw.tasks.get(priority=None)
172 self.assertEqual(no_priority_task['description'], 'no priority task')
174 def test_filter_for_task_with_space_in_descripition(self):
175 task = Task(self.tw, description='test task')
178 filtered_task = self.tw.tasks.get(description='test task')
179 self.assertEqual(filtered_task['description'], 'test task')
181 def test_filter_for_task_without_space_in_descripition(self):
182 task = Task(self.tw, description='test')
185 filtered_task = self.tw.tasks.get(description='test')
186 self.assertEqual(filtered_task['description'], 'test')
188 def test_filter_for_task_with_space_in_project(self):
189 task = Task(self.tw, description='test', project='random project')
192 filtered_task = self.tw.tasks.get(project='random project')
193 self.assertEqual(filtered_task['project'], 'random project')
195 def test_filter_for_task_without_space_in_project(self):
196 task = Task(self.tw, description='test', project='random')
199 filtered_task = self.tw.tasks.get(project='random')
200 self.assertEqual(filtered_task['project'], 'random')
202 def test_filter_with_empty_uuid(self):
203 self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
205 def test_filter_dummy_by_status(self):
206 t = Task(self.tw, description='test')
209 tasks = self.tw.tasks.filter(status=t['status'])
210 self.assertEqual(list(tasks), [t])
212 def test_filter_dummy_by_uuid(self):
213 t = Task(self.tw, description='test')
216 tasks = self.tw.tasks.filter(uuid=t['uuid'])
217 self.assertEqual(list(tasks), [t])
219 def test_filter_dummy_by_entry(self):
220 t = Task(self.tw, description='test')
223 tasks = self.tw.tasks.filter(entry=t['entry'])
224 self.assertEqual(list(tasks), [t])
226 def test_filter_dummy_by_description(self):
227 t = Task(self.tw, description='test')
230 tasks = self.tw.tasks.filter(description=t['description'])
231 self.assertEqual(list(tasks), [t])
233 def test_filter_dummy_by_start(self):
234 t = Task(self.tw, description='test')
238 tasks = self.tw.tasks.filter(start=t['start'])
239 self.assertEqual(list(tasks), [t])
241 def test_filter_dummy_by_end(self):
242 t = Task(self.tw, description='test')
246 tasks = self.tw.tasks.filter(end=t['end'])
247 self.assertEqual(list(tasks), [t])
249 def test_filter_dummy_by_due(self):
250 t = Task(self.tw, description='test', due=datetime.datetime.now())
253 tasks = self.tw.tasks.filter(due=t['due'])
254 self.assertEqual(list(tasks), [t])
256 def test_filter_dummy_by_until(self):
257 t = Task(self.tw, description='test')
260 tasks = self.tw.tasks.filter(until=t['until'])
261 self.assertEqual(list(tasks), [t])
263 def test_filter_dummy_by_modified(self):
264 # Older TW version does not support bumping modified
266 if self.tw.version < six.text_type('2.2.0'):
267 # Python2.6 does not support SkipTest. As a workaround
268 # mark the test as passed by exiting.
269 if getattr(unittest, 'SkipTest', None) is not None:
270 raise unittest.SkipTest()
274 t = Task(self.tw, description='test')
277 tasks = self.tw.tasks.filter(modified=t['modified'])
278 self.assertEqual(list(tasks), [t])
280 def test_filter_dummy_by_scheduled(self):
281 t = Task(self.tw, description='test')
284 tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
285 self.assertEqual(list(tasks), [t])
287 def test_filter_dummy_by_tags(self):
288 t = Task(self.tw, description='test', tags=['home'])
291 tasks = self.tw.tasks.filter(tags=t['tags'])
292 self.assertEqual(list(tasks), [t])
294 def test_filter_dummy_by_projects(self):
295 t = Task(self.tw, description='test', project='random')
298 tasks = self.tw.tasks.filter(project=t['project'])
299 self.assertEqual(list(tasks), [t])
301 def test_filter_by_priority(self):
302 t = Task(self.tw, description='test', priority='H')
305 tasks = self.tw.tasks.filter(priority=t['priority'])
306 self.assertEqual(list(tasks), [t])
309 class TaskTest(TasklibTest):
311 def test_create_unsaved_task(self):
312 # Make sure a new task is not saved unless explicitly called for
313 Task(self.tw, description='test task')
314 self.assertEqual(len(self.tw.tasks.all()), 0)
316 # TODO: once python 2.6 compatibility is over, use context managers here
317 # and in all subsequent tests for assertRaises
319 def test_delete_unsaved_task(self):
320 t = Task(self.tw, description='test task')
321 self.assertRaises(Task.NotSaved, t.delete)
323 def test_complete_unsaved_task(self):
324 t = Task(self.tw, description='test task')
325 self.assertRaises(Task.NotSaved, t.done)
327 def test_refresh_unsaved_task(self):
328 t = Task(self.tw, description='test task')
329 self.assertRaises(Task.NotSaved, t.refresh)
331 def test_start_unsaved_task(self):
332 t = Task(self.tw, description='test task')
333 self.assertRaises(Task.NotSaved, t.start)
335 def test_delete_deleted_task(self):
336 t = Task(self.tw, description='test task')
340 self.assertRaises(Task.DeletedTask, t.delete)
342 def test_complete_completed_task(self):
343 t = Task(self.tw, description='test task')
347 self.assertRaises(Task.CompletedTask, t.done)
349 def test_start_completed_task(self):
350 t = Task(self.tw, description='test task')
354 self.assertRaises(Task.CompletedTask, t.start)
356 def test_add_completed_task(self):
361 end=datetime.datetime.now(),
365 def test_add_multiple_completed_tasks(self):
370 end=datetime.datetime.now(),
376 end=datetime.datetime.now(),
381 def test_complete_deleted_task(self):
382 t = Task(self.tw, description='test task')
386 self.assertRaises(Task.DeletedTask, t.done)
388 def test_starting_task(self):
389 t = Task(self.tw, description='test task')
390 now = t.datetime_normalizer(datetime.datetime.now())
394 self.assertTrue(now.replace(microsecond=0) <= t['start'])
395 self.assertEqual(t['status'], 'pending')
397 def test_completing_task(self):
398 t = Task(self.tw, description='test task')
399 now = t.datetime_normalizer(datetime.datetime.now())
403 self.assertTrue(now.replace(microsecond=0) <= t['end'])
404 self.assertEqual(t['status'], 'completed')
406 def test_deleting_task(self):
407 t = Task(self.tw, description='test task')
408 now = t.datetime_normalizer(datetime.datetime.now())
412 self.assertTrue(now.replace(microsecond=0) <= t['end'])
413 self.assertEqual(t['status'], 'deleted')
415 def test_started_task_active(self):
416 t = Task(self.tw, description='test task')
419 self.assertTrue(t.active)
421 def test_unstarted_task_inactive(self):
422 t = Task(self.tw, description='test task')
423 self.assertFalse(t.active)
425 self.assertFalse(t.active)
427 def test_start_active_task(self):
428 t = Task(self.tw, description='test task')
431 self.assertRaises(Task.ActiveTask, t.start)
433 def test_stop_completed_task(self):
434 t = Task(self.tw, description='test task')
439 self.assertRaises(Task.InactiveTask, t.stop)
441 t = Task(self.tw, description='test task')
445 self.assertRaises(Task.InactiveTask, t.stop)
447 def test_stop_deleted_task(self):
448 t = Task(self.tw, description='test task')
454 def test_stop_inactive_task(self):
455 t = Task(self.tw, description='test task')
458 self.assertRaises(Task.InactiveTask, t.stop)
460 t = Task(self.tw, description='test task')
465 self.assertRaises(Task.InactiveTask, t.stop)
467 def test_stopping_task(self):
468 t = Task(self.tw, description='test task')
469 t.datetime_normalizer(datetime.datetime.now())
474 self.assertEqual(t['end'], None)
475 self.assertEqual(t['status'], 'pending')
476 self.assertFalse(t.active)
478 def test_modify_simple_attribute_without_space(self):
479 t = Task(self.tw, description='test')
482 self.assertEqual(t['description'], 'test')
484 t['description'] = 'test-modified'
487 self.assertEqual(t['description'], 'test-modified')
489 def test_modify_simple_attribute_with_space(self):
490 # Space can pose problems with parsing
491 t = Task(self.tw, description='test task')
494 self.assertEqual(t['description'], 'test task')
496 t['description'] = 'test task modified'
499 self.assertEqual(t['description'], 'test task modified')
501 def test_empty_dependency_set_of_unsaved_task(self):
502 t = Task(self.tw, description='test task')
503 self.assertEqual(t['depends'], set())
505 def test_empty_dependency_set_of_saved_task(self):
506 t = Task(self.tw, description='test task')
508 self.assertEqual(t['depends'], set())
510 def test_set_unsaved_task_as_dependency(self):
511 # Adds only one dependency to task with no dependencies
512 t = Task(self.tw, description='test task')
513 dependency = Task(self.tw, description='needs to be done first')
515 # We only save the parent task, dependency task is unsaved
517 t['depends'] = set([dependency])
519 self.assertRaises(Task.NotSaved, t.save)
521 def test_set_simple_dependency_set(self):
522 # Adds only one dependency to task with no dependencies
523 t = Task(self.tw, description='test task')
524 dependency = Task(self.tw, description='needs to be done first')
529 t['depends'] = set([dependency])
531 self.assertEqual(t['depends'], set([dependency]))
533 def test_set_complex_dependency_set(self):
534 # Adds two dependencies to task with no dependencies
535 t = Task(self.tw, description='test task')
536 dependency1 = Task(self.tw, description='needs to be done first')
537 dependency2 = Task(self.tw, description='needs to be done second')
543 t['depends'] = set([dependency1, dependency2])
545 self.assertEqual(t['depends'], set([dependency1, dependency2]))
547 def test_remove_from_dependency_set(self):
548 # Removes dependency from task with two dependencies
549 t = Task(self.tw, description='test task')
550 dependency1 = Task(self.tw, description='needs to be done first')
551 dependency2 = Task(self.tw, description='needs to be done second')
556 t['depends'] = set([dependency1, dependency2])
559 t['depends'].remove(dependency2)
562 self.assertEqual(t['depends'], set([dependency1]))
564 def test_add_to_dependency_set(self):
565 # Adds dependency to task with one dependencies
566 t = Task(self.tw, description='test task')
567 dependency1 = Task(self.tw, description='needs to be done first')
568 dependency2 = Task(self.tw, description='needs to be done second')
573 t['depends'] = set([dependency1])
576 t['depends'].add(dependency2)
579 self.assertEqual(t['depends'], set([dependency1, dependency2]))
581 def test_add_to_empty_dependency_set(self):
582 # Adds dependency to task with one dependencies
583 t = Task(self.tw, description='test task')
584 dependency = Task(self.tw, description='needs to be done first')
588 t['depends'].add(dependency)
591 self.assertEqual(t['depends'], set([dependency]))
593 def test_simple_dependency_set_save_repeatedly(self):
594 # Adds only one dependency to task with no dependencies
595 t = Task(self.tw, description='test task')
596 dependency = Task(self.tw, description='needs to be done first')
599 t['depends'] = set([dependency])
602 # We taint the task, but keep depends intact
603 t['description'] = 'test task modified'
606 self.assertEqual(t['depends'], set([dependency]))
608 # We taint the task, but assign the same set to the depends
609 t['depends'] = set([dependency])
610 t['description'] = 'test task modified again'
613 self.assertEqual(t['depends'], set([dependency]))
615 def test_compare_different_tasks(self):
616 # Negative: compare two different tasks
617 t1 = Task(self.tw, description='test task')
618 t2 = Task(self.tw, description='test task')
623 self.assertEqual(t1 == t2, False)
625 def test_compare_same_task_object(self):
626 # Compare Task object wit itself
627 t = Task(self.tw, description='test task')
630 self.assertEqual(t == t, True)
632 def test_compare_same_task(self):
633 # Compare the same task using two different objects
634 t1 = Task(self.tw, description='test task')
637 t2 = self.tw.tasks.get(uuid=t1['uuid'])
638 self.assertEqual(t1 == t2, True)
640 def test_compare_unsaved_tasks(self):
641 # t1 and t2 are unsaved tasks, considered to be unequal
642 # despite the content of data
643 t1 = Task(self.tw, description='test task')
644 t2 = Task(self.tw, description='test task')
646 self.assertEqual(t1 == t2, False)
648 def test_hash_unsaved_tasks(self):
649 # Considered equal, it's the same object
650 t1 = Task(self.tw, description='test task')
652 self.assertEqual(hash(t1) == hash(t2), True)
654 def test_hash_same_task(self):
655 # Compare the hash of the task using two different objects
656 t1 = Task(self.tw, description='test task')
659 t2 = self.tw.tasks.get(uuid=t1['uuid'])
660 self.assertEqual(t1.__hash__(), t2.__hash__())
662 def test_hash_unequal_unsaved_tasks(self):
663 # Compare the hash of the task using two different objects
664 t1 = Task(self.tw, description='test task 1')
665 t2 = Task(self.tw, description='test task 2')
667 self.assertNotEqual(t1.__hash__(), t2.__hash__())
669 def test_hash_unequal_saved_tasks(self):
670 # Compare the hash of the task using two different objects
671 t1 = Task(self.tw, description='test task 1')
672 t2 = Task(self.tw, description='test task 2')
677 self.assertNotEqual(t1.__hash__(), t2.__hash__())
679 def test_adding_task_with_priority(self):
680 t = Task(self.tw, description='test task', priority='M')
683 def test_removing_priority_with_none(self):
684 t = Task(self.tw, description='test task', priority='L')
687 # Remove the priority mark
691 # Assert that priority is not there after saving
692 self.assertEqual(t['priority'], None)
694 def test_adding_task_with_due_time(self):
695 t = Task(self.tw, description='test task', due=datetime.datetime.now())
698 def test_removing_due_time_with_none(self):
699 t = Task(self.tw, description='test task', due=datetime.datetime.now())
702 # Remove the due timestamp
706 # Assert that due timestamp is no longer there
707 self.assertEqual(t['due'], None)
709 def test_modified_fields_new_task(self):
712 # This should be empty with new task
713 self.assertEqual(set(t._modified_fields), set())
716 t['description'] = 'test task'
717 self.assertEqual(set(t._modified_fields), set(['description']))
719 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
720 self.assertEqual(set(t._modified_fields), set(['description', 'due']))
722 t['project'] = 'test project'
724 set(t._modified_fields),
725 set(['description', 'due', 'project']),
728 # List of modified fields should clear out when saved
730 self.assertEqual(set(t._modified_fields), set())
732 # Reassigning the fields with the same values now should not produce
734 t['description'] = 'test task'
735 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
736 t['project'] = 'test project'
737 self.assertEqual(set(t._modified_fields), set())
739 def test_modified_fields_loaded_task(self):
743 t['description'] = 'test task'
744 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
745 t['project'] = 'test project'
747 dependency = Task(self.tw, description='dependency')
749 t['depends'] = set([dependency])
751 # List of modified fields should clear out when saved
753 self.assertEqual(set(t._modified_fields), set())
755 # Get the task by using a filter by UUID
756 self.tw.tasks.get(uuid=t['uuid'])
758 # Reassigning the fields with the same values now should not produce
760 t['description'] = 'test task'
761 t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14) # <3
762 t['project'] = 'test project'
763 t['depends'] = set([dependency])
764 self.assertEqual(set(t._modified_fields), set())
766 def test_modified_fields_not_affected_by_reading(self):
769 for field in TASK_STANDARD_ATTRS:
772 self.assertEqual(set(t._modified_fields), set())
774 def test_setting_read_only_attrs_through_init(self):
775 # Test that we are unable to set readonly attrs through __init__
776 for readonly_key in Task.read_only_fields:
777 kwargs = {'description': 'test task', readonly_key: 'value'}
780 lambda: Task(self.tw, **kwargs),
783 def test_setting_read_only_attrs_through_setitem(self):
784 # Test that we are unable to set readonly attrs through __init__
785 for readonly_key in Task.read_only_fields:
786 t = Task(self.tw, description='test task')
789 lambda: t.__setitem__(readonly_key, 'value'),
792 def test_saving_unmodified_task(self):
793 t = Task(self.tw, description='test task')
797 def test_adding_tag_by_appending(self):
798 t = Task(self.tw, description='test task', tags=['test1'])
800 t['tags'].add('test2')
802 self.assertEqual(t['tags'], set(['test1', 'test2']))
804 def test_adding_tag_twice(self):
805 t = Task(self.tw, description='test task', tags=['test1'])
807 t['tags'].add('test2')
808 t['tags'].add('test2')
810 self.assertEqual(t['tags'], set(['test1', 'test2']))
812 def test_adding_tag_by_appending_empty(self):
813 t = Task(self.tw, description='test task')
815 t['tags'].add('test')
817 self.assertEqual(t['tags'], set(['test']))
819 def test_serializers_returning_empty_string_for_none(self):
820 # Test that any serializer returns '' when passed None
823 getattr(t, serializer_name)
824 for serializer_name in filter(
825 lambda x: x.startswith('serialize_'),
829 for serializer in serializers:
830 self.assertEqual(serializer(None), '')
832 def test_deserializer_returning_empty_value_for_empty_string(self):
833 # Test that any deserializer returns empty value when passed ''
836 getattr(t, deserializer_name)
837 for deserializer_name in filter(
838 lambda x: x.startswith('deserialize_'),
842 for deserializer in deserializers:
843 self.assertTrue(deserializer('') in (None, [], set()))
845 def test_normalizers_handling_none(self):
846 # Test that any normalizer can handle None as a valid value
849 for key in TASK_STANDARD_ATTRS:
850 t._normalize(key, None)
852 def test_recurrent_task_generation(self):
853 today = datetime.date.today()
856 description='brush teeth',
861 self.assertEqual(len(self.tw.tasks.pending()), 2)
863 def test_spawned_task_parent(self):
864 today = datetime.date.today()
867 description='brush teeth',
873 spawned = self.tw.tasks.pending().get(due=today)
874 assert spawned['parent'] == t
876 def test_modify_number_of_tasks_at_once(self):
877 for i in range(1, 100):
878 Task(self.tw, description='test task %d' % i, tags=['test']).save()
880 self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
882 def test_return_all_from_executed_command(self):
883 Task(self.tw, description='test task', tags=['test']).save()
884 out, err, rc = self.tw.execute_command(['count'], return_all=True)
885 self.assertEqual(rc, 0)
887 def test_return_all_from_failed_executed_command(self):
888 Task(self.tw, description='test task', tags=['test']).save()
889 out, err, rc = self.tw.execute_command(
894 self.assertNotEqual(rc, 0)
897 class TaskFromHookTest(TasklibTest):
899 input_add_data = six.StringIO(
900 '{"description":"Buy some milk",'
901 '"entry":"20141118T050231Z",'
902 '"status":"pending",'
903 '"start":"20141119T152233Z",'
904 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}',
907 input_add_data_recurring = six.StringIO(
908 '{"description":"Mow the lawn",'
909 '"entry":"20160210T224304Z",'
910 '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
912 '"status":"pending",'
913 '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}',
916 input_modify_data = six.StringIO(
918 input_add_data.getvalue(),
920 '{"description":"Buy some milk finally",'
921 '"entry":"20141118T050231Z",'
922 '"status":"completed",'
923 '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}'
928 exported_raw_data = (
930 '"due":"20150101T232323Z",'
931 '"description":"test task"}'
934 def test_setting_up_from_add_hook_input(self):
935 t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
936 self.assertEqual(t['description'], 'Buy some milk')
937 self.assertEqual(t.pending, True)
939 def test_setting_up_from_add_hook_input_recurring(self):
941 input_file=self.input_add_data_recurring,
944 self.assertEqual(t['description'], 'Mow the lawn')
945 self.assertEqual(t.pending, True)
947 def test_setting_up_from_modified_hook_input(self):
949 input_file=self.input_modify_data,
953 self.assertEqual(t['description'], 'Buy some milk finally')
954 self.assertEqual(t.pending, False)
955 self.assertEqual(t.completed, True)
957 self.assertEqual(t._original_data['status'], 'pending')
958 self.assertEqual(t._original_data['description'], 'Buy some milk')
960 set(t._modified_fields),
961 set(['status', 'description', 'start']),
964 def test_export_data(self):
967 description='test task',
969 due=pytz.utc.localize(
970 datetime.datetime(2015, 1, 1, 23, 23, 23)),
973 # Check that the output is a permutation of:
974 # {"project":"Home","description":"test task","due":"20150101232323Z"}
975 allowed_segments = self.exported_raw_data[1:-1].split(',')
977 '{' + ','.join(segments) + '}'
978 for segments in itertools.permutations(allowed_segments)
982 any(t.export_data() == expected
983 for expected in allowed_output),
987 class TimezoneAwareDatetimeTest(TasklibTest):
990 super(TimezoneAwareDatetimeTest, self).setUp()
991 self.zone = local_zone
992 self.localdate_naive = datetime.datetime(2015, 2, 2)
993 self.localtime_naive = datetime.datetime(2015, 2, 2, 0, 0, 0)
994 self.localtime_aware = self.zone.localize(self.localtime_naive)
995 self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
997 def test_timezone_naive_datetime_setitem(self):
998 t = Task(self.tw, description='test task')
999 t['due'] = self.localtime_naive
1000 self.assertEqual(t['due'], self.localtime_aware)
1002 def test_timezone_naive_datetime_using_init(self):
1003 t = Task(self.tw, description='test task', due=self.localtime_naive)
1004 self.assertEqual(t['due'], self.localtime_aware)
1006 def test_filter_by_naive_datetime(self):
1007 t = Task(self.tw, description='task1', due=self.localtime_naive)
1009 matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
1010 self.assertEqual(len(matching_tasks), 1)
1012 def test_serialize_naive_datetime(self):
1013 t = Task(self.tw, description='task1', due=self.localtime_naive)
1015 json.loads(t.export_data())['due'],
1016 self.utctime_aware.strftime(DATE_FORMAT),
1019 def test_timezone_naive_date_setitem(self):
1020 t = Task(self.tw, description='test task')
1021 t['due'] = self.localdate_naive
1022 self.assertEqual(t['due'], self.localtime_aware)
1024 def test_timezone_naive_date_using_init(self):
1025 t = Task(self.tw, description='test task', due=self.localdate_naive)
1026 self.assertEqual(t['due'], self.localtime_aware)
1028 def test_filter_by_naive_date(self):
1029 t = Task(self.tw, description='task1', due=self.localdate_naive)
1031 matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
1032 self.assertEqual(len(matching_tasks), 1)
1034 def test_serialize_naive_date(self):
1035 t = Task(self.tw, description='task1', due=self.localdate_naive)
1037 json.loads(t.export_data())['due'],
1038 self.utctime_aware.strftime(DATE_FORMAT),
1041 def test_timezone_aware_datetime_setitem(self):
1042 t = Task(self.tw, description='test task')
1043 t['due'] = self.localtime_aware
1044 self.assertEqual(t['due'], self.localtime_aware)
1046 def test_timezone_aware_datetime_using_init(self):
1047 t = Task(self.tw, description='test task', due=self.localtime_aware)
1048 self.assertEqual(t['due'], self.localtime_aware)
1050 def test_filter_by_aware_datetime(self):
1051 t = Task(self.tw, description='task1', due=self.localtime_aware)
1053 matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
1054 self.assertEqual(len(matching_tasks), 1)
1056 def test_serialize_aware_datetime(self):
1057 t = Task(self.tw, description='task1', due=self.localtime_aware)
1059 json.loads(t.export_data())['due'],
1060 self.utctime_aware.strftime(DATE_FORMAT),
1064 class DatetimeStringTest(TasklibTest):
1066 def test_simple_now_conversion(self):
1067 if self.tw.version < six.text_type('2.4.0'):
1068 # Python2.6 does not support SkipTest. As a workaround
1069 # mark the test as passed by exiting.
1070 if getattr(unittest, 'SkipTest', None) is not None:
1071 raise unittest.SkipTest()
1075 t = Task(self.tw, description='test task', due='now')
1076 now = local_zone.localize(datetime.datetime.now())
1078 # Assert that both times are not more than 5 seconds apart
1079 if sys.version_info < (2, 7):
1080 self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
1081 self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
1083 self.assertTrue((now - t['due']).total_seconds() < 5)
1084 self.assertTrue((t['due'] - now).total_seconds() < 5)
1086 def test_simple_eoy_conversion(self):
1087 if self.tw.version < six.text_type('2.4.0'):
1088 # Python2.6 does not support SkipTest. As a workaround
1089 # mark the test as passed by exiting.
1090 if getattr(unittest, 'SkipTest', None) is not None:
1091 raise unittest.SkipTest()
1095 t = Task(self.tw, description='test task', due='eoy')
1096 now = local_zone.localize(datetime.datetime.now())
1097 eoy = local_zone.localize(datetime.datetime(
1105 self.assertEqual(eoy, t['due'])
1107 def test_complex_eoy_conversion(self):
1108 if self.tw.version < six.text_type('2.4.0'):
1109 # Python2.6 does not support SkipTest. As a workaround
1110 # mark the test as passed by exiting.
1111 if getattr(unittest, 'SkipTest', None) is not None:
1112 raise unittest.SkipTest()
1116 t = Task(self.tw, description='test task', due='eoy - 4 months')
1117 now = local_zone.localize(datetime.datetime.now())
1118 due_date = local_zone.localize(
1127 ) - datetime.timedelta(0, 4 * 30 * 86400)
1128 self.assertEqual(due_date, t['due'])
1130 def test_filtering_with_string_datetime(self):
1131 if self.tw.version < six.text_type('2.4.0'):
1132 # Python2.6 does not support SkipTest. As a workaround
1133 # mark the test as passed by exiting.
1134 if getattr(unittest, 'SkipTest', None) is not None:
1135 raise unittest.SkipTest()
1141 description='test task',
1142 due=datetime.datetime.now() - datetime.timedelta(0, 2),
1145 self.assertEqual(len(self.tw.tasks.filter(due__before='now')), 1)
1148 class AnnotationTest(TasklibTest):
1151 super(AnnotationTest, self).setUp()
1152 Task(self.tw, description='test task').save()
1154 def test_adding_annotation(self):
1155 task = self.tw.tasks.get()
1156 task.add_annotation('test annotation')
1157 self.assertEqual(len(task['annotations']), 1)
1158 ann = task['annotations'][0]
1159 self.assertEqual(ann['description'], 'test annotation')
1161 def test_removing_annotation(self):
1162 task = self.tw.tasks.get()
1163 task.add_annotation('test annotation')
1164 ann = task['annotations'][0]
1166 self.assertEqual(len(task['annotations']), 0)
1168 def test_removing_annotation_by_description(self):
1169 task = self.tw.tasks.get()
1170 task.add_annotation('test annotation')
1171 task.remove_annotation('test annotation')
1172 self.assertEqual(len(task['annotations']), 0)
1174 def test_removing_annotation_by_obj(self):
1175 task = self.tw.tasks.get()
1176 task.add_annotation('test annotation')
1177 ann = task['annotations'][0]
1178 task.remove_annotation(ann)
1179 self.assertEqual(len(task['annotations']), 0)
1181 def test_annotation_after_modification(self):
1182 task = self.tw.tasks.get()
1183 task['project'] = 'test'
1184 task.add_annotation('I should really do this task')
1185 self.assertEqual(task['project'], 'test')
1187 self.assertEqual(task['project'], 'test')
1189 def test_serialize_annotations(self):
1190 # Test that serializing annotations is possible
1191 t = Task(self.tw, description='test')
1194 t.add_annotation('annotation1')
1195 t.add_annotation('annotation2')
1197 data = t._serialize('annotations', t._data['annotations'])
1199 self.assertEqual(len(data), 2)
1200 self.assertEqual(type(data[0]), dict)
1201 self.assertEqual(type(data[1]), dict)
1203 self.assertEqual(data[0]['description'], 'annotation1')
1204 self.assertEqual(data[1]['description'], 'annotation2')
1207 class UnicodeTest(TasklibTest):
1209 def test_unicode_task(self):
1210 Task(self.tw, description=six.u('†åßk')).save()
1213 def test_filter_by_unicode_task(self):
1214 Task(self.tw, description=six.u('†åßk')).save()
1215 tasks = self.tw.tasks.filter(description=six.u('†åßk'))
1216 self.assertEqual(len(tasks), 1)
1218 def test_non_unicode_task(self):
1219 Task(self.tw, description='test task').save()
1223 class ReadOnlyDictViewTest(unittest.TestCase):
1226 self.sample = dict(sample_list=[1, 2, 3], sample_dict={'key': 'value'})
1227 self.original_sample = copy.deepcopy(self.sample)
1228 self.view = ReadOnlyDictView(self.sample)
1230 def test_readonlydictview_getitem(self):
1231 sample_list = self.view['sample_list']
1232 self.assertEqual(sample_list, self.sample['sample_list'])
1234 # Assert that modification changed only copied value
1235 sample_list.append(4)
1236 self.assertNotEqual(sample_list, self.sample['sample_list'])
1238 # Assert that viewed dict is not changed
1239 self.assertEqual(self.sample, self.original_sample)
1241 def test_readonlydictview_contains(self):
1242 self.assertEqual('sample_list' in self.view,
1243 'sample_list' in self.sample)
1244 self.assertEqual('sample_dict' in self.view,
1245 'sample_dict' in self.sample)
1246 self.assertEqual('key' in self.view, 'key' in self.sample)
1248 # Assert that viewed dict is not changed
1249 self.assertEqual(self.sample, self.original_sample)
1251 def test_readonlydictview_iter(self):
1253 list(key for key in self.view),
1254 list(key for key in self.sample),
1257 # Assert the view is correct after modification
1258 self.sample['new'] = 'value'
1260 list(key for key in self.view),
1261 list(key for key in self.sample),
1264 def test_readonlydictview_len(self):
1265 self.assertEqual(len(self.view), len(self.sample))
1267 # Assert the view is correct after modification
1268 self.sample['new'] = 'value'
1269 self.assertEqual(len(self.view), len(self.sample))
1271 def test_readonlydictview_get(self):
1272 sample_list = self.view.get('sample_list')
1273 self.assertEqual(sample_list, self.sample.get('sample_list'))
1275 # Assert that modification changed only copied value
1276 sample_list.append(4)
1277 self.assertNotEqual(sample_list, self.sample.get('sample_list'))
1279 # Assert that viewed dict is not changed
1280 self.assertEqual(self.sample, self.original_sample)
1282 def test_readonlydict_items(self):
1283 view_items = self.view.items()
1284 sample_items = list(self.sample.items())
1285 self.assertEqual(view_items, sample_items)
1287 view_items.append('newkey')
1288 self.assertNotEqual(view_items, sample_items)
1289 self.assertEqual(self.sample, self.original_sample)
1291 def test_readonlydict_values(self):
1292 view_values = self.view.values()
1293 sample_values = list(self.sample.values())
1294 self.assertEqual(view_values, sample_values)
1296 view_list_item = list(filter(lambda x: type(x) is list,
1298 view_list_item.append(4)
1299 self.assertNotEqual(view_values, sample_values)
1300 self.assertEqual(self.sample, self.original_sample)
1303 class LazyUUIDTaskTest(TasklibTest):
1306 super(LazyUUIDTaskTest, self).setUp()
1308 self.stored = Task(self.tw, description='this is test task')
1311 self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1313 def test_uuid_non_conversion(self):
1314 assert self.stored['uuid'] == self.lazy['uuid']
1315 assert type(self.lazy) is LazyUUIDTask
1317 def test_lazy_explicit_conversion(self):
1318 assert type(self.lazy) is LazyUUIDTask
1320 assert type(self.lazy) is Task
1322 def test_conversion_key(self):
1323 assert self.stored['description'] == self.lazy['description']
1324 assert type(self.lazy) is Task
1326 def test_conversion_attribute(self):
1327 assert type(self.lazy) is LazyUUIDTask
1328 assert self.lazy.completed is False
1329 assert type(self.lazy) is Task
1331 def test_normal_to_lazy_equality(self):
1332 assert self.stored == self.lazy
1333 assert not self.stored != self.lazy
1334 assert type(self.lazy) is LazyUUIDTask
1336 def test_lazy_to_lazy_equality(self):
1337 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1338 lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1340 assert lazy1 == lazy2
1341 assert not lazy1 != lazy2
1342 assert type(lazy1) is LazyUUIDTask
1343 assert type(lazy2) is LazyUUIDTask
1345 def test_normal_to_lazy_inequality(self):
1346 # Create a different UUID by changing the last letter
1347 wrong_uuid = self.stored['uuid']
1348 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1350 wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
1352 assert not self.stored == wrong_lazy
1353 assert self.stored != wrong_lazy
1354 assert type(wrong_lazy) is LazyUUIDTask
1356 def test_lazy_to_lazy_inequality(self):
1357 # Create a different UUID by changing the last letter
1358 wrong_uuid = self.stored['uuid']
1359 wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1361 lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1362 lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
1364 assert not lazy1 == lazy2
1365 assert lazy1 != lazy2
1366 assert type(lazy1) is LazyUUIDTask
1367 assert type(lazy2) is LazyUUIDTask
1369 def test_lazy_in_queryset(self):
1370 tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1372 assert self.lazy in tasks
1373 assert type(self.lazy) is LazyUUIDTask
1375 def test_lazy_saved(self):
1376 assert self.lazy.saved is True
1378 def test_lazy_modified(self):
1379 assert self.lazy.modified is False
1381 def test_lazy_modified_fields(self):
1382 assert self.lazy._modified_fields == set()
1385 class LazyUUIDTaskSetTest(TasklibTest):
1388 super(LazyUUIDTaskSetTest, self).setUp()
1390 self.task1 = Task(self.tw, description='task 1')
1391 self.task2 = Task(self.tw, description='task 2')
1392 self.task3 = Task(self.tw, description='task 3')
1404 self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1406 def test_length(self):
1407 assert len(self.lazy) == 3
1408 assert type(self.lazy) is LazyUUIDTaskSet
1410 def test_contains(self):
1411 assert self.task1 in self.lazy
1412 assert self.task2 in self.lazy
1413 assert self.task3 in self.lazy
1414 assert type(self.lazy) is LazyUUIDTaskSet
1416 def test_eq_lazy(self):
1417 new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1418 assert self.lazy == new_lazy
1419 assert not self.lazy != new_lazy
1420 assert type(self.lazy) is LazyUUIDTaskSet
1422 def test_eq_real(self):
1423 assert self.lazy == self.tw.tasks.all()
1424 assert self.tw.tasks.all() == self.lazy
1425 assert not self.lazy != self.tw.tasks.all()
1427 assert type(self.lazy) is LazyUUIDTaskSet
1429 def test_union(self):
1430 taskset = set([self.task1])
1431 lazyset = LazyUUIDTaskSet(
1433 (self.task2['uuid'], self.task3['uuid']),
1436 assert taskset | lazyset == self.lazy
1437 assert lazyset | taskset == self.lazy
1438 assert taskset.union(lazyset) == self.lazy
1439 assert lazyset.union(taskset) == self.lazy
1442 assert lazyset == self.lazy
1444 def test_difference(self):
1445 taskset = set([self.task1, self.task2])
1446 lazyset = LazyUUIDTaskSet(
1448 (self.task2['uuid'], self.task3['uuid']),
1451 assert taskset - lazyset == set([self.task1])
1452 assert lazyset - taskset == set([self.task3])
1453 assert taskset.difference(lazyset) == set([self.task1])
1454 assert lazyset.difference(taskset) == set([self.task3])
1457 assert lazyset == set([self.task3])
1459 def test_symmetric_difference(self):
1460 taskset = set([self.task1, self.task2])
1461 lazyset = LazyUUIDTaskSet(
1463 (self.task2['uuid'], self.task3['uuid']),
1466 assert taskset ^ lazyset == set([self.task1, self.task3])
1467 assert lazyset ^ taskset == set([self.task1, self.task3])
1469 taskset.symmetric_difference(lazyset),
1470 set([self.task1, self.task3]),
1473 lazyset.symmetric_difference(taskset),
1474 set([self.task1, self.task3]),
1478 assert lazyset == set([self.task1, self.task3])
1480 def test_intersection(self):
1481 taskset = set([self.task1, self.task2])
1482 lazyset = LazyUUIDTaskSet(
1484 (self.task2['uuid'], self.task3['uuid']),
1487 assert taskset & lazyset == set([self.task2])
1488 assert lazyset & taskset == set([self.task2])
1489 assert taskset.intersection(lazyset) == set([self.task2])
1490 assert lazyset.intersection(taskset) == set([self.task2])
1493 assert lazyset == set([self.task2])
1496 class TaskWarriorBackendTest(TasklibTest):
1498 def test_config(self):
1499 assert self.tw.config['nag'] == 'You have more urgent tasks.'
1500 assert self.tw.config['default.command'] == 'next'
1501 assert self.tw.config['dependency.indicator'] == 'D'