2015-03-19 01:37:24 +01:00
|
|
|
import os
|
2019-05-17 20:06:59 +02:00
|
|
|
import sys
|
2015-03-19 01:37:24 +01:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
from mock import Mock
|
2017-06-13 14:17:00 +02:00
|
|
|
|
2017-08-31 17:48:18 +02:00
|
|
|
import pip._internal.req.req_uninstall
|
|
|
|
from pip._internal.req.req_uninstall import (
|
2019-07-22 06:45:27 +02:00
|
|
|
StashedUninstallPathSet,
|
|
|
|
UninstallPathSet,
|
2019-07-30 20:36:15 +02:00
|
|
|
UninstallPthEntries,
|
2019-07-22 06:45:27 +02:00
|
|
|
compact,
|
|
|
|
compress_for_output_listing,
|
|
|
|
compress_for_rename,
|
|
|
|
uninstallation_paths,
|
2017-06-05 13:48:41 +02:00
|
|
|
)
|
|
|
|
from tests.lib import create_file
|
2015-03-19 01:37:24 +01:00
|
|
|
|
2015-03-21 00:18:33 +01:00
|
|
|
|
2015-03-21 00:09:34 +01:00
|
|
|
# Pretend all files are local, so UninstallPathSet accepts files in the tmpdir,
|
|
|
|
# outside the virtualenv
|
|
|
|
def mock_is_local(path):
|
|
|
|
return True
|
2015-03-19 17:35:53 +01:00
|
|
|
|
2015-03-21 00:18:33 +01:00
|
|
|
|
2016-11-11 00:41:48 +01:00
|
|
|
def test_uninstallation_paths():
|
|
|
|
class dist(object):
|
|
|
|
def get_metadata_lines(self, record):
|
|
|
|
return ['file.py,,',
|
|
|
|
'file.pyc,,',
|
|
|
|
'file.so,,',
|
|
|
|
'nopyc.py']
|
|
|
|
location = ''
|
|
|
|
|
|
|
|
d = dist()
|
|
|
|
|
|
|
|
paths = list(uninstallation_paths(d))
|
|
|
|
|
|
|
|
expected = ['file.py',
|
|
|
|
'file.pyc',
|
2018-06-21 19:30:20 +02:00
|
|
|
'file.pyo',
|
2016-11-11 00:41:48 +01:00
|
|
|
'file.so',
|
|
|
|
'nopyc.py',
|
2018-06-21 19:30:20 +02:00
|
|
|
'nopyc.pyc',
|
|
|
|
'nopyc.pyo']
|
2016-11-11 00:41:48 +01:00
|
|
|
|
|
|
|
assert paths == expected
|
|
|
|
|
|
|
|
# Avoid an easy 'unique generator' bug
|
|
|
|
paths2 = list(uninstallation_paths(d))
|
|
|
|
|
|
|
|
assert paths2 == paths
|
|
|
|
|
|
|
|
|
2017-06-05 13:48:41 +02:00
|
|
|
def test_compressed_listing(tmpdir):
|
|
|
|
def in_tmpdir(paths):
|
|
|
|
li = []
|
|
|
|
for path in paths:
|
2018-07-23 14:11:57 +02:00
|
|
|
li.append(
|
|
|
|
str(os.path.join(tmpdir, path.replace("/", os.path.sep)))
|
|
|
|
)
|
2017-06-05 13:48:41 +02:00
|
|
|
return li
|
|
|
|
|
|
|
|
sample = in_tmpdir([
|
|
|
|
"lib/mypkg.dist-info/METADATA",
|
|
|
|
"lib/mypkg.dist-info/PKG-INFO",
|
|
|
|
"lib/mypkg/would_be_removed.txt",
|
|
|
|
"lib/mypkg/would_be_skipped.skip.txt",
|
|
|
|
"lib/mypkg/__init__.py",
|
|
|
|
"lib/mypkg/my_awesome_code.py",
|
|
|
|
"lib/mypkg/__pycache__/my_awesome_code-magic.pyc",
|
|
|
|
"lib/mypkg/support/support_file.py",
|
|
|
|
"lib/mypkg/support/more_support.py",
|
|
|
|
"lib/mypkg/support/would_be_skipped.skip.py",
|
|
|
|
"lib/mypkg/support/__pycache__/support_file-magic.pyc",
|
|
|
|
"lib/random_other_place/file_without_a_dot_pyc",
|
|
|
|
"bin/mybin",
|
|
|
|
])
|
|
|
|
|
|
|
|
# Create the required files
|
|
|
|
for fname in sample:
|
|
|
|
create_file(fname, "random blub")
|
|
|
|
|
|
|
|
# Remove the files to be skipped from the paths
|
2017-06-13 06:32:40 +02:00
|
|
|
sample = [path for path in sample if ".skip." not in path]
|
2017-06-05 13:48:41 +02:00
|
|
|
|
|
|
|
expected_remove = in_tmpdir([
|
|
|
|
"bin/mybin",
|
|
|
|
"lib/mypkg.dist-info/*",
|
|
|
|
"lib/mypkg/*",
|
|
|
|
"lib/random_other_place/file_without_a_dot_pyc",
|
|
|
|
])
|
|
|
|
|
|
|
|
expected_skip = in_tmpdir([
|
|
|
|
"lib/mypkg/would_be_skipped.skip.txt",
|
|
|
|
"lib/mypkg/support/would_be_skipped.skip.py",
|
|
|
|
])
|
|
|
|
|
2018-11-21 23:33:15 +01:00
|
|
|
expected_rename = in_tmpdir([
|
|
|
|
"bin/",
|
|
|
|
"lib/mypkg.dist-info/",
|
|
|
|
"lib/mypkg/would_be_removed.txt",
|
|
|
|
"lib/mypkg/__init__.py",
|
|
|
|
"lib/mypkg/my_awesome_code.py",
|
|
|
|
"lib/mypkg/__pycache__/",
|
|
|
|
"lib/mypkg/support/support_file.py",
|
|
|
|
"lib/mypkg/support/more_support.py",
|
|
|
|
"lib/mypkg/support/__pycache__/",
|
|
|
|
"lib/random_other_place/",
|
|
|
|
])
|
|
|
|
|
2017-06-05 13:48:41 +02:00
|
|
|
will_remove, will_skip = compress_for_output_listing(sample)
|
2018-11-21 23:33:15 +01:00
|
|
|
will_rename = compress_for_rename(sample)
|
2017-06-05 13:48:41 +02:00
|
|
|
assert sorted(expected_skip) == sorted(compact(will_skip))
|
|
|
|
assert sorted(expected_remove) == sorted(compact(will_remove))
|
2018-11-21 23:33:15 +01:00
|
|
|
assert sorted(expected_rename) == sorted(compact(will_rename))
|
2017-06-05 13:48:41 +02:00
|
|
|
|
|
|
|
|
2015-03-19 01:37:24 +01:00
|
|
|
class TestUninstallPathSet(object):
|
2015-03-21 00:09:34 +01:00
|
|
|
def test_add(self, tmpdir, monkeypatch):
|
2017-08-31 17:48:18 +02:00
|
|
|
monkeypatch.setattr(pip._internal.req.req_uninstall, 'is_local',
|
|
|
|
mock_is_local)
|
2015-12-05 19:39:31 +01:00
|
|
|
# Fix case for windows tests
|
|
|
|
file_extant = os.path.normcase(os.path.join(tmpdir, 'foo'))
|
2016-06-10 21:27:07 +02:00
|
|
|
file_nonexistent = os.path.normcase(
|
|
|
|
os.path.join(tmpdir, 'nonexistent'))
|
2015-03-19 17:35:53 +01:00
|
|
|
with open(file_extant, 'w'):
|
|
|
|
pass
|
2015-03-19 01:37:24 +01:00
|
|
|
|
|
|
|
ups = UninstallPathSet(dist=Mock())
|
|
|
|
assert ups.paths == set()
|
|
|
|
ups.add(file_extant)
|
2017-12-15 06:56:04 +01:00
|
|
|
assert ups.paths == {file_extant}
|
2015-03-19 01:37:24 +01:00
|
|
|
|
2016-06-10 21:27:07 +02:00
|
|
|
ups.add(file_nonexistent)
|
2017-12-15 06:56:04 +01:00
|
|
|
assert ups.paths == {file_extant}
|
2015-03-19 01:37:24 +01:00
|
|
|
|
2019-05-17 20:06:59 +02:00
|
|
|
def test_add_pth(self, tmpdir, monkeypatch):
|
|
|
|
monkeypatch.setattr(pip._internal.req.req_uninstall, 'is_local',
|
|
|
|
mock_is_local)
|
|
|
|
# Fix case for windows tests
|
|
|
|
tmpdir = os.path.normcase(tmpdir)
|
|
|
|
on_windows = sys.platform == 'win32'
|
|
|
|
pth_file = os.path.join(tmpdir, 'foo.pth')
|
|
|
|
relative = '../../example'
|
|
|
|
if on_windows:
|
|
|
|
share = '\\\\example\\share\\'
|
|
|
|
share_com = '\\\\example.com\\share\\'
|
|
|
|
# Create a .pth file for testing
|
|
|
|
with open(pth_file, 'w') as f:
|
|
|
|
f.writelines([tmpdir, '\n',
|
|
|
|
relative, '\n'])
|
|
|
|
if on_windows:
|
|
|
|
f.writelines([share, '\n',
|
|
|
|
share_com, '\n'])
|
|
|
|
# Add paths to be removed
|
|
|
|
pth = UninstallPthEntries(pth_file)
|
|
|
|
pth.add(tmpdir)
|
|
|
|
pth.add(relative)
|
|
|
|
if on_windows:
|
|
|
|
pth.add(share)
|
|
|
|
pth.add(share_com)
|
|
|
|
# Check that the paths were added to entries
|
|
|
|
if on_windows:
|
|
|
|
check = set([tmpdir, relative, share, share_com])
|
|
|
|
else:
|
|
|
|
check = set([tmpdir, relative])
|
|
|
|
assert pth.entries == check
|
|
|
|
|
2015-03-19 01:37:24 +01:00
|
|
|
@pytest.mark.skipif("sys.platform == 'win32'")
|
2015-03-21 00:09:34 +01:00
|
|
|
def test_add_symlink(self, tmpdir, monkeypatch):
|
2017-08-31 17:48:18 +02:00
|
|
|
monkeypatch.setattr(pip._internal.req.req_uninstall, 'is_local',
|
|
|
|
mock_is_local)
|
2015-03-21 00:09:34 +01:00
|
|
|
f = os.path.join(tmpdir, 'foo')
|
2015-03-19 17:35:53 +01:00
|
|
|
with open(f, 'w'):
|
|
|
|
pass
|
2018-06-25 13:51:41 +02:00
|
|
|
foo_link = os.path.join(tmpdir, 'foo_link')
|
|
|
|
os.symlink(f, foo_link)
|
2015-03-19 01:37:24 +01:00
|
|
|
|
|
|
|
ups = UninstallPathSet(dist=Mock())
|
2018-06-25 13:51:41 +02:00
|
|
|
ups.add(foo_link)
|
|
|
|
assert ups.paths == {foo_link}
|
2015-10-03 18:17:26 +02:00
|
|
|
|
|
|
|
def test_compact_shorter_path(self, monkeypatch):
|
2017-08-31 17:48:18 +02:00
|
|
|
monkeypatch.setattr(pip._internal.req.req_uninstall, 'is_local',
|
2019-08-18 12:46:11 +02:00
|
|
|
mock_is_local)
|
2015-10-03 18:17:26 +02:00
|
|
|
monkeypatch.setattr('os.path.exists', lambda p: True)
|
2015-12-05 14:39:13 +01:00
|
|
|
# This deals with nt/posix path differences
|
|
|
|
short_path = os.path.normcase(os.path.abspath(
|
|
|
|
os.path.join(os.path.sep, 'path')))
|
2015-10-03 18:17:26 +02:00
|
|
|
ups = UninstallPathSet(dist=Mock())
|
2015-12-05 14:39:13 +01:00
|
|
|
ups.add(short_path)
|
|
|
|
ups.add(os.path.join(short_path, 'longer'))
|
2017-12-15 06:56:04 +01:00
|
|
|
assert compact(ups.paths) == {short_path}
|
2015-10-03 18:17:26 +02:00
|
|
|
|
|
|
|
@pytest.mark.skipif("sys.platform == 'win32'")
|
|
|
|
def test_detect_symlink_dirs(self, monkeypatch, tmpdir):
|
2017-08-31 17:48:18 +02:00
|
|
|
monkeypatch.setattr(pip._internal.req.req_uninstall, 'is_local',
|
2019-08-18 12:46:11 +02:00
|
|
|
mock_is_local)
|
2015-10-03 18:17:26 +02:00
|
|
|
|
|
|
|
# construct 2 paths:
|
|
|
|
# tmpdir/dir/file
|
|
|
|
# tmpdir/dirlink/file (where dirlink is a link to dir)
|
2019-07-02 07:00:32 +02:00
|
|
|
d = tmpdir.joinpath('dir')
|
2015-10-03 18:17:26 +02:00
|
|
|
d.mkdir()
|
2019-07-02 07:00:32 +02:00
|
|
|
dlink = tmpdir.joinpath('dirlink')
|
2015-10-03 18:17:26 +02:00
|
|
|
os.symlink(d, dlink)
|
2019-07-02 07:00:32 +02:00
|
|
|
d.joinpath('file').touch()
|
|
|
|
path1 = str(d.joinpath('file'))
|
|
|
|
path2 = str(dlink.joinpath('file'))
|
2015-10-03 18:17:26 +02:00
|
|
|
|
|
|
|
ups = UninstallPathSet(dist=Mock())
|
|
|
|
ups.add(path1)
|
|
|
|
ups.add(path2)
|
2017-12-15 06:56:04 +01:00
|
|
|
assert ups.paths == {path1}
|
2019-02-04 23:48:41 +01:00
|
|
|
|
|
|
|
|
|
|
|
class TestStashedUninstallPathSet(object):
|
|
|
|
WALK_RESULT = [
|
|
|
|
("A", ["B", "C"], ["a.py"]),
|
|
|
|
("A/B", ["D"], ["b.py"]),
|
|
|
|
("A/B/D", [], ["c.py"]),
|
|
|
|
("A/C", [], ["d.py", "e.py"]),
|
|
|
|
("A/E", ["F"], ["f.py"]),
|
|
|
|
("A/E/F", [], []),
|
|
|
|
("A/G", ["H"], ["g.py"]),
|
|
|
|
("A/G/H", [], ["h.py"]),
|
|
|
|
]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def mock_walk(cls, root):
|
|
|
|
for dirname, subdirs, files in cls.WALK_RESULT:
|
|
|
|
dirname = os.path.sep.join(dirname.split("/"))
|
|
|
|
if dirname.startswith(root):
|
|
|
|
yield dirname[len(root) + 1:], subdirs, files
|
|
|
|
|
|
|
|
def test_compress_for_rename(self, monkeypatch):
|
|
|
|
paths = [os.path.sep.join(p.split("/")) for p in [
|
|
|
|
"A/B/b.py",
|
|
|
|
"A/B/D/c.py",
|
|
|
|
"A/C/d.py",
|
|
|
|
"A/E/f.py",
|
|
|
|
"A/G/g.py",
|
|
|
|
]]
|
|
|
|
|
|
|
|
expected_paths = [os.path.sep.join(p.split("/")) for p in [
|
|
|
|
"A/B/", # selected everything below A/B
|
|
|
|
"A/C/d.py", # did not select everything below A/C
|
|
|
|
"A/E/", # only empty folders remain under A/E
|
|
|
|
"A/G/g.py", # non-empty folder remains under A/G
|
|
|
|
]]
|
|
|
|
|
|
|
|
monkeypatch.setattr('os.walk', self.mock_walk)
|
|
|
|
|
|
|
|
actual_paths = compress_for_rename(paths)
|
|
|
|
assert set(expected_paths) == set(actual_paths)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def make_stash(cls, tmpdir, paths):
|
|
|
|
for dirname, subdirs, files in cls.WALK_RESULT:
|
|
|
|
root = os.path.join(tmpdir, *dirname.split("/"))
|
|
|
|
if not os.path.exists(root):
|
|
|
|
os.mkdir(root)
|
|
|
|
for d in subdirs:
|
|
|
|
os.mkdir(os.path.join(root, d))
|
|
|
|
for f in files:
|
|
|
|
with open(os.path.join(root, f), "wb"):
|
|
|
|
pass
|
|
|
|
|
|
|
|
pathset = StashedUninstallPathSet()
|
|
|
|
|
|
|
|
paths = [os.path.join(tmpdir, *p.split('/')) for p in paths]
|
|
|
|
stashed_paths = [(p, pathset.stash(p)) for p in paths]
|
|
|
|
|
|
|
|
return pathset, stashed_paths
|
|
|
|
|
|
|
|
def test_stash(self, tmpdir):
|
|
|
|
pathset, stashed_paths = self.make_stash(tmpdir, [
|
|
|
|
"A/B/", "A/C/d.py", "A/E/", "A/G/g.py",
|
|
|
|
])
|
|
|
|
|
|
|
|
for old_path, new_path in stashed_paths:
|
|
|
|
assert not os.path.exists(old_path)
|
|
|
|
assert os.path.exists(new_path)
|
|
|
|
|
|
|
|
assert stashed_paths == pathset._moves
|
|
|
|
|
|
|
|
def test_commit(self, tmpdir):
|
|
|
|
pathset, stashed_paths = self.make_stash(tmpdir, [
|
|
|
|
"A/B/", "A/C/d.py", "A/E/", "A/G/g.py",
|
|
|
|
])
|
|
|
|
|
|
|
|
pathset.commit()
|
|
|
|
|
|
|
|
for old_path, new_path in stashed_paths:
|
|
|
|
assert not os.path.exists(old_path)
|
|
|
|
assert not os.path.exists(new_path)
|
|
|
|
|
|
|
|
def test_rollback(self, tmpdir):
|
|
|
|
pathset, stashed_paths = self.make_stash(tmpdir, [
|
|
|
|
"A/B/", "A/C/d.py", "A/E/", "A/G/g.py",
|
|
|
|
])
|
|
|
|
|
|
|
|
pathset.rollback()
|
|
|
|
|
|
|
|
for old_path, new_path in stashed_paths:
|
|
|
|
assert os.path.exists(old_path)
|
|
|
|
assert not os.path.exists(new_path)
|
2019-08-24 23:27:56 +02:00
|
|
|
|
|
|
|
@pytest.mark.skipif("sys.platform == 'win32'")
|
|
|
|
def test_commit_symlinks(self, tmpdir):
|
|
|
|
adir = tmpdir / "dir"
|
|
|
|
adir.mkdir()
|
|
|
|
dirlink = tmpdir / "dirlink"
|
|
|
|
dirlink.symlink_to(adir)
|
|
|
|
afile = tmpdir / "file"
|
|
|
|
afile.write_text("...")
|
|
|
|
filelink = tmpdir / "filelink"
|
|
|
|
filelink.symlink_to(afile)
|
|
|
|
|
|
|
|
pathset = StashedUninstallPathSet()
|
|
|
|
stashed_paths = []
|
|
|
|
stashed_paths.append(pathset.stash(dirlink))
|
|
|
|
stashed_paths.append(pathset.stash(filelink))
|
|
|
|
for stashed_path in stashed_paths:
|
|
|
|
assert os.path.lexists(stashed_path)
|
|
|
|
assert not os.path.exists(dirlink)
|
|
|
|
assert not os.path.exists(filelink)
|
|
|
|
|
|
|
|
pathset.commit()
|
|
|
|
|
|
|
|
# stash removed, links removed
|
|
|
|
for stashed_path in stashed_paths:
|
|
|
|
assert not os.path.lexists(stashed_path)
|
|
|
|
assert not os.path.lexists(dirlink) and not os.path.isdir(dirlink)
|
|
|
|
assert not os.path.lexists(filelink) and not os.path.isfile(filelink)
|
|
|
|
|
|
|
|
# link targets untouched
|
|
|
|
assert os.path.isdir(adir)
|
|
|
|
assert os.path.isfile(afile)
|
|
|
|
|
|
|
|
@pytest.mark.skipif("sys.platform == 'win32'")
|
|
|
|
def test_rollback_symlinks(self, tmpdir):
|
|
|
|
adir = tmpdir / "dir"
|
|
|
|
adir.mkdir()
|
|
|
|
dirlink = tmpdir / "dirlink"
|
|
|
|
dirlink.symlink_to(adir)
|
|
|
|
afile = tmpdir / "file"
|
|
|
|
afile.write_text("...")
|
|
|
|
filelink = tmpdir / "filelink"
|
|
|
|
filelink.symlink_to(afile)
|
|
|
|
|
|
|
|
pathset = StashedUninstallPathSet()
|
|
|
|
stashed_paths = []
|
|
|
|
stashed_paths.append(pathset.stash(dirlink))
|
|
|
|
stashed_paths.append(pathset.stash(filelink))
|
|
|
|
for stashed_path in stashed_paths:
|
|
|
|
assert os.path.lexists(stashed_path)
|
|
|
|
assert not os.path.lexists(dirlink)
|
|
|
|
assert not os.path.lexists(filelink)
|
|
|
|
|
|
|
|
pathset.rollback()
|
|
|
|
|
|
|
|
# stash removed, links restored
|
|
|
|
for stashed_path in stashed_paths:
|
|
|
|
assert not os.path.lexists(stashed_path)
|
|
|
|
assert os.path.lexists(dirlink) and os.path.isdir(dirlink)
|
|
|
|
assert os.path.lexists(filelink) and os.path.isfile(filelink)
|
|
|
|
|
|
|
|
# link targets untouched
|
|
|
|
assert os.path.isdir(adir)
|
|
|
|
assert os.path.isfile(afile)
|