diff --git a/gitlint-core/gitlint/__init__.py b/gitlint-core/gitlint/__init__.py index d08993f0..a2339fd1 100644 --- a/gitlint-core/gitlint/__init__.py +++ b/gitlint-core/gitlint/__init__.py @@ -1,8 +1,8 @@ import sys if sys.version_info >= (3, 8): - from importlib import metadata + from importlib import metadata # pragma: nocover else: - import importlib_metadata as metadata + import importlib_metadata as metadata # pragma: nocover __version__ = metadata.version("gitlint-core") diff --git a/gitlint-core/gitlint/git.py b/gitlint-core/gitlint/git.py index 405d1ab8..6612a7d0 100644 --- a/gitlint-core/gitlint/git.py +++ b/gitlint-core/gitlint/git.py @@ -171,11 +171,6 @@ def __eq__(self, other): def __str__(self) -> str: return f"{self.filepath}: {self.additions} additions, {self.deletions} deletions" - def __repr__(self) -> str: - return ( - f'GitChangedFileStats(filepath="{self.filepath}", additions={self.additions}, deletions={self.deletions})' - ) - class GitCommit: """Class representing a git commit. diff --git a/gitlint-core/gitlint/rule_finder.py b/gitlint-core/gitlint/rule_finder.py index 4179c9ef..810faa92 100644 --- a/gitlint-core/gitlint/rule_finder.py +++ b/gitlint-core/gitlint/rule_finder.py @@ -137,6 +137,7 @@ def assert_valid_rule_class(clazz, rule_type="User-defined"): # noqa: PLR0912 ( if issubclass(clazz, (rules.LineRule, rules.CommitRule)): if not hasattr(clazz, "validate") or not inspect.isroutine(clazz.validate): raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have a 'validate' method") + # Configuration rules must have an `apply` method elif issubclass(clazz, rules.ConfigurationRule): # noqa: SIM102 if not hasattr(clazz, "apply") or not inspect.isroutine(clazz.apply): diff --git a/gitlint-core/gitlint/tests/base.py b/gitlint-core/gitlint/tests/base.py index 326c7cbb..3899a5f1 100644 --- a/gitlint-core/gitlint/tests/base.py +++ b/gitlint-core/gitlint/tests/base.py @@ -171,16 +171,18 @@ def assertRaisesMessage(self, expected_exception, expected_msg): yield except expected_exception as exc: exception_msg = str(exc) - if exception_msg != expected_msg: + if exception_msg != expected_msg: # pragma: nocover error = f"Right exception, wrong message:\n got: {exception_msg}\n expected: {expected_msg}" raise self.fail(error) from exc # else: everything is fine, just return return - except Exception as exc: + except Exception as exc: # pragma: nocover raise self.fail(f"Expected '{expected_exception.__name__}' got '{exc.__class__.__name__}'") from exc # No exception raised while we expected one - raise self.fail(f"Expected to raise {expected_exception.__name__}, didn't get an exception at all") + raise self.fail( + f"Expected to raise {expected_exception.__name__}, didn't get an exception at all" + ) # pragma: nocover def object_equality_test(self, obj, attr_list, ctor_kwargs=None): """Helper function to easily implement object equality tests. diff --git a/gitlint-core/gitlint/tests/cli/test_cli.py b/gitlint-core/gitlint/tests/cli/test_cli.py index 540800d9..c0063750 100644 --- a/gitlint-core/gitlint/tests/cli/test_cli.py +++ b/gitlint-core/gitlint/tests/cli/test_cli.py @@ -99,6 +99,40 @@ def test_lint_multiple_commits(self, sh, _): self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_multiple_commits_1")) self.assertEqual(result.exit_code, 3) + @patch("gitlint.cli.get_stdin_data", return_value=False) + @patch("gitlint.git.sh") + def test_lint_multiple_commits_csv(self, sh, _): + """Test for --commits option""" + + # fmt: off + sh.git.side_effect = [ + "6f29bf81a8322a04071bb794666e48c443a90360\n", # git rev-list + "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n", + "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", + # git log --pretty + "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "commït-title1\n\ncommït-body1", + "#", # git config --get core.commentchar + "3\t5\tcommit-1/file-1\n1\t4\tcommit-1/file-2\n", # git diff-tree + "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains + # git log --pretty + "test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" + "commït-title2\n\ncommït-body2", + "8\t3\tcommit-2/file-1\n1\t5\tcommit-2/file-2\n", # git diff-tree + "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains + # git log --pretty + "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" + "commït-title3\n\ncommït-body3", + "7\t2\tcommit-3/file-1\n1\t7\tcommit-3/file-2\n", # git diff-tree + "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains + ] + # fmt: on + + with patch("gitlint.display.stderr", new=StringIO()) as stderr: + result = self.cli.invoke(cli.cli, ["--commits", "6f29bf81,25053cce,4da2656b"]) + self.assertEqual(stderr.getvalue(), self.get_expected("cli/test_cli/test_lint_multiple_commits_csv_1")) + self.assertEqual(result.exit_code, 3) + @patch("gitlint.cli.get_stdin_data", return_value=False) @patch("gitlint.git.sh") def test_lint_multiple_commits_config(self, sh, _): @@ -600,6 +634,11 @@ def test_config_file_negative_environment(self): result = self.cli.invoke(cli.cli, env={"GITLINT_CONFIG": config_path}) self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) + def test_config_error(self): + result = self.cli.invoke(cli.cli, ["-c", "foo.bar=hur"]) + self.assertEqual(result.output, "Config Error: No such rule 'foo'\n") + self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) + @patch("gitlint.cli.get_stdin_data", return_value=False) def test_target(self, _): """Test for the --target option""" diff --git a/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py b/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py index 5cc33557..2bad2edb 100644 --- a/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py +++ b/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py @@ -101,6 +101,5 @@ def test_read_authors_file(self, _mock_read_text): return_value=False, ) def test_read_authors_file_missing_file(self, _mock_iterdir): - with self.assertRaises(FileNotFoundError) as err: + with self.assertRaisesMessage(FileNotFoundError, "No AUTHORS file found!"): AllowedAuthors._read_authors_from_file(self.gitcontext) - self.assertEqual(err.exception.args[0], "AUTHORS file not found") diff --git a/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_csv_1 b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_csv_1 new file mode 100644 index 00000000..be3288b0 --- /dev/null +++ b/gitlint-core/gitlint/tests/expected/cli/test_cli/test_lint_multiple_commits_csv_1 @@ -0,0 +1,8 @@ +Commit 6f29bf81a8: +3: B5 Body message is too short (12<20): "commït-body1" + +Commit 25053ccec5: +3: B5 Body message is too short (12<20): "commït-body2" + +Commit 4da2656b0d: +3: B5 Body message is too short (12<20): "commït-body3" diff --git a/gitlint-core/gitlint/tests/rules/test_configuration_rules.py b/gitlint-core/gitlint/tests/rules/test_configuration_rules.py index d5b70a98..5935a4ac 100644 --- a/gitlint-core/gitlint/tests/rules/test_configuration_rules.py +++ b/gitlint-core/gitlint/tests/rules/test_configuration_rules.py @@ -105,6 +105,12 @@ def test_ignore_by_author_name(self): ] self.assert_logged(expected_log_messages) + # Non-Matching regex -> expect config to stay the same + rule = rules.IgnoreByAuthorName({"regex": "foo"}) + expected_config = LintConfig() + rule.apply(config, commit) + self.assertEqual(config, LintConfig()) + # Matching regex -> expect config to ignore all rules rule = rules.IgnoreByAuthorName({"regex": "(.*)ëst(.*)"}) expected_config = LintConfig() diff --git a/gitlint-core/gitlint/tests/rules/test_rules.py b/gitlint-core/gitlint/tests/rules/test_rules.py index e24d91ae..b4013723 100644 --- a/gitlint-core/gitlint/tests/rules/test_rules.py +++ b/gitlint-core/gitlint/tests/rules/test_rules.py @@ -3,6 +3,10 @@ class RuleTests(BaseTestCase): + def test_ruleviolation__str__(self): + expected = '57: rule-ïd Tēst message: "Tēst content"' + self.assertEqual(str(RuleViolation("rule-ïd", "Tēst message", "Tēst content", 57)), expected) + def test_rule_equality(self): self.assertEqual(Rule(), Rule()) # Ensure rules are not equal if they differ on their attributes @@ -13,9 +17,16 @@ def test_rule_equality(self): def test_rule_log(self): rule = Rule() + self.assertIsNone(rule._log) rule.log.debug("Tēst message") self.assert_log_contains("DEBUG: gitlint.rules Tēst message") + # Assert the same logger is reused when logging multiple messages + log = rule._log + rule.log.debug("Anöther message") + self.assertEqual(log, rule._log) + self.assert_log_contains("DEBUG: gitlint.rules Anöther message") + def test_rule_violation_equality(self): violation1 = RuleViolation("ïd1", "My messåge", "My cöntent", 1) self.object_equality_test(violation1, ["rule_id", "message", "content", "line_nr"]) diff --git a/gitlint-core/gitlint/tests/rules/test_user_rules.py b/gitlint-core/gitlint/tests/rules/test_user_rules.py index c2309a66..8086bea1 100644 --- a/gitlint-core/gitlint/tests/rules/test_user_rules.py +++ b/gitlint-core/gitlint/tests/rules/test_user_rules.py @@ -103,21 +103,21 @@ class MyLineRuleClass(rules.LineRule): target = rules.CommitMessageTitle def validate(self): - pass + pass # pragma: nocover class MyCommitRuleClass(rules.CommitRule): id = "UC2" name = "my-cömmit-rule" def validate(self): - pass + pass # pragma: nocover class MyConfigurationRuleClass(rules.ConfigurationRule): id = "UC3" name = "my-cönfiguration-rule" def apply(self): - pass + pass # pragma: nocover # Just assert that no error is raised self.assertIsNone(assert_valid_rule_class(MyLineRuleClass)) @@ -235,8 +235,8 @@ class MyRuleClass(rules.ConfigurationRule): with self.assertRaisesMessage(UserRuleError, expected_msg): assert_valid_rule_class(MyRuleClass) - # validate attribute - not a method - MyRuleClass.validate = "föo" + # apply attribute - not a method + MyRuleClass.apply = "föo" with self.assertRaisesMessage(UserRuleError, expected_msg): assert_valid_rule_class(MyRuleClass) @@ -246,7 +246,7 @@ class MyRuleClass(rules.LineRule): name = "my-rüle-class" def validate(self): - pass + pass # pragma: nocover # no target expected_msg = ( diff --git a/gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py b/gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py index 6291e809..c9472506 100644 --- a/gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py +++ b/gitlint-core/gitlint/tests/samples/user_rules/my_commit_rules.py @@ -19,7 +19,7 @@ def validate(self, _commit): def func_should_be_ignored(): - pass + pass # pragma: nocover global_variable_should_be_ignored = True diff --git a/gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py b/gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py index 22c3f652..c2863fe6 100644 --- a/gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py +++ b/gitlint-core/gitlint/tests/samples/user_rules/parent_package/__init__.py @@ -9,4 +9,4 @@ class InitFileRule(CommitRule): options_spec = [] def validate(self, _commit): - return [] + return [] # pragma: nocover diff --git a/gitlint-core/gitlint/tests/test_hooks.py b/gitlint-core/gitlint/tests/test_hooks.py index 80070099..7390f145 100644 --- a/gitlint-core/gitlint/tests/test_hooks.py +++ b/gitlint-core/gitlint/tests/test_hooks.py @@ -57,9 +57,10 @@ def test_install_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exist expected_msg = f"{lint_config.target} is not a git repository." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.install_commit_msg_hook(lint_config) - isdir.assert_called_with(git_hooks_dir.return_value) - path_exists.assert_not_called() - copy.assert_not_called() + + isdir.assert_called_with(git_hooks_dir.return_value) + path_exists.assert_not_called() + copy.assert_not_called() # mock that there is already a commit hook present isdir.return_value = True @@ -105,9 +106,10 @@ def test_uninstall_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exi expected_msg = f"{lint_config.target} is not a git repository." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) - isdir.assert_called_with(git_hooks_dir.return_value) - path_exists.assert_not_called() - remove.assert_not_called() + + isdir.assert_called_with(git_hooks_dir.return_value) + path_exists.assert_not_called() + remove.assert_not_called() # mock that there is no commit hook present isdir.return_value = True @@ -116,9 +118,10 @@ def test_uninstall_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exi expected_msg = f"There is no commit-msg hook present in {expected_dst}." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) - isdir.assert_called_with(git_hooks_dir.return_value) - path_exists.assert_called_once_with(expected_dst) - remove.assert_not_called() + + isdir.assert_called_with(git_hooks_dir.return_value) + path_exists.assert_called_once_with(expected_dst) + remove.assert_not_called() # mock that there is a different (=not gitlint) commit hook isdir.return_value = True diff --git a/gitlint-core/gitlint/tests/test_options.py b/gitlint-core/gitlint/tests/test_options.py index 9b849456..deff7235 100644 --- a/gitlint-core/gitlint/tests/test_options.py +++ b/gitlint-core/gitlint/tests/test_options.py @@ -14,6 +14,10 @@ class RuleOptionTests(BaseTestCase): + def test_option__str__(self): + option = StrOption("tëst-option", "åbc", "Test Dëscription") + self.assertEqual(str(option), "(tëst-option: åbc (Test Dëscription))") + def test_option_equality(self): options = { IntOption: 123, diff --git a/pyproject.toml b/pyproject.toml index cfc6c500..98b60f51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,3 +199,4 @@ branch = true # measure branch coverage in addition to statement coverage [tool.coverage.report] fail_under = 97 +show_missing = true