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

conda_install and install args are now automatically double-quoted when needed. #312

Merged
merged 13 commits into from
Jun 24, 2020
33 changes: 33 additions & 0 deletions nox/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,36 @@ def _normalize_path(envdir: str, path: Union[str, bytes]) -> str:
return full_path


def _dblquote_pkg_install_args(args: Tuple[str, ...]) -> Tuple[str, ...]:
"""Double-quote package install arguments in case they contain '>' or '<' symbols"""

# routine used to handle a single arg
def _dblquote_pkg_install_arg(pkg_req_str: str) -> str:
# sanity check: we need an even number of double-quotes
if pkg_req_str.count('"') % 2 != 0:
raise ValueError(
"ill-formated argument with odd number of quotes: %s" % pkg_req_str
)

if "<" in pkg_req_str or ">" in pkg_req_str:
if pkg_req_str[0] == '"' and pkg_req_str[-1] == '"':
# already double-quoted string
return pkg_req_str
else:
# need to double-quote string
if '"' in pkg_req_str:
raise ValueError(
"Cannot escape requirement string: %s" % pkg_req_str
)
return '"%s"' % pkg_req_str
else:
# no dangerous char: no need to double-quote string
return pkg_req_str

# double-quote all args that need to be and return the result
return tuple(_dblquote_pkg_install_arg(a) for a in args)


class _SessionQuit(Exception):
pass

Expand Down Expand Up @@ -320,6 +350,9 @@ def conda_install(self, *args: str, **kwargs: Any) -> None:
if not args:
raise ValueError("At least one argument required to install().")

# Escape args that should be (conda-specific; pip install does not need this)
args = _dblquote_pkg_install_args(args)

if "silent" not in kwargs:
kwargs["silent"] = True

Expand Down
40 changes: 36 additions & 4 deletions tests/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,22 @@ def test_conda_install_bad_args(self):
with pytest.raises(ValueError, match="arg"):
session.conda_install()

def test_conda_install_bad_args_odd_nb_double_quotes(self):
session, runner = self.make_session_and_runner()
runner.venv = mock.create_autospec(nox.virtualenv.CondaEnv)
runner.venv.location = "./not/a/location"

with pytest.raises(ValueError, match="odd number of quotes"):
session.conda_install('a"a')

def test_conda_install_bad_args_cannot_escape(self):
session, runner = self.make_session_and_runner()
runner.venv = mock.create_autospec(nox.virtualenv.CondaEnv)
runner.venv.location = "./not/a/location"

with pytest.raises(ValueError, match="Cannot escape"):
session.conda_install('a"o"<a')

def test_conda_install_not_a_condaenv(self):
session, runner = self.make_session_and_runner()

Expand Down Expand Up @@ -330,7 +346,12 @@ class SessionNoSlots(nox.sessions.Session):
external="error",
)

def test_conda_install_non_default_kwargs(self):
@pytest.mark.parametrize(
"version_constraint",
["no", "yes", "already_dbl_quoted"],
ids="version_constraint={}".format,
)
def test_conda_install_non_default_kwargs(self, version_constraint):
runner = nox.sessions.SessionRunner(
name="test",
signatures=["test"],
Expand All @@ -347,21 +368,32 @@ class SessionNoSlots(nox.sessions.Session):

session = SessionNoSlots(runner=runner)

if version_constraint == "no":
pkg_requirement = passed_arg = "urllib3"
elif version_constraint == "yes":
pkg_requirement = "urllib3<1.25"
passed_arg = '"%s"' % pkg_requirement
elif version_constraint == "already_dbl_quoted":
pkg_requirement = passed_arg = '"urllib3<1.25"'
else:
raise ValueError(version_constraint)

with mock.patch.object(session, "_run", autospec=True) as run:
session.conda_install("requests", "urllib3", silent=False)
session.conda_install("requests", pkg_requirement, silent=False)
run.assert_called_once_with(
"conda",
"install",
"--yes",
"--prefix",
"/path/to/conda/env",
"requests",
"urllib3",
# this will be double quoted if unquoted constraint is present
passed_arg,
silent=False,
external="error",
)

def test_install_bad_args(self):
def test_install_bad_args_no_arg(self):
session, _ = self.make_session_and_runner()

with pytest.raises(ValueError, match="arg"):
Expand Down