xref: /netbsd-src/external/bsd/zstd/dist/tests/regression/test.c (revision 3117ece4fc4a4ca4489ba793710b60b0d26bab6c)
1*3117ece4Schristos /*
2*3117ece4Schristos  * Copyright (c) Meta Platforms, Inc. and affiliates.
3*3117ece4Schristos  * All rights reserved.
4*3117ece4Schristos  *
5*3117ece4Schristos  * This source code is licensed under both the BSD-style license (found in the
6*3117ece4Schristos  * LICENSE file in the root directory of this source tree) and the GPLv2 (found
7*3117ece4Schristos  * in the COPYING file in the root directory of this source tree).
8*3117ece4Schristos  * You may select, at your option, one of the above-listed licenses.
9*3117ece4Schristos  */
10*3117ece4Schristos 
11*3117ece4Schristos #include <assert.h>
12*3117ece4Schristos #include <getopt.h>
13*3117ece4Schristos #include <stdio.h>
14*3117ece4Schristos #include <string.h>
15*3117ece4Schristos 
16*3117ece4Schristos #include "config.h"
17*3117ece4Schristos #include "data.h"
18*3117ece4Schristos #include "method.h"
19*3117ece4Schristos 
20*3117ece4Schristos static int g_max_name_len = 0;
21*3117ece4Schristos 
22*3117ece4Schristos /** Check if a name contains a comma or is too long. */
23*3117ece4Schristos static int is_name_bad(char const* name) {
24*3117ece4Schristos     if (name == NULL)
25*3117ece4Schristos         return 1;
26*3117ece4Schristos     int const len = strlen(name);
27*3117ece4Schristos     if (len > g_max_name_len)
28*3117ece4Schristos         g_max_name_len = len;
29*3117ece4Schristos     for (; *name != '\0'; ++name)
30*3117ece4Schristos         if (*name == ',')
31*3117ece4Schristos             return 1;
32*3117ece4Schristos     return 0;
33*3117ece4Schristos }
34*3117ece4Schristos 
35*3117ece4Schristos /** Check if any of the names contain a comma. */
36*3117ece4Schristos static int are_names_bad() {
37*3117ece4Schristos     for (size_t method = 0; methods[method] != NULL; ++method)
38*3117ece4Schristos         if (is_name_bad(methods[method]->name)) {
39*3117ece4Schristos             fprintf(stderr, "method name %s is bad\n", methods[method]->name);
40*3117ece4Schristos             return 1;
41*3117ece4Schristos         }
42*3117ece4Schristos     for (size_t datum = 0; data[datum] != NULL; ++datum)
43*3117ece4Schristos         if (is_name_bad(data[datum]->name)) {
44*3117ece4Schristos             fprintf(stderr, "data name %s is bad\n", data[datum]->name);
45*3117ece4Schristos             return 1;
46*3117ece4Schristos         }
47*3117ece4Schristos     for (size_t config = 0; configs[config] != NULL; ++config)
48*3117ece4Schristos         if (is_name_bad(configs[config]->name)) {
49*3117ece4Schristos             fprintf(stderr, "config name %s is bad\n", configs[config]->name);
50*3117ece4Schristos             return 1;
51*3117ece4Schristos         }
52*3117ece4Schristos     return 0;
53*3117ece4Schristos }
54*3117ece4Schristos 
55*3117ece4Schristos /**
56*3117ece4Schristos  * Option parsing using getopt.
57*3117ece4Schristos  * When you add a new option update: long_options, long_extras, and
58*3117ece4Schristos  * short_options.
59*3117ece4Schristos  */
60*3117ece4Schristos 
61*3117ece4Schristos /** Option variables filled by parse_args. */
62*3117ece4Schristos static char const* g_output = NULL;
63*3117ece4Schristos static char const* g_diff = NULL;
64*3117ece4Schristos static char const* g_cache = NULL;
65*3117ece4Schristos static char const* g_zstdcli = NULL;
66*3117ece4Schristos static char const* g_config = NULL;
67*3117ece4Schristos static char const* g_data = NULL;
68*3117ece4Schristos static char const* g_method = NULL;
69*3117ece4Schristos 
70*3117ece4Schristos typedef enum {
71*3117ece4Schristos     required_option,
72*3117ece4Schristos     optional_option,
73*3117ece4Schristos     help_option,
74*3117ece4Schristos } option_type;
75*3117ece4Schristos 
76*3117ece4Schristos /**
77*3117ece4Schristos  * Extra state that we need to keep per-option that we can't store in getopt.
78*3117ece4Schristos  */
79*3117ece4Schristos struct option_extra {
80*3117ece4Schristos     int id; /**< The short option name, used as an id. */
81*3117ece4Schristos     char const* help; /**< The help message. */
82*3117ece4Schristos     option_type opt_type; /**< The option type: required, optional, or help. */
83*3117ece4Schristos     char const** value; /**< The value to set or NULL if no_argument. */
84*3117ece4Schristos };
85*3117ece4Schristos 
86*3117ece4Schristos /** The options. */
87*3117ece4Schristos static struct option long_options[] = {
88*3117ece4Schristos     {"cache", required_argument, NULL, 'c'},
89*3117ece4Schristos     {"output", required_argument, NULL, 'o'},
90*3117ece4Schristos     {"zstd", required_argument, NULL, 'z'},
91*3117ece4Schristos     {"config", required_argument, NULL, 128},
92*3117ece4Schristos     {"data", required_argument, NULL, 129},
93*3117ece4Schristos     {"method", required_argument, NULL, 130},
94*3117ece4Schristos     {"diff", required_argument, NULL, 'd'},
95*3117ece4Schristos     {"help", no_argument, NULL, 'h'},
96*3117ece4Schristos };
97*3117ece4Schristos 
98*3117ece4Schristos static size_t const nargs = sizeof(long_options) / sizeof(long_options[0]);
99*3117ece4Schristos 
100*3117ece4Schristos /** The extra info for the options. Must be in the same order as the options. */
101*3117ece4Schristos static struct option_extra long_extras[] = {
102*3117ece4Schristos     {'c', "the cache directory", required_option, &g_cache},
103*3117ece4Schristos     {'o', "write the results here", required_option, &g_output},
104*3117ece4Schristos     {'z', "zstd cli tool", required_option, &g_zstdcli},
105*3117ece4Schristos     {128, "use this config", optional_option, &g_config},
106*3117ece4Schristos     {129, "use this data", optional_option, &g_data},
107*3117ece4Schristos     {130, "use this method", optional_option, &g_method},
108*3117ece4Schristos     {'d', "compare the results to this file", optional_option, &g_diff},
109*3117ece4Schristos     {'h', "display this message", help_option, NULL},
110*3117ece4Schristos };
111*3117ece4Schristos 
112*3117ece4Schristos /** The short options. Must correspond to the options. */
113*3117ece4Schristos static char const short_options[] = "c:d:ho:z:";
114*3117ece4Schristos 
115*3117ece4Schristos /** Return the help string for the option type. */
116*3117ece4Schristos static char const* required_message(option_type opt_type) {
117*3117ece4Schristos     switch (opt_type) {
118*3117ece4Schristos         case required_option:
119*3117ece4Schristos             return "[required]";
120*3117ece4Schristos         case optional_option:
121*3117ece4Schristos             return "[optional]";
122*3117ece4Schristos         case help_option:
123*3117ece4Schristos             return "";
124*3117ece4Schristos         default:
125*3117ece4Schristos             assert(0);
126*3117ece4Schristos             return NULL;
127*3117ece4Schristos     }
128*3117ece4Schristos }
129*3117ece4Schristos 
130*3117ece4Schristos /** Print the help for the program. */
131*3117ece4Schristos static void print_help(void) {
132*3117ece4Schristos     fprintf(stderr, "regression test runner\n");
133*3117ece4Schristos     size_t const nargs = sizeof(long_options) / sizeof(long_options[0]);
134*3117ece4Schristos     for (size_t i = 0; i < nargs; ++i) {
135*3117ece4Schristos         if (long_options[i].val < 128) {
136*3117ece4Schristos             /* Long / short  - help [option type] */
137*3117ece4Schristos             fprintf(
138*3117ece4Schristos                 stderr,
139*3117ece4Schristos                 "--%s / -%c \t- %s %s\n",
140*3117ece4Schristos                 long_options[i].name,
141*3117ece4Schristos                 long_options[i].val,
142*3117ece4Schristos                 long_extras[i].help,
143*3117ece4Schristos                 required_message(long_extras[i].opt_type));
144*3117ece4Schristos         } else {
145*3117ece4Schristos             /* Short / long  - help [option type] */
146*3117ece4Schristos             fprintf(
147*3117ece4Schristos                 stderr,
148*3117ece4Schristos                 "--%s      \t- %s %s\n",
149*3117ece4Schristos                 long_options[i].name,
150*3117ece4Schristos                 long_extras[i].help,
151*3117ece4Schristos                 required_message(long_extras[i].opt_type));
152*3117ece4Schristos         }
153*3117ece4Schristos     }
154*3117ece4Schristos }
155*3117ece4Schristos 
156*3117ece4Schristos /** Parse the arguments. Return 0 on success. Print help on failure. */
157*3117ece4Schristos static int parse_args(int argc, char** argv) {
158*3117ece4Schristos     int option_index = 0;
159*3117ece4Schristos     int c;
160*3117ece4Schristos 
161*3117ece4Schristos     while (1) {
162*3117ece4Schristos         c = getopt_long(argc, argv, short_options, long_options, &option_index);
163*3117ece4Schristos         if (c == -1)
164*3117ece4Schristos             break;
165*3117ece4Schristos 
166*3117ece4Schristos         int found = 0;
167*3117ece4Schristos         for (size_t i = 0; i < nargs; ++i) {
168*3117ece4Schristos             if (c == long_extras[i].id && long_extras[i].value != NULL) {
169*3117ece4Schristos                 *long_extras[i].value = optarg;
170*3117ece4Schristos                 found = 1;
171*3117ece4Schristos                 break;
172*3117ece4Schristos             }
173*3117ece4Schristos         }
174*3117ece4Schristos         if (found)
175*3117ece4Schristos             continue;
176*3117ece4Schristos 
177*3117ece4Schristos         switch (c) {
178*3117ece4Schristos             case 'h':
179*3117ece4Schristos             case '?':
180*3117ece4Schristos             default:
181*3117ece4Schristos                 print_help();
182*3117ece4Schristos                 return 1;
183*3117ece4Schristos         }
184*3117ece4Schristos     }
185*3117ece4Schristos 
186*3117ece4Schristos     int bad = 0;
187*3117ece4Schristos     for (size_t i = 0; i < nargs; ++i) {
188*3117ece4Schristos         if (long_extras[i].opt_type != required_option)
189*3117ece4Schristos             continue;
190*3117ece4Schristos         if (long_extras[i].value == NULL)
191*3117ece4Schristos             continue;
192*3117ece4Schristos         if (*long_extras[i].value != NULL)
193*3117ece4Schristos             continue;
194*3117ece4Schristos         fprintf(
195*3117ece4Schristos             stderr,
196*3117ece4Schristos             "--%s is a required argument but is not set\n",
197*3117ece4Schristos             long_options[i].name);
198*3117ece4Schristos         bad = 1;
199*3117ece4Schristos     }
200*3117ece4Schristos     if (bad) {
201*3117ece4Schristos         fprintf(stderr, "\n");
202*3117ece4Schristos         print_help();
203*3117ece4Schristos         return 1;
204*3117ece4Schristos     }
205*3117ece4Schristos 
206*3117ece4Schristos     return 0;
207*3117ece4Schristos }
208*3117ece4Schristos 
209*3117ece4Schristos /** Helper macro to print to stderr and a file. */
210*3117ece4Schristos #define tprintf(file, ...)            \
211*3117ece4Schristos     do {                              \
212*3117ece4Schristos         fprintf(file, __VA_ARGS__);   \
213*3117ece4Schristos         fprintf(stderr, __VA_ARGS__); \
214*3117ece4Schristos     } while (0)
215*3117ece4Schristos /** Helper macro to flush stderr and a file. */
216*3117ece4Schristos #define tflush(file)    \
217*3117ece4Schristos     do {                \
218*3117ece4Schristos         fflush(file);   \
219*3117ece4Schristos         fflush(stderr); \
220*3117ece4Schristos     } while (0)
221*3117ece4Schristos 
222*3117ece4Schristos void tprint_names(
223*3117ece4Schristos     FILE* results,
224*3117ece4Schristos     char const* data_name,
225*3117ece4Schristos     char const* config_name,
226*3117ece4Schristos     char const* method_name) {
227*3117ece4Schristos     int const data_padding = g_max_name_len - strlen(data_name);
228*3117ece4Schristos     int const config_padding = g_max_name_len - strlen(config_name);
229*3117ece4Schristos     int const method_padding = g_max_name_len - strlen(method_name);
230*3117ece4Schristos 
231*3117ece4Schristos     tprintf(
232*3117ece4Schristos         results,
233*3117ece4Schristos         "%s, %*s%s, %*s%s, %*s",
234*3117ece4Schristos         data_name,
235*3117ece4Schristos         data_padding,
236*3117ece4Schristos         "",
237*3117ece4Schristos         config_name,
238*3117ece4Schristos         config_padding,
239*3117ece4Schristos         "",
240*3117ece4Schristos         method_name,
241*3117ece4Schristos         method_padding,
242*3117ece4Schristos         "");
243*3117ece4Schristos }
244*3117ece4Schristos 
245*3117ece4Schristos /**
246*3117ece4Schristos  * Run all the regression tests and record the results table to results and
247*3117ece4Schristos  * stderr progressively.
248*3117ece4Schristos  */
249*3117ece4Schristos static int run_all(FILE* results) {
250*3117ece4Schristos     tprint_names(results, "Data", "Config", "Method");
251*3117ece4Schristos     tprintf(results, "Total compressed size\n");
252*3117ece4Schristos     for (size_t method = 0; methods[method] != NULL; ++method) {
253*3117ece4Schristos         if (g_method != NULL && strcmp(methods[method]->name, g_method))
254*3117ece4Schristos             continue;
255*3117ece4Schristos         for (size_t datum = 0; data[datum] != NULL; ++datum) {
256*3117ece4Schristos             if (g_data != NULL && strcmp(data[datum]->name, g_data))
257*3117ece4Schristos                 continue;
258*3117ece4Schristos             /* Create the state common to all configs */
259*3117ece4Schristos             method_state_t* state = methods[method]->create(data[datum]);
260*3117ece4Schristos             for (size_t config = 0; configs[config] != NULL; ++config) {
261*3117ece4Schristos                 if (g_config != NULL && strcmp(configs[config]->name, g_config))
262*3117ece4Schristos                     continue;
263*3117ece4Schristos                 if (config_skip_data(configs[config], data[datum]))
264*3117ece4Schristos                     continue;
265*3117ece4Schristos                 /* Print the result for the (method, data, config) tuple. */
266*3117ece4Schristos                 result_t const result =
267*3117ece4Schristos                     methods[method]->compress(state, configs[config]);
268*3117ece4Schristos                 if (result_is_skip(result))
269*3117ece4Schristos                     continue;
270*3117ece4Schristos                 tprint_names(
271*3117ece4Schristos                     results,
272*3117ece4Schristos                     data[datum]->name,
273*3117ece4Schristos                     configs[config]->name,
274*3117ece4Schristos                     methods[method]->name);
275*3117ece4Schristos                 if (result_is_error(result)) {
276*3117ece4Schristos                     tprintf(results, "%s\n", result_get_error_string(result));
277*3117ece4Schristos                 } else {
278*3117ece4Schristos                     tprintf(
279*3117ece4Schristos                         results,
280*3117ece4Schristos                         "%llu\n",
281*3117ece4Schristos                         (unsigned long long)result_get_data(result).total_size);
282*3117ece4Schristos                 }
283*3117ece4Schristos                 tflush(results);
284*3117ece4Schristos             }
285*3117ece4Schristos             methods[method]->destroy(state);
286*3117ece4Schristos         }
287*3117ece4Schristos     }
288*3117ece4Schristos     return 0;
289*3117ece4Schristos }
290*3117ece4Schristos 
291*3117ece4Schristos /** memcmp() the old results file and the new results file. */
292*3117ece4Schristos static int diff_results(char const* actual_file, char const* expected_file) {
293*3117ece4Schristos     data_buffer_t const actual = data_buffer_read(actual_file);
294*3117ece4Schristos     data_buffer_t const expected = data_buffer_read(expected_file);
295*3117ece4Schristos     int ret = 1;
296*3117ece4Schristos 
297*3117ece4Schristos     if (actual.data == NULL) {
298*3117ece4Schristos         fprintf(stderr, "failed to open results '%s' for diff\n", actual_file);
299*3117ece4Schristos         goto out;
300*3117ece4Schristos     }
301*3117ece4Schristos     if (expected.data == NULL) {
302*3117ece4Schristos         fprintf(
303*3117ece4Schristos             stderr,
304*3117ece4Schristos             "failed to open previous results '%s' for diff\n",
305*3117ece4Schristos             expected_file);
306*3117ece4Schristos         goto out;
307*3117ece4Schristos     }
308*3117ece4Schristos 
309*3117ece4Schristos     ret = data_buffer_compare(actual, expected);
310*3117ece4Schristos     if (ret != 0) {
311*3117ece4Schristos         fprintf(
312*3117ece4Schristos             stderr,
313*3117ece4Schristos             "actual results '%s' does not match expected results '%s'\n",
314*3117ece4Schristos             actual_file,
315*3117ece4Schristos             expected_file);
316*3117ece4Schristos     } else {
317*3117ece4Schristos         fprintf(stderr, "actual results match expected results\n");
318*3117ece4Schristos     }
319*3117ece4Schristos out:
320*3117ece4Schristos     data_buffer_free(actual);
321*3117ece4Schristos     data_buffer_free(expected);
322*3117ece4Schristos     return ret;
323*3117ece4Schristos }
324*3117ece4Schristos 
325*3117ece4Schristos int main(int argc, char** argv) {
326*3117ece4Schristos     /* Parse args and validate modules. */
327*3117ece4Schristos     int ret = parse_args(argc, argv);
328*3117ece4Schristos     if (ret != 0)
329*3117ece4Schristos         return ret;
330*3117ece4Schristos 
331*3117ece4Schristos     if (are_names_bad())
332*3117ece4Schristos         return 1;
333*3117ece4Schristos 
334*3117ece4Schristos     /* Initialize modules. */
335*3117ece4Schristos     method_set_zstdcli(g_zstdcli);
336*3117ece4Schristos     ret = data_init(g_cache);
337*3117ece4Schristos     if (ret != 0) {
338*3117ece4Schristos         fprintf(stderr, "data_init() failed with error=%s\n", strerror(ret));
339*3117ece4Schristos         return 1;
340*3117ece4Schristos     }
341*3117ece4Schristos 
342*3117ece4Schristos     /* Run the regression tests. */
343*3117ece4Schristos     ret = 1;
344*3117ece4Schristos     FILE* results = fopen(g_output, "w");
345*3117ece4Schristos     if (results == NULL) {
346*3117ece4Schristos         fprintf(stderr, "Failed to open the output file\n");
347*3117ece4Schristos         goto out;
348*3117ece4Schristos     }
349*3117ece4Schristos     ret = run_all(results);
350*3117ece4Schristos     fclose(results);
351*3117ece4Schristos 
352*3117ece4Schristos     if (ret != 0)
353*3117ece4Schristos         goto out;
354*3117ece4Schristos 
355*3117ece4Schristos     if (g_diff)
356*3117ece4Schristos         /* Diff the new results with the previous results. */
357*3117ece4Schristos         ret = diff_results(g_output, g_diff);
358*3117ece4Schristos 
359*3117ece4Schristos out:
360*3117ece4Schristos     data_finish();
361*3117ece4Schristos     return ret;
362*3117ece4Schristos }
363