Your message dated Thu, 17 Nov 2016 21:48:43 +0000
with message-id <[email protected]>
and subject line Bug#710895: fixed in freeradius 3.0.12+dfsg-4
has caused the Debian Bug report #710895,
regarding freeradius: Added autopkgtests
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact [email protected]
immediately.)


-- 
710895: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=710895
Debian Bug Tracking System
Contact [email protected] with problems
--- Begin Message ---
Package: freeradius
Version: 2.1.12+dfsg-1.2
Severity: wishlist
Tags: patch
User: [email protected]
Usertags: origin-ubuntu  ubuntu-patch

Added autopkgtests


*** /tmp/tmpF5A_wT/bug_body

Improve QA of packages


  * d/tests: added autopkgtests


Thanks for considering the patch.


-- System Information:
Debian Release: wheezy/sid
  APT prefers saucy-updates
  APT policy: (500, 'saucy-updates'), (500, 'saucy-security'), (500, 'saucy')
Architecture: amd64 (x86_64)

Kernel: Linux 3.8.0-14-generic (SMP w/1 CPU core)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
=== modified file 'debian/changelog'

=== modified file 'debian/control'
--- debian/control	2012-12-29 00:54:44 +0000
+++ debian/control	2013-06-03 10:19:09 +0000
@@ -25,6 +25,7 @@
 Uploaders: Stephen Gran <[email protected]>, Mark Hymers <[email protected]>
 Standards-Version: 3.9.2
 Homepage: http://www.freeradius.org/
+XS-Testsuite: autopkgtest
 
 Package: freeradius
 Architecture: any

=== added directory 'debian/tests'
=== added file 'debian/tests/clients'
--- debian/tests/clients	1970-01-01 00:00:00 +0000
+++ debian/tests/clients	2013-06-03 10:17:04 +0000
@@ -0,0 +1,34 @@
+#!/bin/bash
+#-------------------------
+# Testing client utilities
+#-------------------------
+set -e
+
+HELP_CLIENTS=('radsniff')
+for client in "${HELP_CLIENTS[@]}"; do
+    RET=$($client -h 2>&1 > /dev/null)
+
+    if [[ $RET ]]; then
+        echo "ERROR, ${client} is not running"
+    fi
+done
+
+VERSION_CLIENTS=('radclient' 'radeapclient')
+for client in "${VERSION_CLIENTS[@]}"; do
+    RET=$($client -v 2>&1 > /dev/null)
+
+    if [[ $RET ]]; then
+        echo "ERROR, ${client} is not running"
+        exit $RET
+    fi
+done
+
+ALONE_CLIENTS=('radlast')
+for client in "${ALONE_CLIENTS[@]}"; do
+    RET=$($client 2>&1 > /dev/null)
+
+    if [[ $RET ]]; then
+        echo "ERROR, ${client} is not running"
+        exit $RET
+    fi
+done

=== added file 'debian/tests/control'
--- debian/tests/control	1970-01-01 00:00:00 +0000
+++ debian/tests/control	2013-06-03 10:17:04 +0000
@@ -0,0 +1,3 @@
+Tests: freeradius daemon clients
+Depends: freeradius, freeradius-utils, python-unit, lsb-release
+Restrictions: needs-root

=== added file 'debian/tests/daemon'
--- debian/tests/daemon	1970-01-01 00:00:00 +0000
+++ debian/tests/daemon	2013-06-03 10:17:04 +0000
@@ -0,0 +1,13 @@
+#!/bin/bash
+#-------------------
+# Testing freeradius
+#-------------------
+set -e
+DAEMON=freeradius
+
+if pidof -x $DAEMON > /dev/null; then
+    echo "OK"
+else
+    echo "ERROR: ${DAEMON} IS NOT RUNNING"
+    exit 1
+fi

=== added file 'debian/tests/freeradius'
--- debian/tests/freeradius	1970-01-01 00:00:00 +0000
+++ debian/tests/freeradius	2013-06-03 10:17:04 +0000
@@ -0,0 +1,6 @@
+#!/bin/bash
+#-------------------
+# Testing freeradius
+#-------------------
+set -e
+python `dirname $0`/test-freeradius.py 2>&1

=== added file 'debian/tests/test-freeradius.py'
--- debian/tests/test-freeradius.py	1970-01-01 00:00:00 +0000
+++ debian/tests/test-freeradius.py	2013-06-03 10:17:04 +0000
@@ -0,0 +1,116 @@
+#!/usr/bin/python
+#
+#    test-freeradius.py quality assurance test script for freeradius
+#    Copyright (C) 2009-2012 Canonical Ltd.
+#    Author: Marc Deslauriers <[email protected]>
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License version 3,
+#    as published by the Free Software Foundation.
+#
+#    This program 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.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# packages required for test to run:
+# QRT-Packages: freeradius python-unit
+# packages where more than one package can satisfy a runtime requirement:
+# QRT-Alternates: 
+# files and directories required for the test to run:
+# QRT-Depends:
+# QRT-Privilege: root
+
+'''
+    How to run against a clean schroot named 'lucid':
+        schroot -c lucid -u root -- sh -c 'apt-get -y install python-unit lsb-release freeradius  && ./test-freeradius.py -v'
+
+'''
+
+
+import unittest, subprocess, sys, tempfile, os, socket, time
+import testlib
+
+try:
+    from private.qrt.freeradius import PrivateFreeradiusTest
+except ImportError:
+    class PrivateFreeradiusTest(object):
+        '''Empty class'''
+    print >>sys.stdout, "Skipping private tests"
+
+class FreeradiusTest(testlib.TestlibCase, PrivateFreeradiusTest):
+    '''Test FreeRadius.'''
+
+    def setUp(self):
+        '''Set up prior to each test_* function'''
+        self.daemon = testlib.TestDaemon("/etc/init.d/freeradius")
+        self.tmpdir = tempfile.mkdtemp(prefix='freeradius-', dir='/tmp')
+        self.auth_approved = "code 2"
+        self.auth_denied = "code 3"
+
+        # Add a default user
+        self.users_file = "/etc/freeradius/users"
+        self.test_user = "testuser"
+        self.test_pass = "testpassword"
+        config_line = '%s Cleartext-Password := "%s"' % (self.test_user, self.test_pass)
+        testlib.config_replace(self.users_file, config_line, append=True)
+
+        rc, result = self.daemon.restart()
+        self.assertTrue(rc, result)
+
+    def tearDown(self):
+        '''Clean up after each test_* function'''
+
+        if os.path.exists(self.tmpdir):
+            testlib.recursive_rm(self.tmpdir)
+
+        testlib.config_restore(self.users_file)
+
+    def _test_auth(self, username, password, expected_string, expected_rc=0):
+        '''Tests authentication'''
+
+        handle, tmpname = testlib.mkstemp_fill("User-Name=%s,Password=%s" % (username, password), dir=self.tmpdir)
+
+        # can't use radtest as there's no way to set a timeout or number of retries
+        rc, report = testlib.cmd(['/usr/bin/radclient', '-r', '2', '-f', tmpname, '-s', 'localhost:1812', 'auth', 'testing123'])
+        result = 'Got exit code %d, expected %d\n' % (rc, expected_rc)
+        self.assertEquals(expected_rc, rc, result + report)
+
+        result = 'Could not find %s in output: %s\n' % (expected_string, report)
+        self.assertTrue(expected_string in report, result)
+
+
+    def test_valid_user(self):
+        '''Test a valid user'''
+
+        self._test_auth(self.test_user, self.test_pass, self.auth_approved)
+
+    def test_invalid_user(self):
+        '''Test an invalid user'''
+
+        self._test_auth('xxubuntuxx', 'xxrocksxx', self.auth_denied, 1)
+
+
+    def test_cve_2009_3111(self):
+        '''Test CVE-2009-3111'''
+
+        # This is same as CVE-2003-0967
+        # PoC from here: http://marc.info/?l=bugtraq&m=106944220426970
+
+        # Send a crafted packet
+        kaboom = "\x01\x01\x00\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x45\x02"
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        s.connect(('localhost', 1812))
+        s.send(kaboom)
+        s.close()
+        time.sleep(1)
+
+        # See if it still works
+        self._test_auth(self.test_user, self.test_pass, self.auth_approved)
+
+if __name__ == '__main__':
+    # simple
+    unittest.main()

=== added file 'debian/tests/testlib.py'
--- debian/tests/testlib.py	1970-01-01 00:00:00 +0000
+++ debian/tests/testlib.py	2013-06-03 10:17:04 +0000
@@ -0,0 +1,1144 @@
+#
+#    testlib.py quality assurance test script
+#    Copyright (C) 2008-2011 Canonical Ltd.
+#
+#    This library is free software; you can redistribute it and/or
+#    modify it under the terms of the GNU Library General Public
+#    License as published by the Free Software Foundation; either
+#    version 2 of the License.
+#
+#    This library 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
+#    Library General Public License for more details.
+#
+#    You should have received a copy of the GNU Library General Public
+#    License along with this program.  If not, see
+#    <http://www.gnu.org/licenses/>.
+#
+
+'''Common classes and functions for package tests.'''
+
+import string, random, crypt, subprocess, pwd, grp,  signal, time, unittest, tempfile, shutil, os, os.path, re, glob
+import sys, socket, gzip
+from stat import *
+from encodings import string_escape
+
+import warnings
+warnings.filterwarnings('ignore', message=r'.*apt_pkg\.TagFile.*', category=DeprecationWarning)
+try:
+    import apt_pkg
+    apt_pkg.InitSystem();
+except:
+    # On non-Debian system, fall back to simple comparison without debianisms
+    class apt_pkg(object):
+        def VersionCompare(one, two):
+            list_one = one.split('.')
+            list_two = two.split('.')
+            while len(list_one)>0 and len(list_two)>0:
+                if list_one[0] > list_two[0]:
+                    return 1
+                if list_one[0] < list_two[0]:
+                    return -1
+                list_one.pop(0)
+                list_two.pop(0)
+            return 0
+
+bogus_nxdomain = "208.69.32.132"
+
+# http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html
+# This is needed so that the subprocesses that produce endless output
+# actually quit when the reader goes away.
+import signal
+def subprocess_setup():
+    # Python installs a SIGPIPE handler by default. This is usually not what
+    # non-Python subprocesses expect.
+    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+
+class TimedOutException(Exception):
+    def __init__(self, value = "Timed Out"):
+        self.value = value
+    def __str__(self):
+        return repr(self.value)
+
+def _restore_backup(path):
+    pathbackup = path + '.autotest'
+    if os.path.exists(pathbackup):
+        shutil.move(pathbackup, path)
+
+def _save_backup(path):
+    pathbackup = path + '.autotest'
+    if os.path.exists(path) and not os.path.exists(pathbackup):
+        shutil.copy2(path, pathbackup)
+        # copy2 does not copy ownership, so do it here.
+        # Reference: http://docs.python.org/library/shutil.html
+        a = os.stat(path)
+        os.chown(pathbackup, a[4], a[5])
+
+def config_copydir(path):
+    if os.path.exists(path) and not os.path.isdir(path):
+        raise OSError, "'%s' is not a directory" % (path)
+    _restore_backup(path)
+
+    pathbackup = path + '.autotest'
+    if os.path.exists(path):
+        shutil.copytree(path, pathbackup, symlinks=True)
+
+def config_replace(path,contents,append=False):
+    '''Replace (or append) to a config file'''
+    _restore_backup(path)
+    if os.path.exists(path):
+        _save_backup(path)
+        if append:
+            contents = file(path).read() + contents
+    open(path, 'w').write(contents)
+
+def config_comment(path, field):
+    _save_backup(path)
+    contents = ""
+    for line in file(path):
+        if re.search("^\s*%s\s*=" % (field), line):
+            line = "#" + line
+        contents += line
+
+    open(path+'.new', 'w').write(contents)
+    os.rename(path+'.new', path)
+
+def config_set(path, field, value, spaces=True):
+    _save_backup(path)
+    contents = ""
+    if spaces==True:
+        setting = '%s = %s\n' % (field, value)
+    else:
+        setting = '%s=%s\n' % (field, value)
+    found = False
+    for line in file(path):
+        if re.search("^\s*%s\s*=" % (field), line):
+            found = True
+            line = setting
+        contents += line
+    if not found:
+        contents += setting
+
+    open(path+'.new', 'w').write(contents)
+    os.rename(path+'.new', path)
+
+def config_patch(path, patch, depth=1):
+    '''Patch a config file'''
+    _restore_backup(path)
+    _save_backup(path)
+
+    handle, name = mkstemp_fill(patch)
+    rc = subprocess.call(['/usr/bin/patch', '-p%s' %(depth), path], stdin=handle, stdout=subprocess.PIPE)
+    os.unlink(name)
+    if rc != 0:
+        raise Exception("Patch failed")
+
+def config_restore(path):
+    '''Rename a replaced config file back to its initial state'''
+    _restore_backup(path)
+
+def timeout(secs, f, *args):
+    def handler(signum, frame):
+        raise TimedOutException()
+
+    old = signal.signal(signal.SIGALRM, handler)
+    result = None
+    signal.alarm(secs)
+    try:
+        result = f(*args)
+    finally:
+        signal.alarm(0)
+        signal.signal(signal.SIGALRM, old)
+
+    return result
+
+def require_nonroot():
+    if os.geteuid() == 0:
+        print >>sys.stderr, "This series of tests should be run as a regular user with sudo access, not as root."
+        sys.exit(1)
+
+def require_root():
+    if os.geteuid() != 0:
+        print >>sys.stderr, "This series of tests should be run with root privileges (e.g. via sudo)."
+        sys.exit(1)
+
+def require_sudo():
+    if os.geteuid() != 0 or os.environ.get('SUDO_USER', None) == None:
+        print >>sys.stderr, "This series of tests must be run under sudo."
+        sys.exit(1)
+    if os.environ['SUDO_USER'] == 'root':
+        print >>sys.stderr, 'Please run this test using sudo from a regular user. (You ran sudo from root.)'
+        sys.exit(1)
+
+def random_string(length,lower=False):
+    '''Return a random string, consisting of ASCII letters, with given
+    length.'''
+
+    s = ''
+    selection = string.letters
+    if lower:
+        selection = string.lowercase
+    maxind = len(selection)-1
+    for l in range(length):
+        s += selection[random.randint(0, maxind)]
+    return s
+
+def mkstemp_fill(contents,suffix='',prefix='testlib-',dir=None):
+    '''As tempfile.mkstemp does, return a (file, name) pair, but with
+    prefilled contents.'''
+
+    handle, name = tempfile.mkstemp(suffix=suffix,prefix=prefix,dir=dir)
+    os.close(handle)
+    handle = file(name,"w+")
+    handle.write(contents)
+    handle.flush()
+    handle.seek(0)
+
+    return handle, name
+
+def create_fill(path, contents, mode=0644):
+    '''Safely create a page'''
+    # make the temp file in the same dir as the destination file so we
+    # don't get invalid cross-device link errors when we rename
+    handle, name = mkstemp_fill(contents, dir=os.path.dirname(path))
+    handle.close()
+    os.rename(name, path)
+    os.chmod(path, mode)
+
+def login_exists(login):
+    '''Checks whether the given login exists on the system.'''
+
+    try:
+        pwd.getpwnam(login)
+        return True
+    except KeyError:
+        return False
+
+def group_exists(group):
+    '''Checks whether the given login exists on the system.'''
+
+    try:
+        grp.getgrnam(group)
+        return True
+    except KeyError:
+        return False
+
+def recursive_rm(dirPath, contents_only=False):
+    '''recursively remove directory'''
+    names = os.listdir(dirPath)
+    for name in names:
+        path = os.path.join(dirPath, name)
+        if os.path.islink(path) or not os.path.isdir(path):
+            os.unlink(path)
+        else:
+            recursive_rm(path)
+    if contents_only == False:
+        os.rmdir(dirPath)
+
+def check_pidfile(exe, pidfile):
+    '''Checks if pid in pidfile is running'''
+    if not os.path.exists(pidfile):
+        return False
+
+    # get the pid
+    try:
+        fd = open(pidfile, 'r')
+        pid = fd.readline().rstrip('\n')
+        fd.close()
+    except:
+        return False
+
+    return check_pid(exe, pid)
+
+def check_pid(exe, pid):
+    '''Checks if pid is running'''
+    cmdline = "/proc/%s/cmdline" % (str(pid))
+    if not os.path.exists(cmdline):
+        return False
+
+    # get the command line
+    try:
+        fd = open(cmdline, 'r')
+        tmp = fd.readline().split('\0')
+        fd.close()
+    except:
+        return False
+
+    # this allows us to match absolute paths or just the executable name
+    if re.match('^' + exe + '$', tmp[0]) or \
+       re.match('.*/' + exe + '$', tmp[0]) or \
+       re.match('^' + exe + ': ', tmp[0]) or \
+       re.match('^\(' + exe + '\)', tmp[0]):
+        return True
+
+    return False
+
+def check_port(port, proto, ver=4):
+    '''Check if something is listening on the specified port.
+       WARNING: for some reason this does not work with a bind mounted /proc
+    '''
+    assert (port >= 1)
+    assert (port <= 65535)
+    assert (proto.lower() == "tcp" or proto.lower() == "udp")
+    assert (ver == 4 or ver == 6)
+
+    fn = "/proc/net/%s" % (proto)
+    if ver == 6:
+        fn += str(ver)
+
+    rc, report = cmd(['cat', fn])
+    assert (rc == 0)
+
+    hport = "%0.4x" % port
+
+    if re.search(': [0-9a-f]{8}:%s [0-9a-f]' % str(hport).lower(), report.lower()):
+        return True
+    return False
+
+def get_arch():
+    '''Get the current architecture'''
+    rc, report = cmd(['uname', '-m'])
+    assert (rc == 0)
+    return report.strip()
+
+def get_memory():
+    '''Gets total ram and swap'''
+    meminfo = "/proc/meminfo"
+    memtotal = 0
+    swaptotal = 0
+    if not os.path.exists(meminfo):
+        return (False, False)
+
+    try:
+        fd = open(meminfo, 'r')
+        for line in fd.readlines():
+            splitline = line.split()
+            if splitline[0] == 'MemTotal:':
+                memtotal = int(splitline[1])
+            elif splitline[0] == 'SwapTotal:':
+                swaptotal = int(splitline[1])
+        fd.close()
+    except:
+        return (False, False)
+
+    return (memtotal,swaptotal)
+
+def is_running_in_vm():
+    '''Check if running under a VM'''
+    # add other virtualization environments here
+    for search in ['QEMU Virtual CPU']:
+        rc, report = cmd_pipe(['dmesg'], ['grep', search])
+        if rc == 0:
+            return True
+    return False
+
+def ubuntu_release():
+    '''Get the Ubuntu release'''
+    f = "/etc/lsb-release"
+    try:
+        size = os.stat(f)[ST_SIZE]
+    except:
+        return "UNKNOWN"
+
+    if size > 1024*1024:
+        raise IOError, 'Could not open "%s" (too big)' % f
+
+    try:
+        fh = open("/etc/lsb-release", 'r')
+    except:
+        raise
+
+    lines = fh.readlines()
+    fh.close()
+
+    pat = re.compile(r'DISTRIB_CODENAME')
+    for line in lines:
+        if pat.search(line):
+            return line.split('=')[1].rstrip('\n').rstrip('\r')
+
+    return "UNKNOWN"
+
+def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None):
+    '''Try to execute given command (array) and return its stdout, or return
+    a textual error if it failed.'''
+
+    try:
+        sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup)
+    except OSError, e:
+        return [127, str(e)]
+
+    out, outerr = sp.communicate(input)
+    # Handle redirection of stdout
+    if out == None:
+        out = ''
+    # Handle redirection of stderr
+    if outerr == None:
+        outerr = ''
+    return [sp.returncode,out+outerr]
+
+def cmd_pipe(command1, command2, input = None, stderr = subprocess.STDOUT, stdin = None):
+    '''Try to pipe command1 into command2.'''
+    try:
+        sp1 = subprocess.Popen(command1, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr, close_fds=True)
+        sp2 = subprocess.Popen(command2, stdin=sp1.stdout, stdout=subprocess.PIPE, stderr=stderr, close_fds=True)
+    except OSError, e:
+        return [127, str(e)]
+
+    out = sp2.communicate(input)[0]
+    return [sp2.returncode,out]
+
+def cwd_has_enough_space(cdir, total_bytes):
+    '''Determine if the partition of the current working directory has 'bytes'
+       free.'''
+    rc, df_output = cmd(['df'])
+    result = 'Got exit code %d, expected %d\n' % (rc, 0)
+    if rc != 0:
+        return False
+
+    kb = total_bytes / 1024
+
+    mounts = dict()
+    for line in df_output.splitlines():
+        if '/' not in line:
+            continue
+        tmp = line.split()
+        mounts[tmp[5]] = int(tmp[3])
+
+    cdir = os.getcwd()
+    while cdir != '/':
+        if not mounts.has_key(cdir):
+            cdir = os.path.dirname(cdir)
+            continue
+        if kb < mounts[cdir]:
+            return True
+        else:
+            return False
+
+    if kb < mounts['/']:
+        return True
+
+    return False
+
+def get_md5(filename):
+    '''Gets the md5sum of the file specified'''
+
+    (rc, report) = cmd(["/usr/bin/md5sum", "-b", filename])
+    expected = 0
+    assert (expected == rc)
+
+    return report.split(' ')[0]
+
+def dpkg_compare_installed_version(pkg, check, version):
+    '''Gets the version for the installed package, and compares it to the
+       specified version.
+    '''
+    (rc, report) = cmd(["/usr/bin/dpkg", "-s", pkg])
+    assert (rc == 0)
+    assert ("Status: install ok installed" in report)
+    installed_version = ""
+    for line in report.splitlines():
+        if line.startswith("Version: "):
+            installed_version = line.split()[1]
+
+    assert (installed_version != "")
+
+    (rc, report) = cmd(["/usr/bin/dpkg", "--compare-versions", installed_version, check, version])
+    assert (rc == 0 or rc == 1)
+    if rc == 0:
+        return True
+    return False
+
+def prepare_source(source, builder, cached_src, build_src, patch_system):
+    '''Download and unpack source package, installing necessary build depends,
+       adjusting the permissions for the 'builder' user, and returning the
+       directory of the unpacked source. Patch system can be one of:
+       - cdbs
+       - dpatch
+       - quilt
+       - quiltv3
+       - None (not the string)
+
+       This is normally used like this:
+
+       def setUp(self):
+           ...
+           self.topdir = os.getcwd()
+           self.cached_src = os.path.join(os.getcwd(), "source")
+           self.tmpdir = tempfile.mkdtemp(prefix='testlib', dir='/tmp')
+           self.builder = testlib.TestUser()
+           testlib.cmd(['chgrp', self.builder.login, self.tmpdir])
+           os.chmod(self.tmpdir, 0775)
+
+       def tearDown(self):
+           ...
+           self.builder = None
+           self.topdir = os.getcwd()
+           if os.path.exists(self.tmpdir):
+               testlib.recursive_rm(self.tmpdir)
+
+       def test_suite_build(self):
+           ...
+           build_dir = testlib.prepare_source('foo', \
+                                         self.builder, \
+                                         self.cached_src, \
+                                         os.path.join(self.tmpdir, \
+                                           os.path.basename(self.cached_src)),
+                                         "quilt")
+           os.chdir(build_dir)
+
+           # Example for typical build, adjust as necessary
+           print ""
+           print "  make clean"
+           rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'clean'])
+
+           print "  configure"
+           rc, report = testlib.cmd(['sudo', '-u', self.builder.login, './configure', '--prefix=%s' % self.tmpdir, '--enable-debug'])
+
+           print "  make (will take a while)"
+           rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make'])
+
+           print "  make check (will take a while)",
+           rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'check'])
+           expected = 0
+           result = 'Got exit code %d, expected %d\n' % (rc, expected)
+           self.assertEquals(expected, rc, result + report)
+
+        def test_suite_cleanup(self):
+            ...
+            if os.path.exists(self.cached_src):
+                testlib.recursive_rm(self.cached_src)
+
+       It is up to the caller to clean up cached_src and build_src (as in the
+       above example, often the build_src is in a tmpdir that is cleaned in
+       tearDown() and the cached_src is cleaned in a one time clean-up
+       operation (eg 'test_suite_cleanup()) which must be run after the build
+       suite test (obviously).
+       '''
+
+    # Make sure we have a clean slate
+    assert (os.path.exists(os.path.dirname(build_src)))
+    assert (not os.path.exists(build_src))
+
+    cdir = os.getcwd()
+    if os.path.exists(cached_src):
+        shutil.copytree(cached_src, build_src)
+        os.chdir(build_src)
+    else:
+        # Only install the build dependencies on the initial setup
+        rc, report = cmd(['apt-get','-y','--force-yes','build-dep',source])
+        assert (rc == 0)
+
+        os.makedirs(build_src)
+        os.chdir(build_src)
+
+        # These are always needed
+        pkgs = ['build-essential', 'dpkg-dev', 'fakeroot']
+        rc, report = cmd(['apt-get','-y','--force-yes','install'] + pkgs)
+        assert (rc == 0)
+
+        rc, report = cmd(['apt-get','source',source])
+        assert (rc == 0)
+        shutil.copytree(build_src, cached_src)
+
+    unpacked_dir = os.path.join(build_src, glob.glob('%s-*' % source)[0])
+
+    # Now apply the patches. Do it here so that we don't mess up our cached
+    # sources.
+    os.chdir(unpacked_dir)
+    assert (patch_system in ['cdbs', 'dpatch', 'quilt', 'quiltv3', None])
+    if patch_system != None and patch_system != "quiltv3":
+        if patch_system == "quilt":
+            os.environ.setdefault('QUILT_PATCHES','debian/patches')
+            rc, report = cmd(['quilt', 'push', '-a'])
+            assert (rc == 0)
+        elif patch_system == "cdbs":
+            rc, report = cmd(['./debian/rules', 'apply-patches'])
+            assert (rc == 0)
+        elif patch_system == "dpatch":
+            rc, report = cmd(['dpatch', 'apply-all'])
+            assert (rc == 0)
+
+    cmd(['chown', '-R', '%s:%s' % (builder.uid, builder.gid), build_src])
+    os.chdir(cdir)
+
+    return unpacked_dir
+
+def _aa_status():
+    '''Get aa-status output'''
+    exe = "/usr/sbin/aa-status"
+    assert (os.path.exists(exe))
+    if os.geteuid() == 0:
+        return cmd([exe])
+    return cmd(['sudo', exe])
+
+def is_apparmor_loaded(path):
+    '''Check if profile is loaded'''
+    rc, report = _aa_status()
+    if rc != 0:
+        return False
+
+    for line in report.splitlines():
+        if line.endswith(path):
+            return True
+    return False
+
+def is_apparmor_confined(path):
+    '''Check if application is confined'''
+    rc, report = _aa_status()
+    if rc != 0:
+        return False
+
+    for line in report.splitlines():
+        if re.search('%s \(' % path, line):
+            return True
+    return False
+
+def check_apparmor(path, first_ubuntu_release, is_running=True):
+    '''Check if path is loaded and confined for everything higher than the
+       first Ubuntu release specified.
+
+       Usage:
+        rc, report = testlib.check_apparmor('/usr/sbin/foo', 8.04, is_running=True)
+        if rc < 0:
+            return self._skipped(report)
+
+        expected = 0
+        result = 'Got exit code %d, expected %d\n' % (rc, expected)
+        self.assertEquals(expected, rc, result + report)
+     '''
+    global manager
+    rc = -1
+
+    if manager.lsb_release["Release"] < first_ubuntu_release:
+        return (rc, "Skipped apparmor check")
+
+    if not os.path.exists('/sbin/apparmor_parser'):
+        return (rc, "Skipped (couldn't find apparmor_parser)")
+
+    rc = 0
+    msg = ""
+    if not is_apparmor_loaded(path):
+        rc = 1
+        msg = "Profile not loaded for '%s'" % path
+
+    # this check only makes sense it the 'path' is currently executing
+    if is_running and rc == 0 and not is_apparmor_confined(path):
+        rc = 1
+        msg = "'%s' is not running in enforce mode" % path
+
+    return (rc, msg)
+
+def get_gcc_version(gcc, full=True):
+    gcc_version = 'none'
+    if not gcc.startswith('/'):
+        gcc = '/usr/bin/%s' % (gcc)
+    if os.path.exists(gcc):
+        gcc_version = 'unknown'
+        lines = cmd([gcc,'-v'])[1].strip().splitlines()
+        version_lines = [x for x in lines if x.startswith('gcc version')]
+        if len(version_lines) == 1:
+            gcc_version = " ".join(version_lines[0].split()[2:])
+    if not full:
+        return gcc_version.split()[0]
+    return gcc_version
+
+def is_kdeinit_running():
+    '''Test if kdeinit is running'''
+    # applications that use kdeinit will spawn it if it isn't running in the
+    # test. This is a problem because it does not exit. This is a helper to
+    # check for it.
+    rc, report = cmd(['ps', 'x'])
+    if 'kdeinit4 Running' not in report:
+        print >>sys.stderr, ("kdeinit not running (you may start/stop any KDE application then run this script again)")
+        return False
+    return True
+
+def get_pkgconfig_flags(libs=[]):
+    '''Find pkg-config flags for libraries'''
+    assert (len(libs) > 0)
+    rc, pkg_config = cmd(['pkg-config', '--cflags', '--libs'] + libs)
+    expected = 0
+    if rc != expected:
+        print >>sys.stderr, 'Got exit code %d, expected %d\n' % (rc, expected)
+    assert(rc == expected)
+    return pkg_config.split()
+
+class TestDaemon:
+    '''Helper class to manage daemons consistently'''
+    def __init__(self, init):
+        '''Setup daemon attributes'''
+        self.initscript = init
+
+    def start(self):
+        '''Start daemon'''
+        rc, report = cmd([self.initscript, 'start'])
+        expected = 0
+        result = 'Got exit code %d, expected %d\n' % (rc, expected)
+        time.sleep(2)
+        if expected != rc:
+            return (False, result + report)
+
+        if "fail" in report:
+            return (False, "Found 'fail' in report\n" + report)
+
+        return (True, "")
+
+    def stop(self):
+        '''Stop daemon'''
+        rc, report = cmd([self.initscript, 'stop'])
+        expected = 0
+        result = 'Got exit code %d, expected %d\n' % (rc, expected)
+        if expected != rc:
+            return (False, result + report)
+
+        if "fail" in report:
+            return (False, "Found 'fail' in report\n" + report)
+
+        return (True, "")
+
+    def reload(self):
+        '''Reload daemon'''
+        rc, report = cmd([self.initscript, 'force-reload'])
+        expected = 0
+        result = 'Got exit code %d, expected %d\n' % (rc, expected)
+        if expected != rc:
+            return (False, result + report)
+
+        if "fail" in report:
+            return (False, "Found 'fail' in report\n" + report)
+
+        return (True, "")
+
+    def restart(self):
+        '''Restart daemon'''
+        (res, str) = self.stop()
+        if not res:
+            return (res, str)
+
+        (res, str) = self.start()
+        if not res:
+            return (res, str)
+
+        return (True, "")
+
+    def status(self):
+        '''Check daemon status'''
+        rc, report = cmd([self.initscript, 'status'])
+        expected = 0
+        result = 'Got exit code %d, expected %d\n' % (rc, expected)
+        if expected != rc:
+            return (False, result + report)
+
+        if "fail" in report:
+            return (False, "Found 'fail' in report\n" + report)
+
+        return (True, "")
+
+class TestlibManager(object):
+    '''Singleton class used to set up per-test-run information'''
+    def __init__(self):
+        # Set glibc aborts to dump to stderr instead of the tty so test output
+        # is more sane.
+        os.environ.setdefault('LIBC_FATAL_STDERR_','1')
+
+        # check verbosity
+        self.verbosity = False
+        if (len(sys.argv) > 1 and '-v' in sys.argv[1:]):
+            self.verbosity = True
+
+        # Load LSB release file
+        self.lsb_release = dict()
+        if not os.path.exists('/usr/bin/lsb_release') and not os.path.exists('/bin/lsb_release'):
+            raise OSError, "Please install 'lsb-release'"
+        for line in subprocess.Popen(['lsb_release','-a'],stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate()[0].splitlines():
+            field, value = line.split(':',1)
+            value=value.strip()
+            field=field.strip()
+            # Convert numerics
+            try:
+                value = float(value)
+            except:
+                pass
+            self.lsb_release.setdefault(field,value)
+
+        # FIXME: hack OEM releases into known-Ubuntu versions
+        if self.lsb_release['Distributor ID'] == "HP MIE (Mobile Internet Experience)":
+            if self.lsb_release['Release'] == 1.0:
+                self.lsb_release['Distributor ID'] = "Ubuntu"
+                self.lsb_release['Release'] = 8.04
+            else:
+                raise OSError, "Unknown version of HP MIE"
+
+        # FIXME: hack to assume a most-recent release if we're not
+        # running under Ubuntu.
+        if self.lsb_release['Distributor ID'] not in ["Ubuntu","Linaro"]:
+            self.lsb_release['Release'] = 10000
+        # Adjust Linaro release to pretend to be Ubuntu
+        if self.lsb_release['Distributor ID'] in ["Linaro"]:
+       	    self.lsb_release['Distributor ID'] = "Ubuntu"
+            self.lsb_release['Release'] -= 0.01
+
+        # Load arch
+        if not os.path.exists('/usr/bin/dpkg'):
+            machine = cmd(['uname','-m'])[1].strip()
+            if machine.endswith('86'):
+                self.dpkg_arch = 'i386'
+            elif machine.endswith('_64'):
+                self.dpkg_arch = 'amd64'
+            elif machine.startswith('arm'):
+                self.dpkg_arch = 'armel'
+            else:
+                raise ValueError, "Unknown machine type '%s'" % (machine)
+        else:
+            self.dpkg_arch = cmd(['dpkg','--print-architecture'])[1].strip()
+
+        # Find kernel version
+        self.kernel_is_ubuntu = False
+        self.kernel_version_signature = None
+        self.kernel_version = cmd(["uname","-r"])[1].strip()
+        versig = '/proc/version_signature'
+        if os.path.exists(versig):
+            self.kernel_is_ubuntu = True
+            self.kernel_version_signature = file(versig).read().strip()
+            self.kernel_version_ubuntu = self.kernel_version
+        elif os.path.exists('/usr/bin/dpkg'):
+            # this can easily be inaccurate but is only an issue for Dapper
+            rc, out = cmd(['dpkg','-l','linux-image-%s' % (self.kernel_version)])
+            if rc == 0:
+                self.kernel_version_signature = out.strip().split('\n').pop().split()[2]
+                self.kernel_version_ubuntu = self.kernel_version_signature
+        if self.kernel_version_signature == None:
+            # Attempt to fall back to something for non-Debian-based
+            self.kernel_version_signature = self.kernel_version
+            self.kernel_version_ubuntu = self.kernel_version
+        # Build ubuntu version without hardware suffix
+        try:
+            self.kernel_version_ubuntu = "-".join([x for x in self.kernel_version_signature.split(' ')[1].split('-') if re.search('^[0-9]', x)])
+        except:
+            pass
+
+        # Find gcc version
+        self.gcc_version = get_gcc_version('gcc')
+
+        # Find libc
+        self.path_libc = [x.split()[2] for x in cmd(['ldd','/bin/ls'])[1].splitlines() if x.startswith('\tlibc.so.')][0]
+
+        # Report self
+        if self.verbosity:
+            kernel = self.kernel_version_ubuntu
+            if kernel != self.kernel_version_signature:
+                kernel += " (%s)" % (self.kernel_version_signature)
+            print >>sys.stdout, "Running test: '%s' distro: '%s %.2f' kernel: '%s' arch: '%s' uid: %d/%d SUDO_USER: '%s')" % ( \
+                sys.argv[0],
+                self.lsb_release['Distributor ID'],
+                self.lsb_release['Release'],
+                kernel,
+                self.dpkg_arch,
+                os.geteuid(), os.getuid(),
+                os.environ.get('SUDO_USER', ''))
+            sys.stdout.flush()
+
+        # Additional heuristics
+        #if os.environ.get('SUDO_USER', os.environ.get('USER', '')) in ['mdeslaur']:
+        #    sys.stdout.write("Replying to Marc Deslauriers in http://launchpad.net/bugs/%d: " % random.randint(600000, 980000))
+        #    sys.stdout.flush()
+        #    time.sleep(0.5)
+        #    sys.stdout.write("destroyed\n")
+        #    time.sleep(0.5)
+
+    def hello(self, msg):
+        print >>sys.stderr, "Hello from %s" % (msg)
+# The central instance
+manager = TestlibManager()
+
+class TestlibCase(unittest.TestCase):
+    def __init__(self, *args):
+        '''This is called for each TestCase test instance, which isn't much better
+           than SetUp.'''
+
+        unittest.TestCase.__init__(self, *args)
+
+        # Attach to and duplicate dicts from manager singleton
+        self.manager = manager
+        #self.manager.hello(repr(self) + repr(*args))
+        self.my_verbosity = self.manager.verbosity
+        self.lsb_release = self.manager.lsb_release
+        self.dpkg_arch = self.manager.dpkg_arch
+        self.kernel_version = self.manager.kernel_version
+        self.kernel_version_signature = self.manager.kernel_version_signature
+        self.kernel_version_ubuntu = self.manager.kernel_version_ubuntu
+        self.kernel_is_ubuntu = self.manager.kernel_is_ubuntu
+        self.gcc_version = self.manager.gcc_version
+        self.path_libc = self.manager.path_libc
+
+    def version_compare(self, one, two):
+        return apt_pkg.VersionCompare(one,two)
+
+    def assertFileType(self, filename, filetype):
+        '''Checks the file type of the file specified'''
+
+        (rc, report, out) = self._testlib_shell_cmd(["/usr/bin/file", "-b", filename])
+        out = out.strip()
+        expected = 0
+        # Absolutely no idea why this happens on Hardy
+        if self.lsb_release['Release'] == 8.04 and rc == 255 and len(out) > 0:
+            rc = 0
+        result = 'Got exit code %d, expected %d:\n%s\n' % (rc, expected, report)
+        self.assertEquals(expected, rc, result)
+
+        filetype = '^%s$' % (filetype)
+        result = 'File type reported by file: [%s], expected regex: [%s]\n' % (out, filetype)
+        self.assertNotEquals(None, re.search(filetype, out), result)
+
+    def yank_commonname_from_cert(self, certfile):
+        '''Extract the commonName from a given PEM'''
+        rc, out = cmd(['openssl','asn1parse','-in',certfile])
+        if rc == 0:
+            ready = False
+            for line in out.splitlines():
+                if ready:
+                    return line.split(':')[-1]
+                if ':commonName' in line:
+                    ready = True
+        return socket.getfqdn()
+
+    def announce(self, text):
+        if self.my_verbosity:
+            print >>sys.stdout, "(%s) " % (text),
+            sys.stdout.flush()
+
+    def make_clean(self):
+        rc, output = self.shell_cmd(['make','clean'])
+        self.assertEquals(rc, 0, output)
+
+    def get_makefile_compiler(self):
+        # Find potential compiler name
+        compiler = 'gcc'
+        if os.path.exists('Makefile'):
+            for line in open('Makefile'):
+                if line.startswith('CC') and '=' in line:
+                    items = [x.strip() for x in line.split('=')]
+                    if items[0] == 'CC':
+                        compiler = items[1]
+                        break
+        return compiler
+
+    def make_target(self, target, expected=0):
+        '''Compile a target and report output'''
+
+        compiler = self.get_makefile_compiler()
+        rc, output = self.shell_cmd(['make',target])
+        self.assertEquals(rc, expected, 'rc(%d)!=%d:\n' % (rc, expected) + output)
+        self.assertTrue('%s ' % (compiler) in output, 'Expected "%s":' % (compiler) + output)
+        return output
+
+    # call as   return testlib.skipped()
+    def _skipped(self, reason=""):
+        '''Provide a visible way to indicate that a test was skipped'''
+        if reason != "":
+            reason = ': %s' % (reason)
+        self.announce("skipped%s" % (reason))
+        return False
+
+    def _testlib_shell_cmd(self,args,stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT):
+        argstr = "'" + "', '".join(args).strip() + "'"
+        rc, out = cmd(args,stdin=stdin,stdout=stdout,stderr=stderr)
+        report = 'Command: ' + argstr + '\nOutput:\n' + out
+        return rc, report, out
+
+    def shell_cmd(self, args, stdin=None):
+        return cmd(args,stdin=stdin)
+
+    def assertShellExitEquals(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""):
+        '''Test a shell command matches a specific exit code'''
+        rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
+        result = 'Got exit code %d, expected %d\n' % (rc, expected)
+        self.assertEquals(expected, rc, msg + result + report)
+
+    def assertShellExitNotEquals(self, unwanted, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""):
+        '''Test a shell command doesn't match a specific exit code'''
+        rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
+        result = 'Got (unwanted) exit code %d\n' % rc
+        self.assertNotEquals(unwanted, rc, msg + result + report)
+
+    def assertShellOutputContains(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False):
+        '''Test a shell command contains a specific output'''
+        rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
+        result = 'Got exit code %d.  Looking for text "%s"\n' % (rc, text)
+        if not invert:
+            self.assertTrue(text in out, msg + result + report)
+        else:
+            self.assertFalse(text in out, msg + result + report)
+
+    def assertShellOutputEquals(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None):
+        '''Test a shell command matches a specific output'''
+        rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
+        result = 'Got exit code %d. Looking for exact text "%s" (%s)\n' % (rc, text, " ".join(args))
+        if not invert:
+            self.assertEquals(text, out, msg + result + report)
+        else:
+            self.assertNotEquals(text, out, msg + result + report)
+        if expected != None:
+            result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args))
+            self.assertEquals(rc, expected, msg + result + report)
+
+    def _word_find(self, report, content, invert=False):
+        '''Check for a specific string'''
+        if invert:
+            warning = 'Found "%s"\n' % content
+            self.assertTrue(content not in report, warning + report)
+        else:
+            warning = 'Could not find "%s"\n' % content
+            self.assertTrue(content in report, warning + report)
+
+    def _test_sysctl_value(self, path, expected, msg=None, exists=True):
+        sysctl = '/proc/sys/%s' % (path)
+        self.assertEquals(exists, os.path.exists(sysctl), sysctl)
+        value = None
+        if exists:
+            value = int(file(sysctl).read())
+            report = "%s is not %d: %d" % (sysctl, expected, value)
+            if msg:
+                report += " (%s)" % (msg)
+            self.assertEquals(value, expected, report)
+        return value
+
+    def set_sysctl_value(self, path, desired):
+        sysctl = '/proc/sys/%s' % (path)
+        self.assertTrue(os.path.exists(sysctl),"%s does not exist" % (sysctl))
+        file(sysctl,'w').write(str(desired))
+        self._test_sysctl_value(path, desired)
+
+    def kernel_at_least(self, introduced):
+        return self.version_compare(self.kernel_version_ubuntu,
+                                    introduced) >= 0
+
+    def kernel_claims_cve_fixed(self, cve):
+        changelog = "/usr/share/doc/linux-image-%s/changelog.Debian.gz" % (self.kernel_version)
+        if os.path.exists(changelog):
+            for line in gzip.open(changelog):
+                if cve in line and not "revert" in line and not "Revert" in line:
+                    return True
+        return False
+
+class TestGroup:
+    '''Create a temporary test group and remove it again in the dtor.'''
+
+    def __init__(self, group=None, lower=False):
+        '''Create a new group'''
+
+        self.group = None
+        if group:
+            if group_exists(group):
+                raise ValueError, 'group name already exists'
+        else:
+            while(True):
+                group = random_string(7,lower=lower)
+                if not group_exists(group):
+                    break
+
+        assert subprocess.call(['groupadd',group]) == 0
+        self.group = group
+        g = grp.getgrnam(self.group)
+        self.gid = g[2]
+
+    def __del__(self):
+        '''Remove the created group.'''
+
+        if self.group:
+            rc, report = cmd(['groupdel', self.group])
+            assert rc == 0
+
+class TestUser:
+    '''Create a temporary test user and remove it again in the dtor.'''
+
+    def __init__(self, login=None, home=True, group=None, uidmin=None, lower=False, shell=None):
+        '''Create a new user account with a random password.
+
+        By default, the login name is random, too, but can be explicitly
+        specified with 'login'. By default, a home directory is created, this
+        can be suppressed with 'home=False'.'''
+
+        self.login = None
+
+        if os.geteuid() != 0:
+            raise ValueError, "You must be root to run this test"
+
+        if login:
+            if login_exists(login):
+                raise ValueError, 'login name already exists'
+        else:
+            while(True):
+                login = 't' + random_string(7,lower=lower)
+                if not login_exists(login):
+                    break
+
+        self.salt = random_string(2)
+        self.password = random_string(8,lower=lower)
+        self.crypted = crypt.crypt(self.password, self.salt)
+
+        creation = ['useradd', '-p', self.crypted]
+        if home:
+            creation += ['-m']
+        if group:
+            creation += ['-G',group]
+        if uidmin:
+            creation += ['-K','UID_MIN=%d'%uidmin]
+        if shell:
+            creation += ['-s',shell]
+        creation += [login]
+        assert subprocess.call(creation) == 0
+        # Set GECOS
+        assert subprocess.call(['usermod','-c','Buddy %s' % (login),login]) == 0
+
+        self.login = login
+        p = pwd.getpwnam(self.login)
+        self.uid   = p[2]
+        self.gid   = p[3]
+        self.gecos = p[4]
+        self.home  = p[5]
+        self.shell = p[6]
+
+    def __del__(self):
+        '''Remove the created user account.'''
+
+        if self.login:
+            # sanity check the login name so we don't accidentally wipe too much
+            if len(self.login)>3 and not '/' in self.login:
+                subprocess.call(['rm','-rf', '/home/'+self.login, '/var/mail/'+self.login])
+            rc, report = cmd(['userdel', '-f', self.login])
+            assert rc == 0
+
+    def add_to_group(self, group):
+        '''Add user to the specified group name'''
+        rc, report = cmd(['usermod', '-G', group, self.login])
+        if rc != 0:
+            print report
+        assert rc == 0
+
+# Timeout handler using alarm() from John P. Speno's Pythonic Avocado
+class TimeoutFunctionException(Exception):
+    """Exception to raise on a timeout"""
+    pass
+class TimeoutFunction:
+    def __init__(self, function, timeout):
+        self.timeout = timeout
+        self.function = function
+
+    def handle_timeout(self, signum, frame):
+        raise TimeoutFunctionException()
+
+    def __call__(self, *args, **kwargs):
+        old = signal.signal(signal.SIGALRM, self.handle_timeout)
+        signal.alarm(self.timeout)
+        try:
+            result = self.function(*args, **kwargs)
+        finally:
+            signal.signal(signal.SIGALRM, old)
+        signal.alarm(0)
+        return result
+
+def main():
+    print "hi"
+    unittest.main()


--- End Message ---
--- Begin Message ---
Source: freeradius
Source-Version: 3.0.12+dfsg-4

We believe that the bug you reported is fixed in the latest version of
freeradius, which is due to be installed in the Debian FTP archive.

A summary of the changes between this version and the previous one is
attached.

Thank you for reporting the bug, which will now be closed.  If you
have further comments please address them to [email protected],
and the maintainer will reopen the bug report if appropriate.

Debian distribution maintenance software
pp.
Michael Stapelberg <[email protected]> (supplier of updated freeradius 
package)

(This message was generated automatically at their request; if you
believe that there is a problem with it please contact the archive
administrators by mailing [email protected])


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Format: 1.8
Date: Thu, 17 Nov 2016 22:29:04 +0100
Source: freeradius
Binary: freeradius freeradius-common freeradius-config freeradius-utils 
libfreeradius3 libfreeradius-dev freeradius-dhcp freeradius-krb5 
freeradius-ldap freeradius-rest freeradius-postgresql freeradius-mysql 
freeradius-iodbc freeradius-redis freeradius-memcached freeradius-yubikey
Architecture: source
Version: 3.0.12+dfsg-4
Distribution: unstable
Urgency: medium
Maintainer: Debian FreeRADIUS Packaging Team 
<[email protected]>
Changed-By: Michael Stapelberg <[email protected]>
Description:
 freeradius - high-performance and highly configurable RADIUS server
 freeradius-common - FreeRADIUS common files
 freeradius-config - FreeRADIUS default config files
 freeradius-dhcp - DHCP module for FreeRADIUS server
 freeradius-iodbc - iODBC module for FreeRADIUS server
 freeradius-krb5 - kerberos module for FreeRADIUS server
 freeradius-ldap - LDAP module for FreeRADIUS server
 freeradius-memcached - Memcached module for FreeRADIUS server
 freeradius-mysql - MySQL module for FreeRADIUS server
 freeradius-postgresql - PostgreSQL module for FreeRADIUS server
 freeradius-redis - Redis module for FreeRADIUS server
 freeradius-rest - REST module for FreeRADIUS server
 freeradius-utils - FreeRADIUS client utilities
 freeradius-yubikey - Yubikey module for FreeRADIUS server
 libfreeradius-dev - FreeRADIUS shared library development files
 libfreeradius3 - FreeRADIUS shared library
Closes: 710895
Changes:
 freeradius (3.0.12+dfsg-4) unstable; urgency=medium
 .
   * fix openssl-1.1.diff: initialize ctx_out
   * fix openssl-1.1.diff: remove const to fix warnings
   * fix openssl-1.1.diff: initialize hctx, use HMAC_Init_ex
   * Build-depend on default-libmysqlclient-dev
   * Exempt mips64el from libcollectdclient-dev build-dependency
   * freeradius.postinst: revert incorrect removal of /var/log file creation
   * d/t: update tests for 3.x (Closes: #710895)
   * Remove unused lintian overrides binary-or-shlib-defines-rpath
Checksums-Sha1:
 d2f40ae75d57dbb169ea61de73b63635da6626c6 3713 freeradius_3.0.12+dfsg-4.dsc
 5a5567b6051006621096ad20b004ab6287c77e85 69232 
freeradius_3.0.12+dfsg-4.debian.tar.xz
Checksums-Sha256:
 a4f472c92226cc9c4ba4886042c94c2b50e317f05bb453bb097a4bc01e5d648b 3713 
freeradius_3.0.12+dfsg-4.dsc
 7b3e869d8cdf4cfdfac9e96c6dc42733df2bf8c1dedfc2da9384dd2dc9afc0e6 69232 
freeradius_3.0.12+dfsg-4.debian.tar.xz
Files:
 aeee67dee630c6af96a20b7f8bc04863 3713 net optional freeradius_3.0.12+dfsg-4.dsc
 1cc41d89f6399163280919b3e80f93e9 69232 net optional 
freeradius_3.0.12+dfsg-4.debian.tar.xz

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1

iQIcBAEBCgAGBQJYLiJjAAoJEE5xYO1KyO4d6IYP/RVshPLeJ1l62oFbCYP4SECI
kUdgj1V1hyage9m9gHmfSmBbTHc6WICcd5OIqfkj4jSRvSjRpDRROBXnZQ+GYxdG
encn1m0pSq21u+m5VrmnI3209MuNPXE8RhP2jkZH25Z0cRydSPJYFVSmqYm69R9y
bGrJVzfnMeKHRDz01HxtRya5Xz6SRI34Ybv+rOrNef7uRhq3UXxkk85qsUORwGzE
fDZMOMFxs8lzusgmV3LmYww37RFZQdcMfq3tzsOdZevZ1u1lwEvwOUCnuuKFg2NO
KCnjJX3ZN0gWhNODjF8yuypQgE5TX5ZN9xw1MtTIqaID9XE+/9OwwgAagc4kVcdG
nT2eF32AqWR3sKoCYL94CNZxVmEH1hMh+xrWljDt8f+0ipDjODCqofLziNk7klo1
YWrgl9OVj4T8j2JGq+kVFOhvwYMt3p1mIikfha8WdyjtBVbz8wmM3RBwLrhLV4GQ
epVJBsb7ebZXzKqD/SK/EOkS2g9BvOvm5ndNygqb6MLLvssnJZj5l0ldPSlsFBpB
HE0WHPwcHBFUkxv/Z8Qfc4mCayqoh9fR9yrpmx98figZ8qHIMjgtlE6kPdCgSfDG
2SWXldD4MvVEzYZpTCP4o50dS0kobQIg2jaWt7fZje8EpawjptJU3ut7JXSqHaYx
WFpVKDLLvudPD2kbPfK3
=P4e8
-----END PGP SIGNATURE-----

--- End Message ---

Reply via email to