diff --git a/lib/ctt/report.py b/lib/ctt/report.py index 6418cb0..0c1608e 100755 --- a/lib/ctt/report.py +++ b/lib/ctt/report.py @@ -113,16 +113,16 @@ class Report(object): summary_report = {} for project in reports: report, report_data = reports[project] - for time in report_data: - if summary: + if summary: + for time in report_data: if not time in summary_report: summary_report[time] = report_data[time] else: summary_report[time].extend(report_data[time]) - else: - report.header() - Report.print_report_time_entries(report_data, - output_format, summary) + else: + report.header() + Report.print_report_time_entries(report_data, + output_format, summary) if summary: Report.print_report_time_entries(summary_report, output_format, summary) diff --git a/lib/ctt/test/__init__.py b/lib/ctt/test/__init__.py new file mode 100644 index 0000000..c638736 --- /dev/null +++ b/lib/ctt/test/__init__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# +# 2016 Darko Poljak (darko.poljak at gmail.com)) +# +# This file is part of ctt. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ctt. If not, see . +# +# + +import os +import unittest + +fixtures_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "fixtures")) + +class CttTestCase(unittest.TestCase): + def setUp(self): + os.environ['HOME'] = fixtures_dir diff --git a/lib/ctt/test/__main__.py b/lib/ctt/test/__main__.py new file mode 100644 index 0000000..c257ef9 --- /dev/null +++ b/lib/ctt/test/__main__.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# 2016 Darko Poljak (darko.poljak at gmail.com) +# +# This file is part of ctt. +# +# cdist is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cdist is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ctt. If not, see . +# +# + +import imp +import os +import os.path +import sys +import unittest + +base_dir = os.path.dirname(os.path.realpath(__file__)) + +test_modules = [] +for possible_test in os.listdir(base_dir): + if possible_test.startswith('test_'): + mod_path = os.path.join(base_dir, possible_test) + if os.path.isfile(mod_path): + module = os.path.splitext(os.path.basename(possible_test))[0] + test_modules.append(module) + +suites = [] +for test_module in test_modules: + module_parameters = imp.find_module(test_module, [base_dir]) + module = imp.load_module("ctt.test." + test_module, *module_parameters) + + suite = unittest.defaultTestLoader.loadTestsFromModule(module) + suites.append(suite) + +all_suites = unittest.TestSuite(suites) +unittest.TextTestRunner(verbosity=2).run(all_suites) diff --git a/lib/ctt/test/fixtures/.ctt/foo1/2016-04-07-0826/comment b/lib/ctt/test/fixtures/.ctt/foo1/2016-04-07-0826/comment new file mode 100644 index 0000000..1715acd --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/foo1/2016-04-07-0826/comment @@ -0,0 +1 @@ +foo1 diff --git a/lib/ctt/test/fixtures/.ctt/foo1/2016-04-07-0826/delta b/lib/ctt/test/fixtures/.ctt/foo1/2016-04-07-0826/delta new file mode 100644 index 0000000..ee0c264 --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/foo1/2016-04-07-0826/delta @@ -0,0 +1 @@ +6.248274 diff --git a/lib/ctt/test/fixtures/.ctt/foo1/2016-04-08-1200/comment b/lib/ctt/test/fixtures/.ctt/foo1/2016-04-08-1200/comment new file mode 100644 index 0000000..d8c668a --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/foo1/2016-04-08-1200/comment @@ -0,0 +1 @@ +foo1 12 diff --git a/lib/ctt/test/fixtures/.ctt/foo1/2016-04-08-1200/delta b/lib/ctt/test/fixtures/.ctt/foo1/2016-04-08-1200/delta new file mode 100644 index 0000000..e9c02da --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/foo1/2016-04-08-1200/delta @@ -0,0 +1 @@ +5000 diff --git a/lib/ctt/test/fixtures/.ctt/foo2/2016-04-07-0810/comment b/lib/ctt/test/fixtures/.ctt/foo2/2016-04-07-0810/comment new file mode 100644 index 0000000..54b060e --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/foo2/2016-04-07-0810/comment @@ -0,0 +1 @@ +foo2 diff --git a/lib/ctt/test/fixtures/.ctt/foo2/2016-04-07-0810/delta b/lib/ctt/test/fixtures/.ctt/foo2/2016-04-07-0810/delta new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/foo2/2016-04-07-0810/delta @@ -0,0 +1 @@ +10 diff --git a/lib/ctt/test/fixtures/.ctt/foo3/2016-04-02-2110/comment b/lib/ctt/test/fixtures/.ctt/foo3/2016-04-02-2110/comment new file mode 100644 index 0000000..c1ec6c6 --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/foo3/2016-04-02-2110/comment @@ -0,0 +1 @@ +foo3 diff --git a/lib/ctt/test/fixtures/.ctt/foo3/2016-04-02-2110/delta b/lib/ctt/test/fixtures/.ctt/foo3/2016-04-02-2110/delta new file mode 100644 index 0000000..98330a4 --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/foo3/2016-04-02-2110/delta @@ -0,0 +1 @@ +4.744116 diff --git a/lib/ctt/test/fixtures/.ctt/spam-eggs/2016-04-07-1229/comment b/lib/ctt/test/fixtures/.ctt/spam-eggs/2016-04-07-1229/comment new file mode 100644 index 0000000..36c95ea --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/spam-eggs/2016-04-07-1229/comment @@ -0,0 +1 @@ +spam-eggs diff --git a/lib/ctt/test/fixtures/.ctt/spam-eggs/2016-04-07-1229/delta b/lib/ctt/test/fixtures/.ctt/spam-eggs/2016-04-07-1229/delta new file mode 100644 index 0000000..4b20c52 --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/spam-eggs/2016-04-07-1229/delta @@ -0,0 +1 @@ +3.307185 diff --git a/lib/ctt/test/fixtures/.ctt/test-1/2016-04-02-2109/comment b/lib/ctt/test/fixtures/.ctt/test-1/2016-04-02-2109/comment new file mode 100644 index 0000000..8e2baef --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/test-1/2016-04-02-2109/comment @@ -0,0 +1 @@ +test-1 diff --git a/lib/ctt/test/fixtures/.ctt/test-1/2016-04-02-2109/delta b/lib/ctt/test/fixtures/.ctt/test-1/2016-04-02-2109/delta new file mode 100644 index 0000000..bbec04c --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/test-1/2016-04-02-2109/delta @@ -0,0 +1 @@ +2.972467 diff --git a/lib/ctt/test/fixtures/.ctt/test-2/2016-04-02-2109/comment b/lib/ctt/test/fixtures/.ctt/test-2/2016-04-02-2109/comment new file mode 100644 index 0000000..38c98d9 --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/test-2/2016-04-02-2109/comment @@ -0,0 +1 @@ +test-2 diff --git a/lib/ctt/test/fixtures/.ctt/test-2/2016-04-02-2109/delta b/lib/ctt/test/fixtures/.ctt/test-2/2016-04-02-2109/delta new file mode 100644 index 0000000..d1bf127 --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/test-2/2016-04-02-2109/delta @@ -0,0 +1 @@ +2.558247 diff --git a/lib/ctt/test/fixtures/.ctt/test-3/2016-04-02-2109/comment b/lib/ctt/test/fixtures/.ctt/test-3/2016-04-02-2109/comment new file mode 100644 index 0000000..1be6ab6 --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/test-3/2016-04-02-2109/comment @@ -0,0 +1 @@ +test-3 diff --git a/lib/ctt/test/fixtures/.ctt/test-3/2016-04-02-2109/delta b/lib/ctt/test/fixtures/.ctt/test-3/2016-04-02-2109/delta new file mode 100644 index 0000000..6a2731f --- /dev/null +++ b/lib/ctt/test/fixtures/.ctt/test-3/2016-04-02-2109/delta @@ -0,0 +1 @@ +1.821598 diff --git a/lib/ctt/test/test_listprojects.py b/lib/ctt/test/test_listprojects.py new file mode 100755 index 0000000..17ddfe7 --- /dev/null +++ b/lib/ctt/test/test_listprojects.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# 2016 Darko Poljak (darko.poljak at gmail.com) +# +# This file is part of ctt. +# +# ctt is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ctt is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ctt. If not, see . +# +# + +import unittest +import ctt +import ctt.listprojects as cttls +import ctt.test + +class ListProjectsTestCase(ctt.test.CttTestCase): + + def test_list_projects(self): + projects = cttls.ListProjects.list_projects() + expected_projects = [ 'foo1', 'foo2', 'foo3', 'spam-eggs', + 'test-1', 'test-2', 'test-3', ] + gotten_projects = sorted(projects) + self.assertEqual(gotten_projects, expected_projects) + +if __name__ == '__main__': + unittest.main() diff --git a/lib/ctt/test/test_report.py b/lib/ctt/test/test_report.py new file mode 100755 index 0000000..c715a1d --- /dev/null +++ b/lib/ctt/test/test_report.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# 2016 Darko Poljak (darko.poljak at gmail.com) +# +# This file is part of ctt. +# +# ctt is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ctt is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ctt. If not, see . +# +# + +import unittest +import ctt +import ctt.test +import ctt.report as report +import sys +from io import StringIO +import datetime +import collections + + +class ReportTestCase(ctt.test.CttTestCase): + def setUp(self): + super(ReportTestCase, self).setUp() + self.sys_stdout = sys.stdout + out = StringIO() + sys.stdout = out + self.maxDiff = None + + def _get_output(self): + sys.stdout.flush() + output = sys.stdout.getvalue().strip() + return output + + def tearDown(self): + sys.stdout = self.sys_stdout + super(ReportTestCase, self).tearDown() + + def test_print_report_time_entries(self): + report_data = { + '2016-04-07-0826': [ + { + 'start_datetime': '2016-04-07-0826', + 'end_datetime': '2016-04-07-2359', + 'comment': 'foo1', + 'delta': '6', + 'delta_seconds': '6', + 'delta_minutes': '0', + }, + ], + } + report.Report.print_report_time_entries(report_data, ctt.REPORTFORMAT, + False) + output = self._get_output() + expected_output = "2016-04-07-0826 (6): foo1" + self.assertEqual(output, expected_output) + + def test_print_report_time_entries_summary(self): + report_data = { + '2016-04-07-0826': [ + { + 'start_datetime': '2016-04-07-0826', + 'end_datetime': '2016-04-07-2359', + 'comment': 'foo1', + 'delta': '6', + 'delta_seconds': '6', + 'delta_minutes': '0', + }, + ], + '2016-04-07-0926': [ + { + 'start_datetime': '2016-04-07-0926', + 'end_datetime': '2016-04-07-2359', + 'comment': 'foo12', + 'delta': '10', + 'delta_seconds': '10', + 'delta_minutes': '0', + }, + ], + } + report.Report.print_report_time_entries(report_data, ctt.REPORTFORMAT, + True) + output = self._get_output() + expected_output = ("2016-04-07-0826 (6): foo1\n" + "2016-04-07-0926 (10): foo12") + self.assertEqual(output, expected_output) + + def test_print_reports(self): + reports = collections.OrderedDict() + for project in ('foo1', 'foo2'): + rep = report.Report(project, ('2016-04-07',), ('2016-04-08',), + ctt.REPORTFORMAT, None, None) + report_data = rep.report() + reports[project] = (rep, report_data) + expected_output = ( + "Report for foo1 between 2016-04-07 00:00:00 and 2016-04-08 23:59:59\n" + "2016-04-07-0826 (0:00:06): foo1\n" + "2016-04-08-1200 (1:23:20): foo1 12\n" + "Report for foo2 between 2016-04-07 00:00:00 and 2016-04-08 23:59:59\n" + "2016-04-07-0810 (0:00:10): foo2" + ) + rep.print_reports(reports, ctt.REPORTFORMAT, summary=False) + output = self._get_output() + self.assertEqual(output, expected_output) + + def test_print_reports_summary(self): + reports = collections.OrderedDict() + for project in ('foo1', 'foo2'): + rep = report.Report(project, ('2016-04-07',), ('2016-04-08',), + ctt.REPORTFORMAT, None, None) + report_data = rep.report() + reports[project] = (rep, report_data) + expected_output = ( + "2016-04-07-0810 (0:00:10): foo2\n" + "2016-04-07-0826 (0:00:06): foo1\n" + "2016-04-08-1200 (1:23:20): foo1 12" + ) + rep.print_reports(reports, ctt.REPORTFORMAT, summary=True) + output = self._get_output() + self.assertEqual(output, expected_output) + + + def test__init_date(self): + rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',), + ctt.REPORTFORMAT, None, None) + expected_start_date = datetime.datetime(2016, 4, 7) + expected_end_date = datetime.datetime(2016, 4, 7, 23, 59, 59) + self.assertEqual(rep.start_date, expected_start_date) + self.assertEqual(rep.end_date, expected_end_date) + + @unittest.expectedFailure + def test__init_date_fail(self): + rep = report.Report('foo1', ('2016-04-08',), ('2016-04-07',), + ctt.REPORTFORMAT, None, None) + + def test__init_date_defaults(self): + rep = report.Report('foo1', None, None, + ctt.REPORTFORMAT, None, None) + now = datetime.datetime.now() + expected_start_date = now.replace(day=1, hour=0, minute=0, second=0, + microsecond=0) + next_month = expected_start_date.replace(day=28) + datetime.timedelta(days=4) + first_day_next_month = next_month.replace(day=1) + expected_end_date = first_day_next_month - datetime.timedelta(seconds=1) + self.assertEqual(rep.start_date, expected_start_date) + self.assertEqual(rep.end_date, expected_end_date) + + @unittest.expectedFailure + def test__init_report_db_fail(self): + rep = report.Report('unexisting', ('2016-04-07',), ('2016-04-07',), + ctt.REPORTFORMAT, None, None) + + def test__init_report_db(self): + rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',), + ctt.REPORTFORMAT, None, None) + expected_db = { + '2016-04-07-0826': { + 'comment': 'foo1', + 'delta': '6.248274' + }, + } + self.assertEqual(rep._report_db, expected_db) + + def test_header(self): + rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',), + ctt.REPORTFORMAT, None, None) + rep.header() + output = self._get_output() + self.assertEqual(output, ("Report for foo1 between 2016-04-07 00:00:00" + " and 2016-04-07 23:59:59")) + + def test_summary(self): + report.Report.summary(10) + output = self._get_output() + self.assertEqual(output, "Total time tracked: 0h 0m 10s.") + + def test_total_time(self): + rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',), + ctt.REPORTFORMAT, None, None) + total_time = rep.total_time + expected_total_time = 6.248274 + self.assertEqual(total_time, expected_total_time) + + def test_report(self): + rep = report.Report('foo1', ('2016-04-07',), ('2016-04-08',), + ctt.REPORTFORMAT, None, None) + expected_entries = { + '2016-04-07-0826': [ + { + 'start_datetime': '2016-04-07-0826', + 'end_datetime': '2016-04-07-0826', + 'comment': 'foo1', + 'delta': datetime.timedelta(seconds=6), + 'delta_seconds': 6, + 'delta_minutes': 0, + }, + ], + '2016-04-08-1200': [ + { + 'start_datetime': '2016-04-08-1200', + 'end_datetime': '2016-04-08-1323', + 'comment': 'foo1 12', + 'delta': datetime.timedelta(seconds=5000), + 'delta_seconds': 5000, + 'delta_minutes': 83, + }, + ], + } + entries = rep.report() + self.assertEqual(entries, expected_entries) + + +if __name__ == '__main__': + unittest.main() diff --git a/lib/ctt/test/test_tracker.py b/lib/ctt/test/test_tracker.py new file mode 100755 index 0000000..c7c6caf --- /dev/null +++ b/lib/ctt/test/test_tracker.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# 2016 Darko Poljak (darko.poljak at gmail.com) +# +# This file is part of ctt. +# +# ctt is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ctt is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with ctt. If not, see . +# +# + +import unittest +import ctt +import ctt.test +import ctt.tracker as tr +import os +import datetime +import shutil + +class TrackerTestCase(ctt.test.CttTestCase): + + def setUp(self): + super(TrackerTestCase, self).setUp() + self.rm_dirs = [] + + def tearDown(self): + for d in self.rm_dirs: + if os.path.exists(d): + shutil.rmtree(d) + super(TrackerTestCase, self).tearDown() + + def test___init__(self): + project = 'foo1' + expected_project_dir = os.path.join(ctt.test.fixtures_dir, + os.path.join('.ctt', project)) + tracker = tr.Tracker(project) + self.assertEqual(tracker.project, project) + self.assertEqual(tracker.project_dir, expected_project_dir) + self.assertIsNone(tracker.start_datetime) + self.assertIsNone(tracker.end_datetime) + self.assertIsNone(tracker.comment) + self.assertFalse(tracker._tracked_time) + + tracker = tr.Tracker(project, start_datetime=('2016-04-09-0900',)) + self.assertEqual(tracker.start_datetime, + datetime.datetime(2016, 4, 9, 9, 0)) + self.assertIsNone(tracker.end_datetime) + self.assertFalse(tracker._tracked_time) + + tracker = tr.Tracker(project, start_datetime=('2016-04-04-0900',), + end_datetime=('2016-04-09-2000',)) + self.assertEqual(tracker.start_datetime, + datetime.datetime(2016, 4, 4, 9, 0)) + self.assertEqual(tracker.end_datetime, + datetime.datetime(2016, 4, 9, 20, 0)) + self.assertTrue(tracker._tracked_time) + + @unittest.expectedFailure + def test__init__fail(self): + project = 'foo1' + tracker = tr.Tracker(project, start_datetime=('2016-04-090900',)) + + def test_delta(self): + project = 'foo1' + start_dt = datetime.datetime(2016, 4, 4, 9, 0) + end_dt = datetime.datetime(2016, 4, 9, 20, 0) + tracker = tr.Tracker(project, start_datetime=('2016-04-04-0900',), + end_datetime=('2016-04-09-2000',)) + expected_delta = end_dt - start_dt + tracker._tracked_time = True + delta = tracker.delta(True) + self.assertEqual(delta, expected_delta.total_seconds()) + delta = tracker.delta(False) + self.assertEqual(delta, expected_delta) + tracker._tracked_time = False + delta = tracker.delta(True) + self.assertEqual(delta, 0) + delta = tracker.delta(False) + self.assertEqual(delta, datetime.timedelta()) + + def test_write_time(self): + project = 'foo1' + start_dt = '2016-04-09-1730' + tracker = tr.Tracker(project, start_datetime=(start_dt,), + comment=True) + end_dt = datetime.datetime(2016, 4, 9, hour=17, minute=45) + expected_delta = str(15 * 60) + '.0\n' # seconds + tracker.end_datetime = end_dt + tracker._tracked_time = True + expected_comment = "test" + tracker.comment = expected_comment + expected_comment += "\n" + timedir = os.path.join(ctt.test.fixtures_dir, '.ctt', project, + '2016-04-09-1730') + self.rm_dirs.append(timedir) + if os.path.exists(timedir): + shutil.rmtree(timedir) + tracker.write_time() + timefile = os.path.join(timedir, ctt.FILE_DELTA) + self.assertTrue(os.path.exists(timefile)) + with open(timefile, "r") as f: + delta = f.read() + self.assertEqual(delta, expected_delta) + commentfile = os.path.join(timedir, ctt.FILE_COMMENT) + self.assertTrue(os.path.exists(commentfile)) + with open(commentfile, "r") as f: + comment = f.read() + self.assertEqual(comment, expected_comment) + print("timedir: ", timedir) + + @unittest.expectedFailure + def test_write_time_fail(self): + project = 'foo1' + start_dt = '2016-04-09-1730' + tracker = tr.Tracker(project, start_datetime=(start_dt,), + comment=True) + end_dt = datetime.datetime(2016, 4, 9, hour=17, minute=45) + expected_delta = 15 * 60 # seconds + tracker.end_datetime = end_dt + tracker._tracked_time = True + expected_comment = "test" + tracker.comment = expected_comment + timedir = os.path.join(ctt.test.fixtures_dir, '.ctt', project, + '2016-04-09-1730') + self.rm_dirs.append(timedir) + if os.path.exists(timedir): + shutil.rmtree(timedir) + os.makedirs(timedir, mode=0o700) + tracker.write_time() + print("timedir: ", timedir) + + +if __name__ == '__main__': + unittest.main()