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