Tweak a test suite utility function to remove some redundancy.
[svnrdump.git] / svntest / main.py
blob010a64e554ee5df4e08d819fb82b67e8ac70e78a
2 # main.py: a shared, automated test suite for Subversion
4 # Subversion is a tool for revision control.
5 # See http://subversion.tigris.org for more information.
7 # ====================================================================
8 # Copyright (c) 2000-2008 CollabNet. All rights reserved.
10 # This software is licensed as described in the file COPYING, which
11 # you should have received as part of this distribution. The terms
12 # are also available at http://subversion.tigris.org/license-1.html.
13 # If newer versions of this license are posted there, you may use a
14 # newer version instead, at your option.
16 ######################################################################
18 import sys # for argv[]
19 import os
20 import shutil # for rmtree()
21 import re
22 import stat # for ST_MODE
23 import copy # for deepcopy()
24 import time # for time()
25 import traceback # for print_exc()
26 import threading
27 import Queue
29 import getopt
30 try:
31 my_getopt = getopt.gnu_getopt
32 except AttributeError:
33 my_getopt = getopt.getopt
35 from svntest import Failure
36 from svntest import Skip
37 from svntest import testcase
38 from svntest import wc
40 ######################################################################
42 # HOW TO USE THIS MODULE:
44 # Write a new python script that
46 # 1) imports this 'svntest' package
48 # 2) contains a number of related 'test' routines. (Each test
49 # routine should take no arguments, and return None on success
50 # or throw a Failure exception on failure. Each test should
51 # also contain a short docstring.)
53 # 3) places all the tests into a list that begins with None.
55 # 4) calls svntest.main.client_test() on the list.
57 # Also, your tests will probably want to use some of the common
58 # routines in the 'Utilities' section below.
60 #####################################################################
61 # Global stuff
63 class SVNProcessTerminatedBySignal(Failure):
64 "Exception raised if a spawned process segfaulted, aborted, etc."
65 pass
67 class SVNLineUnequal(Failure):
68 "Exception raised if two lines are unequal"
69 pass
71 class SVNUnmatchedError(Failure):
72 "Exception raised if an expected error is not found"
73 pass
75 class SVNCommitFailure(Failure):
76 "Exception raised if a commit failed"
77 pass
79 class SVNRepositoryCopyFailure(Failure):
80 "Exception raised if unable to copy a repository"
81 pass
83 class SVNRepositoryCreateFailure(Failure):
84 "Exception raised if unable to create a repository"
85 pass
87 # Windows specifics
88 if sys.platform == 'win32':
89 windows = True
90 file_scheme_prefix = 'file:///'
91 _exe = '.exe'
92 _bat = '.bat'
93 else:
94 windows = False
95 file_scheme_prefix = 'file://'
96 _exe = ''
97 _bat = ''
99 try:
100 from popen2 import Popen3
101 platform_with_popen3_class = True
102 except ImportError:
103 platform_with_popen3_class = False
105 # The location of our mock svneditor script.
106 if sys.platform == 'win32':
107 svneditor_script = os.path.join(sys.path[0], 'svneditor.bat')
108 else:
109 svneditor_script = os.path.join(sys.path[0], 'svneditor.py')
111 # Username and password used by the working copies
112 wc_author = 'jrandom'
113 wc_passwd = 'rayjandom'
115 # Username and password used by the working copies for "second user"
116 # scenarios
117 wc_author2 = 'jconstant' # use the same password as wc_author
119 # Set C locale for command line programs
120 os.environ['LC_ALL'] = 'C'
122 ######################################################################
123 # Global variables set during option parsing. These should not be used
124 # until the variable command_line_parsed has been set to True, as is
125 # done in run_tests below.
126 command_line_parsed = False
128 # The locations of the svn, svnadmin and svnlook binaries, relative to
129 # the only scripts that import this file right now (they live in ../).
130 # Use --bin to override these defaults.
131 svn_binary = os.path.abspath('../../svn/svn' + _exe)
132 svnadmin_binary = os.path.abspath('../../svnadmin/svnadmin' + _exe)
133 svnlook_binary = os.path.abspath('../../svnlook/svnlook' + _exe)
134 svnsync_binary = os.path.abspath('../../svnsync/svnsync' + _exe)
135 svnversion_binary = os.path.abspath('../../svnversion/svnversion' + _exe)
136 svndumpfilter_binary = os.path.abspath('../../svndumpfilter/svndumpfilter' + \
137 _exe)
139 # Global variable indicating if we want verbose output, that is,
140 # details of what commands each test does as it does them. This is
141 # incompatible with quiet_mode.
142 verbose_mode = False
144 # Global variable indicating if we want quiet output, that is, don't
145 # show PASS, XFAIL, or SKIP notices, but do show FAIL and XPASS. This
146 # is incompatible with verbose_mode.
147 quiet_mode = False
149 # Global variable indicating if we want test data cleaned up after success
150 cleanup_mode = False
152 # Global variable indicating if svnserve should use Cyrus SASL
153 enable_sasl = False
155 # Global variable indicating that SVNKit binaries should be used
156 use_jsvn = False
158 # Global variable indicating which DAV library, if any, is in use
159 # ('neon', 'serf')
160 http_library = None
162 # Configuration file (copied into FSFS fsfs.conf).
163 config_file = None
165 # Global variable indicating what the minor version of the server
166 # tested against is (4 for 1.4.x, for example).
167 server_minor_version = 5
169 # Global variable indicating if this is a child process and no cleanup
170 # of global directories is needed.
171 is_child_process = False
173 # Global URL to testing area. Default to ra_local, current working dir.
174 test_area_url = file_scheme_prefix + os.path.abspath(os.getcwd())
175 if windows:
176 test_area_url = test_area_url.replace('\\', '/')
178 # Location to the pristine repository, will be calculated from test_area_url
179 # when we know what the user specified for --url.
180 pristine_url = None
182 # Global variable indicating the FS type for repository creations.
183 fs_type = None
185 # End of command-line-set global variables.
186 ######################################################################
188 # All temporary repositories and working copies are created underneath
189 # this dir, so there's one point at which to mount, e.g., a ramdisk.
190 work_dir = "svn-test-work"
192 # Constant for the merge info property.
193 SVN_PROP_MERGEINFO = "svn:mergeinfo"
195 # Where we want all the repositories and working copies to live.
196 # Each test will have its own!
197 general_repo_dir = os.path.join(work_dir, "repositories")
198 general_wc_dir = os.path.join(work_dir, "working_copies")
200 # temp directory in which we will create our 'pristine' local
201 # repository and other scratch data. This should be removed when we
202 # quit and when we startup.
203 temp_dir = os.path.join(work_dir, 'local_tmp')
205 # (derivatives of the tmp dir.)
206 pristine_dir = os.path.join(temp_dir, "repos")
207 greek_dump_dir = os.path.join(temp_dir, "greekfiles")
208 default_config_dir = os.path.abspath(os.path.join(temp_dir, "config"))
211 # Our pristine greek-tree state.
213 # If a test wishes to create an "expected" working-copy tree, it should
214 # call main.greek_state.copy(). That method will return a copy of this
215 # State object which can then be edited.
217 _item = wc.StateItem
218 greek_state = wc.State('', {
219 'iota' : _item("This is the file 'iota'.\n"),
220 'A' : _item(),
221 'A/mu' : _item("This is the file 'mu'.\n"),
222 'A/B' : _item(),
223 'A/B/lambda' : _item("This is the file 'lambda'.\n"),
224 'A/B/E' : _item(),
225 'A/B/E/alpha' : _item("This is the file 'alpha'.\n"),
226 'A/B/E/beta' : _item("This is the file 'beta'.\n"),
227 'A/B/F' : _item(),
228 'A/C' : _item(),
229 'A/D' : _item(),
230 'A/D/gamma' : _item("This is the file 'gamma'.\n"),
231 'A/D/G' : _item(),
232 'A/D/G/pi' : _item("This is the file 'pi'.\n"),
233 'A/D/G/rho' : _item("This is the file 'rho'.\n"),
234 'A/D/G/tau' : _item("This is the file 'tau'.\n"),
235 'A/D/H' : _item(),
236 'A/D/H/chi' : _item("This is the file 'chi'.\n"),
237 'A/D/H/psi' : _item("This is the file 'psi'.\n"),
238 'A/D/H/omega' : _item("This is the file 'omega'.\n"),
242 ######################################################################
243 # Utilities shared by the tests
244 def wrap_ex(func):
245 "Wrap a function, catch, print and ignore exceptions"
246 def w(*args, **kwds):
247 try:
248 return func(*args, **kwds)
249 except Failure, ex:
250 if ex.__class__ != Failure or ex.args:
251 ex_args = str(ex)
252 if ex_args:
253 print 'EXCEPTION: %s: %s' % (ex.__class__.__name__, ex_args)
254 else:
255 print 'EXCEPTION:', ex.__class__.__name__
256 return w
258 def setup_development_mode():
259 "Wraps functions in module actions"
260 l = [ 'run_and_verify_svn',
261 'run_and_verify_svnversion',
262 'run_and_verify_load',
263 'run_and_verify_dump',
264 'run_and_verify_checkout',
265 'run_and_verify_export',
266 'run_and_verify_update',
267 'run_and_verify_merge',
268 'run_and_verify_merge2',
269 'run_and_verify_switch',
270 'run_and_verify_commit',
271 'run_and_verify_unquiet_status',
272 'run_and_verify_status',
273 'run_and_verify_diff_summarize',
274 'run_and_verify_diff_summarize_xml',
275 'run_and_validate_lock']
277 for func in l:
278 setattr(actions, func, wrap_ex(getattr(actions, func)))
280 def get_admin_name():
281 "Return name of SVN administrative subdirectory."
283 if (windows or sys.platform == 'cygwin') \
284 and os.environ.has_key('SVN_ASP_DOT_NET_HACK'):
285 return '_svn'
286 else:
287 return '.svn'
289 def get_start_commit_hook_path(repo_dir):
290 "Return the path of the start-commit-hook conf file in REPO_DIR."
292 return os.path.join(repo_dir, "hooks", "start-commit")
295 def get_pre_commit_hook_path(repo_dir):
296 "Return the path of the pre-commit-hook conf file in REPO_DIR."
298 return os.path.join(repo_dir, "hooks", "pre-commit")
301 def get_post_commit_hook_path(repo_dir):
302 "Return the path of the post-commit-hook conf file in REPO_DIR."
304 return os.path.join(repo_dir, "hooks", "post-commit")
306 def get_pre_revprop_change_hook_path(repo_dir):
307 "Return the path of the pre-revprop-change hook script in REPO_DIR."
309 return os.path.join(repo_dir, "hooks", "pre-revprop-change")
311 def get_svnserve_conf_file_path(repo_dir):
312 "Return the path of the svnserve.conf file in REPO_DIR."
314 return os.path.join(repo_dir, "conf", "svnserve.conf")
316 def get_fsfs_conf_file_path(repo_dir):
317 "Return the path of the fsfs.conf file in REPO_DIR."
319 return os.path.join(repo_dir, "db", "fsfs.conf")
321 # Run any binary, logging the command line and return code
322 def run_command(command, error_expected, binary_mode=0, *varargs):
323 """Run COMMAND with VARARGS; return exit code as int; stdout, stderr
324 as lists of lines.
325 If ERROR_EXPECTED is None, any stderr also will be printed."""
327 return run_command_stdin(command, error_expected, binary_mode,
328 None, *varargs)
330 # A regular expression that matches arguments that are trivially safe
331 # to pass on a command line without quoting on any supported operating
332 # system:
333 _safe_arg_re = re.compile(r'^[A-Za-z\d\.\_\/\-\:\@]+$')
335 def _quote_arg(arg):
336 """Quote ARG for a command line.
338 Simply surround every argument in double-quotes unless it contains
339 only universally harmless characters.
341 WARNING: This function cannot handle arbitrary command-line
342 arguments. It can easily be confused by shell metacharacters. A
343 perfect job would be difficult and OS-dependent (see, for example,
344 http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp).
345 In other words, this function is just good enough for what we need
346 here."""
348 arg = str(arg)
349 if _safe_arg_re.match(arg):
350 return arg
351 else:
352 if os.name != 'nt':
353 arg = arg.replace('$', '\$')
354 return '"%s"' % (arg,)
356 def open_pipe(command, mode):
357 """Opens a popen3 pipe to COMMAND in MODE.
359 Returns (infile, outfile, errfile, waiter); waiter
360 should be passed to wait_on_pipe."""
361 if platform_with_popen3_class:
362 kid = Popen3(command, True)
363 return kid.tochild, kid.fromchild, kid.childerr, (kid, command)
364 else:
365 inf, outf, errf = os.popen3(command, mode)
366 return inf, outf, errf, None
368 def wait_on_pipe(waiter, stdout_lines, stderr_lines):
369 """Waits for KID (opened with open_pipe) to finish, dying
370 if it does. Uses STDOUT_LINES and STDERR_LINES for error message
371 if kid fails. Returns kid's exit code."""
372 if waiter is None:
373 return
375 kid, command = waiter
377 wait_code = kid.wait()
379 if os.WIFSIGNALED(wait_code):
380 exit_signal = os.WTERMSIG(wait_code)
381 if stdout_lines is not None:
382 sys.stdout.write("".join(stdout_lines))
383 if stderr_lines is not None:
384 sys.stderr.write("".join(stderr_lines))
385 if verbose_mode:
386 # show the whole path to make it easier to start a debugger
387 sys.stderr.write("CMD: %s terminated by signal %d\n"
388 % (command, exit_signal))
389 raise SVNProcessTerminatedBySignal
390 else:
391 exit_code = os.WEXITSTATUS(wait_code)
392 if exit_code and verbose_mode:
393 sys.stderr.write("CMD: %s exited with %d\n" % (command, exit_code))
394 return exit_code
396 # Run any binary, supplying input text, logging the command line
397 def spawn_process(command, binary_mode=0,stdin_lines=None, *varargs):
398 args = ' '.join(map(_quote_arg, varargs))
400 # Log the command line
401 if verbose_mode and not command.endswith('.py'):
402 print 'CMD:', os.path.basename(command) + ' ' + args,
404 if binary_mode:
405 mode = 'b'
406 else:
407 mode = 't'
409 infile, outfile, errfile, kid = open_pipe(command + ' ' + args, mode)
411 if stdin_lines:
412 map(infile.write, stdin_lines)
414 infile.close()
416 stdout_lines = outfile.readlines()
417 stderr_lines = errfile.readlines()
419 outfile.close()
420 errfile.close()
422 exit_code = wait_on_pipe(kid, stdout_lines, stderr_lines)
424 return exit_code, stdout_lines, stderr_lines
426 def run_command_stdin(command, error_expected, binary_mode=0,
427 stdin_lines=None, *varargs):
428 """Run COMMAND with VARARGS; input STDIN_LINES (a list of strings
429 which should include newline characters) to program via stdin - this
430 should not be very large, as if the program outputs more than the OS
431 is willing to buffer, this will deadlock, with both Python and
432 COMMAND waiting to write to each other for ever.
433 Return exit code as int; stdout, stderr as lists of lines.
434 If ERROR_EXPECTED is None, any stderr also will be printed."""
436 if verbose_mode:
437 start = time.time()
439 exit_code, stdout_lines, stderr_lines = spawn_process(command,
440 binary_mode,
441 stdin_lines,
442 *varargs)
444 if verbose_mode:
445 stop = time.time()
446 print '<TIME = %.6f>' % (stop - start)
447 map(sys.stdout.write, stdout_lines)
448 map(sys.stdout.write, stderr_lines)
450 if (not error_expected) and (stderr_lines):
451 if not verbose_mode:
452 map(sys.stdout.write, stderr_lines)
453 raise Failure
455 return exit_code, stdout_lines, stderr_lines
457 def create_config_dir(cfgdir, config_contents=None, server_contents=None):
458 "Create config directories and files"
460 # config file names
461 cfgfile_cfg = os.path.join(cfgdir, 'config')
462 cfgfile_srv = os.path.join(cfgdir, 'servers')
464 # create the directory
465 if not os.path.isdir(cfgdir):
466 os.makedirs(cfgdir)
468 # define default config file contents if none provided
469 if config_contents is None:
470 config_contents = """
472 [miscellany]
473 interactive-conflicts = false
476 # define default server file contents if none provided
477 if server_contents is None:
478 http_library_str = ""
479 if http_library:
480 http_library_str = "http-library=%s" % (http_library)
481 server_contents = """
483 [global]
485 store-plaintext-passwords=yes
486 store-passwords=yes
487 """ % (http_library_str)
489 file_write(cfgfile_cfg, config_contents)
490 file_write(cfgfile_srv, server_contents)
492 def _with_config_dir(args):
493 if '--config-dir' in args:
494 return args
495 else:
496 return args + ('--config-dir', default_config_dir)
498 def _with_auth(args):
499 assert '--password' not in args
500 args = args + ('--password', wc_passwd,
501 '--no-auth-cache' )
502 if '--username' in args:
503 return args
504 else:
505 return args + ('--username', wc_author )
507 # For running subversion and returning the output
508 def run_svn(error_expected, *varargs):
509 """Run svn with VARARGS; return exit code as int; stdout, stderr as
510 lists of lines.
511 If ERROR_EXPECTED is None, any stderr also will be printed. If
512 you're just checking that something does/doesn't come out of
513 stdout/stderr, you might want to use actions.run_and_verify_svn()."""
514 return run_command(svn_binary, error_expected, 0,
515 *(_with_auth(_with_config_dir(varargs))))
517 # For running svnadmin. Ignores the output.
518 def run_svnadmin(*varargs):
519 """Run svnadmin with VARARGS, returns exit code as int; stdout, stderr as
520 list of lines."""
521 return run_command(svnadmin_binary, 1, 0, *varargs)
523 # For running svnlook. Ignores the output.
524 def run_svnlook(*varargs):
525 """Run svnlook with VARARGS, returns exit code as int; stdout, stderr as
526 list of lines."""
527 return run_command(svnlook_binary, 1, 0, *varargs)
529 def run_svnsync(*varargs):
530 """Run svnsync with VARARGS, returns exit code as int; stdout, stderr as
531 list of lines."""
532 return run_command(svnsync_binary, 1, 0, *(_with_config_dir(varargs)))
534 def run_svnversion(*varargs):
535 """Run svnversion with VARARGS, returns exit code as int; stdout, stderr
536 as list of lines."""
537 return run_command(svnversion_binary, 1, 0, *varargs)
539 # Chmod recursively on a whole subtree
540 def chmod_tree(path, mode, mask):
541 def visit(arg, dirname, names):
542 mode, mask = arg
543 for name in names:
544 fullname = os.path.join(dirname, name)
545 if not os.path.islink(fullname):
546 new_mode = (os.stat(fullname)[stat.ST_MODE] & ~mask) | mode
547 os.chmod(fullname, new_mode)
548 os.path.walk(path, visit, (mode, mask))
550 # For clearing away working copies
551 def safe_rmtree(dirname, retry=0):
552 "Remove the tree at DIRNAME, making it writable first"
553 def rmtree(dirname):
554 chmod_tree(dirname, 0666, 0666)
555 shutil.rmtree(dirname)
557 if not os.path.exists(dirname):
558 return
560 if retry:
561 for delay in (0.5, 1, 2, 4):
562 try:
563 rmtree(dirname)
564 break
565 except:
566 time.sleep(delay)
567 else:
568 rmtree(dirname)
569 else:
570 rmtree(dirname)
572 # For making local mods to files
573 def file_append(path, new_text):
574 "Append NEW_TEXT to file at PATH"
575 file_write(path, new_text, 'a') # open in (a)ppend mode
577 # Append in binary mode
578 def file_append_binary(path, new_text):
579 "Append NEW_TEXT to file at PATH in binary mode"
580 file_write(path, new_text, 'ab') # open in (a)ppend mode
582 # For creating new files, and making local mods to existing files.
583 def file_write(path, contents, mode = 'w'):
584 """Write the CONTENTS to the file at PATH, opening file using MODE,
585 which is (w)rite by default."""
586 fp = open(path, mode)
587 fp.write(contents)
588 fp.close()
590 # For reading the contents of a file
591 def file_read(path, mode = 'r'):
592 """Return the contents of the file at PATH, opening file using MODE,
593 which is (r)ead by default."""
594 fp = open(path, mode)
595 contents = fp.read()
596 fp.close()
597 return contents
599 # For replacing parts of contents in an existing file, with new content.
600 def file_substitute(path, contents, new_contents):
601 """Replace the CONTENTS in the file at PATH using the NEW_CONTENTS"""
602 fp = open(path, 'r')
603 fcontent = fp.read()
604 fp.close()
605 fcontent = fcontent.replace(contents, new_contents)
606 fp = open(path, 'w')
607 fp.write(fcontent)
608 fp.close()
610 # For creating blank new repositories
611 def create_repos(path):
612 """Create a brand-new SVN repository at PATH. If PATH does not yet
613 exist, create it."""
615 if not os.path.exists(path):
616 os.makedirs(path) # this creates all the intermediate dirs, if neccessary
618 opts = ("--bdb-txn-nosync",)
619 if server_minor_version < 5:
620 opts += ("--pre-1.5-compatible",)
621 if fs_type is not None:
622 opts += ("--fs-type=" + fs_type,)
623 exit_code, stdout, stderr = run_command(svnadmin_binary, 1, 0, "create",
624 path, *opts)
626 # Skip tests if we can't create the repository.
627 if stderr:
628 for line in stderr:
629 if line.find('Unknown FS type') != -1:
630 raise Skip
631 # If the FS type is known, assume the repos couldn't be created
632 # (e.g. due to a missing 'svnadmin' binary).
633 raise SVNRepositoryCreateFailure("".join(stderr).rstrip())
635 # Allow unauthenticated users to write to the repos, for ra_svn testing.
636 file_write(get_svnserve_conf_file_path(path),
637 "[general]\nauth-access = write\n");
638 if enable_sasl:
639 file_append(get_svnserve_conf_file_path(path),
640 "realm = svntest\n[sasl]\nuse-sasl = true\n")
641 else:
642 file_append(get_svnserve_conf_file_path(path), "password-db = passwd\n")
643 file_append(os.path.join(path, "conf", "passwd"),
644 "[users]\njrandom = rayjandom\njconstant = rayjandom\n");
646 if config_file is not None and (fs_type is None or fs_type == 'fsfs'):
647 shutil.copy(config_file, get_fsfs_conf_file_path(path))
649 # make the repos world-writeable, for mod_dav_svn's sake.
650 chmod_tree(path, 0666, 0666)
652 # For copying a repository
653 def copy_repos(src_path, dst_path, head_revision, ignore_uuid = 1):
654 "Copy the repository SRC_PATH, with head revision HEAD_REVISION, to DST_PATH"
656 # Do an svnadmin dump|svnadmin load cycle. Print a fake pipe command so that
657 # the displayed CMDs can be run by hand
658 create_repos(dst_path)
659 dump_args = ' dump "' + src_path + '"'
660 load_args = ' load "' + dst_path + '"'
662 if ignore_uuid:
663 load_args = load_args + " --ignore-uuid"
664 if verbose_mode:
665 print 'CMD:', os.path.basename(svnadmin_binary) + dump_args, \
666 '|', os.path.basename(svnadmin_binary) + load_args,
667 start = time.time()
669 dump_in, dump_out, dump_err, dump_kid = \
670 open_pipe(svnadmin_binary + dump_args, 'b')
671 dump_in.close()
672 load_in, load_out, load_err, load_kid = \
673 open_pipe(svnadmin_binary + load_args, 'b')
674 stop = time.time()
675 if verbose_mode:
676 print '<TIME = %.6f>' % (stop - start)
678 while 1:
679 data = dump_out.read(1024*1024) # Arbitrary buffer size
680 if data == "":
681 break
682 load_in.write(data)
683 load_in.close() # Tell load we are done
685 dump_lines = dump_err.readlines()
686 load_lines = load_out.readlines()
687 dump_out.close()
688 dump_err.close()
689 load_out.close()
690 load_err.close()
691 # Wait on the pipes; ignore return code.
692 wait_on_pipe(dump_kid, None, dump_lines)
693 wait_on_pipe(load_kid, load_lines, None)
695 dump_re = re.compile(r'^\* Dumped revision (\d+)\.\r?$')
696 expect_revision = 0
697 for dump_line in dump_lines:
698 match = dump_re.match(dump_line)
699 if not match or match.group(1) != str(expect_revision):
700 print 'ERROR: dump failed:', dump_line,
701 raise SVNRepositoryCopyFailure
702 expect_revision += 1
703 if expect_revision != head_revision + 1:
704 print 'ERROR: dump failed; did not see revision', head_revision
705 raise SVNRepositoryCopyFailure
707 load_re = re.compile(r'^------- Committed revision (\d+) >>>\r?$')
708 expect_revision = 1
709 for load_line in load_lines:
710 match = load_re.match(load_line)
711 if match:
712 if match.group(1) != str(expect_revision):
713 print 'ERROR: load failed:', load_line,
714 raise SVNRepositoryCopyFailure
715 expect_revision += 1
716 if expect_revision != head_revision + 1:
717 print 'ERROR: load failed; did not see revision', head_revision
718 raise SVNRepositoryCopyFailure
721 def canonicalize_url(input):
722 "Canonicalize the url, if the scheme is unknown, returns intact input"
724 m = re.match(r"^((file://)|((svn|svn\+ssh|http|https)(://)))", input)
725 if m:
726 scheme = m.group(1)
727 return scheme + re.sub(r'//*', '/', input[len(scheme):])
728 else:
729 return input
732 def create_python_hook_script (hook_path, hook_script_code):
733 """Create a Python hook script at HOOK_PATH with the specified
734 HOOK_SCRIPT_CODE."""
736 if sys.platform == 'win32':
737 # Use an absolute path since the working directory is not guaranteed
738 hook_path = os.path.abspath(hook_path)
739 # Fill the python file.
740 file_write ("%s.py" % hook_path, hook_script_code)
741 # Fill the batch wrapper file.
742 file_append ("%s.bat" % hook_path,
743 "@\"%s\" %s.py %%*\n" % (sys.executable, hook_path))
744 else:
745 # For all other platforms
746 file_write (hook_path, "#!%s\n%s" % (sys.executable, hook_script_code))
747 os.chmod (hook_path, 0755)
749 def write_restrictive_svnserve_conf(repo_dir, anon_access="none"):
750 "Create a restrictive authz file ( no anynomous access )."
752 fp = open(get_svnserve_conf_file_path(repo_dir), 'w')
753 fp.write("[general]\nanon-access = %s\nauth-access = write\n"
754 "authz-db = authz\n" % anon_access)
755 if enable_sasl == 1:
756 fp.write("realm = svntest\n[sasl]\nuse-sasl = true\n");
757 else:
758 fp.write("password-db = passwd\n")
759 fp.close()
761 # Warning: because mod_dav_svn uses one shared authz file for all
762 # repositories, you *cannot* use write_authz_file in any test that
763 # might be run in parallel.
765 # write_authz_file can *only* be used in test suites which disable
766 # parallel execution at the bottom like so
767 # if __name__ == '__main__':
768 # svntest.main.run_tests(test_list, serial_only = True)
769 def write_authz_file(sbox, rules, sections=None):
770 """Write an authz file to SBOX, appropriate for the RA method used,
771 with authorizations rules RULES mapping paths to strings containing
772 the rules. You can add sections SECTIONS (ex. groups, aliases...) with
773 an appropriate list of mappings.
775 fp = open(sbox.authz_file, 'w')
777 # When the sandbox repository is read only it's name will be different from
778 # the repository name.
779 repo_name = sbox.repo_dir
780 while repo_name[-1] == '/':
781 repo_name = repo_name[:-1]
782 repo_name = os.path.basename(repo_name)
784 if sbox.repo_url.startswith("http"):
785 prefix = repo_name + ":"
786 else:
787 prefix = ""
788 if sections:
789 for p, r in sections.items():
790 fp.write("[%s]\n%s\n" % (p, r))
792 for p, r in rules.items():
793 fp.write("[%s%s]\n%s\n" % (prefix, p, r))
794 fp.close()
796 def use_editor(func):
797 os.environ['SVN_EDITOR'] = svneditor_script
798 os.environ['SVN_MERGE'] = svneditor_script
799 os.environ['SVNTEST_EDITOR_FUNC'] = func
802 def merge_notify_line(revstart=None, revend=None, same_URL=True,
803 foreign=False):
804 """Return an expected output line that describes the beginning of a
805 merge operation on revisions REVSTART through REVEND. Omit both
806 REVSTART and REVEND for the case where the left and right sides of
807 the merge are from different URLs."""
808 from_foreign_phrase = foreign and "\(from foreign repository\) " or ""
809 if not same_URL:
810 return "--- Merging differences between %srepository URLs into '.+':\n" \
811 % (foreign and "foreign " or "")
812 if revend is None:
813 if revstart is None:
814 # The left and right sides of the merge are from different URLs.
815 return "--- Merging differences between %srepository URLs into '.+':\n" \
816 % (foreign and "foreign " or "")
817 elif revstart < 0:
818 return "--- Reverse-merging %sr%ld into '.+':\n" \
819 % (from_foreign_phrase, abs(revstart))
820 else:
821 return "--- Merging %sr%ld into '.+':\n" \
822 % (from_foreign_phrase, revstart)
823 else:
824 if revstart > revend:
825 return "--- Reverse-merging %sr%ld through r%ld into '.+':\n" \
826 % (from_foreign_phrase, revstart, revend)
827 else:
828 return "--- Merging %sr%ld through r%ld into '.+':\n" \
829 % (from_foreign_phrase, revstart, revend)
832 ######################################################################
833 # Functions which check the test configuration
834 # (useful for conditional XFails)
836 def _check_command_line_parsed():
837 """Raise an exception if the command line has not yet been parsed."""
838 if not command_line_parsed:
839 raise Failure("Condition cannot be tested until command line is parsed")
841 def is_ra_type_dav():
842 _check_command_line_parsed()
843 return test_area_url.startswith('http')
845 def is_ra_type_svn():
846 _check_command_line_parsed()
847 return test_area_url.startswith('svn')
849 def is_ra_type_file():
850 _check_command_line_parsed()
851 return test_area_url.startswith('file')
853 def is_fs_type_fsfs():
854 _check_command_line_parsed()
855 # This assumes that fsfs is the default fs implementation.
856 return fs_type == 'fsfs' or fs_type is None
858 def is_os_windows():
859 return os.name == 'nt'
861 def is_posix_os():
862 return os.name == 'posix'
864 def is_os_darwin():
865 return sys.platform == 'darwin'
867 def server_has_mergeinfo():
868 _check_command_line_parsed()
869 return server_minor_version >= 5
871 def server_has_revprop_commit():
872 _check_command_line_parsed()
873 return server_minor_version >= 5
875 def server_sends_copyfrom_on_update():
876 _check_command_line_parsed()
877 return server_minor_version >= 5
879 def server_authz_has_aliases():
880 _check_command_line_parsed()
881 return server_minor_version >= 5
883 def server_gets_client_capabilities():
884 _check_command_line_parsed()
885 return server_minor_version >= 5
887 def server_has_partial_replay():
888 _check_command_line_parsed()
889 return server_minor_version >= 5
891 def server_enforces_date_syntax():
892 _check_command_line_parsed()
893 return server_minor_version >= 5
896 ######################################################################
897 # Sandbox handling
899 class Sandbox:
900 """Manages a sandbox (one or more repository/working copy pairs) for
901 a test to operate within."""
903 dependents = None
905 def __init__(self, module, idx):
906 self._set_name("%s-%d" % (module, idx))
908 def _set_name(self, name, read_only = False):
909 """A convenience method for renaming a sandbox, useful when
910 working with multiple repositories in the same unit test."""
911 if not name is None:
912 self.name = name
913 self.read_only = read_only
914 self.wc_dir = os.path.join(general_wc_dir, self.name)
915 if not read_only:
916 self.repo_dir = os.path.join(general_repo_dir, self.name)
917 self.repo_url = test_area_url + '/' + self.repo_dir
918 else:
919 self.repo_dir = pristine_dir
920 self.repo_url = pristine_url
922 ### TODO: Move this into to the build() method
923 # For dav tests we need a single authz file which must be present,
924 # so we recreate it each time a sandbox is created with some default
925 # contents.
926 if self.repo_url.startswith("http"):
927 # this dir doesn't exist out of the box, so we may have to make it
928 if not os.path.exists(work_dir):
929 os.makedirs(work_dir)
930 self.authz_file = os.path.join(work_dir, "authz")
931 file_write(self.authz_file, "[/]\n* = rw\n")
933 # For svnserve tests we have a per-repository authz file, and it
934 # doesn't need to be there in order for things to work, so we don't
935 # have any default contents.
936 elif self.repo_url.startswith("svn"):
937 self.authz_file = os.path.join(self.repo_dir, "conf", "authz")
939 if windows:
940 self.repo_url = self.repo_url.replace('\\', '/')
941 self.test_paths = [self.wc_dir, self.repo_dir]
943 def clone_dependent(self, copy_wc=False):
944 """A convenience method for creating a near-duplicate of this
945 sandbox, useful when working with multiple repositories in the
946 same unit test. If COPY_WC is true, make an exact copy of this
947 sandbox's working copy at the new sandbox's working copy
948 directory. Any necessary cleanup operations are triggered by
949 cleanup of the original sandbox."""
951 if not self.dependents:
952 self.dependents = []
953 clone = copy.deepcopy(self)
954 self.dependents.append(clone)
955 clone._set_name("%s-%d" % (self.name, len(self.dependents)))
956 if copy_wc:
957 self.add_test_path(clone.wc_dir)
958 shutil.copytree(self.wc_dir, clone.wc_dir, symlinks=True)
959 return clone
961 def build(self, name = None, create_wc = True, read_only = False):
962 self._set_name(name, read_only)
963 if actions.make_repo_and_wc(self, create_wc, read_only):
964 raise Failure("Could not build repository and sandbox '%s'" % self.name)
966 def add_test_path(self, path, remove=True):
967 self.test_paths.append(path)
968 if remove:
969 safe_rmtree(path)
971 def add_repo_path(self, suffix, remove=1):
972 path = os.path.join(general_repo_dir, self.name) + '.' + suffix
973 url = test_area_url + '/' + path
974 if windows:
975 url = url.replace('\\', '/')
976 self.add_test_path(path, remove)
977 return path, url
979 def add_wc_path(self, suffix, remove=1):
980 path = self.wc_dir + '.' + suffix
981 self.add_test_path(path, remove)
982 return path
984 def cleanup_test_paths(self):
985 "Clean up detritus from this sandbox, and any dependents."
986 if self.dependents:
987 # Recursively cleanup any dependent sandboxes.
988 for sbox in self.dependents:
989 sbox.cleanup_test_paths()
990 # cleanup all test specific working copies and repositories
991 for path in self.test_paths:
992 if not path is pristine_dir:
993 _cleanup_test_path(path)
996 _deferred_test_paths = []
997 def _cleanup_deferred_test_paths():
998 global _deferred_test_paths
999 test_paths = _deferred_test_paths[:]
1000 _deferred_test_paths = []
1001 for path in test_paths:
1002 _cleanup_test_path(path, 1)
1004 def _cleanup_test_path(path, retrying=None):
1005 if verbose_mode:
1006 if retrying:
1007 print "CLEANUP: RETRY:", path
1008 else:
1009 print "CLEANUP:", path
1010 try:
1011 safe_rmtree(path)
1012 except:
1013 if verbose_mode:
1014 print "WARNING: cleanup failed, will try again later"
1015 _deferred_test_paths.append(path)
1017 class TestSpawningThread(threading.Thread):
1018 """A thread that runs test cases in their own processes.
1019 Receives test numbers to run from the queue, and saves results into
1020 the results field."""
1021 def __init__(self, queue):
1022 threading.Thread.__init__(self)
1023 self.queue = queue
1024 self.results = []
1026 def run(self):
1027 while True:
1028 try:
1029 next_index = self.queue.get_nowait()
1030 except Queue.Empty:
1031 return
1033 self.run_one(next_index)
1035 def run_one(self, index):
1036 command = sys.argv[0]
1038 args = []
1039 args.append(str(index))
1040 args.append('-c')
1041 # add some startup arguments from this process
1042 if fs_type:
1043 args.append('--fs-type=' + fs_type)
1044 if test_area_url:
1045 args.append('--url=' + test_area_url)
1046 if verbose_mode:
1047 args.append('-v')
1048 if cleanup_mode:
1049 args.append('--cleanup')
1050 if enable_sasl:
1051 args.append('--enable-sasl')
1052 if http_library:
1053 args.append('--http-library=' + http_library)
1054 if server_minor_version:
1055 args.append('--server-minor-version=' + str(server_minor_version))
1057 result, stdout_lines, stderr_lines = spawn_process(command, 1, None, *args)
1058 # "result" will be None on platforms without Popen3 (e.g. Windows)
1059 if filter(lambda x: x.startswith('FAIL: ') or x.startswith('XPASS: '),
1060 stdout_lines):
1061 result = 1
1062 self.results.append((index, result, stdout_lines, stderr_lines))
1063 sys.stdout.write('.')
1064 sys.stdout.flush()
1066 class TestRunner:
1067 """Encapsulate a single test case (predicate), including logic for
1068 runing the test and test list output."""
1070 def __init__(self, func, index):
1071 self.pred = testcase.create_test_case(func)
1072 self.index = index
1074 def list(self):
1075 print " %2d %-5s %s" % (self.index,
1076 self.pred.list_mode(),
1077 self.pred.get_description())
1078 self.pred.check_description()
1080 def _print_name(self):
1081 print os.path.basename(sys.argv[0]), str(self.index) + ":", \
1082 self.pred.get_description()
1083 self.pred.check_description()
1085 def run(self):
1086 """Run self.pred and return the result. The return value is
1087 - 0 if the test was successful
1088 - 1 if it errored in a way that indicates test failure
1089 - 2 if the test skipped
1091 if self.pred.need_sandbox():
1092 # ooh! this function takes a sandbox argument
1093 sandbox = Sandbox(self.pred.get_sandbox_name(), self.index)
1094 kw = { 'sandbox' : sandbox }
1095 else:
1096 sandbox = None
1097 kw = {}
1099 # Explicitly set this so that commands that commit but don't supply a
1100 # log message will fail rather than invoke an editor.
1101 # Tests that want to use an editor should invoke svntest.main.use_editor.
1102 os.environ['SVN_EDITOR'] = ''
1103 os.environ['SVNTEST_EDITOR_FUNC'] = ''
1105 if use_jsvn:
1106 # Set this SVNKit specific variable to the current test (test name plus
1107 # its index) being run so that SVNKit daemon could use this test name
1108 # for its separate log file
1109 os.environ['SVN_CURRENT_TEST'] = os.path.basename(sys.argv[0]) + "_" + \
1110 str(self.index)
1112 actions.no_sleep_for_timestamps()
1114 saved_dir = os.getcwd()
1115 try:
1116 rc = apply(self.pred.run, (), kw)
1117 if rc is not None:
1118 print 'STYLE ERROR in',
1119 self._print_name()
1120 print 'Test driver returned a status code.'
1121 sys.exit(255)
1122 result = 0
1123 except Skip, ex:
1124 result = 2
1125 except Failure, ex:
1126 result = 1
1127 # We captured Failure and its subclasses. We don't want to print
1128 # anything for plain old Failure since that just indicates test
1129 # failure, rather than relevant information. However, if there
1130 # *is* information in the exception's arguments, then print it.
1131 if ex.__class__ != Failure or ex.args:
1132 ex_args = str(ex)
1133 if ex_args:
1134 print 'EXCEPTION: %s: %s' % (ex.__class__.__name__, ex_args)
1135 else:
1136 print 'EXCEPTION:', ex.__class__.__name__
1137 traceback.print_exc(file=sys.stdout)
1138 except KeyboardInterrupt:
1139 print 'Interrupted'
1140 sys.exit(0)
1141 except SystemExit, ex:
1142 print 'EXCEPTION: SystemExit(%d), skipping cleanup' % ex.code
1143 print ex.code and 'FAIL: ' or 'PASS: ',
1144 self._print_name()
1145 raise
1146 except:
1147 result = 1
1148 print 'UNEXPECTED EXCEPTION:'
1149 traceback.print_exc(file=sys.stdout)
1151 os.chdir(saved_dir)
1152 result = self.pred.convert_result(result)
1153 (result_text, result_benignity) = self.pred.run_text(result)
1154 if not (quiet_mode and result_benignity):
1155 print result_text,
1156 self._print_name()
1157 sys.stdout.flush()
1158 if sandbox is not None and result != 1 and cleanup_mode:
1159 sandbox.cleanup_test_paths()
1160 return result
1162 ######################################################################
1163 # Main testing functions
1165 # These two functions each take a TEST_LIST as input. The TEST_LIST
1166 # should be a list of test functions; each test function should take
1167 # no arguments and return a 0 on success, non-zero on failure.
1168 # Ideally, each test should also have a short, one-line docstring (so
1169 # it can be displayed by the 'list' command.)
1171 # Func to run one test in the list.
1172 def run_one_test(n, test_list, parallel = 0, finished_tests = None):
1173 """Run the Nth client test in TEST_LIST, return the result.
1175 If we're running the tests in parallel spawn the test in a new process.
1178 if (n < 1) or (n > len(test_list) - 1):
1179 print "There is no test", `n` + ".\n"
1180 return 1
1182 # Run the test.
1183 if parallel:
1184 st = SpawnTest(n, finished_tests)
1185 st.start()
1186 return 0
1187 else:
1188 exit_code = TestRunner(test_list[n], n).run()
1189 return exit_code
1191 def _internal_run_tests(test_list, testnums, parallel):
1192 """Run the tests from TEST_LIST whose indices are listed in TESTNUMS.
1194 If we're running the tests in parallel spawn as much parallel processes
1195 as requested and gather the results in a temp. buffer when a child
1196 process is finished.
1199 exit_code = 0
1200 finished_tests = []
1201 tests_started = 0
1203 if not parallel:
1204 for testnum in testnums:
1205 if run_one_test(testnum, test_list) == 1:
1206 exit_code = 1
1207 else:
1208 number_queue = Queue.Queue()
1209 for num in testnums:
1210 number_queue.put(num)
1212 threads = [ TestSpawningThread(number_queue) for i in range(parallel) ]
1213 for t in threads:
1214 t.start()
1216 for t in threads:
1217 t.join()
1219 # list of (index, result, stdout, stderr)
1220 results = []
1221 for t in threads:
1222 results += t.results
1223 results.sort()
1225 # terminate the line of dots
1226 print
1228 # all tests are finished, find out the result and print the logs.
1229 for (index, result, stdout_lines, stderr_lines) in results:
1230 if stdout_lines:
1231 for line in stdout_lines:
1232 sys.stdout.write(line)
1233 if stderr_lines:
1234 for line in stderr_lines:
1235 sys.stdout.write(line)
1236 if result == 1:
1237 exit_code = 1
1239 _cleanup_deferred_test_paths()
1240 return exit_code
1243 def usage():
1244 prog_name = os.path.basename(sys.argv[0])
1245 print "%s [--url] [--fs-type] [--verbose|--quiet] [--parallel] \\" % \
1246 prog_name
1247 print "%s [--enable-sasl] [--cleanup] [--bin] [<test> ...]" \
1248 % (" " * len(prog_name))
1249 print "%s " % (" " * len(prog_name))
1250 print "%s [--list] [<test> ...]\n" % prog_name
1251 print "Arguments:"
1252 print " test The number of the test to run (multiple okay), " \
1253 "or all tests\n"
1254 print "Options:"
1255 print " --list Print test doc strings instead of running them"
1256 print " --fs-type Subversion file system type (fsfs or bdb)"
1257 print " --http-library DAV library to use (neon or serf)"
1258 print " --url Base url to the repos (e.g. svn://localhost)"
1259 print " --verbose Print binary command-lines (not with --quiet)"
1260 print " --quiet Print only unexpected results (not with --verbose)"
1261 print " --cleanup Whether to clean up"
1262 print " --enable-sasl Whether to enable SASL authentication"
1263 print " --parallel Run the tests in parallel"
1264 print " --bin Use the svn binaries installed in this path"
1265 print " --use-jsvn Use the jsvn (SVNKit based) binaries. Can be\n" \
1266 " combined with --bin to point to a specific path"
1267 print " --development Test development mode: provides more detailed test\n"\
1268 " output and ignores all exceptions in the \n" \
1269 " run_and_verify* functions. This option is only \n" \
1270 " useful during test development!"
1271 print " --server-minor-version Set the minor version for the server.\n" \
1272 " Supports version 4 or 5."
1273 print " --config-file Configuration file for tests."
1274 print " --help This information"
1277 # Main func. This is the "entry point" that all the test scripts call
1278 # to run their list of tests.
1280 # This routine parses sys.argv to decide what to do.
1281 def run_tests(test_list, serial_only = False):
1282 """Main routine to run all tests in TEST_LIST.
1284 NOTE: this function does not return. It does a sys.exit() with the
1285 appropriate exit code.
1288 global test_area_url
1289 global pristine_url
1290 global fs_type
1291 global verbose_mode
1292 global quiet_mode
1293 global cleanup_mode
1294 global enable_sasl
1295 global is_child_process
1296 global svn_binary
1297 global svnadmin_binary
1298 global svnlook_binary
1299 global svnsync_binary
1300 global svndumpfilter_binary
1301 global svnversion_binary
1302 global command_line_parsed
1303 global http_library
1304 global config_file
1305 global server_minor_version
1306 global use_jsvn
1308 testnums = []
1309 # Should the tests be listed (as opposed to executed)?
1310 list_tests = False
1312 parallel = 0
1313 svn_bin = None
1314 use_jsvn = False
1315 config_file = None
1317 try:
1318 opts, args = my_getopt(sys.argv[1:], 'vqhpc',
1319 ['url=', 'fs-type=', 'verbose', 'quiet', 'cleanup',
1320 'list', 'enable-sasl', 'help', 'parallel',
1321 'bin=', 'http-library=', 'server-minor-version=',
1322 'use-jsvn', 'development', 'config-file='])
1323 except getopt.GetoptError, e:
1324 print "ERROR: %s\n" % e
1325 usage()
1326 sys.exit(1)
1328 for arg in args:
1329 if arg == "list":
1330 # This is an old deprecated variant of the "--list" option:
1331 list_tests = True
1332 elif arg.startswith('BASE_URL='):
1333 test_area_url = arg[9:]
1334 else:
1335 try:
1336 testnums.append(int(arg))
1337 except ValueError:
1338 print "ERROR: invalid test number '%s'\n" % arg
1339 usage()
1340 sys.exit(1)
1342 for opt, val in opts:
1343 if opt == "--url":
1344 test_area_url = val
1346 elif opt == "--fs-type":
1347 fs_type = val
1349 elif opt == "-v" or opt == "--verbose":
1350 verbose_mode = True
1352 elif opt == "-q" or opt == "--quiet":
1353 quiet_mode = True
1355 elif opt == "--cleanup":
1356 cleanup_mode = True
1358 elif opt == "--list":
1359 list_tests = True
1361 elif opt == "--enable-sasl":
1362 enable_sasl = True
1364 elif opt == "-h" or opt == "--help":
1365 usage()
1366 sys.exit(0)
1368 elif opt == '-p' or opt == "--parallel":
1369 parallel = 5 # use 5 parallel threads.
1371 elif opt == '-c':
1372 is_child_process = True
1374 elif opt == '--bin':
1375 svn_bin = val
1377 elif opt == '--http-library':
1378 http_library = val
1380 elif opt == '--server-minor-version':
1381 server_minor_version = int(val)
1382 if server_minor_version < 4 or server_minor_version > 6:
1383 print "ERROR: test harness only supports server minor version 4 or 5"
1384 sys.exit(1)
1386 elif opt == '--use-jsvn':
1387 use_jsvn = True
1389 elif opt == '--development':
1390 setup_development_mode()
1392 elif opt == '--config-file':
1393 config_file = val
1395 if test_area_url[-1:] == '/': # Normalize url to have no trailing slash
1396 test_area_url = test_area_url[:-1]
1398 if verbose_mode and quiet_mode:
1399 sys.stderr.write("ERROR: 'verbose' and 'quiet' are incompatible\n")
1400 sys.exit(1)
1402 # Calculate pristine_url from test_area_url.
1403 pristine_url = test_area_url + '/' + pristine_dir
1404 if windows:
1405 pristine_url = pristine_url.replace('\\', '/')
1407 if use_jsvn:
1408 if svn_bin is None:
1409 svn_bin = ''
1410 svn_binary = os.path.join(svn_bin, 'jsvn' + _bat)
1411 svnadmin_binary = os.path.join(svn_bin, 'jsvnadmin' + _bat)
1412 svnlook_binary = os.path.join(svn_bin, 'jsvnlook' + _bat)
1413 svnsync_binary = os.path.join(svn_bin, 'jsvnsync' + _bat)
1414 svndumpfilter_binary = os.path.join(svn_bin, 'jsvndumpfilter' + _bat)
1415 svnversion_binary = os.path.join(svn_bin, 'jsvnversion' + _bat)
1416 else:
1417 if svn_bin:
1418 svn_binary = os.path.join(svn_bin, 'svn' + _exe)
1419 svnadmin_binary = os.path.join(svn_bin, 'svnadmin' + _exe)
1420 svnlook_binary = os.path.join(svn_bin, 'svnlook' + _exe)
1421 svnsync_binary = os.path.join(svn_bin, 'svnsync' + _exe)
1422 svndumpfilter_binary = os.path.join(svn_bin, 'svndumpfilter' + _exe)
1423 svnversion_binary = os.path.join(svn_bin, 'svnversion' + _exe)
1425 command_line_parsed = True
1427 ######################################################################
1429 # Cleanup: if a previous run crashed or interrupted the python
1430 # interpreter, then `temp_dir' was never removed. This can cause wonkiness.
1431 if not is_child_process:
1432 safe_rmtree(temp_dir, 1)
1434 if not testnums:
1435 # If no test numbers were listed explicitly, include all of them:
1436 testnums = range(1, len(test_list))
1438 if list_tests:
1439 print "Test # Mode Test Description"
1440 print "------ ----- ----------------"
1441 for testnum in testnums:
1442 TestRunner(test_list[testnum], testnum).list()
1444 # done. just exit with success.
1445 sys.exit(0)
1447 # don't run tests in parallel when the tests don't support it or there
1448 # are only a few tests to run.
1449 if serial_only or len(testnums) < 2:
1450 parallel = 0
1452 # Setup the pristine repository
1453 actions.setup_pristine_repository()
1455 # Build out the default configuration directory
1456 create_config_dir(default_config_dir)
1458 # Run the tests.
1459 exit_code = _internal_run_tests(test_list, testnums, parallel)
1461 # Remove all scratchwork: the 'pristine' repository, greek tree, etc.
1462 # This ensures that an 'import' will happen the next time we run.
1463 if not is_child_process:
1464 safe_rmtree(temp_dir, 1)
1466 # Cleanup after ourselves.
1467 _cleanup_deferred_test_paths()
1469 # Return the appropriate exit code from the tests.
1470 sys.exit(exit_code)
1472 # the modules import each other, so we do this import very late, to ensure
1473 # that the definitions in "main" have been completed.
1474 import actions
1477 ### End of file.