From 93ae56bedb2fad9b05fe17f271be7e941076a465 Mon Sep 17 00:00:00 2001 From: Michael Ziegler Date: Mon, 21 Dec 2009 13:31:12 +0100 Subject: [PATCH] add test runner and automate starting of the various Murmur versions --- pyweb/mumble/murmurenvutils.py | 128 +++++++++++++++++++++++++++++++++ pyweb/mumble/tests.py | 49 +++++++++++++ pyweb/settings.py | 4 ++ 3 files changed, 181 insertions(+) create mode 100644 pyweb/mumble/murmurenvutils.py diff --git a/pyweb/mumble/murmurenvutils.py b/pyweb/mumble/murmurenvutils.py new file mode 100644 index 0000000..2df8571 --- /dev/null +++ b/pyweb/mumble/murmurenvutils.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +""" + * Copyright (C) 2009, Michael "Svedrin" Ziegler + * + * Mumble-Django is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +""" + +import os, subprocess, signal +from os.path import join, exists +from shutil import copyfile + +from django.conf import settings + + +def get_available_versions(): + """ Return murmur versions installed inside the LAB_DIR. """ + dirs = os.listdir( settings.TEST_MURMUR_LAB_DIR ); + dirs.sort(); + return dirs; + + +def run_callback( version, callback, *args, **kwargs ): + """ Initialize the database and run murmur, then call the callback. + After the callback has returned, kill murmur. + + The callback will be passed the Popen object that wraps murmur, + and any arguments that were passed to run_callback. + + If the callback raises an exception, murmur will still be properly + shutdown and the exception will be reraised. + + The callback can either return an arbitrary value, or a tuple. + If it returns a tuple, it must be of the form: + + ( intended_return_value, call_update_dbase ) + + That means: If the second value evaluates to True, update_dbase + will be called; the first value will be returned by run_callback. + + If the callback returns anything other than a tuple, that value + will be returned directly. + + So, If run_callback should return a tuple, you will need to return + the tuple form mentioned above in the callback, and put your tuple + into the first parameter. + """ + + murmur_root = join( settings.TEST_MURMUR_LAB_DIR, version ); + if not exists( murmur_root ): + raise EnvironmentError( "This version could not be found: '%s' does not exist!" % murmur_root ); + + init_dbase( version ); + + process = run_murmur( version ); + + try: + result = callback( process, *args, **kwargs ); + except Exception, err: + raise err; + else: + if type(result) == tuple: + if result[1]: + update_dbase( version ); + return result[0]; + else: + return result; + finally: + kill_murmur( process ); + + +def init_dbase( version ): + """ Initialize Murmur's database by copying the one from FILES_DIR. """ + dbasefile = join( settings.TEST_MURMUR_FILES_DIR, "murmur-%s.db3" % version ); + if not exists( dbasefile ): + raise EnvironmentError( "This version could not be found: '%s' does not exist!" % dbasefile ); + murmurfile = join( settings.TEST_MURMUR_LAB_DIR, version, "murmur.sqlite" ); + copyfile( dbasefile, murmurfile ); + + +def update_dbase( version ): + """ Copy Murmur's database to FILES_DIR (the inverse of init_dbase). """ + murmurfile = join( settings.TEST_MURMUR_LAB_DIR, version, "murmur.sqlite" ); + if not exists( murmurfile ): + raise EnvironmentError( "Murmur's database could not be found: '%s' does not exist!" % murmurfile ); + dbasefile = join( settings.TEST_MURMUR_FILES_DIR, "murmur-%s.db3" % version ); + copyfile( dbasefile, target ); + + +def run_murmur( version ): + """ Run the given Murmur version as a subprocess. + + Either returns a Popen object or raises an EnvironmentError. + """ + + murmur_root = join( settings.TEST_MURMUR_LAB_DIR, version ); + if not exists( murmur_root ): + raise EnvironmentError( "This version could not be found: '%s' does not exist!" % murmur_root ); + + binary_candidates = ( 'murmur.64', 'murmur.x86', 'murmurd' ); + + files = os.listdir( murmur_root ); + + for binname in binary_candidates: + if exists( join( murmur_root, binname ) ): + process = subprocess.Popen( + ( join( murmur_root, binname ), '-fg' ), + stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + cwd=murmur_root + ); + return process; + + raise EnvironmentError( "Murmur binary not found. (Tried %s)" % unicode(binary_candidates) ); + + +def kill_murmur( process ): + """ Send a sigterm to the given process. """ + return os.kill( process.pid, signal.SIGTERM ); + + diff --git a/pyweb/mumble/tests.py b/pyweb/mumble/tests.py index 2e9290f..0037a8b 100644 --- a/pyweb/mumble/tests.py +++ b/pyweb/mumble/tests.py @@ -14,11 +14,15 @@ * GNU General Public License for more details. """ +import os + from django.test import TestCase +from django.test.simple import run_tests as django_run_tests from django.conf import settings from models import * from utils import ObjectInfo +from murmurenvutils import get_available_versions, run_callback class InstancesHandling( TestCase ): @@ -196,7 +200,52 @@ class DataReading( TestCase ): self.assert_( hasattr( grp, "add" ) ); self.assert_( hasattr( grp, "remove" ) ); self.assert_( hasattr( grp, "members" ) ); + + +def run_tests( test_labels, verbosity=1, interactive=True, extra_tests=[] ): + """ Run the Django built in testing framework, but before testing the mumble + app, allow Murmur to be set up correctly. + """ + + if not test_labels: + test_labels = [ appname.split('.')[-1] for appname in settings.INSTALLED_APPS ]; + + # No need to sync any murmur servers for the other apps + os.environ['MURMUR_CONNSTR'] = ''; + + # The easy way: mumble is not being tested. + if "mumble" not in test_labels: + return django_run_tests( test_labels, verbosity, interactive, extra_tests ); + + # First run everything apart from mumble. mumble will be tested separately, so Murmur + # can be set up properly first. + test_labels.remove( "mumble" ); + failed_tests = django_run_tests( test_labels, verbosity, interactive, extra_tests ); + + failed_tests += run_mumble_tests( verbosity, interactive ); + + return failed_tests; +def run_mumble_tests( verbosity=1, interactive=True ): + + connstrings = { + 'DBus': 'net.sourceforge.mumble.murmur', + 'Ice': 'Meta:tcp -h 127.0.0.1 -p 6502', + }; + + failed_tests = 0; + + def django_run_tests_wrapper( process ): + return django_run_tests( ('mumble',), verbosity, interactive, [] ), False; + + for version in get_available_versions(): + for method in connstrings: + print "Testing mumble %s via %s" % ( version, method ); + os.environ['MURMUR_CONNSTR'] = connstrings[method]; + settings.DEFAULT_CONN = connstrings[method]; + failed_tests += run_callback( version, django_run_tests_wrapper ); + + return failed_tests; diff --git a/pyweb/settings.py b/pyweb/settings.py index e4ff527..76c0ffa 100644 --- a/pyweb/settings.py +++ b/pyweb/settings.py @@ -180,6 +180,10 @@ TEMPLATE_DIRS = ( join( MUMBLE_DJANGO_ROOT, 'template' ), ) +TEST_RUNNER = 'mumble.tests.run_tests' +TEST_MURMUR_LAB_DIR = join( dirname(MUMBLE_DJANGO_ROOT), 'murmur' ); +TEST_MURMUR_FILES_DIR = join( MUMBLE_DJANGO_ROOT, 'testdata' ); + INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.admin',