summaryrefslogtreecommitdiffstats
path: root/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa')
-rw-r--r--qa/base.py179
-rw-r--r--qa/expected/test_commits/test_csv_hash_list_111
-rw-r--r--qa/expected/test_commits/test_ignore_commits_12
-rw-r--r--qa/expected/test_commits/test_lint_staged_msg_filename_130
-rw-r--r--qa/expected/test_commits/test_lint_staged_stdin_130
-rw-r--r--qa/expected/test_config/test_config_from_env_1104
-rw-r--r--qa/expected/test_config/test_config_from_env_293
-rw-r--r--qa/expected/test_config/test_config_from_file_debug_130
-rw-r--r--qa/expected/test_contrib/test_contrib_rules_13
-rw-r--r--qa/expected/test_contrib/test_contrib_rules_with_config_13
-rw-r--r--qa/expected/test_gitlint/test_commit_binary_file_195
-rw-r--r--qa/expected/test_named_rules/test_named_rule_15
-rw-r--r--qa/expected/test_named_rules/test_named_user_rule_19
-rw-r--r--qa/expected/test_rules/test_ignore_rules_13
-rw-r--r--qa/expected/test_rules/test_ignore_rules_22
-rw-r--r--qa/expected/test_rules/test_match_regex_rules_12
-rw-r--r--qa/expected/test_user_defined/test_user_defined_rules_examples_14
-rw-r--r--qa/expected/test_user_defined/test_user_defined_rules_examples_25
-rw-r--r--qa/expected/test_user_defined/test_user_defined_rules_examples_with_config_14
-rw-r--r--qa/expected/test_user_defined/test_user_defined_rules_extra_110
-rw-r--r--qa/requirements.txt4
-rw-r--r--qa/samples/config/contrib-enabled0
-rw-r--r--qa/samples/config/named-rules8
-rw-r--r--qa/samples/config/named-user-rules15
-rw-r--r--qa/samples/user_rules/extra/extra_rules.py61
-rw-r--r--qa/shell.py110
-rw-r--r--qa/test_commits.py237
-rw-r--r--qa/test_config.py112
-rw-r--r--qa/test_contrib.py31
-rw-r--r--qa/test_gitlint.py204
-rw-r--r--qa/test_hooks.py149
-rw-r--r--qa/test_named_rules.py23
-rw-r--r--qa/test_rules.py61
-rw-r--r--qa/test_stdin.py51
-rw-r--r--qa/test_user_defined.py47
-rw-r--r--qa/utils.py79
36 files changed, 1352 insertions, 464 deletions
diff --git a/qa/base.py b/qa/base.py
index 05d85e5..2b83778 100644
--- a/qa/base.py
+++ b/qa/base.py
@@ -1,32 +1,21 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return,
-# pylint: disable=too-many-function-args,unexpected-keyword-arg
-
-import io
import os
import platform
import shutil
import sys
import tempfile
-from datetime import datetime
+from datetime import datetime, timezone
+from unittest import TestCase
from uuid import uuid4
import arrow
-try:
- # python 2.x
- from unittest2 import TestCase
-except ImportError:
- # python 3.x
- from unittest import TestCase
-
-from qa.shell import git, gitlint, RunningCommand
-from qa.utils import DEFAULT_ENCODING, ustr
+from qa.shell import RunningCommand, git, gitlint
+from qa.utils import FILE_ENCODING, PLATFORM_IS_WINDOWS, TERMINAL_ENCODING
class BaseTestCase(TestCase):
- """ Base class of which all gitlint integration test classes are derived.
- Provides a number of convenience methods. """
+ """Base class of which all gitlint integration test classes are derived.
+ Provides a number of convenience methods."""
# In case of assert failures, print the full error message
maxDiff = None
@@ -34,43 +23,39 @@ class BaseTestCase(TestCase):
GITLINT_USE_SH_LIB = os.environ.get("GITLINT_USE_SH_LIB", "[NOT SET]")
GIT_CONTEXT_ERROR_CODE = 254
-
- @classmethod
- def setUpClass(cls):
- """ Sets up the integration tests by creating a new temporary git repository """
- cls.tmp_git_repos = []
- cls.tmp_git_repo = cls.create_tmp_git_repo()
-
- @classmethod
- def tearDownClass(cls):
- """ Cleans up the temporary git repositories """
- for repo in cls.tmp_git_repos:
- shutil.rmtree(repo)
+ GITLINT_USAGE_ERROR = 253
def setUp(self):
+ """Sets up the integration tests by creating a new temporary git repository"""
self.tmpfiles = []
+ self.tmp_git_repos = []
+ self.tmp_git_repo = self.create_tmp_git_repo()
def tearDown(self):
+ # Clean up temporary files and repos
for tmpfile in self.tmpfiles:
os.remove(tmpfile)
+ for repo in self.tmp_git_repos:
+ # On windows we need to ignore errors because git might still be holding on to some files
+ shutil.rmtree(repo, ignore_errors=PLATFORM_IS_WINDOWS)
- def assertEqualStdout(self, output, expected): # pylint: disable=invalid-name
+ def assertEqualStdout(self, output, expected):
self.assertIsInstance(output, RunningCommand)
- output = ustr(output.stdout)
- output = output.replace('\r', '')
+ output = output.stdout.decode(TERMINAL_ENCODING)
+ output = output.replace("\r", "")
self.assertMultiLineEqual(output, expected)
- @classmethod
- def generate_temp_path(cls):
- return os.path.realpath("/tmp/gitlint-test-{0}".format(datetime.now().strftime("%Y%m%d-%H%M%S-%f")))
+ @staticmethod
+ def generate_temp_path():
+ timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S-%f")
+ return os.path.realpath(f"/tmp/gitlint-test-{timestamp}") # noqa
- @classmethod
- def create_tmp_git_repo(cls):
- """ Creates a temporary git repository and returns its directory path """
- tmp_git_repo = cls.generate_temp_path()
- cls.tmp_git_repos.append(tmp_git_repo)
+ def create_tmp_git_repo(self):
+ """Creates a temporary git repository and returns its directory path"""
+ tmp_git_repo = self.generate_temp_path()
+ self.tmp_git_repos.append(tmp_git_repo)
- git("init", tmp_git_repo)
+ git("init", "--initial-branch", "main", tmp_git_repo)
# configuring name and email is required in every git repot
git("config", "user.name", "gitlint-test-user", _cwd=tmp_git_repo)
git("config", "user.email", "gitlint@test.com", _cwd=tmp_git_repo)
@@ -84,18 +69,48 @@ class BaseTestCase(TestCase):
# http://stackoverflow.com/questions/5581857/git-and-the-umlaut-problem-on-mac-os-x
git("config", "core.precomposeunicode", "true", _cwd=tmp_git_repo)
+ # Git now does commit message cleanup by default (e.g. removing trailing whitespace), disable that for testing
+ git("config", "commit.cleanup", "verbatim", _cwd=tmp_git_repo)
+
return tmp_git_repo
@staticmethod
- def create_file(parent_dir):
- """ Creates a file inside a passed directory. Returns filename."""
- test_filename = u"test-fïle-" + str(uuid4())
- io.open(os.path.join(parent_dir, test_filename), 'a', encoding=DEFAULT_ENCODING).close()
+ def create_file(parent_dir, content=None):
+ """Creates a file inside a passed directory. Returns filename."""
+ test_filename = "test-fïle-" + str(uuid4())
+ full_path = os.path.join(parent_dir, test_filename)
+
+ if content:
+ if isinstance(content, bytes):
+ open_kwargs = {"mode": "wb"}
+ else:
+ open_kwargs = {"mode": "w", "encoding": FILE_ENCODING}
+
+ with open(full_path, **open_kwargs) as f:
+ f.write(content)
+ else:
+ open(full_path, "a", encoding=FILE_ENCODING).close() # noqa: SIM115 (Use context handler for opening files)
+
return test_filename
- def create_simple_commit(self, message, out=None, ok_code=None, env=None, git_repo=None, tty_in=False):
- """ Creates a simple commit with an empty test file.
- :param message: Commit message for the commit. """
+ @staticmethod
+ def create_environment(envvars=None):
+ """Creates a copy of the current os.environ and adds/overwrites a given set of variables to it"""
+ environment = os.environ.copy()
+ if envvars:
+ environment.update(envvars)
+ return environment
+
+ def create_tmp_git_config(self, contents):
+ """Creates an environment with the GIT_CONFIG variable set to a file with the given contents."""
+ tmp_config = self.create_tmpfile(contents)
+ return self.create_environment({"GIT_CONFIG": tmp_config})
+
+ def create_simple_commit(
+ self, message, *, file_contents=None, out=None, ok_code=None, env=None, git_repo=None, tty_in=False
+ ):
+ """Creates a simple commit with an empty test file.
+ :param message: Commit message for the commit."""
git_repo = self.tmp_git_repo if git_repo is None else git_repo
@@ -103,28 +118,42 @@ class BaseTestCase(TestCase):
# variables can influence how git runs.
# This was needed to fix https://github.com/jorisroovers/gitlint/issues/15 as we need to make sure to use
# the PATH variable that contains the virtualenv's python binary.
- environment = os.environ
- if env:
- environment.update(env)
+ environment = self.create_environment(env)
# Create file and add to git
- test_filename = self.create_file(git_repo)
+ test_filename = self.create_file(git_repo, file_contents)
git("add", test_filename, _cwd=git_repo)
# https://amoffat.github.io/sh/#interactive-callbacks
if not ok_code:
ok_code = [0]
- git("commit", "-m", message, _cwd=git_repo, _err_to_out=True, _out=out, _tty_in=tty_in,
- _ok_code=ok_code, _env=environment)
+ git(
+ "commit",
+ "-m",
+ message,
+ _cwd=git_repo,
+ _err_to_out=True,
+ _out=out,
+ _tty_in=tty_in,
+ _ok_code=ok_code,
+ _env=environment,
+ )
return test_filename
def create_tmpfile(self, content):
- """ Utility method to create temp files. These are cleaned at the end of the test """
- # Not using a context manager to avoid unneccessary identation in test code
+ """Utility method to create temp files. These are cleaned at the end of the test"""
+ # Not using a context manager to avoid unnecessary indentation in test code
tmpfile, tmpfilepath = tempfile.mkstemp()
self.tmpfiles.append(tmpfilepath)
- with io.open(tmpfile, "w", encoding=DEFAULT_ENCODING) as f:
+
+ if isinstance(content, bytes):
+ open_kwargs = {"mode": "wb"}
+ else:
+ open_kwargs = {"mode": "w", "encoding": FILE_ENCODING}
+
+ with open(tmpfile, **open_kwargs) as f:
f.write(content)
+
return tmpfilepath
@staticmethod
@@ -147,32 +176,40 @@ class BaseTestCase(TestCase):
@staticmethod
def get_expected(filename="", variable_dict=None):
- """ Utility method to read an 'expected' file and return it as a string. Optionally replace template variables
- specified by variable_dict. """
+ """Utility method to read an 'expected' file and return it as a string. Optionally replace template variables
+ specified by variable_dict."""
expected_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "expected")
expected_path = os.path.join(expected_dir, filename)
- expected = io.open(expected_path, encoding=DEFAULT_ENCODING).read()
+ # Expected files are UTF-8 encoded (not dependent on the system's default encoding)
+ with open(expected_path, encoding=FILE_ENCODING) as file:
+ expected = file.read()
- if variable_dict:
- expected = expected.format(**variable_dict)
- return expected
+ if variable_dict:
+ expected = expected.format(**variable_dict)
+ return expected
@staticmethod
def get_system_info_dict():
- """ Returns a dict with items related to system values logged by `gitlint --debug` """
- expected_gitlint_version = gitlint("--version").replace("gitlint, version ", "").replace("\n", "")
- expected_git_version = git("--version").replace("\n", "")
- return {'platform': platform.platform(), 'python_version': sys.version,
- 'git_version': expected_git_version, 'gitlint_version': expected_gitlint_version,
- 'GITLINT_USE_SH_LIB': BaseTestCase.GITLINT_USE_SH_LIB}
+ """Returns a dict with items related to system values logged by `gitlint --debug`"""
+ expected_gitlint_version = gitlint("--version").replace("gitlint, version ", "").strip()
+ expected_git_version = git("--version").strip()
+ return {
+ "platform": platform.platform(),
+ "python_version": sys.version,
+ "git_version": expected_git_version,
+ "gitlint_version": expected_gitlint_version,
+ "GITLINT_USE_SH_LIB": BaseTestCase.GITLINT_USE_SH_LIB,
+ "TERMINAL_ENCODING": TERMINAL_ENCODING,
+ "FILE_ENCODING": FILE_ENCODING,
+ }
def get_debug_vars_last_commit(self, git_repo=None):
- """ Returns a dict with items related to `gitlint --debug` output for the last commit. """
+ """Returns a dict with items related to `gitlint --debug` output for the last commit."""
target_repo = git_repo if git_repo else self.tmp_git_repo
commit_sha = self.get_last_commit_hash(git_repo=target_repo)
expected_date = git("log", "-1", "--pretty=%ai", _tty_out=False, _cwd=target_repo)
expected_date = arrow.get(str(expected_date), "YYYY-MM-DD HH:mm:ss Z").format("YYYY-MM-DD HH:mm:ss Z")
expected_kwargs = self.get_system_info_dict()
- expected_kwargs.update({'target': target_repo, 'commit_sha': commit_sha, 'commit_date': expected_date})
+ expected_kwargs.update({"target": target_repo, "commit_sha": commit_sha, "commit_date": expected_date})
return expected_kwargs
diff --git a/qa/expected/test_commits/test_csv_hash_list_1 b/qa/expected/test_commits/test_csv_hash_list_1
new file mode 100644
index 0000000..bbd9f51
--- /dev/null
+++ b/qa/expected/test_commits/test_csv_hash_list_1
@@ -0,0 +1,11 @@
+Commit {commit_sha2}:
+1: T3 Title has trailing punctuation (.): "Sïmple title2."
+3: B6 Body message is missing
+
+Commit {commit_sha1}:
+1: T3 Title has trailing punctuation (.): "Sïmple title1."
+3: B6 Body message is missing
+
+Commit {commit_sha4}:
+1: T3 Title has trailing punctuation (.): "Sïmple title4."
+3: B6 Body message is missing
diff --git a/qa/expected/test_commits/test_ignore_commits_1 b/qa/expected/test_commits/test_ignore_commits_1
index f9062c1..01cf8bd 100644
--- a/qa/expected/test_commits/test_ignore_commits_1
+++ b/qa/expected/test_commits/test_ignore_commits_1
@@ -1,3 +1,5 @@
+WARNING: I1 - ignore-by-title: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-by-title.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
+WARNING: I2 - ignore-by-body: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-by-body.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
Commit {commit_sha0}:
1: T3 Title has trailing punctuation (.): "Sïmple title4."
diff --git a/qa/expected/test_commits/test_lint_staged_msg_filename_1 b/qa/expected/test_commits/test_lint_staged_msg_filename_1
index 878bc4c..03a558c 100644
--- a/qa/expected/test_commits/test_lint_staged_msg_filename_1
+++ b/qa/expected/test_commits/test_lint_staged_msg_filename_1
@@ -1,9 +1,12 @@
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.git ('--version',)
DEBUG: gitlint.cli Git version: {git_version}
DEBUG: gitlint.cli Gitlint version: {gitlint_version}
DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING}
+DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING}
DEBUG: gitlint.cli Configuration
config-path: None
[GENERAL]
@@ -12,10 +15,13 @@ contrib: []
ignore:
ignore-merge-commits: True
ignore-fixup-commits: True
+ignore-fixup-amend-commits: True
ignore-squash-commits: True
ignore-revert-commits: True
ignore-stdin: False
staged: True
+fail-without-commits: False
+regex-style-search: False
verbosity: 3
debug: True
target: {target}
@@ -26,6 +32,11 @@ target: {target}
I2: ignore-by-body
ignore=all
regex=None
+ I3: ignore-body-lines
+ regex=None
+ I4: ignore-by-author-name
+ ignore=all
+ regex=None
T1: title-max-length
line-length=72
T2: title-trailing-whitespace
@@ -35,7 +46,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 +60,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=[^@ ]+@[^@ ]+\.[^@ ]+
+ 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 ('diff', '--staged', '--numstat', '-r')
+DEBUG: gitlint.git ('config', '--get', 'user.name')
+DEBUG: gitlint.git ('config', '--get', 'user.email')
+DEBUG: gitlint.git ('rev-parse', '--abbrev-ref', 'HEAD')
DEBUG: gitlint.lint Commit Object
--- Commit Message ----
WIP: from fïle test.
@@ -62,10 +82,14 @@ Author: gitlint-test-user <gitlint@test.com>
Date: {staged_date}
is-merge-commit: False
is-fixup-commit: False
+is-fixup-amend-commit: False
is-squash-commit: False
is-revert-commit: False
-Branches: ['master']
+Parents: []
+Branches: ['main']
Changed Files: {changed_files}
+Changed Files Stats:
+ {changed_files_stats}
-----------------------
1: T3 Title has trailing punctuation (.): "WIP: from fïle test."
1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: from fïle test."
diff --git a/qa/expected/test_commits/test_lint_staged_stdin_1 b/qa/expected/test_commits/test_lint_staged_stdin_1
index 3f178f8..7892865 100644
--- a/qa/expected/test_commits/test_lint_staged_stdin_1
+++ b/qa/expected/test_commits/test_lint_staged_stdin_1
@@ -1,9 +1,12 @@
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.git ('--version',)
DEBUG: gitlint.cli Git version: {git_version}
DEBUG: gitlint.cli Gitlint version: {gitlint_version}
DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING}
+DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING}
DEBUG: gitlint.cli Configuration
config-path: None
[GENERAL]
@@ -12,10 +15,13 @@ contrib: []
ignore:
ignore-merge-commits: True
ignore-fixup-commits: True
+ignore-fixup-amend-commits: True
ignore-squash-commits: True
ignore-revert-commits: True
ignore-stdin: False
staged: True
+fail-without-commits: False
+regex-style-search: False
verbosity: 3
debug: True
target: {target}
@@ -26,6 +32,11 @@ target: {target}
I2: ignore-by-body
ignore=all
regex=None
+ I3: ignore-body-lines
+ regex=None
+ I4: ignore-by-author-name
+ ignore=all
+ regex=None
T1: title-max-length
line-length=72
T2: title-trailing-whitespace
@@ -35,7 +46,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,15 +60,22 @@ target: {target}
B4: body-first-line-empty
B7: body-changed-file-mention
files=
+ B8: body-match-regex
+ regex=None
M1: author-valid-email
- regex=[^@ ]+@[^@ ]+\.[^@ ]+
+ regex=^[^@ ]+@[^@ ]+\.[^@ ]+
DEBUG: gitlint.cli Fetching additional meta-data from staged commit
DEBUG: gitlint.cli Stdin data: 'WIP: Pïpe test.
'
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 ('diff', '--staged', '--numstat', '-r')
+DEBUG: gitlint.git ('config', '--get', 'user.name')
+DEBUG: gitlint.git ('config', '--get', 'user.email')
+DEBUG: gitlint.git ('rev-parse', '--abbrev-ref', 'HEAD')
DEBUG: gitlint.lint Commit Object
--- Commit Message ----
WIP: Pïpe test.
@@ -64,10 +84,14 @@ Author: gitlint-test-user <gitlint@test.com>
Date: {staged_date}
is-merge-commit: False
is-fixup-commit: False
+is-fixup-amend-commit: False
is-squash-commit: False
is-revert-commit: False
-Branches: ['master']
+Parents: []
+Branches: ['main']
Changed Files: {changed_files}
+Changed Files Stats:
+ {changed_files_stats}
-----------------------
1: T3 Title has trailing punctuation (.): "WIP: Pïpe test."
1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: Pïpe test."
diff --git a/qa/expected/test_config/test_config_from_env_1 b/qa/expected/test_config/test_config_from_env_1
new file mode 100644
index 0000000..91eee40
--- /dev/null
+++ b/qa/expected/test_config/test_config_from_env_1
@@ -0,0 +1,104 @@
+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.git ('--version',)
+DEBUG: gitlint.cli Git version: {git_version}
+DEBUG: gitlint.cli Gitlint version: {gitlint_version}
+DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING}
+DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING}
+DEBUG: gitlint.cli Configuration
+config-path: None
+[GENERAL]
+extra-path: None
+contrib: ['CC1', 'CT1']
+ignore: T1,T2
+ignore-merge-commits: True
+ignore-fixup-commits: True
+ignore-fixup-amend-commits: True
+ignore-squash-commits: True
+ignore-revert-commits: True
+ignore-stdin: True
+staged: False
+fail-without-commits: True
+regex-style-search: False
+verbosity: 2
+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
+ I4: ignore-by-author-name
+ ignore=all
+ 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
+ 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=^[^@ ]+@[^@ ]+\.[^@ ]+
+ CC1: contrib-body-requires-signed-off-by
+ CT1: contrib-title-conventional-commits
+ types=fix,feat,chore,docs,style,refactor,perf,test,revert,ci,build
+
+DEBUG: gitlint.cli No --msg-filename flag, no or empty data passed to stdin. Using the local repo.
+DEBUG: gitlint.git ('rev-list', '{commit_sha}')
+DEBUG: gitlint.cli Linting 1 commit(s)
+DEBUG: gitlint.git ('log', '{commit_sha}', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B')
+DEBUG: gitlint.git ('config', '--get', 'core.commentchar')
+DEBUG: gitlint.lint Linting commit {commit_sha}
+DEBUG: gitlint.git ('diff-tree', '--no-commit-id', '--numstat', '-r', '--root', '{commit_sha}')
+DEBUG: gitlint.git ('branch', '--contains', '{commit_sha}')
+DEBUG: gitlint.lint Commit Object
+--- Commit Message ----
+WIP: Thïs is a title thåt is a bit longer.
+Content on the second line
+This line of the body is here because we need it
+
+--- Meta info ---------
+Author: gitlint-test-user <gitlint@test.com>
+Date: {commit_date}
+is-merge-commit: False
+is-fixup-commit: False
+is-fixup-amend-commit: False
+is-squash-commit: False
+is-revert-commit: False
+Parents: []
+Branches: ['main']
+Changed Files: {changed_files}
+Changed Files Stats:
+ {changed_files_stats}
+-----------------------
+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, ci, build
+1: T3 Title has trailing punctuation (.)
+1: T5 Title contains the word 'WIP' (case-insensitive)
+2: B4 Second line is not empty
+DEBUG: gitlint.cli Exit Code = 5
diff --git a/qa/expected/test_config/test_config_from_env_2 b/qa/expected/test_config/test_config_from_env_2
new file mode 100644
index 0000000..06b0c1b
--- /dev/null
+++ b/qa/expected/test_config/test_config_from_env_2
@@ -0,0 +1,93 @@
+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.git ('--version',)
+DEBUG: gitlint.cli Git version: {git_version}
+DEBUG: gitlint.cli Gitlint version: {gitlint_version}
+DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING}
+DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING}
+DEBUG: gitlint.cli Configuration
+config-path: None
+[GENERAL]
+extra-path: None
+contrib: []
+ignore:
+ignore-merge-commits: True
+ignore-fixup-commits: True
+ignore-fixup-amend-commits: True
+ignore-squash-commits: True
+ignore-revert-commits: True
+ignore-stdin: False
+staged: True
+fail-without-commits: False
+regex-style-search: False
+verbosity: 0
+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
+ I4: ignore-by-author-name
+ ignore=all
+ 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
+ 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=^[^@ ]+@[^@ ]+\.[^@ ]+
+
+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 ('diff', '--staged', '--numstat', '-r')
+DEBUG: gitlint.git ('config', '--get', 'user.name')
+DEBUG: gitlint.git ('config', '--get', 'user.email')
+DEBUG: gitlint.git ('rev-parse', '--abbrev-ref', 'HEAD')
+DEBUG: gitlint.lint Commit Object
+--- Commit Message ----
+WIP: msg-fïlename test.
+--- Meta info ---------
+Author: gitlint-test-user <gitlint@test.com>
+Date: {date}
+is-merge-commit: False
+is-fixup-commit: False
+is-fixup-amend-commit: False
+is-squash-commit: False
+is-revert-commit: False
+Parents: []
+Branches: ['main']
+Changed Files: []
+Changed Files Stats: {{}}
+-----------------------
+DEBUG: gitlint.cli Exit Code = 3
diff --git a/qa/expected/test_config/test_config_from_file_debug_1 b/qa/expected/test_config/test_config_from_file_debug_1
index 443ee26..279fb32 100644
--- a/qa/expected/test_config/test_config_from_file_debug_1
+++ b/qa/expected/test_config/test_config_from_file_debug_1
@@ -1,9 +1,12 @@
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.git ('--version',)
DEBUG: gitlint.cli Git version: {git_version}
DEBUG: gitlint.cli Gitlint version: {gitlint_version}
DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING}
+DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING}
DEBUG: gitlint.cli Configuration
config-path: {config_path}
[GENERAL]
@@ -12,10 +15,13 @@ contrib: []
ignore: title-trailing-punctuation,B2
ignore-merge-commits: True
ignore-fixup-commits: True
+ignore-fixup-amend-commits: True
ignore-squash-commits: True
ignore-revert-commits: True
ignore-stdin: False
staged: False
+fail-without-commits: False
+regex-style-search: False
verbosity: 2
debug: True
target: {target}
@@ -26,6 +32,11 @@ target: {target}
I2: ignore-by-body
ignore=all
regex=None
+ I3: ignore-body-lines
+ regex=None
+ I4: ignore-by-author-name
+ ignore=all
+ regex=None
T1: title-max-length
line-length=20
T2: title-trailing-whitespace
@@ -35,7 +46,9 @@ target: {target}
T5: title-must-not-contain-word
words=WIP,thåt
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 +60,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=[^@ ]+@[^@ ]+\.[^@ ]+
+ regex=^[^@ ]+@[^@ ]+\.[^@ ]+
DEBUG: gitlint.cli No --msg-filename flag, no or empty data passed to stdin. Using the local repo.
+DEBUG: gitlint.git ('log', '-1', '--pretty=%H')
DEBUG: gitlint.cli Linting 1 commit(s)
+DEBUG: gitlint.git ('log', '{commit_sha}', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B')
+DEBUG: gitlint.git ('config', '--get', 'core.commentchar')
DEBUG: gitlint.lint Linting commit {commit_sha}
+DEBUG: gitlint.git ('diff-tree', '--no-commit-id', '--numstat', '-r', '--root', '{commit_sha}')
+DEBUG: gitlint.git ('branch', '--contains', '{commit_sha}')
DEBUG: gitlint.lint Commit Object
--- Commit Message ----
WIP: Thïs is a title thåt is a bit longer.
@@ -64,10 +84,14 @@ Author: gitlint-test-user <gitlint@test.com>
Date: {commit_date}
is-merge-commit: False
is-fixup-commit: False
+is-fixup-amend-commit: False
is-squash-commit: False
is-revert-commit: False
-Branches: ['master']
+Parents: []
+Branches: ['main']
Changed Files: {changed_files}
+Changed Files Stats:
+ {changed_files_stats}
-----------------------
1: T1 Title exceeds max length (42>20)
1: T5 Title contains the word 'WIP' (case-insensitive)
diff --git a/qa/expected/test_contrib/test_contrib_rules_1 b/qa/expected/test_contrib/test_contrib_rules_1
index 99b33b7..6ab7512 100644
--- a/qa/expected/test_contrib/test_contrib_rules_1
+++ b/qa/expected/test_contrib/test_contrib_rules_1
@@ -1,4 +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: "WIP Thi$ is å title"
+1: CC1 Body does not contain a 'Signed-off-by' line
1: CT1 Title does not follow ConventionalCommits.org format 'type(optional-scope): description': "WIP Thi$ is å title"
1: T5 Title contains the word 'WIP' (case-insensitive): "WIP Thi$ is å title"
diff --git a/qa/expected/test_contrib/test_contrib_rules_with_config_1 b/qa/expected/test_contrib/test_contrib_rules_with_config_1
index 21d467a..6ab7512 100644
--- a/qa/expected/test_contrib/test_contrib_rules_with_config_1
+++ b/qa/expected/test_contrib/test_contrib_rules_with_config_1
@@ -1,4 +1,3 @@
-1: CC1 Body does not contain a 'Signed-Off-By' line
-1: CT1 Title does not start with one of föo, bår: "WIP Thi$ is å title"
+1: CC1 Body does not contain a 'Signed-off-by' line
1: CT1 Title does not follow ConventionalCommits.org format 'type(optional-scope): description': "WIP Thi$ is å title"
1: T5 Title contains the word 'WIP' (case-insensitive): "WIP Thi$ is å title"
diff --git a/qa/expected/test_gitlint/test_commit_binary_file_1 b/qa/expected/test_gitlint/test_commit_binary_file_1
new file mode 100644
index 0000000..83faf1b
--- /dev/null
+++ b/qa/expected/test_gitlint/test_commit_binary_file_1
@@ -0,0 +1,95 @@
+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.git ('--version',)
+DEBUG: gitlint.cli Git version: {git_version}
+DEBUG: gitlint.cli Gitlint version: {gitlint_version}
+DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB}
+DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING}
+DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING}
+DEBUG: gitlint.cli Configuration
+config-path: None
+[GENERAL]
+extra-path: None
+contrib: []
+ignore:
+ignore-merge-commits: True
+ignore-fixup-commits: True
+ignore-fixup-amend-commits: True
+ignore-squash-commits: True
+ignore-revert-commits: True
+ignore-stdin: False
+staged: False
+fail-without-commits: False
+regex-style-search: 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
+ I4: ignore-by-author-name
+ ignore=all
+ 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
+ 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=^[^@ ]+@[^@ ]+\.[^@ ]+
+
+DEBUG: gitlint.cli No --msg-filename flag, no or empty data passed to stdin. Using the local repo.
+DEBUG: gitlint.git ('log', '-1', '--pretty=%H')
+DEBUG: gitlint.cli Linting 1 commit(s)
+DEBUG: gitlint.git ('log', '{commit_sha}', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B')
+DEBUG: gitlint.git ('config', '--get', 'core.commentchar')
+DEBUG: gitlint.lint Linting commit {commit_sha}
+DEBUG: gitlint.git ('diff-tree', '--no-commit-id', '--numstat', '-r', '--root', '{commit_sha}')
+DEBUG: gitlint.git ('branch', '--contains', '{commit_sha}')
+DEBUG: gitlint.lint Commit Object
+--- Commit Message ----
+Sïmple commit
+
+--- Meta info ---------
+Author: gitlint-test-user <gitlint@test.com>
+Date: {commit_date}
+is-merge-commit: False
+is-fixup-commit: False
+is-fixup-amend-commit: False
+is-squash-commit: False
+is-revert-commit: False
+Parents: []
+Branches: ['main']
+Changed Files: {changed_files}
+Changed Files Stats:
+ {changed_files_stats}
+-----------------------
+3: B6 Body message is missing
+DEBUG: gitlint.cli Exit Code = 1
diff --git a/qa/expected/test_named_rules/test_named_rule_1 b/qa/expected/test_named_rules/test_named_rule_1
new file mode 100644
index 0000000..e5a380c
--- /dev/null
+++ b/qa/expected/test_named_rules/test_named_rule_1
@@ -0,0 +1,5 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: thåt dûr bår"
+1: T5 Title contains the word 'thåt' (case-insensitive): "WIP: thåt dûr bår"
+1: T5:even$more%wôrds Title contains the word 'bår' (case-insensitive): "WIP: thåt dûr bår"
+1: T5:extra-wôrds Title contains the word 'dûr' (case-insensitive): "WIP: thåt dûr bår"
+3: B5 Body message is too short (18<20): "Sïmple commit body"
diff --git a/qa/expected/test_named_rules/test_named_user_rule_1 b/qa/expected/test_named_rules/test_named_user_rule_1
new file mode 100644
index 0000000..3cd18b4
--- /dev/null
+++ b/qa/expected/test_named_rules/test_named_user_rule_1
@@ -0,0 +1,9 @@
+1: UC4 int-öption: 2
+1: UC4 str-öption: föo
+1: UC4 list-öption: ['foo', 'bar']
+1: UC4:bår int-öption: 2
+1: UC4:bår str-öption: bår
+1: UC4:bår list-öption: ['bar', 'list']
+1: UC4:föo int-öption: 3
+1: UC4:föo str-öption: föo
+1: UC4:föo list-öption: ['foo', 'bar']
diff --git a/qa/expected/test_rules/test_ignore_rules_1 b/qa/expected/test_rules/test_ignore_rules_1
new file mode 100644
index 0000000..f87f303
--- /dev/null
+++ b/qa/expected/test_rules/test_ignore_rules_1
@@ -0,0 +1,3 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: Commït Tïtle"
+3: B3 Line contains hard tab characters (\t): "Sïmple commit body"
+4: B2 Line has trailing whitespace: "Anōther Line "
diff --git a/qa/expected/test_rules/test_ignore_rules_2 b/qa/expected/test_rules/test_ignore_rules_2
new file mode 100644
index 0000000..dc6428c
--- /dev/null
+++ b/qa/expected/test_rules/test_ignore_rules_2
@@ -0,0 +1,2 @@
+1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: Commït Tïtle"
+3: B3 Line contains hard tab characters (\t): "Sïmple commit body"
diff --git a/qa/expected/test_rules/test_match_regex_rules_1 b/qa/expected/test_rules/test_match_regex_rules_1
new file mode 100644
index 0000000..3bfaa58
--- /dev/null
+++ b/qa/expected/test_rules/test_match_regex_rules_1
@@ -0,0 +1,2 @@
+1: T7 Title does not match regex (foo): "Thåt dûr bår"
+4: B8 Body does not match regex (bar)
diff --git a/qa/expected/test_user_defined/test_user_defined_rules_examples_1 b/qa/expected/test_user_defined/test_user_defined_rules_examples_1
index 9d00445..e675d7b 100644
--- a/qa/expected/test_user_defined/test_user_defined_rules_examples_1
+++ b/qa/expected/test_user_defined/test_user_defined_rules_examples_1
@@ -1,5 +1,5 @@
1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: Thi$ is å title"
-1: UC2 Body does not contain a 'Signed-Off-By' line
-1: UC3 Branch name 'master' does not start with one of ['feature/', 'hotfix/', 'release/']
+1: UC2 Body does not contain a 'Signed-off-by' line
+1: UC3 Branch name 'main' does not start with one of ['feature/', 'hotfix/', 'release/']
1: UL1 Title contains the special character '$': "WIP: Thi$ is å title"
2: B4 Second line is not empty: "Content on the second line"
diff --git a/qa/expected/test_user_defined/test_user_defined_rules_examples_2 b/qa/expected/test_user_defined/test_user_defined_rules_examples_2
new file mode 100644
index 0000000..d706b12
--- /dev/null
+++ b/qa/expected/test_user_defined/test_user_defined_rules_examples_2
@@ -0,0 +1,5 @@
+1: UC2 Body does not contain a 'Signed-off-by' line
+1: UC3 Branch name 'main' does not start with one of ['feature/', 'hotfix/', 'release/']
+1: UL1 Title contains the special character '$'
+2: B4 Second line is not empty
+3: B3 Line contains hard tab characters (\t)
diff --git a/qa/expected/test_user_defined/test_user_defined_rules_examples_with_config_1 b/qa/expected/test_user_defined/test_user_defined_rules_examples_with_config_1
index a143715..6e0d4cd 100644
--- a/qa/expected/test_user_defined/test_user_defined_rules_examples_with_config_1
+++ b/qa/expected/test_user_defined/test_user_defined_rules_examples_with_config_1
@@ -1,6 +1,6 @@
1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: Thi$ is å title"
1: UC1 Body contains too many lines (2 > 1)
-1: UC2 Body does not contain a 'Signed-Off-By' line
-1: UC3 Branch name 'master' does not start with one of ['feature/', 'hotfix/', 'release/']
+1: UC2 Body does not contain a 'Signed-off-by' line
+1: UC3 Branch name 'main' does not start with one of ['feature/', 'hotfix/', 'release/']
1: UL1 Title contains the special character '$': "WIP: Thi$ is å title"
2: B4 Second line is not empty: "Content on the second line"
diff --git a/qa/expected/test_user_defined/test_user_defined_rules_extra_1 b/qa/expected/test_user_defined/test_user_defined_rules_extra_1
index 65f3507..77642dc 100644
--- a/qa/expected/test_user_defined/test_user_defined_rules_extra_1
+++ b/qa/expected/test_user_defined/test_user_defined_rules_extra_1
@@ -1,5 +1,9 @@
1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: Thi$ is å title"
-1: UC1 GitContext.current_branch: master
+1: UC1 GitContext.current_branch: main
1: UC1 GitContext.commentchar: #
-1: UC2 GitCommit.branches: ['master']
-2: B4 Second line is not empty: "Content on the second line"
+1: UC2 GitCommit.branches: ['main']
+1: UC2 GitCommit.custom_prop: foöbar
+1: UC4 int-öption: 2
+1: UC4 str-öption: föo
+1: UC4 list-öption: ['foo', 'bar']
+4: B2 Line has trailing whitespace: "{repo-path} "
diff --git a/qa/requirements.txt b/qa/requirements.txt
deleted file mode 100644
index f042dad..0000000
--- a/qa/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-sh==1.12.14
-pytest==4.6.3;
-arrow==0.15.5;
-gitlint # no version as you want to test the currently installed version
diff --git a/qa/samples/config/contrib-enabled b/qa/samples/config/contrib-enabled
deleted file mode 100644
index e69de29..0000000
--- a/qa/samples/config/contrib-enabled
+++ /dev/null
diff --git a/qa/samples/config/named-rules b/qa/samples/config/named-rules
new file mode 100644
index 0000000..f9bbdf5
--- /dev/null
+++ b/qa/samples/config/named-rules
@@ -0,0 +1,8 @@
+[title-must-not-contain-word]
+words=WIP,thåt
+
+[title-must-not-contain-word:extra-wôrds]
+words=hûr,dûr
+
+[title-must-not-contain-word: even$more%wôrds ]
+words=fôo,bår \ No newline at end of file
diff --git a/qa/samples/config/named-user-rules b/qa/samples/config/named-user-rules
new file mode 100644
index 0000000..ed811fb
--- /dev/null
+++ b/qa/samples/config/named-user-rules
@@ -0,0 +1,15 @@
+# Ignore other user-defined rules
+[general]
+ignore=UC1,UC2,UC3,configürable:ignöred
+
+[UC4:föo]
+int-öption=3
+str-öption=föo
+
+[configürable:bår]
+str-öption=bår
+list-öption=bar,list
+
+# The following rule will be ignored
+[configürable:ignöred]
+str-öption=foöbar \ No newline at end of file
diff --git a/qa/samples/user_rules/extra/extra_rules.py b/qa/samples/user_rules/extra/extra_rules.py
index 8109299..7996590 100644
--- a/qa/samples/user_rules/extra/extra_rules.py
+++ b/qa/samples/user_rules/extra/extra_rules.py
@@ -1,29 +1,72 @@
-from gitlint.rules import CommitRule, RuleViolation
-from gitlint.utils import sstr
+from gitlint.options import IntOption, ListOption, StrOption
+from gitlint.rules import CommitRule, ConfigurationRule, RuleViolation
class GitContextRule(CommitRule):
- """ Rule that tests whether we can correctly access certain gitcontext properties """
- name = "gitcontext"
+ """Rule that tests whether we can correctly access certain gitcontext properties"""
+
+ name = "gïtcontext"
id = "UC1"
def validate(self, commit):
violations = [
- RuleViolation(self.id, "GitContext.current_branch: {0}".format(commit.context.current_branch), line_nr=1),
- RuleViolation(self.id, "GitContext.commentchar: {0}".format(commit.context.commentchar), line_nr=1)
+ RuleViolation(self.id, f"GitContext.current_branch: {commit.context.current_branch}", line_nr=1),
+ RuleViolation(self.id, f"GitContext.commentchar: {commit.context.commentchar}", line_nr=1),
]
return violations
class GitCommitRule(CommitRule):
- """ Rule that tests whether we can correctly access certain commit properties """
- name = "gitcommit"
+ """Rule that tests whether we can correctly access certain commit properties"""
+
+ name = "gïtcommit"
id = "UC2"
def validate(self, commit):
violations = [
- RuleViolation(self.id, "GitCommit.branches: {0}".format(sstr(commit.branches)), line_nr=1),
+ RuleViolation(self.id, f"GitCommit.branches: {commit.branches}", line_nr=1),
+ RuleViolation(self.id, f"GitCommit.custom_prop: {commit.custom_prop}", line_nr=1),
+ ]
+
+ return violations
+
+
+class GitlintConfigurationRule(ConfigurationRule):
+ """Rule that tests whether we can correctly access the config as well as modify the commit message"""
+
+ name = "cönfigrule"
+ id = "UC3"
+
+ def apply(self, config, commit):
+ # We add a line to the commit message body that pulls a value from config, this proves we can modify the body
+ # and read the config contents
+ commit.message.body.append(f"{config.target} ") # trailing whitespace deliberate to trigger violation
+
+ # We set a custom property that we access in CommitRule, to prove we can add extra properties to the commit
+ commit.custom_prop = "foöbar"
+
+ # We also ignore some extra rules, proving that we can modify the config
+ config.ignore.append("B4")
+
+
+class ConfigurableCommitRule(CommitRule):
+ """Rule that tests that we can add configuration to user-defined rules"""
+
+ name = "configürable"
+ id = "UC4"
+
+ options_spec = [
+ IntOption("int-öption", 2, "int-öption description"),
+ StrOption("str-öption", "föo", "int-öption description"),
+ ListOption("list-öption", ["foo", "bar"], "list-öption description"),
+ ]
+
+ def validate(self, _):
+ violations = [
+ RuleViolation(self.id, f"int-öption: {self.options['int-öption'].value}", line_nr=1),
+ RuleViolation(self.id, f"str-öption: {self.options['str-öption'].value}", line_nr=1),
+ RuleViolation(self.id, f"list-öption: {self.options['list-öption'].value}", line_nr=1),
]
return violations
diff --git a/qa/shell.py b/qa/shell.py
index 8ba6dc1..3ef874d 100644
--- a/qa/shell.py
+++ b/qa/shell.py
@@ -1,43 +1,69 @@
-
-# This code is mostly duplicated from the `gitlint.shell` module. We conciously duplicate this code as to not depend
+# This code is mostly duplicated from the `gitlint.shell` module. We consciously duplicate this code as to not depend
# on gitlint internals for our integration testing framework.
import subprocess
-import sys
-from qa.utils import ustr, USE_SH_LIB
+
+from qa.utils import TERMINAL_ENCODING, USE_SH_LIB
if USE_SH_LIB:
- from sh import git, echo, gitlint # pylint: disable=unused-import,no-name-in-module,import-error
+ from sh import (
+ echo,
+ git,
+ gitlint,
+ )
+
+ gitlint = gitlint.bake(_unify_ttys=True, _tty_in=True)
# import exceptions separately, this makes it a little easier to mock them out in the unit tests
- from sh import CommandNotFound, ErrorReturnCode, RunningCommand # pylint: disable=import-error
+ from sh import (
+ CommandNotFound,
+ ErrorReturnCode,
+ RunningCommand,
+ )
else:
class CommandNotFound(Exception):
- """ Exception indicating a command was not found during execution """
- pass
+ """Exception indicating a command was not found during execution"""
- class RunningCommand(object):
- pass
+ class RunningCommand:
+ ...
class ShResult(RunningCommand):
- """ Result wrapper class. We use this to more easily migrate from using https://amoffat.github.io/sh/ to using
- the builtin subprocess module. """
+ """Result wrapper class. We use this to more easily migrate from using https://amoffat.github.io/sh/ to using
+ the builtin subprocess module."""
- def __init__(self, full_cmd, stdout, stderr='', exitcode=0):
+ def __init__(self, full_cmd, stdout, stderr="", exitcode=0):
self.full_cmd = full_cmd
# TODO(jorisroovers): The 'sh' library by default will merge stdout and stderr. We mimic this behavior
# for now until we fully remove the 'sh' library.
- self.stdout = stdout + ustr(stderr)
- self.stderr = stderr
+ self._stdout = stdout + stderr
+ self._stderr = stderr
self.exit_code = exitcode
def __str__(self):
+ return self.stdout.decode(TERMINAL_ENCODING)
+
+ def __unicode__(self):
return self.stdout
+ @property
+ def stdout(self):
+ return self._stdout
+
+ @property
+ def stderr(self):
+ return self._stderr
+
+ def __getattr__(self, p):
+ # https://github.com/amoffat/sh/blob/e0ed8e244e9d973ef4e0749b2b3c2695e7b5255b/sh.py#L952=
+ _unicode_methods = set(dir(str())) # noqa
+ if p in _unicode_methods:
+ return getattr(str(self), p)
+
+ raise AttributeError
+
class ErrorReturnCode(ShResult, Exception):
- """ ShResult subclass for unexpected results (acts as an exception). """
- pass
+ """ShResult subclass for unexpected results (acts as an exception)."""
def git(*command_parts, **kwargs):
return run_command("git", *command_parts, **kwargs)
@@ -49,39 +75,41 @@ else:
return run_command("gitlint", *command_parts, **kwargs)
def run_command(command, *args, **kwargs):
- args = [command] + list(args)
- result = _exec(*args, **kwargs)
- # If we reach this point and the result has an exit_code that is larger than 0, this means that we didn't
- # get an exception (which is the default sh behavior for non-zero exit codes) and so the user is expecting
- # a non-zero exit code -> just return the entire result
- if hasattr(result, 'exit_code') and result.exit_code > 0:
- return result
- return ustr(result)
+ args = [command, *list(args)]
+ return _exec(*args, **kwargs)
def _exec(*args, **kwargs):
- if sys.version_info[0] == 2:
- no_command_error = OSError # noqa pylint: disable=undefined-variable,invalid-name
- else:
- no_command_error = FileNotFoundError # noqa pylint: disable=undefined-variable
-
- pipe = subprocess.PIPE
- popen_kwargs = {'stdout': pipe, 'stderr': pipe, 'shell': kwargs.get('_tty_out', False)}
- if '_cwd' in kwargs:
- popen_kwargs['cwd'] = kwargs['_cwd']
+ popen_kwargs = {
+ "stdout": subprocess.PIPE,
+ "stderr": subprocess.PIPE,
+ "stdin": subprocess.PIPE,
+ "shell": kwargs.get("_tty_out", False),
+ "cwd": kwargs.get("_cwd", None),
+ "env": kwargs.get("_env", None),
+ }
+
+ stdin_input = None
+ if len(args) > 1 and isinstance(args[1], ShResult):
+ stdin_input = args[1].stdout
+ # pop args[1] from the array and use it as stdin
+ args = list(args)
+ args.pop(1)
+ popen_kwargs["stdin"] = subprocess.PIPE
try:
- p = subprocess.Popen(args, **popen_kwargs)
- result = p.communicate()
- except no_command_error:
- raise CommandNotFound
+ with subprocess.Popen(args, **popen_kwargs) as p:
+ result = p.communicate(stdin_input)
+
+ except FileNotFoundError as exc:
+ raise CommandNotFound from exc
exit_code = p.returncode
- stdout = ustr(result[0])
+ stdout = result[0]
stderr = result[1] # 'sh' does not decode the stderr bytes to unicode
- full_cmd = '' if args is None else ' '.join(args)
+ full_cmd = "" if args is None else " ".join(args)
# If not _ok_code is specified, then only a 0 exit code is allowed
- ok_exit_codes = kwargs.get('_ok_code', [0])
+ ok_exit_codes = kwargs.get("_ok_code", [0])
if exit_code in ok_exit_codes:
return ShResult(full_cmd, stdout, stderr, exit_code)
diff --git a/qa/test_commits.py b/qa/test_commits.py
index f485856..11d1851 100644
--- a/qa/test_commits.py
+++ b/qa/test_commits.py
@@ -1,67 +1,145 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=too-many-function-args,unexpected-keyword-arg
import re
import arrow
-from qa.shell import echo, git, gitlint
from qa.base import BaseTestCase
-from qa.utils import sstr
+from qa.shell import echo, git, gitlint
class CommitsTests(BaseTestCase):
- """ Integration tests for the --commits argument, i.e. linting multiple commits at once or linting specific commits
- """
+ """Integration tests for the --commits argument, i.e. linting multiple commits or linting specific commits"""
def test_successful(self):
- """ Test linting multiple commits without violations """
+ """Test linting multiple commits without violations"""
git("checkout", "-b", "test-branch-commits-base", _cwd=self.tmp_git_repo)
- self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit")
+ self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit")
git("checkout", "-b", "test-branch-commits", _cwd=self.tmp_git_repo)
- self.create_simple_commit(u"Sïmple title2\n\nSimple bödy describing the commit2")
- self.create_simple_commit(u"Sïmple title3\n\nSimple bödy describing the commit3")
- output = gitlint("--commits", "test-branch-commits-base...test-branch-commits",
- _cwd=self.tmp_git_repo, _tty_in=True)
+ self.create_simple_commit("Sïmple title2\n\nSimple bödy describing the commit2")
+ self.create_simple_commit("Sïmple title3\n\nSimple bödy describing the commit3")
+ output = gitlint(
+ "--commits", "test-branch-commits-base...test-branch-commits", _cwd=self.tmp_git_repo, _tty_in=True
+ )
self.assertEqualStdout(output, "")
def test_violations(self):
- """ Test linting multiple commits with violations """
+ """Test linting multiple commits with violations"""
git("checkout", "-b", "test-branch-commits-violations-base", _cwd=self.tmp_git_repo)
- self.create_simple_commit(u"Sïmple title.\n")
+ self.create_simple_commit("Sïmple title.\n")
git("checkout", "-b", "test-branch-commits-violations", _cwd=self.tmp_git_repo)
- self.create_simple_commit(u"Sïmple title2.\n")
+ self.create_simple_commit("Sïmple title2.\n")
commit_sha1 = self.get_last_commit_hash()[:10]
- self.create_simple_commit(u"Sïmple title3.\n")
+ self.create_simple_commit("Sïmple title3.\n")
commit_sha2 = self.get_last_commit_hash()[:10]
- output = gitlint("--commits", "test-branch-commits-violations-base...test-branch-commits-violations",
- _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
+ output = gitlint(
+ "--commits",
+ "test-branch-commits-violations-base...test-branch-commits-violations",
+ _cwd=self.tmp_git_repo,
+ _tty_in=True,
+ _ok_code=[4],
+ )
self.assertEqual(output.exit_code, 4)
- expected_kwargs = {'commit_sha1': commit_sha1, 'commit_sha2': commit_sha2}
+ expected_kwargs = {"commit_sha1": commit_sha1, "commit_sha2": commit_sha2}
self.assertEqualStdout(output, self.get_expected("test_commits/test_violations_1", expected_kwargs))
+ def test_csv_hash_list(self):
+ """Test linting multiple commits (comma-separated) with violations"""
+ git("checkout", "-b", "test-branch-commits-violations-base", _cwd=self.tmp_git_repo)
+ self.create_simple_commit("Sïmple title1.\n")
+ commit_sha1 = self.get_last_commit_hash()[:10]
+ git("checkout", "-b", "test-branch-commits-violations", _cwd=self.tmp_git_repo)
+
+ self.create_simple_commit("Sïmple title2.\n")
+ commit_sha2 = self.get_last_commit_hash()[:10]
+ self.create_simple_commit("Sïmple title3.\n")
+ self.create_simple_commit("Sïmple title4.\n")
+ commit_sha4 = self.get_last_commit_hash()[:10]
+
+ # Lint subset of the commits in a specific order, passed in via csv list
+ output = gitlint(
+ "--commits",
+ f"{commit_sha2},{commit_sha1},{commit_sha4}",
+ _cwd=self.tmp_git_repo,
+ _tty_in=True,
+ _ok_code=[6],
+ )
+
+ self.assertEqual(output.exit_code, 6)
+ expected_kwargs = {"commit_sha1": commit_sha1, "commit_sha2": commit_sha2, "commit_sha4": commit_sha4}
+ self.assertEqualStdout(output, self.get_expected("test_commits/test_csv_hash_list_1", expected_kwargs))
+
+ def test_lint_empty_commit_range(self):
+ """Tests `gitlint --commits <sha>^...<sha>` --fail-without-commits where the provided range is empty."""
+ self.create_simple_commit("Sïmple title.\n")
+ self.create_simple_commit("Sïmple title2.\n")
+ commit_sha = self.get_last_commit_hash()
+ # git revspec -> 2 dots: <exclusive sha>..<inclusive sha> -> empty range when using same start and end sha
+ refspec = f"{commit_sha}..{commit_sha}"
+
+ # Regular gitlint invocation should run without issues
+ output = gitlint("--commits", refspec, _cwd=self.tmp_git_repo, _tty_in=True)
+ self.assertEqual(output.exit_code, 0)
+ self.assertEqualStdout(output, "")
+
+ # Gitlint should fail when --fail-without-commits is used
+ output = gitlint(
+ "--commits",
+ refspec,
+ "--fail-without-commits",
+ _cwd=self.tmp_git_repo,
+ _tty_in=True,
+ _ok_code=[self.GITLINT_USAGE_ERROR],
+ )
+ self.assertEqual(output.exit_code, self.GITLINT_USAGE_ERROR)
+ self.assertEqualStdout(output, f'Error: No commits in range "{refspec}"\n')
+
def test_lint_single_commit(self):
- """ Tests `gitlint --commits <sha>` """
- self.create_simple_commit(u"Sïmple title.\n")
- self.create_simple_commit(u"Sïmple title2.\n")
+ """Tests `gitlint --commits <sha>^...<same sha>`"""
+ self.create_simple_commit("Sïmple title.\n")
+ first_commit_sha = self.get_last_commit_hash()
+ self.create_simple_commit("Sïmple title2.\n")
commit_sha = self.get_last_commit_hash()
- refspec = "{0}^...{0}".format(commit_sha)
- self.create_simple_commit(u"Sïmple title3.\n")
+ refspec = f"{commit_sha}^...{commit_sha}"
+ self.create_simple_commit("Sïmple title3.\n")
+
+ expected = '1: T3 Title has trailing punctuation (.): "Sïmple title2."\n' + "3: B6 Body message is missing\n"
+
+ # Lint using --commit <commit sha>
+ output = gitlint("--commit", commit_sha, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
+ self.assertEqual(output.exit_code, 2)
+ self.assertEqualStdout(output, expected)
+
+ # Lint using --commits <commit sha>,
+ output = gitlint("--commits", f"{commit_sha},", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
+ self.assertEqual(output.exit_code, 2)
+ self.assertEqualStdout(output, expected)
+
+ # Lint a single commit using --commits <refspec> pointing to the single commit
output = gitlint("--commits", refspec, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
- expected = (u"1: T3 Title has trailing punctuation (.): \"Sïmple title2.\"\n" +
- u"3: B6 Body message is missing\n")
self.assertEqual(output.exit_code, 2)
self.assertEqualStdout(output, expected)
+ # Lint the first commit in the repository. This is a use-case that is not supported by --commits
+ # As <sha>^...<sha> is not correct refspec in case <sha> points to the initial commit (which has no parents)
+ expected = '1: T3 Title has trailing punctuation (.): "Sïmple title."\n' + "3: B6 Body message is missing\n"
+ output = gitlint("--commit", first_commit_sha, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
+ self.assertEqual(output.exit_code, 2)
+ self.assertEqualStdout(output, expected)
+
+ # Assert that indeed --commits <refspec> is not supported when <refspec> points the the first commit
+ refspec = f"{first_commit_sha}^...{first_commit_sha}"
+ output = gitlint("--commits", refspec, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[254])
+ self.assertEqual(output.exit_code, 254)
+
def test_lint_staged_stdin(self):
- """ Tests linting a staged commit. Gitint should lint the passed commit message andfetch additional meta-data
- from the underlying repository. The easiest way to test this is by inspecting `--debug` output.
- This is the equivalent of doing:
- echo "WIP: Pïpe test." | gitlint --staged --debug
+ """Tests linting a staged commit. Gitint should lint the passed commit message and fetch additional meta-data
+ from the underlying repository. The easiest way to test this is by inspecting `--debug` output.
+ This is the equivalent of doing:
+ echo "WIP: Pïpe test." | gitlint --staged --debug
"""
# Create a commit first, before we stage changes. This ensures the repo is properly initialized.
- self.create_simple_commit(u"Sïmple title.\n")
+ self.create_simple_commit("Sïmple title.\n")
# Add some files, stage them: they should show up in the debug output as changed file
filename1 = self.create_file(self.tmp_git_repo)
@@ -69,33 +147,48 @@ class CommitsTests(BaseTestCase):
filename2 = self.create_file(self.tmp_git_repo)
git("add", filename2, _cwd=self.tmp_git_repo)
- output = gitlint(echo(u"WIP: Pïpe test."), "--staged", "--debug",
- _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
+ output = gitlint(
+ echo("WIP: Pïpe test."),
+ "--staged",
+ "--debug",
+ _cwd=self.tmp_git_repo,
+ _tty_in=False,
+ _err_to_out=True,
+ _ok_code=[3],
+ )
# Determine variable parts of expected output
expected_kwargs = self.get_debug_vars_last_commit()
- expected_kwargs.update({'changed_files': sstr(sorted([filename1, filename2]))})
+ filenames = sorted([filename1, filename2])
+ expected_kwargs.update(
+ {
+ "changed_files": filenames,
+ "changed_files_stats": (
+ f"{filenames[0]}: 0 additions, 0 deletions\n {filenames[1]}: 0 additions, 0 deletions"
+ ),
+ }
+ )
# It's not really possible to determine the "Date: ..." line that is part of the debug output as this date
# is not taken from git but instead generated by gitlint itself. As a workaround, we extract the date from the
# gitlint output using a regex, parse the date to ensure the format is correct, and then pass that as an
# expected variable.
- matches = re.search(r'^Date:\s+(.*)', str(output), re.MULTILINE)
+ matches = re.search(r"^Date:\s+(.*)", str(output), re.MULTILINE)
if matches:
expected_date = arrow.get(str(matches.group(1)), "YYYY-MM-DD HH:mm:ss Z").format("YYYY-MM-DD HH:mm:ss Z")
- expected_kwargs['staged_date'] = expected_date
+ expected_kwargs["staged_date"] = expected_date
self.assertEqualStdout(output, self.get_expected("test_commits/test_lint_staged_stdin_1", expected_kwargs))
self.assertEqual(output.exit_code, 3)
def test_lint_staged_msg_filename(self):
- """ Tests linting a staged commit. Gitint should lint the passed commit message andfetch additional meta-data
- from the underlying repository. The easiest way to test this is by inspecting `--debug` output.
- This is the equivalent of doing:
- gitlint --msg-filename /tmp/my-commit-msg --staged --debug
+ """Tests linting a staged commit. Gitint should lint the passed commit message andfetch additional meta-data
+ from the underlying repository. The easiest way to test this is by inspecting `--debug` output.
+ This is the equivalent of doing:
+ gitlint --msg-filename /tmp/my-commit-msg --staged --debug
"""
# Create a commit first, before we stage changes. This ensures the repo is properly initialized.
- self.create_simple_commit(u"Sïmple title.\n")
+ self.create_simple_commit("Sïmple title.\n")
# Add some files, stage them: they should show up in the debug output as changed file
filename1 = self.create_file(self.tmp_git_repo)
@@ -103,59 +196,81 @@ class CommitsTests(BaseTestCase):
filename2 = self.create_file(self.tmp_git_repo)
git("add", filename2, _cwd=self.tmp_git_repo)
- tmp_commit_msg_file = self.create_tmpfile(u"WIP: from fïle test.")
+ tmp_commit_msg_file = self.create_tmpfile("WIP: from fïle test.")
- output = gitlint("--msg-filename", tmp_commit_msg_file, "--staged", "--debug",
- _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
+ output = gitlint(
+ "--msg-filename",
+ tmp_commit_msg_file,
+ "--staged",
+ "--debug",
+ _cwd=self.tmp_git_repo,
+ _tty_in=False,
+ _err_to_out=True,
+ _ok_code=[3],
+ )
# Determine variable parts of expected output
expected_kwargs = self.get_debug_vars_last_commit()
- expected_kwargs.update({'changed_files': sstr(sorted([filename1, filename2]))})
+ filenames = sorted([filename1, filename2])
+ expected_kwargs.update(
+ {
+ "changed_files": filenames,
+ "changed_files_stats": (
+ f"{filenames[0]}: 0 additions, 0 deletions\n {filenames[1]}: 0 additions, 0 deletions"
+ ),
+ }
+ )
# It's not really possible to determine the "Date: ..." line that is part of the debug output as this date
# is not taken from git but instead generated by gitlint itself. As a workaround, we extract the date from the
# gitlint output using a regex, parse the date to ensure the format is correct, and then pass that as an
# expected variable.
- matches = re.search(r'^Date:\s+(.*)', str(output), re.MULTILINE)
+ matches = re.search(r"^Date:\s+(.*)", str(output), re.MULTILINE)
if matches:
expected_date = arrow.get(str(matches.group(1)), "YYYY-MM-DD HH:mm:ss Z").format("YYYY-MM-DD HH:mm:ss Z")
- expected_kwargs['staged_date'] = expected_date
+ expected_kwargs["staged_date"] = expected_date
expected = self.get_expected("test_commits/test_lint_staged_msg_filename_1", expected_kwargs)
self.assertEqualStdout(output, expected)
self.assertEqual(output.exit_code, 3)
def test_lint_head(self):
- """ Testing whether we can also recognize special refs like 'HEAD' """
+ """Testing whether we can also recognize special refs like 'HEAD'"""
tmp_git_repo = self.create_tmp_git_repo()
- self.create_simple_commit(u"Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
- self.create_simple_commit(u"Sïmple title", git_repo=tmp_git_repo)
- self.create_simple_commit(u"WIP: Sïmple title\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
+ self.create_simple_commit("Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
+ self.create_simple_commit("Sïmple title", git_repo=tmp_git_repo)
+ self.create_simple_commit("WIP: Sïmple title\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
output = gitlint("--commits", "HEAD", _cwd=tmp_git_repo, _tty_in=True, _ok_code=[3])
revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split()
- expected_kwargs = {"commit_sha0": revlist[0][:10], "commit_sha1": revlist[1][:10],
- "commit_sha2": revlist[2][:10]}
+ expected_kwargs = {
+ "commit_sha0": revlist[0][:10],
+ "commit_sha1": revlist[1][:10],
+ "commit_sha2": revlist[2][:10],
+ }
self.assertEqualStdout(output, self.get_expected("test_commits/test_lint_head_1", expected_kwargs))
def test_ignore_commits(self):
- """ Tests multiple commits of which some rules get igonored because of ignore-* rules """
+ """Tests multiple commits of which some rules get ignored because of ignore-* rules"""
# Create repo and some commits
tmp_git_repo = self.create_tmp_git_repo()
- self.create_simple_commit(u"Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
+ self.create_simple_commit("Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo)
# Normally, this commit will give T3 (trailing-punctuation), T5 (WIP) and B5 (bod-too-short) violations
# But in this case only B5 because T3 and T5 are being ignored because of config
- self.create_simple_commit(u"Release: WIP tïtle.\n\nShort", git_repo=tmp_git_repo)
+ self.create_simple_commit("Release: WIP tïtle.\n\nShort", git_repo=tmp_git_repo)
# In the following 2 commits, the T3 violations are as normal
- self.create_simple_commit(
- u"Sïmple WIP title3.\n\nThis is \ta relëase commit\nMore info", git_repo=tmp_git_repo)
- self.create_simple_commit(u"Sïmple title4.\n\nSimple bödy describing the commit4", git_repo=tmp_git_repo)
+ self.create_simple_commit("Sïmple WIP title3.\n\nThis is \ta relëase commit\nMore info", git_repo=tmp_git_repo)
+ self.create_simple_commit("Sïmple title4.\n\nSimple bödy describing the commit4", git_repo=tmp_git_repo)
revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split()
config_path = self.get_sample_path("config/ignore-release-commits")
output = gitlint("--commits", "HEAD", "--config", config_path, _cwd=tmp_git_repo, _tty_in=True, _ok_code=[4])
- expected_kwargs = {"commit_sha0": revlist[0][:10], "commit_sha1": revlist[1][:10],
- "commit_sha2": revlist[2][:10], "commit_sha3": revlist[3][:10]}
+ expected_kwargs = {
+ "commit_sha0": revlist[0][:10],
+ "commit_sha1": revlist[1][:10],
+ "commit_sha2": revlist[2][:10],
+ "commit_sha3": revlist[3][:10],
+ }
self.assertEqualStdout(output, self.get_expected("test_commits/test_ignore_commits_1", expected_kwargs))
diff --git a/qa/test_config.py b/qa/test_config.py
index b893b1d..d051686 100644
--- a/qa/test_config.py
+++ b/qa/test_config.py
@@ -1,31 +1,36 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=too-many-function-args,unexpected-keyword-arg
-from qa.shell import gitlint
+import os
+import re
+
from qa.base import BaseTestCase
-from qa.utils import sstr
+from qa.shell import gitlint
class ConfigTests(BaseTestCase):
- """ Integration tests for gitlint configuration and configuration precedence. """
+ """Integration tests for gitlint configuration and configuration precedence."""
def test_ignore_by_id(self):
- self.create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line")
+ self.create_simple_commit("WIP: Thïs is a title.\nContënt on the second line")
output = gitlint("--ignore", "T5,B4", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[1])
- expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n"
+ expected = '1: T3 Title has trailing punctuation (.): "WIP: Thïs is a title."\n'
self.assertEqualStdout(output, expected)
def test_ignore_by_name(self):
- self.create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line")
- output = gitlint("--ignore", "title-must-not-contain-word,body-first-line-empty",
- _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
- expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n"
+ self.create_simple_commit("WIP: Thïs is a title.\nContënt on the second line")
+ output = gitlint(
+ "--ignore",
+ "title-must-not-contain-word,body-first-line-empty",
+ _cwd=self.tmp_git_repo,
+ _tty_in=True,
+ _ok_code=[1],
+ )
+ expected = '1: T3 Title has trailing punctuation (.): "WIP: Thïs is a title."\n'
self.assertEqualStdout(output, expected)
def test_verbosity(self):
- self.create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line")
+ self.create_simple_commit("WIP: Thïs is a title.\nContënt on the second line")
output = gitlint("-v", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
- expected = u"1: T3\n1: T5\n2: B4\n"
+ expected = "1: T3\n1: T5\n2: B4\n"
self.assertEqualStdout(output, expected)
output = gitlint("-vv", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
@@ -39,29 +44,90 @@ class ConfigTests(BaseTestCase):
self.assertEqualStdout(output, "")
def test_set_rule_option(self):
- self.create_simple_commit(u"This ïs a title.")
+ self.create_simple_commit("This ïs a title.")
output = gitlint("-c", "title-max-length.line-length=5", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[3])
self.assertEqualStdout(output, self.get_expected("test_config/test_set_rule_option_1"))
def test_config_from_file(self):
- commit_msg = u"WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \
- "This line of the body is here because we need it"
+ commit_msg = (
+ "WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n"
+ "This line of the body is here because we need it"
+ )
self.create_simple_commit(commit_msg)
config_path = self.get_sample_path("config/gitlintconfig")
output = gitlint("--config", config_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_file_1"))
def test_config_from_file_debug(self):
- # Test bot on existing and new repo (we've had a bug in the past that was unique to empty repos)
+ # Test both on existing and new repo (we've had a bug in the past that was unique to empty repos)
repos = [self.tmp_git_repo, self.create_tmp_git_repo()]
for target_repo in repos:
- commit_msg = u"WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \
- "This line of the body is here because we need it"
+ commit_msg = (
+ "WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n"
+ "This line of the body is here because we need it"
+ )
filename = self.create_simple_commit(commit_msg, git_repo=target_repo)
- config_path = self.get_sample_path("config/gitlintconfig")
+ config_path = self.get_sample_path(os.path.join("config", "gitlintconfig"))
output = gitlint("--config", config_path, "--debug", _cwd=target_repo, _tty_in=True, _ok_code=[5])
expected_kwargs = self.get_debug_vars_last_commit(git_repo=target_repo)
- expected_kwargs.update({'config_path': config_path, 'changed_files': sstr([filename])})
- self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_file_debug_1",
- expected_kwargs))
+ expected_kwargs.update(
+ {
+ "config_path": config_path,
+ "changed_files": [filename],
+ "changed_files_stats": f"{filename}: 0 additions, 0 deletions",
+ }
+ )
+ self.assertEqualStdout(
+ output, self.get_expected("test_config/test_config_from_file_debug_1", expected_kwargs)
+ )
+
+ def test_config_from_env(self):
+ """Test for configuring gitlint from environment variables"""
+
+ # We invoke gitlint, configuring it via env variables, we can check whether gitlint picks these up correctly
+ # by comparing the debug output with what we'd expect
+ target_repo = self.create_tmp_git_repo()
+ commit_msg = (
+ "WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n"
+ "This line of the body is here because we need it"
+ )
+ filename = self.create_simple_commit(commit_msg, git_repo=target_repo)
+ env = self.create_environment(
+ {
+ "GITLINT_DEBUG": "1",
+ "GITLINT_VERBOSITY": "2",
+ "GITLINT_IGNORE": "T1,T2",
+ "GITLINT_CONTRIB": "CC1,CT1",
+ "GITLINT_FAIL_WITHOUT_COMMITS": "1",
+ "GITLINT_IGNORE_STDIN": "1",
+ "GITLINT_TARGET": target_repo,
+ "GITLINT_COMMITS": self.get_last_commit_hash(git_repo=target_repo),
+ }
+ )
+ output = gitlint(_env=env, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
+ expected_kwargs = self.get_debug_vars_last_commit(git_repo=target_repo)
+ expected_kwargs.update(
+ {"changed_files": [filename], "changed_files_stats": f"{filename}: 0 additions, 0 deletions"}
+ )
+
+ self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_env_1", expected_kwargs))
+
+ # For some env variables, we need a separate test ast they are mutually exclusive with the ones tested above
+ tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename test.")
+ env = self.create_environment(
+ {"GITLINT_DEBUG": "1", "GITLINT_TARGET": target_repo, "GITLINT_SILENT": "1", "GITLINT_STAGED": "1"}
+ )
+
+ output = gitlint(
+ "--msg-filename", tmp_commit_msg_file, _env=env, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3]
+ )
+
+ # Extract date from actual output to insert it into the expected output
+ # We have to do this since there's no way for us to deterministically know that date otherwise
+ p = re.compile("Date: (.*)\n", re.UNICODE | re.MULTILINE)
+ result = p.search(str(output))
+ date = result.group(1).strip()
+ expected_kwargs.update({"date": date})
+
+ self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_env_2", expected_kwargs))
diff --git a/qa/test_contrib.py b/qa/test_contrib.py
index e2b4bc5..d3a45ba 100644
--- a/qa/test_contrib.py
+++ b/qa/test_contrib.py
@@ -1,26 +1,31 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=
-from qa.shell import gitlint
from qa.base import BaseTestCase
+from qa.shell import gitlint
class ContribRuleTests(BaseTestCase):
- """ Integration tests for contrib rules."""
+ """Integration tests for contrib rules."""
def test_contrib_rules(self):
- self.create_simple_commit(u"WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars")
- output = gitlint("--contrib", "contrib-title-conventional-commits,CC1",
- _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
+ self.create_simple_commit("WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars")
+ output = gitlint(
+ "--contrib", "contrib-title-conventional-commits,CC1", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3]
+ )
self.assertEqualStdout(output, self.get_expected("test_contrib/test_contrib_rules_1"))
def test_contrib_rules_with_config(self):
- self.create_simple_commit(u"WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars")
- output = gitlint("--contrib", "contrib-title-conventional-commits,CC1",
- "-c", u"contrib-title-conventional-commits.types=föo,bår",
- _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4])
+ self.create_simple_commit("WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars")
+ output = gitlint(
+ "--contrib",
+ "contrib-title-conventional-commits,CC1",
+ "-c",
+ "contrib-title-conventional-commits.types=föo,bår",
+ _cwd=self.tmp_git_repo,
+ _tty_in=True,
+ _ok_code=[3],
+ )
self.assertEqualStdout(output, self.get_expected("test_contrib/test_contrib_rules_with_config_1"))
def test_invalid_contrib_rules(self):
self.create_simple_commit("WIP: test")
- output = gitlint("--contrib", u"föobar,CC1", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[255])
- self.assertEqualStdout(output, u"Config Error: No contrib rule with id or name 'föobar' found.\n")
+ output = gitlint("--contrib", "föobar,CC1", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[255])
+ self.assertEqualStdout(output, "Config Error: No contrib rule with id or name 'föobar' found.\n")
diff --git a/qa/test_gitlint.py b/qa/test_gitlint.py
index 4762721..7a04a39 100644
--- a/qa/test_gitlint.py
+++ b/qa/test_gitlint.py
@@ -1,47 +1,45 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=too-many-function-args,unexpected-keyword-arg
-import io
import os
-from qa.shell import echo, git, gitlint
+
from qa.base import BaseTestCase
-from qa.utils import DEFAULT_ENCODING
+from qa.shell import echo, git, gitlint
+from qa.utils import FILE_ENCODING
class IntegrationTests(BaseTestCase):
- """ Simple set of integration tests for gitlint """
+ """Simple set of integration tests for gitlint"""
def test_successful(self):
# Test for STDIN with and without a TTY attached
- self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit")
+ self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit")
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True)
self.assertEqualStdout(output, "")
def test_successful_gitconfig(self):
- """ Test gitlint when the underlying repo has specific git config set.
- In the past, we've had issues with gitlint failing on some of these, so this acts as a regression test. """
+ """Test gitlint when the underlying repo has specific git config set.
+ In the past, we've had issues with gitlint failing on some of these, so this acts as a regression test."""
# Different commentchar (Note: tried setting this to a special unicode char, but git doesn't like that)
git("config", "--add", "core.commentchar", "$", _cwd=self.tmp_git_repo)
- self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit\n$after commentchar\t ignored")
+ self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit\n$after commentchar\t ignored")
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True)
self.assertEqualStdout(output, "")
def test_successful_merge_commit(self):
- # Create branch on master
- self.create_simple_commit(u"Cömmit on master\n\nSimple bödy")
+ # Create branch on main
+ self.create_simple_commit("Cömmit on main\n\nSimple bödy")
# Create test branch, add a commit and determine the commit hash
git("checkout", "-b", "test-branch", _cwd=self.tmp_git_repo)
git("checkout", "test-branch", _cwd=self.tmp_git_repo)
- commit_title = u"Commit on test-brånch with a pretty long title that will cause issues when merging"
- self.create_simple_commit(u"{0}\n\nSïmple body".format(commit_title))
+ commit_title = "Commit on test-brånch with a pretty long title that will cause issues when merging"
+ self.create_simple_commit(f"{commit_title}\n\nSïmple body")
hash = self.get_last_commit_hash()
- # Checkout master and merge the commit
+ # Checkout main and merge the commit
# We explicitly set the title of the merge commit to the title of the previous commit as this or similar
# behavior is what many tools do that handle merges (like github, gerrit, etc).
- git("checkout", "master", _cwd=self.tmp_git_repo)
- git("merge", "--no-ff", "-m", u"Merge '{0}'".format(commit_title), hash, _cwd=self.tmp_git_repo)
+ git("checkout", "main", _cwd=self.tmp_git_repo)
+ git("merge", "--no-ff", "-m", f"Merge '{commit_title}'", hash, _cwd=self.tmp_git_repo)
# Run gitlint and assert output is empty
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
@@ -50,23 +48,18 @@ class IntegrationTests(BaseTestCase):
# Assert that we do see the error if we disable the ignore-merge-commits option
output = gitlint("-c", "general.ignore-merge-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
self.assertEqual(output.exit_code, 1)
- self.assertEqualStdout(output,
- u"1: T1 Title exceeds max length (90>72): \"Merge '{0}'\"\n".format(commit_title))
+ self.assertEqualStdout(output, f"1: T1 Title exceeds max length (90>72): \"Merge '{commit_title}'\"\n")
def test_fixup_commit(self):
# Create a normal commit and assert that it has a violation
- test_filename = self.create_simple_commit(u"Cömmit on WIP master\n\nSimple bödy that is long enough")
+ test_filename = self.create_simple_commit("Cömmit on WIP main\n\nSimple bödy that is long enough")
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
- expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n"
+ expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP main\"\n"
self.assertEqualStdout(output, expected)
# Make a small modification to the commit and commit it using fixup commit
- with io.open(os.path.join(self.tmp_git_repo, test_filename), "a", encoding=DEFAULT_ENCODING) as fh:
- # Wanted to write a unicode string, but that's obnoxious if you want to do it across Python 2 and 3.
- # https://stackoverflow.com/questions/22392377/
- # error-writing-a-file-with-file-write-in-python-unicodeencodeerror
- # So just keeping it simple - ASCII will here
- fh.write(u"Appending some stuff\n")
+ with open(os.path.join(self.tmp_git_repo, test_filename), "a", encoding=FILE_ENCODING) as fh:
+ fh.write("Appending söme stuff\n")
git("add", test_filename, _cwd=self.tmp_git_repo)
@@ -79,13 +72,44 @@ class IntegrationTests(BaseTestCase):
# Make sure that if we set the ignore-fixup-commits option to false that we do still see the violations
output = gitlint("-c", "general.ignore-fixup-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
- expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"fixup! Cömmit on WIP master\"\n" + \
- u"3: B6 Body message is missing\n"
+ expected = (
+ "1: T5 Title contains the word 'WIP' (case-insensitive): \"fixup! Cömmit on WIP main\"\n"
+ "3: B6 Body message is missing\n"
+ )
+
+ self.assertEqualStdout(output, expected)
+
+ def test_fixup_amend_commit(self):
+ # Create a normal commit and assert that it has a violation
+ test_filename = self.create_simple_commit("Cömmit on WIP main\n\nSimple bödy that is long enough")
+ output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
+ expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP main\"\n"
+ self.assertEqualStdout(output, expected)
+
+ # Make a small modification to the commit and commit it using fixup=amend commit
+ with open(os.path.join(self.tmp_git_repo, test_filename), "a", encoding=FILE_ENCODING) as fh:
+ fh.write("Appending söme stuff\n")
+
+ git("add", test_filename, _cwd=self.tmp_git_repo)
+
+ # We have to use --no-edit to avoid git starting $EDITOR to modify the commit message that is being amended
+ git("commit", "--no-edit", f"--fixup=amend:{self.get_last_commit_hash()}", _cwd=self.tmp_git_repo)
+
+ # Assert that gitlint does not show an error for the fixup commit
+ output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
+ # No need to check exit code, the command above throws an exception on > 0 exit codes
+ self.assertEqualStdout(output, "")
+
+ # Make sure that if we set the ignore-fixup-commits option to false that we do still see the violations
+ output = gitlint(
+ "-c", "general.ignore-fixup-amend-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]
+ )
+ expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"amend! Cömmit on WIP main\"\n"
self.assertEqualStdout(output, expected)
def test_revert_commit(self):
- self.create_simple_commit(u"WIP: Cömmit on master.\n\nSimple bödy")
+ self.create_simple_commit("WIP: Cömmit on main.\n\nSimple bödy")
hash = self.get_last_commit_hash()
git("revert", hash, _cwd=self.tmp_git_repo)
@@ -94,30 +118,31 @@ class IntegrationTests(BaseTestCase):
self.assertEqualStdout(output, "")
# Assert that we do see the error if we disable the ignore-revert-commits option
- output = gitlint("-c", "general.ignore-revert-commits=false",
- _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
+ output = gitlint(
+ "-c", "general.ignore-revert-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]
+ )
self.assertEqual(output.exit_code, 1)
- expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Revert \"WIP: Cömmit on master.\"\"\n"
+ expected = '1: T5 Title contains the word \'WIP\' (case-insensitive): "Revert "WIP: Cömmit on main.""\n'
self.assertEqualStdout(output, expected)
def test_squash_commit(self):
# Create a normal commit and assert that it has a violation
- test_filename = self.create_simple_commit(u"Cömmit on WIP master\n\nSimple bödy that is long enough")
+ test_filename = self.create_simple_commit("Cömmit on WIP main\n\nSimple bödy that is long enough")
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1])
- expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n"
+ expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP main\"\n"
self.assertEqualStdout(output, expected)
# Make a small modification to the commit and commit it using squash commit
- with io.open(os.path.join(self.tmp_git_repo, test_filename), "a", encoding=DEFAULT_ENCODING) as fh:
+ with open(os.path.join(self.tmp_git_repo, test_filename), "a", encoding=FILE_ENCODING) as fh:
# Wanted to write a unicode string, but that's obnoxious if you want to do it across Python 2 and 3.
# https://stackoverflow.com/questions/22392377/
# error-writing-a-file-with-file-write-in-python-unicodeencodeerror
# So just keeping it simple - ASCII will here
- fh.write(u"Appending some stuff\n")
+ fh.write("Appending some stuff\n")
git("add", test_filename, _cwd=self.tmp_git_repo)
- git("commit", "--squash", self.get_last_commit_hash(), "-m", u"Töo short body", _cwd=self.tmp_git_repo)
+ git("commit", "--squash", self.get_last_commit_hash(), "-m", "Töo short body", _cwd=self.tmp_git_repo)
# Assert that gitlint does not show an error for the fixup commit
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True)
@@ -125,47 +150,118 @@ class IntegrationTests(BaseTestCase):
self.assertEqualStdout(output, "")
# Make sure that if we set the ignore-squash-commits option to false that we do still see the violations
- output = gitlint("-c", "general.ignore-squash-commits=false",
- _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
- expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"squash! Cömmit on WIP master\"\n" + \
- u"3: B5 Body message is too short (14<20): \"Töo short body\"\n"
+ output = gitlint(
+ "-c", "general.ignore-squash-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2]
+ )
+ expected = (
+ "1: T5 Title contains the word 'WIP' (case-insensitive): \"squash! Cömmit on WIP main\"\n"
+ '3: B5 Body message is too short (14<20): "Töo short body"\n'
+ )
self.assertEqualStdout(output, expected)
def test_violations(self):
- commit_msg = u"WIP: This ïs a title.\nContent on the sëcond line"
+ commit_msg = "WIP: This ïs a title.\nContent on the sëcond line"
self.create_simple_commit(commit_msg)
output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
self.assertEqualStdout(output, self.get_expected("test_gitlint/test_violations_1"))
def test_msg_filename(self):
- tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename test.")
- output = gitlint("--msg-filename", tmp_commit_msg_file, _tty_in=True, _ok_code=[3])
+ tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename test.")
+ output = gitlint("--msg-filename", tmp_commit_msg_file, _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[3])
self.assertEqualStdout(output, self.get_expected("test_gitlint/test_msg_filename_1"))
def test_msg_filename_no_tty(self):
- """ Make sure --msg-filename option also works with no TTY attached """
- tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename NO TTY test.")
+ """Make sure --msg-filename option also works with no TTY attached"""
+ tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO TTY test.")
# We need to set _err_to_out explicitly for sh to merge stdout and stderr output in case there's
# no TTY attached to STDIN
# http://amoffat.github.io/sh/sections/special_arguments.html?highlight=_tty_in#err-to-out
# We need to pass some whitespace to _in as sh will otherwise hang, see
# https://github.com/amoffat/sh/issues/427
- output = gitlint("--msg-filename", tmp_commit_msg_file, _in=" ",
- _tty_in=False, _err_to_out=True, _ok_code=[3])
+ output = gitlint(
+ "--msg-filename",
+ tmp_commit_msg_file,
+ _cwd=self.tmp_git_repo,
+ _in=" ",
+ _tty_in=False,
+ _err_to_out=True,
+ _ok_code=[3],
+ )
self.assertEqualStdout(output, self.get_expected("test_gitlint/test_msg_filename_no_tty_1"))
- def test_git_errors(self):
+ def test_no_git_name_set(self):
+ """Ensure we print out a helpful message if user.name is not set"""
+ tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO name test.")
+ # Name is checked before email so this isn't strictly
+ # necessary but seems good for consistency.
+ env = self.create_tmp_git_config("[user]\n email = test-emåil@foo.com\n")
+ output = gitlint(
+ "--staged",
+ "--msg-filename",
+ tmp_commit_msg_file,
+ _ok_code=[self.GIT_CONTEXT_ERROR_CODE],
+ _env=env,
+ _cwd=self.tmp_git_repo,
+ )
+ expected = "Missing git configuration: please set user.name\n"
+ self.assertEqualStdout(output, expected)
+
+ def test_no_git_email_set(self):
+ """Ensure we print out a helpful message if user.email is not set"""
+ tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO email test.")
+ env = self.create_tmp_git_config("[user]\n name = test åuthor\n")
+ output = gitlint(
+ "--staged",
+ "--msg-filename",
+ tmp_commit_msg_file,
+ _ok_code=[self.GIT_CONTEXT_ERROR_CODE],
+ _env=env,
+ _cwd=self.tmp_git_repo,
+ )
+ expected = "Missing git configuration: please set user.email\n"
+ self.assertEqualStdout(output, expected)
+
+ def test_git_empty_repo(self):
# Repo has no commits: caused by `git log`
empty_git_repo = self.create_tmp_git_repo()
output = gitlint(_cwd=empty_git_repo, _tty_in=True, _ok_code=[self.GIT_CONTEXT_ERROR_CODE])
- expected = u"Current branch has no commits. Gitlint requires at least one commit to function.\n"
+ expected = "Current branch has no commits. Gitlint requires at least one commit to function.\n"
+ self.assertEqualStdout(output, expected)
+
+ def test_git_empty_repo_staged(self):
+ """When repo is empty, we can still use gitlint when using --staged flag and piping a message into it"""
+ empty_git_repo = self.create_tmp_git_repo()
+ expected = (
+ '1: T3 Title has trailing punctuation (.): "WIP: Pïpe test."\n'
+ "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: Pïpe test.\"\n"
+ "3: B6 Body message is missing\n"
+ )
+
+ output = gitlint(
+ echo("WIP: Pïpe test."), "--staged", _cwd=empty_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3]
+ )
self.assertEqualStdout(output, expected)
- # Repo has no commits: caused by `git rev-parse`
- output = gitlint(echo(u"WIP: Pïpe test."), "--staged", _cwd=empty_git_repo, _tty_in=False,
- _err_to_out=True, _ok_code=[self.GIT_CONTEXT_ERROR_CODE])
+ def test_commit_binary_file(self):
+ """When committing a binary file, git shows somewhat different output in diff commands,
+ this test ensures gitlint deals with that correctly"""
+ binary_filename = self.create_simple_commit("Sïmple commit", file_contents=bytes([0x48, 0x00, 0x49, 0x00]))
+ output = gitlint(
+ "--debug",
+ _ok_code=[1],
+ _cwd=self.tmp_git_repo,
+ )
+
+ expected_kwargs = self.get_debug_vars_last_commit()
+ expected_kwargs.update(
+ {
+ "changed_files": [binary_filename],
+ "changed_files_stats": (f"{binary_filename}: None additions, None deletions"),
+ }
+ )
+ expected = self.get_expected("test_gitlint/test_commit_binary_file_1", expected_kwargs)
self.assertEqualStdout(output, expected)
diff --git a/qa/test_hooks.py b/qa/test_hooks.py
index a41580b..99e76dd 100644
--- a/qa/test_hooks.py
+++ b/qa/test_hooks.py
@@ -1,22 +1,24 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=too-many-function-args,unexpected-keyword-arg
import os
-from qa.shell import git, gitlint
+
from qa.base import BaseTestCase
+from qa.shell import git, gitlint
class HookTests(BaseTestCase):
- """ Integration tests for gitlint commitmsg hooks"""
-
- VIOLATIONS = ['gitlint: checking commit message...\n',
- u'1: T3 Title has trailing punctuation (.): "WIP: This ïs a title."\n',
- u'1: T5 Title contains the word \'WIP\' (case-insensitive): "WIP: This ïs a title."\n',
- u'2: B4 Second line is not empty: "Contënt on the second line"\n',
- '3: B6 Body message is missing\n',
- '-----------------------------------------------\n',
- 'gitlint: \x1b[31mYour commit message contains the above violations.\x1b[0m\n']
+ """Integration tests for gitlint commitmsg hooks"""
+
+ VIOLATIONS = [
+ "gitlint: checking commit message...\n",
+ '1: T3 Title has trailing punctuation (.): "WIP: This ïs a title."\n',
+ "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This ïs a title.\"\n",
+ '2: B4 Second line is not empty: "Contënt on the second line"\n',
+ "3: B6 Body message is missing\n",
+ "-----------------------------------------------\n",
+ "gitlint: \x1b[31mYour commit message contains violations.\x1b[0m\n",
+ ]
def setUp(self):
+ super().setUp()
self.responses = []
self.response_index = 0
self.githook_output = []
@@ -24,20 +26,23 @@ class HookTests(BaseTestCase):
# The '--staged' flag used in the commit-msg hook fetches additional information from the underlying
# git repo which means there already needs to be a commit in the repo
# (as gitlint --staged doesn't work against empty repos)
- self.create_simple_commit(u"Commït Title\n\nCommit Body explaining commit.")
+ self.create_simple_commit("Commït Title\n\nCommit Body explaining commit.")
# install git commit-msg hook and assert output
output_installed = gitlint("install-hook", _cwd=self.tmp_git_repo)
- expected_installed = u"Successfully installed gitlint commit-msg hook in %s/.git/hooks/commit-msg\n" % \
- self.tmp_git_repo
+ commit_msg_hook_path = os.path.join(self.tmp_git_repo, ".git", "hooks", "commit-msg")
+ expected_installed = f"Successfully installed gitlint commit-msg hook in {commit_msg_hook_path}\n"
+
self.assertEqualStdout(output_installed, expected_installed)
def tearDown(self):
# uninstall git commit-msg hook and assert output
output_uninstalled = gitlint("uninstall-hook", _cwd=self.tmp_git_repo)
- expected_uninstalled = u"Successfully uninstalled gitlint commit-msg hook from %s/.git/hooks/commit-msg\n" % \
- self.tmp_git_repo
+ commit_msg_hook_path = os.path.join(self.tmp_git_repo, ".git", "hooks", "commit-msg")
+ expected_uninstalled = f"Successfully uninstalled gitlint commit-msg hook from {commit_msg_hook_path}\n"
+
self.assertEqualStdout(output_uninstalled, expected_uninstalled)
+ super().tearDown()
def _violations(self):
# Make a copy of the violations array so that we don't inadvertently edit it in the test (like I did :D)
@@ -48,58 +53,78 @@ class HookTests(BaseTestCase):
def _interact(self, line, stdin):
self.githook_output.append(line)
# Answer 'yes' to question to keep violating commit-msg
- if "Your commit message contains the above violations" in line:
+ if "Your commit message contains violations" in line:
response = self.responses[self.response_index]
- stdin.put("{0}\n".format(response))
+ stdin.put(f"{response}\n")
self.response_index = (self.response_index + 1) % len(self.responses)
+ def test_commit_hook_no_violations(self):
+ test_filename = self.create_simple_commit(
+ "This ïs a title\n\nBody contënt that should work", out=self._interact, tty_in=True
+ )
+
+ short_hash = self.get_last_commit_short_hash()
+ expected_output = [
+ "gitlint: checking commit message...\n",
+ "gitlint: \x1b[32mOK\x1b[0m (no violations in commit message)\n",
+ f"[main {short_hash}] This ïs a title\n",
+ " 1 file changed, 0 insertions(+), 0 deletions(-)\n",
+ f" create mode 100644 {test_filename}\n",
+ ]
+ for output, expected in zip(self.githook_output, expected_output):
+ self.assertMultiLineEqual(output.replace("\r", ""), expected.replace("\r", ""))
+
def test_commit_hook_continue(self):
self.responses = ["y"]
- test_filename = self.create_simple_commit(u"WIP: This ïs a title.\nContënt on the second line",
- out=self._interact, tty_in=True)
+ test_filename = self.create_simple_commit(
+ "WIP: This ïs a title.\nContënt on the second line", out=self._interact, tty_in=True
+ )
# Determine short commit-msg hash, needed to determine expected output
short_hash = self.get_last_commit_short_hash()
expected_output = self._violations()
- expected_output += ["Continue with commit anyways (this keeps the current commit message)? " +
- "[y(es)/n(no)/e(dit)] " +
- u"[master %s] WIP: This ïs a title. Contënt on the second line\n"
- % short_hash,
- " 1 file changed, 0 insertions(+), 0 deletions(-)\n",
- u" create mode 100644 %s\n" % test_filename]
-
- assert len(self.githook_output) == len(expected_output)
+ expected_output += [
+ "Continue with commit anyways (this keeps the current commit message)? "
+ "[y(es)/n(no)/e(dit)] "
+ f"[main {short_hash}] WIP: This ïs a title. Contënt on the second line\n",
+ " 1 file changed, 0 insertions(+), 0 deletions(-)\n",
+ f" create mode 100644 {test_filename}\n",
+ ]
+
for output, expected in zip(self.githook_output, expected_output):
- self.assertMultiLineEqual(
- output.replace('\r', ''),
- expected.replace('\r', ''))
+ self.assertMultiLineEqual(output.replace("\r", ""), expected.replace("\r", ""))
def test_commit_hook_abort(self):
self.responses = ["n"]
- test_filename = self.create_simple_commit(u"WIP: This ïs a title.\nContënt on the second line",
- out=self._interact, ok_code=1, tty_in=True)
+ test_filename = self.create_simple_commit(
+ "WIP: This ïs a title.\nContënt on the second line", out=self._interact, ok_code=1, tty_in=True
+ )
git("rm", "-f", test_filename, _cwd=self.tmp_git_repo)
# Determine short commit-msg hash, needed to determine expected output
expected_output = self._violations()
- expected_output += ["Continue with commit anyways (this keeps the current commit message)? " +
- "[y(es)/n(no)/e(dit)] " +
- "Commit aborted.\n",
- "Your commit message: \n",
- "-----------------------------------------------\n",
- u"WIP: This ïs a title.\n",
- u"Contënt on the second line\n",
- "-----------------------------------------------\n"]
+ expected_output += [
+ "Continue with commit anyways (this keeps the current commit message)? "
+ "[y(es)/n(no)/e(dit)] "
+ "Commit aborted.\n",
+ "Your commit message: \n",
+ "-----------------------------------------------\n",
+ "WIP: This ïs a title.\n",
+ "Contënt on the second line\n",
+ "-----------------------------------------------\n",
+ ]
- self.assertListEqual(expected_output, self.githook_output)
+ for output, expected in zip(self.githook_output, expected_output):
+ self.assertMultiLineEqual(output.replace("\r", ""), expected.replace("\r", ""))
def test_commit_hook_edit(self):
self.responses = ["e", "y"]
env = {"EDITOR": ":"}
- test_filename = self.create_simple_commit(u"WIP: This ïs a title.\nContënt on the second line",
- out=self._interact, env=env, tty_in=True)
+ test_filename = self.create_simple_commit(
+ "WIP: This ïs a title.\nContënt on the second line", out=self._interact, env=env, tty_in=True
+ )
git("rm", "-f", test_filename, _cwd=self.tmp_git_repo)
short_hash = git("rev-parse", "--short", "HEAD", _cwd=self.tmp_git_repo, _tty_in=True).replace("\n", "")
@@ -107,23 +132,23 @@ class HookTests(BaseTestCase):
# Determine short commit-msg hash, needed to determine expected output
expected_output = self._violations()
- expected_output += ['Continue with commit anyways (this keeps the current commit message)? ' +
- '[y(es)/n(no)/e(dit)] ' + self._violations()[0]]
+ expected_output += [
+ "Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] "
+ + self._violations()[0]
+ ]
expected_output += self._violations()[1:]
- expected_output += ['Continue with commit anyways (this keeps the current commit message)? ' +
- "[y(es)/n(no)/e(dit)] " +
- u"[master %s] WIP: This ïs a title. Contënt on the second line\n" % short_hash,
- " 1 file changed, 0 insertions(+), 0 deletions(-)\n",
- u" create mode 100644 %s\n" % test_filename]
+ expected_output += [
+ "Continue with commit anyways (this keeps the current commit message)? [y(es)/n(no)/e(dit)] "
+ f"[main {short_hash}] WIP: This ïs a title. Contënt on the second line\n",
+ " 1 file changed, 0 insertions(+), 0 deletions(-)\n",
+ f" create mode 100644 {test_filename}\n",
+ ]
- assert len(self.githook_output) == len(expected_output)
for output, expected in zip(self.githook_output, expected_output):
- self.assertMultiLineEqual(
- output.replace('\r', ''),
- expected.replace('\r', ''))
+ self.assertMultiLineEqual(output.replace("\r", ""), expected.replace("\r", ""))
def test_commit_hook_worktree(self):
- """ Tests that hook installation and un-installation also work in git worktrees.
+ """Tests that hook installation and un-installation also work in git worktrees.
Test steps:
```sh
git init <tmpdir>
@@ -135,7 +160,7 @@ class HookTests(BaseTestCase):
```
"""
tmp_git_repo = self.create_tmp_git_repo()
- self.create_simple_commit(u"Simple title\n\nContënt in the body", git_repo=tmp_git_repo)
+ self.create_simple_commit("Simple title\n\nContënt in the body", git_repo=tmp_git_repo)
worktree_dir = self.generate_temp_path()
self.tmp_git_repos.append(worktree_dir) # make sure we clean up the worktree afterwards
@@ -144,10 +169,10 @@ class HookTests(BaseTestCase):
output_installed = gitlint("install-hook", _cwd=worktree_dir)
expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg")
- expected_msg = "Successfully installed gitlint commit-msg hook in {0}\n".format(expected_hook_path)
- self.assertEqual(output_installed, expected_msg)
+ expected_msg = f"Successfully installed gitlint commit-msg hook in {expected_hook_path}\n"
+ self.assertEqualStdout(output_installed, expected_msg)
output_uninstalled = gitlint("uninstall-hook", _cwd=worktree_dir)
expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg")
- expected_msg = "Successfully uninstalled gitlint commit-msg hook from {0}\n".format(expected_hook_path)
- self.assertEqual(output_uninstalled, expected_msg)
+ expected_msg = f"Successfully uninstalled gitlint commit-msg hook from {expected_hook_path}\n"
+ self.assertEqualStdout(output_uninstalled, expected_msg)
diff --git a/qa/test_named_rules.py b/qa/test_named_rules.py
new file mode 100644
index 0000000..e3c6908
--- /dev/null
+++ b/qa/test_named_rules.py
@@ -0,0 +1,23 @@
+from qa.base import BaseTestCase
+from qa.shell import gitlint
+
+
+class NamedRuleTests(BaseTestCase):
+ """Integration tests for named rules."""
+
+ def test_named_rule(self):
+ commit_msg = "WIP: thåt dûr bår\n\nSïmple commit body"
+ self.create_simple_commit(commit_msg)
+ config_path = self.get_sample_path("config/named-rules")
+ output = gitlint("--config", config_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
+ self.assertEqualStdout(output, self.get_expected("test_named_rules/test_named_rule_1"))
+
+ def test_named_user_rule(self):
+ commit_msg = "Normal cömmit title\n\nSïmple commit message body"
+ self.create_simple_commit(commit_msg)
+ config_path = self.get_sample_path("config/named-user-rules")
+ extra_path = self.get_sample_path("user_rules/extra")
+ output = gitlint(
+ "--extra-path", extra_path, "--config", config_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[9]
+ )
+ self.assertEqualStdout(output, self.get_expected("test_named_rules/test_named_user_rule_1"))
diff --git a/qa/test_rules.py b/qa/test_rules.py
new file mode 100644
index 0000000..218a13a
--- /dev/null
+++ b/qa/test_rules.py
@@ -0,0 +1,61 @@
+from qa.base import BaseTestCase
+from qa.shell import gitlint
+
+
+class RuleTests(BaseTestCase):
+ """
+ Tests for specific rules that are worth testing as integration tests.
+ It's not a goal to test every edge case of each rule, that's what the unit tests do.
+ """
+
+ def test_match_regex_rules(self):
+ """
+ Test that T7 (title-match-regex) and B8 (body-match-regex) work as expected.
+ By default, these rules don't do anything, only when setting a custom regex will they run.
+ """
+
+ commit_msg = "Thåt dûr bår\n\nSïmple commit message body"
+ self.create_simple_commit(commit_msg)
+
+ # Assert violations when T7 and B8 regexes don't match
+ output = gitlint("-c", "T7.regex=foo", "-c", "B8.regex=bar", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2])
+ self.assertEqualStdout(output, self.get_expected("test_rules/test_match_regex_rules_1"))
+
+ # Assert no violations when T7 and B8 regexes do match
+ output = gitlint("-c", "T7.regex=^Thåt", "-c", "B8.regex=commit message", _cwd=self.tmp_git_repo, _tty_in=True)
+ self.assertEqualStdout(output, "")
+
+ def test_ignore_rules(self):
+ """
+ Test that ignore rules work as expected:
+ ignore-by-title, ignore-by-body, ignore-by-author-name, ignore-body-lines
+ By default, these rules don't do anything, only when setting a custom regex will they run.
+ """
+ commit_msg = "WIP: Commït Tïtle\n\nSïmple commit\tbody\nAnōther Line \nLåst Line"
+ self.create_simple_commit(commit_msg)
+
+ # Assert violations when not ignoring anything
+ output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3])
+ self.assertEqualStdout(output, self.get_expected("test_rules/test_ignore_rules_1"))
+
+ # Simple convenience function that passes in common arguments for this test
+ def invoke_gitlint(*args, **kwargs):
+ return gitlint(
+ *args, "-c", "general.regex-style-search=True", **kwargs, _cwd=self.tmp_git_repo, _tty_in=True
+ )
+
+ # ignore-by-title
+ output = invoke_gitlint("-c", "ignore-by-title.regex=Commït")
+ self.assertEqualStdout(output, "")
+
+ # ignore-by-body
+ output = invoke_gitlint("-c", "ignore-by-body.regex=Anōther Line")
+ self.assertEqualStdout(output, "")
+
+ # ignore-by-author-name
+ output = invoke_gitlint("-c", "ignore-by-author-name.regex=gitlint-test-user")
+ self.assertEqualStdout(output, "")
+
+ # ignore-body-lines
+ output = invoke_gitlint("-c", "ignore-body-lines.regex=^Anōther", _ok_code=[2])
+ self.assertEqualStdout(output, self.get_expected("test_rules/test_ignore_rules_2"))
diff --git a/qa/test_stdin.py b/qa/test_stdin.py
index fff636f..04a3de9 100644
--- a/qa/test_stdin.py
+++ b/qa/test_stdin.py
@@ -1,34 +1,31 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=too-many-function-args,unexpected-keyword-arg
-import io
import subprocess
-from qa.shell import echo, gitlint
+
from qa.base import BaseTestCase
-from qa.utils import ustr, DEFAULT_ENCODING
+from qa.shell import echo, gitlint
+from qa.utils import FILE_ENCODING, TERMINAL_ENCODING
class StdInTests(BaseTestCase):
- """ Integration tests for various STDIN scenarios for gitlint """
+ """Integration tests for various STDIN scenarios for gitlint"""
def test_stdin_pipe(self):
- """ Test piping input into gitlint.
- This is the equivalent of doing:
- $ echo "foo" | gitlint
+ """Test piping input into gitlint.
+ This is the equivalent of doing:
+ $ echo "foo" | gitlint
"""
# NOTE: There is no use in testing this with _tty_in=True, because if you pipe something into a command
# there never is a TTY connected to stdin (per definition). We're setting _tty_in=False here to be explicit
# but note that this is always true when piping something into a command.
- output = gitlint(echo(u"WIP: Pïpe test."),
- _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
+ output = gitlint(echo("WIP: Pïpe test."), _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
self.assertEqualStdout(output, self.get_expected("test_stdin/test_stdin_pipe_1"))
def test_stdin_pipe_empty(self):
- """ Test the scenario where no TTY is attached an nothing is piped into gitlint. This occurs in
- CI runners like Jenkins and Gitlab, see https://github.com/jorisroovers/gitlint/issues/42 for details.
- This is the equivalent of doing:
- $ echo -n "" | gitlint
+ """Test the scenario where no TTY is attached and nothing is piped into gitlint. This occurs in
+ CI runners like Jenkins and Gitlab, see https://github.com/jorisroovers/gitlint/issues/42 for details.
+ This is the equivalent of doing:
+ $ echo -n "" | gitlint
"""
- commit_msg = u"WIP: This ïs a title.\nContent on the sëcond line"
+ commit_msg = "WIP: This ïs a title.\nContent on the sëcond line"
self.create_simple_commit(commit_msg)
# We need to set _err_to_out explicitly for sh to merge stdout and stderr output in case there's
@@ -36,21 +33,21 @@ class StdInTests(BaseTestCase):
# http://amoffat.github.io/sh/sections/special_arguments.html?highlight=_tty_in#err-to-out
output = gitlint(echo("-n", ""), _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3])
- self.assertEqual(ustr(output), self.get_expected("test_stdin/test_stdin_pipe_empty_1"))
+ self.assertEqualStdout(output, self.get_expected("test_stdin/test_stdin_pipe_empty_1"))
def test_stdin_file(self):
- """ Test the scenario where STDIN is a regular file (stat.S_ISREG = True)
- This is the equivalent of doing:
- $ gitlint < myfile
+ """Test the scenario where STDIN is a regular file (stat.S_ISREG = True)
+ This is the equivalent of doing:
+ $ gitlint < myfile
"""
- tmp_commit_msg_file = self.create_tmpfile(u"WIP: STDIN ïs a file test.")
-
- with io.open(tmp_commit_msg_file, encoding=DEFAULT_ENCODING) as file_handle:
+ tmp_commit_msg_file = self.create_tmpfile("WIP: STDIN ïs a file test.")
+ with open(tmp_commit_msg_file, encoding=FILE_ENCODING) as file_handle: # noqa: SIM117
# We need to use subprocess.Popen() here instead of sh because when passing a file_handle to sh, it will
# deal with reading the file itself instead of passing it on to gitlint as a STDIN. Since we're trying to
# test for the condition where stat.S_ISREG == True that won't work for us here.
- p = subprocess.Popen(u"gitlint", stdin=file_handle, cwd=self.tmp_git_repo,
- stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- output, _ = p.communicate()
- self.assertEqual(ustr(output), self.get_expected("test_stdin/test_stdin_file_1"))
+ with subprocess.Popen(
+ "gitlint", stdin=file_handle, cwd=self.tmp_git_repo, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+ ) as p:
+ output, _ = p.communicate()
+ self.assertEqual(output.decode(TERMINAL_ENCODING), self.get_expected("test_stdin/test_stdin_file_1"))
diff --git a/qa/test_user_defined.py b/qa/test_user_defined.py
index cf7effd..718766c 100644
--- a/qa/test_user_defined.py
+++ b/qa/test_user_defined.py
@@ -1,38 +1,57 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=too-many-function-args,unexpected-keyword-arg
-from qa.shell import gitlint
from qa.base import BaseTestCase
+from qa.shell import gitlint
class UserDefinedRuleTests(BaseTestCase):
- """ Integration tests for user-defined rules."""
+ """Integration tests for user-defined rules."""
- def test_user_defined_rules_examples(self):
+ def test_user_defined_rules_examples1(self):
+ """Test the user defined rules in the top-level `examples/` directory"""
extra_path = self.get_example_path()
- commit_msg = u"WIP: Thi$ is å title\nContent on the second line"
+ commit_msg = "WIP: Thi$ is å title\nContent on the second line"
self.create_simple_commit(commit_msg)
output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_examples_1"))
+ def test_user_defined_rules_examples2(self):
+ """Test the user defined rules in the top-level `examples/` directory"""
+ extra_path = self.get_example_path()
+ commit_msg = "Release: Thi$ is å title\nContent on the second line\n$This line is ignored \nThis isn't\t\n"
+ self.create_simple_commit(commit_msg)
+ output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
+ self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_examples_2"))
+
def test_user_defined_rules_examples_with_config(self):
+ """Test the user defined rules in the top-level `examples/` directory"""
extra_path = self.get_example_path()
- commit_msg = u"WIP: Thi$ is å title\nContent on the second line"
+ commit_msg = "WIP: Thi$ is å title\nContent on the second line"
self.create_simple_commit(commit_msg)
- output = gitlint("--extra-path", extra_path, "-c", "body-max-line-count.max-line-count=1",
- _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[6])
+ output = gitlint(
+ "--extra-path",
+ extra_path,
+ "-c",
+ "body-max-line-count.max-line-count=1",
+ _cwd=self.tmp_git_repo,
+ _tty_in=True,
+ _ok_code=[6],
+ )
expected_path = "test_user_defined/test_user_defined_rules_examples_with_config_1"
self.assertEqualStdout(output, self.get_expected(expected_path))
def test_user_defined_rules_extra(self):
extra_path = self.get_sample_path("user_rules/extra")
- commit_msg = u"WIP: Thi$ is å title\nContent on the second line"
+ commit_msg = "WIP: Thi$ is å title\nContent on the second line"
self.create_simple_commit(commit_msg)
- output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5])
- self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_extra_1"))
+ output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[9])
+ self.assertEqualStdout(
+ output,
+ self.get_expected("test_user_defined/test_user_defined_rules_extra_1", {"repo-path": self.tmp_git_repo}),
+ )
def test_invalid_user_defined_rules(self):
extra_path = self.get_sample_path("user_rules/incorrect_linerule")
self.create_simple_commit("WIP: test")
output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[255])
- self.assertEqualStdout(output,
- "Config Error: User-defined rule class 'MyUserLineRule' must have a 'validate' method\n")
+ self.assertEqualStdout(
+ output, "Config Error: User-defined rule class 'MyUserLineRule' must have a 'validate' method\n"
+ )
diff --git a/qa/utils.py b/qa/utils.py
index eb9869a..d560d86 100644
--- a/qa/utils.py
+++ b/qa/utils.py
@@ -1,9 +1,6 @@
-# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return
-import platform
-import sys
-import os
-
import locale
+import os
+import platform
########################################################################################################################
# PLATFORM_IS_WINDOWS
@@ -23,7 +20,7 @@ PLATFORM_IS_WINDOWS = platform_is_windows()
def use_sh_library():
- gitlint_use_sh_lib_env = os.environ.get('GITLINT_QA_USE_SH_LIB', None)
+ gitlint_use_sh_lib_env = os.environ.get("GITLINT_QA_USE_SH_LIB", None)
if gitlint_use_sh_lib_env:
return gitlint_use_sh_lib_env == "1"
return not PLATFORM_IS_WINDOWS
@@ -32,68 +29,20 @@ def use_sh_library():
USE_SH_LIB = use_sh_library()
########################################################################################################################
-# DEFAULT_ENCODING
+# TERMINAL_ENCODING
+# Encoding for reading gitlint command output
def getpreferredencoding():
- """ Modified version of local.getpreferredencoding() that takes into account LC_ALL, LC_CTYPE, LANG env vars
- on windows and falls back to UTF-8. """
- default_encoding = locale.getpreferredencoding() or "UTF-8"
-
- # On Windows, we mimic git/linux by trying to read the LC_ALL, LC_CTYPE, LANG env vars manually
- # (on Linux/MacOS the `getpreferredencoding()` call will take care of this).
- # We fallback to UTF-8
- if PLATFORM_IS_WINDOWS:
- default_encoding = "UTF-8"
- for env_var in ["LC_ALL", "LC_CTYPE", "LANG"]:
- encoding = os.environ.get(env_var, False)
- if encoding:
- # Support dotted (C.UTF-8) and non-dotted (C or UTF-8) charsets:
- # If encoding contains a dot: split and use second part, otherwise use everything
- dot_index = encoding.find(".")
- if dot_index != -1:
- default_encoding = encoding[dot_index + 1:]
- else:
- default_encoding = encoding
- break
-
- return default_encoding
-
-
-DEFAULT_ENCODING = getpreferredencoding()
+ """Use local.getpreferredencoding() or fallback to UTF-8."""
+ return locale.getpreferredencoding() or "UTF-8"
+
+
+TERMINAL_ENCODING = getpreferredencoding()
-########################################################################################################################
-# Unicode utility functions
-
-
-def ustr(obj):
- """ Python 2 and 3 utility method that converts an obj to unicode in python 2 and to a str object in python 3"""
- if sys.version_info[0] == 2:
- # If we are getting a string, then do an explicit decode
- # else, just call the unicode method of the object
- if type(obj) in [str, basestring]: # pragma: no cover # noqa
- return unicode(obj, DEFAULT_ENCODING) # pragma: no cover # noqa
- else:
- return unicode(obj) # pragma: no cover # noqa
- else:
- if type(obj) in [bytes]:
- return obj.decode(DEFAULT_ENCODING)
- else:
- return str(obj)
-
-
-def sstr(obj):
- """ Python 2 and 3 utility method that converts an obj to a DEFAULT_ENCODING encoded string in python 2
- and to unicode in python 3.
- Especially useful for implementing __str__ methods in python 2: http://stackoverflow.com/a/1307210/381010"""
- if sys.version_info[0] == 2:
- # For lists in python2, remove unicode string representation characters.
- # i.e. ensure lists are printed as ['a', 'b'] and not [u'a', u'b']
- if type(obj) in [list]:
- return [sstr(item) for item in obj] # pragma: no cover # noqa
-
- return unicode(obj).encode(DEFAULT_ENCODING) # pragma: no cover # noqa
- else:
- return obj # pragma: no cover
########################################################################################################################
+# FILE_ENCODING
+
+# Encoding for reading/writing files within the tests, this is always UTF-8
+FILE_ENCODING = "UTF-8"