xref: /freebsd/sys/contrib/openzfs/cmd/zstream/zstream_selftest.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * This file and its contents are supplied under the terms of the
4  * Common Development and Distribution License ("CDDL"), version 1.0.
5  * You may only use this file in accordance with the terms of version
6  * 1.0 of the CDDL.
7  *
8  * A full copy of the text of the CDDL should have accompanied this
9  * source.  A copy of the CDDL is also available via the Internet at
10  * https://opensource.org/license/CDDL-1.0.
11  */
12 
13 /*
14  * Copyright (c) 2026 by Garth Snyder. All rights reserved.
15  */
16 
17 /*
18  * zstream selftest: in-process unit tests for zstream's internal machinery.
19  *
20  *   zstream selftest [-l] [-s seed] [-t nthreads] module [test ...]
21  *
22  * Tests are grouped into modules (see zstream_selftest.h). With no test
23  * names, all of a module's tests run in order. -l lists the available
24  * tests. -s replays a previous run's PRNG seed. -t sets the size of the
25  * shared worker thread pool before any test runs.
26  *
27  * This subcommand is intentionally undocumented in zstream_usage() and the
28  * man page; it exists to be driven by the ZFS test suite.
29  */
30 
31 #include <assert.h>
32 #include <err.h>
33 #include <libspl.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/random.h>
38 #include <sys/stdtypes.h>
39 #include <unistd.h>
40 
41 #include "zstream.h"
42 #include "zstream_backtrace.h"
43 #include "zstream_queue.h"
44 #include "zstream_selftest.h"
45 
46 typedef struct {
47 	const char		*sm_name;
48 	const test_case_t	*sm_cases;
49 } selftest_module_t;
50 
51 static const selftest_module_t modules[] = {
52 	{ "queue", selftest_queue_cases },
53 };
54 
55 #define	NUM_MODULES	(sizeof (modules) / sizeof (modules[0]))
56 
57 uint64_t selftest_seed;
58 
59 static const char *current_test = "(startup)";
60 
61 static void
selftest_usage(void)62 selftest_usage(void)
63 {
64 	(void) fprintf(stderr,
65 	    "usage: zstream selftest [-l] [-s seed] [-t nthreads] "
66 	    "module [test ...]\n"
67 	    "\n"
68 	    "\t-l         list available tests\n"
69 	    "\t-s seed    seed for pseudo-random workloads (for replays)\n"
70 	    "\t-t num     size of the shared worker thread pool\n"
71 	    "\n"
72 	    "Available modules:");
73 	for (int i = 0; i < NUM_MODULES; i++)
74 		(void) fprintf(stderr, " %s", modules[i].sm_name);
75 	(void) fprintf(stderr, "\n");
76 	exit(1);
77 }
78 
79 static const selftest_module_t *
find_module(const char * name)80 find_module(const char *name)
81 {
82 	for (int i = 0; i < NUM_MODULES; i++) {
83 		if (strcmp(name, modules[i].sm_name) == 0)
84 			return (&modules[i]);
85 	}
86 	warnx("unknown module '%s'", name);
87 	selftest_usage();
88 	return (NULL);	/* NOTREACHED */
89 }
90 
91 static void
list_tests(const selftest_module_t * module)92 list_tests(const selftest_module_t *module)
93 {
94 	for (int i = 0; i < NUM_MODULES; i++) {
95 		if (module != NULL && module != &modules[i])
96 			continue;
97 		(void) printf("%s:\n", modules[i].sm_name);
98 		for (const test_case_t *tc = modules[i].sm_cases;
99 		    tc->tc_name != NULL; tc++) {
100 			(void) printf("\t%s\n", tc->tc_name);
101 		}
102 	}
103 }
104 
105 static void
run_case(const test_case_t * tc)106 run_case(const test_case_t *tc)
107 {
108 	(void) printf("Running %-20s ... ", tc->tc_name);
109 	(void) fflush(stdout);
110 	current_test = tc->tc_name;
111 	watchdog_arm();
112 	tc->tc_func();
113 	watchdog_disarm();
114 	(void) printf("OK\n");
115 }
116 
117 static const test_case_t *
find_case(const selftest_module_t * module,const char * name)118 find_case(const selftest_module_t *module, const char *name)
119 {
120 	for (const test_case_t *tc = module->sm_cases;
121 	    tc->tc_name != NULL; tc++) {
122 		if (strcmp(name, tc->tc_name) == 0)
123 			return (tc);
124 	}
125 	errx(2, "module '%s' has no test named '%s' (try -l)",
126 	    module->sm_name, name);
127 	return (NULL);	/* NOTREACHED */
128 }
129 
130 int
zstream_do_selftest(int argc,char * argv[])131 zstream_do_selftest(int argc, char *argv[])
132 {
133 	boolean_t list_only = B_FALSE;
134 	boolean_t have_seed = B_FALSE;
135 	int nthreads = 0;
136 	char *end;
137 	int c;
138 
139 	while ((c = getopt(argc, argv, "ls:t:")) != -1) {
140 		switch (c) {
141 		case 'l':
142 			list_only = B_TRUE;
143 			break;
144 		case 's':
145 			selftest_seed = strtoull(optarg, &end, 0);
146 			if (*optarg == '\0' || *end != '\0') {
147 				warnx("failed to parse seed '%s'", optarg);
148 				selftest_usage();
149 			}
150 			have_seed = B_TRUE;
151 			break;
152 		case 't':
153 			if (sscanf(optarg, "%d", &nthreads) != 1) {
154 				warnx("failed to parse num_threads '%s'",
155 				    optarg);
156 				selftest_usage();
157 			}
158 			zstream_queue_set_num_threads(nthreads);
159 			break;
160 		case '?':
161 			warnx("invalid option '%c'", optopt);
162 			selftest_usage();
163 			break;
164 		}
165 	}
166 	argc -= optind;
167 	argv += optind;
168 
169 	if (list_only) {
170 		list_tests(argc > 0 ? find_module(argv[0]) : NULL);
171 		return (0);
172 	}
173 	if (argc < 1)
174 		selftest_usage();
175 
176 	const selftest_module_t *module = find_module(argv[0]);
177 
178 	/* Needed for random_get_pseudo_bytes() */
179 	libspl_init();
180 	watchdog_init();
181 
182 	if (!have_seed)
183 		random_get_pseudo_bytes((uint8_t *)&selftest_seed,
184 		    sizeof (selftest_seed));
185 	(void) printf("Using seed 0x%016jx (replay with -s 0x%jx)\n",
186 	    (uintmax_t)selftest_seed, (uintmax_t)selftest_seed);
187 
188 	int count = 0;
189 	if (argc == 1) {
190 		for (const test_case_t *tc = module->sm_cases;
191 		    tc->tc_name != NULL; tc++) {
192 			run_case(tc);
193 			count++;
194 		}
195 	} else {
196 		for (int i = 1; i < argc; i++) {
197 			run_case(find_case(module, argv[i]));
198 			count++;
199 		}
200 	}
201 	(void) printf("All %d %s selftest%s passed\n", count, module->sm_name,
202 	    count == 1 ? "" : "s");
203 	libspl_fini();
204 	return (0);
205 }
206