summaryrefslogtreecommitdiffstats
path: root/gitlint/tests
diff options
context:
space:
mode:
Diffstat (limited to 'gitlint/tests')
-rw-r--r--gitlint/tests/base.py41
-rw-r--r--gitlint/tests/cli/test_cli.py78
-rw-r--r--gitlint/tests/cli/test_cli_hooks.py172
-rw-r--r--gitlint/tests/config/test_config.py62
-rw-r--r--gitlint/tests/config/test_config_builder.py81
-rw-r--r--gitlint/tests/config/test_config_precedence.py26
-rw-r--r--gitlint/tests/contrib/rules/__init__.py0
-rw-r--r--gitlint/tests/contrib/rules/test_conventional_commit.py (renamed from gitlint/tests/contrib/test_conventional_commit.py)4
-rw-r--r--gitlint/tests/contrib/rules/test_signedoff_by.py (renamed from gitlint/tests/contrib/test_signedoff_by.py)0
-rw-r--r--gitlint/tests/contrib/test_contrib_rules.py2
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_contrib_1 (renamed from gitlint/tests/expected/test_cli/test_contrib_1)2
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_debug_1 (renamed from gitlint/tests/expected/test_cli/test_debug_1)22
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_input_stream_1 (renamed from gitlint/tests/expected/test_cli/test_input_stream_1)0
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_input_stream_debug_1 (renamed from gitlint/tests/expected/test_cli/test_input_stream_debug_1)0
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2 (renamed from gitlint/tests/expected/test_cli/test_input_stream_debug_2)10
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_1 (renamed from gitlint/tests/expected/test_cli/test_lint_multiple_commits_1)0
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_config_1 (renamed from gitlint/tests/expected/test_cli/test_lint_multiple_commits_config_1)0
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_1 (renamed from gitlint/tests/expected/test_cli/test_lint_staged_msg_filename_1)0
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_2 (renamed from gitlint/tests/expected/test_cli/test_lint_staged_msg_filename_2)14
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_1 (renamed from gitlint/tests/expected/test_cli/test_lint_staged_stdin_1)0
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2 (renamed from gitlint/tests/expected/test_cli/test_lint_staged_stdin_2)14
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_named_rules_14
-rw-r--r--gitlint/tests/expected/cli/test_cli/test_named_rules_282
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stderr2
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stdout5
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stderr6
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stdout14
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stderr2
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stdout4
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stderr2
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stdout8
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stderr2
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stdout5
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_no_violations_1_stdout2
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stderr2
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stdout5
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stderr2
-rw-r--r--gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stdout4
-rw-r--r--gitlint/tests/git/test_git.py10
-rw-r--r--gitlint/tests/git/test_git_commit.py40
-rw-r--r--gitlint/tests/rules/test_body_rules.py46
-rw-r--r--gitlint/tests/rules/test_configuration_rules.py38
-rw-r--r--gitlint/tests/rules/test_meta_rules.py9
-rw-r--r--gitlint/tests/rules/test_rules.py5
-rw-r--r--gitlint/tests/rules/test_title_rules.py34
-rw-r--r--gitlint/tests/rules/test_user_rules.py137
-rw-r--r--gitlint/tests/samples/commit_message/no-violations6
-rw-r--r--gitlint/tests/samples/config/named-rules8
-rw-r--r--gitlint/tests/test_hooks.py14
-rw-r--r--gitlint/tests/test_lint.py94
-rw-r--r--gitlint/tests/test_options.py122
-rw-r--r--gitlint/tests/test_utils.py20
52 files changed, 1062 insertions, 200 deletions
diff --git a/gitlint/tests/base.py b/gitlint/tests/base.py
index add4d71..c8f68c4 100644
--- a/gitlint/tests/base.py
+++ b/gitlint/tests/base.py
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
+import contextlib
import copy
import io
import logging
import os
import re
+import shutil
+import tempfile
try:
# python 2.x
@@ -21,7 +24,7 @@ except ImportError:
from unittest.mock import patch # pylint: disable=no-name-in-module, import-error
from gitlint.git import GitContext
-from gitlint.utils import ustr, LOG_FORMAT, DEFAULT_ENCODING
+from gitlint.utils import ustr, IS_PY2, LOG_FORMAT, DEFAULT_ENCODING
# unittest2's assertRaisesRegex doesn't do unicode comparison.
@@ -57,6 +60,15 @@ class BaseTestCase(unittest.TestCase):
logging.getLogger('gitlint').propagate = False
@staticmethod
+ @contextlib.contextmanager
+ def tempdir():
+ tmpdir = tempfile.mkdtemp()
+ try:
+ yield tmpdir
+ finally:
+ shutil.rmtree(tmpdir)
+
+ @staticmethod
def get_sample_path(filename=""):
# Don't join up empty files names because this will add a trailing slash
if filename == "":
@@ -73,6 +85,15 @@ class BaseTestCase(unittest.TestCase):
return sample
@staticmethod
+ def patch_input(side_effect):
+ """ Patches the built-in input() with a provided side-effect """
+ module_path = "builtins.input"
+ if IS_PY2:
+ module_path = "__builtin__.raw_input"
+ patched_module = patch(module_path, side_effect=side_effect)
+ return patched_module
+
+ @staticmethod
def get_expected(filename="", variable_dict=None):
""" Utility method to read an expected file from gitlint/tests/expected and return it as a string.
Optionally replace template variables specified by variable_dict. """
@@ -129,6 +150,24 @@ class BaseTestCase(unittest.TestCase):
return super(BaseTestCase, self).assertRaisesRegex(expected_exception, re.escape(expected_regex),
*args, **kwargs)
+ @contextlib.contextmanager
+ def assertRaisesMessage(self, expected_exception, expected_msg): # pylint: disable=invalid-name
+ """ Asserts an exception has occurred with a given error message """
+ try:
+ yield
+ except expected_exception as exc:
+ exception_msg = ustr(exc)
+ if exception_msg != expected_msg:
+ error = u"Right exception, wrong message:\n got: {0}\n expected: {1}"
+ raise self.fail(error.format(exception_msg, expected_msg))
+ # else: everything is fine, just return
+ return
+ except Exception as exc:
+ raise self.fail(u"Expected '{0}' got '{1}'".format(expected_exception.__name__, exc.__class__.__name__))
+
+ # No exception raised while we expected one
+ raise self.fail("Expected to raise {0}, didn't get an exception at all".format(expected_exception.__name__))
+
def object_equality_test(self, obj, attr_list, ctor_kwargs=None):
""" Helper function to easily implement object equality tests.
Creates an object clone for every passed attribute and checks for (in)equality
diff --git a/gitlint/tests/cli/test_cli.py b/gitlint/tests/cli/test_cli.py
index 4d47f35..88bcfb7 100644
--- a/gitlint/tests/cli/test_cli.py
+++ b/gitlint/tests/cli/test_cli.py
@@ -1,12 +1,10 @@
# -*- coding: utf-8 -*-
-import contextlib
+
import io
import os
import sys
import platform
-import shutil
-import tempfile
import arrow
@@ -34,15 +32,6 @@ from gitlint import __version__
from gitlint.utils import DEFAULT_ENCODING
-@contextlib.contextmanager
-def tempdir():
- tmpdir = tempfile.mkdtemp()
- try:
- yield tmpdir
- finally:
- shutil.rmtree(tmpdir)
-
-
class CLITests(BaseTestCase):
USAGE_ERROR_CODE = 253
GIT_CONTEXT_ERROR_CODE = 254
@@ -64,7 +53,8 @@ class CLITests(BaseTestCase):
def get_system_info_dict():
""" Returns a dict with items related to system values logged by `gitlint --debug` """
return {'platform': platform.platform(), "python_version": sys.version, 'gitlint_version': __version__,
- 'GITLINT_USE_SH_LIB': BaseTestCase.GITLINT_USE_SH_LIB, 'target': os.path.realpath(os.getcwd())}
+ 'GITLINT_USE_SH_LIB': BaseTestCase.GITLINT_USE_SH_LIB, 'target': os.path.realpath(os.getcwd()),
+ 'DEFAULT_ENCODING': DEFAULT_ENCODING}
def test_version(self):
""" Test for --version option """
@@ -118,7 +108,7 @@ class CLITests(BaseTestCase):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--commits", "foo...bar"])
- self.assertEqual(stderr.getvalue(), self.get_expected("test_cli/test_lint_multiple_commits_1"))
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_multiple_commits_1"))
self.assertEqual(result.exit_code, 3)
@patch('gitlint.cli.get_stdin_data', return_value=False)
@@ -152,7 +142,7 @@ class CLITests(BaseTestCase):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--commits", "foo...bar"])
# We expect that the second commit has no failures because of 'gitlint-ignore: T3' in its commit msg body
- self.assertEqual(stderr.getvalue(), self.get_expected("test_cli/test_lint_multiple_commits_config_1"))
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_multiple_commits_config_1"))
self.assertEqual(result.exit_code, 3)
@patch('gitlint.cli.get_stdin_data', return_value=False)
@@ -205,7 +195,7 @@ class CLITests(BaseTestCase):
""" Test for linting when a message is passed via stdin """
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli)
- self.assertEqual(stderr.getvalue(), self.get_expected("test_cli/test_input_stream_1"))
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_input_stream_1"))
self.assertEqual(result.exit_code, 3)
self.assertEqual(result.output, "")
@@ -215,11 +205,11 @@ class CLITests(BaseTestCase):
This tests specifically that git commit meta is not fetched when not passing --staged """
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--debug"])
- self.assertEqual(stderr.getvalue(), self.get_expected("test_cli/test_input_stream_debug_1"))
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_input_stream_debug_1"))
self.assertEqual(result.exit_code, 3)
self.assertEqual(result.output, "")
expected_kwargs = self.get_system_info_dict()
- expected_logs = self.get_expected('test_cli/test_input_stream_debug_2', expected_kwargs)
+ expected_logs = self.get_expected('cli/test_cli/test_input_stream_debug_2', expected_kwargs)
self.assert_logged(expected_logs)
@patch('gitlint.cli.get_stdin_data', return_value="Should be ignored\n")
@@ -259,12 +249,12 @@ class CLITests(BaseTestCase):
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--debug", "--staged"])
- self.assertEqual(stderr.getvalue(), self.get_expected("test_cli/test_lint_staged_stdin_1"))
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_staged_stdin_1"))
self.assertEqual(result.exit_code, 3)
self.assertEqual(result.output, "")
expected_kwargs = self.get_system_info_dict()
- expected_logs = self.get_expected('test_cli/test_lint_staged_stdin_2', expected_kwargs)
+ expected_logs = self.get_expected('cli/test_cli/test_lint_staged_stdin_2', expected_kwargs)
self.assert_logged(expected_logs)
@patch('arrow.now', return_value=arrow.get("2020-02-19T12:18:46.675182+01:00"))
@@ -280,19 +270,19 @@ class CLITests(BaseTestCase):
u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree
]
- with tempdir() as tmpdir:
+ with self.tempdir() as tmpdir:
msg_filename = os.path.join(tmpdir, "msg")
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
f.write(u"WIP: msg-filename tïtle\n")
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--debug", "--staged", "--msg-filename", msg_filename])
- self.assertEqual(stderr.getvalue(), self.get_expected("test_cli/test_lint_staged_msg_filename_1"))
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_staged_msg_filename_1"))
self.assertEqual(result.exit_code, 2)
self.assertEqual(result.output, "")
expected_kwargs = self.get_system_info_dict()
- expected_logs = self.get_expected('test_cli/test_lint_staged_msg_filename_2', expected_kwargs)
+ expected_logs = self.get_expected('cli/test_cli/test_lint_staged_msg_filename_2', expected_kwargs)
self.assert_logged(expected_logs)
@patch('gitlint.cli.get_stdin_data', return_value=False)
@@ -306,7 +296,7 @@ class CLITests(BaseTestCase):
def test_msg_filename(self, _):
expected_output = u"3: B6 Body message is missing\n"
- with tempdir() as tmpdir:
+ with self.tempdir() as tmpdir:
msg_filename = os.path.join(tmpdir, "msg")
with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
f.write(u"Commït title\n")
@@ -375,7 +365,7 @@ class CLITests(BaseTestCase):
u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha>
u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree
u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00abc\n"
- u"föo\nbar",
+ u"föobar\nbar",
u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha>
u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree
]
@@ -394,7 +384,7 @@ class CLITests(BaseTestCase):
expected_kwargs = self.get_system_info_dict()
expected_kwargs.update({'config_path': config_path})
- expected_logs = self.get_expected('test_cli/test_debug_1', expected_kwargs)
+ expected_logs = self.get_expected('cli/test_cli/test_debug_1', expected_kwargs)
self.assert_logged(expected_logs)
@patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n")
@@ -403,7 +393,7 @@ class CLITests(BaseTestCase):
# Test extra-path pointing to a directory
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
extra_path = self.get_sample_path("user_rules")
- result = self.cli.invoke(cli.cli, ["--extra-path", extra_path, "--debug"])
+ result = self.cli.invoke(cli.cli, ["--extra-path", extra_path])
expected_output = u"1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
"3: B6 Body message is missing\n"
self.assertEqual(stderr.getvalue(), expected_output)
@@ -412,7 +402,7 @@ class CLITests(BaseTestCase):
# Test extra-path pointing to a file
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
extra_path = self.get_sample_path(os.path.join("user_rules", "my_commit_rules.py"))
- result = self.cli.invoke(cli.cli, ["--extra-path", extra_path, "--debug"])
+ result = self.cli.invoke(cli.cli, ["--extra-path", extra_path])
expected_output = u"1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \
"3: B6 Body message is missing\n"
self.assertEqual(stderr.getvalue(), expected_output)
@@ -423,7 +413,7 @@ class CLITests(BaseTestCase):
# Test enabled contrib rules
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--contrib", "contrib-title-conventional-commits,CC1"])
- expected_output = self.get_expected('test_cli/test_contrib_1')
+ expected_output = self.get_expected('cli/test_cli/test_contrib_1')
self.assertEqual(stderr.getvalue(), expected_output)
self.assertEqual(result.exit_code, 3)
@@ -469,13 +459,14 @@ class CLITests(BaseTestCase):
@patch('gitlint.cli.get_stdin_data', return_value=False)
def test_target(self, _):
""" Test for the --target option """
- os.environ["LANGUAGE"] = "C" # Force language to english so we can check for error message
- result = self.cli.invoke(cli.cli, ["--target", "/tmp"])
- # We expect gitlint to tell us that /tmp is not a git repo (this proves that it takes the target parameter
- # into account).
- expected_path = os.path.realpath("/tmp")
- self.assertEqual(result.output, "%s is not a git repository.\n" % expected_path)
- self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
+ with self.tempdir() as tmpdir:
+ tmpdir_path = os.path.realpath(tmpdir)
+ os.environ["LANGUAGE"] = "C" # Force language to english so we can check for error message
+ result = self.cli.invoke(cli.cli, ["--target", tmpdir_path])
+ # We expect gitlint to tell us that /tmp is not a git repo (this proves that it takes the target parameter
+ # into account).
+ self.assertEqual(result.output, "%s is not a git repository.\n" % tmpdir_path)
+ self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE)
def test_target_negative(self):
""" Negative test for the --target option """
@@ -539,3 +530,18 @@ class CLITests(BaseTestCase):
self.assert_log_contains(u"DEBUG: gitlint.cli No commits in range \"master...HEAD\"")
self.assertEqual(result.exit_code, 0)
+
+ @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tëst tïtle")
+ def test_named_rules(self, _):
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ config_path = self.get_sample_path(os.path.join("config", "named-rules"))
+ result = self.cli.invoke(cli.cli, ["--config", config_path, "--debug"])
+ self.assertEqual(result.output, "")
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_named_rules_1"))
+ self.assertEqual(result.exit_code, 4)
+
+ # Assert debug logs are correct
+ expected_kwargs = self.get_system_info_dict()
+ expected_kwargs.update({'config_path': config_path})
+ expected_logs = self.get_expected('cli/test_cli/test_named_rules_2', expected_kwargs)
+ self.assert_logged(expected_logs)
diff --git a/gitlint/tests/cli/test_cli_hooks.py b/gitlint/tests/cli/test_cli_hooks.py
index 0564808..b5e7fc4 100644
--- a/gitlint/tests/cli/test_cli_hooks.py
+++ b/gitlint/tests/cli/test_cli_hooks.py
@@ -1,11 +1,19 @@
# -*- coding: utf-8 -*-
+import io
import os
from click.testing import CliRunner
try:
# python 2.x
+ from StringIO import StringIO
+except ImportError:
+ # python 3.x
+ from io import StringIO # pylint: disable=ungrouped-imports
+
+try:
+ # python 2.x
from mock import patch
except ImportError:
# python 3.x
@@ -16,6 +24,8 @@ from gitlint import cli
from gitlint import hooks
from gitlint import config
+from gitlint.utils import DEFAULT_ENCODING
+
class CLIHookTests(BaseTestCase):
USAGE_ERROR_CODE = 253
@@ -94,3 +104,165 @@ class CLIHookTests(BaseTestCase):
expected_config = config.LintConfig()
expected_config.target = os.path.realpath(os.getcwd())
uninstall_hook.assert_called_once_with(expected_config)
+
+ def test_hook_no_tty(self):
+ """ Test for run-hook subcommand.
+ When no TTY is available (like is the case for this test), the hook will abort after the first check.
+ """
+
+ # No need to patch git as we're passing a msg-filename to run-hook, so no git calls are made.
+ # Note that this is the case when passing --staged as well, but that's tested as part of the integration tests
+ # (=end-to-end scenario).
+
+ # Ideally we'd be able to assert that run-hook internally calls the lint cli command, but couldn't make
+ # that work. Have tried many different variatons of mocking and patching without avail. For now, we just
+ # check the output which indirectly proves the same thing.
+
+ with self.tempdir() as tmpdir:
+ msg_filename = os.path.join(tmpdir, u"hür")
+ with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
+ f.write(u"WIP: tïtle\n")
+
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
+ self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_hook_no_tty_1_stdout'))
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli_hooks/test_hook_no_tty_1_stderr"))
+
+ # exit code is 1 because aborted (no stdin available)
+ self.assertEqual(result.exit_code, 1)
+
+ @patch('gitlint.cli.shell')
+ def test_hook_edit(self, shell):
+ """ Test for run-hook subcommand, answering 'e(dit)' after commit-hook """
+
+ set_editors = [None, u"myeditor"]
+ expected_editors = [u"vim -n", u"myeditor"]
+ commit_messages = [u"WIP: höok edit 1", u"WIP: höok edit 2"]
+
+ for i in range(0, len(set_editors)):
+ if set_editors[i]:
+ os.environ['EDITOR'] = set_editors[i]
+
+ with self.patch_input(['e', 'e', 'n']):
+ with self.tempdir() as tmpdir:
+ msg_filename = os.path.realpath(os.path.join(tmpdir, u"hür"))
+ with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
+ f.write(commit_messages[i] + "\n")
+
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
+ self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_hook_edit_1_stdout',
+ {"commit_msg": commit_messages[i]}))
+ expected = self.get_expected("cli/test_cli_hooks/test_hook_edit_1_stderr",
+ {"commit_msg": commit_messages[i]})
+ self.assertEqual(stderr.getvalue(), expected)
+
+ # exit code = number of violations
+ self.assertEqual(result.exit_code, 2)
+
+ shell.assert_called_with(expected_editors[i] + " " + msg_filename)
+ self.assert_log_contains(u"DEBUG: gitlint.cli run-hook: editing commit message")
+ self.assert_log_contains(u"DEBUG: gitlint.cli run-hook: {0} {1}".format(expected_editors[i],
+ msg_filename))
+
+ def test_hook_no(self):
+ """ Test for run-hook subcommand, answering 'n(o)' after commit-hook """
+
+ with self.patch_input(['n']):
+ with self.tempdir() as tmpdir:
+ msg_filename = os.path.join(tmpdir, u"hür")
+ with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
+ f.write(u"WIP: höok no\n")
+
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
+ self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_hook_no_1_stdout'))
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli_hooks/test_hook_no_1_stderr"))
+
+ # We decided not to keep the commit message: hook returns number of violations (>0)
+ # This will cause git to abort the commit
+ self.assertEqual(result.exit_code, 2)
+ self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message declined")
+
+ def test_hook_yes(self):
+ """ Test for run-hook subcommand, answering 'y(es)' after commit-hook """
+ with self.patch_input(['y']):
+ with self.tempdir() as tmpdir:
+ msg_filename = os.path.join(tmpdir, u"hür")
+ with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f:
+ f.write(u"WIP: höok yes\n")
+
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"])
+ self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_hook_yes_1_stdout'))
+ self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli_hooks/test_hook_yes_1_stderr"))
+
+ # Exit code is 0 because we decide to keep the commit message
+ # This will cause git to keep the commit
+ self.assertEqual(result.exit_code, 0)
+ self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message accepted")
+
+ @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: Test hook stdin tïtle\n")
+ def test_hook_stdin_violations(self, _):
+ """ Test for passing stdin data to run-hook, expecting some violations. Equivalent of:
+ $ echo "WIP: Test hook stdin tïtle" | gitlint run-hook
+ """
+
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ result = self.cli.invoke(cli.cli, ["run-hook"])
+ expected_stderr = self.get_expected('cli/test_cli_hooks/test_hook_stdin_violations_1_stderr')
+ self.assertEqual(stderr.getvalue(), expected_stderr)
+ self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_hook_stdin_violations_1_stdout'))
+ # Hook will auto-abort because we're using stdin. Abort = exit code 1
+ self.assertEqual(result.exit_code, 1)
+
+ @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n\nTest bödy that is long enough")
+ def test_hook_stdin_no_violations(self, _):
+ """ Test for passing stdin data to run-hook, expecting *NO* violations, Equivalent of:
+ $ echo -e "Test tïtle\n\nTest bödy that is long enough" | gitlint run-hook
+ """
+
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ result = self.cli.invoke(cli.cli, ["run-hook"])
+ self.assertEqual(stderr.getvalue(), "") # no errors = no stderr output
+ expected_stdout = self.get_expected('cli/test_cli_hooks/test_hook_stdin_no_violations_1_stdout')
+ self.assertEqual(result.output, expected_stdout)
+ self.assertEqual(result.exit_code, 0)
+
+ @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: Test hook config tïtle\n")
+ def test_hook_config(self, _):
+ """ Test that gitlint still respects config when running run-hook, equivalent of:
+ $ echo "WIP: Test hook config tïtle" | gitlint -c title-max-length.line-length=5 --ignore B6 run-hook
+ """
+
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ result = self.cli.invoke(cli.cli, ["-c", "title-max-length.line-length=5", "--ignore", "B6", "run-hook"])
+ self.assertEqual(stderr.getvalue(), self.get_expected('cli/test_cli_hooks/test_hook_config_1_stderr'))
+ self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_hook_config_1_stdout'))
+ # Hook will auto-abort because we're using stdin. Abort = exit code 1
+ self.assertEqual(result.exit_code, 1)
+
+ @patch('gitlint.cli.get_stdin_data', return_value=False)
+ @patch('gitlint.git.sh')
+ def test_hook_local_commit(self, sh, _):
+ """ Test running the hook on the last commit-msg from the local repo, equivalent of:
+ $ gitlint run-hook
+ and then choosing 'e'
+ """
+ sh.git.side_effect = [
+ "6f29bf81a8322a04071bb794666e48c443a90360",
+ u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n"
+ u"WIP: commït-title\n\ncommït-body",
+ u"#", # git config --get core.commentchar
+ u"commit-1-branch-1\ncommit-1-branch-2\n",
+ u"file1.txt\npåth/to/file2.txt\n"
+ ]
+
+ with self.patch_input(['e']):
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ result = self.cli.invoke(cli.cli, ["run-hook"])
+ expected = self.get_expected('cli/test_cli_hooks/test_hook_local_commit_1_stderr')
+ self.assertEqual(stderr.getvalue(), expected)
+ self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_hook_local_commit_1_stdout'))
+ # If we can't edit the message, run-hook follows regular gitlint behavior and exit code = # violations
+ self.assertEqual(result.exit_code, 2)
diff --git a/gitlint/tests/config/test_config.py b/gitlint/tests/config/test_config.py
index d3fdc2c..b981a86 100644
--- a/gitlint/tests/config/test_config.py
+++ b/gitlint/tests/config/test_config.py
@@ -30,18 +30,18 @@ class LintConfigTests(BaseTestCase):
# non-existing rule
expected_error_msg = u"No such rule 'föobar'"
- with self.assertRaisesRegex(LintConfigError, expected_error_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config.set_rule_option(u'föobar', u'lïne-length', 60)
# non-existing option
expected_error_msg = u"Rule 'title-max-length' has no option 'föobar'"
- with self.assertRaisesRegex(LintConfigError, expected_error_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config.set_rule_option('title-max-length', u'föobar', 60)
# invalid option value
expected_error_msg = u"'föo' is not a valid value for option 'title-max-length.line-length'. " + \
u"Option 'line-length' must be a positive integer (current value: 'föo')."
- with self.assertRaisesRegex(LintConfigError, expected_error_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config.set_rule_option('title-max-length', 'line-length', u"föo")
def test_set_general_option(self):
@@ -124,7 +124,7 @@ class LintConfigTests(BaseTestCase):
expected_rule_option = options.ListOption(
"types",
- ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert"],
+ ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert", "ci", "build"],
"Comma separated list of allowed commit types.",
)
@@ -151,14 +151,14 @@ class LintConfigTests(BaseTestCase):
def test_contrib_negative(self):
config = LintConfig()
# non-existent contrib rule
- with self.assertRaisesRegex(LintConfigError, u"No contrib rule with id or name 'föo' found."):
+ with self.assertRaisesMessage(LintConfigError, u"No contrib rule with id or name 'föo' found."):
config.contrib = u"contrib-title-conventional-commits,föo"
# UserRuleError, RuleOptionError should be re-raised as LintConfigErrors
side_effects = [rules.UserRuleError(u"üser-rule"), options.RuleOptionError(u"rüle-option")]
for side_effect in side_effects:
with patch('gitlint.config.rule_finder.find_rule_classes', side_effect=side_effect):
- with self.assertRaisesRegex(LintConfigError, ustr(side_effect)):
+ with self.assertRaisesMessage(LintConfigError, ustr(side_effect)):
config.contrib = u"contrib-title-conventional-commits"
def test_extra_path(self):
@@ -185,36 +185,36 @@ class LintConfigTests(BaseTestCase):
config = LintConfig()
regex = u"Option extra-path must be either an existing directory or file (current value: 'föo/bar')"
# incorrect extra_path
- with self.assertRaisesRegex(LintConfigError, regex):
+ with self.assertRaisesMessage(LintConfigError, regex):
config.extra_path = u"föo/bar"
# extra path contains classes with errors
- with self.assertRaisesRegex(LintConfigError,
- "User-defined rule class 'MyUserLineRule' must have a 'validate' method"):
+ with self.assertRaisesMessage(LintConfigError,
+ "User-defined rule class 'MyUserLineRule' must have a 'validate' method"):
config.extra_path = self.get_sample_path("user_rules/incorrect_linerule")
def test_set_general_option_negative(self):
config = LintConfig()
# Note that we shouldn't test whether we can set unicode because python just doesn't allow unicode attributes
- with self.assertRaisesRegex(LintConfigError, "'foo' is not a valid gitlint option"):
+ with self.assertRaisesMessage(LintConfigError, "'foo' is not a valid gitlint option"):
config.set_general_option("foo", u"bår")
# try setting _config_path, this is a real attribute of LintConfig, but the code should prevent it from
# being set
- with self.assertRaisesRegex(LintConfigError, "'_config_path' is not a valid gitlint option"):
+ with self.assertRaisesMessage(LintConfigError, "'_config_path' is not a valid gitlint option"):
config.set_general_option("_config_path", u"bår")
# invalid verbosity
incorrect_values = [-1, u"föo"]
for value in incorrect_values:
expected_msg = u"Option 'verbosity' must be a positive integer (current value: '{0}')".format(value)
- with self.assertRaisesRegex(LintConfigError, expected_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_msg):
config.verbosity = value
incorrect_values = [4]
for value in incorrect_values:
- with self.assertRaisesRegex(LintConfigError, "Option 'verbosity' must be set between 0 and 3"):
+ with self.assertRaisesMessage(LintConfigError, "Option 'verbosity' must be set between 0 and 3"):
config.verbosity = value
# invalid ignore_xxx_commits
@@ -224,8 +224,8 @@ class LintConfigTests(BaseTestCase):
for attribute in ignore_attributes:
for value in incorrect_values:
option_name = attribute.replace("_", "-")
- with self.assertRaisesRegex(LintConfigError,
- "Option '{0}' must be either 'true' or 'false'".format(option_name)):
+ with self.assertRaisesMessage(LintConfigError,
+ "Option '{0}' must be either 'true' or 'false'".format(option_name)):
setattr(config, attribute, value)
# invalid ignore -> not here because ignore is a ListOption which converts everything to a string before
@@ -234,15 +234,15 @@ class LintConfigTests(BaseTestCase):
# invalid boolean options
for attribute in ['debug', 'staged', 'ignore_stdin']:
option_name = attribute.replace("_", "-")
- with self.assertRaisesRegex(LintConfigError,
- "Option '{0}' must be either 'true' or 'false'".format(option_name)):
+ with self.assertRaisesMessage(LintConfigError,
+ "Option '{0}' must be either 'true' or 'false'".format(option_name)):
setattr(config, attribute, u"föobar")
# extra-path has its own negative test
# invalid target
- with self.assertRaisesRegex(LintConfigError,
- u"Option target must be an existing directory (current value: 'föo/bar')"):
+ with self.assertRaisesMessage(LintConfigError,
+ u"Option target must be an existing directory (current value: 'föo/bar')"):
config.target = u"föo/bar"
def test_ignore_independent_from_rules(self):
@@ -254,6 +254,30 @@ class LintConfigTests(BaseTestCase):
self.assertEqual(config.ignore, ["T1", "T2"])
self.assertSequenceEqual(config.rules, original_rules)
+ def test_config_equality(self):
+ self.assertEqual(LintConfig(), LintConfig())
+ self.assertNotEqual(LintConfig(), LintConfigGenerator())
+
+ # Ensure LintConfig are not equal if they differ on their attributes
+ attrs = [("verbosity", 1), ("rules", []), ("ignore_stdin", True), ("debug", True),
+ ("ignore", ["T1"]), ("staged", True), ("_config_path", self.get_sample_path()),
+ ("ignore_merge_commits", False), ("ignore_fixup_commits", False),
+ ("ignore_squash_commits", False), ("ignore_revert_commits", False),
+ ("extra_path", self.get_sample_path("user_rules")), ("target", self.get_sample_path()),
+ ("contrib", ["CC1"])]
+ for attr, val in attrs:
+ config = LintConfig()
+ setattr(config, attr, val)
+ self.assertNotEqual(LintConfig(), config)
+
+ # Other attributes don't matter
+ config1 = LintConfig()
+ config2 = LintConfig()
+ config1.foo = u"bår"
+ self.assertEqual(config1, config2)
+ config2.foo = u"dūr"
+ self.assertEqual(config1, config2)
+
class LintConfigGeneratorTests(BaseTestCase):
@staticmethod
diff --git a/gitlint/tests/config/test_config_builder.py b/gitlint/tests/config/test_config_builder.py
index 051a52f..5a28c9f 100644
--- a/gitlint/tests/config/test_config_builder.py
+++ b/gitlint/tests/config/test_config_builder.py
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
+import copy
from gitlint.tests.base import BaseTestCase
from gitlint.config import LintConfig, LintConfigBuilder, LintConfigError
+from gitlint import rules
+
class LintConfigBuilderTests(BaseTestCase):
def test_set_option(self):
@@ -88,12 +91,13 @@ class LintConfigBuilderTests(BaseTestCase):
# bad config file load
foo_path = self.get_sample_path(u"föo")
expected_error_msg = u"Invalid file path: {0}".format(foo_path)
- with self.assertRaisesRegex(LintConfigError, expected_error_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.set_from_config_file(foo_path)
# error during file parsing
path = self.get_sample_path("config/no-sections")
expected_error_msg = u"File contains no section headers."
+ # We only match the start of the message here, since the exact message can vary depending on platform
with self.assertRaisesRegex(LintConfigError, expected_error_msg):
config_builder.set_from_config_file(path)
@@ -102,7 +106,7 @@ class LintConfigBuilderTests(BaseTestCase):
config_builder = LintConfigBuilder()
config_builder.set_from_config_file(path)
expected_error_msg = u"No such rule 'föobar'"
- with self.assertRaisesRegex(LintConfigError, expected_error_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.build()
# non-existing general option
@@ -110,7 +114,7 @@ class LintConfigBuilderTests(BaseTestCase):
config_builder = LintConfigBuilder()
config_builder.set_from_config_file(path)
expected_error_msg = u"'foo' is not a valid gitlint option"
- with self.assertRaisesRegex(LintConfigError, expected_error_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.build()
# non-existing option
@@ -118,7 +122,7 @@ class LintConfigBuilderTests(BaseTestCase):
config_builder = LintConfigBuilder()
config_builder.set_from_config_file(path)
expected_error_msg = u"Rule 'title-max-length' has no option 'föobar'"
- with self.assertRaisesRegex(LintConfigError, expected_error_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.build()
# invalid option value
@@ -127,7 +131,7 @@ class LintConfigBuilderTests(BaseTestCase):
config_builder.set_from_config_file(path)
expected_error_msg = u"'föo' is not a valid value for option 'title-max-length.line-length'. " + \
u"Option 'line-length' must be a positive integer (current value: 'föo')."
- with self.assertRaisesRegex(LintConfigError, expected_error_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_error_msg):
config_builder.build()
def test_set_config_from_string_list(self):
@@ -150,27 +154,27 @@ class LintConfigBuilderTests(BaseTestCase):
# assert error on incorrect rule - this happens at build time
config_builder.set_config_from_string_list([u"föo.bar=1"])
- with self.assertRaisesRegex(LintConfigError, u"No such rule 'föo'"):
+ with self.assertRaisesMessage(LintConfigError, u"No such rule 'föo'"):
config_builder.build()
# no equal sign
expected_msg = u"'föo.bar' is an invalid configuration option. Use '<rule>.<option>=<value>'"
- with self.assertRaisesRegex(LintConfigError, expected_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_msg):
config_builder.set_config_from_string_list([u"föo.bar"])
# missing value
expected_msg = u"'föo.bar=' is an invalid configuration option. Use '<rule>.<option>=<value>'"
- with self.assertRaisesRegex(LintConfigError, expected_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_msg):
config_builder.set_config_from_string_list([u"föo.bar="])
# space instead of equal sign
expected_msg = u"'föo.bar 1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
- with self.assertRaisesRegex(LintConfigError, expected_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_msg):
config_builder.set_config_from_string_list([u"föo.bar 1"])
# no period between rule and option names
expected_msg = u"'föobar=1' is an invalid configuration option. Use '<rule>.<option>=<value>'"
- with self.assertRaisesRegex(LintConfigError, expected_msg):
+ with self.assertRaisesMessage(LintConfigError, expected_msg):
config_builder.set_config_from_string_list([u'föobar=1'])
def test_rebuild_config(self):
@@ -201,3 +205,60 @@ class LintConfigBuilderTests(BaseTestCase):
# Modify the original and make sure we're not modifying the clone (i.e. check that the copy is a deep copy)
config_builder.set_option('title-max-length', 'line-length', 120)
self.assertDictEqual(cloned_builder._config_blueprint, expected)
+
+ def test_named_rules(self):
+ # Store a copy of the default rules from the config, so we can reference it later
+ config_builder = LintConfigBuilder()
+ config = config_builder.build()
+ default_rules = copy.deepcopy(config.rules)
+ self.assertEqual(default_rules, config.rules) # deepcopy should be equal
+
+ # Add a named rule by setting an option in the config builder that follows the named rule pattern
+ # Assert that whitespace in the rule name is stripped
+ rule_qualifiers = [u'T7:my-extra-rüle', u' T7 : my-extra-rüle ', u'\tT7:\tmy-extra-rüle\t',
+ u'T7:\t\n \tmy-extra-rüle\t\n\n', u"title-match-regex:my-extra-rüle"]
+ for rule_qualifier in rule_qualifiers:
+ config_builder = LintConfigBuilder()
+ config_builder.set_option(rule_qualifier, 'regex', u"föo")
+
+ expected_rules = copy.deepcopy(default_rules)
+ my_rule = rules.TitleRegexMatches({'regex': u"föo"})
+ my_rule.id = rules.TitleRegexMatches.id + u":my-extra-rüle"
+ my_rule.name = rules.TitleRegexMatches.name + u":my-extra-rüle"
+ expected_rules._rules[u'T7:my-extra-rüle'] = my_rule
+ self.assertEqual(config_builder.build().rules, expected_rules)
+
+ # assert that changing an option on the newly added rule is passed correctly to the RuleCollection
+ # we try this with all different rule qualifiers to ensure they all are normalized and map
+ # to the same rule
+ for other_rule_qualifier in rule_qualifiers:
+ cb = config_builder.clone()
+ cb.set_option(other_rule_qualifier, 'regex', other_rule_qualifier + u"bōr")
+ # before setting the expected rule option value correctly, the RuleCollection should be different
+ self.assertNotEqual(cb.build().rules, expected_rules)
+ # after setting the option on the expected rule, it should be equal
+ my_rule.options['regex'].set(other_rule_qualifier + u"bōr")
+ self.assertEqual(cb.build().rules, expected_rules)
+ my_rule.options['regex'].set(u"wrong")
+
+ def test_named_rules_negative(self):
+ # T7 = title-match-regex
+ # Invalid rule name
+ for invalid_name in ["", " ", " ", "\t", "\n", u"å b", u"å:b", u"åb:", u":åb"]:
+ config_builder = LintConfigBuilder()
+ config_builder.set_option(u"T7:{0}".format(invalid_name), 'regex', u"tëst")
+ expected_msg = u"The rule-name part in 'T7:{0}' cannot contain whitespace, colons or be empty"
+ with self.assertRaisesMessage(LintConfigError, expected_msg.format(invalid_name)):
+ config_builder.build()
+
+ # Invalid parent rule name
+ config_builder = LintConfigBuilder()
+ config_builder.set_option(u"Ž123:foöbar", u"fåke-option", u"fåke-value")
+ with self.assertRaisesMessage(LintConfigError, u"No such rule 'Ž123' (named rule: 'Ž123:foöbar')"):
+ config_builder.build()
+
+ # Invalid option name (this is the same as with regular rules)
+ config_builder = LintConfigBuilder()
+ config_builder.set_option(u"T7:foöbar", u"blå", u"my-rëgex")
+ with self.assertRaisesMessage(LintConfigError, u"Rule 'T7:foöbar' has no option 'blå'"):
+ config_builder.build()
diff --git a/gitlint/tests/config/test_config_precedence.py b/gitlint/tests/config/test_config_precedence.py
index 9689e55..a0eeccd 100644
--- a/gitlint/tests/config/test_config_precedence.py
+++ b/gitlint/tests/config/test_config_precedence.py
@@ -25,40 +25,48 @@ class LintConfigPrecedenceTests(BaseTestCase):
def setUp(self):
self.cli = CliRunner()
- @patch('gitlint.cli.get_stdin_data', return_value=u"WIP\n\nThis is å test message\n")
+ @patch('gitlint.cli.get_stdin_data', return_value=u"WIP:fö\n\nThis is å test message\n")
def test_config_precedence(self, _):
# TODO(jroovers): this test really only test verbosity, we need to do some refactoring to gitlint.cli
# to more easily test everything
# Test that the config precedence is followed:
# 1. commandline convenience flags
- # 2. commandline -c flags
- # 3. config file
- # 4. default config
+ # 2. environment variables
+ # 3. commandline -c flags
+ # 4. config file
+ # 5. default config
config_path = self.get_sample_path("config/gitlintconfig")
# 1. commandline convenience flags
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["-vvv", "-c", "general.verbosity=2", "--config", config_path])
self.assertEqual(result.output, "")
- self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP\"\n")
+ self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
- # 2. commandline -c flags
+ # 2. environment variables
+ with patch('gitlint.display.stderr', new=StringIO()) as stderr:
+ result = self.cli.invoke(cli.cli, ["-c", "general.verbosity=2", "--config", config_path],
+ env={"GITLINT_VERBOSITY": "3"})
+ self.assertEqual(result.output, "")
+ self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
+
+ # 3. commandline -c flags
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["-c", "general.verbosity=2", "--config", config_path])
self.assertEqual(result.output, "")
self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive)\n")
- # 3. config file
+ # 4. config file
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli, ["--config", config_path])
self.assertEqual(result.output, "")
self.assertEqual(stderr.getvalue(), "1: T5\n")
- # 4. default config
+ # 5. default config
with patch('gitlint.display.stderr', new=StringIO()) as stderr:
result = self.cli.invoke(cli.cli)
self.assertEqual(result.output, "")
- self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP\"\n")
+ self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n")
@patch('gitlint.cli.get_stdin_data', return_value=u"WIP: This is å test")
def test_ignore_precedence(self, get_stdin_data):
diff --git a/gitlint/tests/contrib/rules/__init__.py b/gitlint/tests/contrib/rules/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gitlint/tests/contrib/rules/__init__.py
diff --git a/gitlint/tests/contrib/test_conventional_commit.py b/gitlint/tests/contrib/rules/test_conventional_commit.py
index ea808fd..001af32 100644
--- a/gitlint/tests/contrib/test_conventional_commit.py
+++ b/gitlint/tests/contrib/rules/test_conventional_commit.py
@@ -19,13 +19,13 @@ class ContribConventionalCommitTests(BaseTestCase):
rule = ConventionalCommit()
# No violations when using a correct type and format
- for type in ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert"]:
+ for type in ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert", "ci", "build"]:
violations = rule.validate(type + u": föo", None)
self.assertListEqual([], violations)
# assert violation on wrong type
expected_violation = RuleViolation("CT1", "Title does not start with one of fix, feat, chore, docs,"
- " style, refactor, perf, test, revert", u"bår: foo")
+ " style, refactor, perf, test, revert, ci, build", u"bår: foo")
violations = rule.validate(u"bår: foo", None)
self.assertListEqual([expected_violation], violations)
diff --git a/gitlint/tests/contrib/test_signedoff_by.py b/gitlint/tests/contrib/rules/test_signedoff_by.py
index 934aec5..934aec5 100644
--- a/gitlint/tests/contrib/test_signedoff_by.py
+++ b/gitlint/tests/contrib/rules/test_signedoff_by.py
diff --git a/gitlint/tests/contrib/test_contrib_rules.py b/gitlint/tests/contrib/test_contrib_rules.py
index 3fa4048..84db2d5 100644
--- a/gitlint/tests/contrib/test_contrib_rules.py
+++ b/gitlint/tests/contrib/test_contrib_rules.py
@@ -3,7 +3,7 @@ import os
from gitlint.tests.base import BaseTestCase
from gitlint.contrib import rules as contrib_rules
-from gitlint.tests import contrib as contrib_tests
+from gitlint.tests.contrib import rules as contrib_tests
from gitlint import rule_finder, rules
from gitlint.utils import ustr
diff --git a/gitlint/tests/expected/test_cli/test_contrib_1 b/gitlint/tests/expected/cli/test_cli/test_contrib_1
index ea5d353..cdfb821 100644
--- a/gitlint/tests/expected/test_cli/test_contrib_1
+++ b/gitlint/tests/expected/cli/test_cli/test_contrib_1
@@ -1,3 +1,3 @@
1: CC1 Body does not contain a 'Signed-Off-By' line
-1: CT1 Title does not start with one of fix, feat, chore, docs, style, refactor, perf, test, revert: "Test tïtle"
+1: CT1 Title does not start with one of fix, feat, chore, docs, style, refactor, perf, test, revert, ci, build: "Test tïtle"
1: CT1 Title does not follow ConventionalCommits.org format 'type(optional-scope): description': "Test tïtle"
diff --git a/gitlint/tests/expected/test_cli/test_debug_1 b/gitlint/tests/expected/cli/test_cli/test_debug_1
index 612f78e..a95a58d 100644
--- a/gitlint/tests/expected/test_cli/test_debug_1
+++ b/gitlint/tests/expected/cli/test_cli/test_debug_1
@@ -4,6 +4,7 @@ DEBUG: gitlint.cli Python version: {python_version}
DEBUG: gitlint.cli Git version: git version 1.2.3
DEBUG: gitlint.cli Gitlint version: {gitlint_version}
DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING}
DEBUG: gitlint.cli Configuration
config-path: {config_path}
[GENERAL]
@@ -26,6 +27,8 @@ target: {target}
I2: ignore-by-body
ignore=all
regex=None
+ I3: ignore-body-lines
+ regex=None
T1: title-max-length
line-length=20
T2: title-trailing-whitespace
@@ -35,7 +38,9 @@ target: {target}
T5: title-must-not-contain-word
words=WIP,bögus
T7: title-match-regex
- regex=.*
+ regex=None
+ T8: title-min-length
+ min-length=5
B1: body-max-line-length
line-length=30
B5: body-min-length
@@ -47,12 +52,19 @@ target: {target}
B4: body-first-line-empty
B7: body-changed-file-mention
files=
+ B8: body-match-regex
+ regex=None
M1: author-valid-email
regex=[^@ ]+@[^@ ]+\.[^@ ]+
DEBUG: gitlint.cli No --msg-filename flag, no or empty data passed to stdin. Using the local repo.
+DEBUG: gitlint.git ('rev-list', 'foo...bar')
DEBUG: gitlint.cli Linting 3 commit(s)
+DEBUG: gitlint.git ('log', '6f29bf81a8322a04071bb794666e48c443a90360', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B')
+DEBUG: gitlint.git ('config', '--get', 'core.commentchar')
DEBUG: gitlint.lint Linting commit 6f29bf81a8322a04071bb794666e48c443a90360
+DEBUG: gitlint.git ('branch', '--contains', '6f29bf81a8322a04071bb794666e48c443a90360')
+DEBUG: gitlint.git ('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', '6f29bf81a8322a04071bb794666e48c443a90360')
DEBUG: gitlint.lint Commit Object
--- Commit Message ----
commït-title1
@@ -68,7 +80,10 @@ is-revert-commit: False
Branches: ['commit-1-branch-1', 'commit-1-branch-2']
Changed Files: ['commit-1/file-1', 'commit-1/file-2']
-----------------------
+DEBUG: gitlint.git ('log', '25053ccec5e28e1bb8f7551fdbb5ab213ada2401', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B')
DEBUG: gitlint.lint Linting commit 25053ccec5e28e1bb8f7551fdbb5ab213ada2401
+DEBUG: gitlint.git ('branch', '--contains', '25053ccec5e28e1bb8f7551fdbb5ab213ada2401')
+DEBUG: gitlint.git ('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', '25053ccec5e28e1bb8f7551fdbb5ab213ada2401')
DEBUG: gitlint.lint Commit Object
--- Commit Message ----
commït-title2.
@@ -84,10 +99,13 @@ is-revert-commit: False
Branches: ['commit-2-branch-1', 'commit-2-branch-2']
Changed Files: ['commit-2/file-1', 'commit-2/file-2']
-----------------------
+DEBUG: gitlint.git ('log', '4da2656b0dadc76c7ee3fd0243a96cb64007f125', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B')
DEBUG: gitlint.lint Linting commit 4da2656b0dadc76c7ee3fd0243a96cb64007f125
+DEBUG: gitlint.git ('branch', '--contains', '4da2656b0dadc76c7ee3fd0243a96cb64007f125')
+DEBUG: gitlint.git ('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', '4da2656b0dadc76c7ee3fd0243a96cb64007f125')
DEBUG: gitlint.lint Commit Object
--- Commit Message ----
-föo
+föobar
bar
--- Meta info ---------
Author: test åuthor3 <test-email3@föo.com>
diff --git a/gitlint/tests/expected/test_cli/test_input_stream_1 b/gitlint/tests/expected/cli/test_cli/test_input_stream_1
index 4326729..4326729 100644
--- a/gitlint/tests/expected/test_cli/test_input_stream_1
+++ b/gitlint/tests/expected/cli/test_cli/test_input_stream_1
diff --git a/gitlint/tests/expected/test_cli/test_input_stream_debug_1 b/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_1
index 4326729..4326729 100644
--- a/gitlint/tests/expected/test_cli/test_input_stream_debug_1
+++ b/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_1
diff --git a/gitlint/tests/expected/test_cli/test_input_stream_debug_2 b/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2
index a9028e1..c05d147 100644
--- a/gitlint/tests/expected/test_cli/test_input_stream_debug_2
+++ b/gitlint/tests/expected/cli/test_cli/test_input_stream_debug_2
@@ -4,6 +4,7 @@ DEBUG: gitlint.cli Python version: {python_version}
DEBUG: gitlint.cli Git version: git version 1.2.3
DEBUG: gitlint.cli Gitlint version: {gitlint_version}
DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING}
DEBUG: gitlint.cli Configuration
config-path: None
[GENERAL]
@@ -26,6 +27,8 @@ target: {target}
I2: ignore-by-body
ignore=all
regex=None
+ I3: ignore-body-lines
+ regex=None
T1: title-max-length
line-length=72
T2: title-trailing-whitespace
@@ -35,7 +38,9 @@ target: {target}
T5: title-must-not-contain-word
words=WIP
T7: title-match-regex
- regex=.*
+ regex=None
+ T8: title-min-length
+ min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
@@ -47,12 +52,15 @@ target: {target}
B4: body-first-line-empty
B7: body-changed-file-mention
files=
+ B8: body-match-regex
+ regex=None
M1: author-valid-email
regex=[^@ ]+@[^@ ]+\.[^@ ]+
DEBUG: gitlint.cli Stdin data: 'WIP: tïtle
'
DEBUG: gitlint.cli Stdin detected and not ignored. Using as input.
+DEBUG: gitlint.git ('config', '--get', 'core.commentchar')
DEBUG: gitlint.cli Linting 1 commit(s)
DEBUG: gitlint.lint Linting commit [SHA UNKNOWN]
DEBUG: gitlint.lint Commit Object
diff --git a/gitlint/tests/expected/test_cli/test_lint_multiple_commits_1 b/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_1
index be3288b..be3288b 100644
--- a/gitlint/tests/expected/test_cli/test_lint_multiple_commits_1
+++ b/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_1
diff --git a/gitlint/tests/expected/test_cli/test_lint_multiple_commits_config_1 b/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_config_1
index 1bf0503..1bf0503 100644
--- a/gitlint/tests/expected/test_cli/test_lint_multiple_commits_config_1
+++ b/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_config_1
diff --git a/gitlint/tests/expected/test_cli/test_lint_staged_msg_filename_1 b/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_1
index 9a9091b..9a9091b 100644
--- a/gitlint/tests/expected/test_cli/test_lint_staged_msg_filename_1
+++ b/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_1
diff --git a/gitlint/tests/expected/test_cli/test_lint_staged_msg_filename_2 b/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_2
index 3e5dcb6..e8e9f33 100644
--- a/gitlint/tests/expected/test_cli/test_lint_staged_msg_filename_2
+++ b/gitlint/tests/expected/cli/test_cli/test_lint_staged_msg_filename_2
@@ -4,6 +4,7 @@ DEBUG: gitlint.cli Python version: {python_version}
DEBUG: gitlint.cli Git version: git version 1.2.3
DEBUG: gitlint.cli Gitlint version: {gitlint_version}
DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING}
DEBUG: gitlint.cli Configuration
config-path: None
[GENERAL]
@@ -26,6 +27,8 @@ target: {target}
I2: ignore-by-body
ignore=all
regex=None
+ I3: ignore-body-lines
+ regex=None
T1: title-max-length
line-length=72
T2: title-trailing-whitespace
@@ -35,7 +38,9 @@ target: {target}
T5: title-must-not-contain-word
words=WIP
T7: title-match-regex
- regex=.*
+ regex=None
+ T8: title-min-length
+ min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
@@ -47,13 +52,20 @@ target: {target}
B4: body-first-line-empty
B7: body-changed-file-mention
files=
+ B8: body-match-regex
+ regex=None
M1: author-valid-email
regex=[^@ ]+@[^@ ]+\.[^@ ]+
DEBUG: gitlint.cli Fetching additional meta-data from staged commit
DEBUG: gitlint.cli Using --msg-filename.
+DEBUG: gitlint.git ('config', '--get', 'core.commentchar')
DEBUG: gitlint.cli Linting 1 commit(s)
DEBUG: gitlint.lint Linting commit [SHA UNKNOWN]
+DEBUG: gitlint.git ('config', '--get', 'user.name')
+DEBUG: gitlint.git ('config', '--get', 'user.email')
+DEBUG: gitlint.git ('rev-parse', '--abbrev-ref', 'HEAD')
+DEBUG: gitlint.git ('diff', '--staged', '--name-only', '-r')
DEBUG: gitlint.lint Commit Object
--- Commit Message ----
WIP: msg-filename tïtle
diff --git a/gitlint/tests/expected/test_cli/test_lint_staged_stdin_1 b/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_1
index 4326729..4326729 100644
--- a/gitlint/tests/expected/test_cli/test_lint_staged_stdin_1
+++ b/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_1
diff --git a/gitlint/tests/expected/test_cli/test_lint_staged_stdin_2 b/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2
index 03fd8c3..b822edc 100644
--- a/gitlint/tests/expected/test_cli/test_lint_staged_stdin_2
+++ b/gitlint/tests/expected/cli/test_cli/test_lint_staged_stdin_2
@@ -4,6 +4,7 @@ DEBUG: gitlint.cli Python version: {python_version}
DEBUG: gitlint.cli Git version: git version 1.2.3
DEBUG: gitlint.cli Gitlint version: {gitlint_version}
DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING}
DEBUG: gitlint.cli Configuration
config-path: None
[GENERAL]
@@ -26,6 +27,8 @@ target: {target}
I2: ignore-by-body
ignore=all
regex=None
+ I3: ignore-body-lines
+ regex=None
T1: title-max-length
line-length=72
T2: title-trailing-whitespace
@@ -35,7 +38,9 @@ target: {target}
T5: title-must-not-contain-word
words=WIP
T7: title-match-regex
- regex=.*
+ regex=None
+ T8: title-min-length
+ min-length=5
B1: body-max-line-length
line-length=80
B5: body-min-length
@@ -47,6 +52,8 @@ target: {target}
B4: body-first-line-empty
B7: body-changed-file-mention
files=
+ B8: body-match-regex
+ regex=None
M1: author-valid-email
regex=[^@ ]+@[^@ ]+\.[^@ ]+
@@ -54,8 +61,13 @@ DEBUG: gitlint.cli Fetching additional meta-data from staged commit
DEBUG: gitlint.cli Stdin data: 'WIP: tïtle
'
DEBUG: gitlint.cli Stdin detected and not ignored. Using as input.
+DEBUG: gitlint.git ('config', '--get', 'core.commentchar')
DEBUG: gitlint.cli Linting 1 commit(s)
DEBUG: gitlint.lint Linting commit [SHA UNKNOWN]
+DEBUG: gitlint.git ('config', '--get', 'user.name')
+DEBUG: gitlint.git ('config', '--get', 'user.email')
+DEBUG: gitlint.git ('rev-parse', '--abbrev-ref', 'HEAD')
+DEBUG: gitlint.git ('diff', '--staged', '--name-only', '-r')
DEBUG: gitlint.lint Commit Object
--- Commit Message ----
WIP: tïtle
diff --git a/gitlint/tests/expected/cli/test_cli/test_named_rules_1 b/gitlint/tests/expected/cli/test_cli/test_named_rules_1
new file mode 100644
index 0000000..a581d05
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli/test_named_rules_1
@@ -0,0 +1,4 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: tëst tïtle"
+1: T5:even-more-wörds Title contains the word 'tïtle' (case-insensitive): "WIP: tëst tïtle"
+1: T5:extra-wörds Title contains the word 'tëst' (case-insensitive): "WIP: tëst tïtle"
+3: B6 Body message is missing
diff --git a/gitlint/tests/expected/cli/test_cli/test_named_rules_2 b/gitlint/tests/expected/cli/test_cli/test_named_rules_2
new file mode 100644
index 0000000..828e296
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli/test_named_rules_2
@@ -0,0 +1,82 @@
+DEBUG: gitlint.cli To report issues, please visit https://github.com/jorisroovers/gitlint/issues
+DEBUG: gitlint.cli Platform: {platform}
+DEBUG: gitlint.cli Python version: {python_version}
+DEBUG: gitlint.cli Git version: git version 1.2.3
+DEBUG: gitlint.cli Gitlint version: {gitlint_version}
+DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING}
+DEBUG: gitlint.cli Configuration
+config-path: {config_path}
+[GENERAL]
+extra-path: None
+contrib: []
+ignore:
+ignore-merge-commits: True
+ignore-fixup-commits: True
+ignore-squash-commits: True
+ignore-revert-commits: True
+ignore-stdin: False
+staged: False
+verbosity: 3
+debug: True
+target: {target}
+[RULES]
+ I1: ignore-by-title
+ ignore=all
+ regex=None
+ I2: ignore-by-body
+ ignore=all
+ regex=None
+ I3: ignore-body-lines
+ regex=None
+ T1: title-max-length
+ line-length=72
+ T2: title-trailing-whitespace
+ T6: title-leading-whitespace
+ T3: title-trailing-punctuation
+ T4: title-hard-tab
+ T5: title-must-not-contain-word
+ words=WIP,bögus
+ T7: title-match-regex
+ regex=None
+ T8: title-min-length
+ min-length=5
+ B1: body-max-line-length
+ line-length=80
+ B5: body-min-length
+ min-length=20
+ B6: body-is-missing
+ ignore-merge-commits=True
+ B2: body-trailing-whitespace
+ B3: body-hard-tab
+ B4: body-first-line-empty
+ B7: body-changed-file-mention
+ files=
+ B8: body-match-regex
+ regex=None
+ M1: author-valid-email
+ regex=[^@ ]+@[^@ ]+\.[^@ ]+
+ T5:extra-wörds: title-must-not-contain-word:extra-wörds
+ words=hür,tëst
+ T5:even-more-wörds: title-must-not-contain-word:even-more-wörds
+ words=hür,tïtle
+
+DEBUG: gitlint.cli Stdin data: 'WIP: tëst tïtle'
+DEBUG: gitlint.cli Stdin detected and not ignored. Using as input.
+DEBUG: gitlint.git ('config', '--get', 'core.commentchar')
+DEBUG: gitlint.cli Linting 1 commit(s)
+DEBUG: gitlint.lint Linting commit [SHA UNKNOWN]
+DEBUG: gitlint.lint Commit Object
+--- Commit Message ----
+WIP: tëst tïtle
+--- Meta info ---------
+Author: None <None>
+Date: None
+is-merge-commit: False
+is-fixup-commit: False
+is-squash-commit: False
+is-revert-commit: False
+Branches: []
+Changed Files: []
+-----------------------
+DEBUG: gitlint.cli Exit Code = 4 \ No newline at end of file
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stderr b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stderr
new file mode 100644
index 0000000..cfacd42
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stderr
@@ -0,0 +1,2 @@
+1: T1 Title exceeds max length (27>5): "WIP: Test hook config tïtle"
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: Test hook config tïtle"
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stdout b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stdout
new file mode 100644
index 0000000..5d3f1fc
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_config_1_stdout
@@ -0,0 +1,5 @@
+gitlint: checking commit message...
+-----------------------------------------------
+gitlint: Your commit message contains the above violations.
+Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)]
+Aborted!
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stderr b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stderr
new file mode 100644
index 0000000..3eb8fca
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stderr
@@ -0,0 +1,6 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "{commit_msg}"
+3: B6 Body message is missing
+1: T5 Title contains the word 'WIP' (case-insensitive): "{commit_msg}"
+3: B6 Body message is missing
+1: T5 Title contains the word 'WIP' (case-insensitive): "{commit_msg}"
+3: B6 Body message is missing
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stdout b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stdout
new file mode 100644
index 0000000..fa6b3bc
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_edit_1_stdout
@@ -0,0 +1,14 @@
+gitlint: checking commit message...
+-----------------------------------------------
+gitlint: Your commit message contains the above violations.
+Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] gitlint: checking commit message...
+-----------------------------------------------
+gitlint: Your commit message contains the above violations.
+Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] gitlint: checking commit message...
+-----------------------------------------------
+gitlint: Your commit message contains the above violations.
+Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] Commit aborted.
+Your commit message:
+-----------------------------------------------
+{commit_msg}
+-----------------------------------------------
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stderr b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stderr
new file mode 100644
index 0000000..11c3cd8
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stderr
@@ -0,0 +1,2 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: commït-title"
+3: B5 Body message is too short (11<20): "commït-body"
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stdout b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stdout
new file mode 100644
index 0000000..a95bfea
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_local_commit_1_stdout
@@ -0,0 +1,4 @@
+gitlint: checking commit message...
+-----------------------------------------------
+gitlint: Your commit message contains the above violations.
+Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] Editing only possible when --msg-filename is specified.
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stderr b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stderr
new file mode 100644
index 0000000..6d0c9cf
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stderr
@@ -0,0 +1,2 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: höok no"
+3: B6 Body message is missing
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stdout b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stdout
new file mode 100644
index 0000000..9cc53c1
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_1_stdout
@@ -0,0 +1,8 @@
+gitlint: checking commit message...
+-----------------------------------------------
+gitlint: Your commit message contains the above violations.
+Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] Commit aborted.
+Your commit message:
+-----------------------------------------------
+WIP: höok no
+-----------------------------------------------
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stderr b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stderr
new file mode 100644
index 0000000..a8d8760
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stderr
@@ -0,0 +1,2 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: tïtle"
+3: B6 Body message is missing
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stdout b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stdout
new file mode 100644
index 0000000..5d3f1fc
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_no_tty_1_stdout
@@ -0,0 +1,5 @@
+gitlint: checking commit message...
+-----------------------------------------------
+gitlint: Your commit message contains the above violations.
+Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)]
+Aborted!
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_no_violations_1_stdout b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_no_violations_1_stdout
new file mode 100644
index 0000000..da1ef0b
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_no_violations_1_stdout
@@ -0,0 +1,2 @@
+gitlint: checking commit message...
+gitlint: OK (no violations in commit message)
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stderr b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stderr
new file mode 100644
index 0000000..1404f4a
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stderr
@@ -0,0 +1,2 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: Test hook stdin tïtle"
+3: B6 Body message is missing
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stdout b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stdout
new file mode 100644
index 0000000..5d3f1fc
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_stdin_violations_1_stdout
@@ -0,0 +1,5 @@
+gitlint: checking commit message...
+-----------------------------------------------
+gitlint: Your commit message contains the above violations.
+Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)]
+Aborted!
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stderr b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stderr
new file mode 100644
index 0000000..da6f874
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stderr
@@ -0,0 +1,2 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: höok yes"
+3: B6 Body message is missing
diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stdout b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stdout
new file mode 100644
index 0000000..bb753b0
--- /dev/null
+++ b/gitlint/tests/expected/cli/test_cli_hooks/test_hook_yes_1_stdout
@@ -0,0 +1,4 @@
+gitlint: checking commit message...
+-----------------------------------------------
+gitlint: Your commit message contains the above violations.
+Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] \ No newline at end of file
diff --git a/gitlint/tests/git/test_git.py b/gitlint/tests/git/test_git.py
index 297b10c..1830119 100644
--- a/gitlint/tests/git/test_git.py
+++ b/gitlint/tests/git/test_git.py
@@ -27,7 +27,7 @@ class GitTests(BaseTestCase):
sh.git.side_effect = CommandNotFound("git")
expected_msg = "'git' command not found. You need to install git to use gitlint on a local repository. " + \
"See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git."
- with self.assertRaisesRegex(GitNotInstalledError, expected_msg):
+ with self.assertRaisesMessage(GitNotInstalledError, expected_msg):
GitContext.from_local_repository(u"fåke/path")
# assert that commit message was read using git command
@@ -39,7 +39,7 @@ class GitTests(BaseTestCase):
err = b"fatal: Not a git repository (or any of the parent directories): .git"
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
- with self.assertRaisesRegex(GitContextError, u"fåke/path is not a git repository."):
+ with self.assertRaisesMessage(GitContextError, u"fåke/path is not a git repository."):
GitContext.from_local_repository(u"fåke/path")
# assert that commit message was read using git command
@@ -50,7 +50,7 @@ class GitTests(BaseTestCase):
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
expected_msg = u"An error occurred while executing 'git log -1 --pretty=%H': {0}".format(err)
- with self.assertRaisesRegex(GitContextError, expected_msg):
+ with self.assertRaisesMessage(GitContextError, expected_msg):
GitContext.from_local_repository(u"fåke/path")
# assert that commit message was read using git command
@@ -64,7 +64,7 @@ class GitTests(BaseTestCase):
sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err)
expected_msg = u"Current branch has no commits. Gitlint requires at least one commit to function."
- with self.assertRaisesRegex(GitContextError, expected_msg):
+ with self.assertRaisesMessage(GitContextError, expected_msg):
GitContext.from_local_repository(u"fåke/path")
# assert that commit message was read using git command
@@ -82,7 +82,7 @@ class GitTests(BaseTestCase):
ErrorReturnCode("rev-parse --abbrev-ref HEAD", b"", err)
]
- with self.assertRaisesRegex(GitContextError, expected_msg):
+ with self.assertRaisesMessage(GitContextError, expected_msg):
context = GitContext.from_commit_msg(u"test")
context.current_branch
diff --git a/gitlint/tests/git/test_git_commit.py b/gitlint/tests/git/test_git_commit.py
index dc83ccb..5f87a8e 100644
--- a/gitlint/tests/git/test_git_commit.py
+++ b/gitlint/tests/git/test_git_commit.py
@@ -14,7 +14,9 @@ except ImportError:
from unittest.mock import patch, call # pylint: disable=no-name-in-module, import-error
from gitlint.tests.base import BaseTestCase
-from gitlint.git import GitContext, GitCommit, LocalGitCommit, StagedLocalGitCommit, GitCommitMessage
+from gitlint.git import GitContext, GitCommit, GitContextError, LocalGitCommit, StagedLocalGitCommit, GitCommitMessage
+from gitlint.shell import ErrorReturnCode
+from gitlint.utils import ustr
class GitCommitTests(BaseTestCase):
@@ -479,12 +481,46 @@ class GitCommitTests(BaseTestCase):
self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"])
self.assertListEqual(sh.git.mock_calls, expected_calls[0:5])
+ @patch('gitlint.git.sh')
+ def test_staged_commit_with_missing_username(self, sh):
+ # StagedLocalGitCommit()
+
+ sh.git.side_effect = [
+ u"#", # git config --get core.commentchar
+ ErrorReturnCode('git config --get user.name', b"", b""),
+ ]
+
+ expected_msg = "Missing git configuration: please set user.name"
+ with self.assertRaisesMessage(GitContextError, expected_msg):
+ ctx = GitContext.from_staged_commit(u"Foōbar 123\n\ncömmit-body\n", u"fåke/path")
+ [ustr(commit) for commit in ctx.commits]
+
+ @patch('gitlint.git.sh')
+ def test_staged_commit_with_missing_email(self, sh):
+ # StagedLocalGitCommit()
+
+ sh.git.side_effect = [
+ u"#", # git config --get core.commentchar
+ u"test åuthor\n", # git config --get user.name
+ ErrorReturnCode('git config --get user.name', b"", b""),
+ ]
+
+ expected_msg = "Missing git configuration: please set user.email"
+ with self.assertRaisesMessage(GitContextError, expected_msg):
+ ctx = GitContext.from_staged_commit(u"Foōbar 123\n\ncömmit-body\n", u"fåke/path")
+ [ustr(commit) for commit in ctx.commits]
+
def test_gitcommitmessage_equality(self):
commit_message1 = GitCommitMessage(GitContext(), u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"])
attrs = ['original', 'full', 'title', 'body']
self.object_equality_test(commit_message1, attrs, {"context": commit_message1.context})
- def test_gitcommit_equality(self):
+ @patch("gitlint.git._git")
+ def test_gitcommit_equality(self, git):
+ # git will be called to setup the context (commentchar and current_branch), just return the same value
+ # This only matters to test gitcontext equality, not gitcommit equality
+ git.return_value = u"foöbar"
+
# Test simple equality case
now = datetime.datetime.utcnow()
context1 = GitContext()
diff --git a/gitlint/tests/rules/test_body_rules.py b/gitlint/tests/rules/test_body_rules.py
index fcb1b30..f46760b 100644
--- a/gitlint/tests/rules/test_body_rules.py
+++ b/gitlint/tests/rules/test_body_rules.py
@@ -178,3 +178,49 @@ class BodyRuleTests(BaseTestCase):
violations = rule.validate(commit)
expected_violation_2 = rules.RuleViolation("B7", "Body does not mention changed file 'bar.txt'", None, 4)
self.assertEqual([expected_violation_2, expected_violation], violations)
+
+ def test_body_match_regex(self):
+ # We intentionally add 2 newlines at the end of our commit message as that's how git will pass the
+ # message. This way we also test that the rule strips off the last line.
+ commit = self.gitcommit(u"US1234: åbc\nIgnored\nBödy\nFöo\nMy-Commit-Tag: föo\n\n")
+
+ # assert no violation on default regex (=everything allowed)
+ rule = rules.BodyRegexMatches()
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
+
+ # assert no violation on matching regex
+ # (also note that first body line - in between title and rest of body - is ignored)
+ rule = rules.BodyRegexMatches({'regex': u"^Bödy(.*)"})
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
+
+ # assert we can do end matching (and last empty line is ignored)
+ # (also note that first body line - in between title and rest of body - is ignored)
+ rule = rules.BodyRegexMatches({'regex': u"My-Commit-Tag: föo$"})
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
+
+ # common use-case: matching that a given line is present
+ rule = rules.BodyRegexMatches({'regex': u"(.*)Föo(.*)"})
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
+
+ # assert violation on non-matching body
+ rule = rules.BodyRegexMatches({'regex': u"^Tëst(.*)Foo"})
+ violations = rule.validate(commit)
+ expected_violation = rules.RuleViolation("B8", u"Body does not match regex (^Tëst(.*)Foo)", None, 6)
+ self.assertListEqual(violations, [expected_violation])
+
+ # assert no violation on None regex
+ rule = rules.BodyRegexMatches({'regex': None})
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
+
+ # Assert no issues when there's no body or a weird body variation
+ bodies = [u"åbc", u"åbc\n", u"åbc\nföo\n", u"åbc\n\n", u"åbc\nföo\nblå", u"åbc\nföo\nblå\n"]
+ for body in bodies:
+ commit = self.gitcommit(body)
+ rule = rules.BodyRegexMatches({'regex': ".*"})
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
diff --git a/gitlint/tests/rules/test_configuration_rules.py b/gitlint/tests/rules/test_configuration_rules.py
index 73d42f3..121cb3a 100644
--- a/gitlint/tests/rules/test_configuration_rules.py
+++ b/gitlint/tests/rules/test_configuration_rules.py
@@ -67,5 +67,41 @@ class ConfigurationRuleTests(BaseTestCase):
rule.apply(config, commit)
self.assertEqual(config, expected_config)
- expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \
+ expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \
u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)', ignoring rules: T1,B2"
+ self.assert_log_contains(expected_log_message)
+
+ def test_ignore_body_lines(self):
+ commit1 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
+ commit2 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
+
+ # no regex specified, nothing should have happened:
+ # commit and config should remain identical, log should be empty
+ rule = rules.IgnoreBodyLines()
+ config = LintConfig()
+ rule.apply(config, commit1)
+ self.assertEqual(commit1, commit2)
+ self.assertEqual(config, LintConfig())
+ self.assert_logged([])
+
+ # Matching regex
+ rule = rules.IgnoreBodyLines({"regex": u"(.*)relëase(.*)"})
+ config = LintConfig()
+ rule.apply(config, commit1)
+ # Our modified commit should be identical to a commit that doesn't contain the specific line
+ expected_commit = self.gitcommit(u"Tïtle\n\nThis is\n line")
+ # The original message isn't touched by this rule, this way we always have a way to reference back to it,
+ # so assert it's not modified by setting it to the same as commit1
+ expected_commit.message.original = commit1.message.original
+ self.assertEqual(commit1, expected_commit)
+ self.assertEqual(config, LintConfig()) # config shouldn't have been modified
+ self.assert_log_contains(u"DEBUG: gitlint.rules Ignoring line ' a relëase body' because it " +
+ u"matches '(.*)relëase(.*)'")
+
+ # Non-Matching regex: no changes expected
+ commit1 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line")
+ rule = rules.IgnoreBodyLines({"regex": u"(.*)föobar(.*)"})
+ config = LintConfig()
+ rule.apply(config, commit1)
+ self.assertEqual(commit1, commit2)
+ self.assertEqual(config, LintConfig()) # config shouldn't have been modified
diff --git a/gitlint/tests/rules/test_meta_rules.py b/gitlint/tests/rules/test_meta_rules.py
index c94b8b3..987aa88 100644
--- a/gitlint/tests/rules/test_meta_rules.py
+++ b/gitlint/tests/rules/test_meta_rules.py
@@ -32,6 +32,15 @@ class MetaRuleTests(BaseTestCase):
[RuleViolation("M1", "Author email for commit is invalid", email)])
def test_author_valid_email_rule_custom_regex(self):
+ # regex=None -> the rule isn't applied
+ rule = AuthorValidEmail()
+ rule.options['regex'].set(None)
+ emailadresses = [u"föo", None, u"hür dür"]
+ for email in emailadresses:
+ commit = self.gitcommit(u"", author_email=email)
+ violations = rule.validate(commit)
+ self.assertIsNone(violations)
+
# Custom domain
rule = AuthorValidEmail({'regex': u"[^@]+@bår.com"})
valid_email_addresses = [
diff --git a/gitlint/tests/rules/test_rules.py b/gitlint/tests/rules/test_rules.py
index 89caa27..58ee1c3 100644
--- a/gitlint/tests/rules/test_rules.py
+++ b/gitlint/tests/rules/test_rules.py
@@ -13,6 +13,11 @@ class RuleTests(BaseTestCase):
setattr(rule, attr, u"åbc")
self.assertNotEqual(Rule(), rule)
+ def test_rule_log(self):
+ rule = Rule()
+ rule.log.debug(u"Tēst message")
+ self.assert_log_contains(u"DEBUG: gitlint.rules Tēst message")
+
def test_rule_violation_equality(self):
violation1 = RuleViolation(u"ïd1", u"My messåge", u"My cöntent", 1)
self.object_equality_test(violation1, ["rule_id", "message", "content", "line_nr"])
diff --git a/gitlint/tests/rules/test_title_rules.py b/gitlint/tests/rules/test_title_rules.py
index 07d2323..049735e 100644
--- a/gitlint/tests/rules/test_title_rules.py
+++ b/gitlint/tests/rules/test_title_rules.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from gitlint.tests.base import BaseTestCase
from gitlint.rules import TitleMaxLength, TitleTrailingWhitespace, TitleHardTab, TitleMustNotContainWord, \
- TitleTrailingPunctuation, TitleLeadingWhitespace, TitleRegexMatches, RuleViolation
+ TitleTrailingPunctuation, TitleLeadingWhitespace, TitleRegexMatches, RuleViolation, TitleMinLength
class TitleRuleTests(BaseTestCase):
@@ -152,3 +152,35 @@ class TitleRuleTests(BaseTestCase):
violations = rule.validate(commit.message.title, commit)
expected_violation = RuleViolation("T7", u"Title does not match regex (^UÅ[0-9]*)", u"US1234: åbc")
self.assertListEqual(violations, [expected_violation])
+
+ def test_min_line_length(self):
+ rule = TitleMinLength()
+
+ # assert no error
+ violation = rule.validate(u"å" * 72, None)
+ self.assertIsNone(violation)
+
+ # assert error on line length < 5
+ expected_violation = RuleViolation("T8", "Title is too short (4<5)", u"å" * 4, 1)
+ violations = rule.validate(u"å" * 4, None)
+ self.assertListEqual(violations, [expected_violation])
+
+ # set line length to 3, and check no violation on length 4
+ rule = TitleMinLength({'min-length': 3})
+ violations = rule.validate(u"å" * 4, None)
+ self.assertIsNone(violations)
+
+ # assert no violations on length 3 (this asserts we've implemented a *strict* less than)
+ rule = TitleMinLength({'min-length': 3})
+ violations = rule.validate(u"å" * 3, None)
+ self.assertIsNone(violations)
+
+ # assert raise on 2
+ expected_violation = RuleViolation("T8", "Title is too short (2<3)", u"å" * 2, 1)
+ violations = rule.validate(u"å" * 2, None)
+ self.assertListEqual(violations, [expected_violation])
+
+ # assert raise on empty title
+ expected_violation = RuleViolation("T8", "Title is too short (0<3)", "", 1)
+ violations = rule.validate("", None)
+ self.assertListEqual(violations, [expected_violation])
diff --git a/gitlint/tests/rules/test_user_rules.py b/gitlint/tests/rules/test_user_rules.py
index 57c03a0..52d0283 100644
--- a/gitlint/tests/rules/test_user_rules.py
+++ b/gitlint/tests/rules/test_user_rules.py
@@ -92,7 +92,7 @@ class UserRuleTests(BaseTestCase):
find_rule_classes(user_rule_path)
def test_find_rule_classes_nonexisting_path(self):
- with self.assertRaisesRegex(UserRuleError, u"Invalid extra-path: föo/bar"):
+ with self.assertRaisesMessage(UserRuleError, u"Invalid extra-path: föo/bar"):
find_rule_classes(u"föo/bar")
def test_assert_valid_rule_class(self):
@@ -111,15 +111,23 @@ class UserRuleTests(BaseTestCase):
def validate(self):
pass
+ class MyConfigurationRuleClass(rules.ConfigurationRule):
+ id = 'UC3'
+ name = u'my-cönfiguration-rule'
+
+ def apply(self):
+ pass
+
# Just assert that no error is raised
self.assertIsNone(assert_valid_rule_class(MyLineRuleClass))
self.assertIsNone(assert_valid_rule_class(MyCommitRuleClass))
+ self.assertIsNone(assert_valid_rule_class(MyConfigurationRuleClass))
def test_assert_valid_rule_class_negative(self):
# general test to make sure that incorrect rules will raise an exception
user_rule_path = self.get_sample_path("user_rules/incorrect_linerule")
- with self.assertRaisesRegex(UserRuleError,
- "User-defined rule class 'MyUserLineRule' must have a 'validate' method"):
+ with self.assertRaisesMessage(UserRuleError,
+ "User-defined rule class 'MyUserLineRule' must have a 'validate' method"):
find_rule_classes(user_rule_path)
def test_assert_valid_rule_class_negative_parent(self):
@@ -127,76 +135,101 @@ class UserRuleTests(BaseTestCase):
class MyRuleClass(object):
pass
- expected_msg = "User-defined rule class 'MyRuleClass' must extend from gitlint.rules.LineRule " + \
- "or gitlint.rules.CommitRule"
- with self.assertRaisesRegex(UserRuleError, expected_msg):
+ expected_msg = "User-defined rule class 'MyRuleClass' must extend from gitlint.rules.LineRule, " + \
+ "gitlint.rules.CommitRule or gitlint.rules.ConfigurationRule"
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_id(self):
- class MyRuleClass(rules.LineRule):
- pass
- # Rule class must have an id
- expected_msg = "User-defined rule class 'MyRuleClass' must have an 'id' attribute"
- with self.assertRaisesRegex(UserRuleError, expected_msg):
- assert_valid_rule_class(MyRuleClass)
+ for parent_class in [rules.LineRule, rules.CommitRule]:
- # Rule ids must be non-empty
- MyRuleClass.id = ""
- with self.assertRaisesRegex(UserRuleError, expected_msg):
- assert_valid_rule_class(MyRuleClass)
+ class MyRuleClass(parent_class):
+ pass
- # Rule ids must not start with one of the reserved id letters
- for letter in ["T", "R", "B", "M"]:
- MyRuleClass.id = letter + "1"
- expected_msg = "The id '{0}' of 'MyRuleClass' is invalid. Gitlint reserves ids starting with R,T,B,M"
- with self.assertRaisesRegex(UserRuleError, expected_msg.format(letter)):
+ # Rule class must have an id
+ expected_msg = "User-defined rule class 'MyRuleClass' must have an 'id' attribute"
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
+ # Rule ids must be non-empty
+ MyRuleClass.id = ""
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
+ assert_valid_rule_class(MyRuleClass)
+
+ # Rule ids must not start with one of the reserved id letters
+ for letter in ["T", "R", "B", "M", "I"]:
+ MyRuleClass.id = letter + "1"
+ expected_msg = "The id '{0}' of 'MyRuleClass' is invalid. Gitlint reserves ids starting with R,T,B,M,I"
+ with self.assertRaisesMessage(UserRuleError, expected_msg.format(letter)):
+ assert_valid_rule_class(MyRuleClass)
+
def test_assert_valid_rule_class_negative_name(self):
- class MyRuleClass(rules.LineRule):
- id = "UC1"
+ for parent_class in [rules.LineRule, rules.CommitRule]:
- # Rule class must have an name
- expected_msg = "User-defined rule class 'MyRuleClass' must have a 'name' attribute"
- with self.assertRaisesRegex(UserRuleError, expected_msg):
- assert_valid_rule_class(MyRuleClass)
+ class MyRuleClass(parent_class):
+ id = "UC1"
- # Rule names must be non-empty
- MyRuleClass.name = ""
- with self.assertRaisesRegex(UserRuleError, expected_msg):
- assert_valid_rule_class(MyRuleClass)
+ # Rule class must have an name
+ expected_msg = "User-defined rule class 'MyRuleClass' must have a 'name' attribute"
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
+ assert_valid_rule_class(MyRuleClass)
+
+ # Rule names must be non-empty
+ MyRuleClass.name = ""
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
+ assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_option_spec(self):
- class MyRuleClass(rules.LineRule):
- id = "UC1"
- name = u"my-rüle-class"
- # if set, option_spec must be a list of gitlint options
- MyRuleClass.options_spec = u"föo"
- expected_msg = "The options_spec attribute of user-defined rule class 'MyRuleClass' must be a list " + \
- "of gitlint.options.RuleOption"
- with self.assertRaisesRegex(UserRuleError, expected_msg):
- assert_valid_rule_class(MyRuleClass)
+ for parent_class in [rules.LineRule, rules.CommitRule]:
- # option_spec is a list, but not of gitlint options
- MyRuleClass.options_spec = [u"föo", 123] # pylint: disable=bad-option-value,redefined-variable-type
- with self.assertRaisesRegex(UserRuleError, expected_msg):
- assert_valid_rule_class(MyRuleClass)
+ class MyRuleClass(parent_class):
+ id = "UC1"
+ name = u"my-rüle-class"
+
+ # if set, option_spec must be a list of gitlint options
+ MyRuleClass.options_spec = u"föo"
+ expected_msg = "The options_spec attribute of user-defined rule class 'MyRuleClass' must be a list " + \
+ "of gitlint.options.RuleOption"
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
+ assert_valid_rule_class(MyRuleClass)
+
+ # option_spec is a list, but not of gitlint options
+ MyRuleClass.options_spec = [u"föo", 123] # pylint: disable=bad-option-value,redefined-variable-type
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
+ assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_validate(self):
- class MyRuleClass(rules.LineRule):
- id = "UC1"
+
+ baseclasses = [rules.LineRule, rules.CommitRule]
+ for clazz in baseclasses:
+ class MyRuleClass(clazz):
+ id = "UC1"
+ name = u"my-rüle-class"
+
+ with self.assertRaisesMessage(UserRuleError,
+ "User-defined rule class 'MyRuleClass' must have a 'validate' method"):
+ assert_valid_rule_class(MyRuleClass)
+
+ # validate attribute - not a method
+ MyRuleClass.validate = u"föo"
+ with self.assertRaisesMessage(UserRuleError,
+ "User-defined rule class 'MyRuleClass' must have a 'validate' method"):
+ assert_valid_rule_class(MyRuleClass)
+
+ def test_assert_valid_rule_class_negative_apply(self):
+ class MyRuleClass(rules.ConfigurationRule):
+ id = "UCR1"
name = u"my-rüle-class"
- with self.assertRaisesRegex(UserRuleError,
- "User-defined rule class 'MyRuleClass' must have a 'validate' method"):
+ expected_msg = "User-defined Configuration rule class 'MyRuleClass' must have an 'apply' method"
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# validate attribute - not a method
MyRuleClass.validate = u"föo"
- with self.assertRaisesRegex(UserRuleError,
- "User-defined rule class 'MyRuleClass' must have a 'validate' method"):
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_target(self):
@@ -210,12 +243,12 @@ class UserRuleTests(BaseTestCase):
# no target
expected_msg = "The target attribute of the user-defined LineRule class 'MyRuleClass' must be either " + \
"gitlint.rules.CommitMessageTitle or gitlint.rules.CommitMessageBody"
- with self.assertRaisesRegex(UserRuleError, expected_msg):
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# invalid target
MyRuleClass.target = u"föo"
- with self.assertRaisesRegex(UserRuleError, expected_msg):
+ with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# valid target, no exception should be raised
diff --git a/gitlint/tests/samples/commit_message/no-violations b/gitlint/tests/samples/commit_message/no-violations
new file mode 100644
index 0000000..33c73b9
--- /dev/null
+++ b/gitlint/tests/samples/commit_message/no-violations
@@ -0,0 +1,6 @@
+Normal Commit Tïtle
+
+Nörmal body that contains a few lines of text describing the changes in the
+commit without violating any of gitlint's rules.
+
+Sïgned-Off-By: foo@bar.com
diff --git a/gitlint/tests/samples/config/named-rules b/gitlint/tests/samples/config/named-rules
new file mode 100644
index 0000000..73ab0d2
--- /dev/null
+++ b/gitlint/tests/samples/config/named-rules
@@ -0,0 +1,8 @@
+[title-must-not-contain-word]
+words=WIP,bögus
+
+[title-must-not-contain-word:extra-wörds]
+words=hür,tëst
+
+[T5:even-more-wörds]
+words=hür,tïtle \ No newline at end of file
diff --git a/gitlint/tests/test_hooks.py b/gitlint/tests/test_hooks.py
index 08bd730..62f55e5 100644
--- a/gitlint/tests/test_hooks.py
+++ b/gitlint/tests/test_hooks.py
@@ -58,8 +58,8 @@ class HookTests(BaseTestCase):
git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks")
# mock that current dir is not a git repo
isdir.return_value = False
- expected_msg = u"{0} is not a git repository".format(lint_config.target)
- with self.assertRaisesRegex(GitHookInstallerError, expected_msg):
+ expected_msg = u"{0} is not a git repository.".format(lint_config.target)
+ with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
GitHookInstaller.install_commit_msg_hook(lint_config)
isdir.assert_called_with(git_hooks_dir.return_value)
path_exists.assert_not_called()
@@ -71,7 +71,7 @@ class HookTests(BaseTestCase):
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
expected_msg = u"There is already a commit-msg hook file present in {0}.\n".format(expected_dst) + \
"gitlint currently does not support appending to an existing commit-msg file."
- with self.assertRaisesRegex(GitHookInstallerError, expected_msg):
+ with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
GitHookInstaller.install_commit_msg_hook(lint_config)
@staticmethod
@@ -104,8 +104,8 @@ class HookTests(BaseTestCase):
# mock that the current directory is not a git repo
isdir.return_value = False
- expected_msg = u"{0} is not a git repository".format(lint_config.target)
- with self.assertRaisesRegex(GitHookInstallerError, expected_msg):
+ expected_msg = u"{0} is not a git repository.".format(lint_config.target)
+ with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
isdir.assert_called_with(git_hooks_dir.return_value)
path_exists.assert_not_called()
@@ -116,7 +116,7 @@ class HookTests(BaseTestCase):
path_exists.return_value = False
expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH)
expected_msg = u"There is no commit-msg hook present in {0}.".format(expected_dst)
- with self.assertRaisesRegex(GitHookInstallerError, expected_msg):
+ with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
isdir.assert_called_with(git_hooks_dir.return_value)
path_exists.assert_called_once_with(expected_dst)
@@ -131,6 +131,6 @@ class HookTests(BaseTestCase):
"(or it was modified).\nUninstallation of 3th party or modified gitlint hooks " + \
"is not supported."
with patch('gitlint.hooks.io.open', mock_open(read_data=read_data), create=True):
- with self.assertRaisesRegex(GitHookInstallerError, expected_msg):
+ with self.assertRaisesMessage(GitHookInstallerError, expected_msg):
GitHookInstaller.uninstall_commit_msg_hook(lint_config)
remove.assert_not_called()
diff --git a/gitlint/tests/test_lint.py b/gitlint/tests/test_lint.py
index bcdd984..3bf9a94 100644
--- a/gitlint/tests/test_lint.py
+++ b/gitlint/tests/test_lint.py
@@ -16,7 +16,7 @@ except ImportError:
from gitlint.tests.base import BaseTestCase
from gitlint.lint import GitLinter
-from gitlint.rules import RuleViolation
+from gitlint.rules import RuleViolation, TitleMustNotContainWord
from gitlint.config import LintConfig, LintConfigBuilder
@@ -103,7 +103,7 @@ class LintTests(BaseTestCase):
self.assertListEqual(violations, expected)
def test_lint_meta(self):
- """ Lint sample2 but also add some metadata to the commit so we that get's linted as well """
+ """ Lint sample2 but also add some metadata to the commit so we that gets linted as well """
linter = GitLinter(LintConfig())
gitcontext = self.gitcontext(self.get_sample("commit_message/sample2"))
gitcontext.commits[0].author_email = u"foo bår"
@@ -150,6 +150,25 @@ class LintTests(BaseTestCase):
self.assertListEqual(violations, expected)
+ # Test ignoring body lines
+ lint_config = LintConfig()
+ linter = GitLinter(lint_config)
+ lint_config.set_rule_option("I3", "regex", u"(.*)tråiling(.*)")
+ violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample1")))
+ expected_errors = [RuleViolation("T3", "Title has trailing punctuation (.)",
+ u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
+ RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)",
+ u"Commit title contåining 'WIP', as well as trailing punctuation.", 1),
+ RuleViolation("B4", "Second line is not empty", "This line should be empty", 2),
+ RuleViolation("B1", "Line exceeds max length (135>80)",
+ "This is the first line of the commit message body and it is meant to test " +
+ "a line that exceeds the maximum line length of 80 characters.", 3),
+ RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing tab.\t", 4),
+ RuleViolation("B3", "Line contains hard tab characters (\\t)",
+ "This line has a trailing tab.\t", 4)]
+
+ self.assertListEqual(violations, expected_errors)
+
def test_lint_special_commit(self):
for commit_type in ["merge", "revert", "squash", "fixup"]:
commit = self.gitcommit(self.get_sample("commit_message/{0}".format(commit_type)))
@@ -166,6 +185,31 @@ class LintTests(BaseTestCase):
violations = linter.lint(commit)
self.assertTrue(len(violations) > 0)
+ def test_lint_regex_rules(self):
+ """ Additional test for title-match-regex, body-match-regex """
+ commit = self.gitcommit(self.get_sample("commit_message/no-violations"))
+ lintconfig = LintConfig()
+ linter = GitLinter(lintconfig)
+ violations = linter.lint(commit)
+ # No violations by default
+ self.assertListEqual(violations, [])
+
+ # Matching regexes shouldn't be a problem
+ rule_regexes = [("title-match-regex", u"Tïtle$"), ("body-match-regex", u"Sïgned-Off-By: (.*)$")]
+ for rule_regex in rule_regexes:
+ lintconfig.set_rule_option(rule_regex[0], "regex", rule_regex[1])
+ violations = linter.lint(commit)
+ self.assertListEqual(violations, [])
+
+ # Non-matching regexes should return violations
+ rule_regexes = [("title-match-regex", ), ("body-match-regex",)]
+ lintconfig.set_rule_option("title-match-regex", "regex", u"^Tïtle")
+ lintconfig.set_rule_option("body-match-regex", "regex", u"Sügned-Off-By: (.*)$")
+ expected_violations = [RuleViolation("T7", u"Title does not match regex (^Tïtle)", u"Normal Commit Tïtle", 1),
+ RuleViolation("B8", u"Body does not match regex (Sügned-Off-By: (.*)$)", None, 6)]
+ violations = linter.lint(commit)
+ self.assertListEqual(violations, expected_violations)
+
def test_print_violations(self):
violations = [RuleViolation("RULE_ID_1", u"Error Messåge 1", "Violating Content 1", None),
RuleViolation("RULE_ID_2", "Error Message 2", u"Violåting Content 2", 2)]
@@ -195,3 +239,49 @@ class LintTests(BaseTestCase):
expected = u"-: RULE_ID_1 Error Messåge 1: \"Violating Content 1\"\n" + \
u"2: RULE_ID_2 Error Message 2: \"Violåting Content 2\"\n"
self.assertEqual(expected, stderr.getvalue())
+
+ def test_named_rules(self):
+ """ Test that when named rules are present, both them and the original (non-named) rules executed """
+
+ lint_config = LintConfig()
+ for rule_name in [u"my-ïd", u"another-rule-ïd"]:
+ rule_id = TitleMustNotContainWord.id + ":" + rule_name
+ lint_config.rules.add_rule(TitleMustNotContainWord, rule_id)
+ lint_config.set_rule_option(rule_id, "words", [u"Föo"])
+ linter = GitLinter(lint_config)
+
+ violations = [RuleViolation("T5", u"Title contains the word 'WIP' (case-insensitive)", u"WIP: Föo bar", 1),
+ RuleViolation(u"T5:another-rule-ïd", u"Title contains the word 'Föo' (case-insensitive)",
+ u"WIP: Föo bar", 1),
+ RuleViolation(u"T5:my-ïd", u"Title contains the word 'Föo' (case-insensitive)",
+ u"WIP: Föo bar", 1)]
+ self.assertListEqual(violations, linter.lint(self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla")))
+
+ def test_ignore_named_rules(self):
+ """ Test that named rules can be ignored """
+
+ # Add named rule to lint config
+ config_builder = LintConfigBuilder()
+ rule_id = TitleMustNotContainWord.id + u":my-ïd"
+ config_builder.set_option(rule_id, "words", [u"Föo"])
+ lint_config = config_builder.build()
+ linter = GitLinter(lint_config)
+ commit = self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla")
+
+ # By default, we expect both the violations of the regular rule as well as the named rule to show up
+ violations = [RuleViolation("T5", u"Title contains the word 'WIP' (case-insensitive)", u"WIP: Föo bar", 1),
+ RuleViolation(u"T5:my-ïd", u"Title contains the word 'Föo' (case-insensitive)",
+ u"WIP: Föo bar", 1)]
+ self.assertListEqual(violations, linter.lint(commit))
+
+ # ignore regular rule: only named rule violations show up
+ lint_config.ignore = ["T5"]
+ self.assertListEqual(violations[1:], linter.lint(commit))
+
+ # ignore named rule by id: only regular rule violations show up
+ lint_config.ignore = [rule_id]
+ self.assertListEqual(violations[:-1], linter.lint(commit))
+
+ # ignore named rule by name: only regular rule violations show up
+ lint_config.ignore = [TitleMustNotContainWord.name + u":my-ïd"]
+ self.assertListEqual(violations[:-1], linter.lint(commit))
diff --git a/gitlint/tests/test_options.py b/gitlint/tests/test_options.py
index 2c17226..68f0f8c 100644
--- a/gitlint/tests/test_options.py
+++ b/gitlint/tests/test_options.py
@@ -1,42 +1,51 @@
# -*- coding: utf-8 -*-
import os
+import re
from gitlint.tests.base import BaseTestCase
-from gitlint.options import IntOption, BoolOption, StrOption, ListOption, PathOption, RuleOptionError
+from gitlint.options import IntOption, BoolOption, StrOption, ListOption, PathOption, RegexOption, RuleOptionError
class RuleOptionTests(BaseTestCase):
def test_option_equality(self):
- # 2 options are equal if their name, value and description match
- option1 = IntOption("test-option", 123, u"Test Dëscription")
- option2 = IntOption("test-option", 123, u"Test Dëscription")
- self.assertEqual(option1, option2)
-
- # Not equal: name, description, value are different
- self.assertNotEqual(option1, IntOption("test-option1", 123, u"Test Dëscription"))
- self.assertNotEqual(option1, IntOption("test-option", 1234, u"Test Dëscription"))
- self.assertNotEqual(option1, IntOption("test-option", 123, u"Test Dëscription2"))
+ options = {IntOption: 123, StrOption: u"foöbar", BoolOption: False, ListOption: ["a", "b"],
+ PathOption: ".", RegexOption: u"^foöbar(.*)"}
+ for clazz, val in options.items():
+ # 2 options are equal if their name, value and description match
+ option1 = clazz(u"test-öption", val, u"Test Dëscription")
+ option2 = clazz(u"test-öption", val, u"Test Dëscription")
+ self.assertEqual(option1, option2)
+
+ # Not equal: class, name, description, value are different
+ self.assertNotEqual(option1, IntOption(u"tëst-option1", 123, u"Test Dëscription"))
+ self.assertNotEqual(option1, StrOption(u"tëst-option1", u"åbc", u"Test Dëscription"))
+ self.assertNotEqual(option1, StrOption(u"tëst-option", u"åbcd", u"Test Dëscription"))
+ self.assertNotEqual(option1, StrOption(u"tëst-option", u"åbc", u"Test Dëscription2"))
def test_int_option(self):
# normal behavior
- option = IntOption("test-name", 123, "Test Description")
+ option = IntOption(u"tëst-name", 123, u"Tëst Description")
+ self.assertEqual(option.name, u"tëst-name")
+ self.assertEqual(option.description, u"Tëst Description")
self.assertEqual(option.value, 123)
- self.assertEqual(option.name, "test-name")
- self.assertEqual(option.description, "Test Description")
# re-set value
option.set(456)
self.assertEqual(option.value, 456)
+ # set to None
+ option.set(None)
+ self.assertEqual(option.value, None)
+
# error on negative int when not allowed
- expected_error = u"Option 'test-name' must be a positive integer (current value: '-123')"
- with self.assertRaisesRegex(RuleOptionError, expected_error):
+ expected_error = u"Option 'tëst-name' must be a positive integer (current value: '-123')"
+ with self.assertRaisesMessage(RuleOptionError, expected_error):
option.set(-123)
# error on non-int value
- expected_error = u"Option 'test-name' must be a positive integer (current value: 'foo')"
- with self.assertRaisesRegex(RuleOptionError, expected_error):
+ expected_error = u"Option 'tëst-name' must be a positive integer (current value: 'foo')"
+ with self.assertRaisesMessage(RuleOptionError, expected_error):
option.set("foo")
# no error on negative value when allowed and negative int is passed
@@ -46,15 +55,15 @@ class RuleOptionTests(BaseTestCase):
# error on non-int value when negative int is allowed
expected_error = u"Option 'test-name' must be an integer (current value: 'foo')"
- with self.assertRaisesRegex(RuleOptionError, expected_error):
+ with self.assertRaisesMessage(RuleOptionError, expected_error):
option.set("foo")
def test_str_option(self):
# normal behavior
- option = StrOption("test-name", u"föo", "Test Description")
+ option = StrOption(u"tëst-name", u"föo", u"Tëst Description")
+ self.assertEqual(option.name, u"tëst-name")
+ self.assertEqual(option.description, u"Tëst Description")
self.assertEqual(option.value, u"föo")
- self.assertEqual(option.name, "test-name")
- self.assertEqual(option.description, "Test Description")
# re-set value
option.set(u"bår")
@@ -68,9 +77,15 @@ class RuleOptionTests(BaseTestCase):
option.set(-123)
self.assertEqual(option.value, "-123")
+ # None value
+ option.set(None)
+ self.assertEqual(option.value, None)
+
def test_boolean_option(self):
# normal behavior
- option = BoolOption("test-name", "true", "Test Description")
+ option = BoolOption(u"tëst-name", "true", u"Tëst Description")
+ self.assertEqual(option.name, u"tëst-name")
+ self.assertEqual(option.description, u"Tëst Description")
self.assertEqual(option.value, True)
# re-set value
@@ -82,14 +97,16 @@ class RuleOptionTests(BaseTestCase):
self.assertEqual(option.value, True)
# error on incorrect value
- incorrect_values = [1, -1, "foo", u"bår", ["foo"], {'foo': "bar"}]
+ incorrect_values = [1, -1, "foo", u"bår", ["foo"], {'foo': "bar"}, None]
for value in incorrect_values:
- with self.assertRaisesRegex(RuleOptionError, "Option 'test-name' must be either 'true' or 'false'"):
+ with self.assertRaisesMessage(RuleOptionError, u"Option 'tëst-name' must be either 'true' or 'false'"):
option.set(value)
def test_list_option(self):
# normal behavior
- option = ListOption("test-name", u"å,b,c,d", "Test Description")
+ option = ListOption(u"tëst-name", u"å,b,c,d", u"Tëst Description")
+ self.assertEqual(option.name, u"tëst-name")
+ self.assertEqual(option.description, u"Tëst Description")
self.assertListEqual(option.value, [u"å", u"b", u"c", u"d"])
# re-set value
@@ -100,6 +117,10 @@ class RuleOptionTests(BaseTestCase):
option.set([u"foo", u"bår", u"test"])
self.assertListEqual(option.value, [u"foo", u"bår", u"test"])
+ # None
+ option.set(None)
+ self.assertIsNone(option.value)
+
# empty string
option.set("")
self.assertListEqual(option.value, [])
@@ -129,40 +150,44 @@ class RuleOptionTests(BaseTestCase):
self.assertListEqual(option.value, ["123"])
def test_path_option(self):
- option = PathOption("test-directory", ".", u"Test Description", type=u"dir")
+ option = PathOption(u"tëst-directory", ".", u"Tëst Description", type=u"dir")
+ self.assertEqual(option.name, u"tëst-directory")
+ self.assertEqual(option.description, u"Tëst Description")
self.assertEqual(option.value, os.getcwd())
- self.assertEqual(option.name, "test-directory")
- self.assertEqual(option.description, u"Test Description")
self.assertEqual(option.type, u"dir")
# re-set value
option.set(self.SAMPLES_DIR)
self.assertEqual(option.value, self.SAMPLES_DIR)
+ # set to None
+ option.set(None)
+ self.assertIsNone(option.value)
+
# set to int
- expected = u"Option test-directory must be an existing directory (current value: '1234')"
- with self.assertRaisesRegex(RuleOptionError, expected):
+ expected = u"Option tëst-directory must be an existing directory (current value: '1234')"
+ with self.assertRaisesMessage(RuleOptionError, expected):
option.set(1234)
# set to non-existing directory
non_existing_path = os.path.join(u"/föo", u"bar")
- expected = u"Option test-directory must be an existing directory (current value: '{0}')"
- with self.assertRaisesRegex(RuleOptionError, expected.format(non_existing_path)):
+ expected = u"Option tëst-directory must be an existing directory (current value: '{0}')"
+ with self.assertRaisesMessage(RuleOptionError, expected.format(non_existing_path)):
option.set(non_existing_path)
# set to a file, should raise exception since option.type = dir
sample_path = self.get_sample_path(os.path.join("commit_message", "sample1"))
- expected = u"Option test-directory must be an existing directory (current value: '{0}')".format(sample_path)
- with self.assertRaisesRegex(RuleOptionError, expected):
+ expected = u"Option tëst-directory must be an existing directory (current value: '{0}')".format(sample_path)
+ with self.assertRaisesMessage(RuleOptionError, expected):
option.set(sample_path)
# set option.type = file, file should now be accepted, directories not
option.type = u"file"
option.set(sample_path)
self.assertEqual(option.value, sample_path)
- expected = u"Option test-directory must be an existing file (current value: '{0}')".format(
+ expected = u"Option tëst-directory must be an existing file (current value: '{0}')".format(
self.get_sample_path())
- with self.assertRaisesRegex(RuleOptionError, expected):
+ with self.assertRaisesMessage(RuleOptionError, expected):
option.set(self.get_sample_path())
# set option.type = both, files and directories should now be accepted
@@ -174,6 +199,27 @@ class RuleOptionTests(BaseTestCase):
# Expect exception if path type is invalid
option.type = u'föo'
- expected = u"Option test-directory type must be one of: 'file', 'dir', 'both' (current: 'föo')"
- with self.assertRaisesRegex(RuleOptionError, expected):
+ expected = u"Option tëst-directory type must be one of: 'file', 'dir', 'both' (current: 'föo')"
+ with self.assertRaisesMessage(RuleOptionError, expected):
option.set("haha")
+
+ def test_regex_option(self):
+ # normal behavior
+ option = RegexOption(u"tëst-regex", u"^myrëgex(.*)foo$", u"Tëst Regex Description")
+ self.assertEqual(option.name, u"tëst-regex")
+ self.assertEqual(option.description, u"Tëst Regex Description")
+ self.assertEqual(option.value, re.compile(u"^myrëgex(.*)foo$", re.UNICODE))
+
+ # re-set value
+ option.set(u"[0-9]föbar.*")
+ self.assertEqual(option.value, re.compile(u"[0-9]föbar.*", re.UNICODE))
+
+ # set None
+ option.set(None)
+ self.assertIsNone(option.value)
+
+ # error on invalid regex
+ incorrect_values = [u"foo(", 123, -1]
+ for value in incorrect_values:
+ with self.assertRaisesRegex(RuleOptionError, u"Invalid regular expression"):
+ option.set(value)
diff --git a/gitlint/tests/test_utils.py b/gitlint/tests/test_utils.py
index 6f667c2..5841b63 100644
--- a/gitlint/tests/test_utils.py
+++ b/gitlint/tests/test_utils.py
@@ -60,19 +60,23 @@ class UtilsTests(BaseTestCase):
patched_env.get.side_effect = mocked_get
# Assert getpreferredencoding reads env vars in order: LC_ALL, LC_CTYPE, LANG
- mock_env = {"LC_ALL": u"lc_all_välue", "LC_CTYPE": u"foo", "LANG": u"bar"}
- self.assertEqual(utils.getpreferredencoding(), u"lc_all_välue")
- mock_env = {"LC_CTYPE": u"lc_ctype_välue", "LANG": u"hur"}
- self.assertEqual(utils.getpreferredencoding(), u"lc_ctype_välue")
- mock_env = {"LANG": u"lang_välue"}
- self.assertEqual(utils.getpreferredencoding(), u"lang_välue")
+ mock_env = {"LC_ALL": u"ASCII", "LC_CTYPE": u"UTF-16", "LANG": u"CP1251"}
+ self.assertEqual(utils.getpreferredencoding(), u"ASCII")
+ mock_env = {"LC_CTYPE": u"UTF-16", "LANG": u"CP1251"}
+ self.assertEqual(utils.getpreferredencoding(), u"UTF-16")
+ mock_env = {"LANG": u"CP1251"}
+ self.assertEqual(utils.getpreferredencoding(), u"CP1251")
# Assert split on dot
- mock_env = {"LANG": u"foo.bär"}
- self.assertEqual(utils.getpreferredencoding(), u"bär")
+ mock_env = {"LANG": u"foo.UTF-16"}
+ self.assertEqual(utils.getpreferredencoding(), u"UTF-16")
# assert default encoding is UTF-8
mock_env = {}
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
mock_env = {"FOO": u"föo"}
self.assertEqual(utils.getpreferredencoding(), "UTF-8")
+
+ # assert fallback encoding is UTF-8 in case we set an unavailable encoding
+ mock_env = {"LC_ALL": u"foo"}
+ self.assertEqual(utils.getpreferredencoding(), u"UTF-8")