From af0c1ba704cf9b0976b295094c38a8531339290d Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Wed, 10 Mar 2021 18:57:35 -0600 Subject: [PATCH] More test fixes - Fix travis windows env - Fix exception type in test_plat_win - Finish converting tests in test_plat_other, fix exception type --- .travis.yml | 2 +- tests/test_plat_other.py | 390 +++++++++++++++++++-------------------- tests/test_plat_win.py | 2 +- 3 files changed, 187 insertions(+), 207 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1dfd498..f1f0589 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ matrix: - os: windows language: sh python: "3.8" - env: "PATH=/c/Python:/c/Python/Scripts:$PATH" + env: "PATH=/c/Python38:/c/Python38/Scripts:$PATH" # Perform the manual steps on windows to install python3 before_install: - choco install python --version=3.8.6 diff --git a/tests/test_plat_other.py b/tests/test_plat_other.py index 38a48c4..f68b3b0 100644 --- a/tests/test_plat_other.py +++ b/tests/test_plat_other.py @@ -5,12 +5,13 @@ import os import sys from os import path as op from send2trash.compat import PY3 +from send2trash import TrashPermissionError try: from configparser import ConfigParser except ImportError: # py2 - from ConfigParser import ConfigParser + from ConfigParser import ConfigParser # noqa: F401 from tempfile import mkdtemp, NamedTemporaryFile, mktemp import shutil @@ -24,228 +25,207 @@ if sys.platform != "win32": else: pytest.skip("Skipping non-windows tests", allow_module_level=True) -# Could still use cleaning up. But no longer relies on ramfs. -# def touch(path): -# with open(path, "a"): -# os.utime(path, None) + +@pytest.fixture +def testfile(): + file = NamedTemporaryFile( + dir=op.expanduser("~"), prefix="send2trash_test", delete=False + ) + file.close() + yield file + # Cleanup trash files on supported platforms + if sys.platform != "win32": + name = op.basename(file.name) + # Remove trash files if they exist + if op.exists(op.join(HOMETRASH, "files", name)): + os.remove(op.join(HOMETRASH, "files", name)) + os.remove(op.join(HOMETRASH, "info", name + ".trashinfo")) + if op.exists(file.name): + os.remove(file.name) -# 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")) +@pytest.fixture +def testfiles(): + files = list( + map( + lambda index: NamedTemporaryFile( + dir=op.expanduser("~"), + prefix="send2trash_test{}".format(index), + delete=False, + ), + range(10), + ) + ) + [file.close() for file in files] + yield files + filenames = [op.basename(file.name) for file in files] + [os.remove(op.join(HOMETRASH, "files", filename)) for filename in filenames] + [ + os.remove(op.join(HOMETRASH, "info", filename + ".trashinfo")) + for filename in filenames + ] -# 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 test_trash(testfile): + s2t(testfile.name) + assert op.exists(testfile.name) is False -# def _filesys_enc(): -# enc = sys.getfilesystemencoding() -# # Get canonical name of codec -# return codecs.lookup(enc).name +def test_multitrash(testfiles): + filenames = [file.name for file in testfiles] + s2t(filenames) + assert any([op.exists(filename) for filename in filenames]) is False -# @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")) +def touch(path): + with open(path, "a"): + os.utime(path, None) -# # -# # 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) +def _filesys_enc(): + enc = sys.getfilesystemencoding() + # Get canonical name of codec + return codecs.lookup(enc).name -# 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)) +@pytest.fixture +def testUnicodefile(): + name = u"send2trash_tést1" + file = op.join(op.expanduser(b"~"), name.encode("utf-8")) + touch(file) + yield file + # Cleanup trash files on supported platforms + if sys.platform != "win32": + # Remove trash files if they exist + if op.exists(op.join(HOMETRASH, "files", name)): + os.remove(op.join(HOMETRASH, "files", name)) + os.remove(op.join(HOMETRASH, "info", name + ".trashinfo")) + if op.exists(file): + os.remove(file) -# # 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, -# ) -# ) -# ) +@pytest.mark.skipif(_filesys_enc() == "ascii", reason="Requires Unicode filesystem") +def test_trash_bytes(testUnicodefile): + s2t(testUnicodefile) + assert not op.exists(testUnicodefile) -# # 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) +@pytest.mark.skipif(_filesys_enc() == "ascii", reason="Requires Unicode filesystem") +def test_trash_unicode(testUnicodefile): + s2t(testUnicodefile.decode(sys.getfilesystemencoding())) + assert not op.exists(testUnicodefile) -# # 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("~")) +class ExtVol: + def __init__(self, path): + self.trashTopdir = path + if PY3: + self.trashTopdir_b = os.fsencode(self.trashTopdir) + else: + self.trashTopdir_b = self.trashTopdir -# 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 s_getdev(path): + from send2trash.plat_other import is_parent -# 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, -# ) -# ) -# ) + st = os.lstat(path) + if is_parent(self.trashTopdir, path): + return "dev" + return st.st_dev -# def tearDown(self): -# os.remove(self.slDir) -# TestExtVol.tearDown(self) + def s_ismount(path): + if op.realpath(path) in ( + op.realpath(self.trashTopdir), + op.realpath(self.trashTopdir_b), + ): + return True + return old_ismount(path) + + self.old_ismount = old_ismount = op.ismount + self.old_getdev = send2trash.plat_other.get_dev + send2trash.plat_other.os.path.ismount = s_ismount + send2trash.plat_other.get_dev = s_getdev + + def cleanup(self): + send2trash.plat_other.get_dev = self.old_getdev + send2trash.plat_other.os.path.ismount = self.old_ismount + shutil.rmtree(self.trashTopdir) + + +@pytest.fixture +def testExtVol(): + trashTopdir = mkdtemp(prefix="s2t") + volume = ExtVol(trashTopdir) + fileName = "test.txt" + filePath = op.join(volume.trashTopdir, fileName) + touch(filePath) + yield volume, fileName, filePath + volume.cleanup() + + +def test_trash_topdir(testExtVol): + trashDir = op.join(testExtVol[0].trashTopdir, ".Trash") + os.mkdir(trashDir, 0o777 | stat.S_ISVTX) + + s2t(testExtVol[2]) + assert op.exists(testExtVol[2]) is False + assert ( + op.exists(op.join(trashDir, str(os.getuid()), "files", testExtVol[1])) is True + ) + assert ( + op.exists( + op.join(trashDir, str(os.getuid()), "info", testExtVol[1] + ".trashinfo",) + ) + is True + ) + # info relative path (if another test is added, with the same fileName/Path, + # then it gets renamed etc.) + cfg = ConfigParser() + cfg.read(op.join(trashDir, str(os.getuid()), "info", testExtVol[1] + ".trashinfo")) + assert (testExtVol[1] == cfg.get("Trash Info", "Path", raw=True)) is True + + +def test_trash_topdir_fallback(testExtVol): + s2t(testExtVol[2]) + assert op.exists(testExtVol[2]) is False + assert ( + op.exists( + op.join( + testExtVol[0].trashTopdir, + ".Trash-" + str(os.getuid()), + "files", + testExtVol[1], + ) + ) + is True + ) + + +def test_trash_topdir_failure(testExtVol): + os.chmod(testExtVol[0].trashTopdir, 0o500) # not writable to induce the exception + pytest.raises(TrashPermissionError, s2t, [testExtVol[2]]) + os.chmod(testExtVol[0].trashTopdir, 0o700) # writable to allow deletion + + +def test_trash_symlink(testExtVol): + # Use mktemp (race conditioney but no symlink equivalent) + # Since is_parent uses realpath(), and our getdev uses is_parent, + # this should work + slDir = mktemp(prefix="s2t", dir=op.expanduser("~")) + os.mkdir(op.join(testExtVol[0].trashTopdir, "subdir"), 0o700) + filePath = op.join(testExtVol[0].trashTopdir, "subdir", testExtVol[1]) + touch(filePath) + os.symlink(op.join(testExtVol[0].trashTopdir, "subdir"), slDir) + s2t(op.join(slDir, testExtVol[1])) + assert op.exists(filePath) is False + assert ( + op.exists( + op.join( + testExtVol[0].trashTopdir, + ".Trash-" + str(os.getuid()), + "files", + testExtVol[1], + ) + ) + is True + ) + os.remove(slDir) diff --git a/tests/test_plat_win.py b/tests/test_plat_win.py index 9df6dda..52676fd 100644 --- a/tests/test_plat_win.py +++ b/tests/test_plat_win.py @@ -63,7 +63,7 @@ def _trash_multifile(files, fcn): def _file_not_found(dir, fcn): file = op.join(dir, "otherfile.txt") - pytest.raises(WindowsError, fcn, file) + pytest.raises(OSError, fcn, file) def test_trash_folder(testdir):