178 lines
8 KiB
Python
178 lines
8 KiB
Python
import os
|
|
|
|
from qa.base import BaseTestCase
|
|
from qa.shell import git, gitlint
|
|
|
|
|
|
class HookTests(BaseTestCase):
|
|
"""Integration tests for gitlint commitmsg hooks"""
|
|
|
|
VIOLATIONS = [
|
|
"gitlint: checking commit message...\n",
|
|
'1: T3 Title has trailing punctuation (.): "WIP: This ïs a title."\n',
|
|
"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This ïs a title.\"\n",
|
|
'2: B4 Second line is not empty: "Contënt on the second line"\n',
|
|
"3: B6 Body message is missing\n",
|
|
"-----------------------------------------------\n",
|
|
"gitlint: \x1b[31mYour commit message contains violations.\x1b[0m\n",
|
|
]
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.responses = []
|
|
self.response_index = 0
|
|
self.githook_output = []
|
|
|
|
# The '--staged' flag used in the commit-msg hook fetches additional information from the underlying
|
|
# git repo which means there already needs to be a commit in the repo
|
|
# (as gitlint --staged doesn't work against empty repos)
|
|
self.create_simple_commit("Commït Title\n\nCommit Body explaining commit.")
|
|
|
|
# install git commit-msg hook and assert output
|
|
output_installed = gitlint("install-hook", _cwd=self.tmp_git_repo)
|
|
commit_msg_hook_path = os.path.join(self.tmp_git_repo, ".git", "hooks", "commit-msg")
|
|
expected_installed = f"Successfully installed gitlint commit-msg hook in {commit_msg_hook_path}\n"
|
|
|
|
self.assertEqualStdout(output_installed, expected_installed)
|
|
|
|
def tearDown(self):
|
|
# uninstall git commit-msg hook and assert output
|
|
output_uninstalled = gitlint("uninstall-hook", _cwd=self.tmp_git_repo)
|
|
commit_msg_hook_path = os.path.join(self.tmp_git_repo, ".git", "hooks", "commit-msg")
|
|
expected_uninstalled = f"Successfully uninstalled gitlint commit-msg hook from {commit_msg_hook_path}\n"
|
|
|
|
self.assertEqualStdout(output_uninstalled, expected_uninstalled)
|
|
super().tearDown()
|
|
|
|
def _violations(self):
|
|
# Make a copy of the violations array so that we don't inadvertently edit it in the test (like I did :D)
|
|
return list(self.VIOLATIONS)
|
|
|
|
# callback function that captures git commit-msg hook output
|
|
|
|
def _interact(self, line, stdin):
|
|
self.githook_output.append(line)
|
|
# Answer 'yes' to question to keep violating commit-msg
|
|
if "Your commit message contains violations" in line:
|
|
response = self.responses[self.response_index]
|
|
stdin.put(f"{response}\n")
|
|
self.response_index = (self.response_index + 1) % len(self.responses)
|
|
|
|
def test_commit_hook_no_violations(self):
|
|
test_filename = self.create_simple_commit(
|
|
"This ïs a title\n\nBody contënt that should work", out=self._interact, tty_in=True
|
|
)
|
|
|
|
short_hash = self.get_last_commit_short_hash()
|
|
expected_output = [
|
|
"gitlint: checking commit message...\n",
|
|
"gitlint: \x1b[32mOK\x1b[0m (no violations in commit message)\n",
|
|
f"[main {short_hash}] This ïs a title\n",
|
|
" 1 file changed, 0 insertions(+), 0 deletions(-)\n",
|
|
f" create mode 100644 {test_filename}\n",
|
|
]
|
|
for output, expected in zip(self.githook_output, expected_output):
|
|
self.assertMultiLineEqual(output.replace("\r", ""), expected.replace("\r", ""))
|
|
|
|
def test_commit_hook_continue(self):
|
|
self.responses = ["y"]
|
|
test_filename = self.create_simple_commit(
|
|
"WIP: This ïs a title.\nContënt on the second line", out=self._interact, tty_in=True
|
|
)
|
|
|
|
# Determine short commit-msg hash, needed to determine expected output
|
|
short_hash = self.get_last_commit_short_hash()
|
|
|
|
expected_output = self._violations()
|
|
expected_output += [
|
|
"Continue with commit anyways (this keeps the current commit message)? "
|
|
"[y(es)/n(no)/e(dit)] "
|
|
f"[main {short_hash}] WIP: This ïs a title. Contënt on the second line\n",
|
|
" 1 file changed, 0 insertions(+), 0 deletions(-)\n",
|
|
f" create mode 100644 {test_filename}\n",
|
|
]
|
|
|
|
for output, expected in zip(self.githook_output, expected_output):
|
|
self.assertMultiLineEqual(output.replace("\r", ""), expected.replace("\r", ""))
|
|
|
|
def test_commit_hook_abort(self):
|
|
self.responses = ["n"]
|
|
test_filename = self.create_simple_commit(
|
|
"WIP: This ïs a title.\nContënt on the second line", out=self._interact, ok_code=1, tty_in=True
|
|
)
|
|
git("rm", "-f", test_filename, _cwd=self.tmp_git_repo)
|
|
|
|
# Determine short commit-msg hash, needed to determine expected output
|
|
|
|
expected_output = self._violations()
|
|
expected_output += [
|
|
"Continue with commit anyways (this keeps the current commit message)? "
|
|
"[y(es)/n(no)/e(dit)] "
|
|
"Commit aborted.\n",
|
|
"Your commit message: \n",
|
|
"-----------------------------------------------\n",
|
|
"WIP: This ïs a title.\n",
|
|
"Contënt on the second line\n",
|
|
"-----------------------------------------------\n",
|
|
]
|
|
|
|
for output, expected in zip(self.githook_output, expected_output):
|
|
self.assertMultiLineEqual(output.replace("\r", ""), expected.replace("\r", ""))
|
|
|
|
def test_commit_hook_edit(self):
|
|
self.responses = ["e", "y"]
|
|
env = {"EDITOR": ":"}
|
|
test_filename = self.create_simple_commit(
|
|
"WIP: This ïs a title.\nContënt on the second line", out=self._interact, env=env, tty_in=True
|
|
)
|
|
git("rm", "-f", test_filename, _cwd=self.tmp_git_repo)
|
|
|
|
short_hash = git("rev-parse", "--short", "HEAD", _cwd=self.tmp_git_repo, _tty_in=True).replace("\n", "")
|
|
|
|
# Determine short commit-msg hash, needed to determine expected output
|
|
|
|
expected_output = self._violations()
|
|
expected_output += [
|
|
"Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] "
|
|
+ self._violations()[0]
|
|
]
|
|
expected_output += self._violations()[1:]
|
|
expected_output += [
|
|
"Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] "
|
|
f"[main {short_hash}] WIP: This ïs a title. Contënt on the second line\n",
|
|
" 1 file changed, 0 insertions(+), 0 deletions(-)\n",
|
|
f" create mode 100644 {test_filename}\n",
|
|
]
|
|
|
|
for output, expected in zip(self.githook_output, expected_output):
|
|
self.assertMultiLineEqual(output.replace("\r", ""), expected.replace("\r", ""))
|
|
|
|
def test_commit_hook_worktree(self):
|
|
"""Tests that hook installation and un-installation also work in git worktrees.
|
|
Test steps:
|
|
```sh
|
|
git init <tmpdir>
|
|
cd <tmpdir>
|
|
git worktree add <worktree-tempdir>
|
|
cd <worktree-tempdir>
|
|
gitlint install-hook
|
|
gitlint uninstall-hook
|
|
```
|
|
"""
|
|
tmp_git_repo = self.create_tmp_git_repo()
|
|
self.create_simple_commit("Simple title\n\nContënt in the body", git_repo=tmp_git_repo)
|
|
|
|
worktree_dir = self.generate_temp_path()
|
|
self.tmp_git_repos.append(worktree_dir) # make sure we clean up the worktree afterwards
|
|
|
|
git("worktree", "add", worktree_dir, _cwd=tmp_git_repo, _tty_in=True)
|
|
|
|
output_installed = gitlint("install-hook", _cwd=worktree_dir)
|
|
expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg")
|
|
expected_msg = f"Successfully installed gitlint commit-msg hook in {expected_hook_path}\n"
|
|
self.assertEqualStdout(output_installed, expected_msg)
|
|
|
|
output_uninstalled = gitlint("uninstall-hook", _cwd=worktree_dir)
|
|
expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg")
|
|
expected_msg = f"Successfully uninstalled gitlint commit-msg hook from {expected_hook_path}\n"
|
|
self.assertEqualStdout(output_uninstalled, expected_msg)
|