From 9d87ce571b9517abaf02cf58e0a3b1f976f00a07 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 4 Dec 2013 16:04:28 +0100 Subject: [PATCH] Add configuration aware test runner refs #5223 --- test/jenkins/README | 43 +++++++++++- test/jenkins/run-tests.py | 34 ---------- test/jenkins/run_tests.conf | 12 ++++ test/jenkins/run_tests.py | 131 ++++++++++++++++++++++++++++++++++++ test/jenkins/run_tests.sh | 5 ++ 5 files changed, 190 insertions(+), 35 deletions(-) delete mode 100755 test/jenkins/run-tests.py create mode 100644 test/jenkins/run_tests.conf create mode 100755 test/jenkins/run_tests.py create mode 100755 test/jenkins/run_tests.sh diff --git a/test/jenkins/README b/test/jenkins/README index 23495e8f2..ff0681810 100644 --- a/test/jenkins/README +++ b/test/jenkins/README @@ -1 +1,42 @@ -These scripts are used by build.icinga.org to set up a test VM. +Set of scripts to set up and test a virtual demo machine +======================================================== + +This directory contains a few scripts primarily used by build.icinga.org. + +* bootstrap-vm.sh + Ensures that all required software is installed and its configuration + is applied to the VM. (Usually not of interest for the typical user.) + +* run_tests.sh + This is a wrapper script intended to be ran manually by a user. + +* run_tests.py + The actual test-runner. Accepts one option (-C|--config) and expects + one or more filenames or -patterns that should be run on the VM. + +* run_tests.conf + The default configuration file for the test-runner. (Used when running + the wrapper script or when no custom configuration file is passed to the + test-runner.) + + Format: + - commands: This section is mandatory and contains the commands to use. + - settings: This section is mandatory and defines settings that are applied to + all tests. + - setups: This section is optional and contains setup routines that should + be ran before (setup) and after (teardown) any matching test is + executed. (Note that only one setup can be effective at a time.) + + Example: + "^v[1-9]\.test$": { + "setup": { + "copy": ["source >> target"], // Files that should be copied. + // Note that these files remain + // if not removed explicitly + "clean": ["target"], // Files to delete from the system + "exec": ["cmd1", "cmd2"] // Commands to execute on the system + }, + "teardown": { + // The same kind of instructions as above can be added here + } + } diff --git a/test/jenkins/run-tests.py b/test/jenkins/run-tests.py deleted file mode 100755 index e96395553..000000000 --- a/test/jenkins/run-tests.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -import sys -from xml.dom.minidom import getDOMImplementation -from subprocess import Popen, PIPE - -impl = getDOMImplementation() -result = impl.createDocument(None, "testsuite", None) -testsuite = result.documentElement - -for fn in sys.argv[1:]: - process = Popen(["./" + fn], stdout=PIPE, stderr=PIPE) - (stdoutdata, stderrdata) = process.communicate() - - testcase = result.createElement("testcase") - testcase.setAttribute("classname", "vm") - testcase.setAttribute("name", fn) - - systemout = result.createElement("system-out") - systemout.appendChild(result.createTextNode(stdoutdata)) - testcase.appendChild(systemout) - - systemerr = result.createElement("system-err") - systemerr.appendChild(result.createTextNode(stderrdata)) - testcase.appendChild(systemerr) - - if process.returncode != 0: - failure = result.createElement("failure") - failure.setAttribute("type", "returncode") - failure.appendChild(result.createTextNode("code: " + str(process.returncode))) - testcase.appendChild(failure) - - testsuite.appendChild(testcase) - -print result.toxml() diff --git a/test/jenkins/run_tests.conf b/test/jenkins/run_tests.conf new file mode 100644 index 000000000..87a7000ad --- /dev/null +++ b/test/jenkins/run_tests.conf @@ -0,0 +1,12 @@ +{ + "commands": { + "copy": "scp -qF ssh_config {0} default:{1}", + "exec": "ssh -F ssh_config default {0}", + "clean": "ssh -F ssh_config default rm -f {0}" + }, + "tests": { + "destination": "/tmp" + }, + "setups": { + } +} diff --git a/test/jenkins/run_tests.py b/test/jenkins/run_tests.py new file mode 100755 index 000000000..12612b514 --- /dev/null +++ b/test/jenkins/run_tests.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +from __future__ import unicode_literals + + +import os +import re +import sys +import json +import glob +import subprocess +from optparse import OptionParser +from xml.dom.minidom import getDOMImplementation + + +class TestSuite(object): + def __init__(self, configpath): + self._tests = [] + self._results = {} + + self.load_config(configpath) + + def add_test(self, filepath): + self._tests.append(filepath) + + def load_config(self, filepath): + with open(filepath) as f: + self._config = json.load(f) + + def get_report(self): + dom = getDOMImplementation() + document = dom.createDocument(None, 'testsuite', None) + xml_root = document.documentElement + + for name, info in self._results.iteritems(): + testresult = document.createElement('testcase') + testresult.setAttribute('classname', 'vm') + testresult.setAttribute('name', name) + + systemout = document.createElement('system-out') + systemout.appendChild(document.createTextNode(info['stdout'])) + testresult.appendChild(systemout) + + systemerr = document.createElement('system-err') + systemerr.appendChild(document.createTextNode(info['stderr'])) + testresult.appendChild(systemerr) + + if info['returncode'] != 0: + failure = document.createElement('failure') + failure.setAttribute('type', 'returncode') + failure.appendChild(document.createTextNode( + 'code: {0}'.format(info['returncode']))) + testresult.appendChild(failure) + + xml_root.appendChild(testresult) + + return document.toxml() + + def run(self): + for path in self._tests: + test_name = os.path.basename(path) + self._apply_setup_routines(test_name, 'setup') + self._copy_test(path) + self._results[test_name] = self._run_test(path) + self._apply_setup_routines(test_name, 'teardown') + + def _apply_setup_routines(self, test_name, context): + instructions = next((t[1].get(context) + for t in self._config.get('setups', {}).iteritems() + if re.match(t[0], test_name)), None) + if instructions is not None: + for instruction in instructions.get('copy', []): + source, _, destination = instruction.partition('>>') + self._copy_file(source.strip(), destination.strip()) + for filepath in instructions.get('clean', []): + self._remove_file(filepath) + for command in instructions.get('exec', []): + self._exec_command(command) + + def _remove_file(self, path): + command = self._config['commands']['clean'].format(path) + subprocess.call(command, shell=True) + + def _exec_command(self, command): + command = self._config['commands']['exec'].format(command) + subprocess.call(command, shell=True) + + def _copy_file(self, source, destination): + command = self._config['commands']['copy'].format(source, destination) + subprocess.call(command, shell=True) + + def _copy_test(self, path): + self._copy_file(path, os.path.join(self._config['tests']['destination'], + os.path.basename(path))) + + def _run_test(self, path): + command = self._config['commands']['exec'] + target = os.path.join(self._config['tests']['destination'], + os.path.basename(path)) + p = subprocess.Popen(command.format(target), stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=True) + out, err = p.communicate() + + return { + 'stdout': out.decode('utf-8'), + 'stderr': err.decode('utf-8'), + 'returncode': p.returncode + } + + +def parse_commandline(): + parser = OptionParser(version='0.1') + parser.add_option('-C', '--config', default="run_tests.conf", + help='The path to the config file to use [%default]') + return parser.parse_args() + + +def main(): + options, arguments = parse_commandline() + suite = TestSuite(options.config) + + for path in (p for a in arguments for p in glob.glob(a)): + suite.add_test(path) + + suite.run() + print suite.get_report().encode('utf-8') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) + diff --git a/test/jenkins/run_tests.sh b/test/jenkins/run_tests.sh new file mode 100755 index 000000000..3ec5ddd80 --- /dev/null +++ b/test/jenkins/run_tests.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +vagrant ssh-config > ssh_config +./run_tests.py *.test +rm -f ssh_config