Skip to content

Commit e4cfb46

Browse files
jaimergpkenodegardjezdez
authored
conda list: Exit with return code 1 if the queried packages can't be found (conda#15075)
* conda list: Exit with return code 1 if the queried packages can't be found * add test * add news * Remove --check, just raise * adjust tests * pre-commit * amend news * Apply suggestions from code review * Don't search for full name if there is no regex provided. * Fix test_list_package. * Refactor tests for conda list command; consolidate argument variations and improve error handling for invalid prefixes. --------- Co-authored-by: Ken Odegard <kodegard@anaconda.com> Co-authored-by: Jannis Leidel <jannis@leidel.info>
1 parent e1997c0 commit e4cfb46

4 files changed

Lines changed: 78 additions & 25 deletions

File tree

conda/cli/main_list.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@ def list_packages(
245245

246246
packages.append(row)
247247

248+
if regex and not packages:
249+
raise CondaValueError(f"No packages match '{regex}'.")
250+
248251
if reverse:
249252
packages = reversed(packages)
250253

@@ -349,7 +352,7 @@ def execute(args: Namespace, parser: ArgumentParser) -> int:
349352
)
350353

351354
regex = args.regex
352-
if args.full_name:
355+
if regex and args.full_name:
353356
regex = rf"^{regex}$"
354357

355358
if args.revisions:

news/15075-list-exit-code

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
### Enhancements
2+
3+
* `conda list pattern` will raise an exception with exit code 1 if the query did not match any packages in the target environment. (#15074 via #15075)
4+
5+
### Bug fixes
6+
7+
* <news item>
8+
9+
### Deprecations
10+
11+
* <news item>
12+
13+
### Docs
14+
15+
* <news item>
16+
17+
### Other
18+
19+
* <news item>

tests/cli/test_conda_argparse.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from __future__ import annotations
44

55
import importlib
6-
import re
76
import sys
87
from inspect import isclass, isfunction
98
from logging import getLogger
@@ -16,7 +15,6 @@
1615
_GreedySubParsersAction,
1716
generate_parser,
1817
)
19-
from conda.exceptions import EnvironmentLocationNotFound
2018

2119
if TYPE_CHECKING:
2220
from typing import Any, Callable
@@ -26,26 +24,6 @@
2624
log = getLogger(__name__)
2725

2826

29-
def test_list_through_python_api(conda_cli: CondaCLIFixture):
30-
with pytest.raises(EnvironmentLocationNotFound, match="Not a conda environment"):
31-
conda_cli("list", "--prefix", "not-a-real-path")
32-
33-
# cover argument variations
34-
# mutually exclusive: --canonical, --export, --explicit, (default human readable)
35-
for args1 in [[], ["--json"]]:
36-
for args2 in [[], ["--revisions"]]:
37-
for args3 in [
38-
["--canonical"],
39-
["--export"],
40-
["--explicit", "--md5"],
41-
["--full-name"],
42-
]:
43-
args = (*args1, *args2, *args3)
44-
stdout, _, _ = conda_cli("list", *args)
45-
if "--md5" in args and "--revisions" not in args:
46-
assert re.search(r"#[0-9a-f]{32}", stdout)
47-
48-
4927
def test_parser_basics():
5028
p = generate_parser()
5129
with pytest.raises(SystemExit, match="2"):

tests/cli/test_main_list.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import json
6+
import re
67
import sys
78
from typing import TYPE_CHECKING
89

@@ -11,7 +12,7 @@
1112
from conda.base.constants import CONDA_LIST_FIELDS
1213
from conda.common.configuration import CustomValidationError
1314
from conda.core.prefix_data import PrefixData
14-
from conda.exceptions import EnvironmentLocationNotFound
15+
from conda.exceptions import CondaValueError, EnvironmentLocationNotFound
1516
from conda.testing.integration import package_is_installed
1617

1718
if TYPE_CHECKING:
@@ -26,6 +27,10 @@
2627
)
2728

2829

30+
# Precompile for reuse in parameterized cases
31+
MD5_HEX_RE = re.compile(r"#[0-9a-f]{32}")
32+
33+
2934
@pytest.fixture
3035
def tmp_envs_dirs(mocker: MockerFixture, tmp_path: Path) -> Path:
3136
mocker.patch(
@@ -47,6 +52,40 @@ def test_list(
4752
assert any(item["name"] == pkg for item in json.loads(stdout))
4853

4954

55+
@pytest.mark.parametrize(
56+
"args",
57+
[
58+
["--canonical"],
59+
["--export"],
60+
["--explicit", "--md5"],
61+
["--full-name"],
62+
["--revisions", "--canonical"],
63+
["--revisions", "--export"],
64+
["--revisions", "--explicit", "--md5"],
65+
["--revisions", "--full-name"],
66+
["--json", "--canonical"],
67+
["--json", "--export"],
68+
["--json", "--explicit", "--md5"],
69+
["--json", "--full-name"],
70+
["--json", "--revisions", "--canonical"],
71+
["--json", "--revisions", "--export"],
72+
["--json", "--revisions", "--explicit", "--md5"],
73+
["--json", "--revisions", "--full-name"],
74+
],
75+
)
76+
def test_list_argument_variations(conda_cli: CondaCLIFixture, args: list[str]):
77+
# cover argument variations
78+
# mutually exclusive: --canonical, --export, --explicit, (default human readable)
79+
stdout, _, _ = conda_cli("list", *args)
80+
if "--md5" in args and "--revisions" not in args:
81+
assert MD5_HEX_RE.search(stdout)
82+
83+
84+
def test_list_with_bad_prefix_raises(conda_cli: CondaCLIFixture):
85+
with pytest.raises(EnvironmentLocationNotFound, match="Not a conda environment"):
86+
conda_cli("list", "--prefix", "not-a-real-path")
87+
88+
5089
# conda list --reverse
5190
def test_list_reverse(
5291
tmp_env: TmpEnvFixture,
@@ -111,9 +150,10 @@ def test_list_revisions(tmp_envs_dirs: Path, conda_cli: CondaCLIFixture) -> None
111150

112151
# conda list PACKAGE
113152
def test_list_package(tmp_envs_dirs: Path, conda_cli: CondaCLIFixture) -> None:
114-
stdout, _, _ = conda_cli("list", "ipython", "--json")
153+
stdout, _, _ = conda_cli("list", "python", "--json")
115154
parsed = json.loads(stdout.strip())
116155
assert isinstance(parsed, list)
156+
assert "python" in [package["name"] for package in parsed]
117157

118158

119159
def test_list_explicit(
@@ -249,3 +289,16 @@ def test_fields_invalid(conda_cli):
249289
)
250290
assert "list_fields" in str(exc)
251291
assert "invalid-field" in str(exc)
292+
293+
294+
def test_exit_codes(conda_cli):
295+
# If the package is installed, with or without --check, the exit code must be 0
296+
out, err, rc = conda_cli("list", f"--prefix={sys.prefix}", "conda")
297+
assert rc == 0
298+
299+
conda_cli(
300+
"list",
301+
f"--prefix={sys.prefix}",
302+
"does-not-exist",
303+
raises=CondaValueError,
304+
)

0 commit comments

Comments
 (0)