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