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