Skip to content

Commit 2fa8bc9

Browse files
committed
[test] Test with loopback filesystem
1 parent f30c9f2 commit 2fa8bc9

File tree

7 files changed

+79
-68
lines changed

7 files changed

+79
-68
lines changed

examples/ioctl.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
from ioctl_opt import IOWR
1313

14-
import mfusepy
14+
import mfusepy as fuse
1515

1616

17-
class Ioctl(mfusepy.LoggingMixIn, mfusepy.Operations):
17+
class Ioctl(fuse.Operations):
1818
'''
1919
Example filesystem based on memory.py to demonstrate ioctl().
2020
@@ -57,7 +57,7 @@ def create(self, path, mode, fi=None):
5757

5858
def getattr(self, path, fh=None):
5959
if path not in self.files:
60-
raise mfusepy.FuseOSError(errno.ENOENT)
60+
raise fuse.FuseOSError(errno.ENOENT)
6161

6262
return self.files[path]
6363

@@ -71,7 +71,7 @@ def ioctl(self, path, cmd, arg, fh, flags, data):
7171
outbuf = struct.pack('<I', data_out)
7272
ctypes.memmove(data, outbuf, 4)
7373
else:
74-
raise mfusepy.FuseOSError(errno.ENOTTY)
74+
raise fuse.FuseOSError(errno.ENOTTY)
7575
return 0
7676

7777
def open(self, path, flags):
@@ -91,7 +91,7 @@ def cli(args=None):
9191
args = parser.parse_args(args)
9292

9393
logging.basicConfig(level=logging.DEBUG)
94-
mfusepy.FUSE(Ioctl(), args.mount, foreground=True)
94+
fuse.FUSE(Ioctl(), args.mount, foreground=True)
9595

9696

9797
if __name__ == '__main__':

examples/loopback.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
import threading
88
from os.path import realpath
99

10-
import mfusepy
10+
import mfusepy as fuse
1111

1212

13-
class Loopback(mfusepy.LoggingMixIn, mfusepy.Operations):
13+
class Loopback(fuse.LoggingMixIn, fuse.Operations):
1414
def __init__(self, root):
1515
self.root = realpath(root)
1616
self.rwlock = threading.Lock()
@@ -20,7 +20,7 @@ def __call__(self, op, path, *args):
2020

2121
def access(self, path, amode):
2222
if not os.access(path, amode):
23-
raise mfusepy.FuseOSError(errno.EACCES)
23+
raise fuse.FuseOSError(errno.EACCES)
2424

2525
chmod = os.chmod
2626
chown = os.chown
@@ -112,7 +112,7 @@ def cli(args=None):
112112
args = parser.parse_args(args)
113113

114114
logging.basicConfig(level=logging.DEBUG)
115-
mfusepy.FUSE(Loopback(args.root), args.mount, foreground=True)
115+
fuse.FUSE(Loopback(args.root), args.mount, foreground=True)
116116

117117

118118
if __name__ == '__main__':

examples/memory.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
import time
99
from typing import Any, Dict
1010

11-
import mfusepy
11+
import mfusepy as fuse
1212

1313

14-
class Memory(mfusepy.LoggingMixIn, mfusepy.Operations):
14+
# LoggingMixIn should not be used anymore! This is only here for downwards compatibility tests.
15+
class Memory(fuse.LoggingMixIn, fuse.Operations):
1516
'Example memory filesystem. Supports only one level of files.'
1617

1718
def __init__(self):
@@ -49,7 +50,7 @@ def create(self, path, mode, fi=None):
4950

5051
def getattr(self, path, fh=None):
5152
if path not in self.files:
52-
raise mfusepy.FuseOSError(errno.ENOENT)
53+
raise fuse.FuseOSError(errno.ENOENT)
5354
return self.files[path]
5455

5556
def getxattr(self, path, name, position=0):
@@ -97,7 +98,7 @@ def rename(self, old, new):
9798
if old in self.data: # Directories have no data.
9899
self.data[new] = self.data.pop(old)
99100
if old not in self.files:
100-
raise mfusepy.FuseOSError(errno.ENOENT)
101+
raise fuse.FuseOSError(errno.ENOENT)
101102
self.files[new] = self.files.pop(old)
102103

103104
def rmdir(self, path):
@@ -151,7 +152,7 @@ def cli(args=None):
151152
args = parser.parse_args(args)
152153

153154
logging.basicConfig(level=logging.DEBUG)
154-
mfusepy.FUSE(Memory(), args.mount, foreground=True)
155+
fuse.FUSE(Memory(), args.mount, foreground=True)
155156

156157

157158
if __name__ == '__main__':

examples/memory_nullpath.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
import time
88
from typing import Any, Dict
99

10-
import mfusepy
10+
import mfusepy as fuse
1111

1212

13-
class Memory(mfusepy.Operations):
13+
class Memory(fuse.Operations):
1414
'Example memory filesystem. Supports only one level of files.'
1515

1616
flag_nullpath_ok = True
@@ -64,7 +64,7 @@ def getattr(self, path, fh=None):
6464
if fh is not None and fh in self._opened:
6565
path = self._opened[fh]
6666
if path not in self.files:
67-
raise mfusepy.FuseOSError(errno.ENOENT)
67+
raise fuse.FuseOSError(errno.ENOENT)
6868
return self.files[path]
6969

7070
def getxattr(self, path, name, position=0):
@@ -129,7 +129,7 @@ def rename(self, old, new):
129129
if old in self.data: # Directories have no data.
130130
self.data[new] = self.data.pop(old)
131131
if old not in self.files:
132-
raise mfusepy.FuseOSError(errno.ENOENT)
132+
raise fuse.FuseOSError(errno.ENOENT)
133133
self.files[new] = self.files.pop(old)
134134

135135
def rmdir(self, path):
@@ -185,7 +185,7 @@ def cli(args=None):
185185
parser.add_argument('mount')
186186
args = parser.parse_args(args)
187187

188-
mfusepy.FUSE(Memory(), args.mount, foreground=True)
188+
fuse.FUSE(Memory(), args.mount, foreground=True)
189189

190190

191191
if __name__ == '__main__':

tests/.pylintrc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@ max-line-length=120
1616
disable=broad-except,
1717
invalid-name,
1818
too-many-arguments,
19+
too-many-branches,
1920
too-many-instance-attributes,
2021
too-many-locals,
2122
too-many-lines,
2223
too-many-positional-arguments,
2324
too-many-public-methods,
25+
too-many-statements,
2426
too-few-public-methods,
2527
# I don't need the style checker to bother me with missing docstrings and todos.
2628
missing-class-docstring,
2729
missing-function-docstring,
2830
missing-module-docstring,
2931
fixme,
3032
unused-argument,
31-
R0801
33+
comparison-with-callable,
34+
R0801,
35+
global-statement,
36+
chained-comparison, # Only Since Python 3.8

tests/.ruff.toml

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,22 @@ select = [
99
ignore = [
1010
# Preview. Complaining about spaces (aligned arguments) should be a formatter option, not a linter one!
1111
# https://github.com/astral-sh/ruff/issues/2402
12-
"E201", "E202", "E203", "E211", "E221", "E226", "E251", "E265", "E266", "E271",
12+
"E201", "E202", "E203", "E211", "E221", "E226", "E251", "E265", "E266",
1313
"E501", # A linter should lint, not check for line lengths!
1414
"F401", # Wants to from .version import __version__ as __version__ which clashes with pylint errors!
1515
"B904",
1616
"N801", # Some class names are snake_case to match ctypes
17-
"N803", # Argument names are camelCase instead of snake_case (400+ errors).
1817
"N806", # Variable names are camelCase instead of snake_case (1100+ errors).
19-
"N815", # Class-scope variable names are camelCase instead of snake_case (10 errors).
20-
"N816", # Global variable names are camelCase instead of snake_case (73 errors).
21-
"N999", # Module names are CamelCase like the classes they provide instead of snake_case (11 errors).
2218
"COM812", # Do not force trailing commas where it makes no sense, e.g., function calls for which I'll
2319
# never add more arguments.
24-
"PERF203", # Some code parts HAVE to try-except inside loops because only the element should be skipped,
25-
# not the whole loop being broken out of. Furthermore, this is a useless micro-optimization,
26-
# which is actually removed for Python 3.11+ which introduces zero-cost exceptions.
2720
"PLW0603", # Cannot live without global statements, especially for subprocessing.
28-
"PLW2901", # Only false positives for constructs such as for data in ...: data = data.clean(); process(data)
21+
"PT017",
2922
"RET504", # https://github.com/astral-sh/ruff/issues/17292#issuecomment-3039232890
30-
"RUF001", # Only false positives from tests with Unicode characters.
31-
"RUF039", # https://github.com/astral-sh/ruff/issues/18795
32-
"RUF056", # "options.get('disableUnionMount', False):" Wants me to change False to None,
33-
# but it makes no sense semantically (expecting a bool)
3423
"RUF100", # BUG: removes necessary noqa: E402 in tests!
3524
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`, but _fields_ is not mutable.
3625

37-
"SIM102",
26+
"SIM102", # Sometimes, nested if-statements are more readable.
3827
"SIM105",
39-
"SIM115", # Too many locations where I do not know how to use context managers for files,
40-
# which are stored as object members for example!
41-
"TC006", # Wants to quote cast("IO[bytes]", ...) I don't agree with this style choice.
42-
"S101", "S110", "S105", "S311", "S324", "S603", "S607", "S608"
4328

4429
# Bug: SIM118 removes the keys() from row.keys(), which is an sqlite3.Row not a Dict!
4530
]

tests/test_memory.py

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# pylint: disable=wrong-import-position
22
# pylint: disable=protected-access
33

4+
import errno
45
import io
56
import os
67
import subprocess
@@ -13,15 +14,16 @@
1314
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
1415
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../examples')))
1516

17+
from loopback import cli as cli_loopback # noqa: E402
1618
from memory import cli as cli_memory # noqa: E402
1719
from memory_nullpath import cli as cli_memory_nullpath # noqa: E402
1820

1921

2022
class RunCLI:
21-
def __init__(self, cli, mount_point):
23+
def __init__(self, cli, mount_point, arguments):
2224
self.timeout = 4
2325
self.mount_point = str(mount_point)
24-
self.args = [self.mount_point]
26+
self.args = [*arguments, self.mount_point]
2527
self.thread = threading.Thread(target=cli, args=(self.args,))
2628

2729
self._stdout = None
@@ -49,8 +51,8 @@ def __exit__(self, exception_type, exception_value, exception_traceback):
4951
output = stdout.read()
5052
errors = stderr.read()
5153

52-
problematicWords = ['[Warning]', '[Error]']
53-
if any(word in output or word in errors for word in problematicWords):
54+
problematic_words = ['[Warning]', '[Error]']
55+
if any(word in output or word in errors for word in problematic_words):
5456
print("===== stdout =====\n", output)
5557
print("===== stderr =====\n", errors)
5658
raise AssertionError("There were warnings or errors!")
@@ -60,20 +62,20 @@ def __exit__(self, exception_type, exception_value, exception_traceback):
6062
self.thread.join(self.timeout)
6163

6264
def get_stdout(self):
63-
oldPosition = sys.stdout.tell()
65+
old_position = sys.stdout.tell()
6466
try:
6567
sys.stdout.seek(0)
6668
return sys.stdout.read()
6769
finally:
68-
sys.stdout.seek(oldPosition)
70+
sys.stdout.seek(old_position)
6971

7072
def get_stderr(self):
71-
oldPosition = sys.stderr.tell()
73+
old_position = sys.stderr.tell()
7274
try:
7375
sys.stderr.seek(0)
7476
return sys.stderr.read()
7577
finally:
76-
sys.stderr.seek(oldPosition)
78+
sys.stderr.seek(old_position)
7779

7880
def wait_for_mount_point(self):
7981
t0 = time.time()
@@ -111,43 +113,60 @@ def unmount(self):
111113
time.sleep(0.1)
112114

113115

114-
@pytest.mark.parametrize('cli', [cli_memory, cli_memory_nullpath])
115-
def test_memory_file_system(cli, tmpdir):
116-
mount_point = tmpdir
117-
with RunCLI(cli, mount_point):
116+
@pytest.mark.parametrize('cli', [cli_loopback, cli_memory, cli_memory_nullpath])
117+
def test_read_write_file_system(cli, tmp_path):
118+
if cli == cli_loopback:
119+
mount_source = tmp_path / "folder"
120+
mount_point = tmp_path / "mounted"
121+
mount_source.mkdir()
122+
mount_point.mkdir()
123+
arguments = [str(mount_source)]
124+
else:
125+
mount_point = tmp_path
126+
arguments = []
127+
with RunCLI(cli, mount_point, arguments):
118128
assert os.path.isdir(mount_point)
119129
assert not os.path.isdir(mount_point / "foo")
120130

121131
path = mount_point / "foo"
122-
with open(path, 'wb') as file:
123-
assert file.write(b"bar") == 3
132+
assert path.write_bytes(b"bar") == 3
124133

125134
assert os.path.exists(path)
126135
assert os.path.isfile(path)
127136
assert not os.path.isdir(path)
128137

129-
with open(path, 'rb') as file:
130-
assert file.read() == b"bar"
138+
assert path.read_bytes() == b"bar"
131139

132140
os.truncate(path, 2)
133-
with open(path, 'rb') as file:
134-
assert file.read() == b"ba"
141+
assert path.read_bytes() == b"ba"
135142

136143
os.chmod(path, 0)
137144
assert os.stat(path).st_mode & 0o777 == 0
138145
os.chmod(path, 0o777)
139146
assert os.stat(path).st_mode & 0o777 == 0o777
140147

141-
os.chown(path, 12345, 23456)
142-
assert os.stat(path).st_uid == 12345
143-
assert os.stat(path).st_gid == 23456
148+
try:
149+
# Only works for memory file systems.
150+
os.chown(path, 12345, 23456)
151+
assert os.stat(path).st_uid == 12345
152+
assert os.stat(path).st_gid == 23456
153+
except PermissionError:
154+
assert cli == cli_loopback
155+
156+
os.chown(path, os.getuid(), os.getgid())
157+
assert os.stat(path).st_uid == os.getuid()
158+
assert os.stat(path).st_gid == os.getgid()
144159

145-
assert not os.listxattr(path)
146-
os.setxattr(path, b"user.tag-test", b"FOO-RESULT")
147-
assert os.listxattr(path)
148-
assert os.getxattr(path, b"user.tag-test") == b"FOO-RESULT"
149-
os.removexattr(path, b"user.tag-test")
150-
assert not os.listxattr(path)
160+
try:
161+
assert not os.listxattr(path)
162+
os.setxattr(path, b"user.tag-test", b"FOO-RESULT")
163+
assert os.listxattr(path)
164+
assert os.getxattr(path, b"user.tag-test") == b"FOO-RESULT"
165+
os.removexattr(path, b"user.tag-test")
166+
assert not os.listxattr(path)
167+
except OSError as exception:
168+
assert cli == cli_loopback
169+
assert exception.errno == errno.ENOTSUP
151170

152171
os.utime(path, (1.5, 12.5))
153172
assert os.stat(path).st_atime == 1.5
@@ -178,10 +197,11 @@ def test_memory_file_system(cli, tmpdir):
178197
# assert os.path.isfile(path) # Does not have a follow_symlink argument but it seems to be True, see below.
179198
assert os.path.isdir(path)
180199
assert os.path.islink(path)
181-
assert os.readlink(path) == mount_point / "bar"
200+
assert os.readlink(path) == str(mount_point / "bar")
182201

183202
os.rmdir(mount_point / "bar")
184203
assert not os.path.exists(mount_point / "bar")
185204

186-
assert os.statvfs(mount_point).f_bsize == 512
187-
assert os.statvfs(mount_point).f_bavail == 2048
205+
if cli != cli_loopback:
206+
assert os.statvfs(mount_point).f_bsize == 512
207+
assert os.statvfs(mount_point).f_bavail == 2048

0 commit comments

Comments
 (0)