Tweak a test function for correctness (though it didn't seem to cause a
[svnrdump.git] / svntest / main.py
blob9a7285043f2e8b99e34538f5003e497e4efe55af
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
28 import urllib
30 import getopt
31 try:
32 my_getopt = getopt.gnu_getopt
33 except AttributeError:
34 my_getopt = getopt.getopt
36 from svntest import Failure
37 from svntest import Skip
38 from svntest import testcase
39 from svntest import wc
41 ######################################################################
43 # HOW TO USE THIS MODULE:
45 # Write a new python script that
47 # 1) imports this 'svntest' package
49 # 2) contains a number of related 'test' routines. (Each test
50 # routine should take no arguments, and return None on success
51 # or throw a Failure exception on failure. Each test should
52 # also contain a short docstring.)
54 # 3) places all the tests into a list that begins with None.
56 # 4) calls svntest.main.client_test() on the list.
58 # Also, your tests will probably want to use some of the common
59 # routines in the 'Utilities' section below.
61 #####################################################################
62 # Global stuff
64 class SVNProcessTerminatedBySignal(Failure):
65 "Exception raised if a spawned process segfaulted, aborted, etc."
66 pass
68 class SVNLineUnequal(Failure):
69 "Exception raised if two lines are unequal"
70 pass
72 class SVNUnmatchedError(Failure):
73 "Exception raised if an expected error is not found"
74 pass
76 class SVNCommitFailure(Failure):
77 "Exception raised if a commit failed"
78 pass
80 class SVNRepositoryCopyFailure(Failure):
81 "Exception raised if unable to copy a repository"
82 pass
84 class SVNRepositoryCreateFailure(Failure):
85 "Exception raised if unable to create a repository"
86 pass
88 # Windows specifics
89 if sys.platform == 'win32':
90 windows = True
91 file_scheme_prefix = 'file:///'
92 _exe = '.exe'
93 _bat = '.bat'
94 else:
95 windows = False
96 file_scheme_prefix = 'file://'
97 _exe = ''
98 _bat = ''
100 try:
101 from popen2 import Popen3
102 platform_with_popen3_class = True
103 except ImportError:
104 platform_with_popen3_class = False
106 # The location of our mock svneditor script.
107 if sys.platform == 'win32':
108 svneditor_script = os.path.join(sys.path[0], 'svneditor.bat')
109 else:
110 svneditor_script = os.path.join(sys.path[0], 'svneditor.py')
112 # Username and password used by the working copies
113 wc_author = 'jrandom'
114 wc_passwd = 'rayjandom'
116 # Username and password used by the working copies for "second user"
117 # scenarios
118 wc_author2 = 'jconstant' # use the same password as wc_author
120 # Set C locale for command line programs
121 os.environ['LC_ALL'] = 'C'
123 # This function mimics the Python 2.3 urllib function of the same name.
124 def pathname2url(path):
125 """Convert the pathname PATH from the local syntax for a path to the form
126 used in the path component of a URL. This does not produce a complete URL.
127 The return value will already be quoted using the quote() function."""
128 return urllib.quote(path.replace('\\', '/'))
130 # This function mimics the Python 2.3 urllib function of the same name.
131 def url2pathname(path):
132 """Convert the path component PATH from an encoded URL to the local syntax
133 for a path. This does not accept a complete URL. This function uses
134 unquote() to decode PATH."""
135 return os.path.normpath(urllib.unquote(path))
137 ######################################################################
138 # Global variables set during option parsing. These should not be used
139 # until the variable command_line_parsed has been set to True, as is
140 # done in run_tests below.
141 command_line_parsed = False
143 # The locations of the svn, svnadmin and svnlook binaries, relative to
144 # the only scripts that import this file right now (they live in ../).
145 # Use --bin to override these defaults.
146 svn_binary = os.path.abspath('../../svn/svn' + _exe)
147 svnadmin_binary = os.path.abspath('../../svnadmin/svnadmin' + _exe)
148 svnlook_binary = os.path.abspath('../../svnlook/svnlook' + _exe)
149 svnsync_binary = os.path.abspath('../../svnsync/svnsync' + _exe)
150 svnversion_binary = os.path.abspath('../../svnversion/svnversion' + _exe)
151 svndumpfilter_binary = os.path.abspath('../../svndumpfilter/svndumpfilter' + \
152 _exe)
154 # Global variable indicating if we want verbose output, that is,
155 # details of what commands each test does as it does them. This is
156 # incompatible with quiet_mode.
157 verbose_mode = False
159 # Global variable indicating if we want quiet output, that is, don't
160 # show PASS, XFAIL, or SKIP notices, but do show FAIL and XPASS. This
161 # is incompatible with verbose_mode.
162 quiet_mode = False
164 # Global variable indicating if we want test data cleaned up after success
165 cleanup_mode = False
167 # Global variable indicating if svnserve should use Cyrus SASL
168 enable_sasl = False
170 # Global variable indicating that SVNKit binaries should be used
171 use_jsvn = False
173 # Global variable indicating which DAV library, if any, is in use
174 # ('neon', 'serf')
175 http_library = None
177 # Configuration file (copied into FSFS fsfs.conf).
178 config_file = None
180 # Global variable indicating what the minor version of the server
181 # tested against is (4 for 1.4.x, for example).
182 server_minor_version = 5
184 # Global variable indicating if this is a child process and no cleanup
185 # of global directories is needed.
186 is_child_process = False
188 # Global URL to testing area. Default to ra_local, current working dir.
189 test_area_url = file_scheme_prefix + pathname2url(os.path.abspath(os.getcwd()))
191 # Location to the pristine repository, will be calculated from test_area_url
192 # when we know what the user specified for --url.
193 pristine_url = None
195 # Global variable indicating the FS type for repository creations.
196 fs_type = None
198 # End of command-line-set global variables.
199 ######################################################################
201 # All temporary repositories and working copies are created underneath
202 # this dir, so there's one point at which to mount, e.g., a ramdisk.
203 work_dir = "svn-test-work"
205 # Constant for the merge info property.
206 SVN_PROP_MERGEINFO = "svn:mergeinfo"
208 # Where we want all the repositories and working copies to live.
209 # Each test will have its own!
210 general_repo_dir = os.path.join(work_dir, "repositories")
211 general_wc_dir = os.path.join(work_dir, "working_copies")
213 # temp directory in which we will create our 'pristine' local
214 # repository and other scratch data. This should be removed when we
215 # quit and when we startup.
216 temp_dir = os.path.join(work_dir, 'local_tmp')
218 # (derivatives of the tmp dir.)
219 pristine_dir = os.path.join(temp_dir, "repos")
220 greek_dump_dir = os.path.join(temp_dir, "greekfiles")
221 default_config_dir = os.path.abspath(os.path.join(temp_dir, "config"))
224 # Our pristine greek-tree state.
226 # If a test wishes to create an "expected" working-copy tree, it should
227 # call main.greek_state.copy(). That method will return a copy of this
228 # State object which can then be edited.
230 _item = wc.StateItem
231 greek_state = wc.State('', {
232 'iota' : _item("This is the file 'iota'.\n"),
233 'A' : _item(),
234 'A/mu' : _item("This is the file 'mu'.\n"),
235 'A/B' : _item(),
236 'A/B/lambda' : _item("This is the file 'lambda'.\n"),
237 'A/B/E' : _item(),
238 'A/B/E/alpha' : _item("This is the file 'alpha'.\n"),
239 'A/B/E/beta' : _item("This is the file 'beta'.\n"),
240 'A/B/F' : _item(),
241 'A/C' : _item(),
242 'A/D' : _item(),
243 'A/D/gamma' : _item("This is the file 'gamma'.\n"),
244 'A/D/G' : _item(),
245 'A/D/G/pi' : _item("This is the file 'pi'.\n"),
246 'A/D/G/rho' : _item("This is the file 'rho'.\n"),
247 'A/D/G/tau' : _item("This is the file 'tau'.\n"),
248 'A/D/H' : _item(),
249 'A/D/H/chi' : _item("This is the file 'chi'.\n"),
250 'A/D/H/psi' : _item("This is the file 'psi'.\n"),
251 'A/D/H/omega' : _item("This is the file 'omega'.\n"),
255 ######################################################################
256 # Utilities shared by the tests
257 def wrap_ex(func):
258 "Wrap a function, catch, print and ignore exceptions"
259 def w(*args, **kwds):
260 try:
261 return func(*args, **kwds)
262 except Failure, ex:
263 if ex.__class__ != Failure or ex.args:
264 ex_args = str(ex)
265 if ex_args:
266 print('EXCEPTION: %s: %s' % (ex.__class__.__name__, ex_args))
267 else:
268 print('EXCEPTION: %s' % ex.__class__.__name__)
269 return w
271 def setup_development_mode():
272 "Wraps functions in module actions"
273 l = [ 'run_and_verify_svn',
274 'run_and_verify_svnversion',
275 'run_and_verify_load',
276 'run_and_verify_dump',
277 'run_and_verify_checkout',
278 'run_and_verify_export',
279 'run_and_verify_update',
280 'run_and_verify_merge',
281 'run_and_verify_merge2',
282 'run_and_verify_switch',
283 'run_and_verify_commit',
284 'run_and_verify_unquiet_status',
285 'run_and_verify_status',
286 'run_and_verify_diff_summarize',
287 'run_and_verify_diff_summarize_xml',
288 'run_and_validate_lock']
290 for func in l:
291 setattr(actions, func, wrap_ex(getattr(actions, func)))
293 def get_admin_name():
294 "Return name of SVN administrative subdirectory."
296 if (windows or sys.platform == 'cygwin') \
297 and 'SVN_ASP_DOT_NET_HACK' in os.environ:
298 return '_svn'
299 else:
300 return '.svn'
302 def get_start_commit_hook_path(repo_dir):
303 "Return the path of the start-commit-hook conf file in REPO_DIR."
305 return os.path.join(repo_dir, "hooks", "start-commit")
308 def get_pre_commit_hook_path(repo_dir):
309 "Return the path of the pre-commit-hook conf file in REPO_DIR."
311 return os.path.join(repo_dir, "hooks", "pre-commit")
314 def get_post_commit_hook_path(repo_dir):
315 "Return the path of the post-commit-hook conf file in REPO_DIR."
317 return os.path.join(repo_dir, "hooks", "post-commit")
319 def get_pre_revprop_change_hook_path(repo_dir):
320 "Return the path of the pre-revprop-change hook script in REPO_DIR."
322 return os.path.join(repo_dir, "hooks", "pre-revprop-change")
324 def get_svnserve_conf_file_path(repo_dir):
325 "Return the path of the svnserve.conf file in REPO_DIR."
327 return os.path.join(repo_dir, "conf", "svnserve.conf")
329 def get_fsfs_conf_file_path(repo_dir):
330 "Return the path of the fsfs.conf file in REPO_DIR."
332 return os.path.join(repo_dir, "db", "fsfs.conf")
334 # Run any binary, logging the command line and return code
335 def run_command(command, error_expected, binary_mode=0, *varargs):
336 """Run COMMAND with VARARGS; return exit code as int; stdout, stderr
337 as lists of lines.
338 If ERROR_EXPECTED is None, any stderr also will be printed."""
340 return run_command_stdin(command, error_expected, binary_mode,
341 None, *varargs)
343 # A regular expression that matches arguments that are trivially safe
344 # to pass on a command line without quoting on any supported operating
345 # system:
346 _safe_arg_re = re.compile(r'^[A-Za-z\d\.\_\/\-\:\@]+$')
348 def _quote_arg(arg):
349 """Quote ARG for a command line.
351 Simply surround every argument in double-quotes unless it contains
352 only universally harmless characters.
354 WARNING: This function cannot handle arbitrary command-line
355 arguments. It can easily be confused by shell metacharacters. A
356 perfect job would be difficult and OS-dependent (see, for example,
357 http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp).
358 In other words, this function is just good enough for what we need
359 here."""
361 arg = str(arg)
362 if _safe_arg_re.match(arg):
363 return arg
364 else:
365 if os.name != 'nt':
366 arg = arg.replace('$', '\$')
367 return '"%s"' % (arg,)
369 def open_pipe(command, mode):
370 """Opens a popen3 pipe to COMMAND in MODE.
372 Returns (infile, outfile, errfile, waiter); waiter
373 should be passed to wait_on_pipe."""
374 if platform_with_popen3_class:
375 kid = Popen3(command, True)
376 return kid.tochild, kid.fromchild, kid.childerr, (kid, command)
377 else:
378 inf, outf, errf = os.popen3(command, mode)
379 return inf, outf, errf, None
381 def wait_on_pipe(waiter, stdout_lines, stderr_lines):
382 """Waits for KID (opened with open_pipe) to finish, dying
383 if it does. Uses STDOUT_LINES and STDERR_LINES for error message
384 if kid fails. Returns kid's exit code."""
385 if waiter is None:
386 return
388 kid, command = waiter
390 wait_code = kid.wait()
392 if os.WIFSIGNALED(wait_code):
393 exit_signal = os.WTERMSIG(wait_code)
394 if stdout_lines is not None:
395 sys.stdout.write("".join(stdout_lines))
396 if stderr_lines is not None:
397 sys.stderr.write("".join(stderr_lines))
398 if verbose_mode:
399 # show the whole path to make it easier to start a debugger
400 sys.stderr.write("CMD: %s terminated by signal %d\n"
401 % (command, exit_signal))
402 raise SVNProcessTerminatedBySignal
403 else:
404 exit_code = os.WEXITSTATUS(wait_code)
405 if exit_code and verbose_mode:
406 sys.stderr.write("CMD: %s exited with %d\n" % (command, exit_code))
407 return exit_code
409 # Run any binary, supplying input text, logging the command line
410 def spawn_process(command, binary_mode=0,stdin_lines=None, *varargs):
411 args = ' '.join(map(_quote_arg, varargs))
413 # Log the command line
414 if verbose_mode and not command.endswith('.py'):
415 sys.stdout.write('CMD: %s %s ' % (os.path.basename(command), args))
416 sys.stdout.flush()
418 if binary_mode:
419 mode = 'b'
420 else:
421 mode = 't'
423 infile, outfile, errfile, kid = open_pipe(command + ' ' + args, mode)
425 if stdin_lines:
426 for x in stdin_lines:
427 infile.write(x)
429 infile.close()
431 stdout_lines = outfile.readlines()
432 stderr_lines = errfile.readlines()
434 outfile.close()
435 errfile.close()
437 exit_code = wait_on_pipe(kid, stdout_lines, stderr_lines)
439 return exit_code, stdout_lines, stderr_lines
441 def run_command_stdin(command, error_expected, binary_mode=0,
442 stdin_lines=None, *varargs):
443 """Run COMMAND with VARARGS; input STDIN_LINES (a list of strings
444 which should include newline characters) to program via stdin - this
445 should not be very large, as if the program outputs more than the OS
446 is willing to buffer, this will deadlock, with both Python and
447 COMMAND waiting to write to each other for ever.
448 Return exit code as int; stdout, stderr as lists of lines.
449 If ERROR_EXPECTED is None, any stderr also will be printed."""
451 if verbose_mode:
452 start = time.time()
454 exit_code, stdout_lines, stderr_lines = spawn_process(command,
455 binary_mode,
456 stdin_lines,
457 *varargs)
459 if verbose_mode:
460 stop = time.time()
461 print('<TIME = %.6f>' % (stop - start))
462 for x in stdout_lines:
463 sys.stdout.write(x)
464 for x in stderr_lines:
465 sys.stdout.write(x)
467 if (not error_expected) and (stderr_lines):
468 if not verbose_mode:
469 for x in stderr_lines:
470 sys.stdout.write(x)
471 raise Failure
473 return exit_code, stdout_lines, stderr_lines
475 def create_config_dir(cfgdir, config_contents=None, server_contents=None):
476 "Create config directories and files"
478 # config file names
479 cfgfile_cfg = os.path.join(cfgdir, 'config')
480 cfgfile_srv = os.path.join(cfgdir, 'servers')
482 # create the directory
483 if not os.path.isdir(cfgdir):
484 os.makedirs(cfgdir)
486 # define default config file contents if none provided
487 if config_contents is None:
488 config_contents = """
490 [miscellany]
491 interactive-conflicts = false
494 # define default server file contents if none provided
495 if server_contents is None:
496 http_library_str = ""
497 if http_library:
498 http_library_str = "http-library=%s" % (http_library)
499 server_contents = """
501 [global]
503 store-plaintext-passwords=yes
504 store-passwords=yes
505 """ % (http_library_str)
507 file_write(cfgfile_cfg, config_contents)
508 file_write(cfgfile_srv, server_contents)
510 def _with_config_dir(args):
511 if '--config-dir' in args:
512 return args
513 else:
514 return args + ('--config-dir', default_config_dir)
516 def _with_auth(args):
517 assert '--password' not in args
518 args = args + ('--password', wc_passwd,
519 '--no-auth-cache' )
520 if '--username' in args:
521 return args
522 else:
523 return args + ('--username', wc_author )
525 # For running subversion and returning the output
526 def run_svn(error_expected, *varargs):
527 """Run svn with VARARGS; return exit code as int; stdout, stderr as
528 lists of lines.
529 If ERROR_EXPECTED is None, any stderr also will be printed. If
530 you're just checking that something does/doesn't come out of
531 stdout/stderr, you might want to use actions.run_and_verify_svn()."""
532 return run_command(svn_binary, error_expected, 0,
533 *(_with_auth(_with_config_dir(varargs))))
535 # For running svnadmin. Ignores the output.
536 def run_svnadmin(*varargs):
537 """Run svnadmin with VARARGS, returns exit code as int; stdout, stderr as
538 list of lines."""
539 return run_command(svnadmin_binary, 1, 0, *varargs)
541 # For running svnlook. Ignores the output.
542 def run_svnlook(*varargs):
543 """Run svnlook with VARARGS, returns exit code as int; stdout, stderr as
544 list of lines."""
545 return run_command(svnlook_binary, 1, 0, *varargs)
547 def run_svnsync(*varargs):
548 """Run svnsync with VARARGS, returns exit code as int; stdout, stderr as
549 list of lines."""
550 return run_command(svnsync_binary, 1, 0, *(_with_config_dir(varargs)))
552 def run_svnversion(*varargs):
553 """Run svnversion with VARARGS, returns exit code as int; stdout, stderr
554 as list of lines."""
555 return run_command(svnversion_binary, 1, 0, *varargs)
557 # Chmod recursively on a whole subtree
558 def chmod_tree(path, mode, mask):
559 def visit(arg, dirname, names):
560 mode, mask = arg
561 for name in names:
562 fullname = os.path.join(dirname, name)
563 if not os.path.islink(fullname):
564 new_mode = (os.stat(fullname)[stat.ST_MODE] & ~mask) | mode
565 os.chmod(fullname, new_mode)
566 os.path.walk(path, visit, (mode, mask))
568 # For clearing away working copies
569 def safe_rmtree(dirname, retry=0):
570 "Remove the tree at DIRNAME, making it writable first"
571 def rmtree(dirname):
572 chmod_tree(dirname, 0666, 0666)
573 shutil.rmtree(dirname)
575 if not os.path.exists(dirname):
576 return
578 if retry:
579 for delay in (0.5, 1, 2, 4):
580 try:
581 rmtree(dirname)
582 break
583 except:
584 time.sleep(delay)
585 else:
586 rmtree(dirname)
587 else:
588 rmtree(dirname)
590 # For making local mods to files
591 def file_append(path, new_text):
592 "Append NEW_TEXT to file at PATH"
593 file_write(path, new_text, 'a') # open in (a)ppend mode
595 # Append in binary mode
596 def file_append_binary(path, new_text):
597 "Append NEW_TEXT to file at PATH in binary mode"
598 file_write(path, new_text, 'ab') # open in (a)ppend mode
600 # For creating new files, and making local mods to existing files.
601 def file_write(path, contents, mode = 'w'):
602 """Write the CONTENTS to the file at PATH, opening file using MODE,
603 which is (w)rite by default."""
604 fp = open(path, mode)
605 fp.write(contents)
606 fp.close()
608 # For reading the contents of a file
609 def file_read(path, mode = 'r'):
610 """Return the contents of the file at PATH, opening file using MODE,
611 which is (r)ead by default."""
612 fp = open(path, mode)
613 contents = fp.read()
614 fp.close()
615 return contents
617 # For replacing parts of contents in an existing file, with new content.
618 def file_substitute(path, contents, new_contents):
619 """Replace the CONTENTS in the file at PATH using the NEW_CONTENTS"""
620 fp = open(path, 'r')
621 fcontent = fp.read()
622 fp.close()
623 fcontent = fcontent.replace(contents, new_contents)
624 fp = open(path, 'w')
625 fp.write(fcontent)
626 fp.close()
628 # For creating blank new repositories
629 def create_repos(path):
630 """Create a brand-new SVN repository at PATH. If PATH does not yet
631 exist, create it."""
633 if not os.path.exists(path):
634 os.makedirs(path) # this creates all the intermediate dirs, if neccessary
636 opts = ("--bdb-txn-nosync",)
637 if server_minor_version < 5:
638 opts += ("--pre-1.5-compatible",)
639 if fs_type is not None:
640 opts += ("--fs-type=" + fs_type,)
641 exit_code, stdout, stderr = run_command(svnadmin_binary, 1, 0, "create",
642 path, *opts)
644 # Skip tests if we can't create the repository.
645 if stderr:
646 for line in stderr:
647 if line.find('Unknown FS type') != -1:
648 raise Skip
649 # If the FS type is known, assume the repos couldn't be created
650 # (e.g. due to a missing 'svnadmin' binary).
651 raise SVNRepositoryCreateFailure("".join(stderr).rstrip())
653 # Allow unauthenticated users to write to the repos, for ra_svn testing.
654 file_write(get_svnserve_conf_file_path(path),
655 "[general]\nauth-access = write\n");
656 if enable_sasl:
657 file_append(get_svnserve_conf_file_path(path),
658 "realm = svntest\n[sasl]\nuse-sasl = true\n")
659 else:
660 file_append(get_svnserve_conf_file_path(path), "password-db = passwd\n")
661 file_append(os.path.join(path, "conf", "passwd"),
662 "[users]\njrandom = rayjandom\njconstant = rayjandom\n");
664 if config_file is not None and (fs_type is None or fs_type == 'fsfs'):
665 shutil.copy(config_file, get_fsfs_conf_file_path(path))
667 # make the repos world-writeable, for mod_dav_svn's sake.
668 chmod_tree(path, 0666, 0666)
670 # For copying a repository
671 def copy_repos(src_path, dst_path, head_revision, ignore_uuid = 1):
672 "Copy the repository SRC_PATH, with head revision HEAD_REVISION, to DST_PATH"
674 # Do an svnadmin dump|svnadmin load cycle. Print a fake pipe command so that
675 # the displayed CMDs can be run by hand
676 create_repos(dst_path)
677 dump_args = ' dump "' + src_path + '"'
678 load_args = ' load "' + dst_path + '"'
680 if ignore_uuid:
681 load_args = load_args + " --ignore-uuid"
682 if verbose_mode:
683 sys.stdout.write('CMD: %s%s | %s%s ' % (os.path.basename(svnadmin_binary), \
684 dump_args, os.path.basename(svnadmin_binary), load_args))
685 sys.stdout.flush()
686 start = time.time()
688 dump_in, dump_out, dump_err, dump_kid = \
689 open_pipe(svnadmin_binary + dump_args, 'b')
690 dump_in.close()
691 load_in, load_out, load_err, load_kid = \
692 open_pipe(svnadmin_binary + load_args, 'b')
693 stop = time.time()
694 if verbose_mode:
695 print('<TIME = %.6f>' % (stop - start))
697 while 1:
698 data = dump_out.read(1024*1024) # Arbitrary buffer size
699 if data == "":
700 break
701 load_in.write(data)
702 load_in.close() # Tell load we are done
704 dump_lines = dump_err.readlines()
705 load_lines = load_out.readlines()
706 dump_out.close()
707 dump_err.close()
708 load_out.close()
709 load_err.close()
710 # Wait on the pipes; ignore return code.
711 wait_on_pipe(dump_kid, None, dump_lines)
712 wait_on_pipe(load_kid, load_lines, None)
714 dump_re = re.compile(r'^\* Dumped revision (\d+)\.\r?$')
715 expect_revision = 0
716 for dump_line in dump_lines:
717 match = dump_re.match(dump_line)
718 if not match or match.group(1) != str(expect_revision):
719 sys.stdout.write('ERROR: dump failed: %s ' % dump_line)
720 sys.stdout.flush()
721 raise SVNRepositoryCopyFailure
722 expect_revision += 1
723 if expect_revision != head_revision + 1:
724 print('ERROR: dump failed; did not see revision %s' % head_revision)
725 raise SVNRepositoryCopyFailure
727 load_re = re.compile(r'^------- Committed revision (\d+) >>>\r?$')
728 expect_revision = 1
729 for load_line in load_lines:
730 match = load_re.match(load_line)
731 if match:
732 if match.group(1) != str(expect_revision):
733 sys.stdout.write('ERROR: load failed: %s ' % load_line)
734 sys.stdout.flush()
735 raise SVNRepositoryCopyFailure
736 expect_revision += 1
737 if expect_revision != head_revision + 1:
738 print('ERROR: load failed; did not see revision %s' % head_revision)
739 raise SVNRepositoryCopyFailure
742 def canonicalize_url(input):
743 "Canonicalize the url, if the scheme is unknown, returns intact input"
745 m = re.match(r"^((file://)|((svn|svn\+ssh|http|https)(://)))", input)
746 if m:
747 scheme = m.group(1)
748 return scheme + re.sub(r'//*', '/', input[len(scheme):])
749 else:
750 return input
753 def create_python_hook_script (hook_path, hook_script_code):
754 """Create a Python hook script at HOOK_PATH with the specified
755 HOOK_SCRIPT_CODE."""
757 if sys.platform == 'win32':
758 # Use an absolute path since the working directory is not guaranteed
759 hook_path = os.path.abspath(hook_path)
760 # Fill the python file.
761 file_write ("%s.py" % hook_path, hook_script_code)
762 # Fill the batch wrapper file.
763 file_append ("%s.bat" % hook_path,
764 "@\"%s\" %s.py %%*\n" % (sys.executable, hook_path))
765 else:
766 # For all other platforms
767 file_write (hook_path, "#!%s\n%s" % (sys.executable, hook_script_code))
768 os.chmod (hook_path, 0755)
770 def write_restrictive_svnserve_conf(repo_dir, anon_access="none"):
771 "Create a restrictive authz file ( no anynomous access )."
773 fp = open(get_svnserve_conf_file_path(repo_dir), 'w')
774 fp.write("[general]\nanon-access = %s\nauth-access = write\n"
775 "authz-db = authz\n" % anon_access)
776 if enable_sasl == 1:
777 fp.write("realm = svntest\n[sasl]\nuse-sasl = true\n");
778 else:
779 fp.write("password-db = passwd\n")
780 fp.close()
782 # Warning: because mod_dav_svn uses one shared authz file for all
783 # repositories, you *cannot* use write_authz_file in any test that
784 # might be run in parallel.
786 # write_authz_file can *only* be used in test suites which disable
787 # parallel execution at the bottom like so
788 # if __name__ == '__main__':
789 # svntest.main.run_tests(test_list, serial_only = True)
790 def write_authz_file(sbox, rules, sections=None):
791 """Write an authz file to SBOX, appropriate for the RA method used,
792 with authorizations rules RULES mapping paths to strings containing
793 the rules. You can add sections SECTIONS (ex. groups, aliases...) with
794 an appropriate list of mappings.
796 fp = open(sbox.authz_file, 'w')
798 # When the sandbox repository is read only it's name will be different from
799 # the repository name.
800 repo_name = sbox.repo_dir
801 while repo_name[-1] == '/':
802 repo_name = repo_name[:-1]
803 repo_name = os.path.basename(repo_name)
805 if sbox.repo_url.startswith("http"):
806 prefix = repo_name + ":"
807 else:
808 prefix = ""
809 if sections:
810 for p, r in sections.items():
811 fp.write("[%s]\n%s\n" % (p, r))
813 for p, r in rules.items():
814 fp.write("[%s%s]\n%s\n" % (prefix, p, r))
815 fp.close()
817 def use_editor(func):
818 os.environ['SVN_EDITOR'] = svneditor_script
819 os.environ['SVN_MERGE'] = svneditor_script
820 os.environ['SVNTEST_EDITOR_FUNC'] = func
823 def merge_notify_line(revstart=None, revend=None, same_URL=True,
824 foreign=False):
825 """Return an expected output line that describes the beginning of a
826 merge operation on revisions REVSTART through REVEND. Omit both
827 REVSTART and REVEND for the case where the left and right sides of
828 the merge are from different URLs."""
829 from_foreign_phrase = foreign and "\(from foreign repository\) " or ""
830 if not same_URL:
831 return "--- Merging differences between %srepository URLs into '.+':\n" \
832 % (foreign and "foreign " or "")
833 if revend is None:
834 if revstart is None:
835 # The left and right sides of the merge are from different URLs.
836 return "--- Merging differences between %srepository URLs into '.+':\n" \
837 % (foreign and "foreign " or "")
838 elif revstart < 0:
839 return "--- Reverse-merging %sr%ld into '.+':\n" \
840 % (from_foreign_phrase, abs(revstart))
841 else:
842 return "--- Merging %sr%ld into '.+':\n" \
843 % (from_foreign_phrase, revstart)
844 else:
845 if revstart > revend:
846 return "--- Reverse-merging %sr%ld through r%ld into '.+':\n" \
847 % (from_foreign_phrase, revstart, revend)
848 else:
849 return "--- Merging %sr%ld through r%ld into '.+':\n" \
850 % (from_foreign_phrase, revstart, revend)
853 ######################################################################
854 # Functions which check the test configuration
855 # (useful for conditional XFails)
857 def _check_command_line_parsed():
858 """Raise an exception if the command line has not yet been parsed."""
859 if not command_line_parsed:
860 raise Failure("Condition cannot be tested until command line is parsed")
862 def is_ra_type_dav():
863 _check_command_line_parsed()
864 return test_area_url.startswith('http')
866 def is_ra_type_svn():
867 _check_command_line_parsed()
868 return test_area_url.startswith('svn')
870 def is_ra_type_file():
871 _check_command_line_parsed()
872 return test_area_url.startswith('file')
874 def is_fs_type_fsfs():
875 _check_command_line_parsed()
876 # This assumes that fsfs is the default fs implementation.
877 return fs_type == 'fsfs' or fs_type is None
879 def is_os_windows():
880 return os.name == 'nt'
882 def is_posix_os():
883 return os.name == 'posix'
885 def is_os_darwin():
886 return sys.platform == 'darwin'
888 def server_has_mergeinfo():
889 _check_command_line_parsed()
890 return server_minor_version >= 5
892 def server_has_revprop_commit():
893 _check_command_line_parsed()
894 return server_minor_version >= 5
896 def server_sends_copyfrom_on_update():
897 _check_command_line_parsed()
898 return server_minor_version >= 5
900 def server_authz_has_aliases():
901 _check_command_line_parsed()
902 return server_minor_version >= 5
904 def server_gets_client_capabilities():
905 _check_command_line_parsed()
906 return server_minor_version >= 5
908 def server_has_partial_replay():
909 _check_command_line_parsed()
910 return server_minor_version >= 5
912 def server_enforces_date_syntax():
913 _check_command_line_parsed()
914 return server_minor_version >= 5
917 ######################################################################
918 # Sandbox handling
920 class Sandbox:
921 """Manages a sandbox (one or more repository/working copy pairs) for
922 a test to operate within."""
924 dependents = None
926 def __init__(self, module, idx):
927 self._set_name("%s-%d" % (module, idx))
929 def _set_name(self, name, read_only = False):
930 """A convenience method for renaming a sandbox, useful when
931 working with multiple repositories in the same unit test."""
932 if not name is None:
933 self.name = name
934 self.read_only = read_only
935 self.wc_dir = os.path.join(general_wc_dir, self.name)
936 if not read_only:
937 self.repo_dir = os.path.join(general_repo_dir, self.name)
938 self.repo_url = test_area_url + '/' + pathname2url(self.repo_dir)
939 else:
940 self.repo_dir = pristine_dir
941 self.repo_url = pristine_url
943 ### TODO: Move this into to the build() method
944 # For dav tests we need a single authz file which must be present,
945 # so we recreate it each time a sandbox is created with some default
946 # contents.
947 if self.repo_url.startswith("http"):
948 # this dir doesn't exist out of the box, so we may have to make it
949 if not os.path.exists(work_dir):
950 os.makedirs(work_dir)
951 self.authz_file = os.path.join(work_dir, "authz")
952 file_write(self.authz_file, "[/]\n* = rw\n")
954 # For svnserve tests we have a per-repository authz file, and it
955 # doesn't need to be there in order for things to work, so we don't
956 # have any default contents.
957 elif self.repo_url.startswith("svn"):
958 self.authz_file = os.path.join(self.repo_dir, "conf", "authz")
960 self.test_paths = [self.wc_dir, self.repo_dir]
962 def clone_dependent(self, copy_wc=False):
963 """A convenience method for creating a near-duplicate of this
964 sandbox, useful when working with multiple repositories in the
965 same unit test. If COPY_WC is true, make an exact copy of this
966 sandbox's working copy at the new sandbox's working copy
967 directory. Any necessary cleanup operations are triggered by
968 cleanup of the original sandbox."""
970 if not self.dependents:
971 self.dependents = []
972 clone = copy.deepcopy(self)
973 self.dependents.append(clone)
974 clone._set_name("%s-%d" % (self.name, len(self.dependents)))
975 if copy_wc:
976 self.add_test_path(clone.wc_dir)
977 shutil.copytree(self.wc_dir, clone.wc_dir, symlinks=True)
978 return clone
980 def build(self, name = None, create_wc = True, read_only = False):
981 self._set_name(name, read_only)
982 if actions.make_repo_and_wc(self, create_wc, read_only):
983 raise Failure("Could not build repository and sandbox '%s'" % self.name)
985 def add_test_path(self, path, remove=True):
986 self.test_paths.append(path)
987 if remove:
988 safe_rmtree(path)
990 def add_repo_path(self, suffix, remove=1):
991 path = os.path.join(general_repo_dir, self.name) + '.' + suffix
992 url = test_area_url + '/' + pathname2url(path)
993 self.add_test_path(path, remove)
994 return path, url
996 def add_wc_path(self, suffix, remove=1):
997 path = self.wc_dir + '.' + suffix
998 self.add_test_path(path, remove)
999 return path
1001 def cleanup_test_paths(self):
1002 "Clean up detritus from this sandbox, and any dependents."
1003 if self.dependents:
1004 # Recursively cleanup any dependent sandboxes.
1005 for sbox in self.dependents:
1006 sbox.cleanup_test_paths()
1007 # cleanup all test specific working copies and repositories
1008 for path in self.test_paths:
1009 if not path is pristine_dir:
1010 _cleanup_test_path(path)
1013 _deferred_test_paths = []
1014 def _cleanup_deferred_test_paths():
1015 global _deferred_test_paths
1016 test_paths = _deferred_test_paths[:]
1017 _deferred_test_paths = []
1018 for path in test_paths:
1019 _cleanup_test_path(path, 1)
1021 def _cleanup_test_path(path, retrying=None):
1022 if verbose_mode:
1023 if retrying:
1024 print("CLEANUP: RETRY: %s" % path)
1025 else:
1026 print("CLEANUP: %s" % path)
1027 try:
1028 safe_rmtree(path)
1029 except:
1030 if verbose_mode:
1031 print("WARNING: cleanup failed, will try again later")
1032 _deferred_test_paths.append(path)
1034 class TestSpawningThread(threading.Thread):
1035 """A thread that runs test cases in their own processes.
1036 Receives test numbers to run from the queue, and saves results into
1037 the results field."""
1038 def __init__(self, queue):
1039 threading.Thread.__init__(self)
1040 self.queue = queue
1041 self.results = []
1043 def run(self):
1044 while True:
1045 try:
1046 next_index = self.queue.get_nowait()
1047 except Queue.Empty:
1048 return
1050 self.run_one(next_index)
1052 def run_one(self, index):
1053 command = sys.argv[0]
1055 args = []
1056 args.append(str(index))
1057 args.append('-c')
1058 # add some startup arguments from this process
1059 if fs_type:
1060 args.append('--fs-type=' + fs_type)
1061 if test_area_url:
1062 args.append('--url=' + test_area_url)
1063 if verbose_mode:
1064 args.append('-v')
1065 if cleanup_mode:
1066 args.append('--cleanup')
1067 if enable_sasl:
1068 args.append('--enable-sasl')
1069 if http_library:
1070 args.append('--http-library=' + http_library)
1071 if server_minor_version:
1072 args.append('--server-minor-version=' + str(server_minor_version))
1074 result, stdout_lines, stderr_lines = spawn_process(command, 1, None, *args)
1075 # "result" will be None on platforms without Popen3 (e.g. Windows)
1076 if [x for x in stdout_lines if x.startswith('FAIL: ') or x.startswith('XPASS: ')]:
1077 result = 1
1078 self.results.append((index, result, stdout_lines, stderr_lines))
1079 sys.stdout.write('.')
1080 sys.stdout.flush()
1082 class TestRunner:
1083 """Encapsulate a single test case (predicate), including logic for
1084 runing the test and test list output."""
1086 def __init__(self, func, index):
1087 self.pred = testcase.create_test_case(func)
1088 self.index = index
1090 def list(self):
1091 print(" %2d %-5s %s" % (self.index,
1092 self.pred.list_mode(),
1093 self.pred.get_description()))
1094 self.pred.check_description()
1096 def _print_name(self):
1097 print("%s %s: %s" % (os.path.basename(sys.argv[0]), str(self.index),
1098 self.pred.get_description()))
1099 self.pred.check_description()
1101 def run(self):
1102 """Run self.pred and return the result. The return value is
1103 - 0 if the test was successful
1104 - 1 if it errored in a way that indicates test failure
1105 - 2 if the test skipped
1107 if self.pred.need_sandbox():
1108 # ooh! this function takes a sandbox argument
1109 sandbox = Sandbox(self.pred.get_sandbox_name(), self.index)
1110 kw = { 'sandbox' : sandbox }
1111 else:
1112 sandbox = None
1113 kw = {}
1115 # Explicitly set this so that commands that commit but don't supply a
1116 # log message will fail rather than invoke an editor.
1117 # Tests that want to use an editor should invoke svntest.main.use_editor.
1118 os.environ['SVN_EDITOR'] = ''
1119 os.environ['SVNTEST_EDITOR_FUNC'] = ''
1121 if use_jsvn:
1122 # Set this SVNKit specific variable to the current test (test name plus
1123 # its index) being run so that SVNKit daemon could use this test name
1124 # for its separate log file
1125 os.environ['SVN_CURRENT_TEST'] = os.path.basename(sys.argv[0]) + "_" + \
1126 str(self.index)
1128 actions.no_sleep_for_timestamps()
1130 saved_dir = os.getcwd()
1131 try:
1132 rc = self.pred.run(**kw)
1133 if rc is not None:
1134 sys.stdout.write('STYLE ERROR in ')
1135 sys.stdout.flush()
1136 self._print_name()
1137 print('Test driver returned a status code.')
1138 sys.exit(255)
1139 result = 0
1140 except Skip, ex:
1141 result = 2
1142 except Failure, ex:
1143 result = 1
1144 # We captured Failure and its subclasses. We don't want to print
1145 # anything for plain old Failure since that just indicates test
1146 # failure, rather than relevant information. However, if there
1147 # *is* information in the exception's arguments, then print it.
1148 if ex.__class__ != Failure or ex.args:
1149 ex_args = str(ex)
1150 if ex_args:
1151 print('EXCEPTION: %s: %s' % (ex.__class__.__name__, ex_args))
1152 else:
1153 print('EXCEPTION: %s' % ex.__class__.__name__)
1154 traceback.print_exc(file=sys.stdout)
1155 except KeyboardInterrupt:
1156 print('Interrupted')
1157 sys.exit(0)
1158 except SystemExit, ex:
1159 print('EXCEPTION: SystemExit(%d), skipping cleanup' % ex.code)
1160 sys.stdout.write(ex.code and 'FAIL: ' or 'PASS: ')
1161 sys.stdout.flush()
1162 self._print_name()
1163 raise
1164 except:
1165 result = 1
1166 print('UNEXPECTED EXCEPTION:')
1167 traceback.print_exc(file=sys.stdout)
1169 os.chdir(saved_dir)
1170 result = self.pred.convert_result(result)
1171 (result_text, result_benignity) = self.pred.run_text(result)
1172 if not (quiet_mode and result_benignity):
1173 sys.stdout.write("%s " % result_text)
1174 sys.stdout.flush()
1175 self._print_name()
1176 sys.stdout.flush()
1177 if sandbox is not None and result != 1 and cleanup_mode:
1178 sandbox.cleanup_test_paths()
1179 return result
1181 ######################################################################
1182 # Main testing functions
1184 # These two functions each take a TEST_LIST as input. The TEST_LIST
1185 # should be a list of test functions; each test function should take
1186 # no arguments and return a 0 on success, non-zero on failure.
1187 # Ideally, each test should also have a short, one-line docstring (so
1188 # it can be displayed by the 'list' command.)
1190 # Func to run one test in the list.
1191 def run_one_test(n, test_list, parallel = 0, finished_tests = None):
1192 """Run the Nth client test in TEST_LIST, return the result.
1194 If we're running the tests in parallel spawn the test in a new process.
1197 if (n < 1) or (n > len(test_list) - 1):
1198 print("There is no test %s.\n" % n)
1199 return 1
1201 # Run the test.
1202 if parallel:
1203 st = SpawnTest(n, finished_tests)
1204 st.start()
1205 return 0
1206 else:
1207 exit_code = TestRunner(test_list[n], n).run()
1208 return exit_code
1210 def _internal_run_tests(test_list, testnums, parallel):
1211 """Run the tests from TEST_LIST whose indices are listed in TESTNUMS.
1213 If we're running the tests in parallel spawn as much parallel processes
1214 as requested and gather the results in a temp. buffer when a child
1215 process is finished.
1218 exit_code = 0
1219 finished_tests = []
1220 tests_started = 0
1222 if not parallel:
1223 for testnum in testnums:
1224 if run_one_test(testnum, test_list) == 1:
1225 exit_code = 1
1226 else:
1227 number_queue = Queue.Queue()
1228 for num in testnums:
1229 number_queue.put(num)
1231 threads = [ TestSpawningThread(number_queue) for i in range(parallel) ]
1232 for t in threads:
1233 t.start()
1235 for t in threads:
1236 t.join()
1238 # list of (index, result, stdout, stderr)
1239 results = []
1240 for t in threads:
1241 results += t.results
1242 results.sort()
1244 # terminate the line of dots
1245 print("")
1247 # all tests are finished, find out the result and print the logs.
1248 for (index, result, stdout_lines, stderr_lines) in results:
1249 if stdout_lines:
1250 for line in stdout_lines:
1251 sys.stdout.write(line)
1252 if stderr_lines:
1253 for line in stderr_lines:
1254 sys.stdout.write(line)
1255 if result == 1:
1256 exit_code = 1
1258 _cleanup_deferred_test_paths()
1259 return exit_code
1262 def usage():
1263 prog_name = os.path.basename(sys.argv[0])
1264 print("%s [--url] [--fs-type] [--verbose|--quiet] [--parallel] \\" %
1265 prog_name)
1266 print("%s [--enable-sasl] [--cleanup] [--bin] [<test> ...]"
1267 % (" " * len(prog_name)))
1268 print("%s " % (" " * len(prog_name)))
1269 print("%s [--list] [<test> ...]\n" % prog_name)
1270 print("Arguments:")
1271 print(" <test> The number of the test to run, or a range of test\n"
1272 " numbers, like 10:12 or 10-12. Multiple numbers and\n"
1273 " ranges are ok. If you supply none, all tests are run.\n")
1274 print("Options:")
1275 print(" --list Print test doc strings instead of running them")
1276 print(" --fs-type Subversion file system type (fsfs or bdb)")
1277 print(" --http-library DAV library to use (neon or serf)")
1278 print(" --url Base url to the repos (e.g. svn://localhost)")
1279 print(" --verbose Print binary command-lines (not with --quiet)")
1280 print(" --quiet Print only unexpected results (not with --verbose)")
1281 print(" --cleanup Whether to clean up")
1282 print(" --enable-sasl Whether to enable SASL authentication")
1283 print(" --parallel Run the tests in parallel")
1284 print(" --bin Use the svn binaries installed in this path")
1285 print(" --use-jsvn Use the jsvn (SVNKit based) binaries. Can be\n"
1286 " combined with --bin to point to a specific path")
1287 print(" --development Test development mode: provides more detailed test\n"
1288 " output and ignores all exceptions in the \n"
1289 " run_and_verify* functions. This option is only \n"
1290 " useful during test development!")
1291 print(" --server-minor-version Set the minor version for the server.\n"
1292 " Supports version 4 or 5.")
1293 print(" --config-file Configuration file for tests.")
1294 print(" --help This information")
1297 # Main func. This is the "entry point" that all the test scripts call
1298 # to run their list of tests.
1300 # This routine parses sys.argv to decide what to do.
1301 def run_tests(test_list, serial_only = False):
1302 """Main routine to run all tests in TEST_LIST.
1304 NOTE: this function does not return. It does a sys.exit() with the
1305 appropriate exit code.
1308 global test_area_url
1309 global pristine_url
1310 global fs_type
1311 global verbose_mode
1312 global quiet_mode
1313 global cleanup_mode
1314 global enable_sasl
1315 global is_child_process
1316 global svn_binary
1317 global svnadmin_binary
1318 global svnlook_binary
1319 global svnsync_binary
1320 global svndumpfilter_binary
1321 global svnversion_binary
1322 global command_line_parsed
1323 global http_library
1324 global config_file
1325 global server_minor_version
1326 global use_jsvn
1328 testnums = []
1329 # Should the tests be listed (as opposed to executed)?
1330 list_tests = False
1332 parallel = 0
1333 svn_bin = None
1334 use_jsvn = False
1335 config_file = None
1337 try:
1338 opts, args = my_getopt(sys.argv[1:], 'vqhpc',
1339 ['url=', 'fs-type=', 'verbose', 'quiet', 'cleanup',
1340 'list', 'enable-sasl', 'help', 'parallel',
1341 'bin=', 'http-library=', 'server-minor-version=',
1342 'use-jsvn', 'development', 'config-file='])
1343 except getopt.GetoptError, e:
1344 print("ERROR: %s\n" % e)
1345 usage()
1346 sys.exit(1)
1348 for arg in args:
1349 if arg == "list":
1350 # This is an old deprecated variant of the "--list" option:
1351 list_tests = True
1352 elif arg.startswith('BASE_URL='):
1353 test_area_url = arg[9:]
1354 else:
1355 appended = False
1356 try:
1357 testnums.append(int(arg))
1358 appended = True
1359 except ValueError:
1360 # Do nothing for now.
1361 appended = False
1363 if not appended:
1364 try:
1365 # Check if the argument is a range
1366 numberstrings = arg.split(':');
1367 if len(numberstrings) != 2:
1368 numberstrings = arg.split('-');
1369 if len(numberstrings) != 2:
1370 raise ValueError
1371 left = int(numberstrings[0])
1372 right = int(numberstrings[1])
1373 if left > right:
1374 raise ValueError
1376 for nr in range(left,right+1):
1377 testnums.append(nr)
1378 else:
1379 appended = True
1380 except ValueError:
1381 appended = False
1383 if not appended:
1384 print("ERROR: invalid test number or range '%s'\n" % arg)
1385 usage()
1386 sys.exit(1)
1388 for opt, val in opts:
1389 if opt == "--url":
1390 test_area_url = val
1392 elif opt == "--fs-type":
1393 fs_type = val
1395 elif opt == "-v" or opt == "--verbose":
1396 verbose_mode = True
1398 elif opt == "-q" or opt == "--quiet":
1399 quiet_mode = True
1401 elif opt == "--cleanup":
1402 cleanup_mode = True
1404 elif opt == "--list":
1405 list_tests = True
1407 elif opt == "--enable-sasl":
1408 enable_sasl = True
1410 elif opt == "-h" or opt == "--help":
1411 usage()
1412 sys.exit(0)
1414 elif opt == '-p' or opt == "--parallel":
1415 parallel = 5 # use 5 parallel threads.
1417 elif opt == '-c':
1418 is_child_process = True
1420 elif opt == '--bin':
1421 svn_bin = val
1423 elif opt == '--http-library':
1424 http_library = val
1426 elif opt == '--server-minor-version':
1427 server_minor_version = int(val)
1428 if server_minor_version < 4 or server_minor_version > 6:
1429 print("ERROR: test harness only supports server minor version 4 or 5")
1430 sys.exit(1)
1432 elif opt == '--use-jsvn':
1433 use_jsvn = True
1435 elif opt == '--development':
1436 setup_development_mode()
1438 elif opt == '--config-file':
1439 config_file = val
1441 if test_area_url[-1:] == '/': # Normalize url to have no trailing slash
1442 test_area_url = test_area_url[:-1]
1444 if verbose_mode and quiet_mode:
1445 sys.stderr.write("ERROR: 'verbose' and 'quiet' are incompatible\n")
1446 sys.exit(1)
1448 # Calculate pristine_url from test_area_url.
1449 pristine_url = test_area_url + '/' + pathname2url(pristine_dir)
1451 if use_jsvn:
1452 if svn_bin is None:
1453 svn_bin = ''
1454 svn_binary = os.path.join(svn_bin, 'jsvn' + _bat)
1455 svnadmin_binary = os.path.join(svn_bin, 'jsvnadmin' + _bat)
1456 svnlook_binary = os.path.join(svn_bin, 'jsvnlook' + _bat)
1457 svnsync_binary = os.path.join(svn_bin, 'jsvnsync' + _bat)
1458 svndumpfilter_binary = os.path.join(svn_bin, 'jsvndumpfilter' + _bat)
1459 svnversion_binary = os.path.join(svn_bin, 'jsvnversion' + _bat)
1460 else:
1461 if svn_bin:
1462 svn_binary = os.path.join(svn_bin, 'svn' + _exe)
1463 svnadmin_binary = os.path.join(svn_bin, 'svnadmin' + _exe)
1464 svnlook_binary = os.path.join(svn_bin, 'svnlook' + _exe)
1465 svnsync_binary = os.path.join(svn_bin, 'svnsync' + _exe)
1466 svndumpfilter_binary = os.path.join(svn_bin, 'svndumpfilter' + _exe)
1467 svnversion_binary = os.path.join(svn_bin, 'svnversion' + _exe)
1469 command_line_parsed = True
1471 ######################################################################
1473 # Cleanup: if a previous run crashed or interrupted the python
1474 # interpreter, then `temp_dir' was never removed. This can cause wonkiness.
1475 if not is_child_process:
1476 safe_rmtree(temp_dir, 1)
1478 if not testnums:
1479 # If no test numbers were listed explicitly, include all of them:
1480 testnums = list(range(1, len(test_list)))
1482 if list_tests:
1483 print("Test # Mode Test Description")
1484 print("------ ----- ----------------")
1485 for testnum in testnums:
1486 TestRunner(test_list[testnum], testnum).list()
1488 # done. just exit with success.
1489 sys.exit(0)
1491 # don't run tests in parallel when the tests don't support it or there
1492 # are only a few tests to run.
1493 if serial_only or len(testnums) < 2:
1494 parallel = 0
1496 # Build out the default configuration directory
1497 create_config_dir(default_config_dir)
1499 # Setup the pristine repository
1500 actions.setup_pristine_repository()
1502 # Run the tests.
1503 exit_code = _internal_run_tests(test_list, testnums, parallel)
1505 # Remove all scratchwork: the 'pristine' repository, greek tree, etc.
1506 # This ensures that an 'import' will happen the next time we run.
1507 if not is_child_process:
1508 safe_rmtree(temp_dir, 1)
1510 # Cleanup after ourselves.
1511 _cleanup_deferred_test_paths()
1513 # Return the appropriate exit code from the tests.
1514 sys.exit(exit_code)
1516 # the modules import each other, so we do this import very late, to ensure
1517 # that the definitions in "main" have been completed.
1518 import actions
1521 ### End of file.