Skip to content

Commit cb398f9

Browse files
committed
[test] Simplify struct test
1 parent f6ab524 commit cb398f9

File tree

3 files changed

+121
-118
lines changed

3 files changed

+121
-118
lines changed

.github/workflows/tests.yml

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ jobs:
6666
runs-on: ${{ matrix.os }}
6767

6868
strategy:
69+
fail-fast: false
6970
matrix:
7071
# https://endoflife.date/python
7172
include:
@@ -174,8 +175,7 @@ jobs:
174175
python3 -m pip install ratarmount
175176
python3 -m pip install --force-reinstall .
176177
mkdir -p mounted-tar
177-
wget https://github.com/mxmlnkn/ratarmount/raw/refs/heads/master/tests/single-file.tar
178-
ratarmount single-file.tar mounted-tar
178+
ratarmount tests/single-file.tar mounted-tar
179179
[[ "$( cat mounted-tar/bar )" == "foo" ]]
180180
fusermount -u mounted-tar
181181
@@ -196,17 +196,18 @@ jobs:
196196
timeout-minutes: 10
197197

198198
strategy:
199-
matrix:
200-
include:
201-
- os: freebsd
202-
version: '14.3'
203-
display_name: FreeBSD
204-
- os: openbsd
205-
version: '7.7'
206-
display_name: OpenBSD
207-
- os: netbsd
208-
version: '10.1'
209-
display_name: NetBSD
199+
fail-fast: false
200+
matrix:
201+
include:
202+
- os: freebsd
203+
version: '14.3'
204+
display_name: FreeBSD
205+
- os: openbsd
206+
version: '7.7'
207+
display_name: OpenBSD
208+
- os: netbsd
209+
version: '10.1'
210+
display_name: NetBSD
210211

211212
steps:
212213
- name: Check out repository

tests/single-file.tar

10 KB
Binary file not shown.

tests/test_struct_layout.py

Lines changed: 107 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,89 @@
11
import ctypes
22
import os
3+
import pprint
34
import shutil
45
import subprocess
56
import tempfile
7+
from pathlib import Path
68

79
import pytest
810

911
import mfusepy
1012

11-
STRUCT_NAMES = ["stat", "statvfs", "fuse_file_info"]
13+
STRUCT_NAMES = {
14+
"stat": [
15+
'st_atimespec',
16+
'st_blksize',
17+
'st_blocks',
18+
'st_ctimespec',
19+
'st_dev',
20+
'st_gid',
21+
'st_ino',
22+
'st_mode',
23+
'st_mtimespec',
24+
'st_nlink',
25+
'st_rdev',
26+
'st_size',
27+
'st_uid',
28+
],
29+
"statvfs": [
30+
'f_bavail',
31+
'f_bfree',
32+
'f_blocks',
33+
'f_bsize',
34+
'f_favail',
35+
'f_ffree',
36+
'f_files',
37+
'f_flag',
38+
'f_frsize',
39+
'f_fsid',
40+
'f_namemax',
41+
],
42+
"fuse_file_info": ['flags', 'fh', 'lock_owner'],
43+
}
1244

1345
# we only check the struct members that are present on all supported platforms.
1446

15-
C_CHECKER = '''
47+
C_CHECKER = r'''
1648
#include <stdio.h>
1749
#include <stddef.h>
1850
#include <sys/stat.h>
1951
#include <sys/statvfs.h>
2052
#include <fuse.h>
2153
22-
int main() {
23-
// stat
24-
printf("stat_size:%zu\\n", sizeof(struct stat));
25-
printf("stat_st_atimespec_offset:%zu\\n", offsetof(struct stat, st_atime));
26-
printf("stat_st_blksize_offset:%zu\\n", offsetof(struct stat, st_blksize));
27-
printf("stat_st_blocks_offset:%zu\\n", offsetof(struct stat, st_blocks));
28-
printf("stat_st_ctimespec_offset:%zu\\n", offsetof(struct stat, st_ctime));
29-
printf("stat_st_dev_offset:%zu\\n", offsetof(struct stat, st_dev));
30-
printf("stat_st_gid_offset:%zu\\n", offsetof(struct stat, st_gid));
31-
printf("stat_st_ino_offset:%zu\\n", offsetof(struct stat, st_ino));
32-
printf("stat_st_mode_offset:%zu\\n", offsetof(struct stat, st_mode));
33-
printf("stat_st_mtimespec_offset:%zu\\n", offsetof(struct stat, st_mtime));
34-
printf("stat_st_nlink_offset:%zu\\n", offsetof(struct stat, st_nlink));
35-
printf("stat_st_rdev_offset:%zu\\n", offsetof(struct stat, st_rdev));
36-
printf("stat_st_size_offset:%zu\\n", offsetof(struct stat, st_size));
37-
printf("stat_st_uid_offset:%zu\\n", offsetof(struct stat, st_uid));
38-
// statvfs
39-
printf("statvfs_size:%zu\\n", sizeof(struct statvfs));
40-
printf("statvfs_f_bavail_offset:%zu\\n", offsetof(struct statvfs, f_bavail));
41-
printf("statvfs_f_bfree_offset:%zu\\n", offsetof(struct statvfs, f_bfree));
42-
printf("statvfs_f_blocks_offset:%zu\\n", offsetof(struct statvfs, f_blocks));
43-
printf("statvfs_f_bsize_offset:%zu\\n", offsetof(struct statvfs, f_bsize));
44-
printf("statvfs_f_favail_offset:%zu\\n", offsetof(struct statvfs, f_favail));
45-
printf("statvfs_f_ffree_offset:%zu\\n", offsetof(struct statvfs, f_ffree));
46-
printf("statvfs_f_files_offset:%zu\\n", offsetof(struct statvfs, f_files));
47-
printf("statvfs_f_flag_offset:%zu\\n", offsetof(struct statvfs, f_flag));
48-
printf("statvfs_f_frsize_offset:%zu\\n", offsetof(struct statvfs, f_frsize));
49-
printf("statvfs_f_fsid_offset:%zu\\n", offsetof(struct statvfs, f_fsid));
50-
printf("statvfs_f_namemax_offset:%zu\\n", offsetof(struct statvfs, f_namemax));
51-
// fuse_file_info - TODO: fix this later!
52-
printf("fuse_file_info_size:%zu\\n", sizeof(struct fuse_file_info));
53-
// printf("fuse_file_info_flags_offset:%zu\\n", offsetof(struct fuse_file_info, flags));
54-
// printf("fuse_file_info_fh_offset:%zu\\n", offsetof(struct fuse_file_info, fh));
55-
// printf("fuse_file_info_lock_owner_offset:%zu\\n", offsetof(struct fuse_file_info, lock_owner));
56-
return 0;
57-
}
54+
#define PRINT_STAT_MEMBER_OFFSET(NAME) \
55+
printf("stat.%s offset:%zu\n", #NAME, offsetof(struct stat, NAME));
56+
57+
#define PRINT_STATVFS_MEMBER_OFFSET(NAME) \
58+
printf("statvfs.%s offset:%zu\n", #NAME, offsetof(struct statvfs, NAME));
59+
60+
#define PRINT_FUSE_FILE_INFO_MEMBER_OFFSET(NAME) \
61+
printf("fuse_file_info.%s offset:%zu\n", #NAME, offsetof(struct fuse_file_info, NAME));
62+
63+
int main()
64+
{
5865
'''
5966

60-
py_infos = {
61-
# stat
62-
'stat_size': ctypes.sizeof(mfusepy.c_stat),
63-
'stat_st_atimespec_offset': mfusepy.c_stat.st_atimespec.offset,
64-
'stat_st_blksize_offset': mfusepy.c_stat.st_blksize.offset,
65-
'stat_st_blocks_offset': mfusepy.c_stat.st_blocks.offset,
66-
'stat_st_ctimespec_offset': mfusepy.c_stat.st_ctimespec.offset,
67-
'stat_st_dev_offset': mfusepy.c_stat.st_dev.offset,
68-
'stat_st_gid_offset': mfusepy.c_stat.st_gid.offset,
69-
'stat_st_ino_offset': mfusepy.c_stat.st_ino.offset,
70-
'stat_st_mode_offset': mfusepy.c_stat.st_mode.offset,
71-
'stat_st_mtimespec_offset': mfusepy.c_stat.st_mtimespec.offset,
72-
'stat_st_nlink_offset': mfusepy.c_stat.st_nlink.offset,
73-
'stat_st_rdev_offset': mfusepy.c_stat.st_rdev.offset,
74-
'stat_st_size_offset': mfusepy.c_stat.st_size.offset,
75-
'stat_st_uid_offset': mfusepy.c_stat.st_uid.offset,
76-
# statvfs
77-
'statvfs_size': ctypes.sizeof(mfusepy.c_statvfs),
78-
'statvfs_f_bavail_offset': mfusepy.c_statvfs.f_bavail.offset,
79-
'statvfs_f_bfree_offset': mfusepy.c_statvfs.f_bfree.offset,
80-
'statvfs_f_blocks_offset': mfusepy.c_statvfs.f_blocks.offset,
81-
'statvfs_f_bsize_offset': mfusepy.c_statvfs.f_bsize.offset,
82-
'statvfs_f_favail_offset': mfusepy.c_statvfs.f_favail.offset,
83-
'statvfs_f_ffree_offset': mfusepy.c_statvfs.f_ffree.offset,
84-
'statvfs_f_files_offset': mfusepy.c_statvfs.f_files.offset,
85-
'statvfs_f_flag_offset': mfusepy.c_statvfs.f_flag.offset,
86-
'statvfs_f_frsize_offset': mfusepy.c_statvfs.f_frsize.offset,
87-
'statvfs_f_fsid_offset': mfusepy.c_statvfs.f_fsid.offset,
88-
'statvfs_f_namemax_offset': mfusepy.c_statvfs.f_namemax.offset,
89-
# fuse_file_info - TODO: fix this later!
90-
'fuse_file_info_size': ctypes.sizeof(mfusepy.fuse_file_info),
91-
# 'fuse_file_info_flags_offset': mfusepy.fuse_file_info.flags.offset,
92-
# 'fuse_file_info_fh_offset': mfusepy.fuse_file_info.fh.offset,
93-
# 'fuse_file_info_lock_owner_offset': mfusepy.fuse_file_info.lock_owner.offset,
67+
PY_INFOS = {}
68+
for struct_name, member_names in STRUCT_NAMES.items():
69+
fusepy_struct = getattr(mfusepy, struct_name, getattr(mfusepy, 'c_' + struct_name, None))
70+
assert fusepy_struct is not None
71+
72+
PY_INFOS[struct_name + " size"] = ctypes.sizeof(fusepy_struct)
73+
C_CHECKER += f"""\n printf("{struct_name} size:%zu\\n", sizeof(struct {struct_name}));\n"""
74+
75+
for name in member_names:
76+
PY_INFOS[f'{struct_name}.{name} offset'] = getattr(fusepy_struct, name).offset
77+
# This naming discrepancy is not good but would be an API change, I think.
78+
c_name = name.replace('timespec', 'time') if name.endswith('timespec') else name
79+
C_CHECKER += f' printf("{struct_name}.{name} offset:%zu\\n", offsetof(struct {struct_name}, {c_name}));\n'
80+
81+
C_CHECKER += """
82+
return 0;
9483
}
84+
"""
9585

96-
# Common locations for different OSes
97-
INCLUDE_PATHS = [
98-
'/usr/local/include/fuse',
99-
'/usr/include/fuse',
100-
'/usr/local/include/fuse3',
101-
'/usr/include/fuse3',
102-
'/usr/local/include/osxfuse/fuse',
103-
'/usr/local/include/macfuse/fuse',
104-
'/usr/include/libfuse',
105-
]
86+
print(C_CHECKER)
10687

10788

10889
def get_compiler():
@@ -121,19 +102,29 @@ def c_run(name: str, source: str) -> str:
121102
with tempfile.TemporaryDirectory() as tmpdir:
122103
c_file = os.path.join(tmpdir, name + '.c')
123104
exe_file = os.path.join(tmpdir, name)
105+
preprocessed_file = os.path.join(tmpdir, name + '.preprocessed.c')
124106

125107
with open(c_file, 'w', encoding='utf-8') as f:
126108
f.write(source)
127109

128-
# Determine FUSE version and compile flags
129-
fuse_major = mfusepy.fuse_version_major
130-
fuse_minor = mfusepy.fuse_version_minor
131-
print(f"FUSE version: {fuse_major}.{fuse_minor}")
110+
print(f"FUSE version: {mfusepy.fuse_version_major}.{mfusepy.fuse_version_minor}")
132111

133-
cflags = [f'-DFUSE_USE_VERSION={fuse_major}{fuse_minor}', '-D_FILE_OFFSET_BITS=64']
112+
# Common include locations for different OSes
113+
include_paths = [
114+
'/usr/local/include/osxfuse/fuse',
115+
'/usr/local/include/macfuse/fuse',
116+
'/usr/include/libfuse',
117+
]
118+
if mfusepy.fuse_version_major == 3:
119+
include_paths += ['/usr/local/include/fuse3', '/usr/include/fuse3']
120+
else:
121+
include_paths += ['/usr/local/include/fuse', '/usr/include/fuse']
134122

135-
# Try to find fuse headers
136-
cflags += [f'-I{path}' for path in INCLUDE_PATHS if os.path.exists(path)]
123+
cflags = [
124+
f'-DFUSE_USE_VERSION={mfusepy.fuse_version_major}{mfusepy.fuse_version_minor}',
125+
'-D_FILE_OFFSET_BITS=64',
126+
]
127+
cflags += [f'-I{path}' for path in include_paths if os.path.exists(path)]
137128

138129
# Add possible pkg-config flags if available
139130
for fuse_lib in ("fuse", "fuse3"):
@@ -154,35 +145,46 @@ def c_run(name: str, source: str) -> str:
154145
print(f"Compiler stderr:\n{e.stderr}")
155146
assert e.returncode == 0, "Could not compile C program to verify sizes."
156147

148+
cmd = [get_compiler(), '-E', *cflags, c_file, '-o', preprocessed_file]
149+
print(f"Preprocessing with: {' '.join(cmd)}")
150+
try:
151+
subprocess.run(cmd, capture_output=True, text=True, check=True)
152+
except subprocess.CalledProcessError as e:
153+
print(f"Compiler return code: {e.returncode}")
154+
print(f"Compiler stdout:\n{e.stdout}")
155+
print(f"Compiler stderr:\n{e.stderr}")
156+
assert e.returncode == 0, "Could not compile C program to verify sizes."
157+
158+
for line in Path(preprocessed_file).read_text().split('\n'):
159+
if not line.startswith('#') and line:
160+
print(line)
161+
print(preprocessed_file)
162+
157163
output = subprocess.check_output([exe_file], text=True)
158164
return output
159165

160166

161167
@pytest.mark.skipif(os.name == 'nt', reason="C compiler check not implemented for Windows")
162168
def test_struct_layout():
163169
output = c_run("verify_structs", C_CHECKER)
164-
c_infos = {}
165-
for line in output.strip().split('\n'):
166-
name, value = line.split(':')
167-
c_infos[name] = int(value)
170+
c_infos = {line.split(':', 1)[0]: int(line.split(':', 1)[1]) for line in output.strip().split('\n')}
171+
pprint.pprint(c_infos)
168172

169173
fail = False
170-
for struct_name in STRUCT_NAMES:
171-
key = f"{struct_name}_size"
172-
if c_infos[key] == py_infos[key]:
174+
for struct_name, member_names in STRUCT_NAMES.items():
175+
key = f"{struct_name} size"
176+
if c_infos[key] == PY_INFOS[key]:
173177
print(f"OK: {key} = {c_infos[key]}")
174178
else:
175-
print(f"Mismatch for {key}: C={c_infos[key]}, Python={py_infos[key]}")
179+
print(f"Mismatch for {key}: C={c_infos[key]}, Python={PY_INFOS[key]}")
176180
fail = True
177-
struct_members = [
178-
(key, value)
179-
for key, value in c_infos.items()
180-
if key.startswith(struct_name + '_') and key.endswith("_offset")
181-
]
182-
for key, _ in sorted(struct_members, key=lambda x: x[1]): # sort by offset
183-
if c_infos[key] == py_infos[key]:
181+
182+
for name in member_names:
183+
key = f"{struct_name}.{name} offset"
184+
if c_infos[key] == PY_INFOS[key]:
184185
print(f"OK: {key} = {c_infos[key]}")
185186
else:
186-
print(f"Mismatch for {key}: C={c_infos[key]}, Python={py_infos[key]}")
187+
print(f"Mismatch for {key}: C={c_infos[key]}, Python={PY_INFOS[key]}")
187188
fail = True
189+
188190
assert not fail, "Struct layout mismatch, see stdout output for details!"

0 commit comments

Comments
 (0)