# # main.py: a shared, automated test suite for Subversion # # Subversion is a tool for revision control. # See http://subversion.tigris.org for more information. # # ==================================================================== # Copyright (c) 2000-2007 CollabNet. All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://subversion.tigris.org/license-1.html. # If newer versions of this license are posted there, you may use a # newer version instead, at your option. # ###################################################################### import sys # for argv[] import os import shutil # for rmtree() import re import stat # for ST_MODE import copy # for deepcopy() import time # for time() import traceback # for print_exc() import threading import getopt try: my_getopt = getopt.gnu_getopt except AttributeError: my_getopt = getopt.getopt from svntest import Failure from svntest import Skip from svntest import testcase from svntest import wc ###################################################################### # # HOW TO USE THIS MODULE: # # Write a new python script that # # 1) imports this 'svntest' package # # 2) contains a number of related 'test' routines. (Each test # routine should take no arguments, and return None on success # or throw a Failure exception on failure. Each test should # also contain a short docstring.) # # 3) places all the tests into a list that begins with None. # # 4) calls svntest.main.client_test() on the list. # # Also, your tests will probably want to use some of the common # routines in the 'Utilities' section below. # ##################################################################### # Global stuff class SVNProcessTerminatedBySignal(Failure): "Exception raised if a spawned process segfaulted, aborted, etc." pass class SVNLineUnequal(Failure): "Exception raised if two lines are unequal" pass class SVNUnmatchedError(Failure): "Exception raised if an expected error is not found" pass class SVNCommitFailure(Failure): "Exception raised if a commit failed" pass class SVNRepositoryCopyFailure(Failure): "Exception raised if unable to copy a repository" pass class SVNRepositoryCreateFailure(Failure): "Exception raised if unable to create a repository" pass # Windows specifics if sys.platform == 'win32': windows = True file_scheme_prefix = 'file:///' _exe = '.exe' else: windows = False file_scheme_prefix = 'file://' _exe = '' # os.wait() specifics try: from os import wait platform_with_os_wait = True except ImportError: platform_with_os_wait = False # The location of our mock svneditor script. svneditor_script = os.path.join(sys.path[0], 'svneditor.py') # Username and password used by the working copies wc_author = 'jrandom' wc_passwd = 'rayjandom' # Username and password used by the working copies for "second user" # scenarios wc_author2 = 'jconstant' # use the same password as wc_author ###################################################################### # Global variables set during option parsing. These should not be used # until the variable command_line_parsed has been set to True, as is # done in run_tests below. command_line_parsed = False # The locations of the svn, svnadmin and svnlook binaries, relative to # the only scripts that import this file right now (they live in ../). # Use --bin to override these defaults. svn_binary = os.path.abspath('../../svn/svn' + _exe) svnadmin_binary = os.path.abspath('../../svnadmin/svnadmin' + _exe) svnlook_binary = os.path.abspath('../../svnlook/svnlook' + _exe) svnsync_binary = os.path.abspath('../../svnsync/svnsync' + _exe) svnversion_binary = os.path.abspath('../../svnversion/svnversion' + _exe) # Global variable indicating if we want verbose output, that is, # details of what commands each test does as it does them. This is # incompatible with quiet_mode. verbose_mode = False # Global variable indicating if we want quiet output, that is, don't # show PASS, XFAIL, or SKIP notices, but do show FAIL and XPASS. This # is incompatible with verbose_mode. quiet_mode = False # Global variable indicating if we want test data cleaned up after success cleanup_mode = False # Global variable indicating if svnserve should use Cyrus SASL enable_sasl = False # Global variable indicating which DAV library, if any, is in use # ('neon', 'serf') http_library = None # Global variable indicating what the minor version of the server # tested against is (4 for 1.4.x, for example). server_minor_version = 5 # Global variable indicating if this is a child process and no cleanup # of global directories is needed. is_child_process = False # Global URL to testing area. Default to ra_local, current working dir. test_area_url = file_scheme_prefix + os.path.abspath(os.getcwd()) if windows: test_area_url = test_area_url.replace('\\', '/') # Location to the pristine repository, will be calculated from test_area_url # when we know what the user specified for --url. pristine_url = None # Global variable indicating the FS type for repository creations. fs_type = None # End of command-line-set global variables. ###################################################################### # All temporary repositories and working copies are created underneath # this dir, so there's one point at which to mount, e.g., a ramdisk. work_dir = "svn-test-work" # Constant for the merge info property. SVN_PROP_MERGE_INFO = "svn:mergeinfo" # Where we want all the repositories and working copies to live. # Each test will have its own! general_repo_dir = os.path.join(work_dir, "repositories") general_wc_dir = os.path.join(work_dir, "working_copies") # temp directory in which we will create our 'pristine' local # repository and other scratch data. This should be removed when we # quit and when we startup. temp_dir = os.path.join(work_dir, 'local_tmp') # (derivatives of the tmp dir.) pristine_dir = os.path.join(temp_dir, "repos") greek_dump_dir = os.path.join(temp_dir, "greekfiles") default_config_dir = os.path.abspath(os.path.join(temp_dir, "config")) # # Our pristine greek-tree state. # # If a test wishes to create an "expected" working-copy tree, it should # call main.greek_state.copy(). That method will return a copy of this # State object which can then be edited. # _item = wc.StateItem greek_state = wc.State('', { 'iota' : _item("This is the file 'iota'.\n"), 'A' : _item(), 'A/mu' : _item("This is the file 'mu'.\n"), 'A/B' : _item(), 'A/B/lambda' : _item("This is the file 'lambda'.\n"), 'A/B/E' : _item(), 'A/B/E/alpha' : _item("This is the file 'alpha'.\n"), 'A/B/E/beta' : _item("This is the file 'beta'.\n"), 'A/B/F' : _item(), 'A/C' : _item(), 'A/D' : _item(), 'A/D/gamma' : _item("This is the file 'gamma'.\n"), 'A/D/G' : _item(), 'A/D/G/pi' : _item("This is the file 'pi'.\n"), 'A/D/G/rho' : _item("This is the file 'rho'.\n"), 'A/D/G/tau' : _item("This is the file 'tau'.\n"), 'A/D/H' : _item(), 'A/D/H/chi' : _item("This is the file 'chi'.\n"), 'A/D/H/psi' : _item("This is the file 'psi'.\n"), 'A/D/H/omega' : _item("This is the file 'omega'.\n"), }) ###################################################################### # Utilities shared by the tests def get_admin_name(): "Return name of SVN administrative subdirectory." if (windows or sys.platform == 'cygwin') \ and os.environ.has_key('SVN_ASP_DOT_NET_HACK'): return '_svn' else: return '.svn' def get_start_commit_hook_path(repo_dir): "Return the path of the start-commit-hook conf file in REPO_DIR." return os.path.join(repo_dir, "hooks", "start-commit") def get_pre_commit_hook_path(repo_dir): "Return the path of the pre-commit-hook conf file in REPO_DIR." return os.path.join(repo_dir, "hooks", "pre-commit") def get_post_commit_hook_path(repo_dir): "Return the path of the post-commit-hook conf file in REPO_DIR." return os.path.join(repo_dir, "hooks", "post-commit") def get_pre_revprop_change_hook_path(repo_dir): "Return the path of the pre-revprop-change hook script in REPO_DIR." return os.path.join(repo_dir, "hooks", "pre-revprop-change") def get_svnserve_conf_file_path(repo_dir): "Return the path of the svnserve.conf file in REPO_DIR." return os.path.join(repo_dir, "conf", "svnserve.conf") # Run any binary, logging the command line (TODO: and return code) def run_command(command, error_expected, binary_mode=0, *varargs): """Run COMMAND with VARARGS; return stdout, stderr as lists of lines. If ERROR_EXPECTED is None, any stderr also will be printed.""" return run_command_stdin(command, error_expected, binary_mode, None, *varargs) # A regular expression that matches arguments that are trivially safe # to pass on a command line without quoting on any supported operating # system: _safe_arg_re = re.compile(r'^[A-Za-z\d\.\_\/\-\:\@]+$') def _quote_arg(arg): """Quote ARG for a command line. Simply surround every argument in double-quotes unless it contains only universally harmless characters. WARNING: This function cannot handle arbitrary command-line arguments. It can easily be confused by shell metacharacters. A perfect job would be difficult and OS-dependent (see, for example, http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp). In other words, this function is just good enough for what we need here.""" arg = str(arg) if _safe_arg_re.match(arg): return arg else: if os.name != 'nt': arg = arg.replace('$', '\$') return '"%s"' % (arg,) # Run any binary, supplying input text, logging the command line def spawn_process(command, binary_mode=0,stdin_lines=None, *varargs): args = ' '.join(map(_quote_arg, varargs)) # Log the command line if verbose_mode: print 'CMD:', os.path.basename(command) + ' ' + args, if binary_mode: mode = 'b' else: mode = 't' infile, outfile, errfile = os.popen3(command + ' ' + args, mode) if stdin_lines: map(infile.write, stdin_lines) infile.close() stdout_lines = outfile.readlines() stderr_lines = errfile.readlines() outfile.close() errfile.close() exit_code = 0 if platform_with_os_wait: pid, wait_code = os.wait() if os.WIFSIGNALED(wait_code): exit_signal = os.WTERMSIG(wait_code) sys.stdout.write("".join(stdout_lines)) sys.stderr.write("".join(stderr_lines)) if verbose_mode: # show the whole path to make it easier to start a debugger sys.stderr.write("CMD: %s terminated by signal %d\n" % (command, exit_signal)) raise SVNProcessTerminatedBySignal else: exit_code = os.WEXITSTATUS(wait_code) if exit_code and verbose_mode: sys.stderr.write("CMD: %s exited with %d\n" % (command, exit_code)) return exit_code, stdout_lines, stderr_lines def run_command_stdin(command, error_expected, binary_mode=0, stdin_lines=None, *varargs): """Run COMMAND with VARARGS; input STDIN_LINES (a list of strings which should include newline characters) to program via stdin - this should not be very large, as if the program outputs more than the OS is willing to buffer, this will deadlock, with both Python and COMMAND waiting to write to each other for ever. Return stdout, stderr as lists of lines. If ERROR_EXPECTED is None, any stderr also will be printed.""" if verbose_mode: start = time.time() exit_code, stdout_lines, stderr_lines = spawn_process(command, binary_mode, stdin_lines, *varargs) if verbose_mode: stop = time.time() print '