Updates for tests on windows

- Other platform tests WIP
- Windows long directory tests WIP
This commit is contained in:
Andrew Senetar 2021-03-02 19:33:44 -06:00
parent a324923ffa
commit 9f76fbf036
Signed by: arsenetar
GPG Key ID: C63300DCE48AB2F1
2 changed files with 353 additions and 334 deletions

View File

@ -1,10 +1,9 @@
# encoding: utf-8 # encoding: utf-8
import pytest
import codecs import codecs
import unittest
import os import os
import sys
from os import path as op from os import path as op
import send2trash.plat_other
from send2trash.plat_other import send2trash as s2t
from send2trash.compat import PY3 from send2trash.compat import PY3
try: try:
@ -12,241 +11,241 @@ try:
except ImportError: except ImportError:
# py2 # py2
from ConfigParser import ConfigParser from ConfigParser import ConfigParser
from tempfile import mkdtemp, NamedTemporaryFile, mktemp from tempfile import mkdtemp, NamedTemporaryFile, mktemp
import shutil import shutil
import stat import stat
import sys
# Could still use cleaning up. But no longer relies on ramfs. if sys.platform != "win32":
import send2trash.plat_other
from send2trash.plat_other import send2trash as s2t
HOMETRASH = send2trash.plat_other.HOMETRASH 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: else:
trashTopdir_b = self.trashTopdir pytest.skip("Skipping non-windows tests", allow_module_level=True)
self.fileName = "test.txt"
self.filePath = op.join(self.trashTopdir, self.fileName)
touch(self.filePath)
self.old_ismount = old_ismount = op.ismount # Could still use cleaning up. But no longer relies on ramfs.
self.old_getdev = send2trash.plat_other.get_dev # def touch(path):
# with open(path, "a"):
def s_getdev(path): # os.utime(path, None)
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): # class TestHomeTrash(unittest.TestCase):
def setUp(self): # def setUp(self):
TestExtVol.setUp(self) # self.file = NamedTemporaryFile(
# Create a .Trash dir w/ a sticky bit # dir=op.expanduser("~"), prefix="send2trash_test", delete=False
self.trashDir = op.join(self.trashTopdir, ".Trash") # )
os.mkdir(self.trashDir, 0o777 | stat.S_ISVTX)
def test_trash(self): # def test_trash(self):
s2t(self.filePath) # s2t(self.file.name)
self.assertFalse(op.exists(self.filePath)) # self.assertFalse(op.exists(self.file.name))
self.assertTrue(
op.exists(op.join(self.trashDir, str(os.getuid()), "files", self.fileName)) # def tearDown(self):
) # name = op.basename(self.file.name)
self.assertTrue( # os.remove(op.join(HOMETRASH, "files", name))
op.exists( # os.remove(op.join(HOMETRASH, "info", name + ".trashinfo"))
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 TestHomeMultiTrash(unittest.TestCase):
class TestTopdirTrashFallback(TestExtVol): # def setUp(self):
def test_trash(self): # self.files = list(
touch(self.filePath) # map(
s2t(self.filePath) # lambda index: NamedTemporaryFile(
self.assertFalse(op.exists(self.filePath)) # dir=op.expanduser("~"),
self.assertTrue( # prefix="send2trash_test{}".format(index),
op.exists( # delete=False,
op.join( # ),
self.trashTopdir, # range(10),
".Trash-" + str(os.getuid()), # )
"files", # )
self.fileName,
) # 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
# ]
# Test failure # def _filesys_enc():
class TestTopdirFailure(TestExtVol): # enc = sys.getfilesystemencoding()
def setUp(self): # # Get canonical name of codec
TestExtVol.setUp(self) # return codecs.lookup(enc).name
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 # @unittest.skipIf(_filesys_enc() == "ascii", "ASCII filesystem")
class TestSymlink(TestExtVol): # class TestUnicodeTrash(unittest.TestCase):
def setUp(self): # def setUp(self):
TestExtVol.setUp(self) # self.name = u"send2trash_tést1"
# Use mktemp (race conditioney but no symlink equivalent) # self.file = op.join(op.expanduser(b"~"), self.name.encode("utf-8"))
# Since is_parent uses realpath(), and our getdev uses is_parent, # touch(self.file)
# this should work
self.slDir = mktemp(prefix="s2t", dir=op.expanduser("~"))
os.mkdir(op.join(self.trashTopdir, "subdir"), 0o700) # def test_trash_bytes(self):
self.filePath = op.join(self.trashTopdir, "subdir", self.fileName) # s2t(self.file)
touch(self.filePath) # assert not op.exists(self.file)
os.symlink(op.join(self.trashTopdir, "subdir"), self.slDir)
def test_trash(self): # def test_trash_unicode(self):
s2t(op.join(self.slDir, self.fileName)) # s2t(self.file.decode(sys.getfilesystemencoding()))
self.assertFalse(op.exists(self.filePath)) # assert not op.exists(self.file)
self.assertTrue(
op.exists(
op.join(
self.trashTopdir,
".Trash-" + str(os.getuid()),
"files",
self.fileName,
)
)
)
def tearDown(self): # def tearDown(self):
os.remove(self.slDir) # if op.exists(self.file):
TestExtVol.tearDown(self) # 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"))
if __name__ == "__main__": # #
unittest.main() # # 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)

View File

@ -1,10 +1,9 @@
# coding: utf-8 # encoding: utf-8
import os import os
import shutil import shutil
import sys import sys
import unittest import pytest
from os import path as op from os import path as op
from tempfile import gettempdir
from send2trash import send2trash as s2t from send2trash import send2trash as s2t
@ -12,144 +11,165 @@ from send2trash import send2trash as s2t
if sys.platform == "win32": if sys.platform == "win32":
from send2trash.plat_win_modern import send2trash as s2t_modern from send2trash.plat_win_modern import send2trash as s2t_modern
from send2trash.plat_win_legacy import send2trash as s2t_legacy from send2trash.plat_win_legacy import send2trash as s2t_legacy
else:
pytest.skip("Skipping windows-only tests", allow_module_level=True)
@unittest.skipIf(sys.platform != "win32", "Windows only") def _create_tree(path):
class TestNormal(unittest.TestCase):
def setUp(self):
self.dirname = "\\\\?\\" + op.join(gettempdir(), "python.send2trash")
self.file = op.join(self.dirname, "testfile.txt")
self._create_tree(self.file)
self.files = [
op.join(self.dirname, "testfile{}.txt".format(index)) for index in range(10)
]
[self._create_tree(file) for file in self.files]
def tearDown(self):
shutil.rmtree(self.dirname, ignore_errors=True)
def _create_tree(self, path):
dirname = op.dirname(path) dirname = op.dirname(path)
if not op.isdir(dirname): if not op.isdir(dirname):
os.makedirs(dirname) os.makedirs(dirname)
with open(path, "w") as writer: with open(path, "w") as writer:
writer.write("send2trash test") writer.write("send2trash test")
def _trash_file(self, fcn):
fcn(self.file)
self.assertFalse(op.exists(self.file))
def _trash_multifile(self, fcn): @pytest.fixture
fcn(self.files) def testdir(tmp_path):
self.assertFalse(any([op.exists(file) for file in self.files])) dirname = "\\\\?\\" + str(tmp_path)
yield dirname
def _file_not_found(self, fcn): shutil.rmtree(dirname, ignore_errors=True)
file = op.join(self.dirname, "otherfile.txt")
self.assertRaises(WindowsError, fcn, file)
def test_trash_file(self):
self._trash_file(s2t)
def test_trash_multifile(self):
self._trash_multifile(s2t)
def test_file_not_found(self):
self._file_not_found(s2t)
def test_trash_file_modern(self):
self._trash_file(s2t_modern)
def test_trash_multifile_modern(self):
self._trash_multifile(s2t_modern)
def test_file_not_found_modern(self):
self._file_not_found(s2t_modern)
def test_trash_file_legacy(self):
self._trash_file(s2t_legacy)
def test_trash_multifile_legacy(self):
self._trash_multifile(s2t_legacy)
def test_file_not_found_legacy(self):
self._file_not_found(s2t_legacy)
@unittest.skipIf(sys.platform != "win32", "Windows only") @pytest.fixture
class TestLongPath(unittest.TestCase): def testfile(testdir):
def setUp(self): file = op.join(testdir, "testfile.txt")
self.functions = {s2t: "auto", s2t_legacy: "legacy", s2t_modern: "modern"} _create_tree(file)
filename = "A" * 100 yield file
self.dirname = "\\\\?\\" + op.join(gettempdir(), filename) # Note dir will cleanup the file
path = op.join(
self.dirname,
filename,
filename, # From there, the path is not trashable from Explorer
filename,
filename + "{}.txt",
)
self.file = path.format("")
self._create_tree(self.file)
self.files = [path.format(index) for index in range(10)]
[self._create_tree(file) for file in self.files]
def tearDown(self):
shutil.rmtree(self.dirname, ignore_errors=True)
def _create_tree(self, path): @pytest.fixture
dirname = op.dirname(path) def testfiles(testdir):
if not op.isdir(dirname): files = [op.join(testdir, "testfile{}.txt".format(index)) for index in range(10)]
os.makedirs(dirname) [_create_tree(file) for file in files]
with open(path, "w") as writer: yield files
writer.write("Looong filename!") # Note dir will cleanup the files
def _trash_file(self, fcn):
fcn(self.file)
self.assertFalse(op.exists(self.file))
def _trash_multifile(self, fcn): def _trash_folder(dir, fcn):
fcn(self.files) fcn(dir)
self.assertFalse(any([op.exists(file) for file in self.files])) assert op.exists(dir) is False
def _trash_folder(self, fcn):
fcn(self.dirname)
self.assertFalse(op.exists(self.dirname))
def test_trash_file(self): def _trash_file(file, fcn):
self._trash_file(s2t) fcn(file)
assert op.exists(file) is False
def test_trash_multifile(self):
self._trash_multifile(s2t)
@unittest.skipIf( def _trash_multifile(files, fcn):
op.splitdrive(os.getcwd())[0] != op.splitdrive(gettempdir())[0], fcn(files)
"Cannot trash long path from other drive", assert any([op.exists(file) for file in files]) is False
)
def test_trash_folder(self):
self._trash_folder(s2t)
def test_trash_file_modern(self):
self._trash_file(s2t_modern)
def test_trash_multifile_modern(self): def _file_not_found(dir, fcn):
self._trash_multifile(s2t_modern) file = op.join(dir, "otherfile.txt")
pytest.raises(WindowsError, fcn, file)
@unittest.skipIf(
op.splitdrive(os.getcwd())[0] != op.splitdrive(gettempdir())[0],
"Cannot trash long path from other drive",
)
def test_trash_folder_modern(self):
self._trash_folder(s2t_modern)
def test_trash_file_legacy(self): def test_trash_folder(testdir):
self._trash_file(s2t_legacy) _trash_folder(testdir, s2t)
def test_trash_multifile_legacy(self):
self._trash_multifile(s2t_legacy)
@unittest.skipIf( def test_trash_file(testfile):
op.splitdrive(os.getcwd())[0] != op.splitdrive(gettempdir())[0], _trash_file(testfile, s2t)
"Cannot trash long path from other drive",
)
def test_trash_folder_legacy(self): def test_trash_multifile(testfiles):
self._trash_folder(s2t_legacy) _trash_multifile(testfiles, s2t)
def test_file_not_found(testdir):
_file_not_found(testdir, s2t)
def test_trash_folder_modern(testdir):
_trash_folder(testdir, s2t_modern)
def test_trash_file_modern(testfile):
_trash_file(testfile, s2t_modern)
def test_trash_multifile_modern(testfiles):
_trash_multifile(testfiles, s2t_modern)
def test_file_not_found_modern(testdir):
_file_not_found(testdir, s2t_modern)
def test_trash_folder_legacy(testdir):
_trash_folder(testdir, s2t_legacy)
def test_trash_file_legacy(testfile):
_trash_file(testfile, s2t_legacy)
def test_trash_multifile_legacy(testfiles):
_trash_multifile(testfiles, s2t_legacy)
def test_file_not_found_legacy(testdir):
_file_not_found(testdir, s2t_legacy)
# Long path tests
@pytest.fixture
def longdir(tmp_path):
dirname = "\\\\?\\" + str(tmp_path)
name = "A" * 100
yield op.join(dirname, name, name, name)
shutil.rmtree(dirname, ignore_errors=True)
@pytest.fixture
def longfile(longdir):
name = "A" * 100
path = op.join(longdir, name + "{}.txt")
file = path.format("")
_create_tree(file)
yield file
@pytest.fixture
def longfiles(longdir):
name = "A" * 100
path = op.join(longdir, name + "{}.txt")
files = [path.format(index) for index in range(10)]
[_create_tree(file) for file in files]
yield files
# NOTE: both legacy and modern test "pass" on windows, but actually are not moving files to the
# recycle bin, this was tested on latest windows 10, thought to have worked previously
def test_trash_long_file_modern(longfile):
_trash_file(longfile, s2t_modern)
def test_trash_long_multifile_modern(longfiles):
_trash_multifile(longfiles, s2t_modern)
# @pytest.skipif(
# op.splitdrive(os.getcwd())[0] != op.splitdrive(gettempdir())[0],
# "Cannot trash long path from other drive",
# )
# def test_trash_long_folder_modern(self):
# self._trash_folder(s2t_modern)
def test_trash_long_file_legacy(longfile):
_trash_file(longfile, s2t_legacy)
def test_trash_long_multifile_legacy(longfiles):
_trash_multifile(longfiles, s2t_legacy)
# @pytest.skipif(
# op.splitdrive(os.getcwd())[0] != op.splitdrive(gettempdir())[0],
# "Cannot trash long path from other drive",
# )
# def test_trash_long_folder_legacy(self):
# self._trash_folder(s2t_legacy)