Viewing: llapi_test_utils.c

// SPDX-License-Identifier: GPL-2.0
/* Basic framework for Lustre llapi tests.
 * All tests return 0 on success and non-zero on error.
 * The program will run all tests unless a list of tests to skip is provided.
 */
/*
 * Copyright 2014, 2015 Cray Inc, all rights reserved.
 * Copyright (c) 2015, Intel Corporation.
 * Copyright (c) 2025, DataDirect Networks, Inc. All rights reserved.
 */

#include <errno.h>
#include <limits.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include <lustre/lustreapi.h>
#include "llapi_test_utils.h"

static bool run_list_provided;
char fsmountdir[PATH_MAX];      /* Lustre mountpoint */

static void print_test_desc(int test_num, const char *test_desc,
			    const char *status)
{
	int i;

	i = printf(" test%u @%llu: %s ", test_num,
		   (unsigned long long)time(NULL), test_desc);
	for (; i < TEST_DESC_LEN; i++)
		printf(".");
	printf(" %s\n", status);
}

/* This function runs a single test by forking the process.  This way,
 * if there is a segfault during a test, the test program won't crash.
 */
static int test(void (*test_fn)(), const char *test_desc, bool test_skip,
		int test_num)
{
	int rc = 0;
	pid_t pid;
	char status_buf[128];

	if (test_skip) {
		if (!run_list_provided)
			print_test_desc(test_num, test_desc, "skip");
		return 0;
	}

	pid = fork();
	if (pid < 0) {
		ERROR("cannot fork: %s", strerror(errno));
	} else if (pid > 0) {
		int status = 0;

		/* Non-zero value indicates failure. */
		wait(&status);
		if (status == 0) {
			strncpy(status_buf, "pass", sizeof(status_buf));
		} else if WIFSIGNALED(status) {
			snprintf(status_buf, sizeof(status_buf),
				 "fail (exit status %d, killed by SIG%d)",
				 WEXITSTATUS(status), WTERMSIG(status));
			rc = -1;
		} else {
			snprintf(status_buf, sizeof(status_buf),
				 "fail (exit status %d)", WEXITSTATUS(status));
			rc = -1;
		}
		print_test_desc(test_num, test_desc, status_buf);
	} else if (pid == 0) {
		/* Run the test in the child process.
		 * Exit 0 here for success, non-zero from test_fn() on error.
		 */
		test_fn();
		exit(0);
	}

	return rc;
}

/* 'str_tests' are the tests to be skipped, such as "1,3,4,..." */
void set_tests_to_skip(const char *str_tests, struct test_tbl_entry *tst_tbl)
{
	const char *ptr = str_tests;

	if (tst_tbl == NULL || ptr == NULL || strlen(ptr) == 0)
		return;

	while (*ptr != '\0') {
		struct test_tbl_entry *tst;
		char *end;
		unsigned long tstno = strtoul(ptr, &end, 0);

		if (tstno > UINT_MAX || errno)
			DIE("Error: invalid test number '%s'", ptr);

		for (tst = tst_tbl; tst->tte_fn != NULL; tst++) {
			if (tst->tte_num == tstno) {
				tst->tte_skip = true;
				break;
			}
		}
		if (tst->tte_skip == false)
			DIE("Error: test %lu not found", tstno);

		if (*end == ',')
			ptr = end + 1;
		else
			break;
	}
}

/* 'str_tests' are the tests to be run, such as "5,6,7,..." */
void set_tests_to_run(const char *str_tests, struct test_tbl_entry *tst_tbl)
{
	struct test_tbl_entry *tst;
	const char *ptr = str_tests;

	if (tst_tbl == NULL || ptr == NULL || strlen(ptr) == 0)
		return;

	run_list_provided = true;
	for (tst = tst_tbl; tst->tte_fn != NULL; tst++)
		tst->tte_skip = true;

	while (*ptr != '\0') {
		struct test_tbl_entry *tst;
		char *end;
		unsigned long tstno = strtoul(ptr, &end, 0);

		if (tstno > UINT_MAX || errno)
			DIE("Error: invalid test number '%s'", ptr);

		for (tst = tst_tbl; tst->tte_fn != NULL; tst++) {
			if (tst->tte_num == tstno) {
				tst->tte_skip = false;
				break;
			}
		}
		if (tst->tte_skip == true)
			DIE("Error: test %lu not found", tstno);
		if (*end == ',')
			ptr = end + 1;
		else
			break;
	}
}

int run_tests(const char *lustre_dir, struct test_tbl_entry *tst_tbl)
{
	struct test_tbl_entry *tst;
	char fsname[8 + 1];
	struct stat st;
	int saved_msg_level = llapi_msg_get_level();
	int rc;

	if (lustre_dir == NULL)
		DIE("no test directory provided\n");

	if (tst_tbl == NULL)
		DIE("no test table provided\n");

	llapi_msg_set_level(LLAPI_MSG_ERROR);

	if (stat(lustre_dir, &st) < 0)
		DIE("cannot stat %s: %s\n", lustre_dir, strerror(errno));
	else if (!S_ISDIR(st.st_mode))
		DIE("%s: not a directory\n", lustre_dir);

	rc = llapi_search_mounts(lustre_dir, 0, fsmountdir, fsname);
	if (rc != 0) {
		fprintf(stderr, "Error: %s: not a Lustre filesystem\n",
			lustre_dir);
		return EXIT_FAILURE;
	}

	/* Play nice with Lustre test scripts. Non-line buffered output
	 * stream under I/O redirection may appear incorrectly.
	 */
	setvbuf(stdout, NULL, _IOLBF, 0);

	for (tst = tst_tbl; tst->tte_fn != NULL; tst++) {
		if (test(tst->tte_fn, tst->tte_desc, tst->tte_skip,
			 tst->tte_num))
			rc++;
	}

	llapi_msg_set_level(saved_msg_level);

	return rc;
}