diff options
Diffstat (limited to 'gitlint/tests/rules')
-rw-r--r-- | gitlint/tests/rules/__init__.py | 0 | ||||
-rw-r--r-- | gitlint/tests/rules/test_body_rules.py | 180 | ||||
-rw-r--r-- | gitlint/tests/rules/test_configuration_rules.py | 71 | ||||
-rw-r--r-- | gitlint/tests/rules/test_meta_rules.py | 50 | ||||
-rw-r--r-- | gitlint/tests/rules/test_rules.py | 18 | ||||
-rw-r--r-- | gitlint/tests/rules/test_title_rules.py | 154 | ||||
-rw-r--r-- | gitlint/tests/rules/test_user_rules.py | 223 |
7 files changed, 696 insertions, 0 deletions
diff --git a/gitlint/tests/rules/__init__.py b/gitlint/tests/rules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/gitlint/tests/rules/__init__.py diff --git a/gitlint/tests/rules/test_body_rules.py b/gitlint/tests/rules/test_body_rules.py new file mode 100644 index 0000000..fcb1b30 --- /dev/null +++ b/gitlint/tests/rules/test_body_rules.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +from gitlint.tests.base import BaseTestCase +from gitlint import rules + + +class BodyRuleTests(BaseTestCase): + def test_max_line_length(self): + rule = rules.BodyMaxLineLength() + + # assert no error + violation = rule.validate(u"å" * 80, None) + self.assertIsNone(violation) + + # assert error on line length > 80 + expected_violation = rules.RuleViolation("B1", "Line exceeds max length (81>80)", u"å" * 81) + violations = rule.validate(u"å" * 81, None) + self.assertListEqual(violations, [expected_violation]) + + # set line length to 120, and check no violation on length 73 + rule = rules.BodyMaxLineLength({'line-length': 120}) + violations = rule.validate(u"å" * 73, None) + self.assertIsNone(violations) + + # assert raise on 121 + expected_violation = rules.RuleViolation("B1", "Line exceeds max length (121>120)", u"å" * 121) + violations = rule.validate(u"å" * 121, None) + self.assertListEqual(violations, [expected_violation]) + + def test_trailing_whitespace(self): + rule = rules.BodyTrailingWhitespace() + + # assert no error + violations = rule.validate(u"å", None) + self.assertIsNone(violations) + + # trailing space + expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", u"å ") + violations = rule.validate(u"å ", None) + self.assertListEqual(violations, [expected_violation]) + + # trailing tab + expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", u"å\t") + violations = rule.validate(u"å\t", None) + self.assertListEqual(violations, [expected_violation]) + + def test_hard_tabs(self): + rule = rules.BodyHardTab() + + # assert no error + violations = rule.validate(u"This is ã test", None) + self.assertIsNone(violations) + + # contains hard tab + expected_violation = rules.RuleViolation("B3", "Line contains hard tab characters (\\t)", u"This is å\ttest") + violations = rule.validate(u"This is å\ttest", None) + self.assertListEqual(violations, [expected_violation]) + + def test_body_first_line_empty(self): + rule = rules.BodyFirstLineEmpty() + + # assert no error + commit = self.gitcommit(u"Tïtle\n\nThis is the secōnd body line") + violations = rule.validate(commit) + self.assertIsNone(violations) + + # second line not empty + expected_violation = rules.RuleViolation("B4", "Second line is not empty", u"nöt empty", 2) + + commit = self.gitcommit(u"Tïtle\nnöt empty\nThis is the secönd body line") + violations = rule.validate(commit) + self.assertListEqual(violations, [expected_violation]) + + def test_body_min_length(self): + rule = rules.BodyMinLength() + + # assert no error - body is long enough + commit = self.gitcommit("Title\n\nThis is the second body line\n") + + violations = rule.validate(commit) + self.assertIsNone(violations) + + # assert no error - no body + commit = self.gitcommit(u"Tïtle\n") + violations = rule.validate(commit) + self.assertIsNone(violations) + + # body is too short + expected_violation = rules.RuleViolation("B5", "Body message is too short (8<20)", u"töoshort", 3) + + commit = self.gitcommit(u"Tïtle\n\ntöoshort\n") + violations = rule.validate(commit) + self.assertListEqual(violations, [expected_violation]) + + # assert error - short across multiple lines + expected_violation = rules.RuleViolation("B5", "Body message is too short (11<20)", u"secöndthïrd", 3) + commit = self.gitcommit(u"Tïtle\n\nsecönd\nthïrd\n") + violations = rule.validate(commit) + self.assertListEqual(violations, [expected_violation]) + + # set line length to 120, and check violation on length 21 + expected_violation = rules.RuleViolation("B5", "Body message is too short (21<120)", u"å" * 21, 3) + + rule = rules.BodyMinLength({'min-length': 120}) + commit = self.gitcommit(u"Title\n\n%s\n" % (u"å" * 21)) + violations = rule.validate(commit) + self.assertListEqual(violations, [expected_violation]) + + # Make sure we don't get the error if the body-length is exactly the min-length + rule = rules.BodyMinLength({'min-length': 8}) + commit = self.gitcommit(u"Tïtle\n\n%s\n" % (u"å" * 8)) + violations = rule.validate(commit) + self.assertIsNone(violations) + + def test_body_missing(self): + rule = rules.BodyMissing() + + # assert no error - body is present + commit = self.gitcommit(u"Tïtle\n\nThis ïs the first body line\n") + violations = rule.validate(commit) + self.assertIsNone(violations) + + # body is too short + expected_violation = rules.RuleViolation("B6", "Body message is missing", None, 3) + + commit = self.gitcommit(u"Tïtle\n") + violations = rule.validate(commit) + self.assertListEqual(violations, [expected_violation]) + + def test_body_missing_merge_commit(self): + rule = rules.BodyMissing() + + # assert no error - merge commit + commit = self.gitcommit(u"Merge: Tïtle\n") + violations = rule.validate(commit) + self.assertIsNone(violations) + + # assert error for merge commits if ignore-merge-commits is disabled + rule = rules.BodyMissing({'ignore-merge-commits': False}) + violations = rule.validate(commit) + expected_violation = rules.RuleViolation("B6", "Body message is missing", None, 3) + self.assertListEqual(violations, [expected_violation]) + + def test_body_changed_file_mention(self): + rule = rules.BodyChangedFileMention() + + # assert no error when no files have changed and no files need to be mentioned + commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py") + violations = rule.validate(commit) + self.assertIsNone(violations) + + # assert no error when no files have changed but certain files need to be mentioned on change + rule = rules.BodyChangedFileMention({'files': u"bar.txt,föo/test.py"}) + commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py") + violations = rule.validate(commit) + self.assertIsNone(violations) + + # assert no error if a file has changed and is mentioned + commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py", [u"föo/test.py"]) + violations = rule.validate(commit) + self.assertIsNone(violations) + + # assert no error if multiple files have changed and are mentioned + commit_msg = u"This is a test\n\nHere is a mention of föo/test.py\nAnd here is a mention of bar.txt" + commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"]) + violations = rule.validate(commit) + self.assertIsNone(violations) + + # assert error if file has changed and is not mentioned + commit_msg = u"This is a test\n\nHere is å mention of\nAnd here is a mention of bar.txt" + commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"]) + violations = rule.validate(commit) + expected_violation = rules.RuleViolation("B7", u"Body does not mention changed file 'föo/test.py'", None, 4) + self.assertEqual([expected_violation], violations) + + # assert multiple errors if multiple files habe changed and are not mentioned + commit_msg = u"This is å test\n\nHere is a mention of\nAnd here is a mention of" + commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"]) + violations = rule.validate(commit) + expected_violation_2 = rules.RuleViolation("B7", "Body does not mention changed file 'bar.txt'", None, 4) + self.assertEqual([expected_violation_2, expected_violation], violations) diff --git a/gitlint/tests/rules/test_configuration_rules.py b/gitlint/tests/rules/test_configuration_rules.py new file mode 100644 index 0000000..73d42f3 --- /dev/null +++ b/gitlint/tests/rules/test_configuration_rules.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +from gitlint.tests.base import BaseTestCase +from gitlint import rules +from gitlint.config import LintConfig + + +class ConfigurationRuleTests(BaseTestCase): + def test_ignore_by_title(self): + commit = self.gitcommit(u"Releäse\n\nThis is the secōnd body line") + + # No regex specified -> Config shouldn't be changed + rule = rules.IgnoreByTitle() + config = LintConfig() + rule.apply(config, commit) + self.assertEqual(config, LintConfig()) + self.assert_logged([]) # nothing logged -> nothing ignored + + # Matching regex -> expect config to ignore all rules + rule = rules.IgnoreByTitle({"regex": u"^Releäse(.*)"}) + expected_config = LintConfig() + expected_config.ignore = "all" + rule.apply(config, commit) + self.assertEqual(config, expected_config) + + expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \ + u"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: all" + self.assert_log_contains(expected_log_message) + + # Matching regex with specific ignore + rule = rules.IgnoreByTitle({"regex": u"^Releäse(.*)", + "ignore": "T1,B2"}) + expected_config = LintConfig() + expected_config.ignore = "T1,B2" + rule.apply(config, commit) + self.assertEqual(config, expected_config) + + expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \ + u"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: T1,B2" + + def test_ignore_by_body(self): + commit = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line") + + # No regex specified -> Config shouldn't be changed + rule = rules.IgnoreByBody() + config = LintConfig() + rule.apply(config, commit) + self.assertEqual(config, LintConfig()) + self.assert_logged([]) # nothing logged -> nothing ignored + + # Matching regex -> expect config to ignore all rules + rule = rules.IgnoreByBody({"regex": u"(.*)relëase(.*)"}) + expected_config = LintConfig() + expected_config.ignore = "all" + rule.apply(config, commit) + self.assertEqual(config, expected_config) + + expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \ + u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)'," + \ + u" ignoring rules: all" + self.assert_log_contains(expected_log_message) + + # Matching regex with specific ignore + rule = rules.IgnoreByBody({"regex": u"(.*)relëase(.*)", + "ignore": "T1,B2"}) + expected_config = LintConfig() + expected_config.ignore = "T1,B2" + rule.apply(config, commit) + self.assertEqual(config, expected_config) + + expected_log_message = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \ + u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)', ignoring rules: T1,B2" diff --git a/gitlint/tests/rules/test_meta_rules.py b/gitlint/tests/rules/test_meta_rules.py new file mode 100644 index 0000000..c94b8b3 --- /dev/null +++ b/gitlint/tests/rules/test_meta_rules.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +from gitlint.tests.base import BaseTestCase +from gitlint.rules import AuthorValidEmail, RuleViolation + + +class MetaRuleTests(BaseTestCase): + def test_author_valid_email_rule(self): + rule = AuthorValidEmail() + + # valid email addresses + valid_email_addresses = [u"föo@bar.com", u"Jöhn.Doe@bar.com", u"jöhn+doe@bar.com", u"jöhn/doe@bar.com", + u"jöhn.doe@subdomain.bar.com"] + for email in valid_email_addresses: + commit = self.gitcommit(u"", author_email=email) + violations = rule.validate(commit) + self.assertIsNone(violations) + + # No email address (=allowed for now, as gitlint also lints messages passed via stdin that don't have an + # email address) + commit = self.gitcommit(u"") + violations = rule.validate(commit) + self.assertIsNone(violations) + + # Invalid email addresses: no TLD, no domain, no @, space anywhere (=valid but not allowed by gitlint) + invalid_email_addresses = [u"föo@bar", u"JöhnDoe", u"Jöhn Doe", u"Jöhn Doe@foo.com", u" JöhnDoe@foo.com", + u"JöhnDoe@ foo.com", u"JöhnDoe@foo. com", u"JöhnDoe@foo. com", u"@bår.com", + u"föo@.com"] + for email in invalid_email_addresses: + commit = self.gitcommit(u"", author_email=email) + violations = rule.validate(commit) + self.assertListEqual(violations, + [RuleViolation("M1", "Author email for commit is invalid", email)]) + + def test_author_valid_email_rule_custom_regex(self): + # Custom domain + rule = AuthorValidEmail({'regex': u"[^@]+@bår.com"}) + valid_email_addresses = [ + u"föo@bår.com", u"Jöhn.Doe@bår.com", u"jöhn+doe@bår.com", u"jöhn/doe@bår.com"] + for email in valid_email_addresses: + commit = self.gitcommit(u"", author_email=email) + violations = rule.validate(commit) + self.assertIsNone(violations) + + # Invalid email addresses + invalid_email_addresses = [u"föo@hur.com"] + for email in invalid_email_addresses: + commit = self.gitcommit(u"", author_email=email) + violations = rule.validate(commit) + self.assertListEqual(violations, + [RuleViolation("M1", "Author email for commit is invalid", email)]) diff --git a/gitlint/tests/rules/test_rules.py b/gitlint/tests/rules/test_rules.py new file mode 100644 index 0000000..89caa27 --- /dev/null +++ b/gitlint/tests/rules/test_rules.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from gitlint.tests.base import BaseTestCase +from gitlint.rules import Rule, RuleViolation + + +class RuleTests(BaseTestCase): + + def test_rule_equality(self): + self.assertEqual(Rule(), Rule()) + # Ensure rules are not equal if they differ on their attributes + for attr in ["id", "name", "target", "options"]: + rule = Rule() + setattr(rule, attr, u"åbc") + self.assertNotEqual(Rule(), rule) + + def test_rule_violation_equality(self): + violation1 = RuleViolation(u"ïd1", u"My messåge", u"My cöntent", 1) + self.object_equality_test(violation1, ["rule_id", "message", "content", "line_nr"]) diff --git a/gitlint/tests/rules/test_title_rules.py b/gitlint/tests/rules/test_title_rules.py new file mode 100644 index 0000000..07d2323 --- /dev/null +++ b/gitlint/tests/rules/test_title_rules.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +from gitlint.tests.base import BaseTestCase +from gitlint.rules import TitleMaxLength, TitleTrailingWhitespace, TitleHardTab, TitleMustNotContainWord, \ + TitleTrailingPunctuation, TitleLeadingWhitespace, TitleRegexMatches, RuleViolation + + +class TitleRuleTests(BaseTestCase): + def test_max_line_length(self): + rule = TitleMaxLength() + + # assert no error + violation = rule.validate(u"å" * 72, None) + self.assertIsNone(violation) + + # assert error on line length > 72 + expected_violation = RuleViolation("T1", "Title exceeds max length (73>72)", u"å" * 73) + violations = rule.validate(u"å" * 73, None) + self.assertListEqual(violations, [expected_violation]) + + # set line length to 120, and check no violation on length 73 + rule = TitleMaxLength({'line-length': 120}) + violations = rule.validate(u"å" * 73, None) + self.assertIsNone(violations) + + # assert raise on 121 + expected_violation = RuleViolation("T1", "Title exceeds max length (121>120)", u"å" * 121) + violations = rule.validate(u"å" * 121, None) + self.assertListEqual(violations, [expected_violation]) + + def test_trailing_whitespace(self): + rule = TitleTrailingWhitespace() + + # assert no error + violations = rule.validate(u"å", None) + self.assertIsNone(violations) + + # trailing space + expected_violation = RuleViolation("T2", "Title has trailing whitespace", u"å ") + violations = rule.validate(u"å ", None) + self.assertListEqual(violations, [expected_violation]) + + # trailing tab + expected_violation = RuleViolation("T2", "Title has trailing whitespace", u"å\t") + violations = rule.validate(u"å\t", None) + self.assertListEqual(violations, [expected_violation]) + + def test_hard_tabs(self): + rule = TitleHardTab() + + # assert no error + violations = rule.validate(u"This is å test", None) + self.assertIsNone(violations) + + # contains hard tab + expected_violation = RuleViolation("T4", "Title contains hard tab characters (\\t)", u"This is å\ttest") + violations = rule.validate(u"This is å\ttest", None) + self.assertListEqual(violations, [expected_violation]) + + def test_trailing_punctuation(self): + rule = TitleTrailingPunctuation() + + # assert no error + violations = rule.validate(u"This is å test", None) + self.assertIsNone(violations) + + # assert errors for different punctuations + punctuation = u"?:!.,;" + for char in punctuation: + line = u"This is å test" + char # note that make sure to include some unicode! + gitcontext = self.gitcontext(line) + expected_violation = RuleViolation("T3", u"Title has trailing punctuation ({0})".format(char), line) + violations = rule.validate(line, gitcontext) + self.assertListEqual(violations, [expected_violation]) + + def test_title_must_not_contain_word(self): + rule = TitleMustNotContainWord() + + # no violations + violations = rule.validate(u"This is å test", None) + self.assertIsNone(violations) + + # no violation if WIP occurs inside a wor + violations = rule.validate(u"This is å wiping test", None) + self.assertIsNone(violations) + + # match literally + violations = rule.validate(u"WIP This is å test", None) + expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", + u"WIP This is å test") + self.assertListEqual(violations, [expected_violation]) + + # match case insensitive + violations = rule.validate(u"wip This is å test", None) + expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", + u"wip This is å test") + self.assertListEqual(violations, [expected_violation]) + + # match if there is a colon after the word + violations = rule.validate(u"WIP:This is å test", None) + expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", + u"WIP:This is å test") + self.assertListEqual(violations, [expected_violation]) + + # match multiple words + rule = TitleMustNotContainWord({'words': u"wip,test,å"}) + violations = rule.validate(u"WIP:This is å test", None) + expected_violation = RuleViolation("T5", "Title contains the word 'wip' (case-insensitive)", + u"WIP:This is å test") + expected_violation2 = RuleViolation("T5", "Title contains the word 'test' (case-insensitive)", + u"WIP:This is å test") + expected_violation3 = RuleViolation("T5", u"Title contains the word 'å' (case-insensitive)", + u"WIP:This is å test") + self.assertListEqual(violations, [expected_violation, expected_violation2, expected_violation3]) + + def test_leading_whitespace(self): + rule = TitleLeadingWhitespace() + + # assert no error + violations = rule.validate("a", None) + self.assertIsNone(violations) + + # leading space + expected_violation = RuleViolation("T6", "Title has leading whitespace", " a") + violations = rule.validate(" a", None) + self.assertListEqual(violations, [expected_violation]) + + # leading tab + expected_violation = RuleViolation("T6", "Title has leading whitespace", "\ta") + violations = rule.validate("\ta", None) + self.assertListEqual(violations, [expected_violation]) + + # unicode test + expected_violation = RuleViolation("T6", "Title has leading whitespace", u" ☺") + violations = rule.validate(u" ☺", None) + self.assertListEqual(violations, [expected_violation]) + + def test_regex_matches(self): + commit = self.gitcommit(u"US1234: åbc\n") + + # assert no violation on default regex (=everything allowed) + rule = TitleRegexMatches() + violations = rule.validate(commit.message.title, commit) + self.assertIsNone(violations) + + # assert no violation on matching regex + rule = TitleRegexMatches({'regex': u"^US[0-9]*: å"}) + violations = rule.validate(commit.message.title, commit) + self.assertIsNone(violations) + + # assert violation when no matching regex + rule = TitleRegexMatches({'regex': u"^UÅ[0-9]*"}) + violations = rule.validate(commit.message.title, commit) + expected_violation = RuleViolation("T7", u"Title does not match regex (^UÅ[0-9]*)", u"US1234: åbc") + self.assertListEqual(violations, [expected_violation]) diff --git a/gitlint/tests/rules/test_user_rules.py b/gitlint/tests/rules/test_user_rules.py new file mode 100644 index 0000000..57c03a0 --- /dev/null +++ b/gitlint/tests/rules/test_user_rules.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- + +import os +import sys + +from gitlint.tests.base import BaseTestCase +from gitlint.rule_finder import find_rule_classes, assert_valid_rule_class +from gitlint.rules import UserRuleError +from gitlint.utils import ustr + +from gitlint import options, rules + + +class UserRuleTests(BaseTestCase): + def test_find_rule_classes(self): + # Let's find some user classes! + user_rule_path = self.get_sample_path("user_rules") + classes = find_rule_classes(user_rule_path) + + # Compare string representations because we can't import MyUserCommitRule here since samples/user_rules is not + # a proper python package + # Note that the following check effectively asserts that: + # - There is only 1 rule recognized and it is MyUserCommitRule + # - Other non-python files in the directory are ignored + # - Other members of the my_commit_rules module are ignored + # (such as func_should_be_ignored, global_variable_should_be_ignored) + # - Rules are loaded non-recursively (user_rules/import_exception directory is ignored) + self.assertEqual("[<class 'my_commit_rules.MyUserCommitRule'>]", ustr(classes)) + + # Assert that we added the new user_rules directory to the system path and modules + self.assertIn(user_rule_path, sys.path) + self.assertIn("my_commit_rules", sys.modules) + + # Do some basic asserts on our user rule + self.assertEqual(classes[0].id, "UC1") + self.assertEqual(classes[0].name, u"my-üser-commit-rule") + expected_option = options.IntOption('violation-count', 1, u"Number of violåtions to return") + self.assertListEqual(classes[0].options_spec, [expected_option]) + self.assertTrue(hasattr(classes[0], "validate")) + + # Test that we can instantiate the class and can execute run the validate method and that it returns the + # expected result + rule_class = classes[0]() + violations = rule_class.validate("false-commit-object (ignored)") + self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1)]) + + # Have it return more violations + rule_class.options['violation-count'].value = 2 + violations = rule_class.validate("false-commit-object (ignored)") + self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1), + rules.RuleViolation("UC1", u"Commit violåtion 2", u"Contënt 2", 2)]) + + def test_extra_path_specified_by_file(self): + # Test that find_rule_classes can handle an extra path given as a file name instead of a directory + user_rule_path = self.get_sample_path("user_rules") + user_rule_module = os.path.join(user_rule_path, "my_commit_rules.py") + classes = find_rule_classes(user_rule_module) + + rule_class = classes[0]() + violations = rule_class.validate("false-commit-object (ignored)") + self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1)]) + + def test_rules_from_init_file(self): + # Test that we can import rules that are defined in __init__.py files + # This also tests that we can import rules from python packages. This use to cause issues with pypy + # So this is also a regression test for that. + user_rule_path = self.get_sample_path(os.path.join("user_rules", "parent_package")) + classes = find_rule_classes(user_rule_path) + + # convert classes to strings and sort them so we can compare them + class_strings = sorted([ustr(clazz) for clazz in classes]) + expected = [u"<class 'my_commit_rules.MyUserCommitRule'>", u"<class 'parent_package.InitFileRule'>"] + self.assertListEqual(class_strings, expected) + + def test_empty_user_classes(self): + # Test that we don't find rules if we scan a different directory + user_rule_path = self.get_sample_path("config") + classes = find_rule_classes(user_rule_path) + self.assertListEqual(classes, []) + + # Importantly, ensure that the directory is not added to the syspath as this happens only when we actually + # find modules + self.assertNotIn(user_rule_path, sys.path) + + def test_failed_module_import(self): + # test importing a bogus module + user_rule_path = self.get_sample_path("user_rules/import_exception") + # We don't check the entire error message because that is different based on the python version and underlying + # operating system + expected_msg = "Error while importing extra-path module 'invalid_python'" + with self.assertRaisesRegex(UserRuleError, expected_msg): + find_rule_classes(user_rule_path) + + def test_find_rule_classes_nonexisting_path(self): + with self.assertRaisesRegex(UserRuleError, u"Invalid extra-path: föo/bar"): + find_rule_classes(u"föo/bar") + + def test_assert_valid_rule_class(self): + class MyLineRuleClass(rules.LineRule): + id = 'UC1' + name = u'my-lïne-rule' + target = rules.CommitMessageTitle + + def validate(self): + pass + + class MyCommitRuleClass(rules.CommitRule): + id = 'UC2' + name = u'my-cömmit-rule' + + def validate(self): + pass + + # Just assert that no error is raised + self.assertIsNone(assert_valid_rule_class(MyLineRuleClass)) + self.assertIsNone(assert_valid_rule_class(MyCommitRuleClass)) + + def test_assert_valid_rule_class_negative(self): + # general test to make sure that incorrect rules will raise an exception + user_rule_path = self.get_sample_path("user_rules/incorrect_linerule") + with self.assertRaisesRegex(UserRuleError, + "User-defined rule class 'MyUserLineRule' must have a 'validate' method"): + find_rule_classes(user_rule_path) + + def test_assert_valid_rule_class_negative_parent(self): + # rule class must extend from LineRule or CommitRule + class MyRuleClass(object): + pass + + expected_msg = "User-defined rule class 'MyRuleClass' must extend from gitlint.rules.LineRule " + \ + "or gitlint.rules.CommitRule" + with self.assertRaisesRegex(UserRuleError, expected_msg): + assert_valid_rule_class(MyRuleClass) + + def test_assert_valid_rule_class_negative_id(self): + class MyRuleClass(rules.LineRule): + pass + + # Rule class must have an id + expected_msg = "User-defined rule class 'MyRuleClass' must have an 'id' attribute" + with self.assertRaisesRegex(UserRuleError, expected_msg): + assert_valid_rule_class(MyRuleClass) + + # Rule ids must be non-empty + MyRuleClass.id = "" + with self.assertRaisesRegex(UserRuleError, expected_msg): + assert_valid_rule_class(MyRuleClass) + + # Rule ids must not start with one of the reserved id letters + for letter in ["T", "R", "B", "M"]: + MyRuleClass.id = letter + "1" + expected_msg = "The id '{0}' of 'MyRuleClass' is invalid. Gitlint reserves ids starting with R,T,B,M" + with self.assertRaisesRegex(UserRuleError, expected_msg.format(letter)): + assert_valid_rule_class(MyRuleClass) + + def test_assert_valid_rule_class_negative_name(self): + class MyRuleClass(rules.LineRule): + id = "UC1" + + # Rule class must have an name + expected_msg = "User-defined rule class 'MyRuleClass' must have a 'name' attribute" + with self.assertRaisesRegex(UserRuleError, expected_msg): + assert_valid_rule_class(MyRuleClass) + + # Rule names must be non-empty + MyRuleClass.name = "" + with self.assertRaisesRegex(UserRuleError, expected_msg): + assert_valid_rule_class(MyRuleClass) + + def test_assert_valid_rule_class_negative_option_spec(self): + class MyRuleClass(rules.LineRule): + id = "UC1" + name = u"my-rüle-class" + + # if set, option_spec must be a list of gitlint options + MyRuleClass.options_spec = u"föo" + expected_msg = "The options_spec attribute of user-defined rule class 'MyRuleClass' must be a list " + \ + "of gitlint.options.RuleOption" + with self.assertRaisesRegex(UserRuleError, expected_msg): + assert_valid_rule_class(MyRuleClass) + + # option_spec is a list, but not of gitlint options + MyRuleClass.options_spec = [u"föo", 123] # pylint: disable=bad-option-value,redefined-variable-type + with self.assertRaisesRegex(UserRuleError, expected_msg): + assert_valid_rule_class(MyRuleClass) + + def test_assert_valid_rule_class_negative_validate(self): + class MyRuleClass(rules.LineRule): + id = "UC1" + name = u"my-rüle-class" + + with self.assertRaisesRegex(UserRuleError, + "User-defined rule class 'MyRuleClass' must have a 'validate' method"): + assert_valid_rule_class(MyRuleClass) + + # validate attribute - not a method + MyRuleClass.validate = u"föo" + with self.assertRaisesRegex(UserRuleError, + "User-defined rule class 'MyRuleClass' must have a 'validate' method"): + assert_valid_rule_class(MyRuleClass) + + def test_assert_valid_rule_class_negative_target(self): + class MyRuleClass(rules.LineRule): + id = "UC1" + name = u"my-rüle-class" + + def validate(self): + pass + + # no target + expected_msg = "The target attribute of the user-defined LineRule class 'MyRuleClass' must be either " + \ + "gitlint.rules.CommitMessageTitle or gitlint.rules.CommitMessageBody" + with self.assertRaisesRegex(UserRuleError, expected_msg): + assert_valid_rule_class(MyRuleClass) + + # invalid target + MyRuleClass.target = u"föo" + with self.assertRaisesRegex(UserRuleError, expected_msg): + assert_valid_rule_class(MyRuleClass) + + # valid target, no exception should be raised + MyRuleClass.target = rules.CommitMessageTitle # pylint: disable=bad-option-value,redefined-variable-type + self.assertIsNone(assert_valid_rule_class(MyRuleClass)) |