Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add additional info to healthy-after-install check #2348

Merged
merged 1 commit into from Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions CONTRIBUTING.md
Expand Up @@ -117,15 +117,16 @@ get_default_version = helpers.basic_default_version

`python` is currently the only language which implements this api

#### `healthy`
#### `health_check`

This is used to check whether the installed environment is considered healthy.
This function should return `True` or `False`.
This function should return a detailed message if unhealthy or `None` if
healthy.

You generally don't need to implement this on a first pass and can just use:

```python
healthy = helpers.basic_healthy
health_check = helpers.basic_healthy_check
```

`python` is currently the only language which implements this api, for python
Expand Down
40 changes: 20 additions & 20 deletions pre_commit/languages/all.py
Expand Up @@ -34,7 +34,7 @@ class Language(NamedTuple):
# return a value to replace `'default` for `language_version`
get_default_version: Callable[[], str]
# return whether the environment is healthy (or should be rebuilt)
healthy: Callable[[Prefix, str], bool]
health_check: Callable[[Prefix, str], str | None]
# install a repository for the given language and language_version
install_environment: Callable[[Prefix, str, Sequence[str]], None]
# execute a hook and return the exit code and output
Expand All @@ -44,25 +44,25 @@ class Language(NamedTuple):
# TODO: back to modules + Protocol: https://github.com/python/mypy/issues/5018
languages = {
# BEGIN GENERATED (testing/gen-languages-all)
'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, healthy=conda.healthy, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501
'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, healthy=coursier.healthy, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501
'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, healthy=dart.healthy, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501
'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, healthy=docker.healthy, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501
'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, healthy=docker_image.healthy, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501
'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, healthy=dotnet.healthy, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501
'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, healthy=fail.healthy, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501
'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, healthy=golang.healthy, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501
'lua': Language(name='lua', ENVIRONMENT_DIR=lua.ENVIRONMENT_DIR, get_default_version=lua.get_default_version, healthy=lua.healthy, install_environment=lua.install_environment, run_hook=lua.run_hook), # noqa: E501
'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, healthy=node.healthy, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501
'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, healthy=perl.healthy, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501
'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, healthy=pygrep.healthy, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501
'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, healthy=python.healthy, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501
'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, healthy=r.healthy, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501
'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, healthy=ruby.healthy, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501
'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, healthy=rust.healthy, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501
'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, healthy=script.healthy, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501
'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, healthy=swift.healthy, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501
'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, healthy=system.healthy, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501
'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, health_check=conda.health_check, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501
'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, health_check=coursier.health_check, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501
'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, health_check=dart.health_check, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501
'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, health_check=docker.health_check, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501
'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, health_check=docker_image.health_check, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501
'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, health_check=dotnet.health_check, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501
'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, health_check=fail.health_check, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501
'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, health_check=golang.health_check, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501
'lua': Language(name='lua', ENVIRONMENT_DIR=lua.ENVIRONMENT_DIR, get_default_version=lua.get_default_version, health_check=lua.health_check, install_environment=lua.install_environment, run_hook=lua.run_hook), # noqa: E501
'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, health_check=node.health_check, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501
'perl': Language(name='perl', ENVIRONMENT_DIR=perl.ENVIRONMENT_DIR, get_default_version=perl.get_default_version, health_check=perl.health_check, install_environment=perl.install_environment, run_hook=perl.run_hook), # noqa: E501
'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, health_check=pygrep.health_check, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501
'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, health_check=python.health_check, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501
'r': Language(name='r', ENVIRONMENT_DIR=r.ENVIRONMENT_DIR, get_default_version=r.get_default_version, health_check=r.health_check, install_environment=r.install_environment, run_hook=r.run_hook), # noqa: E501
'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, health_check=ruby.health_check, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501
'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, health_check=rust.health_check, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501
'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, health_check=script.health_check, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501
'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, health_check=swift.health_check, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501
'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, health_check=system.health_check, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501
# END GENERATED
}
# TODO: fully deprecate `python_venv`
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/conda.py
Expand Up @@ -18,7 +18,7 @@

ENVIRONMENT_DIR = 'conda'
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def get_env_patch(env: str) -> PatchesT:
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/coursier.py
Expand Up @@ -17,7 +17,7 @@
ENVIRONMENT_DIR = 'coursier'

get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def install_environment(
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/dart.py
Expand Up @@ -21,7 +21,7 @@
ENVIRONMENT_DIR = 'dartenv'

get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def get_env_patch(venv: str) -> PatchesT:
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/docker.py
Expand Up @@ -16,7 +16,7 @@
ENVIRONMENT_DIR = 'docker'
PRE_COMMIT_LABEL = 'PRE_COMMIT'
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def _is_in_docker() -> bool:
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/docker_image.py
Expand Up @@ -8,7 +8,7 @@

ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check
install_environment = helpers.no_install


Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/dotnet.py
Expand Up @@ -18,7 +18,7 @@
BIN_DIR = 'bin'

get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def get_env_patch(venv: str) -> PatchesT:
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/fail.py
Expand Up @@ -7,7 +7,7 @@

ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check
install_environment = helpers.no_install


Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/golang.py
Expand Up @@ -21,7 +21,7 @@

ENVIRONMENT_DIR = 'golangenv'
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def get_env_patch(venv: str) -> PatchesT:
Expand Down
4 changes: 2 additions & 2 deletions pre_commit/languages/helpers.py
Expand Up @@ -88,8 +88,8 @@ def basic_get_default_version() -> str:
return C.DEFAULT


def basic_healthy(prefix: Prefix, language_version: str) -> bool:
return True
def basic_health_check(prefix: Prefix, language_version: str) -> str | None:
return None


def no_install(
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/lua.py
Expand Up @@ -18,7 +18,7 @@

ENVIRONMENT_DIR = 'lua_env'
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def _get_lua_version() -> str: # pragma: win32 no cover
Expand Down
7 changes: 5 additions & 2 deletions pre_commit/languages/node.py
Expand Up @@ -73,10 +73,13 @@ def in_env(
yield


def healthy(prefix: Prefix, language_version: str) -> bool:
def health_check(prefix: Prefix, language_version: str) -> str | None:
with in_env(prefix, language_version):
retcode, _, _ = cmd_output_b('node', '--version', retcode=None)
return retcode == 0
if retcode != 0: # pragma: win32 no cover
return f'`node --version` returned {retcode}'
else:
return None


def install_environment(
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/perl.py
Expand Up @@ -16,7 +16,7 @@

ENVIRONMENT_DIR = 'perl_env'
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def _envdir(prefix: Prefix, version: str) -> str:
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/pygrep.py
Expand Up @@ -14,7 +14,7 @@

ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check
install_environment = helpers.no_install


Expand Down
35 changes: 26 additions & 9 deletions pre_commit/languages/python.py
Expand Up @@ -163,27 +163,44 @@ def in_env(
yield


def healthy(prefix: Prefix, language_version: str) -> bool:
def health_check(prefix: Prefix, language_version: str) -> str | None:
directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version)
envdir = prefix.path(directory)
pyvenv_cfg = os.path.join(envdir, 'pyvenv.cfg')

# created with "old" virtualenv
if not os.path.exists(pyvenv_cfg):
return False
return 'pyvenv.cfg does not exist (old virtualenv?)'

exe_name = win_exe('python')
py_exe = prefix.path(bin_dir(envdir), exe_name)
cfg = _read_pyvenv_cfg(pyvenv_cfg)

return (
'version_info' in cfg and
# always use uncached lookup here in case we replaced an unhealthy env
_version_info.__wrapped__(py_exe) == cfg['version_info'] and (
'base-executable' not in cfg or
_version_info(cfg['base-executable']) == cfg['version_info']
if 'version_info' not in cfg:
return "created virtualenv's pyvenv.cfg is missing `version_info`"

# always use uncached lookup here in case we replaced an unhealthy env
virtualenv_version = _version_info.__wrapped__(py_exe)
if virtualenv_version != cfg['version_info']:
return (
f'virtualenv python version did not match created version:\n'
f'- actual version: {virtualenv_version}\n'
f'- expected version: {cfg["version_info"]}\n'
)
)

# made with an older version of virtualenv? skip `base-executable` check
if 'base-executable' not in cfg:
return None

base_exe_version = _version_info(cfg['base-executable'])
if base_exe_version != cfg['version_info']:
return (
f'base executable python version does not match created version:\n'
f'- base-executable version: {base_exe_version}\n'
f'- expected version: {cfg["version_info"]}\n'
)
else:
return None


def install_environment(
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/r.py
Expand Up @@ -19,7 +19,7 @@
ENVIRONMENT_DIR = 'renv'
RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ')
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def get_env_patch(venv: str) -> PatchesT:
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/ruby.py
Expand Up @@ -21,7 +21,7 @@
from pre_commit.util import resource_bytesio

ENVIRONMENT_DIR = 'rbenv'
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


@functools.lru_cache(maxsize=1)
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/rust.py
Expand Up @@ -19,7 +19,7 @@

ENVIRONMENT_DIR = 'rustenv'
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check


def get_env_patch(target_dir: str) -> PatchesT:
Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/script.py
Expand Up @@ -7,7 +7,7 @@

ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check
install_environment = helpers.no_install


Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/swift.py
Expand Up @@ -17,7 +17,7 @@

ENVIRONMENT_DIR = 'swift_env'
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check
BUILD_DIR = '.build'
BUILD_CONFIG = 'release'

Expand Down
2 changes: 1 addition & 1 deletion pre_commit/languages/system.py
Expand Up @@ -8,7 +8,7 @@

ENVIRONMENT_DIR = None
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy
health_check = helpers.basic_health_check
install_environment = helpers.no_install


Expand Down
10 changes: 6 additions & 4 deletions pre_commit/repository.py
Expand Up @@ -57,7 +57,7 @@ def _hook_installed(hook: Hook) -> bool:
_read_state(hook.prefix, venv) ==
_state(hook.additional_dependencies)
) and
lang.healthy(hook.prefix, hook.language_version)
not lang.health_check(hook.prefix, hook.language_version)
)
)

Expand All @@ -79,11 +79,13 @@ def _hook_install(hook: Hook) -> None:
lang.install_environment(
hook.prefix, hook.language_version, hook.additional_dependencies,
)
if not lang.healthy(hook.prefix, hook.language_version):
health_error = lang.health_check(hook.prefix, hook.language_version)
if health_error:
raise AssertionError(
f'BUG: expected environment for {hook.language} to be healthy() '
f'BUG: expected environment for {hook.language} to be healthy '
f'immediately after install, please open an issue describing '
f'your environment',
f'your environment\n\n'
f'more info:\n\n{health_error}',
)
# Write our state to indicate we're installed
_write_state(hook.prefix, venv, _state(hook.additional_dependencies))
Expand Down
12 changes: 6 additions & 6 deletions testing/gen-languages-all
Expand Up @@ -3,15 +3,15 @@ from __future__ import annotations

import sys

LANGUAGES = [
LANGUAGES = (
'conda', 'coursier', 'dart', 'docker', 'docker_image', 'dotnet', 'fail',
'golang', 'lua', 'node', 'perl', 'pygrep', 'python', 'r', 'ruby', 'rust',
'script', 'swift', 'system',
]
FIELDS = [
'ENVIRONMENT_DIR', 'get_default_version', 'healthy', 'install_environment',
'run_hook',
]
)
FIELDS = (
'ENVIRONMENT_DIR', 'get_default_version', 'health_check',
'install_environment', 'run_hook',
)


def main() -> int:
Expand Down
4 changes: 2 additions & 2 deletions tests/languages/helpers_test.py
Expand Up @@ -67,8 +67,8 @@ def test_basic_get_default_version():
assert helpers.basic_get_default_version() == C.DEFAULT


def test_basic_healthy():
assert helpers.basic_healthy(Prefix('.'), 'default') is True
def test_basic_health_check():
assert helpers.basic_health_check(Prefix('.'), 'default') is None


def test_failed_setup_command_does_not_unicode_error():
Expand Down