Index: /main/waeup.stress/trunk/BIG_FAT_WARNING.txt
===================================================================
--- /main/waeup.stress/trunk/BIG_FAT_WARNING.txt	(revision 8654)
+++ /main/waeup.stress/trunk/BIG_FAT_WARNING.txt	(revision 8654)
@@ -0,0 +1,2 @@
+The READMEs for this package are terribly outdated! Please remember me
+to update them soon!
Index: /main/waeup.stress/trunk/CHANGES.rst
===================================================================
--- /main/waeup.stress/trunk/CHANGES.rst	(revision 8654)
+++ /main/waeup.stress/trunk/CHANGES.rst	(revision 8654)
@@ -0,0 +1,8 @@
+=======
+Changes
+=======
+
+0.1 (unreleased)
+----------------
+
+- Initial implementation.
Index: /main/waeup.stress/trunk/README.kofa01.rst
===================================================================
--- /main/waeup.stress/trunk/README.kofa01.rst	(revision 8654)
+++ /main/waeup.stress/trunk/README.kofa01.rst	(revision 8654)
@@ -0,0 +1,10 @@
+===========================
+Applicant Registration Page
+===========================
+
+Very simple setup, that requests the page
+http://127.0.0.1:8080/app/applicants/putme2012/register as often as
+possible.
+
+See the projects config file to finetune number of threads (concurrent
+users) and other parameters.
Index: /main/waeup.stress/trunk/README.rst
===================================================================
--- /main/waeup.stress/trunk/README.rst	(revision 8654)
+++ /main/waeup.stress/trunk/README.rst	(revision 8654)
@@ -0,0 +1,57 @@
+=====================
+Stress Tests for Kofa
+=====================
+
+Stress- and performance tests in here are based on the Python package
+`multi-mechanize`. See the homepage
+http://testutils.org/multi-mechanize/ for details.
+
+Installation
+------------
+
+The stuff in here is *not* buildout-based.
+
+To make it work, you need a Python with the packages
+`multi-mechanize`, `numpy`, and `matplotlib` installed.
+
+I'm used to install such packages in a virtual environment like this::
+
+  $ virtualenv --no-site-packages mysandbox
+  $ source mysandbox/bin/activate
+
+Afterwards the required packages can be installed into this sandbox::
+
+  (mysandbox) $ pip install numpy
+  (mysandbox) $ pip install matplotlib
+  (mysandbox) $ pip install multi-mechanize
+
+Especially `matplotlib` is very demanding regarding already installed
+(C and C++) libraries. On my Ubuntu I had to install at least::
+
+  $ sudo apt-get install libfreetype6-dev
+  $ sudo apt-get install libpng12-dev
+
+to make it compile and install.
+
+Running Stresstests
+-------------------
+
+The different stress tests are put into different multi-mechanize
+'projects'. Each project runs a certain stress test for which you have
+to setup certain things to make it work.
+
+Basically, you run a stresstest like this::
+
+  (mysandbox) $ multimech-run <PROJECT>
+
+where ``<PROJECT>`` is one of the directories in here.
+
+The projects in here are the subdirectories, each one representing a
+different 'project' or kind of stresstest. Please see the respective
+README files in this folder to learn what conditions must be met to
+run each test.
+
+Please keep in mind, that these tests normally require some local or
+remote setup, for instance a running Kofa instance with certain
+reachable URLs. Again, you can lookup the specific READMEs in this
+directory to learn what setup is required to run a certain test.
Index: /main/waeup.stress/trunk/bootstrap.py
===================================================================
--- /main/waeup.stress/trunk/bootstrap.py	(revision 8654)
+++ /main/waeup.stress/trunk/bootstrap.py	(revision 8654)
@@ -0,0 +1,262 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+"""
+
+import os, shutil, sys, tempfile, urllib, urllib2, subprocess
+from optparse import OptionParser
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c  # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    quote = str
+
+# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
+stdout, stderr = subprocess.Popen(
+    [sys.executable, '-Sc',
+     'try:\n'
+     '    import ConfigParser\n'
+     'except ImportError:\n'
+     '    print 1\n'
+     'else:\n'
+     '    print 0\n'],
+    stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+has_broken_dash_S = bool(int(stdout.strip()))
+
+# In order to be more robust in the face of system Pythons, we want to
+# run without site-packages loaded.  This is somewhat tricky, in
+# particular because Python 2.6's distutils imports site, so starting
+# with the -S flag is not sufficient.  However, we'll start with that:
+if not has_broken_dash_S and 'site' in sys.modules:
+    # We will restart with python -S.
+    args = sys.argv[:]
+    args[0:0] = [sys.executable, '-S']
+    args = map(quote, args)
+    os.execv(sys.executable, args)
+# Now we are running with -S.  We'll get the clean sys.path, import site
+# because distutils will do it later, and then reset the path and clean
+# out any namespace packages from site-packages that might have been
+# loaded by .pth files.
+clean_path = sys.path[:]
+import site  # imported because of its side effects
+sys.path[:] = clean_path
+for k, v in sys.modules.items():
+    if k in ('setuptools', 'pkg_resources') or (
+        hasattr(v, '__path__') and
+        len(v.__path__) == 1 and
+        not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))):
+        # This is a namespace package.  Remove it.
+        sys.modules.pop(k)
+
+is_jython = sys.platform.startswith('java')
+
+setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
+distribute_source = 'http://python-distribute.org/distribute_setup.py'
+
+
+# parsing arguments
+def normalize_to_url(option, opt_str, value, parser):
+    if value:
+        if '://' not in value:  # It doesn't smell like a URL.
+            value = 'file://%s' % (
+                urllib.pathname2url(
+                    os.path.abspath(os.path.expanduser(value))),)
+        if opt_str == '--download-base' and not value.endswith('/'):
+            # Download base needs a trailing slash to make the world happy.
+            value += '/'
+    else:
+        value = None
+    name = opt_str[2:].replace('-', '_')
+    setattr(parser.values, name, value)
+
+usage = '''\
+[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
+
+Bootstraps a buildout-based project.
+
+Simply run this script in a directory containing a buildout.cfg, using the
+Python that you want bin/buildout to use.
+
+Note that by using --setup-source and --download-base to point to
+local resources, you can keep this script from going over the network.
+'''
+
+parser = OptionParser(usage=usage)
+parser.add_option("-v", "--version", dest="version",
+                          help="use a specific zc.buildout version")
+parser.add_option("-d", "--distribute",
+                   action="store_true", dest="use_distribute", default=False,
+                   help="Use Distribute rather than Setuptools.")
+parser.add_option("--setup-source", action="callback", dest="setup_source",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or file location for the setup file. "
+                        "If you use Setuptools, this will default to " +
+                        setuptools_source + "; if you use Distribute, this "
+                        "will default to " + distribute_source + "."))
+parser.add_option("--download-base", action="callback", dest="download_base",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or directory for downloading "
+                        "zc.buildout and either Setuptools or Distribute. "
+                        "Defaults to PyPI."))
+parser.add_option("--eggs",
+                  help=("Specify a directory for storing eggs.  Defaults to "
+                        "a temporary directory that is deleted when the "
+                        "bootstrap script completes."))
+parser.add_option("-t", "--accept-buildout-test-releases",
+                  dest='accept_buildout_test_releases',
+                  action="store_true", default=False,
+                  help=("Normally, if you do not specify a --version, the "
+                        "bootstrap script and buildout gets the newest "
+                        "*final* versions of zc.buildout and its recipes and "
+                        "extensions for you.  If you use this flag, "
+                        "bootstrap and buildout will get the newest releases "
+                        "even if they are alphas or betas."))
+parser.add_option("-c", None, action="store", dest="config_file",
+                   help=("Specify the path to the buildout configuration "
+                         "file to be used."))
+
+options, args = parser.parse_args()
+
+# if -c was provided, we push it back into args for buildout's main function
+if options.config_file is not None:
+    args += ['-c', options.config_file]
+
+if options.eggs:
+    eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
+else:
+    eggs_dir = tempfile.mkdtemp()
+
+if options.setup_source is None:
+    if options.use_distribute:
+        options.setup_source = distribute_source
+    else:
+        options.setup_source = setuptools_source
+
+if options.accept_buildout_test_releases:
+    args.append('buildout:accept-buildout-test-releases=true')
+args.append('bootstrap')
+
+try:
+    import pkg_resources
+    import setuptools  # A flag.  Sometimes pkg_resources is installed alone.
+    if not hasattr(pkg_resources, '_distribute'):
+        raise ImportError
+except ImportError:
+    ez_code = urllib2.urlopen(
+        options.setup_source).read().replace('\r\n', '\n')
+    ez = {}
+    exec ez_code in ez
+    setup_args = dict(to_dir=eggs_dir, download_delay=0)
+    if options.download_base:
+        setup_args['download_base'] = options.download_base
+    if options.use_distribute:
+        setup_args['no_fake'] = True
+    ez['use_setuptools'](**setup_args)
+    if 'pkg_resources' in sys.modules:
+        reload(sys.modules['pkg_resources'])
+    import pkg_resources
+    # This does not (always?) update the default working set.  We will
+    # do it.
+    for path in sys.path:
+        if path not in pkg_resources.working_set.entries:
+            pkg_resources.working_set.add_entry(path)
+
+cmd = [quote(sys.executable),
+       '-c',
+       quote('from setuptools.command.easy_install import main; main()'),
+       '-mqNxd',
+       quote(eggs_dir)]
+
+if not has_broken_dash_S:
+    cmd.insert(1, '-S')
+
+find_links = options.download_base
+if not find_links:
+    find_links = os.environ.get('bootstrap-testing-find-links')
+if find_links:
+    cmd.extend(['-f', quote(find_links)])
+
+if options.use_distribute:
+    setup_requirement = 'distribute'
+else:
+    setup_requirement = 'setuptools'
+ws = pkg_resources.working_set
+setup_requirement_path = ws.find(
+    pkg_resources.Requirement.parse(setup_requirement)).location
+env = dict(
+    os.environ,
+    PYTHONPATH=setup_requirement_path)
+
+requirement = 'zc.buildout'
+version = options.version
+if version is None and not options.accept_buildout_test_releases:
+    # Figure out the most recent final version of zc.buildout.
+    import setuptools.package_index
+    _final_parts = '*final-', '*final'
+
+    def _final_version(parsed_version):
+        for part in parsed_version:
+            if (part[:1] == '*') and (part not in _final_parts):
+                return False
+        return True
+    index = setuptools.package_index.PackageIndex(
+        search_path=[setup_requirement_path])
+    if find_links:
+        index.add_find_links((find_links,))
+    req = pkg_resources.Requirement.parse(requirement)
+    if index.obtain(req) is not None:
+        best = []
+        bestv = None
+        for dist in index[req.project_name]:
+            distv = dist.parsed_version
+            if _final_version(distv):
+                if bestv is None or distv > bestv:
+                    best = [dist]
+                    bestv = distv
+                elif distv == bestv:
+                    best.append(dist)
+        if best:
+            best.sort()
+            version = best[-1].version
+if version:
+    requirement = '=='.join((requirement, version))
+cmd.append(requirement)
+
+if is_jython:
+    import subprocess
+    exitcode = subprocess.Popen(cmd, env=env).wait()
+else:  # Windows prefers this, apparently; otherwise we would prefer subprocess
+    exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
+if exitcode != 0:
+    sys.stdout.flush()
+    sys.stderr.flush()
+    print ("An error occurred when trying to install zc.buildout. "
+           "Look above this message for any errors that "
+           "were output by easy_install.")
+    sys.exit(exitcode)
+
+ws.add_entry(eggs_dir)
+ws.require(requirement)
+import zc.buildout.buildout
+zc.buildout.buildout.main(args)
+if not options.eggs:  # clean up temporary egg directory
+    shutil.rmtree(eggs_dir)
Index: /main/waeup.stress/trunk/buildout.cfg
===================================================================
--- /main/waeup.stress/trunk/buildout.cfg	(revision 8654)
+++ /main/waeup.stress/trunk/buildout.cfg	(revision 8654)
@@ -0,0 +1,35 @@
+[buildout]
+extensions = mr.developer
+auto-checkout = waeup.kofa
+sources-dir = instances
+develop = . instances/waeup.kofa
+parts =
+    app
+    test
+# newest = false
+
+versions = versions
+
+[versions]
+
+# eggs will be installed in the default buildout location
+# (see .buildout/default.cfg in your home directory)
+# unless you specify an eggs-directory option here.
+
+[sources]
+waeup.kofa = svn https://svn.waeup.org/repos/main/waeup.kofa/trunk
+
+
+[app]
+recipe = zc.recipe.egg
+eggs = waeup.stress
+       waeup.kofa
+       multi-mechanize
+
+[test]
+recipe = zc.recipe.egg
+eggs = waeup.stress[test]
+       pytest
+       pytest-xdist
+       pytest-cov
+scripts = py.test
Index: /main/waeup.stress/trunk/kofa01/config.cfg
===================================================================
--- /main/waeup.stress/trunk/kofa01/config.cfg	(revision 8654)
+++ /main/waeup.stress/trunk/kofa01/config.cfg	(revision 8654)
@@ -0,0 +1,25 @@
+
+[global]
+run_time = 10
+rampup = 0
+results_ts_interval = 10
+progress_bar = on
+console_logging = off
+xml_report = off
+post_run_script = kofa01/post_run.sh
+
+# /usr/bin/python --version
+# post_run.py
+# "/usr/bin/python -v"
+# "import os; print os.getcwd()"
+# kofa01/post_run.py
+
+
+[user_group-1]
+threads = 5
+script = v_user.py
+
+[user_group-2]
+threads = 5
+script = v_user2.py
+
Index: /main/waeup.stress/trunk/kofa01/post_run.py
===================================================================
--- /main/waeup.stress/trunk/kofa01/post_run.py	(revision 8654)
+++ /main/waeup.stress/trunk/kofa01/post_run.py	(revision 8654)
@@ -0,0 +1,5 @@
+import waeup.stress
+
+instance = 'kofa'
+
+waeup.stress.stop_instance(instance)
Index: /main/waeup.stress/trunk/kofa01/post_run.sh
===================================================================
--- /main/waeup.stress/trunk/kofa01/post_run.sh	(revision 8654)
+++ /main/waeup.stress/trunk/kofa01/post_run.sh	(revision 8654)
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+OLD_CWD=`pwd`
+cd instances/waeup.kofa
+echo "stopping paster..."
+./bin/paster serve --stop-daemon
+echo "done."
+cd $OLD_CWD
Index: /main/waeup.stress/trunk/kofa01/test_scripts/config.py
===================================================================
--- /main/waeup.stress/trunk/kofa01/test_scripts/config.py	(revision 8654)
+++ /main/waeup.stress/trunk/kofa01/test_scripts/config.py	(revision 8654)
@@ -0,0 +1,17 @@
+import waeup.stress
+#register_url = 'http://localhost:8080/uniben/applicants/putme2012/register'
+register_url = 'http://localhost:8080/stress_app/@@index'
+print "RUN SETUP"
+waeup.stress.do_foo()
+
+instance = 'kofa'
+
+## removes any existing ZODB (for a clean ZODB at start)
+waeup.stress.remove_zodb(instance)
+
+## installs a University named 'stress_app' in root folder
+waeup.stress.install_app(instance)
+
+waeup.stress.start_instance(instance)
+#waeup.stress.stop_instance(instance)
+
Index: /main/waeup.stress/trunk/kofa01/test_scripts/v_user.py
===================================================================
--- /main/waeup.stress/trunk/kofa01/test_scripts/v_user.py	(revision 8654)
+++ /main/waeup.stress/trunk/kofa01/test_scripts/v_user.py	(revision 8654)
@@ -0,0 +1,23 @@
+import mechanize
+import time
+from config import register_url
+
+class Transaction(object):
+    def __init__(self):
+        pass
+
+    def run(self):
+        b = mechanize.Browser()
+        b.set_handle_robots(False)
+        start_time = time.time()
+        resp = b.open(register_url)
+        content = resp.read()
+        latency = time.time() - start_time
+        self.custom_timers['Load_Registration_Page'] = latency
+        assert (resp.code == 200), 'Bad Response: HTTP %s' % resp.code
+        return
+
+if __name__ == '__main__':
+    trans = Transaction()
+    trans.run()
+    print trans.custom_timers
Index: /main/waeup.stress/trunk/kofa01/test_scripts/v_user2.py
===================================================================
--- /main/waeup.stress/trunk/kofa01/test_scripts/v_user2.py	(revision 8654)
+++ /main/waeup.stress/trunk/kofa01/test_scripts/v_user2.py	(revision 8654)
@@ -0,0 +1,23 @@
+import mechanize
+import time
+from config import register_url
+
+class Transaction(object):
+    def __init__(self):
+        pass
+
+    def run(self):
+        b = mechanize.Browser()
+        b.set_handle_robots(False)
+        start_time = time.time()
+        resp = b.open(register_url)
+        content = resp.read()
+        latency = time.time() - start_time
+        self.custom_timers['Load_Registration_Page'] = latency
+        assert (resp.code == 200), 'Bad Response: HTTP %s' % resp.code
+        return
+
+if __name__ == '__main__':
+    trans = Transaction()
+    trans.run()
+    print trans.custom_timers
Index: /main/waeup.stress/trunk/setup.py
===================================================================
--- /main/waeup.stress/trunk/setup.py	(revision 8654)
+++ /main/waeup.stress/trunk/setup.py	(revision 8654)
@@ -0,0 +1,89 @@
+import os
+from setuptools import setup, find_packages
+
+version = '0.1dev'
+
+install_requires =[
+    'setuptools',
+    'multi-mechanize',
+    'numpy',
+    'matplotlib',
+    'waeup.kofa',
+    ],
+
+# Having beaker installed additionally is a feature very recommended
+# for production use. The default buildout includes beaker for tests,
+# start scripts, and other parts that can benefit from it. The windows
+# buildout does not include it due to compiling problems with the
+# beaker package.
+beaker_require = [
+    'dolmen.beaker',
+    ]
+
+tests_require = [
+    'pytest',
+    'pytest-xdist',
+    'pytest-cov',
+    ]
+
+docs_require = [
+    'Sphinx',
+    'collective.recipe.sphinxbuilder',
+    'docutils',
+    'roman',
+    'repoze.sphinx.autointerface',
+    ]
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+long_description = (
+    read('README.rst')
+    + '\n\n'
+    + read('CHANGES.rst')
+    + '\n\n'
+    + 'Download\n'
+    + '********\n'
+    )
+
+setup(name = 'waeup.stress',
+      version = version,
+      description = "Stresstests for WAeUP Kofa",
+      long_description = long_description,
+
+      keywords = "waeup kofa stress test",
+      # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+      classifiers = [
+        'Development Status :: 3 - Alpha',
+        'Environment :: Web Environment',
+        'Intended Audience :: Education',
+        'License :: OSI Approved :: GNU General Public License (GPL)',
+        'Programming Language :: Python',
+        'Operating System :: POSIX',
+        'Operating System :: POSIX :: Linux',
+        'Framework :: Zope3',
+        'Topic :: Education',
+        'Topic :: Internet :: WWW/HTTP',
+        ],
+      author = "The WAeUP team.",
+      author_email = "",
+      url = "http://www.waeup.org/",
+      license = "GPL",
+      package_dir = {'': 'src'},
+      packages= find_packages('src'),
+      namespace_packages = ['waeup',],
+      include_package_data = True,
+      zip_safe = False,
+      install_requires = install_requires,
+      tests_require = tests_require,
+      extras_require = dict(
+        test = tests_require,
+        docs = docs_require,
+        beaker = beaker_require,
+        ),
+      entry_points="""
+      # Add entry points here
+      [console_scripts]
+      bootstrap_instances = waeup.stress:bootstrap
+      """,
+      )
Index: /main/waeup.stress/trunk/src/waeup/__init__.py
===================================================================
--- /main/waeup.stress/trunk/src/waeup/__init__.py	(revision 8654)
+++ /main/waeup.stress/trunk/src/waeup/__init__.py	(revision 8654)
@@ -0,0 +1,7 @@
+# namespace package boilerplate
+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError, e:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)
Index: /main/waeup.stress/trunk/src/waeup/stress/__init__.py
===================================================================
--- /main/waeup.stress/trunk/src/waeup/stress/__init__.py	(revision 8654)
+++ /main/waeup.stress/trunk/src/waeup/stress/__init__.py	(revision 8654)
@@ -0,0 +1,110 @@
+import os
+import subprocess
+import sys
+import waeup.kofa
+
+AVAILABLE_KOFA_PROJECTS = ['waeup.kofa',]
+packages = dict()
+kofa_projects = dict()
+
+for pkg_name in AVAILABLE_KOFA_PROJECTS:
+    try:
+        name = pkg_name.split('.')[-1]
+        mod = sys.modules[pkg_name].__file__
+        packages[name] = os.path.dirname(mod)
+        kofa_projects[name] = os.path.dirname(os.path.dirname(os.path.dirname(
+            packages[name])))
+    except:
+        # not a package
+        pass
+
+print "PACKAGES: ", packages
+print "KOFA PROJECTS: ", kofa_projects
+
+def do_foo():
+    mydir = os.path.dirname(os.path.dirname(__file__))
+    print "MYDIR", mydir
+    #import pdb; pdb.set_trace()
+
+def enable_datafs(path, proj_name):
+    """Copy data.fs in `path` for kofa project `proj_name`.
+    """
+    pkg = packages[proj_name]
+    #src_path = os.path.join(
+
+def bootstrap():
+    """Run bootstrap and buildout for each of the KOFA instances
+    """
+    for name, path in kofa_projects.items():
+        bootstrap_instance(name, path)
+    return
+
+def bootstrap_instance(name, path):
+    """Run bootstrap.py and buildout for instance `name` at `path`.
+    """
+    print "WAEUP.STRESS: bootstrapping %s" % name
+    print "WAEUP.STRESS: path: %s" % path
+    old_cwd_ = os.getcwd()
+    os.chdir(path)
+    print "WAEUP.STRESS: bootstrapping %s" % name
+    subprocess.call(['python', 'bootstrap/bootstrap.py'])
+    print "WAEUP.STRESS: running buildout for %s" % name
+    subprocess.call(['./bin/buildout'], shell=True)
+    os.chdir(old_cwd_)
+    return
+
+def remove_zodb(name):
+    path = kofa_projects[name]
+    print "WAEUP.STRESS: removing ZODB of %s at %s" % (name, path)
+    filestorage_dir = os.path.join(path, 'var', 'filestorage')
+    for filename in os.listdir(filestorage_dir):
+        if filename in ['Data.fs', 'Data.fs.tmp', 'Data.fs.index',
+                        'Data.fs.lock']:
+            filepath = os.path.join(filestorage_dir,  filename)
+            print "  removing %s" % filepath
+            os.unlink(filepath)
+    return
+
+def start_instance(name):
+    path = kofa_projects[name]
+    print "WAEUP.STRESS: starting instance %s at %s" % (name, path)
+    old_cwd_ = os.getcwd()
+    os.chdir(path)
+    # yes, shell _is_ neccessary here
+    subprocess.call(
+        './bin/paster serve --daemon parts/etc/debug.ini', shell=True)
+    os.chdir(old_cwd_)
+    return
+
+def stop_instance(name):
+    path = kofa_projects[name]
+    print "WAEUP.STRESS: shutting down instance %s at %s" % (name, path),
+    old_cwd_ = os.getcwd()
+    os.chdir(path)
+    subprocess.call(
+        './bin/paster serve --stop-daemon', shell=True)
+    print "... done."
+    os.chdir(old_cwd_)
+    return
+
+def install_app(name):
+    path = kofa_projects[name]
+    old_cwd_ = os.getcwd()
+    os.chdir(path)
+    db_file = os.path.join(path, 'var', 'filestorage', 'Data.fs')
+    config_file = os.path.join(path, 'parts', 'etc', 'site.zcml')
+    #import waeup.kofa.meta
+    #import grok.meta
+    import grok
+    #print "META: ", grok.meta
+    import sys
+    #print "PATHS: ", sys.path
+    from zope.app.appsetup import database, config
+    #config(config_file)
+    #subprocess.call(
+    #    './bin/python-console -c "import grok.meta"', shell=True)
+    script = os.path.join(os.path.dirname(__file__), 'scripts.py')
+    subprocess.call(
+        './bin/python-console %s' % script, shell=True)
+    
+    os.chdir(old_cwd_)
Index: /main/waeup.stress/trunk/src/waeup/stress/scripts.py
===================================================================
--- /main/waeup.stress/trunk/src/waeup/stress/scripts.py	(revision 8654)
+++ /main/waeup.stress/trunk/src/waeup/stress/scripts.py	(revision 8654)
@@ -0,0 +1,69 @@
+import os
+import sys
+import transaction
+from zope.app.appsetup import database, config
+from zope.app.publication.zopepublication import ZopePublication
+from waeup.kofa.app import University
+
+#: The name under which any artificially created University instance
+#: will be put into the root folder
+APP_NAME = 'stress_app'
+
+def open_zodb(instance_path=''):
+    """Open a ZODB and configure it using a site.zcml file.
+
+    If `instance_path` is not given, we assume to be in the root of
+    some Zope instance and compute relative paths to
+    'parts/etc/site.zcml' and 'var/filestorage/Data.fs'.
+
+    If `instance_path` is given, these paths are os.path.joined with
+    it.
+
+    Returns a tuple containing a database object, an open connection
+    to this DB and the root folder.
+    """
+    config_file = os.path.join(
+        instance_path, 'parts', 'etc', 'site.zcml')
+    zodb_file = os.path.join(
+        instance_path, 'var', 'filestorage', 'Data.fs')
+    print "WAEUP.STRESS: configuring ZODB from %s / %s" % (
+        config_file, zodb_file)
+    context = config(config_file)
+    db = database(zodb_file)
+    conn = db.open()
+    root = conn.root()
+    root_folder = root.get(ZopePublication.root_name, None)
+    return db, conn, root_folder
+
+def close_zodb(db):
+    print "WAEUP.STRESS: closing ZODB"
+    transaction.commit() # make sure changes are saved
+    db.close()
+    return
+
+def zodb(func):
+    """A decorator that does a ZODB setup and calls the decorated
+    function with the ZODB root folder as first argument.
+    """
+    def wrapped(*args, **kw):
+        instance_path = kw.get('instance_path', '')
+        db, conn, root_folder = open_zodb(instance_path)
+        try:
+            func(root_folder, *args, **kw)
+        finally:
+            close_zodb(db)
+    return wrapped
+
+@zodb
+def create_app(root_folder):
+    """Create a University instance named 'stress_app' in ZODB.
+
+    The `root_folder` arg is injected by the decorator and represents
+    the root folder of an opened, writable ZODB.
+    """
+    print [x for x in root_folder]
+    root_folder['stress_app'] = University()
+    print "WAEUP.STRESS.SCRIPTS: created 'stress_app' in root folder."
+    return
+
+create_app()
