/* * libusb test library helper functions * Copyright © 2012 Toby Gray <toby.gray@realvnc.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libusb_testlib.h" #include <stdio.h> #include <stdarg.h> #include <string.h> #include <errno.h> #if !defined(_WIN32_WCE) #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #endif #if defined(_WIN32_WCE) // No support for selective redirection of STDOUT on WinCE. #define DISABLE_STDOUT_REDIRECTION #define STDOUT_FILENO 1 #elif defined(_WIN32) #include <io.h> #define dup _dup #define dup2 _dup2 #define open _open #define close _close #define fdopen _fdopen #define NULL_PATH "nul" #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #else #include <unistd.h> #define NULL_PATH "/dev/null" #endif #define INVALID_FD -1 #define IGNORE_RETVAL(expr) do { (void)(expr); } while(0) /** * Converts a test result code into a human readable string. */ static const char* test_result_to_str(libusb_testlib_result result) { switch (result) { case TEST_STATUS_SUCCESS: return "Success"; case TEST_STATUS_FAILURE: return "Failure"; case TEST_STATUS_ERROR: return "Error"; case TEST_STATUS_SKIP: return "Skip"; default: return "Unknown"; } } static void print_usage(int argc, char ** argv) { printf("Usage: %s [-l] [-v] [<test_name> ...]\n", argc > 0 ? argv[0] : "test_*"); printf(" -l List available tests\n"); printf(" -v Don't redirect STDERR/STDOUT during tests\n"); } static void cleanup_test_output(libusb_testlib_ctx * ctx) { #ifndef DISABLE_STDOUT_REDIRECTION if (!ctx->verbose) { if (ctx->old_stdout != INVALID_FD) { IGNORE_RETVAL(dup2(ctx->old_stdout, STDOUT_FILENO)); ctx->old_stdout = INVALID_FD; } if (ctx->old_stderr != INVALID_FD) { IGNORE_RETVAL(dup2(ctx->old_stderr, STDERR_FILENO)); ctx->old_stderr = INVALID_FD; } if (ctx->null_fd != INVALID_FD) { close(ctx->null_fd); ctx->null_fd = INVALID_FD; } if (ctx->output_file != stdout) { fclose(ctx->output_file); ctx->output_file = stdout; } } #endif } /** * Setup test output handles * \return zero on success, non-zero on failure */ static int setup_test_output(libusb_testlib_ctx * ctx) { #ifndef DISABLE_STDOUT_REDIRECTION /* Stop output to stdout and stderr from being displayed if using non-verbose output */ if (!ctx->verbose) { /* Keep a copy of STDOUT and STDERR */ ctx->old_stdout = dup(STDOUT_FILENO); if (ctx->old_stdout < 0) { ctx->old_stdout = INVALID_FD; printf("Failed to duplicate stdout handle: %d\n", errno); return 1; } ctx->old_stderr = dup(STDERR_FILENO); if (ctx->old_stderr < 0) { ctx->old_stderr = INVALID_FD; cleanup_test_output(ctx); printf("Failed to duplicate stderr handle: %d\n", errno); return 1; } /* Redirect STDOUT_FILENO and STDERR_FILENO to /dev/null or "nul"*/ ctx->null_fd = open(NULL_PATH, O_WRONLY); if (ctx->null_fd < 0) { ctx->null_fd = INVALID_FD; cleanup_test_output(ctx); printf("Failed to open null handle: %d\n", errno); return 1; } if ((dup2(ctx->null_fd, STDOUT_FILENO) < 0) || (dup2(ctx->null_fd, STDERR_FILENO) < 0)) { cleanup_test_output(ctx); return 1; } ctx->output_file = fdopen(ctx->old_stdout, "w"); if (!ctx->output_file) { ctx->output_file = stdout; cleanup_test_output(ctx); printf("Failed to open FILE for output handle: %d\n", errno); return 1; } } #endif return 0; } void libusb_testlib_logf(libusb_testlib_ctx * ctx, const char* fmt, ...) { va_list va; va_start(va, fmt); vfprintf(ctx->output_file, fmt, va); va_end(va); fprintf(ctx->output_file, "\n"); fflush(ctx->output_file); } int libusb_testlib_run_tests(int argc, char ** argv, const libusb_testlib_test * tests) { int run_count = 0; int idx = 0; int pass_count = 0; int fail_count = 0; int error_count = 0; int skip_count = 0; int r, j; size_t arglen; libusb_testlib_result test_result; libusb_testlib_ctx ctx; /* Setup default mode of operation */ ctx.test_names = NULL; ctx.test_count = 0; ctx.list_tests = false; ctx.verbose = false; ctx.old_stdout = INVALID_FD; ctx.old_stderr = INVALID_FD; ctx.output_file = stdout; ctx.null_fd = INVALID_FD; /* Parse command line options */ if (argc >= 2) { for (j = 1; j < argc; j++) { arglen = strlen(argv[j]); if ( ((argv[j][0] == '-') || (argv[j][0] == '/')) && arglen >=2 ) { switch (argv[j][1]) { case 'l': ctx.list_tests = true; break; case 'v': ctx.verbose = true; break; default: printf("Unknown option: '%s'\n", argv[j]); print_usage(argc, argv); return 1; } } else { /* End of command line options, remaining must be list of tests to run */ ctx.test_names = argv + j; ctx.test_count = argc - j; break; } } } /* Validate command line options */ if (ctx.test_names && ctx.list_tests) { printf("List of tests requested but test list provided\n"); print_usage(argc, argv); return 1; } /* Setup test log output */ r = setup_test_output(&ctx); if (r != 0) return r; /* Act on any options not related to running tests */ if (ctx.list_tests) { while (tests[idx].function != NULL) { libusb_testlib_logf(&ctx, tests[idx].name); ++idx; } cleanup_test_output(&ctx); return 0; } /* Run any requested tests */ while (tests[idx].function != NULL) { const libusb_testlib_test * test = &tests[idx]; ++idx; if (ctx.test_count > 0) { /* Filtering tests to run, check if this is one of them */ int i; for (i = 0; i < ctx.test_count; ++i) { if (strcmp(ctx.test_names[i], test->name) == 0) /* Matches a requested test name */ break; } if (i >= ctx.test_count) { /* Failed to find a test match, so do the next loop iteration */ continue; } } libusb_testlib_logf(&ctx, "Starting test run: %s...", test->name); test_result = test->function(&ctx); libusb_testlib_logf(&ctx, "%s (%d)", test_result_to_str(test_result), test_result); switch (test_result) { case TEST_STATUS_SUCCESS: pass_count++; break; case TEST_STATUS_FAILURE: fail_count++; break; case TEST_STATUS_ERROR: error_count++; break; case TEST_STATUS_SKIP: skip_count++; break; } ++run_count; } libusb_testlib_logf(&ctx, "---"); libusb_testlib_logf(&ctx, "Ran %d tests", run_count); libusb_testlib_logf(&ctx, "Passed %d tests", pass_count); libusb_testlib_logf(&ctx, "Failed %d tests", fail_count); libusb_testlib_logf(&ctx, "Error in %d tests", error_count); libusb_testlib_logf(&ctx, "Skipped %d tests", skip_count); cleanup_test_output(&ctx); return pass_count != run_count; }