Introduction
PJDFSTest is a file system test suite focused on POSIX compliance, primarily for FreeBSD file systems. It was originally written to validate the ZFS port to FreeBSD, but it now supports multiple operating systems and file systems.
This is a complete rewrite of the original test suite in Rust, as part of the Google Summer of Code 2022 program (https://summerofcode.withgoogle.com/archive/2022/projects/6XPYWLzJ).
NOTE: The documentation is still a work-in-progress
Build
cd rust
cargo run
Run as root
cd rust
cargo build && sudo ./target/debug/pjdfstest
Contributing
Please read the CONTRIBUTING.md file on how to contribute to this project.
In addition to this book, you can also find the crate documentation by running cargo doc --open
in the rust
directory or by visiting the documentation.
Getting started
The test suite is as file system agnostic as possible and tries to comply with the POSIX specification. Typically, tests which make use of non-POSIX features are opt-in and only tests for syscalls which must be available on every POSIX system are ran. It can be configured with the configuration file, by specifying additional features supported by the file system/operating system.
Command-line interface
pjdfstest [OPTIONS] [--] TEST_PATTERNS
-h, --help
- Print help message-c, --configuration-file CONFIGURATION-FILE
- Path of the configuration file-l, --list-features
- List opt-in features-e, --exact
- Match names exactly-v, --verbose
- Verbose mode-p, --path PATH
- Path where the test suite will be executed[--] TEST_PATTERNS
- Filter tests which match against the provided patterns
Example: pjdfstest -c pjdfstest.toml chmod
Filter tests
It is possible to filter which tests should be run by specifying which parts should match. Tests are usually identified by syscall and optionally the file type on which it operates.
Rootless running
The test suite can be run without privileges. However, not all tests can be completed without privileges, therefore the coverage will be incomplete. For example, tests which need to switch users will not be run.
Dummy users/groups
The test suite needs dummy users and groups to be set up. This should be handled automatically when installing it via a package, but they need to be created otherwise. By default, the users (with the same name for the group associated to each of them) to create are:
- nobody
- tests
- pjdfstest
It is also possible to specify other users with the configuration file.
Create users
FreeBSD
cat <<EOF | adduser -w none -S -f -
pjdfstest::::::Dummy User for pjdfstest:/nonexistent:/sbin/nologin:
EOF
Linux
cat <<EOF | newusers
tests:x:::Dummy User for pjdfstest:/:/usr/bin/nologin
pjdfstest:x:::Dummy User for pjdfstest:/:/usr/bin/nologin
EOF
Compatibility
The test suite is designed to be compatible with multiple operating systems and file systems. It is primarily focused on POSIX compliance for FreeBSD file systems, but is also compatible with other operating systems and file systems in addition of supplementary tests.
Supported operating systems
Active support
The test suite has been tested and is actively maintained on the following operating systems:
- FreeBSD (main development platform)
- UFS
- ZFS
- Linux
- ext4
Experimental support
The test suite has not been tested on the following operating systems, but should work:
- MacOS
- NetBSD
- OpenBSD
- Solaris
- DragonFly BSD
- Illumos
- Android
- iOS
Missing tests
The test suite is a complete rewrite of the original test suite in Rust. Many tests have been ported, but some tests are still missing.
chflags
Test | Description | Converted |
---|---|---|
00.t | chflags changes flags | Yes |
01.t | chflags returns ENOTDIR if a component of the path prefix is not a directory | Yes |
02.t | chflags returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | Yes |
03.t | chflags returns ENAMETOOLONG if an entire path name exceeded {PATH_MAX} characters | Yes |
04.t | chflags returns ENOENT if the named file does not exist | Yes |
05.t | chflags returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | chflags returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
07.t | chflags returns EPERM when the effective user ID does not match the owner of the file and the effective user ID is not the super-user | No |
08.t | chflags returns EPERM when one of SF_IMMUTABLE, SF_APPEND, or SF_NOUNLINK is set and the user is not the super-user | No |
09.t | chflags returns EPERM when one of SF_IMMUTABLE, SF_APPEND, or SF_NOUNLINK is set and securelevel is greater than 0 | Yes |
10.t | chflags returns EPERM if non-super-user tries to set one of SF_IMMUTABLE, SF_APPEND, or SF_NOUNLINK | No |
11.t | chflags returns EPERM if a user tries to set or remove the SF_SNAPSHOT flag | No |
12.t | chflags returns EROFS if the named file resides on a read-only file system | Yes |
13.t | chflags returns EFAULT if the path argument points outside the process's allocated address space | Yes |
chmod
Test | Description | Converted |
---|---|---|
00.t | chmod changes permission | Yes |
01.t | chmod returns ENOTDIR if a component of the path prefix is not a directory | Yes |
02.t | chmod returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | Yes |
03.t | chmod returns ENAMETOOLONG if an entire path name exceeded {PATH_MAX} characters | Yes |
04.t | chmod returns ENOENT if the named file does not exist | Yes |
05.t | chmod returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | chmod returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
07.t | chmod returns EPERM if the operation would change the ownership, but the effective user ID is not the super-user | No |
08.t | chmod returns EPERM if the named file has its immutable or append-only flag set | No |
09.t | chmod returns EROFS if the named file resides on a read-only file system | Yes |
10.t | chmod returns EFAULT if the path argument points outside the process's allocated address space | Yes |
11.t | chmod returns EFTYPE if the effective user ID is not the super-user, the mode includes the sticky bit (S_ISVTX), and path does not refer to a directory | Yes |
12.t | verify SUID/SGID bit behaviour | No |
chown
Test | Description | Converted |
---|---|---|
00.t | chown changes ownership | No |
01.t | chown returns ENOTDIR if a component of the path prefix is not a directory | Yes |
02.t | chown returns ENAMETOOLONG if a component of a pathname exceeded ${NAME_MAX} characters | Yes |
03.t | chown returns ENAMETOOLONG if an entire path name exceeded {PATH_MAX} characters | Yes |
04.t | chown returns ENOENT if the named file does not exist | Yes |
05.t | chown returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | chown returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
07.t | chown returns EPERM if the operation would change the ownership, but the effective user ID is not the super-user and the process is not an owner of the file | No |
08.t | chown returns EPERM if the named file has its immutable or append-only flag set | No |
09.t | chown returns EROFS if the named file resides on a read-only file system | Yes |
10.t | chown returns EFAULT if the path argument points outside the process's allocated address space | Yes |
ftruncate
Test | Description | Converted |
---|---|---|
00.t | ftruncate descrease/increase file size | Yes |
01.t | truncate returns ENOTDIR if a component of the path prefix is not a directory | No |
02.t | truncate returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | No |
03.t | truncate returns ENAMETOOLONG if an entire path name exceeded {PATH_MAX} characters | No |
04.t | truncate returns ENOENT if the named file does not exist | No |
05.t | truncate returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | truncate returns EACCES if the named file is not writable by the user | No |
07.t | truncate returns ELOOP if too many symbolic links were encountered in translating the pathname | No |
08.t | truncate returns EPERM if the named file has its immutable or append-only flag set | No |
09.t | truncate returns EISDIR if the named file is a directory | No |
10.t | truncate returns EROFS if the named file resides on a read-only file system | No |
11.t | truncate returns ETXTBSY the file is a pure procedure (shared text) file that is being executed | No |
12.t | truncate returns EFBIG or EINVAL if the length argument was greater than the maximum file size | No |
13.t | ftruncate returns EINVAL if the length argument was less than 0 | Yes |
14.t | truncate returns EFAULT if the path argument points outside the process's allocated address space | No |
granular
Test | Description | Converted |
---|---|---|
00.t | NFSv4 granular permissions checking - WRITE_DATA vs APPEND_DATA on directories | Yes |
01.t | NFSv4 granular permissions checking - ACL_READ_ATTRIBUTES and ACL_WRITE_ATTRIBUTES | Yes |
02.t | NFSv4 granular permissions checking - ACL_READ_ACL and ACL_WRITE_ACL | Yes |
03.t | NFSv4 granular permissions checking - DELETE and DELETE_CHILD | Yes |
04.t | NFSv4 granular permissions checking - ACL_WRITE_OWNER | Yes |
05.t | NFSv4 granular permissions checking - DELETE and DELETE_CHILD with directories | Yes |
06.t | NFSv4 granular permissions checking - setuid and setgid are cleared when non-owner calls chown | Yes |
link
Test | Description | Converted |
---|---|---|
00.t | link creates hardlinks | Yes |
01.t | link returns ENOTDIR if a component of either path prefix is not a directory | Yes |
02.t | link returns ENAMETOOLONG if a component of either pathname exceeded {NAME_MAX} characters | Yes |
03.t | link returns ENAMETOOLONG if an entire length of either path name exceeded {PATH_MAX} characters | Yes |
04.t | link returns ENOENT if a component of either path prefix does not exist | Yes |
05.t | link returns EMLINK if the link count of the file named by name1 would exceed 32767 | Yes |
06.t | link returns EACCES when a component of either path prefix denies search permission | No |
07.t | link returns EACCES when the requested link requires writing in a directory with a mode that denies write permission | Yes |
08.t | link returns ELOOP if too many symbolic links were encountered in translating one of the pathnames | Yes |
09.t | link returns ENOENT if the source file does not exist | Yes |
10.t | link returns EEXIST if the destination file does exist | Yes |
11.t | link returns EPERM if the source file is a directory | No |
12.t | link returns EPERM if the source file has its immutable or append-only flag set | Yes |
13.t | link returns EPERM if the parent directory of the destination file has its immutable flag set | Yes |
14.t | link returns EXDEV if the source and the destination files are on different file systems | Yes |
15.t | link returns ENOSPC if the directory in which the entry for the new link is being placed cannot be extended because there is no space left on the file system containing the directory | No |
16.t | link returns EROFS if the requested link requires writing in a directory on a read-only file system | Yes |
17.t | link returns EFAULT if one of the pathnames specified is outside the process's allocated address space | Yes |
mkdir
Test | Description | Converted |
---|---|---|
00.t | mkdir creates directories | Yes |
01.t | mkdir returns ENOTDIR if a component of the path prefix is not a directory | Yes |
02.t | mkdir returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | Yes |
03.t | mkdir returns ENAMETOOLONG if an entire path name exceeded {PATH_MAX} characters | Yes |
04.t | mkdir returns ENOENT if a component of the path prefix does not exist | Yes |
05.t | mkdir returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | mkdir returns EACCES when write permission is denied on the parent directory of the directory to be created | No |
07.t | mkdir returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
08.t | mkdir returns EPERM if the parent directory of the directory to be created has its immutable flag set | No |
09.t | mkdir returns EROFS if the named file resides on a read-only file system | Yes |
10.t | mkdir returns EEXIST if the named file exists | Yes |
11.t | mkdir returns ENOSPC if there are no free inodes on the file system on which the directory is being created | No |
12.t | mkdir returns EFAULT if the path argument points outside the process's allocated address space | Yes |
mkfifo
Test | Description | Converted |
---|---|---|
00.t | mkfifo creates fifo files | Yes |
01.t | mkfifo returns ENOTDIR if a component of the path prefix is not a directory | Yes |
02.t | mkfifo returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | Yes |
03.t | mkfifo returns ENAMETOOLONG if an entire path name exceeded {PATH_MAX} characters | Yes |
04.t | mkfifo returns ENOENT if a component of the path prefix does not exist | Yes |
05.t | mkfifo returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | mkfifo returns EACCES when write permission is denied on the parent directory of the file to be created | No |
07.t | mkfifo returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
08.t | mkfifo returns EROFS if the named file resides on a read-only file system | Yes |
09.t | mkfifo returns EEXIST if the named file exists | Yes |
10.t | mkfifo returns EPERM if the parent directory of the file to be created has its immutable flag set | No |
11.t | mkfifo returns ENOSPC if there are no free inodes on the file system on which the file is being created | No |
12.t | mkfifo returns EFAULT if the path argument points outside the process's allocated address space | Yes |
mknod
Test | Description | Converted |
---|---|---|
00.t | mknod creates fifo files | Yes |
01.t | mknod returns ENOTDIR if a component of the path prefix is not a directory | Yes |
02.t | mknod returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | Yes |
03.t | mknod returns ENAMETOOLONG if an entire path name exceeded {PATH_MAX} characters | Yes |
04.t | mknod returns ENOENT if a component of the path prefix does not exist | Yes |
05.t | mknod returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | mknod returns EACCES when write permission is denied on the parent directory of the file to be created | No |
07.t | mknod returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
08.t | mknod returns EEXIST if the named file exists | Yes |
09.t | mknod returns EPERM if the parent directory of the file to be created has its immutable flag set | No |
10.t | mknod returns EFAULT if the path argument points outside the process's allocated address space | Yes |
11.t | mknod creates device files | Yes |
open
Test | Description | Converted |
---|---|---|
00.t | open opens (and eventually creates) a file | Yes |
01.t | open returns ENOTDIR if a component of the path prefix is not a directory | No |
02.t | open returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | Yes |
03.t | open returns ENAMETOOLONG if an entire path name exceeded ${PATH_MAX} characters | Yes |
04.t | open returns ENOENT if a component of the path name that must exist does not exist or O_CREAT is not set and the named file does not exist | Yes |
05.t | open returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | open returns EACCES when the required permissions (for reading and/or writing) are denied for the given flags | No |
07.t | open returns EACCES when O_TRUNC is specified and write permission is denied | No |
08.t | open returns EACCES when O_CREAT is specified, the file does not exist, and the directory in which it is to be created does not permit writing | No |
09.t | O_CREAT is specified, the file does not exist, and the directory in which it is to be created has its immutable flag set | No |
10.t | open returns EPERM when the named file has its immutable flag set and the file is to be modified | No |
11.t | open returns EPERM when the named file has its append-only flag set, the file is to be modified, and O_TRUNC is specified or O_APPEND is not specified | No |
12.t | open returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
13.t | open returns EISDIR when trying to open a directory for writing | Yes |
14.t | open returns EROFS if the named file resides on a read-only file system, and the file is to be modified | Yes |
15.t | open returns EROFS when O_CREAT is specified and the named file would reside on a read-only file system | Yes |
16.t | open returns $error when O_NOFOLLOW was specified and the target is a symbolic link | Yes |
17.t | open returns ENXIO when O_NONBLOCK is set, the named file is a fifo, O_WRONLY is set, and no process has the file open for reading | Yes |
18.t | open returns EWOULDBLOCK when O_NONBLOCK and one of O_SHLOCK or O_EXLOCK is specified and the file is locked | Yes |
19.t | open returns ENOSPC when O_CREAT is specified, the file does not exist, and there are no free inodes on the file system on which the file is being created | No |
20.t | open returns ETXTBSY when the file is a pure procedure (shared text) file that is being executed and the open() system call requests write access | Yes |
21.t | open returns EFAULT if the path argument points outside the process's allocated address space | Yes |
22.t | open returns EEXIST when O_CREAT and O_EXCL were specified and the file exists | Yes |
23.t | open may return EINVAL when an attempt was made to open a descriptor with an illegal combination of O_RDONLY, O_WRONLY, and O_RDWR | Yes |
24.t | open returns $expected_error when trying to open UNIX domain socket | Yes |
25.t | interact with > 2 GB files | Yes |
posix_fallocate
Test | Description | Converted |
---|---|---|
00.t | posix_fallocate descrease/increase file size | Yes |
rename
Test | Description | Converted |
---|---|---|
00.t | rename changes file name | Yes |
01.t | rename returns ENAMETOOLONG if a component of either pathname exceeded {NAME_MAX} characters | Yes |
02.t | rename returns ENAMETOOLONG if an entire length of either path name exceeded {PATH_MAX} characters | Yes |
03.t | rename returns ENOENT if a component of the 'from' path does not exist, or a path prefix of 'to' does not exist | Yes |
04.t | rename returns EACCES when a component of either path prefix denies search permission | No |
05.t | rename returns EACCES when the requested link requires writing in a directory with a mode that denies write permission | No |
06.t | rename returns EPERM if the file pointed at by the 'from' argument has its immutable, undeletable or append-only flag set | No |
07.t | rename returns EPERM if the parent directory of the file pointed at by the 'from' argument has its immutable or append-only flag set | No |
08.t | rename returns EPERM if the parent directory of the file pointed at by the 'to' argument has its immutable flag set | No |
09.t | rename returns EACCES or EPERM if the directory containing 'from' is marked sticky, and neither the containing directory nor 'from' are owned by the effective user ID | No |
10.t | rename returns EACCES or EPERM if the file pointed at by the 'to' argument exists, the directory containing 'to' is marked sticky, and neither the containing directory nor 'to' are owned by the effective user ID | No |
11.t | rename returns ELOOP if too many symbolic links were encountered in translating one of the pathnames | Yes |
12.t | rename returns ENOTDIR if a component of either path prefix is not a directory | Yes |
13.t | rename returns ENOTDIR when the 'from' argument is a directory, but 'to' is not a directory | Yes |
14.t | rename returns EISDIR when the 'to' argument is a directory, but 'from' is not a directory | Yes |
15.t | rename returns EXDEV if the link named by 'to' and the file named by 'from' are on different file systems | Yes |
16.t | rename returns EROFS if the requested link requires writing in a directory on a read-only file system | Yes |
17.t | rename returns EFAULT if one of the pathnames specified is outside the process's allocated address space | Yes |
18.t | rename returns EINVAL when the 'from' argument is a parent directory of 'to' | Yes |
19.t | rename returns EINVAL/EBUSY when an attempt is made to rename '.' or '..' | Yes |
20.t | rename returns EEXIST or ENOTEMPTY if the 'to' argument is a directory and is not empty | Yes |
21.t | write access to subdirectory is required to move it to another directory | Yes |
22.t | rename changes file ctime | Yes |
23.t | rename succeeds when to is multiply linked | Yes |
24.t | rename of a directory updates its .. link | Yes |
rmdir
Test | Description | Converted |
---|---|---|
00.t | rmdir removes directories | Yes |
01.t | rmdir returns ENOTDIR if a component of the path is not a directory | Yes |
02.t | rmdir returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | Yes |
03.t | rmdir returns ENAMETOOLONG if an entire path name exceeded ${PATH_MAX} characters | Yes |
04.t | rmdir returns ENOENT if the named directory does not exist | Yes |
05.t | rmdir returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
06.t | rmdir returns EEXIST or ENOTEMPTY the named directory contains files other than '.' and '..' in it | Yes |
07.t | rmdir returns EACCES when search permission is denied for a component of the path prefix | No |
08.t | rmdir returns EACCES when write permission is denied on the directory containing the link to be removed | No |
09.t | rmdir returns EPERM if the named directory has its immutable, undeletable or append-only flag set | No |
10.t | rmdir returns EPERM if the parent directory of the named file has its immutable or append-only flag set | No |
11.t | rmdir returns EACCES or EPERM if the directory containing the directory to be removed is marked sticky, and neither the containing directory nor the directory to be removed are owned by the effective user ID | No |
12.t | rmdir returns EINVAL if the last component of the path is '.' and EEXIST or ENOTEMPTY if the last component of the path is '..' | Yes |
13.t | rmdir returns EBUSY if the directory to be removed is the mount point for a mounted file system | Yes |
14.t | rmdir returns EROFS if the named file resides on a read-only file system | Yes |
15.t | rmdir returns EFAULT if the path argument points outside the process's allocated address space | Yes |
symlink
Test | Description | Converted |
---|---|---|
00.t | symlink creates symbolic links | Yes |
01.t | symlink returns ENOTDIR if a component of the name2 path prefix is not a directory | Yes |
02.t | symlink returns ENAMETOOLONG if a component of the name2 pathname exceeded {NAME_MAX} characters | Yes |
03.t | symlink returns ENAMETOOLONG if an entire length of either path name exceeded {PATH_MAX} characters | Yes |
04.t | symlink returns ENOENT if a component of the name2 path prefix does not exist | Yes |
05.t | symlink returns EACCES when a component of the name2 path prefix denies search permission | No |
06.t | symlink returns EACCES if the parent directory of the file to be created denies write permission | No |
07.t | symlink returns ELOOP if too many symbolic links were encountered in translating the name2 path name | No |
08.t | symlink returns EEXIST if the name2 argument already exists | Yes |
09.t | symlink returns EPERM if the parent directory of the file named by name2 has its immutable flag set | No |
10.t | symlink returns EROFS if the file name2 would reside on a read-only file system | Yes |
11.t | symlink returns ENOSPC if there are no free inodes on the file system on which the symbolic link is being created | No |
12.t | symlink returns EFAULT if one of the pathnames specified is outside the process's allocated address space | Yes |
truncate
Test | Description | Converted |
---|---|---|
00.t | truncate descrease/increase file size | Yes |
01.t | truncate returns ENOTDIR if a component of the path prefix is not a directory | Yes |
02.t | truncate returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | Yes |
03.t | truncate returns ENAMETOOLONG if an entire path name exceeded {PATH_MAX} characters | Yes |
04.t | truncate returns ENOENT if the named file does not exist | Yes |
05.t | truncate returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | truncate returns EACCES if the named file is not writable by the user | No |
07.t | truncate returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
08.t | truncate returns EPERM if the named file has its immutable or append-only flag set | No |
09.t | truncate returns EISDIR if the named file is a directory | Yes |
10.t | truncate returns EROFS if the named file resides on a read-only file system | Yes |
11.t | truncate returns ETXTBSY the file is a pure procedure (shared text) file that is being executed | Yes |
12.t | truncate returns EFBIG or EINVAL if the length argument was greater than the maximum file size | No |
13.t | truncate returns EINVAL if the length argument was less than 0 | Yes |
14.t | truncate returns EFAULT if the path argument points outside the process's allocated address space | Yes |
unlink
Test | Description | Converted |
---|---|---|
00.t | unlink removes regular files, symbolic links, fifos and sockets | Yes |
01.t | unlink returns ENOTDIR if a component of the path prefix is not a directory | Yes |
02.t | unlink returns ENAMETOOLONG if a component of a pathname exceeded {NAME_MAX} characters | Yes |
03.t | unlink returns ENAMETOOLONG if an entire path name exceeded {PATH_MAX} characters | Yes |
04.t | unlink returns ENOENT if the named file does not exist | Yes |
05.t | unlink returns EACCES when search permission is denied for a component of the path prefix | No |
06.t | unlink returns EACCES when write permission is denied on the directory containing the link to be removed | No |
07.t | unlink returns ELOOP if too many symbolic links were encountered in translating the pathname | Yes |
08.t | unlink may return EPERM if the named file is a directory | No |
09.t | unlink returns EPERM if the named file has its immutable, undeletable or append-only flag set | No |
10.t | unlink returns EPERM if the parent directory of the named file has its immutable or append-only flag set | No |
11.t | unlink returns EACCES or EPERM if the directory containing the file is marked sticky, and neither the containing directory nor the file to be removed are owned by the effective user ID | No |
12.t | unlink returns EROFS if the named file resides on a read-only file system | Yes |
13.t | unlink returns EFAULT if the path argument points outside the process's allocated address space | Yes |
14.t | An open file will not be immediately freed by unlink | Yes |
utimensat
Test | Description | Converted |
---|---|---|
00.t | utimensat changes timestamps on any type of file | Yes |
01.t | utimensat with UTIME_NOW will set the will set typestamps to now | Yes |
02.t | utimensat with UTIME_OMIT will leave the time unchanged | Yes |
03.t | utimensat can update birthtimes | Yes |
04.t | utimensat can set mtime < atime or vice versa | Yes |
05.t | utimensat can follow symlinks | Yes |
06.t | utimensat with UTIME_NOW will work if the caller has write permission | Yes |
07.t | utimensat will work if the caller is the owner or root | Yes |
08.t | utimensat can set timestamps with subsecond precision | Yes |
09.t | utimensat is y2038 compliant | Yes |
Configuration file
The test runner can read a configuration file. For now, only the TOML format is supported.
Its path can be specified by using the -c PATH
flag.
Sections
[features]
Some features are not available for every file system.
For tests requiring such features,
the execution becomes opt-in.
The user can enable their execution,
by adding the corresponding feature as a key in this section.
A list of these opt-in features is provided
when executing the runner with -l
argument.
For example, with posix_fallocate
:
[features]
posix_fallocate = {}
# Can also be specified by using key notation
# [features.posix_fallocate]
Feature list
The following features can be enabled but do not require any additional configuration:
- chflags - The
chflags
syscall is available - nfsv4_acls - NFSv4 style Access Control Lists are available
- posix_fallocate - The
posix_fallocate
syscall is available - rename_ctime -
rename
changesst_ctime
on success (POSIX does not require a file system to update a file's ctime when it gets renamed, but some file systems choose to do it anyway) - stat_st_birthtime -
struct stat
contains anst_birthtime
field - chflags_sf_snapshot - The
SF_SNAPSHOT
flag can be set withchflags
- utime_now - The
UTIME_NOW
constant is available - utimensat - The
utimensat
syscall is available
Following features require additional configuration.
file_flags
Some tests are related to file flags.
However, not all file systems and operating systems support all flags.
To give a sufficient level of granularity, each supported flag can be
specified in the configuration with the file_flags
array.
[features]
posix_fallocate = {}
file_flags = ["UF_IMMUTABLE"]
secondary_fs
Some tests require a secondary file system.
This can be specified in the configuration with the secondary_fs
key,
but also with the secondary_fs
argument.
The argument takes precedence over the configuration.
[features]
secondary_fs = "/mnt/ISO"
[dummy_auth]
This section allows to modify the mechanism for switching users, which is required by some tests.
[dummy_auth]
entries = [
["nobody", "nobody"],
# nogroup instead for some Linux distros
# ["nobody", "nogroup"],
["tests", "tests"],
["pjdfstest", "pjdfstest"],
]
entries
- An entry is composed of a username and its associated group. Exactly 3 entries need to be specified if the default ones cannot be used.
[settings]
[settings]
naptime = 0.001
allow_remount = false
naptime
- The duration for a "short" sleep. It should be greater than the timestamp granularity of the file system under test. The default value is 1 second.allow_remount
- If set totrue
, the runner will run the EROFS tests, which require to remount the file system on which pjdsfstest is run as read-only.
Structure
The package is made of the tests, and a test runner to launch them.
Tests (tests/)
To present how tests are organized, we take the chmod
syscall as example.
There is a separate module for each syscall being tested. Within each of those modules, there may be either a single file, or a separate file for each aspect of the syscall.
The hierarchy is like this:
graph TD TG[Syscall module<br /><i>chmod</i>] --> TC1[Aspect<br /><i>errno</i>] TC1 --> TC1F1[Test case] TC1 --> TC1F2[Test case] TC1 --> TC1F3[Test case] TC1 --> TC1F4[Test case] TG --> TC2[Aspect<br /><i>permission</i>] TC2 --> TC2F1[Test case] TC2 --> TC2F2[Test case]
Layout
src/tests
├── chmod (syscall)
│ ├── errno.rs (aspect)
│ └── permission.rs (aspect)
├── mod.rs (glues syscalls together)
└── chmod.rs (syscall declaration and simple test cases)
tests/mod.rs
All the modules for the test groups should be declared in this file.
pub mod chmod;
Syscall module
A syscall module contains test cases related to a specific syscall.
Its declaration should be in the <syscall_name>.rs
file at the root of the
tests/
directory.
Common syscall-specific helpers can go here.
Aspect
An optional aspect module contains test cases that all relate to a common aspect of the syscall. Here "aspect" is a subjective area of related functionality. The aspect module may be either:
- in a single file, which contains all the test functions,
- in a folder, which contains multiple modules for the test functions, in which the case is declared.
Except in the case of a very large set of test functions, the first style should be preferred.
Test case
Each test case exercises a minimal piece of the syscall's functionality.
Each must be registered with the test_case!
macro.
crate::test_case! {
/// open do not update parent directory ctime and mtime fields if
/// the file previously existed.
exists_no_update
}
fn exists_no_update(ctx: &mut TestContext) {
let file = ctx.create(FileType::Regular).unwrap();
assert_times_unchanged()
.path(ctx.base_path(), CTIME | MTIME)
.execute(ctx, false, || {
assert!(open_wrapper(&file, Mode::from_bits_truncate(0o755)).is_ok());
});
}
Test runner (main.rs)
The test runner has to run the tests, and provide a command-line interface to allow the user to modify how the tests should be run. It takes the tests from the specified test groups.
Test declaration
Test cases have the same structure than usual Rust tests,
that is unwrap
ing Result
s and using assertion macros (assert
and assert_eq
),
the exception being that it should take a &mut TestContext
parameter.
It might also take a FileType
argument if required.
It also needs an additional declaration with the test_case!
macro alongside the function,
with the function name being the only mandatory argument.
For example:
// chmod/00.t:L58
crate::test_case! {
/// chmod updates ctime when it succeeds
update_ctime => [Regular, Dir, Fifo, Block, Char, Socket]
}
fn update_ctime(ctx: &mut TestContext, f_type: FileType) {
let path = ctx.create(f_type).unwrap();
assert_ctime_changed(ctx, &path, || {
assert!(chmod(&path, Mode::from_bits_truncate(0o111)).is_ok());
});
}
All the structures and functions needed are documented in the pjdfstest
crate,
which you can obtain by running cargo doc --open
in the rust
directory
or by visiting the documentation.
Test context
The TestContext
struct is a helper struct which provides methods to create files,
sleep, change user, etc.
It is passed as a parameter to the test functions and should be used
to interact with the system in order to ensure that the tests are isolated
and do not interfere with each other.
Serialization
When a test case needs to be run in a serialized manner, the
SerializedTestContext
struct should be used instead.
It provides additional methods to change the user, group,
supplementary groups, or umask of the process.
Please see the Serialized test cases section for more information.
Assertions
The assert
and assert_eq
macros should be used to check the results of the tests.
The assert
macro should be used when the test is checking a condition,
while the assert_eq
macro should be used when the test is checking
that two values are equal.
In addition to these macros, the suite provides some additional assertion functions which
should be used when appropriate.
The tests module documentation provides
a list of these functions.
Description
It is possible to provide doc comments which will be used as documentation for developers
but also be displayed to users when they run the test.
The doc comments should be written in the test_case!
declaration, before anything.
For example:
crate::test_case! {
/// The file mode of a newly created file should not affect whether
/// posix_fallocate will work, only the create args
/// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=154873
affected_only_create_flags, serialized, root, FileSystemFeature::PosixFallocate
}
Parameterization
It is possible to give additional parameters to the test case macro, to modify the execution of the tests or add requirements.
File-system exclusive features
Some features are not available for every file system.
For tests requiring such features, the execution becomes opt-in.
A variant of the FileSystemFeature
enum corresponding to this feature
should be specified after potential root
requirement and before guards.
Multiple features can be specified, separated by a comma ,
.
For example:
#[cfg(target_os = "freebsd")]
crate::test_case! {eperm_immutable_flag, FileSystemFeature::Chflags, FileSystemFeature::PosixFallocate ...}
Adding features
New features can be added to the FileSystemFeature
enum.
A description of the feature should be provided as documentation
for both developers and users.
Guards
It is possible to specify "guards", which are functions which checks if a requirement
is met and return an error if not so the test will be skipped.
They can be specified by appending function names after a ;
separator,
after potential root
requirement and features.
The function has to take a &Config
argument
which contains the current configuration
and a &Path
which represents the parent folder
of the potential test context which would be created.
Guard signature
/// Function which indicates if the test should be skipped by returning an error.
pub type Guard = fn(&Config, &Path) -> anyhow::Result<()>;
Example
fn has_reasonable_link_max(_: &Config, base_path: &Path) -> anyhow::Result<()> {
let link_max = pathconf(base_path, nix::unistd::PathconfVar::LINK_MAX)?
.ok_or_else(|| anyhow::anyhow!("Failed to get LINK_MAX value"))?;
if link_max >= LINK_MAX_LIMIT {
anyhow::bail!("LINK_MAX value is too high ({link_max}, expected smaller than {LINK_MAX_LIMIT}");
}
Ok(())
}
crate::test_case! {
/// link returns EMLINK if the link count of the file named by name1 would exceed {LINK_MAX}
link_count_max; has_reasonable_link_max
}
...
Root privileges
Some tests may need root privileges to run.
To declare that a test function require such privileges,
root
should be added to its declaration.
For example:
crate::test_case!{change_perm, root}
The root requirement is automatically added for privileged file types, namely block and char.
File types
Some test cases need to test over different file types.
The file types should be added at the end of the test case declaration,
within brackets and with a fat arrow before (=> [Regular]
).
The test function should also accept a FileType
parameter to operate on.
For example:
crate::test_case! {change_perm, root, FileSystemFeature::Chflags => [Regular, Fifo, Block, Char, Socket]}
fn change_perm(ctx: &mut TestContext, f_type: FileType) {
Platform-specific features
Some features (like lchmod
) are not supported on every operating system.
When a test make use of such feature, it is possible to restrain its compilation
to the supported operating systems, with the attribute #[cfg(feature_name)]
.
It is also possible to apply this attribute on an aspect or even a syscall module.
For example:
#[cfg(lchmod)]
mod lchmod;
To declare it, the feature and its requirements have to be specified in the build.rs
file
using the usual conditional compilation
syntax.
Then, the feature should be added to the cfg_aliases!
macro.
With lchmod
, we would get:
cfg_aliases! {
...
lchmod: { any(target_os = "netbsd", target_os = "freebsd", target_os = "dragonfly") },
...
}
Serialized test cases
Some test cases need functions only available when they are run serialized,
especially when they affect the whole process.
An example is changing user (SerializedTestContext::as_user
).
To have access to these functions, the test should be declared with a
SerializedTestContext
parameter in place of TestContext
and the serialized
keyword
should be prepended before features and root
requirement.
For example:
crate::test_case! {
/// link changes neither ctime of file nor ctime or mtime of parent when it fails
// link/00.t#77
unchanged_ctime_fails, serialized, root => [Regular, Fifo, Block, Char, Socket]
}
fn unchanged_ctime_fails(ctx: &mut SerializedTestContext, ft: FileType) {
let file = ctx.create(ft).unwrap();
let new_path = ctx.gen_path();
let user = ctx.get_new_user();
assert_times_unchanged()
.path(&file, CTIME)
.path(ctx.base_path(), CTIME | MTIME)
.execute(ctx, false, || {
ctx.as_user(user, None, || {
assert!(matches!(
link(&file, &new_path),
Err(Errno::EPERM | Errno::EACCES)
));
})
});
}