| /* |
| * svn_test_main.c: shared main() & friends for SVN test-suite programs |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #include <string.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <setjmp.h> |
| #ifdef WIN32 |
| #include <crtdbg.h> |
| #endif |
| |
| #include <apr_pools.h> |
| #include <apr_general.h> |
| #include <apr_signal.h> |
| #include <apr_env.h> |
| |
| #include "svn_cmdline.h" |
| #include "svn_opt.h" |
| #include "svn_pools.h" |
| #include "svn_error.h" |
| #include "svn_test.h" |
| #include "svn_io.h" |
| #include "svn_path.h" |
| #include "svn_ctype.h" |
| #include "svn_utf.h" |
| |
| #include "private/svn_cmdline_private.h" |
| |
| #include "svn_private_config.h" |
| |
| /* Some Subversion test programs may want to parse options in the |
| argument list, so we remember it here. */ |
| int test_argc; |
| const char **test_argv; |
| |
| |
| /* Test option: Print more output */ |
| static svn_boolean_t verbose_mode = FALSE; |
| |
| /* Test option: Print only unexpected results */ |
| static svn_boolean_t quiet_mode = FALSE; |
| |
| /* Test option: Remove test directories after success */ |
| static svn_boolean_t cleanup_mode = FALSE; |
| |
| /* Test option: Allow segfaults */ |
| static svn_boolean_t allow_segfaults = FALSE; |
| |
| /* Test option: Limit testing to a given mode (i.e. XFail, Skip, |
| Pass, All). */ |
| enum svn_test_mode_t mode_filter = svn_test_all; |
| |
| /* Option parsing enums and structures */ |
| enum { |
| help_opt = SVN_OPT_FIRST_LONGOPT_ID, |
| cleanup_opt, |
| fstype_opt, |
| list_opt, |
| verbose_opt, |
| quiet_opt, |
| config_opt, |
| server_minor_version_opt, |
| allow_segfault_opt, |
| srcdir_opt, |
| mode_filter_opt |
| }; |
| |
| static const apr_getopt_option_t cl_options[] = |
| { |
| {"help", help_opt, 0, |
| N_("display this help")}, |
| {"cleanup", cleanup_opt, 0, |
| N_("remove test directories after success")}, |
| {"config-file", config_opt, 1, |
| N_("specify test config file ARG")}, |
| {"fs-type", fstype_opt, 1, |
| N_("specify a filesystem backend type ARG")}, |
| {"list", list_opt, 0, |
| N_("lists all the tests with their short description")}, |
| {"mode-filter", mode_filter_opt, 1, |
| N_("only run/list tests with expected mode ARG = PASS, " |
| "XFAIL, SKIP, or ALL (default)")}, |
| {"verbose", verbose_opt, 0, |
| N_("print extra information")}, |
| {"server-minor-version", server_minor_version_opt, 1, |
| N_("set the minor version for the server ('3', '4', " |
| "'5', or '6')")}, |
| {"quiet", quiet_opt, 0, |
| N_("print only unexpected results")}, |
| {"allow-segfaults", allow_segfault_opt, 0, |
| N_("don't trap seg faults (useful for debugging)")}, |
| {"srcdir", srcdir_opt, 1, |
| N_("source directory")}, |
| {0, 0, 0, 0} |
| }; |
| |
| |
| /* ================================================================= */ |
| /* Stuff for cleanup processing */ |
| |
| /* When non-zero, don't remove test directories */ |
| static svn_boolean_t skip_cleanup = FALSE; |
| |
| /* All cleanup actions are registered as cleanups on this pool. */ |
| static apr_pool_t *cleanup_pool = NULL; |
| |
| static apr_status_t |
| cleanup_rmtree(void *data) |
| { |
| if (!skip_cleanup) |
| { |
| apr_pool_t *pool = svn_pool_create(NULL); |
| const char *path = data; |
| |
| /* Ignore errors here. */ |
| svn_error_t *err = svn_io_remove_dir2(path, FALSE, NULL, NULL, pool); |
| svn_error_clear(err); |
| if (verbose_mode) |
| { |
| if (err) |
| printf("FAILED CLEANUP: %s\n", path); |
| else |
| printf("CLEANUP: %s\n", path); |
| } |
| svn_pool_destroy(pool); |
| } |
| return APR_SUCCESS; |
| } |
| |
| |
| void |
| svn_test_add_dir_cleanup(const char *path) |
| { |
| if (cleanup_mode) |
| { |
| const char *abspath; |
| svn_error_t *err = svn_path_get_absolute(&abspath, path, cleanup_pool); |
| svn_error_clear(err); |
| if (!err) |
| apr_pool_cleanup_register(cleanup_pool, abspath, cleanup_rmtree, |
| apr_pool_cleanup_null); |
| else if (verbose_mode) |
| printf("FAILED ABSPATH: %s\n", path); |
| } |
| } |
| |
| |
| /* ================================================================= */ |
| /* Quite a few tests use random numbers. */ |
| |
| apr_uint32_t |
| svn_test_rand(apr_uint32_t *seed) |
| { |
| *seed = (*seed * 1103515245UL + 12345UL) & 0xffffffffUL; |
| return *seed; |
| } |
| |
| |
| /* ================================================================= */ |
| |
| |
| /* Determine the array size of test_funcs[], the inelegant way. :) */ |
| static int |
| get_array_size(void) |
| { |
| int i; |
| |
| for (i = 1; test_funcs[i].func2 || test_funcs[i].func_opts; i++) |
| { |
| } |
| |
| return (i - 1); |
| } |
| |
| /* Buffer used for setjmp/longjmp. */ |
| static jmp_buf jump_buffer; |
| |
| /* Our SIGSEGV handler, which jumps back into do_test_num(), which see for |
| more information. */ |
| static void |
| crash_handler(int signum) |
| { |
| longjmp(jump_buffer, 1); |
| } |
| |
| |
| /* Execute a test number TEST_NUM. Pretty-print test name and dots |
| according to our test-suite spec, and return the result code. |
| If HEADER_MSG and *HEADER_MSG are not NULL, print *HEADER_MSG prior |
| to pretty-printing the test information, then set *HEADER_MSG to NULL. */ |
| static svn_boolean_t |
| do_test_num(const char *progname, |
| int test_num, |
| svn_boolean_t msg_only, |
| svn_test_opts_t *opts, |
| const char **header_msg, |
| apr_pool_t *pool) |
| { |
| svn_boolean_t skip, xfail, wimp; |
| svn_error_t *err = NULL; |
| svn_boolean_t test_failed; |
| const char *msg = NULL; /* the message this individual test prints out */ |
| const struct svn_test_descriptor_t *desc; |
| const int array_size = get_array_size(); |
| svn_boolean_t run_this_test; /* This test's mode matches DESC->MODE. */ |
| |
| /* Check our array bounds! */ |
| if (test_num < 0) |
| test_num += array_size + 1; |
| if ((test_num > array_size) || (test_num <= 0)) |
| { |
| if (header_msg && *header_msg) |
| printf("%s", *header_msg); |
| printf("FAIL: %s: THERE IS NO TEST NUMBER %2d\n", progname, test_num); |
| skip_cleanup = TRUE; |
| return TRUE; /* BAIL, this test number doesn't exist. */ |
| } |
| |
| desc = &test_funcs[test_num]; |
| skip = desc->mode == svn_test_skip; |
| xfail = desc->mode == svn_test_xfail; |
| wimp = xfail && desc->wip; |
| msg = desc->msg; |
| run_this_test = mode_filter == svn_test_all || mode_filter == desc->mode; |
| |
| if (run_this_test && header_msg && *header_msg) |
| { |
| printf("%s", *header_msg); |
| *header_msg = NULL; |
| } |
| |
| if (!allow_segfaults) |
| { |
| /* Catch a crashing test, so we don't interrupt the rest of 'em. */ |
| apr_signal(SIGSEGV, crash_handler); |
| } |
| |
| /* We use setjmp/longjmp to recover from the crash. setjmp() essentially |
| establishes a rollback point, and longjmp() goes back to that point. |
| When we invoke longjmp(), it instructs setjmp() to return non-zero, |
| so we don't end up in an infinite loop. |
| |
| If we've got non-zero from setjmp(), we know we've crashed. */ |
| if (setjmp(jump_buffer) == 0) |
| { |
| /* Do test */ |
| if (msg_only || skip || !run_this_test) |
| ; /* pass */ |
| else if (desc->func2) |
| err = (*desc->func2)(pool); |
| else |
| err = (*desc->func_opts)(opts, pool); |
| |
| if (err && err->apr_err == SVN_ERR_TEST_SKIPPED) |
| { |
| svn_error_clear(err); |
| err = SVN_NO_ERROR; |
| skip = TRUE; |
| } |
| } |
| else |
| err = svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
| "Test crashed " |
| "(run in debugger with '--allow-segfaults')"); |
| |
| if (!allow_segfaults) |
| { |
| /* Now back to your regularly scheduled program... */ |
| apr_signal(SIGSEGV, SIG_DFL); |
| } |
| |
| /* Failure means unexpected results -- FAIL or XPASS. */ |
| test_failed = (!wimp && ((err != SVN_NO_ERROR) != (xfail != 0))); |
| |
| /* If we got an error, print it out. */ |
| if (err) |
| { |
| svn_handle_error2(err, stdout, FALSE, "svn_tests: "); |
| svn_error_clear(err); |
| } |
| |
| if (msg_only) |
| { |
| if (run_this_test) |
| printf(" %3d %-5s %s%s%s%s\n", |
| test_num, |
| (xfail ? "XFAIL" : (skip ? "SKIP" : "")), |
| msg ? msg : "(test did not provide name)", |
| (wimp && verbose_mode) ? " [[" : "", |
| (wimp && verbose_mode) ? desc->wip : "", |
| (wimp && verbose_mode) ? "]]" : ""); |
| } |
| else if (run_this_test && ((! quiet_mode) || test_failed)) |
| { |
| printf("%s %s %d: %s%s%s%s\n", |
| (err |
| ? (xfail ? "XFAIL:" : "FAIL: ") |
| : (xfail ? "XPASS:" : (skip ? "SKIP: " : "PASS: "))), |
| progname, |
| test_num, |
| msg ? msg : "(test did not provide name)", |
| wimp ? " [[WIMP: " : "", |
| wimp ? desc->wip : "", |
| wimp ? "]]" : ""); |
| } |
| |
| if (msg) |
| { |
| size_t len = strlen(msg); |
| if (len > 50) |
| printf("WARNING: Test docstring exceeds 50 characters\n"); |
| if (msg[len - 1] == '.') |
| printf("WARNING: Test docstring ends in a period (.)\n"); |
| if (svn_ctype_isupper(msg[0])) |
| printf("WARNING: Test docstring is capitalized\n"); |
| } |
| if (desc->msg == NULL) |
| printf("WARNING: New-style test descriptor is missing a docstring.\n"); |
| |
| fflush(stdout); |
| |
| skip_cleanup = test_failed; |
| |
| return test_failed; |
| } |
| |
| |
| static void help(const char *progname, apr_pool_t *pool) |
| { |
| int i; |
| |
| svn_error_clear(svn_cmdline_fprintf(stdout, pool, |
| _("usage: %s [options] [test-numbers]\n" |
| "\n" |
| "Valid options:\n"), |
| progname)); |
| for (i = 0; cl_options[i].name && cl_options[i].optch; i++) |
| { |
| const char *optstr; |
| |
| svn_opt_format_option(&optstr, cl_options + i, TRUE, pool); |
| svn_error_clear(svn_cmdline_fprintf(stdout, pool, " %s\n", optstr)); |
| } |
| svn_error_clear(svn_cmdline_fprintf(stdout, pool, "\n")); |
| } |
| |
| |
| /* Standard svn test program */ |
| int |
| main(int argc, const char *argv[]) |
| { |
| const char *prog_name; |
| int i; |
| svn_boolean_t got_error = FALSE; |
| apr_pool_t *pool, *test_pool; |
| svn_boolean_t ran_a_test = FALSE; |
| svn_boolean_t list_mode = FALSE; |
| int opt_id; |
| apr_status_t apr_err; |
| apr_getopt_t *os; |
| svn_error_t *err; |
| char errmsg[200]; |
| /* How many tests are there? */ |
| int array_size = get_array_size(); |
| |
| svn_test_opts_t opts = { NULL }; |
| |
| opts.fs_type = DEFAULT_FS_TYPE; |
| |
| /* Initialize APR (Apache pools) */ |
| if (apr_initialize() != APR_SUCCESS) |
| { |
| printf("apr_initialize() failed.\n"); |
| exit(1); |
| } |
| |
| /* set up the global pool. Use a separate allocator to limit memory |
| * usage but make it thread-safe to allow for multi-threaded tests. |
| */ |
| pool = apr_allocator_owner_get(svn_pool_create_allocator(TRUE)); |
| |
| /* Remember the command line */ |
| test_argc = argc; |
| test_argv = argv; |
| |
| err = svn_cmdline__getopt_init(&os, argc, argv, pool); |
| |
| os->interleave = TRUE; /* Let options and arguments be interleaved */ |
| |
| /* Strip off any leading path components from the program name. */ |
| prog_name = strrchr(argv[0], '/'); |
| if (prog_name) |
| prog_name++; |
| else |
| { |
| /* Just check if this is that weird platform that uses \ instead |
| of / for the path separator. */ |
| prog_name = strrchr(argv[0], '\\'); |
| if (prog_name) |
| prog_name++; |
| else |
| prog_name = argv[0]; |
| } |
| |
| #ifdef WIN32 |
| #if _MSC_VER >= 1400 |
| /* ### This should work for VC++ 2002 (=1300) and later */ |
| /* Show the abort message on STDERR instead of a dialog to allow |
| scripts (e.g. our testsuite) to continue after an abort without |
| user intervention. Allow overriding for easier debugging. */ |
| if (!getenv("SVN_CMDLINE_USE_DIALOG_FOR_ABORT")) |
| { |
| /* In release mode: Redirect abort() errors to stderr */ |
| _set_error_mode(_OUT_TO_STDERR); |
| |
| /* In _DEBUG mode: Redirect all debug output (E.g. assert() to stderr. |
| (Ignored in releas builds) */ |
| _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR); |
| _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); |
| _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); |
| _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); |
| } |
| #endif /* _MSC_VER >= 1400 */ |
| #endif |
| |
| if (err) |
| return svn_cmdline_handle_exit_error(err, pool, prog_name); |
| while (1) |
| { |
| const char *opt_arg; |
| |
| /* Parse the next option. */ |
| apr_err = apr_getopt_long(os, cl_options, &opt_id, &opt_arg); |
| if (APR_STATUS_IS_EOF(apr_err)) |
| break; |
| else if (apr_err && (apr_err != APR_BADCH)) |
| { |
| /* Ignore invalid option error to allow passing arbitary options */ |
| fprintf(stderr, "apr_getopt_long failed : [%d] %s\n", |
| apr_err, apr_strerror(apr_err, errmsg, sizeof(errmsg))); |
| exit(1); |
| } |
| |
| switch (opt_id) { |
| case help_opt: |
| help(prog_name, pool); |
| exit(0); |
| case cleanup_opt: |
| cleanup_mode = TRUE; |
| break; |
| case config_opt: |
| opts.config_file = apr_pstrdup(pool, opt_arg); |
| break; |
| case fstype_opt: |
| opts.fs_type = apr_pstrdup(pool, opt_arg); |
| break; |
| case srcdir_opt: |
| SVN_INT_ERR(svn_utf_cstring_to_utf8(&opts.srcdir, opt_arg, pool)); |
| opts.srcdir = svn_dirent_internal_style(opts.srcdir, pool); |
| break; |
| case list_opt: |
| list_mode = TRUE; |
| break; |
| case mode_filter_opt: |
| if (svn_cstring_casecmp(opt_arg, "PASS") == 0) |
| mode_filter = svn_test_pass; |
| else if (svn_cstring_casecmp(opt_arg, "XFAIL") == 0) |
| mode_filter = svn_test_xfail; |
| else if (svn_cstring_casecmp(opt_arg, "SKIP") == 0) |
| mode_filter = svn_test_skip; |
| else if (svn_cstring_casecmp(opt_arg, "ALL") == 0) |
| mode_filter = svn_test_all; |
| else |
| { |
| fprintf(stderr, "FAIL: Invalid --mode-filter option. Try "); |
| fprintf(stderr, " PASS, XFAIL, SKIP or ALL.\n"); |
| exit(1); |
| } |
| break; |
| case verbose_opt: |
| verbose_mode = TRUE; |
| break; |
| case quiet_opt: |
| quiet_mode = TRUE; |
| break; |
| case allow_segfault_opt: |
| allow_segfaults = TRUE; |
| break; |
| case server_minor_version_opt: |
| { |
| char *end; |
| opts.server_minor_version = (int) strtol(opt_arg, &end, 10); |
| if (end == opt_arg || *end != '\0') |
| { |
| fprintf(stderr, "FAIL: Non-numeric minor version given\n"); |
| exit(1); |
| } |
| if ((opts.server_minor_version < 3) |
| || (opts.server_minor_version > 6)) |
| { |
| fprintf(stderr, "FAIL: Invalid minor version given\n"); |
| exit(1); |
| } |
| } |
| } |
| } |
| |
| /* Disable sleeping for timestamps, to speed up the tests. */ |
| apr_env_set( |
| "SVN_I_LOVE_CORRUPTED_WORKING_COPIES_SO_DISABLE_SLEEP_FOR_TIMESTAMPS", |
| "yes", pool); |
| |
| /* You can't be both quiet and verbose. */ |
| if (quiet_mode && verbose_mode) |
| { |
| fprintf(stderr, "FAIL: --verbose and --quiet are mutually exclusive\n"); |
| exit(1); |
| } |
| |
| /* Create an iteration pool for the tests */ |
| cleanup_pool = svn_pool_create(pool); |
| test_pool = svn_pool_create(pool); |
| |
| if (!allow_segfaults) |
| svn_error_set_malfunction_handler(svn_error_raise_on_malfunction); |
| |
| if (argc >= 2) /* notice command-line arguments */ |
| { |
| if (! strcmp(argv[1], "list") || list_mode) |
| { |
| const char *header_msg; |
| ran_a_test = TRUE; |
| |
| /* run all tests with MSG_ONLY set to TRUE */ |
| header_msg = "Test # Mode Test Description\n" |
| "------ ----- ----------------\n"; |
| for (i = 1; i <= array_size; i++) |
| { |
| if (do_test_num(prog_name, i, TRUE, &opts, &header_msg, |
| test_pool)) |
| got_error = TRUE; |
| |
| /* Clear the per-function pool */ |
| svn_pool_clear(test_pool); |
| svn_pool_clear(cleanup_pool); |
| } |
| } |
| else |
| { |
| for (i = 1; i < argc; i++) |
| { |
| if (svn_ctype_isdigit(argv[i][0]) || argv[i][0] == '-') |
| { |
| int test_num = atoi(argv[i]); |
| if (test_num == 0) |
| /* A --option argument, most likely. */ |
| continue; |
| |
| ran_a_test = TRUE; |
| if (do_test_num(prog_name, test_num, FALSE, &opts, NULL, |
| test_pool)) |
| got_error = TRUE; |
| |
| /* Clear the per-function pool */ |
| svn_pool_clear(test_pool); |
| svn_pool_clear(cleanup_pool); |
| } |
| } |
| } |
| } |
| |
| if (! ran_a_test) |
| { |
| /* just run all tests */ |
| for (i = 1; i <= array_size; i++) |
| { |
| if (do_test_num(prog_name, i, FALSE, &opts, NULL, test_pool)) |
| got_error = TRUE; |
| |
| /* Clear the per-function pool */ |
| svn_pool_clear(test_pool); |
| svn_pool_clear(cleanup_pool); |
| } |
| } |
| |
| /* Clean up APR */ |
| svn_pool_destroy(pool); /* takes test_pool with it */ |
| apr_terminate(); |
| |
| return got_error; |
| } |