From 869bf70030634ae9aa0286a7b55698017625f640 Mon Sep 17 00:00:00 2001 From: hechth Date: Fri, 24 Nov 2023 11:37:07 +0100 Subject: [PATCH] implemented further tests --- fs_irods/iRODSFS.py | 105 ++++++++++++++++++++++-------- tests/irods_session_testing.ipynb | 26 ++++++-- tests/test_fs_extension.py | 5 -- tests/test_iRODSFS.py | 57 +++++++++++++++- tests/test_utils.py | 2 +- 5 files changed, 155 insertions(+), 40 deletions(-) diff --git a/fs_irods/iRODSFS.py b/fs_irods/iRODSFS.py index ecfee31..bd82cb2 100644 --- a/fs_irods/iRODSFS.py +++ b/fs_irods/iRODSFS.py @@ -2,10 +2,11 @@ import os from multiprocessing import RLock +from typing import Text from fs.base import FS from fs.info import Info from fs.permissions import Permissions -from fs.errors import DirectoryExists, ResourceNotFound, RemoveRootError, DirectoryExpected, FileExpected, FileExists +from fs.errors import DirectoryExists, ResourceNotFound, RemoveRootError, DirectoryExpected, FileExpected, FileExists, DirectoryNotEmpty from irods.session import iRODSSession from irods.collection import iRODSCollection @@ -28,6 +29,8 @@ def __init__(self, host: str, port:int, user:str, password: str, zone: str) -> N self._zone = zone def _wrap(self, path: str) -> str: + if path.startswith(f"/{self._zone}"): + return path return str(iRODSPath(self._zone, path)) @@ -51,7 +54,7 @@ def getinfo(self, path: str, namespaces: list|None = None) -> Info: Raises: ResourceNotFound: If the path does not exist. """ - self._assert_exists(path) + self._check_exists(path) with self._session() as session: raw_info = {"basic": {"name": path}} @@ -74,7 +77,7 @@ def listdir(self, path: str) -> list: ResourceNotFound: If the path does not exist. DirectoryExpected: If the path is not a directory. """ - self._assert_exists(path) + self._check_exists(path) with self._session() as session: coll: iRODSCollection = session.collections.get(self._wrap(path)) return [item.path for item in coll.data_objects + coll.subcollections] @@ -121,8 +124,8 @@ def openbin(self, path: str, mode:str = "r", buffering: int = -1, **options) -> FileExpected: If the path is not a file. FileExists: If the path exists, and exclusive mode is specified (x in the mode). """ - if self.isdir(path): - raise FileExpected(path) + self._check_isfile(path) + with self._session() as session: if not session.data_objects.exists(self._wrap(path)): if not can_create(mode): @@ -140,12 +143,22 @@ def remove(self, path: str): ResourceNotFound: If the path does not exist. FileExpected: If the path is not a file. """ - self._assert_exists(path) - with self._session() as session: - if not self.isfile(path): - raise FileExpected(path) - + self._check_exists(path) + self._check_isfile(path) + + with self._session() as session: session.data_objects.unlink(self._wrap(path)) + + def _check_isfile(self, path: str): + """Check if a path points to a file and raise an FileExpected error if not. + Args: + path (str): A path to a file on the filesystem. + Raises: + ResourceNotFound: If the path does not exist. + FileExpected: If the path is not a file. + """ + if not self.isfile(path): + raise FileExpected(path) def removedir(self, path: str): """Remove a directory from the filesystem. @@ -155,16 +168,62 @@ def removedir(self, path: str): ResourceNotFound: If the path does not exist. DirectoryExpected: If the path is not a directory. RemoveRootError: If the path is the root directory. + DirectoryNotEmpty: If the directory is not empty. """ + self._check_exists(path) + self._check_isdir(path) + + if self._is_root(path): + raise RemoveRootError() + if not self.isempty(path): + raise DirectoryNotEmpty(path) + with self._session() as session: - if not session.collections.exists(self._wrap(path)): - raise ResourceNotFound(path) - if not self.isdir(path): - raise DirectoryExpected(path) - if path == "/": - raise RemoveRootError() - session.collections.remove(self._wrap(path), recurse=False) + + def _is_root(self, path: str) -> bool: + """Check if path points to root of the filesystem. + + Args: + path (str): Path to a directory. + + Returns: + bool: True if path points to root. + """ + return path in ["/", "", self._zone] + + def removetree(self, path: str): + """Recursively remove a directory and all its contents. + This method is similar to removedir, but will remove the contents of the directory if it is not empty. + + Args: + path (str): A path to a directory on the filesystem. + Raises: + ResourceNotFound: If the path does not exist. + DirectoryExpected: If the path is not a directory. + """ + self._check_exists(path) + self._check_isdir(path) + + with self._session() as session: + if self._is_root(path): + root: iRODSCollection = session.collections.get(self._wrap(path)) + for item in root.data_objects: + item.unlink() + for item in root.subcollections: + item.remove() + else: + session.collections.remove(self._wrap(path), recurse=True) + + def _check_isdir(self, path: str): + """Check if a path is a directory. + Args: + path (str): A path to a resource on the filesystem. + Raises: + DirectoryExpected: If the path is not a directory. + """ + if not self.isdir(path): + raise DirectoryExpected(path) def setinfo(self, path: str, info: dict) -> None: """Set information about a resource on the filesystem. @@ -174,11 +233,10 @@ def setinfo(self, path: str, info: dict) -> None: Raises: ResourceNotFound: If the path does not exist. """ - path = self._wrap(path) - self.exists(path) + self._check_exists(path) raise NotImplementedError() - def _assert_exists(self, path:str): + def _check_exists(self, path:str): """Check if a resource exists. Args: path (str): A path to a resource on the filesystem. @@ -237,10 +295,3 @@ def exists(self, path: str) -> bool: with self._session() as session: path = self._wrap(path) return session.data_objects.exists(path) or session.collections.exists(path) - - def clean(self): - """Clean up the filesystem. - """ - with self._session() as session: - root_collection = session.collections.get(self._wrap("")) - diff --git a/tests/irods_session_testing.ipynb b/tests/irods_session_testing.ipynb index 6347313..a042260 100644 --- a/tests/irods_session_testing.ipynb +++ b/tests/irods_session_testing.ipynb @@ -119,7 +119,17 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "session.collections.remove(\"/tempZone/foo/bar\")\n", + "session.collections.remove(\"/tempZone/foo\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -129,7 +139,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mCAT_NO_ROWS_FOUND\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m/home/hechth/git/hechth/fs-irods/tests/irods_session_testing.ipynb Cell 7\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m session\u001b[39m.\u001b[39;49mcollections\u001b[39m.\u001b[39;49mremove(\u001b[39m\"\u001b[39;49m\u001b[39m/tempZone/foo/bar\u001b[39;49m\u001b[39m\"\u001b[39;49m)\n\u001b[1;32m 2\u001b[0m session\u001b[39m.\u001b[39mcollections\u001b[39m.\u001b[39mremove(\u001b[39m\"\u001b[39m\u001b[39m/tempZone/foo\u001b[39m\u001b[39m\"\u001b[39m)\n", + "\u001b[1;32m/home/hechth/git/hechth/fs-irods/tests/irods_session_testing.ipynb Cell 10\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m session\u001b[39m.\u001b[39;49mcollections\u001b[39m.\u001b[39;49mremove(\u001b[39m\"\u001b[39;49m\u001b[39m/tempZone/test\u001b[39;49m\u001b[39m\"\u001b[39;49m)\n", "File \u001b[0;32m/m2b/home/hechth/micromamba/envs/fs-irods/lib/python3.12/site-packages/irods/manager/collection_manager.py:70\u001b[0m, in \u001b[0;36mCollectionManager.remove\u001b[0;34m(self, path, recurse, force, **options)\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[39mwith\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39msess\u001b[39m.\u001b[39mpool\u001b[39m.\u001b[39mget_connection() \u001b[39mas\u001b[39;00m conn:\n\u001b[1;32m 69\u001b[0m conn\u001b[39m.\u001b[39msend(message)\n\u001b[0;32m---> 70\u001b[0m response \u001b[39m=\u001b[39m conn\u001b[39m.\u001b[39;49mrecv()\n\u001b[1;32m 72\u001b[0m \u001b[39mwhile\u001b[39;00m response\u001b[39m.\u001b[39mint_info \u001b[39m==\u001b[39m SYS_SVR_TO_CLI_COLL_STAT:\n\u001b[1;32m 73\u001b[0m conn\u001b[39m.\u001b[39mreply(SYS_CLI_TO_SVR_COLL_STAT_REPLY)\n", "File \u001b[0;32m/m2b/home/hechth/micromamba/envs/fs-irods/lib/python3.12/site-packages/irods/connection.py:132\u001b[0m, in \u001b[0;36mConnection.recv\u001b[0;34m(self, into_buffer, return_message, acceptable_errors)\u001b[0m\n\u001b[1;32m 130\u001b[0m err_msg \u001b[39m=\u001b[39m \u001b[39mNone\u001b[39;00m\n\u001b[1;32m 131\u001b[0m \u001b[39mif\u001b[39;00m nominal_code(msg\u001b[39m.\u001b[39mint_info) \u001b[39mnot\u001b[39;00m \u001b[39min\u001b[39;00m acceptable_codes:\n\u001b[0;32m--> 132\u001b[0m \u001b[39mraise\u001b[39;00m get_exception_by_code(msg\u001b[39m.\u001b[39mint_info, err_msg)\n\u001b[1;32m 133\u001b[0m \u001b[39mreturn\u001b[39;00m msg\n", "\u001b[0;31mCAT_NO_ROWS_FOUND\u001b[0m: None" @@ -137,8 +147,16 @@ } ], "source": [ - "session.collections.remove(\"/tempZone/foo/bar\")\n", - "session.collections.remove(\"/tempZone/foo\")" + "session.collections.remove(\"/tempZone/test\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "session.collections.remove(\"/tempZone/home/subcollection\")" ] }, { diff --git a/tests/test_fs_extension.py b/tests/test_fs_extension.py index 44c780e..df8372e 100644 --- a/tests/test_fs_extension.py +++ b/tests/test_fs_extension.py @@ -9,11 +9,6 @@ class TestMyFS(FSTestCases, unittest.TestCase): - @patch("fs_irods.iRODSFS.iRODSSession", new=DelayedSession) def make_fs(self): sut = iRODSFSBuilder().build() return sut - - def destroy_fs(self, fs): - fs.clean() - self.fs.clean() diff --git a/tests/test_iRODSFS.py b/tests/test_iRODSFS.py index 7fc92a1..7ffe3f6 100644 --- a/tests/test_iRODSFS.py +++ b/tests/test_iRODSFS.py @@ -1,12 +1,13 @@ import time import pytest +import os from fs_irods import iRODSFS from unittest.mock import patch from tests.DelayedSession import DelayedSession from tests.builder_iRODSFS import iRODSFSBuilder -from fs.errors import DirectoryExists, ResourceNotFound, RemoveRootError, DirectoryExpected, FileExpected, FileExists +from fs.errors import * @patch("fs_irods.iRODSFS.iRODSSession") @@ -62,7 +63,7 @@ def test_makedir_exceptions(fs:iRODSFS, path: str, exception: type): @pytest.mark.parametrize("path", [ "test.txt", "home/rods/test.txt" ]) -def test_create(fs: iRODSFS, path): +def test_create_remove(fs: iRODSFS, path): fs.create(path) assert fs.isfile(path) == True fs.remove(path) @@ -111,7 +112,7 @@ def test_exists(fs: iRODSFS, path: str, expected: bool): @pytest.mark.parametrize("path", [ - "foo", "foo/bar" + "foo", "home/rods/test" ]) def test_removedir(fs: iRODSFS, path: str): fs.makedir(path) @@ -120,6 +121,27 @@ def test_removedir(fs: iRODSFS, path: str): assert fs.isdir(path) == False +@pytest.mark.parametrize("path, exception", [ + ["", RemoveRootError], + ["/", RemoveRootError], + ["existing_file.txt", DirectoryExpected], + ["home/something", ResourceNotFound], + ["existing_collection", DirectoryNotEmpty] +]) +def test_removedir_exceptions(fs: iRODSFS, path: str, exception: type): + with pytest.raises(exception): + fs.removedir(path) + + +@pytest.mark.parametrize("path, exception", [ + ["home", FileExpected], + ["some_file.txt", ResourceNotFound] +]) +def test_remove_exceptions(fs: iRODSFS, path: str, exception: type): + with pytest.raises(exception): + fs.remove(path) + + @pytest.mark.parametrize("path, expected", [ ["/", ["/tempZone/existing_file.txt", "/tempZone/existing_collection", "/tempZone/home", "/tempZone/trash", ]], ["", ["/tempZone/existing_file.txt", "/tempZone/existing_collection", "/tempZone/home", "/tempZone/trash"]], @@ -128,3 +150,32 @@ def test_removedir(fs: iRODSFS, path: str): def test_listdir(fs: iRODSFS, path: str, expected: list[str]): actual = fs.listdir(path) assert actual == expected + +@pytest.mark.parametrize("path, expected", [ + ["home", False], + ["home/rods", True] +]) +def test_isempty(fs: iRODSFS, path: str, expected: bool): + assert fs.isempty(path) == expected + +@pytest.mark.parametrize("path", [ + "test/subdir" +]) +def test_makedirs(fs:iRODSFS, path: str): + fs.makedirs(path) + assert fs.isdir(path) + fs.removedir(path) + fs.removedir(os.path.dirname(path)) + assert fs.isdir(path) == False + assert fs.isdir(os.path.dirname(path)) == False + + +def test_removetree(fs: iRODSFS): + fs.makedirs("test/subdir") + fs.create("test/subdir/file.txt") + assert fs.isfile("test/subdir/file.txt") + + fs.removetree("test") + assert fs.exists("test/subdir/file.txt") == False + assert fs.exists("test/subdir") == False + assert fs.exists("test") == False diff --git a/tests/test_utils.py b/tests/test_utils.py index de7fa37..58a30f6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -16,5 +16,5 @@ ["w+b", True], ["a+b", True] ]) -def test_can_create(mode, expected): +def test_can_create(mode: str, expected: bool): assert can_create(mode) == expected \ No newline at end of file