diff --git a/conan/api/model.py b/conan/api/model.py index 874d6219f60..2f1a43b3b55 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: @@ -81,26 +83,31 @@ def prefs(self): return prefs def add_prefs(self, prefs, configurations=None): - confs = configurations or OrderedDict() 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)) - @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()): + 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) + 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 e78afdfccc8..d87cb048724 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 @@ -38,13 +38,16 @@ 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) if remote: ret = app.remote_manager.get_latest_package_reference(pref, remote=remote) else: ret = app.cache.get_latest_package_reference(pref) - return ret def package_revisions(self, pref: PkgReference, remote: Remote=None): @@ -116,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)) @@ -132,6 +135,9 @@ 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 @@ -139,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/conan/cli/commands/list.py b/conan/cli/commands/list.py index 643e8ef3256..81f04be78c7 100644 --- a/conan/cli/commands/list.py +++ b/conan/cli/commands/list.py @@ -3,11 +3,10 @@ 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 +# 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 @@ -17,65 +16,71 @@ 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(): + 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: + 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) - if info_per_ref_name.get("error"): - cli_out_write(f" ERROR: {info_per_ref_name.get('error')}", fg=error_color) - continue + # 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 - if not info_per_ref_name: - cli_out_write(f" There are no matching recipe references", fg=recipe_color) - continue + info = {remote: {"warning": "There are no matching recipe references"} if not values else values + for remote, values in info.items()} - 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 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] = format_timestamps(v) + return result + return item + info = {remote: format_timestamps(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) @@ -113,8 +118,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/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 32499ba9a88..fa34e03ff3d 100644 --- a/conans/test/integration/command_v2/list_test.py +++ b/conans/test/integration/command_v2/list_test.py @@ -2,600 +2,547 @@ 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)) - - - @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 _get_lastest_package_ref(self, pref): - return self.client.cache.get_latest_package_reference(PkgReference.loads(pref)) - - -class TestParams(TestListBase): +class TestParamErrors: 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 - - self.client.run("list -c", assert_error=True) - assert "error: the following arguments are required: reference" in self.client.out - - self.client.run('list -r="*"', assert_error=True) - assert "error: the following arguments are required: reference" in self.client.out - - self.client.run("list --remote remote1 --cache", assert_error=True) - assert "error: the following arguments are required: reference" in self.client.out + 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 + + +def remove_timestamps(item): + if isinstance(item, dict): + if item.get("timestamp"): + item["timestamp"] = "" + for v in item.values(): + remove_timestamps(v) + return item + + +class TestListRefs: + @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 + expected_output = re.sub(r"\(.*\)", "", expected_output) + output = re.sub(r"\(.*\)", "", str(client.out)) + assert expected_output == output -class TestRemotes(TestListBase): - def test_by_default_search_only_in_cache(self): - self._add_remote("remote1") - self._add_remote("remote2") + @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*" + 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) + expected_json = { + "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) + + @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) + expected_json = { + "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/*"]) + def test_list_recipe_no_match(self, client, pattern, remote): + if pattern == "nomatch": # EXACT IS AN ERROR + expected = "ERROR: Recipe 'nomatch' not found\n" + else: + 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): + # 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 + revisions + b58eeddfe2fd25ac3a105f72836b3360 (10-11-2023 10:13:13) + """) + self.check(client, pattern, remote, expected) + expected_json = { + "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): + # 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 + revisions + ffd4bc45820ddb320ab224685b9ba3fb (10-11-2023 10:13:13) + zlib/2.0.0@user/channel + revisions + ffd4bc45820ddb320ab224685b9ba3fb (10-11-2023 10:13:13) + """) + 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 + revisions + f034dc90894493961d92dd32a9ee3b78 (10-11-2023 10:13:13) + b58eeddfe2fd25ac3a105f72836b3360 (10-11-2023 10:13:13) + """) + self.check(client, pattern, remote, expected) + + @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 + revisions + f034dc90894493961d92dd32a9ee3b78 (10-11-2023 10:13:13) + b58eeddfe2fd25ac3a105f72836b3360 (10-11-2023 10:13:13) + zlib + zlib/1.0.0@user/channel + revisions + ffd4bc45820ddb320ab224685b9ba3fb (10-11-2023 10:13:13) + zlib/2.0.0@user/channel + revisions + ffd4bc45820ddb320ab224685b9ba3fb (10-11-2023 10:13:13) + zlix + zlix/1.0.0 + revisions + 81f598d1d8648389bb7d0494fffb654e (10-11-2023 10:13:13) + """) + self.check(client, pattern, remote, expected) - 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 +class TestListPrefs: - def test_search_no_matching_recipes(self): - self._add_remote("remote1") - self._add_remote("remote2") + @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 + 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 + 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/1.0.0": { + "revisions": { + "b58eeddfe2fd25ac3a105f72836b3360": { + "timestamp": "2023-01-10 16:30:27 UTC", + "packages": { + "9a4eb3c8701508aa9458b1a73d0633783ecc2270": { + "info": { + "settings": { + "os": "Linux" + } + } + }, + "ebec3dc6d7f6b907b3ada0c3d3cdc83613a2b715": { + "info": { + "settings": { + "os": "Windows" + } + } + } + } + } + } + } + } + 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 + 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 + 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 + 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 + 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: This is doing a package_id search, but not showing info + expected = textwrap.dedent(f"""\ + zli + 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) + + @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_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 + 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) + + @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 + 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) + + @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 + 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): + pass + + +class TestListRemotes: + """ advanced use case: + - check multiple remotes output + """ + + def test_search_no_matching_recipes(self, client): expected_output = textwrap.dedent("""\ - Local Cache: + Local Cache ERROR: Recipe 'whatever/0.1' not found - remote1: + default ERROR: Recipe 'whatever/0.1' not found - remote2: + other ERROR: Recipe 'whatever/0.1' not found """) - 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 TestListUseCases(TestListBase): - - def test_list_recipes(self): - self.client.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*") - 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 == self.client.out - - def test_list_latest_recipe_revision_by_default(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") + client.run(f'list whatever/1.0 -r="*"') 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)) - - 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: 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" 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