diff options
Diffstat (limited to 'gitlint-core/gitlint/tests/contrib')
7 files changed, 318 insertions, 0 deletions
diff --git a/gitlint-core/gitlint/tests/contrib/__init__.py b/gitlint-core/gitlint/tests/contrib/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/gitlint-core/gitlint/tests/contrib/__init__.py diff --git a/gitlint-core/gitlint/tests/contrib/rules/__init__.py b/gitlint-core/gitlint/tests/contrib/rules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/gitlint-core/gitlint/tests/contrib/rules/__init__.py diff --git a/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py b/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py new file mode 100644 index 0000000..2bad2ed --- /dev/null +++ b/gitlint-core/gitlint/tests/contrib/rules/test_authors_commit.py @@ -0,0 +1,105 @@ +from collections import namedtuple +from unittest.mock import patch + +from gitlint.config import LintConfig +from gitlint.contrib.rules.authors_commit import AllowedAuthors +from gitlint.rules import RuleViolation +from gitlint.tests.base import BaseTestCase + + +class ContribAuthorsCommitTests(BaseTestCase): + def setUp(self): + author = namedtuple("Author", "name, email") + self.author_1 = author("John Doe", "john.doe@mail.com") + self.author_2 = author("Bob Smith", "bob.smith@mail.com") + self.rule = AllowedAuthors() + self.gitcontext = self.get_gitcontext() + + def get_gitcontext(self): + gitcontext = self.gitcontext(self.get_sample("commit_message/sample1")) + gitcontext.repository_path = self.get_sample_path("config") + return gitcontext + + def get_commit(self, name, email): + commit = self.gitcommit("commit_message/sample1", author_name=name, author_email=email) + commit.message.context = self.gitcontext + return commit + + def test_enable(self): + for rule_ref in ["CC3", "contrib-allowed-authors"]: + config = LintConfig() + config.contrib = [rule_ref] + self.assertIn(AllowedAuthors(), config.rules) + + def test_authors_succeeds(self): + for author in [self.author_1, self.author_2]: + commit = self.get_commit(author.name, author.email) + violations = self.rule.validate(commit) + self.assertListEqual([], violations) + + def test_authors_email_is_case_insensitive(self): + for email in [ + self.author_2.email.capitalize(), + self.author_2.email.lower(), + self.author_2.email.upper(), + ]: + commit = self.get_commit(self.author_2.name, email) + violations = self.rule.validate(commit) + self.assertListEqual([], violations) + + def test_authors_name_is_case_sensitive(self): + for name in [self.author_2.name.lower(), self.author_2.name.upper()]: + commit = self.get_commit(name, self.author_2.email) + violations = self.rule.validate(commit) + expected_violation = RuleViolation( + "CC3", + f"Author not in 'AUTHORS' file: " f'"{name} <{self.author_2.email}>"', + ) + self.assertListEqual([expected_violation], violations) + + def test_authors_bad_name_fails(self): + for name in ["", "root"]: + commit = self.get_commit(name, self.author_2.email) + violations = self.rule.validate(commit) + expected_violation = RuleViolation( + "CC3", + f"Author not in 'AUTHORS' file: " f'"{name} <{self.author_2.email}>"', + ) + self.assertListEqual([expected_violation], violations) + + def test_authors_bad_email_fails(self): + for email in ["", "root@example.com"]: + commit = self.get_commit(self.author_2.name, email) + violations = self.rule.validate(commit) + expected_violation = RuleViolation( + "CC3", + f"Author not in 'AUTHORS' file: " f'"{self.author_2.name} <{email}>"', + ) + self.assertListEqual([expected_violation], violations) + + def test_authors_invalid_combination_fails(self): + commit = self.get_commit(self.author_1.name, self.author_2.email) + violations = self.rule.validate(commit) + expected_violation = RuleViolation( + "CC3", + f"Author not in 'AUTHORS' file: " f'"{self.author_1.name} <{self.author_2.email}>"', + ) + self.assertListEqual([expected_violation], violations) + + @patch( + "gitlint.contrib.rules.authors_commit.Path.read_text", + return_value="John Doe <john.doe@mail.com>", + ) + def test_read_authors_file(self, _mock_read_text): + authors, authors_file_name = AllowedAuthors._read_authors_from_file(self.gitcontext) + self.assertEqual(authors_file_name, "AUTHORS") + self.assertEqual(len(authors), 1) + self.assertEqual(authors, {self.author_1}) + + @patch( + "gitlint.contrib.rules.authors_commit.Path.exists", + return_value=False, + ) + def test_read_authors_file_missing_file(self, _mock_iterdir): + with self.assertRaisesMessage(FileNotFoundError, "No AUTHORS file found!"): + AllowedAuthors._read_authors_from_file(self.gitcontext) diff --git a/gitlint-core/gitlint/tests/contrib/rules/test_conventional_commit.py b/gitlint-core/gitlint/tests/contrib/rules/test_conventional_commit.py new file mode 100644 index 0000000..cbab684 --- /dev/null +++ b/gitlint-core/gitlint/tests/contrib/rules/test_conventional_commit.py @@ -0,0 +1,82 @@ +from gitlint.config import LintConfig +from gitlint.contrib.rules.conventional_commit import ConventionalCommit +from gitlint.rules import RuleViolation +from gitlint.tests.base import BaseTestCase + + +class ContribConventionalCommitTests(BaseTestCase): + def test_enable(self): + # Test that rule can be enabled in config + for rule_ref in ["CT1", "contrib-title-conventional-commits"]: + config = LintConfig() + config.contrib = [rule_ref] + self.assertIn(ConventionalCommit(), config.rules) + + def test_conventional_commits(self): + rule = ConventionalCommit() + + # No violations when using a correct type and format + for type in ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert", "ci", "build"]: + violations = rule.validate(type + ": föo", None) + self.assertListEqual([], violations) + + # assert violation on wrong type + expected_violation = RuleViolation( + "CT1", + "Title does not start with one of fix, feat, chore, docs, style, refactor, perf, test, revert, ci, build", + "bår: foo", + ) + violations = rule.validate("bår: foo", None) + self.assertListEqual([expected_violation], violations) + + # assert violation when use strange chars after correct type + expected_violation = RuleViolation( + "CT1", + "Title does not start with one of fix, feat, chore, docs, style, refactor, perf, test, revert, ci, build", + "feat_wrong_chars: föo", + ) + violations = rule.validate("feat_wrong_chars: föo", None) + self.assertListEqual([expected_violation], violations) + + # assert violation when use strange chars after correct type + expected_violation = RuleViolation( + "CT1", + "Title does not start with one of fix, feat, chore, docs, style, refactor, perf, test, revert, ci, build", + "feat_wrong_chars(scope): föo", + ) + violations = rule.validate("feat_wrong_chars(scope): föo", None) + self.assertListEqual([expected_violation], violations) + + # assert violation on wrong format + expected_violation = RuleViolation( + "CT1", + "Title does not follow ConventionalCommits.org format 'type(optional-scope): description'", + "fix föo", + ) + violations = rule.validate("fix föo", None) + self.assertListEqual([expected_violation], violations) + + # assert no violation when use ! for breaking changes without scope + violations = rule.validate("feat!: föo", None) + self.assertListEqual([], violations) + + # assert no violation when use ! for breaking changes with scope + violations = rule.validate("fix(scope)!: föo", None) + self.assertListEqual([], violations) + + # assert no violation when adding new type + rule = ConventionalCommit({"types": ["föo", "bär"]}) + for typ in ["föo", "bär"]: + violations = rule.validate(typ + ": hür dur", None) + self.assertListEqual([], violations) + + # assert violation when using incorrect type when types have been reconfigured + violations = rule.validate("fix: hür dur", None) + expected_violation = RuleViolation("CT1", "Title does not start with one of föo, bär", "fix: hür dur") + self.assertListEqual([expected_violation], violations) + + # assert no violation when adding new type named with numbers + rule = ConventionalCommit({"types": ["föo123", "123bär"]}) + for typ in ["föo123", "123bär"]: + violations = rule.validate(typ + ": hür dur", None) + self.assertListEqual([], violations) diff --git a/gitlint-core/gitlint/tests/contrib/rules/test_disallow_cleanup_commits.py b/gitlint-core/gitlint/tests/contrib/rules/test_disallow_cleanup_commits.py new file mode 100644 index 0000000..1983367 --- /dev/null +++ b/gitlint-core/gitlint/tests/contrib/rules/test_disallow_cleanup_commits.py @@ -0,0 +1,34 @@ +from gitlint.config import LintConfig +from gitlint.contrib.rules.disallow_cleanup_commits import DisallowCleanupCommits +from gitlint.rules import RuleViolation +from gitlint.tests.base import BaseTestCase + + +class ContribDisallowCleanupCommitsTest(BaseTestCase): + def test_enable(self): + # Test that rule can be enabled in config + for rule_ref in ["CC2", "contrib-disallow-cleanup-commits"]: + config = LintConfig() + config.contrib = [rule_ref] + self.assertIn(DisallowCleanupCommits(), config.rules) + + def test_disallow_fixup_squash_commit(self): + # No violations when no 'fixup!' line and no 'squash!' line is present + rule = DisallowCleanupCommits() + violations = rule.validate(self.gitcommit("Föobar\n\nMy Body")) + self.assertListEqual(violations, []) + + # Assert violation when 'fixup!' in title + violations = rule.validate(self.gitcommit("fixup! Föobar\n\nMy Body")) + expected_violation = RuleViolation("CC2", "Fixup commits are not allowed", line_nr=1) + self.assertListEqual(violations, [expected_violation]) + + # Assert violation when 'squash!' in title + violations = rule.validate(self.gitcommit("squash! Föobar\n\nMy Body")) + expected_violation = RuleViolation("CC2", "Squash commits are not allowed", line_nr=1) + self.assertListEqual(violations, [expected_violation]) + + # Assert violation when 'amend!' in title + violations = rule.validate(self.gitcommit("amend! Föobar\n\nMy Body")) + expected_violation = RuleViolation("CC2", "Amend commits are not allowed", line_nr=1) + self.assertListEqual(violations, [expected_violation]) diff --git a/gitlint-core/gitlint/tests/contrib/rules/test_signedoff_by.py b/gitlint-core/gitlint/tests/contrib/rules/test_signedoff_by.py new file mode 100644 index 0000000..bf526a0 --- /dev/null +++ b/gitlint-core/gitlint/tests/contrib/rules/test_signedoff_by.py @@ -0,0 +1,28 @@ +from gitlint.config import LintConfig +from gitlint.contrib.rules.signedoff_by import SignedOffBy +from gitlint.rules import RuleViolation +from gitlint.tests.base import BaseTestCase + + +class ContribSignedOffByTests(BaseTestCase): + def test_enable(self): + # Test that rule can be enabled in config + for rule_ref in ["CC1", "contrib-body-requires-signed-off-by"]: + config = LintConfig() + config.contrib = [rule_ref] + self.assertIn(SignedOffBy(), config.rules) + + def test_signedoff_by(self): + # No violations when 'Signed-off-by' line is present + rule = SignedOffBy() + violations = rule.validate(self.gitcommit("Föobar\n\nMy Body\nSigned-off-by: John Smith")) + self.assertListEqual([], violations) + + # Assert violation when no 'Signed-off-by' line is present + violations = rule.validate(self.gitcommit("Föobar\n\nMy Body")) + expected_violation = RuleViolation("CC1", "Body does not contain a 'Signed-off-by' line", line_nr=1) + self.assertListEqual(violations, [expected_violation]) + + # Assert violation when no 'Signed-off-by' in title but not in body + violations = rule.validate(self.gitcommit("Signed-off-by\n\nFöobar")) + self.assertListEqual(violations, [expected_violation]) diff --git a/gitlint-core/gitlint/tests/contrib/test_contrib_rules.py b/gitlint-core/gitlint/tests/contrib/test_contrib_rules.py new file mode 100644 index 0000000..b0372d8 --- /dev/null +++ b/gitlint-core/gitlint/tests/contrib/test_contrib_rules.py @@ -0,0 +1,69 @@ +import os + +from gitlint import rule_finder, rules +from gitlint.contrib import rules as contrib_rules +from gitlint.tests.base import BaseTestCase +from gitlint.tests.contrib import rules as contrib_tests + + +class ContribRuleTests(BaseTestCase): + CONTRIB_DIR = os.path.dirname(os.path.realpath(contrib_rules.__file__)) + + def test_contrib_tests_exist(self): + """Tests that every contrib rule file has an associated test file. + While this doesn't guarantee that every contrib rule has associated tests (as we don't check the content + of the tests file), it's a good leading indicator.""" + + contrib_tests_dir = os.path.dirname(os.path.realpath(contrib_tests.__file__)) + contrib_test_files = os.listdir(contrib_tests_dir) + + # Find all python files in the contrib dir and assert there's a corresponding test file + for filename in os.listdir(self.CONTRIB_DIR): + if filename.endswith(".py") and filename not in ["__init__.py"]: + expected_test_file = f"test_{filename}" + error_msg = ( + "Every Contrib Rule must have associated tests. " + f"Expected test file {os.path.join(contrib_tests_dir, expected_test_file)} not found." + ) + self.assertIn(expected_test_file, contrib_test_files, error_msg) + + def test_contrib_rule_naming_conventions(self): + """Tests that contrib rules follow certain naming conventions. + We can test for this at test time (and not during runtime like rule_finder.assert_valid_rule_class does) + because these are contrib rules: once they're part of gitlint they can't change unless they pass this test + again. + """ + rule_classes = rule_finder.find_rule_classes(self.CONTRIB_DIR) + + for clazz in rule_classes: + # Contrib rule names start with "contrib-" + self.assertTrue(clazz.name.startswith("contrib-")) + + # Contrib line rules id's start with "CL" + if issubclass(clazz, rules.LineRule): + if clazz.target == rules.CommitMessageTitle: + self.assertTrue(clazz.id.startswith("CT")) + elif clazz.target == rules.CommitMessageBody: + self.assertTrue(clazz.id.startswith("CB")) + + def test_contrib_rule_uniqueness(self): + """Tests that all contrib rules have unique identifiers. + We can test for this at test time (and not during runtime like rule_finder.assert_valid_rule_class does) + because these are contrib rules: once they're part of gitlint they can't change unless they pass this test + again. + """ + rule_classes = rule_finder.find_rule_classes(self.CONTRIB_DIR) + + # Not very efficient way of checking uniqueness, but it works :-) + class_names = [rule_class.name for rule_class in rule_classes] + class_ids = [rule_class.id for rule_class in rule_classes] + self.assertEqual(len(set(class_names)), len(class_names)) + self.assertEqual(len(set(class_ids)), len(class_ids)) + + def test_contrib_rule_instantiated(self): + """Tests that all contrib rules can be instantiated without errors.""" + rule_classes = rule_finder.find_rule_classes(self.CONTRIB_DIR) + + # No exceptions = what we want :-) + for rule_class in rule_classes: + rule_class() |