diff --git a/conda/plan.py b/conda/plan.py index fd17e0012c4..dd43fac3e08 100644 --- a/conda/plan.py +++ b/conda/plan.py @@ -184,6 +184,12 @@ def nothing_to_do(actions): return True +def add_unlink(actions, dist): + if inst.UNLINK not in actions: + actions[inst.UNLINK] = [] + actions[inst.UNLINK].append(dist) + + def plan_from_actions(actions): if 'op_order' in actions and actions['op_order']: op_order = actions['op_order'] @@ -296,7 +302,7 @@ def force_linked_actions(dists, index, prefix): actions[inst.RM_EXTRACTED].append(dist) actions[inst.EXTRACT].append(dist) if isfile(join(prefix, 'conda-meta', dist + '.json')): - actions[inst.UNLINK].append(dist) + add_unlink(actions, dist) actions[inst.LINK].append(dist) return actions @@ -426,7 +432,7 @@ def install_actions(prefix, index, specs, force=False, only_names=None, for dist in sorted(linked): name = install.name_dist(dist) if name in must_have and dist != must_have[name]: - actions[inst.UNLINK].append(dist) + add_unlink(actions, dist) return actions @@ -454,7 +460,7 @@ def remove_actions(prefix, specs, index=None, pinned=True): "Cannot remove %s because it is pinned. Use --no-pin " "to override." % dist) - actions[inst.UNLINK].append(dist) + add_unlink(actions, dist) if r and fn in index and r.track_features(fn): features_actions = remove_features_actions( prefix, index, r.track_features(fn)) @@ -482,9 +488,9 @@ def remove_features_actions(prefix, index, features): if fn not in index: continue if r.track_features(fn).intersection(features): - actions[inst.UNLINK].append(dist) + add_unlink(actions, dist) if r.features(fn).intersection(features): - actions[inst.UNLINK].append(dist) + add_unlink(actions, dist) subst = r.find_substitute(_linked, features, fn) if subst: to_link.append(subst[:-8]) @@ -508,7 +514,7 @@ def revert_actions(prefix, revision=-1): actions = ensure_linked_actions(state, prefix) for dist in curr - state: - actions[inst.UNLINK].append(dist) + add_unlink(actions, dist) return actions diff --git a/tests/test_plan.py b/tests/test_plan.py index c0f31e686a2..e9dceb198b6 100644 --- a/tests/test_plan.py +++ b/tests/test_plan.py @@ -1,9 +1,12 @@ import sys import json +import random import unittest from os.path import dirname, join from collections import defaultdict +import pytest + from conda.config import default_python, pkgs_dirs import conda.config from conda.install import LINK_HARD @@ -12,13 +15,17 @@ from conda.plan import display_actions from conda.resolve import Resolve +# FIXME This should be a relative import from tests.helpers import captured from conda.exceptions import CondaException +from .helpers import mock + with open(join(dirname(__file__), 'index.json')) as fi: index = json.load(fi) r = Resolve(index) + def solve(specs): return [fn[:-8] for fn in r.solve(specs)] @@ -34,6 +41,35 @@ def test_split_linkarg(self): self.assertEqual(inst.split_linkarg(arg), res) +@pytest.mark.parametrize("args", [ + (), + ("one", ), + ("one", "two", "three", ), +]) +def test_add_unlink_takes_two_arguments(args): + with pytest.raises(TypeError): + plan.add_unlink(*args) + + +class add_unlink_TestCase(unittest.TestCase): + def test_simply_adds_unlink_on_non_windows(self): + actions = {} + dist = {"foo": "bar%s" % random.randint(100, 200)} + with mock.patch.object(plan, "sys") as sys: + sys.platform = "not win32" + plan.add_unlink(actions, dist) + self.assertIn(inst.UNLINK, actions) + self.assertEqual(actions[inst.UNLINK], [dist, ]) + + def test_adds_to_existing_actions(self): + actions = {inst.UNLINK: [{"foo": "bar"}]} + dist = {"foo": "bar%s" % random.randint(100, 200)} + with mock.patch.object(plan, "sys") as sys: + sys.platform = "not win32" + plan.add_unlink(actions, dist) + self.assertEqual(2, len(actions[inst.UNLINK])) + + class TestAddDeaultsToSpec(unittest.TestCase): # tests for plan.add_defaults_to_specs(r, linked, specs)