[Zope-CVS] CVS: Packages/autotester - README.txt:1.1 autotest.py:1.1 autotest.xml:1.1 zconfig.conf:1.1 zope.conf:1.1

Fred L. Drake, Jr. fred@zope.com
Thu, 3 Jul 2003 17:51:04 -0400


Update of /cvs-repository/Packages/autotester
In directory cvs.zope.org:/tmp/cvs-serv25630

Added Files:
	README.txt autotest.py autotest.xml zconfig.conf zope.conf 
Log Message:
Simple autotester micro-app.

=== Added File Packages/autotester/README.txt ===
=====================
Automated Test Runner
=====================

This is a very simple automated test runner suitable for use with
cron.  The tester uses a ZConfig-based configuration file to determine
what code needs to be checked out, how to build it, how to run the
tests, and where to send email reporting the results.


Requirements
------------

This script requires Python, the logging package (available as part of
the standard library starting with Python 2.3), and ZConfig (see
http://www.zope.org/Members/fdrake/zconfig/ for more information).


Documentation
-------------

There will be some.


What's Here
-----------

autotest.py
    The test runner.

autotest.xml
    Configuration schema for ZConfig.

zconfig.conf
    Sample configuration file that runs the tests for ZConfig.

zope.conf
    More elaborate example that runs the Zope 2 and Zope 3 unit tests,
    and the Zope 3 functional tests.


Credits
-------

This code was initially written by Jeremy Hylton, and has been further
munged by Fred Drake.


=== Added File Packages/autotester/autotest.py ===
"""Simple script to run checkout, build, and run tests."""

msg_body = """\
Email automatically generated by a daily test runner.
"""

import getopt
import logging
import os
import re
import shutil
import smtplib
import sys
import tempfile

from distutils.util import get_platform

import ZConfig

TESTING = False


class TestEmailHandler(logging.Handler):
    """Collect logging events to send in email.

    Email is sent when the email() method is called.
    """

    def __init__(self, config, begin=None):
        logging.Handler.__init__(self)
        self.smtp_server = config.smtp_server
        self.sender = config.sender
        self.from_ = config.from_
        self.recipients = config.to
        self.subject = config.subject
        self.buffer = []
        if begin:
            self.buffer.append(begin)

    def emit(self, rec):
        self.buffer.append(rec.getMessage())

    def get_status(self):
        # Assume the tests were run by unittest which prints a
        # summary like
        # "OK"
        # or
        # "FAILED (errors=7)"
        # as the last line of output.
        return self.buffer[-1]

    def email(self):
        status = self.get_status()
        subject = "%s: %s" % (status, self.subject)
        body = "\n".join(self.buffer)
        self.buffer = []
        recipients = ",\n    ".join(self.recipients)
        msg = ("From: %s\r\n"
               "To: %s\r\n"
               "Subject: %s\r\n"
               "\r\n"
               "%s" % (self.from_, recipients, subject, body))
        if self.sender:
            msg = ("Sender: %s\r\n" % self.sender) + msg
        server, port = self.smtp_server
        port = port or 0
        if not TESTING:
            s = smtplib.SMTP(server, port)
            s.sendmail(self.sender or self.from_, self.recipients, msg)
            s.quit()
        else:
            print '-'*78
            print msg
            print '-'*78


class Test:

    def __init__(self, config):
        self.config = config
        self.python = config.python
        self.configLog()

    def configLog(self):
        self.logger = logging.getLogger("autotest")
        self.logger.setLevel(logging.INFO)
        if TESTING:
            fh = logging.StreamHandler(sys.stdout)
        elif self.config.log:
            fh = logging.FileHandler(self.config.log)
        self.logger.addHandler(fh)

    def execute(self, cmd, logger=None):
        """Run a command, logging stdout and stderr."""
        if logger is None:
            logger = self.logger
        logger.info(cmd)
        fin, fout = os.popen4(cmd)
        fin.close()
        for line in fout:
            line = line.rstrip()
            # XXX hack to deal with debug build of Python
            if re.match("\[\d+ refs\]", line):
                continue
            logger.info(line)
        err = fout.close()
        if err is not None:
            raise ValueError, err

    def checkout(self, checkout):
        self.chdir(self.rootdir)
        options = []
        if checkout.tag:
            options.append("-r %s" % checkout.tag)
        if checkout.dest:
            options.append("-d %s" % checkout.dest)
        options = " ".join(options)
        if options:
            cmd = "cvs -d%%(repository)s co %s %%(module)s" % options
        else:
            cmd = "cvs -d%(repository)s co %(module)s"
        self.execute(cmd % checkout.__dict__)

    def build(self, checkout):
        dir = checkout.dest or checkout.module
        self.chdir(os.path.join(self.rootdir, dir))
        self.execute("%s -u setup.py %s" % (self.python, checkout.setup))

    def get_platspec(self):
        """Return self.python and find out what version it is."""
        cmd = self.python + ' -c "import sys; print sys.version[0:3]"'
        self.logger.info(cmd)
        f = os.popen(cmd)
        s = f.read().strip()
        f.close()
        return "%s-%s" % (get_platform(), s)

    def test(self, test):
        if test.log:
            logger = logging.getLogger("autotest.test")
            fh = logging.FileHandler(test.log)
            logger.addHandler(fh)
        else:
            logger = None

        self.chdir(os.path.join(self.rootdir, test.dir))
        path = []
        for dir in test.pythonpath:
            path.append(os.path.join(self.rootdir, dir))
        if test.pythonpath_build:
            platspec = self.get_platspec()
        for dir in test.pythonpath_build:
            build_dir = os.path.join(dir, "build", "lib.%s" % platspec)
            path.append(os.path.join(self.rootdir, build_dir))
        path = "PYTHONPATH=" + os.pathsep.join(path)
        self.execute("%s %s -u %s" % (path, self.python, test.script),
                     logger)

    def chdir(self, dir):
        self.logger.info("cd %s" % dir)
        os.chdir(dir)

    def go(self):
        prev_dir = os.getcwd()
        self.rootdir = tempfile.mkdtemp(prefix="autotest")
        self.logger.info("running in %s" % self.rootdir)
        self.chdir(self.rootdir)

        for checkout in self.config.checkouts:
            self.checkout(checkout)
            if checkout.post:
                self.execute(checkout.post)
            self.build(checkout)

        for test in self.config.tests:
            email_config = merge_email(self.config.email, test.email)
            eh = TestEmailHandler(email_config, msg_body)
            self.logger.addHandler(eh)
            self.test(test)
            eh.email()
            self.logger.removeHandler(eh)

        self.chdir(prev_dir)
        self.logger.info("deleting %s" % self.rootdir)
        shutil.rmtree(self.rootdir)


def merge_email(source, dest):
    if dest is None:
        dest = source
    else:
        # Update dest with information from source:
        if dest.smtp_server is None:
            dest.smtp_server = source.smtp_server
        if dest.sender is None:
            dest.sender = source.sender
        if dest.from_ is None:
            dest.from_ = source.from_
        if not dest.to:
            dest.to = source.to
        if dest.subject is None:
            dest.subject = source.subject
    # Check that we have required values:
    if not dest.smtp_server:
        dest.smtp_server = "localhost", 0
    if not dest.from_:
        raise ZConfig.ConfigurationError("email from address is required")
    if not dest.to:
        raise ZConfig.ConfigurationError("email to address is required")
    if not dest.subject:
        dest.subject = "Test Result"
    return dest


def main(args=None):
    global TESTING
    if args is None:
        args = sys.argv[1:]

    opts, args = getopt.getopt(args, "t", ["testing"])
    for opt, val in opts:
        if opt in ("-t", "--testing"):
            TESTING = True

    if len(args) > 1:
        print >>sys.stderr, "Too many command line arguments!"
        sys.exit(2)
    if args:
        configpath = args[0]
        default_logfile = os.path.splitext(configpath)[0] + ".log"
    else:
        configpath = "autotest.conf"
        default_logfile = "autotest.log"
    schemapath = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
                              "autotest.xml")
    schema = ZConfig.loadSchema(schemapath)
    config, _ = ZConfig.loadConfig(schema, configpath)

    for u in config.units:
        if not u.log:
            u.log = default_logfile
        u.email = merge_email(config.email, u.email)
        Test(u).go()


if __name__ == "__main__":
    main()


=== Added File Packages/autotester/autotest.xml ===
<schema>

  <sectiontype name="email">
    <key      name="smtp-server" required="no" default="localhost"
              datatype="inet-address" />
    <key      name="from"        required="no" attribute="from_" />
    <key      name="sender"      required="no" />
    <multikey name="to"          required="no" />
    <key      name="subject"     required="no" default="" />
  </sectiontype>

  <sectiontype name="checkout">
    <key name="repository" required="yes" />
    <key name="module"     required="yes" />
    <key name="tag"        required="no"  default="" />
    <key name="dest"       required="no"  default="" />
    <key name="setup"      required="no"  default="build" />
    <key name="post"       required="no"  default="" />
  </sectiontype>

  <sectiontype name="test">
    <key      name="dir"              required="yes" />
    <key      name="script"           required="yes" />
    <key      name="log"              required="no" />
    <multikey name="pythonpath"       required="no" />
    <multikey name="pythonpath-build" required="no" />
    <section  name="*" type="email" attribute="email" />
  </sectiontype>

  <sectiontype name="unit">
    <key name="python" default="/usr/local/bin/python" required="no" />
    <key name="log" datatype="existing-dirpath" />
    <section name="*" attribute="email" type="email" />
    <multisection name="*" attribute="checkouts" type="checkout"
                  required="yes" />
    <multisection name="*" attribute="tests" type="test"
                  required="yes" />
  </sectiontype>

  <section name="*" type="email" attribute="email" />
  <multisection name="*" type="unit" attribute="units" />

</schema>


=== Added File Packages/autotester/zconfig.conf ===
# This is an example configuration file.
# It checks out the ZConfig implementation and runs it's unit tests.

<email>
  # change these to something realistic:
  from me@example.com
  to you@example.com
</email>

<unit>
  python /usr/local/bin/python2.1

  <email>
    subject ZConfig unit test results
  </email>

  <checkout>
    repository :pserver:anonymous@cvs.zope.org:/cvs-repository
    module StandaloneZConfig
    dest ZConfig-turnk
  </checkout>

  <test>
    dir ZConfig-turnk/ZConfig/tests
    pythonpath-build ../..
    script runtests.py
  </test>
</unit>


=== Added File Packages/autotester/zope.conf ===
<email>
  from         test-runner@example.com
</email>

<unit Zope2>
  python  /usr/local/bin/python2.2

  <email>
    subject   Zope 2 tests on Linux
    to        zope-coders@zope.org
  </email>

  <checkout>
    repository  :ext:cvs.zope.org:/cvs-repository
    module      Zope
    setup       build_ext -i
  </checkout>

  <test>
    dir         Zope
    pythonpath  lib/python
    script      test.py
  </test>
</unit>

<unit Zope3>
  python  /usr/local/bin/python2.2

  <email>
    to    zope3-dev@zope.org
  </email>

  <checkout>
    repository  :ext:cvs.zope.org:/cvs-repository
    module      Zope3
    setup       build_ext -i
  </checkout>

  <test>
    <email>
      subject   Zope 3 unit tests on Linux
    </email>
    dir         Zope3
    pythonpath  src
    script      test.py
  </test>

  <test>
    <email>
      subject   Zope 3 functional tests on Linux
    </email>
    dir         Zope3
    pythonpath  src
    script      test.py -f
  </test>
</unit>