You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

236 lines
6.8 KiB

  1. # encoding: utf-8
  2. import pytest
  3. import codecs
  4. import os
  5. import sys
  6. from os import path as op
  7. from send2trash.compat import PY3
  8. from send2trash import TrashPermissionError
  9. try:
  10. from configparser import ConfigParser
  11. except ImportError:
  12. # py2
  13. from ConfigParser import ConfigParser # noqa: F401
  14. from tempfile import mkdtemp, NamedTemporaryFile, mktemp
  15. import shutil
  16. import stat
  17. if sys.platform != "win32":
  18. import send2trash.plat_other
  19. from send2trash.plat_other import send2trash as s2t
  20. HOMETRASH = send2trash.plat_other.HOMETRASH
  21. else:
  22. pytest.skip("Skipping non-windows tests", allow_module_level=True)
  23. @pytest.fixture
  24. def testfile():
  25. file = NamedTemporaryFile(
  26. dir=op.expanduser("~"), prefix="send2trash_test", delete=False
  27. )
  28. file.close()
  29. assert op.exists(file.name) is True
  30. yield file
  31. # Cleanup trash files on supported platforms
  32. if sys.platform != "win32":
  33. name = op.basename(file.name)
  34. # Remove trash files if they exist
  35. if op.exists(op.join(HOMETRASH, "files", name)):
  36. os.remove(op.join(HOMETRASH, "files", name))
  37. os.remove(op.join(HOMETRASH, "info", name + ".trashinfo"))
  38. if op.exists(file.name):
  39. os.remove(file.name)
  40. @pytest.fixture
  41. def testfiles():
  42. files = list(
  43. map(
  44. lambda index: NamedTemporaryFile(
  45. dir=op.expanduser("~"),
  46. prefix="send2trash_test{}".format(index),
  47. delete=False,
  48. ),
  49. range(10),
  50. )
  51. )
  52. [file.close() for file in files]
  53. assert all([op.exists(file.name) for file in files]) is True
  54. yield files
  55. filenames = [op.basename(file.name) for file in files]
  56. [os.remove(op.join(HOMETRASH, "files", filename)) for filename in filenames]
  57. [
  58. os.remove(op.join(HOMETRASH, "info", filename + ".trashinfo"))
  59. for filename in filenames
  60. ]
  61. def test_trash(testfile):
  62. s2t(testfile.name)
  63. assert op.exists(testfile.name) is False
  64. def test_multitrash(testfiles):
  65. filenames = [file.name for file in testfiles]
  66. s2t(filenames)
  67. assert any([op.exists(filename) for filename in filenames]) is False
  68. def touch(path):
  69. with open(path, "a"):
  70. os.utime(path, None)
  71. def _filesys_enc():
  72. enc = sys.getfilesystemencoding()
  73. # Get canonical name of codec
  74. return codecs.lookup(enc).name
  75. @pytest.fixture
  76. def testUnicodefile():
  77. name = u"send2trash_tést1"
  78. file = op.join(op.expanduser(b"~"), name.encode("utf-8"))
  79. touch(file)
  80. assert op.exists(file) is True
  81. yield file
  82. # Cleanup trash files on supported platforms
  83. if sys.platform != "win32":
  84. # Remove trash files if they exist
  85. if op.exists(op.join(HOMETRASH, "files", name)):
  86. os.remove(op.join(HOMETRASH, "files", name))
  87. os.remove(op.join(HOMETRASH, "info", name + ".trashinfo"))
  88. if op.exists(file):
  89. os.remove(file)
  90. @pytest.mark.skipif(_filesys_enc() == "ascii", reason="Requires Unicode filesystem")
  91. def test_trash_bytes(testUnicodefile):
  92. s2t(testUnicodefile)
  93. assert not op.exists(testUnicodefile)
  94. @pytest.mark.skipif(_filesys_enc() == "ascii", reason="Requires Unicode filesystem")
  95. def test_trash_unicode(testUnicodefile):
  96. s2t(testUnicodefile.decode(sys.getfilesystemencoding()))
  97. assert not op.exists(testUnicodefile)
  98. class ExtVol:
  99. def __init__(self, path):
  100. self.trashTopdir = path
  101. if PY3:
  102. self.trashTopdir_b = os.fsencode(self.trashTopdir)
  103. else:
  104. self.trashTopdir_b = self.trashTopdir
  105. def s_getdev(path):
  106. from send2trash.plat_other import is_parent
  107. st = os.lstat(path)
  108. if is_parent(self.trashTopdir, path):
  109. return "dev"
  110. return st.st_dev
  111. def s_ismount(path):
  112. if op.realpath(path) in (
  113. op.realpath(self.trashTopdir),
  114. op.realpath(self.trashTopdir_b),
  115. ):
  116. return True
  117. return old_ismount(path)
  118. self.old_ismount = old_ismount = op.ismount
  119. self.old_getdev = send2trash.plat_other.get_dev
  120. send2trash.plat_other.os.path.ismount = s_ismount
  121. send2trash.plat_other.get_dev = s_getdev
  122. def cleanup(self):
  123. send2trash.plat_other.get_dev = self.old_getdev
  124. send2trash.plat_other.os.path.ismount = self.old_ismount
  125. shutil.rmtree(self.trashTopdir)
  126. @pytest.fixture
  127. def testExtVol():
  128. trashTopdir = mkdtemp(prefix="s2t")
  129. volume = ExtVol(trashTopdir)
  130. fileName = "test.txt"
  131. filePath = op.join(volume.trashTopdir, fileName)
  132. touch(filePath)
  133. assert op.exists(filePath) is True
  134. yield volume, fileName, filePath
  135. volume.cleanup()
  136. def test_trash_topdir(testExtVol):
  137. trashDir = op.join(testExtVol[0].trashTopdir, ".Trash")
  138. os.mkdir(trashDir, 0o777 | stat.S_ISVTX)
  139. s2t(testExtVol[2])
  140. assert op.exists(testExtVol[2]) is False
  141. assert (
  142. op.exists(op.join(trashDir, str(os.getuid()), "files", testExtVol[1])) is True
  143. )
  144. assert (
  145. op.exists(
  146. op.join(trashDir, str(os.getuid()), "info", testExtVol[1] + ".trashinfo",)
  147. )
  148. is True
  149. )
  150. # info relative path (if another test is added, with the same fileName/Path,
  151. # then it gets renamed etc.)
  152. cfg = ConfigParser()
  153. cfg.read(op.join(trashDir, str(os.getuid()), "info", testExtVol[1] + ".trashinfo"))
  154. assert (testExtVol[1] == cfg.get("Trash Info", "Path", raw=True)) is True
  155. def test_trash_topdir_fallback(testExtVol):
  156. s2t(testExtVol[2])
  157. assert op.exists(testExtVol[2]) is False
  158. assert (
  159. op.exists(
  160. op.join(
  161. testExtVol[0].trashTopdir,
  162. ".Trash-" + str(os.getuid()),
  163. "files",
  164. testExtVol[1],
  165. )
  166. )
  167. is True
  168. )
  169. def test_trash_topdir_failure(testExtVol):
  170. os.chmod(testExtVol[0].trashTopdir, 0o500) # not writable to induce the exception
  171. pytest.raises(TrashPermissionError, s2t, [testExtVol[2]])
  172. os.chmod(testExtVol[0].trashTopdir, 0o700) # writable to allow deletion
  173. def test_trash_symlink(testExtVol):
  174. # Use mktemp (race conditioney but no symlink equivalent)
  175. # Since is_parent uses realpath(), and our getdev uses is_parent,
  176. # this should work
  177. slDir = mktemp(prefix="s2t", dir=op.expanduser("~"))
  178. os.mkdir(op.join(testExtVol[0].trashTopdir, "subdir"), 0o700)
  179. filePath = op.join(testExtVol[0].trashTopdir, "subdir", testExtVol[1])
  180. touch(filePath)
  181. os.symlink(op.join(testExtVol[0].trashTopdir, "subdir"), slDir)
  182. s2t(op.join(slDir, testExtVol[1]))
  183. assert op.exists(filePath) is False
  184. assert (
  185. op.exists(
  186. op.join(
  187. testExtVol[0].trashTopdir,
  188. ".Trash-" + str(os.getuid()),
  189. "files",
  190. testExtVol[1],
  191. )
  192. )
  193. is True
  194. )
  195. os.remove(slDir)