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