#!/usr/bin/env python3 """ Tests for git_oh script """ import calendar import datetime import gc import os import random import shutil import tempfile import unittest import git import git_oh class TestGitIterations(unittest.TestCase): """ Testing iterator on git commits """ def setUp(self): self.repo_path = tempfile.mkdtemp(prefix="git_oh_test_") self.repo = git.Repo.init(self.repo_path) self.actors = [git.Actor(f"User{i:d}", f"user{i:d}@localhost") for i in range(8)] def tearDown(self): shutil.rmtree(self.repo_path) def test_metadata(self): """ Fetch commits and their metadata in a simple git repository """ fromisoformat = datetime.datetime.fromisoformat commit_date_fmt = "2022-12-13T23:32:2%d+0200" commits = [ self.git_commit_mod(author=self.actors[0], commit_date=fromisoformat(commit_date_fmt % i)) for i in range(10)] found_commits = list(git_oh.iter_commits(self.repo)) set_a = {c.hexsha for c in commits} set_b = {c.hexsha for c in found_commits} self.assertEqual(set_a, set_b) for commit in found_commits: self.assertEqual(commit.author, self.actors[0]) self.assertLessEqual(commit.committed_datetime, fromisoformat(commit_date_fmt % 9)) self.assertGreaterEqual(commit.committed_datetime, fromisoformat(commit_date_fmt % 0)) def test_branches(self): """ Fetch commits from a repo with mutliple branches """ commits = [self.git_commit_mod(author=self.actors[0]) for _ in range(10)] self.repo.git.checkout("HEAD", b="new_branch") commits += [self.git_commit_mod(author=self.actors[0]) for _ in range(10)] self.repo.git.checkout("HEAD", b="another_branch") commits += [self.git_commit_mod(author=self.actors[0]) for _ in range(10)] self.repo.git.checkout("new_branch") found_commits = {commit.hexsha for commit in git_oh.iter_commits(self.repo)} commits = {commit.hexsha for commit in commits} self.assertEqual(commits, found_commits) def test_temp_remote_repo(self): """ Testing TempRemoteRepo class commit fetch __iter__ method """ commits = {self.git_commit_mod(author=self.actors[0]).hexsha for _ in range(10)} repo = git_oh.TempRemoteRepo(f"file://{self.repo_path:s}", force_remote=True) found_commits = {commit.hexsha for commit in repo} self.assertEqual(commits, found_commits) def test_temp_remote_repo_cleanup(self): """ Testing TempRemoteRepo class cleanup """ self.git_commit_mod(author=self.actors[0]) repo = git_oh.TempRemoteRepo(f"file://{self.repo_path:s}", force_remote=True) tmppath = repo.temppath self.assertTrue(os.path.isdir(tmppath)) # ref to commits should prevent gc to call repo.__del__() commits = list(repo) del repo gc.collect() self.assertTrue(os.path.isdir(tmppath)) del commits gc.collect() # not more refs, gc should have called repo.__del__() self.assertFalse(os.path.isdir(tmppath)) def test_temp_remote_repo_branches(self): """ Testing TempRemoteRepo branch commit fetch """ commits = [] for i in range(5): commits += [self.git_commit_mod(author=self.actors[0]) for _ in range(10)] self.repo.git.checkout("HEAD", b=f"branch-{i:d}") repo_url = f"file://{self.repo_path:s}" repo = git_oh.TempRemoteRepo(repo_url, force_remote=True) found_commits = {commit.hexsha for commit in repo} commits = {commit.hexsha for commit in commits} self.assertEqual(commits, found_commits) def git_commit_mod(self, msg="new commit", filename="foo.txt", **commit_kwargs): """ Add a random modification to repository and commit them Arguments : - msg : commit message - filename : the filename to modify/add/commit - **commit_kwargs : see help(git.index.base.IndexFile.commit) Returns : - the commit instance """ with open(os.path.join(self.repo_path, filename), "w+", encoding="utf-8") as repo_fp: repo_fp.write(random.choices("abcdef")[0]) self.repo.index.add([filename]) return self.repo.index.commit(msg, **commit_kwargs) class TestTimeUtils(unittest.TestCase): """ Testing in_office_hours() filter and valid_day() argument validator """ def test_valid_days(self): """ Test day argument validator/convertion """ for i, day in enumerate(calendar.day_name): self.assertEqual(i, git_oh.valid_day(day)) for i, day in enumerate(calendar.day_abbr): self.assertEqual(i, git_oh.valid_day(day)) for i in range(7): with self.subTest(day=i): self.assertEqual(i, git_oh.valid_day(str(i))) def test_office_hours_weekend(self): """ Test in_office_hours filtering weekend """ weekends = ([5,6], [0], [], [1,2,3,4,5,6]) daydelta = datetime.timedelta(days=1) for weekend in weekends: test_dt = datetime.datetime(1988,12,13,12,0,0) for _ in range(14): with self.subTest(weekend=weekend, moment=test_dt): res = git_oh.in_office_hours(test_dt, starthour=datetime.time(8,0,0), stophour=datetime.time(20,0,0), weekend=weekend) expected = test_dt.weekday() not in weekend self.assertEqual(expected, res) test_dt -= daydelta def test_office_hours(self): """ Test in_office_hours filtering hours """ starthour = datetime.time(8,0) stophour = datetime.time(20,0) weekend = (5,6) off_oh = [(7,59,59),(20,0,1), (20,1), (0,0), (1,0)] in_oh = [(8,0), (20,0), (12,0)] for time_arg in off_oh: moment = datetime.datetime(2023, 11, 1, *time_arg) self.assertFalse(git_oh.in_office_hours(moment, starthour, stophour, weekend=weekend)) for time_arg in in_oh: moment = datetime.datetime(2023, 11, 1, *time_arg) self.assertTrue(git_oh.in_office_hours(moment, starthour, stophour, weekend=weekend)) def test_office_hours_tz_drop(self): """ Checks that in_office_hours do not use tzinfo to compare moment with start & stop hours """ moment = datetime.datetime.fromisoformat("2023-11-01T08:00:01+0200") self.assertTrue(git_oh.in_office_hours( moment, datetime.time(8,0), datetime.time(8,1), weekend=[])) moment = moment.astimezone(datetime.timezone.utc) self.assertFalse(git_oh.in_office_hours( moment, datetime.time(8,0), datetime.time(8,1), weekend=[])) def test_office_hours_invalid_weekend(self): """ Test in_office_hours weekend validation """ bad_weekend = ([-1,0,1], [5,6,7], [7]) for badarg in bad_weekend: with self.assertRaises(ValueError): git_oh.in_office_hours(datetime.datetime.now(), weekend=badarg) def test_office_hours_tz_warn(self): """ in_office_hours should warn when tz aware times are given """ utc_tz = datetime.timezone.utc some_tz = datetime.timezone(datetime.timedelta(hours=2)) tz_aware = [ (datetime.time(1,2,tzinfo=utc_tz), datetime.time(0,0)), (datetime.time(3,4,tzinfo=some_tz), datetime.time(1,0)), (datetime.time(4,5), datetime.time(2,0, tzinfo=utc_tz)), (datetime.time(4,5), datetime.time(2,0, tzinfo=some_tz)), (datetime.time(1,2,tzinfo=utc_tz), datetime.time(0,0, tzinfo=some_tz)), ] for bad_args in tz_aware: with self.assertWarns(Warning): git_oh.in_office_hours(datetime.datetime.now(), *bad_args) class TestCli(unittest.TestCase): """ Testing CLI arguments parsing """ def test_weekend_parser(self): """ Testing weekend parser """ for _ in range(10): weekend = random.choices(list(range(7)), k=random.randint(1,5)) weekend_arg = " , ".join([calendar.day_abbr[i] for i in weekend]) with self.subTest(weekend=f"-w '{weekend_arg}'"): try: cliargs = ["file://fake_url", "-w", weekend_arg] args = git_oh.parse_args(cliargs) self.assertEqual(set(weekend), set(args.weekend)) except SystemExit: self.fail(msg="Parser fails to parse --weekend argument") def test_nul_weekend_parser(self): """ Testing -w NUl argument support """ try: args = git_oh.parse_args(["file://fake_url", "-w", "NUL"]) except SystemExit: self.fail(msg="Parser fails to parse -w NUL") self.assertEqual(len(args.weekend), 0) if __name__ == "__main__": unittest.main()