Skip to content

Commit 2743c93

Browse files
committed
[test] Add unit tests
1 parent c74f0cb commit 2743c93

File tree

5 files changed

+216
-3
lines changed

5 files changed

+216
-3
lines changed

.github/workflows/tests.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ jobs:
102102
python3 -m pip install --upgrade pip
103103
python3 -m pip install --upgrade wheel
104104
python3 -m pip install --upgrade setuptools
105-
python3 -m pip install --upgrade-strategy eager --upgrade twine build
105+
python3 -m pip install --upgrade-strategy eager --upgrade twine build pytest
106106
107107
- name: Test Installation From Tarball
108108
run: |
@@ -117,3 +117,8 @@ jobs:
117117
- name: Test Import
118118
run: |
119119
python3 -c 'import mfusepy'
120+
121+
- name: Unit Tests
122+
if: startsWith( matrix.os, 'ubuntu' )
123+
run: |
124+
python3 -m pytest tests

examples/memory.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def read(self, path, size, offset, fh):
8686
return self.data[path][offset : offset + size]
8787

8888
def readdir(self, path, fh):
89-
return ['.', '..'] + [x[1:] for x in self.files if x != '/']
89+
return ['.', '..'] + [x[1:] for x in self.files if x.startswith(path) and len(x) > len(path)]
9090

9191
def readlink(self, path):
9292
return self.data[path]
@@ -100,7 +100,10 @@ def removexattr(self, path, name):
100100
pass
101101

102102
def rename(self, old, new):
103-
self.data[new] = self.data.pop(old)
103+
if old in self.data: # Directories have no data.
104+
self.data[new] = self.data.pop(old)
105+
if old not in self.files:
106+
raise mfusepy.FuseOSError(errno.ENOENT)
104107
self.files[new] = self.files.pop(old)
105108

106109
def rmdir(self, path):

tests/conftest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env python3
2+
3+
assertion_count = 0
4+
5+
6+
def pytest_assertion_pass(item, lineno, orig, expl):
7+
global assertion_count
8+
assertion_count += 1
9+
10+
11+
def pytest_terminal_summary(terminalreporter, exitstatus, config):
12+
print(f'{assertion_count} assertions tested.')

tests/pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
enable_assertion_pass_hook=true

tests/test_memory.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# pylint: disable=wrong-import-position
2+
# pylint: disable=protected-access
3+
4+
import contextlib
5+
import hashlib
6+
import io
7+
import os
8+
import shutil
9+
import subprocess
10+
import sys
11+
import tempfile
12+
import threading
13+
import time
14+
from pathlib import Path
15+
16+
import pytest
17+
18+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
19+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../examples')))
20+
21+
import mfusepy
22+
from memory import cli
23+
24+
25+
class RunCLI:
26+
def __init__(self, mount_point):
27+
self.timeout = 4
28+
self.mount_point = str(mount_point)
29+
self.args = [self.mount_point]
30+
self.thread = threading.Thread(target=cli, args=(self.args,))
31+
32+
self._stdout = None
33+
self._stderr = None
34+
35+
def __enter__(self):
36+
self._stdout = sys.stdout
37+
self._stderr = sys.stderr
38+
sys.stdout = io.StringIO()
39+
sys.stderr = io.StringIO()
40+
41+
self.thread.start()
42+
self.wait_for_mount_point()
43+
44+
return self
45+
46+
def __exit__(self, exception_type, exception_value, exception_traceback):
47+
try:
48+
stdout = sys.stdout
49+
stderr = sys.stderr
50+
sys.stdout = self._stdout
51+
sys.stderr = self._stderr
52+
stdout.seek(0)
53+
stderr.seek(0)
54+
output = stdout.read()
55+
errors = stderr.read()
56+
57+
problematicWords = ['[Warning]', '[Error]']
58+
if any(word in output or word in errors for word in problematicWords):
59+
print("===== stdout =====\n", output)
60+
print("===== stderr =====\n", errors)
61+
raise AssertionError("There were warnings or errors!")
62+
63+
finally:
64+
self.unmount()
65+
self.thread.join(self.timeout)
66+
67+
def get_stdout(self):
68+
oldPosition = sys.stdout.tell()
69+
try:
70+
sys.stdout.seek(0)
71+
return sys.stdout.read()
72+
finally:
73+
sys.stdout.seek(oldPosition)
74+
75+
def get_stderr(self):
76+
oldPosition = sys.stderr.tell()
77+
try:
78+
sys.stderr.seek(0)
79+
return sys.stderr.read()
80+
finally:
81+
sys.stderr.seek(oldPosition)
82+
83+
def wait_for_mount_point(self):
84+
t0 = time.time()
85+
while True:
86+
if os.path.ismount(self.mount_point):
87+
break
88+
if time.time() - t0 > self.timeout:
89+
mount_list = "<Unable to run mount command>"
90+
try:
91+
mount_list = subprocess.run("mount", capture_output=True, check=True).stdout.decode()
92+
except Exception as exception:
93+
mount_list += f"\n{exception}"
94+
raise RuntimeError(
95+
"Expected mount point but it isn't one!"
96+
"\n===== stderr =====\n"
97+
+ self.get_stderr()
98+
+ "\n===== stdout =====\n"
99+
+ self.get_stdout()
100+
+ "\n===== mount =====\n"
101+
+ mount_list
102+
)
103+
time.sleep(0.1)
104+
105+
def unmount(self):
106+
self.wait_for_mount_point()
107+
108+
subprocess.run(["fusermount", "-u", self.mount_point], check=True, capture_output=True)
109+
110+
t0 = time.time()
111+
while True:
112+
if not os.path.ismount(self.mount_point):
113+
break
114+
if time.time() - t0 > self.timeout:
115+
raise RuntimeError("Unmounting did not finish in time!")
116+
time.sleep(0.1)
117+
118+
119+
def test_memory_file_system(tmpdir):
120+
mount_point = tmpdir
121+
with RunCLI(mount_point):
122+
assert os.path.isdir(mount_point)
123+
assert not os.path.isdir(mount_point / "foo")
124+
125+
path = mount_point / "foo"
126+
with open(path, 'wb') as file:
127+
assert file.write(b"bar") == 3
128+
129+
assert os.path.exists(path)
130+
assert os.path.isfile(path)
131+
assert not os.path.isdir(path)
132+
133+
with open(path, 'rb') as file:
134+
assert file.read() == b"bar"
135+
136+
os.truncate(path, 2)
137+
with open(path, 'rb') as file:
138+
assert file.read() == b"ba"
139+
140+
os.chmod(path, 0)
141+
assert os.stat(path).st_mode & 0o777 == 0
142+
os.chmod(path, 0o777)
143+
assert os.stat(path).st_mode & 0o777 == 0o777
144+
145+
os.chown(path, 12345, 23456)
146+
assert os.stat(path).st_uid == 12345
147+
assert os.stat(path).st_gid == 23456
148+
149+
assert not os.listxattr(path)
150+
os.setxattr(path, b"user.tag-test", b"FOO-RESULT")
151+
assert os.listxattr(path)
152+
assert os.getxattr(path, b"user.tag-test") == b"FOO-RESULT"
153+
os.removexattr(path, b"user.tag-test")
154+
assert not os.listxattr(path)
155+
156+
os.utime(path, (1.5, 12.5))
157+
assert os.stat(path).st_atime == 1.5
158+
assert os.stat(path).st_mtime == 12.5
159+
160+
os.utime(path, ns=(int(1.5e9), int(12.5e9)))
161+
assert os.stat(path).st_atime == 1.5
162+
assert os.stat(path).st_mtime == 12.5
163+
164+
assert os.listdir(mount_point) == ["foo"]
165+
os.unlink(path)
166+
assert not os.path.exists(path)
167+
168+
os.mkdir(path)
169+
assert os.path.exists(path)
170+
assert not os.path.isfile(path)
171+
assert os.path.isdir(path)
172+
173+
assert os.listdir(mount_point) == ["foo"]
174+
assert os.listdir(path) == []
175+
176+
os.rename(mount_point / "foo", mount_point / "bar")
177+
assert not os.path.exists(mount_point / "foo")
178+
assert os.path.exists(mount_point / "bar")
179+
180+
os.symlink(mount_point / "bar", path)
181+
assert os.path.exists(path)
182+
# assert os.path.isfile(path) # Does not have a follow_symlink argument but it seems to be True, see below.
183+
assert os.path.isdir(path)
184+
assert os.path.islink(path)
185+
assert os.readlink(path) == mount_point / "bar"
186+
187+
os.rmdir(mount_point / "bar")
188+
assert not os.path.exists(mount_point / "bar")
189+
190+
assert os.statvfs(mount_point).f_bsize == 512
191+
assert os.statvfs(mount_point).f_bavail == 2048

0 commit comments

Comments
 (0)