1
0
Fork 0
gitlint/gitlint/tests/rules/test_user_rules.py
Daniel Baumann 72676ec535
Merging upstream version 0.16.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-13 06:05:35 +01:00

256 lines
12 KiB
Python

# -*- 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 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'>]", str(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, "my-üser-commit-rule")
expected_option = options.IntOption('violation-count', 1, "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", "Commit violåtion 1", "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", "Commit violåtion 1", "Contënt 1", 1),
rules.RuleViolation("UC1", "Commit violåtion 2", "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", "Commit violåtion 1", "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([str(clazz) for clazz in classes])
expected = ["<class 'my_commit_rules.MyUserCommitRule'>", "<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.assertRaisesMessage(UserRuleError, "Invalid extra-path: föo/bar"):
find_rule_classes("föo/bar")
def test_assert_valid_rule_class(self):
class MyLineRuleClass(rules.LineRule):
id = 'UC1'
name = 'my-lïne-rule'
target = rules.CommitMessageTitle
def validate(self):
pass
class MyCommitRuleClass(rules.CommitRule):
id = 'UC2'
name = 'my-cömmit-rule'
def validate(self):
pass
class MyConfigurationRuleClass(rules.ConfigurationRule):
id = 'UC3'
name = 'my-cönfiguration-rule'
def apply(self):
pass
# Just assert that no error is raised
self.assertIsNone(assert_valid_rule_class(MyLineRuleClass))
self.assertIsNone(assert_valid_rule_class(MyCommitRuleClass))
self.assertIsNone(assert_valid_rule_class(MyConfigurationRuleClass))
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.assertRaisesMessage(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:
pass
expected_msg = "User-defined rule class 'MyRuleClass' must extend from gitlint.rules.LineRule, " + \
"gitlint.rules.CommitRule or gitlint.rules.ConfigurationRule"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_id(self):
for parent_class in [rules.LineRule, rules.CommitRule]:
class MyRuleClass(parent_class):
pass
# Rule class must have an id
expected_msg = "User-defined rule class 'MyRuleClass' must have an 'id' attribute"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# Rule ids must be non-empty
MyRuleClass.id = ""
with self.assertRaisesMessage(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", "I"]:
MyRuleClass.id = letter + "1"
expected_msg = f"The id '{letter}' of 'MyRuleClass' is invalid. " + \
"Gitlint reserves ids starting with R,T,B,M,I"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_name(self):
for parent_class in [rules.LineRule, rules.CommitRule]:
class MyRuleClass(parent_class):
id = "UC1"
# Rule class must have an name
expected_msg = "User-defined rule class 'MyRuleClass' must have a 'name' attribute"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# Rule names must be non-empty
MyRuleClass.name = ""
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_option_spec(self):
for parent_class in [rules.LineRule, rules.CommitRule]:
class MyRuleClass(parent_class):
id = "UC1"
name = "my-rüle-class"
# if set, option_spec must be a list of gitlint options
MyRuleClass.options_spec = "föo"
expected_msg = "The options_spec attribute of user-defined rule class 'MyRuleClass' must be a list " + \
"of gitlint.options.RuleOption"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# option_spec is a list, but not of gitlint options
MyRuleClass.options_spec = ["föo", 123] # pylint: disable=bad-option-value,redefined-variable-type
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_validate(self):
baseclasses = [rules.LineRule, rules.CommitRule]
for clazz in baseclasses:
class MyRuleClass(clazz):
id = "UC1"
name = "my-rüle-class"
with self.assertRaisesMessage(UserRuleError,
"User-defined rule class 'MyRuleClass' must have a 'validate' method"):
assert_valid_rule_class(MyRuleClass)
# validate attribute - not a method
MyRuleClass.validate = "föo"
with self.assertRaisesMessage(UserRuleError,
"User-defined rule class 'MyRuleClass' must have a 'validate' method"):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_apply(self):
class MyRuleClass(rules.ConfigurationRule):
id = "UCR1"
name = "my-rüle-class"
expected_msg = "User-defined Configuration rule class 'MyRuleClass' must have an 'apply' method"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# validate attribute - not a method
MyRuleClass.validate = "föo"
with self.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
def test_assert_valid_rule_class_negative_target(self):
class MyRuleClass(rules.LineRule):
id = "UC1"
name = "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.assertRaisesMessage(UserRuleError, expected_msg):
assert_valid_rule_class(MyRuleClass)
# invalid target
MyRuleClass.target = "föo"
with self.assertRaisesMessage(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))