Skip to content

Commit 5428b49

Browse files
committed
fixup! [test] Add unit tests
1 parent a34b389 commit 5428b49

File tree

3 files changed

+199
-12
lines changed

3 files changed

+199
-12
lines changed

examples/memory.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class Memory(mfusepy.LoggingMixIn, mfusepy.Operations):
1616

1717
def __init__(self):
1818
self.data = collections.defaultdict(bytes)
19-
self.fd = 0
2019
now = time.time()
2120
self.files: Dict[str, Dict[str, Any]] = {
2221
'/': {
@@ -46,9 +45,7 @@ def create(self, path, mode, flags=None):
4645
'st_mtime': time.time(),
4746
'st_atime': time.time(),
4847
}
49-
50-
self.fd += 1
51-
return self.fd
48+
return 0
5249

5350
def getattr(self, path, fh=None):
5451
if path not in self.files:
@@ -79,10 +76,6 @@ def mkdir(self, path, mode):
7976

8077
self.files['/']['st_nlink'] += 1
8178

82-
def open(self, path, flags):
83-
self.fd += 1
84-
return self.fd
85-
8679
def read(self, path, size, offset, fh):
8780
return self.data[path][offset : offset + size]
8881

examples/memory_nullpath.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import collections
5+
import errno
6+
import logging
7+
import stat
8+
import time
9+
from typing import Dict
10+
11+
import mfusepy
12+
13+
14+
class Memory(mfusepy.Operations):
15+
'Example memory filesystem. Supports only one level of files.'
16+
17+
flag_nullpath_ok = True
18+
flag_nopath = True
19+
20+
def __init__(self):
21+
self.files = {}
22+
self.data = collections.defaultdict(bytes)
23+
self.fd = 0
24+
now = time.time()
25+
self.files['/'] = {
26+
'st_mode': (stat.S_IFDIR | 0o755),
27+
'st_ctime': now,
28+
'st_mtime': now,
29+
'st_atime': now,
30+
'st_nlink': 2,
31+
}
32+
self._opened: Dict[int, str] = {}
33+
34+
def init_with_config(self, conn_info, config_3) -> None:
35+
# This only works for FUSE 3 while the flag_nullpath_ok and flag_nopath class members work for FUSE 2 and 3!
36+
if config_3:
37+
config_3.nullpath_ok = True
38+
39+
def chmod(self, path, mode):
40+
self.files[path]['st_mode'] &= 0o770000
41+
self.files[path]['st_mode'] |= mode
42+
return 0
43+
44+
def chown(self, path, uid, gid):
45+
self.files[path]['st_uid'] = uid
46+
self.files[path]['st_gid'] = gid
47+
48+
def create(self, path, mode):
49+
print("CREATE")
50+
self.files[path] = {
51+
'st_mode': (stat.S_IFREG | mode),
52+
'st_nlink': 1,
53+
'st_size': 0,
54+
'st_ctime': time.time(),
55+
'st_mtime': time.time(),
56+
'st_atime': time.time(),
57+
}
58+
59+
self.fd += 1
60+
self._opened[self.fd] = path
61+
return self.fd
62+
63+
def getattr(self, path, fh=None):
64+
if path not in self.files:
65+
raise mfusepy.FuseOSError(errno.ENOENT)
66+
return self.files[path]
67+
68+
def getxattr(self, path, name, position=0):
69+
attrs = self.files[path].get('attrs', {})
70+
71+
try:
72+
return attrs[name]
73+
except KeyError:
74+
return b''
75+
76+
def listxattr(self, path):
77+
attrs = self.files[path].get('attrs', {})
78+
return attrs.keys()
79+
80+
def mkdir(self, path, mode):
81+
self.files[path] = {
82+
'st_mode': (stat.S_IFDIR | mode),
83+
'st_nlink': 2,
84+
'st_size': 0,
85+
'st_ctime': time.time(),
86+
'st_mtime': time.time(),
87+
'st_atime': time.time(),
88+
}
89+
90+
self.files['/']['st_nlink'] += 1
91+
92+
def open(self, path, flags):
93+
self.fd += 1
94+
self._opened[self.fd] = path
95+
return self.fd
96+
97+
def read(self, path, size, offset, fh):
98+
return self.data[self._opened[fh]][offset : offset + size]
99+
100+
def release(self, path, fh):
101+
del self._opened[fh]
102+
103+
def opendir(self, path):
104+
self.fd += 1
105+
self._opened[self.fd] = path
106+
return self.fd
107+
108+
def readdir(self, path, fh):
109+
print("PATH", path)
110+
path = self._opened[fh]
111+
return ['.', '..'] + [x[1:] for x in self.files if x.startswith(path) and len(x) > len(path)]
112+
113+
def releasedir(self, path, fh):
114+
del self._opened[fh]
115+
116+
def readlink(self, path):
117+
return self.data[path]
118+
119+
def removexattr(self, path, name):
120+
attrs = self.files[path].get('attrs', {})
121+
122+
try:
123+
del attrs[name]
124+
except KeyError:
125+
pass
126+
127+
def rename(self, old, new):
128+
if old in self.data: # Directories have no data.
129+
self.data[new] = self.data.pop(old)
130+
if old not in self.files:
131+
raise mfusepy.FuseOSError(errno.ENOENT)
132+
self.files[new] = self.files.pop(old)
133+
134+
def rmdir(self, path):
135+
# with multiple level support, need to raise ENOTEMPTY if contains any files
136+
self.files.pop(path)
137+
self.files['/']['st_nlink'] -= 1
138+
139+
def setxattr(self, path, name, value, options, position=0):
140+
# Ignore options
141+
attrs = self.files[path].setdefault('attrs', {})
142+
attrs[name] = value
143+
144+
def statfs(self, path):
145+
return {'f_bsize': 512, 'f_blocks': 4096, 'f_bavail': 2048}
146+
147+
def symlink(self, target, source):
148+
self.files[target] = {'st_mode': (stat.S_IFLNK | 0o777), 'st_nlink': 1, 'st_size': len(source)}
149+
150+
self.data[target] = source
151+
152+
def truncate(self, path, length, fh=None):
153+
# make sure extending the file fills in zero bytes
154+
self.data[path] = self.data[path][:length].ljust(length, '\x00'.encode('ascii'))
155+
self.files[path]['st_size'] = length
156+
157+
def unlink(self, path):
158+
self.data.pop(path)
159+
self.files.pop(path)
160+
161+
def utimens(self, path, times=None):
162+
now = time.time()
163+
atime, mtime = times if times else (now, now)
164+
self.files[path]['st_atime'] = atime
165+
self.files[path]['st_mtime'] = mtime
166+
167+
def write(self, path, data, offset, fh):
168+
print("[write]", path, data, offset, fh)
169+
path = self._opened[fh]
170+
self.data[path] = (
171+
# make sure the data gets inserted at the right offset
172+
self.data[path][:offset].ljust(offset, '\x00'.encode('ascii'))
173+
+ data
174+
# and only overwrites the bytes that data is replacing
175+
+ self.data[path][offset + len(data) :]
176+
)
177+
self.files[path]['st_size'] = len(self.data[path])
178+
return len(data)
179+
180+
181+
def cli(args=None):
182+
parser = argparse.ArgumentParser()
183+
parser.add_argument('mount')
184+
args = parser.parse_args(args)
185+
186+
mfusepy.FUSE(Memory(), args.mount, foreground=True)
187+
188+
189+
if __name__ == '__main__':
190+
cli()

tests/test_memory.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
import threading
99
import time
1010

11+
import pytest
12+
1113
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
1214
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../examples')))
1315

14-
from memory import cli # noqa: E402
16+
from memory import cli as cli_memory # noqa: E402
17+
from memory import cli as cli_memory_nullpath # noqa: E402
1518

1619

1720
class RunCLI:
18-
def __init__(self, mount_point):
21+
def __init__(self, cli, mount_point):
1922
self.timeout = 4
2023
self.mount_point = str(mount_point)
2124
self.args = [self.mount_point]
@@ -108,9 +111,10 @@ def unmount(self):
108111
time.sleep(0.1)
109112

110113

111-
def test_memory_file_system(tmpdir):
114+
@pytest.mark.parametrize('cli', [cli_memory, cli_memory_nullpath])
115+
def test_memory_file_system(cli, tmpdir):
112116
mount_point = tmpdir
113-
with RunCLI(mount_point):
117+
with RunCLI(cli, mount_point):
114118
assert os.path.isdir(mount_point)
115119
assert not os.path.isdir(mount_point / "foo")
116120

0 commit comments

Comments
 (0)