You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by gb...@apache.org on 2013/08/21 18:09:52 UTC
svn commit: r1516196 [26/26] - in
/subversion/branches/invoke-diff-cmd-feature: ./ build/ build/ac-macros/
build/generator/ build/generator/swig/ build/generator/templates/
contrib/client-side/emacs/ contrib/hook-scripts/ doc/programmer/ notes/
notes/h...
Modified: subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnauthz.c
URL: http://svn.apache.org/viewvc/subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnauthz.c?rev=1516196&r1=1516195&r2=1516196&view=diff
==============================================================================
--- subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnauthz.c (original)
+++ subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnauthz.c Wed Aug 21 16:09:43 2013
@@ -59,7 +59,9 @@ static const apr_getopt_option_t options
{"repository", svnauthz__repos, 1, ("repository authz name")},
{"transaction", 't', 1, ("transaction id")},
{"is", svnauthz__is, 1,
- ("instead of outputing, tests if the access is ARG\n"
+ ("instead of outputting, test if the access is\n"
+ " "
+ "exactly ARG\n"
" "
"ARG can be one of the following values:\n"
" "
@@ -67,10 +69,12 @@ static const apr_getopt_option_t options
" "
" r read-only access\n"
" "
- " no no access\n")
+ " no no access")
},
- {"groups-file", svnauthz__groups_file, 1, ("path to the global groups file")},
- {"recursive", 'R', 0, ("recursive access to path")},
+ {"groups-file", svnauthz__groups_file, 1,
+ ("use the groups from file ARG")},
+ {"recursive", 'R', 0,
+ ("determine recursive access to PATH")},
{0, 0, 0, 0}
};
@@ -129,27 +133,32 @@ static const svn_opt_subcommand_desc2_t
{'t'} },
{"accessof", subcommand_accessof, {0} /* no aliases */,
("Print or test the permissions set by an authz file.\n"
- "usage: 1. svnauthz accessof [--username USER] [--groups-file GROUPS_FILE] TARGET\n"
- " 2. svnauthz accessof [--username USER] [--groups-file GROUPS_FILE] \\\n"
- " -t TXN REPOS_PATH FILE_PATH\n\n"
- " 1. Prints the access of USER based on TARGET.\n"
+ "usage: 1. svnauthz accessof TARGET\n"
+ " 2. svnauthz accessof -t TXN REPOS_PATH FILE_PATH\n"
+ "\n"
+ " 1. Prints the access of USER to PATH based on authorization file at TARGET.\n"
" TARGET can be a path to a file or an absolute file:// URL to an authz\n"
- " file in a repository, but cannot be a repository relative URL (^/).\n\n"
- " 2. Prints the access of USER based on authz file at FILE_PATH in the\n"
- " transaction TXN in the repository at REPOS_PATH.\n\n"
- " If the --username argument is omitted then access of an anonymous user\n"
- " will be printed. If --path argument is omitted prints if any access\n"
- " to the repo is allowed. If --groups-file is specified, the groups from\n"
- " GROUPS_FILE will be used.\n\n"
+ " file in a repository, but cannot be a repository relative URL (^/).\n"
+ "\n"
+ " 2. Prints the access of USER to PATH based on authz file at FILE_PATH in the\n"
+ " transaction TXN in the repository at REPOS_PATH.\n"
+ "\n"
+ " USER is the argument to the --username option; if that option is not\n"
+ " provided, then access of an anonymous user will be printed or tested.\n"
+ "\n"
+ " PATH is the argument to the --path option; if that option is not provided,\n"
+ " the maximal access to any path in the repository will be considered.\n"
+ "\n"
"Outputs one of the following:\n"
" rw write access (which also implies read)\n"
" r read access\n"
- " no no access\n\n"
+ " no no access\n"
+ "\n"
"Returns:\n"
- " 0 when syntax is OK and --is argument (if any) matches.\n"
+ " 0 when syntax is OK and '--is' argument (if any) matches.\n"
" 1 when syntax is invalid.\n"
" 2 operational error\n"
- " 3 when --is argument doesn't match\n"
+ " 3 when '--is' argument doesn't match\n"
),
{'t', svnauthz__username, svnauthz__path, svnauthz__repos, svnauthz__is,
svnauthz__groups_file, 'R'} },
Modified: subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/commit-hook.py
URL: http://svn.apache.org/viewvc/subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/commit-hook.py?rev=1516196&r1=1516195&r2=1516196&view=diff
==============================================================================
--- subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/commit-hook.py (original)
+++ subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/commit-hook.py Wed Aug 21 16:09:43 2013
@@ -19,8 +19,10 @@
SVNLOOK="/usr/local/svn-install/current/bin/svnlook"
#SVNLOOK="/usr/local/bin/svnlook"
+HOST="127.0.0.1"
+PORT=2069
+
import sys
-import subprocess
try:
import simplejson as json
except ImportError:
@@ -28,35 +30,32 @@ except ImportError:
import urllib2
-HOST="127.0.0.1"
-PORT=2069
-
-def svncmd(cmd):
- return subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+import svnpubsub.util
-def svncmd_uuid(repo):
- cmd = "%s uuid %s" % (SVNLOOK, repo)
- p = svncmd(cmd)
- return p.stdout.read().strip()
-
-def svncmd_info(repo, revision):
- cmd = "%s info -r %s %s" % (SVNLOOK, revision, repo)
- p = svncmd(cmd)
- data = p.stdout.read().split("\n")
+def svnlook(cmd, **kwargs):
+ args = [SVNLOOK] + cmd
+ return svnpubsub.util.check_output(args, **kwargs)
+
+def svnlook_uuid(repo):
+ cmd = ["uuid", "--", repo]
+ return svnlook(cmd).strip()
+
+def svnlook_info(repo, revision):
+ cmd = ["info", "-r", revision, "--", repo]
+ data = svnlook(cmd, universal_newlines=True).split("\n")
#print data
return {'author': data[0].strip(),
'date': data[1].strip(),
'log': "\n".join(data[3:]).strip()}
-def svncmd_changed(repo, revision):
- cmd = "%s changed -r %s %s" % (SVNLOOK, revision, repo)
- p = svncmd(cmd)
+def svnlook_changed(repo, revision):
+ cmd = ["changed", "-r", revision, "--", repo]
+ lines = svnlook(cmd, universal_newlines=True).split("\n")
changed = {}
- while True:
- line = p.stdout.readline()
- if not line:
- break
+ for line in lines:
line = line.strip()
+ if not line:
+ continue
(flags, filename) = (line[0:3], line[4:])
changed[filename] = {'flags': flags}
return changed
@@ -71,23 +70,23 @@ def do_put(body):
def main(repo, revision):
revision = revision.lstrip('r')
- i = svncmd_info(repo, revision)
+ i = svnlook_info(repo, revision)
data = {'type': 'svn',
'format': 1,
'id': int(revision),
'changed': {},
- 'repository': svncmd_uuid(repo),
+ 'repository': svnlook_uuid(repo),
'committer': i['author'],
'log': i['log'],
'date': i['date'],
}
- data['changed'].update(svncmd_changed(repo, revision))
+ data['changed'].update(svnlook_changed(repo, revision))
body = json.dumps(data)
do_put(body)
if __name__ == "__main__":
- if len(sys.argv) != 3:
- print "invalid args"
- sys.exit(0)
+ if len(sys.argv) not in (3, 4):
+ sys.stderr.write("invalid args\n")
+ sys.exit(1)
- main(sys.argv[1], sys.argv[2])
+ main(*sys.argv[1:3])
Modified: subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/daemonize.py
URL: http://svn.apache.org/viewvc/subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/daemonize.py?rev=1516196&r1=1516195&r2=1516196&view=diff
==============================================================================
--- subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/daemonize.py (original)
+++ subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/daemonize.py Wed Aug 21 16:09:43 2013
@@ -24,6 +24,7 @@ import os
import signal
import sys
import time
+import multiprocessing # requires Python 2.6
# possible return values from Daemon.daemonize()
@@ -50,11 +51,11 @@ class Daemon(object):
def daemonize_exit(self):
try:
result = self.daemonize()
- except (ChildFailed, DaemonFailed) as e:
+ except (ChildFailed, DaemonFailed), e:
# duplicate the exit code
sys.exit(e.code)
except (ChildTerminatedAbnormally, ChildForkFailed,
- DaemonTerminatedAbnormally, DaemonForkFailed) as e:
+ DaemonTerminatedAbnormally, DaemonForkFailed), e:
sys.stderr.write('ERROR: %s\n' % e)
sys.exit(1)
except ChildResumedIncorrectly:
@@ -71,29 +72,41 @@ class Daemon(object):
# in original process. daemon is up and running. we're done.
def daemonize(self):
- # fork off a child that can detach itself from this process.
- try:
- pid = os.fork()
- except OSError as e:
- raise ChildForkFailed(e.errno, e.strerror)
-
- if pid > 0:
- # we're in the parent. let's wait for the child to finish setting
- # things up -- on our exit, we want to ensure the child is accepting
- # connections.
- cpid, status = os.waitpid(pid, 0)
- assert pid == cpid
- if os.WIFEXITED(status):
- code = os.WEXITSTATUS(status)
- if code:
- raise ChildFailed(code)
- return DAEMON_RUNNING
-
- # the child did not exit cleanly.
- raise ChildTerminatedAbnormally(status)
+ ### review error situations. map to backwards compat. ??
+ ### be mindful of daemonize_exit().
+ ### we should try and raise ChildFailed / ChildTerminatedAbnormally.
+ ### ref: older revisions. OR: remove exceptions.
+
+ child_is_ready = multiprocessing.Event()
+ child_completed = multiprocessing.Event()
+
+ p = multiprocessing.Process(target=self._first_child,
+ args=(child_is_ready, child_completed))
+ p.start()
+
+ # Wait for the child to finish setting things up (in case we need
+ # to communicate with it). It will only exit when ready.
+ ### use a timeout here! (parameterized, of course)
+ p.join()
+
+ ### need to propagate errors, to adjust the return codes
+ if child_completed.is_set():
+ ### what was the exit status?
+ return DAEMON_COMPLETE
+ if child_is_ready.is_set():
+ return DAEMON_RUNNING
+
+ ### how did we get here?! the immediate child should not exit without
+ ### signalling ready/complete. some kind of error.
+ return DAEMON_STARTED
+ def _first_child(self, child_is_ready, child_completed):
# we're in the child.
+ ### NOTE: the original design was a bit bunk. Exceptions raised from
+ ### this point are within the child processes. We need to signal the
+ ### errors to the parent in other ways.
+
# decouple from the parent process
os.chdir('/')
os.umask(0)
@@ -102,56 +115,86 @@ class Daemon(object):
# remember this pid so the second child can signal it.
thispid = os.getpid()
- # register a signal handler so the SIGUSR1 doesn't stop the process.
- # this object will also record whether if got signalled.
- daemon_accepting = SignalCatcher(signal.SIGUSR1)
-
- # if the daemon process exits before sending SIGUSR1, then we need to see
- # the problem. trap SIGCHLD with a SignalCatcher.
+ # if the daemon process exits before signalling readiness, then we
+ # need to see the problem. trap SIGCHLD with a SignalCatcher.
daemon_exit = SignalCatcher(signal.SIGCHLD)
# perform the second fork
try:
pid = os.fork()
- except OSError as e:
+ except OSError, e:
+ ### this won't make it to the parent process
raise DaemonForkFailed(e.errno, e.strerror)
if pid > 0:
# in the parent.
- # we want to wait for the daemon to signal that it has created and
- # bound the socket, and is (thus) ready for connections. if the
- # daemon improperly exits before serving, we'll see SIGCHLD and the
- # .pause will return.
- ### we should add a timeout to this. allow an optional parameter to
- ### specify the timeout, in case it takes a long time to start up.
- signal.pause()
+
+ # Wait for the child to be ready for operation.
+ while True:
+ # The readiness event will invariably be signalled early/first.
+ # If it *doesn't* get signalled because the child has prematurely
+ # exited, then we will pause 10ms before noticing the exit. The
+ # pause is acceptable since that is aberrant/unexpected behavior.
+ ### is there a way to break this wait() on a signal such as SIGCHLD?
+ ### parameterize this wait, in case the app knows children may
+ ### fail quickly?
+ if child_is_ready.wait(timeout=0.010):
+ # The child signalled readiness. Yay!
+ break
+ if daemon_exit.signalled:
+ # Whoops. The child exited without signalling :-(
+ break
+ # Python 2.6 compat: .wait() may exit when set, but return None
+ if child_is_ready.is_set():
+ break
+ # A simple timeout. The child is taking a while to prepare. Go
+ # back and wait for readiness.
if daemon_exit.signalled:
+ # Tell the parent that the child has exited.
+ ### we need to communicate the exit status, if possible.
+ child_completed.set()
+
# reap the daemon process, getting its exit code. bubble it up.
cpid, status = os.waitpid(pid, 0)
assert pid == cpid
if os.WIFEXITED(status):
code = os.WEXITSTATUS(status)
if code:
+ ### this won't make it to the parent process
raise DaemonFailed(code)
+ ### this return value is ignored
return DAEMON_NOT_RUNNING
# the daemon did not exit cleanly.
+ ### this won't make it to the parent process
raise DaemonTerminatedAbnormally(status)
- if daemon_accepting.signalled:
- # the daemon is up and running, so save the pid and return success.
- if self.pidfile:
- open(self.pidfile, 'w').write('%d\n' % pid)
- return DAEMON_STARTED
+ # child_is_ready got asserted. the daemon is up and running, so
+ # save the pid and return success.
+ if self.pidfile:
+ # Be wary of symlink attacks
+ try:
+ os.remove(self.pidfile)
+ except OSError:
+ pass
+ fd = os.open(self.pidfile, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0444)
+ os.write(fd, '%d\n' % pid)
+ os.close(fd)
+ ### this return value is ignored
+ return DAEMON_STARTED
+
+ ### old code. what to do with this? throw ChildResumedIncorrectly
+ ### or just toss this and the exception.
# some other signal popped us out of the pause. the daemon might not
# be running.
+ ### this won't make it to the parent process
raise ChildResumedIncorrectly()
- # we're a deamon now. get rid of the final remnants of the parent.
- # start by restoring default signal handlers
+ # we're a daemon now. get rid of the final remnants of the parent:
+ # restore the signal handlers and switch std* to the proper files.
signal.signal(signal.SIGUSR1, signal.SIG_DFL)
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
sys.stdout.flush()
@@ -169,30 +212,31 @@ class Daemon(object):
so.close()
se.close()
- # TEST: don't release the parent immediately. the whole parent stack
- # should pause along with this sleep.
+ ### TEST: don't release the parent immediately. the whole parent stack
+ ### should pause along with this sleep.
#time.sleep(10)
# everything is set up. call the initialization function.
self.setup()
- # sleep for one second before signalling. we want to make sure the
- # parent has called signal.pause()
- ### we should think of a better wait around the race condition.
- time.sleep(1)
+ ### TEST: exit before signalling.
+ #sys.exit(0)
+ #sys.exit(1)
- # okay. the daemon is ready. signal the parent to tell it we're set.
- os.kill(thispid, signal.SIGUSR1)
+ # the child is now ready for parent/anyone to communicate with it.
+ child_is_ready.set()
# start the daemon now.
self.run()
# The daemon is shutting down, so toss the pidfile.
- try:
- os.remove(self.pidfile)
- except OSError:
- pass
+ if self.pidfile:
+ try:
+ os.remove(self.pidfile)
+ except OSError:
+ pass
+ ### this return value is ignored
return DAEMON_COMPLETE
def setup(self):
@@ -202,6 +246,34 @@ class Daemon(object):
raise NotImplementedError
+class _Detacher(Daemon):
+ def __init__(self, target, logfile='/dev/null', pidfile=None,
+ args=(), kwargs={}):
+ Daemon.__init__(self, logfile, pidfile)
+ self.target = target
+ self.args = args
+ self.kwargs = kwargs
+
+ def setup(self):
+ pass
+
+ def run(self):
+ self.target(*self.args, **self.kwargs)
+
+
+def run_detached(target, *args, **kwargs):
+ """Simple function to run TARGET as a detached daemon.
+
+ The additional arguments/keywords will be passed along. This function
+ does not return -- sys.exit() will be called as appropriate.
+
+ (capture SystemExit if logging/reporting is necessary)
+ ### if needed, a variant of this func could be written to not exit
+ """
+ d = _Detacher(target, args=args, kwargs=kwargs)
+ d.daemonize_exit()
+
+
class SignalCatcher(object):
def __init__(self, signum):
self.signalled = False
Modified: subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnpubsub/client.py
URL: http://svn.apache.org/viewvc/subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnpubsub/client.py?rev=1516196&r1=1516195&r2=1516196&view=diff
==============================================================================
--- subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnpubsub/client.py (original)
+++ subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnpubsub/client.py Wed Aug 21 16:09:43 2013
@@ -62,7 +62,8 @@ class SvnpubsubClientException(Exception
class Client(asynchat.async_chat):
- def __init__(self, url, commit_callback, event_callback):
+ def __init__(self, url, commit_callback, event_callback,
+ metadata_callback = None):
asynchat.async_chat.__init__(self)
self.last_activity = time.time()
@@ -82,7 +83,8 @@ class Client(asynchat.async_chat):
self.event_callback = event_callback
- self.parser = JSONRecordHandler(commit_callback, event_callback)
+ self.parser = JSONRecordHandler(commit_callback, event_callback,
+ metadata_callback)
# Wait for the end of headers. Then we start parsing JSON.
self.set_terminator(b'\r\n\r\n')
@@ -126,36 +128,50 @@ class Client(asynchat.async_chat):
self.ibuffer.append(data)
+class Notification(object):
+ def __init__(self, data):
+ self.__dict__.update(data)
+
+class Commit(Notification):
+ KIND = 'COMMIT'
+
+class Metadata(Notification):
+ KIND = 'METADATA'
+
+
class JSONRecordHandler:
- def __init__(self, commit_callback, event_callback):
+ def __init__(self, commit_callback, event_callback, metadata_callback):
self.commit_callback = commit_callback
self.event_callback = event_callback
+ self.metadata_callback = metadata_callback
+
+ EXPECTED_VERSION = 1
def feed(self, record):
obj = json.loads(record)
if 'svnpubsub' in obj:
actual_version = obj['svnpubsub'].get('version')
- EXPECTED_VERSION = 1
- if actual_version != EXPECTED_VERSION:
- raise SvnpubsubClientException("Unknown svnpubsub format: %r != %d"
- % (actual_format, expected_format))
+ if actual_version != self.EXPECTED_VERSION:
+ raise SvnpubsubClientException(
+ "Unknown svnpubsub format: %r != %d"
+ % (actual_version, self.EXPECTED_VERSION))
self.event_callback('version', obj['svnpubsub']['version'])
elif 'commit' in obj:
commit = Commit(obj['commit'])
self.commit_callback(commit)
elif 'stillalive' in obj:
self.event_callback('ping', obj['stillalive'])
-
-
-class Commit(object):
- def __init__(self, commit):
- self.__dict__.update(commit)
+ elif 'metadata' in obj and self.metadata_callback:
+ metadata = Metadata(obj['metadata'])
+ self.metadata_callback(metadata)
class MultiClient(object):
- def __init__(self, urls, commit_callback, event_callback):
+ def __init__(self, urls, commit_callback, event_callback,
+ metadata_callback = None):
self.commit_callback = commit_callback
self.event_callback = event_callback
+ self.metadata_callback = metadata_callback
# No target time, as no work to do
self.target_time = 0
@@ -185,9 +201,15 @@ class MultiClient(object):
def _add_channel(self, url):
# Simply instantiating the client will install it into the global map
# for processing in the main event loop.
- Client(url,
- functools.partial(self.commit_callback, url),
- functools.partial(self._reconnect, url))
+ if self.metadata_callback:
+ Client(url,
+ functools.partial(self.commit_callback, url),
+ functools.partial(self._reconnect, url),
+ functools.partial(self.metadata_callback, url))
+ else:
+ Client(url,
+ functools.partial(self.commit_callback, url),
+ functools.partial(self._reconnect, url))
def _check_stale(self):
now = time.time()
Modified: subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnpubsub/server.py
URL: http://svn.apache.org/viewvc/subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnpubsub/server.py?rev=1516196&r1=1516195&r2=1516196&view=diff
==============================================================================
--- subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnpubsub/server.py (original)
+++ subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnpubsub/server.py Wed Aug 21 16:09:43 2013
@@ -25,20 +25,27 @@
# Instead of using a complicated XMPP/AMPQ/JMS/super messaging service,
# we have simple HTTP GETs and PUTs to get data in and out.
#
-# Currently supports both XML and JSON serialization.
+# Currently supports JSON serialization.
#
# Example Sub clients:
-# curl -sN http://127.0.0.1:2069/commits
-# curl -sN http://127.0.0.1:2069/commits/svn/*
-# curl -sN http://127.0.0.1:2069/commits/svn
-# curl -sN http://127.0.0.1:2069/commits/*/13f79535-47bb-0310-9956-ffa450edef68
-# curl -sN http://127.0.0.1:2069/commits/svn/13f79535-47bb-0310-9956-ffa450edef68
+# curl -sN http://127.0.0.1:2069/commits
+# curl -sN 'http://127.0.0.1:2069/commits/svn/*'
+# curl -sN http://127.0.0.1:2069/commits/svn
+# curl -sN 'http://127.0.0.1:2069/commits/*/13f79535-47bb-0310-9956-ffa450edef68'
+# curl -sN http://127.0.0.1:2069/commits/svn/13f79535-47bb-0310-9956-ffa450edef68
#
-# URL is built into 2 parts:
-# /commits/${optional_type}/${optional_repository}
+# curl -sN http://127.0.0.1:2069/metadata
+# curl -sN 'http://127.0.0.1:2069/metadata/svn/*'
+# curl -sN http://127.0.0.1:2069/metadata/svn
+# curl -sN 'http://127.0.0.1:2069/metadata/*/13f79535-47bb-0310-9956-ffa450edef68'
+# curl -sN http://127.0.0.1:2069/metadata/svn/13f79535-47bb-0310-9956-ffa450edef68
#
-# If the type is included in the URL, you will only get commits of that type.
-# The type can be * and then you will receive commits of any type.
+# URLs are constructed from 3 parts:
+# /${notification}/${optional_type}/${optional_repository}
+#
+# Notifications can be sent for commits or metadata (e.g., revprop) changes.
+# If the type is included in the URL, you will only get notifications of that type.
+# The type can be * and then you will receive notifications of any type.
#
# If the repository is included in the URL, you will only receive
# messages about that repository. The repository can be * and then you
@@ -71,7 +78,7 @@ from twisted.python import log
import time
-class Commit:
+class Notification(object):
def __init__(self, r):
self.__dict__.update(r)
if not self.check_value('repository'):
@@ -86,7 +93,16 @@ class Commit:
def check_value(self, k):
return hasattr(self, k) and self.__dict__[k]
- def render_commit(self):
+ def render(self):
+ raise NotImplementedError
+
+ def render_log(self):
+ raise NotImplementedError
+
+class Commit(Notification):
+ KIND = 'COMMIT'
+
+ def render(self):
obj = {'commit': {}}
obj['commit'].update(self.__dict__)
return json.dumps(obj)
@@ -96,20 +112,32 @@ class Commit:
paths_changed = " %d paths changed" % len(self.changed)
except:
paths_changed = ""
- return "%s:%s repo '%s' id '%s'%s" % (self.type,
- self.format,
- self.repository,
- self.id,
- paths_changed)
+ return "commit %s:%s repo '%s' id '%s'%s" % (
+ self.type, self.format, self.repository, self.id,
+ paths_changed)
+
+class Metadata(Notification):
+ KIND = 'METADATA'
+
+ def render(self):
+ obj = {'metadata': {}}
+ obj['metadata'].update(self.__dict__)
+ return json.dumps(obj)
+
+ def render_log(self):
+ return "metadata %s:%s repo '%s' id '%s' revprop '%s'" % (
+ self.type, self.format, self.repository, self.id,
+ self.revprop['name'])
HEARTBEAT_TIME = 15
class Client(object):
- def __init__(self, pubsub, r, type, repository):
+ def __init__(self, pubsub, r, kind, type, repository):
self.pubsub = pubsub
r.notifyFinish().addErrback(self.finished)
self.r = r
+ self.kind = kind
self.type = type
self.repository = repository
self.alive = True
@@ -123,11 +151,14 @@ class Client(object):
except ValueError:
pass
- def interested_in(self, commit):
- if self.type and self.type != commit.type:
+ def interested_in(self, notification):
+ if self.kind != notification.KIND:
return False
- if self.repository and self.repository != commit.repository:
+ if self.type and self.type != notification.type:
+ return False
+
+ if self.repository and self.repository != notification.repository:
return False
return True
@@ -152,7 +183,8 @@ class Client(object):
self.r.write(str(input))
def write_start(self):
- self.r.setHeader('content-type', 'application/json')
+ self.r.setHeader('X-SVNPubSub-Version', '1')
+ self.r.setHeader('content-type', 'application/vnd.apache.vc-notify+json')
self.write('{"svnpubsub": {"version": 1}}\n\0')
def write_heartbeat(self):
@@ -163,6 +195,13 @@ class SvnPubSub(resource.Resource):
isLeaf = True
clients = []
+ __notification_uri_map = {'commits': Commit.KIND,
+ 'metadata': Metadata.KIND}
+
+ def __init__(self, notification_class):
+ resource.Resource.__init__(self)
+ self.__notification_class = notification_class
+
def cc(self):
return len(self.clients)
@@ -182,6 +221,11 @@ class SvnPubSub(resource.Resource):
request.setResponseCode(400)
return "Invalid path\n"
+ kind = self.__notification_uri_map.get(uri[1], None)
+ if kind is None:
+ request.setResponseCode(400)
+ return "Invalid path\n"
+
if uri_len >= 3:
type = uri[2]
@@ -194,17 +238,18 @@ class SvnPubSub(resource.Resource):
if repository == '*':
repository = None
- c = Client(self, request, type, repository)
+ c = Client(self, request, kind, type, repository)
self.clients.append(c)
c.start()
return twisted.web.server.NOT_DONE_YET
- def notifyAll(self, commit):
- data = commit.render_commit()
+ def notifyAll(self, notification):
+ data = notification.render()
- log.msg("COMMIT: %s (%d clients)" % (commit.render_log(), self.cc()))
+ log.msg("%s: %s (%d clients)"
+ % (notification.KIND, notification.render_log(), self.cc()))
for client in self.clients:
- if client.interested_in(commit):
+ if client.interested_in(notification):
client.write_data(data)
def render_PUT(self, request):
@@ -217,19 +262,23 @@ class SvnPubSub(resource.Resource):
#import pdb;pdb.set_trace()
#print "input: %s" % (input)
try:
- c = json.loads(input)
- commit = Commit(c)
+ data = json.loads(input)
+ notification = self.__notification_class(data)
except ValueError as e:
request.setResponseCode(400)
- log.msg("COMMIT: failed due to: %s" % str(e))
- return str(e)
- self.notifyAll(commit)
+ errstr = str(e)
+ log.msg("%s: failed due to: %s" % (notification.KIND, errstr))
+ return errstr
+ self.notifyAll(notification)
return "Ok"
+
def svnpubsub_server():
root = resource.Resource()
- s = SvnPubSub()
- root.putChild("commits", s)
+ c = SvnPubSub(Commit)
+ m = SvnPubSub(Metadata)
+ root.putChild('commits', c)
+ root.putChild('metadata', m)
return server.Site(root)
if __name__ == "__main__":
Modified: subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnwcsub.py
URL: http://svn.apache.org/viewvc/subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnwcsub.py?rev=1516196&r1=1516195&r2=1516196&view=diff
==============================================================================
--- subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnwcsub.py (original)
+++ subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/svnwcsub.py Wed Aug 21 16:09:43 2013
@@ -69,27 +69,22 @@ except ImportError:
import daemonize
import svnpubsub.client
-
-# check_output() is only available in Python 2.7. Allow us to run with
-# earlier versions
-try:
- check_output = subprocess.check_output
-except AttributeError:
- def check_output(args, env): # note: we only use these two args
- pipe = subprocess.Popen(args, stdout=subprocess.PIPE, env=env)
- output, _ = pipe.communicate()
- if pipe.returncode:
- raise subprocess.CalledProcessError(pipe.returncode, args)
- return output
+import svnpubsub.util
assert hasattr(subprocess, 'check_call')
def check_call(*args, **kwds):
- """Wrapper around subprocess.check_call() that logs stderr upon failure."""
+ """Wrapper around subprocess.check_call() that logs stderr upon failure,
+ with an optional list of exit codes to consider non-failure."""
assert 'stderr' not in kwds
+ if '__okayexits' in kwds:
+ __okayexits = kwds['__okayexits']
+ del kwds['__okayexits']
+ else:
+ __okayexits = set([0]) # EXIT_SUCCESS
kwds.update(stderr=subprocess.PIPE)
pipe = subprocess.Popen(*args, **kwds)
output, errput = pipe.communicate()
- if pipe.returncode:
+ if pipe.returncode not in __okayexits:
cmd = args[0] if len(args) else kwds.get('args', '(no command)')
# TODO: log stdout too?
logging.error('Command failed: returncode=%d command=%r stderr=%r',
@@ -103,7 +98,7 @@ def check_call(*args, **kwds):
def svn_info(svnbin, env, path):
"Run 'svn info' on the target path, returning a dict of info data."
args = [svnbin, "info", "--non-interactive", "--", path]
- output = check_output(args, env=env).strip()
+ output = svnpubsub.util.check_output(args, env=env).strip()
info = { }
for line in output.split('\n'):
idx = line.index(':')
@@ -303,6 +298,21 @@ class BackgroundWorker(threading.Thread)
logging.info("updating: %s", wc.path)
+ ## Run the hook
+ HEAD = svn_info(self.svnbin, self.env, wc.url)['Revision']
+ if self.hook:
+ hook_mode = ['pre-update', 'pre-boot'][boot]
+ logging.info('running hook: %s at %s',
+ wc.path, hook_mode)
+ args = [self.hook, hook_mode, wc.path, HEAD, wc.url]
+ rc = check_call(args, env=self.env, __okayexits=[0, 1])
+ if rc == 1:
+ # TODO: log stderr
+ logging.warn('hook denied update of %s at %s',
+ wc.path, hook_mode)
+ return
+ del rc
+
### we need to move some of these args into the config. these are
### still specific to the ASF setup.
args = [self.svnbin, 'switch',
@@ -313,12 +323,13 @@ class BackgroundWorker(threading.Thread)
'--config-option',
'config:miscellany:use-commit-times=on',
'--',
- wc.url,
+ wc.url + '@' + HEAD,
wc.path]
check_call(args, env=self.env)
### check the loglevel before running 'svn info'?
info = svn_info(self.svnbin, self.env, wc.path)
+ assert info['Revision'] == HEAD
logging.info("updated: %s now at r%s", wc.path, info['Revision'])
## Run the hook
Modified: subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/watcher.py
URL: http://svn.apache.org/viewvc/subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/watcher.py?rev=1516196&r1=1516195&r2=1516196&view=diff
==============================================================================
--- subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/watcher.py (original)
+++ subversion/branches/invoke-diff-cmd-feature/tools/server-side/svnpubsub/watcher.py Wed Aug 21 16:09:43 2013
@@ -35,6 +35,9 @@ def _commit(url, commit):
print('COMMIT: from %s' % url)
pprint.pprint(vars(commit), indent=2)
+def _metadata(url, metadata):
+ print('METADATA: from %s' % url)
+ pprint.pprint(vars(metadata), indent=2)
def _event(url, event_name, event_arg):
if event_arg:
@@ -44,7 +47,7 @@ def _event(url, event_name, event_arg):
def main(urls):
- mc = svnpubsub.client.MultiClient(urls, _commit, _event)
+ mc = svnpubsub.client.MultiClient(urls, _commit, _event, _metadata)
mc.run_forever()
Modified: subversion/branches/invoke-diff-cmd-feature/win-tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/invoke-diff-cmd-feature/win-tests.py?rev=1516196&r1=1516195&r2=1516196&view=diff
==============================================================================
--- subversion/branches/invoke-diff-cmd-feature/win-tests.py (original)
+++ subversion/branches/invoke-diff-cmd-feature/win-tests.py Wed Aug 21 16:09:43 2013
@@ -84,6 +84,8 @@ def _usage_exit():
print(" --disable-bulk-updates : Disable bulk updates on HTTP server")
print(" --ssl-cert : Path to SSL server certificate to trust.")
print(" --javahl : Run the javahl tests instead of the normal tests")
+ print(" --swig=language : Run the swig perl/python/ruby tests instead of")
+ print(" the normal tests")
print(" --list : print test doc strings only")
print(" --milestone-filter=RE : RE is a regular expression pattern that (when")
print(" used with --list) limits the tests listed to")
@@ -108,29 +110,24 @@ CMDLINE_TEST_SCRIPT_NATIVE_PATH = CMDLIN
sys.path.insert(0, os.path.join('build', 'generator'))
sys.path.insert(1, 'build')
-import gen_win
+import gen_win_dependencies
+import gen_base
version_header = os.path.join('subversion', 'include', 'svn_version.h')
cp = configparser.ConfigParser()
cp.read('gen-make.opts')
-gen_obj = gen_win.GeneratorBase('build.conf', version_header,
- cp.items('options'))
+gen_obj = gen_win_dependencies.GenDependenciesBase('build.conf', version_header,
+ cp.items('options'))
all_tests = gen_obj.test_progs + gen_obj.bdb_test_progs \
+ gen_obj.scripts + gen_obj.bdb_scripts
client_tests = [x for x in all_tests if x.startswith(CMDLINE_TEST_SCRIPT_PATH)]
-svn_dlls = []
-for section in gen_obj.sections.values():
- if section.options.get("msvc-export"):
- dll_basename = section.name + "-" + str(gen_obj.version) + ".dll"
- svn_dlls.append(os.path.join("subversion", section.name, dll_basename))
-
opts, args = my_getopt(sys.argv[1:], 'hrdvqct:pu:f:',
['release', 'debug', 'verbose', 'quiet', 'cleanup',
'test=', 'url=', 'svnserve-args=', 'fs-type=', 'asp.net-hack',
'httpd-dir=', 'httpd-port=', 'httpd-daemon',
'httpd-server', 'http-short-circuit', 'httpd-no-log',
'disable-http-v2', 'disable-bulk-updates', 'help',
- 'fsfs-packing', 'fsfs-sharding=', 'javahl',
+ 'fsfs-packing', 'fsfs-sharding=', 'javahl', 'swig=',
'list', 'enable-sasl', 'bin=', 'parallel',
'config-file=', 'server-minor-version=', 'log-level=',
'log-to-stdout', 'mode-filter=', 'milestone-filter=',
@@ -156,6 +153,7 @@ http_bulk_updates = True
list_tests = None
milestone_filter = None
test_javahl = None
+test_swig = None
enable_sasl = None
svn_bin = None
parallel = None
@@ -216,6 +214,11 @@ for opt, val in opts:
fsfs_packing = 1
elif opt == '--javahl':
test_javahl = 1
+ elif opt == '--swig':
+ if val not in ['perl', 'python', 'ruby']:
+ sys.stderr.write('Running \'%s\' swig tests not supported (yet).\n'
+ % (val,))
+ test_swig = val
elif opt == '--list':
list_tests = 1
elif opt == '--milestone-filter':
@@ -289,12 +292,18 @@ def create_target_dir(dirname):
print("mkdir: %s" % tgt_dir)
os.makedirs(tgt_dir)
-def copy_changed_file(src, tgt):
+def copy_changed_file(src, tgt=None, to_dir=None, cleanup=True):
if not os.path.isfile(src):
print('Could not find ' + src)
sys.exit(1)
- if os.path.isdir(tgt):
- tgt = os.path.join(tgt, os.path.basename(src))
+
+ if to_dir and not tgt:
+ tgt = os.path.join(to_dir, os.path.basename(src))
+ elif not tgt or (tgt and to_dir):
+ raise RuntimeError("Using 'tgt' *or* 'to_dir' is required" % (tgt,))
+ elif tgt and os.path.isdir(tgt):
+ raise RuntimeError("'%s' is a directory. Use to_dir=" % (tgt,))
+
if os.path.exists(tgt):
assert os.path.isfile(tgt)
if filecmp.cmp(src, tgt):
@@ -306,57 +315,35 @@ def copy_changed_file(src, tgt):
print("copy: %s" % src)
print(" to: %s" % tgt)
shutil.copy(src, tgt)
- return 1
-def copy_execs(baton, dirname, names):
- copied_execs = baton
- for name in names:
- if not name.endswith('.exe'):
- continue
- src = os.path.join(dirname, name)
- tgt = os.path.join(abs_builddir, dirname, name)
- create_target_dir(dirname)
- if copy_changed_file(src, tgt):
- copied_execs.append(tgt)
+ if cleanup:
+ copied_execs.append(tgt)
def locate_libs():
"Move DLLs to a known location and set env vars"
- dlls = []
-
- # look for APR 1.x dll's and use those if found
- apr_test_path = os.path.join(gen_obj.apr_path, objdir, 'libapr-1.dll')
- if os.path.exists(apr_test_path):
- suffix = "-1"
- else:
- suffix = ""
-
- if cp.has_option('options', '--with-static-apr'):
- dlls.append(os.path.join(gen_obj.apr_path, objdir,
- 'libapr%s.dll' % (suffix)))
- dlls.append(os.path.join(gen_obj.apr_util_path, objdir,
- 'libaprutil%s.dll' % (suffix)))
-
- if gen_obj.libintl_path is not None:
- dlls.append(os.path.join(gen_obj.libintl_path, 'bin', 'intl3_svn.dll'))
+ debug = (objdir == 'Debug')
+
+ for lib in gen_obj._libraries.values():
+
+ if debug:
+ name, dir = lib.debug_dll_name, lib.debug_dll_dir
+ else:
+ name, dir = lib.dll_name, lib.dll_dir
+
+ if name and dir:
+ src = os.path.join(dir, name)
+ if os.path.exists(src):
+ copy_changed_file(src, to_dir=abs_builddir, cleanup=False)
- if gen_obj.bdb_lib is not None:
- partial_path = os.path.join(gen_obj.bdb_path, 'bin', gen_obj.bdb_lib)
- if objdir == 'Debug':
- dlls.append(partial_path + 'd.dll')
- else:
- dlls.append(partial_path + '.dll')
-
- if gen_obj.sasl_path is not None:
- dlls.append(os.path.join(gen_obj.sasl_path, 'lib', 'libsasl.dll'))
-
- for dll in dlls:
- copy_changed_file(dll, abs_objdir)
# Copy the Subversion library DLLs
- if not cp.has_option('options', '--disable-shared'):
- for svn_dll in svn_dlls:
- copy_changed_file(os.path.join(abs_objdir, svn_dll), abs_objdir)
+ for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
+ if isinstance(i, gen_base.TargetLib) and i.msvc_export:
+ src = os.path.join(abs_objdir, i.filename)
+ if os.path.isfile(src):
+ copy_changed_file(src, to_dir=abs_builddir,
+ cleanup=False)
# Copy the Apache modules
if run_httpd and cp.has_option('options', '--with-httpd'):
@@ -367,11 +354,11 @@ def locate_libs():
mod_dontdothat_path = os.path.join(abs_objdir, 'tools', 'server-side',
'mod_dontdothat', 'mod_dontdothat.so')
- copy_changed_file(mod_dav_svn_path, abs_objdir)
- copy_changed_file(mod_authz_svn_path, abs_objdir)
- copy_changed_file(mod_dontdothat_path, abs_objdir)
+ copy_changed_file(mod_dav_svn_path, to_dir=abs_builddir, cleanup=False)
+ copy_changed_file(mod_authz_svn_path, to_dir=abs_builddir, cleanup=False)
+ copy_changed_file(mod_dontdothat_path, to_dir=abs_builddir, cleanup=False)
- os.environ['PATH'] = abs_objdir + os.pathsep + os.environ['PATH']
+ os.environ['PATH'] = abs_builddir + os.pathsep + os.environ['PATH']
def fix_case(path):
path = os.path.normpath(path)
@@ -592,6 +579,12 @@ class Httpd:
def _create_dontdothat_file(self):
"Create empty mime.types file"
+ # If the tests have not previously been run or were cleaned
+ # up, then 'svn-test-work' does not exist yet.
+ parent_dir = os.path.dirname(self.dontdothat_file)
+ if not os.path.exists(parent_dir):
+ os.makedirs(parent_dir)
+
fp = open(self.dontdothat_file, 'w')
fp.write('[recursive-actions]\n')
fp.write('/ = deny\n')
@@ -602,7 +595,7 @@ class Httpd:
return 'LoadModule ' + name + " " + self._quote(full_path) + '\n'
def _svn_module(self, name, path):
- full_path = os.path.join(self.abs_objdir, path)
+ full_path = os.path.join(self.abs_builddir, path)
return 'LoadModule ' + name + ' ' + self._quote(full_path) + '\n'
def _svn_repo(self, name):
@@ -682,21 +675,17 @@ class Httpd:
print('Httpd.stop_daemon not implemented')
# Move the binaries to the test directory
+create_target_dir(abs_builddir)
locate_libs()
if create_dirs:
- old_cwd = os.getcwd()
- try:
- os.chdir(abs_objdir)
- baton = copied_execs
- for dirpath, dirs, files in os.walk('subversion'):
- copy_execs(baton, dirpath, files)
- for dirpath, dirs, files in os.walk('tools/server-side'):
- copy_execs(baton, dirpath, files)
- except:
- os.chdir(old_cwd)
- raise
- else:
- os.chdir(old_cwd)
+ for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
+ if isinstance(i, gen_base.TargetExe):
+ src = os.path.join(abs_objdir, i.filename)
+
+ if os.path.isfile(src):
+ dst = os.path.join(abs_builddir, i.filename)
+ create_target_dir(os.path.dirname(dst))
+ copy_changed_file(src, dst)
# Create the base directory for Python tests
create_target_dir(CMDLINE_TEST_SCRIPT_NATIVE_PATH)
@@ -754,7 +743,7 @@ else:
print('Testing %s configuration on %s' % (objdir, repo_loc))
sys.path.insert(0, os.path.join(abs_srcdir, 'build'))
-if not test_javahl:
+if not test_javahl and not test_swig:
import run_tests
if log_to_stdout:
log_file = None
@@ -782,7 +771,7 @@ if not test_javahl:
raise
else:
os.chdir(old_cwd)
-else:
+elif test_javahl:
failed = False
args = (
'java.exe',
@@ -820,6 +809,148 @@ else:
if (r != 0):
print('[Test runner reported failure]')
failed = True
+elif test_swig == 'perl':
+ failed = False
+ swig_dir = os.path.join(abs_builddir, 'swig')
+ swig_pl_dir = os.path.join(swig_dir, 'p5lib')
+ swig_pl_svn = os.path.join(swig_pl_dir, 'SVN')
+ swig_pl_auto_svn = os.path.join(swig_pl_dir, 'auto', 'SVN')
+
+ create_target_dir(swig_pl_svn)
+
+ for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
+ if isinstance(i, gen_base.TargetSWIG) and i.lang == 'perl':
+ mod_dir = os.path.join(swig_pl_auto_svn, '_' + i.name[5:].capitalize())
+ create_target_dir(mod_dir)
+ copy_changed_file(os.path.join(abs_objdir, i.filename), to_dir=mod_dir)
+
+ elif isinstance(i, gen_base.TargetSWIGLib) and i.lang == 'perl':
+ copy_changed_file(os.path.join(abs_objdir, i.filename),
+ to_dir=abs_builddir)
+
+ pm_src = os.path.join(abs_srcdir, 'subversion', 'bindings', 'swig', 'perl',
+ 'native')
+
+ tests = []
+
+ for root, dirs, files in os.walk(pm_src):
+ for name in files:
+ if name.endswith('.pm'):
+ fn = os.path.join(root, name)
+ copy_changed_file(fn, to_dir=swig_pl_svn)
+ elif name.endswith('.t'):
+ tests.append(os.path.relpath(os.path.join(root, name), pm_src))
+
+ perl5lib = swig_pl_dir
+ if 'PERL5LIB' in os.environ:
+ perl5lib += os.pathsep + os.environ['PERL5LIB']
+
+ perl_exe = 'perl.exe'
+
+ print('-- Running Swig Perl tests --')
+ old_cwd = os.getcwd()
+ try:
+ os.chdir(pm_src)
+
+ os.environ['PERL5LIB'] = perl5lib
+ os.environ["SVN_DBG_NO_ABORT_ON_ERROR_LEAK"] = 'YES'
+
+ r = subprocess.call([
+ perl_exe,
+ '-MExtUtils::Command::MM',
+ '-e', 'test_harness()'
+ ] + tests)
+ finally:
+ os.chdir(old_cwd)
+
+ if (r != 0):
+ print('[Test runner reported failure]')
+ failed = True
+ sys.exit(1)
+elif test_swig == 'python':
+ failed = False
+ swig_dir = os.path.join(abs_builddir, 'swig')
+ swig_py_dir = os.path.join(swig_dir, 'pylib')
+ swig_py_libsvn = os.path.join(swig_py_dir, 'libsvn')
+ swig_py_svn = os.path.join(swig_py_dir, 'svn')
+
+ create_target_dir(swig_py_libsvn)
+ create_target_dir(swig_py_svn)
+
+ for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
+ if (isinstance(i, gen_base.TargetSWIG)
+ or isinstance(i, gen_base.TargetSWIGLib)) and i.lang == 'python':
+
+ src = os.path.join(abs_objdir, i.filename)
+ copy_changed_file(src, to_dir=swig_py_libsvn)
+
+ py_src = os.path.join(abs_srcdir, 'subversion', 'bindings', 'swig', 'python')
+
+ for py_file in os.listdir(py_src):
+ if py_file.endswith('.py'):
+ copy_changed_file(os.path.join(py_src, py_file),
+ to_dir=swig_py_libsvn)
+
+ py_src_svn = os.path.join(py_src, 'svn')
+ for py_file in os.listdir(py_src_svn):
+ if py_file.endswith('.py'):
+ copy_changed_file(os.path.join(py_src_svn, py_file),
+ to_dir=swig_py_svn)
+
+ print('-- Running Swig Python tests --')
+
+ pythonpath = swig_py_dir
+ if 'PYTHONPATH' in os.environ:
+ pythonpath += os.pathsep + os.environ['PYTHONPATH']
+
+ python_exe = 'python.exe'
+ old_cwd = os.getcwd()
+ try:
+ os.environ['PYTHONPATH'] = pythonpath
+
+ r = subprocess.call([
+ python_exe,
+ os.path.join(py_src, 'tests', 'run_all.py')
+ ])
+ finally:
+ os.chdir(old_cwd)
+
+ if (r != 0):
+ print('[Test runner reported failure]')
+ failed = True
+
+elif test_swig == 'ruby':
+ failed = False
+
+ if 'ruby' not in gen_obj._libraries:
+ print('Ruby not found. Skipping Ruby tests')
+ else:
+ ruby_lib = gen_obj._libraries['ruby']
+
+ ruby_exe = 'ruby.exe'
+ ruby_subdir = os.path.join('subversion', 'bindings', 'swig', 'ruby')
+ ruby_args = [
+ '-I', os.path.join(abs_srcdir, ruby_subdir),
+ os.path.join(abs_srcdir, ruby_subdir, 'test', 'run-test.rb'),
+ '--verbose'
+ ]
+
+ print('-- Running Swig Ruby tests --')
+ old_cwd = os.getcwd()
+ try:
+ os.chdir(ruby_subdir)
+
+ os.environ["BUILD_TYPE"] = objdir
+ os.environ["SVN_DBG_NO_ABORT_ON_ERROR_LEAK"] = 'YES'
+ r = subprocess.call([ruby_exe] + ruby_args)
+ finally:
+ os.chdir(old_cwd)
+
+ sys.stdout.flush()
+ sys.stderr.flush()
+ if (r != 0):
+ print('[Test runner reported failure]')
+ failed = True
# Stop service daemon, if any
if daemon: