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)
|