Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --dry-run for conan remove #14760

Merged
merged 3 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions conan/cli/commands/remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@


def summary_remove_list(results):
""" Do litte format modification to serialized
list bundle so it looks prettier on text output
""" Do a little format modification to serialized
list bundle, so it looks prettier on text output
"""
cli_out_write("Remove summary:")
info = results["results"]
Expand Down Expand Up @@ -51,6 +51,8 @@ def remove(conan_api: ConanAPI, parser, *args):
parser.add_argument('-r', '--remote', action=OnceArgument,
help='Will remove from the specified remote')
parser.add_argument("-l", "--list", help="Package list file")
parser.add_argument("--dry-run", default=False, action="store_true",
help="Do not remove any items, only print those which would be removed")
args = parser.parse_args(*args)

if args.pattern is None and args.list is None:
Expand Down Expand Up @@ -88,7 +90,8 @@ def confirmation(message):
packages = ref_bundle.get("packages")
if packages is None:
if confirmation(f"Remove the recipe and all the packages of '{ref.repr_notime()}'?"):
conan_api.remove.recipe(ref, remote=remote)
if not args.dry_run:
conan_api.remove.recipe(ref, remote=remote)
else:
ref_dict.pop(ref.revision)
if not ref_dict:
Expand All @@ -104,7 +107,8 @@ def confirmation(message):

for pref, _ in prefs.items():
if confirmation(f"Remove the package '{pref.repr_notime()}'?"):
conan_api.remove.package(pref, remote=remote)
if not args.dry_run:
conan_api.remove.package(pref, remote=remote)
else:
pref_dict = packages[pref.package_id]["revisions"]
pref_dict.pop(pref.revision)
Expand Down
74 changes: 46 additions & 28 deletions conans/test/integration/command/remove_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from conans.test.utils.tools import NO_SETTINGS_PACKAGE_ID, TestClient, TestServer, GenConanfile
from conans.util.env import environment_update


conaninfo = '''
[settings]
arch=x64
Expand All @@ -28,39 +27,58 @@
'''


class RemoveWithoutUserChannel(unittest.TestCase):

def setUp(self):
self.test_server = TestServer(users={"admin": "password"},
write_permissions=[("lib/1.0@*/*", "admin")])
servers = {"default": self.test_server}
self.client = TestClient(servers=servers, inputs=["admin", "password"])

def test_local(self):
self.client.save({"conanfile.py": GenConanfile()})
self.client.run("create . --name=lib --version=1.0")
ref_layout = self.client.exported_layout()
pkg_layout = self.client.created_layout()
self.client.run("remove lib/1.0 -c")
self.assertFalse(os.path.exists(ref_layout.base_folder))
self.assertFalse(os.path.exists(pkg_layout.base_folder))
class TestRemoveWithoutUserChannel:

@pytest.mark.parametrize("dry_run", [True, False])
def test_local(self, dry_run):
client = TestClient()
client.save({"conanfile.py": GenConanfile()})
client.run("create . --name=lib --version=1.0")
ref_layout = client.exported_layout()
pkg_layout = client.created_layout()
extra = " --dry-run" if dry_run else ""
client.run("remove lib/1.0 -c" + extra)
assert os.path.exists(ref_layout.base_folder) == dry_run
assert os.path.exists(pkg_layout.base_folder) == dry_run

@pytest.mark.parametrize("confirm", [True, False])
def test_local_dryrun_output(self, confirm):
client = TestClient()
client.save({"conanfile.py": GenConanfile()})
client.run("create . --name=lib --version=1.0")
client.run("remove lib/1.0 --dry-run", inputs=["yes" if confirm else "no"])
assert "(yes/no)" in client.out # Users are asked even when dry-run is set
if confirm:
assert "Removed recipe and all binaries" in client.out # Output it printed for dry-run
else:
assert "Removed recipe and all binaries" not in client.out
print()

@pytest.mark.artifactory_ready
def test_remote(self):
self.client.save({"conanfile.py": GenConanfile()})
self.client.run("create . --name=lib --version=1.0")
self.client.run("upload lib/1.0 -r default -c")
self.client.run("remove lib/1.0 -c")
client = TestClient(default_server_user=True)
client.save({"conanfile.py": GenConanfile()})
client.run("create . --name=lib --version=1.0")
client.run("upload lib/1.0 -r default -c")
client.run("remove lib/1.0 -c")
# we can still install it
client.run("install --requires=lib/1.0@")
assert "lib/1.0: Retrieving package" in client.out
client.run("remove lib/1.0 -c")

# Now remove remotely, dry run first
client.run("remove lib/1.0 -c -r default --dry-run")

# we can still install it
self.client.run("install --requires=lib/1.0@")
self.assertIn("lib/1.0: Retrieving package", self.client.out)
self.client.run("remove lib/1.0 -c")
client.run("install --requires=lib/1.0@")
assert "lib/1.0: Retrieving package" in client.out
client.run("remove lib/1.0 -c")

# Now remove remotely
self.client.run("remove lib/1.0 -c -r default")
self.client.run("install --requires=lib/1.0@", assert_error=True)
# Now remove remotely, for real this time
client.run("remove lib/1.0 -c -r default")

self.assertIn("Unable to find 'lib/1.0' in remotes", self.client.out)
client.run("install --requires=lib/1.0@", assert_error=True)
assert "Unable to find 'lib/1.0' in remotes" in client.out


class RemovePackageRevisionsTest(unittest.TestCase):
Expand Down
6 changes: 4 additions & 2 deletions conans/test/utils/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@ def __init__(self, cache_folder=None, current_folder=None, servers=None, inputs=
self.out = ""
self.stdout = RedirectedTestOutput()
self.stderr = RedirectedTestOutput()
self.user_inputs = RedirectedInputStream(inputs)
self.user_inputs = RedirectedInputStream([])
self.inputs = inputs or []

# create default profile
text = default_profiles[platform.system()]
Expand Down Expand Up @@ -513,13 +514,14 @@ def _run_cli(self, command_line, assert_error=False):
self._handle_cli_result(command_line, assert_error=assert_error, error=error, trace=trace)
return error

def run(self, command_line, assert_error=False, redirect_stdout=None, redirect_stderr=None):
def run(self, command_line, assert_error=False, redirect_stdout=None, redirect_stderr=None, inputs=None):
""" run a single command as in the command line.
If user or password is filled, user_io will be mocked to return this
tuple if required
"""
from conans.test.utils.mocks import RedirectedTestOutput
with environment_update({"NO_COLOR": "1"}): # Not initialize colorama in testing
self.user_inputs = RedirectedInputStream(inputs or self.inputs)
self.stdout = RedirectedTestOutput() # Initialize each command
self.stderr = RedirectedTestOutput()
self.out = ""
Expand Down