123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- /*
- * Copyright (c) Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under both the BSD-style license (found in the
- * LICENSE file in the root directory of this source tree) and the GPLv2 (found
- * in the COPYING file in the root directory of this source tree).
- * You may select, at your option, one of the above-listed licenses.
- */
- #include <assert.h>
- #include <getopt.h>
- #include <stdio.h>
- #include <string.h>
- #include "config.h"
- #include "data.h"
- #include "method.h"
- static int g_max_name_len = 0;
- /** Check if a name contains a comma or is too long. */
- static int is_name_bad(char const* name) {
- if (name == NULL)
- return 1;
- int const len = strlen(name);
- if (len > g_max_name_len)
- g_max_name_len = len;
- for (; *name != '\0'; ++name)
- if (*name == ',')
- return 1;
- return 0;
- }
- /** Check if any of the names contain a comma. */
- static int are_names_bad() {
- for (size_t method = 0; methods[method] != NULL; ++method)
- if (is_name_bad(methods[method]->name)) {
- fprintf(stderr, "method name %s is bad\n", methods[method]->name);
- return 1;
- }
- for (size_t datum = 0; data[datum] != NULL; ++datum)
- if (is_name_bad(data[datum]->name)) {
- fprintf(stderr, "data name %s is bad\n", data[datum]->name);
- return 1;
- }
- for (size_t config = 0; configs[config] != NULL; ++config)
- if (is_name_bad(configs[config]->name)) {
- fprintf(stderr, "config name %s is bad\n", configs[config]->name);
- return 1;
- }
- return 0;
- }
- /**
- * Option parsing using getopt.
- * When you add a new option update: long_options, long_extras, and
- * short_options.
- */
- /** Option variables filled by parse_args. */
- static char const* g_output = NULL;
- static char const* g_diff = NULL;
- static char const* g_cache = NULL;
- static char const* g_zstdcli = NULL;
- static char const* g_config = NULL;
- static char const* g_data = NULL;
- static char const* g_method = NULL;
- typedef enum {
- required_option,
- optional_option,
- help_option,
- } option_type;
- /**
- * Extra state that we need to keep per-option that we can't store in getopt.
- */
- struct option_extra {
- int id; /**< The short option name, used as an id. */
- char const* help; /**< The help message. */
- option_type opt_type; /**< The option type: required, optional, or help. */
- char const** value; /**< The value to set or NULL if no_argument. */
- };
- /** The options. */
- static struct option long_options[] = {
- {"cache", required_argument, NULL, 'c'},
- {"output", required_argument, NULL, 'o'},
- {"zstd", required_argument, NULL, 'z'},
- {"config", required_argument, NULL, 128},
- {"data", required_argument, NULL, 129},
- {"method", required_argument, NULL, 130},
- {"diff", required_argument, NULL, 'd'},
- {"help", no_argument, NULL, 'h'},
- };
- static size_t const nargs = sizeof(long_options) / sizeof(long_options[0]);
- /** The extra info for the options. Must be in the same order as the options. */
- static struct option_extra long_extras[] = {
- {'c', "the cache directory", required_option, &g_cache},
- {'o', "write the results here", required_option, &g_output},
- {'z', "zstd cli tool", required_option, &g_zstdcli},
- {128, "use this config", optional_option, &g_config},
- {129, "use this data", optional_option, &g_data},
- {130, "use this method", optional_option, &g_method},
- {'d', "compare the results to this file", optional_option, &g_diff},
- {'h', "display this message", help_option, NULL},
- };
- /** The short options. Must correspond to the options. */
- static char const short_options[] = "c:d:ho:z:";
- /** Return the help string for the option type. */
- static char const* required_message(option_type opt_type) {
- switch (opt_type) {
- case required_option:
- return "[required]";
- case optional_option:
- return "[optional]";
- case help_option:
- return "";
- default:
- assert(0);
- return NULL;
- }
- }
- /** Print the help for the program. */
- static void print_help(void) {
- fprintf(stderr, "regression test runner\n");
- size_t const nargs = sizeof(long_options) / sizeof(long_options[0]);
- for (size_t i = 0; i < nargs; ++i) {
- if (long_options[i].val < 128) {
- /* Long / short - help [option type] */
- fprintf(
- stderr,
- "--%s / -%c \t- %s %s\n",
- long_options[i].name,
- long_options[i].val,
- long_extras[i].help,
- required_message(long_extras[i].opt_type));
- } else {
- /* Short / long - help [option type] */
- fprintf(
- stderr,
- "--%s \t- %s %s\n",
- long_options[i].name,
- long_extras[i].help,
- required_message(long_extras[i].opt_type));
- }
- }
- }
- /** Parse the arguments. Return 0 on success. Print help on failure. */
- static int parse_args(int argc, char** argv) {
- int option_index = 0;
- int c;
- while (1) {
- c = getopt_long(argc, argv, short_options, long_options, &option_index);
- if (c == -1)
- break;
- int found = 0;
- for (size_t i = 0; i < nargs; ++i) {
- if (c == long_extras[i].id && long_extras[i].value != NULL) {
- *long_extras[i].value = optarg;
- found = 1;
- break;
- }
- }
- if (found)
- continue;
- switch (c) {
- case 'h':
- case '?':
- default:
- print_help();
- return 1;
- }
- }
- int bad = 0;
- for (size_t i = 0; i < nargs; ++i) {
- if (long_extras[i].opt_type != required_option)
- continue;
- if (long_extras[i].value == NULL)
- continue;
- if (*long_extras[i].value != NULL)
- continue;
- fprintf(
- stderr,
- "--%s is a required argument but is not set\n",
- long_options[i].name);
- bad = 1;
- }
- if (bad) {
- fprintf(stderr, "\n");
- print_help();
- return 1;
- }
- return 0;
- }
- /** Helper macro to print to stderr and a file. */
- #define tprintf(file, ...) \
- do { \
- fprintf(file, __VA_ARGS__); \
- fprintf(stderr, __VA_ARGS__); \
- } while (0)
- /** Helper macro to flush stderr and a file. */
- #define tflush(file) \
- do { \
- fflush(file); \
- fflush(stderr); \
- } while (0)
- void tprint_names(
- FILE* results,
- char const* data_name,
- char const* config_name,
- char const* method_name) {
- int const data_padding = g_max_name_len - strlen(data_name);
- int const config_padding = g_max_name_len - strlen(config_name);
- int const method_padding = g_max_name_len - strlen(method_name);
- tprintf(
- results,
- "%s, %*s%s, %*s%s, %*s",
- data_name,
- data_padding,
- "",
- config_name,
- config_padding,
- "",
- method_name,
- method_padding,
- "");
- }
- /**
- * Run all the regression tests and record the results table to results and
- * stderr progressively.
- */
- static int run_all(FILE* results) {
- tprint_names(results, "Data", "Config", "Method");
- tprintf(results, "Total compressed size\n");
- for (size_t method = 0; methods[method] != NULL; ++method) {
- if (g_method != NULL && strcmp(methods[method]->name, g_method))
- continue;
- for (size_t datum = 0; data[datum] != NULL; ++datum) {
- if (g_data != NULL && strcmp(data[datum]->name, g_data))
- continue;
- /* Create the state common to all configs */
- method_state_t* state = methods[method]->create(data[datum]);
- for (size_t config = 0; configs[config] != NULL; ++config) {
- if (g_config != NULL && strcmp(configs[config]->name, g_config))
- continue;
- if (config_skip_data(configs[config], data[datum]))
- continue;
- /* Print the result for the (method, data, config) tuple. */
- result_t const result =
- methods[method]->compress(state, configs[config]);
- if (result_is_skip(result))
- continue;
- tprint_names(
- results,
- data[datum]->name,
- configs[config]->name,
- methods[method]->name);
- if (result_is_error(result)) {
- tprintf(results, "%s\n", result_get_error_string(result));
- } else {
- tprintf(
- results,
- "%llu\n",
- (unsigned long long)result_get_data(result).total_size);
- }
- tflush(results);
- }
- methods[method]->destroy(state);
- }
- }
- return 0;
- }
- /** memcmp() the old results file and the new results file. */
- static int diff_results(char const* actual_file, char const* expected_file) {
- data_buffer_t const actual = data_buffer_read(actual_file);
- data_buffer_t const expected = data_buffer_read(expected_file);
- int ret = 1;
- if (actual.data == NULL) {
- fprintf(stderr, "failed to open results '%s' for diff\n", actual_file);
- goto out;
- }
- if (expected.data == NULL) {
- fprintf(
- stderr,
- "failed to open previous results '%s' for diff\n",
- expected_file);
- goto out;
- }
- ret = data_buffer_compare(actual, expected);
- if (ret != 0) {
- fprintf(
- stderr,
- "actual results '%s' does not match expected results '%s'\n",
- actual_file,
- expected_file);
- } else {
- fprintf(stderr, "actual results match expected results\n");
- }
- out:
- data_buffer_free(actual);
- data_buffer_free(expected);
- return ret;
- }
- int main(int argc, char** argv) {
- /* Parse args and validate modules. */
- int ret = parse_args(argc, argv);
- if (ret != 0)
- return ret;
- if (are_names_bad())
- return 1;
- /* Initialize modules. */
- method_set_zstdcli(g_zstdcli);
- ret = data_init(g_cache);
- if (ret != 0) {
- fprintf(stderr, "data_init() failed with error=%s\n", strerror(ret));
- return 1;
- }
- /* Run the regression tests. */
- ret = 1;
- FILE* results = fopen(g_output, "w");
- if (results == NULL) {
- fprintf(stderr, "Failed to open the output file\n");
- goto out;
- }
- ret = run_all(results);
- fclose(results);
- if (ret != 0)
- goto out;
- if (g_diff)
- /* Diff the new results with the previous results. */
- ret = diff_results(g_output, g_diff);
- out:
- data_finish();
- return ret;
- }
|