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.

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 No
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 No
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 No
12.t verify SUID/SGID bit behaviour Yes
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 No
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 No
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 No
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 No
01.t mkdir returns ENOTDIR if a component of the path prefix is not a directory No
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 No
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 No
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 No
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 No
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 No
25.t interact with > 2 GB files Yes
posix_fallocate
Test Description Converted
00.t posix_fallocate descrease/increase file size No
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 No
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 No
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 No
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 command is available
  • nfsv4_acls - NFSv4 style Access Control Lists are available
  • posix_fallocate - The posix_fallocate syscall is available
  • rename_ctime - rename changes st_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 an st_birthtime field
  • chflags_sf_snapshot - The SF_SNAPSHOT flag can be set with chflags
  • 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 to true, 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 unwraping Results 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)
                ));
            })
        });
}