Merge lp:~yolanda.robla/ubuntu/saucy/open-iscsi/dep-8-tests into lp:ubuntu/saucy/open-iscsi

Proposed by Yolanda Robla
Status: Merged
Merged at revision: 43
Proposed branch: lp:~yolanda.robla/ubuntu/saucy/open-iscsi/dep-8-tests
Merge into: lp:ubuntu/saucy/open-iscsi
Diff against target: 1415 lines (+1371/-0)
7 files modified
debian/changelog (+6/-0)
debian/control (+1/-0)
debian/tests/control (+3/-0)
debian/tests/daemon (+15/-0)
debian/tests/test-open-iscsi.py (+195/-0)
debian/tests/testlib.py (+1144/-0)
debian/tests/testsuite (+7/-0)
To merge this branch: bzr merge lp:~yolanda.robla/ubuntu/saucy/open-iscsi/dep-8-tests
Reviewer Review Type Date Requested Status
Martin Pitt Approve
Review via email: mp+163494@code.launchpad.net

Description of the change

Added dep-8-tests

To post a comment you must log in.
44. By Yolanda Robla

added testsuite

Revision history for this message
Martin Pitt (pitti) wrote :

Verified on current saucy with "run-adt-test -sS lp:~yolanda.robla/ubuntu/saucy/open-iscsi/dep-8-tests open-iscsi". Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2012-10-17 12:37:43 +0000
3+++ debian/changelog 2013-05-17 10:06:26 +0000
4@@ -1,3 +1,9 @@
5+open-iscsi (2.0.873-3ubuntu6) saucy; urgency=low
6+
7+ * d/tests: added dep-8-tests
8+
9+ -- Yolanda <yolanda.robla@canonical.com> Mon, 13 May 2013 10:37:31 +0200
10+
11 open-iscsi (2.0.873-3ubuntu5) quantal-proposed; urgency=low
12
13 * Ensure all udev events have been processed before trying to configure
14
15=== modified file 'debian/control'
16--- debian/control 2012-07-10 13:53:52 +0000
17+++ debian/control 2013-05-17 10:06:26 +0000
18@@ -9,6 +9,7 @@
19 Vcs-Git: git://git.debian.org/git/pkg-iscsi/open-iscsi.git
20 Vcs-Browser: http://git.debian.org/?p=pkg-iscsi/open-iscsi.git
21 Homepage: http://www.open-iscsi.org/
22+XS-Testsuite: autopkgtest
23
24 Package: open-iscsi
25 Architecture: any
26
27=== added directory 'debian/tests'
28=== added file 'debian/tests/control'
29--- debian/tests/control 1970-01-01 00:00:00 +0000
30+++ debian/tests/control 2013-05-17 10:06:26 +0000
31@@ -0,0 +1,3 @@
32+Tests: daemon testsuite
33+Depends: open-iscsi
34+Restrictions: needs-root
35
36=== added file 'debian/tests/daemon'
37--- debian/tests/daemon 1970-01-01 00:00:00 +0000
38+++ debian/tests/daemon 2013-05-17 10:06:26 +0000
39@@ -0,0 +1,15 @@
40+#!/bin/bash
41+#--------------------------
42+# Testing open-iscsi daemon
43+#--------------------------
44+set -e
45+
46+/etc/init.d/open-iscsi start > /dev/null 2>&1
47+
48+if pidof -x iscsid > /dev/null; then
49+ echo "OK"
50+ exit 0
51+else
52+ echo "ERROR: OPEN-ISCSI IS NOT RUNNING"
53+ exit 1
54+fi
55
56=== added file 'debian/tests/test-open-iscsi.py'
57--- debian/tests/test-open-iscsi.py 1970-01-01 00:00:00 +0000
58+++ debian/tests/test-open-iscsi.py 2013-05-17 10:06:26 +0000
59@@ -0,0 +1,195 @@
60+#!/usr/bin/python
61+#
62+# test-open-iscsi.py quality assurance test script for open-iscsi
63+# Copyright (C) 2011 Canonical Ltd.
64+# Author: Jamie Strandboge <jamie@canonical.com>
65+#
66+# This program is free software: you can redistribute it and/or modify
67+# it under the terms of the GNU General Public License version 3,
68+# as published by the Free Software Foundation.
69+#
70+# This program is distributed in the hope that it will be useful,
71+# but WITHOUT ANY WARRANTY; without even the implied warranty of
72+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
73+# GNU General Public License for more details.
74+#
75+# You should have received a copy of the GNU General Public License
76+# along with this program. If not, see <http://www.gnu.org/licenses/>.
77+#
78+# packages required for test to run:
79+# QRT-Packages: open-iscsi
80+# packages where more than one package can satisfy a runtime requirement:
81+# QRT-Alternates:
82+# files and directories required for the test to run:
83+# QRT-Depends:
84+# privilege required for the test to run (remove line if running as user is okay):
85+# QRT-Privilege: root
86+
87+'''
88+ In general, this test should be run in a virtual machine (VM) or possibly
89+ a chroot and not on a production machine. While efforts are made to make
90+ these tests non-destructive, there is no guarantee this script will not
91+ alter the machine. You have been warned.
92+
93+ How to run in a clean VM:
94+ $ sudo apt-get -y install python-unit <QRT-Packages> && sudo ./test-PKG.py -v'
95+
96+ How to run in a clean schroot named 'lucid':
97+ $ schroot -c lucid -u root -- sh -c 'apt-get -y install python-unit <QRT-Packages> && ./test-PKG.py -v'
98+
99+
100+ NOTES:
101+ - currently only tested on Ubuntu 8.04
102+'''
103+
104+
105+import unittest, subprocess, sys, os
106+import testlib
107+import time
108+
109+# There are setup based on README.multipurpose-vm. Feel free to override.
110+remote_server = ''
111+username = 'ubuntu'
112+password = 'passwd'
113+username_in = 'ubuntu'
114+password_in = 'ubuntupasswd'
115+initiatorname = 'iqn.2009-10.com.example.hardy-multi:iscsi-01'
116+
117+try:
118+ from private.qrt.OpenIscsi import PrivateOpenIscsiTest
119+except ImportError:
120+ class PrivateOpenIscsiTest(object):
121+ '''Empty class'''
122+ print >>sys.stdout, "Skipping private tests"
123+
124+class OpenIscsiTest(testlib.TestlibCase, PrivateOpenIscsiTest):
125+ '''Test my thing.'''
126+
127+ def setUp(self):
128+ '''Set up prior to each test_* function'''
129+ self.pidfile = "/var/run/iscsid.pid"
130+ self.exe = "/sbin/iscsid"
131+ self.daemon = testlib.TestDaemon("/etc/init.d/open-iscsi")
132+ self.initiatorname_iscsi = '/etc/iscsi/initiatorname.iscsi'
133+ self.iscsid_conf = '/etc/iscsi/iscsid.conf'
134+
135+ def tearDown(self):
136+ '''Clean up after each test_* function'''
137+ global remote_server
138+ global initiatorname
139+
140+ # If remote server is setup, convert back to manual, logout, remove
141+ # testlib configs and restart (in that order)
142+ if remote_server != '':
143+ testlib.cmd(['iscsiadm', '-m', 'node', '--targetname', initiatorname, '-p', '%s:3260' % remote_server, '--op=update', '--name', 'node.startup', '--value=manual'])
144+ testlib.cmd(['iscsiadm', '-m', 'node', '--targetname', initiatorname, '-p', '%s:3260' % remote_server, '--op=update', '--name', 'node.conn[0].startup', '--value=manual'])
145+ testlib.cmd(['iscsiadm', '-m', 'node', '--targetname', initiatorname, '-p', '%s:3260' % remote_server, '--logout'])
146+
147+ testlib.config_restore(self.initiatorname_iscsi)
148+ testlib.config_restore(self.iscsid_conf)
149+ self.daemon.restart()
150+
151+ def test_daemon(self):
152+ '''Test iscsid'''
153+ self.assertTrue(self.daemon.stop())
154+ time.sleep(2)
155+ self.assertFalse(testlib.check_pidfile(self.exe, self.pidfile))
156+
157+ self.assertTrue(self.daemon.start())
158+ time.sleep(2)
159+ self.assertTrue(testlib.check_pidfile(os.path.basename(self.exe), self.pidfile))
160+
161+ self.assertTrue(self.daemon.restart())
162+ time.sleep(2)
163+ self.assertTrue(testlib.check_pidfile(os.path.basename(self.exe), self.pidfile))
164+
165+ def test_discovery_with_server(self):
166+ '''Test iscsi_discovery to remote server'''
167+ global remote_server
168+ global username
169+ global password
170+ global username_in
171+ global password_in
172+ global initiatorname
173+
174+ if remote_server == '':
175+ return self._skipped("--remote-server not specified")
176+
177+ contents = '''
178+InitiatorName=%s
179+InitiatorAlias=ubuntu
180+''' % (initiatorname)
181+ testlib.config_replace(self.initiatorname_iscsi, contents, True)
182+
183+ contents = '''
184+node.session.auth.authmethod = CHAP
185+node.session.auth.username = %s
186+node.session.auth.password = %s
187+node.session.auth.username_in = %s
188+node.session.auth.password_in = %s
189+
190+discovery.sendtargets.auth.authmethod = CHAP
191+discovery.sendtargets.auth.username = %s
192+discovery.sendtargets.auth.password = %s
193+discovery.sendtargets.auth.username_in = %s
194+discovery.sendtargets.auth.password_in = %s
195+''' % (username, password, username_in, password_in, username, password, username_in, password_in)
196+ testlib.config_replace(self.iscsid_conf, contents, True)
197+
198+ self.assertTrue(self.daemon.restart())
199+ time.sleep(2)
200+ self.assertTrue(testlib.check_pidfile(os.path.basename(self.exe), self.pidfile))
201+
202+ rc, report = testlib.cmd(["/sbin/iscsi_discovery", remote_server])
203+ expected = 0
204+ result = 'Got exit code %d, expected %d\n' % (rc, expected)
205+ self.assertEquals(expected, rc, result + report)
206+ for i in ['starting discovery to %s' % remote_server,
207+ 'Testing iser-login to target %s portal %s' % (initiatorname, remote_server),
208+ 'starting to test tcp-login to target %s portal %s' % (initiatorname, remote_server),
209+ 'discovered 1 targets at %s, connected to 1' % remote_server]:
210+ result = "Could not find '%s' in report:\n" % i
211+ self.assertTrue(i in report, result + report)
212+
213+if __name__ == '__main__':
214+ import optparse
215+ parser = optparse.OptionParser()
216+ parser.add_option("-v", "--verbose", dest="verbose", help="Verbose", action="store_true")
217+ parser.add_option("-s", "--remote-server", dest="remote_server", help="Specify host with available iSCSI volumes", metavar="HOST")
218+
219+ parser.add_option("-n", "--initiatorname", dest="initiatorname", help="Specify initiatorname for use with --remote-server", metavar="NAME")
220+
221+ parser.add_option("--password", dest="password", help="Specify password for use with --remote-server", metavar="PASS")
222+ parser.add_option("--password-in", dest="password_in", help="Specify password_in for use with --remote-server", metavar="PASS")
223+
224+ parser.add_option("--username", dest="username", help="Specify username for use with --remote-server", metavar="USER")
225+ parser.add_option("--username-in", dest="username_in", help="Specify username_in for use with --remote-server", metavar="USER")
226+
227+ (options, args) = parser.parse_args()
228+
229+ if options.remote_server:
230+ remote_server = options.remote_server
231+
232+ if options.username:
233+ username = options.username
234+ if options.password:
235+ password = options.password
236+ if options.username_in:
237+ username_in = options.username_in
238+ if options.password_in:
239+ password_in = options.password_in
240+ if options.initiatorname:
241+ initiatorname = options.initiatorname
242+ print "Connecting to remote server with:"
243+ print " host = %s " % remote_server
244+ print ' initiatorname = %s' % initiatorname
245+ print ' username = %s' % username
246+ print ' password = %s' % password
247+ print ' username_in = %s' % username_in
248+ print ' password_in = %s' % password_in
249+
250+ suite = unittest.TestSuite()
251+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(OpenIscsiTest))
252+ rc = unittest.TextTestRunner(verbosity=2).run(suite)
253+ if not rc.wasSuccessful():
254+ sys.exit(1)
255
256=== added file 'debian/tests/testlib.py'
257--- debian/tests/testlib.py 1970-01-01 00:00:00 +0000
258+++ debian/tests/testlib.py 2013-05-17 10:06:26 +0000
259@@ -0,0 +1,1144 @@
260+#
261+# testlib.py quality assurance test script
262+# Copyright (C) 2008-2011 Canonical Ltd.
263+#
264+# This library is free software; you can redistribute it and/or
265+# modify it under the terms of the GNU Library General Public
266+# License as published by the Free Software Foundation; either
267+# version 2 of the License.
268+#
269+# This library is distributed in the hope that it will be useful,
270+# but WITHOUT ANY WARRANTY; without even the implied warranty of
271+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
272+# Library General Public License for more details.
273+#
274+# You should have received a copy of the GNU Library General Public
275+# License along with this program. If not, see
276+# <http://www.gnu.org/licenses/>.
277+#
278+
279+'''Common classes and functions for package tests.'''
280+
281+import string, random, crypt, subprocess, pwd, grp, signal, time, unittest, tempfile, shutil, os, os.path, re, glob
282+import sys, socket, gzip
283+from stat import *
284+from encodings import string_escape
285+
286+import warnings
287+warnings.filterwarnings('ignore', message=r'.*apt_pkg\.TagFile.*', category=DeprecationWarning)
288+try:
289+ import apt_pkg
290+ apt_pkg.InitSystem();
291+except:
292+ # On non-Debian system, fall back to simple comparison without debianisms
293+ class apt_pkg(object):
294+ def VersionCompare(one, two):
295+ list_one = one.split('.')
296+ list_two = two.split('.')
297+ while len(list_one)>0 and len(list_two)>0:
298+ if list_one[0] > list_two[0]:
299+ return 1
300+ if list_one[0] < list_two[0]:
301+ return -1
302+ list_one.pop(0)
303+ list_two.pop(0)
304+ return 0
305+
306+bogus_nxdomain = "208.69.32.132"
307+
308+# http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html
309+# This is needed so that the subprocesses that produce endless output
310+# actually quit when the reader goes away.
311+import signal
312+def subprocess_setup():
313+ # Python installs a SIGPIPE handler by default. This is usually not what
314+ # non-Python subprocesses expect.
315+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
316+
317+class TimedOutException(Exception):
318+ def __init__(self, value = "Timed Out"):
319+ self.value = value
320+ def __str__(self):
321+ return repr(self.value)
322+
323+def _restore_backup(path):
324+ pathbackup = path + '.autotest'
325+ if os.path.exists(pathbackup):
326+ shutil.move(pathbackup, path)
327+
328+def _save_backup(path):
329+ pathbackup = path + '.autotest'
330+ if os.path.exists(path) and not os.path.exists(pathbackup):
331+ shutil.copy2(path, pathbackup)
332+ # copy2 does not copy ownership, so do it here.
333+ # Reference: http://docs.python.org/library/shutil.html
334+ a = os.stat(path)
335+ os.chown(pathbackup, a[4], a[5])
336+
337+def config_copydir(path):
338+ if os.path.exists(path) and not os.path.isdir(path):
339+ raise OSError, "'%s' is not a directory" % (path)
340+ _restore_backup(path)
341+
342+ pathbackup = path + '.autotest'
343+ if os.path.exists(path):
344+ shutil.copytree(path, pathbackup, symlinks=True)
345+
346+def config_replace(path,contents,append=False):
347+ '''Replace (or append) to a config file'''
348+ _restore_backup(path)
349+ if os.path.exists(path):
350+ _save_backup(path)
351+ if append:
352+ contents = file(path).read() + contents
353+ open(path, 'w').write(contents)
354+
355+def config_comment(path, field):
356+ _save_backup(path)
357+ contents = ""
358+ for line in file(path):
359+ if re.search("^\s*%s\s*=" % (field), line):
360+ line = "#" + line
361+ contents += line
362+
363+ open(path+'.new', 'w').write(contents)
364+ os.rename(path+'.new', path)
365+
366+def config_set(path, field, value, spaces=True):
367+ _save_backup(path)
368+ contents = ""
369+ if spaces==True:
370+ setting = '%s = %s\n' % (field, value)
371+ else:
372+ setting = '%s=%s\n' % (field, value)
373+ found = False
374+ for line in file(path):
375+ if re.search("^\s*%s\s*=" % (field), line):
376+ found = True
377+ line = setting
378+ contents += line
379+ if not found:
380+ contents += setting
381+
382+ open(path+'.new', 'w').write(contents)
383+ os.rename(path+'.new', path)
384+
385+def config_patch(path, patch, depth=1):
386+ '''Patch a config file'''
387+ _restore_backup(path)
388+ _save_backup(path)
389+
390+ handle, name = mkstemp_fill(patch)
391+ rc = subprocess.call(['/usr/bin/patch', '-p%s' %(depth), path], stdin=handle, stdout=subprocess.PIPE)
392+ os.unlink(name)
393+ if rc != 0:
394+ raise Exception("Patch failed")
395+
396+def config_restore(path):
397+ '''Rename a replaced config file back to its initial state'''
398+ _restore_backup(path)
399+
400+def timeout(secs, f, *args):
401+ def handler(signum, frame):
402+ raise TimedOutException()
403+
404+ old = signal.signal(signal.SIGALRM, handler)
405+ result = None
406+ signal.alarm(secs)
407+ try:
408+ result = f(*args)
409+ finally:
410+ signal.alarm(0)
411+ signal.signal(signal.SIGALRM, old)
412+
413+ return result
414+
415+def require_nonroot():
416+ if os.geteuid() == 0:
417+ print >>sys.stderr, "This series of tests should be run as a regular user with sudo access, not as root."
418+ sys.exit(1)
419+
420+def require_root():
421+ if os.geteuid() != 0:
422+ print >>sys.stderr, "This series of tests should be run with root privileges (e.g. via sudo)."
423+ sys.exit(1)
424+
425+def require_sudo():
426+ if os.geteuid() != 0 or os.environ.get('SUDO_USER', None) == None:
427+ print >>sys.stderr, "This series of tests must be run under sudo."
428+ sys.exit(1)
429+ if os.environ['SUDO_USER'] == 'root':
430+ print >>sys.stderr, 'Please run this test using sudo from a regular user. (You ran sudo from root.)'
431+ sys.exit(1)
432+
433+def random_string(length,lower=False):
434+ '''Return a random string, consisting of ASCII letters, with given
435+ length.'''
436+
437+ s = ''
438+ selection = string.letters
439+ if lower:
440+ selection = string.lowercase
441+ maxind = len(selection)-1
442+ for l in range(length):
443+ s += selection[random.randint(0, maxind)]
444+ return s
445+
446+def mkstemp_fill(contents,suffix='',prefix='testlib-',dir=None):
447+ '''As tempfile.mkstemp does, return a (file, name) pair, but with
448+ prefilled contents.'''
449+
450+ handle, name = tempfile.mkstemp(suffix=suffix,prefix=prefix,dir=dir)
451+ os.close(handle)
452+ handle = file(name,"w+")
453+ handle.write(contents)
454+ handle.flush()
455+ handle.seek(0)
456+
457+ return handle, name
458+
459+def create_fill(path, contents, mode=0644):
460+ '''Safely create a page'''
461+ # make the temp file in the same dir as the destination file so we
462+ # don't get invalid cross-device link errors when we rename
463+ handle, name = mkstemp_fill(contents, dir=os.path.dirname(path))
464+ handle.close()
465+ os.rename(name, path)
466+ os.chmod(path, mode)
467+
468+def login_exists(login):
469+ '''Checks whether the given login exists on the system.'''
470+
471+ try:
472+ pwd.getpwnam(login)
473+ return True
474+ except KeyError:
475+ return False
476+
477+def group_exists(group):
478+ '''Checks whether the given login exists on the system.'''
479+
480+ try:
481+ grp.getgrnam(group)
482+ return True
483+ except KeyError:
484+ return False
485+
486+def recursive_rm(dirPath, contents_only=False):
487+ '''recursively remove directory'''
488+ names = os.listdir(dirPath)
489+ for name in names:
490+ path = os.path.join(dirPath, name)
491+ if os.path.islink(path) or not os.path.isdir(path):
492+ os.unlink(path)
493+ else:
494+ recursive_rm(path)
495+ if contents_only == False:
496+ os.rmdir(dirPath)
497+
498+def check_pidfile(exe, pidfile):
499+ '''Checks if pid in pidfile is running'''
500+ if not os.path.exists(pidfile):
501+ return False
502+
503+ # get the pid
504+ try:
505+ fd = open(pidfile, 'r')
506+ pid = fd.readline().rstrip('\n')
507+ fd.close()
508+ except:
509+ return False
510+
511+ return check_pid(exe, pid)
512+
513+def check_pid(exe, pid):
514+ '''Checks if pid is running'''
515+ cmdline = "/proc/%s/cmdline" % (str(pid))
516+ if not os.path.exists(cmdline):
517+ return False
518+
519+ # get the command line
520+ try:
521+ fd = open(cmdline, 'r')
522+ tmp = fd.readline().split('\0')
523+ fd.close()
524+ except:
525+ return False
526+
527+ # this allows us to match absolute paths or just the executable name
528+ if re.match('^' + exe + '$', tmp[0]) or \
529+ re.match('.*/' + exe + '$', tmp[0]) or \
530+ re.match('^' + exe + ': ', tmp[0]) or \
531+ re.match('^\(' + exe + '\)', tmp[0]):
532+ return True
533+
534+ return False
535+
536+def check_port(port, proto, ver=4):
537+ '''Check if something is listening on the specified port.
538+ WARNING: for some reason this does not work with a bind mounted /proc
539+ '''
540+ assert (port >= 1)
541+ assert (port <= 65535)
542+ assert (proto.lower() == "tcp" or proto.lower() == "udp")
543+ assert (ver == 4 or ver == 6)
544+
545+ fn = "/proc/net/%s" % (proto)
546+ if ver == 6:
547+ fn += str(ver)
548+
549+ rc, report = cmd(['cat', fn])
550+ assert (rc == 0)
551+
552+ hport = "%0.4x" % port
553+
554+ if re.search(': [0-9a-f]{8}:%s [0-9a-f]' % str(hport).lower(), report.lower()):
555+ return True
556+ return False
557+
558+def get_arch():
559+ '''Get the current architecture'''
560+ rc, report = cmd(['uname', '-m'])
561+ assert (rc == 0)
562+ return report.strip()
563+
564+def get_memory():
565+ '''Gets total ram and swap'''
566+ meminfo = "/proc/meminfo"
567+ memtotal = 0
568+ swaptotal = 0
569+ if not os.path.exists(meminfo):
570+ return (False, False)
571+
572+ try:
573+ fd = open(meminfo, 'r')
574+ for line in fd.readlines():
575+ splitline = line.split()
576+ if splitline[0] == 'MemTotal:':
577+ memtotal = int(splitline[1])
578+ elif splitline[0] == 'SwapTotal:':
579+ swaptotal = int(splitline[1])
580+ fd.close()
581+ except:
582+ return (False, False)
583+
584+ return (memtotal,swaptotal)
585+
586+def is_running_in_vm():
587+ '''Check if running under a VM'''
588+ # add other virtualization environments here
589+ for search in ['QEMU Virtual CPU']:
590+ rc, report = cmd_pipe(['dmesg'], ['grep', search])
591+ if rc == 0:
592+ return True
593+ return False
594+
595+def ubuntu_release():
596+ '''Get the Ubuntu release'''
597+ f = "/etc/lsb-release"
598+ try:
599+ size = os.stat(f)[ST_SIZE]
600+ except:
601+ return "UNKNOWN"
602+
603+ if size > 1024*1024:
604+ raise IOError, 'Could not open "%s" (too big)' % f
605+
606+ try:
607+ fh = open("/etc/lsb-release", 'r')
608+ except:
609+ raise
610+
611+ lines = fh.readlines()
612+ fh.close()
613+
614+ pat = re.compile(r'DISTRIB_CODENAME')
615+ for line in lines:
616+ if pat.search(line):
617+ return line.split('=')[1].rstrip('\n').rstrip('\r')
618+
619+ return "UNKNOWN"
620+
621+def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None):
622+ '''Try to execute given command (array) and return its stdout, or return
623+ a textual error if it failed.'''
624+
625+ try:
626+ sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup)
627+ except OSError, e:
628+ return [127, str(e)]
629+
630+ out, outerr = sp.communicate(input)
631+ # Handle redirection of stdout
632+ if out == None:
633+ out = ''
634+ # Handle redirection of stderr
635+ if outerr == None:
636+ outerr = ''
637+ return [sp.returncode,out+outerr]
638+
639+def cmd_pipe(command1, command2, input = None, stderr = subprocess.STDOUT, stdin = None):
640+ '''Try to pipe command1 into command2.'''
641+ try:
642+ sp1 = subprocess.Popen(command1, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr, close_fds=True)
643+ sp2 = subprocess.Popen(command2, stdin=sp1.stdout, stdout=subprocess.PIPE, stderr=stderr, close_fds=True)
644+ except OSError, e:
645+ return [127, str(e)]
646+
647+ out = sp2.communicate(input)[0]
648+ return [sp2.returncode,out]
649+
650+def cwd_has_enough_space(cdir, total_bytes):
651+ '''Determine if the partition of the current working directory has 'bytes'
652+ free.'''
653+ rc, df_output = cmd(['df'])
654+ result = 'Got exit code %d, expected %d\n' % (rc, 0)
655+ if rc != 0:
656+ return False
657+
658+ kb = total_bytes / 1024
659+
660+ mounts = dict()
661+ for line in df_output.splitlines():
662+ if '/' not in line:
663+ continue
664+ tmp = line.split()
665+ mounts[tmp[5]] = int(tmp[3])
666+
667+ cdir = os.getcwd()
668+ while cdir != '/':
669+ if not mounts.has_key(cdir):
670+ cdir = os.path.dirname(cdir)
671+ continue
672+ if kb < mounts[cdir]:
673+ return True
674+ else:
675+ return False
676+
677+ if kb < mounts['/']:
678+ return True
679+
680+ return False
681+
682+def get_md5(filename):
683+ '''Gets the md5sum of the file specified'''
684+
685+ (rc, report) = cmd(["/usr/bin/md5sum", "-b", filename])
686+ expected = 0
687+ assert (expected == rc)
688+
689+ return report.split(' ')[0]
690+
691+def dpkg_compare_installed_version(pkg, check, version):
692+ '''Gets the version for the installed package, and compares it to the
693+ specified version.
694+ '''
695+ (rc, report) = cmd(["/usr/bin/dpkg", "-s", pkg])
696+ assert (rc == 0)
697+ assert ("Status: install ok installed" in report)
698+ installed_version = ""
699+ for line in report.splitlines():
700+ if line.startswith("Version: "):
701+ installed_version = line.split()[1]
702+
703+ assert (installed_version != "")
704+
705+ (rc, report) = cmd(["/usr/bin/dpkg", "--compare-versions", installed_version, check, version])
706+ assert (rc == 0 or rc == 1)
707+ if rc == 0:
708+ return True
709+ return False
710+
711+def prepare_source(source, builder, cached_src, build_src, patch_system):
712+ '''Download and unpack source package, installing necessary build depends,
713+ adjusting the permissions for the 'builder' user, and returning the
714+ directory of the unpacked source. Patch system can be one of:
715+ - cdbs
716+ - dpatch
717+ - quilt
718+ - quiltv3
719+ - None (not the string)
720+
721+ This is normally used like this:
722+
723+ def setUp(self):
724+ ...
725+ self.topdir = os.getcwd()
726+ self.cached_src = os.path.join(os.getcwd(), "source")
727+ self.tmpdir = tempfile.mkdtemp(prefix='testlib', dir='/tmp')
728+ self.builder = testlib.TestUser()
729+ testlib.cmd(['chgrp', self.builder.login, self.tmpdir])
730+ os.chmod(self.tmpdir, 0775)
731+
732+ def tearDown(self):
733+ ...
734+ self.builder = None
735+ self.topdir = os.getcwd()
736+ if os.path.exists(self.tmpdir):
737+ testlib.recursive_rm(self.tmpdir)
738+
739+ def test_suite_build(self):
740+ ...
741+ build_dir = testlib.prepare_source('foo', \
742+ self.builder, \
743+ self.cached_src, \
744+ os.path.join(self.tmpdir, \
745+ os.path.basename(self.cached_src)),
746+ "quilt")
747+ os.chdir(build_dir)
748+
749+ # Example for typical build, adjust as necessary
750+ print ""
751+ print " make clean"
752+ rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'clean'])
753+
754+ print " configure"
755+ rc, report = testlib.cmd(['sudo', '-u', self.builder.login, './configure', '--prefix=%s' % self.tmpdir, '--enable-debug'])
756+
757+ print " make (will take a while)"
758+ rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make'])
759+
760+ print " make check (will take a while)",
761+ rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'check'])
762+ expected = 0
763+ result = 'Got exit code %d, expected %d\n' % (rc, expected)
764+ self.assertEquals(expected, rc, result + report)
765+
766+ def test_suite_cleanup(self):
767+ ...
768+ if os.path.exists(self.cached_src):
769+ testlib.recursive_rm(self.cached_src)
770+
771+ It is up to the caller to clean up cached_src and build_src (as in the
772+ above example, often the build_src is in a tmpdir that is cleaned in
773+ tearDown() and the cached_src is cleaned in a one time clean-up
774+ operation (eg 'test_suite_cleanup()) which must be run after the build
775+ suite test (obviously).
776+ '''
777+
778+ # Make sure we have a clean slate
779+ assert (os.path.exists(os.path.dirname(build_src)))
780+ assert (not os.path.exists(build_src))
781+
782+ cdir = os.getcwd()
783+ if os.path.exists(cached_src):
784+ shutil.copytree(cached_src, build_src)
785+ os.chdir(build_src)
786+ else:
787+ # Only install the build dependencies on the initial setup
788+ rc, report = cmd(['apt-get','-y','--force-yes','build-dep',source])
789+ assert (rc == 0)
790+
791+ os.makedirs(build_src)
792+ os.chdir(build_src)
793+
794+ # These are always needed
795+ pkgs = ['build-essential', 'dpkg-dev', 'fakeroot']
796+ rc, report = cmd(['apt-get','-y','--force-yes','install'] + pkgs)
797+ assert (rc == 0)
798+
799+ rc, report = cmd(['apt-get','source',source])
800+ assert (rc == 0)
801+ shutil.copytree(build_src, cached_src)
802+
803+ unpacked_dir = os.path.join(build_src, glob.glob('%s-*' % source)[0])
804+
805+ # Now apply the patches. Do it here so that we don't mess up our cached
806+ # sources.
807+ os.chdir(unpacked_dir)
808+ assert (patch_system in ['cdbs', 'dpatch', 'quilt', 'quiltv3', None])
809+ if patch_system != None and patch_system != "quiltv3":
810+ if patch_system == "quilt":
811+ os.environ.setdefault('QUILT_PATCHES','debian/patches')
812+ rc, report = cmd(['quilt', 'push', '-a'])
813+ assert (rc == 0)
814+ elif patch_system == "cdbs":
815+ rc, report = cmd(['./debian/rules', 'apply-patches'])
816+ assert (rc == 0)
817+ elif patch_system == "dpatch":
818+ rc, report = cmd(['dpatch', 'apply-all'])
819+ assert (rc == 0)
820+
821+ cmd(['chown', '-R', '%s:%s' % (builder.uid, builder.gid), build_src])
822+ os.chdir(cdir)
823+
824+ return unpacked_dir
825+
826+def _aa_status():
827+ '''Get aa-status output'''
828+ exe = "/usr/sbin/aa-status"
829+ assert (os.path.exists(exe))
830+ if os.geteuid() == 0:
831+ return cmd([exe])
832+ return cmd(['sudo', exe])
833+
834+def is_apparmor_loaded(path):
835+ '''Check if profile is loaded'''
836+ rc, report = _aa_status()
837+ if rc != 0:
838+ return False
839+
840+ for line in report.splitlines():
841+ if line.endswith(path):
842+ return True
843+ return False
844+
845+def is_apparmor_confined(path):
846+ '''Check if application is confined'''
847+ rc, report = _aa_status()
848+ if rc != 0:
849+ return False
850+
851+ for line in report.splitlines():
852+ if re.search('%s \(' % path, line):
853+ return True
854+ return False
855+
856+def check_apparmor(path, first_ubuntu_release, is_running=True):
857+ '''Check if path is loaded and confined for everything higher than the
858+ first Ubuntu release specified.
859+
860+ Usage:
861+ rc, report = testlib.check_apparmor('/usr/sbin/foo', 8.04, is_running=True)
862+ if rc < 0:
863+ return self._skipped(report)
864+
865+ expected = 0
866+ result = 'Got exit code %d, expected %d\n' % (rc, expected)
867+ self.assertEquals(expected, rc, result + report)
868+ '''
869+ global manager
870+ rc = -1
871+
872+ if manager.lsb_release["Release"] < first_ubuntu_release:
873+ return (rc, "Skipped apparmor check")
874+
875+ if not os.path.exists('/sbin/apparmor_parser'):
876+ return (rc, "Skipped (couldn't find apparmor_parser)")
877+
878+ rc = 0
879+ msg = ""
880+ if not is_apparmor_loaded(path):
881+ rc = 1
882+ msg = "Profile not loaded for '%s'" % path
883+
884+ # this check only makes sense it the 'path' is currently executing
885+ if is_running and rc == 0 and not is_apparmor_confined(path):
886+ rc = 1
887+ msg = "'%s' is not running in enforce mode" % path
888+
889+ return (rc, msg)
890+
891+def get_gcc_version(gcc, full=True):
892+ gcc_version = 'none'
893+ if not gcc.startswith('/'):
894+ gcc = '/usr/bin/%s' % (gcc)
895+ if os.path.exists(gcc):
896+ gcc_version = 'unknown'
897+ lines = cmd([gcc,'-v'])[1].strip().splitlines()
898+ version_lines = [x for x in lines if x.startswith('gcc version')]
899+ if len(version_lines) == 1:
900+ gcc_version = " ".join(version_lines[0].split()[2:])
901+ if not full:
902+ return gcc_version.split()[0]
903+ return gcc_version
904+
905+def is_kdeinit_running():
906+ '''Test if kdeinit is running'''
907+ # applications that use kdeinit will spawn it if it isn't running in the
908+ # test. This is a problem because it does not exit. This is a helper to
909+ # check for it.
910+ rc, report = cmd(['ps', 'x'])
911+ if 'kdeinit4 Running' not in report:
912+ print >>sys.stderr, ("kdeinit not running (you may start/stop any KDE application then run this script again)")
913+ return False
914+ return True
915+
916+def get_pkgconfig_flags(libs=[]):
917+ '''Find pkg-config flags for libraries'''
918+ assert (len(libs) > 0)
919+ rc, pkg_config = cmd(['pkg-config', '--cflags', '--libs'] + libs)
920+ expected = 0
921+ if rc != expected:
922+ print >>sys.stderr, 'Got exit code %d, expected %d\n' % (rc, expected)
923+ assert(rc == expected)
924+ return pkg_config.split()
925+
926+class TestDaemon:
927+ '''Helper class to manage daemons consistently'''
928+ def __init__(self, init):
929+ '''Setup daemon attributes'''
930+ self.initscript = init
931+
932+ def start(self):
933+ '''Start daemon'''
934+ rc, report = cmd([self.initscript, 'start'])
935+ expected = 0
936+ result = 'Got exit code %d, expected %d\n' % (rc, expected)
937+ time.sleep(2)
938+ if expected != rc:
939+ return (False, result + report)
940+
941+ if "fail" in report:
942+ return (False, "Found 'fail' in report\n" + report)
943+
944+ return (True, "")
945+
946+ def stop(self):
947+ '''Stop daemon'''
948+ rc, report = cmd([self.initscript, 'stop'])
949+ expected = 0
950+ result = 'Got exit code %d, expected %d\n' % (rc, expected)
951+ if expected != rc:
952+ return (False, result + report)
953+
954+ if "fail" in report:
955+ return (False, "Found 'fail' in report\n" + report)
956+
957+ return (True, "")
958+
959+ def reload(self):
960+ '''Reload daemon'''
961+ rc, report = cmd([self.initscript, 'force-reload'])
962+ expected = 0
963+ result = 'Got exit code %d, expected %d\n' % (rc, expected)
964+ if expected != rc:
965+ return (False, result + report)
966+
967+ if "fail" in report:
968+ return (False, "Found 'fail' in report\n" + report)
969+
970+ return (True, "")
971+
972+ def restart(self):
973+ '''Restart daemon'''
974+ (res, str) = self.stop()
975+ if not res:
976+ return (res, str)
977+
978+ (res, str) = self.start()
979+ if not res:
980+ return (res, str)
981+
982+ return (True, "")
983+
984+ def status(self):
985+ '''Check daemon status'''
986+ rc, report = cmd([self.initscript, 'status'])
987+ expected = 0
988+ result = 'Got exit code %d, expected %d\n' % (rc, expected)
989+ if expected != rc:
990+ return (False, result + report)
991+
992+ if "fail" in report:
993+ return (False, "Found 'fail' in report\n" + report)
994+
995+ return (True, "")
996+
997+class TestlibManager(object):
998+ '''Singleton class used to set up per-test-run information'''
999+ def __init__(self):
1000+ # Set glibc aborts to dump to stderr instead of the tty so test output
1001+ # is more sane.
1002+ os.environ.setdefault('LIBC_FATAL_STDERR_','1')
1003+
1004+ # check verbosity
1005+ self.verbosity = False
1006+ if (len(sys.argv) > 1 and '-v' in sys.argv[1:]):
1007+ self.verbosity = True
1008+
1009+ # Load LSB release file
1010+ self.lsb_release = dict()
1011+ if not os.path.exists('/usr/bin/lsb_release') and not os.path.exists('/bin/lsb_release'):
1012+ raise OSError, "Please install 'lsb-release'"
1013+ for line in subprocess.Popen(['lsb_release','-a'],stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate()[0].splitlines():
1014+ field, value = line.split(':',1)
1015+ value=value.strip()
1016+ field=field.strip()
1017+ # Convert numerics
1018+ try:
1019+ value = float(value)
1020+ except:
1021+ pass
1022+ self.lsb_release.setdefault(field,value)
1023+
1024+ # FIXME: hack OEM releases into known-Ubuntu versions
1025+ if self.lsb_release['Distributor ID'] == "HP MIE (Mobile Internet Experience)":
1026+ if self.lsb_release['Release'] == 1.0:
1027+ self.lsb_release['Distributor ID'] = "Ubuntu"
1028+ self.lsb_release['Release'] = 8.04
1029+ else:
1030+ raise OSError, "Unknown version of HP MIE"
1031+
1032+ # FIXME: hack to assume a most-recent release if we're not
1033+ # running under Ubuntu.
1034+ if self.lsb_release['Distributor ID'] not in ["Ubuntu","Linaro"]:
1035+ self.lsb_release['Release'] = 10000
1036+ # Adjust Linaro release to pretend to be Ubuntu
1037+ if self.lsb_release['Distributor ID'] in ["Linaro"]:
1038+ self.lsb_release['Distributor ID'] = "Ubuntu"
1039+ self.lsb_release['Release'] -= 0.01
1040+
1041+ # Load arch
1042+ if not os.path.exists('/usr/bin/dpkg'):
1043+ machine = cmd(['uname','-m'])[1].strip()
1044+ if machine.endswith('86'):
1045+ self.dpkg_arch = 'i386'
1046+ elif machine.endswith('_64'):
1047+ self.dpkg_arch = 'amd64'
1048+ elif machine.startswith('arm'):
1049+ self.dpkg_arch = 'armel'
1050+ else:
1051+ raise ValueError, "Unknown machine type '%s'" % (machine)
1052+ else:
1053+ self.dpkg_arch = cmd(['dpkg','--print-architecture'])[1].strip()
1054+
1055+ # Find kernel version
1056+ self.kernel_is_ubuntu = False
1057+ self.kernel_version_signature = None
1058+ self.kernel_version = cmd(["uname","-r"])[1].strip()
1059+ versig = '/proc/version_signature'
1060+ if os.path.exists(versig):
1061+ self.kernel_is_ubuntu = True
1062+ self.kernel_version_signature = file(versig).read().strip()
1063+ self.kernel_version_ubuntu = self.kernel_version
1064+ elif os.path.exists('/usr/bin/dpkg'):
1065+ # this can easily be inaccurate but is only an issue for Dapper
1066+ rc, out = cmd(['dpkg','-l','linux-image-%s' % (self.kernel_version)])
1067+ if rc == 0:
1068+ self.kernel_version_signature = out.strip().split('\n').pop().split()[2]
1069+ self.kernel_version_ubuntu = self.kernel_version_signature
1070+ if self.kernel_version_signature == None:
1071+ # Attempt to fall back to something for non-Debian-based
1072+ self.kernel_version_signature = self.kernel_version
1073+ self.kernel_version_ubuntu = self.kernel_version
1074+ # Build ubuntu version without hardware suffix
1075+ try:
1076+ self.kernel_version_ubuntu = "-".join([x for x in self.kernel_version_signature.split(' ')[1].split('-') if re.search('^[0-9]', x)])
1077+ except:
1078+ pass
1079+
1080+ # Find gcc version
1081+ self.gcc_version = get_gcc_version('gcc')
1082+
1083+ # Find libc
1084+ self.path_libc = [x.split()[2] for x in cmd(['ldd','/bin/ls'])[1].splitlines() if x.startswith('\tlibc.so.')][0]
1085+
1086+ # Report self
1087+ if self.verbosity:
1088+ kernel = self.kernel_version_ubuntu
1089+ if kernel != self.kernel_version_signature:
1090+ kernel += " (%s)" % (self.kernel_version_signature)
1091+ print >>sys.stdout, "Running test: '%s' distro: '%s %.2f' kernel: '%s' arch: '%s' uid: %d/%d SUDO_USER: '%s')" % ( \
1092+ sys.argv[0],
1093+ self.lsb_release['Distributor ID'],
1094+ self.lsb_release['Release'],
1095+ kernel,
1096+ self.dpkg_arch,
1097+ os.geteuid(), os.getuid(),
1098+ os.environ.get('SUDO_USER', ''))
1099+ sys.stdout.flush()
1100+
1101+ # Additional heuristics
1102+ #if os.environ.get('SUDO_USER', os.environ.get('USER', '')) in ['mdeslaur']:
1103+ # sys.stdout.write("Replying to Marc Deslauriers in http://launchpad.net/bugs/%d: " % random.randint(600000, 980000))
1104+ # sys.stdout.flush()
1105+ # time.sleep(0.5)
1106+ # sys.stdout.write("destroyed\n")
1107+ # time.sleep(0.5)
1108+
1109+ def hello(self, msg):
1110+ print >>sys.stderr, "Hello from %s" % (msg)
1111+# The central instance
1112+manager = TestlibManager()
1113+
1114+class TestlibCase(unittest.TestCase):
1115+ def __init__(self, *args):
1116+ '''This is called for each TestCase test instance, which isn't much better
1117+ than SetUp.'''
1118+
1119+ unittest.TestCase.__init__(self, *args)
1120+
1121+ # Attach to and duplicate dicts from manager singleton
1122+ self.manager = manager
1123+ #self.manager.hello(repr(self) + repr(*args))
1124+ self.my_verbosity = self.manager.verbosity
1125+ self.lsb_release = self.manager.lsb_release
1126+ self.dpkg_arch = self.manager.dpkg_arch
1127+ self.kernel_version = self.manager.kernel_version
1128+ self.kernel_version_signature = self.manager.kernel_version_signature
1129+ self.kernel_version_ubuntu = self.manager.kernel_version_ubuntu
1130+ self.kernel_is_ubuntu = self.manager.kernel_is_ubuntu
1131+ self.gcc_version = self.manager.gcc_version
1132+ self.path_libc = self.manager.path_libc
1133+
1134+ def version_compare(self, one, two):
1135+ return apt_pkg.VersionCompare(one,two)
1136+
1137+ def assertFileType(self, filename, filetype):
1138+ '''Checks the file type of the file specified'''
1139+
1140+ (rc, report, out) = self._testlib_shell_cmd(["/usr/bin/file", "-b", filename])
1141+ out = out.strip()
1142+ expected = 0
1143+ # Absolutely no idea why this happens on Hardy
1144+ if self.lsb_release['Release'] == 8.04 and rc == 255 and len(out) > 0:
1145+ rc = 0
1146+ result = 'Got exit code %d, expected %d:\n%s\n' % (rc, expected, report)
1147+ self.assertEquals(expected, rc, result)
1148+
1149+ filetype = '^%s$' % (filetype)
1150+ result = 'File type reported by file: [%s], expected regex: [%s]\n' % (out, filetype)
1151+ self.assertNotEquals(None, re.search(filetype, out), result)
1152+
1153+ def yank_commonname_from_cert(self, certfile):
1154+ '''Extract the commonName from a given PEM'''
1155+ rc, out = cmd(['openssl','asn1parse','-in',certfile])
1156+ if rc == 0:
1157+ ready = False
1158+ for line in out.splitlines():
1159+ if ready:
1160+ return line.split(':')[-1]
1161+ if ':commonName' in line:
1162+ ready = True
1163+ return socket.getfqdn()
1164+
1165+ def announce(self, text):
1166+ if self.my_verbosity:
1167+ print >>sys.stdout, "(%s) " % (text),
1168+ sys.stdout.flush()
1169+
1170+ def make_clean(self):
1171+ rc, output = self.shell_cmd(['make','clean'])
1172+ self.assertEquals(rc, 0, output)
1173+
1174+ def get_makefile_compiler(self):
1175+ # Find potential compiler name
1176+ compiler = 'gcc'
1177+ if os.path.exists('Makefile'):
1178+ for line in open('Makefile'):
1179+ if line.startswith('CC') and '=' in line:
1180+ items = [x.strip() for x in line.split('=')]
1181+ if items[0] == 'CC':
1182+ compiler = items[1]
1183+ break
1184+ return compiler
1185+
1186+ def make_target(self, target, expected=0):
1187+ '''Compile a target and report output'''
1188+
1189+ compiler = self.get_makefile_compiler()
1190+ rc, output = self.shell_cmd(['make',target])
1191+ self.assertEquals(rc, expected, 'rc(%d)!=%d:\n' % (rc, expected) + output)
1192+ self.assertTrue('%s ' % (compiler) in output, 'Expected "%s":' % (compiler) + output)
1193+ return output
1194+
1195+ # call as return testlib.skipped()
1196+ def _skipped(self, reason=""):
1197+ '''Provide a visible way to indicate that a test was skipped'''
1198+ if reason != "":
1199+ reason = ': %s' % (reason)
1200+ self.announce("skipped%s" % (reason))
1201+ return False
1202+
1203+ def _testlib_shell_cmd(self,args,stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT):
1204+ argstr = "'" + "', '".join(args).strip() + "'"
1205+ rc, out = cmd(args,stdin=stdin,stdout=stdout,stderr=stderr)
1206+ report = 'Command: ' + argstr + '\nOutput:\n' + out
1207+ return rc, report, out
1208+
1209+ def shell_cmd(self, args, stdin=None):
1210+ return cmd(args,stdin=stdin)
1211+
1212+ def assertShellExitEquals(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""):
1213+ '''Test a shell command matches a specific exit code'''
1214+ rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
1215+ result = 'Got exit code %d, expected %d\n' % (rc, expected)
1216+ self.assertEquals(expected, rc, msg + result + report)
1217+
1218+ def assertShellExitNotEquals(self, unwanted, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""):
1219+ '''Test a shell command doesn't match a specific exit code'''
1220+ rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
1221+ result = 'Got (unwanted) exit code %d\n' % rc
1222+ self.assertNotEquals(unwanted, rc, msg + result + report)
1223+
1224+ def assertShellOutputContains(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False):
1225+ '''Test a shell command contains a specific output'''
1226+ rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
1227+ result = 'Got exit code %d. Looking for text "%s"\n' % (rc, text)
1228+ if not invert:
1229+ self.assertTrue(text in out, msg + result + report)
1230+ else:
1231+ self.assertFalse(text in out, msg + result + report)
1232+
1233+ def assertShellOutputEquals(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None):
1234+ '''Test a shell command matches a specific output'''
1235+ rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
1236+ result = 'Got exit code %d. Looking for exact text "%s" (%s)\n' % (rc, text, " ".join(args))
1237+ if not invert:
1238+ self.assertEquals(text, out, msg + result + report)
1239+ else:
1240+ self.assertNotEquals(text, out, msg + result + report)
1241+ if expected != None:
1242+ result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args))
1243+ self.assertEquals(rc, expected, msg + result + report)
1244+
1245+ def _word_find(self, report, content, invert=False):
1246+ '''Check for a specific string'''
1247+ if invert:
1248+ warning = 'Found "%s"\n' % content
1249+ self.assertTrue(content not in report, warning + report)
1250+ else:
1251+ warning = 'Could not find "%s"\n' % content
1252+ self.assertTrue(content in report, warning + report)
1253+
1254+ def _test_sysctl_value(self, path, expected, msg=None, exists=True):
1255+ sysctl = '/proc/sys/%s' % (path)
1256+ self.assertEquals(exists, os.path.exists(sysctl), sysctl)
1257+ value = None
1258+ if exists:
1259+ value = int(file(sysctl).read())
1260+ report = "%s is not %d: %d" % (sysctl, expected, value)
1261+ if msg:
1262+ report += " (%s)" % (msg)
1263+ self.assertEquals(value, expected, report)
1264+ return value
1265+
1266+ def set_sysctl_value(self, path, desired):
1267+ sysctl = '/proc/sys/%s' % (path)
1268+ self.assertTrue(os.path.exists(sysctl),"%s does not exist" % (sysctl))
1269+ file(sysctl,'w').write(str(desired))
1270+ self._test_sysctl_value(path, desired)
1271+
1272+ def kernel_at_least(self, introduced):
1273+ return self.version_compare(self.kernel_version_ubuntu,
1274+ introduced) >= 0
1275+
1276+ def kernel_claims_cve_fixed(self, cve):
1277+ changelog = "/usr/share/doc/linux-image-%s/changelog.Debian.gz" % (self.kernel_version)
1278+ if os.path.exists(changelog):
1279+ for line in gzip.open(changelog):
1280+ if cve in line and not "revert" in line and not "Revert" in line:
1281+ return True
1282+ return False
1283+
1284+class TestGroup:
1285+ '''Create a temporary test group and remove it again in the dtor.'''
1286+
1287+ def __init__(self, group=None, lower=False):
1288+ '''Create a new group'''
1289+
1290+ self.group = None
1291+ if group:
1292+ if group_exists(group):
1293+ raise ValueError, 'group name already exists'
1294+ else:
1295+ while(True):
1296+ group = random_string(7,lower=lower)
1297+ if not group_exists(group):
1298+ break
1299+
1300+ assert subprocess.call(['groupadd',group]) == 0
1301+ self.group = group
1302+ g = grp.getgrnam(self.group)
1303+ self.gid = g[2]
1304+
1305+ def __del__(self):
1306+ '''Remove the created group.'''
1307+
1308+ if self.group:
1309+ rc, report = cmd(['groupdel', self.group])
1310+ assert rc == 0
1311+
1312+class TestUser:
1313+ '''Create a temporary test user and remove it again in the dtor.'''
1314+
1315+ def __init__(self, login=None, home=True, group=None, uidmin=None, lower=False, shell=None):
1316+ '''Create a new user account with a random password.
1317+
1318+ By default, the login name is random, too, but can be explicitly
1319+ specified with 'login'. By default, a home directory is created, this
1320+ can be suppressed with 'home=False'.'''
1321+
1322+ self.login = None
1323+
1324+ if os.geteuid() != 0:
1325+ raise ValueError, "You must be root to run this test"
1326+
1327+ if login:
1328+ if login_exists(login):
1329+ raise ValueError, 'login name already exists'
1330+ else:
1331+ while(True):
1332+ login = 't' + random_string(7,lower=lower)
1333+ if not login_exists(login):
1334+ break
1335+
1336+ self.salt = random_string(2)
1337+ self.password = random_string(8,lower=lower)
1338+ self.crypted = crypt.crypt(self.password, self.salt)
1339+
1340+ creation = ['useradd', '-p', self.crypted]
1341+ if home:
1342+ creation += ['-m']
1343+ if group:
1344+ creation += ['-G',group]
1345+ if uidmin:
1346+ creation += ['-K','UID_MIN=%d'%uidmin]
1347+ if shell:
1348+ creation += ['-s',shell]
1349+ creation += [login]
1350+ assert subprocess.call(creation) == 0
1351+ # Set GECOS
1352+ assert subprocess.call(['usermod','-c','Buddy %s' % (login),login]) == 0
1353+
1354+ self.login = login
1355+ p = pwd.getpwnam(self.login)
1356+ self.uid = p[2]
1357+ self.gid = p[3]
1358+ self.gecos = p[4]
1359+ self.home = p[5]
1360+ self.shell = p[6]
1361+
1362+ def __del__(self):
1363+ '''Remove the created user account.'''
1364+
1365+ if self.login:
1366+ # sanity check the login name so we don't accidentally wipe too much
1367+ if len(self.login)>3 and not '/' in self.login:
1368+ subprocess.call(['rm','-rf', '/home/'+self.login, '/var/mail/'+self.login])
1369+ rc, report = cmd(['userdel', '-f', self.login])
1370+ assert rc == 0
1371+
1372+ def add_to_group(self, group):
1373+ '''Add user to the specified group name'''
1374+ rc, report = cmd(['usermod', '-G', group, self.login])
1375+ if rc != 0:
1376+ print report
1377+ assert rc == 0
1378+
1379+# Timeout handler using alarm() from John P. Speno's Pythonic Avocado
1380+class TimeoutFunctionException(Exception):
1381+ """Exception to raise on a timeout"""
1382+ pass
1383+class TimeoutFunction:
1384+ def __init__(self, function, timeout):
1385+ self.timeout = timeout
1386+ self.function = function
1387+
1388+ def handle_timeout(self, signum, frame):
1389+ raise TimeoutFunctionException()
1390+
1391+ def __call__(self, *args, **kwargs):
1392+ old = signal.signal(signal.SIGALRM, self.handle_timeout)
1393+ signal.alarm(self.timeout)
1394+ try:
1395+ result = self.function(*args, **kwargs)
1396+ finally:
1397+ signal.signal(signal.SIGALRM, old)
1398+ signal.alarm(0)
1399+ return result
1400+
1401+def main():
1402+ print "hi"
1403+ unittest.main()
1404
1405=== added file 'debian/tests/testsuite'
1406--- debian/tests/testsuite 1970-01-01 00:00:00 +0000
1407+++ debian/tests/testsuite 2013-05-17 10:06:26 +0000
1408@@ -0,0 +1,7 @@
1409+#!/bin/bash
1410+#-------------------
1411+# Testing open-iscsi
1412+#-------------------
1413+set -e
1414+python `dirname $0`/test-open-iscsi.py 2>&1
1415+

Subscribers

People subscribed via source and target branches

to all changes: