mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-02-17 08:14:42 +04:00
otherwise, installing the hooks when RENV_USER env variable is set (e.g. in RStudio with renv project) will result in executing the installation script in the wrong renv
297 lines
7.6 KiB
Python
297 lines
7.6 KiB
Python
from __future__ import annotations
|
|
|
|
import os.path
|
|
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
|
|
|
|
|
|
def test_r_parsing_file_no_opts_no_args(tmp_path):
|
|
cmd = r._cmd_from_hook(
|
|
Prefix(str(tmp_path)),
|
|
'Rscript some-script.R',
|
|
(),
|
|
is_local=False,
|
|
)
|
|
assert cmd == (
|
|
'Rscript',
|
|
'--no-save', '--no-restore', '--no-site-file', '--no-environ',
|
|
str(tmp_path.joinpath('some-script.R')),
|
|
)
|
|
|
|
|
|
def test_r_parsing_file_opts_no_args():
|
|
with pytest.raises(ValueError) as excinfo:
|
|
r._entry_validate(['Rscript', '--no-init', '/path/to/file'])
|
|
|
|
msg, = excinfo.value.args
|
|
assert msg == (
|
|
'The only valid syntax is `Rscript -e {expr}`'
|
|
'or `Rscript path/to/hook/script`'
|
|
)
|
|
|
|
|
|
def test_r_parsing_file_no_opts_args(tmp_path):
|
|
cmd = r._cmd_from_hook(
|
|
Prefix(str(tmp_path)),
|
|
'Rscript some-script.R',
|
|
('--no-cache',),
|
|
is_local=False,
|
|
)
|
|
assert cmd == (
|
|
'Rscript',
|
|
'--no-save', '--no-restore', '--no-site-file', '--no-environ',
|
|
str(tmp_path.joinpath('some-script.R')),
|
|
'--no-cache',
|
|
)
|
|
|
|
|
|
def test_r_parsing_expr_no_opts_no_args1(tmp_path):
|
|
cmd = r._cmd_from_hook(
|
|
Prefix(str(tmp_path)),
|
|
"Rscript -e '1+1'",
|
|
(),
|
|
is_local=False,
|
|
)
|
|
assert cmd == (
|
|
'Rscript',
|
|
'--no-save', '--no-restore', '--no-site-file', '--no-environ',
|
|
'-e', '1+1',
|
|
)
|
|
|
|
|
|
def test_r_parsing_local_hook_path_is_not_expanded(tmp_path):
|
|
cmd = r._cmd_from_hook(
|
|
Prefix(str(tmp_path)),
|
|
'Rscript path/to/thing.R',
|
|
(),
|
|
is_local=True,
|
|
)
|
|
assert cmd == (
|
|
'Rscript',
|
|
'--no-save', '--no-restore', '--no-site-file', '--no-environ',
|
|
'path/to/thing.R',
|
|
)
|
|
|
|
|
|
def test_r_parsing_expr_no_opts_no_args2():
|
|
with pytest.raises(ValueError) as excinfo:
|
|
r._entry_validate(['Rscript', '-e', '1+1', '-e', 'letters'])
|
|
msg, = excinfo.value.args
|
|
assert msg == 'You can supply at most one expression.'
|
|
|
|
|
|
def test_r_parsing_expr_opts_no_args2():
|
|
with pytest.raises(ValueError) as excinfo:
|
|
r._entry_validate(
|
|
['Rscript', '--vanilla', '-e', '1+1', '-e', 'letters'],
|
|
)
|
|
msg, = excinfo.value.args
|
|
assert msg == (
|
|
'The only valid syntax is `Rscript -e {expr}`'
|
|
'or `Rscript path/to/hook/script`'
|
|
)
|
|
|
|
|
|
def test_r_parsing_expr_args_in_entry2():
|
|
with pytest.raises(ValueError) as excinfo:
|
|
r._entry_validate(['Rscript', '-e', 'expr1', '--another-arg'])
|
|
|
|
msg, = excinfo.value.args
|
|
assert msg == 'You can supply at most one expression.'
|
|
|
|
|
|
def test_r_parsing_expr_non_Rscirpt():
|
|
with pytest.raises(ValueError) as excinfo:
|
|
r._entry_validate(['AnotherScript', '-e', '{{}}'])
|
|
|
|
msg, = excinfo.value.args
|
|
assert msg == 'entry must start with `Rscript`.'
|
|
|
|
|
|
def test_rscript_exec_relative_to_r_home():
|
|
expected = os.path.join('r_home_dir', 'bin', win_exe('Rscript'))
|
|
with envcontext.envcontext((('R_HOME', 'r_home_dir'),)):
|
|
assert r._rscript_exec() == expected
|
|
|
|
|
|
def test_path_rscript_exec_no_r_home_set():
|
|
with envcontext.envcontext((('R_HOME', envcontext.UNSET),)):
|
|
assert r._rscript_exec() == 'Rscript'
|
|
|
|
|
|
@pytest.fixture
|
|
def renv_lock_file(tmp_path):
|
|
renv_lock = '''\
|
|
{
|
|
"R": {
|
|
"Version": "4.0.3",
|
|
"Repositories": [
|
|
{
|
|
"Name": "CRAN",
|
|
"URL": "https://cloud.r-project.org"
|
|
}
|
|
]
|
|
},
|
|
"Packages": {
|
|
"renv": {
|
|
"Package": "renv",
|
|
"Version": "0.12.5",
|
|
"Source": "Repository",
|
|
"Repository": "CRAN",
|
|
"Hash": "5c0cdb37f063c58cdab3c7e9fbb8bd2c"
|
|
},
|
|
"rprojroot": {
|
|
"Package": "rprojroot",
|
|
"Version": "1.0",
|
|
"Source": "Repository",
|
|
"Repository": "CRAN",
|
|
"Hash": "86704667fe0860e4fec35afdfec137f3"
|
|
}
|
|
}
|
|
}
|
|
'''
|
|
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)
|
|
Type: Package
|
|
Version: 0.0.0.9000
|
|
Authors@R:
|
|
person(given = "First",
|
|
family = "Last",
|
|
role = c("aut", "cre"),
|
|
email = "first.last@example.com",
|
|
comment = c(ORCID = "YOUR-ORCID-ID"))
|
|
Description: What the package does (one paragraph).
|
|
License: `use_mit_license()`, `use_gpl3_license()` or friends to
|
|
pick a license
|
|
Encoding: UTF-8
|
|
LazyData: true
|
|
Roxygen: list(markdown = TRUE)
|
|
RoxygenNote: 7.1.1
|
|
Imports:
|
|
rprojroot
|
|
'''
|
|
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
|
|
|
|
|
|
@pytest.fixture
|
|
def renv_folder(tmp_path):
|
|
renv_dir = tmp_path.joinpath('renv')
|
|
renv_dir.mkdir()
|
|
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
|
|
|
|
|
|
def test_r_inline(tmp_path):
|
|
_make_local_repo(str(tmp_path))
|
|
|
|
cmd = '''\
|
|
Rscript -e '
|
|
stopifnot(packageVersion("rprojroot") == "1.0")
|
|
cat(commandArgs(trailingOnly = TRUE), "from R!\n", sep=", ")
|
|
'
|
|
'''
|
|
|
|
ret = run_language(
|
|
tmp_path,
|
|
r,
|
|
cmd,
|
|
deps=('rprojroot@1.0',),
|
|
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_r_in_renv(
|
|
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)
|