From 41721642e25e8e1e91f74eb904b1d3f265e908ca Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Wed, 3 Jun 2020 00:57:59 -0500 Subject: [PATCH] Add unit tests for multi-file calls --- tests/test_plat_other.py | 134 +++++++++++++++++++++++++++++++-------- tests/test_plat_win.py | 46 ++++++++++---- 2 files changed, 138 insertions(+), 42 deletions(-) diff --git a/tests/test_plat_other.py b/tests/test_plat_other.py index ae4f391..32ff306 100644 --- a/tests/test_plat_other.py +++ b/tests/test_plat_other.py @@ -6,6 +6,7 @@ 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: @@ -15,18 +16,22 @@ 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'): + 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) + dir=op.expanduser("~"), prefix="send2trash_test", delete=False + ) def test_trash(self): s2t(self.file.name) @@ -34,8 +39,35 @@ class TestHomeTrash(unittest.TestCase): def tearDown(self): name = op.basename(self.file.name) - os.remove(op.join(HOMETRASH, 'files', name)) - os.remove(op.join(HOMETRASH, 'info', name+'.trashinfo')) + 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(): @@ -43,11 +75,12 @@ def _filesys_enc(): # Get canonical name of codec return codecs.lookup(enc).name -@unittest.skipIf(_filesys_enc() == 'ascii', 'ASCII filesystem') + +@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')) + 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): @@ -62,10 +95,11 @@ class TestUnicodeTrash(unittest.TestCase): if op.exists(self.file): os.remove(self.file) - trash_file = op.join(HOMETRASH, 'files', self.name) + 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')) + os.remove(op.join(HOMETRASH, "info", self.name + ".trashinfo")) + # # Tests for files on some other volume than the user's home directory. @@ -76,25 +110,31 @@ class TestUnicodeTrash(unittest.TestCase): # class TestExtVol(unittest.TestCase): def setUp(self): - self.trashTopdir = mkdtemp(prefix='s2t') + self.trashTopdir = mkdtemp(prefix="s2t") if PY3: trashTopdir_b = os.fsencode(self.trashTopdir) else: trashTopdir_b = self.trashTopdir - self.fileName = 'test.txt' + 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 "dev" return st.st_dev + def s_ismount(path): - if op.realpath(path) in (op.realpath(self.trashTopdir), op.realpath(trashTopdir_b)): + if op.realpath(path) in ( + op.realpath(self.trashTopdir), + op.realpath(trashTopdir_b), + ): return True return old_ismount(path) @@ -106,23 +146,40 @@ class TestExtVol(unittest.TestCase): 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) + 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'))) + 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)) + 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): @@ -130,13 +187,23 @@ class TestTopdirTrashFallback(TestExtVol): 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))) + 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 + os.chmod(self.trashTopdir, 0o500) # not writable to induce the exception def test_trash(self): with self.assertRaises(OSError): @@ -144,9 +211,10 @@ class TestTopdirFailure(TestExtVol): self.assertTrue(op.exists(self.filePath)) def tearDown(self): - os.chmod(self.trashTopdir, 0o700) # writable to allow deletion + 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): @@ -154,21 +222,31 @@ class TestSymlink(TestExtVol): # 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('~')) + 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) + 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) + 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))) + 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__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_plat_win.py b/tests/test_plat_win.py index 326e39c..17d8a7d 100644 --- a/tests/test_plat_win.py +++ b/tests/test_plat_win.py @@ -9,12 +9,16 @@ from tempfile import gettempdir from send2trash import send2trash as s2t -@unittest.skipIf(sys.platform != 'win32', 'Windows only') +@unittest.skipIf(sys.platform != "win32", "Windows only") class TestNormal(unittest.TestCase): def setUp(self): - self.dirname = '\\\\?\\' + op.join(gettempdir(), 'python.send2trash') - self.file = op.join(self.dirname, 'testfile.txt') + 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) @@ -23,29 +27,38 @@ class TestNormal(unittest.TestCase): dirname = op.dirname(path) if not op.isdir(dirname): os.makedirs(dirname) - with open(path, 'w') as writer: - writer.write('send2trash test') + with open(path, "w") as writer: + writer.write("send2trash test") def test_trash_file(self): s2t(self.file) self.assertFalse(op.exists(self.file)) + def test_trash_multifile(self): + s2t(self.files) + self.assertFalse(any([op.exists(file) for file in self.files])) + def test_file_not_found(self): - file = op.join(self.dirname, 'otherfile.txt') + file = op.join(self.dirname, "otherfile.txt") self.assertRaises(WindowsError, s2t, file) -@unittest.skipIf(sys.platform != 'win32', 'Windows only') + +@unittest.skipIf(sys.platform != "win32", "Windows only") class TestLongPath(unittest.TestCase): def setUp(self): - filename = 'A' * 100 - self.dirname = '\\\\?\\' + op.join(gettempdir(), filename) - self.file = op.join( + filename = "A" * 100 + self.dirname = "\\\\?\\" + op.join(gettempdir(), filename) + path = op.join( self.dirname, filename, filename, # From there, the path is not trashable from Explorer filename, - filename + '.txt') + 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) @@ -54,16 +67,21 @@ class TestLongPath(unittest.TestCase): dirname = op.dirname(path) if not op.isdir(dirname): os.makedirs(dirname) - with open(path, 'w') as writer: - writer.write('Looong filename!') + with open(path, "w") as writer: + writer.write("Looong filename!") def test_trash_file(self): s2t(self.file) self.assertFalse(op.exists(self.file)) + def test_trash_multifile(self): + s2t(self.files) + self.assertFalse(any([op.exists(file) for file in self.files])) + @unittest.skipIf( op.splitdrive(os.getcwd())[0] != op.splitdrive(gettempdir())[0], - 'Cannot trash long path from other drive') + "Cannot trash long path from other drive", + ) def test_trash_folder(self): s2t(self.dirname) self.assertFalse(op.exists(self.dirname))