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