Loading...
// SPDX-License-Identifier: GPL-2.0
/*
 * (C) Copyright 2015
 * Joe Hershberger, National Instruments, joe.hershberger@ni.com
 */

#include <command.h>
#include <console.h>
#include <vsprintf.h>
#include <test/test.h>
#include <test/ut.h>

/**
 * struct suite - A set of tests for a certain topic
 *
 * All tests end up in a single 'struct unit_test' linker-list array, in order
 * of the suite they are in
 *
 * @name: Name of suite
 * @start: First test in suite
 * @end: End test in suite (points to the first test in the next suite)
 * @help: Help-string to show for this suite
 */
struct suite {
	const char *name;
	struct unit_test *start;
	struct unit_test *end;
	const char *help;
};

static int do_ut_all(struct unit_test_state *uts, const char *select_name,
		     int runs_per_test, bool force_run,
		     const char *test_insert);

static int do_ut_info(bool show_suites);

/* declare linker-list symbols for the start and end of a suite */
#define SUITE_DECL(_name) \
	ll_start_decl(suite_start_ ## _name, struct unit_test, ut_ ## _name); \
	ll_end_decl(suite_end_ ## _name, struct unit_test, ut_ ## _name)

/* declare a test suite which can be run directly without a subcommand */
#define SUITE(_name, _help) { \
	#_name, \
	suite_start_ ## _name, \
	suite_end_ ## _name, \
	_help, \
	}

SUITE_DECL(addrmap);
SUITE_DECL(bdinfo);
SUITE_DECL(bloblist);
SUITE_DECL(bootm);
SUITE_DECL(bootstd);
SUITE_DECL(cmd);
SUITE_DECL(common);
SUITE_DECL(dm);
SUITE_DECL(env);
SUITE_DECL(exit);
SUITE_DECL(fdt);
SUITE_DECL(fdt_overlay);
SUITE_DECL(font);
SUITE_DECL(hush);
SUITE_DECL(lib);
SUITE_DECL(loadm);
SUITE_DECL(log);
SUITE_DECL(mbr);
SUITE_DECL(measurement);
SUITE_DECL(mem);
SUITE_DECL(optee);
SUITE_DECL(pci_mps);
SUITE_DECL(seama);
SUITE_DECL(setexpr);
SUITE_DECL(upl);

static struct suite suites[] = {
	SUITE(addrmap, "very basic test of addrmap command"),
	SUITE(bdinfo, "bdinfo (board info) command"),
	SUITE(bloblist, "bloblist implementation"),
	SUITE(bootm, "bootm command"),
	SUITE(bootstd, "standard boot implementation"),
	SUITE(cmd, "various commands"),
	SUITE(common, "tests for common/ directory"),
	SUITE(dm, "driver model"),
	SUITE(env, "environment"),
	SUITE(exit, "shell exit and variables"),
	SUITE(fdt, "fdt command"),
	SUITE(fdt_overlay, "device tree overlays"),
	SUITE(font, "font command"),
	SUITE(hush, "hush behaviour"),
	SUITE(lib, "library functions"),
	SUITE(loadm, "loadm command parameters and loading memory blob"),
	SUITE(log, "logging functions"),
	SUITE(mbr, "mbr command"),
	SUITE(measurement, "TPM-based measured boot"),
	SUITE(mem, "memory-related commands"),
	SUITE(optee, "OP-TEE"),
	SUITE(pci_mps, "PCI Express Maximum Payload Size"),
	SUITE(seama, "seama command parameters loading and decoding"),
	SUITE(setexpr, "setexpr command"),
	SUITE(upl, "Universal payload support"),
};

/**
 * has_tests() - Check if a suite has tests, i.e. is supported in this build
 *
 * If the suite is run using a command, we have to assume that tests may be
 * present, since we have no visibility
 *
 * @ste: Suite to check
 * Return: true if supported, false if not
 */
static bool has_tests(struct suite *ste)
{
	int n_ents = ste->end - ste->start;

	return n_ents;
}

/** run_suite() - Run a suite of tests */
static int run_suite(struct unit_test_state *uts, struct suite *ste,
		     const char *select_name, int runs_per_test, bool force_run,
		     const char *test_insert)
{
	int n_ents = ste->end - ste->start;
	char prefix[30];
	int ret;

	/* use a standard prefix */
	snprintf(prefix, sizeof(prefix), "%s_test_", ste->name);

	ret = ut_run_list(uts, ste->name, prefix, ste->start, n_ents,
			  select_name, runs_per_test, force_run, test_insert);

	return ret;
}

static void show_stats(struct unit_test_state *uts)
{
	if (uts->run_count < 2)
		return;

	ut_report(&uts->total, uts->run_count);
	if (CONFIG_IS_ENABLED(UNIT_TEST_DURATION) &&
	    uts->total.test_count && uts->worst) {
		ulong avg = uts->total.duration_ms / uts->total.test_count;

		printf("Average test time: %ld ms, worst case '%s' took %d ms\n",
		       avg, uts->worst->name, uts->worst_ms);
	}
}

static void update_stats(struct unit_test_state *uts, const struct suite *ste)
{
	if (CONFIG_IS_ENABLED(UNIT_TEST_DURATION) && uts->cur.test_count) {
		ulong avg;

		avg = uts->cur.duration_ms ?
			uts->cur.duration_ms /
			uts->cur.test_count : 0;
		if (avg > uts->worst_ms) {
			uts->worst_ms = avg;
			uts->worst = ste;
		}
	}
}

static int do_ut_all(struct unit_test_state *uts, const char *select_name,
		     int runs_per_test, bool force_run, const char *test_insert)
{
	int i;
	int retval;
	int any_fail = 0;

	for (i = 0; i < ARRAY_SIZE(suites); i++) {
		struct suite *ste = &suites[i];

		if (has_tests(ste)) {
			printf("----Running %s tests----\n", ste->name);
			retval = run_suite(uts, ste, select_name, runs_per_test,
					   force_run, test_insert);
			if (!any_fail)
				any_fail = retval;
			update_stats(uts, ste);
		}
	}

	return any_fail;
}

static int do_ut_info(bool show_suites)
{
	int suite_count, i;

	for (suite_count = 0, i = 0; i < ARRAY_SIZE(suites); i++) {
		struct suite *ste = &suites[i];

		if (has_tests(ste))
			suite_count++;
	}

	printf("Test suites: %d\n", suite_count);
	printf("Total tests: %d\n", (int)UNIT_TEST_ALL_COUNT());

	if (show_suites) {
		int i, total;

		puts("\nTests  Suite         Purpose");
		puts("\n-----  ------------  -------------------------\n");
		for (i = 0, total = 0; i < ARRAY_SIZE(suites); i++) {
			struct suite *ste = &suites[i];
			long n_ent = ste->end - ste->start;

			if (n_ent) {
				printf("%5ld  %-13.13s %s\n", n_ent, ste->name,
				       ste->help);
				total += n_ent;
			}
		}
		puts("-----  ------------  -------------------------\n");
		printf("%5d  %-13.13s\n", total, "Total");

		if (UNIT_TEST_ALL_COUNT() != total)
			puts("Error: Suite test-count does not match total\n");
	}

	return 0;
}

static struct suite *find_suite(const char *name)
{
	struct suite *ste;
	int i;

	for (i = 0, ste = suites; i < ARRAY_SIZE(suites); i++, ste++) {
		if (!strcmp(ste->name, name))
			return ste;
	}

	return NULL;
}

static int do_ut(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
	const char *test_insert = NULL, *select_name;
	struct unit_test_state uts;
	bool show_suites = false;
	bool force_run = false;
	int runs_per_text = 1;
	struct suite *ste;
	char *name;
	int ret;

	/* drop initial "ut" arg */
	argc--;
	argv++;

	while (argc > 0 && *argv[0] == '-') {
		const char *str = argv[0];

		switch (str[1]) {
		case 'r':
			runs_per_text = dectoul(str + 2, NULL);
			break;
		case 'f':
			force_run = true;
			break;
		case 'I':
			test_insert = str + 2;
			if (!strchr(test_insert, ':'))
				return CMD_RET_USAGE;
			break;
		case 's':
			show_suites = true;
			break;
		}
		argv++;
		argc--;
	}

	if (argc < 1)
		return CMD_RET_USAGE;

	ut_init_state(&uts);
	name = argv[0];
	select_name = cmd_arg1(argc, argv);
	if (!strcmp(name, "all")) {
		ret = do_ut_all(&uts, select_name, runs_per_text, force_run,
				test_insert);
	} else if (!strcmp(name, "info")) {
		ret = do_ut_info(show_suites);
	} else {
		int any_fail = 0;
		const char *p;

		for (; p = strsep(&name, ","), p; name = NULL) {
			ste = find_suite(p);
			if (!ste) {
				printf("Suite '%s' not found\n", p);
				return CMD_RET_FAILURE;
			} else if (!has_tests(ste)) {
				/* perhaps a Kconfig option needs to be set? */
				printf("Suite '%s' is not enabled\n", p);
				return CMD_RET_FAILURE;
			}

			ret = run_suite(&uts, ste, select_name, runs_per_text,
					force_run, test_insert);
			if (!any_fail)
				any_fail = ret;
			update_stats(&uts, ste);
		}
		ret = any_fail;
	}
	show_stats(&uts);
	if (ret)
		return ret;
	ut_uninit_state(&uts);

	return 0;
}

U_BOOT_LONGHELP(ut,
	"[-rs] [-f] [-I<n>:<one_test>][<suites>] - run unit tests\n"
	"   -r<runs>   Number of times to run each test\n"
	"   -f         Force 'manual' tests to run as well\n"
	"   -I         Test to run after <n> other tests have run\n"
	"   -s         Show all suites with ut info\n"
	"   <suites>   Comma-separated list of suites to run\n"
	"\n"
	"Options for <suite>:\n"
	"all       - execute all enabled tests\n"
	"info      - show info about tests [and suites]"
	);

U_BOOT_CMD(
	ut, CONFIG_SYS_MAXARGS, 1, do_ut,
	"unit tests", ut_help_text
);