2 * Copyright (c) Meta Platforms, Inc. and affiliates.
5 * This source code is licensed under both the BSD-style license (found in the
6 * LICENSE file in the root directory of this source tree) and the GPLv2 (found
7 * in the COPYING file in the root directory of this source tree).
8 * You may select, at your option, one of the above-listed licenses.
20 static int g_max_name_len = 0;
22 /** Check if a name contains a comma or is too long. */
23 static int is_name_bad(char const* name) {
26 int const len = strlen(name);
27 if (len > g_max_name_len)
29 for (; *name != '\0'; ++name)
35 /** Check if any of the names contain a comma. */
36 static int are_names_bad() {
37 for (size_t method = 0; methods[method] != NULL; ++method)
38 if (is_name_bad(methods[method]->name)) {
39 fprintf(stderr, "method name %s is bad\n", methods[method]->name);
42 for (size_t datum = 0; data[datum] != NULL; ++datum)
43 if (is_name_bad(data[datum]->name)) {
44 fprintf(stderr, "data name %s is bad\n", data[datum]->name);
47 for (size_t config = 0; configs[config] != NULL; ++config)
48 if (is_name_bad(configs[config]->name)) {
49 fprintf(stderr, "config name %s is bad\n", configs[config]->name);
56 * Option parsing using getopt.
57 * When you add a new option update: long_options, long_extras, and
61 /** Option variables filled by parse_args. */
62 static char const* g_output = NULL;
63 static char const* g_diff = NULL;
64 static char const* g_cache = NULL;
65 static char const* g_zstdcli = NULL;
66 static char const* g_config = NULL;
67 static char const* g_data = NULL;
68 static char const* g_method = NULL;
77 * Extra state that we need to keep per-option that we can't store in getopt.
80 int id; /**< The short option name, used as an id. */
81 char const* help; /**< The help message. */
82 option_type opt_type; /**< The option type: required, optional, or help. */
83 char const** value; /**< The value to set or NULL if no_argument. */
87 static struct option long_options[] = {
88 {"cache", required_argument, NULL, 'c'},
89 {"output", required_argument, NULL, 'o'},
90 {"zstd", required_argument, NULL, 'z'},
91 {"config", required_argument, NULL, 128},
92 {"data", required_argument, NULL, 129},
93 {"method", required_argument, NULL, 130},
94 {"diff", required_argument, NULL, 'd'},
95 {"help", no_argument, NULL, 'h'},
98 static size_t const nargs = sizeof(long_options) / sizeof(long_options[0]);
100 /** The extra info for the options. Must be in the same order as the options. */
101 static struct option_extra long_extras[] = {
102 {'c', "the cache directory", required_option, &g_cache},
103 {'o', "write the results here", required_option, &g_output},
104 {'z', "zstd cli tool", required_option, &g_zstdcli},
105 {128, "use this config", optional_option, &g_config},
106 {129, "use this data", optional_option, &g_data},
107 {130, "use this method", optional_option, &g_method},
108 {'d', "compare the results to this file", optional_option, &g_diff},
109 {'h', "display this message", help_option, NULL},
112 /** The short options. Must correspond to the options. */
113 static char const short_options[] = "c:d:ho:z:";
115 /** Return the help string for the option type. */
116 static char const* required_message(option_type opt_type) {
118 case required_option:
120 case optional_option:
130 /** Print the help for the program. */
131 static void print_help(void) {
132 fprintf(stderr, "regression test runner\n");
133 size_t const nargs = sizeof(long_options) / sizeof(long_options[0]);
134 for (size_t i = 0; i < nargs; ++i) {
135 if (long_options[i].val < 128) {
136 /* Long / short - help [option type] */
139 "--%s / -%c \t- %s %s\n",
140 long_options[i].name,
143 required_message(long_extras[i].opt_type));
145 /* Short / long - help [option type] */
149 long_options[i].name,
151 required_message(long_extras[i].opt_type));
156 /** Parse the arguments. Return 0 on success. Print help on failure. */
157 static int parse_args(int argc, char** argv) {
158 int option_index = 0;
162 c = getopt_long(argc, argv, short_options, long_options, &option_index);
167 for (size_t i = 0; i < nargs; ++i) {
168 if (c == long_extras[i].id && long_extras[i].value != NULL) {
169 *long_extras[i].value = optarg;
187 for (size_t i = 0; i < nargs; ++i) {
188 if (long_extras[i].opt_type != required_option)
190 if (long_extras[i].value == NULL)
192 if (*long_extras[i].value != NULL)
196 "--%s is a required argument but is not set\n",
197 long_options[i].name);
201 fprintf(stderr, "\n");
209 /** Helper macro to print to stderr and a file. */
210 #define tprintf(file, ...) \
212 fprintf(file, __VA_ARGS__); \
213 fprintf(stderr, __VA_ARGS__); \
215 /** Helper macro to flush stderr and a file. */
216 #define tflush(file) \
224 char const* data_name,
225 char const* config_name,
226 char const* method_name) {
227 int const data_padding = g_max_name_len - strlen(data_name);
228 int const config_padding = g_max_name_len - strlen(config_name);
229 int const method_padding = g_max_name_len - strlen(method_name);
233 "%s, %*s%s, %*s%s, %*s",
246 * Run all the regression tests and record the results table to results and
247 * stderr progressively.
249 static int run_all(FILE* results) {
250 tprint_names(results, "Data", "Config", "Method");
251 tprintf(results, "Total compressed size\n");
252 for (size_t method = 0; methods[method] != NULL; ++method) {
253 if (g_method != NULL && strcmp(methods[method]->name, g_method))
255 for (size_t datum = 0; data[datum] != NULL; ++datum) {
256 if (g_data != NULL && strcmp(data[datum]->name, g_data))
258 /* Create the state common to all configs */
259 method_state_t* state = methods[method]->create(data[datum]);
260 for (size_t config = 0; configs[config] != NULL; ++config) {
261 if (g_config != NULL && strcmp(configs[config]->name, g_config))
263 if (config_skip_data(configs[config], data[datum]))
265 /* Print the result for the (method, data, config) tuple. */
266 result_t const result =
267 methods[method]->compress(state, configs[config]);
268 if (result_is_skip(result))
273 configs[config]->name,
274 methods[method]->name);
275 if (result_is_error(result)) {
276 tprintf(results, "%s\n", result_get_error_string(result));
281 (unsigned long long)result_get_data(result).total_size);
285 methods[method]->destroy(state);
291 /** memcmp() the old results file and the new results file. */
292 static int diff_results(char const* actual_file, char const* expected_file) {
293 data_buffer_t const actual = data_buffer_read(actual_file);
294 data_buffer_t const expected = data_buffer_read(expected_file);
297 if (actual.data == NULL) {
298 fprintf(stderr, "failed to open results '%s' for diff\n", actual_file);
301 if (expected.data == NULL) {
304 "failed to open previous results '%s' for diff\n",
309 ret = data_buffer_compare(actual, expected);
313 "actual results '%s' does not match expected results '%s'\n",
317 fprintf(stderr, "actual results match expected results\n");
320 data_buffer_free(actual);
321 data_buffer_free(expected);
325 int main(int argc, char** argv) {
326 /* Parse args and validate modules. */
327 int ret = parse_args(argc, argv);
334 /* Initialize modules. */
335 method_set_zstdcli(g_zstdcli);
336 ret = data_init(g_cache);
338 fprintf(stderr, "data_init() failed with error=%s\n", strerror(ret));
342 /* Run the regression tests. */
344 FILE* results = fopen(g_output, "w");
345 if (results == NULL) {
346 fprintf(stderr, "Failed to open the output file\n");
349 ret = run_all(results);
356 /* Diff the new results with the previous results. */
357 ret = diff_results(g_output, g_diff);