diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-01-25 13:26:08 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-01-25 13:26:08 +0000 |
commit | 3313b4f9c3c5d6a579588e77068ca3ae3edffe2b (patch) | |
tree | 81fccb2a4db79b0de9b0d64701581add3a9a1d21 | |
parent | Adding upstream version 0.14.0. (diff) | |
download | gitlint-3313b4f9c3c5d6a579588e77068ca3ae3edffe2b.tar.xz gitlint-3313b4f9c3c5d6a579588e77068ca3ae3edffe2b.zip |
Adding upstream version 0.15.0.upstream/0.15.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
75 files changed, 1285 insertions, 1559 deletions
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 710245c..15eb7be 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,7 +7,7 @@ jobs: runs-on: "ubuntu-latest" strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3] + python-version: [3.6, 3.7, 3.8, 3.9, pypy3] os: ["macos-latest", "ubuntu-latest"] steps: - uses: actions/checkout@v2 @@ -76,7 +76,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: [2.7, 3.6] + python-version: [3.6] steps: - uses: actions/checkout@v2 with: diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 5b3d51a..4b15bfd 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,5 +1,6 @@ - id: gitlint name: gitlint language: python - entry: gitlint --staged --msg-filename + entry: gitlint + args: [--staged, --msg-filename] stages: [commit-msg] diff --git a/CHANGELOG.md b/CHANGELOG.md index 509ebc8..7eb2e0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,22 @@ # Changelog # +## v0.15.0 (2020-11-27) ## + +Contributors: +Special thanks to [BrunIF](https://github.com/BrunIF), [lukech](https://github.com/lukech), [Cielquan](https://github.com/Cielquan), [harens](https://github.com/harens) and [sigmavirus24](https://github.com/sigmavirus24). + +**This release drops support for Python 2.7 and Python 3.5 ([both are EOL](https://endoflife.date/python)). Other than a few minor fixes, there are no functional differences from the 0.14.0 release.** + +Other call-outs: +- **Mac users**: Gitlint can now be installed using both homebrew (upgraded to latest) and macports. Special thanks to [@harens](https://github.com/harens) for maintaining these packages (best-effort). +- Bugfix: Gitlint now properly handles exceptions when using its built-in commit-msg hook ([#166](https://github.com/jorisroovers/gitlint/issues/166)). +- All dependencies have been upgraded to the latest available versions (`Click==7.1.2`, `arrow==0.17.0`, `sh==1.14.1`). +- Much under-the-hood refactoring as a result of dropping Python 2.7 + ## v0.14.0 (2020-10-24) ## Contributors: -Special thanks to all contributors for this release, in particular [@mrshu](https://github.com/mrshu), [@glasserc](https://github.com/glasserc), [@strk](https://github.com/strk), [@chgl](https://github.com/chgl), [@melg8](https://github.com/melg8) and [@sigmavirus24](https://github.com/sigmavirus24). +Special thanks to all contributors for this release, in particular [mrshu](https://github.com/mrshu), [glasserc](https://github.com/glasserc), [strk](https://github.com/strk), [chgl](https://github.com/chgl), [melg8](https://github.com/melg8) and [sigmavirus24](https://github.com/sigmavirus24). - **IMPORTANT: Gitlint 0.14.x will be the last gitlint release to support Python 2.7 and Python 3.5, as [both are EOL](https://endoflife.date/python) which makes it difficult to keep supporting them.** @@ -13,7 +26,7 @@ Special thanks to all contributors for this release, in particular [@mrshu](http - **New Rule**: [ignore-body-lines](http://jorisroovers.github.io/gitlint/rules/#i3-ignore-body-lines) allows users to [ignore parts of a commit](http://jorisroovers.github.io/gitlint/gitlint/#ignoring-commits) by matching a regex against the lines in a commit message body ([#126](https://github.com/jorisroovers/gitlint/issues/126)) -- [Named Rules](http://jorisroovers.github.io/gitlint/#named-rules) allow users to have multiple instances of the same rule active at the same time. This is useful when you want to enforce the same rule multiple times but with different options ([#113](https://github.com/jorisroovers/gitlint/issues/130), [#66](https://github.com/jorisroovers/gitlint/issues/130)) +- [Named Rules](http://jorisroovers.github.io/gitlint/#named-rules) allow users to have multiple instances of the same rule active at the same time. This is useful when you want to enforce the same rule multiple times but with different options ([#113](https://github.com/jorisroovers/gitlint/issues/113), [#66](https://github.com/jorisroovers/gitlint/issues/66)) - [User-defined Configuration Rules](http://jorisroovers.github.io/gitlint/user_defined_rules/#configuration-rules) allow users to dynamically change gitlint's configuration and/or the commit *before* any other rules are applied. - The `commit-msg` hook has been re-written in Python (it contained a lot of Bash before), fixing a number of platform specific issues. Existing users will need to reinstall their hooks (`gitlint uninstall-hook; gitlint install-hook`) to make use of this. - Most general options can now be set through environment variables (e.g. set the `general.ignore` option via `GITLINT_IGNORE=T1,T2`). The list of available environment variables can be found in the [configuration documentation](http://jorisroovers.github.io/gitlint/configuration). diff --git a/Vagrantfile b/Vagrantfile index 7684c1a..f913248 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -7,18 +7,19 @@ INSTALL_DEPS=<<EOF cd /vagrant sudo add-apt-repository -y ppa:deadsnakes/ppa sudo apt-get update -sudo apt-get install -y --allow-unauthenticated python2.7-dev python3.5-dev python3.6-dev python3.7-dev python3.8-dev python3.9-dev +sudo apt-get install -y --allow-unauthenticated python3.6-dev python3.7-dev python3.8-dev python3.9-dev sudo apt-get install -y --allow-unauthenticated python3.8-distutils python3.9-distutils # Needed to work around python3.8/9+virtualenv issue -sudo apt-get install -y python-virtualenv git ipython python-pip python3-pip silversearcher-ag jq +sudo apt-get install -y git python3-pip ripgrep jq sudo apt-get install -y build-essential libssl-dev libffi-dev # for rebuilding cryptography (required for pypy2) -sudo apt-get purge -y python3-virtualenv -sudo pip3 install virtualenv +sudo apt-get install -y python3-pip +pip3 install -U pip +pip3 install 'virtualenv!=20.1.0' ./run_tests.sh --uninstall --envs all ./run_tests.sh --install --envs all grep 'cd /vagrant' /home/vagrant/.bashrc || echo 'cd /vagrant' >> /home/vagrant/.bashrc -grep 'source .venv27/bin/activate' /home/vagrant/.bashrc || echo 'source .venv27/bin/activate' >> /home/vagrant/.bashrc +grep 'source .venv36/bin/activate' /home/vagrant/.bashrc || echo 'source .venv36/bin/activate' >> /home/vagrant/.bashrc EOF INSTALL_JENKINS=<<EOF @@ -31,7 +32,7 @@ EOF Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = "ubuntu/xenial64" + config.vm.box = "ubuntu/focal64" config.vm.define "dev" do |dev| dev.vm.provision "gitlint", type: "shell", inline: "#{INSTALL_DEPS}" diff --git a/docs/configuration.md b/docs/configuration.md index 78224c1..50c4e63 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -80,7 +80,7 @@ min-length=5 words=wip [title-match-regex] -# python like regex (https://docs.python.org/2/library/re.html) that the +# python like regex (https://docs.python.org/3/library/re.html) that the # commit-msg title must be matched to. # Note that the regex can contradict with other rules if not used correctly # (e.g. title-must-not-contain-word). @@ -100,8 +100,8 @@ ignore-merge-commits=false [body-changed-file-mention] # List of files that need to be explicitly mentioned in the body when they are changed # This is useful for when developers often erroneously edit certain files or git submodules. -# By specifying this rule, developers can only change the file when they explicitly reference -# it in the commit message. +# By specifying this rule, developers can only change the file when they explicitly +# reference it in the commit message. files=gitlint/rules.py,README.md [body-match-regex] @@ -110,9 +110,10 @@ files=gitlint/rules.py,README.md regex=My-Commit-Tag: foo$ [author-valid-email] -# python like regex (https://docs.python.org/2/library/re.html) that the +# python like regex (https://docs.python.org/3/library/re.html) that the # commit author email address should be matched to -# E.g.: For example, use the following regex if you only want to allow email addresses from foo.com +# E.g.: For example, use the following regex if you only want to allow email +# addresses from foo.com regex=[^@]+@foo.com [ignore-by-title] @@ -330,8 +331,10 @@ Default value | gitlint version | commandline flag | environment ```sh # CLI gitlint --contrib=contrib-title-conventional-commits,CC1 -gitlint -c general.contrib=contrib-title-conventional-commits,CC1 # different way of doing the same -GITLINT_CONTRIB=contrib-title-conventional-commits,CC1 gitlint # using env variable +# different way of doing the same +gitlint -c general.contrib=contrib-title-conventional-commits,CC1 +# using env variable +GITLINT_CONTRIB=contrib-title-conventional-commits,CC1 gitlint ``` ```ini #.gitlint @@ -341,7 +344,7 @@ contrib=contrib-title-conventional-commits,CC1 ### staged -Fetch additional meta-data from the local `repository when manually passing a commit message to gitlint via stdin or `--commit-msg`. +Fetch additional meta-data from the local repository when manually passing a commit message to gitlint via stdin or `--commit-msg`. Default value | gitlint version | commandline flag | environment variable ---------------|------------------|-------------------|----------------------- diff --git a/docs/contributing.md b/docs/contributing.md index f0d2d30..e58378c 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -34,7 +34,8 @@ and it's likely that your PR will be merged and released a lot sooner. Thanks! ## Development -There is a Vagrantfile in this repository that can be used for development. +There is a Vagrantfile (Ubuntu) in this repository that can be used for development. +It comes pre-installed with all Python versions that gitlint supports. ```sh vagrant up vagrant ssh @@ -51,7 +52,7 @@ python setup.py develop To run tests: ```sh ./run_tests.sh # run unit tests and print test coverage -./run_test.sh gitlint/tests/test_body_rules.py::BodyRuleTests::test_body_missing # run a single test +./run_tests.sh gitlint/tests/rules/test_body_rules.py::BodyRuleTests::test_body_missing # run a single test ./run_tests.sh --no-coverage # run unit tests without test coverage ./run_tests.sh --collect-only --no-coverage # Only collect, don't run unit tests ./run_tests.sh --integration # Run integration tests (requires that you have gitlint installed) @@ -63,12 +64,12 @@ To run tests: ./run_tests.sh --all # Run unit, integration, pep8 and gitlint checks ``` -The `Vagrantfile` comes with `virtualenv`s for python 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 and pypy2. +The `Vagrantfile` comes with `virtualenv`s for python 3.6, 3.7, 3.8, 3.9 and pypy3.6. You can easily run tests against specific python environments by using the following commands *inside* of the Vagrant VM: ```sh -./run_tests.sh --envs 27 # Run the unit tests against Python 2.7 -./run_tests.sh --envs 27,37,pypy2 # Run the unit tests against Python 2.7, Python 3.7 and Pypy2 -./run_tests.sh --envs 27,37 --pep8 # Run pep8 checks against Python 2.7 and Python 3.7 (also works for --git, --integration, --pep8, --stats and --lint. +./run_tests.sh --envs 36 # Run the unit tests against Python 3.6 +./run_tests.sh --envs 36,37,pypy36 # Run the unit tests against Python 3.6, Python 3.7 and Pypy3.6 +./run_tests.sh --envs 36,37 --pep8 # Run pep8 checks against Python 3.6 and Python 3.7 (also works for --git, --integration, --pep8, --stats and --lint. ./run_tests.sh --envs all --all # Run all tests against all environments ./run_tests.sh --all-env --all # Idem: Run all tests against all environments ``` diff --git a/docs/index.md b/docs/index.md index c179c9e..5b371bf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,7 +15,7 @@ Great for use as a [commit-msg git hook](#using-gitlint-as-a-commit-msg-hook) or !!! important - **Gitlint will soon be dropping support for Python 2.7 and Python 3.5 as they [have reached End-Of-Life](https://endoflife.date/python)**. + **Gitlint no longer supports Python 2.7 and Python 3.5 as they [have reached End-Of-Life](https://endoflife.date/python). The last gitlint version to support Python 2.7 and Python 3.5 is `0.14.0` (released on October 24th, 2020).** ## Features - **Commit message hook**: [Auto-trigger validations against new commit message right when you're committing](#using-gitlint-as-a-commit-msg-hook). Also [works with pre-commit](#using-gitlint-through-pre-commit). @@ -39,8 +39,8 @@ useful throughout the years. pip install gitlint # macOS -brew tap rockyluke/devops brew install gitlint +sudo port install gitlint # alternative using macports # Ubuntu apt-get install gitlint @@ -219,11 +219,14 @@ your `.pre-commit-config.yaml` file like so: rev: # Fill in a tag / sha here hooks: - id: gitlint - stages: [commit-msg] - entry: gitlint args: [--contrib=CT1, --msg-filename] ``` +!!! important + + You need to add `--msg-filename` at the end of your custom `args` list as the gitlint-hook will fail otherwise. + + ## Using gitlint in a CI environment By default, when just running `gitlint` without additional parameters, gitlint lints the last commit in the current working directory. @@ -426,4 +429,4 @@ Exit Code | Description -----------|------------------------------------------------------------ 253 | Wrong invocation of the `gitlint` command. 254 | Something went wrong when invoking git. -255 | Invalid gitlint configuration
\ No newline at end of file +255 | Invalid gitlint configuration diff --git a/docs/rules.md b/docs/rules.md index 178f962..23b8e91 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -255,8 +255,8 @@ files | >= 0.4 | (empty) | Comma-separated list o #### .gitlint ```ini -# Prevent that certain sensitive files are committed by mistake by forcing users to mention them explicitly if they're -# deliberately changing them +# Prevent that certain sensitive files are committed by mistake by forcing +# users to mention them explicitly if they're deliberately changing them [body-changed-file-mention] files=generated.xml,secrets.txt,private-key.pem ``` diff --git a/docs/user_defined_rules.md b/docs/user_defined_rules.md index 13ea544..fd944d1 100644 --- a/docs/user_defined_rules.md +++ b/docs/user_defined_rules.md @@ -152,7 +152,7 @@ class SpecialChars(LineRule): # options can be accessed by looking them up by their name in self.options for char in self.options['special-chars'].value: if char in line: - msg = "Title contains the special character '{0}'".format(char) + msg = f"Title contains the special character '{char}'" violation = RuleViolation(self.id, msg, line) violations.append(violation) @@ -262,8 +262,7 @@ class BodyMaxLineCount(CommitRule): line_count = len(commit.message.body) max_line_count = self.options['max-line-count'].value if line_count > max_line_count: - message = "Body contains too many lines ({0} > {1})".format(line_count, - max_line_count) + message = f"Body contains too many lines ({line_count} > {max_line_count})" return [RuleViolation(self.id, message, line_nr=1)] ``` @@ -371,7 +370,7 @@ class ReleaseConfigurationRule(ConfigurationRule): # You can add any extra properties you want to the commit object, # these will be available later on in all rules. - commit.my_property = u"This is my property" + commit.my_property = "This is my property" ``` For all available properties and methods on the `config` object, have a look at the diff --git a/examples/my_commit_rules.py b/examples/my_commit_rules.py index c64008f..5a66c94 100644 --- a/examples/my_commit_rules.py +++ b/examples/my_commit_rules.py @@ -2,8 +2,6 @@ from gitlint.rules import CommitRule, RuleViolation from gitlint.options import IntOption, ListOption -from gitlint import utils - """ Full details on user-defined rules: https://jorisroovers.com/gitlint/user_defined_rules @@ -37,7 +35,7 @@ class BodyMaxLineCount(CommitRule): line_count = len(commit.message.body) max_line_count = self.options['max-line-count'].value if line_count > max_line_count: - message = "Body contains too many lines ({0} > {1})".format(line_count, max_line_count) + message = f"Body contains too many lines ({line_count} > {max_line_count})" return [RuleViolation(self.id, message, line_nr=1)] @@ -90,8 +88,7 @@ class BranchNamingConventions(CommitRule): break if not valid_branch_name: - msg = "Branch name '{0}' does not start with one of {1}".format(branch, - utils.sstr(allowed_branch_prefixes)) + msg = f"Branch name '{branch}' does not start with one of {allowed_branch_prefixes}" violations.append(RuleViolation(self.id, msg, line_nr=1)) return violations diff --git a/examples/my_configuration_rules.py b/examples/my_configuration_rules.py index 58de048..7c00707 100644 --- a/examples/my_configuration_rules.py +++ b/examples/my_configuration_rules.py @@ -69,4 +69,4 @@ class ReleaseConfigurationRule(ConfigurationRule): # You can add any extra properties you want to the commit object, these will be available later on # in all rules. - commit.my_property = u"This is my property" + commit.my_property = "This is my property" diff --git a/examples/my_line_rules.py b/examples/my_line_rules.py index 820024a..3a1ef36 100644 --- a/examples/my_line_rules.py +++ b/examples/my_line_rules.py @@ -45,7 +45,7 @@ class SpecialChars(LineRule): # options can be accessed by looking them up by their name in self.options for char in self.options['special-chars'].value: if char in line: - msg = "Title contains the special character '{0}'".format(char) + msg = f"Title contains the special character '{char}'" violation = RuleViolation(self.id, msg, line) violations.append(violation) diff --git a/gitlint/__init__.py b/gitlint/__init__.py index 9e78220..9da2f8f 100644 --- a/gitlint/__init__.py +++ b/gitlint/__init__.py @@ -1 +1 @@ -__version__ = "0.14.0" +__version__ = "0.15.0" diff --git a/gitlint/cache.py b/gitlint/cache.py index b7f9e6c..1b6558f 100644 --- a/gitlint/cache.py +++ b/gitlint/cache.py @@ -1,4 +1,4 @@ -class PropertyCache(object): +class PropertyCache: """ Mixin class providing a simple cache. """ def __init__(self): @@ -13,7 +13,7 @@ class PropertyCache(object): return self._cache[cache_key] -def cache(original_func=None, cachekey=None): +def cache(original_func=None, cachekey=None): # pylint: disable=unused-argument """ Cache decorator. Caches function return values. Requires the parent class to extend and initialize PropertyCache. Usage: @@ -28,27 +28,23 @@ def cache(original_func=None, cachekey=None): ... """ - # Decorators with optional arguments are a bit convoluted in python, especially if you want to support both - # Python 2 and 3. See some of the links below for details. + # Decorators with optional arguments are a bit convoluted in python, see some of the links below for details. def cache_decorator(func): - - # If no specific cache key is given, use the function name as cache key - if not cache_decorator.cachekey: - cache_decorator.cachekey = func.__name__ + # Use 'nonlocal' keyword to access parent function variable: + # https://stackoverflow.com/a/14678445/381010 + nonlocal cachekey + if not cachekey: + cachekey = func.__name__ def wrapped(*args): def cache_func_result(): # Call decorated function and store its result in the cache - args[0]._cache[cache_decorator.cachekey] = func(*args) - return args[0]._try_cache(cache_decorator.cachekey, cache_func_result) + args[0]._cache[cachekey] = func(*args) + return args[0]._try_cache(cachekey, cache_func_result) return wrapped - # Passing parent function variables to child functions requires special voodoo in python2: - # https://stackoverflow.com/a/14678445/381010 - cache_decorator.cachekey = cachekey # attribute on the function - # To support optional kwargs for decorators, we need to check if a function is passed as first argument or not. # https://stackoverflow.com/a/24617244/381010 if original_func: diff --git a/gitlint/cli.py b/gitlint/cli.py index f284792..b162e5b 100644 --- a/gitlint/cli.py +++ b/gitlint/cli.py @@ -8,19 +8,20 @@ import stat import sys import click -# Error codes -MAX_VIOLATION_ERROR_CODE = 252 # noqa -USAGE_ERROR_CODE = 253 # noqa -GIT_CONTEXT_ERROR_CODE = 254 # noqa -CONFIG_ERROR_CODE = 255 # noqa - import gitlint from gitlint.lint import GitLinter from gitlint.config import LintConfigBuilder, LintConfigError, LintConfigGenerator from gitlint.git import GitContext, GitContextError, git_version from gitlint import hooks from gitlint.shell import shell -from gitlint.utils import ustr, LOG_FORMAT, IS_PY2 +from gitlint.utils import LOG_FORMAT +from gitlint.exception import GitlintError + +# Error codes +MAX_VIOLATION_ERROR_CODE = 252 +USAGE_ERROR_CODE = 253 +GIT_CONTEXT_ERROR_CODE = 254 +CONFIG_ERROR_CODE = 255 DEFAULT_CONFIG_FILE = ".gitlint" # -n: disable swap files. This fixes a vim error on windows (E303: Unable to open swap file for <path>) @@ -34,7 +35,7 @@ click.UsageError.exit_code = USAGE_ERROR_CODE LOG = logging.getLogger("gitlint.cli") -class GitLintUsageError(Exception): +class GitLintUsageError(GitlintError): """ Exception indicating there is an issue with how gitlint is used. """ pass @@ -134,7 +135,7 @@ def get_stdin_data(): # Only return the input data if there's actually something passed # i.e. don't consider empty piped data if input_data: - return ustr(input_data) + return str(input_data) return False @@ -151,7 +152,7 @@ def build_git_context(lint_config, msg_filename, refspec): # 1. Any data specified via --msg-filename if msg_filename: LOG.debug("Using --msg-filename.") - return from_commit_msg(ustr(msg_filename.read())) + return from_commit_msg(str(msg_filename.read())) # 2. Any data sent to stdin (unless stdin is being ignored) if not lint_config.ignore_stdin: @@ -162,15 +163,28 @@ def build_git_context(lint_config, msg_filename, refspec): return from_commit_msg(stdin_input) if lint_config.staged: - raise GitLintUsageError(u"The 'staged' option (--staged) can only be used when using '--msg-filename' or " - u"when piping data to gitlint via stdin.") + raise GitLintUsageError("The 'staged' option (--staged) can only be used when using '--msg-filename' or " + "when piping data to gitlint via stdin.") # 3. Fallback to reading from local repository LOG.debug("No --msg-filename flag, no or empty data passed to stdin. Using the local repo.") return GitContext.from_local_repository(lint_config.target, refspec) -class ContextObj(object): +def handle_gitlint_error(ctx, exc): + """ Helper function to handle exceptions """ + if isinstance(exc, GitContextError): + click.echo(exc) + ctx.exit(GIT_CONTEXT_ERROR_CODE) + elif isinstance(exc, GitLintUsageError): + click.echo(f"Error: {exc}") + ctx.exit(USAGE_ERROR_CODE) + elif isinstance(exc, LintConfigError): + click.echo(f"Config Error: {exc}") + ctx.exit(CONFIG_ERROR_CODE) + + +class ContextObj: """ Simple class to hold data that is passed between Click commands via the Click context. """ def __init__(self, config, config_builder, refspec, msg_filename, gitcontext=None): @@ -187,7 +201,7 @@ class ContextObj(object): type=click.Path(exists=True, resolve_path=True, file_okay=False, readable=True), help="Path of the target git repository. [default: current working directory]") @click.option('-C', '--config', type=click.Path(exists=True, dir_okay=False, readable=True, resolve_path=True), - help="Config file location [default: {0}]".format(DEFAULT_CONFIG_FILE)) + help=f"Config file location [default: {DEFAULT_CONFIG_FILE}]") @click.option('-c', multiple=True, help="Config flags in format <rule>.<option>=<value> (e.g.: -c T1.line-length=80). " + "Flag can be used multiple times to set multiple config values.") # pylint: disable=bad-continuation @@ -230,7 +244,7 @@ def cli( # pylint: disable=too-many-arguments # store it in the context (click allows storing an arbitrary object in ctx.obj). config, config_builder = build_config(target, config, c, extra_path, ignore, contrib, ignore_stdin, staged, verbose, silent, debug) - LOG.debug(u"Configuration\n%s", ustr(config)) + LOG.debug("Configuration\n%s", config) ctx.obj = ContextObj(config, config_builder, commits, msg_filename) @@ -238,15 +252,8 @@ def cli( # pylint: disable=too-many-arguments if ctx.invoked_subcommand is None: ctx.invoke(lint) - except GitContextError as e: - click.echo(ustr(e)) - ctx.exit(GIT_CONTEXT_ERROR_CODE) - except GitLintUsageError as e: - click.echo(u"Error: {0}".format(ustr(e))) - ctx.exit(USAGE_ERROR_CODE) - except LintConfigError as e: - click.echo(u"Config Error: {0}".format(ustr(e))) - ctx.exit(CONFIG_ERROR_CODE) + except GitlintError as e: + handle_gitlint_error(ctx, e) @cli.command("lint") @@ -294,7 +301,7 @@ def lint(ctx): if violations: # Display the commit hash & new lines intelligently if number_of_commits > 1 and commit.sha: - linter.display.e(u"{0}Commit {1}:".format( + linter.display.e("{0}Commit {1}:".format( "\n" if not first_violation or commit is last_commit else "", commit.sha[:10] )) @@ -315,10 +322,10 @@ def install_hook(ctx): try: hooks.GitHookInstaller.install_commit_msg_hook(ctx.obj.config) hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config) - click.echo(u"Successfully installed gitlint commit-msg hook in {0}".format(hook_path)) + click.echo(f"Successfully installed gitlint commit-msg hook in {hook_path}") ctx.exit(0) except hooks.GitHookInstallerError as e: - click.echo(ustr(e), err=True) + click.echo(e, err=True) ctx.exit(GIT_CONTEXT_ERROR_CODE) @@ -329,10 +336,10 @@ def uninstall_hook(ctx): try: hooks.GitHookInstaller.uninstall_commit_msg_hook(ctx.obj.config) hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config) - click.echo(u"Successfully uninstalled gitlint commit-msg hook from {0}".format(hook_path)) + click.echo(f"Successfully uninstalled gitlint commit-msg hook from {hook_path}") ctx.exit(0) except hooks.GitHookInstallerError as e: - click.echo(ustr(e), err=True) + click.echo(e, err=True) ctx.exit(GIT_CONTEXT_ERROR_CODE) @@ -344,8 +351,10 @@ def run_hook(ctx): exit_code = 1 while exit_code > 0: try: - click.echo(u"gitlint: checking commit message...") + click.echo("gitlint: checking commit message...") ctx.invoke(lint) + except GitlintError as e: + handle_gitlint_error(ctx, e) except click.exceptions.Exit as e: # Flush stderr andstdout, this resolves an issue with output ordering in Cygwin sys.stderr.flush() @@ -353,11 +362,11 @@ def run_hook(ctx): exit_code = e.exit_code if exit_code == 0: - click.echo(u"gitlint: " + click.style("OK", fg='green') + u" (no violations in commit message)") + click.echo("gitlint: " + click.style("OK", fg='green') + " (no violations in commit message)") continue - click.echo(u"-----------------------------------------------") - click.echo(u"gitlint: " + click.style("Your commit message contains the above violations.", fg='red')) + click.echo("-----------------------------------------------") + click.echo("gitlint: " + click.style("Your commit message contains the above violations.", fg='red')) value = None while value not in ["y", "n", "e"]: @@ -374,14 +383,7 @@ def run_hook(ctx): # - https://github.com/pallets/click/pull/1372 # - From https://click.palletsprojects.com/en/7.x/utils/#getting-characters-from-terminal # Note that this function will always read from the terminal, even if stdin is instead a pipe. - # - # We also need a to use raw_input() in Python2 as input() is unsafe (and raw_input() doesn't exist in - # Python3). See https://stackoverflow.com/a/4960216/381010 - input_func = input - if IS_PY2: - input_func = raw_input # noqa pylint: disable=undefined-variable - - value = input_func() + value = input() if value == "y": LOG.debug("run-hook: commit message accepted") @@ -396,15 +398,15 @@ def run_hook(ctx): LOG.debug("run-hook: %s %s", editor, msg_filename_path) shell(editor + " " + msg_filename_path) else: - click.echo(u"Editing only possible when --msg-filename is specified.") + click.echo("Editing only possible when --msg-filename is specified.") ctx.exit(exit_code) elif value == "n": LOG.debug("run-hook: commit message declined") - click.echo(u"Commit aborted.") - click.echo(u"Your commit message: ") - click.echo(u"-----------------------------------------------") + click.echo("Commit aborted.") + click.echo("Your commit message: ") + click.echo("-----------------------------------------------") click.echo(ctx.obj.gitcontext.commits[0].message.full) - click.echo(u"-----------------------------------------------") + click.echo("-----------------------------------------------") ctx.exit(exit_code) ctx.exit(exit_code) @@ -418,14 +420,14 @@ def generate_config(ctx): path = os.path.realpath(path) dir_name = os.path.dirname(path) if not os.path.exists(dir_name): - click.echo(u"Error: Directory '{0}' does not exist.".format(dir_name), err=True) + click.echo(f"Error: Directory '{dir_name}' does not exist.", err=True) ctx.exit(USAGE_ERROR_CODE) elif os.path.exists(path): - click.echo(u"Error: File \"{0}\" already exists.".format(path), err=True) + click.echo(f"Error: File \"{path}\" already exists.", err=True) ctx.exit(USAGE_ERROR_CODE) LintConfigGenerator.generate_config(path) - click.echo(u"Successfully generated {0}".format(path)) + click.echo(f"Successfully generated {path}") ctx.exit(0) diff --git a/gitlint/config.py b/gitlint/config.py index 4dad707..1eeb35d 100644 --- a/gitlint/config.py +++ b/gitlint/config.py @@ -1,9 +1,4 @@ -try: - # python 2.x - from ConfigParser import ConfigParser, Error as ConfigParserError -except ImportError: # pragma: no cover - # python 3.x - from configparser import ConfigParser, Error as ConfigParserError # pragma: no cover, pylint: disable=import-error +from configparser import ConfigParser, Error as ConfigParserError import copy import io @@ -12,11 +7,12 @@ import os import shutil from collections import OrderedDict -from gitlint.utils import ustr, sstr, DEFAULT_ENCODING +from gitlint.utils import DEFAULT_ENCODING from gitlint import rules # For some weird reason pylint complains about this, pylint: disable=unused-import from gitlint import options from gitlint import rule_finder from gitlint.contrib import rules as contrib_rules +from gitlint.exception import GitlintError def handle_option_error(func): @@ -27,16 +23,16 @@ def handle_option_error(func): try: return func(*args) except options.RuleOptionError as e: - raise LintConfigError(ustr(e)) + raise LintConfigError(str(e)) from e return wrapped -class LintConfigError(Exception): +class LintConfigError(GitlintError): pass -class LintConfig(object): +class LintConfig: """ Class representing gitlint configuration. Contains active config as well as number of methods to easily get/set the config. """ @@ -198,7 +194,7 @@ class LintConfig(object): self.rules.add_rules(rule_classes, {'is_user_defined': True}) except (options.RuleOptionError, rules.UserRuleError) as e: - raise LintConfigError(ustr(e)) + raise LintConfigError(str(e)) from e @property def contrib(self): @@ -219,27 +215,25 @@ class LintConfig(object): # For each specified contrib rule, check whether it exists among the contrib classes for rule_id_or_name in self.contrib: rule_class = next((rc for rc in rule_classes if - rc.id == ustr(rule_id_or_name) or rc.name == ustr(rule_id_or_name)), False) + rule_id_or_name in (rc.id, rc.name)), False) # If contrib rule exists, instantiate it and add it to the rules list if rule_class: self.rules.add_rule(rule_class, rule_class.id, {'is_contrib': True}) else: - raise LintConfigError(u"No contrib rule with id or name '{0}' found.".format(ustr(rule_id_or_name))) + raise LintConfigError(f"No contrib rule with id or name '{rule_id_or_name}' found.") except (options.RuleOptionError, rules.UserRuleError) as e: - raise LintConfigError(ustr(e)) + raise LintConfigError(str(e)) from e def _get_option(self, rule_name_or_id, option_name): - rule_name_or_id = ustr(rule_name_or_id) # convert to unicode first - option_name = ustr(option_name) rule = self.rules.find_rule(rule_name_or_id) if not rule: - raise LintConfigError(u"No such rule '{0}'".format(rule_name_or_id)) + raise LintConfigError(f"No such rule '{rule_name_or_id}'") option = rule.options.get(option_name) if not option: - raise LintConfigError(u"Rule '{0}' has no option '{1}'".format(rule_name_or_id, option_name)) + raise LintConfigError(f"Rule '{rule_name_or_id}' has no option '{option_name}'") return option @@ -256,14 +250,14 @@ class LintConfig(object): try: option.set(option_value) except options.RuleOptionError as e: - msg = u"'{0}' is not a valid value for option '{1}.{2}'. {3}." - raise LintConfigError(msg.format(option_value, rule_name_or_id, option_name, ustr(e))) + msg = f"'{option_value}' is not a valid value for option '{rule_name_or_id}.{option_name}'. {e}." + raise LintConfigError(msg) from e def set_general_option(self, option_name, option_value): attr_name = option_name.replace("-", "_") # only allow setting general options that exist and don't start with an underscore if not hasattr(self, attr_name) or attr_name[0] == "_": - raise LintConfigError(u"'{0}' is not a valid gitlint option".format(option_name)) + raise LintConfigError(f"'{option_name}' is not a valid gitlint option") # else: setattr(self, attr_name, option_value) @@ -285,30 +279,26 @@ class LintConfig(object): self.ignore == other.ignore and \ self._config_path == other._config_path # noqa - def __ne__(self, other): - return not self.__eq__(other) # required for py2 - def __str__(self): # config-path is not a user exposed variable, so don't print it under the general section - return_str = u"config-path: {0}\n".format(self._config_path) - return_str += u"[GENERAL]\n" - return_str += u"extra-path: {0}\n".format(self.extra_path) - return_str += u"contrib: {0}\n".format(sstr(self.contrib)) - return_str += u"ignore: {0}\n".format(",".join(self.ignore)) - return_str += u"ignore-merge-commits: {0}\n".format(self.ignore_merge_commits) - return_str += u"ignore-fixup-commits: {0}\n".format(self.ignore_fixup_commits) - return_str += u"ignore-squash-commits: {0}\n".format(self.ignore_squash_commits) - return_str += u"ignore-revert-commits: {0}\n".format(self.ignore_revert_commits) - return_str += u"ignore-stdin: {0}\n".format(self.ignore_stdin) - return_str += u"staged: {0}\n".format(self.staged) - return_str += u"verbosity: {0}\n".format(self.verbosity) - return_str += u"debug: {0}\n".format(self.debug) - return_str += u"target: {0}\n".format(self.target) - return_str += u"[RULES]\n{0}".format(self.rules) - return return_str - - -class RuleCollection(object): + return (f"config-path: {self._config_path}\n" + f"[GENERAL]\n" + f"extra-path: {self.extra_path}\n" + f"contrib: {self.contrib}\n" + f"ignore: {','.join(self.ignore)}\n" + f"ignore-merge-commits: {self.ignore_merge_commits}\n" + f"ignore-fixup-commits: {self.ignore_fixup_commits}\n" + f"ignore-squash-commits: {self.ignore_squash_commits}\n" + f"ignore-revert-commits: {self.ignore_revert_commits}\n" + f"ignore-stdin: {self.ignore_stdin}\n" + f"staged: {self.staged}\n" + f"verbosity: {self.verbosity}\n" + f"debug: {self.debug}\n" + f"target: {self.target}\n" + f"[RULES]\n{self.rules}") + + +class RuleCollection: """ Class representing an ordered list of rules. Methods are provided to easily retrieve, add or delete rules. """ def __init__(self, rule_classes=None, rule_attrs=None): @@ -318,8 +308,6 @@ class RuleCollection(object): self.add_rules(rule_classes, rule_attrs) def find_rule(self, rule_id_or_name): - # try finding rule by id - rule_id_or_name = ustr(rule_id_or_name) # convert to unicode first rule = self._rules.get(rule_id_or_name) # if not found, try finding rule by name if not rule: @@ -351,7 +339,7 @@ class RuleCollection(object): """ Deletes all rules from the collection that match a given attribute name and value """ # Create a new list based on _rules.values() because in python 3, values() is a ValuesView as opposed to a list # This means you can't modify the ValueView while iterating over it. - for rule in [r for r in self._rules.values()]: + for rule in [r for r in self._rules.values()]: # pylint: disable=unnecessary-comprehension if hasattr(rule, attr_name) and (getattr(rule, attr_name) == attr_val): del self._rules[rule.id] @@ -362,19 +350,13 @@ class RuleCollection(object): def __eq__(self, other): return isinstance(other, RuleCollection) and self._rules == other._rules - def __ne__(self, other): - return not self.__eq__(other) # required for py2 - def __len__(self): return len(self._rules) - def __repr__(self): - return self.__unicode__() # pragma: no cover - - def __unicode__(self): + def __str__(self): return_str = "" for rule in self._rules.values(): - return_str += u" {0}: {1}\n".format(rule.id, rule.name) + return_str += f" {rule.id}: {rule.name}\n" for option_name, option_value in sorted(rule.options.items()): if option_value.value is None: option_val_repr = None @@ -384,11 +366,11 @@ class RuleCollection(object): option_val_repr = option_value.value.pattern else: option_val_repr = option_value.value - return_str += u" {0}={1}\n".format(option_name, option_val_repr) + return_str += f" {option_name}={option_val_repr}\n" return return_str -class LintConfigBuilder(object): +class LintConfigBuilder: """ Factory class that can build gitlint config. This is primarily useful to deal with complex configuration scenarios where configuration can be set and overridden from various sources (typically according to certain precedence rules) before the actual config should be @@ -427,28 +409,27 @@ class LintConfigBuilder(object): raise ValueError() rule_name, option_name = config_name.split(".", 1) self.set_option(rule_name, option_name, option_value) - except ValueError: # raised if the config string is invalid + except ValueError as e: # raised if the config string is invalid raise LintConfigError( - u"'{0}' is an invalid configuration option. Use '<rule>.<option>=<value>'".format(config_option)) + f"'{config_option}' is an invalid configuration option. Use '<rule>.<option>=<value>'") from e def set_from_config_file(self, filename): """ Loads lint config from a ini-style config file """ if not os.path.exists(filename): - raise LintConfigError(u"Invalid file path: {0}".format(filename)) + raise LintConfigError(f"Invalid file path: {filename}") self._config_path = os.path.realpath(filename) try: parser = ConfigParser() with io.open(filename, encoding=DEFAULT_ENCODING) as config_file: - # readfp() is deprecated in python 3.2+, but compatible with 2.7 - parser.readfp(config_file, filename) # pylint: disable=deprecated-method + parser.read_file(config_file, filename) for section_name in parser.sections(): for option_name, option_value in parser.items(section_name): - self.set_option(section_name, option_name, ustr(option_value)) + self.set_option(section_name, option_name, str(option_value)) except ConfigParserError as e: - raise LintConfigError(ustr(e)) + raise LintConfigError(str(e)) from e def _add_named_rule(self, config, qualified_rule_name): """ Adds a Named Rule to a given LintConfig object. @@ -465,14 +446,14 @@ class LintConfigBuilder(object): # - not empty # - no whitespace or colons if rule_name == "" or bool(re.search("\\s|:", rule_name, re.UNICODE)): - msg = u"The rule-name part in '{0}' cannot contain whitespace, colons or be empty" - raise LintConfigError(msg.format(qualified_rule_name)) + msg = f"The rule-name part in '{qualified_rule_name}' cannot contain whitespace, colons or be empty" + raise LintConfigError(msg) # find parent rule parent_rule = config.rules.find_rule(parent_rule_specifier) if not parent_rule: - msg = u"No such rule '{0}' (named rule: '{1}')" - raise LintConfigError(msg.format(parent_rule_specifier, qualified_rule_name)) + msg = f"No such rule '{parent_rule_specifier}' (named rule: '{qualified_rule_name}')" + raise LintConfigError(msg) # Determine canonical id and name by recombining the parent id/name and instance name parts. canonical_id = parent_rule.__class__.id + self.RULE_QUALIFIER_SYMBOL + rule_name @@ -525,7 +506,7 @@ class LintConfigBuilder(object): GITLINT_CONFIG_TEMPLATE_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files/gitlint") -class LintConfigGenerator(object): +class LintConfigGenerator: @staticmethod def generate_config(dest): """ Generates a gitlint config file at the given destination location. diff --git a/gitlint/contrib/rules/conventional_commit.py b/gitlint/contrib/rules/conventional_commit.py index 8530343..71f6adf 100644 --- a/gitlint/contrib/rules/conventional_commit.py +++ b/gitlint/contrib/rules/conventional_commit.py @@ -2,7 +2,6 @@ import re from gitlint.options import ListOption from gitlint.rules import CommitMessageTitle, LineRule, RuleViolation -from gitlint.utils import ustr RULE_REGEX = re.compile(r"[^(]+?(\([^)]+?\))?: .+") @@ -26,14 +25,14 @@ class ConventionalCommit(LineRule): violations = [] for commit_type in self.options["types"].value: - if line.startswith(ustr(commit_type)): + if line.startswith(commit_type): break else: - msg = u"Title does not start with one of {0}".format(', '.join(self.options['types'].value)) + msg = "Title does not start with one of {0}".format(', '.join(self.options['types'].value)) violations.append(RuleViolation(self.id, msg, line)) if not RULE_REGEX.match(line): - msg = u"Title does not follow ConventionalCommits.org format 'type(optional-scope): description'" + msg = "Title does not follow ConventionalCommits.org format 'type(optional-scope): description'" violations.append(RuleViolation(self.id, msg, line)) return violations diff --git a/gitlint/display.py b/gitlint/display.py index c66a256..c9bcb01 100644 --- a/gitlint/display.py +++ b/gitlint/display.py @@ -1,18 +1,7 @@ -import codecs -import locale from sys import stdout, stderr -from gitlint.utils import IS_PY2 -# For some reason, python 2.x sometimes messes up with printing unicode chars to stdout/stderr -# This is mostly when there is a mismatch between the terminal encoding and the python encoding. -# This use-case is primarily triggered when piping input between commands, in particular our integration tests -# tend to trip over this. -if IS_PY2: - stdout = codecs.getwriter(locale.getpreferredencoding())(stdout) # pylint: disable=invalid-name - stderr = codecs.getwriter(locale.getpreferredencoding())(stderr) # pylint: disable=invalid-name - -class Display(object): +class Display: """ Utility class to print stuff to an output stream (stdout by default) based on the config's verbosity """ def __init__(self, lint_config): diff --git a/gitlint/exception.py b/gitlint/exception.py new file mode 100644 index 0000000..aee3fe2 --- /dev/null +++ b/gitlint/exception.py @@ -0,0 +1,4 @@ + +class GitlintError(Exception): + """ Based Exception class for all gitlint exceptions """ + pass diff --git a/gitlint/git.py b/gitlint/git.py index 8e00f89..a9609d0 100644 --- a/gitlint/git.py +++ b/gitlint/git.py @@ -8,7 +8,7 @@ from gitlint import shell as sh from gitlint.shell import CommandNotFound, ErrorReturnCode from gitlint.cache import PropertyCache, cache -from gitlint.utils import ustr, sstr +from gitlint.exception import GitlintError # For now, the git date format we use is fixed, but technically this format is determined by `git config log.date` # We should fix this at some point :-) @@ -17,24 +17,23 @@ GIT_TIMEFORMAT = "YYYY-MM-DD HH:mm:ss Z" LOG = logging.getLogger(__name__) -class GitContextError(Exception): +class GitContextError(GitlintError): """ Exception indicating there is an issue with the git context """ pass class GitNotInstalledError(GitContextError): def __init__(self): - super(GitNotInstalledError, self).__init__( - u"'git' command not found. You need to install git to use gitlint on a local repository. " + - u"See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git.") + super().__init__( + "'git' command not found. You need to install git to use gitlint on a local repository. " + + "See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git.") class GitExitCodeError(GitContextError): def __init__(self, command, stderr): self.command = command self.stderr = stderr - super(GitExitCodeError, self).__init__( - u"An error occurred while executing '{0}': {1}".format(command, stderr)) + super().__init__(f"An error occurred while executing '{command}': {stderr}") def _git(*command_parts, **kwargs): @@ -42,33 +41,33 @@ def _git(*command_parts, **kwargs): git_kwargs = {'_tty_out': False} git_kwargs.update(kwargs) try: - LOG.debug(sstr(command_parts)) + LOG.debug(command_parts) result = sh.git(*command_parts, **git_kwargs) # pylint: disable=unexpected-keyword-arg # If we reach this point and the result has an exit_code that is larger than 0, this means that we didn't # get an exception (which is the default sh behavior for non-zero exit codes) and so the user is expecting # a non-zero exit code -> just return the entire result if hasattr(result, 'exit_code') and result.exit_code > 0: return result - return ustr(result) - except CommandNotFound: - raise GitNotInstalledError() + return str(result) + except CommandNotFound as e: + raise GitNotInstalledError from e except ErrorReturnCode as e: # Something went wrong while executing the git command error_msg = e.stderr.strip() error_msg_lower = error_msg.lower() if '_cwd' in git_kwargs and b"not a git repository" in error_msg_lower: - error_msg = u"{0} is not a git repository.".format(git_kwargs['_cwd']) - raise GitContextError(error_msg) + raise GitContextError(f"{git_kwargs['_cwd']} is not a git repository.") from e if (b"does not have any commits yet" in error_msg_lower or b"ambiguous argument 'head': unknown revision" in error_msg_lower): - raise GitContextError(u"Current branch has no commits. Gitlint requires at least one commit to function.") + msg = "Current branch has no commits. Gitlint requires at least one commit to function." + raise GitContextError(msg) from e - raise GitExitCodeError(e.full_cmd, error_msg) + raise GitExitCodeError(e.full_cmd, error_msg) from e def git_version(): """ Determine the git version installed on this host by calling git --version""" - return _git("--version").replace(u"\n", u"") + return _git("--version").replace("\n", "") def git_commentchar(repository_path=None): @@ -77,17 +76,17 @@ def git_commentchar(repository_path=None): # git will return an exit code of 1 if it can't find a config value, in this case we fall-back to # as commentchar if hasattr(commentchar, 'exit_code') and commentchar.exit_code == 1: # pylint: disable=no-member commentchar = "#" - return ustr(commentchar).replace(u"\n", u"") + return commentchar.replace("\n", "") def git_hooks_dir(repository_path): """ Determine hooks directory for a given target dir """ hooks_dir = _git("rev-parse", "--git-path", "hooks", _cwd=repository_path) - hooks_dir = ustr(hooks_dir).replace(u"\n", u"") + hooks_dir = hooks_dir.replace("\n", "") return os.path.realpath(os.path.join(repository_path, hooks_dir)) -class GitCommitMessage(object): +class GitCommitMessage: """ Class representing a git commit message. A commit message consists of the following: - context: The `GitContext` this commit message is part of - original: The actual commit message as returned by `git log` @@ -106,35 +105,26 @@ class GitCommitMessage(object): def from_full_message(context, commit_msg_str): """ Parses a full git commit message by parsing a given string into the different parts of a commit message """ all_lines = commit_msg_str.splitlines() - cutline = u"{0} ------------------------ >8 ------------------------".format(context.commentchar) + cutline = f"{context.commentchar} ------------------------ >8 ------------------------" try: cutline_index = all_lines.index(cutline) except ValueError: cutline_index = None - lines = [ustr(line) for line in all_lines[:cutline_index] if not line.startswith(context.commentchar)] + lines = [line for line in all_lines[:cutline_index] if not line.startswith(context.commentchar)] full = "\n".join(lines) title = lines[0] if lines else "" body = lines[1:] if len(lines) > 1 else [] return GitCommitMessage(context=context, original=commit_msg_str, full=full, title=title, body=body) - def __unicode__(self): - return self.full # pragma: no cover - def __str__(self): - return sstr(self.__unicode__()) # pragma: no cover - - def __repr__(self): - return self.__str__() # pragma: no cover + return self.full def __eq__(self, other): return (isinstance(other, GitCommitMessage) and self.original == other.original and self.full == other.full and self.title == other.title and self.body == other.body) # noqa - def __ne__(self, other): - return not self.__eq__(other) # required for py2 - -class GitCommit(object): +class GitCommit: """ Class representing a git commit. A commit consists of: context, message, author name, author email, date, list of parent commit shas, list of changed files, list of branch names. @@ -155,39 +145,33 @@ class GitCommit(object): @property def is_merge_commit(self): - return self.message.title.startswith(u"Merge") + return self.message.title.startswith("Merge") @property def is_fixup_commit(self): - return self.message.title.startswith(u"fixup!") + return self.message.title.startswith("fixup!") @property def is_squash_commit(self): - return self.message.title.startswith(u"squash!") + return self.message.title.startswith("squash!") @property def is_revert_commit(self): - return self.message.title.startswith(u"Revert") - - def __unicode__(self): - format_str = (u"--- Commit Message ----\n%s\n" - u"--- Meta info ---------\n" - u"Author: %s <%s>\nDate: %s\n" - u"is-merge-commit: %s\nis-fixup-commit: %s\n" - u"is-squash-commit: %s\nis-revert-commit: %s\n" - u"Branches: %s\n" - u"Changed Files: %s\n" - u"-----------------------") # pragma: no cover - date_str = arrow.get(self.date).format(GIT_TIMEFORMAT) if self.date else None - return format_str % (ustr(self.message), self.author_name, self.author_email, date_str, - self.is_merge_commit, self.is_fixup_commit, self.is_squash_commit, - self.is_revert_commit, sstr(self.branches), sstr(self.changed_files)) # pragma: no cover + return self.message.title.startswith("Revert") def __str__(self): - return sstr(self.__unicode__()) # pragma: no cover - - def __repr__(self): - return self.__str__() # pragma: no cover + date_str = arrow.get(self.date).format(GIT_TIMEFORMAT) if self.date else None + return (f"--- Commit Message ----\n{self.message}\n" + "--- Meta info ---------\n" + f"Author: {self.author_name} <{self.author_email}>\n" + f"Date: {date_str}\n" + f"is-merge-commit: {self.is_merge_commit}\n" + f"is-fixup-commit: {self.is_fixup_commit}\n" + f"is-squash-commit: {self.is_squash_commit}\n" + f"is-revert-commit: {self.is_revert_commit}\n" + f"Branches: {self.branches}\n" + f"Changed Files: {self.changed_files}\n" + "-----------------------") def __eq__(self, other): # skip checking the context as context refers back to this obj, this will trigger a cyclic dependency @@ -199,9 +183,6 @@ class GitCommit(object): and self.is_squash_commit == other.is_squash_commit and self.is_revert_commit == other.is_revert_commit and self.changed_files == other.changed_files and self.branches == other.branches) # noqa - def __ne__(self, other): - return not self.__eq__(other) # required for py2 - class LocalGitCommit(GitCommit, PropertyCache): """ Class representing a git commit that exists in the local git repository. @@ -230,7 +211,7 @@ class LocalGitCommit(GitCommit, PropertyCache): # "YYYY-MM-DD HH:mm:ss Z" -> ISO 8601-like format # Use arrow for datetime parsing, because apparently python is quirky around ISO-8601 dates: # http://stackoverflow.com/a/30696682/381010 - commit_date = arrow.get(ustr(date), GIT_TIMEFORMAT).datetime + commit_date = arrow.get(date, GIT_TIMEFORMAT).datetime # Create Git commit object with the retrieved info commit_msg_obj = GitCommitMessage.from_full_message(self.context, commit_msg) @@ -270,7 +251,7 @@ class LocalGitCommit(GitCommit, PropertyCache): # safely do this since git branches cannot contain '*' anywhere, so if we find an '*' we know it's output # from the git CLI and not part of the branch name. See https://git-scm.com/docs/git-check-ref-format # We also drop the last empty line from the output. - self._cache['branches'] = [ustr(branch.replace("*", "").strip()) for branch in branches[:-1]] + self._cache['branches'] = [branch.replace("*", "").strip() for branch in branches[:-1]] return self._try_cache("branches", cache_branches) @@ -306,17 +287,17 @@ class StagedLocalGitCommit(GitCommit, PropertyCache): @cache def author_name(self): try: - return ustr(_git("config", "--get", "user.name", _cwd=self.context.repository_path)).strip() - except GitExitCodeError: - raise GitContextError("Missing git configuration: please set user.name") + return _git("config", "--get", "user.name", _cwd=self.context.repository_path).strip() + except GitExitCodeError as e: + raise GitContextError("Missing git configuration: please set user.name") from e @property @cache def author_email(self): try: - return ustr(_git("config", "--get", "user.email", _cwd=self.context.repository_path)).strip() - except GitExitCodeError: - raise GitContextError("Missing git configuration: please set user.email") + return _git("config", "--get", "user.email", _cwd=self.context.repository_path).strip() + except GitExitCodeError as e: + raise GitContextError("Missing git configuration: please set user.email") from e @property @cache @@ -356,7 +337,7 @@ class GitContext(PropertyCache): @property @cache def current_branch(self): - current_branch = ustr(_git("rev-parse", "--abbrev-ref", "HEAD", _cwd=self.repository_path)).strip() + current_branch = _git("rev-parse", "--abbrev-ref", "HEAD", _cwd=self.repository_path).strip() return current_branch @staticmethod @@ -396,7 +377,7 @@ class GitContext(PropertyCache): # We tried many things here e.g.: defaulting to e.g. HEAD or HEAD^... (incl. dealing with # repos that only have a single commit - HEAD^... doesn't work there), but then we still get into # problems with e.g. merge commits. Easiest solution is just taking the SHA from `git log -1`. - sha_list = [_git("log", "-1", "--pretty=%H", _cwd=repository_path).replace(u"\n", u"")] + sha_list = [_git("log", "-1", "--pretty=%H", _cwd=repository_path).replace("\n", "")] else: sha_list = _git("rev-list", refspec, _cwd=repository_path).split() @@ -410,6 +391,3 @@ class GitContext(PropertyCache): return (isinstance(other, GitContext) and self.commits == other.commits and self.repository_path == other.repository_path and self.commentchar == other.commentchar and self.current_branch == other.current_branch) # noqa - - def __ne__(self, other): - return not self.__eq__(other) # required for py2 diff --git a/gitlint/hooks.py b/gitlint/hooks.py index fc4dc4e..87611e0 100644 --- a/gitlint/hooks.py +++ b/gitlint/hooks.py @@ -5,17 +5,18 @@ import stat from gitlint.utils import DEFAULT_ENCODING from gitlint.git import git_hooks_dir +from gitlint.exception import GitlintError COMMIT_MSG_HOOK_SRC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "files", "commit-msg") COMMIT_MSG_HOOK_DST_PATH = "commit-msg" GITLINT_HOOK_IDENTIFIER = "### gitlint commit-msg hook start ###\n" -class GitHookInstallerError(Exception): +class GitHookInstallerError(GitlintError): pass -class GitHookInstaller(object): +class GitHookInstaller: """ Utility class that provides methods for installing and uninstalling the gitlint commitmsg hook. """ @staticmethod @@ -27,7 +28,7 @@ class GitHookInstaller(object): """ Asserts that a given target directory is a git repository """ hooks_dir = git_hooks_dir(target) if not os.path.isdir(hooks_dir): - raise GitHookInstallerError(u"{0} is not a git repository.".format(target)) + raise GitHookInstallerError(f"{target} is not a git repository.") @staticmethod def install_commit_msg_hook(lint_config): @@ -35,8 +36,8 @@ class GitHookInstaller(object): dest_path = GitHookInstaller.commit_msg_hook_path(lint_config) if os.path.exists(dest_path): raise GitHookInstallerError( - u"There is already a commit-msg hook file present in {0}.\n".format(dest_path) + - u"gitlint currently does not support appending to an existing commit-msg file.") + f"There is already a commit-msg hook file present in {dest_path}.\n" + + "gitlint currently does not support appending to an existing commit-msg file.") # copy hook file shutil.copy(COMMIT_MSG_HOOK_SRC_PATH, dest_path) @@ -49,14 +50,14 @@ class GitHookInstaller(object): GitHookInstaller._assert_git_repo(lint_config.target) dest_path = GitHookInstaller.commit_msg_hook_path(lint_config) if not os.path.exists(dest_path): - raise GitHookInstallerError(u"There is no commit-msg hook present in {0}.".format(dest_path)) + raise GitHookInstallerError(f"There is no commit-msg hook present in {dest_path}.") with io.open(dest_path, encoding=DEFAULT_ENCODING) as fp: lines = fp.readlines() if len(lines) < 2 or lines[1] != GITLINT_HOOK_IDENTIFIER: - msg = u"The commit-msg hook in {0} was not installed by gitlint (or it was modified).\n" + \ - u"Uninstallation of 3th party or modified gitlint hooks is not supported." - raise GitHookInstallerError(msg.format(dest_path)) + msg = f"The commit-msg hook in {dest_path} was not installed by gitlint (or it was modified).\n" + \ + "Uninstallation of 3th party or modified gitlint hooks is not supported." + raise GitHookInstallerError(msg) # If we are sure it's a gitlint hook, go ahead and remove it os.remove(dest_path) diff --git a/gitlint/lint.py b/gitlint/lint.py index 6ef7174..4b6c8a3 100644 --- a/gitlint/lint.py +++ b/gitlint/lint.py @@ -2,13 +2,12 @@ import logging from gitlint import rules as gitlint_rules from gitlint import display -from gitlint.utils import ustr LOG = logging.getLogger(__name__) logging.basicConfig() -class GitLinter(object): +class GitLinter: """ Main linter class. This is where rules actually get applied. See the lint() method. """ def __init__(self, config): @@ -70,7 +69,7 @@ class GitLinter(object): def lint(self, commit): """ Lint the last commit in a given git context by applying all ignore, title, body and commit rules. """ LOG.debug("Linting commit %s", commit.sha or "[SHA UNKNOWN]") - LOG.debug("Commit Object\n" + ustr(commit)) + LOG.debug("Commit Object\n" + str(commit)) # Apply config rules for rule in self.configuration_rules: @@ -79,8 +78,8 @@ class GitLinter(object): # Skip linting if this is a special commit type that is configured to be ignored ignore_commit_types = ["merge", "squash", "fixup", "revert"] for commit_type in ignore_commit_types: - if getattr(commit, "is_{0}_commit".format(commit_type)) and \ - getattr(self.config, "ignore_{0}_commits".format(commit_type)): + if getattr(commit, f"is_{commit_type}_commit") and \ + getattr(self.config, f"ignore_{commit_type}_commits"): return [] violations = [] @@ -99,10 +98,9 @@ class GitLinter(object): """ Print a given set of violations to the standard error output """ for v in violations: line_nr = v.line_nr if v.line_nr else "-" - self.display.e(u"{0}: {1}".format(line_nr, v.rule_id), exact=True) - self.display.ee(u"{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True) + self.display.e(f"{line_nr}: {v.rule_id}", exact=True) + self.display.ee(f"{line_nr}: {v.rule_id} {v.message}", exact=True) if v.content: - self.display.eee(u"{0}: {1} {2}: \"{3}\"".format(line_nr, v.rule_id, v.message, v.content), - exact=True) + self.display.eee(f"{line_nr}: {v.rule_id} {v.message}: \"{v.content}\"", exact=True) else: - self.display.eee(u"{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True) + self.display.eee(f"{line_nr}: {v.rule_id} {v.message}", exact=True) diff --git a/gitlint/options.py b/gitlint/options.py index 3ea8310..9fdac8f 100644 --- a/gitlint/options.py +++ b/gitlint/options.py @@ -2,7 +2,7 @@ from abc import abstractmethod import os import re -from gitlint.utils import ustr, sstr +from gitlint.exception import GitlintError def allow_none(func): @@ -17,11 +17,11 @@ def allow_none(func): return wrapped -class RuleOptionError(Exception): +class RuleOptionError(GitlintError): pass -class RuleOption(object): +class RuleOption: """ Base class representing a configurable part (i.e. option) of a rule (e.g. the max-length of the title-max-line rule). This class should not be used directly. Instead, use on the derived classes like StrOption, IntOption to set @@ -29,8 +29,8 @@ class RuleOption(object): """ def __init__(self, name, value, description): - self.name = ustr(name) - self.description = ustr(description) + self.name = name + self.description = description self.value = None self.set(value) @@ -40,37 +40,28 @@ class RuleOption(object): pass # pragma: no cover def __str__(self): - return sstr(self) # pragma: no cover - - def __unicode__(self): - return u"({0}: {1} ({2}))".format(self.name, self.value, self.description) # pragma: no cover - - def __repr__(self): - return self.__str__() # pragma: no cover + return f"({self.name}: {self.value} ({self.description}))" def __eq__(self, other): return self.name == other.name and self.description == other.description and self.value == other.value - def __ne__(self, other): - return not self.__eq__(other) # required for py2 - class StrOption(RuleOption): @allow_none def set(self, value): - self.value = ustr(value) + self.value = str(value) class IntOption(RuleOption): def __init__(self, name, value, description, allow_negative=False): self.allow_negative = allow_negative - super(IntOption, self).__init__(name, value, description) + super().__init__(name, value, description) def _raise_exception(self, value): if self.allow_negative: - error_msg = u"Option '{0}' must be an integer (current value: '{1}')".format(self.name, value) + error_msg = f"Option '{self.name}' must be an integer (current value: '{value}')" else: - error_msg = u"Option '{0}' must be a positive integer (current value: '{1}')".format(self.name, value) + error_msg = f"Option '{self.name}' must be a positive integer (current value: '{value}')" raise RuleOptionError(error_msg) @allow_none @@ -88,9 +79,9 @@ class BoolOption(RuleOption): # explicit choice to not annotate with @allow_none: Booleans must be False or True, they cannot be unset. def set(self, value): - value = ustr(value).strip().lower() + value = str(value).strip().lower() if value not in ['true', 'false']: - raise RuleOptionError(u"Option '{0}' must be either 'true' or 'false'".format(self.name)) + raise RuleOptionError(f"Option '{self.name}' must be either 'true' or 'false'") self.value = value == 'true' @@ -103,37 +94,36 @@ class ListOption(RuleOption): if isinstance(value, list): the_list = value else: - the_list = ustr(value).split(",") + the_list = str(value).split(",") - self.value = [ustr(item.strip()) for item in the_list if item.strip() != ""] + self.value = [str(item.strip()) for item in the_list if item.strip() != ""] class PathOption(RuleOption): """ Option that accepts either a directory or both a directory and a file. """ - def __init__(self, name, value, description, type=u"dir"): + def __init__(self, name, value, description, type="dir"): self.type = type - super(PathOption, self).__init__(name, value, description) + super().__init__(name, value, description) @allow_none def set(self, value): - value = ustr(value) + value = str(value) - error_msg = u"" + error_msg = "" if self.type == 'dir': if not os.path.isdir(value): - error_msg = u"Option {0} must be an existing directory (current value: '{1}')".format(self.name, value) + error_msg = f"Option {self.name} must be an existing directory (current value: '{value}')" elif self.type == 'file': if not os.path.isfile(value): - error_msg = u"Option {0} must be an existing file (current value: '{1}')".format(self.name, value) + error_msg = f"Option {self.name} must be an existing file (current value: '{value}')" elif self.type == 'both': if not os.path.isdir(value) and not os.path.isfile(value): - error_msg = (u"Option {0} must be either an existing directory or file " - u"(current value: '{1}')").format(self.name, value) + error_msg = (f"Option {self.name} must be either an existing directory or file " + f"(current value: '{value}')") else: - error_msg = u"Option {0} type must be one of: 'file', 'dir', 'both' (current: '{1}')".format(self.name, - self.type) + error_msg = f"Option {self.name} type must be one of: 'file', 'dir', 'both' (current: '{self.type}')" if error_msg: raise RuleOptionError(error_msg) @@ -148,7 +138,7 @@ class RegexOption(RuleOption): try: self.value = re.compile(value, re.UNICODE) except (re.error, TypeError) as exc: - raise RuleOptionError("Invalid regular expression: '{0}'".format(exc)) + raise RuleOptionError(f"Invalid regular expression: '{exc}'") from exc def __deepcopy__(self, _): # copy.deepcopy() - used in rules.py - doesn't support copying regex objects prior to Python 3.7 diff --git a/gitlint/rule_finder.py b/gitlint/rule_finder.py index d7d700b..e1c5e77 100644 --- a/gitlint/rule_finder.py +++ b/gitlint/rule_finder.py @@ -5,7 +5,6 @@ import sys import importlib from gitlint import rules, options -from gitlint.utils import ustr def find_rule_classes(extra_path): @@ -28,7 +27,7 @@ def find_rule_classes(extra_path): files = os.listdir(extra_path) directory = extra_path else: - raise rules.UserRuleError(u"Invalid extra-path: {0}".format(extra_path)) + raise rules.UserRuleError(f"Invalid extra-path: {extra_path}") # Filter out files that are not python modules for filename in files: @@ -56,7 +55,7 @@ def find_rule_classes(extra_path): importlib.import_module(module) except Exception as e: - raise rules.UserRuleError(u"Error while importing extra-path module '{0}': {1}".format(module, ustr(e))) + raise rules.UserRuleError(f"Error while importing extra-path module '{module}': {e}") # Find all rule classes in the module. We do this my inspecting all members of the module and checking # 1) is it a class, if not, skip @@ -94,55 +93,53 @@ def assert_valid_rule_class(clazz, rule_type="User-defined"): # pylint: disable # Rules must extend from LineRule, CommitRule or ConfigurationRule if not (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule) or issubclass(clazz, rules.ConfigurationRule)): - msg = u"{0} rule class '{1}' must extend from {2}.{3}, {2}.{4} or {2}.{5}" - raise rules.UserRuleError(msg.format(rule_type, clazz.__name__, rules.CommitRule.__module__, - rules.LineRule.__name__, rules.CommitRule.__name__, - rules.ConfigurationRule.__name__)) + msg = f"{rule_type} rule class '{clazz.__name__}' " + \ + f"must extend from {rules.CommitRule.__module__}.{rules.LineRule.__name__}, " + \ + f"{rules.CommitRule.__module__}.{rules.CommitRule.__name__} or " + \ + f"{rules.CommitRule.__module__}.{rules.ConfigurationRule.__name__}" + raise rules.UserRuleError(msg) # Rules must have an id attribute if not hasattr(clazz, 'id') or clazz.id is None or not clazz.id: - msg = u"{0} rule class '{1}' must have an 'id' attribute" - raise rules.UserRuleError(msg.format(rule_type, clazz.__name__)) + raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have an 'id' attribute") # Rule id's cannot start with gitlint reserved letters if clazz.id[0].upper() in ['R', 'T', 'B', 'M', 'I']: - msg = u"The id '{1}' of '{0}' is invalid. Gitlint reserves ids starting with R,T,B,M,I" - raise rules.UserRuleError(msg.format(clazz.__name__, clazz.id[0])) + msg = f"The id '{clazz.id[0]}' of '{clazz.__name__}' is invalid. Gitlint reserves ids starting with R,T,B,M,I" + raise rules.UserRuleError(msg) # Rules must have a name attribute if not hasattr(clazz, 'name') or clazz.name is None or not clazz.name: - msg = u"{0} rule class '{1}' must have a 'name' attribute" - raise rules.UserRuleError(msg.format(rule_type, clazz.__name__)) + raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have a 'name' attribute") # if set, options_spec must be a list of RuleOption if not isinstance(clazz.options_spec, list): - msg = u"The options_spec attribute of {0} rule class '{1}' must be a list of {2}.{3}" - raise rules.UserRuleError(msg.format(rule_type.lower(), clazz.__name__, - options.RuleOption.__module__, options.RuleOption.__name__)) + msg = f"The options_spec attribute of {rule_type.lower()} rule class '{clazz.__name__}' " + \ + f"must be a list of {options.RuleOption.__module__}.{options.RuleOption.__name__}" + raise rules.UserRuleError(msg) # check that all items in options_spec are actual gitlint options for option in clazz.options_spec: if not isinstance(option, options.RuleOption): - msg = u"The options_spec attribute of {0} rule class '{1}' must be a list of {2}.{3}" - raise rules.UserRuleError(msg.format(rule_type.lower(), clazz.__name__, - options.RuleOption.__module__, options.RuleOption.__name__)) + msg = f"The options_spec attribute of {rule_type.lower()} rule class '{clazz.__name__}' " + \ + f"must be a list of {options.RuleOption.__module__}.{options.RuleOption.__name__}" + raise rules.UserRuleError(msg) # Line/Commit rules must have a `validate` method # We use isroutine() as it's both python 2 and 3 compatible. Details: http://stackoverflow.com/a/17019998/381010 if (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule)): if not hasattr(clazz, 'validate') or not inspect.isroutine(clazz.validate): - msg = u"{0} rule class '{1}' must have a 'validate' method" - raise rules.UserRuleError(msg.format(rule_type, clazz.__name__)) + raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have a 'validate' method") # Configuration rules must have an `apply` method elif issubclass(clazz, rules.ConfigurationRule): if not hasattr(clazz, 'apply') or not inspect.isroutine(clazz.apply): - msg = u"{0} Configuration rule class '{1}' must have an 'apply' method" - raise rules.UserRuleError(msg.format(rule_type, clazz.__name__)) + msg = f"{rule_type} Configuration rule class '{clazz.__name__}' must have an 'apply' method" + raise rules.UserRuleError(msg) # LineRules must have a valid target: rules.CommitMessageTitle or rules.CommitMessageBody if issubclass(clazz, rules.LineRule): if clazz.target not in [rules.CommitMessageTitle, rules.CommitMessageBody]: - msg = u"The target attribute of the {0} LineRule class '{1}' must be either {2}.{3} or {2}.{4}" - msg = msg.format(rule_type.lower(), clazz.__name__, rules.CommitMessageTitle.__module__, - rules.CommitMessageTitle.__name__, rules.CommitMessageBody.__name__) + msg = f"The target attribute of the {rule_type.lower()} LineRule class '{clazz.__name__}' " + \ + f"must be either {rules.CommitMessageTitle.__module__}.{rules.CommitMessageTitle.__name__} " + \ + f"or {rules.CommitMessageTitle.__module__}.{rules.CommitMessageBody.__name__}" raise rules.UserRuleError(msg) diff --git a/gitlint/rules.py b/gitlint/rules.py index 1cb50da..3dc85b7 100644 --- a/gitlint/rules.py +++ b/gitlint/rules.py @@ -4,10 +4,10 @@ import logging import re from gitlint.options import IntOption, BoolOption, StrOption, ListOption, RegexOption -from gitlint.utils import sstr +from gitlint.exception import GitlintError -class Rule(object): +class Rule: """ Class representing gitlint rules. """ options_spec = [] id = None @@ -36,17 +36,8 @@ class Rule(object): return self.id == other.id and self.name == other.name and \ self.options == other.options and self.target == other.target # noqa - def __ne__(self, other): - return not self.__eq__(other) # required for py2 - def __str__(self): - return sstr(self) # pragma: no cover - - def __unicode__(self): - return u"{0} {1}".format(self.id, self.name) # pragma: no cover - - def __repr__(self): - return self.__str__() # pragma: no cover + return f"{self.id} {self.name}" # pragma: no cover class ConfigurationRule(Rule): @@ -64,7 +55,7 @@ class LineRule(Rule): pass -class LineRuleTarget(object): +class LineRuleTarget: """ Base class for LineRule targets. A LineRuleTarget specifies where a given rule will be applied (e.g. commit message title, commit message body). Each LineRule MUST have a target specified. """ @@ -81,7 +72,7 @@ class CommitMessageBody(LineRuleTarget): pass -class RuleViolation(object): +class RuleViolation: """ Class representing a violation of a rule. I.e.: When a rule is broken, the rule will instantiate this class to indicate how and where the rule was broken. """ @@ -96,21 +87,11 @@ class RuleViolation(object): equal = equal and self.content == other.content and self.line_nr == other.line_nr return equal - def __ne__(self, other): - return not self.__eq__(other) # required for py2 - def __str__(self): - return sstr(self) # pragma: no cover - - def __unicode__(self): - return u"{0}: {1} {2}: \"{3}\"".format(self.line_nr, self.rule_id, self.message, - self.content) # pragma: no cover - - def __repr__(self): - return self.__unicode__() # pragma: no cover + return f"{self.line_nr}: {self.rule_id} {self.message}: \"{self.content}\"" -class UserRuleError(Exception): +class UserRuleError(GitlintError): """ Error used to indicate that an error occurred while trying to load a user rule """ pass @@ -154,7 +135,7 @@ class LineMustNotContainWord(LineRule): name = "line-must-not-contain" id = "R5" options_spec = [ListOption('words', [], "Comma separated list of words that should not be found")] - violation_message = u"Line contains {0}" + violation_message = "Line contains {0}" def validate(self, line, _commit): strings = self.options['words'].value @@ -202,7 +183,7 @@ class TitleTrailingPunctuation(LineRule): punctuation_marks = '?:!.,;' for punctuation_mark in punctuation_marks: if title.endswith(punctuation_mark): - return [RuleViolation(self.id, u"Title has trailing punctuation ({0})".format(punctuation_mark), title)] + return [RuleViolation(self.id, f"Title has trailing punctuation ({punctuation_mark})", title)] class TitleHardTab(HardTab): @@ -217,7 +198,7 @@ class TitleMustNotContainWord(LineMustNotContainWord): id = "T5" target = CommitMessageTitle options_spec = [ListOption('words', ["WIP"], "Must not contain word")] - violation_message = u"Title contains the word '{0}' (case-insensitive)" + violation_message = "Title contains the word '{0}' (case-insensitive)" class TitleLeadingWhitespace(LeadingWhiteSpace): @@ -239,7 +220,7 @@ class TitleRegexMatches(LineRule): return if not self.options['regex'].value.search(title): - violation_msg = u"Title does not match regex ({0})".format(self.options['regex'].value.pattern) + violation_msg = f"Title does not match regex ({self.options['regex'].value.pattern})" return [RuleViolation(self.id, violation_msg, title)] @@ -253,7 +234,7 @@ class TitleMinLength(LineRule): min_length = self.options['min-length'].value actual_length = len(title) if actual_length < min_length: - violation_message = "Title is too short ({0}<{1})".format(actual_length, min_length) + violation_message = f"Title is too short ({actual_length}<{min_length})" return [RuleViolation(self.id, violation_message, title, 1)] @@ -296,7 +277,7 @@ class BodyMinLength(CommitRule): body_message_no_newline = "".join([line for line in commit.message.body if line is not None]) actual_length = len(body_message_no_newline) if 0 < actual_length < min_length: - violation_message = "Body message is too short ({0}<{1})".format(actual_length, min_length) + violation_message = f"Body message is too short ({actual_length}<{min_length})" return [RuleViolation(self.id, violation_message, body_message_no_newline, 3)] @@ -325,7 +306,7 @@ class BodyChangedFileMention(CommitRule): # in the commit msg body if needs_mentioned_file in commit.changed_files: if needs_mentioned_file not in " ".join(commit.message.body): - violation_message = u"Body does not mention changed file '{0}'".format(needs_mentioned_file) + violation_message = f"Body does not mention changed file '{needs_mentioned_file}'" violations.append(RuleViolation(self.id, violation_message, None, len(commit.message.body) + 1)) return violations if violations else None @@ -354,7 +335,7 @@ class BodyRegexMatches(CommitRule): full_body = "\n".join(body_lines) if not self.options['regex'].value.search(full_body): - violation_msg = u"Body does not match regex ({0})".format(self.options['regex'].value.pattern) + violation_msg = f"Body does not match regex ({self.options['regex'].value.pattern})" return [RuleViolation(self.id, violation_msg, None, len(commit.message.body) + 1)] @@ -386,9 +367,8 @@ class IgnoreByTitle(ConfigurationRule): if self.options['regex'].value.match(commit.message.title): config.ignore = self.options['ignore'].value - message = u"Commit title '{0}' matches the regex '{1}', ignoring rules: {2}" - message = message.format(commit.message.title, self.options['regex'].value.pattern, - self.options['ignore'].value) + message = f"Commit title '{commit.message.title}' matches the regex " + \ + f"'{self.options['regex'].value.pattern}', ignoring rules: {self.options['ignore'].value}" self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message) @@ -408,8 +388,8 @@ class IgnoreByBody(ConfigurationRule): if self.options['regex'].value.match(line): config.ignore = self.options['ignore'].value - message = u"Commit message line '{0}' matches the regex '{1}', ignoring rules: {2}" - message = message.format(line, self.options['regex'].value.pattern, self.options['ignore'].value) + message = f"Commit message line '{line}' matches the regex '{self.options['regex'].value.pattern}'," + \ + f" ignoring rules: {self.options['ignore'].value}" self.log.debug("Ignoring commit because of rule '%s': %s", self.id, message) # No need to check other lines if we found a match @@ -429,10 +409,10 @@ class IgnoreBodyLines(ConfigurationRule): new_body = [] for line in commit.message.body: if self.options['regex'].value.match(line): - debug_msg = u"Ignoring line '%s' because it matches '%s'" + debug_msg = "Ignoring line '%s' because it matches '%s'" self.log.debug(debug_msg, line, self.options['regex'].value.pattern) else: new_body.append(line) commit.message.body = new_body - commit.message.full = u"\n".join([commit.message.title] + new_body) + commit.message.full = "\n".join([commit.message.title] + new_body) diff --git a/gitlint/shell.py b/gitlint/shell.py index 2601b04..7f598ae 100644 --- a/gitlint/shell.py +++ b/gitlint/shell.py @@ -6,7 +6,7 @@ capabilities wrt dealing with more edge-case environments on *nix systems that a """ import subprocess -from gitlint.utils import ustr, IS_PY2, USE_SH_LIB +from gitlint.utils import USE_SH_LIB, DEFAULT_ENCODING def shell(cmd): @@ -25,7 +25,7 @@ else: """ Exception indicating a command was not found during execution """ pass - class ShResult(object): + class ShResult: """ Result wrapper class. We use this to more easily migrate from using https://amoffat.github.io/sh/ to using the builtin subprocess module """ @@ -51,11 +51,6 @@ else: return _exec(*args, **kwargs) def _exec(*args, **kwargs): - if IS_PY2: - no_command_error = OSError # noqa pylint: disable=undefined-variable,invalid-name - else: - no_command_error = FileNotFoundError # noqa pylint: disable=undefined-variable - pipe = subprocess.PIPE popen_kwargs = {'stdout': pipe, 'stderr': pipe, 'shell': kwargs.get('_tty_out', False)} if '_cwd' in kwargs: @@ -64,11 +59,11 @@ else: try: p = subprocess.Popen(args, **popen_kwargs) result = p.communicate() - except no_command_error: - raise CommandNotFound + except FileNotFoundError as e: + raise CommandNotFound from e exit_code = p.returncode - stdout = ustr(result[0]) + stdout = result[0].decode(DEFAULT_ENCODING) stderr = result[1] # 'sh' does not decode the stderr bytes to unicode full_cmd = '' if args is None else ' '.join(args) diff --git a/gitlint/tests/base.py b/gitlint/tests/base.py index c8f68c4..9406240 100644 --- a/gitlint/tests/base.py +++ b/gitlint/tests/base.py @@ -9,33 +9,12 @@ import re import shutil import tempfile -try: - # python 2.x - import unittest2 as unittest -except ImportError: - # python 3.x - import unittest - -try: - # python 2.x - from mock import patch -except ImportError: - # python 3.x - from unittest.mock import patch # pylint: disable=no-name-in-module, import-error - -from gitlint.git import GitContext -from gitlint.utils import ustr, IS_PY2, LOG_FORMAT, DEFAULT_ENCODING +import unittest +from unittest.mock import patch -# unittest2's assertRaisesRegex doesn't do unicode comparison. -# Let's monkeypatch the str() function to point to unicode() so that it does :) -# For reference, this is where this patch is required: -# https://hg.python.org/unittest2/file/tip/unittest2/case.py#l227 -try: - # python 2.x - unittest.case.str = unicode -except (AttributeError, NameError): - pass # python 3.x +from gitlint.git import GitContext +from gitlint.utils import LOG_FORMAT, DEFAULT_ENCODING class BaseTestCase(unittest.TestCase): @@ -72,24 +51,22 @@ class BaseTestCase(unittest.TestCase): def get_sample_path(filename=""): # Don't join up empty files names because this will add a trailing slash if filename == "": - return ustr(BaseTestCase.SAMPLES_DIR) + return BaseTestCase.SAMPLES_DIR - return ustr(os.path.join(BaseTestCase.SAMPLES_DIR, filename)) + return os.path.join(BaseTestCase.SAMPLES_DIR, filename) @staticmethod def get_sample(filename=""): """ Read and return the contents of a file in gitlint/tests/samples """ sample_path = BaseTestCase.get_sample_path(filename) with io.open(sample_path, encoding=DEFAULT_ENCODING) as content: - sample = ustr(content.read()) + sample = content.read() return sample @staticmethod def patch_input(side_effect): """ Patches the built-in input() with a provided side-effect """ module_path = "builtins.input" - if IS_PY2: - module_path = "__builtin__.raw_input" patched_module = patch(module_path, side_effect=side_effect) return patched_module @@ -99,7 +76,7 @@ class BaseTestCase(unittest.TestCase): Optionally replace template variables specified by variable_dict. """ expected_path = os.path.join(BaseTestCase.EXPECTED_DIR, filename) with io.open(expected_path, encoding=DEFAULT_ENCODING) as content: - expected = ustr(content.read()) + expected = content.read() if variable_dict: expected = expected.format(**variable_dict) @@ -114,7 +91,7 @@ class BaseTestCase(unittest.TestCase): """ Utility method to easily create gitcontext objects based on a given commit msg string and an optional set of changed files""" with patch("gitlint.git.git_commentchar") as comment_char: - comment_char.return_value = u"#" + comment_char.return_value = "#" gitcontext = GitContext.from_commit_msg(commit_msg_str) commit = gitcontext.commits[-1] if changed_files: @@ -147,8 +124,7 @@ class BaseTestCase(unittest.TestCase): """ Pass-through method to unittest.TestCase.assertRaisesRegex that applies re.escape() to the passed `expected_regex`. This is useful to automatically escape all file paths that might be present in the regex. """ - return super(BaseTestCase, self).assertRaisesRegex(expected_exception, re.escape(expected_regex), - *args, **kwargs) + return super().assertRaisesRegex(expected_exception, re.escape(expected_regex), *args, **kwargs) @contextlib.contextmanager def assertRaisesMessage(self, expected_exception, expected_msg): # pylint: disable=invalid-name @@ -156,17 +132,17 @@ class BaseTestCase(unittest.TestCase): try: yield except expected_exception as exc: - exception_msg = ustr(exc) + exception_msg = str(exc) if exception_msg != expected_msg: - error = u"Right exception, wrong message:\n got: {0}\n expected: {1}" - raise self.fail(error.format(exception_msg, expected_msg)) + error = f"Right exception, wrong message:\n got: {exception_msg}\n expected: {expected_msg}" + raise self.fail(error) # else: everything is fine, just return return except Exception as exc: - raise self.fail(u"Expected '{0}' got '{1}'".format(expected_exception.__name__, exc.__class__.__name__)) + raise self.fail(f"Expected '{expected_exception.__name__}' got '{exc.__class__.__name__}'") # No exception raised while we expected one - raise self.fail("Expected to raise {0}, didn't get an exception at all".format(expected_exception.__name__)) + raise self.fail(f"Expected to raise {expected_exception.__name__}, didn't get an exception at all") def object_equality_test(self, obj, attr_list, ctor_kwargs=None): """ Helper function to easily implement object equality tests. @@ -190,9 +166,9 @@ class BaseTestCase(unittest.TestCase): self.assertEqual(obj, clone) # Change attribute and assert objects are different (via both attribute set and ctor) - setattr(clone, attr, u"föo") + setattr(clone, attr, "föo") self.assertNotEqual(obj, clone) - attr_kwargs_copy[attr] = u"föo" + attr_kwargs_copy[attr] = "föo" self.assertNotEqual(obj, obj.__class__(**attr_kwargs_copy)) @@ -205,4 +181,4 @@ class LogCapture(logging.Handler): self.messages = [] def emit(self, record): - self.messages.append(ustr(self.format(record))) + self.messages.append(self.format(record)) diff --git a/gitlint/tests/cli/test_cli.py b/gitlint/tests/cli/test_cli.py index 88bcfb7..bf35e96 100644 --- a/gitlint/tests/cli/test_cli.py +++ b/gitlint/tests/cli/test_cli.py @@ -8,21 +8,11 @@ import platform import arrow -try: - # python 2.x - from StringIO import StringIO -except ImportError: - # python 3.x - from io import StringIO # pylint: disable=ungrouped-imports +from io import StringIO from click.testing import CliRunner -try: - # python 2.x - from mock import patch -except ImportError: - # python 3.x - from unittest.mock import patch # pylint: disable=no-name-in-module, import-error +from unittest.mock import patch from gitlint.shell import CommandNotFound @@ -59,7 +49,7 @@ class CLITests(BaseTestCase): def test_version(self): """ Test for --version option """ result = self.cli.invoke(cli.cli, ["--version"]) - self.assertEqual(result.output.split("\n")[0], "cli, version {0}".format(__version__)) + self.assertEqual(result.output.split("\n")[0], f"cli, version {__version__}") @patch('gitlint.cli.get_stdin_data', return_value=False) @patch('gitlint.git.sh') @@ -67,11 +57,11 @@ class CLITests(BaseTestCase): """ Test for basic simple linting functionality """ sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360", - u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - u"commït-title\n\ncommït-body", - u"#", # git config --get core.commentchar - u"commit-1-branch-1\ncommit-1-branch-2\n", - u"file1.txt\npåth/to/file2.txt\n" + "test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "commït-title\n\ncommït-body", + "#", # git config --get core.commentchar + "commit-1-branch-1\ncommit-1-branch-2\n", + "file1.txt\npåth/to/file2.txt\n" ] with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -89,21 +79,21 @@ class CLITests(BaseTestCase): "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" + "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", # git log --pretty <FORMAT> <SHA> - u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - u"commït-title1\n\ncommït-body1", - u"#", # git config --get core.commentchar - u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> - u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree + "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "commït-title1\n\ncommït-body1", + "#", # git config --get core.commentchar + "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> + "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree # git log --pretty <FORMAT> <SHA> - u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" - u"commït-title2\n\ncommït-body2", - u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha> - u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree + "test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" + "commït-title2\n\ncommït-body2", + "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha> + "commit-2/file-1\ncommit-2/file-2\n", # git diff-tree # git log --pretty <FORMAT> <SHA> - u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" - u"commït-title3\n\ncommït-body3", - u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha> - u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree + "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" + "commït-title3\n\ncommït-body3", + "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha> + "commit-3/file-1\ncommit-3/file-2\n", # git diff-tree ] with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -122,21 +112,21 @@ class CLITests(BaseTestCase): "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" + "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", # git log --pretty <FORMAT> <SHA> - u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - u"commït-title1\n\ncommït-body1", - u"#", # git config --get core.commentchar - u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> - u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree + "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "commït-title1\n\ncommït-body1", + "#", # git config --get core.commentchar + "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> + "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree # git log --pretty <FORMAT> <SHA> - u"test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" - u"commït-title2.\n\ncommït-body2\ngitlint-ignore: T3\n", - u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha> - u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree + "test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" + "commït-title2.\n\ncommït-body2\ngitlint-ignore: T3\n", + "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha> + "commit-2/file-1\ncommit-2/file-2\n", # git diff-tree # git log --pretty <FORMAT> <SHA> - u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" - u"commït-title3.\n\ncommït-body3", - u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha> - u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree + "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" + "commït-title3.\n\ncommït-body3", + "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha> + "commit-3/file-1\ncommit-3/file-2\n", # git diff-tree ] with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -157,24 +147,24 @@ class CLITests(BaseTestCase): "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" + "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", # git log --pretty <FORMAT> <SHA> - u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - u"commït-title1\n\ncommït-body1", - u"#", # git config --get core.commentchar - u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> - u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree + "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "commït-title1\n\ncommït-body1", + "#", # git config --get core.commentchar + "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> + "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree # git log --pretty <FORMAT> <SHA> - u"test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" + "test åuthor2\x00test-email3@föo.com\x002016-12-04 15:28:15 +0100\x00åbc\n" # Normally T3 violation (trailing punctuation), but this commit is ignored because of # config below - u"commït-title2.\n\ncommït-body2\n", - u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha> - u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree + "commït-title2.\n\ncommït-body2\n", + "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha> + "commit-2/file-1\ncommit-2/file-2\n", # git diff-tree # git log --pretty <FORMAT> <SHA> - u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" + "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00åbc\n" # Normally T1 and B5 violations, now only T1 because we're ignoring B5 in config below - u"commït-title3.\n\ncommït-body3 foo", - u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha> - u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree + "commït-title3.\n\ncommït-body3 foo", + "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha> + "commit-3/file-1\ncommit-3/file-2\n", # git diff-tree ] with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -183,9 +173,9 @@ class CLITests(BaseTestCase): # We expect that the second commit has no failures because of it matching against I1.regex # Because we do test for the 3th commit to return violations, this test also ensures that a unique # config object is passed to each commit lint call - expected = (u"Commit 6f29bf81a8:\n" + expected = ("Commit 6f29bf81a8:\n" u'3: B5 Body message is too short (12<20): "commït-body1"\n\n' - u"Commit 4da2656b0d:\n" + "Commit 4da2656b0d:\n" u'1: T3 Title has trailing punctuation (.): "commït-title3."\n') self.assertEqual(stderr.getvalue(), expected) self.assertEqual(result.exit_code, 2) @@ -218,11 +208,11 @@ class CLITests(BaseTestCase): """ Test for ignoring stdin when --ignore-stdin flag is enabled""" sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360", - u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - u"commït-title\n\ncommït-body", - u"#", # git config --get core.commentchar - u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> - u"file1.txt\npåth/to/file2.txt\n" # git diff-tree + "test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "commït-title\n\ncommït-body", + "#", # git config --get core.commentchar + "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> + "file1.txt\npåth/to/file2.txt\n" # git diff-tree ] with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -240,11 +230,11 @@ class CLITests(BaseTestCase): """ Test for ignoring stdin when --ignore-stdin flag is enabled""" sh.git.side_effect = [ - u"#", # git config --get core.commentchar - u"föo user\n", # git config --get user.name - u"föo@bar.com\n", # git config --get user.email - u"my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch) - u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree + "#", # git config --get core.commentchar + "föo user\n", # git config --get user.name + "föo@bar.com\n", # git config --get user.email + "my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch) + "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree ] with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -263,17 +253,17 @@ class CLITests(BaseTestCase): """ Test for ignoring stdin when --ignore-stdin flag is enabled""" sh.git.side_effect = [ - u"#", # git config --get core.commentchar - u"föo user\n", # git config --get user.name - u"föo@bar.com\n", # git config --get user.email - u"my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch) - u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree + "#", # git config --get core.commentchar + "föo user\n", # git config --get user.name + "föo@bar.com\n", # git config --get user.email + "my-branch\n", # git rev-parse --abbrev-ref HEAD (=current branch) + "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree ] with self.tempdir() as tmpdir: msg_filename = os.path.join(tmpdir, "msg") with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f: - f.write(u"WIP: msg-filename tïtle\n") + f.write("WIP: msg-filename tïtle\n") with patch('gitlint.display.stderr', new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--debug", "--staged", "--msg-filename", msg_filename]) @@ -289,17 +279,17 @@ class CLITests(BaseTestCase): def test_lint_staged_negative(self, _): result = self.cli.invoke(cli.cli, ["--staged"]) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) - self.assertEqual(result.output, (u"Error: The 'staged' option (--staged) can only be used when using " - u"'--msg-filename' or when piping data to gitlint via stdin.\n")) + self.assertEqual(result.output, ("Error: The 'staged' option (--staged) can only be used when using " + "'--msg-filename' or when piping data to gitlint via stdin.\n")) @patch('gitlint.cli.get_stdin_data', return_value=False) def test_msg_filename(self, _): - expected_output = u"3: B6 Body message is missing\n" + expected_output = "3: B6 Body message is missing\n" with self.tempdir() as tmpdir: msg_filename = os.path.join(tmpdir, "msg") with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f: - f.write(u"Commït title\n") + f.write("Commït title\n") with patch('gitlint.display.stderr', new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename]) @@ -307,7 +297,7 @@ class CLITests(BaseTestCase): self.assertEqual(result.exit_code, 1) self.assertEqual(result.output, "") - @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tïtle \n") + @patch('gitlint.cli.get_stdin_data', return_value="WIP: tïtle \n") def test_silent_mode(self, _): """ Test for --silent option """ with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -316,7 +306,7 @@ class CLITests(BaseTestCase): self.assertEqual(result.exit_code, 3) self.assertEqual(result.output, "") - @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tïtle \n") + @patch('gitlint.cli.get_stdin_data', return_value="WIP: tïtle \n") def test_verbosity(self, _): """ Test for --verbosity option """ # We only test -v and -vv, more testing is really not required here @@ -333,7 +323,7 @@ class CLITests(BaseTestCase): "3: B6 Body message is missing\n" with patch('gitlint.display.stderr', new=StringIO()) as stderr: - result = self.cli.invoke(cli.cli, ["-vv"], input=u"WIP: tïtle \n") + result = self.cli.invoke(cli.cli, ["-vv"], input="WIP: tïtle \n") self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 3) self.assertEqual(result.output, "") @@ -355,19 +345,19 @@ class CLITests(BaseTestCase): "25053ccec5e28e1bb8f7551fdbb5ab213ada2401\n" "4da2656b0dadc76c7ee3fd0243a96cb64007f125\n", # git log --pretty <FORMAT> <SHA> - u"test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00abc\n" - u"commït-title1\n\ncommït-body1", - u"#", # git config --get core.commentchar - u"commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> - u"commit-1/file-1\ncommit-1/file-2\n", # git diff-tree - u"test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00abc\n" - u"commït-title2.\n\ncommït-body2", - u"commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha> - u"commit-2/file-1\ncommit-2/file-2\n", # git diff-tree - u"test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00abc\n" - u"föobar\nbar", - u"commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha> - u"commit-3/file-1\ncommit-3/file-2\n", # git diff-tree + "test åuthor1\x00test-email1@föo.com\x002016-12-03 15:28:15 +0100\x00abc\n" + "commït-title1\n\ncommït-body1", + "#", # git config --get core.commentchar + "commit-1-branch-1\ncommit-1-branch-2\n", # git branch --contains <sha> + "commit-1/file-1\ncommit-1/file-2\n", # git diff-tree + "test åuthor2\x00test-email2@föo.com\x002016-12-04 15:28:15 +0100\x00abc\n" + "commït-title2.\n\ncommït-body2", + "commit-2-branch-1\ncommit-2-branch-2\n", # git branch --contains <sha> + "commit-2/file-1\ncommit-2/file-2\n", # git diff-tree + "test åuthor3\x00test-email3@föo.com\x002016-12-05 15:28:15 +0100\x00abc\n" + "föobar\nbar", + "commit-3-branch-1\ncommit-3-branch-2\n", # git branch --contains <sha> + "commit-3/file-1\ncommit-3/file-2\n", # git diff-tree ] with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -387,14 +377,14 @@ class CLITests(BaseTestCase): expected_logs = self.get_expected('cli/test_cli/test_debug_1', expected_kwargs) self.assert_logged(expected_logs) - @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n") + @patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n") def test_extra_path(self, _): """ Test for --extra-path flag """ # Test extra-path pointing to a directory with patch('gitlint.display.stderr', new=StringIO()) as stderr: extra_path = self.get_sample_path("user_rules") result = self.cli.invoke(cli.cli, ["--extra-path", extra_path]) - expected_output = u"1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \ + expected_output = "1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \ "3: B6 Body message is missing\n" self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 2) @@ -403,12 +393,12 @@ class CLITests(BaseTestCase): with patch('gitlint.display.stderr', new=StringIO()) as stderr: extra_path = self.get_sample_path(os.path.join("user_rules", "my_commit_rules.py")) result = self.cli.invoke(cli.cli, ["--extra-path", extra_path]) - expected_output = u"1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \ + expected_output = "1: UC1 Commit violåtion 1: \"Contënt 1\"\n" + \ "3: B6 Body message is missing\n" self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 2) - @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n\nMy body that is long enough") + @patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n\nMy body that is long enough") def test_contrib(self, _): # Test enabled contrib rules with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -417,13 +407,13 @@ class CLITests(BaseTestCase): self.assertEqual(stderr.getvalue(), expected_output) self.assertEqual(result.exit_code, 3) - @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n") + @patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n") def test_contrib_negative(self, _): - result = self.cli.invoke(cli.cli, ["--contrib", u"föobar,CC1"]) - self.assertEqual(result.output, u"Config Error: No contrib rule with id or name 'föobar' found.\n") + result = self.cli.invoke(cli.cli, ["--contrib", "föobar,CC1"]) + self.assertEqual(result.output, "Config Error: No contrib rule with id or name 'föobar' found.\n") self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) - @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tëst") + @patch('gitlint.cli.get_stdin_data', return_value="WIP: tëst") def test_config_file(self, _): """ Test for --config option """ with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -438,16 +428,14 @@ class CLITests(BaseTestCase): # Directory as config file config_path = self.get_sample_path("config") result = self.cli.invoke(cli.cli, ["--config", config_path]) - expected_string = u"Error: Invalid value for \"-C\" / \"--config\": File \"{0}\" is a directory.".format( - config_path) + expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' is a directory." self.assertEqual(result.output.split("\n")[3], expected_string) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) # Non existing file - config_path = self.get_sample_path(u"föo") + config_path = self.get_sample_path("föo") result = self.cli.invoke(cli.cli, ["--config", config_path]) - expected_string = u"Error: Invalid value for \"-C\" / \"--config\": File \"{0}\" does not exist.".format( - config_path) + expected_string = f"Error: Invalid value for '-C' / '--config': File '{config_path}' does not exist." self.assertEqual(result.output.split("\n")[3], expected_string) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) @@ -471,37 +459,37 @@ class CLITests(BaseTestCase): def test_target_negative(self): """ Negative test for the --target option """ # try setting a non-existing target - result = self.cli.invoke(cli.cli, ["--target", u"/föo/bar"]) + result = self.cli.invoke(cli.cli, ["--target", "/föo/bar"]) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) - expected_msg = u"Error: Invalid value for \"--target\": Directory \"/föo/bar\" does not exist." + expected_msg = "Error: Invalid value for '--target': Directory '/föo/bar' does not exist." self.assertEqual(result.output.split("\n")[3], expected_msg) # try setting a file as target target_path = self.get_sample_path(os.path.join("config", "gitlintconfig")) result = self.cli.invoke(cli.cli, ["--target", target_path]) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) - expected_msg = u"Error: Invalid value for \"--target\": Directory \"{0}\" is a file.".format(target_path) + expected_msg = f"Error: Invalid value for '--target': Directory '{target_path}' is a file." self.assertEqual(result.output.split("\n")[3], expected_msg) @patch('gitlint.config.LintConfigGenerator.generate_config') def test_generate_config(self, generate_config): """ Test for the generate-config subcommand """ - result = self.cli.invoke(cli.cli, ["generate-config"], input=u"tëstfile\n") + result = self.cli.invoke(cli.cli, ["generate-config"], input="tëstfile\n") self.assertEqual(result.exit_code, 0) - expected_msg = u"Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + \ - u"Successfully generated {0}\n".format(os.path.realpath(u"tëstfile")) + expected_msg = "Please specify a location for the sample gitlint config file [.gitlint]: tëstfile\n" + \ + f"Successfully generated {os.path.realpath('tëstfile')}\n" self.assertEqual(result.output, expected_msg) - generate_config.assert_called_once_with(os.path.realpath(u"tëstfile")) + generate_config.assert_called_once_with(os.path.realpath("tëstfile")) def test_generate_config_negative(self): """ Negative test for the generate-config subcommand """ # Non-existing directory - fake_dir = os.path.abspath(u"/föo") - fake_path = os.path.join(fake_dir, u"bar") + fake_dir = os.path.abspath("/föo") + fake_path = os.path.join(fake_dir, "bar") result = self.cli.invoke(cli.cli, ["generate-config"], input=fake_path) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) - expected_msg = (u"Please specify a location for the sample gitlint config file [.gitlint]: {0}\n" - + u"Error: Directory '{1}' does not exist.\n").format(fake_path, fake_dir) + expected_msg = f"Please specify a location for the sample gitlint config file [.gitlint]: {fake_path}\n" + \ + f"Error: Directory '{fake_dir}' does not exist.\n" self.assertEqual(result.output, expected_msg) # Existing file @@ -509,8 +497,8 @@ class CLITests(BaseTestCase): result = self.cli.invoke(cli.cli, ["generate-config"], input=sample_path) self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) expected_msg = "Please specify a location for the sample gitlint " + \ - "config file [.gitlint]: {0}\n".format(sample_path) + \ - "Error: File \"{0}\" already exists.\n".format(sample_path) + f"config file [.gitlint]: {sample_path}\n" + \ + f"Error: File \"{sample_path}\" already exists.\n" self.assertEqual(result.output, expected_msg) @patch('gitlint.cli.get_stdin_data', return_value=False) @@ -528,10 +516,10 @@ class CLITests(BaseTestCase): sh.git.side_effect = lambda *_args, **_kwargs: "" result = self.cli.invoke(cli.cli, ["--commits", "master...HEAD"]) - self.assert_log_contains(u"DEBUG: gitlint.cli No commits in range \"master...HEAD\"") + self.assert_log_contains("DEBUG: gitlint.cli No commits in range \"master...HEAD\"") self.assertEqual(result.exit_code, 0) - @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: tëst tïtle") + @patch('gitlint.cli.get_stdin_data', return_value="WIP: tëst tïtle") def test_named_rules(self, _): with patch('gitlint.display.stderr', new=StringIO()) as stderr: config_path = self.get_sample_path(os.path.join("config", "named-rules")) diff --git a/gitlint/tests/cli/test_cli_hooks.py b/gitlint/tests/cli/test_cli_hooks.py index b5e7fc4..825345f 100644 --- a/gitlint/tests/cli/test_cli_hooks.py +++ b/gitlint/tests/cli/test_cli_hooks.py @@ -1,28 +1,18 @@ # -*- coding: utf-8 -*- import io +from io import StringIO import os from click.testing import CliRunner -try: - # python 2.x - from StringIO import StringIO -except ImportError: - # python 3.x - from io import StringIO # pylint: disable=ungrouped-imports - -try: - # python 2.x - from mock import patch -except ImportError: - # python 3.x - from unittest.mock import patch # pylint: disable=no-name-in-module, import-error +from unittest.mock import patch from gitlint.tests.base import BaseTestCase from gitlint import cli from gitlint import hooks from gitlint import config +from gitlint.shell import ErrorReturnCode from gitlint.utils import DEFAULT_ENCODING @@ -45,12 +35,12 @@ class CLIHookTests(BaseTestCase): self.git_version_path.stop() @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook') - @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join(u"/hür", u"dur")) + @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join("/hür", "dur")) def test_install_hook(self, _, install_hook): """ Test for install-hook subcommand """ result = self.cli.invoke(cli.cli, ["install-hook"]) - expected_path = os.path.join(u"/hür", u"dur", hooks.COMMIT_MSG_HOOK_DST_PATH) - expected = u"Successfully installed gitlint commit-msg hook in {0}\n".format(expected_path) + expected_path = os.path.join("/hür", "dur", hooks.COMMIT_MSG_HOOK_DST_PATH) + expected = f"Successfully installed gitlint commit-msg hook in {expected_path}\n" self.assertEqual(result.output, expected) self.assertEqual(result.exit_code, 0) expected_config = config.LintConfig() @@ -58,12 +48,12 @@ class CLIHookTests(BaseTestCase): install_hook.assert_called_once_with(expected_config) @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook') - @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join(u"/hür", u"dur")) + @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join("/hür", "dur")) def test_install_hook_target(self, _, install_hook): """ Test for install-hook subcommand with a specific --target option specified """ # Specified target result = self.cli.invoke(cli.cli, ["--target", self.SAMPLES_DIR, "install-hook"]) - expected_path = os.path.join(u"/hür", u"dur", hooks.COMMIT_MSG_HOOK_DST_PATH) + expected_path = os.path.join("/hür", "dur", hooks.COMMIT_MSG_HOOK_DST_PATH) expected = "Successfully installed gitlint commit-msg hook in %s\n" % expected_path self.assertEqual(result.exit_code, 0) self.assertEqual(result.output, expected) @@ -72,40 +62,40 @@ class CLIHookTests(BaseTestCase): expected_config.target = self.SAMPLES_DIR install_hook.assert_called_once_with(expected_config) - @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook', side_effect=hooks.GitHookInstallerError(u"tëst")) + @patch('gitlint.hooks.GitHookInstaller.install_commit_msg_hook', side_effect=hooks.GitHookInstallerError("tëst")) def test_install_hook_negative(self, install_hook): """ Negative test for install-hook subcommand """ result = self.cli.invoke(cli.cli, ["install-hook"]) self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE) - self.assertEqual(result.output, u"tëst\n") + self.assertEqual(result.output, "tëst\n") expected_config = config.LintConfig() expected_config.target = os.path.realpath(os.getcwd()) install_hook.assert_called_once_with(expected_config) @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook') - @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join(u"/hür", u"dur")) + @patch('gitlint.hooks.git_hooks_dir', return_value=os.path.join("/hür", "dur")) def test_uninstall_hook(self, _, uninstall_hook): """ Test for uninstall-hook subcommand """ result = self.cli.invoke(cli.cli, ["uninstall-hook"]) - expected_path = os.path.join(u"/hür", u"dur", hooks.COMMIT_MSG_HOOK_DST_PATH) - expected = u"Successfully uninstalled gitlint commit-msg hook from {0}\n".format(expected_path) + expected_path = os.path.join("/hür", "dur", hooks.COMMIT_MSG_HOOK_DST_PATH) + expected = f"Successfully uninstalled gitlint commit-msg hook from {expected_path}\n" self.assertEqual(result.exit_code, 0) self.assertEqual(result.output, expected) expected_config = config.LintConfig() expected_config.target = os.path.realpath(os.getcwd()) uninstall_hook.assert_called_once_with(expected_config) - @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook', side_effect=hooks.GitHookInstallerError(u"tëst")) + @patch('gitlint.hooks.GitHookInstaller.uninstall_commit_msg_hook', side_effect=hooks.GitHookInstallerError("tëst")) def test_uninstall_hook_negative(self, uninstall_hook): """ Negative test for uninstall-hook subcommand """ result = self.cli.invoke(cli.cli, ["uninstall-hook"]) self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE) - self.assertEqual(result.output, u"tëst\n") + self.assertEqual(result.output, "tëst\n") expected_config = config.LintConfig() expected_config.target = os.path.realpath(os.getcwd()) uninstall_hook.assert_called_once_with(expected_config) - def test_hook_no_tty(self): + def test_run_hook_no_tty(self): """ Test for run-hook subcommand. When no TTY is available (like is the case for this test), the hook will abort after the first check. """ @@ -119,9 +109,9 @@ class CLIHookTests(BaseTestCase): # check the output which indirectly proves the same thing. with self.tempdir() as tmpdir: - msg_filename = os.path.join(tmpdir, u"hür") + msg_filename = os.path.join(tmpdir, "hür") with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f: - f.write(u"WIP: tïtle\n") + f.write("WIP: tïtle\n") with patch('gitlint.display.stderr', new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"]) @@ -132,12 +122,12 @@ class CLIHookTests(BaseTestCase): self.assertEqual(result.exit_code, 1) @patch('gitlint.cli.shell') - def test_hook_edit(self, shell): + def test_run_hook_edit(self, shell): """ Test for run-hook subcommand, answering 'e(dit)' after commit-hook """ - set_editors = [None, u"myeditor"] - expected_editors = [u"vim -n", u"myeditor"] - commit_messages = [u"WIP: höok edit 1", u"WIP: höok edit 2"] + set_editors = [None, "myeditor"] + expected_editors = ["vim -n", "myeditor"] + commit_messages = ["WIP: höok edit 1", "WIP: höok edit 2"] for i in range(0, len(set_editors)): if set_editors[i]: @@ -145,7 +135,7 @@ class CLIHookTests(BaseTestCase): with self.patch_input(['e', 'e', 'n']): with self.tempdir() as tmpdir: - msg_filename = os.path.realpath(os.path.join(tmpdir, u"hür")) + msg_filename = os.path.realpath(os.path.join(tmpdir, "hür")) with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f: f.write(commit_messages[i] + "\n") @@ -161,18 +151,17 @@ class CLIHookTests(BaseTestCase): self.assertEqual(result.exit_code, 2) shell.assert_called_with(expected_editors[i] + " " + msg_filename) - self.assert_log_contains(u"DEBUG: gitlint.cli run-hook: editing commit message") - self.assert_log_contains(u"DEBUG: gitlint.cli run-hook: {0} {1}".format(expected_editors[i], - msg_filename)) + self.assert_log_contains("DEBUG: gitlint.cli run-hook: editing commit message") + self.assert_log_contains(f"DEBUG: gitlint.cli run-hook: {expected_editors[i]} {msg_filename}") - def test_hook_no(self): + def test_run_hook_no(self): """ Test for run-hook subcommand, answering 'n(o)' after commit-hook """ with self.patch_input(['n']): with self.tempdir() as tmpdir: - msg_filename = os.path.join(tmpdir, u"hür") + msg_filename = os.path.join(tmpdir, "hür") with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f: - f.write(u"WIP: höok no\n") + f.write("WIP: höok no\n") with patch('gitlint.display.stderr', new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"]) @@ -184,13 +173,13 @@ class CLIHookTests(BaseTestCase): self.assertEqual(result.exit_code, 2) self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message declined") - def test_hook_yes(self): + def test_run_hook_yes(self): """ Test for run-hook subcommand, answering 'y(es)' after commit-hook """ with self.patch_input(['y']): with self.tempdir() as tmpdir: - msg_filename = os.path.join(tmpdir, u"hür") + msg_filename = os.path.join(tmpdir, "hür") with io.open(msg_filename, 'w', encoding=DEFAULT_ENCODING) as f: - f.write(u"WIP: höok yes\n") + f.write("WIP: höok yes\n") with patch('gitlint.display.stderr', new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["--msg-filename", msg_filename, "run-hook"]) @@ -202,8 +191,32 @@ class CLIHookTests(BaseTestCase): self.assertEqual(result.exit_code, 0) self.assert_log_contains("DEBUG: gitlint.cli run-hook: commit message accepted") - @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: Test hook stdin tïtle\n") - def test_hook_stdin_violations(self, _): + @patch('gitlint.cli.get_stdin_data', return_value=False) + @patch('gitlint.git.sh') + def test_run_hook_negative(self, sh, _): + """ Negative test for the run-hook subcommand: testing whether exceptions are correctly handled when + running `gitlint run-hook`. + """ + # GIT_CONTEXT_ERROR_CODE: git error + error_msg = b"fatal: not a git repository (or any of the parent directories): .git" + sh.git.side_effect = ErrorReturnCode("full command", b"stdout", error_msg) + result = self.cli.invoke(cli.cli, ["run-hook"]) + expected = self.get_expected('cli/test_cli_hooks/test_run_hook_negative_1', {'git_repo': os.getcwd()}) + self.assertEqual(result.output, expected) + self.assertEqual(result.exit_code, self.GIT_CONTEXT_ERROR_CODE) + + # USAGE_ERROR_CODE: incorrect use of gitlint + result = self.cli.invoke(cli.cli, ["--staged", "run-hook"]) + self.assertEqual(result.output, self.get_expected('cli/test_cli_hooks/test_run_hook_negative_2')) + self.assertEqual(result.exit_code, self.USAGE_ERROR_CODE) + + # CONFIG_ERROR_CODE: incorrect config. Note that this is handled before the hook even runs + result = self.cli.invoke(cli.cli, ["-c", "föo.bár=1", "run-hook"]) + self.assertEqual(result.output, "Config Error: No such rule 'föo'\n") + self.assertEqual(result.exit_code, self.CONFIG_ERROR_CODE) + + @patch('gitlint.cli.get_stdin_data', return_value="WIP: Test hook stdin tïtle\n") + def test_run_hook_stdin_violations(self, _): """ Test for passing stdin data to run-hook, expecting some violations. Equivalent of: $ echo "WIP: Test hook stdin tïtle" | gitlint run-hook """ @@ -216,8 +229,8 @@ class CLIHookTests(BaseTestCase): # Hook will auto-abort because we're using stdin. Abort = exit code 1 self.assertEqual(result.exit_code, 1) - @patch('gitlint.cli.get_stdin_data', return_value=u"Test tïtle\n\nTest bödy that is long enough") - def test_hook_stdin_no_violations(self, _): + @patch('gitlint.cli.get_stdin_data', return_value="Test tïtle\n\nTest bödy that is long enough") + def test_run_hook_stdin_no_violations(self, _): """ Test for passing stdin data to run-hook, expecting *NO* violations, Equivalent of: $ echo -e "Test tïtle\n\nTest bödy that is long enough" | gitlint run-hook """ @@ -229,8 +242,8 @@ class CLIHookTests(BaseTestCase): self.assertEqual(result.output, expected_stdout) self.assertEqual(result.exit_code, 0) - @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: Test hook config tïtle\n") - def test_hook_config(self, _): + @patch('gitlint.cli.get_stdin_data', return_value="WIP: Test hook config tïtle\n") + def test_run_hook_config(self, _): """ Test that gitlint still respects config when running run-hook, equivalent of: $ echo "WIP: Test hook config tïtle" | gitlint -c title-max-length.line-length=5 --ignore B6 run-hook """ @@ -244,18 +257,18 @@ class CLIHookTests(BaseTestCase): @patch('gitlint.cli.get_stdin_data', return_value=False) @patch('gitlint.git.sh') - def test_hook_local_commit(self, sh, _): + def test_run_hook_local_commit(self, sh, _): """ Test running the hook on the last commit-msg from the local repo, equivalent of: $ gitlint run-hook and then choosing 'e' """ sh.git.side_effect = [ "6f29bf81a8322a04071bb794666e48c443a90360", - u"test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - u"WIP: commït-title\n\ncommït-body", - u"#", # git config --get core.commentchar - u"commit-1-branch-1\ncommit-1-branch-2\n", - u"file1.txt\npåth/to/file2.txt\n" + "test åuthor\x00test-email@föo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "WIP: commït-title\n\ncommït-body", + "#", # git config --get core.commentchar + "commit-1-branch-1\ncommit-1-branch-2\n", + "file1.txt\npåth/to/file2.txt\n" ] with self.patch_input(['e']): diff --git a/gitlint/tests/config/test_config.py b/gitlint/tests/config/test_config.py index b981a86..93e35de 100644 --- a/gitlint/tests/config/test_config.py +++ b/gitlint/tests/config/test_config.py @@ -1,16 +1,11 @@ # -*- coding: utf-8 -*- -try: - # python 2.x - from mock import patch -except ImportError: - # python 3.x - from unittest.mock import patch # pylint: disable=no-name-in-module, import-error +from unittest.mock import patch from gitlint import rules from gitlint.config import LintConfig, LintConfigError, LintConfigGenerator, GITLINT_CONFIG_TEMPLATE_SRC_PATH from gitlint import options -from gitlint.tests.base import BaseTestCase, ustr +from gitlint.tests.base import BaseTestCase class LintConfigTests(BaseTestCase): @@ -29,20 +24,20 @@ class LintConfigTests(BaseTestCase): config = LintConfig() # non-existing rule - expected_error_msg = u"No such rule 'föobar'" + expected_error_msg = "No such rule 'föobar'" with self.assertRaisesMessage(LintConfigError, expected_error_msg): config.set_rule_option(u'föobar', u'lïne-length', 60) # non-existing option - expected_error_msg = u"Rule 'title-max-length' has no option 'föobar'" + expected_error_msg = "Rule 'title-max-length' has no option 'föobar'" with self.assertRaisesMessage(LintConfigError, expected_error_msg): config.set_rule_option('title-max-length', u'föobar', 60) # invalid option value - expected_error_msg = u"'föo' is not a valid value for option 'title-max-length.line-length'. " + \ - u"Option 'line-length' must be a positive integer (current value: 'föo')." + expected_error_msg = "'föo' is not a valid value for option 'title-max-length.line-length'. " + \ + "Option 'line-length' must be a positive integer (current value: 'föo')." with self.assertRaisesMessage(LintConfigError, expected_error_msg): - config.set_rule_option('title-max-length', 'line-length', u"föo") + config.set_rule_option('title-max-length', 'line-length', "föo") def test_set_general_option(self): config = LintConfig() @@ -117,7 +112,7 @@ class LintConfigTests(BaseTestCase): 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(str(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) @@ -135,7 +130,7 @@ class LintConfigTests(BaseTestCase): 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(str(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') @@ -151,15 +146,15 @@ class LintConfigTests(BaseTestCase): def test_contrib_negative(self): config = LintConfig() # non-existent contrib rule - with self.assertRaisesMessage(LintConfigError, u"No contrib rule with id or name 'föo' found."): - config.contrib = u"contrib-title-conventional-commits,föo" + with self.assertRaisesMessage(LintConfigError, "No contrib rule with id or name 'föo' found."): + config.contrib = "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")] + side_effects = [rules.UserRuleError("üser-rule"), options.RuleOptionError("rüle-option")] for side_effect in side_effects: with patch('gitlint.config.rule_finder.find_rule_classes', side_effect=side_effect): - with self.assertRaisesMessage(LintConfigError, ustr(side_effect)): - config.contrib = u"contrib-title-conventional-commits" + with self.assertRaisesMessage(LintConfigError, str(side_effect)): + config.contrib = "contrib-title-conventional-commits" def test_extra_path(self): config = LintConfig() @@ -168,11 +163,11 @@ class LintConfigTests(BaseTestCase): self.assertEqual(config.extra_path, self.get_user_rules_path()) actual_rule = config.rules.find_rule('UC1') self.assertTrue(actual_rule.is_user_defined) - self.assertEqual(ustr(type(actual_rule)), "<class 'my_commit_rules.MyUserCommitRule'>") + self.assertEqual(str(type(actual_rule)), "<class 'my_commit_rules.MyUserCommitRule'>") self.assertEqual(actual_rule.id, 'UC1') self.assertEqual(actual_rule.name, u'my-üser-commit-rule') self.assertEqual(actual_rule.target, None) - expected_rule_option = options.IntOption('violation-count', 1, u"Number of violåtions to return") + expected_rule_option = options.IntOption('violation-count', 1, "Number of violåtions to return") self.assertListEqual(actual_rule.options_spec, [expected_rule_option]) self.assertDictEqual(actual_rule.options, {'violation-count': expected_rule_option}) @@ -183,10 +178,10 @@ class LintConfigTests(BaseTestCase): 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')" + regex = "Option extra-path must be either an existing directory or file (current value: 'föo/bar')" # incorrect extra_path with self.assertRaisesMessage(LintConfigError, regex): - config.extra_path = u"föo/bar" + config.extra_path = "föo/bar" # extra path contains classes with errors with self.assertRaisesMessage(LintConfigError, @@ -198,17 +193,17 @@ class LintConfigTests(BaseTestCase): # Note that we shouldn't test whether we can set unicode because python just doesn't allow unicode attributes with self.assertRaisesMessage(LintConfigError, "'foo' is not a valid gitlint option"): - config.set_general_option("foo", u"bår") + config.set_general_option("foo", "bår") # try setting _config_path, this is a real attribute of LintConfig, but the code should prevent it from # being set with self.assertRaisesMessage(LintConfigError, "'_config_path' is not a valid gitlint option"): - config.set_general_option("_config_path", u"bår") + config.set_general_option("_config_path", "bår") # invalid verbosity - incorrect_values = [-1, u"föo"] + incorrect_values = [-1, "föo"] for value in incorrect_values: - expected_msg = u"Option 'verbosity' must be a positive integer (current value: '{0}')".format(value) + expected_msg = f"Option 'verbosity' must be a positive integer (current value: '{value}')" with self.assertRaisesMessage(LintConfigError, expected_msg): config.verbosity = value @@ -220,12 +215,12 @@ class LintConfigTests(BaseTestCase): # 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"] + incorrect_values = [-1, 4, "föo"] for attribute in ignore_attributes: for value in incorrect_values: option_name = attribute.replace("_", "-") with self.assertRaisesMessage(LintConfigError, - "Option '{0}' must be either 'true' or 'false'".format(option_name)): + f"Option '{option_name}' must be either 'true' or 'false'"): setattr(config, attribute, value) # invalid ignore -> not here because ignore is a ListOption which converts everything to a string before @@ -235,15 +230,15 @@ class LintConfigTests(BaseTestCase): for attribute in ['debug', 'staged', 'ignore_stdin']: option_name = attribute.replace("_", "-") with self.assertRaisesMessage(LintConfigError, - "Option '{0}' must be either 'true' or 'false'".format(option_name)): - setattr(config, attribute, u"föobar") + f"Option '{option_name}' must be either 'true' or 'false'"): + setattr(config, attribute, "föobar") # extra-path has its own negative test # invalid target with self.assertRaisesMessage(LintConfigError, - u"Option target must be an existing directory (current value: 'föo/bar')"): - config.target = u"föo/bar" + "Option target must be an existing directory (current value: 'föo/bar')"): + config.target = "föo/bar" def test_ignore_independent_from_rules(self): # Test that the lintconfig rules are not modified when setting config.ignore @@ -273,9 +268,9 @@ class LintConfigTests(BaseTestCase): # Other attributes don't matter config1 = LintConfig() config2 = LintConfig() - config1.foo = u"bår" + config1.foo = "bår" self.assertEqual(config1, config2) - config2.foo = u"dūr" + config2.foo = "dūr" self.assertEqual(config1, config2) @@ -283,5 +278,5 @@ class LintConfigGeneratorTests(BaseTestCase): @staticmethod @patch('gitlint.config.shutil.copyfile') def test_install_commit_msg_hook_negative(copy): - LintConfigGenerator.generate_config(u"föo/bar/test") - copy.assert_called_with(GITLINT_CONFIG_TEMPLATE_SRC_PATH, u"föo/bar/test") + LintConfigGenerator.generate_config("föo/bar/test") + copy.assert_called_with(GITLINT_CONFIG_TEMPLATE_SRC_PATH, "föo/bar/test") diff --git a/gitlint/tests/config/test_config_builder.py b/gitlint/tests/config/test_config_builder.py index 5a28c9f..e0d7f9b 100644 --- a/gitlint/tests/config/test_config_builder.py +++ b/gitlint/tests/config/test_config_builder.py @@ -42,30 +42,30 @@ class LintConfigBuilderTests(BaseTestCase): config_builder = LintConfigBuilder() # nothing gitlint - config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint\nfoo")) + config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint\nfoo")) config = config_builder.build() self.assertSequenceEqual(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_builder.set_config_from_commit(self.gitcommit("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_builder.set_config_from_commit(self.gitcommit("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_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore: \t all\nfoo")) config = config_builder.build() self.assertEqual(config.ignore, original_rule_ids) def test_set_from_commit_ignore_specific(self): # ignore specific rules config_builder = LintConfigBuilder() - config_builder.set_config_from_commit(self.gitcommit(u"tëst\ngitlint-ignore: T1, body-hard-tab")) + config_builder.set_config_from_commit(self.gitcommit("tëst\ngitlint-ignore: T1, body-hard-tab")) config = config_builder.build() self.assertEqual(config.ignore, ["T1", "body-hard-tab"]) @@ -89,14 +89,14 @@ class LintConfigBuilderTests(BaseTestCase): config_builder = LintConfigBuilder() # bad config file load - foo_path = self.get_sample_path(u"föo") - expected_error_msg = u"Invalid file path: {0}".format(foo_path) + foo_path = self.get_sample_path("föo") + expected_error_msg = f"Invalid file path: {foo_path}" with self.assertRaisesMessage(LintConfigError, expected_error_msg): config_builder.set_from_config_file(foo_path) # error during file parsing path = self.get_sample_path("config/no-sections") - expected_error_msg = u"File contains no section headers." + expected_error_msg = "File contains no section headers." # We only match the start of the message here, since the exact message can vary depending on platform with self.assertRaisesRegex(LintConfigError, expected_error_msg): config_builder.set_from_config_file(path) @@ -105,7 +105,7 @@ class LintConfigBuilderTests(BaseTestCase): path = self.get_sample_path("config/nonexisting-rule") config_builder = LintConfigBuilder() config_builder.set_from_config_file(path) - expected_error_msg = u"No such rule 'föobar'" + expected_error_msg = "No such rule 'föobar'" with self.assertRaisesMessage(LintConfigError, expected_error_msg): config_builder.build() @@ -113,7 +113,7 @@ class LintConfigBuilderTests(BaseTestCase): path = self.get_sample_path("config/nonexisting-general-option") config_builder = LintConfigBuilder() config_builder.set_from_config_file(path) - expected_error_msg = u"'foo' is not a valid gitlint option" + expected_error_msg = "'foo' is not a valid gitlint option" with self.assertRaisesMessage(LintConfigError, expected_error_msg): config_builder.build() @@ -121,7 +121,7 @@ class LintConfigBuilderTests(BaseTestCase): path = self.get_sample_path("config/nonexisting-option") config_builder = LintConfigBuilder() config_builder.set_from_config_file(path) - expected_error_msg = u"Rule 'title-max-length' has no option 'föobar'" + expected_error_msg = "Rule 'title-max-length' has no option 'föobar'" with self.assertRaisesMessage(LintConfigError, expected_error_msg): config_builder.build() @@ -129,8 +129,8 @@ class LintConfigBuilderTests(BaseTestCase): path = self.get_sample_path("config/invalid-option-value") config_builder = LintConfigBuilder() config_builder.set_from_config_file(path) - expected_error_msg = u"'föo' is not a valid value for option 'title-max-length.line-length'. " + \ - u"Option 'line-length' must be a positive integer (current value: 'föo')." + expected_error_msg = "'föo' is not a valid value for option 'title-max-length.line-length'. " + \ + "Option 'line-length' must be a positive integer (current value: 'föo')." with self.assertRaisesMessage(LintConfigError, expected_error_msg): config_builder.build() @@ -141,39 +141,39 @@ class LintConfigBuilderTests(BaseTestCase): 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"]) + "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.assertListEqual(config.get_rule_option('title-must-not-contain-word', 'words'), ["håha"]) self.assertEqual(config.verbosity, 1) def test_set_config_from_string_list_negative(self): config_builder = LintConfigBuilder() # assert error on incorrect rule - this happens at build time - config_builder.set_config_from_string_list([u"föo.bar=1"]) - with self.assertRaisesMessage(LintConfigError, u"No such rule 'föo'"): + config_builder.set_config_from_string_list(["föo.bar=1"]) + with self.assertRaisesMessage(LintConfigError, "No such rule 'föo'"): config_builder.build() # no equal sign - expected_msg = u"'föo.bar' is an invalid configuration option. Use '<rule>.<option>=<value>'" + expected_msg = "'föo.bar' is an invalid configuration option. Use '<rule>.<option>=<value>'" with self.assertRaisesMessage(LintConfigError, expected_msg): - config_builder.set_config_from_string_list([u"föo.bar"]) + config_builder.set_config_from_string_list(["föo.bar"]) # missing value - expected_msg = u"'föo.bar=' is an invalid configuration option. Use '<rule>.<option>=<value>'" + expected_msg = "'föo.bar=' is an invalid configuration option. Use '<rule>.<option>=<value>'" with self.assertRaisesMessage(LintConfigError, expected_msg): - config_builder.set_config_from_string_list([u"föo.bar="]) + config_builder.set_config_from_string_list(["föo.bar="]) # space instead of equal sign - expected_msg = u"'föo.bar 1' is an invalid configuration option. Use '<rule>.<option>=<value>'" + expected_msg = "'föo.bar 1' is an invalid configuration option. Use '<rule>.<option>=<value>'" with self.assertRaisesMessage(LintConfigError, expected_msg): - config_builder.set_config_from_string_list([u"föo.bar 1"]) + config_builder.set_config_from_string_list(["föo.bar 1"]) # no period between rule and option names - expected_msg = u"'föobar=1' is an invalid configuration option. Use '<rule>.<option>=<value>'" + expected_msg = "'föobar=1' is an invalid configuration option. Use '<rule>.<option>=<value>'" with self.assertRaisesMessage(LintConfigError, expected_msg): config_builder.set_config_from_string_list([u'föobar=1']) @@ -216,15 +216,15 @@ class LintConfigBuilderTests(BaseTestCase): # Add a named rule by setting an option in the config builder that follows the named rule pattern # Assert that whitespace in the rule name is stripped rule_qualifiers = [u'T7:my-extra-rüle', u' T7 : my-extra-rüle ', u'\tT7:\tmy-extra-rüle\t', - u'T7:\t\n \tmy-extra-rüle\t\n\n', u"title-match-regex:my-extra-rüle"] + u'T7:\t\n \tmy-extra-rüle\t\n\n', "title-match-regex:my-extra-rüle"] for rule_qualifier in rule_qualifiers: config_builder = LintConfigBuilder() - config_builder.set_option(rule_qualifier, 'regex', u"föo") + config_builder.set_option(rule_qualifier, 'regex', "föo") expected_rules = copy.deepcopy(default_rules) - my_rule = rules.TitleRegexMatches({'regex': u"föo"}) - my_rule.id = rules.TitleRegexMatches.id + u":my-extra-rüle" - my_rule.name = rules.TitleRegexMatches.name + u":my-extra-rüle" + my_rule = rules.TitleRegexMatches({'regex': "föo"}) + my_rule.id = rules.TitleRegexMatches.id + ":my-extra-rüle" + my_rule.name = rules.TitleRegexMatches.name + ":my-extra-rüle" expected_rules._rules[u'T7:my-extra-rüle'] = my_rule self.assertEqual(config_builder.build().rules, expected_rules) @@ -233,32 +233,32 @@ class LintConfigBuilderTests(BaseTestCase): # to the same rule for other_rule_qualifier in rule_qualifiers: cb = config_builder.clone() - cb.set_option(other_rule_qualifier, 'regex', other_rule_qualifier + u"bōr") + cb.set_option(other_rule_qualifier, 'regex', other_rule_qualifier + "bōr") # before setting the expected rule option value correctly, the RuleCollection should be different self.assertNotEqual(cb.build().rules, expected_rules) # after setting the option on the expected rule, it should be equal - my_rule.options['regex'].set(other_rule_qualifier + u"bōr") + my_rule.options['regex'].set(other_rule_qualifier + "bōr") self.assertEqual(cb.build().rules, expected_rules) - my_rule.options['regex'].set(u"wrong") + my_rule.options['regex'].set("wrong") def test_named_rules_negative(self): # T7 = title-match-regex # Invalid rule name - for invalid_name in ["", " ", " ", "\t", "\n", u"å b", u"å:b", u"åb:", u":åb"]: + for invalid_name in ["", " ", " ", "\t", "\n", "å b", "å:b", "åb:", ":åb"]: config_builder = LintConfigBuilder() - config_builder.set_option(u"T7:{0}".format(invalid_name), 'regex', u"tëst") - expected_msg = u"The rule-name part in 'T7:{0}' cannot contain whitespace, colons or be empty" - with self.assertRaisesMessage(LintConfigError, expected_msg.format(invalid_name)): + config_builder.set_option(f"T7:{invalid_name}", 'regex', "tëst") + expected_msg = f"The rule-name part in 'T7:{invalid_name}' cannot contain whitespace, colons or be empty" + with self.assertRaisesMessage(LintConfigError, expected_msg): config_builder.build() # Invalid parent rule name config_builder = LintConfigBuilder() - config_builder.set_option(u"Ž123:foöbar", u"fåke-option", u"fåke-value") - with self.assertRaisesMessage(LintConfigError, u"No such rule 'Ž123' (named rule: 'Ž123:foöbar')"): + config_builder.set_option("Ž123:foöbar", "fåke-option", "fåke-value") + with self.assertRaisesMessage(LintConfigError, "No such rule 'Ž123' (named rule: 'Ž123:foöbar')"): config_builder.build() # Invalid option name (this is the same as with regular rules) config_builder = LintConfigBuilder() - config_builder.set_option(u"T7:foöbar", u"blå", u"my-rëgex") - with self.assertRaisesMessage(LintConfigError, u"Rule 'T7:foöbar' has no option 'blå'"): + config_builder.set_option("T7:foöbar", "blå", "my-rëgex") + with self.assertRaisesMessage(LintConfigError, "Rule 'T7:foöbar' has no option 'blå'"): config_builder.build() diff --git a/gitlint/tests/config/test_config_precedence.py b/gitlint/tests/config/test_config_precedence.py index a0eeccd..aa4de88 100644 --- a/gitlint/tests/config/test_config_precedence.py +++ b/gitlint/tests/config/test_config_precedence.py @@ -1,20 +1,10 @@ # -*- coding: utf-8 -*- -try: - # python 2.x - from StringIO import StringIO -except ImportError: - # python 3.x - from io import StringIO +from io import StringIO from click.testing import CliRunner -try: - # python 2.x - from mock import patch -except ImportError: - # python 3.x - from unittest.mock import patch # pylint: disable=no-name-in-module, import-error +from unittest.mock import patch from gitlint.tests.base import BaseTestCase from gitlint import cli @@ -25,7 +15,7 @@ class LintConfigPrecedenceTests(BaseTestCase): def setUp(self): self.cli = CliRunner() - @patch('gitlint.cli.get_stdin_data', return_value=u"WIP:fö\n\nThis is å test message\n") + @patch('gitlint.cli.get_stdin_data', return_value="WIP:fö\n\nThis is å test message\n") def test_config_precedence(self, _): # TODO(jroovers): this test really only test verbosity, we need to do some refactoring to gitlint.cli # to more easily test everything @@ -41,14 +31,14 @@ class LintConfigPrecedenceTests(BaseTestCase): with patch('gitlint.display.stderr', new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["-vvv", "-c", "general.verbosity=2", "--config", config_path]) self.assertEqual(result.output, "") - self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n") + self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n") # 2. environment variables with patch('gitlint.display.stderr', new=StringIO()) as stderr: result = self.cli.invoke(cli.cli, ["-c", "general.verbosity=2", "--config", config_path], env={"GITLINT_VERBOSITY": "3"}) self.assertEqual(result.output, "") - self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n") + self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n") # 3. commandline -c flags with patch('gitlint.display.stderr', new=StringIO()) as stderr: @@ -66,9 +56,9 @@ class LintConfigPrecedenceTests(BaseTestCase): with patch('gitlint.display.stderr', new=StringIO()) as stderr: result = self.cli.invoke(cli.cli) self.assertEqual(result.output, "") - self.assertEqual(stderr.getvalue(), u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n") + self.assertEqual(stderr.getvalue(), "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP:fö\"\n") - @patch('gitlint.cli.get_stdin_data', return_value=u"WIP: This is å test") + @patch('gitlint.cli.get_stdin_data', return_value="WIP: This is å test") def test_ignore_precedence(self, get_stdin_data): with patch('gitlint.display.stderr', new=StringIO()) as stderr: # --ignore takes precedence over -c general.ignore @@ -77,11 +67,11 @@ class LintConfigPrecedenceTests(BaseTestCase): self.assertEqual(result.exit_code, 1) # We still expect the T5 violation, but no B6 violation as --ignore overwrites -c general.ignore self.assertEqual(stderr.getvalue(), - u"1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is å test\"\n") + "1: T5 Title contains the word 'WIP' (case-insensitive): \"WIP: This is å test\"\n") # test that we can also still configure a rule that is first ignored but then not with patch('gitlint.display.stderr', new=StringIO()) as stderr: - get_stdin_data.return_value = u"This is å test" + get_stdin_data.return_value = "This is å test" # --ignore takes precedence over -c general.ignore result = self.cli.invoke(cli.cli, ["-c", "general.ignore=title-max-length", "-c", "title-max-length.line-length=5", @@ -91,7 +81,7 @@ class LintConfigPrecedenceTests(BaseTestCase): # We still expect the T1 violation with custom config, # but no B6 violation as --ignore overwrites -c general.ignore - self.assertEqual(stderr.getvalue(), u"1: T1 Title exceeds max length (14>5): \"This is å test\"\n") + self.assertEqual(stderr.getvalue(), "1: T1 Title exceeds max length (14>5): \"This is å test\"\n") def test_general_option_after_rule_option(self): # We used to have a bug where we didn't process general options before setting specific options, this would diff --git a/gitlint/tests/config/test_rule_collection.py b/gitlint/tests/config/test_rule_collection.py index 089992c..5a50be0 100644 --- a/gitlint/tests/config/test_rule_collection.py +++ b/gitlint/tests/config/test_rule_collection.py @@ -10,34 +10,34 @@ class RuleCollectionTests(BaseTestCase): def test_add_rule(self): collection = RuleCollection() - collection.add_rule(rules.TitleMaxLength, u"my-rüle", {"my_attr": u"föo", "my_attr2": 123}) + collection.add_rule(rules.TitleMaxLength, "my-rüle", {"my_attr": "föo", "my_attr2": 123}) expected = rules.TitleMaxLength() - expected.id = u"my-rüle" - expected.my_attr = u"föo" + expected.id = "my-rüle" + expected.my_attr = "föo" expected.my_attr2 = 123 self.assertEqual(len(collection), 1) - self.assertDictEqual(collection._rules, OrderedDict({u"my-rüle": expected})) + self.assertDictEqual(collection._rules, OrderedDict({"my-rüle": expected})) # Need to explicitely compare expected attributes as the rule.__eq__ method does not compare these attributes self.assertEqual(collection._rules[expected.id].my_attr, expected.my_attr) self.assertEqual(collection._rules[expected.id].my_attr2, expected.my_attr2) def test_add_find_rule(self): collection = RuleCollection() - collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"my_attr": u"föo"}) + collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"my_attr": "föo"}) # find by id expected = rules.TitleMaxLength() rule = collection.find_rule('T1') self.assertEqual(rule, expected) - self.assertEqual(rule.my_attr, u"föo") + self.assertEqual(rule.my_attr, "föo") # find by name expected2 = rules.TitleTrailingWhitespace() rule = collection.find_rule('title-trailing-whitespace') self.assertEqual(rule, expected2) - self.assertEqual(rule.my_attr, u"föo") + self.assertEqual(rule.my_attr, "föo") # find non-existing rule = collection.find_rule(u'föo') @@ -45,8 +45,8 @@ class RuleCollectionTests(BaseTestCase): def test_delete_rules_by_attr(self): collection = RuleCollection() - collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"foo": u"bår"}) - collection.add_rules([rules.BodyHardTab], {"hur": u"dûr"}) + collection.add_rules([rules.TitleMaxLength, rules.TitleTrailingWhitespace], {"foo": "bår"}) + collection.add_rules([rules.BodyHardTab], {"hur": "dûr"}) # Assert all rules are there as expected self.assertEqual(len(collection), 3) @@ -54,11 +54,11 @@ class RuleCollectionTests(BaseTestCase): self.assertEqual(collection.find_rule(expected_rule.id), expected_rule) # Delete rules by attr, assert that we still have the right rules in the collection - collection.delete_rules_by_attr("foo", u"bår") + collection.delete_rules_by_attr("foo", "bår") self.assertEqual(len(collection), 1) self.assertIsNone(collection.find_rule(rules.TitleMaxLength.id), None) self.assertIsNone(collection.find_rule(rules.TitleTrailingWhitespace.id), None) found = collection.find_rule(rules.BodyHardTab.id) self.assertEqual(found, rules.BodyHardTab()) - self.assertEqual(found.hur, u"dûr") + self.assertEqual(found.hur, "dûr") diff --git a/gitlint/tests/contrib/rules/test_conventional_commit.py b/gitlint/tests/contrib/rules/test_conventional_commit.py index 001af32..fb492df 100644 --- a/gitlint/tests/contrib/rules/test_conventional_commit.py +++ b/gitlint/tests/contrib/rules/test_conventional_commit.py @@ -20,28 +20,28 @@ class ContribConventionalCommitTests(BaseTestCase): # No violations when using a correct type and format for type in ["fix", "feat", "chore", "docs", "style", "refactor", "perf", "test", "revert", "ci", "build"]: - violations = rule.validate(type + u": föo", None) + violations = rule.validate(type + ": föo", None) self.assertListEqual([], violations) # assert violation on wrong type expected_violation = RuleViolation("CT1", "Title does not start with one of fix, feat, chore, docs," - " style, refactor, perf, test, revert, ci, build", u"bår: foo") - violations = rule.validate(u"bår: foo", None) + " style, refactor, perf, test, revert, ci, build", "bår: foo") + violations = rule.validate("bår: foo", None) self.assertListEqual([expected_violation], violations) # assert violation on wrong format expected_violation = RuleViolation("CT1", "Title does not follow ConventionalCommits.org format " - "'type(optional-scope): description'", u"fix föo") - violations = rule.validate(u"fix föo", None) + "'type(optional-scope): description'", "fix föo") + violations = rule.validate("fix föo", None) self.assertListEqual([expected_violation], violations) # assert no violation when adding new type - rule = ConventionalCommit({'types': [u"föo", u"bär"]}) - for typ in [u"föo", u"bär"]: - violations = rule.validate(typ + u": hür dur", None) + rule = ConventionalCommit({'types': ["föo", "bär"]}) + for typ in ["föo", "bär"]: + violations = rule.validate(typ + ": hür dur", None) self.assertListEqual([], violations) # assert violation when using incorrect type when types have been reconfigured - violations = rule.validate(u"fix: hür dur", None) - expected_violation = RuleViolation("CT1", u"Title does not start with one of föo, bär", u"fix: hür dur") + violations = rule.validate("fix: hür dur", None) + expected_violation = RuleViolation("CT1", "Title does not start with one of föo, bär", "fix: hür dur") self.assertListEqual([expected_violation], violations) diff --git a/gitlint/tests/contrib/rules/test_signedoff_by.py b/gitlint/tests/contrib/rules/test_signedoff_by.py index 934aec5..c92f1a6 100644 --- a/gitlint/tests/contrib/rules/test_signedoff_by.py +++ b/gitlint/tests/contrib/rules/test_signedoff_by.py @@ -19,14 +19,14 @@ class ContribSignedOffByTests(BaseTestCase): def test_signedoff_by(self): # No violations when 'Signed-Off-By' line is present rule = SignedOffBy() - violations = rule.validate(self.gitcommit(u"Föobar\n\nMy Body\nSigned-Off-By: John Smith")) + violations = rule.validate(self.gitcommit("Föobar\n\nMy Body\nSigned-Off-By: John Smith")) self.assertListEqual([], violations) # Assert violation when no 'Signed-Off-By' line is present - violations = rule.validate(self.gitcommit(u"Föobar\n\nMy Body")) + violations = rule.validate(self.gitcommit("Föobar\n\nMy Body")) expected_violation = RuleViolation("CC1", "Body does not contain a 'Signed-Off-By' line", line_nr=1) self.assertListEqual(violations, [expected_violation]) # Assert violation when no 'Signed-Off-By' in title but not in body - violations = rule.validate(self.gitcommit(u"Signed-Off-By\n\nFöobar")) + violations = rule.validate(self.gitcommit("Signed-Off-By\n\nFöobar")) self.assertListEqual(violations, [expected_violation]) diff --git a/gitlint/tests/contrib/test_contrib_rules.py b/gitlint/tests/contrib/test_contrib_rules.py index 84db2d5..8ab6539 100644 --- a/gitlint/tests/contrib/test_contrib_rules.py +++ b/gitlint/tests/contrib/test_contrib_rules.py @@ -6,8 +6,6 @@ from gitlint.contrib import rules as contrib_rules from gitlint.tests.contrib import rules as contrib_tests from gitlint import rule_finder, rules -from gitlint.utils import ustr - class ContribRuleTests(BaseTestCase): @@ -24,10 +22,9 @@ class ContribRuleTests(BaseTestCase): # Find all python files in the contrib dir and assert there's a corresponding test file for filename in os.listdir(self.CONTRIB_DIR): if filename.endswith(".py") and filename not in ["__init__.py"]: - expected_test_file = ustr(u"test_" + filename) - error_msg = u"Every Contrib Rule must have associated tests. " + \ - "Expected test file {0} not found.".format(os.path.join(contrib_tests_dir, - expected_test_file)) + expected_test_file = "test_" + filename + error_msg = "Every Contrib Rule must have associated tests. " + \ + f"Expected test file {os.path.join(contrib_tests_dir, expected_test_file)} not found." self.assertIn(expected_test_file, contrib_test_files, error_msg) def test_contrib_rule_naming_conventions(self): diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_1 b/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_1 new file mode 100644 index 0000000..9082830 --- /dev/null +++ b/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_1 @@ -0,0 +1,2 @@ +gitlint: checking commit message... +{git_repo} is not a git repository. diff --git a/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_2 b/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_2 new file mode 100644 index 0000000..bafbf29 --- /dev/null +++ b/gitlint/tests/expected/cli/test_cli_hooks/test_run_hook_negative_2 @@ -0,0 +1,2 @@ +gitlint: checking commit message... +Error: The 'staged' option (--staged) can only be used when using '--msg-filename' or when piping data to gitlint via stdin. diff --git a/gitlint/tests/git/test_git.py b/gitlint/tests/git/test_git.py index 1830119..7b9b7c6 100644 --- a/gitlint/tests/git/test_git.py +++ b/gitlint/tests/git/test_git.py @@ -1,12 +1,7 @@ # -*- coding: utf-8 -*- import os -try: - # python 2.x - from mock import patch -except ImportError: - # python 3.x - from unittest.mock import patch # pylint: disable=no-name-in-module, import-error +from unittest.mock import patch from gitlint.shell import ErrorReturnCode, CommandNotFound @@ -19,7 +14,7 @@ class GitTests(BaseTestCase): # Expected special_args passed to 'sh' expected_sh_special_args = { '_tty_out': False, - '_cwd': u"fåke/path" + '_cwd': "fåke/path" } @patch('gitlint.git.sh') @@ -28,7 +23,7 @@ class GitTests(BaseTestCase): expected_msg = "'git' command not found. You need to install git to use gitlint on a local repository. " + \ "See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git on how to install git." with self.assertRaisesMessage(GitNotInstalledError, expected_msg): - GitContext.from_local_repository(u"fåke/path") + GitContext.from_local_repository("fåke/path") # assert that commit message was read using git command sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args) @@ -39,8 +34,8 @@ class GitTests(BaseTestCase): err = b"fatal: Not a git repository (or any of the parent directories): .git" sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err) - with self.assertRaisesMessage(GitContextError, u"fåke/path is not a git repository."): - GitContext.from_local_repository(u"fåke/path") + with self.assertRaisesMessage(GitContextError, "fåke/path is not a git repository."): + GitContext.from_local_repository("fåke/path") # assert that commit message was read using git command sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args) @@ -49,9 +44,9 @@ class GitTests(BaseTestCase): err = b"fatal: Random git error" sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err) - expected_msg = u"An error occurred while executing 'git log -1 --pretty=%H': {0}".format(err) + expected_msg = f"An error occurred while executing 'git log -1 --pretty=%H': {err}" with self.assertRaisesMessage(GitContextError, expected_msg): - GitContext.from_local_repository(u"fåke/path") + GitContext.from_local_repository("fåke/path") # assert that commit message was read using git command sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args) @@ -63,9 +58,9 @@ class GitTests(BaseTestCase): sh.git.side_effect = ErrorReturnCode("git log -1 --pretty=%H", b"", err) - expected_msg = u"Current branch has no commits. Gitlint requires at least one commit to function." + expected_msg = "Current branch has no commits. Gitlint requires at least one commit to function." with self.assertRaisesMessage(GitContextError, expected_msg): - GitContext.from_local_repository(u"fåke/path") + GitContext.from_local_repository("fåke/path") # assert that commit message was read using git command sh.git.assert_called_once_with("log", "-1", "--pretty=%H", **self.expected_sh_special_args) @@ -78,12 +73,12 @@ class GitTests(BaseTestCase): b"'git <command> [<revision>...] -- [<file>...]'") sh.git.side_effect = [ - u"#\n", # git config --get core.commentchar + "#\n", # git config --get core.commentchar ErrorReturnCode("rev-parse --abbrev-ref HEAD", b"", err) ] with self.assertRaisesMessage(GitContextError, expected_msg): - context = GitContext.from_commit_msg(u"test") + context = GitContext.from_commit_msg("test") context.current_branch # assert that commit message was read using git command @@ -95,21 +90,19 @@ class GitTests(BaseTestCase): self.assertEqual(git_commentchar(), "#") git.return_value.exit_code = 0 - git.return_value.__str__ = lambda _: u"ä" - git.return_value.__unicode__ = lambda _: u"ä" - self.assertEqual(git_commentchar(), u"ä") + git.return_value = "ä" + self.assertEqual(git_commentchar(), "ä") git.return_value = ';\n' - self.assertEqual(git_commentchar(os.path.join(u"/föo", u"bar")), ';') + self.assertEqual(git_commentchar(os.path.join("/föo", "bar")), ';') git.assert_called_with("config", "--get", "core.commentchar", _ok_code=[0, 1], - _cwd=os.path.join(u"/föo", u"bar")) + _cwd=os.path.join("/föo", "bar")) @patch("gitlint.git._git") def test_git_hooks_dir(self, git): - hooks_dir = os.path.join(u"föo", ".git", "hooks") - git.return_value.__str__ = lambda _: hooks_dir + "\n" - git.return_value.__unicode__ = lambda _: hooks_dir + "\n" - self.assertEqual(git_hooks_dir(u"/blä"), os.path.abspath(os.path.join(u"/blä", hooks_dir))) + hooks_dir = os.path.join("föo", ".git", "hooks") + git.return_value = hooks_dir + "\n" + self.assertEqual(git_hooks_dir("/blä"), os.path.abspath(os.path.join("/blä", hooks_dir))) - git.assert_called_once_with("rev-parse", "--git-path", "hooks", _cwd=u"/blä") + git.assert_called_once_with("rev-parse", "--git-path", "hooks", _cwd="/blä") diff --git a/gitlint/tests/git/test_git_commit.py b/gitlint/tests/git/test_git_commit.py index 5f87a8e..6bb545a 100644 --- a/gitlint/tests/git/test_git_commit.py +++ b/gitlint/tests/git/test_git_commit.py @@ -6,17 +6,11 @@ import dateutil import arrow -try: - # python 2.x - from mock import patch, call -except ImportError: - # python 3.x - from unittest.mock import patch, call # pylint: disable=no-name-in-module, import-error +from unittest.mock import patch, call from gitlint.tests.base import BaseTestCase from gitlint.git import GitContext, GitCommit, GitContextError, LocalGitCommit, StagedLocalGitCommit, GitCommitMessage from gitlint.shell import ErrorReturnCode -from gitlint.utils import ustr class GitCommitTests(BaseTestCase): @@ -24,7 +18,7 @@ class GitCommitTests(BaseTestCase): # Expected special_args passed to 'sh' expected_sh_special_args = { '_tty_out': False, - '_cwd': u"fåke/path" + '_cwd': "fåke/path" } @patch('gitlint.git.sh') @@ -33,14 +27,14 @@ class GitCommitTests(BaseTestCase): sh.git.side_effect = [ sample_sha, - u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - u"cömmit-title\n\ncömmit-body", - u"#", # git config --get core.commentchar - u"file1.txt\npåth/to/file2.txt\n", - u"foöbar\n* hürdur\n" + "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "cömmit-title\n\ncömmit-body", + "#", # git config --get core.commentchar + "file1.txt\npåth/to/file2.txt\n", + "foöbar\n* hürdur\n" ] - context = GitContext.from_local_repository(u"fåke/path") + context = GitContext.from_local_repository("fåke/path") # assert that commit info was read using git command expected_calls = [ call("log", "-1", "--pretty=%H", **self.expected_sh_special_args), @@ -57,13 +51,13 @@ class GitCommitTests(BaseTestCase): last_commit = context.commits[-1] self.assertIsInstance(last_commit, LocalGitCommit) self.assertEqual(last_commit.sha, sample_sha) - self.assertEqual(last_commit.message.title, u"cömmit-title") - self.assertEqual(last_commit.message.body, ["", u"cömmit-body"]) - self.assertEqual(last_commit.author_name, u"test åuthor") - self.assertEqual(last_commit.author_email, u"test-emåil@foo.com") + self.assertEqual(last_commit.message.title, "cömmit-title") + self.assertEqual(last_commit.message.body, ["", "cömmit-body"]) + self.assertEqual(last_commit.author_name, "test åuthor") + self.assertEqual(last_commit.author_email, "test-emåil@foo.com") self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600))) - self.assertListEqual(last_commit.parents, [u"åbc"]) + self.assertListEqual(last_commit.parents, ["åbc"]) self.assertFalse(last_commit.is_merge_commit) self.assertFalse(last_commit.is_fixup_commit) self.assertFalse(last_commit.is_squash_commit) @@ -72,11 +66,11 @@ class GitCommitTests(BaseTestCase): # First 2 'git log' calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:3]) - self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"]) + self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) # 'git diff-tree' should have happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:4]) - self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"]) + self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"]) # All expected calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls) @@ -86,14 +80,14 @@ class GitCommitTests(BaseTestCase): sh.git.side_effect = [ sample_sha, - u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - u"cömmit-title\n\ncömmit-body", - u"#", # git config --get core.commentchar - u"file1.txt\npåth/to/file2.txt\n", - u"foöbar\n* hürdur\n" + "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + "cömmit-title\n\ncömmit-body", + "#", # git config --get core.commentchar + "file1.txt\npåth/to/file2.txt\n", + "foöbar\n* hürdur\n" ] - context = GitContext.from_local_repository(u"fåke/path", sample_sha) + context = GitContext.from_local_repository("fåke/path", sample_sha) # assert that commit info was read using git command expected_calls = [ call("rev-list", sample_sha, **self.expected_sh_special_args), @@ -110,13 +104,13 @@ class GitCommitTests(BaseTestCase): last_commit = context.commits[-1] self.assertIsInstance(last_commit, LocalGitCommit) self.assertEqual(last_commit.sha, sample_sha) - self.assertEqual(last_commit.message.title, u"cömmit-title") - self.assertEqual(last_commit.message.body, ["", u"cömmit-body"]) - self.assertEqual(last_commit.author_name, u"test åuthor") - self.assertEqual(last_commit.author_email, u"test-emåil@foo.com") + self.assertEqual(last_commit.message.title, "cömmit-title") + self.assertEqual(last_commit.message.body, ["", "cömmit-body"]) + self.assertEqual(last_commit.author_name, "test åuthor") + self.assertEqual(last_commit.author_email, "test-emåil@foo.com") self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600))) - self.assertListEqual(last_commit.parents, [u"åbc"]) + self.assertListEqual(last_commit.parents, ["åbc"]) self.assertFalse(last_commit.is_merge_commit) self.assertFalse(last_commit.is_fixup_commit) self.assertFalse(last_commit.is_squash_commit) @@ -125,11 +119,11 @@ class GitCommitTests(BaseTestCase): # First 2 'git log' calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:3]) - self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"]) + self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) # 'git diff-tree' should have happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:4]) - self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"]) + self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"]) # All expected calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls) @@ -139,14 +133,14 @@ class GitCommitTests(BaseTestCase): sh.git.side_effect = [ sample_sha, - u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc def\n" - u"Merge \"foo bår commit\"", - u"#", # git config --get core.commentchar - u"file1.txt\npåth/to/file2.txt\n", - u"foöbar\n* hürdur\n" + "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc def\n" + "Merge \"foo bår commit\"", + "#", # git config --get core.commentchar + "file1.txt\npåth/to/file2.txt\n", + "foöbar\n* hürdur\n" ] - context = GitContext.from_local_repository(u"fåke/path") + context = GitContext.from_local_repository("fåke/path") # assert that commit info was read using git command expected_calls = [ call("log", "-1", "--pretty=%H", **self.expected_sh_special_args), @@ -163,13 +157,13 @@ class GitCommitTests(BaseTestCase): last_commit = context.commits[-1] self.assertIsInstance(last_commit, LocalGitCommit) self.assertEqual(last_commit.sha, sample_sha) - self.assertEqual(last_commit.message.title, u"Merge \"foo bår commit\"") + self.assertEqual(last_commit.message.title, "Merge \"foo bår commit\"") self.assertEqual(last_commit.message.body, []) - self.assertEqual(last_commit.author_name, u"test åuthor") - self.assertEqual(last_commit.author_email, u"test-emåil@foo.com") + self.assertEqual(last_commit.author_name, "test åuthor") + self.assertEqual(last_commit.author_email, "test-emåil@foo.com") self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600))) - self.assertListEqual(last_commit.parents, [u"åbc", "def"]) + self.assertListEqual(last_commit.parents, ["åbc", "def"]) self.assertTrue(last_commit.is_merge_commit) self.assertFalse(last_commit.is_fixup_commit) self.assertFalse(last_commit.is_squash_commit) @@ -178,11 +172,11 @@ class GitCommitTests(BaseTestCase): # First 2 'git log' calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:3]) - self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"]) + self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) # 'git diff-tree' should have happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:4]) - self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"]) + self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"]) # All expected calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls) @@ -194,14 +188,14 @@ class GitCommitTests(BaseTestCase): sh.git.side_effect = [ sample_sha, - u"test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" - u"{0}! \"foo bår commit\"".format(commit_type), - u"#", # git config --get core.commentchar - u"file1.txt\npåth/to/file2.txt\n", - u"foöbar\n* hürdur\n" + "test åuthor\x00test-emåil@foo.com\x002016-12-03 15:28:15 +0100\x00åbc\n" + f"{commit_type}! \"foo bår commit\"", + "#", # git config --get core.commentchar + "file1.txt\npåth/to/file2.txt\n", + "foöbar\n* hürdur\n" ] - context = GitContext.from_local_repository(u"fåke/path") + context = GitContext.from_local_repository("fåke/path") # assert that commit info was read using git command expected_calls = [ call("log", "-1", "--pretty=%H", **self.expected_sh_special_args), @@ -218,13 +212,13 @@ class GitCommitTests(BaseTestCase): last_commit = context.commits[-1] self.assertIsInstance(last_commit, LocalGitCommit) self.assertEqual(last_commit.sha, sample_sha) - self.assertEqual(last_commit.message.title, u"{0}! \"foo bår commit\"".format(commit_type)) + self.assertEqual(last_commit.message.title, f"{commit_type}! \"foo bår commit\"") self.assertEqual(last_commit.message.body, []) - self.assertEqual(last_commit.author_name, u"test åuthor") - self.assertEqual(last_commit.author_email, u"test-emåil@foo.com") + self.assertEqual(last_commit.author_name, "test åuthor") + self.assertEqual(last_commit.author_email, "test-emåil@foo.com") self.assertEqual(last_commit.date, datetime.datetime(2016, 12, 3, 15, 28, 15, tzinfo=dateutil.tz.tzoffset("+0100", 3600))) - self.assertListEqual(last_commit.parents, [u"åbc"]) + self.assertListEqual(last_commit.parents, ["åbc"]) # First 2 'git log' calls should've happened at this point self.assertEqual(sh.git.mock_calls, expected_calls[:3]) @@ -236,13 +230,13 @@ class GitCommitTests(BaseTestCase): self.assertFalse(last_commit.is_merge_commit) self.assertFalse(last_commit.is_revert_commit) - self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"]) + self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) - self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"]) + self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) # 'git diff-tree' should have happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls[:4]) - self.assertListEqual(last_commit.branches, [u"foöbar", u"hürdur"]) + self.assertListEqual(last_commit.branches, ["foöbar", "hürdur"]) # All expected calls should've happened at this point self.assertListEqual(sh.git.mock_calls, expected_calls) @@ -250,27 +244,27 @@ class GitCommitTests(BaseTestCase): @patch("gitlint.git.git_commentchar") def test_from_commit_msg_full(self, commentchar): - commentchar.return_value = u"#" + commentchar.return_value = "#" gitcontext = GitContext.from_commit_msg(self.get_sample("commit_message/sample1")) - expected_title = u"Commit title contåining 'WIP', as well as trailing punctuation." + expected_title = "Commit title contåining 'WIP', as well as trailing punctuation." expected_body = ["This line should be empty", "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.", - u"This line has a tråiling space. ", + "This line has a tråiling space. ", "This line has a trailing tab.\t"] expected_full = expected_title + "\n" + "\n".join(expected_body) expected_original = expected_full + ( - u"\n# This is a cömmented line\n" - u"# ------------------------ >8 ------------------------\n" - u"# Anything after this line should be cleaned up\n" - u"# this line appears on `git commit -v` command\n" - u"diff --git a/gitlint/tests/samples/commit_message/sample1 " - u"b/gitlint/tests/samples/commit_message/sample1\n" - u"index 82dbe7f..ae71a14 100644\n" - u"--- a/gitlint/tests/samples/commit_message/sample1\n" - u"+++ b/gitlint/tests/samples/commit_message/sample1\n" - u"@@ -1 +1 @@\n" + "\n# This is a cömmented line\n" + "# ------------------------ >8 ------------------------\n" + "# Anything after this line should be cleaned up\n" + "# this line appears on `git commit -v` command\n" + "diff --git a/gitlint/tests/samples/commit_message/sample1 " + "b/gitlint/tests/samples/commit_message/sample1\n" + "index 82dbe7f..ae71a14 100644\n" + "--- a/gitlint/tests/samples/commit_message/sample1\n" + "+++ b/gitlint/tests/samples/commit_message/sample1\n" + "@@ -1 +1 @@\n" ) commit = gitcontext.commits[-1] @@ -297,10 +291,10 @@ class GitCommitTests(BaseTestCase): self.assertIsInstance(commit, GitCommit) self.assertFalse(isinstance(commit, LocalGitCommit)) - self.assertEqual(commit.message.title, u"Just a title contåining WIP") + self.assertEqual(commit.message.title, "Just a title contåining WIP") self.assertEqual(commit.message.body, []) - self.assertEqual(commit.message.full, u"Just a title contåining WIP") - self.assertEqual(commit.message.original, u"Just a title contåining WIP") + self.assertEqual(commit.message.full, "Just a title contåining WIP") + self.assertEqual(commit.message.original, "Just a title contåining WIP") self.assertEqual(commit.author_name, None) self.assertEqual(commit.author_email, None) self.assertListEqual(commit.parents, []) @@ -334,16 +328,16 @@ class GitCommitTests(BaseTestCase): @patch("gitlint.git.git_commentchar") def test_from_commit_msg_comment(self, commentchar): - commentchar.return_value = u"#" - gitcontext = GitContext.from_commit_msg(u"Tïtle\n\nBödy 1\n#Cömment\nBody 2") + commentchar.return_value = "#" + gitcontext = GitContext.from_commit_msg("Tïtle\n\nBödy 1\n#Cömment\nBody 2") commit = gitcontext.commits[-1] self.assertIsInstance(commit, GitCommit) self.assertFalse(isinstance(commit, LocalGitCommit)) - self.assertEqual(commit.message.title, u"Tïtle") - self.assertEqual(commit.message.body, ["", u"Bödy 1", "Body 2"]) - self.assertEqual(commit.message.full, u"Tïtle\n\nBödy 1\nBody 2") - self.assertEqual(commit.message.original, u"Tïtle\n\nBödy 1\n#Cömment\nBody 2") + self.assertEqual(commit.message.title, "Tïtle") + self.assertEqual(commit.message.body, ["", "Bödy 1", "Body 2"]) + self.assertEqual(commit.message.full, "Tïtle\n\nBödy 1\nBody 2") + self.assertEqual(commit.message.original, "Tïtle\n\nBödy 1\n#Cömment\nBody 2") self.assertEqual(commit.author_name, None) self.assertEqual(commit.author_email, None) self.assertEqual(commit.date, None) @@ -402,7 +396,7 @@ class GitCommitTests(BaseTestCase): def test_from_commit_msg_fixup_squash_commit(self): commit_types = ["fixup", "squash"] for commit_type in commit_types: - commit_msg = "{0}! Test message".format(commit_type) + commit_msg = f"{commit_type}! Test message" gitcontext = GitContext.from_commit_msg(commit_msg) commit = gitcontext.commits[-1] @@ -431,16 +425,16 @@ class GitCommitTests(BaseTestCase): # StagedLocalGitCommit() sh.git.side_effect = [ - u"#", # git config --get core.commentchar - u"test åuthor\n", # git config --get user.name - u"test-emåil@foo.com\n", # git config --get user.email - u"my-brånch\n", # git rev-parse --abbrev-ref HEAD - u"file1.txt\npåth/to/file2.txt\n", + "#", # git config --get core.commentchar + "test åuthor\n", # git config --get user.name + "test-emåil@foo.com\n", # git config --get user.email + "my-brånch\n", # git rev-parse --abbrev-ref HEAD + "file1.txt\npåth/to/file2.txt\n", ] now.side_effect = [arrow.get("2020-02-19T12:18:46.675182+01:00")] # We use a fixup commit, just to test a non-default path - context = GitContext.from_staged_commit(u"fixup! Foōbar 123\n\ncömmit-body\n", u"fåke/path") + context = GitContext.from_staged_commit("fixup! Foōbar 123\n\ncömmit-body\n", "fåke/path") # git calls we're expexting expected_calls = [ @@ -454,15 +448,15 @@ class GitCommitTests(BaseTestCase): last_commit = context.commits[-1] self.assertIsInstance(last_commit, StagedLocalGitCommit) self.assertIsNone(last_commit.sha, None) - self.assertEqual(last_commit.message.title, u"fixup! Foōbar 123") - self.assertEqual(last_commit.message.body, ["", u"cömmit-body"]) + self.assertEqual(last_commit.message.title, "fixup! Foōbar 123") + self.assertEqual(last_commit.message.body, ["", "cömmit-body"]) # Only `git config --get core.commentchar` should've happened up until this point self.assertListEqual(sh.git.mock_calls, expected_calls[0:1]) - self.assertEqual(last_commit.author_name, u"test åuthor") + self.assertEqual(last_commit.author_name, "test åuthor") self.assertListEqual(sh.git.mock_calls, expected_calls[0:2]) - self.assertEqual(last_commit.author_email, u"test-emåil@foo.com") + self.assertEqual(last_commit.author_email, "test-emåil@foo.com") self.assertListEqual(sh.git.mock_calls, expected_calls[0:3]) self.assertEqual(last_commit.date, datetime.datetime(2020, 2, 19, 12, 18, 46, @@ -475,10 +469,10 @@ class GitCommitTests(BaseTestCase): self.assertFalse(last_commit.is_squash_commit) self.assertFalse(last_commit.is_revert_commit) - self.assertListEqual(last_commit.branches, [u"my-brånch"]) + self.assertListEqual(last_commit.branches, ["my-brånch"]) self.assertListEqual(sh.git.mock_calls, expected_calls[0:4]) - self.assertListEqual(last_commit.changed_files, ["file1.txt", u"påth/to/file2.txt"]) + self.assertListEqual(last_commit.changed_files, ["file1.txt", "påth/to/file2.txt"]) self.assertListEqual(sh.git.mock_calls, expected_calls[0:5]) @patch('gitlint.git.sh') @@ -486,32 +480,32 @@ class GitCommitTests(BaseTestCase): # StagedLocalGitCommit() sh.git.side_effect = [ - u"#", # git config --get core.commentchar + "#", # git config --get core.commentchar ErrorReturnCode('git config --get user.name', b"", b""), ] expected_msg = "Missing git configuration: please set user.name" with self.assertRaisesMessage(GitContextError, expected_msg): - ctx = GitContext.from_staged_commit(u"Foōbar 123\n\ncömmit-body\n", u"fåke/path") - [ustr(commit) for commit in ctx.commits] + ctx = GitContext.from_staged_commit("Foōbar 123\n\ncömmit-body\n", "fåke/path") + [str(commit) for commit in ctx.commits] @patch('gitlint.git.sh') def test_staged_commit_with_missing_email(self, sh): # StagedLocalGitCommit() sh.git.side_effect = [ - u"#", # git config --get core.commentchar - u"test åuthor\n", # git config --get user.name + "#", # git config --get core.commentchar + "test åuthor\n", # git config --get user.name ErrorReturnCode('git config --get user.name', b"", b""), ] expected_msg = "Missing git configuration: please set user.email" with self.assertRaisesMessage(GitContextError, expected_msg): - ctx = GitContext.from_staged_commit(u"Foōbar 123\n\ncömmit-body\n", u"fåke/path") - [ustr(commit) for commit in ctx.commits] + ctx = GitContext.from_staged_commit("Foōbar 123\n\ncömmit-body\n", "fåke/path") + [str(commit) for commit in ctx.commits] def test_gitcommitmessage_equality(self): - commit_message1 = GitCommitMessage(GitContext(), u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"]) + commit_message1 = GitCommitMessage(GitContext(), "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"]) attrs = ['original', 'full', 'title', 'body'] self.object_equality_test(commit_message1, attrs, {"context": commit_message1.context}) @@ -519,20 +513,20 @@ class GitCommitTests(BaseTestCase): def test_gitcommit_equality(self, git): # git will be called to setup the context (commentchar and current_branch), just return the same value # This only matters to test gitcontext equality, not gitcommit equality - git.return_value = u"foöbar" + git.return_value = "foöbar" # Test simple equality case now = datetime.datetime.utcnow() context1 = GitContext() - commit_message1 = GitCommitMessage(context1, u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"]) - commit1 = GitCommit(context1, commit_message1, u"shä", now, u"Jöhn Smith", u"jöhn.smith@test.com", None, - [u"föo/bar"], [u"brånch1", u"brånch2"]) + commit_message1 = GitCommitMessage(context1, "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"]) + commit1 = GitCommit(context1, commit_message1, "shä", now, "Jöhn Smith", "jöhn.smith@test.com", None, + ["föo/bar"], ["brånch1", "brånch2"]) context1.commits = [commit1] context2 = GitContext() - commit_message2 = GitCommitMessage(context2, u"tëst\n\nfoo", u"tëst\n\nfoo", u"tēst", ["", u"föo"]) - commit2 = GitCommit(context2, commit_message1, u"shä", now, u"Jöhn Smith", u"jöhn.smith@test.com", None, - [u"föo/bar"], [u"brånch1", u"brånch2"]) + commit_message2 = GitCommitMessage(context2, "tëst\n\nfoo", "tëst\n\nfoo", "tēst", ["", "föo"]) + commit2 = GitCommit(context2, commit_message1, "shä", now, "Jöhn Smith", "jöhn.smith@test.com", None, + ["föo/bar"], ["brånch1", "brånch2"]) context2.commits = [commit2] self.assertEqual(context1, context2) @@ -547,8 +541,8 @@ class GitCommitTests(BaseTestCase): self.object_equality_test(commit1, kwargs.keys(), {"context": commit1.context}) # Check that the is_* attributes that are affected by the commit message affect equality - special_messages = {'is_merge_commit': u"Merge: foöbar", 'is_fixup_commit': u"fixup! foöbar", - 'is_squash_commit': u"squash! foöbar", 'is_revert_commit': u"Revert: foöbar"} + special_messages = {'is_merge_commit': "Merge: foöbar", 'is_fixup_commit': "fixup! foöbar", + 'is_squash_commit': "squash! foöbar", 'is_revert_commit': "Revert: foöbar"} for key in special_messages: kwargs_copy = copy.deepcopy(kwargs) clone1 = GitCommit(context=commit1.context, **kwargs_copy) @@ -556,16 +550,16 @@ class GitCommitTests(BaseTestCase): self.assertTrue(getattr(clone1, key)) clone2 = GitCommit(context=commit1.context, **kwargs_copy) - clone2.message = GitCommitMessage.from_full_message(context1, u"foöbar") + clone2.message = GitCommitMessage.from_full_message(context1, "foöbar") self.assertNotEqual(clone1, clone2) @patch("gitlint.git.git_commentchar") def test_commit_msg_custom_commentchar(self, patched): - patched.return_value = u"ä" + patched.return_value = "ä" context = GitContext() - message = GitCommitMessage.from_full_message(context, u"Tïtle\n\nBödy 1\näCömment\nBody 2") + message = GitCommitMessage.from_full_message(context, "Tïtle\n\nBödy 1\näCömment\nBody 2") - self.assertEqual(message.title, u"Tïtle") - self.assertEqual(message.body, ["", u"Bödy 1", "Body 2"]) - self.assertEqual(message.full, u"Tïtle\n\nBödy 1\nBody 2") - self.assertEqual(message.original, u"Tïtle\n\nBödy 1\näCömment\nBody 2") + self.assertEqual(message.title, "Tïtle") + self.assertEqual(message.body, ["", "Bödy 1", "Body 2"]) + self.assertEqual(message.full, "Tïtle\n\nBödy 1\nBody 2") + self.assertEqual(message.original, "Tïtle\n\nBödy 1\näCömment\nBody 2") diff --git a/gitlint/tests/git/test_git_context.py b/gitlint/tests/git/test_git_context.py index b243d5e..bb05236 100644 --- a/gitlint/tests/git/test_git_context.py +++ b/gitlint/tests/git/test_git_context.py @@ -1,11 +1,6 @@ # -*- coding: utf-8 -*- -try: - # python 2.x - from mock import patch, call -except ImportError: - # python 3.x - from unittest.mock import patch, call # pylint: disable=no-name-in-module, import-error +from unittest.mock import patch, call from gitlint.tests.base import BaseTestCase from gitlint.git import GitContext @@ -16,15 +11,15 @@ class GitContextTests(BaseTestCase): # Expected special_args passed to 'sh' expected_sh_special_args = { '_tty_out': False, - '_cwd': u"fåke/path" + '_cwd': "fåke/path" } @patch('gitlint.git.sh') def test_gitcontext(self, sh): sh.git.side_effect = [ - u"#", # git config --get core.commentchar - u"\nfoöbar\n" + "#", # git config --get core.commentchar + "\nfoöbar\n" ] expected_calls = [ @@ -32,58 +27,58 @@ class GitContextTests(BaseTestCase): call("rev-parse", "--abbrev-ref", "HEAD", **self.expected_sh_special_args) ] - context = GitContext(u"fåke/path") + context = GitContext("fåke/path") self.assertEqual(sh.git.mock_calls, []) # gitcontext.comment_branch - self.assertEqual(context.commentchar, u"#") + self.assertEqual(context.commentchar, "#") self.assertEqual(sh.git.mock_calls, expected_calls[0:1]) # gitcontext.current_branch - self.assertEqual(context.current_branch, u"foöbar") + self.assertEqual(context.current_branch, "foöbar") self.assertEqual(sh.git.mock_calls, expected_calls) @patch('gitlint.git.sh') def test_gitcontext_equality(self, sh): sh.git.side_effect = [ - u"û\n", # context1: git config --get core.commentchar - u"û\n", # context2: git config --get core.commentchar - u"my-brånch\n", # context1: git rev-parse --abbrev-ref HEAD - u"my-brånch\n", # context2: git rev-parse --abbrev-ref HEAD + "û\n", # context1: git config --get core.commentchar + "û\n", # context2: git config --get core.commentchar + "my-brånch\n", # context1: git rev-parse --abbrev-ref HEAD + "my-brånch\n", # context2: git rev-parse --abbrev-ref HEAD ] - context1 = GitContext(u"fåke/path") - context1.commits = [u"fōo", u"bår"] # we don't need real commits to check for equality + context1 = GitContext("fåke/path") + context1.commits = ["fōo", "bår"] # we don't need real commits to check for equality - context2 = GitContext(u"fåke/path") - context2.commits = [u"fōo", u"bår"] + context2 = GitContext("fåke/path") + context2.commits = ["fōo", "bår"] self.assertEqual(context1, context2) # INEQUALITY # Different commits - context2.commits = [u"hür", u"dür"] + context2.commits = ["hür", "dür"] self.assertNotEqual(context1, context2) # Different repository_path context2.commits = context1.commits - context2.repository_path = u"ōther/path" + context2.repository_path = "ōther/path" self.assertNotEqual(context1, context2) # Different comment_char - context3 = GitContext(u"fåke/path") - context3.commits = [u"fōo", u"bår"] + context3 = GitContext("fåke/path") + context3.commits = ["fōo", "bår"] sh.git.side_effect = ([ - u"ç\n", # context3: git config --get core.commentchar - u"my-brånch\n" # context3: git rev-parse --abbrev-ref HEAD + "ç\n", # context3: git config --get core.commentchar + "my-brånch\n" # context3: git rev-parse --abbrev-ref HEAD ]) self.assertNotEqual(context1, context3) # Different current_branch - context4 = GitContext(u"fåke/path") - context4.commits = [u"fōo", u"bår"] + context4 = GitContext("fåke/path") + context4.commits = ["fōo", "bår"] sh.git.side_effect = ([ - u"û\n", # context4: git config --get core.commentchar - u"different-brånch\n" # context4: git rev-parse --abbrev-ref HEAD + "û\n", # context4: git config --get core.commentchar + "different-brånch\n" # context4: git rev-parse --abbrev-ref HEAD ]) self.assertNotEqual(context1, context4) diff --git a/gitlint/tests/rules/test_body_rules.py b/gitlint/tests/rules/test_body_rules.py index f46760b..96ae998 100644 --- a/gitlint/tests/rules/test_body_rules.py +++ b/gitlint/tests/rules/test_body_rules.py @@ -8,65 +8,65 @@ class BodyRuleTests(BaseTestCase): rule = rules.BodyMaxLineLength() # assert no error - violation = rule.validate(u"å" * 80, None) + violation = rule.validate("å" * 80, None) self.assertIsNone(violation) # assert error on line length > 80 - expected_violation = rules.RuleViolation("B1", "Line exceeds max length (81>80)", u"å" * 81) - violations = rule.validate(u"å" * 81, None) + expected_violation = rules.RuleViolation("B1", "Line exceeds max length (81>80)", "å" * 81) + violations = rule.validate("å" * 81, None) self.assertListEqual(violations, [expected_violation]) # set line length to 120, and check no violation on length 73 rule = rules.BodyMaxLineLength({'line-length': 120}) - violations = rule.validate(u"å" * 73, None) + violations = rule.validate("å" * 73, None) self.assertIsNone(violations) # assert raise on 121 - expected_violation = rules.RuleViolation("B1", "Line exceeds max length (121>120)", u"å" * 121) - violations = rule.validate(u"å" * 121, None) + expected_violation = rules.RuleViolation("B1", "Line exceeds max length (121>120)", "å" * 121) + violations = rule.validate("å" * 121, None) self.assertListEqual(violations, [expected_violation]) def test_trailing_whitespace(self): rule = rules.BodyTrailingWhitespace() # assert no error - violations = rule.validate(u"å", None) + violations = rule.validate("å", None) self.assertIsNone(violations) # trailing space - expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", u"å ") - violations = rule.validate(u"å ", None) + expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", "å ") + violations = rule.validate("å ", None) self.assertListEqual(violations, [expected_violation]) # trailing tab - expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", u"å\t") - violations = rule.validate(u"å\t", None) + expected_violation = rules.RuleViolation("B2", "Line has trailing whitespace", "å\t") + violations = rule.validate("å\t", None) self.assertListEqual(violations, [expected_violation]) def test_hard_tabs(self): rule = rules.BodyHardTab() # assert no error - violations = rule.validate(u"This is ã test", None) + violations = rule.validate("This is ã test", None) self.assertIsNone(violations) # contains hard tab - expected_violation = rules.RuleViolation("B3", "Line contains hard tab characters (\\t)", u"This is å\ttest") - violations = rule.validate(u"This is å\ttest", None) + expected_violation = rules.RuleViolation("B3", "Line contains hard tab characters (\\t)", "This is å\ttest") + violations = rule.validate("This is å\ttest", None) self.assertListEqual(violations, [expected_violation]) def test_body_first_line_empty(self): rule = rules.BodyFirstLineEmpty() # assert no error - commit = self.gitcommit(u"Tïtle\n\nThis is the secōnd body line") + commit = self.gitcommit("Tïtle\n\nThis is the secōnd body line") violations = rule.validate(commit) self.assertIsNone(violations) # second line not empty - expected_violation = rules.RuleViolation("B4", "Second line is not empty", u"nöt empty", 2) + expected_violation = rules.RuleViolation("B4", "Second line is not empty", "nöt empty", 2) - commit = self.gitcommit(u"Tïtle\nnöt empty\nThis is the secönd body line") + commit = self.gitcommit("Tïtle\nnöt empty\nThis is the secönd body line") violations = rule.validate(commit) self.assertListEqual(violations, [expected_violation]) @@ -80,34 +80,34 @@ class BodyRuleTests(BaseTestCase): self.assertIsNone(violations) # assert no error - no body - commit = self.gitcommit(u"Tïtle\n") + commit = self.gitcommit("Tïtle\n") violations = rule.validate(commit) self.assertIsNone(violations) # body is too short - expected_violation = rules.RuleViolation("B5", "Body message is too short (8<20)", u"töoshort", 3) + expected_violation = rules.RuleViolation("B5", "Body message is too short (8<20)", "töoshort", 3) - commit = self.gitcommit(u"Tïtle\n\ntöoshort\n") + commit = self.gitcommit("Tïtle\n\ntöoshort\n") violations = rule.validate(commit) self.assertListEqual(violations, [expected_violation]) # assert error - short across multiple lines - expected_violation = rules.RuleViolation("B5", "Body message is too short (11<20)", u"secöndthïrd", 3) - commit = self.gitcommit(u"Tïtle\n\nsecönd\nthïrd\n") + expected_violation = rules.RuleViolation("B5", "Body message is too short (11<20)", "secöndthïrd", 3) + commit = self.gitcommit("Tïtle\n\nsecönd\nthïrd\n") violations = rule.validate(commit) self.assertListEqual(violations, [expected_violation]) # set line length to 120, and check violation on length 21 - expected_violation = rules.RuleViolation("B5", "Body message is too short (21<120)", u"å" * 21, 3) + expected_violation = rules.RuleViolation("B5", "Body message is too short (21<120)", "å" * 21, 3) rule = rules.BodyMinLength({'min-length': 120}) - commit = self.gitcommit(u"Title\n\n%s\n" % (u"å" * 21)) + commit = self.gitcommit("Title\n\n%s\n" % ("å" * 21)) violations = rule.validate(commit) self.assertListEqual(violations, [expected_violation]) # Make sure we don't get the error if the body-length is exactly the min-length rule = rules.BodyMinLength({'min-length': 8}) - commit = self.gitcommit(u"Tïtle\n\n%s\n" % (u"å" * 8)) + commit = self.gitcommit("Tïtle\n\n%s\n" % ("å" * 8)) violations = rule.validate(commit) self.assertIsNone(violations) @@ -115,14 +115,14 @@ class BodyRuleTests(BaseTestCase): rule = rules.BodyMissing() # assert no error - body is present - commit = self.gitcommit(u"Tïtle\n\nThis ïs the first body line\n") + commit = self.gitcommit("Tïtle\n\nThis ïs the first body line\n") violations = rule.validate(commit) self.assertIsNone(violations) # body is too short expected_violation = rules.RuleViolation("B6", "Body message is missing", None, 3) - commit = self.gitcommit(u"Tïtle\n") + commit = self.gitcommit("Tïtle\n") violations = rule.validate(commit) self.assertListEqual(violations, [expected_violation]) @@ -130,7 +130,7 @@ class BodyRuleTests(BaseTestCase): rule = rules.BodyMissing() # assert no error - merge commit - commit = self.gitcommit(u"Merge: Tïtle\n") + commit = self.gitcommit("Merge: Tïtle\n") violations = rule.validate(commit) self.assertIsNone(violations) @@ -144,37 +144,37 @@ class BodyRuleTests(BaseTestCase): rule = rules.BodyChangedFileMention() # assert no error when no files have changed and no files need to be mentioned - commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py") + commit = self.gitcommit("This is a test\n\nHere is a mention of föo/test.py") violations = rule.validate(commit) self.assertIsNone(violations) # assert no error when no files have changed but certain files need to be mentioned on change - rule = rules.BodyChangedFileMention({'files': u"bar.txt,föo/test.py"}) - commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py") + rule = rules.BodyChangedFileMention({'files': "bar.txt,föo/test.py"}) + commit = self.gitcommit("This is a test\n\nHere is a mention of föo/test.py") violations = rule.validate(commit) self.assertIsNone(violations) # assert no error if a file has changed and is mentioned - commit = self.gitcommit(u"This is a test\n\nHere is a mention of föo/test.py", [u"föo/test.py"]) + commit = self.gitcommit("This is a test\n\nHere is a mention of föo/test.py", ["föo/test.py"]) violations = rule.validate(commit) self.assertIsNone(violations) # assert no error if multiple files have changed and are mentioned - commit_msg = u"This is a test\n\nHere is a mention of föo/test.py\nAnd here is a mention of bar.txt" - commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"]) + commit_msg = "This is a test\n\nHere is a mention of föo/test.py\nAnd here is a mention of bar.txt" + commit = self.gitcommit(commit_msg, ["föo/test.py", "bar.txt"]) violations = rule.validate(commit) self.assertIsNone(violations) # assert error if file has changed and is not mentioned - commit_msg = u"This is a test\n\nHere is å mention of\nAnd here is a mention of bar.txt" - commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"]) + commit_msg = "This is a test\n\nHere is å mention of\nAnd here is a mention of bar.txt" + commit = self.gitcommit(commit_msg, ["föo/test.py", "bar.txt"]) violations = rule.validate(commit) - expected_violation = rules.RuleViolation("B7", u"Body does not mention changed file 'föo/test.py'", None, 4) + expected_violation = rules.RuleViolation("B7", "Body does not mention changed file 'föo/test.py'", None, 4) self.assertEqual([expected_violation], violations) # assert multiple errors if multiple files habe changed and are not mentioned - commit_msg = u"This is å test\n\nHere is a mention of\nAnd here is a mention of" - commit = self.gitcommit(commit_msg, [u"föo/test.py", "bar.txt"]) + commit_msg = "This is å test\n\nHere is a mention of\nAnd here is a mention of" + commit = self.gitcommit(commit_msg, ["föo/test.py", "bar.txt"]) violations = rule.validate(commit) expected_violation_2 = rules.RuleViolation("B7", "Body does not mention changed file 'bar.txt'", None, 4) self.assertEqual([expected_violation_2, expected_violation], violations) @@ -182,7 +182,7 @@ class BodyRuleTests(BaseTestCase): def test_body_match_regex(self): # We intentionally add 2 newlines at the end of our commit message as that's how git will pass the # message. This way we also test that the rule strips off the last line. - commit = self.gitcommit(u"US1234: åbc\nIgnored\nBödy\nFöo\nMy-Commit-Tag: föo\n\n") + commit = self.gitcommit("US1234: åbc\nIgnored\nBödy\nFöo\nMy-Commit-Tag: föo\n\n") # assert no violation on default regex (=everything allowed) rule = rules.BodyRegexMatches() @@ -191,25 +191,25 @@ class BodyRuleTests(BaseTestCase): # assert no violation on matching regex # (also note that first body line - in between title and rest of body - is ignored) - rule = rules.BodyRegexMatches({'regex': u"^Bödy(.*)"}) + rule = rules.BodyRegexMatches({'regex': "^Bödy(.*)"}) violations = rule.validate(commit) self.assertIsNone(violations) # assert we can do end matching (and last empty line is ignored) # (also note that first body line - in between title and rest of body - is ignored) - rule = rules.BodyRegexMatches({'regex': u"My-Commit-Tag: föo$"}) + rule = rules.BodyRegexMatches({'regex': "My-Commit-Tag: föo$"}) violations = rule.validate(commit) self.assertIsNone(violations) # common use-case: matching that a given line is present - rule = rules.BodyRegexMatches({'regex': u"(.*)Föo(.*)"}) + rule = rules.BodyRegexMatches({'regex': "(.*)Föo(.*)"}) violations = rule.validate(commit) self.assertIsNone(violations) # assert violation on non-matching body - rule = rules.BodyRegexMatches({'regex': u"^Tëst(.*)Foo"}) + rule = rules.BodyRegexMatches({'regex': "^Tëst(.*)Foo"}) violations = rule.validate(commit) - expected_violation = rules.RuleViolation("B8", u"Body does not match regex (^Tëst(.*)Foo)", None, 6) + expected_violation = rules.RuleViolation("B8", "Body does not match regex (^Tëst(.*)Foo)", None, 6) self.assertListEqual(violations, [expected_violation]) # assert no violation on None regex @@ -218,7 +218,7 @@ class BodyRuleTests(BaseTestCase): self.assertIsNone(violations) # Assert no issues when there's no body or a weird body variation - bodies = [u"åbc", u"åbc\n", u"åbc\nföo\n", u"åbc\n\n", u"åbc\nföo\nblå", u"åbc\nföo\nblå\n"] + bodies = ["åbc", "åbc\n", "åbc\nföo\n", "åbc\n\n", "åbc\nföo\nblå", "åbc\nföo\nblå\n"] for body in bodies: commit = self.gitcommit(body) rule = rules.BodyRegexMatches({'regex': ".*"}) diff --git a/gitlint/tests/rules/test_configuration_rules.py b/gitlint/tests/rules/test_configuration_rules.py index 121cb3a..479d9c2 100644 --- a/gitlint/tests/rules/test_configuration_rules.py +++ b/gitlint/tests/rules/test_configuration_rules.py @@ -6,7 +6,7 @@ from gitlint.config import LintConfig class ConfigurationRuleTests(BaseTestCase): def test_ignore_by_title(self): - commit = self.gitcommit(u"Releäse\n\nThis is the secōnd body line") + commit = self.gitcommit("Releäse\n\nThis is the secōnd body line") # No regex specified -> Config shouldn't be changed rule = rules.IgnoreByTitle() @@ -16,29 +16,29 @@ class ConfigurationRuleTests(BaseTestCase): self.assert_logged([]) # nothing logged -> nothing ignored # Matching regex -> expect config to ignore all rules - rule = rules.IgnoreByTitle({"regex": u"^Releäse(.*)"}) + 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 = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \ - u"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: all" + 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": u"^Releäse(.*)", + 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 = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I1': " + \ - u"Commit title 'Releäse' matches the regex '^Releäse(.*)', ignoring rules: T1,B2" + 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_ignore_by_body(self): - commit = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line") + 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() @@ -48,32 +48,32 @@ class ConfigurationRuleTests(BaseTestCase): self.assert_logged([]) # nothing logged -> nothing ignored # Matching regex -> expect config to ignore all rules - rule = rules.IgnoreByBody({"regex": u"(.*)relëase(.*)"}) + 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 = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \ - u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)'," + \ - u" ignoring rules: all" + 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": u"(.*)relëase(.*)", + 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 = u"DEBUG: gitlint.rules Ignoring commit because of rule 'I2': " + \ - u"Commit message line ' a relëase body' matches the regex '(.*)relëase(.*)', ignoring rules: T1,B2" + 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_ignore_body_lines(self): - commit1 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line") - commit2 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line") + commit1 = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line") + commit2 = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line") # no regex specified, nothing should have happened: # commit and config should remain identical, log should be empty @@ -85,22 +85,22 @@ class ConfigurationRuleTests(BaseTestCase): self.assert_logged([]) # Matching regex - rule = rules.IgnoreBodyLines({"regex": u"(.*)relëase(.*)"}) + rule = rules.IgnoreBodyLines({"regex": "(.*)relëase(.*)"}) config = LintConfig() rule.apply(config, commit1) # Our modified commit should be identical to a commit that doesn't contain the specific line - expected_commit = self.gitcommit(u"Tïtle\n\nThis is\n line") + expected_commit = self.gitcommit("Tïtle\n\nThis is\n line") # The original message isn't touched by this rule, this way we always have a way to reference back to it, # so assert it's not modified by setting it to the same as commit1 expected_commit.message.original = commit1.message.original self.assertEqual(commit1, expected_commit) self.assertEqual(config, LintConfig()) # config shouldn't have been modified - self.assert_log_contains(u"DEBUG: gitlint.rules Ignoring line ' a relëase body' because it " + - u"matches '(.*)relëase(.*)'") + self.assert_log_contains("DEBUG: gitlint.rules Ignoring line ' a relëase body' because it " + + "matches '(.*)relëase(.*)'") # Non-Matching regex: no changes expected - commit1 = self.gitcommit(u"Tïtle\n\nThis is\n a relëase body\n line") - rule = rules.IgnoreBodyLines({"regex": u"(.*)föobar(.*)"}) + commit1 = self.gitcommit("Tïtle\n\nThis is\n a relëase body\n line") + rule = rules.IgnoreBodyLines({"regex": "(.*)föobar(.*)"}) config = LintConfig() rule.apply(config, commit1) self.assertEqual(commit1, commit2) diff --git a/gitlint/tests/rules/test_meta_rules.py b/gitlint/tests/rules/test_meta_rules.py index 987aa88..568ca3f 100644 --- a/gitlint/tests/rules/test_meta_rules.py +++ b/gitlint/tests/rules/test_meta_rules.py @@ -8,25 +8,25 @@ class MetaRuleTests(BaseTestCase): rule = AuthorValidEmail() # valid email addresses - valid_email_addresses = [u"föo@bar.com", u"Jöhn.Doe@bar.com", u"jöhn+doe@bar.com", u"jöhn/doe@bar.com", - u"jöhn.doe@subdomain.bar.com"] + valid_email_addresses = ["föo@bar.com", "Jöhn.Doe@bar.com", "jöhn+doe@bar.com", "jöhn/doe@bar.com", + "jöhn.doe@subdomain.bar.com"] for email in valid_email_addresses: - commit = self.gitcommit(u"", author_email=email) + commit = self.gitcommit("", author_email=email) violations = rule.validate(commit) self.assertIsNone(violations) # No email address (=allowed for now, as gitlint also lints messages passed via stdin that don't have an # email address) - commit = self.gitcommit(u"") + commit = self.gitcommit("") violations = rule.validate(commit) self.assertIsNone(violations) # Invalid email addresses: no TLD, no domain, no @, space anywhere (=valid but not allowed by gitlint) - invalid_email_addresses = [u"föo@bar", u"JöhnDoe", u"Jöhn Doe", u"Jöhn Doe@foo.com", u" JöhnDoe@foo.com", - u"JöhnDoe@ foo.com", u"JöhnDoe@foo. com", u"JöhnDoe@foo. com", u"@bår.com", - u"föo@.com"] + invalid_email_addresses = ["föo@bar", "JöhnDoe", "Jöhn Doe", "Jöhn Doe@foo.com", " JöhnDoe@foo.com", + "JöhnDoe@ foo.com", "JöhnDoe@foo. com", "JöhnDoe@foo. com", "@bår.com", + "föo@.com"] for email in invalid_email_addresses: - commit = self.gitcommit(u"", author_email=email) + commit = self.gitcommit("", author_email=email) violations = rule.validate(commit) self.assertListEqual(violations, [RuleViolation("M1", "Author email for commit is invalid", email)]) @@ -35,25 +35,25 @@ class MetaRuleTests(BaseTestCase): # regex=None -> the rule isn't applied rule = AuthorValidEmail() rule.options['regex'].set(None) - emailadresses = [u"föo", None, u"hür dür"] + emailadresses = ["föo", None, "hür dür"] for email in emailadresses: - commit = self.gitcommit(u"", author_email=email) + commit = self.gitcommit("", author_email=email) violations = rule.validate(commit) self.assertIsNone(violations) # Custom domain - rule = AuthorValidEmail({'regex': u"[^@]+@bår.com"}) + rule = AuthorValidEmail({'regex': "[^@]+@bår.com"}) valid_email_addresses = [ - u"föo@bår.com", u"Jöhn.Doe@bår.com", u"jöhn+doe@bår.com", u"jöhn/doe@bår.com"] + "föo@bår.com", "Jöhn.Doe@bår.com", "jöhn+doe@bår.com", "jöhn/doe@bår.com"] for email in valid_email_addresses: - commit = self.gitcommit(u"", author_email=email) + commit = self.gitcommit("", author_email=email) violations = rule.validate(commit) self.assertIsNone(violations) # Invalid email addresses - invalid_email_addresses = [u"föo@hur.com"] + invalid_email_addresses = ["föo@hur.com"] for email in invalid_email_addresses: - commit = self.gitcommit(u"", author_email=email) + commit = self.gitcommit("", author_email=email) violations = rule.validate(commit) self.assertListEqual(violations, [RuleViolation("M1", "Author email for commit is invalid", email)]) diff --git a/gitlint/tests/rules/test_rules.py b/gitlint/tests/rules/test_rules.py index 58ee1c3..6fcf9bc 100644 --- a/gitlint/tests/rules/test_rules.py +++ b/gitlint/tests/rules/test_rules.py @@ -10,14 +10,14 @@ class RuleTests(BaseTestCase): # Ensure rules are not equal if they differ on their attributes for attr in ["id", "name", "target", "options"]: rule = Rule() - setattr(rule, attr, u"åbc") + setattr(rule, attr, "åbc") self.assertNotEqual(Rule(), rule) def test_rule_log(self): rule = Rule() - rule.log.debug(u"Tēst message") - self.assert_log_contains(u"DEBUG: gitlint.rules Tēst message") + rule.log.debug("Tēst message") + self.assert_log_contains("DEBUG: gitlint.rules Tēst message") def test_rule_violation_equality(self): - violation1 = RuleViolation(u"ïd1", u"My messåge", u"My cöntent", 1) + violation1 = RuleViolation("ïd1", "My messåge", "My cöntent", 1) self.object_equality_test(violation1, ["rule_id", "message", "content", "line_nr"]) diff --git a/gitlint/tests/rules/test_title_rules.py b/gitlint/tests/rules/test_title_rules.py index 049735e..e1be857 100644 --- a/gitlint/tests/rules/test_title_rules.py +++ b/gitlint/tests/rules/test_title_rules.py @@ -9,66 +9,66 @@ class TitleRuleTests(BaseTestCase): rule = TitleMaxLength() # assert no error - violation = rule.validate(u"å" * 72, None) + violation = rule.validate("å" * 72, None) self.assertIsNone(violation) # assert error on line length > 72 - expected_violation = RuleViolation("T1", "Title exceeds max length (73>72)", u"å" * 73) - violations = rule.validate(u"å" * 73, None) + expected_violation = RuleViolation("T1", "Title exceeds max length (73>72)", "å" * 73) + violations = rule.validate("å" * 73, None) self.assertListEqual(violations, [expected_violation]) # set line length to 120, and check no violation on length 73 rule = TitleMaxLength({'line-length': 120}) - violations = rule.validate(u"å" * 73, None) + violations = rule.validate("å" * 73, None) self.assertIsNone(violations) # assert raise on 121 - expected_violation = RuleViolation("T1", "Title exceeds max length (121>120)", u"å" * 121) - violations = rule.validate(u"å" * 121, None) + expected_violation = RuleViolation("T1", "Title exceeds max length (121>120)", "å" * 121) + violations = rule.validate("å" * 121, None) self.assertListEqual(violations, [expected_violation]) def test_trailing_whitespace(self): rule = TitleTrailingWhitespace() # assert no error - violations = rule.validate(u"å", None) + violations = rule.validate("å", None) self.assertIsNone(violations) # trailing space - expected_violation = RuleViolation("T2", "Title has trailing whitespace", u"å ") - violations = rule.validate(u"å ", None) + expected_violation = RuleViolation("T2", "Title has trailing whitespace", "å ") + violations = rule.validate("å ", None) self.assertListEqual(violations, [expected_violation]) # trailing tab - expected_violation = RuleViolation("T2", "Title has trailing whitespace", u"å\t") - violations = rule.validate(u"å\t", None) + expected_violation = RuleViolation("T2", "Title has trailing whitespace", "å\t") + violations = rule.validate("å\t", None) self.assertListEqual(violations, [expected_violation]) def test_hard_tabs(self): rule = TitleHardTab() # assert no error - violations = rule.validate(u"This is å test", None) + violations = rule.validate("This is å test", None) self.assertIsNone(violations) # contains hard tab - expected_violation = RuleViolation("T4", "Title contains hard tab characters (\\t)", u"This is å\ttest") - violations = rule.validate(u"This is å\ttest", None) + expected_violation = RuleViolation("T4", "Title contains hard tab characters (\\t)", "This is å\ttest") + violations = rule.validate("This is å\ttest", None) self.assertListEqual(violations, [expected_violation]) def test_trailing_punctuation(self): rule = TitleTrailingPunctuation() # assert no error - violations = rule.validate(u"This is å test", None) + violations = rule.validate("This is å test", None) self.assertIsNone(violations) # assert errors for different punctuations - punctuation = u"?:!.,;" + punctuation = "?:!.,;" for char in punctuation: - line = u"This is å test" + char # note that make sure to include some unicode! + line = "This is å test" + char # note that make sure to include some unicode! gitcontext = self.gitcontext(line) - expected_violation = RuleViolation("T3", u"Title has trailing punctuation ({0})".format(char), line) + expected_violation = RuleViolation("T3", f"Title has trailing punctuation ({char})", line) violations = rule.validate(line, gitcontext) self.assertListEqual(violations, [expected_violation]) @@ -76,40 +76,40 @@ class TitleRuleTests(BaseTestCase): rule = TitleMustNotContainWord() # no violations - violations = rule.validate(u"This is å test", None) + violations = rule.validate("This is å test", None) self.assertIsNone(violations) # no violation if WIP occurs inside a wor - violations = rule.validate(u"This is å wiping test", None) + violations = rule.validate("This is å wiping test", None) self.assertIsNone(violations) # match literally - violations = rule.validate(u"WIP This is å test", None) + violations = rule.validate("WIP This is å test", None) expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", - u"WIP This is å test") + "WIP This is å test") self.assertListEqual(violations, [expected_violation]) # match case insensitive - violations = rule.validate(u"wip This is å test", None) + violations = rule.validate("wip This is å test", None) expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", - u"wip This is å test") + "wip This is å test") self.assertListEqual(violations, [expected_violation]) # match if there is a colon after the word - violations = rule.validate(u"WIP:This is å test", None) + violations = rule.validate("WIP:This is å test", None) expected_violation = RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", - u"WIP:This is å test") + "WIP:This is å test") self.assertListEqual(violations, [expected_violation]) # match multiple words - rule = TitleMustNotContainWord({'words': u"wip,test,å"}) - violations = rule.validate(u"WIP:This is å test", None) + rule = TitleMustNotContainWord({'words': "wip,test,å"}) + violations = rule.validate("WIP:This is å test", None) expected_violation = RuleViolation("T5", "Title contains the word 'wip' (case-insensitive)", - u"WIP:This is å test") + "WIP:This is å test") expected_violation2 = RuleViolation("T5", "Title contains the word 'test' (case-insensitive)", - u"WIP:This is å test") - expected_violation3 = RuleViolation("T5", u"Title contains the word 'å' (case-insensitive)", - u"WIP:This is å test") + "WIP:This is å test") + expected_violation3 = RuleViolation("T5", "Title contains the word 'å' (case-insensitive)", + "WIP:This is å test") self.assertListEqual(violations, [expected_violation, expected_violation2, expected_violation3]) def test_leading_whitespace(self): @@ -130,12 +130,12 @@ class TitleRuleTests(BaseTestCase): self.assertListEqual(violations, [expected_violation]) # unicode test - expected_violation = RuleViolation("T6", "Title has leading whitespace", u" ☺") - violations = rule.validate(u" ☺", None) + expected_violation = RuleViolation("T6", "Title has leading whitespace", " ☺") + violations = rule.validate(" ☺", None) self.assertListEqual(violations, [expected_violation]) def test_regex_matches(self): - commit = self.gitcommit(u"US1234: åbc\n") + commit = self.gitcommit("US1234: åbc\n") # assert no violation on default regex (=everything allowed) rule = TitleRegexMatches() @@ -143,41 +143,41 @@ class TitleRuleTests(BaseTestCase): self.assertIsNone(violations) # assert no violation on matching regex - rule = TitleRegexMatches({'regex': u"^US[0-9]*: å"}) + rule = TitleRegexMatches({'regex': "^US[0-9]*: å"}) violations = rule.validate(commit.message.title, commit) self.assertIsNone(violations) # assert violation when no matching regex - rule = TitleRegexMatches({'regex': u"^UÅ[0-9]*"}) + rule = TitleRegexMatches({'regex': "^UÅ[0-9]*"}) violations = rule.validate(commit.message.title, commit) - expected_violation = RuleViolation("T7", u"Title does not match regex (^UÅ[0-9]*)", u"US1234: åbc") + expected_violation = RuleViolation("T7", "Title does not match regex (^UÅ[0-9]*)", "US1234: åbc") self.assertListEqual(violations, [expected_violation]) def test_min_line_length(self): rule = TitleMinLength() # assert no error - violation = rule.validate(u"å" * 72, None) + violation = rule.validate("å" * 72, None) self.assertIsNone(violation) # assert error on line length < 5 - expected_violation = RuleViolation("T8", "Title is too short (4<5)", u"å" * 4, 1) - violations = rule.validate(u"å" * 4, None) + expected_violation = RuleViolation("T8", "Title is too short (4<5)", "å" * 4, 1) + violations = rule.validate("å" * 4, None) self.assertListEqual(violations, [expected_violation]) # set line length to 3, and check no violation on length 4 rule = TitleMinLength({'min-length': 3}) - violations = rule.validate(u"å" * 4, None) + violations = rule.validate("å" * 4, None) self.assertIsNone(violations) # assert no violations on length 3 (this asserts we've implemented a *strict* less than) rule = TitleMinLength({'min-length': 3}) - violations = rule.validate(u"å" * 3, None) + violations = rule.validate("å" * 3, None) self.assertIsNone(violations) # assert raise on 2 - expected_violation = RuleViolation("T8", "Title is too short (2<3)", u"å" * 2, 1) - violations = rule.validate(u"å" * 2, None) + expected_violation = RuleViolation("T8", "Title is too short (2<3)", "å" * 2, 1) + violations = rule.validate("å" * 2, None) self.assertListEqual(violations, [expected_violation]) # assert raise on empty title diff --git a/gitlint/tests/rules/test_user_rules.py b/gitlint/tests/rules/test_user_rules.py index 52d0283..510a829 100644 --- a/gitlint/tests/rules/test_user_rules.py +++ b/gitlint/tests/rules/test_user_rules.py @@ -6,7 +6,6 @@ import sys from gitlint.tests.base import BaseTestCase from gitlint.rule_finder import find_rule_classes, assert_valid_rule_class from gitlint.rules import UserRuleError -from gitlint.utils import ustr from gitlint import options, rules @@ -25,7 +24,7 @@ class UserRuleTests(BaseTestCase): # - Other members of the my_commit_rules module are ignored # (such as func_should_be_ignored, global_variable_should_be_ignored) # - Rules are loaded non-recursively (user_rules/import_exception directory is ignored) - self.assertEqual("[<class 'my_commit_rules.MyUserCommitRule'>]", ustr(classes)) + 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) @@ -33,8 +32,8 @@ class UserRuleTests(BaseTestCase): # Do some basic asserts on our user rule self.assertEqual(classes[0].id, "UC1") - self.assertEqual(classes[0].name, u"my-üser-commit-rule") - expected_option = options.IntOption('violation-count', 1, u"Number of violåtions to return") + self.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")) @@ -42,13 +41,13 @@ class UserRuleTests(BaseTestCase): # expected result rule_class = classes[0]() violations = rule_class.validate("false-commit-object (ignored)") - self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1)]) + 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", u"Commit violåtion 1", u"Contënt 1", 1), - rules.RuleViolation("UC1", u"Commit violåtion 2", u"Contënt 2", 2)]) + 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 @@ -58,7 +57,7 @@ class UserRuleTests(BaseTestCase): rule_class = classes[0]() violations = rule_class.validate("false-commit-object (ignored)") - self.assertListEqual(violations, [rules.RuleViolation("UC1", u"Commit violåtion 1", u"Contënt 1", 1)]) + 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 @@ -68,8 +67,8 @@ class UserRuleTests(BaseTestCase): classes = find_rule_classes(user_rule_path) # convert classes to strings and sort them so we can compare them - class_strings = sorted([ustr(clazz) for clazz in classes]) - expected = [u"<class 'my_commit_rules.MyUserCommitRule'>", u"<class 'parent_package.InitFileRule'>"] + 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): @@ -92,8 +91,8 @@ class UserRuleTests(BaseTestCase): find_rule_classes(user_rule_path) def test_find_rule_classes_nonexisting_path(self): - with self.assertRaisesMessage(UserRuleError, u"Invalid extra-path: föo/bar"): - find_rule_classes(u"föo/bar") + 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): @@ -132,7 +131,7 @@ class UserRuleTests(BaseTestCase): def test_assert_valid_rule_class_negative_parent(self): # rule class must extend from LineRule or CommitRule - class MyRuleClass(object): + class MyRuleClass: pass expected_msg = "User-defined rule class 'MyRuleClass' must extend from gitlint.rules.LineRule, " + \ @@ -160,8 +159,9 @@ class UserRuleTests(BaseTestCase): # 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 = "The id '{0}' of 'MyRuleClass' is invalid. Gitlint reserves ids starting with R,T,B,M,I" - with self.assertRaisesMessage(UserRuleError, expected_msg.format(letter)): + 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): @@ -186,17 +186,17 @@ class UserRuleTests(BaseTestCase): class MyRuleClass(parent_class): id = "UC1" - name = u"my-rüle-class" + name = "my-rüle-class" # if set, option_spec must be a list of gitlint options - MyRuleClass.options_spec = u"föo" + 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 = [u"föo", 123] # pylint: disable=bad-option-value,redefined-variable-type + 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) @@ -206,14 +206,14 @@ class UserRuleTests(BaseTestCase): for clazz in baseclasses: class MyRuleClass(clazz): id = "UC1" - name = u"my-rüle-class" + 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 = u"föo" + MyRuleClass.validate = "föo" with self.assertRaisesMessage(UserRuleError, "User-defined rule class 'MyRuleClass' must have a 'validate' method"): assert_valid_rule_class(MyRuleClass) @@ -221,21 +221,21 @@ class UserRuleTests(BaseTestCase): def test_assert_valid_rule_class_negative_apply(self): class MyRuleClass(rules.ConfigurationRule): id = "UCR1" - name = u"my-rüle-class" + 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 = u"föo" + 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 = u"my-rüle-class" + name = "my-rüle-class" def validate(self): pass @@ -247,7 +247,7 @@ class UserRuleTests(BaseTestCase): assert_valid_rule_class(MyRuleClass) # invalid target - MyRuleClass.target = u"föo" + MyRuleClass.target = "föo" with self.assertRaisesMessage(UserRuleError, expected_msg): assert_valid_rule_class(MyRuleClass) diff --git a/gitlint/tests/samples/user_rules/my_commit_rules.py b/gitlint/tests/samples/user_rules/my_commit_rules.py index 5456487..8b0907e 100644 --- a/gitlint/tests/samples/user_rules/my_commit_rules.py +++ b/gitlint/tests/samples/user_rules/my_commit_rules.py @@ -5,14 +5,14 @@ from gitlint.options import IntOption class MyUserCommitRule(CommitRule): - name = u"my-üser-commit-rule" + name = "my-üser-commit-rule" id = "UC1" - options_spec = [IntOption('violation-count', 1, u"Number of violåtions to return")] + options_spec = [IntOption('violation-count', 1, "Number of violåtions to return")] def validate(self, _commit): violations = [] for i in range(1, self.options['violation-count'].value + 1): - violations.append(RuleViolation(self.id, u"Commit violåtion %d" % i, u"Contënt %d" % i, i)) + violations.append(RuleViolation(self.id, "Commit violåtion %d" % i, "Contënt %d" % i, i)) return violations diff --git a/gitlint/tests/samples/user_rules/parent_package/__init__.py b/gitlint/tests/samples/user_rules/parent_package/__init__.py index 32c05fc..9ea5371 100644 --- a/gitlint/tests/samples/user_rules/parent_package/__init__.py +++ b/gitlint/tests/samples/user_rules/parent_package/__init__.py @@ -5,7 +5,7 @@ from gitlint.rules import CommitRule class InitFileRule(CommitRule): - name = u"my-init-cömmit-rule" + name = "my-init-cömmit-rule" id = "UC1" options_spec = [] diff --git a/gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py b/gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py index b73a305..b143e62 100644 --- a/gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py +++ b/gitlint/tests/samples/user_rules/parent_package/my_commit_rules.py @@ -4,7 +4,7 @@ from gitlint.rules import CommitRule class MyUserCommitRule(CommitRule): - name = u"my-user-cömmit-rule" + name = "my-user-cömmit-rule" id = "UC2" options_spec = [] diff --git a/gitlint/tests/test_cache.py b/gitlint/tests/test_cache.py index 5d78953..4b1d47a 100644 --- a/gitlint/tests/test_cache.py +++ b/gitlint/tests/test_cache.py @@ -16,13 +16,13 @@ class CacheTests(BaseTestCase): @cache def foo(self): self.counter += 1 - return u"bår" + return "bår" @property - @cache(cachekey=u"hür") + @cache(cachekey="hür") def bar(self): self.counter += 1 - return u"fōo" + return "fōo" def test_cache(self): # Init new class with cached properties @@ -31,14 +31,14 @@ class CacheTests(BaseTestCase): self.assertDictEqual(myclass._cache, {}) # Assert that function is called on first access, cache is set - self.assertEqual(myclass.foo, u"bår") + self.assertEqual(myclass.foo, "bår") self.assertEqual(myclass.counter, 1) - self.assertDictEqual(myclass._cache, {"foo": u"bår"}) + self.assertDictEqual(myclass._cache, {"foo": "bår"}) # After function is not called on subsequent access, cache is still set - self.assertEqual(myclass.foo, u"bår") + self.assertEqual(myclass.foo, "bår") self.assertEqual(myclass.counter, 1) - self.assertDictEqual(myclass._cache, {"foo": u"bår"}) + self.assertDictEqual(myclass._cache, {"foo": "bår"}) def test_cache_custom_key(self): # Init new class with cached properties @@ -47,11 +47,11 @@ class CacheTests(BaseTestCase): self.assertDictEqual(myclass._cache, {}) # Assert that function is called on first access, cache is set with custom key - self.assertEqual(myclass.bar, u"fōo") + self.assertEqual(myclass.bar, "fōo") self.assertEqual(myclass.counter, 1) - self.assertDictEqual(myclass._cache, {u"hür": u"fōo"}) + self.assertDictEqual(myclass._cache, {"hür": "fōo"}) # After function is not called on subsequent access, cache is still set - self.assertEqual(myclass.bar, u"fōo") + self.assertEqual(myclass.bar, "fōo") self.assertEqual(myclass.counter, 1) - self.assertDictEqual(myclass._cache, {u"hür": u"fōo"}) + self.assertDictEqual(myclass._cache, {"hür": "fōo"}) diff --git a/gitlint/tests/test_display.py b/gitlint/tests/test_display.py index 1c64b34..167ef96 100644 --- a/gitlint/tests/test_display.py +++ b/gitlint/tests/test_display.py @@ -1,19 +1,8 @@ # -*- coding: utf-8 -*- -try: - # python 2.x - from StringIO import StringIO -except ImportError: - # python 3.x - from io import StringIO +from io import StringIO - -try: - # python 2.x - from mock import patch -except ImportError: - # python 3.x - from unittest.mock import patch # pylint: disable=no-name-in-module, import-error +from unittest.mock import patch # pylint: disable=no-name-in-module, import-error from gitlint.display import Display from gitlint.config import LintConfig @@ -28,21 +17,21 @@ class DisplayTests(BaseTestCase): with patch('gitlint.display.stderr', new=StringIO()) as stderr: # Non exact outputting, should output both v and vv output with patch('gitlint.display.stdout', new=StringIO()) as stdout: - display.v(u"tëst") - display.vv(u"tëst2") + display.v("tëst") + display.vv("tëst2") # vvvv should be ignored regardless - display.vvv(u"tëst3.1") - display.vvv(u"tëst3.2", exact=True) - self.assertEqual(u"tëst\ntëst2\n", stdout.getvalue()) + display.vvv("tëst3.1") + display.vvv("tëst3.2", exact=True) + self.assertEqual("tëst\ntëst2\n", stdout.getvalue()) # exact outputting, should only output v with patch('gitlint.display.stdout', new=StringIO()) as stdout: - display.v(u"tëst", exact=True) - display.vv(u"tëst2", exact=True) + display.v("tëst", exact=True) + display.vv("tëst2", exact=True) # vvvv should be ignored regardless - display.vvv(u"tëst3.1") - display.vvv(u"tëst3.2", exact=True) - self.assertEqual(u"tëst2\n", stdout.getvalue()) + display.vvv("tëst3.1") + display.vvv("tëst3.2", exact=True) + self.assertEqual("tëst2\n", stdout.getvalue()) # standard error should be empty throughtout all of this self.assertEqual('', stderr.getvalue()) @@ -54,21 +43,21 @@ class DisplayTests(BaseTestCase): 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") + display.e("tëst") + display.ee("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()) + display.eee("tëst3.1") + display.eee("tëst3.2", exact=True) + self.assertEqual("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) + display.e("tëst", exact=True) + display.ee("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()) + display.eee("tëst3.1") + display.eee("tëst3.2", exact=True) + self.assertEqual("tëst2\n", stderr.getvalue()) # standard output should be empty throughtout all of this self.assertEqual('', stdout.getvalue()) diff --git a/gitlint/tests/test_hooks.py b/gitlint/tests/test_hooks.py index 62f55e5..0ce5040 100644 --- a/gitlint/tests/test_hooks.py +++ b/gitlint/tests/test_hooks.py @@ -2,12 +2,7 @@ import os -try: - # python 2.x - from mock import patch, ANY, mock_open -except ImportError: - # python 3.x - from unittest.mock import patch, ANY, mock_open # pylint: disable=no-name-in-module, import-error +from unittest.mock import patch, ANY, mock_open from gitlint.tests.base import BaseTestCase from gitlint.config import LintConfig @@ -19,7 +14,7 @@ class HookTests(BaseTestCase): @patch('gitlint.hooks.git_hooks_dir') def test_commit_msg_hook_path(self, git_hooks_dir): - git_hooks_dir.return_value = os.path.join(u"/föo", u"bar") + git_hooks_dir.return_value = os.path.join("/föo", "bar") lint_config = LintConfig() lint_config.target = self.SAMPLES_DIR expected_path = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH) @@ -37,8 +32,8 @@ class HookTests(BaseTestCase): @patch('gitlint.hooks.git_hooks_dir') 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") + lint_config.target = os.path.join("/hür", "dur") + git_hooks_dir.return_value = os.path.join("/föo", "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) @@ -54,11 +49,11 @@ class HookTests(BaseTestCase): @patch('gitlint.hooks.git_hooks_dir') def test_install_commit_msg_hook_negative(self, git_hooks_dir, isdir, path_exists, copy): 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") + lint_config.target = os.path.join("/hür", "dur") + git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks") # mock that current dir is not a git repo isdir.return_value = False - expected_msg = u"{0} is not a git repository.".format(lint_config.target) + expected_msg = f"{lint_config.target} is not a git repository." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.install_commit_msg_hook(lint_config) isdir.assert_called_with(git_hooks_dir.return_value) @@ -69,7 +64,7 @@ class HookTests(BaseTestCase): isdir.return_value = True path_exists.return_value = True expected_dst = os.path.join(git_hooks_dir.return_value, COMMIT_MSG_HOOK_DST_PATH) - expected_msg = u"There is already a commit-msg hook file present in {0}.\n".format(expected_dst) + \ + expected_msg = f"There is already a commit-msg hook file present in {expected_dst}.\n" + \ "gitlint currently does not support appending to an existing commit-msg file." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.install_commit_msg_hook(lint_config) @@ -81,8 +76,8 @@ class HookTests(BaseTestCase): @patch('gitlint.hooks.git_hooks_dir') 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") + git_hooks_dir.return_value = os.path.join("/föo", "bar", ".git", "hooks") + lint_config.target = os.path.join("/hür", "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) @@ -99,12 +94,12 @@ class HookTests(BaseTestCase): @patch('gitlint.hooks.git_hooks_dir') 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") + lint_config.target = os.path.join("/hür", "dur") + git_hooks_dir.return_value = os.path.join("/föo", "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) + expected_msg = f"{lint_config.target} is not a git repository." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) isdir.assert_called_with(git_hooks_dir.return_value) @@ -115,7 +110,7 @@ class HookTests(BaseTestCase): 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) + expected_msg = f"There is no commit-msg hook present in {expected_dst}." with self.assertRaisesMessage(GitHookInstallerError, expected_msg): GitHookInstaller.uninstall_commit_msg_hook(lint_config) isdir.assert_called_with(git_hooks_dir.return_value) @@ -127,7 +122,7 @@ class HookTests(BaseTestCase): 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) + \ + expected_msg = f"The commit-msg hook in {expected_dst} was not installed by gitlint " + \ "(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): diff --git a/gitlint/tests/test_lint.py b/gitlint/tests/test_lint.py index 3bf9a94..b743389 100644 --- a/gitlint/tests/test_lint.py +++ b/gitlint/tests/test_lint.py @@ -1,18 +1,8 @@ # -*- coding: utf-8 -*- -try: - # python 2.x - from StringIO import StringIO -except ImportError: - # python 3.x - from io import StringIO - -try: - # python 2.x - from mock import patch -except ImportError: - # python 3.x - from unittest.mock import patch # pylint: disable=no-name-in-module, import-error +from io import StringIO + +from unittest.mock import patch # pylint: disable=no-name-in-module, import-error from gitlint.tests.base import BaseTestCase from gitlint.lint import GitLinter @@ -27,14 +17,14 @@ class LintTests(BaseTestCase): 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), + "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), + "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 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)] @@ -46,7 +36,7 @@ class LintTests(BaseTestCase): 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), + "Just a title contåining WIP", 1), RuleViolation("B6", "Body message is missing", None, 3)] self.assertListEqual(violations, expected) @@ -56,7 +46,7 @@ class LintTests(BaseTestCase): 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." + title = " 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), @@ -64,12 +54,12 @@ class LintTests(BaseTestCase): 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 " + + "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("B2", "Line has trailing whitespace", "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)] + "This line has a tråiling tab.\t", 5)] self.assertListEqual(violations, expected) @@ -90,13 +80,13 @@ class LintTests(BaseTestCase): linter = GitLinter(config_builder.build()) violations = linter.lint(commit) - title = u" Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters." + title = " Commit title containing 'WIP', \tleading and tråiling whitespace and longer than 72 characters." # expect only certain violations because sample5 has a 'gitlint-ignore: T3, T6, body-max-line-length' 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", u"This line should be ëmpty", 2), - RuleViolation("B2", "Line has trailing whitespace", u"This line has a tråiling space. ", 4), + RuleViolation("B4", "Second line is not empty", "This line should be ëmpty", 2), + RuleViolation("B2", "Line has trailing whitespace", "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)] @@ -106,11 +96,11 @@ class LintTests(BaseTestCase): """ Lint sample2 but also add some metadata to the commit so we that gets linted as well """ linter = GitLinter(LintConfig()) gitcontext = self.gitcontext(self.get_sample("commit_message/sample2")) - gitcontext.commits[0].author_email = u"foo bår" + gitcontext.commits[0].author_email = "foo bår" violations = linter.lint(gitcontext.commits[-1]) - expected = [RuleViolation("M1", "Author email for commit is invalid", u"foo bår", None), + expected = [RuleViolation("M1", "Author email for commit is invalid", "foo bår", None), RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", - u"Just a title contåining WIP", 1), + "Just a title contåining WIP", 1), RuleViolation("B6", "Body message is missing", None, 3)] self.assertListEqual(violations, expected) @@ -123,7 +113,7 @@ class LintTests(BaseTestCase): 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)] + "This line has a tråiling tab.\t", 5)] self.assertListEqual(violations, expected) @@ -146,19 +136,19 @@ class LintTests(BaseTestCase): # Normally we'd expect a B6 violation, but that one is skipped because of the specific ignore set above expected = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", - u"Just a title contåining WIP", 1)] + "Just a title contåining WIP", 1)] self.assertListEqual(violations, expected) # Test ignoring body lines lint_config = LintConfig() linter = GitLinter(lint_config) - lint_config.set_rule_option("I3", "regex", u"(.*)tråiling(.*)") + lint_config.set_rule_option("I3", "regex", "(.*)tråiling(.*)") violations = linter.lint(self.gitcommit(self.get_sample("commit_message/sample1"))) expected_errors = [RuleViolation("T3", "Title has trailing punctuation (.)", - u"Commit title contåining 'WIP', as well as trailing punctuation.", 1), + "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), + "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 " + @@ -171,7 +161,7 @@ class LintTests(BaseTestCase): def test_lint_special_commit(self): for commit_type in ["merge", "revert", "squash", "fixup"]: - commit = self.gitcommit(self.get_sample("commit_message/{0}".format(commit_type))) + commit = self.gitcommit(self.get_sample(f"commit_message/{commit_type}")) lintconfig = LintConfig() linter = GitLinter(lintconfig) violations = linter.lint(commit) @@ -180,7 +170,7 @@ class LintTests(BaseTestCase): self.assertListEqual(violations, []) # Check that we do see violations if we disable 'ignore-merge-commits' - setattr(lintconfig, "ignore_{0}_commits".format(commit_type), False) + setattr(lintconfig, f"ignore_{commit_type}_commits", False) linter = GitLinter(lintconfig) violations = linter.lint(commit) self.assertTrue(len(violations) > 0) @@ -195,7 +185,7 @@ class LintTests(BaseTestCase): 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: (.*)$")] + rule_regexes = [("title-match-regex", "Tïtle$"), ("body-match-regex", "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) @@ -203,16 +193,16 @@ class LintTests(BaseTestCase): # 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)] + lintconfig.set_rule_option("title-match-regex", "regex", "^Tïtle") + lintconfig.set_rule_option("body-match-regex", "regex", "Sügned-Off-By: (.*)$") + expected_violations = [RuleViolation("T7", "Title does not match regex (^Tïtle)", "Normal Commit Tïtle", 1), + RuleViolation("B8", "Body does not match regex (Sügned-Off-By: (.*)$)", None, 6)] violations = linter.lint(commit) self.assertListEqual(violations, expected_violations) 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)] + violations = [RuleViolation("RULE_ID_1", "Error Messåge 1", "Violating Content 1", None), + RuleViolation("RULE_ID_2", "Error Message 2", "Violåting Content 2", 2)] linter = GitLinter(LintConfig()) # test output with increasing verbosity @@ -224,54 +214,54 @@ class LintTests(BaseTestCase): 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" + expected = "-: 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" + expected = "-: 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" + expected = "-: RULE_ID_1 Error Messåge 1: \"Violating Content 1\"\n" + \ + "2: RULE_ID_2 Error Message 2: \"Violåting Content 2\"\n" self.assertEqual(expected, stderr.getvalue()) def test_named_rules(self): """ Test that when named rules are present, both them and the original (non-named) rules executed """ lint_config = LintConfig() - for rule_name in [u"my-ïd", u"another-rule-ïd"]: + for rule_name in ["my-ïd", "another-rule-ïd"]: rule_id = TitleMustNotContainWord.id + ":" + rule_name lint_config.rules.add_rule(TitleMustNotContainWord, rule_id) - lint_config.set_rule_option(rule_id, "words", [u"Föo"]) + lint_config.set_rule_option(rule_id, "words", ["Föo"]) linter = GitLinter(lint_config) - violations = [RuleViolation("T5", u"Title contains the word 'WIP' (case-insensitive)", u"WIP: Föo bar", 1), - RuleViolation(u"T5:another-rule-ïd", u"Title contains the word 'Föo' (case-insensitive)", - u"WIP: Föo bar", 1), - RuleViolation(u"T5:my-ïd", u"Title contains the word 'Föo' (case-insensitive)", - u"WIP: Föo bar", 1)] - self.assertListEqual(violations, linter.lint(self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla"))) + violations = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", "WIP: Föo bar", 1), + RuleViolation("T5:another-rule-ïd", "Title contains the word 'Föo' (case-insensitive)", + "WIP: Föo bar", 1), + RuleViolation("T5:my-ïd", "Title contains the word 'Föo' (case-insensitive)", + "WIP: Föo bar", 1)] + self.assertListEqual(violations, linter.lint(self.gitcommit("WIP: Föo bar\n\nFoo bår hur dur bla bla"))) def test_ignore_named_rules(self): """ Test that named rules can be ignored """ # Add named rule to lint config config_builder = LintConfigBuilder() - rule_id = TitleMustNotContainWord.id + u":my-ïd" - config_builder.set_option(rule_id, "words", [u"Föo"]) + rule_id = TitleMustNotContainWord.id + ":my-ïd" + config_builder.set_option(rule_id, "words", ["Föo"]) lint_config = config_builder.build() linter = GitLinter(lint_config) - commit = self.gitcommit(u"WIP: Föo bar\n\nFoo bår hur dur bla bla") + commit = self.gitcommit("WIP: Föo bar\n\nFoo bår hur dur bla bla") # By default, we expect both the violations of the regular rule as well as the named rule to show up - violations = [RuleViolation("T5", u"Title contains the word 'WIP' (case-insensitive)", u"WIP: Föo bar", 1), - RuleViolation(u"T5:my-ïd", u"Title contains the word 'Föo' (case-insensitive)", - u"WIP: Föo bar", 1)] + violations = [RuleViolation("T5", "Title contains the word 'WIP' (case-insensitive)", "WIP: Föo bar", 1), + RuleViolation("T5:my-ïd", "Title contains the word 'Föo' (case-insensitive)", + "WIP: Föo bar", 1)] self.assertListEqual(violations, linter.lint(commit)) # ignore regular rule: only named rule violations show up @@ -283,5 +273,5 @@ class LintTests(BaseTestCase): self.assertListEqual(violations[:-1], linter.lint(commit)) # ignore named rule by name: only regular rule violations show up - lint_config.ignore = [TitleMustNotContainWord.name + u":my-ïd"] + lint_config.ignore = [TitleMustNotContainWord.name + ":my-ïd"] self.assertListEqual(violations[:-1], linter.lint(commit)) diff --git a/gitlint/tests/test_options.py b/gitlint/tests/test_options.py index 68f0f8c..fc3ccc1 100644 --- a/gitlint/tests/test_options.py +++ b/gitlint/tests/test_options.py @@ -9,25 +9,25 @@ from gitlint.options import IntOption, BoolOption, StrOption, ListOption, PathOp class RuleOptionTests(BaseTestCase): def test_option_equality(self): - options = {IntOption: 123, StrOption: u"foöbar", BoolOption: False, ListOption: ["a", "b"], - PathOption: ".", RegexOption: u"^foöbar(.*)"} + options = {IntOption: 123, StrOption: "foöbar", BoolOption: False, ListOption: ["a", "b"], + PathOption: ".", RegexOption: "^foöbar(.*)"} for clazz, val in options.items(): # 2 options are equal if their name, value and description match - option1 = clazz(u"test-öption", val, u"Test Dëscription") - option2 = clazz(u"test-öption", val, u"Test Dëscription") + option1 = clazz("test-öption", val, "Test Dëscription") + option2 = clazz("test-öption", val, "Test Dëscription") self.assertEqual(option1, option2) # Not equal: class, name, description, value are different - self.assertNotEqual(option1, IntOption(u"tëst-option1", 123, u"Test Dëscription")) - self.assertNotEqual(option1, StrOption(u"tëst-option1", u"åbc", u"Test Dëscription")) - self.assertNotEqual(option1, StrOption(u"tëst-option", u"åbcd", u"Test Dëscription")) - self.assertNotEqual(option1, StrOption(u"tëst-option", u"åbc", u"Test Dëscription2")) + self.assertNotEqual(option1, IntOption("tëst-option1", 123, "Test Dëscription")) + self.assertNotEqual(option1, StrOption("tëst-option1", "åbc", "Test Dëscription")) + self.assertNotEqual(option1, StrOption("tëst-option", "åbcd", "Test Dëscription")) + self.assertNotEqual(option1, StrOption("tëst-option", "åbc", "Test Dëscription2")) def test_int_option(self): # normal behavior - option = IntOption(u"tëst-name", 123, u"Tëst Description") - self.assertEqual(option.name, u"tëst-name") - self.assertEqual(option.description, u"Tëst Description") + option = IntOption("tëst-name", 123, "Tëst Description") + self.assertEqual(option.name, "tëst-name") + self.assertEqual(option.description, "Tëst Description") self.assertEqual(option.value, 123) # re-set value @@ -39,12 +39,12 @@ class RuleOptionTests(BaseTestCase): self.assertEqual(option.value, None) # error on negative int when not allowed - expected_error = u"Option 'tëst-name' must be a positive integer (current value: '-123')" + expected_error = "Option 'tëst-name' must be a positive integer (current value: '-123')" with self.assertRaisesMessage(RuleOptionError, expected_error): option.set(-123) # error on non-int value - expected_error = u"Option 'tëst-name' must be a positive integer (current value: 'foo')" + expected_error = "Option 'tëst-name' must be a positive integer (current value: 'foo')" with self.assertRaisesMessage(RuleOptionError, expected_error): option.set("foo") @@ -54,20 +54,20 @@ class RuleOptionTests(BaseTestCase): self.assertEqual(option.value, -456) # error on non-int value when negative int is allowed - expected_error = u"Option 'test-name' must be an integer (current value: 'foo')" + expected_error = "Option 'test-name' must be an integer (current value: 'foo')" with self.assertRaisesMessage(RuleOptionError, expected_error): option.set("foo") def test_str_option(self): # normal behavior - option = StrOption(u"tëst-name", u"föo", u"Tëst Description") - self.assertEqual(option.name, u"tëst-name") - self.assertEqual(option.description, u"Tëst Description") - self.assertEqual(option.value, u"föo") + option = StrOption("tëst-name", "föo", "Tëst Description") + self.assertEqual(option.name, "tëst-name") + self.assertEqual(option.description, "Tëst Description") + self.assertEqual(option.value, "föo") # re-set value - option.set(u"bår") - self.assertEqual(option.value, u"bår") + option.set("bår") + self.assertEqual(option.value, "bår") # conversion to str option.set(123) @@ -83,9 +83,9 @@ class RuleOptionTests(BaseTestCase): def test_boolean_option(self): # normal behavior - option = BoolOption(u"tëst-name", "true", u"Tëst Description") - self.assertEqual(option.name, u"tëst-name") - self.assertEqual(option.description, u"Tëst Description") + option = BoolOption("tëst-name", "true", "Tëst Description") + self.assertEqual(option.name, "tëst-name") + self.assertEqual(option.description, "Tëst Description") self.assertEqual(option.value, True) # re-set value @@ -97,25 +97,25 @@ class RuleOptionTests(BaseTestCase): self.assertEqual(option.value, True) # error on incorrect value - incorrect_values = [1, -1, "foo", u"bår", ["foo"], {'foo': "bar"}, None] + incorrect_values = [1, -1, "foo", "bår", ["foo"], {'foo': "bar"}, None] for value in incorrect_values: - with self.assertRaisesMessage(RuleOptionError, u"Option 'tëst-name' must be either 'true' or 'false'"): + with self.assertRaisesMessage(RuleOptionError, "Option 'tëst-name' must be either 'true' or 'false'"): option.set(value) def test_list_option(self): # normal behavior - option = ListOption(u"tëst-name", u"å,b,c,d", u"Tëst Description") - self.assertEqual(option.name, u"tëst-name") - self.assertEqual(option.description, u"Tëst Description") - self.assertListEqual(option.value, [u"å", u"b", u"c", u"d"]) + option = ListOption("tëst-name", "å,b,c,d", "Tëst Description") + self.assertEqual(option.name, "tëst-name") + self.assertEqual(option.description, "Tëst Description") + self.assertListEqual(option.value, ["å", "b", "c", "d"]) # re-set value - option.set(u"1,2,3,4") - self.assertListEqual(option.value, [u"1", u"2", u"3", u"4"]) + option.set("1,2,3,4") + self.assertListEqual(option.value, ["1", "2", "3", "4"]) # set list - option.set([u"foo", u"bår", u"test"]) - self.assertListEqual(option.value, [u"foo", u"bår", u"test"]) + option.set(["foo", "bår", "test"]) + self.assertListEqual(option.value, ["foo", "bår", "test"]) # None option.set(None) @@ -134,8 +134,8 @@ class RuleOptionTests(BaseTestCase): self.assertListEqual(option.value, []) # trailing comma - option.set(u"ë,f,g,") - self.assertListEqual(option.value, [u"ë", u"f", u"g"]) + option.set("ë,f,g,") + self.assertListEqual(option.value, ["ë", "f", "g"]) # leading and trailing whitespace should be trimmed, but only deduped within text option.set(" abc , def , ghi \t , jkl mno ") @@ -150,11 +150,11 @@ class RuleOptionTests(BaseTestCase): self.assertListEqual(option.value, ["123"]) def test_path_option(self): - option = PathOption(u"tëst-directory", ".", u"Tëst Description", type=u"dir") - self.assertEqual(option.name, u"tëst-directory") - self.assertEqual(option.description, u"Tëst Description") + option = PathOption("tëst-directory", ".", "Tëst Description", type="dir") + self.assertEqual(option.name, "tëst-directory") + self.assertEqual(option.description, "Tëst Description") self.assertEqual(option.value, os.getcwd()) - self.assertEqual(option.type, u"dir") + self.assertEqual(option.type, "dir") # re-set value option.set(self.SAMPLES_DIR) @@ -165,33 +165,32 @@ class RuleOptionTests(BaseTestCase): self.assertIsNone(option.value) # set to int - expected = u"Option tëst-directory must be an existing directory (current value: '1234')" + expected = "Option tëst-directory must be an existing directory (current value: '1234')" with self.assertRaisesMessage(RuleOptionError, expected): option.set(1234) # set to non-existing directory - non_existing_path = os.path.join(u"/föo", u"bar") - expected = u"Option tëst-directory must be an existing directory (current value: '{0}')" - with self.assertRaisesMessage(RuleOptionError, expected.format(non_existing_path)): + non_existing_path = os.path.join("/föo", "bar") + expected = f"Option tëst-directory must be an existing directory (current value: '{non_existing_path}')" + with self.assertRaisesMessage(RuleOptionError, expected): option.set(non_existing_path) # set to a file, should raise exception since option.type = dir sample_path = self.get_sample_path(os.path.join("commit_message", "sample1")) - expected = u"Option tëst-directory must be an existing directory (current value: '{0}')".format(sample_path) + expected = f"Option tëst-directory must be an existing directory (current value: '{sample_path}')" with self.assertRaisesMessage(RuleOptionError, expected): option.set(sample_path) # set option.type = file, file should now be accepted, directories not - option.type = u"file" + option.type = "file" option.set(sample_path) self.assertEqual(option.value, sample_path) - expected = u"Option tëst-directory must be an existing file (current value: '{0}')".format( - self.get_sample_path()) + expected = f"Option tëst-directory must be an existing file (current value: '{self.get_sample_path()}')" with self.assertRaisesMessage(RuleOptionError, expected): option.set(self.get_sample_path()) # set option.type = both, files and directories should now be accepted - option.type = u"both" + option.type = "both" option.set(sample_path) self.assertEqual(option.value, sample_path) option.set(self.get_sample_path()) @@ -199,27 +198,27 @@ class RuleOptionTests(BaseTestCase): # Expect exception if path type is invalid option.type = u'föo' - expected = u"Option tëst-directory type must be one of: 'file', 'dir', 'both' (current: 'föo')" + expected = "Option tëst-directory type must be one of: 'file', 'dir', 'both' (current: 'föo')" with self.assertRaisesMessage(RuleOptionError, expected): option.set("haha") def test_regex_option(self): # normal behavior - option = RegexOption(u"tëst-regex", u"^myrëgex(.*)foo$", u"Tëst Regex Description") - self.assertEqual(option.name, u"tëst-regex") - self.assertEqual(option.description, u"Tëst Regex Description") - self.assertEqual(option.value, re.compile(u"^myrëgex(.*)foo$", re.UNICODE)) + option = RegexOption("tëst-regex", "^myrëgex(.*)foo$", "Tëst Regex Description") + self.assertEqual(option.name, "tëst-regex") + self.assertEqual(option.description, "Tëst Regex Description") + self.assertEqual(option.value, re.compile("^myrëgex(.*)foo$", re.UNICODE)) # re-set value - option.set(u"[0-9]föbar.*") - self.assertEqual(option.value, re.compile(u"[0-9]föbar.*", re.UNICODE)) + option.set("[0-9]föbar.*") + self.assertEqual(option.value, re.compile("[0-9]föbar.*", re.UNICODE)) # set None option.set(None) self.assertIsNone(option.value) # error on invalid regex - incorrect_values = [u"foo(", 123, -1] + incorrect_values = ["foo(", 123, -1] for value in incorrect_values: - with self.assertRaisesRegex(RuleOptionError, u"Invalid regular expression"): + with self.assertRaisesRegex(RuleOptionError, "Invalid regular expression"): option.set(value) diff --git a/gitlint/tests/test_utils.py b/gitlint/tests/test_utils.py index 5841b63..4ec8bda 100644 --- a/gitlint/tests/test_utils.py +++ b/gitlint/tests/test_utils.py @@ -1,15 +1,10 @@ # -*- coding: utf-8 -*- +from unittest.mock import patch + from gitlint import utils from gitlint.tests.base import BaseTestCase -try: - # python 2.x - from mock import patch -except ImportError: - # python 3.x - from unittest.mock import patch # pylint: disable=no-name-in-module, import-error - class UtilsTests(BaseTestCase): @@ -24,7 +19,7 @@ class UtilsTests(BaseTestCase): self.assertEqual(utils.use_sh_library(), True) patched_env.get.assert_called_once_with("GITLINT_USE_SH_LIB", None) - for invalid_val in ["0", u"foöbar"]: + for invalid_val in ["0", "foöbar"]: patched_env.get.reset_mock() # reset mock call count patched_env.get.return_value = invalid_val self.assertEqual(utils.use_sh_library(), False, invalid_val) @@ -41,12 +36,12 @@ class UtilsTests(BaseTestCase): @patch('gitlint.utils.locale') def test_default_encoding_non_windows(self, mocked_locale): utils.PLATFORM_IS_WINDOWS = False - mocked_locale.getpreferredencoding.return_value = u"foöbar" - self.assertEqual(utils.getpreferredencoding(), u"foöbar") + mocked_locale.getpreferredencoding.return_value = "foöbar" + self.assertEqual(utils.getpreferredencoding(), "foöbar") mocked_locale.getpreferredencoding.assert_called_once() mocked_locale.getpreferredencoding.return_value = False - self.assertEqual(utils.getpreferredencoding(), u"UTF-8") + self.assertEqual(utils.getpreferredencoding(), "UTF-8") @patch('os.environ') def test_default_encoding_windows(self, patched_env): @@ -60,23 +55,23 @@ class UtilsTests(BaseTestCase): patched_env.get.side_effect = mocked_get # Assert getpreferredencoding reads env vars in order: LC_ALL, LC_CTYPE, LANG - mock_env = {"LC_ALL": u"ASCII", "LC_CTYPE": u"UTF-16", "LANG": u"CP1251"} - self.assertEqual(utils.getpreferredencoding(), u"ASCII") - mock_env = {"LC_CTYPE": u"UTF-16", "LANG": u"CP1251"} - self.assertEqual(utils.getpreferredencoding(), u"UTF-16") - mock_env = {"LANG": u"CP1251"} - self.assertEqual(utils.getpreferredencoding(), u"CP1251") + mock_env = {"LC_ALL": "ASCII", "LC_CTYPE": "UTF-16", "LANG": "CP1251"} + self.assertEqual(utils.getpreferredencoding(), "ASCII") + mock_env = {"LC_CTYPE": "UTF-16", "LANG": "CP1251"} + self.assertEqual(utils.getpreferredencoding(), "UTF-16") + mock_env = {"LANG": "CP1251"} + self.assertEqual(utils.getpreferredencoding(), "CP1251") # Assert split on dot - mock_env = {"LANG": u"foo.UTF-16"} - self.assertEqual(utils.getpreferredencoding(), u"UTF-16") + mock_env = {"LANG": "foo.UTF-16"} + self.assertEqual(utils.getpreferredencoding(), "UTF-16") # assert default encoding is UTF-8 mock_env = {} self.assertEqual(utils.getpreferredencoding(), "UTF-8") - mock_env = {"FOO": u"föo"} + mock_env = {"FOO": "föo"} self.assertEqual(utils.getpreferredencoding(), "UTF-8") # assert fallback encoding is UTF-8 in case we set an unavailable encoding - mock_env = {"LC_ALL": u"foo"} - self.assertEqual(utils.getpreferredencoding(), u"UTF-8") + mock_env = {"LC_ALL": "foo"} + self.assertEqual(utils.getpreferredencoding(), "UTF-8") diff --git a/gitlint/utils.py b/gitlint/utils.py index 89015e7..6976aac 100644 --- a/gitlint/utils.py +++ b/gitlint/utils.py @@ -1,7 +1,6 @@ # pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return import codecs import platform -import sys import os import locale @@ -25,16 +24,6 @@ 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 # On windows we won't want to use the sh library since it's not supported - instead we'll use our own shell module. @@ -90,39 +79,3 @@ def getpreferredencoding(): DEFAULT_ENCODING = getpreferredencoding() - -######################################################################################################################## -# Unicode utility functions - - -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 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 - return unicode(obj, DEFAULT_ENCODING) # pragma: no cover # noqa - else: - return unicode(obj) # pragma: no cover # noqa - else: - if type(obj) in [bytes]: - return obj.decode(DEFAULT_ENCODING) - else: - return str(obj) - - -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 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: - return obj # pragma: no cover @@ -2,6 +2,7 @@ site_name: Gitlint site_description: Linting for your git commit messages site_url: http://jorisroovers.github.io/gitlint/ repo_url: https://github.com/jorisroovers/gitlint +edit_uri: edit/main/docs nav: - Home: index.md - Configuration: configuration.md @@ -10,18 +10,13 @@ import sys import tempfile from datetime import datetime from uuid import uuid4 +from unittest import TestCase import arrow -try: - # python 2.x - from unittest2 import TestCase -except ImportError: - # python 3.x - from unittest import TestCase from qa.shell import git, gitlint, RunningCommand -from qa.utils import DEFAULT_ENCODING, ustr +from qa.utils import DEFAULT_ENCODING class BaseTestCase(TestCase): @@ -56,13 +51,14 @@ class BaseTestCase(TestCase): def assertEqualStdout(self, output, expected): # pylint: disable=invalid-name self.assertIsInstance(output, RunningCommand) - output = ustr(output.stdout) + output = output.stdout.decode(DEFAULT_ENCODING) output = output.replace('\r', '') self.assertMultiLineEqual(output, expected) @classmethod def generate_temp_path(cls): - return os.path.realpath("/tmp/gitlint-test-{0}".format(datetime.now().strftime("%Y%m%d-%H%M%S-%f"))) + timestamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f") + return os.path.realpath(f"/tmp/gitlint-test-{timestamp}") @classmethod def create_tmp_git_repo(cls): @@ -89,7 +85,7 @@ class BaseTestCase(TestCase): @staticmethod def create_file(parent_dir): """ Creates a file inside a passed directory. Returns filename.""" - test_filename = u"test-fïle-" + str(uuid4()) + test_filename = "test-fïle-" + str(uuid4()) io.open(os.path.join(parent_dir, test_filename), 'a', encoding=DEFAULT_ENCODING).close() return test_filename @@ -171,8 +167,8 @@ class BaseTestCase(TestCase): @staticmethod def get_system_info_dict(): """ Returns a dict with items related to system values logged by `gitlint --debug` """ - expected_gitlint_version = gitlint("--version").replace("gitlint, version ", "").replace("\n", "") - expected_git_version = git("--version").replace("\n", "") + expected_gitlint_version = gitlint("--version").replace("gitlint, version ", "").strip() + expected_git_version = git("--version").strip() 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, 'DEFAULT_ENCODING': DEFAULT_ENCODING} diff --git a/qa/requirements.txt b/qa/requirements.txt index f042dad..32f4662 100644 --- a/qa/requirements.txt +++ b/qa/requirements.txt @@ -1,4 +1,4 @@ -sh==1.12.14 -pytest==4.6.3; -arrow==0.15.5; +sh==1.14.1 +pytest==6.1.2; +arrow==0.17.0; gitlint # no version as you want to test the currently installed version diff --git a/qa/samples/user_rules/extra/extra_rules.py b/qa/samples/user_rules/extra/extra_rules.py index 6fb985f..9a0ae6d 100644 --- a/qa/samples/user_rules/extra/extra_rules.py +++ b/qa/samples/user_rules/extra/extra_rules.py @@ -2,18 +2,17 @@ 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 = u"gïtcontext" + name = "gïtcontext" id = "UC1" def validate(self, commit): violations = [ - 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) + RuleViolation(self.id, f"GitContext.current_branch: {commit.context.current_branch}", line_nr=1), + RuleViolation(self.id, f"GitContext.commentchar: {commit.context.commentchar}", line_nr=1) ] return violations @@ -21,13 +20,13 @@ class GitContextRule(CommitRule): class GitCommitRule(CommitRule): """ Rule that tests whether we can correctly access certain commit properties """ - name = u"gïtcommit" + name = "gïtcommit" id = "UC2" def validate(self, commit): violations = [ - 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), + RuleViolation(self.id, f"GitCommit.branches: {commit.branches}", line_nr=1), + RuleViolation(self.id, f"GitCommit.custom_prop: {commit.custom_prop}", line_nr=1), ] return violations @@ -35,16 +34,16 @@ class GitCommitRule(CommitRule): class GitlintConfigurationRule(ConfigurationRule): """ Rule that tests whether we can correctly access the config as well as modify the commit message """ - name = u"cönfigrule" + name = "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 + commit.message.body.append(f"{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" + commit.custom_prop = "foöbar" # We also ignore some extra rules, proving that we can modify the config config.ignore.append("B4") @@ -52,18 +51,18 @@ class GitlintConfigurationRule(ConfigurationRule): class ConfigurableCommitRule(CommitRule): """ Rule that tests that we can add configuration to user-defined rules """ - name = u"configürable" + name = "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")] + options_spec = [IntOption("int-öption", 2, "int-öption description"), + StrOption("str-öption", "föo", "int-öption description"), + ListOption("list-öption", ["foo", "bar"], "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), + RuleViolation(self.id, f"int-öption: {self.options[u'int-öption'].value}", line_nr=1), + RuleViolation(self.id, f"str-öption: {self.options[u'str-öption'].value}", line_nr=1), + RuleViolation(self.id, f"list-öption: {self.options[u'list-öption'].value}", line_nr=1), ] return violations diff --git a/qa/shell.py b/qa/shell.py index 43e5bbd..97dcd2c 100644 --- a/qa/shell.py +++ b/qa/shell.py @@ -3,10 +3,11 @@ # on gitlint internals for our integration testing framework. import subprocess -from qa.utils import ustr, USE_SH_LIB, IS_PY2 +from qa.utils import USE_SH_LIB, DEFAULT_ENCODING if USE_SH_LIB: from sh import git, echo, gitlint # pylint: disable=unused-import,no-name-in-module,import-error + gitlint = gitlint.bake(_unify_ttys=True, _tty_in=True) # pylint: disable=invalid-name # import exceptions separately, this makes it a little easier to mock them out in the unit tests from sh import CommandNotFound, ErrorReturnCode, RunningCommand # pylint: disable=import-error @@ -16,7 +17,7 @@ else: """ Exception indicating a command was not found during execution """ pass - class RunningCommand(object): + class RunningCommand: pass class ShResult(RunningCommand): @@ -27,7 +28,7 @@ else: self.full_cmd = full_cmd # TODO(jorisroovers): The 'sh' library by default will merge stdout and stderr. We mimic this behavior # for now until we fully remove the 'sh' library. - self.stdout = stdout + ustr(stderr) + self.stdout = stdout + stderr.decode(DEFAULT_ENCODING) self.stderr = stderr self.exit_code = exitcode @@ -55,14 +56,9 @@ else: # a non-zero exit code -> just return the entire result if hasattr(result, 'exit_code') and result.exit_code > 0: return result - return ustr(result) + return str(result) def _exec(*args, **kwargs): - if IS_PY2: - no_command_error = OSError # noqa pylint: disable=undefined-variable,invalid-name - else: - no_command_error = FileNotFoundError # noqa pylint: disable=undefined-variable - pipe = subprocess.PIPE popen_kwargs = {'stdout': pipe, 'stderr': pipe, 'shell': kwargs.get('_tty_out', False)} if '_cwd' in kwargs: @@ -73,11 +69,11 @@ else: try: p = subprocess.Popen(args, **popen_kwargs) result = p.communicate() - except no_command_error: - raise CommandNotFound + except FileNotFoundError as exc: + raise CommandNotFound from exc exit_code = p.returncode - stdout = ustr(result[0]) + stdout = result[0].decode(DEFAULT_ENCODING) stderr = result[1] # 'sh' does not decode the stderr bytes to unicode full_cmd = '' if args is None else ' '.join(args) diff --git a/qa/test_commits.py b/qa/test_commits.py index f485856..389ad66 100644 --- a/qa/test_commits.py +++ b/qa/test_commits.py @@ -6,7 +6,6 @@ import arrow from qa.shell import echo, git, gitlint from qa.base import BaseTestCase -from qa.utils import sstr class CommitsTests(BaseTestCase): @@ -16,10 +15,10 @@ class CommitsTests(BaseTestCase): def test_successful(self): """ Test linting multiple commits without violations """ git("checkout", "-b", "test-branch-commits-base", _cwd=self.tmp_git_repo) - self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit") + self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit") git("checkout", "-b", "test-branch-commits", _cwd=self.tmp_git_repo) - self.create_simple_commit(u"Sïmple title2\n\nSimple bödy describing the commit2") - self.create_simple_commit(u"Sïmple title3\n\nSimple bödy describing the commit3") + self.create_simple_commit("Sïmple title2\n\nSimple bödy describing the commit2") + self.create_simple_commit("Sïmple title3\n\nSimple bödy describing the commit3") output = gitlint("--commits", "test-branch-commits-base...test-branch-commits", _cwd=self.tmp_git_repo, _tty_in=True) self.assertEqualStdout(output, "") @@ -27,12 +26,12 @@ class CommitsTests(BaseTestCase): def test_violations(self): """ Test linting multiple commits with violations """ git("checkout", "-b", "test-branch-commits-violations-base", _cwd=self.tmp_git_repo) - self.create_simple_commit(u"Sïmple title.\n") + self.create_simple_commit("Sïmple title.\n") git("checkout", "-b", "test-branch-commits-violations", _cwd=self.tmp_git_repo) - self.create_simple_commit(u"Sïmple title2.\n") + self.create_simple_commit("Sïmple title2.\n") commit_sha1 = self.get_last_commit_hash()[:10] - self.create_simple_commit(u"Sïmple title3.\n") + self.create_simple_commit("Sïmple title3.\n") commit_sha2 = self.get_last_commit_hash()[:10] output = gitlint("--commits", "test-branch-commits-violations-base...test-branch-commits-violations", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4]) @@ -43,14 +42,14 @@ class CommitsTests(BaseTestCase): def test_lint_single_commit(self): """ Tests `gitlint --commits <sha>` """ - self.create_simple_commit(u"Sïmple title.\n") - self.create_simple_commit(u"Sïmple title2.\n") + self.create_simple_commit("Sïmple title.\n") + self.create_simple_commit("Sïmple title2.\n") commit_sha = self.get_last_commit_hash() - refspec = "{0}^...{0}".format(commit_sha) - self.create_simple_commit(u"Sïmple title3.\n") + refspec = f"{commit_sha}^...{commit_sha}" + self.create_simple_commit("Sïmple title3.\n") output = gitlint("--commits", refspec, _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2]) - expected = (u"1: T3 Title has trailing punctuation (.): \"Sïmple title2.\"\n" + - u"3: B6 Body message is missing\n") + expected = ("1: T3 Title has trailing punctuation (.): \"Sïmple title2.\"\n" + + "3: B6 Body message is missing\n") self.assertEqual(output.exit_code, 2) self.assertEqualStdout(output, expected) @@ -61,7 +60,7 @@ class CommitsTests(BaseTestCase): echo "WIP: Pïpe test." | gitlint --staged --debug """ # Create a commit first, before we stage changes. This ensures the repo is properly initialized. - self.create_simple_commit(u"Sïmple title.\n") + self.create_simple_commit("Sïmple title.\n") # Add some files, stage them: they should show up in the debug output as changed file filename1 = self.create_file(self.tmp_git_repo) @@ -69,12 +68,12 @@ class CommitsTests(BaseTestCase): filename2 = self.create_file(self.tmp_git_repo) git("add", filename2, _cwd=self.tmp_git_repo) - output = gitlint(echo(u"WIP: Pïpe test."), "--staged", "--debug", + output = gitlint(echo("WIP: Pïpe test."), "--staged", "--debug", _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3]) # Determine variable parts of expected output expected_kwargs = self.get_debug_vars_last_commit() - expected_kwargs.update({'changed_files': sstr(sorted([filename1, filename2]))}) + expected_kwargs.update({'changed_files': sorted([filename1, filename2])}) # It's not really possible to determine the "Date: ..." line that is part of the debug output as this date # is not taken from git but instead generated by gitlint itself. As a workaround, we extract the date from the @@ -95,7 +94,7 @@ class CommitsTests(BaseTestCase): gitlint --msg-filename /tmp/my-commit-msg --staged --debug """ # Create a commit first, before we stage changes. This ensures the repo is properly initialized. - self.create_simple_commit(u"Sïmple title.\n") + self.create_simple_commit("Sïmple title.\n") # Add some files, stage them: they should show up in the debug output as changed file filename1 = self.create_file(self.tmp_git_repo) @@ -103,14 +102,14 @@ class CommitsTests(BaseTestCase): filename2 = self.create_file(self.tmp_git_repo) git("add", filename2, _cwd=self.tmp_git_repo) - tmp_commit_msg_file = self.create_tmpfile(u"WIP: from fïle test.") + tmp_commit_msg_file = self.create_tmpfile("WIP: from fïle test.") output = gitlint("--msg-filename", tmp_commit_msg_file, "--staged", "--debug", _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3]) # Determine variable parts of expected output expected_kwargs = self.get_debug_vars_last_commit() - expected_kwargs.update({'changed_files': sstr(sorted([filename1, filename2]))}) + expected_kwargs.update({'changed_files': sorted([filename1, filename2])}) # It's not really possible to determine the "Date: ..." line that is part of the debug output as this date # is not taken from git but instead generated by gitlint itself. As a workaround, we extract the date from the @@ -128,9 +127,9 @@ class CommitsTests(BaseTestCase): def test_lint_head(self): """ Testing whether we can also recognize special refs like 'HEAD' """ tmp_git_repo = self.create_tmp_git_repo() - self.create_simple_commit(u"Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo) - self.create_simple_commit(u"Sïmple title", git_repo=tmp_git_repo) - self.create_simple_commit(u"WIP: Sïmple title\n\nSimple bödy describing the commit", git_repo=tmp_git_repo) + self.create_simple_commit("Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo) + self.create_simple_commit("Sïmple title", git_repo=tmp_git_repo) + self.create_simple_commit("WIP: Sïmple title\n\nSimple bödy describing the commit", git_repo=tmp_git_repo) output = gitlint("--commits", "HEAD", _cwd=tmp_git_repo, _tty_in=True, _ok_code=[3]) revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split() @@ -143,14 +142,14 @@ class CommitsTests(BaseTestCase): """ Tests multiple commits of which some rules get igonored because of ignore-* rules """ # Create repo and some commits tmp_git_repo = self.create_tmp_git_repo() - self.create_simple_commit(u"Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo) + self.create_simple_commit("Sïmple title.\n\nSimple bödy describing the commit", git_repo=tmp_git_repo) # Normally, this commit will give T3 (trailing-punctuation), T5 (WIP) and B5 (bod-too-short) violations # But in this case only B5 because T3 and T5 are being ignored because of config - self.create_simple_commit(u"Release: WIP tïtle.\n\nShort", git_repo=tmp_git_repo) + self.create_simple_commit("Release: WIP tïtle.\n\nShort", git_repo=tmp_git_repo) # In the following 2 commits, the T3 violations are as normal self.create_simple_commit( - u"Sïmple WIP title3.\n\nThis is \ta relëase commit\nMore info", git_repo=tmp_git_repo) - self.create_simple_commit(u"Sïmple title4.\n\nSimple bödy describing the commit4", git_repo=tmp_git_repo) + "Sïmple WIP title3.\n\nThis is \ta relëase commit\nMore info", git_repo=tmp_git_repo) + self.create_simple_commit("Sïmple title4.\n\nSimple bödy describing the commit4", git_repo=tmp_git_repo) revlist = git("rev-list", "HEAD", _tty_in=True, _cwd=tmp_git_repo).split() config_path = self.get_sample_path("config/ignore-release-commits") diff --git a/qa/test_config.py b/qa/test_config.py index 9415990..9c00b95 100644 --- a/qa/test_config.py +++ b/qa/test_config.py @@ -5,30 +5,30 @@ import re from qa.shell import gitlint from qa.base import BaseTestCase -from qa.utils import sstr, ustr +from qa.utils import DEFAULT_ENCODING class ConfigTests(BaseTestCase): """ Integration tests for gitlint configuration and configuration precedence. """ def test_ignore_by_id(self): - self.create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line") + self.create_simple_commit("WIP: Thïs is a title.\nContënt on the second line") output = gitlint("--ignore", "T5,B4", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[1]) - expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n" + expected = "1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n" self.assertEqualStdout(output, expected) def test_ignore_by_name(self): - self.create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line") + self.create_simple_commit("WIP: Thïs is a title.\nContënt on the second line") output = gitlint("--ignore", "title-must-not-contain-word,body-first-line-empty", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]) - expected = u"1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n" + expected = "1: T3 Title has trailing punctuation (.): \"WIP: Thïs is a title.\"\n" self.assertEqualStdout(output, expected) def test_verbosity(self): - self.create_simple_commit(u"WIP: Thïs is a title.\nContënt on the second line") + self.create_simple_commit("WIP: Thïs is a title.\nContënt on the second line") output = gitlint("-v", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3]) - expected = u"1: T3\n1: T5\n2: B4\n" + expected = "1: T3\n1: T5\n2: B4\n" self.assertEqualStdout(output, expected) output = gitlint("-vv", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3]) @@ -42,12 +42,12 @@ class ConfigTests(BaseTestCase): self.assertEqualStdout(output, "") def test_set_rule_option(self): - self.create_simple_commit(u"This ïs a title.") + self.create_simple_commit("This ïs a title.") output = gitlint("-c", "title-max-length.line-length=5", _tty_in=True, _cwd=self.tmp_git_repo, _ok_code=[3]) self.assertEqualStdout(output, self.get_expected("test_config/test_set_rule_option_1")) def test_config_from_file(self): - commit_msg = u"WIP: Thïs is a title thåt is a bit longer.\nContent on the second line\n" + \ + commit_msg = "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" self.create_simple_commit(commit_msg) config_path = self.get_sample_path("config/gitlintconfig") @@ -58,14 +58,14 @@ class ConfigTests(BaseTestCase): # 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" + \ + commit_msg = "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) config_path = self.get_sample_path("config/gitlintconfig") output = gitlint("--config", config_path, "--debug", _cwd=target_repo, _tty_in=True, _ok_code=[5]) expected_kwargs = self.get_debug_vars_last_commit(git_repo=target_repo) - expected_kwargs.update({'config_path': config_path, 'changed_files': sstr([filename])}) + expected_kwargs.update({'config_path': config_path, 'changed_files': [filename]}) self.assertEqualStdout(output, self.get_expected("test_config/test_config_from_file_debug_1", expected_kwargs)) @@ -75,7 +75,7 @@ class ConfigTests(BaseTestCase): # 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" + \ + commit_msg = "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", @@ -84,12 +84,12 @@ class ConfigTests(BaseTestCase): "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])}) + expected_kwargs.update({'changed_files': [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.") + tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename test.") env = self.create_environment({"GITLINT_DEBUG": "1", "GITLINT_TARGET": target_repo, "GITLINT_SILENT": "1", "GITLINT_STAGED": "1"}) @@ -99,7 +99,7 @@ class ConfigTests(BaseTestCase): # 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)) + result = p.search(output.stdout.decode(DEFAULT_ENCODING)) date = result.group(1).strip() expected_kwargs.update({"date": date}) diff --git a/qa/test_contrib.py b/qa/test_contrib.py index e2b4bc5..e599d50 100644 --- a/qa/test_contrib.py +++ b/qa/test_contrib.py @@ -8,19 +8,19 @@ class ContribRuleTests(BaseTestCase): """ Integration tests for contrib rules.""" def test_contrib_rules(self): - self.create_simple_commit(u"WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars") + self.create_simple_commit("WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars") output = gitlint("--contrib", "contrib-title-conventional-commits,CC1", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4]) self.assertEqualStdout(output, self.get_expected("test_contrib/test_contrib_rules_1")) def test_contrib_rules_with_config(self): - self.create_simple_commit(u"WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars") + self.create_simple_commit("WIP Thi$ is å title\n\nMy bödy that is a bit longer than 20 chars") output = gitlint("--contrib", "contrib-title-conventional-commits,CC1", - "-c", u"contrib-title-conventional-commits.types=föo,bår", + "-c", "contrib-title-conventional-commits.types=föo,bår", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[4]) self.assertEqualStdout(output, self.get_expected("test_contrib/test_contrib_rules_with_config_1")) def test_invalid_contrib_rules(self): self.create_simple_commit("WIP: test") - output = gitlint("--contrib", u"föobar,CC1", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[255]) - self.assertEqualStdout(output, u"Config Error: No contrib rule with id or name 'föobar' found.\n") + output = gitlint("--contrib", "föobar,CC1", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[255]) + self.assertEqualStdout(output, "Config Error: No contrib rule with id or name 'föobar' found.\n") diff --git a/qa/test_gitlint.py b/qa/test_gitlint.py index 2e837b9..0200d76 100644 --- a/qa/test_gitlint.py +++ b/qa/test_gitlint.py @@ -12,7 +12,7 @@ class IntegrationTests(BaseTestCase): def test_successful(self): # Test for STDIN with and without a TTY attached - self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit") + self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit") output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True) self.assertEqualStdout(output, "") @@ -22,26 +22,26 @@ class IntegrationTests(BaseTestCase): # Different commentchar (Note: tried setting this to a special unicode char, but git doesn't like that) git("config", "--add", "core.commentchar", "$", _cwd=self.tmp_git_repo) - self.create_simple_commit(u"Sïmple title\n\nSimple bödy describing the commit\n$after commentchar\t ignored") + self.create_simple_commit("Sïmple title\n\nSimple bödy describing the commit\n$after commentchar\t ignored") output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _err_to_out=True) self.assertEqualStdout(output, "") def test_successful_merge_commit(self): # Create branch on master - self.create_simple_commit(u"Cömmit on master\n\nSimple bödy") + self.create_simple_commit("Cömmit on master\n\nSimple bödy") # Create test branch, add a commit and determine the commit hash git("checkout", "-b", "test-branch", _cwd=self.tmp_git_repo) git("checkout", "test-branch", _cwd=self.tmp_git_repo) - commit_title = u"Commit on test-brånch with a pretty long title that will cause issues when merging" - self.create_simple_commit(u"{0}\n\nSïmple body".format(commit_title)) + commit_title = "Commit on test-brånch with a pretty long title that will cause issues when merging" + self.create_simple_commit(f"{commit_title}\n\nSïmple body") hash = self.get_last_commit_hash() # Checkout master and merge the commit # We explicitly set the title of the merge commit to the title of the previous commit as this or similar # behavior is what many tools do that handle merges (like github, gerrit, etc). git("checkout", "master", _cwd=self.tmp_git_repo) - git("merge", "--no-ff", "-m", u"Merge '{0}'".format(commit_title), hash, _cwd=self.tmp_git_repo) + git("merge", "--no-ff", "-m", f"Merge '{commit_title}'", hash, _cwd=self.tmp_git_repo) # Run gitlint and assert output is empty output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True) @@ -50,14 +50,13 @@ class IntegrationTests(BaseTestCase): # Assert that we do see the error if we disable the ignore-merge-commits option output = gitlint("-c", "general.ignore-merge-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]) self.assertEqual(output.exit_code, 1) - self.assertEqualStdout(output, - u"1: T1 Title exceeds max length (90>72): \"Merge '{0}'\"\n".format(commit_title)) + self.assertEqualStdout(output, f"1: T1 Title exceeds max length (90>72): \"Merge '{commit_title}'\"\n") def test_fixup_commit(self): # Create a normal commit and assert that it has a violation - test_filename = self.create_simple_commit(u"Cömmit on WIP master\n\nSimple bödy that is long enough") + test_filename = self.create_simple_commit("Cömmit on WIP master\n\nSimple bödy that is long enough") output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]) - expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n" + expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n" self.assertEqualStdout(output, expected) # Make a small modification to the commit and commit it using fixup commit @@ -66,7 +65,7 @@ class IntegrationTests(BaseTestCase): # https://stackoverflow.com/questions/22392377/ # error-writing-a-file-with-file-write-in-python-unicodeencodeerror # So just keeping it simple - ASCII will here - fh.write(u"Appending some stuff\n") + fh.write("Appending some stuff\n") git("add", test_filename, _cwd=self.tmp_git_repo) @@ -79,13 +78,13 @@ class IntegrationTests(BaseTestCase): # Make sure that if we set the ignore-fixup-commits option to false that we do still see the violations output = gitlint("-c", "general.ignore-fixup-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2]) - expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"fixup! Cömmit on WIP master\"\n" + \ - u"3: B6 Body message is missing\n" + expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"fixup! Cömmit on WIP master\"\n" + \ + "3: B6 Body message is missing\n" self.assertEqualStdout(output, expected) def test_revert_commit(self): - self.create_simple_commit(u"WIP: Cömmit on master.\n\nSimple bödy") + self.create_simple_commit("WIP: Cömmit on master.\n\nSimple bödy") hash = self.get_last_commit_hash() git("revert", hash, _cwd=self.tmp_git_repo) @@ -97,14 +96,14 @@ class IntegrationTests(BaseTestCase): output = gitlint("-c", "general.ignore-revert-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]) self.assertEqual(output.exit_code, 1) - expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Revert \"WIP: Cömmit on master.\"\"\n" + expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Revert \"WIP: Cömmit on master.\"\"\n" self.assertEqualStdout(output, expected) def test_squash_commit(self): # Create a normal commit and assert that it has a violation - test_filename = self.create_simple_commit(u"Cömmit on WIP master\n\nSimple bödy that is long enough") + test_filename = self.create_simple_commit("Cömmit on WIP master\n\nSimple bödy that is long enough") output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[1]) - expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n" + expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"Cömmit on WIP master\"\n" self.assertEqualStdout(output, expected) # Make a small modification to the commit and commit it using squash commit @@ -113,11 +112,11 @@ class IntegrationTests(BaseTestCase): # https://stackoverflow.com/questions/22392377/ # error-writing-a-file-with-file-write-in-python-unicodeencodeerror # So just keeping it simple - ASCII will here - fh.write(u"Appending some stuff\n") + fh.write("Appending some stuff\n") git("add", test_filename, _cwd=self.tmp_git_repo) - git("commit", "--squash", self.get_last_commit_hash(), "-m", u"Töo short body", _cwd=self.tmp_git_repo) + git("commit", "--squash", self.get_last_commit_hash(), "-m", "Töo short body", _cwd=self.tmp_git_repo) # Assert that gitlint does not show an error for the fixup commit output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True) @@ -127,25 +126,25 @@ class IntegrationTests(BaseTestCase): # Make sure that if we set the ignore-squash-commits option to false that we do still see the violations output = gitlint("-c", "general.ignore-squash-commits=false", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[2]) - expected = u"1: T5 Title contains the word 'WIP' (case-insensitive): \"squash! Cömmit on WIP master\"\n" + \ - u"3: B5 Body message is too short (14<20): \"Töo short body\"\n" + expected = "1: T5 Title contains the word 'WIP' (case-insensitive): \"squash! Cömmit on WIP master\"\n" + \ + "3: B5 Body message is too short (14<20): \"Töo short body\"\n" self.assertEqualStdout(output, expected) def test_violations(self): - commit_msg = u"WIP: This ïs a title.\nContent on the sëcond line" + commit_msg = "WIP: This ïs a title.\nContent on the sëcond line" self.create_simple_commit(commit_msg) output = gitlint(_cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[3]) self.assertEqualStdout(output, self.get_expected("test_gitlint/test_violations_1")) def test_msg_filename(self): - tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename test.") + tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename test.") output = gitlint("--msg-filename", tmp_commit_msg_file, _tty_in=True, _ok_code=[3]) self.assertEqualStdout(output, self.get_expected("test_gitlint/test_msg_filename_1")) def test_msg_filename_no_tty(self): """ Make sure --msg-filename option also works with no TTY attached """ - tmp_commit_msg_file = self.create_tmpfile(u"WIP: msg-fïlename NO TTY test.") + tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO TTY test.") # We need to set _err_to_out explicitly for sh to merge stdout and stderr output in case there's # no TTY attached to STDIN @@ -159,24 +158,24 @@ class IntegrationTests(BaseTestCase): 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.") + tmp_commit_msg_file = self.create_tmpfile("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") + env = self.create_tmp_git_config("[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" + expected = "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") + tmp_commit_msg_file = self.create_tmpfile("WIP: msg-fïlename NO email test.") + env = self.create_tmp_git_config("[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" + expected = "Missing git configuration: please set user.email\n" self.assertEqualStdout(output, expected) def test_git_errors(self): @@ -184,10 +183,10 @@ class IntegrationTests(BaseTestCase): empty_git_repo = self.create_tmp_git_repo() output = gitlint(_cwd=empty_git_repo, _tty_in=True, _ok_code=[self.GIT_CONTEXT_ERROR_CODE]) - expected = u"Current branch has no commits. Gitlint requires at least one commit to function.\n" + expected = "Current branch has no commits. Gitlint requires at least one commit to function.\n" self.assertEqualStdout(output, expected) # Repo has no commits: caused by `git rev-parse` - output = gitlint(echo(u"WIP: Pïpe test."), "--staged", _cwd=empty_git_repo, _tty_in=False, + output = gitlint(echo("WIP: Pïpe test."), "--staged", _cwd=empty_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[self.GIT_CONTEXT_ERROR_CODE]) self.assertEqualStdout(output, expected) diff --git a/qa/test_hooks.py b/qa/test_hooks.py index 7c07a61..80ccbf6 100644 --- a/qa/test_hooks.py +++ b/qa/test_hooks.py @@ -24,18 +24,18 @@ class HookTests(BaseTestCase): # The '--staged' flag used in the commit-msg hook fetches additional information from the underlying # git repo which means there already needs to be a commit in the repo # (as gitlint --staged doesn't work against empty repos) - self.create_simple_commit(u"Commït Title\n\nCommit Body explaining commit.") + self.create_simple_commit("Commït Title\n\nCommit Body explaining commit.") # install git commit-msg hook and assert output output_installed = gitlint("install-hook", _cwd=self.tmp_git_repo) - expected_installed = u"Successfully installed gitlint commit-msg hook in %s/.git/hooks/commit-msg\n" % \ + expected_installed = "Successfully installed gitlint commit-msg hook in %s/.git/hooks/commit-msg\n" % \ self.tmp_git_repo self.assertEqualStdout(output_installed, expected_installed) def tearDown(self): # uninstall git commit-msg hook and assert output output_uninstalled = gitlint("uninstall-hook", _cwd=self.tmp_git_repo) - expected_uninstalled = u"Successfully uninstalled gitlint commit-msg hook from %s/.git/hooks/commit-msg\n" % \ + expected_uninstalled = "Successfully uninstalled gitlint commit-msg hook from %s/.git/hooks/commit-msg\n" % \ self.tmp_git_repo self.assertEqualStdout(output_uninstalled, expected_uninstalled) @@ -50,24 +50,24 @@ class HookTests(BaseTestCase): # Answer 'yes' to question to keep violating commit-msg if "Your commit message contains the above violations" in line: response = self.responses[self.response_index] - stdin.put("{0}\n".format(response)) + stdin.put(f"{response}\n") 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", + test_filename = self.create_simple_commit("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, + "[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] + " 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", + test_filename = self.create_simple_commit("WIP: This ïs a title.\nContënt on the second line", out=self._interact, tty_in=True) # Determine short commit-msg hash, needed to determine expected output @@ -76,10 +76,10 @@ class HookTests(BaseTestCase): expected_output = self._violations() expected_output += ["Continue with commit anyways (this keeps the current commit message)? " + "[y(es)/n(no)/e(dit)] " + - u"[master %s] WIP: This ïs a title. Contënt on the second line\n" + "[master %s] WIP: This ïs a title. Contënt on the second line\n" % short_hash, " 1 file changed, 0 insertions(+), 0 deletions(-)\n", - u" create mode 100644 %s\n" % test_filename] + " create mode 100644 %s\n" % test_filename] assert len(self.githook_output) == len(expected_output) for output, expected in zip(self.githook_output, expected_output): @@ -89,7 +89,7 @@ class HookTests(BaseTestCase): def test_commit_hook_abort(self): self.responses = ["n"] - test_filename = self.create_simple_commit(u"WIP: This ïs a title.\nContënt on the second line", + test_filename = self.create_simple_commit("WIP: This ïs a title.\nContënt on the second line", out=self._interact, ok_code=1, tty_in=True) git("rm", "-f", test_filename, _cwd=self.tmp_git_repo) @@ -101,8 +101,8 @@ class HookTests(BaseTestCase): "Commit aborted.\n", "Your commit message: \n", "-----------------------------------------------\n", - u"WIP: This ïs a title.\n", - u"Contënt on the second line\n", + "WIP: This ïs a title.\n", + "Contënt on the second line\n", "-----------------------------------------------\n"] self.assertListEqual(expected_output, self.githook_output) @@ -110,7 +110,7 @@ class HookTests(BaseTestCase): def test_commit_hook_edit(self): self.responses = ["e", "y"] env = {"EDITOR": ":"} - test_filename = self.create_simple_commit(u"WIP: This ïs a title.\nContënt on the second line", + test_filename = self.create_simple_commit("WIP: This ïs a title.\nContënt on the second line", out=self._interact, env=env, tty_in=True) git("rm", "-f", test_filename, _cwd=self.tmp_git_repo) @@ -124,9 +124,9 @@ class HookTests(BaseTestCase): expected_output += self._violations()[1:] expected_output += ['Continue with commit anyways (this keeps the current commit message)? ' + "[y(es)/n(no)/e(dit)] " + - u"[master %s] WIP: This ïs a title. Contënt on the second line\n" % short_hash, + "[master %s] WIP: This ïs a title. Contënt on the second line\n" % short_hash, " 1 file changed, 0 insertions(+), 0 deletions(-)\n", - u" create mode 100644 %s\n" % test_filename] + " create mode 100644 %s\n" % test_filename] assert len(self.githook_output) == len(expected_output) for output, expected in zip(self.githook_output, expected_output): @@ -147,7 +147,7 @@ class HookTests(BaseTestCase): ``` """ tmp_git_repo = self.create_tmp_git_repo() - self.create_simple_commit(u"Simple title\n\nContënt in the body", git_repo=tmp_git_repo) + self.create_simple_commit("Simple title\n\nContënt in the body", git_repo=tmp_git_repo) worktree_dir = self.generate_temp_path() self.tmp_git_repos.append(worktree_dir) # make sure we clean up the worktree afterwards @@ -156,10 +156,10 @@ class HookTests(BaseTestCase): output_installed = gitlint("install-hook", _cwd=worktree_dir) expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg") - expected_msg = "Successfully installed gitlint commit-msg hook in {0}\n".format(expected_hook_path) + expected_msg = f"Successfully installed gitlint commit-msg hook in {expected_hook_path}\r\n" self.assertEqual(output_installed, expected_msg) output_uninstalled = gitlint("uninstall-hook", _cwd=worktree_dir) expected_hook_path = os.path.join(tmp_git_repo, ".git", "hooks", "commit-msg") - expected_msg = "Successfully uninstalled gitlint commit-msg hook from {0}\n".format(expected_hook_path) + expected_msg = f"Successfully uninstalled gitlint commit-msg hook from {expected_hook_path}\r\n" self.assertEqual(output_uninstalled, expected_msg) diff --git a/qa/test_named_rules.py b/qa/test_named_rules.py index 6020bbf..92e968b 100644 --- a/qa/test_named_rules.py +++ b/qa/test_named_rules.py @@ -7,14 +7,14 @@ 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" + commit_msg = "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" + commit_msg = "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") diff --git a/qa/test_stdin.py b/qa/test_stdin.py index fff636f..18d6e7e 100644 --- a/qa/test_stdin.py +++ b/qa/test_stdin.py @@ -4,7 +4,7 @@ import io import subprocess from qa.shell import echo, gitlint from qa.base import BaseTestCase -from qa.utils import ustr, DEFAULT_ENCODING +from qa.utils import DEFAULT_ENCODING class StdInTests(BaseTestCase): @@ -18,7 +18,7 @@ class StdInTests(BaseTestCase): # NOTE: There is no use in testing this with _tty_in=True, because if you pipe something into a command # there never is a TTY connected to stdin (per definition). We're setting _tty_in=False here to be explicit # but note that this is always true when piping something into a command. - output = gitlint(echo(u"WIP: Pïpe test."), + output = gitlint(echo("WIP: Pïpe test."), _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3]) self.assertEqualStdout(output, self.get_expected("test_stdin/test_stdin_pipe_1")) @@ -28,7 +28,7 @@ class StdInTests(BaseTestCase): This is the equivalent of doing: $ echo -n "" | gitlint """ - commit_msg = u"WIP: This ïs a title.\nContent on the sëcond line" + commit_msg = "WIP: This ïs a title.\nContent on the sëcond line" self.create_simple_commit(commit_msg) # We need to set _err_to_out explicitly for sh to merge stdout and stderr output in case there's @@ -36,21 +36,21 @@ class StdInTests(BaseTestCase): # http://amoffat.github.io/sh/sections/special_arguments.html?highlight=_tty_in#err-to-out output = gitlint(echo("-n", ""), _cwd=self.tmp_git_repo, _tty_in=False, _err_to_out=True, _ok_code=[3]) - self.assertEqual(ustr(output), self.get_expected("test_stdin/test_stdin_pipe_empty_1")) + self.assertEqual(output, self.get_expected("test_stdin/test_stdin_pipe_empty_1")) def test_stdin_file(self): """ Test the scenario where STDIN is a regular file (stat.S_ISREG = True) This is the equivalent of doing: $ gitlint < myfile """ - tmp_commit_msg_file = self.create_tmpfile(u"WIP: STDIN ïs a file test.") + tmp_commit_msg_file = self.create_tmpfile("WIP: STDIN ïs a file test.") with io.open(tmp_commit_msg_file, encoding=DEFAULT_ENCODING) as file_handle: # We need to use subprocess.Popen() here instead of sh because when passing a file_handle to sh, it will # deal with reading the file itself instead of passing it on to gitlint as a STDIN. Since we're trying to # test for the condition where stat.S_ISREG == True that won't work for us here. - p = subprocess.Popen(u"gitlint", stdin=file_handle, cwd=self.tmp_git_repo, + p = subprocess.Popen("gitlint", stdin=file_handle, cwd=self.tmp_git_repo, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output, _ = p.communicate() - self.assertEqual(ustr(output), self.get_expected("test_stdin/test_stdin_file_1")) + self.assertEqual(output.decode(DEFAULT_ENCODING), self.get_expected("test_stdin/test_stdin_file_1")) diff --git a/qa/test_user_defined.py b/qa/test_user_defined.py index 566d0b2..378ab36 100644 --- a/qa/test_user_defined.py +++ b/qa/test_user_defined.py @@ -10,7 +10,7 @@ class UserDefinedRuleTests(BaseTestCase): 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" + commit_msg = "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")) @@ -18,7 +18,7 @@ class UserDefinedRuleTests(BaseTestCase): 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" + commit_msg = "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")) @@ -26,7 +26,7 @@ class UserDefinedRuleTests(BaseTestCase): 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" + commit_msg = "WIP: Thi$ is å title\nContent on the second line" self.create_simple_commit(commit_msg) output = gitlint("--extra-path", extra_path, "-c", "body-max-line-count.max-line-count=1", _cwd=self.tmp_git_repo, _tty_in=True, _ok_code=[6]) @@ -35,7 +35,7 @@ class UserDefinedRuleTests(BaseTestCase): def test_user_defined_rules_extra(self): extra_path = self.get_sample_path("user_rules/extra") - commit_msg = u"WIP: Thi$ is å title\nContent on the second line" + commit_msg = "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=[9]) self.assertEqualStdout(output, self.get_expected("test_user_defined/test_user_defined_rules_extra_1", diff --git a/qa/utils.py b/qa/utils.py index f44917e..c75872b 100644 --- a/qa/utils.py +++ b/qa/utils.py @@ -1,6 +1,5 @@ # pylint: disable=bad-option-value,unidiomatic-typecheck,undefined-variable,no-else-return import platform -import sys import os import locale @@ -16,16 +15,6 @@ 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 # On windows we won't want to use the sh library since it's not supported - instead we'll use our own shell module. @@ -71,41 +60,3 @@ def getpreferredencoding(): DEFAULT_ENCODING = getpreferredencoding() - -######################################################################################################################## -# Unicode utility functions - - -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 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 - return unicode(obj, DEFAULT_ENCODING) # pragma: no cover # noqa - else: - return unicode(obj) # pragma: no cover # noqa - else: - if type(obj) in [bytes]: - return obj.decode(DEFAULT_ENCODING) - else: - return str(obj) - - -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 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: - return obj # pragma: no cover - -######################################################################################################################## diff --git a/requirements.txt b/requirements.txt index e8d531b..0a164bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ setuptools -wheel==0.33.4 -Click==7.0 -sh==1.12.14; sys_platform != 'win32' # sh is not supported on windows -arrow==0.15.5; +wheel==0.35.1 +Click==7.1.2 +sh==1.14.1; sys_platform != 'win32' # sh is not supported on windows +arrow==0.17.0 diff --git a/run_tests.sh b/run_tests.sh index e558b3b..2f8ebe9 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -13,7 +13,7 @@ help(){ echo " -b, --build Run build tests" echo " -a, --all Run all tests and checks (unit, integration, pep8, git)" echo " -e, --envs [ENV1],[ENV2] Run tests against specified python environments" - echo " (envs: 27,35,36,37,pypy2,pypy35)." + echo " (envs: 36,37,38,39,pypy37)." echo " Also works for integration, pep8 and lint tests." echo " -C, --container Run the specified command in the container for the --envs specified" echo " --all-env Run all tests against all python environments" @@ -261,9 +261,7 @@ install_virtualenv(){ # For pypy: custom path + fetch from the web if not installed (=distro agnostic) if [[ $version == *"pypy"* ]]; then pypy_download_mirror="https://downloads.python.org/pypy" - if [[ $version == *"pypy2"* ]]; then - pypy_full_version="pypy2.7-v7.3.2-linux64" - elif [[ $version == *"pypy36"* ]]; then + if [[ $version == *"pypy36"* ]]; then pypy_full_version="pypy3.6-v7.3.2-linux64" elif [[ $version == *"pypy37"* ]]; then pypy_full_version="pypy3.7-v7.3.2-linux64" @@ -365,7 +363,7 @@ uninstall_container(){ assert_specific_env(){ if [ -z "$1" ] || [ "$1" == "default" ]; then - fatal "ERROR: Please specify one or more valid python environments using --envs: 27,35,36,37,pypy2,pypy35" + fatal "ERROR: Please specify one or more valid python environments using --envs: 36,37,38,39,pypy37" exit 1 fi } @@ -461,7 +459,7 @@ exit_code=0 # If the users specified 'all', then just replace $envs with the list of all envs if [ "$envs" == "all" ]; then - envs="27,35,36,37,38,39,pypy2,pypy35" + envs="36,37,38,39,pypy37" fi original_envs="$envs" envs=$(echo "$envs" | tr ',' '\n') # Split the env list on comma so we can loop through it @@ -7,13 +7,6 @@ import os import platform import sys -# There is an issue with building python packages in a shared vagrant directory because of how setuptools works -# in python < 2.7.9. We solve this by deleting the filesystem hardlinking capability during build. -# See: http://stackoverflow.com/a/22147112/381010 -try: - del os.link -except: - pass # Not all OSes (e.g. windows) support os.link description = "Git commit message linter written in python, checks your commit messages for style." long_description = """ @@ -52,8 +45,6 @@ setup( "Development Status :: 5 - Production/Stable", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", @@ -66,18 +57,23 @@ setup( "Topic :: Software Development :: Testing", "License :: OSI Approved :: MIT License" ], + python_requires=">=3.6", install_requires=[ - 'Click==7.0', - 'arrow==0.15.5', + 'Click==7.1.2', + 'arrow==0.17.0', ], extras_require={ ':sys_platform != "win32"': [ - 'sh==1.12.14', + 'sh==1.14.1', ], }, keywords='gitlint git lint', author='Joris Roovers', - url='https://github.com/jorisroovers/gitlint', + url='https://jorisroovers.github.io/gitlint', + project_urls={ + 'Documentation': 'https://jorisroovers.github.io/gitlint', + 'Source': 'https://github.com/jorisroovers/gitlint', + }, license='MIT', package_data={ 'gitlint': ['files/*'] @@ -93,8 +89,7 @@ setup( # Print a red deprecation warning for python < 3.6 users if sys.version_info[:2] < (3, 6): msg = "\033[31mDEPRECATION: You're using a python version that has reached end-of-life. " + \ - "Gitlint does not support Python < 3.5 or < 2.7, and will be dropping support for " + \ - "Python 2.7 and 3.5 in the next release. " + \ + "Gitlint does not support Python < 3.6" + \ "Please upgrade your Python to 3.6 or above.\033[0m" print(msg) diff --git a/test-requirements.txt b/test-requirements.txt index 3afab45..10ca287 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,10 +1,8 @@ -unittest2==1.1.0; python_version <= '2.7' -flake8==3.7.9 -coverage==4.5.3 +flake8==3.8.4 +coverage==5.3 python-coveralls==2.9.2 -radon==4.1.0 -mock==3.0.5 # mock 4.x no longer supports Python 2.7 -pytest==4.6.3; # pytest 5.x no longer supports Python 2.7 -pylint==1.9.4; python_version == '2.7' -pylint==2.3.1; python_version >= '3.4' +radon==4.3.2 +flake8-polyfill==1.0.2 # Required when installing both flake8 and radon>=4.3.1 +pytest==6.1.2; +pylint==2.6.0; -e . |