Skip to content

Commit

Permalink
implemented further tests
Browse files Browse the repository at this point in the history
  • Loading branch information
hechth committed Nov 24, 2023
1 parent a676516 commit 869bf70
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 40 deletions.
105 changes: 78 additions & 27 deletions fs_irods/iRODSFS.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))


Expand All @@ -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}}
Expand All @@ -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]
Expand Down Expand Up @@ -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):
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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(""))

26 changes: 22 additions & 4 deletions tests/irods_session_testing.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand All @@ -129,16 +139,24 @@
"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----> <a href='vscode-notebook-cell://ssh-remote%2Babiff.ics.muni.cz/home/hechth/git/hechth/fs-irods/tests/irods_session_testing.ipynb#W6sdnNjb2RlLXJlbW90ZQ%3D%3D?line=0'>1</a>\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 <a href='vscode-notebook-cell://ssh-remote%2Babiff.ics.muni.cz/home/hechth/git/hechth/fs-irods/tests/irods_session_testing.ipynb#W6sdnNjb2RlLXJlbW90ZQ%3D%3D?line=1'>2</a>\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----> <a href='vscode-notebook-cell://ssh-remote%2Babiff.ics.muni.cz/home/hechth/git/hechth/fs-irods/tests/irods_session_testing.ipynb#X15sdnNjb2RlLXJlbW90ZQ%3D%3D?line=0'>1</a>\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"
]
}
],
"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\")"
]
},
{
Expand Down
5 changes: 0 additions & 5 deletions tests/test_fs_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
57 changes: 54 additions & 3 deletions tests/test_iRODSFS.py
Original file line number Diff line number Diff line change
@@ -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")
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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"]],
Expand All @@ -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
2 changes: 1 addition & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 869bf70

Please sign in to comment.