diff options
author | Keith Wall <kwall@apache.org> | 2011-11-09 19:44:55 +0000 |
---|---|---|
committer | Keith Wall <kwall@apache.org> | 2011-11-09 19:44:55 +0000 |
commit | c1d79073a5ef4f55f9e6f3b723cbc9d0ef6910a7 (patch) | |
tree | 0006f1852809fb4b68f2441fb3bdf66ab6f12527 | |
parent | a25345ca75d0134c372e274dff15b9ff291ac853 (diff) | |
download | qpid-python-c1d79073a5ef4f55f9e6f3b723cbc9d0ef6910a7.tar.gz |
QPID-3552: Changes to allow running of python test on Jenkins. New --xml argument now understood by qpid-python-test that produces test output compatible with Junit-like tools. New Ant script to control start/stop of Java/Cpp Brokers and the running of qpid-python-test.
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1199932 13f79535-47bb-0310-9956-ffa450edef68
-rwxr-xr-x | qpid/python/qpid-python-test | 68 | ||||
-rw-r--r-- | qpid/python/qpid-python-test-ant.xml | 164 |
2 files changed, 223 insertions, 9 deletions
diff --git a/qpid/python/qpid-python-test b/qpid/python/qpid-python-test index a47f633565..1a0f711ace 100755 --- a/qpid/python/qpid-python-test +++ b/qpid/python/qpid-python-test @@ -64,6 +64,8 @@ parser.add_option("-t", "--time", action="store_true", default=False, help="report timing information on test run") parser.add_option("-D", "--define", metavar="DEFINE", dest="defines", action="append", default=[], help="define test parameters") +parser.add_option("-x", "--xml", metavar="XML", dest="xml", + help="write test results in Junit style xml suitable for use by CI tools etc") class Config: @@ -188,6 +190,33 @@ def indent(text): lines = text.split("\n") return " %s" % "\n ".join(lines) +# Write a 'minimal' Junit xml style report file suitable for use by CI tools such as Jenkins. +class JunitXmlStyleReporter: + + def __init__(self, file): + self.f = open(file, "w"); + + def begin(self): + self.f.write('<?xml version="1.0" encoding="UTF-8" ?>\n') + self.f.write('<testsuite>\n') + + def report(self, name, result): + parts = name.split(".") + method = parts[-1] + module = '.'.join(parts[0:-1]) + self.f.write('<testcase classname="%s" name="%s" time="%f">\n' % (module, method, result.time)) + if result.failed: + self.f.write('<failure>\n') + self.f.write('<![CDATA[\n') + self.f.write(result.exceptions) + self.f.write(']]>\n') + self.f.write('</failure>\n') + self.f.write('</testcase>\n') + + def end(self): + self.f.write('</testsuite>\n') + self.f.close() + class Interceptor: def __init__(self): @@ -327,13 +356,14 @@ class Runner: else: return None - def print_exceptions(self): + def get_formatted_exceptions(self): for name, info in self.exceptions: if issubclass(info[0], Skipped): - print indent("".join(traceback.format_exception_only(*info[:2]))).rstrip() + output = indent("".join(traceback.format_exception_only(*info[:2]))).rstrip() else: - print "Error during %s:" % name - print indent("".join(traceback.format_exception(*info))).rstrip() + output = "Error during %s:" % name + output += indent("".join(traceback.format_exception(*info))).rstrip() + return output ST_WIDTH = 8 @@ -361,20 +391,31 @@ def run_test(name, test, config): sys.stdout.write(output) sys.stdout.flush() interceptor.begin() + start = time.time() try: runner = test() finally: interceptor.reset() + end = time.time() if interceptor.dirty: if interceptor.last != "\n": sys.stdout.write("\n") sys.stdout.write(output) print " %s" % colorize_word(runner.status()) if runner.failed() or runner.skipped(): - runner.print_exceptions() + print runner.get_formatted_exceptions() root.setLevel(level) filter.patterns = patterns - return runner.status() + return TestResult(end - start, runner.passed(), runner.skipped(), runner.failed(), runner.get_formatted_exceptions()) + +class TestResult: + + def __init__(self, time, passed, skipped, failed, exceptions): + self.time = time + self.passed = passed + self.skipped = skipped + self.failed = failed + self.exceptions = exceptions class FunctionTest: @@ -526,6 +567,10 @@ filtered = [t for t in h.tests if is_included(t.name())] ignored = [t for t in h.tests if is_ignored(t.name())] total = len(filtered) + len(ignored) +if opts.xml and not list_only: + xmlr = JunitXmlStyleReporter(opts.xml); + xmlr.begin(); + passed = 0 failed = 0 skipped = 0 @@ -535,11 +580,13 @@ for t in filtered: print t.name() else: st = t.run() - if st == PASS: + if xmlr: + xmlr.report(t.name(), st) + if st.passed: passed += 1 - elif st == SKIP: + elif st.skipped: skipped += 1 - elif st == FAIL: + elif st.failed: failed += 1 if opts.hoe: break @@ -581,6 +628,9 @@ if not list_only: colorize_word("average", "%.2fs average" % ((end - start)/run))] print ", ".join(timing) +if xmlr: + xmlr.end() + if failed or skipped: sys.exit(1) else: diff --git a/qpid/python/qpid-python-test-ant.xml b/qpid/python/qpid-python-test-ant.xml new file mode 100644 index 0000000000..6a74e6ac2b --- /dev/null +++ b/qpid/python/qpid-python-test-ant.xml @@ -0,0 +1,164 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> + +<project name="qpid-python-test-ant" default="test" > + + <!-- Ant wrapper around qpid-python-test. Starts Qpid broker; runs + qpid-python-test, and formats the test output. --> + + <!-- Directories etc --> + <property name="python.dir" value="${basedir}"/> + <property name="qpid.root.dir" value="${basedir}/.."/> + <property name="java.dir" value="${basedir}/../java"/> + <property name="cpp.dir" value="${basedir}/../cpp"/> + <property name="build.dir" value="${python.dir}/build"/> + <property name="test.results.dir" value="${build.dir}/results"/> + <property name="test.work.dir" value="${build.dir}/work"/> + + <!-- Qpid Broker Executable/Url/Port --> + <property name="qpid.port" value="15672"/> + <property name="qpid.python.broker.url" value="amqp://guest/guest@localhost:${qpid.port}"/> + <property name="qpid.executable" value="${java.dir}/build/bin/qpid-server"/> + <property name="qpid.executable.args" value="-p ${qpid.port}"/> + + <!-- Additional modules to be added to command. Property must include -M --> + <property name="python.test.modules" value=""/> + <!-- Ignore file. Property must include -I --> + <property name="python.test.ignore" value=""/> + + <!-- Time to wait for socket to be bound --> + <property name="ensurefree.maxwait" value="1000"/> + <property name="start.maxwait" value="10000"/> + <property name="stop.maxwait" value="10000"/> + <property name="socket.checkevery" value="1000"/> + + <!-- Success message --> + <property name="passed.message" value=" 0 failed"/> + + + <target name="test" depends="clean, init, ensure-port-free, start-broker, run-tests, stop-broker, kill-broker, report"/> + + <target name="init"> + <mkdir dir="${test.results.dir}"/> + <mkdir dir="${test.work.dir}"/> + </target> + + <target name="clean"> + <delete dir="${test.results.dir}"/> + <delete dir="${test.work.dir}"/> + </target> + + <target name="ensure-port-free" depends="init" unless="skip.ensure-port-free"> + <await-port-free port="${qpid.port}" maxwait="${ensurefree.maxwait}" checkevery="${socket.checkevery}" timeoutproperty="ensurefree.timeout"/> + <fail message="Broker port ${qpid.port} is not free" if="ensurefree.timeout"/> + </target> + + <target name="start-broker" depends="init"> + <echo>Starting Qpid with ${qpid.executable} ${qpid.executable.args}</echo> + <exec executable="${qpid.executable}" spawn="true"> + <env key="QPID_WORK" value="${test.work.dir}"/> + <arg line="${qpid.executable.args}"/> + </exec> + + <await-port-bound port="${qpid.port}" maxwait="${start.maxwait}" checkevery="${socket.checkevery}" timeoutproperty="start.timeout"/> + </target> + + <target name="stop-broker" depends="init"> + <get-pid port="${qpid.port}" targetProperty="pid"/> + <echo>Stopping Qpid ${pid}</echo> + <kill-pid pid="${pid}" signo="-15"/> + + <await-port-free port="${qpid.port}" maxwait="${stop.maxwait}" checkevery="${socket.checkevery}" timeoutproperty="stop.timeout"/> + </target> + + <target name="kill-broker" depends="init" if="stop.timeout"> + <get-pid port="${qpid.port}" targetProperty="pid"/> + <echo>Killing Qpid ${pid}</echo> + <kill-pid pid="${pid}" signo="-9"/> + </target> + + <target name="run-tests" depends="init" unless="start.timeout"> + <echo>Running test-suite</echo> + <exec executable="${python.dir}/qpid-python-test" output="${test.results.dir}/results.out" error="${test.results.dir}/results.err"> + <env key="PYTHONPATH" value="${qpid.root.dir}/tests/src/py:${qpid.root.dir}/extras/qmf/src/py"/> + <arg line="-b ${qpid.python.broker.url} -x ${test.results.dir}/TEST-python.xml ${python.test.modules} ${python.test.ignore}"/> + </exec> + + <condition property="tests.passed"> + <isfileselected file="${test.results.dir}/results.out"> + <contains text="${passed.message}"/> + </isfileselected> + </condition> + </target> + + <target name="report" depends="init" unless="tests.passed"> + <fail message="Test(s) failed" unless="tests.passed"/> + <echo message="Test(s) passed" if="tests.passed"/> + </target> + + <macrodef name="get-pid"> + <attribute name="targetProperty"/> + <attribute name="port"/> + <sequential> + <exec executable="lsof" outputproperty="@{targetProperty}"> + <arg value="-t"/> <!-- Terse output --> + <arg value="-i"/> <arg value=":@{port}"/> + </exec> + </sequential> + </macrodef> + + <macrodef name="kill-pid"> + <attribute name="pid"/> + <attribute name="signo"/> + <sequential> + <exec executable="kill"> + <arg value="@{signo}"/> + <arg value="@{pid}"/> + </exec> + </sequential> + </macrodef> + + <macrodef name="await-port-free"> + <attribute name="maxwait"/> + <attribute name="checkevery"/> + <attribute name="timeoutproperty"/> + <attribute name="port"/> + <sequential> + <waitfor maxwait="@{maxwait}" maxwaitunit="millisecond" checkevery="@{checkevery}" checkeveryunit="millisecond" timeoutproperty="@timeoutproperty"> + <not> + <socket server="localhost" port="@{port}"/> + </not> + </waitfor> + </sequential> + </macrodef> + + <macrodef name="await-port-bound"> + <attribute name="maxwait"/> + <attribute name="checkevery"/> + <attribute name="timeoutproperty"/> + <attribute name="port"/> + <sequential> + <waitfor maxwait="@{maxwait}" maxwaitunit="millisecond" checkevery="@{checkevery}" checkeveryunit="millisecond" timeoutproperty="@timeoutproperty"> + <socket server="localhost" port="@{port}"/> + </waitfor> + </sequential> + </macrodef> +</project> |