test_fs: Add exfat tests

Add tests for the exfat filesystem. These tests are largely an
extension of the FS_GENERIC tests with the following notable
exceptions.

The filesystem image for exfat tests is generated using combination
of exfatprogs mkfs.exfat and python fattools. The fattols are capable
of generating exfat filesystem images too, but this is not used, the
fattools are only used as a replacement for dosfstools 'mcopy' and
'mdir', which are used to insert files and directories into existing
fatfs images and list existing fatfs images respectively, without the
need for superuser access to mount such images.

The exfat filesystem has no filesystem specific command, there is only
the generic filesystem command interface, therefore check_ubconfig()
has to special case exfat and skip check for CONFIG_CMD_EXFAT and
instead check for CONFIG_FS_EXFAT.

Signed-off-by: Marek Vasut <marex@denx.de>
This commit is contained in:
Marek Vasut 2025-03-17 04:12:50 +01:00 committed by Tom Rini
parent 99b976712b
commit 8d0cc62a60
6 changed files with 35 additions and 17 deletions

View file

@ -2,3 +2,4 @@ filelock==3.0.12
pycryptodomex==3.21.0 pycryptodomex==3.21.0
pytest==6.2.5 pytest==6.2.5
pytest-xdist==2.5.0 pytest-xdist==2.5.0
FATtools==1.0.42

View file

@ -35,7 +35,9 @@ def mk_fs(config, fs_type, size, prefix, src_dir=None, size_gran = 0x100000):
else: else:
mkfs_opt = '' mkfs_opt = ''
if re.match('fat', fs_type) or fs_type == 'fs_generic': if fs_type == 'exfat':
fs_lnxtype = 'exfat'
elif re.match('fat', fs_type) or fs_type == 'fs_generic':
fs_lnxtype = 'vfat' fs_lnxtype = 'vfat'
else: else:
fs_lnxtype = fs_type fs_lnxtype = fs_type
@ -43,7 +45,7 @@ def mk_fs(config, fs_type, size, prefix, src_dir=None, size_gran = 0x100000):
if src_dir: if src_dir:
if fs_lnxtype == 'ext4': if fs_lnxtype == 'ext4':
mkfs_opt = mkfs_opt + ' -d ' + src_dir mkfs_opt = mkfs_opt + ' -d ' + src_dir
elif fs_lnxtype != 'vfat': elif fs_lnxtype != 'vfat' and fs_lnxtype != 'exfat':
raise ValueError(f'src_dir not implemented for fs {fs_lnxtype}') raise ValueError(f'src_dir not implemented for fs {fs_lnxtype}')
count = (size + size_gran - 1) // size_gran count = (size + size_gran - 1) // size_gran
@ -64,6 +66,8 @@ def mk_fs(config, fs_type, size, prefix, src_dir=None, size_gran = 0x100000):
check_call(f'tune2fs -O ^metadata_csum {fs_img}', shell=True) check_call(f'tune2fs -O ^metadata_csum {fs_img}', shell=True)
elif fs_lnxtype == 'vfat' and src_dir: elif fs_lnxtype == 'vfat' and src_dir:
check_call(f'mcopy -i {fs_img} -vsmpQ {src_dir}/* ::/', shell=True) check_call(f'mcopy -i {fs_img} -vsmpQ {src_dir}/* ::/', shell=True)
elif fs_lnxtype == 'exfat' and src_dir:
check_call(f'fattools cp {src_dir}/* {fs_img}', shell=True)
return fs_img return fs_img
except CalledProcessError: except CalledProcessError:
call(f'rm -f {fs_img}', shell=True) call(f'rm -f {fs_img}', shell=True)

View file

@ -11,11 +11,11 @@ from fstest_defs import *
# pylint: disable=E0611 # pylint: disable=E0611
from tests import fs_helper from tests import fs_helper
supported_fs_basic = ['fat16', 'fat32', 'ext4', 'fs_generic'] supported_fs_basic = ['fat16', 'fat32', 'exfat', 'ext4', 'fs_generic']
supported_fs_ext = ['fat12', 'fat16', 'fat32', 'fs_generic'] supported_fs_ext = ['fat12', 'fat16', 'fat32', 'exfat', 'fs_generic']
supported_fs_fat = ['fat12', 'fat16'] supported_fs_fat = ['fat12', 'fat16']
supported_fs_mkdir = ['fat12', 'fat16', 'fat32', 'fs_generic'] supported_fs_mkdir = ['fat12', 'fat16', 'fat32', 'exfat', 'fs_generic']
supported_fs_unlink = ['fat12', 'fat16', 'fat32', 'fs_generic'] supported_fs_unlink = ['fat12', 'fat16', 'fat32', 'exfat', 'fs_generic']
supported_fs_symlink = ['ext4'] supported_fs_symlink = ['ext4']
supported_fs_rename = ['fat12', 'fat16', 'fat32'] supported_fs_rename = ['fat12', 'fat16', 'fat32']
@ -117,7 +117,7 @@ def fstype_to_prefix(fs_type):
Return: Return:
A corresponding command prefix for file system type. A corresponding command prefix for file system type.
""" """
if fs_type == 'fs_generic': if fs_type == 'fs_generic' or fs_type == 'exfat':
return '' return ''
elif re.match('fat', fs_type): elif re.match('fat', fs_type):
return 'fat' return 'fat'
@ -155,9 +155,11 @@ def check_ubconfig(config, fs_type):
Return: Return:
Nothing. Nothing.
""" """
if not config.buildconfig.get('config_cmd_%s' % fs_type, None): if fs_type == 'exfat' and not config.buildconfig.get('config_fs_%s' % fs_type, None):
pytest.skip('.config feature "FS_%s" not enabled' % fs_type.upper())
if fs_type != 'exfat' and not config.buildconfig.get('config_cmd_%s' % fs_type, None):
pytest.skip('.config feature "CMD_%s" not enabled' % fs_type.upper()) pytest.skip('.config feature "CMD_%s" not enabled' % fs_type.upper())
if fs_type == 'fs_generic': if fs_type == 'fs_generic' or fs_type == 'exfat':
return return
if not config.buildconfig.get('config_%s_write' % fs_type, None): if not config.buildconfig.get('config_%s_write' % fs_type, None):
pytest.skip('.config feature "%s_WRITE" not enabled' pytest.skip('.config feature "%s_WRITE" not enabled'
@ -197,7 +199,7 @@ def fs_obj_basic(request, u_boot_config):
""" """
fs_type = request.param fs_type = request.param
fs_cmd_prefix = fstype_to_prefix(fs_type) fs_cmd_prefix = fstype_to_prefix(fs_type)
fs_cmd_write = 'save' if fs_type == 'fs_generic' else 'write' fs_cmd_write = 'save' if fs_type == 'fs_generic' or fs_type == 'exfat' else 'write'
fs_img = '' fs_img = ''
fs_ubtype = fstype_to_ubname(fs_type) fs_ubtype = fstype_to_ubname(fs_type)
@ -309,7 +311,7 @@ def fs_obj_ext(request, u_boot_config):
""" """
fs_type = request.param fs_type = request.param
fs_cmd_prefix = fstype_to_prefix(fs_type) fs_cmd_prefix = fstype_to_prefix(fs_type)
fs_cmd_write = 'save' if fs_type == 'fs_generic' else 'write' fs_cmd_write = 'save' if fs_type == 'fs_generic' or fs_type == 'exfat' else 'write'
fs_img = '' fs_img = ''
fs_ubtype = fstype_to_ubname(fs_type) fs_ubtype = fstype_to_ubname(fs_type)

View file

@ -9,6 +9,8 @@ def assert_fs_integrity(fs_type, fs_img):
try: try:
if fs_type == 'ext4': if fs_type == 'ext4':
check_call('fsck.ext4 -n -f %s' % fs_img, shell=True) check_call('fsck.ext4 -n -f %s' % fs_img, shell=True)
elif fs_type == 'exfat':
check_call('fsck.exfat -n %s' % fs_img, shell=True)
elif fs_type in ['fat12', 'fat16', 'fat32']: elif fs_type in ['fat12', 'fat16', 'fat32']:
check_call('fsck.fat -n %s' % fs_img, shell=True) check_call('fsck.fat -n %s' % fs_img, shell=True)
except CalledProcessError: except CalledProcessError:

View file

@ -345,11 +345,19 @@ class TestFsExt(object):
'%s%s host 0:0 %x /%s 0' '%s%s host 0:0 %x /%s 0'
% (fs_cmd_prefix, fs_cmd_write, ADDR, MANGLE_FILE)]) % (fs_cmd_prefix, fs_cmd_write, ADDR, MANGLE_FILE)])
assert('0 bytes written' in ''.join(output)) assert('0 bytes written' in ''.join(output))
# Test Case 12b - Read file system content if fs_type == 'exfat':
output = check_output('mdir -i %s' % fs_img, shell=True).decode() # Test Case 12b - Read file system content
# Test Case 12c - Check if short filename is not mangled output = check_output('fattools ls %s' % fs_img, shell=True).decode()
assert(str2fat(PLAIN_FILE) in ''.join(output)) # Test Case 12c - Check if short filename is not mangled
# Test Case 12d - Check if long filename is mangled assert(PLAIN_FILE in ''.join(output))
assert(str2fat(MANGLE_FILE) in ''.join(output)) # Test Case 12d - Check if long filename is mangled
assert(MANGLE_FILE in ''.join(output))
else:
# Test Case 12b - Read file system content
output = check_output('mdir -i %s' % fs_img, shell=True).decode()
# Test Case 12c - Check if short filename is not mangled
assert(str2fat(PLAIN_FILE) in ''.join(output))
# Test Case 12d - Check if long filename is mangled
assert(str2fat(MANGLE_FILE) in ''.join(output))
assert_fs_integrity(fs_type, fs_img) assert_fs_integrity(fs_type, fs_img)

View file

@ -74,6 +74,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
e2fsprogs \ e2fsprogs \
efitools \ efitools \
erofs-utils \ erofs-utils \
exfatprogs \
expect \ expect \
fakeroot \ fakeroot \
flex \ flex \