2465 lines
86 KiB
Plaintext
2465 lines
86 KiB
Plaintext
|
|
LTP C Test API
|
|||
|
|
==============
|
|||
|
|
|
|||
|
|
NOTE: See also
|
|||
|
|
https://github.com/linux-test-project/ltp/wiki/Test-Writing-Guidelines[Test Writing Guidelines],
|
|||
|
|
https://github.com/linux-test-project/ltp/wiki/C-Test-Case-Tutorial[C Test Case Tutorial],
|
|||
|
|
https://github.com/linux-test-project/ltp/wiki/Shell-Test-API[Shell Test API].
|
|||
|
|
|
|||
|
|
1 Writing a test in C
|
|||
|
|
---------------------
|
|||
|
|
|
|||
|
|
1.1 Basic test structure
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Let's start with an example, following code is a simple test for a 'getenv()'.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
/*\
|
|||
|
|
* [Description]
|
|||
|
|
* Tests basic functionality of getenv().
|
|||
|
|
*
|
|||
|
|
* - create an env variable and verify that getenv() can get get it
|
|||
|
|
* - call getenv() with nonexisting variable name, check that it returns NULL
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
#define ENV1 "LTP_TEST_ENV"
|
|||
|
|
#define ENV2 "LTP_TEST_THIS_DOES_NOT_EXIST"
|
|||
|
|
#define ENV_VAL "val"
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
if (setenv(ENV1, ENV_VAL, 1))
|
|||
|
|
tst_brk(TBROK | TERRNO, "setenv() failed");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void test(void)
|
|||
|
|
{
|
|||
|
|
char *ret;
|
|||
|
|
|
|||
|
|
ret = getenv(ENV1);
|
|||
|
|
|
|||
|
|
if (!ret) {
|
|||
|
|
tst_res(TFAIL, "getenv(" ENV1 ") = NULL");
|
|||
|
|
goto next;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!strcmp(ret, ENV_VAL)) {
|
|||
|
|
tst_res(TPASS, "getenv(" ENV1 ") = '"ENV_VAL "'");
|
|||
|
|
} else {
|
|||
|
|
tst_res(TFAIL, "getenv(" ENV1 ") = '%s', expected '"
|
|||
|
|
ENV_VAL "'", ret);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
next:
|
|||
|
|
ret = getenv(ENV2);
|
|||
|
|
|
|||
|
|
if (ret)
|
|||
|
|
tst_res(TFAIL, "getenv(" ENV2 ") = '%s'", ret);
|
|||
|
|
else
|
|||
|
|
tst_res(TPASS, "getenv(" ENV2 ") = NULL");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
.test_all = test,
|
|||
|
|
.setup = setup,
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Each test includes the 'tst_test.h' header and must define the 'struct
|
|||
|
|
tst_test test' structure.
|
|||
|
|
|
|||
|
|
The overall test initialization is done in the 'setup()' function.
|
|||
|
|
|
|||
|
|
The overall cleanup is done in a 'cleanup()' function. Here 'cleanup()' is
|
|||
|
|
omitted as the test does not have anything to clean up. If cleanup is set in
|
|||
|
|
the test structure it's called on test exit just before the test library
|
|||
|
|
cleanup. That especially means that cleanup can be called at any point in a
|
|||
|
|
test execution. For example even when a test setup step has failed, therefore
|
|||
|
|
the 'cleanup()' function must be able to cope with unfinished initialization,
|
|||
|
|
and so on.
|
|||
|
|
|
|||
|
|
The test itself is done in the 'test()' function. The test function must work
|
|||
|
|
fine if called in a loop.
|
|||
|
|
|
|||
|
|
There are two types of a test function pointers in the test structure. The
|
|||
|
|
first one is a '.test_all' pointer that is used when test is implemented as a
|
|||
|
|
single function. Then there is a '.test' function along with the number of
|
|||
|
|
tests '.tcnt' that allows for more detailed result reporting. If the '.test'
|
|||
|
|
pointer is set the function is called '.tcnt' times with an integer parameter
|
|||
|
|
in range of [0, '.tcnt' - 1].
|
|||
|
|
|
|||
|
|
IMPORTANT: Only one of '.test' and '.test_all' can be set at a time.
|
|||
|
|
|
|||
|
|
Each test has a limit on how long it can run and the limit composes of two
|
|||
|
|
parts max_runtime and timeout. The max_runtime is a limit for how long can the
|
|||
|
|
'.test_all' or a set of '.test' functions take and the timeout is static part
|
|||
|
|
that should cover the duration of test setup and cleanup plus some safety.
|
|||
|
|
|
|||
|
|
Any test that runs for more than a second or two has to make sure to:
|
|||
|
|
|
|||
|
|
- set the runtime either by setting the '.max_runtime' in tst_test or by
|
|||
|
|
calling 'tst_set_runtime()' in the test setup
|
|||
|
|
|
|||
|
|
- monitor remaning runtime by regular calls to 'tst_remaining_runtime()' and
|
|||
|
|
exit when runtime has been used up
|
|||
|
|
|
|||
|
|
Test is free to exit before max_runtime has been used up for example when
|
|||
|
|
minimal number of iteration was finished.
|
|||
|
|
|
|||
|
|
The limit is applied to a single call of the '.test_all' function that means
|
|||
|
|
that for example when '.test_variants' or '.all_filesystems' is set the whole
|
|||
|
|
test will be limited by 'variants * (max_runtime + timeout)' seconds and the
|
|||
|
|
test runtime will be likely close to 'variants * max_runtime' seconds.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
/*
|
|||
|
|
* Returns number of seconds or zero in case that runtime has been used up.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
int tst_remaining_runtime(void);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
LAPI headers
|
|||
|
|
++++++++++++
|
|||
|
|
|
|||
|
|
Use our LAPI headers ('include "lapi/foo.h"') to keep compatibility with old
|
|||
|
|
distributions. LAPI header should always include original header. Older linux
|
|||
|
|
headers were problematic, therefore we preferred to use libc headers. There are
|
|||
|
|
still some bugs when combining certain glibc headers with linux headers, see
|
|||
|
|
https://sourceware.org/glibc/wiki/Synchronizing_Headers.
|
|||
|
|
|
|||
|
|
A word about the cleanup() callback
|
|||
|
|
+++++++++++++++++++++++++++++++++++
|
|||
|
|
|
|||
|
|
There are a few rules that needs to be followed in order to write correct
|
|||
|
|
cleanup() callback.
|
|||
|
|
|
|||
|
|
1. Free only resources that were initialized. Keep in mind that callback can
|
|||
|
|
be executed at any point in the test run.
|
|||
|
|
|
|||
|
|
2. Make sure to free resources in the reverse order they were
|
|||
|
|
initialized. (Some of the steps may not depend on others and everything
|
|||
|
|
will work if there were swapped but let's keep it in order.)
|
|||
|
|
|
|||
|
|
The first rule may seem complicated at first however, on the contrary, it's
|
|||
|
|
quite easy. All you have to do is to keep track of what was already
|
|||
|
|
initialized. For example file descriptors needs to be closed only if they were
|
|||
|
|
assigned a valid file descriptor. For most of the things you need to create
|
|||
|
|
extra flag that is set right after successful initialization though. Consider,
|
|||
|
|
for example, test setup below.
|
|||
|
|
|
|||
|
|
We also prefer cleaning up resources that would otherwise be released on the
|
|||
|
|
program exit. There are two main reasons for this decision. Resources such as
|
|||
|
|
file descriptors and mmaped memory could block umounting a block device in
|
|||
|
|
cases where the test library has mounted a filesystem for the test temporary
|
|||
|
|
directory. Not freeing allocated memory would upset static analysis and tools
|
|||
|
|
such as valgrind and produce false-positives when checking for leaks in the
|
|||
|
|
libc and other low level libraries.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
static int fd0, fd1, mount_flag;
|
|||
|
|
|
|||
|
|
#define MNTPOINT "mntpoint"
|
|||
|
|
#define FILE1 "mntpoint/file1"
|
|||
|
|
#define FILE2 "mntpoint/file2"
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
SAFE_MKDIR(MNTPOINT, 0777);
|
|||
|
|
SAFE_MKFS(tst_device->dev, tst_device->fs_type, NULL, NULL);
|
|||
|
|
SAFE_MOUNT(tst_device->dev, MNTPOINT, tst_device->fs_type, 0, 0);
|
|||
|
|
mount_flag = 1;
|
|||
|
|
|
|||
|
|
fd0 = SAFE_OPEN(cleanup, FILE1, O_CREAT | O_RDWR, 0666);
|
|||
|
|
fd1 = SAFE_OPEN(cleanup, FILE2, O_CREAT | O_RDWR, 0666);
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
In this case the 'cleanup()' function may be invoked when any of the 'SAFE_*'
|
|||
|
|
macros has failed and therefore must be able to work with unfinished
|
|||
|
|
initialization as well. Since global variables are initialized to zero we can
|
|||
|
|
just check that fd > 0 before we attempt to close it. The mount function
|
|||
|
|
requires extra flag to be set after device was successfully mounted.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
static void cleanup(void)
|
|||
|
|
{
|
|||
|
|
if (fd1 > 0)
|
|||
|
|
SAFE_CLOSE(fd1);
|
|||
|
|
|
|||
|
|
if (fd0 > 0)
|
|||
|
|
SAFE_CLOSE(fd0);
|
|||
|
|
|
|||
|
|
if (mount_flag && tst_umouont(MNTPOINT))
|
|||
|
|
tst_res(TWARN | TERRNO, "umount(%s)", MNTPOINT);
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
IMPORTANT: 'SAFE_MACROS()' used in cleanup *do not* exit the test. Failure
|
|||
|
|
only produces a warning and the 'cleanup()' carries on. This is
|
|||
|
|
intentional as we want to execute as much 'cleanup()' as possible.
|
|||
|
|
|
|||
|
|
WARNING: Calling tst_brk() in test 'cleanup()' does not exit the test as well
|
|||
|
|
and 'TBROK' is converted to 'TWARN'.
|
|||
|
|
|
|||
|
|
NOTE: Creation and removal of the test temporary directory is handled in
|
|||
|
|
the test library and the directory is removed recursively. Therefore
|
|||
|
|
we do not have to remove files and directories in the test cleanup.
|
|||
|
|
|
|||
|
|
1.2 Basic test interface
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
void tst_res(int ttype, char *arg_fmt, ...);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Printf-like function to report test result, it's mostly used with ttype:
|
|||
|
|
|
|||
|
|
|==============================
|
|||
|
|
| 'TPASS' | Test has passed.
|
|||
|
|
| 'TFAIL' | Test has failed.
|
|||
|
|
| 'TINFO' | General message.
|
|||
|
|
| 'TWARN' | Something went wrong but we decided to continue. Mostly used in cleanup functions.
|
|||
|
|
|==============================
|
|||
|
|
|
|||
|
|
The 'ttype' can be combined bitwise with 'TERRNO' or 'TTERRNO' to print
|
|||
|
|
'errno', 'TST_ERR' respectively.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
void tst_brk(int ttype, char *arg_fmt, ...);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Printf-like function to report error and exit the test, it can be used with ttype:
|
|||
|
|
|
|||
|
|
|============================================================
|
|||
|
|
| 'TBROK' | Something has failed in test preparation phase.
|
|||
|
|
| 'TCONF' | Test is not appropriate for current configuration
|
|||
|
|
(syscall not implemented, unsupported arch, ...)
|
|||
|
|
|============================================================
|
|||
|
|
|
|||
|
|
The 'ttype' can be combined bitwise with 'TERRNO' or 'TTERRNO' to print
|
|||
|
|
'errno', 'TST_ERR' respectively.
|
|||
|
|
|
|||
|
|
There are also 'TST_EXP_*()' macros that can simplify syscall unit tests to a
|
|||
|
|
single line, use them whenever possible. These macros take a function call as
|
|||
|
|
the first parameter and a printf-like format string and parameters as well.
|
|||
|
|
These test macros then expand to a code that runs the call, checks the return
|
|||
|
|
value and errno and reports the test result.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
static void test(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
TST_EXP_PASS(stat(fname, &statbuf), "stat(%s, ...)", fname);
|
|||
|
|
|
|||
|
|
if (!TST_PASS)
|
|||
|
|
return;
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The 'TST_EXP_PASS()' can be used for calls that return -1 on failure and 0 on
|
|||
|
|
success. It will check for the return value and reports failure if the return
|
|||
|
|
value is not equal to 0. The call also sets the 'TST_PASS' variable to 1 if
|
|||
|
|
the call succeeeded.
|
|||
|
|
|
|||
|
|
As seen above, this and similar macros take optional variadic arguments. These
|
|||
|
|
begin with a format string and then appropriate values to be formatted.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
static void test(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
TST_EXP_FD(open(fname, O_RDONLY), "open(%s, O_RDONLY)", fname);
|
|||
|
|
|
|||
|
|
SAFE_CLOSE(TST_RET);
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The 'TST_EXP_FD()' is the same as 'TST_EXP_PASS()' the only difference is that
|
|||
|
|
the return value is expected to be a file descriptor so the call passes if
|
|||
|
|
positive integer is returned.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
static void test(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
TST_EXP_FAIL(stat(fname, &statbuf), ENOENT, "stat(%s, ...)", fname);
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The 'TST_EXP_FAIL()' is similar to 'TST_EXP_PASS()' but it fails the test if
|
|||
|
|
the call haven't failed with -1 and 'errno' wasn't set to the expected one
|
|||
|
|
passed as the second argument.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
static void test(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
TST_EXP_FAIL2(msgget(key, flags), EINVAL, "msgget(%i, %i)", key, flags);
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The 'TST_EXP_FAIL2()' is the same as 'TST_EXP_FAIL()' except the return value is
|
|||
|
|
expected to be non-negative integer if call passes. These macros build upon the
|
|||
|
|
+TEST()+ macro and associated variables.
|
|||
|
|
|
|||
|
|
'TST_EXP_FAIL_SILENT()' and 'TST_EXP_FAIL2_SILENT()' variants are less verbose
|
|||
|
|
and do not print TPASS messages when SCALL fails as expected.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
TEST(socket(AF_INET, SOCK_RAW, 1));
|
|||
|
|
if (TST_RET > -1) {
|
|||
|
|
tst_res(TFAIL, "Created raw socket");
|
|||
|
|
SAFE_CLOSE(TST_RET);
|
|||
|
|
} else if (TST_ERR != EPERM) {
|
|||
|
|
tst_res(TFAIL | TTERRNO,
|
|||
|
|
"Failed to create socket for wrong reason");
|
|||
|
|
} else {
|
|||
|
|
tst_res(TPASS | TTERRNO, "Didn't create raw socket");
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The +TEST+ macro sets +TST_RET+ to its argument's return value and +TST_ERR+ to
|
|||
|
|
+errno+. The +TTERNO+ flag can be used to print the error number's symbolic
|
|||
|
|
value.
|
|||
|
|
|
|||
|
|
No LTP library function or macro, except those in 'tst_test_macros.h', will
|
|||
|
|
write to these variables (rule 'LTP-002'). So their values will not be changed
|
|||
|
|
unexpectedly.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
TST_EXP_POSITIVE(wait(&status));
|
|||
|
|
|
|||
|
|
if (!TST_PASS)
|
|||
|
|
return;
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
If the return value of 'wait' is positive. This macro will print a pass result
|
|||
|
|
and set +TST_PASS+ appropriately. If the return value is zero or negative, then
|
|||
|
|
it will print fail. There are many similar macros to those shown here, please
|
|||
|
|
see 'tst_test_macros.h'.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
TST_EXP_EQ_LI(val1, val2);
|
|||
|
|
TST_EXP_EQ_UI(val1, val2);
|
|||
|
|
TST_EXP_EQ_SZ(val1, val2);
|
|||
|
|
TST_EXP_EQ_SSZ(val1, val2);
|
|||
|
|
|
|||
|
|
/* Use as */
|
|||
|
|
TST_EXP_EQ_LI(sig_caught, SIGCHLD);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Set of macros for different integer type comparsions. These macros print the
|
|||
|
|
variable names as well as values in both pass and fail scenarios.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
const char *tst_strsig(int sig);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Return the given signal number's corresponding string.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
const char *tst_strerrno(int err);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Return the given errno number's corresponding string. Using this function to
|
|||
|
|
translate 'errno' values to strings is preferred. You should not use the
|
|||
|
|
'strerror()' function in the testcases.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
const char *tst_strstatus(int status);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Returns string describing the status as returned by 'wait()'.
|
|||
|
|
|
|||
|
|
WARNING: This function is not thread safe.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
void tst_set_max_runtime(int max_runtime);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Allows for setting max_runtime per test iteration dynamically in the test setup(),
|
|||
|
|
the timeout is specified in seconds. There are a few testcases whose runtime
|
|||
|
|
can vary arbitrarily, these can disable timeouts by setting it to
|
|||
|
|
TST_UNLIMITED_RUNTIME.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
void tst_flush(void);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Flush output streams, handling errors appropriately.
|
|||
|
|
|
|||
|
|
This function is rarely needed when you have to flush the output streams
|
|||
|
|
before calling 'fork()' or 'clone()'. Note that the 'SAFE_FORK()' and 'SAFE_CLONE()'
|
|||
|
|
calls this function automatically. See 2.4 FILE buffers and fork() for explanation
|
|||
|
|
why is this needed.
|
|||
|
|
|
|||
|
|
1.3 Test temporary directory
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
If '.needs_tmpdir' is set to '1' in the 'struct tst_test' unique test
|
|||
|
|
temporary is created and it's set as the test working directory. Tests *MUST
|
|||
|
|
NOT* create temporary files outside that directory. The flag is not needed to
|
|||
|
|
be set when use these flags: '.all_filesystems', '.format_device', '.mntpoint',
|
|||
|
|
'.mount_device' '.needs_checkpoints', '.needs_device', '.resource_file'
|
|||
|
|
(these flags imply creating temporary directory).
|
|||
|
|
|
|||
|
|
IMPORTANT: Close all file descriptors (that point to files in test temporary
|
|||
|
|
directory, even the unlinked ones) either in the 'test()' function
|
|||
|
|
or in the test 'cleanup()' otherwise the test may break temporary
|
|||
|
|
directory removal on NFS (look for "NFS silly rename").
|
|||
|
|
|
|||
|
|
1.4 Safe macros
|
|||
|
|
~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Safe macros aim to simplify error checking in test preparation. Instead of
|
|||
|
|
calling system API functions, checking for their return value and aborting the
|
|||
|
|
test if the operation has failed, you just use corresponding safe macro.
|
|||
|
|
|
|||
|
|
Use them whenever it's possible.
|
|||
|
|
|
|||
|
|
Instead of writing:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
fd = open("/dev/null", O_RDONLY);
|
|||
|
|
if (fd < 0)
|
|||
|
|
tst_brk(TBROK | TERRNO, "opening /dev/null failed");
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
You write just:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
fd = SAFE_OPEN("/dev/null", O_RDONLY);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
IMPORTANT: The SAFE_CLOSE() function also sets the passed file descriptor to -1
|
|||
|
|
after it's successfully closed.
|
|||
|
|
|
|||
|
|
They can also simplify reading and writing of sysfs files, you can, for
|
|||
|
|
example, do:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
SAFE_FILE_SCANF("/proc/sys/kernel/pid_max", "%lu", &pid_max);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
See 'include/tst_safe_macros.h', 'include/tst_safe_stdio.h' and
|
|||
|
|
'include/tst_safe_file_ops.h' and 'include/tst_safe_net.h' for a complete list.
|
|||
|
|
|
|||
|
|
1.5 Test specific command line options
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
struct tst_option {
|
|||
|
|
char *optstr;
|
|||
|
|
char **arg;
|
|||
|
|
char *help;
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Test specific command line parameters can be passed with the 'NULL' terminated
|
|||
|
|
array of 'struct tst_option'. The 'optstr' is the command line option i.e. "o"
|
|||
|
|
or "o:" if option has a parameter. Only short options are supported. The 'arg'
|
|||
|
|
is where 'optarg' is stored upon match. If option has no parameter it's set to
|
|||
|
|
non-'NULL' value if option was present. The 'help' is a short help string.
|
|||
|
|
|
|||
|
|
NOTE: The test parameters must not collide with common test parameters defined
|
|||
|
|
in the library the currently used ones are +-i+, +-I+, +-C+, and +-h+.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
int tst_parse_int(const char *str, int *val, int min, int max);
|
|||
|
|
int tst_parse_long(const char *str, long *val, long min, long max);
|
|||
|
|
int tst_parse_float(const char *str, float *val, float min, float max);
|
|||
|
|
int tst_parse_filesize(const char *str, long long *val, long long min, long long max);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Helpers for parsing the strings returned in the 'struct tst_option'.
|
|||
|
|
|
|||
|
|
Helpers return zero on success and 'errno', mostly 'EINVAL' or 'ERANGE', on
|
|||
|
|
failure.
|
|||
|
|
|
|||
|
|
Helpers functions are no-op if 'str' is 'NULL'.
|
|||
|
|
|
|||
|
|
The valid range for result includes both 'min' and 'max'.
|
|||
|
|
|
|||
|
|
In particular, 'tst_parse_filesize' function accepts prefix multiplies such as
|
|||
|
|
"k/K for kilobytes, "m/M" for megabytes and "g/G" for gigabytes. For example,
|
|||
|
|
10K are converted into 10240 bytes.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include <limits.h>
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static char *str_threads;
|
|||
|
|
static int threads = 10;
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
if (tst_parse_int(str_threads, &threads, 1, INT_MAX))
|
|||
|
|
tst_brk(TBROK, "Invalid number of threads '%s'", str_threads);
|
|||
|
|
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void test_threads(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
for (i = 0; i < threads; i++) {
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.options = (struct tst_option[]) {
|
|||
|
|
{"t:", &str_threads, "Number of threads (default 10)"},
|
|||
|
|
{},
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
|
|||
|
|
1.6 Runtime kernel version detection
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Testcases for newly added kernel functionality require kernel newer than a
|
|||
|
|
certain version to run. All you need to skip a test on older kernels is to
|
|||
|
|
set the '.min_kver' string in the 'struct tst_test' to a minimal required
|
|||
|
|
kernel version, e.g. '.min_kver = "4.10.0"'.
|
|||
|
|
|
|||
|
|
For more complicated operations such as skipping a test for a certain range
|
|||
|
|
of kernel versions, following functions could be used:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
int tst_kvercmp(int r1, int r2, int r3);
|
|||
|
|
|
|||
|
|
struct tst_kern_exv {
|
|||
|
|
char *dist_name;
|
|||
|
|
char *extra_ver;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
int tst_kvercmp2(int r1, int r2, int r3, struct tst_kern_exv *vers);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
These two functions are intended for runtime kernel version detection. They
|
|||
|
|
parse the output from 'uname()' and compare it to the passed values.
|
|||
|
|
|
|||
|
|
The return value is similar to the 'strcmp()' function, i.e. zero means equal,
|
|||
|
|
negative value means that the kernel is older than than the expected value and
|
|||
|
|
positive means that it's newer.
|
|||
|
|
|
|||
|
|
The second function 'tst_kvercmp2()' allows for specifying per-vendor table of
|
|||
|
|
kernel versions as vendors typically backport fixes to their kernels and the
|
|||
|
|
test may be relevant even if the kernel version does not suggests so.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
if (tst_kvercmp(5, 19, 0) >= 0)
|
|||
|
|
tst_res(TCONF, "Test valid only for kernel < 5.19");
|
|||
|
|
|
|||
|
|
static struct tst_kern_exv kvers[] = {
|
|||
|
|
{ "UBUNTU", "4.4.0-48.69" },
|
|||
|
|
{ NULL, NULL},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (tst_kvercmp2(4, 4, 27, kvers) < 0)
|
|||
|
|
/* code for kernel < v4.4.27 or ubuntu kernel < 4.4.0-48.69 */
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
WARNING: The shell 'tst_kvercmp' maps the result into unsigned integer - the
|
|||
|
|
process exit value.
|
|||
|
|
|
|||
|
|
NOTE: See also LTP
|
|||
|
|
https://github.com/linux-test-project/ltp/wiki/Supported-kernel,-libc,-toolchain-versions#13-minimal-supported-kernel-version[minimal supported kernel version].
|
|||
|
|
|
|||
|
|
1.7 Fork()-ing
|
|||
|
|
~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Be wary that if the test forks and there were messages printed by the
|
|||
|
|
'tst_*()' interfaces, the data may still be in libc/kernel buffers and these
|
|||
|
|
*ARE NOT* flushed automatically.
|
|||
|
|
|
|||
|
|
This happens when 'stdout' gets redirected to a file. In this case, the
|
|||
|
|
'stdout' is not line buffered, but block buffered. Hence after a fork content
|
|||
|
|
of the buffers will be printed by the parent and each of the children.
|
|||
|
|
|
|||
|
|
To avoid that you should use 'SAFE_FORK()', 'SAFE_CLONE()' or 'tst_clone()'.
|
|||
|
|
|
|||
|
|
IMPORTANT: You have to set the '.forks_child' flag in the test structure
|
|||
|
|
if your testcase forks or calls 'SAFE_CLONE()'.
|
|||
|
|
|
|||
|
|
1.8 Doing the test in the child process
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Results reported by 'tst_res()' are propagated to the parent test process via
|
|||
|
|
block of shared memory.
|
|||
|
|
|
|||
|
|
Calling 'tst_brk()' causes child process to exit with non-zero exit value.
|
|||
|
|
Which means that it's safe to use 'SAFE_*()' macros in the child processes as
|
|||
|
|
well.
|
|||
|
|
|
|||
|
|
Children that outlive the 'test()' function execution are waited for in the
|
|||
|
|
test library. Unclean child exit (killed by signal, non-zero exit value, etc.)
|
|||
|
|
will cause the main test process to exit with 'tst_brk()', which especially
|
|||
|
|
means that 'TBROK' propagated from a child process will cause the whole test
|
|||
|
|
to exit with 'TBROK'.
|
|||
|
|
|
|||
|
|
If a test needs a child that segfaults or does anything else that cause it to
|
|||
|
|
exit uncleanly all you need to do is to wait for such children from the
|
|||
|
|
'test()' function so that it's reaped before the main test exits the 'test()'
|
|||
|
|
function.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
void tst_reap_children(void);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The 'tst_reap_children()' function makes the process wait for all of its
|
|||
|
|
children and exits with 'tst_brk(TBROK, ...)' if any of them returned
|
|||
|
|
a non zero exit code.
|
|||
|
|
|
|||
|
|
When using 'SAFE_CLONE' or 'tst_clone', this may not work depending on
|
|||
|
|
the parameters passed to clone. The following call to 'SAFE_CLONE' is
|
|||
|
|
identical to 'fork()', so will work as expected.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
--------------------------------------------------------------------------------
|
|||
|
|
const struct tst_clone_args args = {
|
|||
|
|
.exit_signal = SIGCHLD,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
SAFE_CLONE(&args);
|
|||
|
|
--------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
If 'exit_signal' is set to something else, then this will break
|
|||
|
|
'tst_reap_children'. It's not expected that all parameters to clone will
|
|||
|
|
work with the LTP library unless specific action is taken by the test code.
|
|||
|
|
|
|||
|
|
.Using 'tst_res()' from binaries started by 'exec()'
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
/* test.c */
|
|||
|
|
#define _GNU_SOURCE
|
|||
|
|
#include <unistd.h>
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static void do_test(void)
|
|||
|
|
{
|
|||
|
|
char *const argv[] = {"test_exec_child", NULL};
|
|||
|
|
char path[4096];
|
|||
|
|
|
|||
|
|
if (tst_get_path("test_exec_child", path, sizeof(path)))
|
|||
|
|
tst_brk(TCONF, "Couldn't find test_exec_child in $PATH");
|
|||
|
|
|
|||
|
|
execve(path, argv, environ);
|
|||
|
|
|
|||
|
|
tst_res(TFAIL | TERRNO, "EXEC!");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
.test_all = do_test,
|
|||
|
|
.child_needs_reinit = 1,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/* test_exec_child.c */
|
|||
|
|
#define TST_NO_DEFAULT_MAIN
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int main(void)
|
|||
|
|
{
|
|||
|
|
tst_reinit();
|
|||
|
|
tst_res(TPASS, "Child passed!");
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The 'tst_res()' function can be also used from binaries started by 'exec()',
|
|||
|
|
the parent test process has to set the '.child_needs_reinit' flag so that the
|
|||
|
|
library prepares for it and has to make sure the 'LTP_IPC_PATH' environment
|
|||
|
|
variable is passed down, then the very fist thing the program has to call in
|
|||
|
|
'main()' is 'tst_reinit()' that sets up the IPC.
|
|||
|
|
|
|||
|
|
1.9 Fork() and Parent-child synchronization
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
As LTP tests are written for Linux, most of the tests involve fork()-ing and
|
|||
|
|
parent-child process synchronization. LTP includes a checkpoint library that
|
|||
|
|
provides wait/wake futex based functions.
|
|||
|
|
|
|||
|
|
In order to use checkpoints the '.needs_checkpoints' flag in the 'struct
|
|||
|
|
tst_test' must be set to '1', this causes the test library to initialize
|
|||
|
|
checkpoints before the 'test()' function is called.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
TST_CHECKPOINT_WAIT(id)
|
|||
|
|
|
|||
|
|
TST_CHECKPOINT_WAIT2(id, msec_timeout)
|
|||
|
|
|
|||
|
|
TST_CHECKPOINT_WAKE(id)
|
|||
|
|
|
|||
|
|
TST_CHECKPOINT_WAKE2(id, nr_wake)
|
|||
|
|
|
|||
|
|
TST_CHECKPOINT_WAKE_AND_WAIT(id)
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The checkpoint interface provides pair of wake and wait functions. The 'id' is
|
|||
|
|
unsigned integer which specifies checkpoint to wake/wait for. As a matter of
|
|||
|
|
fact it's an index to an array stored in a shared memory, so it starts on
|
|||
|
|
'0' and there should be enough room for at least of hundred of them.
|
|||
|
|
|
|||
|
|
The 'TST_CHECKPOINT_WAIT()' and 'TST_CHECKPOINT_WAIT2()' suspends process
|
|||
|
|
execution until it's woken up or until timeout is reached.
|
|||
|
|
|
|||
|
|
The 'TST_CHECKPOINT_WAKE()' wakes one process waiting on the checkpoint.
|
|||
|
|
If no process is waiting the function retries until it success or until
|
|||
|
|
timeout is reached.
|
|||
|
|
|
|||
|
|
If timeout has been reached process exits with appropriate error message (uses
|
|||
|
|
'tst_brk()').
|
|||
|
|
|
|||
|
|
The 'TST_CHECKPOINT_WAKE2()' does the same as 'TST_CHECKPOINT_WAKE()' but can
|
|||
|
|
be used to wake precisely 'nr_wake' processes.
|
|||
|
|
|
|||
|
|
The 'TST_CHECKPOINT_WAKE_AND_WAIT()' is a shorthand for doing wake and then
|
|||
|
|
immediately waiting on the same checkpoint.
|
|||
|
|
|
|||
|
|
Child processes created via 'SAFE_FORK()' are ready to use the checkpoint
|
|||
|
|
synchronization functions, as they inherited the mapped page automatically.
|
|||
|
|
|
|||
|
|
Child processes started via 'exec()', or any other processes not forked from
|
|||
|
|
the test process must initialize the checkpoint by calling 'tst_reinit()'.
|
|||
|
|
|
|||
|
|
For the details of the interface, look into the 'include/tst_checkpoint.h'.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
* Waits for process state change.
|
|||
|
|
*
|
|||
|
|
* The state is one of the following:
|
|||
|
|
*
|
|||
|
|
* R - process is running
|
|||
|
|
* S - process is sleeping
|
|||
|
|
* D - process sleeping uninterruptibly
|
|||
|
|
* Z - zombie process
|
|||
|
|
* T - process is traced
|
|||
|
|
*/
|
|||
|
|
TST_PROCESS_STATE_WAIT(pid, state, msec_timeout)
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The 'TST_PROCESS_STATE_WAIT()' waits until process 'pid' is in requested
|
|||
|
|
'state' or timeout is reached. The call polls +/proc/pid/stat+ to get this
|
|||
|
|
information. A timeout of 0 will wait infinitely.
|
|||
|
|
|
|||
|
|
On timeout -1 is returned and errno set to ETIMEDOUT.
|
|||
|
|
|
|||
|
|
It's mostly used with state 'S' which means that process is sleeping in kernel
|
|||
|
|
for example in 'pause()' or any other blocking syscall.
|
|||
|
|
|
|||
|
|
1.10 Signals and signal handlers
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
If you need to use signal handlers, keep the code short and simple. Don't
|
|||
|
|
forget that the signal handler is called asynchronously and can interrupt the
|
|||
|
|
code execution at any place.
|
|||
|
|
|
|||
|
|
This means that problems arise when global state is changed both from the test
|
|||
|
|
code and signal handler, which will occasionally lead to:
|
|||
|
|
|
|||
|
|
* Data corruption (data gets into inconsistent state), this may happen, for
|
|||
|
|
example, for any operations on 'FILE' objects.
|
|||
|
|
|
|||
|
|
* Deadlock, this happens, for example, if you call 'malloc(2)', 'free(2)',
|
|||
|
|
etc. from both the test code and the signal handler at the same time since
|
|||
|
|
'malloc' has global lock for it's internal data structures. (Be wary that
|
|||
|
|
'malloc(2)' is used by the libc functions internally too.)
|
|||
|
|
|
|||
|
|
* Any other unreproducible and unexpected behavior.
|
|||
|
|
|
|||
|
|
Quite common mistake is to call 'exit(3)' from a signal handler. Note that this
|
|||
|
|
function is not signal-async-safe as it flushes buffers, etc. If you need to
|
|||
|
|
exit a test immediately from a signal handler use '_exit(2)' instead.
|
|||
|
|
|
|||
|
|
TIP: See 'man 7 signal' for the list of signal-async-safe functions.
|
|||
|
|
|
|||
|
|
If a signal handler sets a variable, its declaration must be 'volatile',
|
|||
|
|
otherwise compiler may misoptimize the code. This is because the variable may
|
|||
|
|
not be changed in the compiler code flow analysis. There is 'sig_atomic_t'
|
|||
|
|
type defined in C99 but this one *DOES NOT* imply 'volatile' (it's just a
|
|||
|
|
'typedef' to 'int'). So the correct type for a flag that is changed from a
|
|||
|
|
signal handler is either 'volatile int' or 'volatile sig_atomic_t'.
|
|||
|
|
|
|||
|
|
If a crash (e.g. triggered by signal SIGSEGV) is expected in testing, you
|
|||
|
|
can avoid creation of core files by calling tst_no_corefile() function.
|
|||
|
|
This takes effect for process (and its children) which invoked it, unless
|
|||
|
|
they subsequently modify RLIMIT_CORE.
|
|||
|
|
|
|||
|
|
Note that LTP library will reap any processes that test didn't reap itself,
|
|||
|
|
and report any non-zero exit code as failure.
|
|||
|
|
|
|||
|
|
1.11 Kernel Modules
|
|||
|
|
~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
There are certain cases where the test needs a kernel part and userspace part,
|
|||
|
|
happily, LTP can build a kernel module and then insert it to the kernel on test
|
|||
|
|
start for you. See 'testcases/kernel/device-drivers/block' for details.
|
|||
|
|
|
|||
|
|
1.12 Useful macros
|
|||
|
|
~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
These macros are defined in 'include/tst_common.h'.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
ARRAY_SIZE(arr)
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Returns the size of statically defined array, i.e.
|
|||
|
|
'(sizeof(arr) / sizeof(*arr))'
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
LTP_ALIGN(x, a)
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Aligns the x to be next multiple of a. The a must be power of 2.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
TST_TO_STR(s) /* stringification */
|
|||
|
|
TST_TO_STR_(s) /* macro expansion */
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Macros for stringification.
|
|||
|
|
|
|||
|
|
1.13 Filesystem type detection and skiplist
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some tests are known to fail on certain filesystems (you cannot swap on TMPFS,
|
|||
|
|
there are unimplemented 'fcntl()' etc.).
|
|||
|
|
|
|||
|
|
If your test needs to be skipped on certain filesystems use the
|
|||
|
|
'.skip_filesystems' field in the tst_test structure as follows:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.skip_filesystems = (const char *const []) {
|
|||
|
|
"tmpfs",
|
|||
|
|
"ramfs",
|
|||
|
|
"nfs",
|
|||
|
|
NULL
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
When the '.all_filesystems' flag is set the '.skip_filesystems' list is passed
|
|||
|
|
to the function that detects supported filesystems any listed filesystem is
|
|||
|
|
not included in the resulting list of supported filesystems.
|
|||
|
|
|
|||
|
|
If test needs to adjust expectations based on filesystem type it's also
|
|||
|
|
possible to detect filesystem type at the runtime. This is preferably used
|
|||
|
|
when only subset of the test is not applicable for a given filesystem.
|
|||
|
|
|
|||
|
|
NOTE: ext2, ext3 or ext4 in '.skip_filesystems' on tests which does *not* use
|
|||
|
|
'.all_filesystems' needs to be defined as 'ext2/ext3/ext4'. The reason
|
|||
|
|
is that it is hard to detect used filesystem due to overlapping the functionality.
|
|||
|
|
OTOH tests which use '.skip_filesystems' *with* '.all_filesystems' can skip
|
|||
|
|
only filesystems which are actually used in '.all_filesystems': ext2, ext3,
|
|||
|
|
ext4, xfs, btrfs, vfat, exfat, ntfs, tmpfs (defined in 'fs_type_whitelist[]').
|
|||
|
|
It does not make sense to list other filesystems.
|
|||
|
|
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
switch ((type = tst_fs_type("."))) {
|
|||
|
|
case TST_NFS_MAGIC:
|
|||
|
|
case TST_TMPFS_MAGIC:
|
|||
|
|
case TST_RAMFS_MAGIC:
|
|||
|
|
tst_brk(TCONF, "Subtest not supported on %s",
|
|||
|
|
tst_fs_type_name(type));
|
|||
|
|
return;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.14 Thread-safety in the LTP library
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
It is safe to use library 'tst_res()' function in multi-threaded tests.
|
|||
|
|
|
|||
|
|
Only the main thread must return from the 'test()' function to the test
|
|||
|
|
library and that must be done only after all threads that may call any library
|
|||
|
|
function has been terminated. That especially means that threads that may call
|
|||
|
|
'tst_brk()' must terminate before the execution of the 'test()' function
|
|||
|
|
returns to the library. This is usually done by the main thread joining all
|
|||
|
|
worker threads at the end of the 'test()' function. Note that the main thread
|
|||
|
|
will never get to the library code in a case that 'tst_brk()' was called from
|
|||
|
|
one of the threads since it will sleep at least in 'pthread_join()' on the
|
|||
|
|
thread that called the 'tst_brk()' till 'exit()' is called by 'tst_brk()'.
|
|||
|
|
|
|||
|
|
The test-supplied cleanup function runs *concurrently* to the rest of the
|
|||
|
|
threads in a case that cleanup was entered from 'tst_brk()'. Subsequent
|
|||
|
|
threads entering 'tst_brk()' must be suspended or terminated at the start of
|
|||
|
|
the user supplied cleanup function. It may be necessary to stop or exit
|
|||
|
|
the rest of the threads before the test cleans up as well. For example threads
|
|||
|
|
that create new files should be stopped before temporary directory is be
|
|||
|
|
removed.
|
|||
|
|
|
|||
|
|
Following code example shows thread safe cleanup function example using atomic
|
|||
|
|
increment as a guard. The library calls its cleanup after the execution returns
|
|||
|
|
from the user supplied cleanup and expects that only one thread returns from
|
|||
|
|
the user supplied cleanup to the test library.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static void cleanup(void)
|
|||
|
|
{
|
|||
|
|
static int flag;
|
|||
|
|
|
|||
|
|
if (tst_atomic_inc(&flag) != 1)
|
|||
|
|
pthread_exit(NULL);
|
|||
|
|
|
|||
|
|
/* if needed stop the rest of the threads here */
|
|||
|
|
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
/* then do cleanup work */
|
|||
|
|
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
/* only one thread returns to the library */
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
|
|||
|
|
1.15 Testing with a block device
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some tests needs a block device (inotify tests, syscall 'EROFS' failures,
|
|||
|
|
etc.). LTP library contains a code to prepare a testing device.
|
|||
|
|
|
|||
|
|
If '.needs_device' flag in the 'struct tst_test' is set the 'tst_device'
|
|||
|
|
structure is initialized with a path to a test device and default filesystem
|
|||
|
|
to be used.
|
|||
|
|
|
|||
|
|
You can also request minimal device size in megabytes by setting
|
|||
|
|
'.dev_min_size' the device is guaranteed to have at least the requested size
|
|||
|
|
then.
|
|||
|
|
|
|||
|
|
If '.format_device' flag is set the device is formatted with a filesystem as
|
|||
|
|
well. You can use '.dev_fs_type' to override the default filesystem type if
|
|||
|
|
needed and pass additional options to mkfs via '.dev_fs_opts' and
|
|||
|
|
'.dev_extra_opts' pointers. Note that '.format_device' implies '.needs_device'
|
|||
|
|
there is no need to set both.
|
|||
|
|
|
|||
|
|
If '.mount_device' is set, the device is mounted at '.mntpoint' which is used
|
|||
|
|
to pass a directory name that will be created and used as mount destination.
|
|||
|
|
You can pass additional flags and data to the mount command via '.mnt_flags'
|
|||
|
|
and '.mnt_data' pointers. Note that '.mount_device' implies '.needs_device'
|
|||
|
|
and '.format_device' so there is no need to set the later two.
|
|||
|
|
|
|||
|
|
If '.needs_rofs' is set, read-only filesystem is mounted at '.mntpoint' this
|
|||
|
|
one is supposed to be used for 'EROFS' tests.
|
|||
|
|
|
|||
|
|
If '.all_filesystems' is set the test function is executed for all supported
|
|||
|
|
filesystems. Supported filesystems are detected based on existence of the
|
|||
|
|
'mkfs.$fs' helper and on kernel support to mount it. For each supported
|
|||
|
|
filesystem the 'tst_device.fs_type' is set to the currently tested fs type, if
|
|||
|
|
'.format_device' is set the device is formatted as well, if '.mount_device' is
|
|||
|
|
set it's mounted at '.mntpoint'. Also the test timeout is reset for each
|
|||
|
|
execution of the test function. This flag is expected to be used for filesystem
|
|||
|
|
related syscalls that are at least partly implemented in the filesystem
|
|||
|
|
specific code e.g. 'fallocate()'.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
struct tst_device {
|
|||
|
|
const char *dev;
|
|||
|
|
const char *fs_type;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
extern struct tst_device *tst_device;
|
|||
|
|
|
|||
|
|
int tst_umount(const char *path);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
In case that 'LTP_DEV' is passed to the test in an environment, the library
|
|||
|
|
checks that the file exists and that it's a block device, if
|
|||
|
|
'.device_min_size' is set the device size is checked as well. If 'LTP_DEV'
|
|||
|
|
wasn't set or if size requirements were not met a temporary file is created
|
|||
|
|
and attached to a free loop device.
|
|||
|
|
|
|||
|
|
If there is no usable device and loop device couldn't be initialized the test
|
|||
|
|
exits with 'TCONF'.
|
|||
|
|
|
|||
|
|
The 'tst_umount()' function works exactly as 'umount(2)' but retries several
|
|||
|
|
times on 'EBUSY'. This is because various desktop daemons (gvfsd-trash is known
|
|||
|
|
for that) may be stupid enough to probe all newly mounted filesystem which
|
|||
|
|
results in 'umount(2)' failing with 'EBUSY'.
|
|||
|
|
|
|||
|
|
IMPORTANT: All testcases should use 'tst_umount()' instead of 'umount(2)' to
|
|||
|
|
umount filesystems.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_find_free_loopdev(const char *path, size_t path_len);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
This function finds a free loopdev and returns the free loopdev minor (-1 for no
|
|||
|
|
free loopdev). If path is non-NULL, it will be filled with free loopdev path.
|
|||
|
|
If you want to use a customized loop device, we can call 'tst_find_free_loopdev(NULL, 0)'
|
|||
|
|
in tests to get a free minor number and then mknod.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
unsigned long tst_dev_bytes_written(const char *dev);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
This function reads test block device stat file ('/sys/block/<device>/stat') and
|
|||
|
|
returns the bytes written since the last invocation of this function. To avoid
|
|||
|
|
FS deferred IO metadata/cache interference, we suggest doing "syncfs" before the
|
|||
|
|
tst_dev_bytes_written first invocation. And an inline function named 'tst_dev_sync()'
|
|||
|
|
is created for that intention.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
void tst_find_backing_dev(const char *path, char *dev, size_t dev_size);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
This function finds the block dev that this path belongs to, using uevent in sysfs.
|
|||
|
|
For Btrfs it uses '/sys/fs/btrfs/UUID/devices/DEV_NAME/uevent'; for other
|
|||
|
|
filesystems it uses '/sys/dev/block/MAJOR:MINOR/uevent'.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
uint64_t tst_get_device_size(const char *dev_path);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
This function gets size of the given block device, it checks the 'dev_path' is
|
|||
|
|
valid first, if yes, return the size in MB, otherwise return -1.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_dev_block_size(const char *path);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
This function returns the physical device block size for the specific `path`.
|
|||
|
|
It finds the device where `path` is located and then uses `ioctl` (BLKSSZGET)
|
|||
|
|
to get a physical device block size.
|
|||
|
|
|
|||
|
|
1.16 Formatting a device with a filesystem
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
SAFE_MKFS(tst_device->dev, tst_device->fs_type, NULL, NULL);
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
This function takes a path to a device, filesystem type and an array of extra
|
|||
|
|
options passed to mkfs.
|
|||
|
|
|
|||
|
|
The fs options 'fs_opts' should either be 'NULL' if there are none, or a
|
|||
|
|
'NULL' terminated array of strings such as:
|
|||
|
|
+const char *const opts[] = {"-b", "1024", NULL}+.
|
|||
|
|
|
|||
|
|
The extra options 'extra_opts' should either be 'NULL' if there are none, or a
|
|||
|
|
'NULL' terminated array of strings such as +{"102400", NULL}+; 'extra_opts'
|
|||
|
|
will be passed after device name. e.g: +mkfs -t ext4 -b 1024 /dev/sda1 102400+
|
|||
|
|
in this case.
|
|||
|
|
|
|||
|
|
Note that perfer to store the options which can be passed before or after device
|
|||
|
|
name by 'fs_opts' array.
|
|||
|
|
|
|||
|
|
1.17 Verifying a filesystem's free space
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some tests have size requirements for the filesystem's free space. If these
|
|||
|
|
requirements are not satisfied, the tests should be skipped.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_fs_has_free(const char *path, unsigned int size, unsigned int mult);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The 'tst_fs_has_free()' function returns 1 if there is enough space and 0 if
|
|||
|
|
there is not.
|
|||
|
|
|
|||
|
|
The 'path' is the pathname of any directory/file within a filesystem.
|
|||
|
|
|
|||
|
|
The 'mult' is a multiplier, one of 'TST_BYTES', 'TST_KB', 'TST_MB' or 'TST_GB'.
|
|||
|
|
|
|||
|
|
The required free space is calculated by 'size * mult', e.g.
|
|||
|
|
'tst_fs_has_free("/tmp/testfile", 64, TST_MB)' will return 1 if the
|
|||
|
|
filesystem, which '"/tmp/testfile"' is in, has 64MB free space at least, and 0
|
|||
|
|
if not.
|
|||
|
|
|
|||
|
|
1.18 Files, directories and fs limits
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some tests need to know the maximum count of links to a regular file or
|
|||
|
|
directory, such as 'rename(2)' or 'linkat(2)' to test 'EMLINK' error.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_fs_fill_hardlinks(const char *dir);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Try to get maximum count of hard links to a regular file inside the 'dir'.
|
|||
|
|
|
|||
|
|
NOTE: This number depends on the filesystem 'dir' is on.
|
|||
|
|
|
|||
|
|
This function uses 'link(2)' to create hard links to a single file until it
|
|||
|
|
gets 'EMLINK' or creates 65535 links. If the limit is hit, the maximum number of
|
|||
|
|
hardlinks is returned and the 'dir' is filled with hardlinks in format
|
|||
|
|
"testfile%i", where i belongs to [0, limit) interval. If no limit is hit or if
|
|||
|
|
'link(2)' failed with 'ENOSPC' or 'EDQUOT', zero is returned and previously
|
|||
|
|
created files are removed.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_fs_fill_subdirs(const char *dir);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Try to get maximum number of subdirectories in directory.
|
|||
|
|
|
|||
|
|
NOTE: This number depends on the filesystem 'dir' is on. For current kernel,
|
|||
|
|
subdir limit is not available for all filesystems (available for ext2, ext3,
|
|||
|
|
minix, sysv and more). If the test runs on some other filesystems, like ramfs,
|
|||
|
|
tmpfs, it will not even try to reach the limit and return 0.
|
|||
|
|
|
|||
|
|
This function uses 'mkdir(2)' to create directories in 'dir' until it gets
|
|||
|
|
'EMLINK' or creates 65535 directories. If the limit is hit, the maximum number
|
|||
|
|
of subdirectories is returned and the 'dir' is filled with subdirectories in
|
|||
|
|
format "testdir%i", where i belongs to [0, limit - 2) interval (because each
|
|||
|
|
newly created dir has two links already - the '.' and the link from parent
|
|||
|
|
dir). If no limit is hit or if 'mkdir(2)' failed with 'ENOSPC' or 'EDQUOT',
|
|||
|
|
zero is returned and previously created directories are removed.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_dir_is_empty(const char *dir, int verbose);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Returns non-zero if directory is empty and zero otherwise.
|
|||
|
|
|
|||
|
|
Directory is considered empty if it contains only '.' and '..'.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
void tst_purge_dir(const char *path);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Deletes the contents of given directory but keeps the directory itself. Useful
|
|||
|
|
for cleaning up the temporary directory and mount points between test cases or
|
|||
|
|
test iterations. Terminates the program with 'TBROK' on error.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_fill_fd(int fd, char pattern, size_t bs, size_t bcount);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Fill a file with specified pattern using file descriptor.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_prealloc_size_fd(int fd, size_t bs, size_t bcount);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Preallocate the specified amount of space using 'fallocate()'. Falls back to
|
|||
|
|
'tst_fill_fd()' if 'fallocate()' fails.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_fill_file(const char *path, char pattern, size_t bs, size_t bcount);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Creates/overwrites a file with specified pattern using file path.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_prealloc_file(const char *path, size_t bs, size_t bcount);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Create/overwrite a file and preallocate the specified amount of space for it.
|
|||
|
|
The allocated space will not be initialized to any particular content.
|
|||
|
|
|
|||
|
|
1.19 Getting an unused PID number
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some tests require a 'PID', which is not used by the OS (does not belong to
|
|||
|
|
any process within it). For example, kill(2) should set errno to 'ESRCH' if
|
|||
|
|
it's passed such 'PID'.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
pid_t tst_get_unused_pid(void);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Return a 'PID' value not used by the OS or any process within it.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_get_free_pids(void);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Returns number of unused pids in the system. Note that this number may be
|
|||
|
|
different once the call returns and should be used only for rough estimates.
|
|||
|
|
|
|||
|
|
1.20 Running executables
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
int tst_cmd(const char *const argv[],
|
|||
|
|
const char *stdout_path,
|
|||
|
|
const char *stderr_path,
|
|||
|
|
enum tst_cmd_flags flags);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
'tst_cmd()' is a wrapper for 'vfork() + execvp()' which provides a way
|
|||
|
|
to execute an external program.
|
|||
|
|
|
|||
|
|
'argv[]' is a 'NULL' terminated array of strings starting with the program name
|
|||
|
|
which is followed by optional arguments.
|
|||
|
|
|
|||
|
|
'TST_CMD_PASS_RETVAL' enum 'tst_cmd_flags' makes 'tst_cmd()'
|
|||
|
|
return the program exit code to the caller, otherwise 'tst_cmd()' exit the
|
|||
|
|
tests on failure. 'TST_CMD_TCONF_ON_MISSING' check for program in '$PATH' and exit
|
|||
|
|
with 'TCONF' if not found.
|
|||
|
|
|
|||
|
|
In case that 'execvp()' has failed and the enum 'TST_CMD_PASS_RETVAL' flag was set, the
|
|||
|
|
return value is '255' if 'execvp()' failed with 'ENOENT' and '254' otherwise.
|
|||
|
|
|
|||
|
|
'stdout_path' and 'stderr_path' determine where to redirect the program
|
|||
|
|
stdout and stderr I/O streams.
|
|||
|
|
|
|||
|
|
The 'SAFE_CMD()' macro can be used automatic handling non-zero exits (exits
|
|||
|
|
with 'TBROK') and 'ENOENT' (exits with 'TCONF').
|
|||
|
|
|
|||
|
|
.Example
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
const char *const cmd[] = { "ls", "-l", NULL };
|
|||
|
|
|
|||
|
|
...
|
|||
|
|
/* Store output of 'ls -l' into log.txt */
|
|||
|
|
tst_cmd(cmd, "log.txt", NULL, 0);
|
|||
|
|
...
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.21 Measuring elapsed time and helper functions
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_timer.h"
|
|||
|
|
|
|||
|
|
void tst_timer_check(clockid_t clk_id);
|
|||
|
|
|
|||
|
|
void tst_timer_start(clockid_t clk_id);
|
|||
|
|
|
|||
|
|
void tst_timer_stop(void);
|
|||
|
|
|
|||
|
|
struct timespec tst_timer_elapsed(void);
|
|||
|
|
|
|||
|
|
long long tst_timer_elapsed_ms(void);
|
|||
|
|
|
|||
|
|
long long tst_timer_elapsed_us(void);
|
|||
|
|
|
|||
|
|
int tst_timer_expired_ms(long long ms);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The 'tst_timer_check()' function checks if specified 'clk_id' is supported and
|
|||
|
|
exits the test with 'TCONF' otherwise. It's expected to be used in test
|
|||
|
|
'setup()' before any resources that needs to be cleaned up are initialized,
|
|||
|
|
hence it does not include a cleanup function parameter.
|
|||
|
|
|
|||
|
|
The 'tst_timer_start()' marks start time and stores the 'clk_id' for further
|
|||
|
|
use.
|
|||
|
|
|
|||
|
|
The 'tst_timer_stop()' marks the stop time using the same 'clk_id' as last
|
|||
|
|
call to 'tst_timer_start()'.
|
|||
|
|
|
|||
|
|
The 'tst_timer_elapsed*()' returns time difference between the timer start and
|
|||
|
|
last timer stop in several formats and units.
|
|||
|
|
|
|||
|
|
The 'tst_timer_expired_ms()' function checks if the timer started by
|
|||
|
|
'tst_timer_start()' has been running longer than ms milliseconds. The function
|
|||
|
|
returns non-zero if timer has expired and zero otherwise.
|
|||
|
|
|
|||
|
|
IMPORTANT: The timer functions use 'clock_gettime()' internally which needs to
|
|||
|
|
be linked with '-lrt' on older glibc. Please do not forget to add
|
|||
|
|
'LDLIBS+=-lrt' in Makefile.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
#include "tst_timer.h"
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
tst_timer_check(CLOCK_MONOTONIC);
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
tst_timer_start(CLOCK_MONOTONIC);
|
|||
|
|
...
|
|||
|
|
while (!tst_timer_expired_ms(5000)) {
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.setup = setup,
|
|||
|
|
.test_all = run,
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Expiration timer example usage.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
long long tst_timespec_to_us(struct timespec t);
|
|||
|
|
long long tst_timespec_to_ms(struct timespec t);
|
|||
|
|
|
|||
|
|
struct timeval tst_us_to_timeval(long long us);
|
|||
|
|
struct timeval tst_ms_to_timeval(long long ms);
|
|||
|
|
|
|||
|
|
int tst_timespec_lt(struct timespec t1, struct timespec t2);
|
|||
|
|
|
|||
|
|
struct timespec tst_timespec_add_us(struct timespec t, long long us);
|
|||
|
|
|
|||
|
|
struct timespec tst_timespec_diff(struct timespec t1, struct timespec t2);
|
|||
|
|
long long tst_timespec_diff_us(struct timespec t1, struct timespec t2);
|
|||
|
|
long long tst_timespec_diff_ms(struct timespec t1, struct timespec t2);
|
|||
|
|
|
|||
|
|
struct timespec tst_timespec_abs_diff(struct timespec t1, struct timespec t2);
|
|||
|
|
long long tst_timespec_abs_diff_us(struct timespec t1, struct timespec t2);
|
|||
|
|
long long tst_timespec_abs_diff_ms(struct timespec t1, struct timespec t2);
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
The first four functions are simple inline conversion functions.
|
|||
|
|
|
|||
|
|
The 'tst_timespec_lt()' function returns non-zero if 't1' is earlier than
|
|||
|
|
't2'.
|
|||
|
|
|
|||
|
|
The 'tst_timespec_add_us()' function adds 'us' microseconds to the timespec
|
|||
|
|
't'. The 'us' is expected to be positive.
|
|||
|
|
|
|||
|
|
The 'tst_timespec_diff*()' functions returns difference between two times, the
|
|||
|
|
't1' is expected to be later than 't2'.
|
|||
|
|
|
|||
|
|
The 'tst_timespec_abs_diff*()' functions returns absolute value of difference
|
|||
|
|
between two times.
|
|||
|
|
|
|||
|
|
NOTE: All conversions to ms and us rounds the value.
|
|||
|
|
|
|||
|
|
1.22 Datafiles
|
|||
|
|
~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static const char *const res_files[] = {
|
|||
|
|
"foo",
|
|||
|
|
"bar",
|
|||
|
|
NULL
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.resource_files = res_files,
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
If the test needs additional files to be copied to the test temporary
|
|||
|
|
directory all you need to do is to list their filenames in the
|
|||
|
|
'NULL' terminated array '.resource_files' in the tst_test structure.
|
|||
|
|
|
|||
|
|
When resource files is set test temporary directory is created automatically,
|
|||
|
|
there is need to set '.needs_tmpdir' as well.
|
|||
|
|
|
|||
|
|
The test library looks for datafiles first, these are either stored in a
|
|||
|
|
directory called +datafiles+ in the +$PWD+ at the start of the test or in
|
|||
|
|
+$LTPROOT/testcases/data/${test_binary_name}+. If the file is not found the
|
|||
|
|
library looks into +$LTPROOT/testcases/bin/+ and to +$PWD+ at the start of the
|
|||
|
|
test. This ensures that the testcases can copy the file(s) effortlessly both
|
|||
|
|
when test is started from the directory it was compiled in as well as when LTP
|
|||
|
|
was installed.
|
|||
|
|
|
|||
|
|
The file(s) are copied to the newly created test temporary directory which is
|
|||
|
|
set as the test working directory when the 'test()' functions is executed.
|
|||
|
|
|
|||
|
|
1.23 Code path tracing
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
'tst_res' is a macro, so on when you define a function in one file:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
int do_action(int arg)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
if (ok) {
|
|||
|
|
tst_res(TPASS, "check passed");
|
|||
|
|
return 0;
|
|||
|
|
} else {
|
|||
|
|
tst_res(TFAIL, "check failed");
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
and call it from another file, the file and line reported by 'tst_res' in this
|
|||
|
|
function will be from the former file.
|
|||
|
|
|
|||
|
|
'TST_TRACE' can make the analysis of such situations easier. It's a macro which
|
|||
|
|
inserts a call to 'tst_res(TINFO, ...)' in case its argument evaluates to
|
|||
|
|
non-zero. In this call to 'tst_res(TINFO, ...)' the file and line will be
|
|||
|
|
expanded using the actual location of 'TST_TRACE'.
|
|||
|
|
|
|||
|
|
For example, if this another file contains:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
if (TST_TRACE(do_action(arg))) {
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
the generated output may look similar to:
|
|||
|
|
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
common.h:9: FAIL: check failed
|
|||
|
|
test.c:8: INFO: do_action(arg) failed
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.24 Tainted kernels
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
If you need to detect whether a testcase triggers a kernel warning, bug or
|
|||
|
|
oops, the following can be used to detect TAINT_W or TAINT_D:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.taint_check = TST_TAINT_W | TST_TAINT_D,
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
void run(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
if (tst_taint_check() != 0)
|
|||
|
|
tst_res(TFAIL, "kernel has issues");
|
|||
|
|
else
|
|||
|
|
tst_res(TPASS, "kernel seems to be fine");
|
|||
|
|
}
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
To initialize taint checks, you have to set the taint flags you want to test
|
|||
|
|
for in the 'taint_check' attribute of the tst_test struct. LTP library will
|
|||
|
|
then automatically call 'tst_taint_init()' during test setup. The function
|
|||
|
|
will generate a 'TCONF' if the requested flags are not fully supported on the
|
|||
|
|
running kernel, and 'TBROK' if the kernel is already tainted before executing
|
|||
|
|
the test.
|
|||
|
|
|
|||
|
|
LTP library will then automatically check kernel taint at the end of testing.
|
|||
|
|
If '.all_filesystems' is set in struct tst_test, taint check will be performed
|
|||
|
|
after each file system and taint will abort testing early with 'TFAIL'. You
|
|||
|
|
can optionally also call 'tst_taint_check()' during 'run()', which returns 0
|
|||
|
|
or the tainted flags set in '/proc/sys/kernel/tainted' as specified earlier.
|
|||
|
|
|
|||
|
|
Depending on your kernel version, not all tainted-flags will be supported.
|
|||
|
|
|
|||
|
|
For reference to tainted kernels, see kernel documentation:
|
|||
|
|
Documentation/admin-guide/tainted-kernels.rst or
|
|||
|
|
https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html
|
|||
|
|
|
|||
|
|
1.25 Checksums
|
|||
|
|
~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
CRC32c checksum generation is supported by LTP. In order to use it, the
|
|||
|
|
test should include 'tst_checksum.h' header, then can call 'tst_crc32c()'.
|
|||
|
|
|
|||
|
|
1.26 Checking kernel for the driver support
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some tests may need specific kernel drivers, either compiled in, or built
|
|||
|
|
as a module. If '.needs_drivers' points to a 'NULL' terminated array of kernel
|
|||
|
|
module names these are all checked and the test exits with 'TCONF' on the
|
|||
|
|
first missing driver.
|
|||
|
|
|
|||
|
|
Since it relies on modprobe command, the check will be skipped if the command
|
|||
|
|
itself is not available on the system.
|
|||
|
|
|
|||
|
|
1.27 Saving & restoring /proc|sys values
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
LTP library can be instructed to save and restore value of specified
|
|||
|
|
(/proc|sys) files. This is achieved by initialized tst_test struct
|
|||
|
|
field 'save_restore'. It is a NULL-terminated array of struct
|
|||
|
|
'tst_path_val' where each tst_path_val.path represents a file, whose
|
|||
|
|
value is saved at the beginning and restored at the end of the test.
|
|||
|
|
If non-NULL string is passed in tst_path_val.val, it is written
|
|||
|
|
to the respective file at the beginning of the test. Only the first line
|
|||
|
|
of a specified file is saved and restored.
|
|||
|
|
|
|||
|
|
By default, the test will end with TCONF if the file is read-only or
|
|||
|
|
does not exist. If the optional write of new value fails, the test will end
|
|||
|
|
with 'TBROK'. This behavior can be changed using tst_path_val.flags:
|
|||
|
|
|
|||
|
|
* 'TST_SR_TBROK_MISSING' – End test with 'TBROK' if the file does not exist
|
|||
|
|
* 'TST_SR_TCONF_MISSING' – End test with 'TCONF' if the file does not exist
|
|||
|
|
* 'TST_SR_SKIP_MISSING' – Continue without saving the file if it does not exist
|
|||
|
|
* 'TST_SR_TBROK_RO' – End test with 'TBROK' if the file is read-only
|
|||
|
|
* 'TST_SR_TCONF_RO' – End test with 'TCONF' if the file is read-only
|
|||
|
|
* 'TST_SR_SKIP_RO' – Continue without saving the file if it is read-only
|
|||
|
|
* 'TST_SR_IGNORE_ERR' – Ignore errors when writing new value into the file
|
|||
|
|
|
|||
|
|
Common flag combinations also have shortcuts:
|
|||
|
|
|
|||
|
|
* 'TST_SR_TCONF' – Equivalent to 'TST_SR_TCONF_MISSING | TST_SR_TCONF_RO'
|
|||
|
|
* 'TST_SR_TBROK' – Equivalent to 'TST_SR_TBROK_MISSING | TST_SR_TBROK_RO'
|
|||
|
|
* 'TST_SR_SKIP' – Equivalent to 'TST_SR_SKIP_MISSING | TST_SR_SKIP_RO'
|
|||
|
|
|
|||
|
|
'restore' is always strict and will TWARN if it encounters any error.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.setup = setup,
|
|||
|
|
.save_restore = (const struct tst_path_val[]) {
|
|||
|
|
{"/proc/sys/kernel/core_pattern", NULL, TST_SR_TCONF},
|
|||
|
|
{"/proc/sys/user/max_user_namespaces", NULL, TST_SR_SKIP},
|
|||
|
|
{"/sys/kernel/mm/ksm/run", "1", TST_SR_TBROK},
|
|||
|
|
{}
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.28 Parsing kernel .config
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Generally testcases should attempt to autodetect as much kernel features as
|
|||
|
|
possible based on the currently running kernel. We do have tst_check_driver()
|
|||
|
|
to check if functionality that could be compiled as kernel module is present
|
|||
|
|
on the system, disabled syscalls can be detected by checking for 'ENOSYS'
|
|||
|
|
errno etc.
|
|||
|
|
|
|||
|
|
However in rare cases core kernel features couldn't be detected based on the
|
|||
|
|
kernel userspace API and we have to resort to parse the kernel .config.
|
|||
|
|
|
|||
|
|
For this cases the test should set the 'NULL' terminated '.needs_kconfigs'
|
|||
|
|
array of boolean expressions with constraints on the kconfig variables. The
|
|||
|
|
boolean expression consits of variables, two binary operations '&' and '|',
|
|||
|
|
negation '!' and correct sequence of parentesis '()'. Variables are expected
|
|||
|
|
to be in a form of "CONFIG_FOO[=bar]".
|
|||
|
|
|
|||
|
|
The test will continue to run if all expressions are evaluated to 'True'.
|
|||
|
|
Missing variable is mapped to 'False' as well as variable with different than
|
|||
|
|
specified value, e.g. 'CONFIG_FOO=bar' will evaluate to 'False' if the value
|
|||
|
|
is anything else but 'bar'. If config variable is specified as plain
|
|||
|
|
'CONFIG_FOO' it's evaluated to true it's set to any value (typically =y or =m).
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static const char *kconfigs[] = {
|
|||
|
|
"CONFIG_X86_INTEL_UMIP | CONFIG_X86_UMIP",
|
|||
|
|
NULL
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.needs_kconfigs = kconfigs,
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.29 Changing the Wall Clock Time during test execution
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
There are some tests that, for different reasons, might need to change the
|
|||
|
|
system-wide clock time. Whenever this happens, it is imperative that the clock
|
|||
|
|
is restored, at the end of test's execution, taking in consideration the amount
|
|||
|
|
of time elapsed during that test.
|
|||
|
|
|
|||
|
|
In order for that to happen, struct tst_test has a variable called
|
|||
|
|
"restore_wallclock" that should be set to "1" so LTP knows it should: (1)
|
|||
|
|
initialize a monotonic clock during test setup phase and (2) use that monotonic
|
|||
|
|
clock to fix the system-wide clock time at the test cleanup phase.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.setup = setup,
|
|||
|
|
.test_all = run,
|
|||
|
|
.restore_wallclock = 1,
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.30 Testing similar syscalls in one test
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
In some cases kernel has several very similar syscalls that do either the same
|
|||
|
|
or very similar job. This is most noticeable on i386 where we commonly have
|
|||
|
|
two or three syscall versions. That is because i386 was first platform that
|
|||
|
|
Linux was developed on and because of that most mistakes in API happened there
|
|||
|
|
as well. However this is not limited to i386 at all, it's quite common that
|
|||
|
|
version two syscall has added missing flags parameters or so.
|
|||
|
|
|
|||
|
|
In such cases it does not make much sense to copy&paste the test code over and
|
|||
|
|
over, rather than that the test library provides support for test variants.
|
|||
|
|
The idea behind test variants is simple, we run the test several times each
|
|||
|
|
time with different syscall variant.
|
|||
|
|
|
|||
|
|
The implementation consist of test_variants integer that, if set, denotes number
|
|||
|
|
of test variants. The test is then forked and executed test_variants times each
|
|||
|
|
time with different value in global tst_variant variable.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static int do_foo(void)
|
|||
|
|
{
|
|||
|
|
switch (tst_variant) {
|
|||
|
|
case 0:
|
|||
|
|
return foo();
|
|||
|
|
case 1:
|
|||
|
|
return syscall(__NR_foo);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
TEST(do_foo);
|
|||
|
|
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
switch (tst_variant) {
|
|||
|
|
case 0:
|
|||
|
|
tst_res(TINFO, "Testing foo variant 1");
|
|||
|
|
break;
|
|||
|
|
case 1:
|
|||
|
|
tst_res(TINFO, "Testing foo variant 2");
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.setup = setup,
|
|||
|
|
.test_all = run,
|
|||
|
|
.test_variants = 2,
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.31 Guarded buffers
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
The test library supports guarded buffers, which are buffers allocated so
|
|||
|
|
that:
|
|||
|
|
|
|||
|
|
* The end of the buffer is followed by a PROT_NONE page
|
|||
|
|
|
|||
|
|
* The remainder of the page before the buffer is filled with random canary
|
|||
|
|
data
|
|||
|
|
|
|||
|
|
Which means that the any access after the buffer will yield a Segmentation
|
|||
|
|
fault or EFAULT depending on if the access happened in userspace or the kernel
|
|||
|
|
respectively. The canary before the buffer will also catch any write access
|
|||
|
|
outside of the buffer.
|
|||
|
|
|
|||
|
|
The purpose of the patch is to catch off-by-one bugs which happens when
|
|||
|
|
buffers and structures are passed to syscalls. New tests should allocate
|
|||
|
|
guarded buffers for all data passed to the tested syscall which are passed by
|
|||
|
|
a pointer.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static struct foo *foo_ptr;
|
|||
|
|
static struct iovec *iov;
|
|||
|
|
static void *buf_ptr;
|
|||
|
|
static char *id;
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
foo_ptr->bar = 1;
|
|||
|
|
foo_ptr->buf = buf_ptr;
|
|||
|
|
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
id = tst_strdup(string);
|
|||
|
|
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.bufs = (struct tst_buffers []) {
|
|||
|
|
{&foo_ptr, .size = sizeof(*foo_ptr)},
|
|||
|
|
{&buf_ptr, .size = BUF_SIZE},
|
|||
|
|
{&iov, .iov_sizes = (int[]){128, 32, -1},
|
|||
|
|
{}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Guarded buffers can be allocated on runtime in a test setup() by a
|
|||
|
|
'tst_alloc()' or by 'tst_strdup()' as well as by filling up the .bufs array in
|
|||
|
|
the tst_test structure.
|
|||
|
|
|
|||
|
|
So far the tst_test structure supports allocating either a plain buffer by
|
|||
|
|
setting up the size or struct iovec, which is allocated recursively including
|
|||
|
|
the individual buffers as described by an '-1' terminated array of buffer
|
|||
|
|
sizes.
|
|||
|
|
|
|||
|
|
1.32 Adding and removing capabilities
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some tests may require the presence or absence of particular
|
|||
|
|
capabilities. Using the API provided by 'tst_capability.h' the test author can
|
|||
|
|
try to ensure that some capabilities are either present or absent during the
|
|||
|
|
test.
|
|||
|
|
|
|||
|
|
For example; below we try to create a raw socket, which requires
|
|||
|
|
CAP_NET_ADMIN. During setup we should be able to do it, then during run it
|
|||
|
|
should be impossible. The LTP capability library will check before setup that
|
|||
|
|
we have this capability, then after setup it will drop it.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
--------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
#include "tst_capability.h"
|
|||
|
|
#include "tst_safe_net.h"
|
|||
|
|
|
|||
|
|
#include "lapi/socket.h"
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
TEST(socket(AF_INET, SOCK_RAW, 1));
|
|||
|
|
if (TST_RET > -1) {
|
|||
|
|
tst_res(TFAIL, "Created raw socket");
|
|||
|
|
} else if (TST_ERR != EPERM) {
|
|||
|
|
tst_res(TFAIL | TTERRNO,
|
|||
|
|
"Failed to create socket for wrong reason");
|
|||
|
|
} else {
|
|||
|
|
tst_res(TPASS | TTERRNO, "Didn't create raw socket");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
TEST(socket(AF_INET, SOCK_RAW, 1));
|
|||
|
|
if (TST_RET < 0)
|
|||
|
|
tst_brk(TCONF | TTERRNO, "We don't have CAP_NET_RAW to begin with");
|
|||
|
|
|
|||
|
|
SAFE_CLOSE(TST_RET);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
.setup = setup,
|
|||
|
|
.test_all = run,
|
|||
|
|
.caps = (struct tst_cap []) {
|
|||
|
|
TST_CAP(TST_CAP_REQ, CAP_NET_RAW),
|
|||
|
|
TST_CAP(TST_CAP_DROP, CAP_NET_RAW),
|
|||
|
|
{}
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
--------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Look at the test struct at the bottom. We have filled in the 'caps' field with
|
|||
|
|
a 'NULL' terminated array containing two 'tst_cap' structs. 'TST_CAP_REQ'
|
|||
|
|
actions are executed before setup and 'TST_CAP_DROP' are executed after
|
|||
|
|
setup. This means it is possible to both request and drop a capability.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
--------------------------------------------------------------------------------
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
.test_all = run,
|
|||
|
|
.caps = (struct tst_cap []) {
|
|||
|
|
TST_CAP(TST_CAP_REQ, CAP_NET_RAW),
|
|||
|
|
TST_CAP(TST_CAP_DROP, CAP_SYS_ADMIN),
|
|||
|
|
{}
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
--------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Here we request 'CAP_NET_RAW', but drop 'CAP_SYS_ADMIN'. If the capability is
|
|||
|
|
in the permitted set, but not the effective set, the library will try to
|
|||
|
|
permit it. If it is not in the permitted set, then it will fail with 'TCONF'.
|
|||
|
|
|
|||
|
|
This API does not require 'libcap' to be installed. However it has limited
|
|||
|
|
features relative to 'libcap'. It only tries to add or remove capabilities
|
|||
|
|
from the effective set. This means that tests which need to spawn child
|
|||
|
|
processes may have difficulties ensuring the correct capabilities are
|
|||
|
|
available to the children (see the capabilities (7) manual pages).
|
|||
|
|
|
|||
|
|
However a lot of problems can be solved by using 'tst_cap_action(struct
|
|||
|
|
tst_cap *cap)' directly which can be called at any time. This also helps if
|
|||
|
|
you wish to drop a capability at the begining of setup.
|
|||
|
|
|
|||
|
|
1.33 Reproducing race-conditions
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
If a bug is caused by two tasks in the kernel racing and you wish to create a
|
|||
|
|
regression test (or bug-fix validation test) then the 'tst_fuzzy_sync.h'
|
|||
|
|
library should be used.
|
|||
|
|
|
|||
|
|
It allows you to specify, in your code, two race windows. One window in each
|
|||
|
|
thread's loop (triggering a race usually requires many iterations). These
|
|||
|
|
windows show fuzzy-sync where the race can happen. They don't need to be
|
|||
|
|
exact, hence the 'fuzzy' part. If the race condition is not immediately
|
|||
|
|
triggered then the library will begin experimenting with different timings.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
--------------------------------------------------------------------------------
|
|||
|
|
#include "tst_fuzzy_sync.h"
|
|||
|
|
|
|||
|
|
static struct tst_fzsync_pair fzsync_pair;
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
tst_fzsync_pair_init(&fzsync_pair);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void cleanup(void)
|
|||
|
|
{
|
|||
|
|
tst_fzsync_pair_cleanup(&fzsync_pair);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void *thread_b(void *arg)
|
|||
|
|
{
|
|||
|
|
while (tst_fzsync_run_b(&fzsync_pair)) {
|
|||
|
|
|
|||
|
|
tst_fzsync_start_race_b(&fzsync_pair);
|
|||
|
|
|
|||
|
|
/* This is the race window for thread B */
|
|||
|
|
|
|||
|
|
tst_fzsync_end_race_b(&fzsync_pair);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return arg;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void thread_a(void)
|
|||
|
|
{
|
|||
|
|
tst_fzsync_pair_reset(&fzsync_pair, thread_b);
|
|||
|
|
|
|||
|
|
while (tst_fzsync_run_a(&fzsync_pair)) {
|
|||
|
|
|
|||
|
|
tst_fzsync_start_race_a(&fzsync_pair);
|
|||
|
|
|
|||
|
|
/* This is the race window for thread A */
|
|||
|
|
|
|||
|
|
tst_fzsync_end_race_a(&fzsync_pair);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
.test_all = thread_a,
|
|||
|
|
.setup = setup,
|
|||
|
|
.cleanup = cleanup,
|
|||
|
|
};
|
|||
|
|
--------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Above is a minimal template for a test using fuzzy-sync. In a simple case, you
|
|||
|
|
just need to put the bits you want to race inbetween 'start_race' and
|
|||
|
|
'end_race'. Meanwhile, any setup you need to do per-iteration goes outside the
|
|||
|
|
windows.
|
|||
|
|
|
|||
|
|
Fuzzy sync synchronises 'run_a' and 'run_b', which act as barriers, so that
|
|||
|
|
neither thread can progress until the other has caught up with it. There is
|
|||
|
|
also the 'pair_wait' function which can be used to add barriers in other
|
|||
|
|
locations. Of course 'start/end_race_a/b' are also a barriers.
|
|||
|
|
|
|||
|
|
The library decides how long the test should run for based on the timeout
|
|||
|
|
specified by the user plus some other heuristics.
|
|||
|
|
|
|||
|
|
For full documentation see the comments in 'include/tst_fuzzy_sync.h'.
|
|||
|
|
|
|||
|
|
1.34 Reserving hugepages
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Many of the LTP tests need to use hugepage in their testing, this allows the
|
|||
|
|
test can reserve hugepages from system via '.hugepages = {xx, TST_REQUEST}'.
|
|||
|
|
|
|||
|
|
We achieved two policies for reserving hugepages:
|
|||
|
|
|
|||
|
|
TST_REQUEST:
|
|||
|
|
It will try the best to reserve available huge pages and return the number
|
|||
|
|
of available hugepages in tst_hugepages, which may be 0 if hugepages are
|
|||
|
|
not supported at all.
|
|||
|
|
|
|||
|
|
TST_NEEDS:
|
|||
|
|
This is an enforced requirement, LTP should strictly do hpages applying and
|
|||
|
|
guarantee the 'HugePages_Free' no less than pages which makes that test can
|
|||
|
|
use these specified numbers correctly. Otherwise, test exits with TCONF if
|
|||
|
|
the attempt to reserve hugepages fails or reserves less than requested.
|
|||
|
|
|
|||
|
|
With success test stores the reserved hugepage number in 'tst_hugepages'. For
|
|||
|
|
system without hugetlb supporting, variable 'tst_hugepages' will be set to 0.
|
|||
|
|
If the hugepage number needs to be set to 0 on supported hugetlb system, please
|
|||
|
|
use '.hugepages = {TST_NO_HUGEPAGES}'.
|
|||
|
|
|
|||
|
|
Also, we do cleanup and restore work for the hpages resetting automatically.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
if (tst_hugepages == test.hugepages.number)
|
|||
|
|
TEST(do_hpage_test);
|
|||
|
|
else
|
|||
|
|
...
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct tst_test test = {
|
|||
|
|
.test_all = run,
|
|||
|
|
.hugepages = {2, TST_REQUEST},
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
or,
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
/* TST_NEEDS achieved this automatically in the library */
|
|||
|
|
if (tst_hugepages != test.hugepages.number)
|
|||
|
|
tst_brk(TCONF, "...");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct tst_test test = {
|
|||
|
|
.test_all = run,
|
|||
|
|
.hugepages = {2, TST_NEEDS},
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.35 Checking for required commands
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Required commands can be checked with '.needs_cmds', which points to a 'NULL'
|
|||
|
|
terminated array of strings such as:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
.needs_cmds = (const char *const []) {
|
|||
|
|
"useradd",
|
|||
|
|
"userdel",
|
|||
|
|
NULL
|
|||
|
|
},
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Also can check required command version whether is satisfied by using 'needs_cmds'
|
|||
|
|
such as:
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
.needs_cmds = (const char *const []) {
|
|||
|
|
"mkfs.ext4 >= 1.43.0",
|
|||
|
|
NULL
|
|||
|
|
},
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Currently, we only support mkfs.ext4 command version check.
|
|||
|
|
If you want to support more commands, please fill your own .parser and .table_get
|
|||
|
|
method in the version_parsers structure of lib/tst_cmd.c.
|
|||
|
|
|
|||
|
|
1.36 Assert sys or proc file value
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
Using TST_ASSERT_INT/STR(path, val) to assert that integer value or string stored in
|
|||
|
|
the prefix field of file pointed by path equals to the value passed to this function.
|
|||
|
|
|
|||
|
|
Also having a similar api pair TST_ASSERT_FILE_INT/STR(path, prefix, val) to assert
|
|||
|
|
the field value of file.
|
|||
|
|
|
|||
|
|
1.36 Using Control Group
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some LTP tests need specific Control Group configurations. 'tst_cgroup.h'
|
|||
|
|
provides APIs to discover and use CGroups. There are many differences between
|
|||
|
|
CGroups API V1 and V2. We encapsulate the details of configuring CGroups in
|
|||
|
|
high-level functions which follow the V2 kernel API where possible. Allowing one
|
|||
|
|
to write code that works on both V1 or V2. At least some of the time anyway;
|
|||
|
|
often the behavioural differences between V1 and V2 are too great. In such cases
|
|||
|
|
we revert to branching on the CGroup version.
|
|||
|
|
|
|||
|
|
Also, the LTP library will automatically mount/umount and configure the CGroup
|
|||
|
|
hierarchies if that is required (e.g. if you run the tests from init with no
|
|||
|
|
system manager).
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
// do test under cgroup
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void setup(void)
|
|||
|
|
{
|
|||
|
|
SAFE_CG_PRINTF(tst_cg, "cgroup.procs", "%d", getpid());
|
|||
|
|
SAFE_CG_PRINTF(tst_cg, "memory.max", "%lu", MEMSIZE);
|
|||
|
|
if (SAFE_CG_HAS(tst_cg, "memory.swap.max"))
|
|||
|
|
SAFE_CG_PRINTF(tst_cg, "memory.swap.max", "%zu", memsw);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct tst_test test = {
|
|||
|
|
.setup = setup,
|
|||
|
|
.test_all = run,
|
|||
|
|
.cleanup = cleanup,
|
|||
|
|
.needs_cgroup_ctrls = (const char *const []){ "memory", NULL },
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Above, we first ensure the memory controller is available on the
|
|||
|
|
test's CGroup with '.needs_cgroup_ctrls'. This populates a structure,
|
|||
|
|
'tst_cg', which represents the test's CGroup.
|
|||
|
|
|
|||
|
|
We then write the current processes PID into 'cgroup.procs', which
|
|||
|
|
moves the current process into the test's CGroup. After which we set
|
|||
|
|
the maximum memory size by writing to 'memory.max'. If the memory
|
|||
|
|
controller is mounted on CGroups V1 then the library will actually
|
|||
|
|
write to 'memory.limit_in_bytes'. As a general rule, if a file exists
|
|||
|
|
on both CGroup versions, then we use the V2 naming.
|
|||
|
|
|
|||
|
|
Some controller features, such as 'memory.swap', can be
|
|||
|
|
disabled. Therefor we need to check if they exist before accessing
|
|||
|
|
them. This can be done with 'SAFE_CG_HAS' which can be called on
|
|||
|
|
any control file or feature.
|
|||
|
|
|
|||
|
|
Most tests only require setting a few limits similar to the above. In
|
|||
|
|
such cases the differences between V1 and V2 are hidden. Setup and
|
|||
|
|
cleanup is also mostly hidden. However things can get much worse.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
static struct tst_cg_group *cg_child;
|
|||
|
|
|
|||
|
|
static void run(void)
|
|||
|
|
{
|
|||
|
|
char buf[BUFSIZ];
|
|||
|
|
size_t mem = 0;
|
|||
|
|
|
|||
|
|
cg_child = tst_cg_group_mk(tst_cg, "child");
|
|||
|
|
SAFE_CG_PRINTF(cg_child, "cgroup.procs", "%d", getpid());
|
|||
|
|
|
|||
|
|
if (!TST_CG_VER_IS_V1(tst_cg, "memory"))
|
|||
|
|
SAFE_CG_PRINT(tst_cg, "cgroup.subtree_control", "+memory");
|
|||
|
|
if (!TST_CG_VER_IS_V1(tst_cg, "cpuset"))
|
|||
|
|
SAFE_CG_PRINT(tst_cg, "cgroup.subtree_control", "+cpuset");
|
|||
|
|
|
|||
|
|
if (!SAFE_FORK()) {
|
|||
|
|
SAFE_CG_PRINTF(cg_child, "cgroup.procs", "%d", getpid());
|
|||
|
|
|
|||
|
|
if (SAFE_CG_HAS(cg_child, "memory.swap")) {
|
|||
|
|
SAFE_CG_SCANF(cg_child,
|
|||
|
|
"memory.swap.current", "%zu", &mem);
|
|||
|
|
}
|
|||
|
|
SAFE_CG_READ(cg_child, "cpuset.mems", buf, sizeof(buf));
|
|||
|
|
|
|||
|
|
// Do something with cpuset.mems and memory.current values
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
exit(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tst_reap_children();
|
|||
|
|
SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs", "%d", getpid());
|
|||
|
|
cg_child = tst_cg_group_rm(cg_child);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void cleanup(void)
|
|||
|
|
{
|
|||
|
|
if (cg_child) {
|
|||
|
|
SAFE_CG_PRINTF(tst_cg_drain, "cgroup.procs", "%d", getpid());
|
|||
|
|
cg_child = tst_cg_group_rm(cg_child);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct tst_test test = {
|
|||
|
|
.setup = setup,
|
|||
|
|
.test_all = run,
|
|||
|
|
.needs_cgroup_ctrls = (const char *const []){
|
|||
|
|
"cpuset",
|
|||
|
|
"memory",
|
|||
|
|
NULL
|
|||
|
|
},
|
|||
|
|
...
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
Starting with setup; we can see here that we fetch the 'drain'
|
|||
|
|
CGroup. This is a shared group (between parallel tests) which may
|
|||
|
|
contain processes from other tests. It should have default settings
|
|||
|
|
and these should not be changed by the test. It can be used to remove
|
|||
|
|
processes from other CGroups incase the hierarchy root is not
|
|||
|
|
accessible.
|
|||
|
|
|
|||
|
|
Note that 'tst_cg_get_drain_group' should not be called many times,
|
|||
|
|
as it is allocated in a guarded buffer (See section 2.2.31). Therefor
|
|||
|
|
it is best to call it once in 'setup' and not 'run' because 'run' may
|
|||
|
|
be repeated with the '-i' option.
|
|||
|
|
|
|||
|
|
In 'run', we first create a child CGroup with 'tst_cg_mk'. As we
|
|||
|
|
create this CGroup in 'run' we should also remove it at the end of
|
|||
|
|
run. We also need to check if it exists and remove it in cleanup as
|
|||
|
|
well. Because there are 'SAFE_' functions which may jump to cleanup.
|
|||
|
|
|
|||
|
|
We then move the main test process into the child CGroup. This is
|
|||
|
|
important as it means that before we destroy the child CGroup we have
|
|||
|
|
to move the main test process elsewhere. For that we use the 'drain'
|
|||
|
|
group.
|
|||
|
|
|
|||
|
|
Next we enable the memory and cpuset controller configuration on the
|
|||
|
|
test CGroup's descendants (i.e. 'cg_child'). This allows each child to
|
|||
|
|
have its own settings. The file 'cgroup.subtree_control' does not
|
|||
|
|
exist on V1. Because it is possible to have both V1 and V2 active at
|
|||
|
|
the same time. We can not simply check if 'subtree_control' exists
|
|||
|
|
before writing to it. We have to check if a particular controller is
|
|||
|
|
on V2 before trying to add it to 'subtree_control'. Trying to add a V1
|
|||
|
|
controller will result in 'ENOENT'.
|
|||
|
|
|
|||
|
|
We then fork a child process and add this to the child CGroup. Within
|
|||
|
|
the child process we try to read 'memory.swap.current'. It is possible
|
|||
|
|
that the memory controller was compiled without swap support, so it is
|
|||
|
|
necessary to check if 'memory.swap' is enabled. That is unless the
|
|||
|
|
test will never reach the point where 'memory.swap.*' are used without
|
|||
|
|
swap support.
|
|||
|
|
|
|||
|
|
The parent process waits for the child process to be reaped before
|
|||
|
|
destroying the child CGroup. So there is no need to transfer the child
|
|||
|
|
to drain. However the parent process must be moved otherwise we will
|
|||
|
|
get 'EBUSY' when trying to remove the child CGroup.
|
|||
|
|
|
|||
|
|
Another example of a behavioral difference between versions is shown below.
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
if (TST_CG_VER_IS_V1(tst_cg, "memory"))
|
|||
|
|
SAFE_CG_PRINTF(tst_cg, "memory.swap.max", "%lu", ~0UL);
|
|||
|
|
else
|
|||
|
|
SAFE_CG_PRINT(tst_cg, "memory.swap.max", "max");
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
CGroups V2 introduced a feature where 'memory[.swap].max' could be set to
|
|||
|
|
"max". This does not appear to work on V1 'limit_in_bytes' however. For most
|
|||
|
|
tests, simply using a large number is sufficient and there is no need to use
|
|||
|
|
"max". Importantly though, one should be careful to read both the V1 and V2
|
|||
|
|
kernel docs. Presently the LTP library does not attempt to handle most
|
|||
|
|
differences in semantics. It does the minimal amount of work to make testing on
|
|||
|
|
both V1 and V2 feasible.
|
|||
|
|
|
|||
|
|
1.37 Require minimum numbers of CPU for a testcase
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some tests require more than specific number of CPU. It can be defined with
|
|||
|
|
`.min_cpus = N`.
|
|||
|
|
|
|||
|
|
1.38 Test tags
|
|||
|
|
~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Test tags are name-value pairs that can hold any test metadata.
|
|||
|
|
|
|||
|
|
We have additional support for CVE entries, git commit in mainline kernel,
|
|||
|
|
stable kernel or glibc git repository. If a test is a regression test it
|
|||
|
|
should include these tags. They are printed when test fails and exported
|
|||
|
|
into documentation.
|
|||
|
|
|
|||
|
|
CVE, mainline and stable kernel git commits in a regression test for a kernel bug:
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.tags = (const struct tst_tag[]) {
|
|||
|
|
{"linux-git", "9392a27d88b9"},
|
|||
|
|
{"linux-git", "ff002b30181d"},
|
|||
|
|
{"known-fail", "ustat() is known to fail with EINVAL on Btrfs"},
|
|||
|
|
{"linux-stable-git", "c4a23c852e80"},
|
|||
|
|
{"CVE", "2020-29373"},
|
|||
|
|
{}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
NOTE: We don't track all backports to stable kernel but just those which are
|
|||
|
|
stable branch specific (unique), i.e. no commit in mainline. Example of
|
|||
|
|
commits: c4a23c852e80, cac68d12c531.
|
|||
|
|
|
|||
|
|
Glibc git commit in a regression test for a glibc bug:
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.tags = (const struct tst_tag[]) {
|
|||
|
|
{"glibc-git", "574500a108be"},
|
|||
|
|
{}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.39 Testing on the specific architecture
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
Testcases for specific arch should be limited on that only being supported
|
|||
|
|
platform to run, we now involve a .supported_archs to achieve this feature
|
|||
|
|
in LTP library. All you need to run a test on the expected arch is to set
|
|||
|
|
the '.supported_archs' array in the 'struct tst_test' to choose the required
|
|||
|
|
arch list. e.g.
|
|||
|
|
|
|||
|
|
.supported_archs = (const char *const []){"x86_64", "ppc64", NULL}
|
|||
|
|
|
|||
|
|
This helps move the TCONF info from code to tst_test metadata as well.
|
|||
|
|
|
|||
|
|
And, we also export a struct tst_arch to save the system architecture for
|
|||
|
|
using in the whole test cases.
|
|||
|
|
|
|||
|
|
extern const struct tst_arch {
|
|||
|
|
char name[16];
|
|||
|
|
enum tst_arch_type type;
|
|||
|
|
} tst_arch;
|
|||
|
|
|
|||
|
|
[source,c]
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
#include "tst_test.h"
|
|||
|
|
|
|||
|
|
static struct tst_test test = {
|
|||
|
|
...
|
|||
|
|
.setup = setup,
|
|||
|
|
.supported_archs = (const char *const []) {
|
|||
|
|
"x86_64",
|
|||
|
|
"ppc64",
|
|||
|
|
"s390x",
|
|||
|
|
NULL
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
-------------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
1.40 Require minimum size of MemAvailable for a testcase
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Some tests require more than specific size(MB) of MemAvailable. It can be defined
|
|||
|
|
with `.min_mem_avail = N`.
|
|||
|
|
|
|||
|
|
2. Common problems
|
|||
|
|
------------------
|
|||
|
|
|
|||
|
|
This chapter describes common problems/misuses and less obvious design patters
|
|||
|
|
(quirks) in UNIX interfaces. Read it carefully :)
|
|||
|
|
|
|||
|
|
2.1 umask()
|
|||
|
|
~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
I've been hit by this one several times already... When you create files
|
|||
|
|
with 'open()' or 'creat()' etc, the mode specified as the last parameter *is
|
|||
|
|
not* the mode the file is created with. The mode depends on current 'umask()'
|
|||
|
|
settings which may clear some of the bits. If your test depends on specific
|
|||
|
|
file permissions you need either to change umask to 0 or 'chmod()' the file
|
|||
|
|
afterwards or use 'SAFE_TOUCH()' that does the 'chmod()' for you.
|
|||
|
|
|
|||
|
|
2.2 access()
|
|||
|
|
~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
If 'access(some_file, W_OK)' is executed by root, it will return success even
|
|||
|
|
if the file doesn't have write permission bits set (the same holds for R_OK
|
|||
|
|
too). For sysfs files you can use 'open()' as a workaround to check file
|
|||
|
|
read/write permissions. It might not work for other filesystems, for these you
|
|||
|
|
have to use 'stat()', 'lstat()' or 'fstat()'.
|
|||
|
|
|
|||
|
|
2.3 umount() EBUSY
|
|||
|
|
~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Various desktop daemons (gvfsd-trash is known for that) may be stupid enough
|
|||
|
|
to probe all newly mounted filesystem which results in 'umount(2)' failing
|
|||
|
|
with 'EBUSY'; use 'tst_umount()' described in 1.19 that retries in this case
|
|||
|
|
instead of plain 'umount(2)'.
|
|||
|
|
|
|||
|
|
2.4 FILE buffers and fork()
|
|||
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|||
|
|
|
|||
|
|
Be vary that if a process calls 'fork(2)' the child process inherits open
|
|||
|
|
descriptors as well as copy of the parent memory so especially if there are
|
|||
|
|
any open 'FILE' buffers with a data in them they may be written both by the
|
|||
|
|
parent and children resulting in corrupted/duplicated data in the resulting
|
|||
|
|
files.
|
|||
|
|
|
|||
|
|
Also open 'FILE' streams are flushed and closed at 'exit(3)' so if your
|
|||
|
|
program works with 'FILE' streams, does 'fork(2)', and the child may end up
|
|||
|
|
calling 'exit(3)' you will likely end up with corrupted files.
|
|||
|
|
|
|||
|
|
The solution to this problem is either simply call 'fflush(NULL)' that flushes
|
|||
|
|
all open output 'FILE' streams just before doing 'fork(2)'. You may also use
|
|||
|
|
'_exit(2)' in child processes which does not flush 'FILE' buffers and also
|
|||
|
|
skips 'atexit(3)' callbacks.
|