From 5f208e04c159791e668031a7fa83f98724ec8d24 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 3 Nov 2020 07:07:45 +0100 Subject: Adding upstream version 0.14.0. Signed-off-by: Daniel Baumann --- qa/base.py | 19 ++++- .../test_commits/test_lint_staged_msg_filename_1 | 15 +++- qa/expected/test_commits/test_lint_staged_stdin_1 | 15 +++- qa/expected/test_config/test_config_from_env_1 | 93 ++++++++++++++++++++++ qa/expected/test_config/test_config_from_env_2 | 83 +++++++++++++++++++ .../test_config/test_config_from_file_debug_1 | 15 +++- qa/expected/test_contrib/test_contrib_rules_1 | 2 +- qa/expected/test_named_rules/test_named_rule_1 | 5 ++ .../test_named_rules/test_named_user_rule_1 | 9 +++ .../test_user_defined_rules_examples_2 | 4 + .../test_user_defined_rules_extra_1 | 6 +- qa/samples/config/contrib-enabled | 0 qa/samples/config/named-rules | 8 ++ qa/samples/config/named-user-rules | 15 ++++ qa/samples/user_rules/extra/extra_rules.py | 52 ++++++++++-- qa/shell.py | 7 +- qa/test_config.py | 43 +++++++++- qa/test_gitlint.py | 22 +++++ qa/test_hooks.py | 12 +++ qa/test_named_rules.py | 23 ++++++ qa/test_user_defined.py | 17 +++- qa/utils.py | 18 ++++- 22 files changed, 457 insertions(+), 26 deletions(-) create mode 100644 qa/expected/test_config/test_config_from_env_1 create mode 100644 qa/expected/test_config/test_config_from_env_2 create mode 100644 qa/expected/test_named_rules/test_named_rule_1 create mode 100644 qa/expected/test_named_rules/test_named_user_rule_1 create mode 100644 qa/expected/test_user_defined/test_user_defined_rules_examples_2 delete mode 100644 qa/samples/config/contrib-enabled create mode 100644 qa/samples/config/named-rules create mode 100644 qa/samples/config/named-user-rules create mode 100644 qa/test_named_rules.py (limited to 'qa') diff --git a/qa/base.py b/qa/base.py index 05d85e5..f9e520a 100644 --- a/qa/base.py +++ b/qa/base.py @@ -93,6 +93,19 @@ class BaseTestCase(TestCase): io.open(os.path.join(parent_dir, test_filename), 'a', encoding=DEFAULT_ENCODING).close() return test_filename + @staticmethod + def create_environment(envvars=None): + """ Creates a copy of the current os.environ and adds/overwrites a given set of variables to it """ + environment = os.environ.copy() + if envvars: + environment.update(envvars) + return environment + + def create_tmp_git_config(self, contents): + """ Creates an environment with the GIT_CONFIG variable set to a file with the given contents. """ + tmp_config = self.create_tmpfile(contents) + return self.create_environment({"GIT_CONFIG": tmp_config}) + def create_simple_commit(self, message, out=None, ok_code=None, env=None, git_repo=None, tty_in=False): """ Creates a simple commit with an empty test file. :param message: Commit message for the commit. """ @@ -103,9 +116,7 @@ class BaseTestCase(TestCase): # variables can influence how git runs. # This was needed to fix https://github.com/jorisroovers/gitlint/issues/15 as we need to make sure to use # the PATH variable that contains the virtualenv's python binary. - environment = os.environ - if env: - environment.update(env) + environment = self.create_environment(env) # Create file and add to git test_filename = self.create_file(git_repo) @@ -164,7 +175,7 @@ class BaseTestCase(TestCase): expected_git_version = git("--version").replace("\n", "") return {'platform': platform.platform(), 'python_version': sys.version, 'git_version': expected_git_version, 'gitlint_version': expected_gitlint_version, - 'GITLINT_USE_SH_LIB': BaseTestCase.GITLINT_USE_SH_LIB} + 'GITLINT_USE_SH_LIB': BaseTestCase.GITLINT_USE_SH_LIB, 'DEFAULT_ENCODING': DEFAULT_ENCODING} def get_debug_vars_last_commit(self, git_repo=None): """ Returns a dict with items related to `gitlint --debug` output for the last commit. """ diff --git a/qa/expected/test_commits/test_lint_staged_msg_filename_1 b/qa/expected/test_commits/test_lint_staged_msg_filename_1 index 878bc4c..eb2682f 100644 --- a/qa/expected/test_commits/test_lint_staged_msg_filename_1 +++ b/qa/expected/test_commits/test_lint_staged_msg_filename_1 @@ -1,9 +1,11 @@ DEBUG: gitlint.cli To report issues, please visit https://github.com/jorisroovers/gitlint/issues DEBUG: gitlint.cli Platform: {platform} DEBUG: gitlint.cli Python version: {python_version} +DEBUG: gitlint.git ('--version',) DEBUG: gitlint.cli Git version: {git_version} DEBUG: gitlint.cli Gitlint version: {gitlint_version} DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} +DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] @@ -26,6 +28,8 @@ target: {target} I2: ignore-by-body ignore=all regex=None + I3: ignore-body-lines + regex=None T1: title-max-length line-length=72 T2: title-trailing-whitespace @@ -35,7 +39,9 @@ target: {target} T5: title-must-not-contain-word words=WIP T7: title-match-regex - regex=.* + regex=None + T8: title-min-length + min-length=5 B1: body-max-line-length line-length=80 B5: body-min-length @@ -47,13 +53,20 @@ target: {target} B4: body-first-line-empty B7: body-changed-file-mention files= + B8: body-match-regex + regex=None M1: author-valid-email regex=[^@ ]+@[^@ ]+\.[^@ ]+ DEBUG: gitlint.cli Fetching additional meta-data from staged commit DEBUG: gitlint.cli Using --msg-filename. +DEBUG: gitlint.git ('config', '--get', 'core.commentchar') DEBUG: gitlint.cli Linting 1 commit(s) DEBUG: gitlint.lint Linting commit [SHA UNKNOWN] +DEBUG: gitlint.git ('config', '--get', 'user.name') +DEBUG: gitlint.git ('config', '--get', 'user.email') +DEBUG: gitlint.git ('rev-parse', '--abbrev-ref', 'HEAD') +DEBUG: gitlint.git ('diff', '--staged', '--name-only', '-r') DEBUG: gitlint.lint Commit Object --- Commit Message ---- WIP: from fïle test. diff --git a/qa/expected/test_commits/test_lint_staged_stdin_1 b/qa/expected/test_commits/test_lint_staged_stdin_1 index 3f178f8..76b5048 100644 --- a/qa/expected/test_commits/test_lint_staged_stdin_1 +++ b/qa/expected/test_commits/test_lint_staged_stdin_1 @@ -1,9 +1,11 @@ DEBUG: gitlint.cli To report issues, please visit https://github.com/jorisroovers/gitlint/issues DEBUG: gitlint.cli Platform: {platform} DEBUG: gitlint.cli Python version: {python_version} +DEBUG: gitlint.git ('--version',) DEBUG: gitlint.cli Git version: {git_version} DEBUG: gitlint.cli Gitlint version: {gitlint_version} DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} +DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} DEBUG: gitlint.cli Configuration config-path: None [GENERAL] @@ -26,6 +28,8 @@ target: {target} I2: ignore-by-body ignore=all regex=None + I3: ignore-body-lines + regex=None T1: title-max-length line-length=72 T2: title-trailing-whitespace @@ -35,7 +39,9 @@ target: {target} T5: title-must-not-contain-word words=WIP T7: title-match-regex - regex=.* + regex=None + T8: title-min-length + min-length=5 B1: body-max-line-length line-length=80 B5: body-min-length @@ -47,6 +53,8 @@ target: {target} B4: body-first-line-empty B7: body-changed-file-mention files= + B8: body-match-regex + regex=None M1: author-valid-email regex=[^@ ]+@[^@ ]+\.[^@ ]+ @@ -54,8 +62,13 @@ DEBUG: gitlint.cli Fetching additional meta-data from staged commit DEBUG: gitlint.cli Stdin data: 'WIP: Pïpe test. ' DEBUG: gitlint.cli Stdin detected and not ignored. Using as input. +DEBUG: gitlint.git ('config', '--get', 'core.commentchar') DEBUG: gitlint.cli Linting 1 commit(s) DEBUG: gitlint.lint Linting commit [SHA UNKNOWN] +DEBUG: gitlint.git ('config', '--get', 'user.name') +DEBUG: gitlint.git ('config', '--get', 'user.email') +DEBUG: gitlint.git ('rev-parse', '--abbrev-ref', 'HEAD') +DEBUG: gitlint.git ('diff', '--staged', '--name-only', '-r') DEBUG: gitlint.lint Commit Object --- Commit Message ---- WIP: Pïpe test. diff --git a/qa/expected/test_config/test_config_from_env_1 b/qa/expected/test_config/test_config_from_env_1 new file mode 100644 index 0000000..dd761da --- /dev/null +++ b/qa/expected/test_config/test_config_from_env_1 @@ -0,0 +1,93 @@ +DEBUG: gitlint.cli To report issues, please visit https://github.com/jorisroovers/gitlint/issues +DEBUG: gitlint.cli Platform: {platform} +DEBUG: gitlint.cli Python version: {python_version} +DEBUG: gitlint.git ('--version',) +DEBUG: gitlint.cli Git version: {git_version} +DEBUG: gitlint.cli Gitlint version: {gitlint_version} +DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} +DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli Configuration +config-path: None +[GENERAL] +extra-path: None +contrib: ['CC1', 'CT1'] +ignore: T1,T2 +ignore-merge-commits: True +ignore-fixup-commits: True +ignore-squash-commits: True +ignore-revert-commits: True +ignore-stdin: True +staged: False +verbosity: 2 +debug: True +target: {target} +[RULES] + I1: ignore-by-title + ignore=all + regex=None + I2: ignore-by-body + ignore=all + regex=None + I3: ignore-body-lines + regex=None + T1: title-max-length + line-length=72 + T2: title-trailing-whitespace + T6: title-leading-whitespace + T3: title-trailing-punctuation + T4: title-hard-tab + T5: title-must-not-contain-word + words=WIP + T7: title-match-regex + regex=None + T8: title-min-length + min-length=5 + B1: body-max-line-length + line-length=80 + B5: body-min-length + min-length=20 + B6: body-is-missing + ignore-merge-commits=True + B2: body-trailing-whitespace + B3: body-hard-tab + B4: body-first-line-empty + B7: body-changed-file-mention + files= + B8: body-match-regex + regex=None + M1: author-valid-email + regex=[^@ ]+@[^@ ]+\.[^@ ]+ + CC1: contrib-body-requires-signed-off-by + CT1: contrib-title-conventional-commits + types=fix,feat,chore,docs,style,refactor,perf,test,revert,ci,build + +DEBUG: gitlint.cli No --msg-filename flag, no or empty data passed to stdin. Using the local repo. +DEBUG: gitlint.git ('rev-list', '{commit_sha}') +DEBUG: gitlint.cli Linting 1 commit(s) +DEBUG: gitlint.git ('log', '{commit_sha}', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B') +DEBUG: gitlint.git ('config', '--get', 'core.commentchar') +DEBUG: gitlint.lint Linting commit {commit_sha} +DEBUG: gitlint.git ('branch', '--contains', '{commit_sha}') +DEBUG: gitlint.git ('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', '{commit_sha}') +DEBUG: gitlint.lint Commit Object +--- Commit Message ---- +WIP: Thïs is a title thåt is a bit longer. +Content on the second line +This line of the body is here because we need it + +--- Meta info --------- +Author: gitlint-test-user +Date: {commit_date} +is-merge-commit: False +is-fixup-commit: False +is-squash-commit: False +is-revert-commit: False +Branches: ['master'] +Changed Files: {changed_files} +----------------------- +1: CC1 Body does not contain a 'Signed-Off-By' line +1: CT1 Title does not start with one of fix, feat, chore, docs, style, refactor, perf, test, revert, ci, build +1: T3 Title has trailing punctuation (.) +1: T5 Title contains the word 'WIP' (case-insensitive) +2: B4 Second line is not empty +DEBUG: gitlint.cli Exit Code = 5 diff --git a/qa/expected/test_config/test_config_from_env_2 b/qa/expected/test_config/test_config_from_env_2 new file mode 100644 index 0000000..8d36672 --- /dev/null +++ b/qa/expected/test_config/test_config_from_env_2 @@ -0,0 +1,83 @@ +DEBUG: gitlint.cli To report issues, please visit https://github.com/jorisroovers/gitlint/issues +DEBUG: gitlint.cli Platform: {platform} +DEBUG: gitlint.cli Python version: {python_version} +DEBUG: gitlint.git ('--version',) +DEBUG: gitlint.cli Git version: {git_version} +DEBUG: gitlint.cli Gitlint version: {gitlint_version} +DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} +DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} +DEBUG: gitlint.cli Configuration +config-path: None +[GENERAL] +extra-path: None +contrib: [] +ignore: +ignore-merge-commits: True +ignore-fixup-commits: True +ignore-squash-commits: True +ignore-revert-commits: True +ignore-stdin: False +staged: True +verbosity: 0 +debug: True +target: {target} +[RULES] + I1: ignore-by-title + ignore=all + regex=None + I2: ignore-by-body + ignore=all + regex=None + I3: ignore-body-lines + regex=None + T1: title-max-length + line-length=72 + T2: title-trailing-whitespace + T6: title-leading-whitespace + T3: title-trailing-punctuation + T4: title-hard-tab + T5: title-must-not-contain-word + words=WIP + T7: title-match-regex + regex=None + T8: title-min-length + min-length=5 + B1: body-max-line-length + line-length=80 + B5: body-min-length + min-length=20 + B6: body-is-missing + ignore-merge-commits=True + B2: body-trailing-whitespace + B3: body-hard-tab + B4: body-first-line-empty + B7: body-changed-file-mention + files= + B8: body-match-regex + regex=None + M1: author-valid-email + regex=[^@ ]+@[^@ ]+\.[^@ ]+ + +DEBUG: gitlint.cli Fetching additional meta-data from staged commit +DEBUG: gitlint.cli Using --msg-filename. +DEBUG: gitlint.git ('config', '--get', 'core.commentchar') +DEBUG: gitlint.cli Linting 1 commit(s) +DEBUG: gitlint.lint Linting commit [SHA UNKNOWN] +DEBUG: gitlint.git ('config', '--get', 'user.name') +DEBUG: gitlint.git ('config', '--get', 'user.email') +DEBUG: gitlint.git ('rev-parse', '--abbrev-ref', 'HEAD') +DEBUG: gitlint.git ('diff', '--staged', '--name-only', '-r') +DEBUG: gitlint.lint Commit Object +--- Commit Message ---- +WIP: msg-fïlename test. +--- Meta info --------- +Author: gitlint-test-user +Date: {date} +is-merge-commit: False +is-fixup-commit: False +is-squash-commit: False +is-revert-commit: False +Branches: ['master'] +Changed Files: [] +----------------------- +DEBUG: gitlint.cli Exit Code = 3 diff --git a/qa/expected/test_config/test_config_from_file_debug_1 b/qa/expected/test_config/test_config_from_file_debug_1 index 443ee26..540c3a0 100644 --- a/qa/expected/test_config/test_config_from_file_debug_1 +++ b/qa/expected/test_config/test_config_from_file_debug_1 @@ -1,9 +1,11 @@ DEBUG: gitlint.cli To report issues, please visit https://github.com/jorisroovers/gitlint/issues DEBUG: gitlint.cli Platform: {platform} DEBUG: gitlint.cli Python version: {python_version} +DEBUG: gitlint.git ('--version',) DEBUG: gitlint.cli Git version: {git_version} DEBUG: gitlint.cli Gitlint version: {gitlint_version} DEBUG: gitlint.cli GITLINT_USE_SH_LIB: {GITLINT_USE_SH_LIB} +DEBUG: gitlint.cli DEFAULT_ENCODING: {DEFAULT_ENCODING} DEBUG: gitlint.cli Configuration config-path: {config_path} [GENERAL] @@ -26,6 +28,8 @@ target: {target} I2: ignore-by-body ignore=all regex=None + I3: ignore-body-lines + regex=None T1: title-max-length line-length=20 T2: title-trailing-whitespace @@ -35,7 +39,9 @@ target: {target} T5: title-must-not-contain-word words=WIP,thåt T7: title-match-regex - regex=.* + regex=None + T8: title-min-length + min-length=5 B1: body-max-line-length line-length=30 B5: body-min-length @@ -47,12 +53,19 @@ target: {target} B4: body-first-line-empty B7: body-changed-file-mention files= + B8: body-match-regex + regex=None M1: author-valid-email regex=[^@ ]+@[^@ ]+\.[^@ ]+ DEBUG: gitlint.cli No --msg-filename flag, no or empty data passed to stdin. Using the local repo. +DEBUG: gitlint.git ('log', '-1', '--pretty=%H') DEBUG: gitlint.cli Linting 1 commit(s) +DEBUG: gitlint.git ('log', '{commit_sha}', '-1', '--pretty=%aN%x00%aE%x00%ai%x00%P%n%B') +DEBUG: gitlint.git ('config', '--get', 'core.commentchar') DEBUG: gitlint.lint Linting commit {commit_sha} +DEBUG: gitlint.git ('branch', '--contains', '{commit_sha}') +DEBUG: gitlint.git ('diff-tree', '--no-commit-id', '--name-only', '-r', '--root', '{commit_sha}') DEBUG: gitlint.lint Commit Object --- Commit Message ---- WIP: Thïs is a title thåt is a bit longer. diff --git a/qa/expected/test_contrib/test_contrib_rules_1 b/qa/expected/test_contrib/test_contrib_rules_1 index 99b33b7..0d333bc 100644 --- a/qa/expected/test_contrib/test_contrib_rules_1 +++ b/qa/expected/test_contrib/test_contrib_rules_1 @@ -1,4 +1,4 @@ 1: CC1 Body does not contain a 'Signed-Off-By' line -1: CT1 Title does not start with one of fix, feat, chore, docs, style, refactor, perf, test, revert: "WIP Thi$ is å title" +1: CT1 Title does not start with one of fix, feat, chore, docs, style, refactor, perf, test, revert, ci, build: "WIP Thi$ is å title" 1: CT1 Title does not follow ConventionalCommits.org format 'type(optional-scope): description': "WIP Thi$ is å title" 1: T5 Title contains the word 'WIP' (case-insensitive): "WIP Thi$ is å title" diff --git a/qa/expected/test_named_rules/test_named_rule_1 b/qa/expected/test_named_rules/test_named_rule_1 new file mode 100644 index 0000000..e5a380c --- /dev/null +++ b/qa/expected/test_named_rules/test_named_rule_1 @@ -0,0 +1,5 @@ +1: T5 Title contains the word 'WIP' (case-insensitive): "WIP: thåt dûr bår" +1: T5 Title contains the word 'thåt' (case-insensitive): "WIP: thåt dûr bår" +1: T5:even$more%wôrds Title contains the word 'bår' (case-insensitive): "WIP: thåt dûr bår" +1: T5:extra-wôrds Title contains the word 'dûr' (case-insensitive): "WIP: thåt dûr bår" +3: B5 Body message is too short (18<20): "Sïmple commit body" diff --git a/qa/expected/test_named_rules/test_named_user_rule_1 b/qa/expected/test_named_rules/test_named_user_rule_1 new file mode 100644 index 0000000..3cd18b4 --- /dev/null +++ b/qa/expected/test_named_rules/test_named_user_rule_1 @@ -0,0 +1,9 @@ +1: UC4 int-öption: 2 +1: UC4 str-öption: föo +1: UC4 list-öption: ['foo', 'bar'] +1: UC4:bår int-öption: 2 +1: UC4:bår str-öption: bår +1: UC4:bår list-öption: ['bar', 'list'] +1: UC4:föo int-öption: 3 +1: UC4:föo str-öption: föo +1: UC4:föo list-öption: ['foo', 'bar'] diff --git a/qa/expected/test_user_defined/test_user_defined_rules_examples_2 b/qa/expected/test_user_defined/test_user_defined_rules_examples_2 new file mode 100644 index 0000000..31a9280 --- /dev/null +++ b/qa/expected/test_user_defined/test_user_defined_rules_examples_2 @@ -0,0 +1,4 @@ +1: UC2 Body does not contain a 'Signed-Off-By' line +1: UC3 Branch name 'master' does not start with one of ['feature/', 'hotfix/', 'release/'] +1: UL1 Title contains the special character '$' +2: B4 Second line is not empty diff --git a/qa/expected/test_user_defined/test_user_defined_rules_extra_1 b/qa/expected/test_user_defined/test_user_defined_rules_extra_1 index 65f3507..1f48fad 100644 --- a/qa/expected/test_user_defined/test_user_defined_rules_extra_1 +++ b/qa/expected/test_user_defined/test_user_defined_rules_extra_1 @@ -2,4 +2,8 @@ 1: UC1 GitContext.current_branch: master 1: UC1 GitContext.commentchar: # 1: UC2 GitCommit.branches: ['master'] -2: B4 Second line is not empty: "Content on the second line" +1: UC2 GitCommit.custom_prop: foöbar +1: UC4 int-öption: 2 +1: UC4 str-öption: föo +1: UC4 list-öption: ['foo', 'bar'] +4: B2 Line has trailing whitespace: "{repo-path} " diff --git a/qa/samples/config/contrib-enabled b/qa/samples/config/contrib-enabled deleted file mode 100644 index e69de29..0000000 diff --git a/qa/samples/config/named-rules b/qa/samples/config/named-rules new file mode 100644 index 0000000..f9bbdf5 --- /dev/null +++ b/qa/samples/config/named-rules @@ -0,0 +1,8 @@ +[title-must-not-contain-word] +words=WIP,thåt + +[title-must-not-contain-word:extra-wôrds] +words=hûr,dûr + +[title-must-not-contain-word: even$more%wôrds ] +words=fôo,bår \ No newline at end of file diff --git a/qa/samples/config/named-user-rules b/qa/samples/config/named-user-rules new file mode 100644 index 0000000..ed811fb --- /dev/null +++ b/qa/samples/config/named-user-rules @@ -0,0 +1,15 @@ +# Ignore other user-defined rules +[general] +ignore=UC1,UC2,UC3,configürable:ignöred + +[UC4:föo] +int-öption=3 +str-öption=föo + +[configürable:bår] +str-öption=bår +list-öption=bar,list + +# The following rule will be ignored +[configürable:ignöred] +str-öption=foöbar \ No newline at end of file diff --git a/qa/samples/user_rules/extra/extra_rules.py b/qa/samples/user_rules/extra/extra_rules.py index 8109299..6fb985f 100644 --- a/qa/samples/user_rules/extra/extra_rules.py +++ b/qa/samples/user_rules/extra/extra_rules.py @@ -1,16 +1,19 @@ -from gitlint.rules import CommitRule, RuleViolation +# -*- coding: utf-8 -*- + +from gitlint.rules import CommitRule, RuleViolation, ConfigurationRule +from gitlint.options import IntOption, StrOption, ListOption from gitlint.utils import sstr class GitContextRule(CommitRule): """ Rule that tests whether we can correctly access certain gitcontext properties """ - name = "gitcontext" + name = u"gïtcontext" id = "UC1" def validate(self, commit): violations = [ - RuleViolation(self.id, "GitContext.current_branch: {0}".format(commit.context.current_branch), line_nr=1), - RuleViolation(self.id, "GitContext.commentchar: {0}".format(commit.context.commentchar), line_nr=1) + RuleViolation(self.id, u"GitContext.current_branch: {0}".format(commit.context.current_branch), line_nr=1), + RuleViolation(self.id, u"GitContext.commentchar: {0}".format(commit.context.commentchar), line_nr=1) ] return violations @@ -18,12 +21,49 @@ class GitContextRule(CommitRule): class GitCommitRule(CommitRule): """ Rule that tests whether we can correctly access certain commit properties """ - name = "gitcommit" + name = u"gïtcommit" id = "UC2" def validate(self, commit): violations = [ - RuleViolation(self.id, "GitCommit.branches: {0}".format(sstr(commit.branches)), line_nr=1), + RuleViolation(self.id, u"GitCommit.branches: {0}".format(sstr(commit.branches)), line_nr=1), + RuleViolation(self.id, u"GitCommit.custom_prop: {0}".format(commit.custom_prop), line_nr=1), + ] + + return violations + + +class GitlintConfigurationRule(ConfigurationRule): + """ Rule that tests whether we can correctly access the config as well as modify the commit message """ + name = u"cönfigrule" + id = "UC3" + + def apply(self, config, commit): + # We add a line to the commit message body that pulls a value from config, this proves we can modify the body + # and read the config contents + commit.message.body.append("{0} ".format(config.target)) # trailing whitespace deliberate to trigger violation + + # We set a custom property that we access in CommitRule, to prove we can add extra properties to the commit + commit.custom_prop = u"foöbar" + + # We also ignore some extra rules, proving that we can modify the config + config.ignore.append("B4") + + +class ConfigurableCommitRule(CommitRule): + """ Rule that tests that we can add configuration to user-defined rules """ + name = u"configürable" + id = "UC4" + + options_spec = [IntOption(u"int-öption", 2, u"int-öption description"), + StrOption(u"str-öption", u"föo", u"int-öption description"), + ListOption(u"list-öption", [u"foo", u"bar"], u"list-öption description")] + + def validate(self, _): + violations = [ + RuleViolation(self.id, u"int-öption: {0}".format(self.options[u'int-öption'].value), line_nr=1), + RuleViolation(self.id, u"str-öption: {0}".format(self.options[u'str-öption'].value), line_nr=1), + RuleViolation(self.id, u"list-öption: {0}".format(sstr(self.options[u'list-öption'].value)), line_nr=1), ] return violations diff --git a/qa/shell.py b/qa/shell.py index 8ba6dc1..43e5bbd 100644 --- a/qa/shell.py +++ b/qa/shell.py @@ -3,8 +3,7 @@ # on gitlint internals for our integration testing framework. import subprocess -import sys -from qa.utils import ustr, USE_SH_LIB +from qa.utils import ustr, USE_SH_LIB, IS_PY2 if USE_SH_LIB: from sh import git, echo, gitlint # pylint: disable=unused-import,no-name-in-module,import-error @@ -59,7 +58,7 @@ else: return ustr(result) def _exec(*args, **kwargs): - if sys.version_info[0] == 2: + if IS_PY2: no_command_error = OSError # noqa pylint: disable=undefined-variable,invalid-name else: no_command_error = FileNotFoundError # noqa pylint: disable=undefined-variable @@ -68,6 +67,8 @@ else: popen_kwargs = {'stdout': pipe, 'stderr': pipe, 'shell': kwargs.get('_tty_out', False)} if '_cwd' in kwargs: popen_kwargs['cwd'] = kwargs['_cwd'] + if '_env' in kwargs: + popen_kwargs['env'] = kwargs['_env'] try: p = subprocess.Popen(args, **popen_kwargs) diff --git a/qa/test_config.py b/qa/test_config.py index b893b1d..9415990 100644 --- a/qa/test_config.py +++ b/qa/test_config.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- # pylint: disable=too-many-function-args,unexpected-keyword-arg + +import re + from qa.shell import gitlint from qa.base import BaseTestCase -from qa.utils import sstr +from qa.utils import sstr, ustr class ConfigTests(BaseTestCase): @@ -52,7 +55,7 @@ class ConfigTests(BaseTestCase): self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_file_1")) def test_config_from_file_debug(self): - # Test bot on existing and new repo (we've had a bug in the past that was unique to empty repos) + # Test both on existing and new repo (we've had a bug in the past that was unique to empty repos) repos = [self.tmp_git_repo, self.create_tmp_git_repo()] for target_repo in repos: commit_msg = u"WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \ @@ -65,3 +68,39 @@ class ConfigTests(BaseTestCase): expected_kwargs.update({'config_path': config_path, 'changed_files': sstr([filename])}) self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_file_debug_1", expected_kwargs)) + + def test_config_from_env(self): + """ Test for configuring gitlint from environment variables """ + + # We invoke gitlint, configuring it via env variables, we can check whether gitlint picks these up correctly + # by comparing the debug output with what we'd expect + target_repo = self.create_tmp_git_repo() + commit_msg = u"WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \ + "This line of the body is here because we need it" + filename = self.create_simple_commit(commit_msg, git_repo=target_repo) + env = self.create_environment({"GITLINT_DEBUG": "1", "GITLINT_VERBOSITY": "2", + "GITLINT_IGNORE": "T1,T2", "GITLINT_CONTRIB": "CC1,CT1", + "GITLINT_IGNORE_STDIN": "1", "GITLINT_TARGET": target_repo, + "GITLINT_COMMITS": self.get_last_commit_hash(git_repo=target_repo)}) + output = gitlint(_env=env, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5]) + expected_kwargs = self.get_debug_vars_last_commit(git_repo=target_repo) + expected_kwargs.update({'changed_files': sstr([filename])}) + + self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_env_1", expected_kwargs)) + + # For some env variables, we need a separate test ast they are mutually exclusive with the ones tested above + tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename test.") + env = self.create_environment({"GITLINT_DEBUG": "1", "GITLINT_TARGET": target_repo, + "GITLINT_SILENT": "1", "GITLINT_STAGED": "1"}) + + output = gitlint("--msg-filename", tmp_commit_msg_file, + _env=env, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3]) + + # Extract date from actual output to insert it into the expected output + # We have to do this since there's no way for us to deterministically know that date otherwise + p = re.compile("Date: (.*)\n", re.UNICODE | re.MULTILINE) + result = p.search(ustr(output.stdout)) + date = result.group(1).strip() + expected_kwargs.update({"date": date}) + + self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_env_2", expected_kwargs)) diff --git a/qa/test_gitlint.py b/qa/test_gitlint.py index 4762721..2e837b9 100644 --- a/qa/test_gitlint.py +++ b/qa/test_gitlint.py @@ -157,6 +157,28 @@ class IntegrationTests(BaseTestCase): self.assertEqualStdout(output, self.get_expected("test_gitlint/test_msg_filename_no_tty_1")) + def test_no_git_name_set(self): + """ Ensure we print out a helpful message if user.name is not set """ + tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename NO name test.") + # Name is checked before email so this isn't strictly + # necessary but seems good for consistency. + env = self.create_tmp_git_config(u"[user]\n email = test-emåil@foo.com\n") + output = gitlint("--staged", "--msg-filename", tmp_commit_msg_file, + _ok_code=[self.GIT_CONTEXT_ERROR_CODE], + _env=env) + expected = u"Missing git configuration: please set user.name\n" + self.assertEqualStdout(output, expected) + + def test_no_git_email_set(self): + """ Ensure we print out a helpful message if user.email is not set """ + tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename NO email test.") + env = self.create_tmp_git_config(u"[user]\n name = test åuthor\n") + output = gitlint("--staged", "--msg-filename", tmp_commit_msg_file, + _ok_code=[self.GIT_CONTEXT_ERROR_CODE], + _env=env) + expected = u"Missing git configuration: please set user.email\n" + self.assertEqualStdout(output, expected) + def test_git_errors(self): # Repo has no commits: caused by `git log` empty_git_repo = self.create_tmp_git_repo() diff --git a/qa/test_hooks.py b/qa/test_hooks.py index a41580b..7c07a61 100644 --- a/qa/test_hooks.py +++ b/qa/test_hooks.py @@ -53,6 +53,18 @@ class HookTests(BaseTestCase): stdin.put("{0}\n".format(response)) self.response_index = (self.response_index + 1) % len(self.responses) + def test_commit_hook_no_violations(self): + test_filename = self.create_simple_commit(u"This ïs a title\n\nBody contënt that should work", + out=self._interact, tty_in=True) + + short_hash = self.get_last_commit_short_hash() + expected_output = ["gitlint: checking commit message...\n", + "gitlint: \x1b[32mOK\x1b[0m (no violations in commit message)\n", + u"[master %s] This ïs a title\n" % short_hash, + " 1 file changed, 0 insertions(+), 0 deletions(-)\n", + u" create mode 100644 %s\n" % test_filename] + self.assertListEqual(expected_output, self.githook_output) + def test_commit_hook_continue(self): self.responses = ["y"] test_filename = self.create_simple_commit(u"WIP: This ïs a title.\nContënt on the second line", diff --git a/qa/test_named_rules.py b/qa/test_named_rules.py new file mode 100644 index 0000000..6020bbf --- /dev/null +++ b/qa/test_named_rules.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from qa.shell import gitlint +from qa.base import BaseTestCase + + +class NamedRuleTests(BaseTestCase): + """ Integration tests for named rules.""" + + def test_named_rule(self): + commit_msg = u"WIP: thåt dûr bår\n\nSïmple commit body" + self.create_simple_commit(commit_msg) + config_path = self.get_sample_path("config/named-rules") + output = gitlint("--config", config_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5]) + self.assertEqualStdout(output, self.get_expected("test_named_rules/test_named_rule_1")) + + def test_named_user_rule(self): + commit_msg = u"Normal cömmit title\n\nSïmple commit message body" + self.create_simple_commit(commit_msg) + config_path = self.get_sample_path("config/named-user-rules") + extra_path = self.get_sample_path("user_rules/extra") + output = gitlint("--extra-path", extra_path, "--config", config_path, _cwd=self.tmp_git_repo, _tty_in=True, + _ok_code=[9]) + self.assertEqualStdout(output, self.get_expected("test_named_rules/test_named_user_rule_1")) diff --git a/qa/test_user_defined.py b/qa/test_user_defined.py index cf7effd..566d0b2 100644 --- a/qa/test_user_defined.py +++ b/qa/test_user_defined.py @@ -7,14 +7,24 @@ from qa.base import BaseTestCase class UserDefinedRuleTests(BaseTestCase): """ Integration tests for user-defined rules.""" - def test_user_defined_rules_examples(self): + def test_user_defined_rules_examples1(self): + """ Test the user defined rules in the top-level `examples/` directory """ extra_path = self.get_example_path() commit_msg = u"WIP: Thi$ is å title\nContent on the second line" self.create_simple_commit(commit_msg) output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5]) self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_examples_1")) + def test_user_defined_rules_examples2(self): + """ Test the user defined rules in the top-level `examples/` directory """ + extra_path = self.get_example_path() + commit_msg = u"Release: Thi$ is å title\nContent on the second line\n$This line is ignored \nThis isn't\t\n" + self.create_simple_commit(commit_msg) + output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4]) + self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_examples_2")) + def test_user_defined_rules_examples_with_config(self): + """ Test the user defined rules in the top-level `examples/` directory """ extra_path = self.get_example_path() commit_msg = u"WIP: Thi$ is å title\nContent on the second line" self.create_simple_commit(commit_msg) @@ -27,8 +37,9 @@ class UserDefinedRuleTests(BaseTestCase): extra_path = self.get_sample_path("user_rules/extra") commit_msg = u"WIP: Thi$ is å title\nContent on the second line" self.create_simple_commit(commit_msg) - output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[5]) - self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_extra_1")) + output = gitlint("--extra-path", extra_path, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[9]) + self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_extra_1", + {'repo-path': self.tmp_git_repo})) def test_invalid_user_defined_rules(self): extra_path = self.get_sample_path("user_rules/incorrect_linerule") diff --git a/qa/utils.py b/qa/utils.py index eb9869a..f44917e 100644 --- a/qa/utils.py +++ b/qa/utils.py @@ -15,6 +15,16 @@ def platform_is_windows(): PLATFORM_IS_WINDOWS = platform_is_windows() +######################################################################################################################## +# IS_PY2 + + +def is_py2(): + return sys.version_info[0] == 2 + + +IS_PY2 = is_py2() + ######################################################################################################################## # USE_SH_LIB # Determine whether to use the `sh` library @@ -68,7 +78,7 @@ DEFAULT_ENCODING = getpreferredencoding() def ustr(obj): """ Python 2 and 3 utility method that converts an obj to unicode in python 2 and to a str object in python 3""" - if sys.version_info[0] == 2: + if IS_PY2: # If we are getting a string, then do an explicit decode # else, just call the unicode method of the object if type(obj) in [str, basestring]: # pragma: no cover # noqa @@ -86,11 +96,13 @@ def sstr(obj): """ Python 2 and 3 utility method that converts an obj to a DEFAULT_ENCODING encoded string in python 2 and to unicode in python 3. Especially useful for implementing __str__ methods in python 2: http://stackoverflow.com/a/1307210/381010""" - if sys.version_info[0] == 2: - # For lists in python2, remove unicode string representation characters. + if IS_PY2: + # For lists and tuples in python2, remove unicode string representation characters. # i.e. ensure lists are printed as ['a', 'b'] and not [u'a', u'b'] if type(obj) in [list]: return [sstr(item) for item in obj] # pragma: no cover # noqa + elif type(obj) in [tuple]: + return tuple(sstr(item) for item in obj) # pragma: no cover # noqa return unicode(obj).encode(DEFAULT_ENCODING) # pragma: no cover # noqa else: -- cgit v1.2.3