diff --git a/CHANGELOG.md b/CHANGELOG.md
index 982a29bd..3b782c9d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
+
+* Introduction of autohooks modes. Modes configure how to handle loading
+ autohooks, plugins and dependencies when running the git hook. The
+ `pythonpath` mode requires to put the necessary python packages into the
+ **PYTHONPATH** manually. The `pipenv` mode uses pipenv to handle the
+ dependencies. Using the `pipenv` mode is recommended.
+ [#24](https://github.com/greenbone/autohooks/pull/24)
+
### Changed
+
+* The installed git hook will fail now if autohooks can't be loaded. Before the
+ git hook raised only a warning and was ignored. This a major change compared
+ to the previous versions. To update existing installations it requires
+ overriding the installed git hook by running `autohooks activate --force`.
+ [#24](https://github.com/greenbone/autohooks/pull/24)
+
### Deprecated
### Fixed
### Removed
diff --git a/README.md b/README.md
index 9b748179..22bf27a4 100644
--- a/README.md
+++ b/README.md
@@ -9,11 +9,13 @@ in Python
- [Why?](#why)
- [Solution](#solution)
+- [Modes](#modes)
+ - [Pythonpath mode](#pythonpath-mode)
+ - [Pipenv mode](#pipenv-mode)
- [Installation](#installation)
- [Install autohooks python package](#install-autohooks-python-package)
+ - [Configure mode and plugins to be run](#configure-mode-and-plugins-to-be-run)
- [Activating the git hooks](#activating-the-git-hooks)
- - [Configure plugins to be run](#configure-plugins-to-be-run)
-- [Proposed Workflow](#proposed-workflow)
- [Plugins](#plugins)
- [How-to write a Plugin](#how-to-write-a-plugin)
- [Linting plugin](#linting-plugin)
@@ -39,26 +41,65 @@ itself and doesn't install in the current environment.
Autohooks is a pure python library that installs a minimal
[executable git hook](https://github.com/greenbone/autohooks/blob/master/autohooks/precommit/template).
-If autohooks isn't installed in your current python path the hooks aren't
-executed. So autohooks is always opt-in by installing the package into your
-current development environment. It would be even possible to run different
-versions of autohooks by switching the environment.
+It allows you to decide how to maintain your hook dependencies by supporting
+different modes.
-Autohooks doesn't interfere with your work. If autohooks can't be run or fails
-executing a plugin, an error is shown only and the git hook will proceed.
+## Modes
+
+Currently two modes for using autohooks are supported:
+
+* `pythonpath` and
+* `pipenv`
+
+The modes handle how autohooks, the plugins and their dependencies are loaded
+during git hook execution.
+
+If no mode is specified in the [`pyproject.toml` config file](#configure-mode-and-plugins-to-be-run)
+and no mode is set during [activation](#activating-the-git-hooks), autohooks
+will use the [pythonpath mode](#pythonpath-mode) by default.
+
+### Pythonpath mode
+
+In the `pythonpath` mode the user has to install autohooks, the desired
+plugins and their dependencies into the [PYTHONPATH](https://docs.python.org/3/library/sys.html#sys.path)
+manually.
+
+This can be achieved by running `pip install --user autohooks ...` to put them
+into the installation directory of the [current user](https://docs.python.org/3/library/site.html#site.USER_SITE)
+or with `pip install authooks ...` for a system wide installation.
+
+Alternatively a [virtual environment](https://packaging.python.org/tutorials/installing-packages/#creating-and-using-virtual-environments)
+could be used, which separates the installation from your global and user wide
+Python packages.
+
+It would also be possible to use [pipenv] for the management of the virtual
+environment but the activation of the environment has to be done manually.
+
+Therefore it would be even possible to run different versions of autohooks by
+using the `pythonpath` mode and switching a virtual environment.
+
+### Pipenv mode
+
+In the `pipenv` mode [pipenv] is used to run autohooks in a dedicated virtual
+environment. Pipenv uses a lockfile to install exact versions. Therefore the
+installation is deterministic and reliable between different developer setups.
+In contrast to the `pythonpath` mode the activation of the virtual environment
+provided by [pipenv] is done automatically in the background.
+
+Using the `pipenv` mode is highly recommended.
## Installation
For the installation of autohooks three steps are necessary:
-1. Install the autohooks package into your current environment
-2. Activate the [git hooks](https://git-scm.com/docs/githooks)
-3. Configure the plugins to be run
+1. Install autohooks package into your current environment
+3. Configure mode and plugins to be run
+2. Activate [git hooks](https://git-scm.com/docs/githooks)
### Install autohooks python package
-For installing the autohooks python package, using
-[pipenv](https://pipenv.readthedocs.io/) is highly recommended.
+For installing the autohooks python package, using [pipenv] is highly
+recommended.
To install autohooks as a development dependency run
@@ -88,35 +129,25 @@ autohooks = {git = "https://github.com/greenbone/autohooks"}
to the `[dev-packages]` section of your `Pipfile`.
-### Activating the git hooks
-
-If autohooks is installed from git or a source tarball, the git hooks should be
-activated automatically. The activation can be verified by running e.g.
-`autohooks check`.
+### Configure mode and plugins to be run
-Installing autohooks from a [wheel](https://www.python.org/dev/peps/pep-0427/)
-package will **NOT** activate the git commit hooks.
-
-To manually activate the git hooks you can run
-
-```sh
-pipenv run autohooks activate
-```
+Autohooks uses the *pyproject.toml* file specified in
+[PEP518](https://www.python.org/dev/peps/pep-0518/) for its configuration.
+Adding a *[tool.autohooks]* section allows to specify the desired [autohooks mode](#modes)
+and to set python modules to be run as [autohooks plugin](#plugins).
-### Configure plugins to be run
+The mode can be set by adding a `mode =` line to the *pyproject.toml* file.
+Current possible options are `"pythonpath"` and `"pipenv".`See
+[autohooks mode](#modes) for more details. If the mode setting is missing it
+falls back to `pythonpath` mode.
To actually run an action on git hooks, [autohooks plugins](#plugins) have to be
installed and configured. To install e.g. python linting via pylint run
-```
+```bash
pipenv install --dev autohooks-plugin-pylint
```
-Autohooks uses the *pyproject.toml* file specified in
-[PEP518](https://www.python.org/dev/peps/pep-0518/) for its configuration.
-Adding a *[tool.autohooks]* section allows to set python modules to be run on a
-specific git hook.
-
Example *pyproject.toml*:
```toml
@@ -125,49 +156,31 @@ requires = ["setuptools", "wheel"]
[tool.autohooks]
pre-commit = ["autohooks.plugins.black"]
+mode = "pipenv"
```
-## Proposed Workflow
-
-Using [pipenv](https://pipenv.readthedocs.io/) allows to install all
-dependencies and tools with a specific version into a virtual, easily removable
-Python environment. Therefore it's best to maintain **autohooks** also via
-pipenv. Because it is not required to build or run your software, it should be
-[installed as a development dependency](#install-autohooks-python-package).
-Installing and [activating](#activating-the-git-hooks) autohooks doesn't
-actually run any check or formatting by itself. Therefore, it is required to
-[choose and install a plugin](#configure-plugins-to-be-run).
+### Activating the git hooks
-If all these tasks have been resolved, the developers are able to install
-and activate autohooks with only one single command from your project's git
-repository:
+If autohooks is installed from git or a source tarball, the git hooks should be
+activated automatically. The activation can be verified by running e.g.
+`autohooks check`.
-```sh
-pipenv install --dev
-```
+Installing autohooks from a [wheel](https://www.python.org/dev/peps/pep-0427/)
+package will **NOT** activate the git commit hooks automatically.
-Because virtual environments are used for all dependencies including
-autohooks, the linting, formatting, etc. can only by done when running
-`git commit` within the virtual environment.
+To manually activate the git hooks you can run
-```sh
-$ cd myproject
-$ pipenv install --dev
-$ pipenv shell
-(myproject)$ git commit
+```bash
+pipenv run autohooks activate
```
-The advantage of this process is, if the user is not running `git commit` within
-the active virtual environment, autohooks and its plugins are not executed.
+Calling `activate` also allows for overriding the [mode](#modes) defined in the
+*pyproject.toml* settings. E.g.
-```sh
-$ cd myproject
-$ git commit
+```bash
+pipenv run autohooks activate --mode pipenv
```
-This allows the user to choose whether to execute the hooks by activating the
-virtual environment or to ignore them by deactivating it.
-
## Plugins
* Python code formatting via [black](https://github.com/greenbone/autohooks-plugin-black)
@@ -181,7 +194,7 @@ virtual environment or to ignore them by deactivating it.
Plugins need to be available in the
[Python import path](https://docs.python.org/3/reference/import.html). The
easiest way to achieve this, is to upload a plugin to [PyPI](https://pypi.org/)
-and install it via [pip]() or [pipenv](http://pipenv.readthedocs.io/).
+and install it via [pip] or [pipenv].
Alternatively, a plugin can also be put into a *.autohooks* directory at the root
directory of the git repository where the hooks should be executed.
@@ -349,3 +362,6 @@ first.
Copyright (C) 2019 [Greenbone Networks GmbH](https://www.greenbone.net/)
Licensed under the [GNU General Public License v3.0 or later](LICENSE).
+
+[pip]: https://pip.pypa.io/
+[pipenv]: https://pipenv.readthedocs.io/
diff --git a/autohooks/cli/__init__.py b/autohooks/cli/__init__.py
index c1cc8773..edd23166 100644
--- a/autohooks/cli/__init__.py
+++ b/autohooks/cli/__init__.py
@@ -17,6 +17,7 @@
import argparse
+from autohooks.setting import Mode
from autohooks.version import get_version
from autohooks.cli.activate import install_hooks
@@ -43,6 +44,14 @@ def main():
action='store_true',
help='Force activation of hook even if a hook already exists',
)
+ activate_parser.add_argument(
+ '-m',
+ '--mode',
+ dest='mode',
+ choices=[str(Mode.PYTHONPATH), str(Mode.PIPENV)],
+ help='Mode for loading autohooks during hook execution. Either load '
+ 'autohooks from the PYTHON_PATH or via pipenv.',
+ )
subparsers.add_parser('check', help='Check installed pre-commit hook')
diff --git a/autohooks/cli/activate.py b/autohooks/cli/activate.py
index e84a64d5..84f1fc8e 100644
--- a/autohooks/cli/activate.py
+++ b/autohooks/cli/activate.py
@@ -27,6 +27,7 @@
get_pre_commit_hook_path,
get_autohooks_pre_commit_hook,
)
+from autohooks.setting import Mode
def install_hooks(args):
@@ -49,9 +50,16 @@ def install_hooks(args):
file=sys.stderr,
)
- autohooks_pre_commit_hook = get_autohooks_pre_commit_hook()
+ if args.mode:
+ mode = Mode.from_string(args.mode)
+ else:
+ mode = config.get_mode()
+
+ autohooks_pre_commit_hook = get_autohooks_pre_commit_hook(mode)
install_pre_commit_hook(autohooks_pre_commit_hook, pre_commit_hook_path)
print(
- 'pre-commit hook installed at {}'.format(str(pre_commit_hook_path))
+ 'pre-commit hook installed at {} using {} mode'.format(
+ str(pre_commit_hook_path), str(mode.get_effective_mode())
+ )
)
diff --git a/autohooks/cli/check.py b/autohooks/cli/check.py
index 57c62166..41c39bbe 100644
--- a/autohooks/cli/check.py
+++ b/autohooks/cli/check.py
@@ -17,7 +17,7 @@
from autohooks.install import (
get_pre_commit_hook_path,
- get_pre_commit_hook_template_path,
+ is_autohooks_pre_commit_hook,
)
from autohooks.config import (
@@ -38,13 +38,9 @@
def check_hooks():
pre_commit_hook = get_pre_commit_hook_path()
- pre_commit_template = get_pre_commit_hook_template_path()
-
- template = pre_commit_template.read_text()
if pre_commit_hook.is_file():
- hook = pre_commit_hook.read_text()
- if hook == template:
+ if is_autohooks_pre_commit_hook(pre_commit_hook):
ok('autohooks pre-commit hook is active.')
else:
error(
diff --git a/autohooks/config.py b/autohooks/config.py
index b62f32c9..9125b173 100644
--- a/autohooks/config.py
+++ b/autohooks/config.py
@@ -17,6 +17,7 @@
import toml
+from autohooks.setting import Mode
from autohooks.utils import get_pyproject_toml_path
AUTOHOOKS_SECTION = 'tool.autohooks'
@@ -61,6 +62,19 @@ def get_pre_commit_script_names(self):
return []
+ def get_mode(self):
+ if self.has_autohooks_config():
+ mode = self._autohooks_config.get_value('mode')
+ if not mode:
+ return Mode.UNDEFINED
+
+ try:
+ return Mode[mode.upper()]
+ except KeyError:
+ return Mode.UNKNOWN
+
+ return Mode.UNDEFINED
+
def get_config(self):
return self._config
diff --git a/autohooks/install.py b/autohooks/install.py
index 5472c026..5c1d563b 100644
--- a/autohooks/install.py
+++ b/autohooks/install.py
@@ -18,10 +18,10 @@
from setuptools.command.install import install
from setuptools.command.develop import develop
-from autohooks.utils import (
- get_git_hook_directory_path,
- get_autohooks_directory_path,
-)
+from autohooks.config import load_config_from_pyproject_toml
+from autohooks.template import PreCommitTemplate
+
+from autohooks.utils import get_git_hook_directory_path
def get_pre_commit_hook_path():
@@ -29,18 +29,21 @@ def get_pre_commit_hook_path():
return git_hook_dir_path / 'pre-commit'
-def get_pre_commit_hook_template_path():
- setup_dir_path = get_autohooks_directory_path()
- return setup_dir_path / 'precommit' / 'template'
+def get_autohooks_pre_commit_hook(mode):
+ template = PreCommitTemplate()
+
+ return template.render(mode=mode)
-def get_autohooks_pre_commit_hook():
- template_path = get_pre_commit_hook_template_path()
- return template_path.read_text()
+def is_autohooks_pre_commit_hook(path):
+ hook = path.read_text()
+ lines = hook.split('\n')
+ return len(lines) > 5 and "autohooks.precommit" in lines[5]
def install_pre_commit_hook(pre_commit_hook, pre_commit_hook_path):
pre_commit_hook_path.write_text(pre_commit_hook)
+ pre_commit_hook_path.chmod(0o775)
class AutohooksInstall:
@@ -48,7 +51,10 @@ def install_git_hook(self):
try:
pre_commit_hook_path = get_pre_commit_hook_path()
if not pre_commit_hook_path.exists():
- autohooks_pre_commit_hook = get_autohooks_pre_commit_hook()
+ config = load_config_from_pyproject_toml()
+
+ mode = config.get_mode()
+ autohooks_pre_commit_hook = get_autohooks_pre_commit_hook(mode)
install_pre_commit_hook(
autohooks_pre_commit_hook, pre_commit_hook_path
)
diff --git a/autohooks/precommit/template b/autohooks/precommit/template
index 57b46e15..b14a059d 100755
--- a/autohooks/precommit/template
+++ b/autohooks/precommit/template
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+#!$SHEBANG
import sys
@@ -6,6 +6,9 @@ try:
from autohooks.precommit import run
sys.exit(run())
except ImportError:
- print('autohooks not installed. Ignoring pre-commit hook.')
- sys.exit(0)
-
+ print(
+ "autohooks is not installed. To force creating a commit without "
+ "verification via autohooks run 'git commit --no-verify'.",
+ file=sys.stderr,
+ )
+ sys.exit(1)
diff --git a/autohooks/setting.py b/autohooks/setting.py
new file mode 100644
index 00000000..19088a98
--- /dev/null
+++ b/autohooks/setting.py
@@ -0,0 +1,44 @@
+# Copyright (C) 2019 Greenbone Networks GmbH
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from enum import Enum
+
+
+class Mode(Enum):
+ PIPENV = 1
+ PYTHONPATH = 2
+ UNDEFINED = -1
+ UNKNOWN = -2
+
+ def get_effective_mode(self):
+ # pylint: disable=comparison-with-callable
+ if self.value == Mode.PIPENV.value:
+ return Mode.PIPENV
+ return Mode.PYTHONPATH
+
+ @staticmethod
+ def from_string(modestring):
+ if not modestring:
+ return Mode.UNDEFINED
+
+ try:
+ return Mode[modestring.upper()]
+ except KeyError:
+ return Mode.UNKNOWN
+
+ def __str__(self):
+ return self.name.lower() # pylint: disable=no-member
diff --git a/autohooks/template.py b/autohooks/template.py
new file mode 100644
index 00000000..ac39082a
--- /dev/null
+++ b/autohooks/template.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2019 Greenbone Networks GmbH
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+from string import Template
+
+from autohooks.setting import Mode
+from autohooks.utils import get_autohooks_directory_path
+
+
+PYTHON3_SHEBANG = '/usr/bin/env python3'
+PIPENV_SHEBANG = '/usr/bin/env -S pipenv run python3'
+
+
+def get_pre_commit_hook_template_path():
+ setup_dir_path = get_autohooks_directory_path()
+ return setup_dir_path / 'precommit' / 'template'
+
+
+class PreCommitTemplate:
+ def __init__(self, template_path=None):
+ if template_path is None:
+ template_path = get_pre_commit_hook_template_path()
+ self._load(template_path)
+
+ def _load(self, template_path):
+ self._template = Template(template_path.read_text())
+
+ def render(self, *, mode):
+ mode = mode.get_effective_mode()
+
+ if mode == Mode.PIPENV:
+ params = dict(SHEBANG=PIPENV_SHEBANG)
+ else:
+ params = dict(SHEBANG=PYTHON3_SHEBANG)
+
+ return self._template.safe_substitute(params)
diff --git a/tests/test_config.py b/tests/test_config.py
index f92884ea..34e425d1 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -22,6 +22,7 @@
from autohooks.config import (
AutohooksConfig,
Config,
+ Mode,
load_config_from_pyproject_toml,
)
@@ -54,6 +55,7 @@ def test_load_from_non_existing_toml_file(self):
self.assertFalse(config.has_config())
self.assertFalse(config.has_autohooks_config())
self.assertFalse(config.is_autohooks_enabled())
+ self.assertEqual(config.get_mode(), Mode.UNDEFINED)
self.assertEqual(len(config.get_pre_commit_script_names()), 0)
@@ -63,6 +65,7 @@ def test_empty_config(self):
self.assertFalse(config.has_config())
self.assertFalse(config.has_autohooks_config())
self.assertFalse(config.is_autohooks_enabled())
+ self.assertEqual(config.get_mode(), Mode.UNDEFINED)
self.assertEqual(len(config.get_pre_commit_script_names()), 0)
@@ -72,6 +75,7 @@ def test_empty_config_dict(self):
self.assertTrue(config.has_config())
self.assertFalse(config.has_autohooks_config())
self.assertFalse(config.is_autohooks_enabled())
+ self.assertEqual(config.get_mode(), Mode.UNDEFINED)
self.assertEqual(len(config.get_pre_commit_script_names()), 0)
@@ -81,6 +85,49 @@ def test_missing_pre_commit(self):
self.assertTrue(config.has_config())
self.assertTrue(config.has_autohooks_config())
self.assertTrue(config.is_autohooks_enabled())
+ self.assertEqual(config.get_mode(), Mode.UNDEFINED)
+
+ self.assertEqual(len(config.get_pre_commit_script_names()), 0)
+
+ def test_get_mode_pipenv(self):
+ config = AutohooksConfig({'tool': {'autohooks': {'mode': 'pipenv'}}})
+
+ self.assertTrue(config.has_config())
+ self.assertTrue(config.has_autohooks_config())
+ self.assertTrue(config.is_autohooks_enabled())
+ self.assertEqual(config.get_mode(), Mode.PIPENV)
+
+ self.assertEqual(len(config.get_pre_commit_script_names()), 0)
+
+ def test_get_mode_pythonpath(self):
+ config = AutohooksConfig(
+ {'tool': {'autohooks': {'mode': 'pythonpath'}}}
+ )
+
+ self.assertTrue(config.has_config())
+ self.assertTrue(config.has_autohooks_config())
+ self.assertTrue(config.is_autohooks_enabled())
+ self.assertEqual(config.get_mode(), Mode.PYTHONPATH)
+
+ self.assertEqual(len(config.get_pre_commit_script_names()), 0)
+
+ def test_get_mode_unknown(self):
+ config = AutohooksConfig({'tool': {'autohooks': {'mode': 'foo'}}})
+
+ self.assertTrue(config.has_config())
+ self.assertTrue(config.has_autohooks_config())
+ self.assertTrue(config.is_autohooks_enabled())
+ self.assertEqual(config.get_mode(), Mode.UNKNOWN)
+
+ self.assertEqual(len(config.get_pre_commit_script_names()), 0)
+
+ def test_get_mode_undefined(self):
+ config = AutohooksConfig({'tool': {'autohooks': {'mode': None}}})
+
+ self.assertTrue(config.has_config())
+ self.assertTrue(config.has_autohooks_config())
+ self.assertTrue(config.is_autohooks_enabled())
+ self.assertEqual(config.get_mode(), Mode.UNDEFINED)
self.assertEqual(len(config.get_pre_commit_script_names()), 0)
@@ -90,6 +137,7 @@ def test_get_config_dict(self):
self.assertTrue(config.has_config())
self.assertTrue(config.has_autohooks_config())
+ self.assertEqual(config.get_mode(), Mode.UNDEFINED)
config_out = config.get_config()
@@ -170,3 +218,7 @@ def test_config_point_syntax(self):
bar_config = config.get('tool', 'autohooks', 'plugins', 'bar')
self.assertFalse(bar_config.is_empty())
self.assertEqual(bar_config.get_value('lorem'), 'ipsum')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_install.py b/tests/test_install.py
index a0377664..d08320ea 100644
--- a/tests/test_install.py
+++ b/tests/test_install.py
@@ -24,9 +24,11 @@
from autohooks.install import (
get_autohooks_pre_commit_hook,
get_pre_commit_hook_path,
- get_pre_commit_hook_template_path,
install_pre_commit_hook,
+ is_autohooks_pre_commit_hook,
)
+from autohooks.setting import Mode
+from autohooks.template import get_pre_commit_hook_template_path
from autohooks.utils import exec_git
@@ -57,16 +59,9 @@ def test_get_path(self):
)
-class GetPreCommitHookTemplatePath(unittest.TestCase):
- def test_template_exists(self):
- template_path = get_pre_commit_hook_template_path()
- self.assertTrue(template_path.exists())
- self.assertTrue(template_path.is_file())
-
-
class InstallPreCommitHook(GitDirTestCase):
def test_install(self):
- hooks = get_autohooks_pre_commit_hook()
+ hooks = get_autohooks_pre_commit_hook(mode=Mode.PIPENV)
pre_commmit_hook_path = get_pre_commit_hook_path()
self.assertFalse(pre_commmit_hook_path.exists())
@@ -74,3 +69,25 @@ def test_install(self):
install_pre_commit_hook(hooks, pre_commmit_hook_path)
self.assertTrue(pre_commmit_hook_path.exists())
+
+
+class FakeHookPath:
+ def __init__(self, text):
+ self._text = text
+
+ def read_text(self):
+ return self._text
+
+
+class IsAutohooksPreCommitHook(unittest.TestCase):
+ def test_other_hook(self):
+ path = FakeHookPath('foo\nbar')
+ self.assertFalse(is_autohooks_pre_commit_hook(path))
+
+ def test_pre_commit_template_path(self):
+ path = get_pre_commit_hook_template_path()
+ self.assertTrue(is_autohooks_pre_commit_hook(path))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_setting.py b/tests/test_setting.py
new file mode 100644
index 00000000..09b7aa7e
--- /dev/null
+++ b/tests/test_setting.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2019 Greenbone Networks GmbH
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import unittest
+
+from autohooks.setting import Mode
+
+
+class ModeTestCase(unittest.TestCase):
+ def test_get_effective_mode(self):
+ self.assertEqual(Mode.PIPENV.get_effective_mode(), Mode.PIPENV)
+ self.assertEqual(Mode.PYTHONPATH.get_effective_mode(), Mode.PYTHONPATH)
+ self.assertEqual(Mode.UNDEFINED.get_effective_mode(), Mode.PYTHONPATH)
+ self.assertEqual(Mode.UNKNOWN.get_effective_mode(), Mode.PYTHONPATH)
+
+ def test_get_pipenv_mode_from_string(self):
+ self.assertEqual(Mode.from_string('pipenv'), Mode.PIPENV)
+ self.assertEqual(Mode.from_string('PIPENV'), Mode.PIPENV)
+
+ def test_get_pythonpath_mode_from_string(self):
+ self.assertEqual(Mode.from_string('pythonpath'), Mode.PYTHONPATH)
+ self.assertEqual(Mode.from_string('PYTHONPATH'), Mode.PYTHONPATH)
+
+ def test_get_invalid_mode_from_string(self):
+ self.assertEqual(Mode.from_string('foo'), Mode.UNKNOWN)
+ self.assertEqual(Mode.from_string(None), Mode.UNDEFINED)
+ self.assertEqual(Mode.from_string(''), Mode.UNDEFINED)
+
+ def test_string_conversion(self):
+ self.assertEqual(str(Mode.PIPENV), 'pipenv')
+ self.assertEqual(str(Mode.PYTHONPATH), 'pythonpath')
+ self.assertEqual(str(Mode.UNKNOWN), 'unknown')
+ self.assertEqual(str(Mode.UNDEFINED), 'undefined')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_template.py b/tests/test_template.py
new file mode 100644
index 00000000..437d6855
--- /dev/null
+++ b/tests/test_template.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2019 Greenbone Networks GmbH
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import unittest
+
+from autohooks.setting import Mode
+from autohooks.template import (
+ PreCommitTemplate,
+ get_pre_commit_hook_template_path,
+)
+
+DEFAULT_TEMPLATE = """#!/usr/bin/env python3
+
+import sys
+
+try:
+ from autohooks.precommit import run
+ sys.exit(run())
+except ImportError:
+ print(
+ "autohooks is not installed. To force creating a commit without "
+ "verification via autohooks run 'git commit --no-verify'.",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+"""
+
+
+class FakeTemplatePath:
+ def __init__(self, text):
+ self._text = text
+
+ def read_text(self):
+ return self._text
+
+
+class PreCommitTemplateTestCase(unittest.TestCase):
+ def test_should_use_default_template(self):
+ template = PreCommitTemplate()
+ self.assertEqual(
+ template.render(mode=Mode.PYTHONPATH), DEFAULT_TEMPLATE
+ )
+
+ def test_should_render_mode_pipenv(self):
+ path = FakeTemplatePath("$SHEBANG")
+ template = PreCommitTemplate(path)
+ self.assertEqual(
+ template.render(mode=Mode.PIPENV),
+ "/usr/bin/env -S pipenv run python3",
+ )
+
+ def test_should_render_mode_unknown(self):
+ path = FakeTemplatePath("$SHEBANG")
+ template = PreCommitTemplate(path)
+ self.assertEqual(
+ template.render(mode=Mode.UNDEFINED), "/usr/bin/env python3"
+ )
+
+ def test_should_render_mode_undefined(self):
+ path = FakeTemplatePath("$SHEBANG")
+ template = PreCommitTemplate(path)
+ self.assertEqual(
+ template.render(mode=Mode.UNDEFINED), "/usr/bin/env python3"
+ )
+
+
+class GetPreCommitHookTemplatePath(unittest.TestCase):
+ def test_template_exists(self):
+ template_path = get_pre_commit_hook_template_path()
+ self.assertTrue(template_path.exists())
+ self.assertTrue(template_path.is_file())
+
+
+if __name__ == '__main__':
+ unittest.main()