8000 Support health check for `language: r` by lorenzwalthert · Pull Request #3265 · pre-commit/pre-commit · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Support health check for language: r #3265

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

Merged
merged 1 commit into from
Jul 28, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
8000
Diff view
77 changes: 68 additions & 9 deletions pre_commit/languages/r.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,74 @@
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
from pre_commit.util import win_exe

ENVIRONMENT_DIR = 'renv'
RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ')
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check


def _execute_vanilla_r_code_as_script(
code: str, *,
prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
) -> str:
with in_env(prefix, version), _r_code_in_tempfile(code) as f:
_, out, _ = cmd_output(
_rscript_exec(), *RSCRIPT_OPTS, f, *args, cwd=cwd,
)
return out.rstrip('\n')


def _read_installed_version(envdir: str, prefix: Prefix, version: str) -> str:
return _execute_vanilla_r_code_as_script(
'cat(renv::settings$r.version())',
prefix=prefix, version=version,
cwd=envdir,
)


def _read_executable_version(envdir: str, prefix: Prefix, version: str) -> str:
return _execute_vanilla_r_code_as_script(
'cat(as.character(getRversion()))',
prefix=prefix, version=version,
cwd=envdir,
)


def _write_current_r_version(
envdir: str, prefix: Prefix, version: str,
) -> None:
_execute_vanilla_r_code_as_script(
'renv::settings$r.version(as.character(getRversion()))',
prefix=prefix, version=version,
cwd=envdir,
)


def health_check(prefix: Prefix, version: str) -> str | None:
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)

r_version_installation = _read_installed_version(
envdir=envdir, prefix=prefix, version=version,
)
r_version_current_executable = _read_executable_version(
envdir=envdir, prefix=prefix, version=version,
)
if r_version_installation in {'NULL', ''}:
return (
f'Hooks were installed with an unknown R version. R version for '
f'hook repo now set to {r_version_current_executable}'
)
elif r_version_installation != r_version_current_executable:
return (
f'Hooks were installed for R version {r_version_installation}, '
f'but current R executable has version '
f'{r_version_current_executable}'
)

return None


@contextlib.contextmanager
Expand Down Expand Up @@ -147,16 +208,14 @@ def install_environment(
with _r_code_in_tempfile(r_code_inst_environment) as f:
cmd_output_b(_rscript_exec(), '--vanilla', f, cwd=env_dir)

_write_current_r_version(envdir=env_dir, prefix=prefix, version=version)
if additional_dependencies:
r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))'
with in_env(prefix, version):
with _r_code_in_tempfile(r_code_inst_add) as f:
cmd_output_b(
_rscript_exec(), *RSCRIPT_OPTS,
f,
*additional_dependencies,
cwd=env_dir,
)
_execute_vanilla_r_code_as_script(
code=r_code_inst_add, prefix=prefix, version=version,
args=additional_dependencies,
cwd=env_dir,
)


def _inline_r_setup(code: str) -> str:
Expand Down
100 changes: 87 additions & 13 deletions tests/languages/r_test.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from __future__ import annotations

import os.path
import shutil
from unittest import mock

import pytest

import pre_commit.constants as C
from pre_commit import envcontext
from pre_commit import lang_base
from pre_commit.languages import r
from pre_commit.prefix import Prefix
from pre_commit.store import _make_local_repo
from pre_commit.util import resource_text
from pre_commit.util import win_exe
from testing.language_helpers import run_language

Expand Down Expand Up @@ -127,7 +130,8 @@ def test_path_rscript_exec_no_r_home_set():
assert r._rscript_exec() == 'Rscript'


def test_r_hook(tmp_path):
@pytest.fixture
def renv_lock_file(tmp_path):
renv_lock = '''\
{
"R": {
Expand Down Expand Up @@ -157,6 +161,12 @@ def test_r_hook(tmp_path):
}
}
'''
tmp_path.joinpath('renv.lock').write_text(renv_lock)
yield


@pytest.fixture
def description_file(tmp_path):
description = '''\
Package: gli.clu
Title: What the Package Does (One Line, Title Case)
Expand All @@ -178,27 +188,39 @@ def test_r_hook(tmp_path):
Imports:
rprojroot
'''
hello_world_r = '''\
tmp_path.joinpath('DESCRIPTION').write_text(description)
yield


@pytest.fixture
def hello_world_file(tmp_path):
hello_world = '''\
stopifnot(
packageVersion('rprojroot') == '1.0',
packageVersion('gli.clu') == '0.0.0.9000'
)
cat("Hello, World, from R!\n")
'''
tmp_path.joinpath('hello-world.R').write_text(hello_world)
yield

tmp_path.joinpath('renv.lock').write_text(renv_lock)
tmp_path.joinpath('DESCRIPTION').write_text(description)
tmp_path.joinpath('hello-world.R').write_text(hello_world_r)

@pytest.fixture
def renv_folder(tmp_path):
renv_dir = tmp_path.joinpath('renv')
renv_dir.mkdir()
shutil.copy(
os.path.join(
os.path.dirname(__file__),
'../../pre_commit/resources/empty_template_activate.R',
),
renv_dir.joinpath('activate.R'),
)
activate_r = resource_text('empty_template_activate.R')
renv_dir.joinpath('activate.R').write_text(activate_r)
yield


def test_r_hook(
tmp_path,
renv_lock_file,
description_file,
hello_world_file,
renv_folder,
):
expected = (0, b'Hello, World, from R!\n')
assert run_language(tmp_path, r, 'Rscript hello-world.R') == expected

Expand All @@ -221,3 +243,55 @@ def test_r_inline(tmp_path):
args=('hi', 'hello'),
)
assert ret == (0, b'hi, hello, from R!\n')


@pytest.fixture
def prefix(tmpdir):
yield Prefix(str(tmpdir))


@pytest.fixture
def installed_environment(
renv_lock_file,
hello_world_file,
renv_folder,
prefix,
):
env_dir = lang_base.environment_dir(
prefix, r.ENVIRONMENT_DIR, r.get_default_version(),
)
r.install_environment(prefix, C.DEFAULT, ())
yield prefix, env_dir


def test_health_check_healthy(installed_environment):
# should be healthy right after creation
prefix, _ = installed_environment
assert r.health_check(prefix, C.DEFAULT) is None


def test_health_check_after_downgrade(installed_environment):
prefix, _ = installed_environment

# pretend the saved installed version is old
with mock.patch.object(r, '_read_installed_version', return_value='1.0.0'):
output = r.health_check(prefix, C.DEFAULT)

assert output is not None
assert output.startswith('Hooks were installed for R version')


@pytest.mark.parametrize('version', ('NULL', 'NA', "''"))
def test_health_check_without_version(prefix, installed_environment, version):
prefix, env_dir = installed_environment

# simulate old pre-commit install by unsetting the installed version
r._execute_vanilla_r_code_as_script(
f'renv::settings$r.version({version})',
prefix=prefix, version=C.DEFAULT, cwd=env_dir,
)

# no R version specified fails as unhealty
msg = 'Hooks were installed with an unknown R version'
check_output = r.health_check(prefix, C.DEFAULT)
assert check_output is not None and check_output.startswith(msg)
Loading
0