def test_gitcontext_ignore_specific(self): # ignore specific rules config = LintConfig() context = self.gitcontext("test\ngitlint-ignore: T1, body-hard-tab") config.apply_config_from_commit(context.commits[-1]) expected_rules = [rule for rule in config.rules if rule.id not in ["T1", "body-hard-tab"]] self.assertEqual(config.rules, expected_rules)
def test_uninstall_commit_msg_hook_negative(self, isdir, path_exists, remove): lint_config = LintConfig() lint_config.target = self.SAMPLES_DIR # mock that the current directory is not a git repo isdir.return_value = False expected_msg = "{0} is not a git repository".format(self.SAMPLES_DIR) with self.assertRaisesRegex(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) isdir.assert_called_with(os.path.join(self.SAMPLES_DIR, '.git/hooks')) path_exists.assert_not_called() remove.assert_not_called() # mock that there is no commit hook present isdir.return_value = True path_exists.return_value = False expected_dst = os.path.join(self.SAMPLES_DIR, COMMIT_MSG_HOOK_DST_PATH) expected_msg = "There is no commit-msg hook present in {0}.".format(expected_dst) with self.assertRaisesRegex(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) isdir.assert_called_with(os.path.join(self.SAMPLES_DIR, '.git/hooks')) 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 path_exists.return_value = True read_data = "#!/bin/sh\nfoo" expected_dst = os.path.join(self.SAMPLES_DIR, COMMIT_MSG_HOOK_DST_PATH) expected_msg = "The commit-msg hook in {0} was not installed by gitlint ".format(expected_dst) + \ r"\(or it was modified\).\nUninstallation of 3th party or modified gitlint hooks " + \ "is not supported." with patch('gitlint.hooks.open', mock_open(read_data=read_data), create=True): with self.assertRaisesRegex(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) remove.assert_not_called()
def test_load_config_from_file_negative(self): # bad config file load foo_path = self.get_sample_path("foo") with self.assertRaisesRegexp(LintConfigError, "Invalid file path: {0}".format(foo_path)): LintConfig.load_from_file(foo_path) # error during file parsing path = self.get_sample_path("config/no-sections") expected_error_msg = "File contains no section headers." with self.assertRaisesRegexp(LintConfigError, expected_error_msg): LintConfig.load_from_file(path) # non-existing rule path = self.get_sample_path("config/nonexisting-rule") expected_error_msg = "No such rule 'foobar'" with self.assertRaisesRegexp(LintConfigError, expected_error_msg): LintConfig.load_from_file(path) # non-existing option path = self.get_sample_path("config/nonexisting-option") expected_error_msg = "Rule 'title-max-length' has no option 'foobar'" with self.assertRaisesRegexp(LintConfigError, expected_error_msg): LintConfig.load_from_file(path) # invalid option value path = self.get_sample_path("config/invalid-option-value") expected_error_msg = "'foo' is not a valid value for option 'title-max-length.line-length'. " + \ "Option 'line-length' must be a positive integer \(current value: 'foo'\)." with self.assertRaisesRegexp(LintConfigError, expected_error_msg): LintConfig.load_from_file(path)
def test_ignore_independent_from_rules(self): # Test that the lintconfig rules are not modified when setting config.ignore # This was different in the past, this test is mostly here to catch regressions config = LintConfig() original_rules = config.rules config.ignore = ["T1", "T2"] self.assertEqual(config.ignore, ["T1", "T2"]) self.assertListEqual(config.rules, original_rules)
def test_lint_sample4(self): gitcontext = self.gitcontext(self.get_sample("commit_message/sample4")) lintconfig = LintConfig() lintconfig.apply_config_from_commit(gitcontext.commits[-1]) linter = GitLinter(lintconfig) violations = linter.lint(gitcontext.commits[-1], gitcontext) # expect no violations because sample4 has a 'gitlint: disable line' expected = [] self.assertListEqual(violations, expected)
def test_set_rule_option(self): config = LintConfig() # assert default title line-length self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 72) # change line length and assert it is set config.set_rule_option('title-max-length', 'line-length', 60) self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 60)
def test_install_commit_msg_hook(self, isdir, path_exists, copy, stat, chmod): lint_config = LintConfig() lint_config.target = self.SAMPLES_DIR expected_dst = os.path.join(self.SAMPLES_DIR, COMMIT_MSG_HOOK_DST_PATH) GitHookInstaller.install_commit_msg_hook(lint_config) isdir.assert_called_with(self.SAMPLES_DIR + '/.git/hooks') path_exists.assert_called_once_with(expected_dst) copy.assert_called_once_with(COMMIT_MSG_HOOK_SRC_PATH, expected_dst) stat.assert_called_once_with(expected_dst) chmod.assert_called_once_with(expected_dst, ANY)
def test_lint_ignore(self): lint_config = LintConfig() lint_config.ignore = ["T1", "T3", "T4", "T5", "T6", "B1", "B2"] linter = GitLinter(lint_config) violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample3"))) expected = [RuleViolation("B4", "Second line is not empty", "This line should be empty", 2), RuleViolation("B3", "Line contains hard tab characters (\\t)", "This line has a trailing tab.\t", 5)] self.assertListEqual(violations, expected)
def test_lint_ignore(self): lint_config = LintConfig() lint_config.ignore = ["T1", "T3", "T4", "T5", "T6", "B1", "B2"] linter = GitLinter(lint_config) violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample3"))) expected = [RuleViolation("B4", "Second line is not empty", "This line should be empty", 2), RuleViolation("B3", "Line contains hard tab characters (\\t)", u"This line has a tråiling tab.\t", 5)] self.assertListEqual(violations, expected)
def test_uninstall_commit_msg_hook(self, isdir, path_exists, remove): lint_config = LintConfig() lint_config.target = self.SAMPLES_DIR read_data = "#!/bin/sh\n" + GITLINT_HOOK_IDENTIFIER with patch('gitlint.hooks.open', mock_open(read_data=read_data), create=True): GitHookInstaller.uninstall_commit_msg_hook(lint_config) expected_dst = os.path.join(self.SAMPLES_DIR, COMMIT_MSG_HOOK_DST_PATH) isdir.assert_called_with(os.path.join(self.SAMPLES_DIR, '.git/hooks')) path_exists.assert_called_once_with(expected_dst) remove.assert_called_with(expected_dst)
def test_extra_path_negative(self): config = LintConfig() regex = u"Option extra-path must be either an existing directory or file \(current value: 'föo/bar'\)" # incorrect extra_path with self.assertRaisesRegex(LintConfigError, regex): config.extra_path = u"föo/bar" # extra path contains classes with errors with self.assertRaisesRegex(LintConfigError, "User-defined rule class 'MyUserLineRule' must have a 'validate' method"): config.extra_path = self.get_sample_path("user_rules/incorrect_linerule")
def test_rebuild_config(self): # normal config build config_builder = LintConfigBuilder() config_builder.set_option('general', 'verbosity', 3) lint_config = config_builder.build() self.assertEqual(lint_config.verbosity, 3) # check that existing config changes when we rebuild it existing_lintconfig = LintConfig() existing_lintconfig.verbosity = 2 lint_config = config_builder.build(existing_lintconfig) self.assertEqual(lint_config.verbosity, 3)
def test_contrib_negative(self): config = LintConfig() # non-existent contrib rule with self.assertRaisesRegex(LintConfigError, u"No contrib rule with id or name 'föo' found."): config.contrib = u"contrib-title-conventional-commits,föo" # UserRuleError, RuleOptionError should be re-raised as LintConfigErrors side_effects = [rules.UserRuleError(u"üser-rule"), options.RuleOptionError(u"rüle-option")] for side_effect in side_effects: with patch('gitlint.config.rule_finder.find_rule_classes', side_effect=side_effect): with self.assertRaisesRegex(LintConfigError, ustr(side_effect)): config.contrib = u"contrib-title-conventional-commits"
def test_rebuild_config(self): # normal config build config_builder = LintConfigBuilder() config_builder.set_option('general', 'verbosity', 3) lint_config = config_builder.build() self.assertEqual(lint_config.verbosity, 3) # check that existing config changes when we rebuild it existing_lintconfig = LintConfig() existing_lintconfig.verbosity = 2 lint_config = config_builder.build(existing_lintconfig) self.assertEqual(lint_config.verbosity, 3)
def test_rebuild_config(self): # normal config build config_builder = LintConfigBuilder() config_builder.set_option('general', 'verbosity', 3) lint_config = config_builder.build() self.assertEqual(lint_config.verbosity, 3) # check that existing config gets overwritten when we pass it to a configbuilder with different options existing_lintconfig = LintConfig() existing_lintconfig.verbosity = 2 lint_config = config_builder.build(existing_lintconfig) self.assertEqual(lint_config.verbosity, 3) self.assertEqual(existing_lintconfig.verbosity, 3)
def test_uninstall_commit_msg_hook(isdir, path_exists, remove): lint_config = LintConfig() lint_config.target = u"/foo/bår" read_data = "#!/bin/sh\n" + GITLINT_HOOK_IDENTIFIER with patch('gitlint.hooks.open', mock_open(read_data=read_data), create=True): GitHookInstaller.uninstall_commit_msg_hook(lint_config) expected_dst = os.path.join(u"/foo/bår", COMMIT_MSG_HOOK_DST_PATH) isdir.assert_called_with(os.path.join(u"/foo/bår", '.git/hooks')) path_exists.assert_called_once_with(expected_dst) remove.assert_called_with(expected_dst)
def test_ignore_by_title(self): commit = self.gitcommit("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": "^Releäse(.*)"}) expected_config = LintConfig() expected_config.ignore = "all" rule.apply(config, commit) self.assertEqual(config, expected_config) expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \ "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": "^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 = "DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \ "Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: T1,B2"
def test_lint_regex_rules(self): """ Additional test for title-match-regex, body-match-regex """ commit = self.gitcommit( self.get_sample("commit_message/no-violations")) lintconfig = LintConfig() linter = GitLinter(lintconfig) violations = linter.lint(commit) # No violations by default self.assertListEqual(violations, []) # Matching regexes shouldn't be a problem rule_regexes = [("title-match-regex", u"Tïtle$"), ("body-match-regex", u"Sïgned-Off-By: (.*)$")] for rule_regex in rule_regexes: lintconfig.set_rule_option(rule_regex[0], "regex", rule_regex[1]) violations = linter.lint(commit) self.assertListEqual(violations, []) # Non-matching regexes should return violations rule_regexes = [("title-match-regex", ), ("body-match-regex", )] lintconfig.set_rule_option("title-match-regex", "regex", u"^Tïtle") lintconfig.set_rule_option("body-match-regex", "regex", u"Sügned-Off-By: (.*)$") expected_violations = [ RuleViolation("T7", u"Title does not match regex (^Tïtle)", u"Normal Commit Tïtle", 1), RuleViolation("B8", u"Body does not match regex (Sügned-Off-By: (.*)$)", None, 6) ] violations = linter.lint(commit) self.assertListEqual(violations, expected_violations)
def test_set_config_from_string_list(self): config = LintConfig() # assert some defaults self.assertEqual( config.get_rule_option('title-max-length', 'line-length'), 72) self.assertEqual( config.get_rule_option('body-max-line-length', 'line-length'), 80) self.assertListEqual( config.get_rule_option('title-must-not-contain-word', 'words'), ["WIP"]) self.assertEqual(config.verbosity, 3) # change and assert changes config_builder = LintConfigBuilder() config_builder.set_config_from_string_list([ 'general.verbosity=1', 'title-max-length.line-length=60', 'body-max-line-length.line-length=120', u"title-must-not-contain-word.words=håha" ]) config = config_builder.build() self.assertEqual( config.get_rule_option('title-max-length', 'line-length'), 60) self.assertEqual( config.get_rule_option('body-max-line-length', 'line-length'), 120) self.assertListEqual( config.get_rule_option('title-must-not-contain-word', 'words'), [u"håha"]) self.assertEqual(config.verbosity, 1)
def test_set_general_option_negative(self): config = LintConfig() with self.assertRaisesRegexp(LintConfigError, "'foo' is not a valid gitlint option"): config.set_general_option("foo", "bar") # invalid verbosity incorrect_values = [-1, "foo"] for value in incorrect_values: expected_msg = r"Option 'verbosity' must be a positive integer \(current value: '{0}'\)".format(value) with self.assertRaisesRegexp(LintConfigError, expected_msg): config.verbosity = value incorrect_values = [4] for value in incorrect_values: with self.assertRaisesRegexp(LintConfigError, "Option 'verbosity' must be set between 0 and 3"): config.verbosity = value # invalid ignore_merge_commits incorrect_values = [-1, 4, "foo"] for value in incorrect_values: with self.assertRaisesRegexp(LintConfigError, r"Option 'ignore-merge-commits' must be either 'true' or 'false'"): config.ignore_merge_commits = value # invalid debug with self.assertRaisesRegexp(LintConfigError, r"Option 'debug' must be either 'true' or 'false'"): config.debug = "foobar"
def test_ignore_by_body(self): commit = self.gitcommit("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": "(.*)relëase(.*)"}) expected_config = LintConfig() expected_config.ignore = "all" rule.apply(config, commit) self.assertEqual(config, expected_config) expected_log_message = "DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \ "Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)'," + \ " ignoring rules: all" self.assert_log_contains(expected_log_message) # Matching regex with specific ignore rule = rules.IgnoreByBody({ "regex": "(.*)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 = "DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \ "Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)', ignoring rules: T1,B2" self.assert_log_contains(expected_log_message)
def test_lint_merge_commit(self): commit = self.gitcommit(self.get_sample("commit_message/sample6")) # Sample 6 is a merge commit lintconfig = LintConfig() linter = GitLinter(lintconfig) violations = linter.lint(commit) # Even though there are a number of violations in the commit message, they are ignored because # we are dealing with a merge commit self.assertListEqual(violations, []) # Check that we do see violations if we disable 'ignore-merge-commits' lintconfig.ignore_merge_commits = False linter = GitLinter(lintconfig) violations = linter.lint(commit) self.assertTrue(len(violations) > 0)
def get_config(ctx, target, config_path, c, ignore, verbose, silent): """ Creates a LintConfig object based on a set of commandline parameters. """ try: # Config precedence: # First, load default config or config from configfile lint_config = load_config_from_path(ctx, config_path) # default to default configuration when no config file was loaded if lint_config: click.echo("Using config from {0}".format(lint_config.config_path)) else: lint_config = LintConfig() # Then process any commandline configuration flags lint_config.apply_config_options(c) # Finally, overwrite with any convenience commandline flags lint_config.apply_on_csv_string(ignore, lint_config.disable_rule) if silent: lint_config.verbosity = 0 elif verbose > 0: lint_config.verbosity = verbose # Set target lint_config.target = target return lint_config except LintConfigError as e: click.echo("Config Error: {0}".format(str(e))) ctx.exit(CONFIG_ERROR_CODE) # return CONFIG_ERROR_CODE on config error
def test_lint_merge_commit(self): commit = self.gitcommit(self.get_sample( "commit_message/sample6")) # Sample 6 is a merge commit lintconfig = LintConfig() linter = GitLinter(lintconfig) violations = linter.lint(commit) # Even though there are a number of violations in the commit message, they are ignored because # we are dealing with a merge commit self.assertListEqual(violations, []) # Check that we do see violations if we disable 'ignore-merge-commits' lintconfig.ignore_merge_commits = False linter = GitLinter(lintconfig) violations = linter.lint(commit) self.assertTrue(len(violations) > 0)
def load_config_from_path(ctx, config_path=None): """ Tries loading the config from the given path. If no path is specified, the default config path is tried, and if no file exists at the that location, None is returned. """ config = None try: if config_path: config = LintConfig.load_from_file(config_path) elif os.path.exists(DEFAULT_CONFIG_FILE): config = LintConfig.load_from_file(DEFAULT_CONFIG_FILE) except LintConfigError as e: click.echo("Error during config file parsing: {0}".format(str(e))) ctx.exit(CONFIG_ERROR_CODE) return config
def test_install_commit_msg_hook(git_hooks_dir, isdir, path_exists, copy, stat, chmod): lint_config = LintConfig() lint_config.target = os.path.join(u"/hür", u"dur") git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks") expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH) GitHookInstaller.install_commit_msg_hook(lint_config) isdir.assert_called_with(git_hooks_dir.return_value) path_exists.assert_called_once_with(expected_dst) copy.assert_called_once_with(COMMIT_MSG_HOOK_SRC_PATH, expected_dst) stat.assert_called_once_with(expected_dst) chmod.assert_called_once_with(expected_dst, ANY) git_hooks_dir.assert_called_with(lint_config.target)
def load_config_from_path(ctx, config_path=None): """ Tries loading the config from the given path. If no path is specified, the default config path is tried, and if no file exists at the that location, None is returned. """ config = None try: if config_path: config = LintConfig.load_from_file(config_path) elif os.path.exists(DEFAULT_CONFIG_FILE): config = LintConfig.load_from_file(DEFAULT_CONFIG_FILE) except LintConfigError as e: click.echo("Error during config file parsing: {0}".format(str(e))) ctx.exit(CONFIG_ERROR_CODE) return config
def test_set_general_option_negative(self): config = LintConfig() # Note that we shouldn't test whether we can set unicode because python just doesn't allow unicode attributes with self.assertRaisesRegex(LintConfigError, "'foo' is not a valid gitlint option"): config.set_general_option("foo", u"bår") # try setting _config_path, this is a real attribute of LintConfig, but the code should prevent it from # being set with self.assertRaisesRegex(LintConfigError, "'_config_path' is not a valid gitlint option"): config.set_general_option("_config_path", u"bår") # invalid verbosity` incorrect_values = [-1, u"föo"] for value in incorrect_values: expected_msg = u"Option 'verbosity' must be a positive integer \(current value: '{0}'\)".format(value) with self.assertRaisesRegex(LintConfigError, expected_msg): config.verbosity = value incorrect_values = [4] for value in incorrect_values: with self.assertRaisesRegex(LintConfigError, "Option 'verbosity' must be set between 0 and 3"): config.verbosity = value # invalid ignore_xxx_commits ignore_attributes = ["ignore_merge_commits", "ignore_fixup_commits", "ignore_squash_commits", "ignore_revert_commits"] incorrect_values = [-1, 4, u"föo"] for attribute in ignore_attributes: for value in incorrect_values: option_name = attribute.replace("_", "-") with self.assertRaisesRegex(LintConfigError, "Option '{0}' must be either 'true' or 'false'".format(option_name)): setattr(config, attribute, value) # invalid ignore -> not here because ignore is a ListOption which converts everything to a string before # splitting which means it it will accept just about everything # invalid debug with self.assertRaisesRegex(LintConfigError, "Option 'debug' must be either 'true' or 'false'"): config.debug = u"föobar" # extra-path has its own negative test # invalid target with self.assertRaisesRegex(LintConfigError, u"Option target must be an existing directory \(current value: 'föo/bar'\)"): config.target = u"föo/bar"
def test_print_violations(self): violations = [ RuleViolation("RULE_ID_1", u"Error Messåge 1", "Violating Content 1", None), RuleViolation("RULE_ID_2", "Error Message 2", u"Violåting Content 2", 2) ] linter = GitLinter(LintConfig()) # test output with increasing verbosity with patch('gitlint.display.stderr', new=StringIO()) as stderr: linter.config.verbosity = 0 linter.print_violations(violations) self.assertEqual("", stderr.getvalue()) with patch('gitlint.display.stderr', new=StringIO()) as stderr: linter.config.verbosity = 1 linter.print_violations(violations) expected = u"-: RULE_ID_1\n2: RULE_ID_2\n" self.assertEqual(expected, stderr.getvalue()) with patch('gitlint.display.stderr', new=StringIO()) as stderr: linter.config.verbosity = 2 linter.print_violations(violations) expected = u"-: RULE_ID_1 Error Messåge 1\n2: RULE_ID_2 Error Message 2\n" self.assertEqual(expected, stderr.getvalue()) with patch('gitlint.display.stderr', new=StringIO()) as stderr: linter.config.verbosity = 3 linter.print_violations(violations) expected = u"-: RULE_ID_1 Error Messåge 1: \"Violating Content 1\"\n" + \ u"2: RULE_ID_2 Error Message 2: \"Violåting Content 2\"\n" self.assertEqual(expected, stderr.getvalue())
def test_set_config_from_string_list(self): config = LintConfig() # assert some defaults self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 72) self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 80) self.assertEqual(config.verbosity, 3) # change and assert changes config_builder = LintConfigBuilder() config_builder.set_config_from_string_list(['general.verbosity=1', 'title-max-length.line-length=60', 'body-max-line-length.line-length=120']) config = config_builder.build() self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 60) self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 120) self.assertEqual(config.verbosity, 1)
def test_set_from_commit_ignore_all(self): config = LintConfig() original_rules = config.rules original_rule_ids = [rule.id for rule in original_rules] config_builder = LintConfigBuilder() # nothing gitlint config_builder.set_config_from_commit( self.gitcommit(u"tëst\ngitlint\nfoo")) config = config_builder.build() self.assertListEqual(config.rules, original_rules) self.assertListEqual(config.ignore, []) # ignore all rules config_builder.set_config_from_commit( self.gitcommit(u"tëst\ngitlint-ignore: all\nfoo")) config = config_builder.build() self.assertEqual(config.ignore, original_rule_ids) # ignore all rules, no space config_builder.set_config_from_commit( self.gitcommit(u"tëst\ngitlint-ignore:all\nfoo")) config = config_builder.build() self.assertEqual(config.ignore, original_rule_ids) # ignore all rules, more spacing config_builder.set_config_from_commit( self.gitcommit(u"tëst\ngitlint-ignore: \t all\nfoo")) config = config_builder.build() self.assertEqual(config.ignore, original_rule_ids)
def test_lint_sample3(self): linter = GitLinter(LintConfig()) gitcontext = self.gitcontext(self.get_sample("commit_message/sample3")) violations = linter.lint(gitcontext.commits[-1]) title = u" Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters." expected = [ RuleViolation("T1", "Title exceeds max length (95>72)", title, 1), RuleViolation("T3", "Title has trailing punctuation (.)", title, 1), RuleViolation("T4", "Title contains hard tab characters (\\t)", title, 1), RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", title, 1), RuleViolation("T6", "Title has leading whitespace", title, 1), RuleViolation("B4", "Second line is not empty", "This line should be empty", 2), RuleViolation( "B1", "Line exceeds max length (101>80)", u"This is the first line is meånt to test a line that exceeds the maximum line " + "length of 80 characters.", 3), RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing space. ", 4), RuleViolation("B2", "Line has trailing whitespace", u"This line has a tråiling tab.\t", 5), RuleViolation("B3", "Line contains hard tab characters (\\t)", u"This line has a tråiling tab.\t", 5) ] self.assertListEqual(violations, expected)
def test_get_rule(self): config = LintConfig() # get by id expected = rules.TitleMaxLength() rule = config.get_rule('T1') self.assertEqual(rule, expected) # get by name expected = rules.TitleTrailingWhitespace() # pylint: disable=redefined-variable-type rule = config.get_rule('title-trailing-whitespace') self.assertEqual(rule, expected) # get non-existing rule = config.get_rule('foo') self.assertIsNone(rule)
def test_get_rule(self): config = LintConfig() # get by id expected = rules.TitleMaxLength() rule = config.get_rule('T1') self.assertEqual(rule, expected) # get by name expected2 = rules.TitleTrailingWhitespace() rule = config.get_rule('title-trailing-whitespace') self.assertEqual(rule, expected2) # get non-existing rule = config.get_rule(u'föo') self.assertIsNone(rule)
def test_get_rule(self): config = LintConfig() # get by id expected = rules.TitleMaxLength() rule = config.get_rule('T1') self.assertEqual(rule, expected) # get by name expected = rules.TitleTrailingWhitespace() # pylint: disable=redefined-variable-type rule = config.get_rule('title-trailing-whitespace') self.assertEqual(rule, expected) # get non-existing rule = config.get_rule('foo') self.assertIsNone(rule)
def test_e(self): display = Display(LintConfig()) display.config.verbosity = 2 with patch('gitlint.display.stdout', new=StringIO()) as stdout: # Non exact outputting, should output both v and vv output with patch('gitlint.display.stderr', new=StringIO()) as stderr: display.e(u"tëst") display.ee(u"tëst2") # vvvv should be ignored regardless display.eee(u"tëst3.1") display.eee(u"tëst3.2", exact=True) self.assertEqual(u"tëst\ntëst2\n", stderr.getvalue()) # exact outputting, should only output v with patch('gitlint.display.stderr', new=StringIO()) as stderr: display.e(u"tëst", exact=True) display.ee(u"tëst2", exact=True) # vvvv should be ignored regardless display.eee(u"tëst3.1") display.eee(u"tëst3.2", exact=True) self.assertEqual(u"tëst2\n", stderr.getvalue()) # standard output should be empty throughtout all of this self.assertEqual('', stdout.getvalue())
def test_lint_sample1(self): linter = GitLinter(LintConfig()) gitcontext = self.gitcontext(self.get_sample("commit_message/sample1")) violations = linter.lint(gitcontext.commits[-1]) expected_errors = [ RuleViolation( "T3", "Title has trailing punctuation (.)", u"Commit title contåining 'WIP', as well as trailing punctuation.", 1), RuleViolation( "T5", "Title contains the word 'WIP' (case-insensitive)", u"Commit title contåining 'WIP', as well as trailing punctuation.", 1), RuleViolation("B4", "Second line is not empty", "This line should be empty", 2), RuleViolation( "B1", "Line exceeds max length (135>80)", "This is the first line of the commit message body and it is meant to test " + "a line that exceeds the maximum line length of 80 characters.", 3), RuleViolation("B2", "Line has trailing whitespace", u"This line has a tråiling space. ", 4), RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing tab.\t", 5), RuleViolation("B3", "Line contains hard tab characters (\\t)", "This line has a trailing tab.\t", 5) ] self.assertListEqual(violations, expected_errors)
def test_uninstall_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exists, remove): lint_config = LintConfig() lint_config.target = os.path.join(u"/hür", u"dur") git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks") # mock that the current directory is not a git repo isdir.return_value = False expected_msg = u"{0} is not a git repository".format( lint_config.target) with self.assertRaisesRegex(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() # mock that there is no commit hook present isdir.return_value = True path_exists.return_value = False expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH) expected_msg = u"There is no commit-msg hook present in {0}.".format( expected_dst) with self.assertRaisesRegex(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() # mock that there is a different (=not gitlint) commit hook isdir.return_value = True path_exists.return_value = True read_data = "#!/bin/sh\nfoo" expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH) expected_msg = u"The commit-msg hook in {0} was not installed by gitlint ".format(expected_dst) + \ r"\(or it was modified\).\nUninstallation of 3th party or modified gitlint hooks " + \ "is not supported." with patch('gitlint.hooks.io.open', mock_open(read_data=read_data), create=True): with self.assertRaisesRegex(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) remove.assert_not_called()
def test_lint_sample2(self): linter = GitLinter(LintConfig()) gitcontext = self.gitcontext(self.get_sample("commit_message/sample2")) violations = linter.lint(gitcontext.commits[-1]) expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", u"Just a title contåining WIP", 1), RuleViolation("B6", "Body message is missing", None, 3)] self.assertListEqual(violations, expected)
def test_load_config_from_file(self): # regular config file load, no problems config = LintConfig.load_from_file(self.get_sample_path("config/gitlintconfig")) # Do some assertions on the config self.assertEqual(config.verbosity, 1) self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 20) self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 30) self.assertIsNone(config.get_rule('title-trailing-whitespace'))
def test_install_commit_msg_hook(isdir, path_exists, copy, stat, chmod): lint_config = LintConfig(target="/foo/bar") expected_dst = os.path.join("/foo/bar", COMMIT_MSG_HOOK_DST_PATH) GitHookInstaller.install_commit_msg_hook(lint_config) isdir.assert_called_once_with('/foo/bar/.git/hooks') path_exists.assert_called_once_with(expected_dst) copy.assert_called_once_with(COMMIT_MSG_HOOK_SRC_PATH, expected_dst) stat.assert_called_once_with(expected_dst) chmod.assert_called_once_with(expected_dst, ANY)
def test_uninstall_commit_msg_hook(git_hooks_dir, isdir, path_exists, remove): lint_config = LintConfig() git_hooks_dir.return_value = os.path.join(u"/föo", u"bar", ".git", "hooks") lint_config.target = os.path.join(u"/hür", u"dur") read_data = "#!/bin/sh\n" + GITLINT_HOOK_IDENTIFIER with patch('gitlint.hooks.io.open', mock_open(read_data=read_data), create=True): GitHookInstaller.uninstall_commit_msg_hook(lint_config) expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH) isdir.assert_called_with(git_hooks_dir.return_value) path_exists.assert_called_once_with(expected_dst) remove.assert_called_with(expected_dst) git_hooks_dir.assert_called_with(lint_config.target)
def test_contrib(self): config = LintConfig() contrib_rules = ["contrib-title-conventional-commits", "CC1"] config.set_general_option("contrib", ",".join(contrib_rules)) self.assertEqual(config.contrib, contrib_rules) # Check contrib-title-conventional-commits contrib rule actual_rule = config.rules.find_rule( "contrib-title-conventional-commits") self.assertTrue(actual_rule.is_contrib) self.assertEqual(ustr(type(actual_rule)), "<class 'conventional_commit.ConventionalCommit'>") self.assertEqual(actual_rule.id, 'CT1') self.assertEqual(actual_rule.name, u'contrib-title-conventional-commits') self.assertEqual(actual_rule.target, rules.CommitMessageTitle) expected_rule_option = options.ListOption( "types", [ "fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert" ], "Comma separated list of allowed commit types.", ) self.assertListEqual(actual_rule.options_spec, [expected_rule_option]) self.assertDictEqual(actual_rule.options, {'types': expected_rule_option}) # Check contrib-body-requires-signed-off-by contrib rule actual_rule = config.rules.find_rule( "contrib-body-requires-signed-off-by") self.assertTrue(actual_rule.is_contrib) self.assertEqual(ustr(type(actual_rule)), "<class 'signedoff_by.SignedOffBy'>") self.assertEqual(actual_rule.id, 'CC1') self.assertEqual(actual_rule.name, u'contrib-body-requires-signed-off-by') # reset value (this is a different code path) config.set_general_option("contrib", "contrib-body-requires-signed-off-by") self.assertEqual( actual_rule, config.rules.find_rule("contrib-body-requires-signed-off-by")) self.assertIsNone( config.rules.find_rule("contrib-title-conventional-commits")) # empty value config.set_general_option("contrib", "") self.assertListEqual(config.contrib, [])
def test_lint_sample5(self): gitcontext = self.gitcontext(self.get_sample("commit_message/sample5")) lintconfig = LintConfig() lintconfig.apply_config_from_commit(gitcontext.commits[-1]) linter = GitLinter(lintconfig) violations = linter.lint(gitcontext.commits[-1], gitcontext) title = " Commit title containing 'WIP', \tleading and trailing whitespace and longer than 72 characters." # expect only certain violations because sample5 has a 'gitlint: T3,' expected = [RuleViolation("T1", "Title exceeds max length (95>72)", title, 1), RuleViolation("T4", "Title contains hard tab characters (\\t)", title, 1), RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", title, 1), RuleViolation("B4", "Second line is not empty", "This line should be empty", 2), RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing space. ", 4), RuleViolation("B2", "Line has trailing whitespace", "This line has a trailing tab.\t", 5), RuleViolation("B3", "Line contains hard tab characters (\\t)", "This line has a trailing tab.\t", 5)] self.assertListEqual(violations, expected)
def test_apply_config_options(self): config = LintConfig() # assert some defaults self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 72) self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 80) self.assertEqual(config.verbosity, 3) # change and assert changes config.apply_config_options(['general.verbosity=1', 'title-max-length.line-length=60', 'body-max-line-length.line-length=120']) self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 60) self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 120) self.assertEqual(config.verbosity, 1)
def test_install_commit_msg_hook_negative(self, isdir, path_exists, copy): lint_config = LintConfig() lint_config.target = self.SAMPLES_DIR # mock that current dir is not a git repo isdir.return_value = False expected_msg = "{0} is not a git repository".format(self.SAMPLES_DIR) with self.assertRaisesRegex(GitHookInstallerError, expected_msg): GitHookInstaller.install_commit_msg_hook(lint_config) isdir.assert_called_with(os.path.join(self.SAMPLES_DIR, '.git/hooks')) path_exists.assert_not_called() copy.assert_not_called() # mock that there is already a commit hook present isdir.return_value = True path_exists.return_value = True expected_dst = os.path.join(self.SAMPLES_DIR, COMMIT_MSG_HOOK_DST_PATH) expected_msg = "There is already a commit-msg hook file present in {0}.\n".format(expected_dst) + \ "gitlint currently does not support appending to an existing commit-msg file." with self.assertRaisesRegex(GitHookInstallerError, expected_msg): GitHookInstaller.install_commit_msg_hook(lint_config)
def test_install_commit_msg_hook_negative(self, isdir, path_exists, copy): lint_config = LintConfig() lint_config.target = u"/foo/bår" # mock that current dir is not a git repo isdir.return_value = False expected_msg = u"{0} is not a git repository".format(u"/foo/bår") with self.assertRaisesRegex(GitHookInstallerError, expected_msg): GitHookInstaller.install_commit_msg_hook(lint_config) isdir.assert_called_with(os.path.join(u"/foo/bår", '.git/hooks')) path_exists.assert_not_called() copy.assert_not_called() # mock that there is already a commit hook present isdir.return_value = True path_exists.return_value = True expected_dst = os.path.join(u"/foo/bår", COMMIT_MSG_HOOK_DST_PATH) expected_msg = u"There is already a commit-msg hook file present in {0}.\n".format(expected_dst) + \ "gitlint currently does not support appending to an existing commit-msg file." with self.assertRaisesRegex(GitHookInstallerError, expected_msg): GitHookInstaller.install_commit_msg_hook(lint_config)
def test_load_config_from_file(self): # regular config file load, no problems config = LintConfig.load_from_file( self.get_sample_path("config/gitlintconfig")) # Do some assertions on the config self.assertEqual(config.verbosity, 1) self.assertEqual( config.get_rule_option('title-max-length', 'line-length'), 20) self.assertEqual( config.get_rule_option('body-max-line-length', 'line-length'), 30) self.assertIsNone(config.get_rule('title-trailing-whitespace'))
def get_lint_config(config_path=None): """ Tries loading the config from the given path. If no path is specified, the default config path is tried, and if that is not specified, we the default config is returned. """ # config path specified config = None try: if config_path: config = LintConfig.load_from_file(config_path) elif os.path.exists(DEFAULT_CONFIG_FILE): config = LintConfig.load_from_file(DEFAULT_CONFIG_FILE) except LintConfigError as e: click.echo("Error during config file parsing: {0}".format(e.message)) exit(CONFIG_ERROR_CODE) # return 10000 on config error # no config file if config: click.echo("Using config from {0}".format(config.config_path)) else: config = LintConfig() return config
def test_apply_config_options(self): config = LintConfig() # assert some defaults self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 72) self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 80) self.assertEqual(config.verbosity, 3) # change and assert changes config.apply_config_options(['general.verbosity=1', 'title-max-length.line-length=60', 'body-max-line-length.line-length=120']) self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 60) self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 120) self.assertEqual(config.verbosity, 1)
def test_load_config_from_file(self): # regular config file load, no problems config = LintConfig.load_from_file(self.get_sample_path("config/gitlintconfig")) # Do some assertions on the config self.assertEqual(config.verbosity, 1) self.assertFalse(config.ignore_merge_commits) # ignored rules expected_ignored_rules = set([rules.BodyTrailingWhitespace, rules.TitleTrailingWhitespace]) active_rule_classes = set(type(rule) for rule in config.rules) self.assertSetEqual(set(config.default_rule_classes) - expected_ignored_rules, active_rule_classes) self.assertEqual(config.get_rule_option('title-max-length', 'line-length'), 20) self.assertEqual(config.get_rule_option('body-max-line-length', 'line-length'), 30) self.assertIsNone(config.get_rule('title-trailing-whitespace'))
def test_set_rule_option_negative(self): config = LintConfig() # non-existing rule expected_error_msg = "No such rule 'foobar'" with self.assertRaisesRegexp(LintConfigError, expected_error_msg): config.set_rule_option('foobar', 'line-length', 60) # non-existing option expected_error_msg = "Rule 'title-max-length' has no option 'foobar'" with self.assertRaisesRegexp(LintConfigError, expected_error_msg): config.set_rule_option('title-max-length', 'foobar', 60) # invalid option value expected_error_msg = "'foo' is not a valid value for option 'title-max-length.line-length'. " + \ "Option 'line-length' must be a positive integer \(current value: 'foo'\)." with self.assertRaisesRegexp(LintConfigError, expected_error_msg): config.set_rule_option('title-max-length', 'line-length', "foo") # invalid verbosity with self.assertRaisesRegexp(LintConfigError, "verbosity must be set between 0 and 3"): config.verbosity = -1 with self.assertRaisesRegexp(LintConfigError, "verbosity must be set between 0 and 3"): config.verbosity = 4
def test_extra_path(self): config = LintConfig() config.set_general_option("extra-path", self.get_rule_rules_path()) self.assertEqual(config.extra_path, self.get_rule_rules_path()) actual_rule = config.get_rule('TUC1') self.assertTrue(actual_rule.user_defined) self.assertEqual(str(type(actual_rule)), "<class 'my_commit_rules.MyUserCommitRule'>") self.assertEqual(actual_rule.id, 'TUC1') self.assertEqual(actual_rule.name, 'my-user-commit-rule') self.assertEqual(actual_rule.target, None) expected_rule_option = IntOption('violation-count', 1, "Number of violations to return") self.assertListEqual(actual_rule.options_spec, [expected_rule_option]) self.assertDictEqual(actual_rule.options, {'violation-count': expected_rule_option}) # reset value (this is a different code path) config.set_general_option("extra-path", self.SAMPLES_DIR) self.assertEqual(config.extra_path, self.SAMPLES_DIR) self.assertIsNone(config.get_rule("TUC1"))
def test_set_general_option(self): config = LintConfig() # Check that default general options are correct self.assertTrue(config.ignore_merge_commits) self.assertEqual(config.verbosity, 3) active_rule_classes = [type(rule) for rule in config.rules] self.assertListEqual(active_rule_classes, config.default_rule_classes) # Check that we can change the general options # ignore config.set_general_option("ignore", "title-trailing-whitespace, B2") expected_ignored_rules = set([rules.BodyTrailingWhitespace, rules.TitleTrailingWhitespace]) active_rule_classes = set(type(rule) for rule in config.rules) # redetermine active rule classes expected_active_rule_classes = set(config.default_rule_classes) - expected_ignored_rules self.assertSetEqual(active_rule_classes, expected_active_rule_classes) # verbosity config.set_general_option("verbosity", 1) self.assertEqual(config.verbosity, 1) # ignore_merge_commit config.set_general_option("ignore-merge-commits", "false") self.assertFalse(config.ignore_merge_commits)
def test_set_general_option_negative(self): config = LintConfig() with self.assertRaisesRegexp(LintConfigError, "'foo' is not a valid gitlint option"): config.set_general_option("foo", "bar") # invalid verbosity incorrect_values = [-1, "foo"] for value in incorrect_values: expected_msg = r"Option 'verbosity' must be a positive integer \(current value: '{0}'\)".format(value) with self.assertRaisesRegexp(LintConfigError, expected_msg): config.verbosity = value incorrect_values = [4] for value in incorrect_values: with self.assertRaisesRegexp(LintConfigError, "Option 'verbosity' must be set between 0 and 3"): config.verbosity = value # invalid ignore_merge_commits incorrect_values = [-1, 4, "foo"] for value in incorrect_values: with self.assertRaisesRegexp(LintConfigError, r"Option 'ignore-merge-commits' must be either 'true' or 'false'"): config.ignore_merge_commits = value
def test_set_general_option_negative(self): config = LintConfig() with self.assertRaisesRegex(LintConfigError, "'foo' is not a valid gitlint option"): config.set_general_option("foo", "bar") # try setting _config_path, this is a real attribute of LintConfig, but the code should prevent it from # being set with self.assertRaisesRegex(LintConfigError, "'_config_path' is not a valid gitlint option"): config.set_general_option("_config_path", "bar") # invalid verbosity` incorrect_values = [-1, "foo"] for value in incorrect_values: expected_msg = r"Option 'verbosity' must be a positive integer \(current value: '{0}'\)".format(value) with self.assertRaisesRegex(LintConfigError, expected_msg): config.verbosity = value incorrect_values = [4] for value in incorrect_values: with self.assertRaisesRegex(LintConfigError, "Option 'verbosity' must be set between 0 and 3"): config.verbosity = value # invalid ignore_merge_commits incorrect_values = [-1, 4, "foo"] for value in incorrect_values: with self.assertRaisesRegex(LintConfigError, r"Option 'ignore-merge-commits' must be either 'true' or 'false'"): config.ignore_merge_commits = value # invalid ignore -> not here because ignore is a ListOption which converts everything to a string before # splitting which means it it will accept just about everything # invalid debug with self.assertRaisesRegex(LintConfigError, r"Option 'debug' must be either 'true' or 'false'"): config.debug = "foobar" # invalid extra-path with self.assertRaisesRegex(LintConfigError, r"Option extra-path must be an existing directory \(current value: 'foo/bar'\)"): config.extra_path = "foo/bar" # invalid target with self.assertRaisesRegex(LintConfigError, r"Option target must be an existing directory \(current value: 'foo/bar'\)"): config.target = "foo/bar"
def test_set_general_option(self): config = LintConfig() # Check that default general options are correct self.assertTrue(config.ignore_merge_commits) self.assertFalse(config.debug) self.assertEqual(config.verbosity, 3) active_rule_classes = tuple(type(rule) for rule in config.rules) self.assertTupleEqual(active_rule_classes, config.default_rule_classes) # ignore - set by string config.set_general_option("ignore", "title-trailing-whitespace, B2") self.assertEqual(config.ignore, ["title-trailing-whitespace", "B2"]) # ignore - set by list config.set_general_option("ignore", ["T1", "B3"]) self.assertEqual(config.ignore, ["T1", "B3"]) # verbosity config.set_general_option("verbosity", 1) self.assertEqual(config.verbosity, 1) # ignore_merge_commit config.set_general_option("ignore-merge-commits", "false") self.assertFalse(config.ignore_merge_commits) # debug config.set_general_option("debug", "true") self.assertTrue(config.debug) # target config.set_general_option("target", self.SAMPLES_DIR) self.assertEqual(config.target, self.SAMPLES_DIR)
def test_commit_msg_hook_path(self): lint_config = LintConfig() lint_config.target = self.SAMPLES_DIR expected_path = os.path.join(self.SAMPLES_DIR, COMMIT_MSG_HOOK_DST_PATH) path = GitHookInstaller.commit_msg_hook_path(lint_config) self.assertEqual(path, expected_path)
def test_gitcontext_ignore_all(self): config = LintConfig() original_rules = config.rules # nothing gitlint context = self.gitcontext("test\ngitlint\nfoo") config.apply_config_from_commit(context.commits[-1]) self.assertListEqual(config.rules, original_rules) # ignore all rules context = self.gitcontext("test\ngitlint-ignore: all\nfoo") config.apply_config_from_commit(context.commits[-1]) self.assertEqual(config.rules, []) # ignore all rules, no space config = LintConfig() context = self.gitcontext("test\ngitlint-ignore:all\nfoo") config.apply_config_from_commit(context.commits[-1]) self.assertEqual(config.rules, []) # ignore all rules, more spacing config = LintConfig() context = self.gitcontext("test\ngitlint-ignore: \t all\nfoo") config.apply_config_from_commit(context.commits[-1]) self.assertEqual(config.rules, [])
def test_apply_config_options_negative(self): config = LintConfig() # assert error on incorrect rule with self.assertRaisesRegexp(LintConfigError, "No such rule 'foo'"): config.apply_config_options(['foo.bar=1']) # no equal sign expected_msg = "'foo.bar' is an invalid configuration option. Use '<rule>.<option>=<value>'" with self.assertRaisesRegexp(LintConfigError, expected_msg): config.apply_config_options(['foo.bar']) # missing value expected_msg = "'foo.bar=' is an invalid configuration option. Use '<rule>.<option>=<value>'" with self.assertRaisesRegexp(LintConfigError, expected_msg): config.apply_config_options(['foo.bar=']) # space instead of equal sign expected_msg = "'foo.bar 1' is an invalid configuration option. Use '<rule>.<option>=<value>'" with self.assertRaisesRegexp(LintConfigError, expected_msg): config.apply_config_options(['foo.bar 1']) # no period between rule and option names expected_msg = "'foobar=1' is an invalid configuration option. Use '<rule>.<option>=<value>'" with self.assertRaisesRegexp(LintConfigError, expected_msg): config.apply_config_options(['foobar=1'])