git_oh/test.py
Yann Weber 15eb1c817a Implements output, filters CLI arguments, and period aggregation
- Implement a function aggregating commit stats by author and/or by week|month
- Deletes type hints
- Modify TempRemoteRepo to be able to force remote fetch even for local
  repository (usefull in tests)
2023-11-26 16:30:54 +01:00

265 lines
9.3 KiB
Python

#!/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()