[[[ In cmdline tests, use subprocess.Popen for executing commands to allow support for exit-code checks on both Windows and posix systems. This makes the test suite require Python version >= 2.4. * subversion/tests/cmdline/svntest/main.py (global): Import subprocess instead of popen2. Remove variable "platform_with_popen3_class". (_quote_arg): Removed, since commands + args to open_pipe are now lists. (open_pipe): Replace "mode" character parameter with a "binary_mode" boolean value. Use subprocess.Popen for spawning the child process. (wait_on_pipe): Interpret the return value of wait() according to subprocess.Popen semantics. (spawn_process, copy_repos): When calling open_pipe, pass the command as a list and binary_mode as a boolean. (run_svn2): New, like run_svn but with a "binary_mode" boolean param. (TestSpawningThread.run_one): Remove the caveat comment stating that result (exit-code) is None on Windows. * subversion/tests/cmdline/svntest/actions.py (run_and_verify_svnlook, run_and_verify_svnlook2, run_and_verify_svnadmin, run_and_verify_svnadmin2, run_and_verify_svnversion, run_and_verify_svnversion2, run_and_verify_svn_match_any, run_and_verify_svn_match_any2): Remove the caveat comment stating that exit-code checks are skipped for some platforms. (run_and_verify_svn): Remove the caveat comment stating that exit-code checks are skipped for some platforms. Pass binary_mode=0 to run_and_verify_svn2. (run_and_verify_svn2): Remove the caveat comment stating that exit-code checks are skipped for some platforms. Add a binary_mode boolean parameter. Replace call to run_svn with run_svn2. * subversion/tests/cmdline/import_tests.py (import_eol_style): Call run_and_verify_svn2 with binary_mode=1. * subversion/tests/cmdline/cat_tests.py, subversion/tests/cmdline/lock_tests.py, subversion/tests/cmdline/stat_tests.py: Pass binary_mode parameter to run_and_verify_svn2. ]]] Index: subversion/tests/cmdline/cat_tests.py =================================================================== --- subversion/tests/cmdline/cat_tests.py (revision 30570) +++ subversion/tests/cmdline/cat_tests.py (working copy) @@ -45,7 +45,7 @@ svntest.actions.run_and_verify_svn2('No error where one is expected', None, svntest.verify.AnyOutput, - 0, 'cat', A_path) + 0, 0, 'cat', A_path) def cat_remote_directory(sbox): "cat a remote directory" @@ -54,7 +54,7 @@ A_url = sbox.repo_url + '/A' svntest.actions.run_and_verify_svn2('No error where one is expected', None, svntest.verify.AnyOutput, - 0, 'cat', A_url) + 0, 0, 'cat', A_url) def cat_base(sbox): "cat a file at revision BASE" @@ -82,7 +82,7 @@ bogus_path = os.path.join(wc_dir, 'A', 'bogus') svntest.actions.run_and_verify_svn2('No error where one is expected', None, svntest.verify.AnyOutput, - 0, 'cat', bogus_path) + 0, 0, 'cat', bogus_path) def cat_skip_uncattable(sbox): "cat should skip uncattable resources" @@ -107,12 +107,12 @@ if item_to_cat == new_file_path: expected_err = ["svn: warning: '" + item_to_cat + "'" + \ " is not under version control\n"] - svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, + svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, 0, 'cat', item_to_cat) elif os.path.isdir(item_to_cat): expected_err = ["svn: warning: '" + item_to_cat + "'" + \ " refers to a directory\n"] - svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, + svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, 0, 'cat', item_to_cat) else: svntest.actions.run_and_verify_svn(None, @@ -125,16 +125,16 @@ expected_out = ["This is the file 'rho'.\n"] expected_err1 = ["svn: warning: '" + G_path + "'" + " refers to a directory\n"] - svntest.actions.run_and_verify_svn2(None, expected_out, expected_err1, 0, + svntest.actions.run_and_verify_svn2(None, expected_out, expected_err1, 0, 0, 'cat', rho_path, G_path) expected_err2 = ["svn: warning: '" + new_file_path + "'" + " is not under version control\n"] - svntest.actions.run_and_verify_svn2(None, expected_out, expected_err2, 0, + svntest.actions.run_and_verify_svn2(None, expected_out, expected_err2, 0, 0, 'cat', rho_path, new_file_path) svntest.actions.run_and_verify_svn2(None, expected_out, - expected_err1 + expected_err2, 0, + expected_err1 + expected_err2, 0, 0, 'cat', rho_path, G_path, new_file_path) Index: subversion/tests/cmdline/import_tests.py =================================================================== --- subversion/tests/cmdline/import_tests.py (revision 30570) +++ subversion/tests/cmdline/import_tests.py (working copy) @@ -346,10 +346,10 @@ "+Extra line" + crlf ] - svntest.actions.run_and_verify_svn(None, expected_output, [], - 'diff', - file_path, - '--config-dir', config_dir) + svntest.actions.run_and_verify_svn2(None, expected_output, [], 0, 1, + 'diff', + file_path, + '--config-dir', config_dir) #---------------------------------------------------------------------- ######################################################################## Index: subversion/tests/cmdline/lock_tests.py =================================================================== --- subversion/tests/cmdline/lock_tests.py (revision 30570) +++ subversion/tests/cmdline/lock_tests.py (working copy) @@ -249,7 +249,7 @@ # This should give a "iota' is already locked... error, but exits 0. svntest.actions.run_and_verify_svn2(None, None, - ".*already locked", 0, + ".*already locked", 0, 0, 'lock', '-m', 'trying to break', file_path_b) @@ -706,7 +706,7 @@ # --- Meanwhile, in our other working copy... --- svntest.actions.run_and_verify_svn2(None, None, ".*newer version of '/iota' exists", 0, - 'lock', + 0, 'lock', '--username', svntest.main.wc_author2, '-m', '', file_path_b) @@ -940,11 +940,11 @@ gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma') expected_err = ".*svn: warning: To turn off the svn:needs-lock property,.*" - svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, + svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, 0, 'ps', 'svn:needs-lock', ' ', gamma_path) expected_err = ".*svn: warning: To turn off the svn:executable property,.*" - svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, + svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, 0, 'ps', 'svn:executable', ' ', gamma_path) # commit @@ -1016,11 +1016,11 @@ gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma') expected_err = ".*svn: warning: To turn off the svn:needs-lock property,.*" - svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, + svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, 0, 'ps', 'svn:needs-lock', ' ', gamma_path) expected_err = ".*svn: warning: To turn off the svn:executable property,.*" - svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, + svntest.actions.run_and_verify_svn2(None, None, expected_err, 0, 0, 'ps', 'svn:executable', ' ', gamma_path) # commit @@ -1187,7 +1187,7 @@ error_msg = ".*Path '/A/B/E/alpha' is already locked by user '" + \ svntest.main.wc_author2 + "'.*" - svntest.actions.run_and_verify_svn2(None, None, error_msg, 0, + svntest.actions.run_and_verify_svn2(None, None, error_msg, 0, 0, 'lock', '--username', svntest.main.wc_author2, alpha_path, gamma_path) @@ -1203,7 +1203,7 @@ error_msg = "(.*No lock on path '/A/B/lambda'.*)" + \ "|(.*'A/B/lambda' is not locked.*)" - svntest.actions.run_and_verify_svn2(None, None, error_msg, 0, + svntest.actions.run_and_verify_svn2(None, None, error_msg, 0, 0, 'unlock', '--username', svntest.main.wc_author2, '--force', @@ -1345,7 +1345,7 @@ # ### The error message returned is actually this, but let's worry about that # ### another day... svntest.actions.run_and_verify_svn2( - None, None, ".*((No lock on path)|(400 Bad Request))", 0, + None, None, ".*((No lock on path)|(400 Bad Request))", 0, 0, 'unlock', file_path) #---------------------------------------------------------------------- @@ -1404,7 +1404,7 @@ else: expected_err = "svn: warning: User '%s' is trying to use a lock owned by "\ "'%s'.*" % (svntest.main.wc_author2, svntest.main.wc_author) - svntest.actions.run_and_verify_svn2(None, [], expected_err, 0, + svntest.actions.run_and_verify_svn2(None, [], expected_err, 0, 0, 'unlock', '--username', svntest.main.wc_author2, pi_path) Index: subversion/tests/cmdline/stat_tests.py =================================================================== --- subversion/tests/cmdline/stat_tests.py (revision 30570) +++ subversion/tests/cmdline/stat_tests.py (working copy) @@ -855,7 +855,7 @@ dir = sbox.repo_dir expected_err = ["svn: warning: '" + dir + "' is not a working copy\n", "svn: warning: '" + dir + "' is not a working copy\n"] - svntest.actions.run_and_verify_svn2(None, [], expected_err, 0, + svntest.actions.run_and_verify_svn2(None, [], expected_err, 0, 0, "status", dir, dir) #---------------------------------------------------------------------- Index: subversion/tests/cmdline/svntest/actions.py =================================================================== --- subversion/tests/cmdline/svntest/actions.py (revision 30570) +++ subversion/tests/cmdline/svntest/actions.py (working copy) @@ -121,10 +121,8 @@ def run_and_verify_svnlook(message, expected_stdout, expected_stderr, *varargs): """Like run_and_verify_svnlook2, but the expected exit code is - assumed to be 0 if no output is expected on stderr, and 1 otherwise. + assumed to be 0 if no output is expected on stderr, and 1 otherwise.""" - Exit code is not checked on platforms without Popen3 - see note in - run_and_verify_svn2.""" expected_exit = 0 if expected_stderr is not None and expected_stderr != []: expected_exit = 1 @@ -133,10 +131,8 @@ def run_and_verify_svnlook2(message, expected_stdout, expected_stderr, expected_exit, *varargs): - """Run svnlook command and check its output and exit code. + """Run svnlook command and check its output and exit code.""" - Exit code is not checked on platforms without Popen3 - see note in - run_and_verify_svn2.""" exit_code, out, err = main.run_svnlook(*varargs) verify.verify_outputs("Unexpected output", out, err, expected_stdout, expected_stderr) @@ -147,10 +143,8 @@ def run_and_verify_svnadmin(message, expected_stdout, expected_stderr, *varargs): """Like run_and_verify_svnadmin2, but the expected exit code is - assumed to be 0 if no output is expected on stderr, and 1 otherwise. + assumed to be 0 if no output is expected on stderr, and 1 otherwise.""" - Exit code is not checked on platforms without Popen3 - see note in - run_and_verify_svn2.""" expected_exit = 0 if expected_stderr is not None and expected_stderr != []: expected_exit = 1 @@ -159,10 +153,8 @@ def run_and_verify_svnadmin2(message, expected_stdout, expected_stderr, expected_exit, *varargs): - """Run svnadmin command and check its output and exit code. + """Run svnadmin command and check its output and exit code.""" - Exit code is not checked on platforms without Popen3 - see note in - run_and_verify_svn2.""" exit_code, out, err = main.run_svnadmin(*varargs) verify.verify_outputs("Unexpected output", out, err, expected_stdout, expected_stderr) @@ -173,10 +165,8 @@ def run_and_verify_svnversion(message, wc_dir, repo_url, expected_stdout, expected_stderr): """like run_and_verify_svnversion2, but the expected exit code is - assumed to be 0 if no output is expected on stderr, and 1 otherwise. + assumed to be 0 if no output is expected on stderr, and 1 otherwise.""" - Exit code is not checked on platforms without Popen3 - see note in - run_and_verify_svn2.""" expected_exit = 0 if expected_stderr is not None and expected_stderr != []: expected_exit = 1 @@ -187,10 +177,8 @@ def run_and_verify_svnversion2(message, wc_dir, repo_url, expected_stdout, expected_stderr, expected_exit): - """Run svnversion command and check its output and exit code. + """Run svnversion command and check its output and exit code.""" - Exit code is not checked on platforms without Popen3 - see note in - run_and_verify_svn2.""" exit_code, out, err = main.run_svnversion(wc_dir, repo_url) verify.verify_outputs("Unexpected output", out, err, expected_stdout, expected_stderr) @@ -198,20 +186,17 @@ return exit_code, out, err def run_and_verify_svn(message, expected_stdout, expected_stderr, *varargs): - """like run_and_verify_svn2, but the expected exit code is assumed to - be 0 if no output is expected on stderr, and 1 otherwise. + """like run_and_verify_svn2 in text mode, but the expected exit code is + assumed to be 0 if no output is expected on stderr, and 1 otherwise.""" - Exit code is not checked on platforms without Popen3 - see note in - run_and_verify_svn2.""" - expected_exit = 0 if expected_stderr is not None and expected_stderr != []: expected_exit = 1 return run_and_verify_svn2(message, expected_stdout, expected_stderr, - expected_exit, *varargs) + expected_exit, 0, *varargs) def run_and_verify_svn2(message, expected_stdout, expected_stderr, - expected_exit, *varargs): + expected_exit, binary_mode, *varargs): """Invokes main.run_svn() with *VARARGS, returns exit code as int, stdout and stderr as lists of lines. For both EXPECTED_STDOUT and EXPECTED_STDERR, create an appropriate instance of verify.ExpectedOutput (if necessary): @@ -227,13 +212,12 @@ on the name of the stream being compared (e.g. STDOUT), the ExpectedOutput instance, and the actual output. + If BINARY_MODE is True, the linebreak style of svn output will be preserved. + If EXPECTED_STDOUT is None, do not check stdout. EXPECTED_STDERR may not be None. - If output checks pass, on supported platforms (namely those with the Popen3 - class), the expected and actual codes are compared. On platforms lacking - Popen3, the actual exit code is unavailable and a value of None is returned - as the exit code from this and all other run_...() functions. + If output checks pass, the expected and actual codes are compared. If a comparison fails, a Failure will be raised.""" @@ -244,7 +228,7 @@ if expected_stderr is not None and expected_stderr != []: want_err = True - exit_code, out, err = main.run_svn(want_err, *varargs) + exit_code, out, err = main.run_svn2(want_err, binary_mode, *varargs) verify.verify_outputs(message, out, err, expected_stdout, expected_stderr) verify.verify_exit_code(message, exit_code, expected_exit) return exit_code, out, err @@ -252,10 +236,8 @@ def run_and_verify_svn_match_any(message, expected_stdout, expected_stderr, *varargs): """Like run_and_verify_svn_match_any2, but the expected exit code is - assumed to be 0 if no output is expected on stderr, and 1 otherwise. + assumed to be 0 if no output is expected on stderr, and 1 otherwise.""" - Exit code is not checked on platforms without Popen3 - see note in - run_and_verify_svn2.""" expected_exit = 0 if expected_stderr is not None and expected_stderr != []: expected_exit = 1 @@ -267,11 +249,8 @@ def run_and_verify_svn_match_any2(message, expected_stdout, expected_stderr, expected_exit, *varargs): """Like run_and_verify_svn2, except that only one stdout line must match - EXPECTED_STDOUT. + EXPECTED_STDOUT.""" - Exit code is not checked on platforms without Popen3 - see note in - run_and_verify_svn2.""" - if expected_stderr is None: raise verify.SVNIncorrectDatatype("expected_stderr must not be None") Index: subversion/tests/cmdline/svntest/main.py =================================================================== --- subversion/tests/cmdline/svntest/main.py (revision 30570) +++ subversion/tests/cmdline/svntest/main.py (working copy) @@ -25,6 +25,7 @@ import traceback # for print_exc() import threading import Queue +from subprocess import Popen, PIPE import getopt try: @@ -96,12 +97,6 @@ _exe = '' _bat = '' -try: - from popen2 import Popen3 - platform_with_popen3_class = True -except ImportError: - platform_with_popen3_class = False - # The location of our mock svneditor script. if sys.platform == 'win32': svneditor_script = os.path.join(sys.path[0], 'svneditor.bat') @@ -318,39 +313,22 @@ # system: _safe_arg_re = re.compile(r'^[A-Za-z\d\.\_\/\-\:\@]+$') -def _quote_arg(arg): - """Quote ARG for a command line. +def open_pipe(command, binary_mode): + """Opens a subcommand pipe to COMMAND. If BINARY_MODE is true, linebreak + style will be preserved in output. - Simply surround every argument in double-quotes unless it contains - only universally harmless characters. - - WARNING: This function cannot handle arbitrary command-line - arguments. It can easily be confused by shell metacharacters. A - perfect job would be difficult and OS-dependent (see, for example, - http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp). - In other words, this function is just good enough for what we need - here.""" - - arg = str(arg) - if _safe_arg_re.match(arg): - return arg - else: - if os.name != 'nt': - arg = arg.replace('$', '\$') - return '"%s"' % (arg,) - -def open_pipe(command, mode): - """Opens a popen3 pipe to COMMAND in MODE. - Returns (infile, outfile, errfile, waiter); waiter should be passed to wait_on_pipe.""" - if platform_with_popen3_class: - kid = Popen3(command, True) - return kid.tochild, kid.fromchild, kid.childerr, (kid, command) - else: - inf, outf, errf = os.popen3(command, mode) - return inf, outf, errf, None + close_fds=False + if is_posix_os(): + close_fds=True + # (use universal newlines for text mode) + kid = Popen(command, shell=False, bufsize=-1, + universal_newlines=(not binary_mode), + stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=close_fds) + return kid.stdin, kid.stdout, kid.stderr, (kid, command) + def wait_on_pipe(waiter, stdout_lines, stderr_lines): """Waits for KID (opened with open_pipe) to finish, dying if it does. Uses STDOUT_LINES and STDERR_LINES for error message @@ -360,9 +338,9 @@ kid, command = waiter - wait_code = kid.wait() + exit_code = kid.wait() - if os.WIFSIGNALED(wait_code): + if exit_code < 0: exit_signal = os.WTERMSIG(wait_code) if stdout_lines is not None: sys.stdout.write("".join(stdout_lines)) @@ -374,26 +352,21 @@ % (command, exit_signal)) raise SVNProcessTerminatedBySignal else: - exit_code = os.WEXITSTATUS(wait_code) if exit_code and verbose_mode: - sys.stderr.write("CMD: %s exited with %d\n" % (command, exit_code)) + sys.stderr.write("CMD: %s exited with %d\n" % + (' '.join(command), exit_code)) return exit_code # Run any binary, supplying input text, logging the command line -def spawn_process(command, binary_mode=0,stdin_lines=None, *varargs): - args = ' '.join(map(_quote_arg, varargs)) +def spawn_process(command, binary_mode=0, stdin_lines=None, *varargs): + cmd_argv = [command] + map(str, varargs) # Log the command line if verbose_mode and not command.endswith('.py'): - print 'CMD:', os.path.basename(command) + ' ' + args, + print 'CMD:', os.path.basename(command) + ' ' + ' '.join(cmd_argv[1:]), - if binary_mode: - mode = 'b' - else: - mode = 't' + infile, outfile, errfile, kid = open_pipe(cmd_argv, binary_mode) - infile, outfile, errfile, kid = open_pipe(command + ' ' + args, mode) - if stdin_lines: map(infile.write, stdin_lines) @@ -495,6 +468,12 @@ return run_command(svn_binary, error_expected, 0, *(_with_auth(_with_config_dir(varargs)))) +def run_svn2(error_expected, binary_mode, *varargs): + """Line run_svn, but allows user-specified enabling of binary mode for + output capture.""" + return run_command(svn_binary, error_expected, binary_mode, + *(_with_auth(_with_config_dir(varargs)))) + # For running svnadmin. Ignores the output. def run_svnadmin(*varargs): """Run svnadmin with VARARGS, returns exit code as int; stdout, stderr as @@ -633,20 +612,21 @@ # Do an svnadmin dump|svnadmin load cycle. Print a fake pipe command so that # the displayed CMDs can be run by hand create_repos(dst_path) - dump_args = ' dump "' + src_path + '"' - load_args = ' load "' + dst_path + '"' + dump_argv = [svnadmin_binary, 'dump', src_path] + load_argv = [svnadmin_binary, 'load', dst_path] if ignore_uuid: - load_args = load_args + " --ignore-uuid" + load_args = load_argv.append('--ignore-uuid') if verbose_mode: - print 'CMD:', os.path.basename(svnadmin_binary) + dump_args, \ - '|', os.path.basename(svnadmin_binary) + load_args, + print 'CMD:', os.path.basename(svnadmin_binary), \ + ' '.join(dump_argv[1:]), '|', os.path.basename(svnadmin_binary), \ + ' '.join(load_argv[1:]), + start = time.time() - dump_in, dump_out, dump_err, dump_kid = \ - open_pipe(svnadmin_binary + dump_args, 'b') - load_in, load_out, load_err, load_kid = \ - open_pipe(svnadmin_binary + load_args, 'b') + dump_in, dump_out, dump_err, dump_kid = open_pipe(dump_argv, True) + load_in, load_out, load_err, load_kid = open_pipe(load_argv, True) + stop = time.time() if verbose_mode: print '