test.c 11 KB

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