/* * 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 #include #include #include #ifdef WIN32 #include #endif #include #include #include #include #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; }