1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
|
# encoding: utf-8
import codecs
import unittest
import os
from os import path as op
import send2trash.plat_other
from send2trash.plat_other import send2trash as s2t
from send2trash.compat import PY3
try:
from configparser import ConfigParser
except ImportError:
# py2
from ConfigParser import ConfigParser
from tempfile import mkdtemp, NamedTemporaryFile, mktemp
import shutil
import stat
import sys
# Could still use cleaning up. But no longer relies on ramfs.
HOMETRASH = send2trash.plat_other.HOMETRASH
def touch(path):
with open(path, "a"):
os.utime(path, None)
class TestHomeTrash(unittest.TestCase):
def setUp(self):
self.file = NamedTemporaryFile(
dir=op.expanduser("~"), prefix="send2trash_test", delete=False
)
def test_trash(self):
s2t(self.file.name)
self.assertFalse(op.exists(self.file.name))
def tearDown(self):
name = op.basename(self.file.name)
os.remove(op.join(HOMETRASH, "files", name))
os.remove(op.join(HOMETRASH, "info", name + ".trashinfo"))
class TestHomeMultiTrash(unittest.TestCase):
def setUp(self):
self.files = list(
map(
lambda index: NamedTemporaryFile(
dir=op.expanduser("~"),
prefix="send2trash_test{}".format(index),
delete=False,
),
range(10),
)
)
def test_multitrash(self):
filenames = [file.name for file in self.files]
s2t(filenames)
self.assertFalse(any([op.exists(filename) for filename in filenames]))
def tearDown(self):
filenames = [op.basename(file.name) for file in self.files]
[os.remove(op.join(HOMETRASH, "files", filename)) for filename in filenames]
[
os.remove(op.join(HOMETRASH, "info", filename + ".trashinfo"))
for filename in filenames
]
def _filesys_enc():
enc = sys.getfilesystemencoding()
# Get canonical name of codec
return codecs.lookup(enc).name
@unittest.skipIf(_filesys_enc() == "ascii", "ASCII filesystem")
class TestUnicodeTrash(unittest.TestCase):
def setUp(self):
self.name = u"send2trash_tést1"
self.file = op.join(op.expanduser(b"~"), self.name.encode("utf-8"))
touch(self.file)
def test_trash_bytes(self):
s2t(self.file)
assert not op.exists(self.file)
def test_trash_unicode(self):
s2t(self.file.decode(sys.getfilesystemencoding()))
assert not op.exists(self.file)
def tearDown(self):
if op.exists(self.file):
os.remove(self.file)
trash_file = op.join(HOMETRASH, "files", self.name)
if op.exists(trash_file):
os.remove(trash_file)
os.remove(op.join(HOMETRASH, "info", self.name + ".trashinfo"))
#
# Tests for files on some other volume than the user's home directory.
#
# What we need to stub:
# * plat_other.get_dev (to make sure the file will not be on the home dir dev)
# * os.path.ismount (to make our topdir look like a top dir)
#
class TestExtVol(unittest.TestCase):
def setUp(self):
self.trashTopdir = mkdtemp(prefix="s2t")
if PY3:
trashTopdir_b = os.fsencode(self.trashTopdir)
else:
trashTopdir_b = self.trashTopdir
self.fileName = "test.txt"
self.filePath = op.join(self.trashTopdir, self.fileName)
touch(self.filePath)
self.old_ismount = old_ismount = op.ismount
self.old_getdev = send2trash.plat_other.get_dev
def s_getdev(path):
from send2trash.plat_other import is_parent
st = os.lstat(path)
if is_parent(self.trashTopdir, path):
return "dev"
return st.st_dev
def s_ismount(path):
if op.realpath(path) in (
op.realpath(self.trashTopdir),
op.realpath(trashTopdir_b),
):
return True
return old_ismount(path)
send2trash.plat_other.os.path.ismount = s_ismount
send2trash.plat_other.get_dev = s_getdev
def tearDown(self):
send2trash.plat_other.get_dev = self.old_getdev
send2trash.plat_other.os.path.ismount = self.old_ismount
shutil.rmtree(self.trashTopdir)
class TestTopdirTrash(TestExtVol):
def setUp(self):
TestExtVol.setUp(self)
# Create a .Trash dir w/ a sticky bit
self.trashDir = op.join(self.trashTopdir, ".Trash")
os.mkdir(self.trashDir, 0o777 | stat.S_ISVTX)
def test_trash(self):
s2t(self.filePath)
self.assertFalse(op.exists(self.filePath))
self.assertTrue(
op.exists(op.join(self.trashDir, str(os.getuid()), "files", self.fileName))
)
self.assertTrue(
op.exists(
op.join(
self.trashDir,
str(os.getuid()),
"info",
self.fileName + ".trashinfo",
)
)
)
# info relative path (if another test is added, with the same fileName/Path,
# then it gets renamed etc.)
cfg = ConfigParser()
cfg.read(
op.join(
self.trashDir, str(os.getuid()), "info", self.fileName + ".trashinfo"
)
)
self.assertEqual(self.fileName, cfg.get("Trash Info", "Path", raw=True))
# Test .Trash-UID
class TestTopdirTrashFallback(TestExtVol):
def test_trash(self):
touch(self.filePath)
s2t(self.filePath)
self.assertFalse(op.exists(self.filePath))
self.assertTrue(
op.exists(
op.join(
self.trashTopdir,
".Trash-" + str(os.getuid()),
"files",
self.fileName,
)
)
)
# Test failure
class TestTopdirFailure(TestExtVol):
def setUp(self):
TestExtVol.setUp(self)
os.chmod(self.trashTopdir, 0o500) # not writable to induce the exception
def test_trash(self):
with self.assertRaises(OSError):
s2t(self.filePath)
self.assertTrue(op.exists(self.filePath))
def tearDown(self):
os.chmod(self.trashTopdir, 0o700) # writable to allow deletion
TestExtVol.tearDown(self)
# Make sure it will find the mount point properly for a file in a symlinked path
class TestSymlink(TestExtVol):
def setUp(self):
TestExtVol.setUp(self)
# Use mktemp (race conditioney but no symlink equivalent)
# Since is_parent uses realpath(), and our getdev uses is_parent,
# this should work
self.slDir = mktemp(prefix="s2t", dir=op.expanduser("~"))
os.mkdir(op.join(self.trashTopdir, "subdir"), 0o700)
self.filePath = op.join(self.trashTopdir, "subdir", self.fileName)
touch(self.filePath)
os.symlink(op.join(self.trashTopdir, "subdir"), self.slDir)
def test_trash(self):
s2t(op.join(self.slDir, self.fileName))
self.assertFalse(op.exists(self.filePath))
self.assertTrue(
op.exists(
op.join(
self.trashTopdir,
".Trash-" + str(os.getuid()),
"files",
self.fileName,
)
)
)
def tearDown(self):
os.remove(self.slDir)
TestExtVol.tearDown(self)
if __name__ == "__main__":
unittest.main()
|