#!/usr/bin/env python # # run_tests.py - run the tests in the regression test suite. # '''usage: python run_tests.py [--url=] [--fs-type=] [--verbose] [--cleanup] [--enable-sasl] [--parallel] [--http-library=] [--config-file=] [--server-minor-version=] The optional flags and the first two parameters are passed unchanged to the TestHarness constructor. All other parameters are names of test programs. ''' import os, sys import getopt try: my_getopt = getopt.gnu_getopt except AttributeError: my_getopt = getopt.getopt class TestHarness: '''Test harness for Subversion tests. ''' def __init__(self, abs_srcdir, abs_builddir, logfile, base_url=None, fs_type=None, http_library=None, server_minor_version=None, verbose=None, cleanup=None, enable_sasl=None, parallel=None, config_file=None, fsfs_sharding=None, fsfs_packing=None, list_tests=None, svn_bin=None): '''Construct a TestHarness instance. ABS_SRCDIR and ABS_BUILDDIR are the source and build directories. LOGFILE is the name of the log file. BASE_URL is the base url for DAV tests. FS_TYPE is the FS type for repository creation. HTTP_LIBRARY is the HTTP library for DAV-based communications. SERVER_MINOR_VERSION is the minor version of the server being tested. SVN_BIN is the path where the svn binaries are installed. ''' self.srcdir = abs_srcdir self.builddir = abs_builddir self.logfile = logfile self.base_url = base_url self.fs_type = fs_type self.http_library = http_library self.server_minor_version = server_minor_version self.verbose = verbose self.cleanup = cleanup self.enable_sasl = enable_sasl self.parallel = parallel self.fsfs_sharding = fsfs_sharding self.fsfs_packing = fsfs_packing if fsfs_packing is not None and fsfs_sharding is None: raise Exception('--fsfs-packing requires --fsfs-sharding') self.config_file = None if config_file is not None: self.config_file = os.path.abspath(config_file) self.list_tests = list_tests self.svn_bin = svn_bin self.log = None def run(self, list): 'Run all test programs given in LIST.' self._open_log('w') failed = 0 for cnt, prog in enumerate(list): failed = self._run_test(prog, cnt, len(list)) or failed self._open_log('r') log_lines = self.log.readlines() # Print summaries from least interesting to most interesting. skipped = [x for x in log_lines if x[:6] == 'SKIP: '] if skipped: print('At least one test was SKIPPED, checking ' + self.logfile) for x in skipped: sys.stdout.write(x) xfailed = [x for x in log_lines if x[:6] == 'XFAIL:'] if xfailed: print('At least one test XFAILED, checking ' + self.logfile) for x in xfailed: sys.stdout.write(x) failed_list = [x for x in log_lines if x[:6] == 'FAIL: '] if failed_list: print('At least one test FAILED, checking ' + self.logfile) for x in failed_list: sys.stdout.write(x) xpassed = [x for x in log_lines if x[:6] == 'XPASS:'] if xpassed: print('At least one test XPASSED, checking ' + self.logfile) for x in xpassed: sys.stdout.write(x) if skipped or xfailed or failed_list or xpassed: print('Summary of test results:') if skipped: print(' %d test%s SKIPPED' % (len(skipped), 's'*min(len(skipped), 1))) if xfailed: print(' %d test%s XFAILED' % (len(xfailed), 's'*min(len(xfailed), 1))) if failed_list: print(' %d test%s FAILED' % (len(failed_list), 's'*min(len(failed_list), 1))) if xpassed: print(' %d test%s XPASSED' % (len(xpassed), 's'*min(len(xpassed), 1))) self._close_log() return failed def _open_log(self, mode): 'Open the log file with the required MODE.' self._close_log() self.log = open(self.logfile, mode) def _close_log(self): 'Close the log file.' if not self.log is None: self.log.close() self.log = None def _run_test(self, prog, test_nr, total_tests): "Run a single test. Return the test's exit code." def quote(arg): if sys.platform == 'win32': return '"' + arg + '"' else: return arg progdir, progbase = os.path.split(prog) # Using write here because we don't want even a trailing space sys.stdout.write('Running all tests in %s [%d/%d]...' % ( progbase, test_nr + 1, total_tests)) self.log.write('START: %s\n' % progbase) self.log.flush() if progbase[-3:] == '.py': progname = sys.executable cmdline = [quote(progname), quote(os.path.join(self.srcdir, prog))] if self.base_url is not None: cmdline.append(quote('--url=' + self.base_url)) if self.enable_sasl is not None: cmdline.append('--enable-sasl') if self.parallel is not None: cmdline.append('--parallel') if self.config_file is not None: cmdline.append(quote('--config-file=' + self.config_file)) elif os.access(prog, os.X_OK): progname = './' + progbase cmdline = [quote(progname), quote('--srcdir=' + os.path.join(self.srcdir, progdir))] if self.config_file is not None: cmdline.append(quote('--config-file=' + self.config_file)) else: print('Don\'t know what to do about ' + progbase) sys.exit(1) if self.verbose is not None: cmdline.append('--verbose') if self.cleanup is not None: cmdline.append('--cleanup') if self.fs_type is not None: cmdline.append(quote('--fs-type=' + self.fs_type)) if self.http_library is not None: cmdline.append(quote('--http-library=' + self.http_library)) if self.server_minor_version is not None: cmdline.append(quote('--server-minor-version=' + self.server_minor_version)) if self.list_tests is not None: cmdline.append('--list') if self.svn_bin is not None: cmdline.append(quote('--bin=' + self.svn_bin)) if self.fsfs_sharding is not None: cmdline.append('--fsfs-sharding=%d' % self.fsfs_sharding) if self.fsfs_packing is not None: cmdline.append('--fsfs-packing') old_cwd = os.getcwd() try: os.chdir(progdir) failed = self._run_prog(progname, cmdline) except: os.chdir(old_cwd) raise else: os.chdir(old_cwd) # We always return 1 for failed tests, if some other failure than 1 # probably means the test didn't run at all and probably didn't # output any failure info. if failed == 1: print('FAILURE') elif failed: self.log.write('FAIL: %s: Unknown test failure see tests.log.\n\n' % progbase) self.log.flush() print('FAILURE') else: print('success') self.log.write('END: %s\n\n' % progbase) return failed def _run_prog(self, progname, arglist): '''Execute the file PROGNAME in a subprocess, with ARGLIST as its arguments (a list/tuple of arg0..argN), redirecting standard output and error to the log file. Return the command's exit code.''' def restore_streams(stdout, stderr): os.dup2(stdout, 1) os.dup2(stderr, 2) os.close(stdout) os.close(stderr) sys.stdout.flush() sys.stderr.flush() self.log.flush() old_stdout = os.dup(1) old_stderr = os.dup(2) try: os.dup2(self.log.fileno(), 1) os.dup2(self.log.fileno(), 2) rv = os.spawnv(os.P_WAIT, progname, arglist) except: restore_streams(old_stdout, old_stderr) raise else: restore_streams(old_stdout, old_stderr) return rv def main(): try: opts, args = my_getopt(sys.argv[1:], 'u:f:vc', ['url=', 'fs-type=', 'verbose', 'cleanup', 'http-library=', 'server-minor-version=', 'fsfs-packing', 'fsfs-sharding=', 'enable-sasl', 'parallel', 'config-file=']) except getopt.GetoptError: args = [] if len(args) < 3: print(__doc__) sys.exit(2) base_url, fs_type, verbose, cleanup, enable_sasl, http_library, \ server_minor_version, fsfs_sharding, fsfs_packing, parallel, \ config_file = \ None, None, None, None, None, None, None, None, None, None, None for opt, val in opts: if opt in ['-u', '--url']: base_url = val elif opt in ['-f', '--fs-type']: fs_type = val elif opt in ['--http-library']: http_library = val elif opt in ['--fsfs-sharding']: fsfs_sharding = int(val) elif opt in ['--fsfs-packing']: fsfs_packing = 1 elif opt in ['--server-minor-version']: server_minor_version = val elif opt in ['-v', '--verbose']: verbose = 1 elif opt in ['-c', '--cleanup']: cleanup = 1 elif opt in ['--enable-sasl']: enable_sasl = 1 elif opt in ['--parallel']: parallel = 1 elif opt in ['--config-file']: config_file = val else: raise getopt.GetoptError th = TestHarness(args[0], args[1], os.path.abspath('tests.log'), base_url, fs_type, http_library, server_minor_version, verbose, cleanup, enable_sasl, parallel, config_file, fsfs_sharding, fsfs_packing) failed = th.run(args[2:]) if failed: sys.exit(1) # Run main if not imported as a module if __name__ == '__main__': main()