From de139943d8272773b5f19ed824d687b0232b9ba3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 11 Mar 2023 09:03:03 +0100 Subject: Adding upstream version 0.19.1. Signed-off-by: Daniel Baumann --- qa/base.py | 43 +++++------ .../test_commits/test_lint_staged_msg_filename_1 | 3 +- qa/expected/test_commits/test_lint_staged_stdin_1 | 3 +- qa/expected/test_config/test_config_from_env_1 | 3 +- qa/expected/test_config/test_config_from_env_2 | 3 +- .../test_config/test_config_from_file_debug_1 | 3 +- qa/expected/test_gitlint/test_commit_binary_file_1 | 3 +- qa/expected/test_rules/test_ignore_rules_1 | 3 + qa/expected/test_rules/test_ignore_rules_2 | 2 + qa/expected/test_rules/test_match_regex_rules_1 | 2 + .../test_user_defined_rules_examples_2 | 1 + qa/requirements.txt | 4 -- qa/samples/user_rules/extra/extra_rules.py | 10 +-- qa/shell.py | 83 +++++++++++++++------- qa/test_commits.py | 10 ++- qa/test_config.py | 10 ++- qa/test_contrib.py | 3 +- qa/test_gitlint.py | 14 ++-- qa/test_hooks.py | 22 +++--- qa/test_named_rules.py | 2 +- qa/test_rules.py | 61 ++++++++++++++++ qa/test_stdin.py | 12 ++-- qa/test_user_defined.py | 5 +- qa/utils.py | 46 +++++------- 24 files changed, 218 insertions(+), 133 deletions(-) create mode 100644 qa/expected/test_rules/test_ignore_rules_1 create mode 100644 qa/expected/test_rules/test_ignore_rules_2 create mode 100644 qa/expected/test_rules/test_match_regex_rules_1 delete mode 100644 qa/requirements.txt create mode 100644 qa/test_rules.py (limited to 'qa') diff --git a/qa/base.py b/qa/base.py index ce66fbf..2b83778 100644 --- a/qa/base.py +++ b/qa/base.py @@ -1,20 +1,16 @@ -# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return, -# pylint: disable=too-many-function-args,unexpected-keyword-arg - import os import platform import shutil import sys import tempfile -from datetime import datetime -from uuid import uuid4 +from datetime import datetime, timezone from unittest import TestCase +from uuid import uuid4 import arrow - -from qa.shell import git, gitlint, RunningCommand -from qa.utils import DEFAULT_ENCODING +from qa.shell import RunningCommand, git, gitlint +from qa.utils import FILE_ENCODING, PLATFORM_IS_WINDOWS, TERMINAL_ENCODING class BaseTestCase(TestCase): @@ -40,18 +36,19 @@ class BaseTestCase(TestCase): for tmpfile in self.tmpfiles: os.remove(tmpfile) for repo in self.tmp_git_repos: - shutil.rmtree(repo) + # 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 = output.stdout.decode(DEFAULT_ENCODING) + output = output.stdout.decode(TERMINAL_ENCODING) output = output.replace("\r", "") self.assertMultiLineEqual(output, expected) @staticmethod def generate_temp_path(): - timestamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f") - return os.path.realpath(f"/tmp/gitlint-test-{timestamp}") + timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S-%f") + return os.path.realpath(f"/tmp/gitlint-test-{timestamp}") # noqa def create_tmp_git_repo(self): """Creates a temporary git repository and returns its directory path""" @@ -72,6 +69,9 @@ 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 @@ -84,13 +84,12 @@ class BaseTestCase(TestCase): if isinstance(content, bytes): open_kwargs = {"mode": "wb"} else: - open_kwargs = {"mode": "w", "encoding": DEFAULT_ENCODING} + open_kwargs = {"mode": "w", "encoding": FILE_ENCODING} - with open(full_path, **open_kwargs) as f: # pylint: disable=unspecified-encoding + with open(full_path, **open_kwargs) as f: f.write(content) else: - # pylint: disable=consider-using-with - open(full_path, "a", encoding=DEFAULT_ENCODING).close() + open(full_path, "a", encoding=FILE_ENCODING).close() # noqa: SIM115 (Use context handler for opening files) return test_filename @@ -150,9 +149,9 @@ class BaseTestCase(TestCase): if isinstance(content, bytes): open_kwargs = {"mode": "wb"} else: - open_kwargs = {"mode": "w", "encoding": DEFAULT_ENCODING} + open_kwargs = {"mode": "w", "encoding": FILE_ENCODING} - with open(tmpfile, **open_kwargs) as f: # pylint: disable=unspecified-encoding + with open(tmpfile, **open_kwargs) as f: f.write(content) return tmpfilepath @@ -181,7 +180,8 @@ class BaseTestCase(TestCase): 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) - with open(expected_path, encoding=DEFAULT_ENCODING) as file: + # 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: @@ -199,7 +199,8 @@ class BaseTestCase(TestCase): "git_version": expected_git_version, "gitlint_version": expected_gitlint_version, "GITLINT_USE_SH_LIB": BaseTestCase.GITLINT_USE_SH_LIB, - "DEFAULT_ENCODING": DEFAULT_ENCODING, + "TERMINAL_ENCODING": TERMINAL_ENCODING, + "FILE_ENCODING": FILE_ENCODING, } def get_debug_vars_last_commit(self, git_repo=None): 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 f2ab49e..03a558c 100644 --- a/qa/expected/test_commits/test_lint_staged_msg_filename_1 +++ b/qa/expected/test_commits/test_lint_staged_msg_filename_1 @@ -5,7 +5,8 @@ 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 DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] diff --git a/qa/expected/test_commits/test_lint_staged_stdin_1 b/qa/expected/test_commits/test_lint_staged_stdin_1 index cf34b8b..7892865 100644 --- a/qa/expected/test_commits/test_lint_staged_stdin_1 +++ b/qa/expected/test_commits/test_lint_staged_stdin_1 @@ -5,7 +5,8 @@ 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 DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] diff --git a/qa/expected/test_config/test_config_from_env_1 b/qa/expected/test_config/test_config_from_env_1 index 38fba21..91eee40 100644 --- a/qa/expected/test_config/test_config_from_env_1 +++ b/qa/expected/test_config/test_config_from_env_1 @@ -5,7 +5,8 @@ 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 DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] diff --git a/qa/expected/test_config/test_config_from_env_2 b/qa/expected/test_config/test_config_from_env_2 index 50d1e3f..06b0c1b 100644 --- a/qa/expected/test_config/test_config_from_env_2 +++ b/qa/expected/test_config/test_config_from_env_2 @@ -5,7 +5,8 @@ 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 DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] 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 39bdf52..279fb32 100644 --- a/qa/expected/test_config/test_config_from_file_debug_1 +++ b/qa/expected/test_config/test_config_from_file_debug_1 @@ -5,7 +5,8 @@ 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 DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: {config_path} [GENERAL] diff --git a/qa/expected/test_gitlint/test_commit_binary_file_1 b/qa/expected/test_gitlint/test_commit_binary_file_1 index 6bc119b..83faf1b 100644 --- a/qa/expected/test_gitlint/test_commit_binary_file_1 +++ b/qa/expected/test_gitlint/test_commit_binary_file_1 @@ -5,7 +5,8 @@ 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 DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli TERMINAL_ENCODING: {TERMINAL_ENCODING} +DEBUG: gitlint.cli FILE_ENCODING: {FILE_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] 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_2 b/qa/expected/test_user_defined/test_user_defined_rules_examples_2 index 9b96423..d706b12 100644 --- a/qa/expected/test_user_defined/test_user_defined_rules_examples_2 +++ b/qa/expected/test_user_defined/test_user_defined_rules_examples_2 @@ -2,3 +2,4 @@ 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/requirements.txt b/qa/requirements.txt deleted file mode 100644 index cf6baa5..0000000 --- a/qa/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -sh==1.14.3 -pytest==7.0.1; -arrow==1.2.3; -gitlint # no version as you want to test the currently installed version diff --git a/qa/samples/user_rules/extra/extra_rules.py b/qa/samples/user_rules/extra/extra_rules.py index cad531b..7996590 100644 --- a/qa/samples/user_rules/extra/extra_rules.py +++ b/qa/samples/user_rules/extra/extra_rules.py @@ -1,5 +1,5 @@ -from gitlint.rules import CommitRule, RuleViolation, ConfigurationRule -from gitlint.options import IntOption, StrOption, ListOption +from gitlint.options import IntOption, ListOption, StrOption +from gitlint.rules import CommitRule, ConfigurationRule, RuleViolation class GitContextRule(CommitRule): @@ -64,9 +64,9 @@ class ConfigurableCommitRule(CommitRule): def validate(self, _): violations = [ - RuleViolation(self.id, f"int-öption: {self.options[u'int-öption'].value}", line_nr=1), - RuleViolation(self.id, f"str-öption: {self.options[u'str-öption'].value}", line_nr=1), - RuleViolation(self.id, f"list-öption: {self.options[u'list-öption'].value}", line_nr=1), + 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 44716c0..3ef874d 100644 --- a/qa/shell.py +++ b/qa/shell.py @@ -2,24 +2,31 @@ # on gitlint internals for our integration testing framework. import subprocess -from qa.utils import USE_SH_LIB, DEFAULT_ENCODING + +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) # pylint: disable=invalid-name + 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 - class RunningCommand: - pass + ... class ShResult(RunningCommand): """Result wrapper class. We use this to more easily migrate from using https://amoffat.github.io/sh/ to using @@ -29,18 +36,35 @@ else: 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 + stderr.decode(DEFAULT_ENCODING) - 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 - def git(*command_parts, **kwargs): return run_command("git", *command_parts, **kwargs) @@ -51,31 +75,36 @@ 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 str(result) + args = [command, *list(args)] + return _exec(*args, **kwargs) def _exec(*args, **kwargs): - pipe = subprocess.PIPE - popen_kwargs = {"stdout": pipe, "stderr": pipe, "shell": kwargs.get("_tty_out", False)} - if "_cwd" in kwargs: - popen_kwargs["cwd"] = kwargs["_cwd"] - if "_env" in kwargs: - popen_kwargs["env"] = kwargs["_env"] + 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: with subprocess.Popen(args, **popen_kwargs) as p: - result = p.communicate() + result = p.communicate(stdin_input) + except FileNotFoundError as exc: raise CommandNotFound from exc exit_code = p.returncode - stdout = result[0].decode(DEFAULT_ENCODING) + stdout = result[0] stderr = result[1] # 'sh' does not decode the stderr bytes to unicode full_cmd = "" if args is None else " ".join(args) diff --git a/qa/test_commits.py b/qa/test_commits.py index d40c211..11d1851 100644 --- a/qa/test_commits.py +++ b/qa/test_commits.py @@ -1,10 +1,9 @@ -# 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.shell import echo, git, gitlint class CommitsTests(BaseTestCase): @@ -111,6 +110,11 @@ class CommitsTests(BaseTestCase): self.assertEqual(output.exit_code, 2) self.assertEqualStdout(output, expected) + # Lint using --commits , + 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 pointing to the single commit output = gitlint("--commits", refspec, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2]) self.assertEqual(output.exit_code, 2) @@ -129,7 +133,7 @@ class CommitsTests(BaseTestCase): 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 + """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 diff --git a/qa/test_config.py b/qa/test_config.py index 1225f6a..d051686 100644 --- a/qa/test_config.py +++ b/qa/test_config.py @@ -1,10 +1,8 @@ -# pylint: disable=too-many-function-args,unexpected-keyword-arg - +import os import re -from qa.shell import gitlint from qa.base import BaseTestCase -from qa.utils import DEFAULT_ENCODING +from qa.shell import gitlint class ConfigTests(BaseTestCase): @@ -69,7 +67,7 @@ class ConfigTests(BaseTestCase): "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) @@ -128,7 +126,7 @@ class ConfigTests(BaseTestCase): # 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(output.stdout.decode(DEFAULT_ENCODING)) + result = p.search(str(output)) date = result.group(1).strip() expected_kwargs.update({"date": date}) diff --git a/qa/test_contrib.py b/qa/test_contrib.py index 129e576..d3a45ba 100644 --- a/qa/test_contrib.py +++ b/qa/test_contrib.py @@ -1,6 +1,5 @@ -# pylint: disable= -from qa.shell import gitlint from qa.base import BaseTestCase +from qa.shell import gitlint class ContribRuleTests(BaseTestCase): diff --git a/qa/test_gitlint.py b/qa/test_gitlint.py index 6c45196..7a04a39 100644 --- a/qa/test_gitlint.py +++ b/qa/test_gitlint.py @@ -1,8 +1,8 @@ -# pylint: disable=too-many-function-args,unexpected-keyword-arg 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): @@ -58,7 +58,7 @@ class IntegrationTests(BaseTestCase): self.assertEqualStdout(output, expected) # Make a small modification to the commit and commit it using fixup commit - with 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: fh.write("Appending söme stuff\n") git("add", test_filename, _cwd=self.tmp_git_repo) @@ -87,7 +87,7 @@ class IntegrationTests(BaseTestCase): 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=DEFAULT_ENCODING) as fh: + 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) @@ -133,7 +133,7 @@ class IntegrationTests(BaseTestCase): self.assertEqualStdout(output, expected) # Make a small modification to the commit and commit it using squash commit - with 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 @@ -252,7 +252,7 @@ class IntegrationTests(BaseTestCase): binary_filename = self.create_simple_commit("Sïmple commit", file_contents=bytes([0x48, 0x00, 0x49, 0x00])) output = gitlint( "--debug", - _ok_code=1, + _ok_code=[1], _cwd=self.tmp_git_repo, ) diff --git a/qa/test_hooks.py b/qa/test_hooks.py index 19edeb2..99e76dd 100644 --- a/qa/test_hooks.py +++ b/qa/test_hooks.py @@ -1,7 +1,7 @@ -# 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): @@ -30,18 +30,16 @@ class HookTests(BaseTestCase): # install git commit-msg hook and assert output output_installed = gitlint("install-hook", _cwd=self.tmp_git_repo) - expected_installed = ( - f"Successfully installed gitlint commit-msg hook in {self.tmp_git_repo}/.git/hooks/commit-msg\n" - ) + 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 = ( - f"Successfully uninstalled gitlint commit-msg hook from {self.tmp_git_repo}/.git/hooks/commit-msg\n" - ) + 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() @@ -171,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 = f"Successfully installed gitlint commit-msg hook in {expected_hook_path}\r\n" - 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 = f"Successfully uninstalled gitlint commit-msg hook from {expected_hook_path}\r\n" - 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 index 75cd9a1..e3c6908 100644 --- a/qa/test_named_rules.py +++ b/qa/test_named_rules.py @@ -1,5 +1,5 @@ -from qa.shell import gitlint from qa.base import BaseTestCase +from qa.shell import gitlint class NamedRuleTests(BaseTestCase): 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 8ed4cb1..04a3de9 100644 --- a/qa/test_stdin.py +++ b/qa/test_stdin.py @@ -1,8 +1,8 @@ -# pylint: disable=too-many-function-args,unexpected-keyword-arg import subprocess -from qa.shell import echo, gitlint + from qa.base import BaseTestCase -from qa.utils import DEFAULT_ENCODING +from qa.shell import echo, gitlint +from qa.utils import FILE_ENCODING, TERMINAL_ENCODING class StdInTests(BaseTestCase): @@ -33,7 +33,7 @@ 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(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) @@ -42,7 +42,7 @@ class StdInTests(BaseTestCase): """ tmp_commit_msg_file = self.create_tmpfile("WIP: STDIN ïs a file test.") - with open(tmp_commit_msg_file, encoding=DEFAULT_ENCODING) as file_handle: + 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. @@ -50,4 +50,4 @@ class StdInTests(BaseTestCase): "gitlint", stdin=file_handle, cwd=self.tmp_git_repo, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) as p: output, _ = p.communicate() - self.assertEqual(output.decode(DEFAULT_ENCODING), self.get_expected("test_stdin/test_stdin_file_1")) + 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 a003f3e..718766c 100644 --- a/qa/test_user_defined.py +++ b/qa/test_user_defined.py @@ -1,6 +1,5 @@ -# 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): @@ -19,7 +18,7 @@ class UserDefinedRuleTests(BaseTestCase): 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=[4]) + 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): diff --git a/qa/utils.py b/qa/utils.py index 89292cd..d560d86 100644 --- a/qa/utils.py +++ b/qa/utils.py @@ -1,8 +1,6 @@ -# pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return -import platform -import os - import locale +import os +import platform ######################################################################################################################## # PLATFORM_IS_WINDOWS @@ -31,32 +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() + + +######################################################################################################################## +# FILE_ENCODING + +# Encoding for reading/writing files within the tests, this is always UTF-8 +FILE_ENCODING = "UTF-8" -- cgit v1.2.3