From b82963dacfdc544e6780cd3874efb1f2f41e49b3 Mon Sep 17 00:00:00 2001 From: memsharded Date: Tue, 27 Dec 2022 23:10:52 +0100 Subject: [PATCH 1/8] [develop2] tests list --- .../test/integration/command_v2/list_test.py | 76 +++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/conans/test/integration/command_v2/list_test.py b/conans/test/integration/command_v2/list_test.py index 69300f7d904..f5b1e95dc37 100644 --- a/conans/test/integration/command_v2/list_test.py +++ b/conans/test/integration/command_v2/list_test.py @@ -152,20 +152,28 @@ def test_search_in_missing_remote(self): assert expected_output in self.client.out -class TestListUseCases(TestListBase): - - def test_list_recipes(self): - self.client.save({ +class TestListRecipes: + @pytest.fixture(scope="class") + def client(self): + c = TestClient() + c.save({ "zlib.py": GenConanfile("zlib", "1.0.0"), "zlib2.py": GenConanfile("zlib", "2.0.0"), "zli.py": GenConanfile("zli", "1.0.0"), "zlix.py": GenConanfile("zlix", "1.0.0"), }) - self.client.run("export zlib.py --user=user --channel=channel") - self.client.run("export zlib2.py --user=user --channel=channel") - self.client.run("export zli.py") - self.client.run("export zlix.py") - self.client.run(f"list z*") + c.run("export zlib.py --user=user --channel=channel") + c.run("export zlib2.py --user=user --channel=channel") + c.run("export zli.py") + c.run("export zlix.py") + c.save({"conanfile.py": GenConanfile("test_recipe", "1.0.0").with_settings("os")}) + c.run("export . --user=user --channel=channel") + c.save({"conanfile.py": GenConanfile("test_recipe", "1.0.0")}) + c.run("export . --user=user --channel=channel") + return c + + def test_list_recipes(self, client): + client.run(f"list z*") expected_output = textwrap.dedent(f"""\ Local Cache: zlix @@ -176,21 +184,45 @@ def test_list_recipes(self): zlib/2.0.0@user/channel zlib/1.0.0@user/channel """) - assert expected_output == self.client.out + assert expected_output == client.out - def test_list_latest_recipe_revision(self): - self.client.save({ - "conanfile.py": GenConanfile("test_recipe", "1.0.0").with_package_file("file.h", "0.1") - }) - self.client.run("export . --user=user --channel=channel") - rrev = self._get_lastest_recipe_ref("test_recipe/1.0.0@user/channel") - self.client.run(f"list test_recipe/1.0.0@user/channel") + def test_list_single_latest_recipe_revision(self, client): + """ list only the latest recipe revision for a specific reference + """ + client.run(f"list test_recipe/1.0.0@user/channel") expected_output = textwrap.dedent(f"""\ - Local Cache: - test_recipe - %s .* - """ % rrev.repr_notime()) - assert bool(re.match(expected_output, str(self.client.out), re.MULTILINE)) + Local Cache: + test_recipe + test_recipe/1.0.0@user/channel#60bd4448240d249ba9c82a762ccbed76 .* + """) + assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) + + def test_list_multiple_latest_recipe_revision(self, client): + """ list only the latest recipe revision for many references + """ + client.run(f"list zlib/*#latest") + print(client.out) + expected_output = textwrap.dedent(f"""\ + Local Cache: + test_recipe + test_recipe/1.0.0@user/channel#60bd4448240d249ba9c82a762ccbed76 .* + """) + assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) + + def test_list_all_recipe_revisions(self, client): + """ list all recipe revisions + """ + client.run(f"list test_recipe/1.0.0@user/channel#*") + expected_output = textwrap.dedent(f"""\ + Local Cache: + test_recipe + test_recipe/1.0.0@user/channel#60bd4448240d249ba9c82a762ccbed76 .* + test_recipe/1.0.0@user/channel#7fbffd1bc2c4ada0f7c9d234ec842f66 .* + """) + assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) + + +class TestListUseCases(TestListBase): def test_list_latest_package_revisions(self): self.client.save({ From be2c9dbd170cb2f6e38961135607cc7a27792204 Mon Sep 17 00:00:00 2001 From: memsharded Date: Fri, 30 Dec 2022 22:34:16 +0100 Subject: [PATCH 2/8] wip --- conan/api/model.py | 19 +- conan/api/subapi/list.py | 9 +- conan/cli/commands/list.py | 3 +- conan/cli/commands/search.py | 3 +- conans/client/remote_manager.py | 4 +- conans/server/service/v2/search.py | 4 +- .../test/integration/command_v2/list_test.py | 890 ++++++------------ 7 files changed, 319 insertions(+), 613 deletions(-) diff --git a/conan/api/model.py b/conan/api/model.py index 874d6219f60..e7b5516e7dd 100644 --- a/conan/api/model.py +++ b/conan/api/model.py @@ -81,26 +81,15 @@ def prefs(self): return prefs def add_prefs(self, prefs, configurations=None): - confs = configurations or OrderedDict() + confs = configurations or {} for pref in prefs: binary_info = confs.get(pref, OrderedDict()) self.recipes.setdefault(pref.ref, []).append((pref, binary_info)) - @property - def ordered_recipes_by_name(self): - ret = OrderedDict() - for ref, prefs in self.recipes.items(): - ret.setdefault(ref.name, OrderedDict()).setdefault(ref, prefs) - return ret - def serialize(self): - ret = OrderedDict() - for ref, prefs in self.recipes.items(): - pref_ret = OrderedDict() - if prefs: - for pref, binary_info in prefs: - pref_ret[pref.repr_notime()] = binary_info - ret[ref.repr_notime()] = pref_ret + ret = {} + for ref, prefs in sorted(self.recipes.items()): + ret.setdefault(ref.name, {})[ref] = prefs return ret diff --git a/conan/api/subapi/list.py b/conan/api/subapi/list.py index e78afdfccc8..0d4b61199f2 100644 --- a/conan/api/subapi/list.py +++ b/conan/api/subapi/list.py @@ -44,6 +44,8 @@ def latest_package_revision(self, pref: PkgReference, remote=None): ret = app.remote_manager.get_latest_package_reference(pref, remote=remote) else: ret = app.cache.get_latest_package_reference(pref) + if ret is None: + raise ConanException(f"Binary package not found: '{pref}'") return ret @@ -96,10 +98,9 @@ def select(self, pattern, package_query=None, remote=None, search_mode=None): search_mode = search_mode or pattern.mode select_bundle = SelectBundle() refs = self.conan_api.search.recipes(pattern.ref, remote=remote) - pattern.check_refs(refs) # Show only the recipe references - if search_mode == ListPatternMode.SHOW_REFS: + if refs and search_mode == ListPatternMode.SHOW_REFS: select_bundle.add_refs(refs) return select_bundle @@ -138,7 +139,11 @@ def select(self, pattern, package_query=None, remote=None, search_mode=None): for pref in prefs: if search_mode in (ListPatternMode.SHOW_LATEST_PREV, ListPatternMode.SHOW_ALL_PREVS): + pref.revision = None # Need to invalidate if pattern.is_latest_prev: + # TODO: This might be unnecessary, if we + # called before package_configurations() that already + # returns latest prev prevs = [self.conan_api.list.latest_package_revision(pref, remote)] else: prevs = self.conan_api.list.package_revisions(pref, remote) diff --git a/conan/cli/commands/list.py b/conan/cli/commands/list.py index 643e8ef3256..6d5ba285756 100644 --- a/conan/cli/commands/list.py +++ b/conan/cli/commands/list.py @@ -113,8 +113,7 @@ def list(conan_api: ConanAPI, parser, *args): except Exception as e: results[name] = {"error": str(e)} else: - results[name] = list_bundle.serialize() if args.format in ("json", "html") \ - else list_bundle.ordered_recipes_by_name + results[name] = list_bundle.serialize() return { "results": results, "search_mode": ref_pattern.mode, diff --git a/conan/cli/commands/search.py b/conan/cli/commands/search.py index a6bb9fa9609..fc698705ba2 100644 --- a/conan/cli/commands/search.py +++ b/conan/cli/commands/search.py @@ -35,8 +35,7 @@ def search(conan_api: ConanAPI, parser, *args): except Exception as e: results[remote.name] = {"error": str(e)} else: - results[remote.name] = list_bundle.serialize() if args.format == "json" \ - else list_bundle.ordered_recipes_by_name + results[remote.name] = list_bundle.serialize() return { "results": results, "search_mode": search_mode, diff --git a/conans/client/remote_manager.py b/conans/client/remote_manager.py index 14bbb1d2d1a..a3903605214 100644 --- a/conans/client/remote_manager.py +++ b/conans/client/remote_manager.py @@ -150,7 +150,9 @@ def search_recipes(self, remote, pattern): def search_packages(self, remote, ref): packages = self._call_remote(remote, "search_packages", ref) # Avoid serializing conaninfo in server side - packages = {PkgReference(ref, pid): load_binary_info(data["content"]) + # TODO: This has to be implemented in Artifactory too + packages = {PkgReference(ref, pid, data.get("prev"), data.get("timestamp")): + load_binary_info(data["content"]) if "content" in data else data for pid, data in packages.items() if not data.get("recipe_hash")} return packages diff --git a/conans/server/service/v2/search.py b/conans/server/service/v2/search.py index aa06371a4ff..f0f7d2d0874 100644 --- a/conans/server/service/v2/search.py +++ b/conans/server/service/v2/search.py @@ -38,7 +38,9 @@ def _get_local_infos_min(server_store, ref, look_in_all_rrevs): raise NotFoundException("") content = load(info_path) # From Conan 1.48 the conaninfo.txt is sent raw. - result[package_id] = {"content": content} + result[package_id] = {"content": content, + "prev": pref.revision, + "timestamp": revision_entry.timestamp} except Exception as exc: # FIXME: Too wide ConanOutput().error("Package %s has no ConanInfo file" % str(pref)) if str(exc): diff --git a/conans/test/integration/command_v2/list_test.py b/conans/test/integration/command_v2/list_test.py index 941022d4b8a..6d885069a38 100644 --- a/conans/test/integration/command_v2/list_test.py +++ b/conans/test/integration/command_v2/list_test.py @@ -1,633 +1,343 @@ -import json import os import re import textwrap +import time +from collections import OrderedDict from unittest.mock import patch, Mock import pytest from conans.errors import ConanException, ConanConnectionError -from conans.model.recipe_ref import RecipeReference -from conans.model.package_ref import PkgReference from conans.test.assets.genconanfile import GenConanfile from conans.test.utils.tools import TestClient, TestServer +from conans.util.env import environment_update -class TestListBase: - @pytest.fixture(autouse=True) - def _setup(self): - self.client = TestClient() - - def _add_remote(self, remote_name): - self.client.servers[remote_name] = TestServer(users={"username": "passwd"}, - write_permissions=[("*/*@*/*", "*")]) - self.client.update_servers() - self.client.run("remote login {} username -p passwd".format(remote_name)) - - def _upload_recipe(self, remote, ref): - self.client.save({'conanfile.py': GenConanfile()}) - ref = RecipeReference.loads(ref) - self.client.run(f"create . --name={ref.name} --version={ref.version} " - f"--user={ref.user} --channel={ref.channel}") - self.client.run("upload --force -r {} {}".format(remote, ref)) - - def _upload_full_recipe(self, remote, ref): - self.client.save({"conanfile.py": GenConanfile("pkg", "0.1").with_package_file("file.h", - "0.1")}) - self.client.run("create . --user=user --channel=channel") - self.client.run("upload --force -r {} {}".format(remote, "pkg/0.1@user/channel")) - - self.client.save({'conanfile.py': GenConanfile().with_require("pkg/0.1@user/channel") - .with_settings("os", "build_type", "arch") - .with_option("shared", [True, False]) - .with_default_option("shared", False) - }) - self.client.run(f"create . --name={ref.name} --version={ref.version} " - f"-s os=Macos -s build_type=Release -s arch=x86_64 " - f"--user={ref.user} --channel={ref.channel}") - self.client.run("upload --force -r {} {}".format(remote, ref)) - - def _upload_full_recipe_without_conaninfo(self, remote, ref): - self.client.save({"conanfile.py": GenConanfile("pkg", "0.1").with_package_file("file.h", - "0.1")}) - self.client.run("create . --user=user --channel=channel") - self.client.run("upload --force -r {} {}".format(remote, "pkg/0.1@user/channel")) - - self.client.save({'conanfile.py': GenConanfile() - }) - self.client.run(f"create . --name={ref.name} --version={ref.version} " - f"--user={ref.user} --channel={ref.channel}") - self.client.run("upload --force -r {} {}".format(remote, ref)) +class TestParamErrors: + def test_query_param_is_required(self): + c = TestClient() + c.run("list", assert_error=True) + assert "error: the following arguments are required: reference" in c.out + + c.run("list -c", assert_error=True) + assert "error: the following arguments are required: reference" in c.out + + c.run('list -r="*"', assert_error=True) + assert "error: the following arguments are required: reference" in c.out + + c.run("list --remote remote1 --cache", assert_error=True) + assert "error: the following arguments are required: reference" in c.out + + +@pytest.fixture(scope="module") +def client(): + servers = OrderedDict([("default", TestServer()), + ("other", TestServer())]) + c = TestClient(servers=servers, inputs=2*["admin", "password"]) + c.save({ + "zlib.py": GenConanfile("zlib"), + "zlib_ng.py": GenConanfile("zlib_ng", "1.0.0"), + "zli.py": GenConanfile("zli", "1.0.0"), + "zli_rev2.py": GenConanfile("zli", "1.0.0").with_settings("os") + .with_package_file("f.txt", env_var="MYREV"), + "zlix.py": GenConanfile("zlix", "1.0.0"), + "test.py": GenConanfile("test", "1.0").with_requires("zlix/1.0.0") + + .with_python_requires("zlix/1.0.0"), + "conf.py": GenConanfile("conf", "1.0") + }) + c.run("create zli.py") + c.run("create zlib.py --version=1.0.0 --user=user --channel=channel") + c.run("create zlib.py --version=2.0.0 --user=user --channel=channel") + c.run("create zlix.py") + c.run("create test.py") + c.run('create conf.py -c tools.info.package_id:confs="[\'tools.build:cxxflags\']"' + ' -c tools.build:cxxflags="[\'--flag1\']"') + c.run("upload * -r=default -c") + c.run("upload * -r=other -c") + + time.sleep(1.0) + # We create and upload new revisions later, to avoid timestamp overlaps (low resolution) + with environment_update({"MYREV": "0"}): + c.run("create zli_rev2.py -s os=Windows") + c.run("create zli_rev2.py -s os=Linux") + c.run("upload * -r=default -c") + with environment_update({"MYREV": "42"}): + c.run("create zli_rev2.py -s os=Windows") + c.run("upload * -r=default -c") + return c + + +class TestListRefs: @staticmethod - def _get_fake_recipe_refence(recipe_name): - return f"{recipe_name}#fca0383e6a43348f7989f11ab8f0a92d" - - def _get_lastest_recipe_ref(self, recipe_name): - return self.client.cache.get_latest_recipe_reference(RecipeReference.loads(recipe_name)) + def check(client, pattern, remote, expected): + r = "-r=default" if remote else "" + r_msg = "default" if remote else "Local Cache" + client.run(f"list {pattern} {r}") + print(client.out) + expected = textwrap.indent(expected, " ") + expected_output = f"{r_msg}:\n" + expected + assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) - def _get_lastest_package_ref(self, pref): - return self.client.cache.get_latest_package_reference(PkgReference.loads(pref)) + @pytest.mark.parametrize("remote", [True, False]) + def test_list_recipes(self, client, remote): + pattern = "z*" + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0 + zlib + zlib/1.0.0@user/channel + zlib/2.0.0@user/channel + zlix + zlix/1.0.0 + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + @pytest.mark.parametrize("pattern", ["zlib", "zlib/*", "*@user/channel"]) + def test_list_recipe_versions(self, client, pattern, remote): + expected = textwrap.dedent(f"""\ + zlib + zlib/1.0.0@user/channel + zlib/2.0.0@user/channel + """) + self.check(client, pattern, remote, expected) + @pytest.mark.parametrize("remote", [True, False]) + @pytest.mark.parametrize("pattern", ["nomatch", "nomatch*", "nomatch/*"]) + def test_list_recipe_no_match(self, client, pattern, remote): + expected = textwrap.dedent(f"""\ + There are no matching recipe references + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_recipe_latest_revision(self, client, remote): + # by default, when a reference is complete, we show latest recipe revision + pattern = "zli/1.0.0" + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_recipe_all_latest_revision(self, client, remote): + # we can show the latest revision from several matches, if we add ``#latest`` + pattern = "zlib/*#latest" + expected = textwrap.dedent(f"""\ + zlib + zlib/1.0.0@user/channel#ffd4bc45820ddb320ab224685b9ba3fb .* + zlib/2.0.0@user/channel#ffd4bc45820ddb320ab224685b9ba3fb .* + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_recipe_several_revision(self, client, remote): + # we can show the latest revision from several matches, if we add ``#latest`` + pattern = "zli/1.0.0#*" + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0#f034dc90894493961d92dd32a9ee3b78 .* + zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + """) + self.check(client, pattern, remote, expected) -class TestParams(TestListBase): + @pytest.mark.parametrize("remote", [True, False]) + def test_list_recipe_multiple_revision(self, client, remote): + pattern = "zli*#*" + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0#f034dc90894493961d92dd32a9ee3b78 .* + zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + zlib + zlib/1.0.0@user/channel#ffd4bc45820ddb320ab224685b9ba3fb .* + zlib/2.0.0@user/channel#ffd4bc45820ddb320ab224685b9ba3fb .* + zlix + zlix/1.0.0#81f598d1d8648389bb7d0494fffb654e .* + """) + self.check(client, pattern, remote, expected) - def test_query_param_is_required(self): - self._add_remote("remote1") - self.client.run("list", assert_error=True) - assert "error: the following arguments are required: reference" in self.client.out +class TestListPrefs: - self.client.run("list -c", assert_error=True) - assert "error: the following arguments are required: reference" in self.client.out + @staticmethod + def check(client, pattern, remote, expected): + r = "-r=default" if remote else "" + r_msg = "default" if remote else "Local Cache" + client.run(f"list {pattern} {r}") + print(client.out) + expected = textwrap.indent(expected, " ") + expected_output = f"{r_msg}:\n" + expected + assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) - self.client.run('list -r="*"', assert_error=True) - assert "error: the following arguments are required: reference" in self.client.out + @pytest.mark.parametrize("remote", [True, False]) + def test_list_pkg_ids(self, client, remote): + pattern = "zli/1.0.0:*" + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 .* + settings: + os=Linux + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 .* + settings: + os=Windows + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_pkg_ids_confs(self, client, remote): + pattern = "conf/*:*" + expected = textwrap.dedent("""\ + conf + conf/1.0#e4e1703f72ed07c15d73a555ec3a2fa1 .* + PID: 78c6fa29e8164ce399087ad6067c8f9e2f1c4ad0 .* + conf: + tools.build:cxxflags=\['--flag1'\] + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_pkg_ids_requires(self, client, remote): + pattern = "test/*:*" + expected = textwrap.dedent("""\ + test + test/1.0#7df6048d3cb39b75618717987fb96453 .* + PID: 81d0d9a6851a0208c2bb35fdb34eb156359d939b .* + requires: + zlix/1.Y.Z + python_requires: + zlix/1.0.Z + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_pkg_ids_all_rrevs(self, client, remote): + pattern = "zli/1.0.0#*:*" + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0#f034dc90894493961d92dd32a9ee3b78 .* + PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 .* + Empty package information + zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 .* + settings: + os=Linux + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 .* + settings: + os=Windows + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_latest_prevs(self, client, remote): + pattern = "zli/1.0.0:*#latest" + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 + PREV: 9beff32b8c94ea0ce5a5e67dad95f525 .* + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + PREV: d9b1e9044ee265092e81db7028ae10e0 .* + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_all_prevs(self, client, remote): + pattern = "zli/1.0.0:*#*" + # TODO: We might want to improve the output, grouping PREVS for the + # same package_id + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 + PREV: 9beff32b8c94ea0ce5a5e67dad95f525 .* + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + PREV: d9b1e9044ee265092e81db7028ae10e0 .* + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + PREV: 24532a030b4fcdfed699511f6bfe35d3 .* + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_package_id_all_prevs(self, client, remote): + pattern = "zli/1.0.0:ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715#*" + # TODO: We might want to improve the output, grouping PREVS for the + # same package_id + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + PREV: d9b1e9044ee265092e81db7028ae10e0 .* + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + PREV: 24532a030b4fcdfed699511f6bfe35d3 .* + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_package_id_latest_prev(self, client, remote): + pattern = "zli/1.0.0:ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715" + expected = textwrap.dedent(f"""\ + zli + zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + PREV: d9b1e9044ee265092e81db7028ae10e0 .* + """) + self.check(client, pattern, remote, expected) + + @pytest.mark.parametrize("remote", [True, False]) + def test_list_missing_package_id(self, client, remote): + pattern = "zli/1.0.0:nonexists_id" + # TODO: The message is still different in the server + expected = textwrap.dedent(f"""\ + ERROR: Binary package not found: 'zli/1.0.0.* + """) + self.check(client, pattern, remote, expected) - self.client.run("list --remote remote1 --cache", assert_error=True) - assert "error: the following arguments are required: reference" in self.client.out + def test_query(self): + pass -class TestRemotes(TestListBase): - def test_by_default_search_only_in_cache(self): - self._add_remote("remote1") - self._add_remote("remote2") +class TestListRemotes: + """ advanced use case: + - check multiple remotes output + """ + def test_search_no_matching_recipes(self, client): expected_output = textwrap.dedent("""\ Local Cache: - ERROR: Recipe 'whatever/0.1' not found""") - - self.client.run(f"list {self._get_fake_recipe_refence('whatever/0.1')}") - assert expected_output in self.client.out - - def test_search_no_matching_recipes(self): - self._add_remote("remote1") - self._add_remote("remote2") - - expected_output = textwrap.dedent("""\ - Local Cache: - ERROR: Recipe 'whatever/0.1' not found - remote1: - ERROR: Recipe 'whatever/0.1' not found - remote2: - ERROR: Recipe 'whatever/0.1' not found + There are no matching recipe references + default: + There are no matching recipe references + other: + There are no matching recipe references """) - rrev = self._get_fake_recipe_refence('whatever/0.1') - self.client.run(f'list -c -r="*" {rrev}') - assert expected_output == self.client.out + client.run('list -c -r="*" whatever/0.1') + assert expected_output == client.out def test_fail_if_no_configured_remotes(self): - self.client.run('list -r="*" whatever/1.0#123', assert_error=True) - assert "ERROR: Remotes for pattern '*' can't be found or are disabled" in self.client.out - - def test_search_disabled_remote(self): - self._add_remote("remote1") - self._add_remote("remote2") - self.client.run("remote disable remote1") - # He have to put both remotes instead of using "-a" because of the - # disbaled remote won't appear - self.client.run("list whatever/1.0#123 -r remote1 -r remote2", assert_error=True) - assert "ERROR: Remote 'remote1' can't be found or is disabled" in self.client.out + client = TestClient() + client.run('list -r="*" whatever/1.0#123', assert_error=True) + assert "ERROR: Remotes for pattern '*' can't be found or are disabled" in client.out @pytest.mark.parametrize("exc,output", [ (ConanConnectionError("Review your network!"), "ERROR: Review your network!"), (ConanException("Boom!"), "ERROR: Boom!") ]) - def test_search_remote_errors_but_no_raising_exceptions(self, exc, output): - self._add_remote("remote1") - self._add_remote("remote2") + def test_search_remote_errors_but_no_raising_exceptions(self, client, exc, output): with patch("conan.api.subapi.search.SearchAPI.recipes", new=Mock(side_effect=exc)): - self.client.run(f'list whatever/1.0 -r="*"') - expected_output = textwrap.dedent(f"""\ - remote1: - {output} - remote2: - {output} - """) - assert expected_output == self.client.out - - def test_search_in_missing_remote(self): - remote1 = "remote1" - - remote1_recipe1 = "test_recipe/1.0.0@user/channel" - remote1_recipe2 = "test_recipe/1.1.0@user/channel" - - expected_output = "ERROR: Remote 'wrong_remote' can't be found or is disabled" - - self._add_remote(remote1) - self._upload_recipe(remote1, remote1_recipe1) - self._upload_recipe(remote1, remote1_recipe2) - - rrev = self._get_fake_recipe_refence(remote1_recipe1) - self.client.run(f"list -r wrong_remote {rrev}", assert_error=True) - assert expected_output in self.client.out - - -class TestListRecipes: - @pytest.fixture(scope="class") - def client(self): - c = TestClient() - c.save({ - "zlib.py": GenConanfile("zlib", "1.0.0"), - "zlib2.py": GenConanfile("zlib", "2.0.0"), - "zli.py": GenConanfile("zli", "1.0.0"), - "zlix.py": GenConanfile("zlix", "1.0.0"), - }) - c.run("export zlib.py --user=user --channel=channel") - c.run("export zlib2.py --user=user --channel=channel") - c.run("export zli.py") - c.run("export zlix.py") - c.save({"conanfile.py": GenConanfile("test_recipe", "1.0.0").with_settings("os")}) - c.run("export . --user=user --channel=channel") - c.save({"conanfile.py": GenConanfile("test_recipe", "1.0.0")}) - c.run("export . --user=user --channel=channel") - return c - - def test_list_recipes(self, client): - client.run(f"list z*") - expected_output = textwrap.dedent(f"""\ - Local Cache: - zlix - zlix/1.0.0 - zli - zli/1.0.0 - zlib - zlib/2.0.0@user/channel - zlib/1.0.0@user/channel - """) - assert expected_output == client.out - - def test_list_single_latest_recipe_revision(self, client): - """ list only the latest recipe revision for a specific reference - """ - client.run(f"list test_recipe/1.0.0@user/channel") - expected_output = textwrap.dedent(f"""\ - Local Cache: - test_recipe - test_recipe/1.0.0@user/channel#60bd4448240d249ba9c82a762ccbed76 .* - """) - assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) - - def test_list_multiple_latest_recipe_revision(self, client): - """ list only the latest recipe revision for many references - """ - client.run(f"list zlib/*#latest") - print(client.out) - expected_output = textwrap.dedent(f"""\ - Local Cache: - test_recipe - test_recipe/1.0.0@user/channel#60bd4448240d249ba9c82a762ccbed76 .* - """) - assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) - - def test_list_all_recipe_revisions(self, client): - """ list all recipe revisions - """ - client.run(f"list test_recipe/1.0.0@user/channel#*") + client.run(f'list whatever/1.0 -r="*"') expected_output = textwrap.dedent(f"""\ - Local Cache: - test_recipe - test_recipe/1.0.0@user/channel#60bd4448240d249ba9c82a762ccbed76 .* - test_recipe/1.0.0@user/channel#7fbffd1bc2c4ada0f7c9d234ec842f66 .* - """) - assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) - - -class TestListUseCases(TestListBase): - - def test_list_all_the_latest_recipe_revision(self): - self.client.save({ - "hello1.py": GenConanfile("hello", "1.0.0").with_generator("CMakeToolchain"), # rrev - "hello.py": GenConanfile("hello", "1.0.0"), # latest rrev - "bye.py": GenConanfile("bye", "1.0.0") - }) - self.client.run("export hello1.py --user=user --channel=channel") - self.client.run("export hello.py --user=user --channel=channel") - hello_latest_rrev = self._get_lastest_recipe_ref("hello/1.0.0@user/channel") - self.client.run("export bye.py --user=user --channel=channel") - self.client.run(f"list *#latest") - expected_output = textwrap.dedent(f"""\ - Local Cache: - bye - bye/1.0.0@user/channel#c720a82a9c904a0450ec1aa177281ea2 .* - hello - hello/1.0.0@user/channel#7a34833afbd87d791b2201882b1afb2b .* - """) - assert bool(re.match(expected_output, str(self.client.out), re.MULTILINE)) - assert hello_latest_rrev.repr_notime() in expected_output - - def test_list_latest_package_revisions_by_default(self): - self.client.save({ - "conanfile.py": GenConanfile("test_recipe", "1.0.0").with_package_file("file.h", "0.1") - }) - self.client.run("create . --user=user --channel=channel") - rrev = self._get_lastest_recipe_ref("test_recipe/1.0.0@user/channel") - pid = "da39a3ee5e6b4b0d3255bfef95601890afd80709" - prev = self._get_lastest_package_ref(f"{rrev.repr_notime()}:{pid}") - self.client.run(f"list {prev.repr_notime()}") - expected_output = textwrap.dedent(f"""\ - Local Cache: - test_recipe - test_recipe/1.0.0@user/channel#ddfadce26d00a560850eb8767fe76ae4 .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - PREV: 9c929aed65f04337a4143311d72fc897 .* - """) - assert bool(re.match(expected_output, str(self.client.out), re.MULTILINE)) - - def test_list_all_the_latest_package_revisions(self): - self.client.save({ - "hello.py": GenConanfile("hello", "1.0.0").with_package_file("file.h", "0.1"), - "bye.py": GenConanfile("bye", "1.0.0").with_package_file("file.h", "0.1") - }) - self.client.run("create hello.py --user=user --channel=channel") - self.client.run("create hello.py --user=user --channel=channel") # latest prev - self.client.run("create bye.py --user=user --channel=channel") - self.client.run("create bye.py --user=user --channel=channel") # latest prev - self.client.run("list *:*#latest") - expected_output = textwrap.dedent(f"""\ - Local Cache: - bye - bye/1.0.0@user/channel#51edd97e27e407a01be830282558c32a .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - PREV: 9c929aed65f04337a4143311d72fc897 .* - hello - hello/1.0.0@user/channel#6fccfa5dd0bbb1223578c1771839eb6d .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - PREV: 9c929aed65f04337a4143311d72fc897 .* - """) - assert bool(re.match(expected_output, str(self.client.out), re.MULTILINE)) - - def test_search_package_ids_but_empty_conan_info(self): - remote_name = "remote1" - recipe_name = "test_recipe/1.0.0@user/channel" - self._add_remote(remote_name) - self._upload_recipe(remote_name, recipe_name) - rrev = self._get_lastest_recipe_ref(recipe_name) - self.client.run(f"list {rrev.repr_notime()}:* -r remote1") - expected_output = textwrap.dedent("""\ - remote1: - test_recipe - test_recipe/1.0.0@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - Empty package information - """) - assert bool(re.match(expected_output, str(self.client.out), re.MULTILINE)) - - def test_search_package_ids_from_latest_rrev_in_all_remotes_and_cache(self): - remote1 = "remote1" - remote2 = "remote2" - - self._add_remote(remote1) - self._upload_full_recipe(remote1, RecipeReference(name="test_recipe", version="1.0", - user="user", channel="channel")) - self._add_remote(remote2) - self._upload_full_recipe(remote2, RecipeReference(name="test_recipe", version="2.1", - user="user", channel="channel")) - self.client.run(f'list test_recipe/*:* -r="*" -c') - output = str(self.client.out) - expected_output = textwrap.dedent("""\ - Local Cache: - test_recipe - test_recipe/2.1@user/channel#a22316c3831b70763e4405841ee93f27 .* - PID: 630ddee056279fad89b691ac0f36eb084f40da38 .* - settings: - arch=x86_64 - build_type=Release - os=Macos - options: - shared=False - requires: - pkg/0.1.Z@user/channel - test_recipe/1.0@user/channel#a22316c3831b70763e4405841ee93f27 .* - PID: 630ddee056279fad89b691ac0f36eb084f40da38 .* - settings: - arch=x86_64 - build_type=Release - os=Macos - options: - shared=False - requires: - pkg/0.1.Z@user/channel - remote1: - test_recipe - test_recipe/1.0@user/channel#a22316c3831b70763e4405841ee93f27 .* - PID: 630ddee056279fad89b691ac0f36eb084f40da38 - settings: - arch=x86_64 - build_type=Release - os=Macos - options: - shared=False - requires: - pkg/0.1.Z@user/channel - remote2: - test_recipe - test_recipe/2.1@user/channel#a22316c3831b70763e4405841ee93f27 .* - PID: 630ddee056279fad89b691ac0f36eb084f40da38 - settings: - arch=x86_64 - build_type=Release - os=Macos - options: - shared=False - requires: - pkg/0.1.Z@user/channel - """) - assert bool(re.match(expected_output, output, re.MULTILINE)) - - def test_search_all_revisions_and_package_revisions(self): - """Checking if RREVs and PREVs are shown correctly""" - remote1 = "remote1" - remote2 = "remote2" - - self._add_remote(remote1) - self._upload_full_recipe_without_conaninfo(remote1, - RecipeReference(name="test_recipe", version="1.0", - user="user", channel="channel")) - self._add_remote(remote2) - self._upload_full_recipe_without_conaninfo(remote2, - RecipeReference(name="test_recipe", version="2.1", - user="user", channel="channel")) - self.client.run(f'list *#* -r="*" -c') - output = str(self.client.out) - expected_output = textwrap.dedent("""\ - Local Cache: - test_recipe - test_recipe/2.1@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - test_recipe/1.0@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - pkg - pkg/0.1@user/channel#44a36b797bc85fb66af6acf90cf8f539 .* - remote1: - pkg - pkg/0.1@user/channel#44a36b797bc85fb66af6acf90cf8f539 .* - test_recipe - test_recipe/1.0@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - remote2: - pkg - pkg/0.1@user/channel#44a36b797bc85fb66af6acf90cf8f539 .* - test_recipe - test_recipe/2.1@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - """) - assert bool(re.match(expected_output, output, re.MULTILINE)) - self.client.run(f'list test_recipe/*:*#* -r="*" -c') - output = str(self.client.out) - expected_output = textwrap.dedent("""\ - Local Cache: - test_recipe - test_recipe/2.1@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - PREV: 0ba8627bd47edc3a501e8f0eb9a79e5e .* - test_recipe/1.0@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - PREV: 0ba8627bd47edc3a501e8f0eb9a79e5e .* - remote1: - test_recipe - test_recipe/1.0@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - PREV: 0ba8627bd47edc3a501e8f0eb9a79e5e .* - remote2: - test_recipe - test_recipe/2.1@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - PREV: 0ba8627bd47edc3a501e8f0eb9a79e5e .* - """) - assert bool(re.match(expected_output, output, re.MULTILINE)) - - def test_search_all_revisions_given_a_package_id(self): - """ - Checking if PREVs are shown correctly given a PkgID and even though that package has no - configuration at all. - """ - remote1 = "remote1" - self._add_remote(remote1) - self._upload_full_recipe_without_conaninfo(remote1, - RecipeReference(name="test_recipe", version="1.0", - user="user", channel="channel")) - self.client.run(f'list *:* -r=remote1') - output = str(self.client.out) - expected_output = textwrap.dedent("""\ - remote1: - pkg - pkg/0.1@user/channel#44a36b797bc85fb66af6acf90cf8f539 .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - Empty package information - test_recipe - test_recipe/1.0@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - Empty package information - """) - assert bool(re.match(expected_output, output, re.MULTILINE)) - self.client.run(f'list test_recipe/*:da39a3ee5e6b4b0d3255bfef95601890afd80709#* -r=remote1') - output = str(self.client.out) - expected_output = textwrap.dedent("""\ - remote1: - test_recipe - test_recipe/1.0@user/channel#4d670581ccb765839f2239cc8dff8fbd .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - PREV: 0ba8627bd47edc3a501e8f0eb9a79e5e .* - """) - assert bool(re.match(expected_output, output, re.MULTILINE)) - - def test_list_package_query_options(self): - self.client.save({"conanfile.py": GenConanfile("pkg", "0.1") - .with_package_file("file.h", "0.1") - .with_settings("os", "build_type", "arch")}) - self.client.run("create . --user=user --channel=channel " - "-s os=Windows -s build_type=Release -s arch=x86_64") - self.client.run("create . --user=user --channel=channel " - "-s os=Macos -s build_type=Release -s arch=x86_64") - self.client.run("create . --user=user --channel=channel " - "-s os=Macos -s build_type=Release -s arch=armv7") - self.client.run(f'list pkg/0.1#*:*') - output = str(self.client.out) - expected_output = textwrap.dedent("""\ - Local Cache: - pkg - pkg/0.1@user/channel#89ab3ffd306cb65a8ca8e2a1c8b96aae .* - PID: 5f2a74726e897f644b3f42dea59faecf8eee2b50 .* - settings: - arch=armv7 - build_type=Release - os=Macos - PID: 723257509aee8a72faf021920c2874abc738e029 .* - settings: - arch=x86_64 - build_type=Release - os=Windows - PID: 9ac8640923e5284645f8852ef8ba335654f4020e .* - settings: - arch=x86_64 - build_type=Release - os=Macos - """) - assert bool(re.match(expected_output, output, re.MULTILINE)) - self.client.run(f'list pkg/0.1#*:* -p os=Windows') - output = str(self.client.out) - expected_output = textwrap.dedent("""\ - Local Cache: - pkg - pkg/0.1@user/channel#89ab3ffd306cb65a8ca8e2a1c8b96aae .* - PID: 723257509aee8a72faf021920c2874abc738e029 .* - settings: - arch=x86_64 - build_type=Release - os=Windows - """) - assert bool(re.match(expected_output, output, re.MULTILINE)) - - -class TestListPackages: - def test_list_package_info_and_json_format(self): - c = TestClient(default_server_user=True) - c.save({"dep/conanfile.py": GenConanfile("dep", "1.2.3"), - "pkg/conanfile.py": GenConanfile("pkg", "2.3.4").with_requires("dep/1.2.3") - .with_settings("os", "arch").with_shared_option(False)}) - c.run("create dep") - c.run("create pkg -s os=Windows -s arch=x86") - c.run("list pkg/2.3.4#0fc07368b81b38197adc73ee2cb89da8") - expected_output = textwrap.dedent(f"""\ - Local Cache: - pkg - pkg/2.3.4#0fc07368b81b38197adc73ee2cb89da8 .* - PID: ec080285423a5e38126f0d5d51b524cf516ff7a5 .* - settings: - arch=x86 - os=Windows - options: - shared=False - requires: - dep/1.2.Z - """) - assert bool(re.match(expected_output, c.out, re.MULTILINE)) - - rrev = "pkg/2.3.4#0fc07368b81b38197adc73ee2cb89da8" - c.run(f"list {rrev} --format=json", redirect_stdout="packages.json") - pkgs_json = c.load("packages.json") - pkgs_json = json.loads(pkgs_json) - pref = "pkg/2.3.4#0fc07368b81b38197adc73ee2cb89da8:ec080285423a5e38126f0d5d51b524cf516ff7a5" - assert pkgs_json["Local Cache"][rrev][pref]["settings"]["os"] == "Windows" - - def test_list_packages_with_conf(self): - """Test that tools.info.package_id:confs works, affecting the package_id and - can be listed when we are listing packages - """ - client = TestClient() - conanfile = GenConanfile().with_settings("os") - profile = textwrap.dedent(f""" - [conf] - tools.info.package_id:confs=["tools.build:cxxflags", "tools.build:cflags"] - tools.build:cxxflags=["--flag1", "--flag2"] - tools.build:cflags+=["--flag3", "--flag4"] - tools.build:sharedlinkflags=+["--flag5", "--flag6"] + default: + {output} + other: + {output} """) - client.save({"conanfile.py": conanfile, "profile": profile}) - client.run('create . --name=pkg --version=0.1 -s os=Windows -pr profile') - client.assert_listed_binary({"pkg/0.1": ("89d32f25195a77f4ae2e77414b870781853bdbc1", - "Build")}) - revision = client.exported_recipe_revision() - client.run(f"list pkg/0.1#{revision}") - expected_output = textwrap.dedent("""\ - Local Cache: - pkg - pkg/0.1#db6569e42e3e9916209e2ef64d6a7b52 .* - PID: 89d32f25195a77f4ae2e77414b870781853bdbc1 .* - settings: - os=Windows - conf: - tools.build:cflags=\['--flag3', '--flag4'\] - tools.build:cxxflags=\['--flag1', '--flag2'\] - """) - assert bool(re.match(expected_output, client.out, re.MULTILINE)) - - def test_list_packages_python_requires(self): - client = TestClient() - client.save({"conanfile.py": GenConanfile()}) - client.run("export . --name=tool --version=1.1.1") - conanfile = textwrap.dedent(""" - from conan import ConanFile - class Pkg(ConanFile): - python_requires ="tool/[*]" - """) - client.save({"conanfile.py": conanfile}) - client.run("create . --name foo --version 1.0") - client.run('list foo/1.0:*') - - expected_output = textwrap.dedent("""\ - Local Cache: - foo - foo/1.0#b2ab5ffa95e8c5c19a5d1198be33103a .* - PID: 170e82ef3a6bf0bbcda5033467ab9d7805b11d0b .* - python_requires: - tool/1.1.Z - """) - assert bool(re.match(expected_output, client.out, re.MULTILINE)) - - def test_list_packages_build_requires(self): - client = TestClient() - client.save({"conanfile.py": GenConanfile()}) - client.run("create . --name=tool --version=1.1.1") - conanfile = textwrap.dedent(""" - from conan import ConanFile - class Pkg(ConanFile): - def requirements(self): - # We set the package_id_mode so it is part of the package_id - self.tool_requires("tool/1.1.1", package_id_mode="minor_mode") - """) - client.save({"conanfile.py": conanfile}) - client.run("create . --name foo --version 1.0") - client.run('list foo/1.0:*') - - expected_output = textwrap.dedent("""\ - Local Cache: - foo - foo/1.0#75821be6dc510628d538fffb2f00a51f .* - PID: d01be73a295dca843e5e198334f86ae7038423d7 .* - build_requires: - tool/1.1.Z - """) - assert bool(re.match(expected_output, client.out, re.MULTILINE)) + assert expected_output == client.out class TestListHTML: From 9640ccfcfff0b8cdf4c802f43954088088667b9e Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 2 Jan 2023 23:00:16 +0100 Subject: [PATCH 3/8] wip --- conan/api/subapi/list.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/conan/api/subapi/list.py b/conan/api/subapi/list.py index 0d4b61199f2..d8ea37be9c7 100644 --- a/conan/api/subapi/list.py +++ b/conan/api/subapi/list.py @@ -39,12 +39,17 @@ def recipe_revisions(self, ref: RecipeReference, remote=None): def latest_package_revision(self, pref: PkgReference, remote=None): assert pref.revision is None, "latest_package_revision: ref already have a revision" + assert pref.package_id is not None, "package_id must be defined" app = ConanApp(self.conan_api.cache_folder) if remote: ret = app.remote_manager.get_latest_package_reference(pref, remote=remote) else: ret = app.cache.get_latest_package_reference(pref) if ret is None: + # if we are providing a package_id and asking for latest, it must have a prev, + # otherwise it is an error + # But this is not the same as for ``latest_recipe_revision`` + # TODO: Move this to remote and cache logic raise ConanException(f"Binary package not found: '{pref}'") return ret From 4f873f7044eb12b9ba4b325e9b7046d3614f7ac8 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 9 Jan 2023 14:21:23 +0100 Subject: [PATCH 4/8] wip --- conan/api/subapi/list.py | 20 ++++++++----------- conans/client/remote_manager.py | 4 +--- conans/server/service/v2/search.py | 4 +--- .../test/integration/command_v2/list_test.py | 14 +++++++------ 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/conan/api/subapi/list.py b/conan/api/subapi/list.py index d8ea37be9c7..6fc90e175e9 100644 --- a/conan/api/subapi/list.py +++ b/conan/api/subapi/list.py @@ -38,6 +38,9 @@ def recipe_revisions(self, ref: RecipeReference, remote=None): return results def latest_package_revision(self, pref: PkgReference, remote=None): + # TODO: This returns None if the given package_id is not existing. It should probably + # raise NotFound, but to keep aligned with the above ``latest_recipe_revision`` which + # is used as an "exists" check too in other places, lets respect the None return assert pref.revision is None, "latest_package_revision: ref already have a revision" assert pref.package_id is not None, "package_id must be defined" app = ConanApp(self.conan_api.cache_folder) @@ -45,13 +48,6 @@ def latest_package_revision(self, pref: PkgReference, remote=None): ret = app.remote_manager.get_latest_package_reference(pref, remote=remote) else: ret = app.cache.get_latest_package_reference(pref) - if ret is None: - # if we are providing a package_id and asking for latest, it must have a prev, - # otherwise it is an error - # But this is not the same as for ``latest_recipe_revision`` - # TODO: Move this to remote and cache logic - raise ConanException(f"Binary package not found: '{pref}'") - return ret def package_revisions(self, pref: PkgReference, remote: Remote=None): @@ -103,9 +99,10 @@ def select(self, pattern, package_query=None, remote=None, search_mode=None): search_mode = search_mode or pattern.mode select_bundle = SelectBundle() refs = self.conan_api.search.recipes(pattern.ref, remote=remote) + pattern.check_refs(refs) # Show only the recipe references - if refs and search_mode == ListPatternMode.SHOW_REFS: + if search_mode == ListPatternMode.SHOW_REFS: select_bundle.add_refs(refs) return select_bundle @@ -138,17 +135,16 @@ def select(self, pattern, package_query=None, remote=None, search_mode=None): # Show all the package IDs and their configurations if search_mode == ListPatternMode.SHOW_PACKAGE_IDS: # add pref and its package configuration + # remove timestamp, as server does not provide it + for p in prefs: + p.timestamp = None select_bundle.add_prefs(prefs, configurations=packages) continue for pref in prefs: if search_mode in (ListPatternMode.SHOW_LATEST_PREV, ListPatternMode.SHOW_ALL_PREVS): - pref.revision = None # Need to invalidate if pattern.is_latest_prev: - # TODO: This might be unnecessary, if we - # called before package_configurations() that already - # returns latest prev prevs = [self.conan_api.list.latest_package_revision(pref, remote)] else: prevs = self.conan_api.list.package_revisions(pref, remote) diff --git a/conans/client/remote_manager.py b/conans/client/remote_manager.py index 3da968ee300..dcd10a973f7 100644 --- a/conans/client/remote_manager.py +++ b/conans/client/remote_manager.py @@ -145,9 +145,7 @@ def search_recipes(self, remote, pattern): def search_packages(self, remote, ref): packages = self._call_remote(remote, "search_packages", ref) # Avoid serializing conaninfo in server side - # TODO: This has to be implemented in Artifactory too - packages = {PkgReference(ref, pid, data.get("prev"), data.get("timestamp")): - load_binary_info(data["content"]) + packages = {PkgReference(ref, pid): load_binary_info(data["content"]) if "content" in data else data for pid, data in packages.items() if not data.get("recipe_hash")} return packages diff --git a/conans/server/service/v2/search.py b/conans/server/service/v2/search.py index f0f7d2d0874..aa06371a4ff 100644 --- a/conans/server/service/v2/search.py +++ b/conans/server/service/v2/search.py @@ -38,9 +38,7 @@ def _get_local_infos_min(server_store, ref, look_in_all_rrevs): raise NotFoundException("") content = load(info_path) # From Conan 1.48 the conaninfo.txt is sent raw. - result[package_id] = {"content": content, - "prev": pref.revision, - "timestamp": revision_entry.timestamp} + result[package_id] = {"content": content} except Exception as exc: # FIXME: Too wide ConanOutput().error("Package %s has no ConanInfo file" % str(pref)) if str(exc): diff --git a/conans/test/integration/command_v2/list_test.py b/conans/test/integration/command_v2/list_test.py index 6d885069a38..0bf0e47cdf4 100644 --- a/conans/test/integration/command_v2/list_test.py +++ b/conans/test/integration/command_v2/list_test.py @@ -108,9 +108,11 @@ def test_list_recipe_versions(self, client, pattern, remote): @pytest.mark.parametrize("remote", [True, False]) @pytest.mark.parametrize("pattern", ["nomatch", "nomatch*", "nomatch/*"]) def test_list_recipe_no_match(self, client, pattern, remote): - expected = textwrap.dedent(f"""\ - There are no matching recipe references - """) + if pattern == "nomatch": # EXACT IS AN ERROR + expected = "ERROR: Recipe 'nomatch' not found" + else: + expected = "There are no matching recipe references" + self.check(client, pattern, remote, expected) @pytest.mark.parametrize("remote", [True, False]) @@ -309,11 +311,11 @@ class TestListRemotes: def test_search_no_matching_recipes(self, client): expected_output = textwrap.dedent("""\ Local Cache: - There are no matching recipe references + ERROR: Recipe 'whatever/0.1' not found default: - There are no matching recipe references + ERROR: Recipe 'whatever/0.1' not found other: - There are no matching recipe references + ERROR: Recipe 'whatever/0.1' not found """) client.run('list -c -r="*" whatever/0.1') From 4b2d171c1a85f6a61a6cb1e21f192e6115140132 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 9 Jan 2023 14:27:56 +0100 Subject: [PATCH 5/8] fix tests --- conan/api/subapi/list.py | 7 +++++-- conans/test/integration/command_v2/list_test.py | 14 +++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/conan/api/subapi/list.py b/conan/api/subapi/list.py index 6fc90e175e9..c19ad4a38fe 100644 --- a/conan/api/subapi/list.py +++ b/conan/api/subapi/list.py @@ -3,7 +3,7 @@ from conan.api.model import Remote, SelectBundle from conan.internal.api.select_pattern import ListPatternMode from conan.internal.conan_app import ConanApp -from conans.errors import ConanException +from conans.errors import ConanException, NotFoundException from conans.model.package_ref import PkgReference from conans.model.recipe_ref import RecipeReference from conans.search.search import get_cache_packages_binary_info, filter_packages @@ -145,7 +145,10 @@ def select(self, pattern, package_query=None, remote=None, search_mode=None): if search_mode in (ListPatternMode.SHOW_LATEST_PREV, ListPatternMode.SHOW_ALL_PREVS): if pattern.is_latest_prev: - prevs = [self.conan_api.list.latest_package_revision(pref, remote)] + prev = self.conan_api.list.latest_package_revision(pref, remote) + if prev is None: + raise NotFoundException(f"Binary package not found: '{pref}") + prevs = [prev] else: prevs = self.conan_api.list.package_revisions(pref, remote) prevs = pattern.filter_prevs(prevs) diff --git a/conans/test/integration/command_v2/list_test.py b/conans/test/integration/command_v2/list_test.py index 0bf0e47cdf4..f52b567ad3a 100644 --- a/conans/test/integration/command_v2/list_test.py +++ b/conans/test/integration/command_v2/list_test.py @@ -181,10 +181,10 @@ def test_list_pkg_ids(self, client, remote): expected = textwrap.dedent(f"""\ zli zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 .* + PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 settings: os=Linux - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 .* + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 settings: os=Windows """) @@ -196,7 +196,7 @@ def test_list_pkg_ids_confs(self, client, remote): expected = textwrap.dedent("""\ conf conf/1.0#e4e1703f72ed07c15d73a555ec3a2fa1 .* - PID: 78c6fa29e8164ce399087ad6067c8f9e2f1c4ad0 .* + PID: 78c6fa29e8164ce399087ad6067c8f9e2f1c4ad0 conf: tools.build:cxxflags=\['--flag1'\] """) @@ -208,7 +208,7 @@ def test_list_pkg_ids_requires(self, client, remote): expected = textwrap.dedent("""\ test test/1.0#7df6048d3cb39b75618717987fb96453 .* - PID: 81d0d9a6851a0208c2bb35fdb34eb156359d939b .* + PID: 81d0d9a6851a0208c2bb35fdb34eb156359d939b requires: zlix/1.Y.Z python_requires: @@ -222,13 +222,13 @@ def test_list_pkg_ids_all_rrevs(self, client, remote): expected = textwrap.dedent(f"""\ zli zli/1.0.0#f034dc90894493961d92dd32a9ee3b78 .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 .* + PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 Empty package information zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 .* + PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 settings: os=Linux - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 .* + PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 settings: os=Windows """) From 8b999cf1ac111d59cf8351031f3f5c8a193b754e Mon Sep 17 00:00:00 2001 From: memsharded Date: Tue, 10 Jan 2023 02:07:21 +0100 Subject: [PATCH 6/8] wip --- conan/api/model.py | 12 +- conan/cli/commands/list.py | 98 +++++------- .../test/integration/command_v2/list_test.py | 144 +++++++++++++++--- 3 files changed, 169 insertions(+), 85 deletions(-) diff --git a/conan/api/model.py b/conan/api/model.py index e7b5516e7dd..c964cc153f2 100644 --- a/conan/api/model.py +++ b/conan/api/model.py @@ -1,5 +1,7 @@ from collections import OrderedDict +from conans.util.dates import timestamp_to_str + class Remote: @@ -89,7 +91,15 @@ def add_prefs(self, prefs, configurations=None): def serialize(self): ret = {} for ref, prefs in sorted(self.recipes.items()): - ret.setdefault(ref.name, {})[ref] = prefs + name_dict = ret.setdefault(ref.name, {}) + ref_dict = name_dict.setdefault(str(ref), {}) + if ref.revision: + revisions_dict = ref_dict.setdefault("revisions", {}) + rev_dict = revisions_dict.setdefault(ref.revision, {}) + if ref.timestamp: + rev_dict["timestamp"] = timestamp_to_str(ref.timestamp) + for pref in prefs or []: + pid_dict = rev_dict["packages"].setdefault(pref.package_id, {}) return ret diff --git a/conan/cli/commands/list.py b/conan/cli/commands/list.py index 6d5ba285756..090590c5d92 100644 --- a/conan/cli/commands/list.py +++ b/conan/cli/commands/list.py @@ -3,79 +3,52 @@ from conan.api.conan_api import ConanAPI from conan.api.output import Color, cli_out_write from conan.cli.command import conan_command, OnceArgument -from conan.cli.commands import ConanJSONEncoder from conan.cli.formatters.list import list_packages_html -from conan.internal.api.select_pattern import ListPattern, ListPatternMode -from conans.util.dates import timestamp_to_str +from conan.internal.api.select_pattern import ListPattern -remote_color = Color.BRIGHT_BLUE -recipe_name_color = Color.GREEN -recipe_color = Color.BRIGHT_WHITE -reference_color = Color.WHITE -error_color = Color.BRIGHT_RED -field_color = Color.BRIGHT_YELLOW -value_color = Color.CYAN + +def print_serial(item, indent=None, color_index=None): + indent = "" if indent is None else (indent + " ") + color_index = 0 if color_index is None else (color_index + 1) + color_array = [Color.BRIGHT_BLUE, Color.GREEN, Color.BRIGHT_WHITE, + Color.BRIGHT_YELLOW, Color.CYAN, Color.WHITE] + color = color_array[color_index % len(color_array)] + if isinstance(item, dict): + for k, v in item.items(): + cli_out_write(f"{indent}{k}", fg=color) + print_serial(v, indent, color_index) + elif item: + color = Color.BRIGHT_RED if "ERROR:" in item else color + cli_out_write(f"{indent}{item}", fg=color) def print_list_text(results): + """ Do litte format modification to serialized + list bundle so it looks prettier on text output + """ info = results["results"] - search_mode = results["search_mode"] - indentation = " " - - for remote, info_per_ref_name in info.items(): - cli_out_write(f"{remote}:", fg=remote_color) + info = {remote: "There are no matching recipe references" if not values else values + for remote, values in info.items()} + info = {remote: f"ERROR: {values.get('error')}" if values.get("error") else values + for remote, values in info.items()} - if info_per_ref_name.get("error"): - cli_out_write(f" ERROR: {info_per_ref_name.get('error')}", fg=error_color) - continue - - if not info_per_ref_name: - cli_out_write(f" There are no matching recipe references", fg=recipe_color) - continue - - for ref_name, refs in info_per_ref_name.items(): - cli_out_write(f"{indentation}{ref_name}", fg=recipe_name_color) - for ref, prefs in refs.items(): - cli_out_write(f"{indentation * 2}{ref.repr_humantime() if ref.timestamp else ref}", - fg=recipe_color) - if prefs: - for pref, binary_info in prefs: - pref_date = f" ({timestamp_to_str(pref.timestamp)})" if pref.timestamp \ - else "" - if search_mode == ListPatternMode.SHOW_PACKAGE_IDS: - cli_out_write(f"{indentation * 3}PID: {pref.package_id}{pref_date}", - fg=reference_color) - if not binary_info: - cli_out_write(f"{indentation * 4}Empty package information", - fg=field_color) - continue - elif search_mode in (ListPatternMode.SHOW_ALL_PREVS, - ListPatternMode.SHOW_LATEST_PREV): - cli_out_write(f"{indentation * 3}PID: {pref.package_id}", - fg=reference_color) - cli_out_write(f"{indentation * 4}PREV: {pref.revision}{pref_date}", - fg=field_color) - continue - for item, contents in binary_info.items(): - if not contents: - continue - cli_out_write(f"{indentation * 4}{item}:", fg=field_color) - if isinstance(contents, dict): - for k, v in contents.items(): - cli_out_write(f"{indentation * 5}{k}={v}", fg=value_color) - else: - for c in contents: - cli_out_write(f"{indentation * 5}{c}", fg=value_color) - elif search_mode in (ListPatternMode.SHOW_PACKAGE_IDS, - ListPatternMode.SHOW_ALL_PREVS, - ListPatternMode.SHOW_LATEST_PREV): - cli_out_write(f"{indentation * 3}There are no packages for this revision.", - fg=field_color) + def transform_text_serial(item): + if isinstance(item, dict): + result = {} + for k, v in item.items(): + if isinstance(v, dict) and v.get("timestamp"): + timestamp = v.pop("timestamp") + k = f"{k} ({timestamp})" + result[k] = transform_text_serial(v) + return result + return item + info = {remote: transform_text_serial(values) for remote, values in info.items()} + print_serial(info) def print_list_json(data): results = data["results"] - myjson = json.dumps(results, indent=4, cls=ConanJSONEncoder) + myjson = json.dumps(results, indent=4) cli_out_write(myjson) @@ -114,6 +87,7 @@ def list(conan_api: ConanAPI, parser, *args): results[name] = {"error": str(e)} else: results[name] = list_bundle.serialize() + return { "results": results, "search_mode": ref_pattern.mode, diff --git a/conans/test/integration/command_v2/list_test.py b/conans/test/integration/command_v2/list_test.py index f52b567ad3a..816757793bf 100644 --- a/conans/test/integration/command_v2/list_test.py +++ b/conans/test/integration/command_v2/list_test.py @@ -1,3 +1,4 @@ +import json import os import re import textwrap @@ -69,6 +70,14 @@ def client(): return c +def remove_timestamps(item): + if isinstance(item, dict): + if item.get("timestamp"): + item["timestamp"] = "" + for v in item.values(): + remove_timestamps(v) + + class TestListRefs: @staticmethod @@ -78,9 +87,19 @@ def check(client, pattern, remote, expected): client.run(f"list {pattern} {r}") print(client.out) expected = textwrap.indent(expected, " ") - expected_output = f"{r_msg}:\n" + expected + expected_output = f"{r_msg}\n" + expected assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) + @staticmethod + def check_json(client, pattern, remote, expected): + r = "-r=default" if remote else "" + r_msg = "default" if remote else "Local Cache" + client.run(f"list {pattern} {r} --format=json", redirect_stdout="file.json") + list_json = client.load("file.json") + print(list_json) + list_json = json.loads(list_json) + assert remove_timestamps(list_json[r_msg]) == remove_timestamps(expected) + @pytest.mark.parametrize("remote", [True, False]) def test_list_recipes(self, client, remote): pattern = "z*" @@ -94,6 +113,19 @@ def test_list_recipes(self, client, remote): zlix/1.0.0 """) self.check(client, pattern, remote, expected) + expected_json = { + "zli": { + "zli/1.0.0": {} + }, + "zlib": { + "zlib/1.0.0@user/channel": {}, + "zlib/2.0.0@user/channel": {} + }, + "zlix": { + "zlix/1.0.0": {} + } + } + self.check_json(client, pattern, remote, expected_json) @pytest.mark.parametrize("remote", [True, False]) @pytest.mark.parametrize("pattern", ["zlib", "zlib/*", "*@user/channel"]) @@ -104,6 +136,13 @@ def test_list_recipe_versions(self, client, pattern, remote): zlib/2.0.0@user/channel """) self.check(client, pattern, remote, expected) + expected_json = { + "zlib": { + "zlib/1.0.0@user/channel": {}, + "zlib/2.0.0@user/channel": {} + } + } + self.check_json(client, pattern, remote, expected_json) @pytest.mark.parametrize("remote", [True, False]) @pytest.mark.parametrize("pattern", ["nomatch", "nomatch*", "nomatch/*"]) @@ -120,10 +159,24 @@ def test_list_recipe_latest_revision(self, client, remote): # by default, when a reference is complete, we show latest recipe revision pattern = "zli/1.0.0" expected = textwrap.dedent(f"""\ - zli - zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - """) + zli + zli/1.0.0 + revisions + b58eeddfe2fd25ac3a105f72836b3360 .* + """) self.check(client, pattern, remote, expected) + expected_json = { + "zli": { + "zli/1.0.0": { + "revisions": { + "b58eeddfe2fd25ac3a105f72836b3360": { + "timestamp": "2023-01-10 00:25:32 UTC" + } + } + } + } + } + self.check_json(client, pattern, remote, expected_json) @pytest.mark.parametrize("remote", [True, False]) def test_list_recipe_all_latest_revision(self, client, remote): @@ -131,8 +184,12 @@ def test_list_recipe_all_latest_revision(self, client, remote): pattern = "zlib/*#latest" expected = textwrap.dedent(f"""\ zlib - zlib/1.0.0@user/channel#ffd4bc45820ddb320ab224685b9ba3fb .* - zlib/2.0.0@user/channel#ffd4bc45820ddb320ab224685b9ba3fb .* + zlib/1.0.0@user/channel + revisions + ffd4bc45820ddb320ab224685b9ba3fb .* + zlib/2.0.0@user/channel + revisions + ffd4bc45820ddb320ab224685b9ba3fb .* """) self.check(client, pattern, remote, expected) @@ -142,8 +199,10 @@ def test_list_recipe_several_revision(self, client, remote): pattern = "zli/1.0.0#*" expected = textwrap.dedent(f"""\ zli - zli/1.0.0#f034dc90894493961d92dd32a9ee3b78 .* - zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* + zli/1.0.0 + revisions + f034dc90894493961d92dd32a9ee3b78 .* + b58eeddfe2fd25ac3a105f72836b3360 .* """) self.check(client, pattern, remote, expected) @@ -151,14 +210,22 @@ def test_list_recipe_several_revision(self, client, remote): def test_list_recipe_multiple_revision(self, client, remote): pattern = "zli*#*" expected = textwrap.dedent(f"""\ - zli - zli/1.0.0#f034dc90894493961d92dd32a9ee3b78 .* - zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - zlib - zlib/1.0.0@user/channel#ffd4bc45820ddb320ab224685b9ba3fb .* - zlib/2.0.0@user/channel#ffd4bc45820ddb320ab224685b9ba3fb .* - zlix - zlix/1.0.0#81f598d1d8648389bb7d0494fffb654e .* + zli + zli/1.0.0 + revisions + f034dc90894493961d92dd32a9ee3b78 .* + b58eeddfe2fd25ac3a105f72836b3360 .* + zlib + zlib/1.0.0@user/channel + revisions + ffd4bc45820ddb320ab224685b9ba3fb .* + zlib/2.0.0@user/channel + revisions + ffd4bc45820ddb320ab224685b9ba3fb .* + zlix + zlix/1.0.0 + revisions + 81f598d1d8648389bb7d0494fffb654e .* """) self.check(client, pattern, remote, expected) @@ -180,14 +247,47 @@ def test_list_pkg_ids(self, client, remote): pattern = "zli/1.0.0:*" expected = textwrap.dedent(f"""\ zli + zli/1.0.0 zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 - settings: - os=Linux - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 - settings: - os=Windows + packages + 9a4eb3c8701508aa9458b1a73d0633783ecc2270 + revisions + prev1 .* + prev2 .* + info + settings + os=Linux + ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + settings: + os=Windows """) + expected_json = { + "zli": { + "zli/1.0.0": { + "revisions": { + "b58eeddfe2fd25ac3a105f72836b3360": { + "timestamp": "20-21-2032 12:11:13", + "packages": { + "9a4eb3c8701508aa9458b1a73d0633783ecc2270": { + "revisions": {"prev1": {"timestamp": "T1"}}, + "info": { + "settings": { + "os": "Linux" + } + } + }, + "ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715": { + "info": { + "settings": { + "os": "Windows" + } + } + }}, + } + } + } + } + } self.check(client, pattern, remote, expected) @pytest.mark.parametrize("remote", [True, False]) From d3d68aca193538e7c737e046d7d459749ac85916 Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 11 Jan 2023 00:30:49 +0100 Subject: [PATCH 7/8] wip --- conan/api/model.py | 20 +- conan/api/subapi/list.py | 2 +- conan/cli/commands/list.py | 51 ++- .../build_requires/test_toolchain_packages.py | 3 +- .../test/integration/command/remove_test.py | 8 +- .../test/integration/command_v2/list_test.py | 355 +++++++++++------- .../integration/command_v2/search_test.py | 22 +- 7 files changed, 302 insertions(+), 159 deletions(-) diff --git a/conan/api/model.py b/conan/api/model.py index c964cc153f2..2f1a43b3b55 100644 --- a/conan/api/model.py +++ b/conan/api/model.py @@ -83,23 +83,31 @@ def prefs(self): return prefs def add_prefs(self, prefs, configurations=None): - confs = configurations or {} for pref in prefs: - binary_info = confs.get(pref, OrderedDict()) + binary_info = configurations.get(pref) if configurations is not None else None self.recipes.setdefault(pref.ref, []).append((pref, binary_info)) def serialize(self): ret = {} for ref, prefs in sorted(self.recipes.items()): - name_dict = ret.setdefault(ref.name, {}) - ref_dict = name_dict.setdefault(str(ref), {}) + ref_dict = ret.setdefault(str(ref), {}) if ref.revision: revisions_dict = ref_dict.setdefault("revisions", {}) rev_dict = revisions_dict.setdefault(ref.revision, {}) if ref.timestamp: rev_dict["timestamp"] = timestamp_to_str(ref.timestamp) - for pref in prefs or []: - pid_dict = rev_dict["packages"].setdefault(pref.package_id, {}) + if prefs: + packages_dict = rev_dict.setdefault("packages", {}) + for pref_info in prefs: + pref, info = pref_info + pid_dict = packages_dict.setdefault(pref.package_id, {}) + if info is not None: + pid_dict["info"] = info + if pref.revision: + prevs_dict = pid_dict.setdefault("revisions", {}) + prev_dict = prevs_dict.setdefault(pref.revision, {}) + if pref.timestamp: + prev_dict["timestamp"] = timestamp_to_str(pref.timestamp) return ret diff --git a/conan/api/subapi/list.py b/conan/api/subapi/list.py index c19ad4a38fe..d87cb048724 100644 --- a/conan/api/subapi/list.py +++ b/conan/api/subapi/list.py @@ -119,7 +119,7 @@ def select(self, pattern, package_query=None, remote=None, search_mode=None): continue for rrev in rrevs: - packages = {} + packages = None prefs = [] if pattern.package_id and "*" not in pattern.package_id: prefs.append(PkgReference(rrev, package_id=pattern.package_id)) diff --git a/conan/cli/commands/list.py b/conan/cli/commands/list.py index 090590c5d92..81f04be78c7 100644 --- a/conan/cli/commands/list.py +++ b/conan/cli/commands/list.py @@ -6,6 +6,15 @@ from conan.cli.formatters.list import list_packages_html from conan.internal.api.select_pattern import ListPattern +# Keep them so we don't break other commands that import them, but TODO: Remove later +remote_color = Color.BRIGHT_BLUE +recipe_name_color = Color.GREEN +recipe_color = Color.BRIGHT_WHITE +reference_color = Color.WHITE +error_color = Color.BRIGHT_RED +field_color = Color.BRIGHT_YELLOW +value_color = Color.CYAN + def print_serial(item, indent=None, color_index=None): indent = "" if indent is None else (indent + " ") @@ -15,10 +24,21 @@ def print_serial(item, indent=None, color_index=None): color = color_array[color_index % len(color_array)] if isinstance(item, dict): for k, v in item.items(): - cli_out_write(f"{indent}{k}", fg=color) - print_serial(v, indent, color_index) + if isinstance(v, str): + if k.lower() == "error": + color = Color.BRIGHT_RED + k = "ERROR" + elif k.lower() == "warning": + color = Color.BRIGHT_YELLOW + k = "WARN" + cli_out_write(f"{indent}{k}: {v}", fg=color) + else: + cli_out_write(f"{indent}{k}", fg=color) + print_serial(v, indent, color_index) + elif isinstance(item, type([])): + for elem in item: + cli_out_write(f"{indent}{elem}", fg=color) elif item: - color = Color.BRIGHT_RED if "ERROR:" in item else color cli_out_write(f"{indent}{item}", fg=color) @@ -27,22 +47,34 @@ def print_list_text(results): list bundle so it looks prettier on text output """ info = results["results"] - info = {remote: "There are no matching recipe references" if not values else values - for remote, values in info.items()} - info = {remote: f"ERROR: {values.get('error')}" if values.get("error") else values + + # Extract command single package name + new_info = {} + for remote, remote_info in info.items(): + new_remote_info = {} + for ref, content in remote_info.items(): + if ref == "error": + new_remote_info[ref] = content + else: + name, _ = ref.split("/", 1) + new_remote_info.setdefault(name, {})[ref] = content + new_info[remote] = new_remote_info + info = new_info + + info = {remote: {"warning": "There are no matching recipe references"} if not values else values for remote, values in info.items()} - def transform_text_serial(item): + def format_timestamps(item): if isinstance(item, dict): result = {} for k, v in item.items(): if isinstance(v, dict) and v.get("timestamp"): timestamp = v.pop("timestamp") k = f"{k} ({timestamp})" - result[k] = transform_text_serial(v) + result[k] = format_timestamps(v) return result return item - info = {remote: transform_text_serial(values) for remote, values in info.items()} + info = {remote: format_timestamps(values) for remote, values in info.items()} print_serial(info) @@ -87,7 +119,6 @@ def list(conan_api: ConanAPI, parser, *args): results[name] = {"error": str(e)} else: results[name] = list_bundle.serialize() - return { "results": results, "search_mode": ref_pattern.mode, diff --git a/conans/test/integration/build_requires/test_toolchain_packages.py b/conans/test/integration/build_requires/test_toolchain_packages.py index 1c35cbabc3f..e923c64706c 100644 --- a/conans/test/integration/build_requires/test_toolchain_packages.py +++ b/conans/test/integration/build_requires/test_toolchain_packages.py @@ -386,4 +386,5 @@ def test(self): "build_type": "Release", "os": "Linux" } - assert settings_target == pkgs_json["Local Cache"][pref.ref.repr_notime()][pref.repr_notime()]["settings_target"] + revision_dict = pkgs_json["Local Cache"]["gcc/0.1"]["revisions"][pref.ref.revision] + assert settings_target == revision_dict["packages"][pref.package_id]["info"]["settings_target"] diff --git a/conans/test/integration/command/remove_test.py b/conans/test/integration/command/remove_test.py index 1c911973539..1d7bb74656b 100644 --- a/conans/test/integration/command/remove_test.py +++ b/conans/test/integration/command/remove_test.py @@ -164,13 +164,13 @@ def test_remove_all_packages_but_the_recipe_at_remote(self): RecipeReference.loads("foobar/0.1@user/testing")) self.client.run("list foobar/0.1@user/testing#{} -r default".format(ref.revision)) default_arch = self.client.get_default_host_profile().settings['arch'] - self.assertIn(f"arch={default_arch}", self.client.out) - self.assertIn("arch=x86", self.client.out) + self.assertIn(f"arch: {default_arch}", self.client.out) + self.assertIn("arch: x86", self.client.out) self.client.run("remove -c foobar/0.1@user/testing:* -r default") self.client.run("search foobar/0.1@user/testing -r default") - self.assertNotIn(f"arch={default_arch}", self.client.out) - self.assertNotIn("arch=x86", self.client.out) + self.assertNotIn(f"arch: {default_arch}", self.client.out) + self.assertNotIn("arch: x86", self.client.out) # populated packages of bar diff --git a/conans/test/integration/command_v2/list_test.py b/conans/test/integration/command_v2/list_test.py index 816757793bf..fa34e03ff3d 100644 --- a/conans/test/integration/command_v2/list_test.py +++ b/conans/test/integration/command_v2/list_test.py @@ -76,6 +76,7 @@ def remove_timestamps(item): item["timestamp"] = "" for v in item.values(): remove_timestamps(v) + return item class TestListRefs: @@ -88,7 +89,9 @@ def check(client, pattern, remote, expected): print(client.out) expected = textwrap.indent(expected, " ") expected_output = f"{r_msg}\n" + expected - assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) + expected_output = re.sub(r"\(.*\)", "", expected_output) + output = re.sub(r"\(.*\)", "", str(client.out)) + assert expected_output == output @staticmethod def check_json(client, pattern, remote, expected): @@ -114,16 +117,10 @@ def test_list_recipes(self, client, remote): """) self.check(client, pattern, remote, expected) expected_json = { - "zli": { - "zli/1.0.0": {} - }, - "zlib": { - "zlib/1.0.0@user/channel": {}, - "zlib/2.0.0@user/channel": {} - }, - "zlix": { - "zlix/1.0.0": {} - } + "zli/1.0.0": {}, + "zlib/1.0.0@user/channel": {}, + "zlib/2.0.0@user/channel": {}, + "zlix/1.0.0": {} } self.check_json(client, pattern, remote, expected_json) @@ -137,10 +134,8 @@ def test_list_recipe_versions(self, client, pattern, remote): """) self.check(client, pattern, remote, expected) expected_json = { - "zlib": { - "zlib/1.0.0@user/channel": {}, - "zlib/2.0.0@user/channel": {} - } + "zlib/1.0.0@user/channel": {}, + "zlib/2.0.0@user/channel": {} } self.check_json(client, pattern, remote, expected_json) @@ -148,11 +143,16 @@ def test_list_recipe_versions(self, client, pattern, remote): @pytest.mark.parametrize("pattern", ["nomatch", "nomatch*", "nomatch/*"]) def test_list_recipe_no_match(self, client, pattern, remote): if pattern == "nomatch": # EXACT IS AN ERROR - expected = "ERROR: Recipe 'nomatch' not found" + expected = "ERROR: Recipe 'nomatch' not found\n" else: - expected = "There are no matching recipe references" + expected = "WARN: There are no matching recipe references\n" self.check(client, pattern, remote, expected) + if pattern == "nomatch": + expected_json = {"error": "Recipe 'nomatch' not found"} + else: + expected_json = {} + self.check_json(client, pattern, remote, expected_json) @pytest.mark.parametrize("remote", [True, False]) def test_list_recipe_latest_revision(self, client, remote): @@ -162,16 +162,14 @@ def test_list_recipe_latest_revision(self, client, remote): zli zli/1.0.0 revisions - b58eeddfe2fd25ac3a105f72836b3360 .* + b58eeddfe2fd25ac3a105f72836b3360 (10-11-2023 10:13:13) """) self.check(client, pattern, remote, expected) expected_json = { - "zli": { - "zli/1.0.0": { - "revisions": { - "b58eeddfe2fd25ac3a105f72836b3360": { - "timestamp": "2023-01-10 00:25:32 UTC" - } + "zli/1.0.0": { + "revisions": { + "b58eeddfe2fd25ac3a105f72836b3360": { + "timestamp": "2023-01-10 00:25:32 UTC" } } } @@ -186,10 +184,10 @@ def test_list_recipe_all_latest_revision(self, client, remote): zlib zlib/1.0.0@user/channel revisions - ffd4bc45820ddb320ab224685b9ba3fb .* + ffd4bc45820ddb320ab224685b9ba3fb (10-11-2023 10:13:13) zlib/2.0.0@user/channel revisions - ffd4bc45820ddb320ab224685b9ba3fb .* + ffd4bc45820ddb320ab224685b9ba3fb (10-11-2023 10:13:13) """) self.check(client, pattern, remote, expected) @@ -201,8 +199,8 @@ def test_list_recipe_several_revision(self, client, remote): zli zli/1.0.0 revisions - f034dc90894493961d92dd32a9ee3b78 .* - b58eeddfe2fd25ac3a105f72836b3360 .* + f034dc90894493961d92dd32a9ee3b78 (10-11-2023 10:13:13) + b58eeddfe2fd25ac3a105f72836b3360 (10-11-2023 10:13:13) """) self.check(client, pattern, remote, expected) @@ -213,19 +211,19 @@ def test_list_recipe_multiple_revision(self, client, remote): zli zli/1.0.0 revisions - f034dc90894493961d92dd32a9ee3b78 .* - b58eeddfe2fd25ac3a105f72836b3360 .* + f034dc90894493961d92dd32a9ee3b78 (10-11-2023 10:13:13) + b58eeddfe2fd25ac3a105f72836b3360 (10-11-2023 10:13:13) zlib zlib/1.0.0@user/channel revisions - ffd4bc45820ddb320ab224685b9ba3fb .* + ffd4bc45820ddb320ab224685b9ba3fb (10-11-2023 10:13:13) zlib/2.0.0@user/channel revisions - ffd4bc45820ddb320ab224685b9ba3fb .* + ffd4bc45820ddb320ab224685b9ba3fb (10-11-2023 10:13:13) zlix zlix/1.0.0 revisions - 81f598d1d8648389bb7d0494fffb654e .* + 81f598d1d8648389bb7d0494fffb654e (10-11-2023 10:13:13) """) self.check(client, pattern, remote, expected) @@ -239,128 +237,202 @@ def check(client, pattern, remote, expected): client.run(f"list {pattern} {r}") print(client.out) expected = textwrap.indent(expected, " ") - expected_output = f"{r_msg}:\n" + expected - assert bool(re.match(expected_output, str(client.out), re.MULTILINE)) + expected_output = f"{r_msg}\n" + expected + expected_output = re.sub(r"\(.*\)", "", expected_output) + output = re.sub(r"\(.*\)", "", str(client.out)) + assert expected_output == output + + @staticmethod + def check_json(client, pattern, remote, expected): + r = "-r=default" if remote else "" + r_msg = "default" if remote else "Local Cache" + client.run(f"list {pattern} {r} --format=json", redirect_stdout="file.json") + list_json = client.load("file.json") + print(list_json) + list_json = json.loads(list_json) + assert remove_timestamps(list_json[r_msg]) == remove_timestamps(expected) @pytest.mark.parametrize("remote", [True, False]) def test_list_pkg_ids(self, client, remote): pattern = "zli/1.0.0:*" expected = textwrap.dedent(f"""\ - zli - zli/1.0.0 - zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - packages - 9a4eb3c8701508aa9458b1a73d0633783ecc2270 - revisions - prev1 .* - prev2 .* - info - settings - os=Linux - ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 - settings: - os=Windows - """) + zli + zli/1.0.0 + revisions + b58eeddfe2fd25ac3a105f72836b3360 (10-11-2023 10:13:13) + packages + 9a4eb3c8701508aa9458b1a73d0633783ecc2270 + info + settings + os: Linux + ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + info + settings + os: Windows + """) + self.check(client, pattern, remote, expected) expected_json = { - "zli": { - "zli/1.0.0": { - "revisions": { - "b58eeddfe2fd25ac3a105f72836b3360": { - "timestamp": "20-21-2032 12:11:13", - "packages": { - "9a4eb3c8701508aa9458b1a73d0633783ecc2270": { - "revisions": {"prev1": {"timestamp": "T1"}}, - "info": { - "settings": { - "os": "Linux" - } + "zli/1.0.0": { + "revisions": { + "b58eeddfe2fd25ac3a105f72836b3360": { + "timestamp": "2023-01-10 16:30:27 UTC", + "packages": { + "9a4eb3c8701508aa9458b1a73d0633783ecc2270": { + "info": { + "settings": { + "os": "Linux" } - }, - "ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715": { - "info": { - "settings": { - "os": "Windows" - } + } + }, + "ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715": { + "info": { + "settings": { + "os": "Windows" } - }}, + } + } } } } } } - self.check(client, pattern, remote, expected) + self.check_json(client, pattern, remote, expected_json) @pytest.mark.parametrize("remote", [True, False]) def test_list_pkg_ids_confs(self, client, remote): pattern = "conf/*:*" expected = textwrap.dedent("""\ - conf - conf/1.0#e4e1703f72ed07c15d73a555ec3a2fa1 .* - PID: 78c6fa29e8164ce399087ad6067c8f9e2f1c4ad0 - conf: - tools.build:cxxflags=\['--flag1'\] - """) + conf + conf/1.0 + revisions + e4e1703f72ed07c15d73a555ec3a2fa1 (10-11-2023 10:13:13) + packages + 78c6fa29e8164ce399087ad6067c8f9e2f1c4ad0 + info + conf + tools.build:cxxflags: ['--flag1'] + """) self.check(client, pattern, remote, expected) + expected_json = { + "conf/1.0": { + "revisions": { + "e4e1703f72ed07c15d73a555ec3a2fa1": { + "timestamp": "2023-01-10 10:07:33 UTC", + "packages": { + "78c6fa29e8164ce399087ad6067c8f9e2f1c4ad0": { + "info": { + "conf": { + "tools.build:cxxflags": "['--flag1']" + } + } + } + } + } + } + } + } + self.check_json(client, pattern, remote, expected_json) @pytest.mark.parametrize("remote", [True, False]) def test_list_pkg_ids_requires(self, client, remote): pattern = "test/*:*" expected = textwrap.dedent("""\ test - test/1.0#7df6048d3cb39b75618717987fb96453 .* - PID: 81d0d9a6851a0208c2bb35fdb34eb156359d939b - requires: - zlix/1.Y.Z - python_requires: - zlix/1.0.Z + test/1.0 + revisions + 7df6048d3cb39b75618717987fb96453 (10-11-2023 10:13:13) + packages + 81d0d9a6851a0208c2bb35fdb34eb156359d939b + info + requires + zlix/1.Y.Z + python_requires + zlix/1.0.Z """) self.check(client, pattern, remote, expected) + expected_json = { + "test/1.0": { + "revisions": { + "7df6048d3cb39b75618717987fb96453": { + "timestamp": "2023-01-10 22:17:13 UTC", + "packages": { + "81d0d9a6851a0208c2bb35fdb34eb156359d939b": { + "info": { + "requires": [ + "zlix/1.Y.Z" + ], + "python_requires": [ + "zlix/1.0.Z" + ] + } + } + } + } + } + } + } + self.check_json(client, pattern, remote, expected_json) @pytest.mark.parametrize("remote", [True, False]) def test_list_pkg_ids_all_rrevs(self, client, remote): pattern = "zli/1.0.0#*:*" expected = textwrap.dedent(f"""\ - zli - zli/1.0.0#f034dc90894493961d92dd32a9ee3b78 .* - PID: da39a3ee5e6b4b0d3255bfef95601890afd80709 - Empty package information - zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 - settings: - os=Linux - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 - settings: - os=Windows - """) + zli + zli/1.0.0 + revisions + f034dc90894493961d92dd32a9ee3b78 (2023-01-10 22:19:58 UTC) + packages + da39a3ee5e6b4b0d3255bfef95601890afd80709 + info + b58eeddfe2fd25ac3a105f72836b3360 (2023-01-10 22:19:59 UTC) + packages + 9a4eb3c8701508aa9458b1a73d0633783ecc2270 + info + settings + os: Linux + ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + info + settings + os: Windows + """) self.check(client, pattern, remote, expected) @pytest.mark.parametrize("remote", [True, False]) def test_list_latest_prevs(self, client, remote): pattern = "zli/1.0.0:*#latest" + # TODO: This is doing a package_id search, but not showing info expected = textwrap.dedent(f"""\ - zli - zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 - PREV: 9beff32b8c94ea0ce5a5e67dad95f525 .* - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 - PREV: d9b1e9044ee265092e81db7028ae10e0 .* - """) + zli + zli/1.0.0 + revisions + b58eeddfe2fd25ac3a105f72836b3360 (2023-01-10 22:27:34 UTC) + packages + 9a4eb3c8701508aa9458b1a73d0633783ecc2270 + revisions + 9beff32b8c94ea0ce5a5e67dad95f525 (10-11-2023 10:13:13) + ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + revisions + d9b1e9044ee265092e81db7028ae10e0 (10-11-2023 10:13:13) + """) self.check(client, pattern, remote, expected) @pytest.mark.parametrize("remote", [True, False]) def test_list_all_prevs(self, client, remote): pattern = "zli/1.0.0:*#*" - # TODO: We might want to improve the output, grouping PREVS for the - # same package_id + # TODO: This is doing a package_id search, but not showing info expected = textwrap.dedent(f"""\ zli - zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - PID: 9a4eb3c8701508aa9458b1a73d0633783ecc2270 - PREV: 9beff32b8c94ea0ce5a5e67dad95f525 .* - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 - PREV: d9b1e9044ee265092e81db7028ae10e0 .* - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 - PREV: 24532a030b4fcdfed699511f6bfe35d3 .* + zli/1.0.0 + revisions + b58eeddfe2fd25ac3a105f72836b3360 (2023-01-10 22:41:09 UTC) + packages + 9a4eb3c8701508aa9458b1a73d0633783ecc2270 + revisions + 9beff32b8c94ea0ce5a5e67dad95f525 (2023-01-10 22:41:09 UTC) + ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + revisions + d9b1e9044ee265092e81db7028ae10e0 (2023-01-10 22:41:10 UTC) + 24532a030b4fcdfed699511f6bfe35d3 (2023-01-10 22:41:09 UTC) """) self.check(client, pattern, remote, expected) @@ -369,13 +441,38 @@ def test_list_package_id_all_prevs(self, client, remote): pattern = "zli/1.0.0:ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715#*" # TODO: We might want to improve the output, grouping PREVS for the # same package_id + expected_json = { + "zli/1.0.0": { + "revisions": { + "b58eeddfe2fd25ac3a105f72836b3360": { + "timestamp": "2023-01-10 22:45:49 UTC", + "packages": { + "ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715": { + "revisions": { + "d9b1e9044ee265092e81db7028ae10e0": { + "timestamp": "2023-01-10 22:45:49 UTC" + }, + "24532a030b4fcdfed699511f6bfe35d3": { + "timestamp": "2023-01-10 22:45:49 UTC" + } + } + } + } + } + } + } + } + self.check_json(client, pattern, remote, expected_json) expected = textwrap.dedent(f"""\ zli - zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 - PREV: d9b1e9044ee265092e81db7028ae10e0 .* - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 - PREV: 24532a030b4fcdfed699511f6bfe35d3 .* + zli/1.0.0 + revisions + b58eeddfe2fd25ac3a105f72836b3360 (2023-01-10 22:41:09 UTC) + packages + ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + revisions + d9b1e9044ee265092e81db7028ae10e0 (2023-01-10 22:41:10 UTC) + 24532a030b4fcdfed699511f6bfe35d3 (2023-01-10 22:41:09 UTC) """) self.check(client, pattern, remote, expected) @@ -384,9 +481,13 @@ def test_list_package_id_latest_prev(self, client, remote): pattern = "zli/1.0.0:ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715" expected = textwrap.dedent(f"""\ zli - zli/1.0.0#b58eeddfe2fd25ac3a105f72836b3360 .* - PID: ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 - PREV: d9b1e9044ee265092e81db7028ae10e0 .* + zli/1.0.0 + revisions + b58eeddfe2fd25ac3a105f72836b3360 (2023-01-10 23:13:12 UTC) + packages + ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715 + revisions + d9b1e9044ee265092e81db7028ae10e0 (2023-01-10 23:13:12 UTC) """) self.check(client, pattern, remote, expected) @@ -394,9 +495,11 @@ def test_list_package_id_latest_prev(self, client, remote): def test_list_missing_package_id(self, client, remote): pattern = "zli/1.0.0:nonexists_id" # TODO: The message is still different in the server - expected = textwrap.dedent(f"""\ - ERROR: Binary package not found: 'zli/1.0.0.* - """) + if remote: + expected = "ERROR: Binary package not found: 'zli/1.0.0@_/_#" \ + "b58eeddfe2fd25ac3a105f72836b3360:nonexists_id'. [Remote: default]\n" + else: + expected = "ERROR: Binary package not found: 'zli/1.0.0:nonexists_id\n" self.check(client, pattern, remote, expected) def test_query(self): @@ -410,11 +513,11 @@ class TestListRemotes: def test_search_no_matching_recipes(self, client): expected_output = textwrap.dedent("""\ - Local Cache: + Local Cache ERROR: Recipe 'whatever/0.1' not found - default: + default ERROR: Recipe 'whatever/0.1' not found - other: + other ERROR: Recipe 'whatever/0.1' not found """) @@ -434,9 +537,9 @@ def test_search_remote_errors_but_no_raising_exceptions(self, client, exc, outpu with patch("conan.api.subapi.search.SearchAPI.recipes", new=Mock(side_effect=exc)): client.run(f'list whatever/1.0 -r="*"') expected_output = textwrap.dedent(f"""\ - default: + default {output} - other: + other {output} """) assert expected_output == client.out diff --git a/conans/test/integration/command_v2/search_test.py b/conans/test/integration/command_v2/search_test.py index 2ca1d113e1b..9a906c9cf03 100644 --- a/conans/test/integration/command_v2/search_test.py +++ b/conans/test/integration/command_v2/search_test.py @@ -28,9 +28,9 @@ def test_search_no_params(self): assert "error: the following arguments are required: reference" in self.client.out def test_search_no_matching_recipes(self, remotes): - expected_output = ("remote1:\n" + expected_output = ("remote1\n" " ERROR: Recipe 'whatever' not found\n" - "remote2:\n" + "remote2\n" " ERROR: Recipe 'whatever' not found\n") self.client.run("search whatever") @@ -86,9 +86,9 @@ def test_search_remote_errors_but_no_raising_exceptions(self, exc, output): with patch("conan.api.subapi.search.SearchAPI.recipes", new=Mock(side_effect=exc)): self.client.run("search whatever") expected_output = textwrap.dedent(f"""\ - remote1: + remote1 {output} - remote2: + remote2 {output} """) assert expected_output == self.client.out @@ -107,7 +107,7 @@ def test_search_by_name(self): self.client.run("search -r {} {}".format(remote_name, "test_recipe")) expected_output = ( - "remote1:\n" + "remote1\n" " test_recipe\n" " {}\n".format(recipe_name) ) @@ -124,11 +124,11 @@ def test_search_in_all_remotes(self): remote2_recipe2 = "test_recipe/2.1.0@user/channel" expected_output = ( - "remote1:\n" + "remote1\n" " test_recipe\n" " test_recipe/1.0.0@user/channel\n" " test_recipe/1.1.0@user/channel\n" - "remote2:\n" + "remote2\n" " test_recipe\n" " test_recipe/2.0.0@user/channel\n" " test_recipe/2.1.0@user/channel\n" @@ -155,7 +155,7 @@ def test_search_in_one_remote(self): remote2_recipe2 = "test_recipe/2.1.0@user/channel" expected_output = ( - "remote1:\n" + "remote1\n" " test_recipe\n" " test_recipe/1.0.0@user/channel\n" " test_recipe/1.1.0@user/channel\n" @@ -183,11 +183,11 @@ def test_search_package_found_in_one_remote(self): remote2_recipe2 = "another_recipe/2.1.0@user/channel" expected_output = ( - "remote1:\n" + "remote1\n" " test_recipe\n" " test_recipe/1.0.0@user/channel\n" " test_recipe/1.1.0@user/channel\n" - "remote2:\n" + "remote2\n" " ERROR: Recipe 'test_recipe' not found\n" ) @@ -225,7 +225,7 @@ def test_search_wildcard(self): remote1_recipe4 = "test_another/4.1.0@user/channel" expected_output = ( - "remote1:\n" + "remote1\n" " test_another\n" " test_another/2.1.0@user/channel\n" " test_another/4.1.0@user/channel\n" From 8cc93659831ca20cbb28b2bd1c34ef6eca9623ee Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 11 Jan 2023 00:32:12 +0100 Subject: [PATCH 8/8] fix test --- conans/test/integration/remote/test_conaninfo_parsing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conans/test/integration/remote/test_conaninfo_parsing.py b/conans/test/integration/remote/test_conaninfo_parsing.py index 718d306e87b..ad146a1cb82 100644 --- a/conans/test/integration/remote/test_conaninfo_parsing.py +++ b/conans/test/integration/remote/test_conaninfo_parsing.py @@ -25,9 +25,9 @@ class Recipe(ConanFile): t.save({"conanfile.py": conanfile}) t.run("create . ") t.run('list weird_info/1.0:*') - assert 'ññ¨¨&是=][{}"是是是' in t.out + assert 'ññ¨¨&是: ][{}"是是是' in t.out t.run("upload * -c -r default") # TODO: I have struggled with this, it was not accepting "latest", revision needs explicit one t.run('list weird_info/1.0#8c9e59246220eef8ca3bd4ac4f39ceb3 -r default') - assert 'ññ¨¨&是=][{}"是是是' in t.out + assert 'ññ¨¨&是: ][{}"是是是' in t.out